66 < meta name ="viewport " content ="width=device-width, initial-scale=1.0 " />
77 < title > Gridstack.js React integration example</ title >
88 < link rel ="stylesheet " href ="demo.css " />
9+ < link rel ="stylesheet " href ="../dist/gridstack-extra.css "/>
910 < script src ="../dist/gridstack-all.js "> </ script >
1011
11- <!-- Scripts to use react inside html -->
12- < script src ="https://unpkg.com/react@16/umd/react.production.min .js "> </ script >
13- < script src ="https://unpkg.com/react-dom@16/umd/react-dom.production.min .js "> </ script >
14- < script src ="https://unpkg.com/babel-standalone@6.15.0/babel.min. js "> </ script >
12+ <!-- Scripts to use react inside html - DEVELOPMENT FILES - ->
13+ < script src ="https://unpkg.com/react@16/umd/react.development .js "> </ script >
14+ < script src ="https://unpkg.com/react-dom@16/umd/react-dom.development .js "> </ script >
15+ < script src ="https://unpkg.com/babel-standalone@6.15.0/babel.js "> </ script >
1516</ head >
1617
1718< body >
@@ -22,8 +23,12 @@ <h2>Controlled stack</h2>
2223</ body >
2324
2425< script type ="text/babel ">
26+ /***********************************************************************************************************/
27+ /********************************* NOT IDEAL - see comments below line 111) ********************************/
28+ /***********************************************************************************************************/
29+
2530 const { useState, useEffect, useLayoutEffect, createRef, useRef } = React
26- const Item = ( { id } ) => < div > I am item: { id } </ div >
31+ const Item = ( { id } ) => < div > { id } </ div >
2732
2833 //
2934 // Controlled example
@@ -40,45 +45,57 @@ <h2>Controlled stack</h2>
4045 }
4146
4247 useLayoutEffect ( ( ) => {
43- gridRef . current =
44- // gridRef.current ||
45- GridStack . init (
48+ if ( ! gridRef . current ) {
49+ // no need to init twice (would will return same grid) or register dup events
50+ const grid = gridRef . current = GridStack . init (
4651 {
4752 float : true ,
4853 acceptWidgets : true ,
54+ disableOneColumnMode : true , // side-by-side and fever columns to fit smaller screens
55+ column : 6 ,
56+ minRow : 1 ,
4957 } ,
5058 `.controlled-${ id } `
5159 )
52- const grid = gridRef . current
53-
54- grid . on ( 'removed' , ( e , el ) => {
55- const elId = Array . isArray ( el ) ? el [ el . length - 1 ] . id : el . id ;
56- removeItem ( elId . split ( ':' ) [ 1 ] )
57- } )
58-
59- grid . on ( 'dropped' , ( e , p , n ) => {
60- console . log ( 'dropped' , p , n )
61- // Remove "placeholder" item
62- // Gridstack creates a DOM element for dropped items, those need to be removed
63- // as they are rendered by React
64- grid . getGridItems ( ) . forEach ( ( item ) => {
65- const ids = item . getAttribute ( 'gs-id' ) . split ( ':' )
66- if ( ids [ 0 ] !== id ) {
67- grid . removeWidget ( item , true , false ) ;
68- }
69- } ) ;
70- addItem ( { id : n . id . split ( ':' ) [ 1 ] , x : n . x , y : n . y , w : n . w , h : n . h } )
71- } )
60+ . on ( 'added' , ( ev , gsItems ) => {
61+ if ( grid . _ignoreCB ) return ;
62+ // remove the new element as React will re-create it again (dup) once we add to the list or we get 2 of them with same ids but different DOM el!
63+ // TODO: this is really not ideal - we shouldn't mix React templating with GS making it own edits as those get out of sync! see comment below @111.
64+ gsItems . forEach ( n => {
65+ grid . removeWidget ( n . el , true , false ) ; // true=remove DOM, false=don't call use back!
66+ // can't pass n directly even though similar structs as it has n.el.gridstackNode which gives JSON error for circular write.
67+ addItem ( { id :n . id , x :n . x , y :n . y , w :n . w , h :n . h } ) ;
68+ } ) ;
69+ } )
70+ . on ( 'removed' , ( ev , gsItems ) => {
71+ if ( grid . _ignoreCB ) return ;
72+ gsItems . forEach ( n => removeItem ( n . id ) ) ;
73+ } )
74+
75+ } else {
76+ //
77+ // update existing grid layout, which is optimized to updates only diffs (will add new/delete items for examples)
78+ //
79+ const grid = gridRef . current ;
80+ const layout = [ ] ;
81+ items . forEach ( ( a ) => layout . push (
82+ refs . current [ a . id ] . current . gridstackNode || { ...a , el : refs . current [ a . id ] . current }
83+ ) ) ;
84+ grid . _ignoreCB = true ; // hack: ignore added/removed since we're the one doing the update
85+ grid . load ( layout ) ;
86+ delete grid . _ignoreCB ;
87+ }
7288
73- grid . batchUpdate ( )
74- items . forEach ( ( a ) => {
75- // remove existing widgets
76- if ( refs . current [ a . id ] && refs . current [ a . id ] . current ) {
77- grid . removeWidget ( refs . current [ a . id ] . current , false , false )
78- }
79- grid . makeWidget ( refs . current [ a . id ] . current )
80- } )
81- grid . batchUpdate ( false )
89+ // NOTE: old code is incorrect as it re-does the GS binding, but dragged item is left behind so you get dup DOM elements with same ids
90+ // grid.batchUpdate()
91+ // items.forEach((a) => {
92+ // // remove existing widgets
93+ // if (refs.current[a.id] && refs.current[a.id].current) {
94+ // grid.removeWidget(refs.current[a.id].current, false, false)
95+ // }
96+ // grid.makeWidget(refs.current[a.id].current)
97+ // })
98+ // grid.batchUpdate(false)
8299
83100 } , [ items ] )
84101
@@ -91,12 +108,21 @@ <h2>Controlled stack</h2>
91108 } )
92109
93110 return (
94- < div style = { { width : '100%' } } >
111+ // ********************
112+ // NOTE: constructing DOM grid items in template when gridstack is also allowed editing (dragging between grids, or adding/removing from say a toolbar)
113+ // is NOT A GOOD IDEA as you end up fighting between gridstack users' edits and your template items structure which are not in sync.
114+ // At best, you end up re-creating widgets DOM (from React template) and all their content & state after a widget was inserted/re-parented by the user.
115+ // a MUCH better way is to let GS create React components using it's API/user interactions, with only initial load() of a stored layout.
116+ // see the upcoming Angular component wrapper that does that instead (lib author uses Angular). TBD creating React equivalent...
117+ //
118+ // Also templating forces you to spell out the 12+ attributes GS supports (only x,w,w,h done below) instead of passing a option structure that supports everything
119+ // is not robust as things get added, and pollutes the DOM attr for default/missing entries, vs optimized code in GS.
120+ // ********************
121+ < div style = { { width : '100%' , marginRight : '10px' } } >
95122 < div className = { `grid-stack controlled-${ id } ` } >
96123 { items . map ( ( item , i ) => {
97- // console.log('render', id, item.id)
98124 return (
99- < div ref = { refs . current [ item . id ] } key = { `${ id } -${ item . id } ` } className = { 'grid-stack-item' } gs-id = { ` ${ id } : ${ item . id } ` } gs-w = { item . w } gs-h = { item . h } gs-x = { item . x } gs-y = { item . y } >
125+ < div ref = { refs . current [ item . id ] } key = { `${ id } -${ item . id } ` } className = { 'grid-stack-item' } gs-id = { item . id } gs-w = { item . w } gs-h = { item . h } gs-x = { item . x } gs-y = { item . y } >
100126 < div className = "grid-stack-item-content" >
101127 < Item { ...item } />
102128 </ div >
@@ -122,10 +148,10 @@ <h2>Controlled stack</h2>
122148 id = 'gs1'
123149 items = { items1 }
124150 addItem = { ( item ) => {
125- setItems1 ( [ ...items1 , item ] )
151+ setItems1 ( items => [ ...items , item ] )
126152 } }
127153 removeItem = { ( id ) => {
128- setItems1 ( items1 . filter ( i => i . id !== id ) )
154+ setItems1 ( items => items . filter ( i => i . id !== id ) )
129155 } }
130156 />
131157 </ div >
@@ -134,10 +160,10 @@ <h2>Controlled stack</h2>
134160 id = 'gs2'
135161 items = { items2 }
136162 addItem = { ( item ) => {
137- setItems2 ( [ ...items2 , item ] )
163+ setItems2 ( items => [ ...items , item ] )
138164 } }
139165 removeItem = { ( id ) => {
140- setItems2 ( items2 . filter ( i => i . id !== id ) )
166+ setItems2 ( items => items . filter ( i => i . id !== id ) )
141167 } }
142168 />
143169 </ div >
0 commit comments