diff --git a/README.md b/README.md
index e6bc92cf..6d138d7b 100644
--- a/README.md
+++ b/README.md
@@ -314,6 +314,24 @@ Alternately, if you pass a string, it is compiled just like an Angular template,
'';
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', ''])
+
+*Tip*: This `selection` variable is also in the scope of the main view actions.
+
+```js
+listView.actions('create', '');
+```
+
## Fields
A field is the representation of a property of an entity.
diff --git a/src/javascripts/ng-admin/Crud/CrudModule.js b/src/javascripts/ng-admin/Crud/CrudModule.js
index ee970f3f..29ba0ad6 100644
--- a/src/javascripts/ng-admin/Crud/CrudModule.js
+++ b/src/javascripts/ng-admin/Crud/CrudModule.js
@@ -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'));
@@ -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'));
@@ -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'));
diff --git a/src/javascripts/ng-admin/Crud/button/maBatchDeleteButton.js b/src/javascripts/ng-admin/Crud/button/maBatchDeleteButton.js
new file mode 100644
index 00000000..4ae5fc39
--- /dev/null
+++ b/src/javascripts/ng-admin/Crud/button/maBatchDeleteButton.js
@@ -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:
+'' +
+ ' Delete' +
+''
+
+ };
+ }
+
+ maBatchDeleteButtonDirective.$inject = ['$state'];
+
+ return maBatchDeleteButtonDirective;
+});
diff --git a/src/javascripts/ng-admin/Crud/delete/BatchDeleteController.js b/src/javascripts/ng-admin/Crud/delete/BatchDeleteController.js
new file mode 100644
index 00000000..f0854f64
--- /dev/null
+++ b/src/javascripts/ng-admin/Crud/delete/BatchDeleteController.js
@@ -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;
+});
diff --git a/src/javascripts/ng-admin/Crud/delete/batchDelete.html b/src/javascripts/ng-admin/Crud/delete/batchDelete.html
new file mode 100644
index 00000000..5b2b2859
--- /dev/null
+++ b/src/javascripts/ng-admin/Crud/delete/batchDelete.html
@@ -0,0 +1,31 @@
+
+
+
+
+
Are you sure ?
+
+
+
+
+
+
diff --git a/src/javascripts/ng-admin/Crud/list/Datagrid.html b/src/javascripts/ng-admin/Crud/list/Datagrid.html
index ada1b018..38334b6c 100644
--- a/src/javascripts/ng-admin/Crud/list/Datagrid.html
+++ b/src/javascripts/ng-admin/Crud/list/Datagrid.html
@@ -1,6 +1,9 @@
+
+
+ |
@@ -16,6 +19,9 @@
+
+
+ |
|
diff --git a/src/javascripts/ng-admin/Crud/list/DatagridController.js b/src/javascripts/ng-admin/Crud/list/DatagridController.js
index 94e4c28a..94cdb943 100644
--- a/src/javascripts/ng-admin/Crud/list/DatagridController.js
+++ b/src/javascripts/ng-admin/Crud/list/DatagridController.js
@@ -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();
@@ -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;
diff --git a/src/javascripts/ng-admin/Crud/list/ListController.js b/src/javascripts/ng-admin/Crud/list/ListController.js
index 5fdad2b7..29f1db76 100644
--- a/src/javascripts/ng-admin/Crud/list/ListController.js
+++ b/src/javascripts/ng-admin/Crud/list/ListController.js
@@ -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;
@@ -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));
};
diff --git a/src/javascripts/ng-admin/Crud/list/list.html b/src/javascripts/ng-admin/Crud/list/list.html
index cc3ba465..10412f25 100644
--- a/src/javascripts/ng-admin/Crud/list/list.html
+++ b/src/javascripts/ng-admin/Crud/list/list.html
@@ -1,6 +1,7 @@
|