From 77df15cb435db368502c9b5d2204b83cfbe05457 Mon Sep 17 00:00:00 2001 From: Alain Dumesny Date: Mon, 3 Feb 2020 12:19:15 -0800 Subject: [PATCH] Custom Responsive Sort Order * add `oneColumnModeDomSort` true|false to let you specify a custom layout (use dom order instead of x,y) for oneColumnMode `setColumn(1)` * fix for #713 * better than PR #966 which attempted to make a generic support for oneColumn only mode, yet didn't modify the other layouts sorting. Instead user will have complete control (dom order) iresptive of layouts sort. Also works with current setColumn() implementation * added karma tests * Note: you cannot change this flag and expect 1 column (which is cached) to change (can see in column.html, unless you add/remove items in larger layouts the blows caches). Future todo. --- demo/column.html | 10 ++-- doc/CHANGES.md | 1 + spec/gridstack-spec.js | 104 +++++++++++++++++++++++++++++++++++++++++ src/gridstack.d.ts | 6 +++ src/gridstack.js | 62 +++++++++++++++++------- 5 files changed, 162 insertions(+), 21 deletions(-) diff --git a/demo/column.html b/demo/column.html index 8de31e93b..40c6f5c2f 100644 --- a/demo/column.html +++ b/demo/column.html @@ -22,6 +22,7 @@

setColumn() grid demo

Add Widget 1 Column + 1 Column DOM 2 Column 3 Column 4 Column @@ -54,15 +55,15 @@

setColumn() grid demo

var items = [ {x: 0, y: 0, width: 2, height: 2}, {x: 2, y: 0, width: 2, height: 1}, - {x: 5, y: 0, width: 1, height: 1}, + {x: 5, y: 1, width: 1, height: 1}, + {x: 5, y: 0, width: 2, height: 1}, {text: ' auto'}, // autoPosition testing - {x: 1, y: 3, width: 4, height: 1}, {x: 5, y: 3, width: 2, height: 1}, {x: 0, y: 4, width: 12, height: 1} ]; var count = 0; grid.batchUpdate(); - for (count=0; count<3;) { + for (count=0; count<4;) { var n = items[count]; grid.addWidget($('
' + count++ + (n.text ? n.text : '') + '
'), n); }; @@ -78,7 +79,8 @@

setColumn() grid demo

grid.addWidget($('
' + count++ + (n.text ? n.text : '') + '
'), n); }); - $('#1column').click(function() { grid.setColumn(1); $text.text(1);}); + $('#1column').click(function() { delete grid.opts.oneColumnModeDomSort; grid.setColumn(1); $text.text(1);}); + $('#1columnDOM').click(function() { grid.opts.oneColumnModeDomSort = true; grid.setColumn(1); $text.text('1 DOM');}); $('#2column').click(function() { grid.setColumn(2); $text.text(2);}); $('#3column').click(function() { grid.setColumn(3); $text.text(3);}); $('#4column').click(function() { grid.setColumn(4); $text.text(4);}); diff --git a/doc/CHANGES.md b/doc/CHANGES.md index 6c2a9c819..0d27e40f4 100644 --- a/doc/CHANGES.md +++ b/doc/CHANGES.md @@ -29,6 +29,7 @@ Change log ## v0.6.1-dev (upcoming changes) +- add `oneColumnModeDomSort` true|false to let you specify a custom layout (use dom order instead of x,y) for oneColumnMode `setColumn(1)` [#713](https://github.com/gridstack/gridstack.js/issues/713) - fix oneColumnMode to only restore if we auto went to it as window sizes up [#1125](https://github.com/gridstack/gridstack.js/pull/1125) ## v0.6.1 (2020-02-02) diff --git a/spec/gridstack-spec.js b/spec/gridstack-spec.js index 0b650c3b1..19170776f 100644 --- a/spec/gridstack-spec.js +++ b/spec/gridstack-spec.js @@ -15,6 +15,12 @@ describe('gridstack', function() { '
' + ' ' + ''; + // empty grid + var gridstackEmptyHTML = + '
' + + '
' + + '
' + + '
'; // generic widget with no param var widgetHTML = '
hello
'; @@ -441,6 +447,104 @@ describe('gridstack', function() { }); }); + describe('oneColumnModeDomSort', function() { + beforeEach(function() { + document.body.insertAdjacentHTML('afterbegin', gridstackEmptyHTML); + }); + afterEach(function() { + document.body.removeChild(document.getElementById('gs-cont')); + }); + it('should support default going to 1 column', function() { + var options = { + column: 12, + float: true + }; + $('.grid-stack').gridstack(options); + var grid = $('.grid-stack').data('gridstack'); + var el1 = grid.addWidget(widgetHTML, {width:1, height:1}); + var el2 = grid.addWidget(widgetHTML, {x:2, y:0, width:2, height:1}); + var el3 = grid.addWidget(widgetHTML, {x:1, y:0, width:1, height:2}); + + // items are item1[1x1], item3[1x1], item2[2x1] + expect(parseInt(el1.attr('data-gs-x'))).toBe(0); + expect(parseInt(el1.attr('data-gs-y'))).toBe(0); + expect(parseInt(el1.attr('data-gs-width'))).toBe(1); + expect(parseInt(el1.attr('data-gs-height'))).toBe(1); + + expect(parseInt(el3.attr('data-gs-x'))).toBe(1); + expect(parseInt(el3.attr('data-gs-y'))).toBe(0); + expect(parseInt(el3.attr('data-gs-width'))).toBe(1); + expect(parseInt(el3.attr('data-gs-height'))).toBe(2); + + expect(parseInt(el2.attr('data-gs-x'))).toBe(2); + expect(parseInt(el2.attr('data-gs-y'))).toBe(0); + expect(parseInt(el2.attr('data-gs-width'))).toBe(2); + expect(parseInt(el2.attr('data-gs-height'))).toBe(1); + + // items are item1[1x1], item3[1x2], item2[1x1] in 1 column + grid.setColumn(1); + expect(parseInt(el1.attr('data-gs-x'))).toBe(0); + expect(parseInt(el1.attr('data-gs-y'))).toBe(0); + expect(parseInt(el1.attr('data-gs-width'))).toBe(1); + expect(parseInt(el1.attr('data-gs-height'))).toBe(1); + + expect(parseInt(el3.attr('data-gs-x'))).toBe(0); + expect(parseInt(el3.attr('data-gs-y'))).toBe(1); + expect(parseInt(el3.attr('data-gs-width'))).toBe(1); + expect(parseInt(el3.attr('data-gs-height'))).toBe(2); + + expect(parseInt(el2.attr('data-gs-x'))).toBe(0); + expect(parseInt(el2.attr('data-gs-y'))).toBe(3); + expect(parseInt(el2.attr('data-gs-width'))).toBe(1); + expect(parseInt(el2.attr('data-gs-height'))).toBe(1); + }); + it('should support oneColumnModeDomSort ON going to 1 column', function() { + var options = { + column: 12, + oneColumnModeDomSort: true, + float: true + }; + $('.grid-stack').gridstack(options); + var grid = $('.grid-stack').data('gridstack'); + var el1 = grid.addWidget(widgetHTML, {width:1, height:1}); + var el2 = grid.addWidget(widgetHTML, {x:2, y:0, width:2, height:1}); + var el3 = grid.addWidget(widgetHTML, {x:1, y:0, width:1, height:2}); + + // items are item1[1x1], item3[1x1], item2[2x1] + expect(parseInt(el1.attr('data-gs-x'))).toBe(0); + expect(parseInt(el1.attr('data-gs-y'))).toBe(0); + expect(parseInt(el1.attr('data-gs-width'))).toBe(1); + expect(parseInt(el1.attr('data-gs-height'))).toBe(1); + + expect(parseInt(el3.attr('data-gs-x'))).toBe(1); + expect(parseInt(el3.attr('data-gs-y'))).toBe(0); + expect(parseInt(el3.attr('data-gs-width'))).toBe(1); + expect(parseInt(el3.attr('data-gs-height'))).toBe(2); + + expect(parseInt(el2.attr('data-gs-x'))).toBe(2); + expect(parseInt(el2.attr('data-gs-y'))).toBe(0); + expect(parseInt(el2.attr('data-gs-width'))).toBe(2); + expect(parseInt(el2.attr('data-gs-height'))).toBe(1); + + // items are item1[1x1], item2[1x1], item3[1x2] in 1 column dom ordered + grid.setColumn(1); + expect(parseInt(el1.attr('data-gs-x'))).toBe(0); + expect(parseInt(el1.attr('data-gs-y'))).toBe(0); + expect(parseInt(el1.attr('data-gs-width'))).toBe(1); + expect(parseInt(el1.attr('data-gs-height'))).toBe(1); + + expect(parseInt(el2.attr('data-gs-x'))).toBe(0); + expect(parseInt(el2.attr('data-gs-y'))).toBe(1); + expect(parseInt(el2.attr('data-gs-width'))).toBe(1); + expect(parseInt(el2.attr('data-gs-height'))).toBe(1); + + expect(parseInt(el3.attr('data-gs-x'))).toBe(0); + expect(parseInt(el3.attr('data-gs-y'))).toBe(2); + expect(parseInt(el3.attr('data-gs-width'))).toBe(1); + expect(parseInt(el3.attr('data-gs-height'))).toBe(2); + }); + }); + describe('grid.minWidth', function() { beforeEach(function() { document.body.insertAdjacentHTML('afterbegin', gridstackHTML); diff --git a/src/gridstack.d.ts b/src/gridstack.d.ts index 0858cc2e1..4d6c30bc1 100644 --- a/src/gridstack.d.ts +++ b/src/gridstack.d.ts @@ -496,6 +496,12 @@ interface GridstackOptions { */ oneColumnModeClass ? : string; + /** + * set to true if you want oneColumnMode to use the DOM order and ignore x,y from normal multi column + * layouts during sorting. This enables you to have custom 1 column layout that differ from the rest. (default?: false) + */ + oneColumnModeDomSort?: boolean; + /** * class for placeholder (default?: 'grid-stack-placeholder') */ diff --git a/src/gridstack.js b/src/gridstack.js index 35d6dfdac..1e06a8aff 100644 --- a/src/gridstack.js +++ b/src/gridstack.js @@ -56,16 +56,16 @@ return !(a.x + a.width <= b.x || b.x + b.width <= a.x || a.y + a.height <= b.y || b.y + b.height <= a.y); }, - sort: function(nodes, dir, width) { - if (!width) { + sort: function(nodes, dir, column) { + if (!column) { var widths = nodes.map(function(node) { return node.x + node.width; }); - width = Math.max.apply(Math, widths); + column = Math.max.apply(Math, widths); } if (dir === -1) - return Utils.sortBy(nodes, function(n) { return -(n.x + n.y * width); }); + return Utils.sortBy(nodes, function(n) { return -(n.x + n.y * column); }); else - return Utils.sortBy(nodes, function(n) { return (n.x + n.y * width); }); + return Utils.sortBy(nodes, function(n) { return (n.x + n.y * column); }); }, createStylesheet: function(id) { @@ -721,6 +721,7 @@ cellHeightUnit: 'px', disableOneColumnMode: opts.disableOneColumnMode || false, oneColumnModeClass: opts.oneColumnModeClass || 'grid-stack-one-column-mode', + oneColumnModeDomSort: opts.oneColumnModeDomSort, ddPlugin: null }); @@ -1823,20 +1824,37 @@ * Called to scale the widget width & position up/down based on the column change. * Note we store previous layouts (especially original ones) to make it possible to go * from say 12 -> 1 -> 12 and get back to where we were. + * + * oldColumn: previous number of columns + * column: new column number + * nodes?: different sorted list (ex: DOM order) instead of current list */ - GridStackEngine.prototype._updateNodeWidths = function(oldColumn, column) { - if (this.nodes.length === 0 || oldColumn === column) { return; } - var nodes = Utils.sort(this.nodes, -1, oldColumn); // current column reverse sorting so we can insert last to front (limit collision) + GridStackEngine.prototype._updateNodeWidths = function(oldColumn, column, nodes) { + if (!this.nodes.length || oldColumn === column) { return; } // cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data - var copy = [nodes.length]; - nodes.forEach(function(n, i) {copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id}}); // only thing we change is x,y,w and id to find it back + var copy = [this.nodes.length]; + this.nodes.forEach(function(n, i) {copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id}}); // only thing we change is x,y,w and id to find it back this._layouts = this._layouts || []; // use array to find larger quick this._layouts[oldColumn] = copy; - // see if we have cached previous layout. if NOT and we are going up in size start with the largest layout as down-scaling is more accurate - var lastIndex = this._layouts.length - 1; + // if we're going to 1 column and using DOM order rather than default sorting, then generate that layout + if (column === 1 && nodes && nodes.length) { + var top = 0; + nodes.forEach(function(n) { + n.x = 0; + n.width = 1; + n.y = Math.max(n.y, top); + top = n.y + n.height; + }); + } else { + nodes = Utils.sort(this.nodes, -1, oldColumn); // current column reverse sorting so we can insert last to front (limit collision) + } + + // see if we have cached previous layout. var cacheNodes = this._layouts[column] || []; + // if not AND we are going up in size start with the largest layout as down-scaling is more accurate + var lastIndex = this._layouts.length - 1; if (cacheNodes.length === 0 && column > oldColumn && column < lastIndex) { cacheNodes = this._layouts[lastIndex] || []; if (cacheNodes.length) { @@ -1872,13 +1890,13 @@ var ratio = column / oldColumn; nodes.forEach(function(node) { if (!node) return; - node.x = Math.round(node.x * ratio); - node.width = (oldColumn === 1 ? 1 : (Math.round(node.width * ratio) || 1)); + node.x = (column === 1 ? 0 : Math.round(node.x * ratio)); + node.width = ((column === 1 || oldColumn === 1) ? 1 : (Math.round(node.width * ratio) || 1)); newNodes.push(node); }); - newNodes = Utils.sort(newNodes, -1, column); // finally relayout them in reverse order (to get correct placement) + newNodes = Utils.sort(newNodes, -1, column); this._ignoreLayoutsNodeChange = true; this.batchUpdate(); this.nodes = []; // pretend we have no nodes to start with (we use same structures) to simplify layout @@ -1913,9 +1931,19 @@ this.container.addClass('grid-stack-' + column); this.opts.column = this.grid.column = column; - // update the items now if (doNotPropagate === true) { return; } - this.grid._updateNodeWidths(oldColumn, column); + + // update the items now - see if the dom order nodes should be passed instead (else default to current list) + var domNodes; + if (this.opts.oneColumnModeDomSort && column === 1) { + domNodes = []; + this.container.children('.' + this.opts.itemClass).each(function(index, el) { + var node = $(el).data('_gridstack_node'); + if (node) { domNodes.push(node); } + }); + if (!domNodes.length) { domNodes = undefined; } + } + this.grid._updateNodeWidths(oldColumn, column, domNodes); // and trigger our event last... this.grid._ignoreLayoutsNodeChange = true;