forked from gridstack/gridstack.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathreact-hooks-controlled-multiple.html
165 lines (151 loc) · 7.14 KB
/
react-hooks-controlled-multiple.html
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
<!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/[email protected]/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 ~96) ********************************/
/***********************************************************************************************************/
const { useState, useEffect, useLayoutEffect, createRef, useRef } = React
const Item = ({ id }) => <div>{id}</div>
//
// Controlled example
//
const ControlledStack = ({ items, addItem, changeItems }) => {
const refs = useRef({})
const gridRef = useRef()
const gridContainerRef = useRef(null)
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: false,
acceptWidgets: true,
column: 6,
minRow: 1,
},
gridContainerRef.current
)
.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 ~96.
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 change', (ev, gsItems) => {
// synch our version from GS....
// Note: we could just update those few items passed but save() is fast and it's easier to just set an entire new list
// and since we have the same ids, React will not re-create anything...
const newItems = grid.save(false); // saveContent=false
changeItems(newItems);
})
// addEvents(grid, i);
} else {
//
// update existing GS layout, which is optimized to updates only diffs and add new/delete items as well
//
const grid = gridRef.current;
const layout = items.map((a) =>
// use exiting nodes (which will skip diffs being the same) else new elements Widget but passing the React dom .el so we know what to makeWidget() on!
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;
}
}, [items])
return (
// ********************
// NOTE: constructing DOM grid items in template when gridstack is also allowed creating (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 Angular component wrapper that does that: https://github.com/gridstack/gridstack.js/tree/master/angular/ (lib author uses Angular)
// ...TBD creating React equivalent...
//
// Also templating forces you to spell out the 15+ GridStackWidget attributes (only x,y,w,h done below), instead of passing an option structure that
// supports everything, is not robust as things get added and pollutes the DOM attr for default/missing entries, vs the optimized code in GS.
// ********************
<div style={{ width: '100%', marginRight: '10px' }}>
<div className="grid-stack" ref={gridContainerRef}>
{items.map((item, i) => {
return (
<div ref={refs.current[item.id]} key={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 }, { id: 'item-2-2', x: 0, y: 1 }, { id: 'item-2-3', x: 1, y: 0 }])
return (
<div>
<div style={{display: 'flex', gap: '16px', marginBottom: '16px'}}>
<div></div>
</div>
<div style={{ display: 'flex', gap: '16px', marginBottom: '16px' }}>
<button onClick={() => setItems1(items => [...items, { id: `item-1-${Date.now()}`, x: 2, y: 0, w: 2, h: 2 }])}>Add Item to 1 grid</button>
<button onClick={() => setItems2(items => [...items, { id: `item-2-${Date.now()}`, x: 2, y: 0, w: 2, h: 2 }])}>Add Item to 2 grid</button>
</div>
<div style={{display: 'flex'}}>
<div style={{ display: 'flex', width: '50%' }}>
<ControlledStack
items={items1}
addItem={(item) => {
setItems1(items => [...items, item])
}}
changeItems={(items) => setItems1(items)}
/>
</div >
<div style={{ display: 'flex', width: '50%' }}>
<ControlledStack
items={items2}
addItem={(item) => {
setItems2(items => [...items, item])
}}
changeItems={(items) => setItems2(items)}
/>
</div>
</div >
</div>
)
}
ReactDOM.render(<ControlledExample />, document.getElementById('controlled-stack'))
</script>
</html>