Skip to content

Commit

Permalink
1 column mode loading, saving 12 column layout
Browse files Browse the repository at this point in the history
fix gridstack#1985
fix gridstack#1975 (oneColumnModeDomSort case)

* we now handle DOM and addWidget() when temporally into 1 column mode better to save the full 12 column mode
* also DOM reading will reverse-sort entries to lay them in 1 column mode correctly (like if you had started with 12 and scaled down)
  • Loading branch information
adumesny committed Oct 23, 2022
1 parent 43625f5 commit 9ca815c
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 23 deletions.
9 changes: 6 additions & 3 deletions doc/CHANGES.md
Expand Up @@ -5,7 +5,7 @@ Change log
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*

- [7.0.2 (TBD)](#702-tbd)
- [7.0.1-dev (TBD)](#701-dev-tbd)
- [7.0.1 (2022-10-14)](#701-2022-10-14)
- [7.0.0 (2022-10-09)](#700-2022-10-09)
- [6.0.3 (2022-10-08)](#603-2022-10-08)
Expand Down Expand Up @@ -75,9 +75,12 @@ Change log

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## 7.0.2 (TBD)
## 7.0.1-dev (TBD)
* add `GridStackEngine.findEmptyPosition()`
* fixed [#2081](https://github.com/gridstack/gridstack.js/issues/2081) removeWidget() after it's gone from DOM
* add GridStackEngine.findEmptyPosition()
* fixed [#1985](https://github.com/gridstack/gridstack.js/issues/1985) addWidget() or DOM read in single column mode will not adjust to multi column mode
* fixed [#1975](https://github.com/gridstack/gridstack.js/issues/1975) oneColumnModeDomSort not respected when loading in 1 column

## 7.0.1 (2022-10-14)
* fixed [#2073](https://github.com/gridstack/gridstack.js/issues/2073) SSR (server side rendering) isTouch issue (introduced in v6)
* fixed - removing last item delete sub-grid that are not auto-generated (nested.html vs nested_advanced.html)
Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,7 +1,7 @@
{
"name": "gridstack",
"version": "7.0.1-dev",
"description": "TypeScript/JS lib for dashboard layout and creation, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)",
"description": "TypeScript/JS lib for dashboard layout and creation, responsive, mobile support, no external dependencies, with many wrappers (React, Angular, Vue, Ember, knockout...)",
"main": "./dist/gridstack.js",
"types": "./dist/gridstack.d.ts",
"repository": {
Expand Down
38 changes: 38 additions & 0 deletions spec/e2e/html/1985_read_1_column_wrong_12.html
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>read 1 column</title>

<link rel="stylesheet" href="../../../demo/demo.css"/>
<script src="../../../dist/gridstack-all.js"></script>

</head>
<body>
<div class="container-fluid">
<h1>read from dom into 1 column has wrong order and 12 column layout</h1>
<div class="grid-stack">
<div class="grid-stack-item" gs-x="4" gs-y="0"><div class="grid-stack-item-content">1</div></div>
<div class="grid-stack-item" gs-x="0" gs-y="0"><div class="grid-stack-item-content">0</div></div>
<div class="grid-stack-item" gs-x="1" gs-y="1"><div class="grid-stack-item-content">2</div></div>
</div>
</div>
<script type="text/javascript">
let grid = GridStack.init({
cellHeight: 70,
float: true,
});

var items = [
{x: 0, y: 0, content: '0'},
{x: 2, y: 0, content: '1'},
];
// grid.addWidget(items[1]); // need to be reverse sort-order to be correct (otherwise DOM above is wrong)
// grid.addWidget(items[0]);
// grid.load(items); // or this work (code reverse sort)

</script>
</body>
</html>
37 changes: 24 additions & 13 deletions src/gridstack-engine.ts
Expand Up @@ -37,7 +37,7 @@ export class GridStackEngine {
protected _float: boolean;
/** @internal */
protected _prevFloat: boolean;
/** @internal cached layouts of difference column count so we can restore ack (eg 12 -> 1 -> 12) */
/** @internal cached layouts of difference column count so we can restore back (eg 12 -> 1 -> 12) */
protected _layouts?: GridStackNode[][]; // maps column # to array of values nodes
/** @internal true while we are resizing widgets during column resize to skip certain parts */
protected _inColumnResize: boolean;
Expand Down Expand Up @@ -362,7 +362,7 @@ export class GridStackEngine {
return this.nodeBoundFix(node, resizing);
}

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

let before = node._orig || Utils.copyPos({}, node);
Expand All @@ -372,14 +372,18 @@ export class GridStackEngine {
if (node.minW && node.minW <= this.column) { node.w = Math.max(node.w, node.minW); }
if (node.minH) { node.h = Math.max(node.h, node.minH); }

// if user loaded a larger than allowed widget for current # of columns (or force 1 column mode),
// remember it's position & width so we can restore back (1 -> 12 column) #1655 #1985
// IFF we're not in the middle of column resizing!
const saveOrig = this.column === 1 || node.x + node.w > this.column;
if (saveOrig && this.column < 12 && !this._inColumnResize && !node.autoPosition && node._id && this.findCacheLayout(node, 12) === -1) {
let copy = {...node}; // need _id + positions
copy.x = Math.min(11, copy.x);
copy.w = Math.min(12, copy.w);
this.cacheOneLayout(copy, 12);
}

if (node.w > this.column) {
// if user loaded a larger than allowed widget for current # of columns,
// remember it's full width so we can restore back (1 -> 12 column) #1655
// IFF we're not in the middle of column resizing!
if (this.column < 12 && !this._inColumnResize) {
node.w = Math.min(12, node.w);
this.cacheOneLayout(node, 12);
}
node.w = this.column;
} else if (node.w < 1) {
node.w = 1;
Expand Down Expand Up @@ -706,7 +710,7 @@ export class GridStackEngine {
return this;
}

/** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode, so we don't loose orig layout),
/** saves a copy of the largest column layout (eg 12 even when rendering oneColumnMode) so we don't loose orig layout,
* returning a list of widgets for serialization */
public save(saveElement = true): GridStackNode[] {
// use the highest layout for any saved info so we can have full detail on reload #1849
Expand Down Expand Up @@ -852,7 +856,7 @@ export class GridStackEngine {
}

// finally re-layout them in reverse order (to get correct placement)
newNodes = Utils.sort(newNodes, -1, column);
if (!domOrder) newNodes = Utils.sort(newNodes, -1, column);
this._inColumnResize = true; // prevent cache update
this.nodes = []; // pretend we have no nodes to start with (add() will use same structures) to simplify layout
newNodes.forEach(node => {
Expand Down Expand Up @@ -891,11 +895,18 @@ export class GridStackEngine {
let layout: GridStackNode = {x: n.x, y: n.y, w: n.w, _id: n._id}
this._layouts = this._layouts || [];
this._layouts[column] = this._layouts[column] || [];
let index = this._layouts[column].findIndex(l => l._id === n._id);
index === -1 ? this._layouts[column].push(layout) : this._layouts[column][index] = layout;
let index = this.findCacheLayout(n, column);
if (index === -1)
this._layouts[column].push(layout);
else
this._layouts[column][index] = layout;
return this;
}

protected findCacheLayout(n: GridStackNode, column: number): number {
return this._layouts?.[column]?.findIndex(l => l._id === n._id) ?? -1;
}


/** called to remove all internal values but the _id */
public cleanupNode(node: GridStackNode): GridStackEngine {
Expand Down
21 changes: 16 additions & 5 deletions src/gridstack.ts
Expand Up @@ -34,6 +34,9 @@ export interface CellPosition {
y: number;
}

/** optional function called during load() to callback the user on new added/remove items */
export type AddRemoveFcn = (g: GridStack, w: GridStackWidget, add: boolean) => GridItemHTMLElement;

interface GridCSSStyleSheet extends CSSStyleSheet {
_max?: number; // internal tracker of the max # of rows we created
}
Expand Down Expand Up @@ -186,7 +189,7 @@ export class GridStack {
protected _placeholder: HTMLElement;
/** @internal */
protected _prevColumn: number;
/** @internal */
/** @internal prevent cached layouts from being updated when loading into small column layouts */
protected _ignoreLayoutsNodeChange: boolean;
/** @internal */
public _gsEventHandler = {};
Expand Down Expand Up @@ -328,16 +331,18 @@ export class GridStack {
if (this.opts.auto) {
this.batchUpdate(); // prevent in between re-layout #1535 TODO: this only set float=true, need to prevent collision check...
let elements: {el: HTMLElement; i: number}[] = [];
let column = this.getColumn();
if (column === 1 && this._prevColumn) column = this._prevColumn; // do 12 column when reading into 1 column mode
this.getGridItems().forEach(el => { // get dom elements (not nodes yet)
let x = parseInt(el.getAttribute('gs-x'));
let y = parseInt(el.getAttribute('gs-y'));
elements.push({
el,
// if x,y are missing (autoPosition) add them to end of list - but keep their respective DOM order
i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * this.getColumn()
i: (Number.isNaN(x) ? 1000 : x) + (Number.isNaN(y) ? 1000 : y) * column
});
});
elements.sort((a, b) => a.i - b.i).forEach(e => this._prepareElement(e.el));
elements.sort((a, b) => b.i - a.i).forEach(e => this._prepareElement(e.el)); // revert sort so lowest item wins
this.batchUpdate(false);
}

Expand Down Expand Up @@ -421,8 +426,14 @@ export class GridStack {
this.makeSubGrid(node.el, undefined, undefined, false);
}

// if we're adding an item into 1 column (_prevColumn is set only when going to 1) make sure
// we don't override the larger 12 column layout that was already saved. #1985
if (this._prevColumn && this.opts.column === 1) {
this._ignoreLayoutsNodeChange = true;
}
this._triggerAddEvent();
this._triggerChangeEvent();
delete this._ignoreLayoutsNodeChange;

return el;
}
Expand Down Expand Up @@ -603,11 +614,11 @@ export class GridStack {
* @example
* see http://gridstackjs.com/demo/serialization.html
**/
public load(layout: GridStackWidget[], addAndRemove: boolean | ((g: GridStack, w: GridStackWidget, add: boolean) => GridItemHTMLElement) = true): GridStack {
public load(layout: GridStackWidget[], addAndRemove: boolean | AddRemoveFcn = true): GridStack {
let items = GridStack.Utils.sort([...layout], -1, this._prevColumn || this.getColumn()); // make copy before we mod/sort
this._insertNotAppend = true; // since create in reverse order...

// if we're loading a layout into 1 column (_prevColumn is set only when going to 1) and items don't fit, make sure to save
// 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
// the original wanted layout so we can scale back up correctly #1471
if (this._prevColumn && this._prevColumn !== this.opts.column && items.some(n => (n.x + n.w) > this.opts.column)) {
this._ignoreLayoutsNodeChange = true; // skip layout update
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Expand Up @@ -236,7 +236,7 @@ export class Utils {
return true;
}

/** copies over b size & position (GridStackPosition), and possibly min/max as well */
/** copies over b size & position (GridStackPosition), and optionally min/max as well */
static copyPos(a: GridStackWidget, b: GridStackWidget, doMinMax = false): GridStackWidget {
a.x = b.x;
a.y = b.y;
Expand Down

0 comments on commit 9ca815c

Please sign in to comment.