Skip to content

Commit

Permalink
Merge pull request #374 from marmelab/multi_select_list
Browse files Browse the repository at this point in the history
[RFR] Add optional selection column to list
  • Loading branch information
fzaninotto committed Apr 1, 2015
2 parents 53f914a + 13fe5a3 commit c04b5ca
Show file tree
Hide file tree
Showing 27 changed files with 661 additions and 8 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,24 @@ Alternately, if you pass a string, it is compiled just like an Angular template,
'<my-custom-directive entry="entry"></my-custom-directive>';
listView.listActions(template);

* `selectable(boolean)`
Enable the selection column (an initial column made of checkboxes) on the list.

Once the user selects lines, a button appears and displays the number of selected entries. A click on this button reveals the list of "batch actions", i.e. actions that can be performed on a selection of entries. By default, the only batch action available is a batch delete.

* `batchActions(String|Array)`
Add you own batch action directives.

The scope contains a `selection` variable which contains the current selection:

listView.batchActions(['select', '<my-custom-directive selection="selection"></my-custom-directive>'])

*Tip*: This `selection` variable is also in the scope of the main view actions.

```js
listView.actions('create', '<my-custom-directive selection="selection"></my-custom-directive>');
```

## Fields

A field is the representation of a property of an entity.
Expand Down
5 changes: 5 additions & 0 deletions src/javascripts/ng-admin/Crud/CrudModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ define(function (require) {
CrudModule.controller('ShowController', require('ng-admin/Crud/show/ShowController'));
CrudModule.controller('FormController', require('ng-admin/Crud/form/FormController'));
CrudModule.controller('DeleteController', require('ng-admin/Crud/delete/DeleteController'));
CrudModule.controller('BatchDeleteController', require('ng-admin/Crud/delete/BatchDeleteController'));

CrudModule.service('PromisesResolver', require('ng-admin/Crud/misc/PromisesResolver'));
CrudModule.service('RetrieveQueries', require('ng-admin/Crud/repository/RetrieveQueries'));
Expand Down Expand Up @@ -52,6 +53,8 @@ define(function (require) {
CrudModule.directive('maDatagrid', require('ng-admin/Crud/list/maDatagrid'));
CrudModule.directive('maDatagridPagination', require('ng-admin/Crud/list/maDatagridPagination'));
CrudModule.directive('maDatagridInfinitePagination', require('ng-admin/Crud/list/maDatagridInfinitePagination'));
CrudModule.directive('maDatagridItemSelector', require('ng-admin/Crud/list/maDatagridItemSelector'));
CrudModule.directive('maDatagridMultiSelector', require('ng-admin/Crud/list/maDatagridMultiSelector'));
CrudModule.directive('maFilter', require('ng-admin/Crud/filter/maFilter'));

CrudModule.directive('maColumn', require('ng-admin/Crud/column/maColumn'));
Expand All @@ -73,8 +76,10 @@ define(function (require) {
CrudModule.directive('maShowButton', require('ng-admin/Crud/button/maShowButton'));
CrudModule.directive('maListButton', require('ng-admin/Crud/button/maListButton'));
CrudModule.directive('maDeleteButton', require('ng-admin/Crud/button/maDeleteButton'));
CrudModule.directive('maBatchDeleteButton', require('ng-admin/Crud/button/maBatchDeleteButton'));

CrudModule.directive('maViewActions', require('ng-admin/Crud/misc/ViewActions'));
CrudModule.directive('maViewBatchActions', require('ng-admin/Crud/misc/ViewBatchActions'));
CrudModule.directive('compile', require('ng-admin/Crud/misc/Compile'));

CrudModule.config(require('ng-admin/Crud/routing'));
Expand Down
33 changes: 33 additions & 0 deletions src/javascripts/ng-admin/Crud/button/maBatchDeleteButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*global define*/

define(function () {
'use strict';

function maBatchDeleteButtonDirective($state) {
return {
restrict: 'E',
scope: {
'entity': '&',
'selection': '&',
},
link: function ($scope) {
$scope.gotoBatchDelete = function () {
var entity = $scope.entity();
var ids = $scope.selection().map(function(entry) {
return entry.identifierValue
});
$state.go('batchDelete', { ids: ids, entity: entity.name() });
};
},
template:
'<span ng-click="gotoBatchDelete()">' +
'<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>&nbsp;Delete' +
'</span>'

};
}

maBatchDeleteButtonDirective.$inject = ['$state'];

return maBatchDeleteButtonDirective;
});
63 changes: 63 additions & 0 deletions src/javascripts/ng-admin/Crud/delete/BatchDeleteController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*global define*/

define(function () {
'use strict';

var BatchDeleteController = function ($scope, $state, $stateParams, $filter, $location, $window, DeleteQueries, notification, view) {
this.$scope = $scope;
this.$state = $state;
this.$stateParams = $stateParams;
this.$filter = $filter;
this.$location = $location;
this.$window = $window;
this.DeleteQueries = DeleteQueries;
this.notification = notification;
this.view = view;
this.entity = view.getEntity();
this.entityIds = $stateParams.ids;
this.selection = []; // fixme: query db to get selection
this.title = view.title();
this.description = view.description();
this.actions = view.actions();
this.loadingPage = false;
this.fields = this.$filter('orderElement')(view.fields());

$scope.$on('$destroy', this.destroy.bind(this));
};

BatchDeleteController.prototype.batchDelete = function () {
var notification = this.notification,
$state = this.$state,
entityName = this.entity.name();

this.DeleteQueries.batchDelete(this.view, this.entityIds).then(function () {
$state.go($state.get('list'), { 'entity': entityName });
}, function (response) {
// @TODO: share this method when splitting controllers
var body = response.data;
if (typeof body === 'object') {
body = JSON.stringify(body);
}

notification.log('Oops, an error occured : (code: ' + response.status + ') ' + body, {addnCls: 'humane-flatty-error'});
});
};

BatchDeleteController.prototype.back = function () {
this.$window.history.back();
};

BatchDeleteController.prototype.destroy = function () {
this.$scope = undefined;
this.$state = undefined;
this.$stateParams = undefined;
this.$filter = undefined;
this.$location = undefined;
this.$window = undefined;
this.DeleteQueries = undefined;
};

BatchDeleteController.$inject = ['$scope', '$state', '$stateParams', '$filter', '$location', '$window', 'DeleteQueries', 'notification', 'view'];

return BatchDeleteController;
});
31 changes: 31 additions & 0 deletions src/javascripts/ng-admin/Crud/delete/batchDelete.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<div class="row list-header">
<div class="col-lg-12">
<ma-view-actions override="batchDeleteController.actions" selection="batchDeleteController.selection" entity="batchDeleteController.entity">
<ma-list-button entity="entity"></ma-list-button>
</ma-view-actions>

<div class="page-header">
<h1 compile="batchDeleteController.title">
Delete {{ batchDeleteController.entityIds.length }} {{ batchDeleteController.view.entity.name() | humanize | pluralize }}
</h1>
</div>
</div>
</div>

<div class="row">
<div class="col-lg-12">
<p>Are you sure ?</p>
<button class="btn btn-danger" ng-click="batchDeleteController.batchDelete()">Yes</button>
<button class="btn btn-default" ng-click="batchDeleteController.back()">No</button>
</div>
</div>

<div ng-if="selection" class="row list-view" ng-class="'ng-admin-entity-' + batchDeleteController.entity.name()">
<div class="col-lg-12">
<ma-datagrid name="{{ batchDeleteController.view.name() }}"
entries="batchDeleteController.selection"
fields="::batchDeleteController.fields"
entity="::batchDeleteController.entity">
</ma-datagrid>
</div>
</div>
6 changes: 6 additions & 0 deletions src/javascripts/ng-admin/Crud/list/Datagrid.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<table class="grid table table-condensed table-hover table-striped">
<thead>
<tr>
<th ng-if="selection">
<ma-datagrid-multi-selector toggle-select-all="toggleSelectAll()" selection="selection" entries="entries"/>
</th>
<th ng-repeat="field in fields() track by $index" ng-class="'ng-admin-column-' + field.name()">
<a ng-click="datagrid.sort(field)">
<span class="glyphicon {{ datagrid.sortDir === 'DESC' ? 'glyphicon-chevron-down': 'glyphicon-chevron-up' }}" ng-if="datagrid.isSorting(field)"></span>
Expand All @@ -16,6 +19,9 @@

<tbody>
<tr ng-repeat="entry in entries track by $index">
<td ng-if="selection">
<ma-datagrid-item-selector toggle-select="toggleSelect(entry)" selection="selection" entry="entry"/>
</td>
<td ng-repeat="field in fields() track by $index" ng-class="field.getCssClasses(entry)">
<ma-column field="::field" entry="::entry" entity="::entity"></ma-column>
</td>
Expand Down
26 changes: 26 additions & 0 deletions src/javascripts/ng-admin/Crud/list/DatagridController.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ define(function () {
this.$anchorScroll = $anchorScroll;
this.filters = {};

$scope.toggleSelect = this.toggleSelect.bind(this);
$scope.toggleSelectAll = this.toggleSelectAll.bind(this);

this.$scope.gotoDetail = this.gotoDetail.bind(this);

var searchParams = this.$location.search();
Expand Down Expand Up @@ -94,6 +97,29 @@ define(function () {
return this.$scope.name + '.' + field.name();
};

DatagridController.prototype.toggleSelect = function (entry) {
var selection = this.$scope.selection.slice();

var index = selection.indexOf(entry);

if (index === -1) {
this.$scope.selection = selection.concat(entry);
return;
}
selection.splice(index, 1);
this.$scope.selection = selection;
};

DatagridController.prototype.toggleSelectAll = function () {

if (this.$scope.selection.length < this.$scope.entries.length) {
this.$scope.selection = this.$scope.entries;
return;
}

this.$scope.selection = [];
};

DatagridController.$inject = ['$scope', '$location', '$anchorScroll'];

return DatagridController;
Expand Down
2 changes: 2 additions & 0 deletions src/javascripts/ng-admin/Crud/list/ListController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ define(function () {
this.title = view.title();
this.description = view.description();
this.actions = view.actions();
this.batchActions = view.batchActions();
this.loadingPage = false;
this.filters = this.$filter('orderElement')(view.filters());
this.hasFilters = Object.keys(this.filters).length > 0;
Expand All @@ -27,6 +28,7 @@ define(function () {
this.infinitePagination = this.view.infinitePagination();
this.nextPageCallback = this.nextPage.bind(this);
this.setPageCallback = this.setPage.bind(this);
this.selection = this.batchActions.length ? [] : null;

$scope.$on('$destroy', this.destroy.bind(this));
};
Expand Down
4 changes: 3 additions & 1 deletion src/javascripts/ng-admin/Crud/list/list.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="row list-header">
<div class="col-lg-12">
<ma-view-actions override="listController.actions" entity="listController.entity">
<ma-view-actions override="listController.actions" selection="listController.selection" batch-buttons="listController.batchActions" entity="listController.entity">
<ma-view-batch-actions buttons="batchButtons()" selection="selection" entity="entity"></ma-view-batch-actions>
<ma-create-button ng-if="!entity.isReadOnly" entity="entity"></ma-create-button>
</ma-view-actions>

Expand All @@ -19,6 +20,7 @@ <h1 compile="listController.title">
<div class="col-lg-12">
<ma-datagrid name="{{ listController.view.name() }}"
entries="listController.entries"
selection="listController.selection"
fields="::listController.fields"
list-actions="::listController.listActions"
entity="::listController.entity">
Expand Down
1 change: 1 addition & 0 deletions src/javascripts/ng-admin/Crud/list/maDatagrid.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ define(function (require) {
scope: {
name: '@',
entries: '=',
selection: '=',
fields: '&',
listActions: '&',
entity: '&'
Expand Down
26 changes: 26 additions & 0 deletions src/javascripts/ng-admin/Crud/list/maDatagridItemSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*global define*/

define(function () {
'use strict';

function DatagridItemSelectorDirective() {
return {
restrict: 'E',
scope: {
entry: '=',
selection: '=',
toggleSelect: '&'
},
template: '<input type="checkbox" ng-click="toggle(entry)" ng-checked="selection.indexOf(entry) !== -1"/>',
link: function (scope) {
scope.toggle = function (entry) {
scope.toggleSelect({entry: entry});
};
}
};
}

DatagridItemSelectorDirective.$inject = [];

return DatagridItemSelectorDirective;
});
29 changes: 29 additions & 0 deletions src/javascripts/ng-admin/Crud/list/maDatagridMultiSelector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*global define*/

define(function () {
'use strict';

function DatagridMultiSelectorDirective() {
return {
restrict: 'E',
scope: {
entries: '=',
selection: '=',
toggleSelectAll: '&'
},
template: '<input type="checkbox" ng-click="toggleSelectAll()" ng-checked="selection.length == entries.length" />',
link: function (scope, element) {
scope.$watch('selection', function (selection) {
element.children()[0].indeterminate = selection.length > 0 && selection.length != scope.entries.length;
});
scope.$watch('entries', function (entries) {
element.children()[0].indeterminate = scope.selection.length > 0 && scope.selection.length != entries.length;
});
}
};
}

DatagridMultiSelectorDirective.$inject = [];

return DatagridMultiSelectorDirective;
});
4 changes: 3 additions & 1 deletion src/javascripts/ng-admin/Crud/misc/ViewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ define(function (require) {
scope: {
'override': '&',
'entry': '=',
'entity': '='
'entity': '=',
'selection': '=',
batchButtons: '&'
},
template: viewActionsTemplate,
link: function($scope, element, attrs, controller, transcludeFn) {
Expand Down
43 changes: 43 additions & 0 deletions src/javascripts/ng-admin/Crud/misc/ViewBatchActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*global define*/

define(function (require) {
'use strict';

var viewActionsTemplate = require('text!./view-batch-actions.html');

function ViewBatchActionsDirective($injector) {
var $compile = $injector.get('$compile');

return {
restrict: 'E',
transclude: true,
scope: {
'entity': '=',
'selection': '=',
'buttons': '&'
},
template: viewActionsTemplate,
link: function(scope) {

scope.isopen = false;

scope.toggleDropdown = function($event) {
$event.preventDefault();
$event.stopPropagation();
scope.isopen = !scope.isopen;
};

scope.buttons = scope.buttons();
if (typeof scope.buttons === 'string') {
scope.customTemplate = scope.buttons;
scope.buttons = null;
}

}
};
}

ViewBatchActionsDirective.$inject = ['$injector'];

return ViewBatchActionsDirective;
});
Loading

0 comments on commit c04b5ca

Please sign in to comment.