Skip to content

Dynamic columns support #451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Simply pass your desired options to the gridster directive
```JavaScript
$scope.gridsterOpts = {
columns: 6, // the width of the grid, in columns
dynamicColumns: false, // whether to place into the grid the maximum suitable columns with a minimum width of minWidthToAddANewColumn
minWidthToAddANewColumn: 140, // minimum width within gridster to add a new column when dynamicColumns is true. The predefined margin in between columns and outerMargin (if true) are added to this number in the calculation.
pushing: true, // whether to push other items out of the way on move or resize
floating: true, // whether to automatically float items up so they stack (you can temporarily disable if you are adding unsorted items with ng-repeat)
swapping: false, // whether or not to have items of the same size switch places instead of pushing down if they are the same size
Expand Down Expand Up @@ -221,6 +223,14 @@ scope.$on('gridster-resizable-changed', function(gridster) {
})
```

#### gridster-columns-changed
When the number of columns changes, a 'gridster-columns-changed' event is broadcast on rootScope:

```js
scope.$on('gridster-columns-changed', function(gridster) {
})
```

#### gridster-resized
When the gridster element's size changes, a 'gridster-resized' event is broadcast on rootScope:

Expand Down
5 changes: 4 additions & 1 deletion demo/common/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ input {
font-weight: bold;
float: right;
padding: 10px;
}
}
.controls {
margin-bottom: 5px;
}
5 changes: 1 addition & 4 deletions demo/dashboard/style.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@

.controls {
margin-bottom: 20px;
}
.page-header {
margin-top: 20px;
}
Expand Down Expand Up @@ -41,4 +38,4 @@ form {
}
.gridster {
border: 1px solid #ccc;
}
}
26 changes: 16 additions & 10 deletions demo/main/view.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
<div class="demo-container">
<h2>Standard Items</h2>
<input type="button" ng-click="gridsterOpts.draggable.enabled = !gridsterOpts.draggable.enabled" value="{{gridsterOpts.draggable.enabled ? 'Disable' : 'Enable' }} Draggability" />
<input type="button" ng-click="gridsterOpts.resizable.enabled = !gridsterOpts.resizable.enabled" value="{{gridsterOpts.resizable.enabled ? 'Disable' : 'Enable' }} Resizability" />
<input type="button" ng-click="gridsterOpts.floating = !gridsterOpts.floating" value="{{gridsterOpts.floating ? 'Disable' : 'Enable' }} Floating" />
<input type="button" ng-click="gridsterOpts.pushing = !gridsterOpts.pushing" value="{{gridsterOpts.pushing ? 'Disable' : 'Enable' }} Pushing" />
<input type="button" ng-click="gridsterOpts.swapping = !gridsterOpts.swapping" value="{{gridsterOpts.swapping ? 'Disable' : 'Enable' }} Swapping" />

Margins:
<input type="text" ng-model="gridsterOpts.margins[0]" size="3" />
x
<input type="text" ng-model="gridsterOpts.margins[1]" size="3" />
<div class="controls">
<input type="button" ng-click="gridsterOpts.draggable.enabled = !gridsterOpts.draggable.enabled" value="{{gridsterOpts.draggable.enabled ? 'Disable' : 'Enable' }} Draggability" />
<input type="button" ng-click="gridsterOpts.resizable.enabled = !gridsterOpts.resizable.enabled" value="{{gridsterOpts.resizable.enabled ? 'Disable' : 'Enable' }} Resizability" />
<input type="button" ng-click="gridsterOpts.floating = !gridsterOpts.floating" value="{{gridsterOpts.floating ? 'Disable' : 'Enable' }} Floating" />
<input type="button" ng-click="gridsterOpts.pushing = !gridsterOpts.pushing" value="{{gridsterOpts.pushing ? 'Disable' : 'Enable' }} Pushing" />
<input type="button" ng-click="gridsterOpts.swapping = !gridsterOpts.swapping" value="{{gridsterOpts.swapping ? 'Disable' : 'Enable' }} Swapping" />
<input type="button" ng-click="gridsterOpts.dynamicColumns = !gridsterOpts.dynamicColumns" value="{{gridsterOpts.dynamicColumns ? 'Disable' : 'Enable' }} Dynamic Columns" />
</div>

<p>
Margins:
<input type="text" ng-model="gridsterOpts.margins[0]" size="3" />
x
<input type="text" ng-model="gridsterOpts.margins[1]" size="3" />
</p>

<p>
Each item provides its own dimensions and position using the standard fields: { row: row, col: col, sizeX: sizeX, sizeY: sizeY }.
Expand Down Expand Up @@ -85,4 +91,4 @@ <h2>No Configuration or Binding</h2>
<li gridster-item></li>
</ul>
</div>
</div>
</div>
2 changes: 1 addition & 1 deletion dist/angular-gridster.min.js

Large diffs are not rendered by default.

101 changes: 101 additions & 0 deletions src/angular-gridster.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

.constant('gridsterConfig', {
columns: 6, // number of columns in the grid
dynamicColumns: false, // whether to place into the grid the maximum suitable columns
minWidthToAddANewColumn: 140, // minimum width within gridster to add a new column when dynamicColumns is true. The predefined margin in between columns and outerMargin (if true) are added to this number in the calculation.
pushing: true, // whether to push other items out of the way
floating: true, // whether to automatically float items up so they stack
swapping: false, // whether or not to have items switch places instead of push down if they are the same size
Expand Down Expand Up @@ -577,6 +579,54 @@
this.gridHeight = this.maxRows - maxHeight > 0 ? Math.min(this.maxRows, maxHeight) : Math.max(this.maxRows, maxHeight);
};

/**
* Sets all the items in the first suitable position after a change in the number of columns available.
*
*/
this.updateColumnLayout = function() {
var didntFitItems = [];

for (var rowIndex = 0, l = gridster.grid.length; rowIndex < l; ++rowIndex) {
var columns = gridster.grid[rowIndex];
if (!columns) {
continue;
}

for (var colIndex = 0, len = columns.length; colIndex < len; ++colIndex) {
if (columns[colIndex]) {
var item = columns[colIndex];

// If an item was resized it regains its original size to check if it fits now with its original size
if (item.preferredX !== undefined) {
item.sizeX = item.preferredX;
item.sizeY = item.preferredY;
}
if (item.sizeX > gridster.columns) {
// doesn't fit, we store its original size for a later reassignation if possible
item.preferredX = item.sizeX;
item.preferredY = item.sizeY;

item.sizeX = gridster.columns;
item.sizeY = Math.round((item.sizeX * item.sizeY) / item.preferredX); // to mantain original proportion
item.sizeY = item.sizeY > 0 ? item.sizeY : 1;

didntFitItems.push(item);
} else if (item.col + item.sizeX > gridster.columns) {
didntFitItems.push(item);
} else {
gridster.autoSetItemPosition(item);
}
}
}
}

for (var itemlIndex = 0, length = didntFitItems.length; itemlIndex < length; ++itemlIndex) {
gridster.autoSetItemPosition(didntFitItems[itemlIndex]);
}

didntFitItems.length = 0;
};

/**
* Returns the number of rows that will fit in given amount of pixels
*
Expand Down Expand Up @@ -696,6 +746,28 @@
gridster.updateHeight(gridster.movingItem ? gridster.movingItem.sizeY : 0);
});

function calculateColumns() {
if (gridster.dynamicColumns) {
var width = $elem[0].offsetWidth || parseInt($elem.css('width'), 10);

if (!width) {
return;
}

var numberOfMargins = gridster.outerMargin ? 2 : 1;
var minColWidth = (numberOfMargins * gridster.margins[1]) + parseInt(gridster.minWidthToAddANewColumn, 10);
var columns = Math.floor(width / minColWidth);

if (columns < gridster.minColumns) {
columns = gridster.minColumns;
}

if (columns !== gridster.columns) {
gridster.columns = columns;
}
}
}

function refresh(config) {
gridster.setOptions(config);

Expand Down Expand Up @@ -792,6 +864,26 @@
$rootScope.$broadcast('gridster-resizable-changed', gridster);
}, true);

scope.$watch(function() {
return gridster.columns;
}, function() {
$rootScope.$broadcast('gridster-columns-changed', gridster);
});

scope.$watch(function() {
return gridster.dynamicColumns;
}, function() {
calculateColumns();
refresh();
});

scope.$watch(function() {
return gridster.minWidthToAddANewColumn;
}, function() {
calculateColumns();
refresh();
});

var prevWidth = $elem[0].offsetWidth || parseInt($elem.css('width'), 10);

var resize = function() {
Expand All @@ -802,6 +894,8 @@
}
prevWidth = width;

calculateColumns();

if (gridster.loaded) {
$elem.removeClass('gridster-loaded');
}
Expand Down Expand Up @@ -2173,13 +2267,20 @@
};
updateDraggable();

var updateColumnLayout = function() {
if (gridster.dynamicColumns) {
gridster.updateColumnLayout();
}
};

scope.$on('gridster-draggable-changed', updateDraggable);
scope.$on('gridster-resizable-changed', updateResizable);
scope.$on('gridster-resized', updateResizable);
scope.$on('gridster-mobile-changed', function() {
updateResizable();
updateDraggable();
});
scope.$on('gridster-columns-changed', updateColumnLayout);

function whichTransitionEvent() {
var el = document.createElement('div');
Expand Down
29 changes: 29 additions & 0 deletions test/spec/gridster-directive.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,33 @@ describe('gridster directive', function() {
}));
});

it('should broadcast "gridster-columns-changed" on columns change', function() {
// arrange
broadcastOnRootScope.calls.reset();

// act
GridsterCtrl.columns = 10;

$scope.$digest();

// assert
expect(broadcastOnRootScope).toHaveBeenCalledWith('gridster-columns-changed', jasmine.objectContaining({
columns: 10
}));
});

it('number of columns should be different when enabling dynamicColumns and setting minWidthToAddANewColumn to a lower value', function() {
//precondition
expect(GridsterCtrl.columns).toBe(6);

// act
GridsterCtrl.dynamicColumns = true;
GridsterCtrl.minWidthToAddANewColumn = 50;

$scope.$digest();

// assert
expect(GridsterCtrl.columns).not.toBe(6);
});

});
1 change: 1 addition & 0 deletions test/spec/gridster.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ describe('GridsterCtrl', function() {
expect(GridsterCtrl.mobileBreakPoint).toBe(600);
expect(GridsterCtrl.resizable.enabled).toBe(true);
expect(GridsterCtrl.draggable.enabled).toBe(true);
expect(GridsterCtrl.dynamicColumns).toBe(false);
});

// todo: move these to e2e test
Expand Down