Skip to content

Commit

Permalink
Merge pull request #1545 from yayasoft/3.0-subgrid-final
Browse files Browse the repository at this point in the history
subgrid feature
  • Loading branch information
swalters committed Sep 18, 2014
2 parents 1fb89a7 + 7369575 commit eccd70e
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 22 deletions.
91 changes: 91 additions & 0 deletions misc/tutorial/306_expandable_grid.ngdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@ngdoc overview
@name Tutorial: 306 Expandable grid
@description

Module 'ui.grid.expandable' adds the subgrid feature to grid. To show the subgrid you need to provide following grid option:

<pre>
$scope.gridOptions.expandable = {
//This is the template that will be used to render subgrid.
rowExpandableTemplate: 'rowExpandableTemplate.html',
//This will be the height of the subgrid
expandableRowHeight: 140
}
</pre>

rowExpandableTemplate will be template for subgrid and expandableRowHeight will be height of the subgrid. The grid api
provided following events and methods fos subGrids:

<pre>
//rowExpandedStateChanged is fired for each row as its expanded:
gridApi.expandable.on.rowExpandedStateChanged($scope,function(row){
});
//Following methods can be used to expand/ collapse all rows of the grid:
gridApi.expandable.expandAllRows();
gridApi.expandable.collapseAllRows();
</pre>

SubGrid nesting can be done upto multiple levels.

@example
<example module="app">
<file name="app.js">
var app = angular.module('app', ['ui.grid', 'ui.grid.expandable']);

app.controller('MainCtrl', ['$scope', '$http', '$log', function ($scope, $http, $log) {
$scope.gridOptions = {};
$scope.gridOptions.expandable = {
rowExpandableTemplate: 'rowExpandableTemplate.html',
expandableRowHeight: 150
}

$scope.gridOptions.columnDefs = [
{ name: 'id'},
{ name: 'name'},
{ name: 'age'},
{ name: 'address.city'}
];

$http.get('/data/500_complex.json')
.success(function(data) {
for(i = 0; i < data.length; i++){
data[i].subGridOptions = {
columnDefs: [ {name:"Id", field:"id"},{name:"Name", field:"name"} ],
data: data[i].friends
}
}
$scope.gridOptions.data = data;
});

$scope.gridOptions.onRegisterApi = function(gridApi){
$scope.gridApi = gridApi;
};

$scope.expandAllRows = function() {
$scope.gridApi.expandable.expandAllRows();
}

$scope.collapseAllRows = function() {
$scope.gridApi.expandable.collapseAllRows();
}
}]);
</file>
<file name="index.html">
<div ng-controller="MainCtrl">
<div class="control-group">
<input type="button" class="btn btn-small" ng-click="expandAllRows()" value="Expand All"/>
<input type="button" class="btn btn-small" ng-click="collapseAllRows()" value="Collapse All"/>
</div>
<div ui-grid="gridOptions" ui-grid-pinning ui-grid-expandable class="grid"></div>
</div>
</file>
<file name="main.css">
.grid {
width: 100%;
height: 400px;
}
</file>
<file name="rowExpandableTemplate.html">
<div ui-grid="row.entity.subGridOptions" style="height:140px;"></div>
</file>
</example>
172 changes: 172 additions & 0 deletions src/features/expandable/js/expandable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
(function () {
'use strict';

var module = angular.module('ui.grid.expandable', ['ui.grid']);

module.service('uiGridExpandableService', ['gridUtil', '$log', '$compile', function (gridUtil, $log, $compile) {
var service = {
initializeGrid: function (grid) {
var publicApi = {
events: {
expandable: {
rowExpandedStateChanged: function (scope, row) {
}
}
},
methods: {
expandable: {
toggleRowExpansion: function (rowEntity) {
var row = grid.getRow(rowEntity);
if (row !== null) {
service.toggleRowExpansion(grid, row);
}
},
expandAllRows: function() {
service.expandAllRows(grid);
},
collapseAllRows: function() {
service.collapseAllRows(grid);
}
}
}
};
grid.api.registerEventsFromObject(publicApi.events);
grid.api.registerMethodsFromObject(publicApi.methods);
},
toggleRowExpansion: function (grid, row) {
row.isExpanded = !row.isExpanded;

if (row.isExpanded) {
row.height = row.grid.options.rowHeight + grid.options.expandable.expandableRowHeight;
}
else {
row.height = row.grid.options.rowHeight;
}

grid.api.expandable.raise.rowExpandedStateChanged(row);
},
expandAllRows: function(grid, $scope) {
angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
if (!row.isExpanded) {
service.toggleRowExpansion(grid, row);
}
});
grid.refresh();
},
collapseAllRows: function(grid) {
angular.forEach(grid.renderContainers.body.visibleRowCache, function(row) {
if (row.isExpanded) {
service.toggleRowExpansion(grid, row);
}
});
grid.refresh();
}
};
return service;
}]);

module.directive('uiGridExpandable', ['$log', 'uiGridExpandableService', '$templateCache',
function ($log, uiGridExpandableService, $templateCache) {
return {
replace: true,
priority: 0,
require: '^uiGrid',
scope: false,
compile: function () {
return {
pre: function ($scope, $elm, $attrs, uiGridCtrl) {
var expandableRowHeaderColDef = {name: 'expandableButtons', width: 40};
expandableRowHeaderColDef.cellTemplate = $templateCache.get('ui-grid/expandableRowHeader');
uiGridCtrl.grid.addRowHeaderColumn(expandableRowHeaderColDef);
uiGridExpandableService.initializeGrid(uiGridCtrl.grid);
},
post: function ($scope, $elm, $attrs, uiGridCtrl) {
}
};
}
};
}]);

module.directive('uiGridExpandableRow',
['uiGridExpandableService', '$timeout', '$log', '$compile', 'uiGridConstants','gridUtil','$interval',
function (uiGridExpandableService, $timeout, $log, $compile, uiGridConstants, gridUtil, $interval) {

return {
replace: false,
priority: 0,
scope: false,

compile: function () {
return {
pre: function ($scope, $elm, $attrs, uiGridCtrl) {
gridUtil.getTemplate($scope.grid.options.expandable.rowExpandableTemplate).then(
function (template) {
var expandedRowElement = $compile(template)($scope);
$elm.append(expandedRowElement);
$scope.row.expandedRendered = true;
});
},

post: function ($scope, $elm, $attrs, uiGridCtrl) {
$scope.$on('$destroy', function() {
$scope.row.expandedRendered = false;
});
}
};
}
};
}]);

module.directive('uiGridRow',
['$compile', '$log', '$templateCache',
function ($compile, $log, $templateCache) {
return {
priority: -200,
scope: false,
compile: function ($elm, $attrs) {
return {
pre: function ($scope, $elm, $attrs, controllers) {

$scope.expandableRow = {};

$scope.expandableRow.shouldRenderExpand = function () {
var ret = $scope.colContainer.name === 'body' && $scope.row.isExpanded && (!$scope.grid.isScrollingVertically || $scope.row.expandedRendered);
return ret;
};

$scope.expandableRow.shouldRenderFiller = function () {
var ret = $scope.row.isExpanded && ( $scope.colContainer.name !== 'body' || ($scope.grid.isScrollingVertically && !$scope.row.expandedRendered));
return ret;
};

},
post: function ($scope, $elm, $attrs, controllers) {
}
};
}
};
}]);

module.directive('uiGridViewport',
['$compile', '$log', '$templateCache',
function ($compile, $log, $templateCache) {
return {
priority: -200,
scope: false,
compile: function ($elm, $attrs) {
var rowRepeatDiv = angular.element($elm.children().children()[0]);
var expandedRowFillerElement = $templateCache.get('ui-grid/expandableScrollFiller');
var expandedRowElement = $templateCache.get('ui-grid/expandableRow');
rowRepeatDiv.append(expandedRowElement);
rowRepeatDiv.append(expandedRowFillerElement);
return {
pre: function ($scope, $elm, $attrs, controllers) {
},
post: function ($scope, $elm, $attrs, controllers) {
}
};
}
};
}]);

})();
20 changes: 20 additions & 0 deletions src/features/expandable/less/expandable.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
@import '../../../less/variables';

.expandableRow {
.ui-grid-row {

&:nth-child(odd) .ui-grid-cell {
background-color: @rowColorOdd;
}

&:nth-child(even) .ui-grid-cell {
background-color: @rowColorEven;
}

}


}

.uiGridExpandableButtonsCell {
}
4 changes: 4 additions & 0 deletions src/features/expandable/templates/expandableRow.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<div ui-grid-expandable-row ng-if="expandableRow.shouldRenderExpand()" class="expandableRow"
style="float:left;margin-top: 1px;margin-bottom: 1px;"
ng-style="{width: (grid.renderContainers.body.getCanvasWidth() - grid.verticalScrollbarWidth)
,height: grid.options.expandable.expandableRowHeight}"></div>
6 changes: 6 additions & 0 deletions src/features/expandable/templates/expandableRowHeader.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="ui-grid-row-header-cell uiGridExpandableButtonsCell">
<div class="ui-grid-cell-contents">
<i ng-class="{'ui-grid-icon-plus-squared':!row.isExpanded, 'ui-grid-icon-minus-squared':row.isExpanded}"
ng-click="grid.api.expandable.toggleRowExpansion(row.entity)"></i>
</div>
</div>
9 changes: 9 additions & 0 deletions src/features/expandable/templates/expandableScrollFiller.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div ng-if="expandableRow.shouldRenderFiller()"
style="float:left;margin-top: 2px;margin-bottom: 2px"
ng-style="{width: (grid.getViewportWidth())
,height: grid.options.expandable.expandableRowHeight, 'margin-left': grid.options.rowHeader.rowHeaderWidth}"
>
<i class="ui-grid-icon-spin5 ui-grid-animate-spin" ng-style=
"{'margin-top': ( grid.options.expandable.expandableRowHeight/2 - 5),
'margin-left':((grid.getViewportWidth() - grid.options.rowHeader.rowHeaderWidth)/2 - 5)}">
</i></div>
72 changes: 72 additions & 0 deletions src/features/expandable/test/expandable.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
describe('ui.grid.expandable', function () {

var scope, element, timeout;

beforeEach(module('ui.grid.expandable'));

beforeEach(inject(function (_$compile_, $rootScope, $timeout, $httpBackend) {

var $compile = _$compile_;
scope = $rootScope;
timeout = $timeout;

scope.gridOptions = {};
scope.gridOptions.data = [
{ col1: 'col1', col2: 'col2' }
];
scope.gridOptions.expandable = {
rowExpandableTemplate: 'rowExpandableTemplate.html',
expandableRowHeight: 150
};
scope.gridOptions.onRegisterApi = function (gridApi) {
scope.gridApi = gridApi;
scope.grid = gridApi.grid;
};

$httpBackend.when('GET', 'rowExpandableTemplate.html').respond("<div class='test'></div>");
element = angular.element('<div class="col-md-5" ui-grid="gridOptions" ui-grid-expandable></div>');

$timeout(function () {
$compile(element)(scope);
});
$timeout.flush();
}));

it('public api expandable should be well defined', function () {
expect(scope.gridApi.expandable).toBeDefined();
expect(scope.gridApi.expandable.on.rowExpandedStateChanged).toBeDefined();
expect(scope.gridApi.expandable.raise.rowExpandedStateChanged).toBeDefined();
expect(scope.gridApi.expandable.toggleRowExpansion).toBeDefined();
expect(scope.gridApi.expandable.expandAllRows).toBeDefined();
expect(scope.gridApi.expandable.collapseAllRows).toBeDefined();
});

it('expandAll and collapseAll should set and unset row.isExpanded', function () {
scope.gridApi.expandable.expandAllRows();
scope.grid.rows.forEach(function(row) {
expect(row.isExpanded).toBe(true);
});
scope.gridApi.expandable.collapseAllRows();
scope.grid.rows.forEach(function(row) {
expect(row.isExpanded).toBe(false);
});
});

it('event rowExpandedStateChanged should be fired whenever row expands', function () {
var functionCalled = false;
scope.gridApi.expandable.on.rowExpandedStateChanged(scope,function(row){
functionCalled = true;
});
scope.gridApi.expandable.toggleRowExpansion(scope.grid.rows[0].entity);
expect(functionCalled).toBe(true);
});

it('subgrid should be addeed to the dom when we expand row', function () {
expect(element.find('.test').length).toBe(0);
scope.gridApi.expandable.toggleRowExpansion(scope.grid.rows[0].entity);
scope.$digest();
timeout(function () {
expect(element.find('.test').length).toBe(1);
});
});
});
Loading

0 comments on commit eccd70e

Please sign in to comment.