From baa8cde77ef66db67aaf206cf830ae91c01600ca Mon Sep 17 00:00:00 2001 From: Andrei Radoi Date: Fri, 28 Feb 2020 00:10:09 +0200 Subject: [PATCH 1/4] Added minRow and row --- src/gridstack.js | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/gridstack.js b/src/gridstack.js index 9013979f5..364993c8f 100644 --- a/src/gridstack.js +++ b/src/gridstack.js @@ -296,10 +296,12 @@ var idSeq = 0; - var GridStackEngine = function(column, onchange, float, maxRow, items) { + var GridStackEngine = function(row, column, onchange, float, minRow, maxRow, items) { + this.row = row || 0; this.column = column || 12; this.float = float || false; - this.maxRow = maxRow || 0; + this.minRow = minRow || 0; + this.maxRow = this.row ? this.row : maxRow || 0; this.nodes = items || []; this.onchange = onchange || function() {}; @@ -558,16 +560,18 @@ } var hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; })); - if (!this.maxRow && !hasLocked) { + if (!this.maxRow && !this.row && !hasLocked) { return true; } var clonedNode; var clone = new GridStackEngine( + 0, this.column, null, this.float, 0, + 0, this.nodes.map(function(n) { if (n === node) { clonedNode = $.extend({}, n); @@ -587,7 +591,7 @@ return n !== clonedNode && Boolean(n.locked) && Boolean(n._dirty); })); } - if (this.maxRow) { + if (this.maxRow || this.row) { res &= clone.getRow() <= this.maxRow; } @@ -595,15 +599,16 @@ }; GridStackEngine.prototype.canBePlacedWithRespectToHeight = function(node) { - if (!this.maxRow) { + if (!this.maxRow && !this.row) { return true; } - var clone = new GridStackEngine( + 0, this.column, null, this.float, 0, + 0, this.nodes.map(function(n) { return $.extend({}, n); })); clone.addNode(node); return clone.getRow() <= this.maxRow; @@ -665,7 +670,7 @@ }; GridStackEngine.prototype.getRow = function() { - return this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0); + return this.row ? this.row : this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0); }; GridStackEngine.prototype.beginUpdate = function(node) { @@ -709,8 +714,10 @@ var isNested = this.$el.closest('.' + opts.itemClass).length > 0; this.opts = Utils.defaults(opts, { + row: parseInt(this.$el.attr('data-gs-row')) || 0, column: parseInt(this.$el.attr('data-gs-column')) || 12, - maxRow: parseInt(this.$el.attr('data-gs-max-row')) || 0, + maxRow: parseInt(this.$el.attr('data-gs-row')) || 0 ? parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-max-row')) || 0, + minRow: parseInt(this.$el.attr('data-gs-min-row')) || 0, itemClass: 'grid-stack-item', placeholderClass: 'grid-stack-placeholder', placeholderText: '', @@ -787,7 +794,7 @@ this._initStyles(); - this.engine = new GridStackEngine(this.opts.column, function(nodes, detachNode) { + this.engine = new GridStackEngine(this.opts.row, this.opts.column, function(nodes, detachNode) { detachNode = (detachNode === undefined ? true : detachNode); var maxHeight = 0; this.nodes.forEach(function(n) { @@ -807,7 +814,7 @@ } }); self._updateStyles(maxHeight + 10); - }, this.opts.float, this.opts.maxRow); + }, this.opts.float, this.opts.minRow, this.opts.maxRow); if (this.opts.auto) { var elements = []; @@ -1155,12 +1162,12 @@ GridStack.prototype._updateContainerHeight = function() { if (this.engine._batchMode) { return; } - var row = this.engine.getRow(); + var row = this.opts.minRow > this.engine.getRow() ? this.opts.minRow : this.engine.getRow(); // check for css min height. Each row is cellHeight + verticalMargin, until last one which has no margin below var cssMinHeight = parseInt(this.$el.css('min-height')); if (cssMinHeight > 0) { var verticalMargin = this.opts.verticalMargin; - var minRow = Math.round((cssMinHeight + verticalMargin) / (this.cellHeight() + verticalMargin)); + var minRow = Math.round((cssMinHeight + verticalMargin) / (this.cellHeight() + verticalMargin)); if (row < minRow) { row = minRow; } From d52cc9335e14f95ba4b4026b75a838f5fcc1553f Mon Sep 17 00:00:00 2001 From: Andrei Radoi Date: Fri, 28 Feb 2020 09:04:37 +0200 Subject: [PATCH 2/4] Improved minRow and row is not a shortcut for minRow and maxRow --- src/gridstack.js | 112 ++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/src/gridstack.js b/src/gridstack.js index 364993c8f..9872fcae5 100644 --- a/src/gridstack.js +++ b/src/gridstack.js @@ -296,12 +296,10 @@ var idSeq = 0; - var GridStackEngine = function(row, column, onchange, float, minRow, maxRow, items) { - this.row = row || 0; + var GridStackEngine = function(column, onchange, float, maxRow, items) { this.column = column || 12; this.float = float || false; - this.minRow = minRow || 0; - this.maxRow = this.row ? this.row : maxRow || 0; + this.maxRow = maxRow || 0; this.nodes = items || []; this.onchange = onchange || function() {}; @@ -344,8 +342,9 @@ while (true) { var collisionNode = this.nodes.find(Utils._collisionNodeCheck, {node: node, nn: nn}); if (!collisionNode) { return; } - this.moveNode(collisionNode, collisionNode.x, node.y + node.height, + var moved = this.moveNode(collisionNode, collisionNode.x, node.y + node.height, collisionNode.width, collisionNode.height, true); + if (!moved) { return; } // break inf loop if we couldn't move after all (ex: maxRow, fixed) } }; @@ -434,19 +433,28 @@ if (Number.isNaN(node.width)) { node.width = defaults.width; } if (Number.isNaN(node.height)) { node.height = defaults.height; } + if (node.maxWidth !== undefined) { node.width = Math.min(node.width, node.maxWidth); } + if (node.maxHeight !== undefined) { node.height = Math.min(node.height, node.maxHeight); } + if (node.minWidth !== undefined) { node.width = Math.max(node.width, node.minWidth); } + if (node.minHeight !== undefined) { node.height = Math.max(node.height, node.minHeight); } + if (node.width > this.column) { node.width = this.column; } else if (node.width < 1) { node.width = 1; } - - if (node.height < 1) { + if (this.maxRow && node.height > this.maxRow) { + node.height = this.maxRow; + } else if (node.height < 1) { node.height = 1; } if (node.x < 0) { node.x = 0; } + if (node.y < 0) { + node.y = 0; + } if (node.x + node.width > this.column) { if (resizing) { @@ -455,9 +463,12 @@ node.x = this.column - node.width; } } - - if (node.y < 0) { - node.y = 0; + if (this.maxRow && node.y + node.height > this.maxRow) { + if (resizing) { + node.height = this.maxRow - node.y; + } else { + node.y = this.maxRow - node.height; + } } return node; @@ -499,11 +510,6 @@ GridStackEngine.prototype.addNode = function(node, triggerAddEvent) { node = this._prepareNode(node); - if (node.maxWidth !== undefined) { node.width = Math.min(node.width, node.maxWidth); } - if (node.maxHeight !== undefined) { node.height = Math.min(node.height, node.maxHeight); } - if (node.minWidth !== undefined) { node.width = Math.max(node.width, node.minWidth); } - if (node.minHeight !== undefined) { node.height = Math.max(node.height, node.minHeight); } - node._id = node._id || ++idSeq; if (node.autoPosition) { @@ -560,18 +566,16 @@ } var hasLocked = Boolean(this.nodes.find(function(n) { return n.locked; })); - if (!this.maxRow && !this.row && !hasLocked) { + if (!this.maxRow && !hasLocked) { return true; } var clonedNode; var clone = new GridStackEngine( - 0, this.column, null, this.float, 0, - 0, this.nodes.map(function(n) { if (n === node) { clonedNode = $.extend({}, n); @@ -591,7 +595,7 @@ return n !== clonedNode && Boolean(n.locked) && Boolean(n._dirty); })); } - if (this.maxRow || this.row) { + if (this.maxRow) { res &= clone.getRow() <= this.maxRow; } @@ -599,16 +603,15 @@ }; GridStackEngine.prototype.canBePlacedWithRespectToHeight = function(node) { - if (!this.maxRow && !this.row) { + if (!this.maxRow) { return true; } + var clone = new GridStackEngine( - 0, this.column, null, this.float, 0, - 0, this.nodes.map(function(n) { return $.extend({}, n); })); clone.addNode(node); return clone.getRow() <= this.maxRow; @@ -637,29 +640,21 @@ if (typeof width !== 'number') { width = node.width; } if (typeof height !== 'number') { height = node.height; } - if (node.maxWidth !== undefined) { width = Math.min(width, node.maxWidth); } - if (node.maxHeight !== undefined) { height = Math.min(height, node.maxHeight); } - if (node.minWidth !== undefined) { width = Math.max(width, node.minWidth); } - if (node.minHeight !== undefined) { height = Math.max(height, node.minHeight); } - - if (node.x === x && node.y === y && node.width === width && node.height === height) { - return node; + // constrain the passed in values and check if we're still changing our node + var resizing = (node.width !== width || node.height !== height); + var nn = { x: x, y: y, width: width, height: height, + maxWidth: node.maxWidth, maxHeight: NodeIterator.maxHeight, minWidth: node.minWidth, minHeight: node.minHeight}; + nn = this._prepareNode(nn, resizing); + if (node.x === nn.x && node.y === nn.y && node.width === nn.width && node.height === nn.height) { + return null; } - var resizing = node.width !== width; node._dirty = true; - node.x = x; - node.y = y; - node.width = width; - node.height = height; - - node.lastTriedX = x; - node.lastTriedY = y; - node.lastTriedWidth = width; - node.lastTriedHeight = height; - - node = this._prepareNode(node, resizing); + node.x = node.lastTriedX = nn.x; + node.y = node.lastTriedY = nn.y; + node.width = node.lastTriedWidth = nn.width; + node.height = node.lastTriedHeight = nn.height; this._fixCollisions(node); if (!noPack) { @@ -670,7 +665,7 @@ }; GridStackEngine.prototype.getRow = function() { - return this.row ? this.row : this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0); + return this.nodes.reduce(function(memo, n) { return Math.max(memo, n.y + n.height); }, 0); }; GridStackEngine.prototype.beginUpdate = function(node) { @@ -716,8 +711,8 @@ this.opts = Utils.defaults(opts, { row: parseInt(this.$el.attr('data-gs-row')) || 0, column: parseInt(this.$el.attr('data-gs-column')) || 12, - maxRow: parseInt(this.$el.attr('data-gs-row')) || 0 ? parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-max-row')) || 0, - minRow: parseInt(this.$el.attr('data-gs-min-row')) || 0, + minRow: opts.row || parseInt(this.$el.attr('data-gs-row')) ? opts.row || parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-min-row')) || 0, + maxRow: opts.row || parseInt(this.$el.attr('data-gs-row')) ? opts.row || parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-max-row')) || 0, itemClass: 'grid-stack-item', placeholderClass: 'grid-stack-placeholder', placeholderText: '', @@ -794,7 +789,7 @@ this._initStyles(); - this.engine = new GridStackEngine(this.opts.row, this.opts.column, function(nodes, detachNode) { + this.engine = new GridStackEngine(this.opts.column, function(nodes, detachNode) { detachNode = (detachNode === undefined ? true : detachNode); var maxHeight = 0; this.nodes.forEach(function(n) { @@ -814,7 +809,7 @@ } }); self._updateStyles(maxHeight + 10); - }, this.opts.float, this.opts.minRow, this.opts.maxRow); + }, this.opts.float, this.opts.maxRow); if (this.opts.auto) { var elements = []; @@ -1162,12 +1157,15 @@ GridStack.prototype._updateContainerHeight = function() { if (this.engine._batchMode) { return; } - var row = this.opts.minRow > this.engine.getRow() ? this.opts.minRow : this.engine.getRow(); + var row = this.engine.getRow(); + if (row < this.opts.minRow) { + row = this.opts.minRow; + } // check for css min height. Each row is cellHeight + verticalMargin, until last one which has no margin below var cssMinHeight = parseInt(this.$el.css('min-height')); if (cssMinHeight > 0) { var verticalMargin = this.opts.verticalMargin; - var minRow = Math.round((cssMinHeight + verticalMargin) / (this.cellHeight() + verticalMargin)); + var minRow = Math.round((cssMinHeight + verticalMargin) / (this.cellHeight() + verticalMargin)); if (row < minRow) { row = minRow; } @@ -1469,7 +1467,6 @@ // Tempting to initialize the passed in opt with default and valid values, but this break knockout demos // as the actual value are filled in when _prepareElement() calls el.attr('data-gs-xyz) before adding the node. // opt = this.engine._prepareNode(opt); - opt = opt || {}; } else { // old legacy way of calling with items spelled out - call us back with single object instead (so we can properly initialized values) return this.addWidget(el, {x: opt, y: y, width: width, height: height, autoPosition: autoPosition, @@ -1477,6 +1474,9 @@ } el = $(el); + if (opt) { // see knockout above + this.engine._prepareNode(opt); + } this._writeAttr(el, opt); this.$el.append(el); return this.makeWidget(el); @@ -2030,6 +2030,13 @@ * notifications (see doc for supported events) */ GridStack.prototype.on = function(eventName, callback) { + // check for array of names being passed instead + if (eventName.indexOf(' ') !== -1) { + var names = eventName.split(' '); + names.forEach(function(name) { this.on(name, callback) }, this); + return; + } + if (eventName === 'change' || eventName === 'added' || eventName === 'removed') { // native CustomEvent handlers - cash the generic handlers so we can remove this._gsEventHandler = this._gsEventHandler || {}; @@ -2043,6 +2050,13 @@ /** unsubscribe from the 'on' event */ GridStack.prototype.off = function(eventName) { + // check for array of names being passed instead + if (eventName.indexOf(' ') !== -1) { + var names = eventName.split(' '); + names.forEach(function(name) { this.off(name, callback) }, this); + return; + } + if (eventName === 'change' || eventName === 'added' || eventName === 'removed') { // remove native CustomEvent handlers if (this._gsEventHandler && this._gsEventHandler[eventName]) { From 03c986db088bae3cf13236ae60b0cce781f4dd94 Mon Sep 17 00:00:00 2001 From: Andrei Radoi Date: Fri, 28 Feb 2020 09:18:14 +0200 Subject: [PATCH 3/4] Fixed minRow and maxRow --- src/gridstack.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/gridstack.js b/src/gridstack.js index 9872fcae5..1d33753a4 100644 --- a/src/gridstack.js +++ b/src/gridstack.js @@ -693,6 +693,12 @@ opts = opts || {}; + // if row property exists, replace minRow and maxRow + if (opts.row) { + opts.minRow = opts.row; + opts.maxRow = opts.row; + } + this.$el = $(el); // TODO: legacy code this.el = this.$el.get(0); // exposed HTML element to the user @@ -711,8 +717,8 @@ this.opts = Utils.defaults(opts, { row: parseInt(this.$el.attr('data-gs-row')) || 0, column: parseInt(this.$el.attr('data-gs-column')) || 12, - minRow: opts.row || parseInt(this.$el.attr('data-gs-row')) ? opts.row || parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-min-row')) || 0, - maxRow: opts.row || parseInt(this.$el.attr('data-gs-row')) ? opts.row || parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-max-row')) || 0, + minRow: parseInt(this.$el.attr('data-gs-row')) ? parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-min-row')) || 0, + maxRow: parseInt(this.$el.attr('data-gs-row')) ? parseInt(this.$el.attr('data-gs-row')) : parseInt(this.$el.attr('data-gs-max-row')) || 0, itemClass: 'grid-stack-item', placeholderClass: 'grid-stack-placeholder', placeholderText: '', From 597d48416533b810ef55d1be76360d804bae91cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20R=C4=83doi?= <46427158+RadoiAndrei@users.noreply.github.com> Date: Fri, 28 Feb 2020 13:13:00 +0200 Subject: [PATCH 4/4] Update README.md --- doc/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/README.md b/doc/README.md index b677bacbf..fa3f0b861 100644 --- a/doc/README.md +++ b/doc/README.md @@ -75,6 +75,7 @@ gridstack.js API * a string (ex: '100px', '10em', '10rem', '10%') * 0 or null, in which case the library will not generate styles for rows. Everything must be defined in CSS files. * `'auto'` - height will be calculated to match cell width (initial square grid). +- `row` - number of rows. This is a shortcut of writting `minRow: sameValue, maxRow: sameValue`. - `column` - number of columns (default: `12`) which can change on the fly with `column(N)` as well. See [example](http://gridstackjs.com/demo/column.html) - `ddPlugin` - class that implement drag'n'drop functionallity for gridstack. If `false` grid will be static. (default: `null` - first available plugin will be used) - `disableDrag` - disallows dragging of widgets (default: `false`). @@ -86,6 +87,7 @@ gridstack.js API - `handle` - draggable handle selector (default: `'.grid-stack-item-content'`) - `handleClass` - draggable handle class (e.g. `'grid-stack-item-content'`). If set `handle` is ignored (default: `null`) - `itemClass` - widget class (default: `'grid-stack-item'`) +- `minRow` - minimum rows amount. Default is `0` - `maxRow` - maximum rows amount. Default is `0` which means no maximum rows - `minWidth` - minimal width. If window width is less than or equal to, grid will be shown in one-column mode (default: `768`) - `oneColumnModeDomSort` - 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`) @@ -103,7 +105,9 @@ gridstack.js API ## Grid attributes - `data-gs-animate` - turns animation on +- `data-gs-row` - number of rows. This is a shortcut of writting `data-gs-min-row="sameValue" data-gs-max-row="sameValue"` . - `data-gs-column` - amount of columns. Setting non-default value must be supported by equivalent change in CSS, [see docs here](https://github.com/gridstack/gridstack.js#change-grid-columns). +- `data-gs-min-row` - minimum rows amount. Default is `0`. - `data-gs-max-row` - maximum rows amount. Default is `0` which means no maximum rows. - `data-gs-current-row` - current rows amount. Set by the library only. Can be used by the CSS rules.