From 7f9c8b6a380cbc8b0268bb301280095028a4f86b Mon Sep 17 00:00:00 2001 From: Emanuel Schuetze Date: Sat, 5 Sep 2015 16:30:55 +0200 Subject: [PATCH] Agenda item list: new multiselect delete mode Remove action column. --- bower.json | 1 + openslides/agenda/static/js/agenda/agenda.js | 667 ++++++++++-------- .../static/templates/agenda/item-list.html | 119 ++-- .../templates/agenda/slide-item-list.html | 10 +- openslides/core/static/css/app.css | 17 +- openslides/core/static/js/core/core.js | 1 + 6 files changed, 467 insertions(+), 348 deletions(-) diff --git a/bower.json b/bower.json index 20ff31753..dfed8f57a 100644 --- a/bower.json +++ b/bower.json @@ -14,6 +14,7 @@ "angular-loading-bar": "~0.7.1", "angular-ui-router": "~0.2.13", "angular-ui-select": "~0.12", + "angular-ui-switch": "~0.1.0", "angular-ui-tree": "~2.2.0", "angular-gettext": "~2.0.2", "angular-sanitize": "~1.3.15", diff --git a/openslides/agenda/static/js/agenda/agenda.js b/openslides/agenda/static/js/agenda/agenda.js index 36b40a59f..217e3c6da 100644 --- a/openslides/agenda/static/js/agenda/agenda.js +++ b/openslides/agenda/static/js/agenda/agenda.js @@ -2,44 +2,52 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) -.factory('Speaker', ['DS', function(DS) { - return DS.defineResource({ - name: 'agenda/speaker', - relations: { - belongsTo: { - 'users/user': { - localField: 'user', - localKey: 'user_id', +.factory('Speaker', [ + 'DS', + function(DS) { + return DS.defineResource({ + name: 'agenda/speaker', + relations: { + belongsTo: { + 'users/user': { + localField: 'user', + localKey: 'user_id', + } } } - } - }); -}]) + }); + } +]) -.factory('Agenda', ['DS', 'Speaker', 'jsDataModel', function(DS, Speaker, jsDataModel) { - var name = 'agenda/item' - return DS.defineResource({ - name: name, - useClass: jsDataModel, - methods: { - getResourceName: function () { - return name; - } - }, - relations: { - hasMany: { - 'core/tag': { - localField: 'tags', - localKeys: 'tags_id', - }, - 'agenda/speaker': { - localField: 'speakers', - foreignKey: 'item_id', +.factory('Agenda', [ + 'DS', + 'Speaker', + 'jsDataModel', + function(DS, Speaker, jsDataModel) { + var name = 'agenda/item' + return DS.defineResource({ + name: name, + useClass: jsDataModel, + methods: { + getResourceName: function () { + return name; + } + }, + relations: { + hasMany: { + 'core/tag': { + localField: 'tags', + localKeys: 'tags_id', + }, + 'agenda/speaker': { + localField: 'speakers', + foreignKey: 'item_id', + } } } - } - }); -}]) + }); + } +]) // Make sure that the Agenda resource is loaded. .run(['Agenda', function(Agenda) {}]); @@ -60,79 +68,82 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) } ]) -.config(function($stateProvider) { - $stateProvider - .state('agenda', { - url: '/agenda', - abstract: true, - template: "", - }) - .state('agenda.item', { - abstract: true, - template: "", - }) - .state('agenda.item.list', { - resolve: { - items: function(Agenda) { - return Agenda.findAll(); +.config([ + '$stateProvider', + function($stateProvider) { + $stateProvider + .state('agenda', { + url: '/agenda', + abstract: true, + template: "", + }) + .state('agenda.item', { + abstract: true, + template: "", + }) + .state('agenda.item.list', { + resolve: { + items: function(Agenda) { + return Agenda.findAll(); + } } - } - }) - .state('agenda.item.create', { - resolve: { - types: function($http) { - // get all item types - return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' }); + }) + .state('agenda.item.create', { + resolve: { + types: function($http) { + // get all item types + return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' }); + }, + tags: function(Tag) { + return Tag.findAll(); + } + } + }) + .state('agenda.item.detail', { + resolve: { + item: function(Agenda, $stateParams) { + return Agenda.find($stateParams.id); + }, + users: function(User) { + return User.findAll(); + }, + tags: function(Tag) { + return Tag.findAll(); + } + } + }) + .state('agenda.item.detail.update', { + views: { + '@agenda.item': {} }, - tags: function(Tag) { - return Tag.findAll(); + resolve: { + types: function($http) { + // get all item types + return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' }); + } } - } - }) - .state('agenda.item.detail', { - resolve: { - item: function(Agenda, $stateParams) { - return Agenda.find($stateParams.id); + }) + .state('agenda.item.sort', { + resolve: { + items: function(Agenda) { + return Agenda.findAll(); + } }, - users: function(User) { - return User.findAll(); - }, - tags: function(Tag) { - return Tag.findAll(); - } - } - }) - .state('agenda.item.detail.update', { - views: { - '@agenda.item': {} - }, - resolve: { - types: function($http) { - // get all item types - return $http({ 'method': 'OPTIONS', 'url': '/rest/agenda/item/' }); - } - } - }) - .state('agenda.item.sort', { - resolve: { - items: function(Agenda) { - return Agenda.findAll(); - } - }, - url: '/sort', - controller: 'AgendaSortCtrl', - }) - .state('agenda.item.import', { - url: '/import', - controller: 'AgendaImportCtrl', - }); -}) + url: '/sort', + controller: 'AgendaSortCtrl', + }) + .state('agenda.item.import', { + url: '/import', + controller: 'AgendaImportCtrl', + }); + } +]) .factory('AgendaTree', [ function () { return { getTree: function (items) { - // sortieren nach weight??? + // TODO: sortieren nach weight??? // Build a dict with all children (dict-value) to a specific // item id (dict-key). @@ -190,199 +201,270 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) } ]) -.controller('ItemListCtrl', function($scope, $http, Agenda, Projector, AgendaTree) { - // Bind agenda tree to the scope - $scope.$watch(function () { - return Agenda.lastModified(); - }, function () { - $scope.items = AgendaTree.getFlatTree(Agenda.getAll()); - }); - - // save changed item - $scope.save = function (item) { - Agenda.save(item); - }; - // delete selected item - $scope.delete = function (id) { - Agenda.destroy(id); - }; - // project agenda - $scope.projectAgenda = function () { - $http.post('/rest/core/projector/1/prune_elements/', - [{name: 'agenda/item-list'}]); - }; - // check if agenda is projected - $scope.isAgendaProjected = function () { - // Returns true if there is a projector element with the same - // name and agenda is active. - var projector = Projector.get(1); - if (typeof projector === 'undefined') return false; - var self = this; - return _.findIndex(projector.elements, function(element) { - return element.name == 'agenda/item-list' - }) > -1; - - }; -}) - -.controller('ItemDetailCtrl', function($scope, $http, Agenda, User, item) { - Agenda.bindOne(item.id, $scope, 'item'); - User.bindAll({}, $scope, 'users'); - $scope.speaker = {}; - $scope.alert = {}; - - // close/open list of speakers of current item - $scope.closeList = function (listClosed) { - item.speaker_list_closed = listClosed; - Agenda.save(item); - }; - // add user to list of speakers - $scope.addSpeaker = function (userId) { - $http.post('/rest/agenda/item/' + item.id + '/manage_speaker/', {'user': userId}) - .success(function(data){ - $scope.alert.show = false; - }) - .error(function(data){ - $scope.alert = { type: 'danger', msg: data.detail, show: true }; - }); - }; - // delete speaker(!) from list of speakers - $scope.removeSpeaker = function (speakerId) { - $http.delete('/rest/agenda/item/' + item.id + '/manage_speaker/', - {headers: {'Content-Type': 'application/json'}, - data: JSON.stringify({speaker: speakerId})}) - .error(function(data){ - $scope.alert = { type: 'danger', msg: data.detail, show: true }; - }); - }; - // begin speech of selected/next speaker - $scope.beginSpeech = function (speakerId) { - $http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId}) - .success(function(data){ - $scope.alert.show = false; - }) - .error(function(data){ - $scope.alert = { type: 'danger', msg: data.detail, show: true }; - }); - }; - // end speech of current speaker - $scope.endSpeech = function () { - $http.delete('/rest/agenda/item/' + item.id + '/speak/', - {headers: {'Content-Type': 'application/json'}, - data: JSON.stringify()}) - .error(function(data){ - $scope.alert = { type: 'danger', msg: data.detail, show: true }; - }); - }; - // project list of speakers - $scope.projectListOfSpeakers = function () { - $http.post('/rest/core/projector/1/prune_elements/', - [{name: 'agenda/item', id: item.id, list_of_speakers: true}]); - }; -}) - -.controller('ItemCreateCtrl', function($scope, $state, Agenda, Tag, types) { - $scope.types = types.data.actions.POST.type.choices; // get all item types - Tag.bindAll({}, $scope, 'tags'); - $scope.save = function (item) { - if (!item) - return null; - Agenda.create(item).then( - function(success) { - $state.go('agenda.item.list'); - } - ); - }; -}) - -.controller('ItemUpdateCtrl', function($scope, $state, Agenda, Tag, types, item) { - $scope.types = types.data.actions.POST.type.choices; // get all item types - Tag.bindAll({}, $scope, 'tags'); - $scope.item = item; - $scope.save = function (item) { - Agenda.save(item).then( - function(success) { - $state.go('agenda.item.list'); - } - ); - }; -}) - -.controller('AgendaSortCtrl', function($scope, $http, Agenda, AgendaTree) { - // Bind agenda tree to the scope - $scope.$watch(function () { - return Agenda.lastModified(); - }, function () { - $scope.items = AgendaTree.getTree(Agenda.getAll()); - }); - - // set changed agenda tree - $scope.treeOptions = { - dropped: function() { - $http.put('/rest/agenda/item/tree/', {tree: $scope.items}); - } - }; -}) - -.controller('AgendaImportCtrl', function($scope, $state, Agenda) { - // import from textarea - $scope.importByLine = function () { - $scope.items = $scope.itemlist[0].split("\n"); - $scope.importcounter = 0; - $scope.items.forEach(function(title) { - var item = {title: title}; - // TODO: create all items in bulk mode - Agenda.create(item).then( - function(success) { - $scope.importcounter++; - } - ); +.controller('ItemListCtrl', [ + '$scope', + '$http', + '$state', + 'Agenda', + 'AgendaTree', + 'Projector', + function($scope, $http, $state, Agenda, AgendaTree, Projector) { + // Bind agenda tree to the scope + $scope.$watch(function () { + return Agenda.lastModified(); + }, function () { + $scope.items = AgendaTree.getFlatTree(Agenda.getAll()); }); - } - // import from csv file - $scope.csv = { - content: null, - header: true, - separator: ',', - result: null - }; - $scope.importByCSV = function (result) { - var obj = JSON.parse(JSON.stringify(result)); - $scope.csvimporting = true; - $scope.csvlines = Object.keys(obj).length; - $scope.csvimportcounter = 0; - for (var i = 0; i < obj.length; i++) { - var item = {}; - item.title = obj[i].title; - item.text = obj[i].text; - item.duration = obj[i].duration; + // open detail view link + $scope.openDetail = function (id) { + $state.go('agenda.item.detail', {id: id}); + } + + // save changed item + $scope.save = function (item) { + Agenda.save(item); + }; + + // *** delete mode functions *** + $scope.isDeleteMode = false; + // check all checkboxes + $scope.checkAll = function () { + angular.forEach($scope.items, function (item) { + item.selected = $scope.selectedAll; + }); + }; + // uncheck all checkboxes if isDeleteMode is closed + $scope.uncheckAll = function () { + if (!$scope.isDeleteMode) { + $scope.selectedAll = false; + angular.forEach($scope.items, function (item) { + item.selected = false; + }); + } + }; + // delete selected item + $scope.delete = function () { + angular.forEach($scope.items, function (item) { + if (item.selected) + Agenda.destroy(item.id); + }); + $scope.isDeleteMode = false; + $scope.uncheckAll(); + }; + + // project agenda + $scope.projectAgenda = function () { + $http.post('/rest/core/projector/1/prune_elements/', + [{name: 'agenda/item-list'}]); + }; + // check if agenda is projected + $scope.isAgendaProjected = function () { + // Returns true if there is a projector element with the same + // name and agenda is active. + var projector = Projector.get(1); + if (typeof projector === 'undefined') return false; + var self = this; + return _.findIndex(projector.elements, function(element) { + return element.name == 'agenda/item-list' + }) > -1; + + }; + } +]) + +.controller('ItemDetailCtrl', [ + '$scope', + '$http', + 'Agenda', + 'User', + 'item', + function ($scope, $http, Agenda, User, item) { + Agenda.bindOne(item.id, $scope, 'item'); + User.bindAll({}, $scope, 'users'); + $scope.speaker = {}; + $scope.alert = {}; + + // close/open list of speakers of current item + $scope.closeList = function (listClosed) { + item.speaker_list_closed = listClosed; + Agenda.save(item); + }; + // add user to list of speakers + $scope.addSpeaker = function (userId) { + $http.post('/rest/agenda/item/' + item.id + '/manage_speaker/', {'user': userId}) + .success(function(data){ + $scope.alert.show = false; + }) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // delete speaker(!) from list of speakers + $scope.removeSpeaker = function (speakerId) { + $http.delete('/rest/agenda/item/' + item.id + '/manage_speaker/', + {headers: {'Content-Type': 'application/json'}, + data: JSON.stringify({speaker: speakerId})}) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // begin speech of selected/next speaker + $scope.beginSpeech = function (speakerId) { + $http.put('/rest/agenda/item/' + item.id + '/speak/', {'speaker': speakerId}) + .success(function(data){ + $scope.alert.show = false; + }) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // end speech of current speaker + $scope.endSpeech = function () { + $http.delete('/rest/agenda/item/' + item.id + '/speak/', + {headers: {'Content-Type': 'application/json'}, + data: JSON.stringify()}) + .error(function(data){ + $scope.alert = { type: 'danger', msg: data.detail, show: true }; + }); + }; + // project list of speakers + $scope.projectListOfSpeakers = function () { + $http.post('/rest/core/projector/1/prune_elements/', + [{name: 'agenda/item', id: item.id, list_of_speakers: true}]); + }; + } +]) + +.controller('ItemCreateCtrl', [ + '$scope', + '$state', + 'Agenda', + 'Tag', + 'types', + function($scope, $state, Agenda, Tag, types) { + $scope.types = types.data.actions.POST.type.choices; // get all item types + Tag.bindAll({}, $scope, 'tags'); + $scope.save = function (item) { + if (!item) + return null; Agenda.create(item).then( function(success) { - $scope.csvimportcounter++; + $state.go('agenda.item.list'); } ); - } - $scope.csvimported = true; + }; } +]) - $scope.clear = function () { - $scope.csv.result = null; - }; +.controller('ItemUpdateCtrl', [ + '$scope', + '$state', + 'Agenda', + 'Tag', + 'types', + 'item', + function($scope, $state, Agenda, Tag, types, item) { + $scope.types = types.data.actions.POST.type.choices; // get all item types + Tag.bindAll({}, $scope, 'tags'); + $scope.item = item; + $scope.save = function (item) { + Agenda.save(item).then( + function(success) { + $state.go('agenda.item.list'); + } + ); + }; + } +]) -}); +.controller('AgendaSortCtrl', [ + '$scope', + '$http', + 'Agenda', + 'AgendaTree', + function($scope, $http, Agenda, AgendaTree) { + // Bind agenda tree to the scope + $scope.$watch(function () { + return Agenda.lastModified(); + }, function () { + $scope.items = AgendaTree.getTree(Agenda.getAll()); + }); + + // set changed agenda tree + $scope.treeOptions = { + dropped: function() { + $http.put('/rest/agenda/item/tree/', {tree: $scope.tree}); + } + }; + } +]) + +.controller('AgendaImportCtrl', [ + '$scope', + '$state', + 'Agenda', + function($scope, $state, Agenda) { + // import from textarea + $scope.importByLine = function () { + $scope.items = $scope.itemlist[0].split("\n"); + $scope.importcounter = 0; + $scope.items.forEach(function(title) { + var item = {title: title}; + // TODO: create all items in bulk mode + Agenda.create(item).then( + function(success) { + $scope.importcounter++; + } + ); + }); + } + + // import from csv file + $scope.csv = { + content: null, + header: true, + separator: ',', + result: null + }; + $scope.importByCSV = function (result) { + var obj = JSON.parse(JSON.stringify(result)); + $scope.csvimporting = true; + $scope.csvlines = Object.keys(obj).length; + $scope.csvimportcounter = 0; + for (var i = 0; i < obj.length; i++) { + var item = {}; + item.title = obj[i].title; + item.text = obj[i].text; + item.duration = obj[i].duration; + Agenda.create(item).then( + function(success) { + $scope.csvimportcounter++; + } + ); + } + $scope.csvimported = true; + } + $scope.clear = function () { + $scope.csv.result = null; + }; + } +]); angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda']) -.config(function(slidesProvider) { - slidesProvider.registerSlide('agenda/item', { - template: 'static/templates/agenda/slide-item-detail.html', - }); - slidesProvider.registerSlide('agenda/item-list', { - template: 'static/templates/agenda/slide-item-list.html', - }); -}) +.config([ + 'slidesProvider', + function(slidesProvider) { + slidesProvider.registerSlide('agenda/item', { + template: 'static/templates/agenda/slide-item-detail.html', + }); + slidesProvider.registerSlide('agenda/item-list', { + template: 'static/templates/agenda/slide-item-list.html', + }); + } +]) .controller('SlideItemDetailCtrl', [ '$scope', @@ -401,18 +483,21 @@ angular.module('OpenSlidesApp.agenda.projector', ['OpenSlidesApp.agenda']) } ]) -.controller('SlideItemListCtrl', function($scope, $http, Agenda) { - // Attention! Each object that is used here has to be dealt on server side. - // Add it to the coresponding get_requirements method of the ProjectorElement - // class. - Agenda.findAll(); - Agenda.bindAll({}, $scope, 'items'); - $scope.ids = []; - var tree = $http.get('/rest/agenda/item/tree/').success(function(data) { - var ids = []; - angular.forEach(data,function(element) { - ids.push(element.id) +.controller('SlideItemListCtrl', [ + '$scope', + '$http', + 'Agenda', + 'AgendaTree', + function($scope, $http, Agenda, AgendaTree) { + // Attention! Each object that is used here has to be dealt on server side. + // Add it to the coresponding get_requirements method of the ProjectorElement + // class. + + // Bind agenda tree to the scope + $scope.$watch(function () { + return Agenda.lastModified(); + }, function () { + $scope.items = AgendaTree.getFlatTree(Agenda.getAll()); }); - $scope.ids = ids; - }) -}); + } +]); diff --git a/openslides/agenda/static/templates/agenda/item-list.html b/openslides/agenda/static/templates/agenda/item-list.html index 0fbc07f9d..9b2041ffb 100644 --- a/openslides/agenda/static/templates/agenda/item-list.html +++ b/openslides/agenda/static/templates/agenda/item-list.html @@ -23,74 +23,93 @@ -
+ +
- +
+
+ +
-{{ items.length }} {{ "items" | translate }} + + {{ items.length }} {{ "items" | translate }}, + {{(items|filter:{selected:true}).length}} {{ "selected" | translate }} + - - - + +
- Closed + + + + + + Agenda item Duration - - Actions + + Done
- - +
+ + + + + + + - - {{ item.item_number }} - {{ item.title }} - + {{ item.item_number }} {{ item.title }}
{{ item.comment }}
- - {{ item.duration }} - - min - - - - - - - - - - - - - + {{ item.duration }} + h + +
diff --git a/openslides/agenda/static/templates/agenda/slide-item-list.html b/openslides/agenda/static/templates/agenda/slide-item-list.html index 040d206f6..62bdbc1b6 100644 --- a/openslides/agenda/static/templates/agenda/slide-item-list.html +++ b/openslides/agenda/static/templates/agenda/slide-item-list.html @@ -2,10 +2,10 @@

Agenda

- - +
- {{ (items | filter: {id: id})[0].item_number }} - - {{ (items | filter: {id: id})[0].title }} +
+ {{ item.item_number }} + + {{ item.title }}
diff --git a/openslides/core/static/css/app.css b/openslides/core/static/css/app.css index 50b73c41b..82918b547 100644 --- a/openslides/core/static/css/app.css +++ b/openslides/core/static/css/app.css @@ -184,7 +184,7 @@ a:hover { } /* List tables */ -th.sortable:hover { +th.sortable:hover, tr.pointer:hover { cursor: pointer; } @@ -246,7 +246,7 @@ tr.offline td, li.offline { background-color: #EAEAEA !important; } tr.activeline td, li.activeline { - background-color: #bed4de !important; + background-color: #bed4de; } .nopadding { padding: 0; @@ -266,6 +266,19 @@ tr.total td { .indentation { margin-left: 12px; } +.firstColumn { + width: 55px; +} +.deleteColumn { + text-align: center; + background-color: #ff9999 !important; +} +.switch.checked { + background-color: #ff9999 !important; + border-color: #ff9999; +} + + .minimum, .mini_width { width: 1px; } diff --git a/openslides/core/static/js/core/core.js b/openslides/core/static/js/core/core.js index d9d08010c..31762348d 100644 --- a/openslides/core/static/js/core/core.js +++ b/openslides/core/static/js/core/core.js @@ -8,6 +8,7 @@ angular.module('OpenSlidesApp.core', [ 'ngAnimate', 'ui.bootstrap', 'ui.tree', + 'uiSwitch', ]) .config(['DSProvider', 'DSHttpAdapterProvider', function(DSProvider, DSHttpAdapterProvider) {