Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 048767c

Browse files
committed
(feat) column() resizing now supports 'list' | 'compact' modes
* fix gridstack#2358 when your grid is display a sorted list of items, we now support reflowing that content during column resize and compact() methods. * compact() now takes options which today can be CompactOptions = 'list' | 'compact'; * column(N) now has new options ColumnOptions = 'list' | 'compact' | 'moveScale', etc... * also optimized some code while stepping in debugger. API is backward compatible and defaults to the same behavior. TODO: remember bigger width and restore back when going up in columns...
1 parent ac25d86 commit 048767c

File tree

5 files changed

+69
-38
lines changed

5 files changed

+69
-38
lines changed

demo/column.html

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ <h1>column() grid demo (fix cellHeight)</h1>
1717
<div>
1818
<label>Choose re-layout:</label>
1919
<select onchange="setLayout(this.value)">
20+
<option value="list">list</option>
21+
<option value="compact">compact</option>
2022
<option value="moveScale">move + scale</option>
2123
<option value="move">move</option>
2224
<option value="scale">scale</option>
@@ -25,6 +27,8 @@ <h1>column() grid demo (fix cellHeight)</h1>
2527
</select>
2628
</div>
2729
<div>
30+
<a onClick="grid.removeAll().load(layout1)" class="btn btn-primary" href="#">random</a>
31+
<a onClick="grid.removeAll().load(layout2)" class="btn btn-primary" href="#">list</a>
2832
<a onClick="addWidget()" class="btn btn-primary" href="#">Add Widget</a>
2933
<a onClick="setOneColumn(false)" class="btn btn-primary" href="#">1 Column</a>
3034
<a onClick="setOneColumn(true)" class="btn btn-primary" href="#">1 Column DOM</a>
@@ -41,21 +45,7 @@ <h1>column() grid demo (fix cellHeight)</h1>
4145
</div>
4246

4347
<script type="text/javascript">
44-
let grid = GridStack.init({
45-
float: true,
46-
disableOneColumnMode: true, // prevent auto column for this manual demo
47-
cellHeight: 100 // fixed as default 'auto' (square) makes it hard to test 1-3 column in actual large windows tests
48-
});
49-
let text = document.querySelector('#column-text');
50-
let layout = 'moveScale';
51-
52-
grid.on('added removed change', function(e, items) {
53-
let str = '';
54-
items.forEach(function(item) { str += ' (x,y)=' + item.x + ',' + item.y; });
55-
console.log(e.type + ' ' + items.length + ' items:' + str );
56-
});
57-
58-
let items = [
48+
let layout1 = [ // DOM order will be 0,1,2,3,4,5,6 vs column1 = 0,1,4,3,2,5,6
5949
/* match karma testing
6050
{x: 0, y: 0, w: 4, h: 2},
6151
{x: 4, y: 0, w: 4, h: 4},
@@ -71,12 +61,30 @@ <h1>column() grid demo (fix cellHeight)</h1>
7161
{x: 5, y: 3, w: 2},
7262
{x: 0, y: 4, w: 12}
7363
];
64+
let layout2 = [{h:2},{},{},{},{},{},{},{},{},{w:2},{},{},{},{},{},{}];
65+
layout2.forEach((n,i) => {
66+
n.content = '<button onClick="grid.removeWidget(this.parentNode.parentNode)">X</button><br>' + ++i;
67+
});
7468
let count = 0;
75-
items.forEach(n => {
69+
layout1.forEach(n => {
7670
n.content = '<button onClick="grid.removeWidget(this.parentNode.parentNode)">X</button><br>' + count++ + (n.text ? n.text : '');
77-
grid.addWidget(n); // DOM order will be 0,1,2,3,4,5,6 vs column1 = 0,1,4,3,2,5,6
7871
});
7972

73+
let grid = GridStack.init({
74+
float: true,
75+
disableOneColumnMode: true, // prevent auto column for this manual demo
76+
cellHeight: 100 // fixed as default 'auto' (square) makes it hard to test 1-3 column in actual large windows tests
77+
}).load(layout2);
78+
let text = document.querySelector('#column-text');
79+
let layout = 'list';
80+
81+
grid.on('added removed change', function(e, items) {
82+
let str = '';
83+
items.forEach(function(item) { str += ' (x,y)=' + item.x + ',' + item.y; });
84+
console.log(e.type + ' ' + items.length + ' items:' + str );
85+
});
86+
87+
8088
function addWidget() {
8189
let n = items[count] || {
8290
x: Math.round(12 * Math.random()),

doc/CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Change log
55
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
66
**Table of Contents** *generated with [DocToc](http://doctoc.herokuapp.com/)*
77

8+
- [8.2.3-dev TBD](#823-dev-tbd)
89
- [8.2.3 (2023-06-11)](#823-2023-06-11)
910
- [8.2.1 (2023-05-26)](#821-2023-05-26)
1011
- [8.2.0 (2023-05-24)](#820-2023-05-24)
@@ -90,6 +91,9 @@ Change log
9091

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

94+
## 8.2.3-dev TBD
95+
* feat [#2358](https://github.com/gridstack/gridstack.js/issues/2358) column() resizing now support reflowing content as list
96+
9397
## 8.2.3 (2023-06-11)
9498
* fix [#2349](https://github.com/gridstack/gridstack.js/issues/2349) grid NoMove vs item NoMove support
9599
* fix [#2352](https://github.com/gridstack/gridstack.js/issues/2352) .ui-draggable-dragging z-index for modal dialogs

src/gridstack-engine.ts

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { Utils } from './utils';
7-
import { GridStackNode, ColumnOptions, GridStackPosition, GridStackMoveOpts, SaveFcn } from './types';
7+
import { GridStackNode, ColumnOptions, GridStackPosition, GridStackMoveOpts, SaveFcn, CompactOptions } from './types';
88

99
/** callback to update the DOM attributes since this class is generic (no HTML or other info) for items that changed - see _notify() */
1010
type OnChangeCB = (nodes: GridStackNode[]) => void;
@@ -258,20 +258,23 @@ export class GridStackEngine {
258258
return !this.collide(nn);
259259
}
260260

261-
/** re-layout grid items to reclaim any empty space */
262-
public compact(): GridStackEngine {
261+
/** re-layout grid items to reclaim any empty space - optionally keeping the sort order exactly the same (list mode) vs truly finding an empty spaces */
262+
public compact(layout: CompactOptions = 'compact', sortBefore = true): GridStackEngine {
263263
if (this.nodes.length === 0) return this;
264264
this.batchUpdate()
265-
.sortNodes();
265+
if (sortBefore) this.sortNodes();
266+
this._inColumnResize = true; // faster addNode()
266267
let copyNodes = this.nodes;
267268
this.nodes = []; // pretend we have no nodes to conflict layout to start with...
268-
copyNodes.forEach(node => {
269-
if (!node.locked) {
270-
node.autoPosition = true;
269+
copyNodes.forEach((n, index, list) => {
270+
let after: GridStackNode;
271+
if (!n.locked) {
272+
n.autoPosition = true;
273+
if (layout === 'list' && index) after = list[index - 1];
271274
}
272-
this.addNode(node, false); // 'false' for add event trigger
273-
node._dirty = true; // will force attr update
275+
this.addNode(n, false, after); // 'false' for add event trigger
274276
});
277+
delete this._inColumnResize;
275278
return this.batchUpdate(false);
276279
}
277280

@@ -288,8 +291,8 @@ export class GridStackEngine {
288291
public get float(): boolean { return this._float || false; }
289292

290293
/** sort the nodes array from first to last, or reverse. Called during collision/placement to force an order */
291-
public sortNodes(dir?: -1 | 1): GridStackEngine {
292-
this.nodes = Utils.sort(this.nodes, dir, this.column);
294+
public sortNodes(dir: 1 | -1 = 1, column = this.column): GridStackEngine {
295+
this.nodes = Utils.sort(this.nodes, dir, column);
293296
return this;
294297
}
295298

@@ -482,18 +485,20 @@ export class GridStackEngine {
482485

483486
/** find the first available empty spot for the given node width/height, updating the x,y attributes. return true if found.
484487
* optionally you can pass your own existing node list and column count, otherwise defaults to that engine data.
488+
* Optionally pass a widget to start search AFTER, meaning the order will remain the same but possibly have empty slots we skipped
485489
*/
486-
public findEmptyPosition(node: GridStackNode, nodeList = this.nodes, column = this.column): boolean {
487-
nodeList = Utils.sort(nodeList, -1, column);
490+
public findEmptyPosition(node: GridStackNode, nodeList = this.nodes, column = this.column, after?: GridStackNode): boolean {
491+
let start = after ? after.y * column + (after.x + after.w) : 0;
488492
let found = false;
489-
for (let i = 0; !found; ++i) {
493+
for (let i = start; !found; ++i) {
490494
let x = i % column;
491495
let y = Math.floor(i / column);
492496
if (x + node.w > column) {
493497
continue;
494498
}
495499
let box = {x, y, w: node.w, h: node.h};
496500
if (!nodeList.find(n => Utils.isIntercepted(box, n))) {
501+
if (node.x !== x || node.y !== y) node._dirty = true;
497502
node.x = x;
498503
node.y = y;
499504
delete node.autoPosition;
@@ -504,7 +509,7 @@ export class GridStackEngine {
504509
}
505510

506511
/** call to add the given node to our list, fixing collision and re-packing */
507-
public addNode(node: GridStackNode, triggerAddEvent = false): GridStackNode {
512+
public addNode(node: GridStackNode, triggerAddEvent = false, after?: GridStackNode): GridStackNode {
508513
let dup = this.nodes.find(n => n._id === node._id);
509514
if (dup) return dup; // prevent inserting twice! return it instead.
510515

@@ -513,7 +518,7 @@ export class GridStackEngine {
513518
delete node._temporaryRemoved;
514519
delete node._removeDOM;
515520

516-
if (node.autoPosition && this.findEmptyPosition(node)) {
521+
if (node.autoPosition && this.findEmptyPosition(node, this.nodes, this.column, after)) {
517522
delete node.autoPosition; // found our slot
518523
}
519524

@@ -790,11 +795,19 @@ export class GridStackEngine {
790795
public updateNodeWidths(prevColumn: number, column: number, nodes: GridStackNode[], layout: ColumnOptions = 'moveScale'): GridStackEngine {
791796
if (!this.nodes.length || !column || prevColumn === column) return this;
792797

798+
// simpler shortcuts layouts
799+
const doCompact = layout === 'compact' || layout === 'list';
800+
if (doCompact) {
801+
this.sortNodes(1, prevColumn); // sort with original layout once and only once (new column will affect order otherwise)
802+
return this.compact(layout, false);
803+
}
804+
793805
// cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data
794806
this.cacheLayout(this.nodes, prevColumn);
795807
this.batchUpdate(); // do this EARLY as it will call saveInitial() so we can detect where we started for _dirty and collision
796808
let newNodes: GridStackNode[] = [];
797809

810+
798811
// if we're going to 1 column and using DOM order rather than default sorting, then generate that layout
799812
let domOrder = false;
800813
if (column === 1 && nodes?.length) {

src/types.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,19 @@ export const dragInDefaultOptions: DDDragInOpt = {
4949
// scroll: false,
5050
};
5151

52-
/** different layout options when changing # of columns,
53-
* including a custom function that takes new/old column count, and array of new/old positions
52+
/**
53+
* different layout options when changing # of columns, including a custom function that takes new/old column count, and array of new/old positions
5454
* Note: new list may be partially already filled if we have a cache of the layout at that size and new items were added later.
55+
* Options are:
56+
* 'list' - treat items as sorted list, keeping items (unsized unless too big for column count) sequentially reflowing them
57+
* 'compact' - similar to list, but using compact() method which will possibly re-order items if an empty slots are available due to a larger item needing to be pushed to next row
58+
* 'moveScale' - will scale and move items by the ratio new newColumnCount / oldColumnCount
59+
* 'move' | 'scale' - will only size or move items
60+
* 'none' will leave items unchanged, unless they don't fit in column count
5561
*/
56-
export type ColumnOptions = 'moveScale' | 'move' | 'scale' | 'none' |
62+
export type ColumnOptions = 'list' | 'compact' | 'moveScale' | 'move' | 'scale' | 'none' |
5763
((column: number, oldColumn: number, nodes: GridStackNode[], oldNodes: GridStackNode[]) => void);
58-
64+
export type CompactOptions = 'list' | 'compact';
5965
export type numberOrString = number | string;
6066
export interface GridItemHTMLElement extends HTMLElement {
6167
/** pointer to grid node instance */

src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export class Utils {
134134
* @param dir 1 for asc, -1 for desc (optional)
135135
* @param width width of the grid. If undefined the width will be calculated automatically (optional).
136136
**/
137-
static sort(nodes: GridStackNode[], dir?: -1 | 1, column?: number): GridStackNode[] {
137+
static sort(nodes: GridStackNode[], dir: 1 | -1 = 1, column?: number): GridStackNode[] {
138138
column = column || nodes.reduce((col, n) => Math.max(n.x + n.w, col), 0) || 12;
139139
if (dir === -1)
140140
return nodes.sort((a, b) => (b.x + b.y * column)-(a.x + a.y * column));

0 commit comments

Comments
 (0)