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;