Skip to content

Commit 87e1b28

Browse files
committed
Incorrect layout on grid load in one column mode
* fix #2527 * turned out to be much more complicated fix than expected. cleanup some of the code while debugging through it... * make sure we nodeBoundFix() before widthChanged is checked * cacheLayout() now checks fo existing node id so loading again doesn't mess things up
1 parent d0b6830 commit 87e1b28

File tree

4 files changed

+46
-43
lines changed

4 files changed

+46
-43
lines changed

demo/serialization.html

Lines changed: 27 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,43 @@
1212
<body>
1313
<div class="container-fluid">
1414
<h1>Serialization demo</h1>
15-
<a onClick="saveGrid()" class="btn btn-primary" href="#">Save</a>
16-
<a onClick="loadGrid()" class="btn btn-primary" href="#">Load</a>
17-
<a onClick="saveFullGrid()" class="btn btn-primary" href="#">Save Full</a>
18-
<a onClick="loadFullGrid()" class="btn btn-primary" href="#">Load Full</a>
19-
<a onClick="clearGrid()" class="btn btn-primary" href="#">Clear</a>
15+
<a onclick="saveGrid()" class="btn btn-primary" href="#">Save</a>
16+
<a onclick="loadGrid()" class="btn btn-primary" href="#">Load</a>
17+
<a onclick="saveFullGrid()" class="btn btn-primary" href="#">Save Full</a>
18+
<a onclick="loadFullGrid()" class="btn btn-primary" href="#">Load Full</a>
19+
<a onclick="clearGrid()" class="btn btn-primary" href="#">Clear</a>
2020
<br/><br/>
21-
<div contentEditable="true">Editable Div</div>
2221
<div id="gridCont"><div class="grid-stack"></div></div>
2322
<hr/>
24-
<textarea id="saved-data" cols="100" rows="20" readonly="readonly"></textarea>
23+
<textarea id="saved-data" style="width: 100%" cols="100" rows="20" readonly="readonly"></textarea>
2524
</div>
26-
25+
<script src="events.js"></script>
2726
<script type="text/javascript">
28-
let grid = GridStack.init({
29-
minRow: 1, // don't let it collapse when empty
30-
cellHeight: '7rem',
31-
draggable: { cancel: '.no-drag'} // example of additional custom elements to skip drag on
32-
});
33-
34-
grid.on('added removed change', function(e, items) {
35-
if (!items) return;
36-
let str = '';
37-
items.forEach(function(item) { str += ' (x,y)=' + item.x + ',' + item.y; });
38-
console.log(e.type + ' ' + items.length + ' items:' + str );
39-
});
40-
4127
let serializedData = [
42-
{x: 0, y: 0, w: 2, h: 2, id: '0'},
43-
{x: 3, y: 1, h: 3, id: '1',
44-
content: "<button onclick=\"alert('clicked!')\">Press me</button><div>text area</div><div><textarea></textarea></div><div>Input Field</div><input type='text'><div contentEditable=\"true\">Editable Div</div><div class=\"no-drag\">no drag</div>"},
45-
{x: 4, y: 1, id: '2'},
46-
{x: 2, y: 3, w: 3, id: '3'},
47-
{x: 1, y: 3, id: '4'}
28+
{x: 0, y: 0, w: 2, h: 2},
29+
{x: 2, y: 1, w: 2, h: 3,
30+
content: `<button onclick=\"alert('clicked!')\">Press me</button><div>text area</div><div><textarea></textarea></div><div>Input Field</div><input type="text"><div contenteditable=\"true\">Editable Div</div><div class=\"no-drag\">no drag</div>`},
31+
{x: 4, y: 1},
32+
{x: 1, y: 3},
33+
{x: 2, y: 3, w:3},
4834
];
49-
serializedData.forEach((n, i) =>
50-
n.content = `<button onClick="removeWidget(this.parentElement.parentElement)">X</button><br> ${i}<br> ${n.content ? n.content : ''}`);
35+
serializedData.forEach((n, i) => {
36+
n.id = String(i);
37+
n.content = `<button onclick="removeWidget(this.parentElement.parentElement)">X</button><br> ${i}<br> ${n.content ? n.content : ''}`;
38+
});
5139
let serializedFull;
5240

41+
let grid = GridStack.init({
42+
minRow: 1, // don't let it collapse when empty
43+
cellHeight: 80,
44+
float: true,
45+
draggable: { cancel: '.no-drag'} // example of additional custom elements to skip drag on
46+
}).load(serializedData);
47+
addEvents(grid);
48+
5349
// 2.x method - just saving list of widgets with content (default)
5450
function loadGrid() {
55-
grid.load(serializedData, true); // update things
51+
grid.load(serializedData);
5652
}
5753

5854
// 2.x method
@@ -86,7 +82,7 @@ <h1>Serialization demo</h1>
8682
grid.removeWidget(el, false);
8783
}
8884

89-
loadGrid();
85+
// setTimeout(() => loadGrid(), 1000); // TEST force a second load which should be no-op
9086
</script>
9187
</body>
9288
</html>

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ Change log
107107
## 9.5.0-dev (TBD)
108108
* fix [#2525](https://github.com/gridstack/gridstack.js/commit/2525) Fixed unhandled exception happening in _mouseMove handler
109109
* fix potential crash in doContentResize() if grid gets deleted by the time the delay happens
110+
* fix [#2527](https://github.com/gridstack/gridstack.js/issues/2527) Incorrect layout on grid load in one column mode
110111

111112
## 9.5.0 (2023-10-26)
112113
* feat [#1275](https://github.com/gridstack/gridstack.js/issues/1275) div scale support - Thank you [elmehdiamlou](https://github.com/elmehdiamlou) for implementing this teh right way (add scale to current code)

src/gridstack-engine.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,6 @@ export class GridStackEngine {
346346
* @param resizing if out of bound, resize down or move into the grid to fit ?
347347
*/
348348
public prepareNode(node: GridStackNode, resizing?: boolean): GridStackNode {
349-
node = node || {};
350349
node._id = node._id ?? GridStackEngine._idSeq++;
351350

352351
// if we're missing position, have the grid position us automatically (before we set them to 0,0)
@@ -373,11 +372,12 @@ export class GridStackEngine {
373372
if (isNaN(node.w)) { node.w = defaults.w; }
374373
if (isNaN(node.h)) { node.h = defaults.h; }
375374

376-
return this.nodeBoundFix(node, resizing);
375+
this.nodeBoundFix(node, resizing);
376+
return node;
377377
}
378378

379379
/** part2 of preparing a node to fit inside our grid - checks for x,y,w from grid dimensions */
380-
public nodeBoundFix(node: GridStackNode, resizing?: boolean): GridStackNode {
380+
public nodeBoundFix(node: GridStackNode, resizing?: boolean): GridStackEngine {
381381

382382
let before = node._orig || Utils.copyPos({}, node);
383383

@@ -436,7 +436,7 @@ export class GridStackEngine {
436436
node._dirty = true;
437437
}
438438

439-
return node;
439+
return this;
440440
}
441441

442442
/** returns a list of modified nodes from their original values */
@@ -520,7 +520,7 @@ export class GridStackEngine {
520520
if (dup) return dup; // prevent inserting twice! return it instead.
521521

522522
// skip prepareNode if we're in middle of column resize (not new) but do check for bounds!
523-
node = this._inColumnResize ? this.nodeBoundFix(node) : this.prepareNode(node);
523+
this._inColumnResize ? this.nodeBoundFix(node) : this.prepareNode(node);
524524
delete node._temporaryRemoved;
525525
delete node._removeDOM;
526526

@@ -667,7 +667,7 @@ export class GridStackEngine {
667667
let resizing = (node.w !== o.w || node.h !== o.h);
668668
let nn: GridStackNode = Utils.copyPos({}, node, true); // get min/max out first, then opt positions next
669669
Utils.copyPos(nn, o);
670-
nn = this.nodeBoundFix(nn, resizing);
670+
this.nodeBoundFix(nn, resizing);
671671
Utils.copyPos(o, nn);
672672

673673
if (!o.forceCollide && Utils.samePos(node, o)) return false;
@@ -926,7 +926,11 @@ export class GridStackEngine {
926926
public cacheLayout(nodes: GridStackNode[], column: number, clear = false): GridStackEngine {
927927
let copy: GridStackNode[] = [];
928928
nodes.forEach((n, i) => {
929-
n._id = n._id ?? GridStackEngine._idSeq++; // make sure we have an id in case this is new layout, else re-use id already set
929+
// make sure we have an id in case this is new layout, else re-use id already set
930+
if (n._id === undefined) {
931+
const existing = n.id ? this.nodes.find(n2 => n2.id === n.id) : undefined; // find existing node using users id
932+
n._id = existing?._id ?? GridStackEngine._idSeq++;
933+
}
930934
copy[i] = {x: n.x, y: n.y, w: n.w, _id: n._id} // only thing we change is x,y,w and id to find it back
931935
});
932936
this._layouts = clear ? [] : this._layouts || []; // use array to find larger quick

src/gridstack.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -666,7 +666,8 @@ export class GridStack {
666666

667667
// if we're loading a layout into for example 1 column (_prevColumn is set only when going to 1) and items don't fit, make sure to save
668668
// the original wanted layout so we can scale back up correctly #1471
669-
if (this._prevColumn && this._prevColumn !== this.opts.column && items.some(n => ((n.x || 0) + n.w) > (this.opts.column as number))) {
669+
const column = this.opts.column as number;
670+
if (this._prevColumn && this._prevColumn !== column && items.some(n => ((n.x || 0) + n.w) > column)) {
670671
this._ignoreLayoutsNodeChange = true; // skip layout update
671672
this.engine.cacheLayout(items, this._prevColumn, true);
672673
}
@@ -707,6 +708,7 @@ export class GridStack {
707708
// if item sizes to content, re-use the exiting height so it's a better guess at the final size 9same if width doesn't change)
708709
if (Utils.shouldSizeToContent(item)) w.h = item.h;
709710
// check if missing coord, in which case find next empty slot with new (or old if missing) sizes
711+
this.engine.nodeBoundFix(w); // before widthChanged is checked below...
710712
if (w.autoPosition || w.x === undefined || w.y === undefined) {
711713
w.w = w.w || item.w;
712714
w.h = w.h || item.h;
@@ -718,7 +720,6 @@ export class GridStack {
718720
this.engine.nodes.push(item);
719721
if (Utils.samePos(item, w)) {
720722
this.moveNode(item, {...w, forceCollide: true});
721-
Utils.copyPos(w, item, true);
722723
}
723724

724725
this.update(item.el, w);
@@ -1219,6 +1220,7 @@ export class GridStack {
12191220
let n = el?.gridstackNode;
12201221
if (!n) return;
12211222
let w = Utils.cloneDeep(opt); // make a copy we can modify in case they re-use it or multiple items
1223+
this.engine.nodeBoundFix(w);
12221224
delete w.autoPosition;
12231225
delete w.id;
12241226

@@ -1263,9 +1265,9 @@ export class GridStack {
12631265
}
12641266
Utils.sanitizeMinMax(n);
12651267

1266-
// finally move the widget
1267-
if (m !== undefined) this.moveNode(n, m);
1268-
if (changed) { // move will only update x,y,w,h so update the rest too
1268+
// finally move the widget and update attr
1269+
if (m) this.moveNode(n, m);
1270+
if (m || changed) {
12691271
this._writeAttr(el, n);
12701272
}
12711273
if (ddChanged) {

0 commit comments

Comments
 (0)