Skip to content

Commit 79ec5fe

Browse files
author
Alain Dumesny
authored
Merge pull request #1101 from adumesny/develop
new compact() method, improved setColumn()
2 parents e91937e + 30dc3d2 commit 79ec5fe

File tree

7 files changed

+155
-32
lines changed

7 files changed

+155
-32
lines changed

demo/column.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,30 +48,30 @@ <h1>setColumn() grid demo</h1>
4848
}
4949

5050
var items = [
51-
{x: 0, y: 4, width: 12, height: 1},
52-
{x: 5, y: 3, width: 2, height: 1},
53-
{x: 1, y: 3, width: 4, height: 1},
54-
{x: 0, y: 0, width: 1, height: 1},
55-
{}, // autoPosition testing
56-
{x: 5, y: 0, width: 1, height: 1},
51+
{x: 0, y: 0, width: 2, height: 2},
5752
{x: 2, y: 0, width: 2, height: 1},
58-
{x: 0, y: 0, width: 2, height: 2}
53+
{x: 5, y: 0, width: 1, height: 1},
54+
{text: ' auto'}, // autoPosition testing
55+
{x: 1, y: 3, width: 4, height: 1},
56+
{x: 5, y: 3, width: 2, height: 1},
57+
{x: 0, y: 4, width: 12, height: 1}
5958
];
6059
var count = 0;
6160
grid.batchUpdate();
6261
for (count=0; count<3; count++) {
63-
grid.addWidget($('<div><div class="grid-stack-item-content">' + count + '</div></div>'), items.pop());
62+
var n = items[count];
63+
grid.addWidget($('<div><div class="grid-stack-item-content">' + count + (n.text ? n.text : '') + '</div></div>'), n);
6464
};
6565
grid.commit();
6666

6767
$('#add-widget').click(function() {
68-
var node = items.pop() || {
68+
var n = items[count++] || {
6969
x: Math.round(12 * Math.random()),
7070
y: Math.round(5 * Math.random()),
7171
width: Math.round(1 + 3 * Math.random()),
7272
height: Math.round(1 + 3 * Math.random())
7373
};
74-
grid.addWidget($('<div><div class="grid-stack-item-content">' + count++ + '</div></div>'), node);
74+
grid.addWidget($('<div><div class="grid-stack-item-content">' + count + (n.text ? n.text : '') + '</div></div>'), n);
7575
});
7676

7777
$('#1column').click(function() { grid.setColumn(1); });

demo/two.html

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,13 @@ <h1>Two grids demo</h1>
7474
<div class="row">
7575
<div class="col-md-6">
7676
<a class="btn btn-primary" id="float1" href="#">float: false</a>
77+
<a class="btn btn-primary" id="compact1" href="#">Compact</a>
7778
<div class="grid-stack grid-stack-6" id="grid1">
7879
</div>
7980
</div>
8081
<div class="col-md-6">
8182
<a class="btn btn-primary" id="float2" href="#">float: true</a>
83+
<a class="btn btn-primary" id="compact2" href="#">Compact</a>
8284
<div class="grid-stack grid-stack-6" id="grid2">
8385
</div>
8486
</div>
@@ -136,13 +138,21 @@ <h1>Two grids demo</h1>
136138
appendTo: 'body',
137139
});
138140

139-
$('#float1').click(function() { toggleFloat('#float1', '#grid1'); });
140-
$('#float2').click(function() { toggleFloat('#float2', '#grid2'); });
141+
$('#float1').click(function() { toggleFloat('#float1', '#grid1') });
142+
$('#float2').click(function() { toggleFloat('#float2', '#grid2') });
141143
function toggleFloat(button, grid) {
142144
var grid = $(grid).data('gridstack');
143145
grid.float(! grid.float());
144146
$(button).html('float: ' + grid.float());
145147
}
148+
149+
$('#compact1').click(function() { compact('#grid1') });
150+
$('#compact2').click(function() { compact('#grid2') });
151+
function compact(grid) {
152+
var grid = $(grid).data('gridstack');
153+
grid.compact();
154+
}
155+
146156
});
147157
</script>
148158
</body>

doc/CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Change log
2828
## v0.5.5-dev (upcoming changes)
2929

3030
- add `float(val)` to set/get the grid float mode [#1088](https://github.com/gridstack/gridstack.js/pull/1088)
31+
- add `compact()` relayout grid items to reclaim any empty space [#1101](https://github.com/gridstack/gridstack.js/pull/1101)
3132
- Allow percentage as a valid unit for height [#1093](https://github.com/gridstack/gridstack.js/pull/1093)
3233
- fixed callbacks to get either `added, removed, change` or combination if adding a node require also to change its (x,y) for example.
3334
Also you can now call `batchUpdate()` before calling a bunch of `addWidget()` and get a single event callback (more efficient).

doc/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ gridstack.js API
2323
- [addWidget(el, [options])](#addwidgetel-options)
2424
- [addWidget(el, [x, y, width, height, autoPosition, minWidth, maxWidth, minHeight, maxHeight, id])](#addwidgetel-x-y-width-height-autoposition-minwidth-maxwidth-minheight-maxheight-id)
2525
- [batchUpdate()](#batchupdate)
26+
- [compact()](#compact)
2627
- [cellHeight()](#cellheight)
2728
- [cellHeight(val, noUpdate)](#cellheightval-noupdate)
2829
- [cellWidth()](#cellwidth)
@@ -252,6 +253,10 @@ grid.addWidget(el, 0, 0, 3, 2, true);
252253

253254
starts batch updates. You will see no changes until `commit()` method is called.
254255

256+
### compact()
257+
258+
relayout grid items to reclaim any empty space.
259+
255260
### cellHeight()
256261

257262
Gets current cell height.

spec/gridstack-spec.js

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ describe('gridstack', function() {
276276
});
277277
});
278278

279-
describe('grid.column', function() {
279+
describe('grid.setColumn', function() {
280280
beforeEach(function() {
281281
document.body.insertAdjacentHTML('afterbegin', gridstackHTML);
282282
});
@@ -341,7 +341,7 @@ describe('gridstack', function() {
341341
expect(node2.width).toBe(4);
342342
expect(node2.height).toBe(4);
343343

344-
// one column will have item1, item2
344+
// 1 column will have item1, item2
345345
grid.setColumn(1);
346346
node1 = $('#item1').data('_gridstack_node');
347347
node2 = $('#item2').data('_gridstack_node');
@@ -365,6 +365,27 @@ describe('gridstack', function() {
365365
expect(node3.width).toBe(1);
366366
expect(node3.height).toBe(1);
367367

368+
// 2 column will have item1, item2, item3 in 1 column still
369+
grid.setColumn(2);
370+
node1 = $('#item1').data('_gridstack_node');
371+
node2 = $('#item2').data('_gridstack_node');
372+
node3 = $('#item3').data('_gridstack_node');
373+
expect(grid.opts.column).toBe(2);
374+
expect(node1.x).toBe(0);
375+
expect(node1.y).toBe(0);
376+
expect(node1.width).toBe(1);
377+
expect(node1.height).toBe(2);
378+
379+
expect(node2.x).toBe(1);
380+
expect(node2.y).toBe(0);
381+
expect(node2.width).toBe(1);
382+
expect(node2.height).toBe(4);
383+
384+
expect(node3.x).toBe(0);
385+
expect(node3.y).toBe(6);
386+
expect(node3.width).toBe(1); // ??? could stay at 1 or take entire width still ?
387+
expect(node3.height).toBe(1);
388+
368389
// back to 12 column and initial layout (other than new item3)
369390
grid.setColumn(12);
370391
expect(grid.opts.column).toBe(12);
@@ -383,7 +404,7 @@ describe('gridstack', function() {
383404

384405
expect(node3.x).toBe(0);
385406
expect(node3.y).toBe(6);
386-
expect(node3.width).toBe(12); // take entire row still
407+
expect(node3.width).toBe(6); // ??? could 6 or taken entire width if it did above
387408
expect(node3.height).toBe(1);
388409
});
389410
});
@@ -1251,4 +1272,38 @@ describe('gridstack', function() {
12511272
}
12521273
});
12531274
});
1275+
1276+
describe('grid.compact', function() {
1277+
beforeEach(function() {
1278+
document.body.insertAdjacentHTML('afterbegin', gridstackHTML);
1279+
});
1280+
afterEach(function() {
1281+
document.body.removeChild(document.getElementById('gs-cont'));
1282+
});
1283+
it('should move all 3 items to top-left with no space', function() {
1284+
$('.grid-stack').gridstack({float: true});
1285+
var grid = $('.grid-stack').data('gridstack');
1286+
1287+
var el3 = grid.addWidget(widgetHTML, {x: 3, y: 5});
1288+
expect(parseInt(el3.attr('data-gs-x'))).toBe(3);
1289+
expect(parseInt(el3.attr('data-gs-y'))).toBe(5);
1290+
1291+
grid.compact();
1292+
expect(parseInt(el3.attr('data-gs-x'))).toBe(8);
1293+
expect(parseInt(el3.attr('data-gs-y'))).toBe(0);
1294+
});
1295+
it('not move locked item', function() {
1296+
$('.grid-stack').gridstack({float: true});
1297+
var grid = $('.grid-stack').data('gridstack');
1298+
1299+
var el3 = grid.addWidget(widgetHTML, {x: 3, y: 5, locked: true, noMove: true});
1300+
expect(parseInt(el3.attr('data-gs-x'))).toBe(3);
1301+
expect(parseInt(el3.attr('data-gs-y'))).toBe(5);
1302+
1303+
grid.compact();
1304+
expect(parseInt(el3.attr('data-gs-x'))).toBe(3);
1305+
expect(parseInt(el3.attr('data-gs-y'))).toBe(5);
1306+
});
1307+
1308+
});
12541309
});

src/gridstack.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ interface GridStack {
9696
*/
9797
commit(): void;
9898

99+
/**
100+
* relayout grid items to reclaim any empty space
101+
*/
102+
compact(): void;
103+
99104
/**
100105
* Destroys a grid instance.
101106
* @param detachGrid if false nodes and grid will not be removed from the DOM (Optional. Default true).

src/gridstack.js

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -469,14 +469,16 @@
469469
};
470470

471471
GridStackEngine.prototype.addNode = function(node, triggerAddEvent) {
472+
var prev = {x: node.x, y: node.y, width: node.width, height: node.height};
473+
472474
node = this._prepareNode(node);
473475

474476
if (node.maxWidth !== undefined) { node.width = Math.min(node.width, node.maxWidth); }
475477
if (node.maxHeight !== undefined) { node.height = Math.min(node.height, node.maxHeight); }
476478
if (node.minWidth !== undefined) { node.width = Math.max(node.width, node.minWidth); }
477479
if (node.minHeight !== undefined) { node.height = Math.max(node.height, node.minHeight); }
478480

479-
node._id = ++idSeq;
481+
node._id = node._id || ++idSeq;
480482
// node._dirty = true; will be addEvent instead, unless it changes below...
481483

482484
if (node.autoPosition) {
@@ -489,10 +491,10 @@
489491
continue;
490492
}
491493
if (!this.nodes.find(Utils._isAddNodeIntercepted, {x: x, y: y, node: node})) {
494+
node._dirty = (node.x !== x || node.y !== y);
492495
node.x = x;
493496
node.y = y;
494497
delete node.autoPosition; // found our slot
495-
node._dirty = (node.x !== x || node.y !== y);
496498
break;
497499
}
498500
}
@@ -502,6 +504,10 @@
502504
if (triggerAddEvent) {
503505
this._addedNodes.push(node);
504506
}
507+
// use single equal as they come as string/undefined but end as number....
508+
if (!node._dirty && (prev.x != node.x || prev.y != node.y || prev.width != node.width || prev.height != node.height)) {
509+
node._dirty = true;
510+
}
505511

506512
this._fixCollisions(node);
507513
this._packNodes();
@@ -1697,6 +1703,24 @@
16971703
});
16981704
};
16991705

1706+
/**
1707+
* relayout grid items to reclaim any empty space
1708+
*/
1709+
GridStack.prototype.compact = function() {
1710+
if (this.grid.nodes.length === 0) { return; }
1711+
this.batchUpdate();
1712+
this.grid._sortNodes();
1713+
var nodes = this.grid.nodes;
1714+
this.grid.nodes = []; // pretend we have no nodes to conflict layout to start with...
1715+
nodes.forEach(function(n) {
1716+
if (!n.noMove && !n.locked) {
1717+
n.autoPosition = true;
1718+
}
1719+
this.grid.addNode(n, false); // 'false' for add event trigger
1720+
}, this);
1721+
this.commit();
1722+
};
1723+
17001724
GridStack.prototype.verticalMargin = function(val, noUpdate) {
17011725
if (val === undefined) {
17021726
return this.opts.verticalMargin;
@@ -1809,44 +1833,67 @@
18091833
//
18101834
// now update the nodes positions, using the original ones with new ratio
18111835
//
1836+
18121837
if (doNotPropagate === true || this.grid.nodes.length === 0) { return; }
18131838
var nodes = Utils.sort(this.grid.nodes, -1, oldColumn); // current column reverse sorting so we can insert last to front (limit collision)
18141839

18151840
// cache the current layout in case they want to go back (like 12 -> 1 -> 12) as it requires original data
18161841
var copy = [nodes.length];
1817-
nodes.forEach(function(n, i) {copy[i] = Utils.clone(n)}); // clone to preserve _id that gets reset during removal, and changing x,y,w,h live objects
1818-
this.grid._layouts = this.grid._layouts || {};
1842+
nodes.forEach(function(n, i) {copy[i] = {x: n.x, y: n.y, width: n.width, _id: n._id}}); // only thing we use change is x,y,w and need id to find it back
1843+
this.grid._layouts = this.grid._layouts || []; // use array to find larger quick
18191844
this.grid._layouts[oldColumn] = copy;
18201845

1821-
// see if we have cached prev values and if so re-use those nodes that are still current...
1822-
var newNodes = [];
1846+
// see if we have cached previous layout. if NOT and we are going up in size (up-sampling) start with the largest layout we have (down-sampling) instead
1847+
var lastIndex = this.grid._layouts.length - 1;
18231848
var cacheNodes = this.grid._layouts[column] || [];
1849+
if (cacheNodes.length === 0 && column > oldColumn && lastIndex > column) {
1850+
cacheNodes = this.grid._layouts[lastIndex] || [];
1851+
if (cacheNodes.length) {
1852+
// pretend we came from that larger column by assigning those values at starting point)
1853+
oldColumn = lastIndex;
1854+
cacheNodes.forEach(function(cacheNode) {
1855+
var j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id});
1856+
if (j !== -1) {
1857+
// still current, use cache info positions
1858+
nodes[j].x = cacheNode.x;
1859+
nodes[j].y = cacheNode.y;
1860+
nodes[j].width = cacheNode.width;
1861+
}
1862+
});
1863+
cacheNodes = []; // we still don't have new column cached data... will generate from larger one.
1864+
}
1865+
}
1866+
1867+
// if we found cache re-use those nodes that are still current
1868+
var newNodes = [];
18241869
cacheNodes.forEach(function(cacheNode) {
18251870
var j = nodes.findIndex(function(n) {return n && n._id === cacheNode._id});
18261871
if (j !== -1) {
1827-
newNodes.push(cacheNode); // still current, use cache info
1828-
nodes[j] = null;
1872+
// still current, use cache info positions
1873+
nodes[j].x = cacheNode.x;
1874+
nodes[j].y = cacheNode.y;
1875+
nodes[j].width = cacheNode.width;
1876+
newNodes.push(nodes[j]);
1877+
nodes[j] = null; // erase it so we know what's left
18291878
}
18301879
});
18311880
// ...and add any extra non-cached ones
18321881
var ratio = column / oldColumn;
18331882
nodes.forEach(function(node) {
18341883
if (!node) return;
1835-
newNodes.push($.extend({}, node, {x: Math.round(node.x * ratio), width: Math.round(node.width * ratio) || 1}));
1884+
node.x = Math.round(node.x * ratio);
1885+
node.width = Math.round(node.width * ratio) || 1;
1886+
newNodes.push(node);
18361887
});
18371888
newNodes = Utils.sort(newNodes, -1, column);
18381889

1839-
// now temporary remove the existing gs info and add them from last to make sure we insert them where needed
1840-
// (batch mode will set float=true so we can position anywhere and do gravity relayout after)
1890+
// finally relayout them in reverse order (to get correct placement)
18411891
this.batchUpdate();
1842-
this.grid.removeAll(false); // 'false' = leave DOm elements behind
1892+
this.grid.nodes = []; // pretend we have no nodes to start with (we use same structures) to simplify layout
18431893
newNodes.forEach(function(node) {
1844-
var newNode = this.addWidget(node.el, node).data('_gridstack_node');
1845-
newNode._id = node._id; // keep same ID so we can re-use caches
1846-
newNode._dirty = true;
1894+
this.grid.addNode(node, false); // 'false' for add event trigger
1895+
node._dirty = true; // force attr update
18471896
}, this);
1848-
this.grid._removedNodes = []; // prevent add/remove from being called (kept DOM) only change event
1849-
this.grid._addedNodes = [];
18501897
this.commit();
18511898
};
18521899

0 commit comments

Comments
 (0)