forked from gridstack/gridstack.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathreact-hooks-controlled-multiple.html
More file actions
176 lines (160 loc) · 7.02 KB
/
react-hooks-controlled-multiple.html
File metadata and controls
176 lines (160 loc) · 7.02 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Gridstack.js React integration example</title>
<link rel="stylesheet" href="demo.css" />
<link rel="stylesheet" href="../dist/gridstack-extra.css"/>
<script src="../dist/gridstack-all.js"></script>
<!-- Scripts to use react inside html - DEVELOPMENT FILES -->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.js"></script>
</head>
<body>
<div>
<h2>Controlled stack</h2>
<div id="controlled-stack"></div>
</div>
</body>
<script type="text/babel">
/***********************************************************************************************************/
/********************************* NOT IDEAL - see comments below line 111) ********************************/
/***********************************************************************************************************/
const { useState, useEffect, useLayoutEffect, createRef, useRef } = React
const Item = ({ id }) => <div>{id}</div>
//
// Controlled example
//
const ControlledStack = ({ items, addItem, removeItem, id }) => {
const refs = useRef({})
const gridRef = useRef()
refs.current = {}
if (Object.keys(refs.current).length !== items.length) {
items.forEach(({ id }) => {
refs.current[id] = refs.current[id] || createRef()
})
}
useLayoutEffect(() => {
if (!gridRef.current) {
// no need to init twice (would will return same grid) or register dup events
const grid = gridRef.current = GridStack.init(
{
float: true,
acceptWidgets: true,
disableOneColumnMode: true, // side-by-side and fever columns to fit smaller screens
column: 6,
minRow: 1,
},
`.controlled-${id}`
)
.on('added', (ev, gsItems) => {
if (grid._ignoreCB) return;
// 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!
// 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.
gsItems.forEach(n => {
grid.removeWidget(n.el, true, false); // true=remove DOM, false=don't call use back!
// can't pass n directly even though similar structs as it has n.el.gridstackNode which gives JSON error for circular write.
addItem({id:n.id, x:n.x, y:n.y, w:n.w, h:n.h});
});
})
.on('removed', (ev, gsItems) => {
if (grid._ignoreCB) return;
gsItems.forEach(n => removeItem(n.id));
})
} else {
//
// update existing grid layout, which is optimized to updates only diffs (will add new/delete items for examples)
//
const grid = gridRef.current;
const layout = [];
items.forEach((a) => layout.push(
refs.current[a.id].current.gridstackNode || {...a, el: refs.current[a.id].current}
));
grid._ignoreCB = true; // hack: ignore added/removed since we're the one doing the update
grid.load(layout);
delete grid._ignoreCB;
}
// 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
// grid.batchUpdate()
// items.forEach((a) => {
// // remove existing widgets
// if (refs.current[a.id] && refs.current[a.id].current) {
// grid.removeWidget(refs.current[a.id].current, false, false)
// }
// grid.makeWidget(refs.current[a.id].current)
// })
// grid.batchUpdate(false)
}, [items])
useEffect(() => {
return () => {
// console.log('cleanup', id)
// gridRef.current.destroy(false, false)
// gridRef.current = null
}
})
return (
// ********************
// NOTE: constructing DOM grid items in template when gridstack is also allowed editing (dragging between grids, or adding/removing from say a toolbar)
// is NOT A GOOD IDEA as you end up fighting between gridstack users' edits and your template items structure which are not in sync.
// 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.
// 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.
// see the upcoming Angular component wrapper that does that instead (lib author uses Angular). TBD creating React equivalent...
//
// 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
// is not robust as things get added, and pollutes the DOM attr for default/missing entries, vs optimized code in GS.
// ********************
<div style={{ width: '100%', marginRight: '10px' }}>
<div className={`grid-stack controlled-${id}`}>
{items.map((item, i) => {
return (
<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}>
<div className="grid-stack-item-content">
<Item {...item} />
</div>
</div>
)
})}
</div>
<code>
<pre>{JSON.stringify(items, null, 2)}</pre>
</code>
</div>
)
}
const ControlledExample = () => {
const [items1, setItems1] = useState([{ id: 'item-1-1', x: 0, y: 0, w: 2, h: 2 }, { id: 'item-1-2', x: 2, y: 0, w: 2, h: 2 }])
const [items2, setItems2] = useState([{ id: 'item-2-1', x: 0, y: 0, w: 1, h: 1 }, { id: 'item-2-2', x: 0, y: 1, w: 1, h: 1 }, { id: 'item-2-3', x: 1, y: 0, w: 1, h: 1 }])
return (
<div style={{display: 'flex'}}>
<div style={{ display: 'flex', width: '50%' }}>
<ControlledStack
id='gs1'
items={items1}
addItem={(item) => {
setItems1(items => [...items, item])
}}
removeItem={(id) => {
setItems1(items => items.filter(i => i.id !== id))
}}
/>
</div >
<div style={{ display: 'flex', width: '50%' }}>
<ControlledStack
id='gs2'
items={items2}
addItem={(item) => {
setItems2(items => [...items, item])
}}
removeItem={(id) => {
setItems2(items => items.filter(i => i.id !== id))
}}
/>
</div>
</div >
)
}
ReactDOM.render(<ControlledExample />, document.getElementById('controlled-stack'))
</script>
</html>