diff --git a/CHANGELOG b/CHANGELOG index 3dc095794..ad568cbf3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ Version 2.1 (unreleased) Agenda: - Added button to remove all speakers from a list of speakers. +- Added option to create or edit agenda items as subitems of others. Assignments: - Remove unused assignment config to publish winner election results only. diff --git a/openslides/agenda/static/js/agenda/base.js b/openslides/agenda/static/js/agenda/base.js index 3d4739338..25ce9a5c9 100644 --- a/openslides/agenda/static/js/agenda/base.js +++ b/openslides/agenda/static/js/agenda/base.js @@ -21,6 +21,28 @@ angular.module('OpenSlidesApp.agenda', ['OpenSlidesApp.users']) } ]) +.factory('AgendaUpdate',[ + 'Agenda', + function(Agenda) { + return { + saveChanges: function (item_id, changes) { + Agenda.find(item_id).then(function(item) { + var something = false; + _.each(changes, function(change) { + if (change.value !== item[change.key]) { + item[change.key] = change.value; + something = true; + } + }); + if (something === true) { + Agenda.save(item); + } + }); + } + }; + } +]) + .factory('Agenda', [ '$http', 'DS', diff --git a/openslides/agenda/static/js/agenda/site.js b/openslides/agenda/static/js/agenda/site.js index 9e1794895..854c6bda0 100644 --- a/openslides/agenda/static/js/agenda/site.js +++ b/openslides/agenda/static/js/agenda/site.js @@ -129,7 +129,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda']) // open edit dialog $scope.editDialog = function (item) { $state.go(item.content_object.collection.replace('/','.')+'.detail.update', - {id: item.content_object.id}); + {id: item.content_object.id}); }; // detail view of related item (content object) $scope.open = function (item) { diff --git a/openslides/assignments/static/js/assignments/site.js b/openslides/assignments/static/js/assignments/site.js index c69597df2..c229c4a20 100644 --- a/openslides/assignments/static/js/assignments/site.js +++ b/openslides/assignments/static/js/assignments/site.js @@ -36,16 +36,16 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) assignments: function(Assignment) { return Assignment.findAll(); }, + tags: function(Tag) { + return Tag.findAll(); + }, items: function(Agenda) { return Agenda.findAll().catch( - function () { + function() { return null; } ); }, - tags: function(Tag) { - return Tag.findAll(); - }, phases: function(Assignment) { return Assignment.getPhases(); } @@ -56,7 +56,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) resolve: { assignment: function(Assignment, $stateParams) { return Assignment.find($stateParams.id).then(function(assignment) { - return Assignment.loadRelations(assignment, 'agenda_item'); + return assignment; }); }, users: function(User) { @@ -65,6 +65,13 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) tags: function(Tag) { return Tag.findAll(); }, + items: function(Agenda) { + return Agenda.findAll().catch( + function() { + return null; + } + ); + }, phases: function(Assignment) { return Assignment.getPhases(); } @@ -75,8 +82,8 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) // (from assignment controller use AssignmentForm factory instead to open dialog in front // of current view without redirect) .state('assignments.assignment.detail.update', { - onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment', - function($stateParams, $state, ngDialog, Assignment) { + onEnter: ['$stateParams', '$state', 'ngDialog', 'Assignment', 'Agenda', + function($stateParams, $state, ngDialog, Assignment, Agenda) { ngDialog.open({ template: 'static/templates/assignments/assignment-form.html', controller: 'AssignmentUpdateCtrl', @@ -89,6 +96,12 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) return Assignment.loadRelations(assignment, 'agenda_item'); }); }, + items: function(Agenda) { + return Agenda.findAll().catch( + function() { + return null; + }); + }, }, preCloseCallback: function() { $state.go('assignments.assignment.detail', {assignment: $stateParams.id}); @@ -106,28 +119,32 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'gettextCatalog', 'operator', 'Tag', - function (gettextCatalog, operator, Tag) { + 'Assignment', + 'Agenda', + 'AgendaTree', + function (gettextCatalog, operator, Tag, Assignment, Agenda, AgendaTree) { return { // ngDialog for assignment form getDialog: function (assignment) { - var resolve; + var resolve = {}; if (assignment) { - resolve = { - assignment: function() { - return assignment; - }, - agenda_item: function(Assignment) { - return Assignment.loadRelations(assignment, 'agenda_item'); - } + resolve.assignment = function () { + return assignment; + }; + resolve.agenda_item = function () { + return Assignment.loadRelations(assignment, 'agenda_item'); }; } + resolve.items = function() { + return Agenda.getAll(); + }; return { template: 'static/templates/assignments/assignment-form.html', controller: (assignment) ? 'AssignmentUpdateCtrl' : 'AssignmentCreateCtrl', className: 'ngdialog-theme-default wide-form', closeByEscape: false, closeByDocument: false, - resolve: (resolve) ? resolve : null + resolve: resolve }; }, // angular-formly fields for assignment form @@ -173,6 +190,17 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) }, hide: !operator.hasPerms('assignments.can_manage') }, + { + key: 'agenda_parent_item_id', + type: 'select-single', + templateOptions: { + label: gettextCatalog.getString('Parent item'), + options: AgendaTree.getFlatTree(Agenda.getAll()), + ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id', + placeholder: gettextCatalog.getString('Select a parent item ...') + }, + hide: !operator.hasPerms('agenda.can_manage') + }, { key: 'more', type: 'checkbox', @@ -203,8 +231,9 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'AssignmentForm', 'Assignment', 'Tag', + 'Agenda', 'phases', - function($scope, ngDialog, AssignmentForm, Assignment, Tag, phases) { + function($scope, ngDialog, AssignmentForm, Assignment, Tag, Agenda, phases) { Assignment.bindAll({}, $scope, 'assignments'); Tag.bindAll({}, $scope, 'tags'); $scope.phases = phases; @@ -257,7 +286,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) assignment.quickEdit = false; $scope.alert.show = false; }, - function(error){ + function(error) { var message = ''; for (var e in error.data) { message += e + ': ' + error.data[e] + ' '; @@ -317,7 +346,6 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) $scope.candidateSelectBox = {}; $scope.phases = phases; $scope.alert = {}; - // open edit dialog $scope.openDialog = function (assignment) { ngDialog.open(AssignmentForm.getDialog(assignment)); @@ -471,27 +499,22 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'Assignment', 'AssignmentForm', 'Agenda', - function($scope, Assignment, AssignmentForm, Agenda) { + 'AgendaUpdate', + function($scope, Assignment, AssignmentForm, Agenda, AgendaUpdate) { $scope.model = {}; // set default value for open posts form field $scope.model.open_posts = 1; // get all form fields $scope.formFields = AssignmentForm.getFormFields(); - // save assignment $scope.save = function(assignment) { Assignment.create(assignment).then( function(success) { - // find related agenda item - Agenda.find(success.agenda_item_id).then(function(item) { - // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2) - var type = assignment.showAsAgendaItem ? 1 : 2; - // save only if agenda item type is modified - if (item.type != type) { - item.type = type; - Agenda.save(item); - } - }); + // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, + // see openslides.agenda.models.Item.ITEM_TYPE. + var changes = [{key: 'type', value: (assignment.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: assignment.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); } ); @@ -504,18 +527,22 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) 'Assignment', 'AssignmentForm', 'Agenda', + 'AgendaUpdate', 'assignment', - function($scope, Assignment, AssignmentForm, Agenda, assignment) { + function($scope, Assignment, AssignmentForm, Agenda, AgendaUpdate, assignment) { $scope.alert = {}; // set initial values for form model by create deep copy of assignment object // so list/detail view is not updated while editing $scope.model = angular.copy(assignment); // get all form fields $scope.formFields = AssignmentForm.getFormFields(); + var agenda_item = Agenda.get(assignment.agenda_item_id); for (var i = 0; i < $scope.formFields.length; i++) { if ($scope.formFields[i].key == "showAsAgendaItem") { // get state from agenda item (hidden/internal or agenda item) $scope.formFields[i].defaultValue = !assignment.agenda_item.is_hidden; + } else if($scope.formFields[i].key == 'agenda_parent_item_id') { + $scope.formFields[i].defaultValue = agenda_item.parent_id; } } @@ -526,13 +553,9 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments']) // save change assignment object on server Assignment.save(assignment).then( function(success) { - // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2) - var type = assignment.showAsAgendaItem ? 1 : 2; - // save only if agenda item type is modified - if (assignment.agenda_item.type != type) { - assignment.agenda_item.type = type; - Agenda.save(assignment.agenda_item); - } + var changes = [{key: 'type', value: (assignment.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: assignment.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); }, function (error) { diff --git a/openslides/core/static/js/core/base.js b/openslides/core/static/js/core/base.js index ab51690bc..8747da906 100644 --- a/openslides/core/static/js/core/base.js +++ b/openslides/core/static/js/core/base.js @@ -479,6 +479,26 @@ angular.module('OpenSlidesApp.core', [ } ]) +// filters the requesting object (id=selfid) from a list of input objects +.filter('notself', function() { + return function(input, selfid) { + var result; + if (selfid) { + result = []; + for (var key in input){ + var obj = input[key]; + if (selfid != obj.id) { + result.push(obj); + } + } + } else { + result = input; + } + return result; + }; +}) + + // Make sure that the DS factories are loaded by making them a dependency .run([ 'ChatMessage', diff --git a/openslides/core/static/js/core/site.js b/openslides/core/static/js/core/site.js index 77a347a01..e605a4407 100644 --- a/openslides/core/static/js/core/site.js +++ b/openslides/core/static/js/core/site.js @@ -647,6 +647,9 @@ angular.module('OpenSlidesApp.core.site', [ resolve: { customslide: function(Customslide, $stateParams) { return Customslide.find($stateParams.id); + }, + items: function(Agenda) { + return Agenda.findAll(); } } }) @@ -655,21 +658,26 @@ angular.module('OpenSlidesApp.core.site', [ // (from customslide controller use CustomSlideForm factory instead to open dialog in front // of current view without redirect) .state('core.customslide.detail.update', { - onEnter: ['$stateParams', '$state', 'ngDialog', 'Customslide', - function($stateParams, $state, ngDialog, Customslide) { + onEnter: ['$stateParams', '$state', 'ngDialog', 'Customslide', 'Agenda', + function($stateParams, $state, ngDialog, Customslide, Agenda) { ngDialog.open({ template: 'static/templates/core/customslide-form.html', controller: 'CustomslideUpdateCtrl', className: 'ngdialog-theme-default wide-form', resolve: { - customslide: function() {return Customslide.find($stateParams.id);} + customslide: function() { + return Customslide.find($stateParams.id); + }, + items: function() { + return Agenda.findAll(); + } }, preCloseCallback: function() { $state.go('core.customslide.detail', {customslide: $stateParams.id}); return true; } }); - }] + }], }) // tag .state('core.tag', { @@ -979,7 +987,9 @@ angular.module('OpenSlidesApp.core.site', [ 'gettextCatalog', 'Editor', 'Mediafile', - function (gettextCatalog, Editor, Mediafile) { + 'Agenda', + 'AgendaTree', + function (gettextCatalog, Editor, Mediafile, Agenda, AgendaTree) { return { // ngDialog for customslide form getDialog: function (customslide) { @@ -1038,7 +1048,16 @@ angular.module('OpenSlidesApp.core.site', [ description: gettextCatalog.getString('If deactivated it appears as internal item on agenda.') } }, - ]; + { + key: 'agenda_parent_item_id', + type: 'select-single', + templateOptions: { + label: gettextCatalog.getString('Parent item'), + options: AgendaTree.getFlatTree(Agenda.getAll()), + ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id', + placeholder: gettextCatalog.getString('Select a parent item ...') + } + }]; } }; } @@ -1251,7 +1270,6 @@ angular.module('OpenSlidesApp.core.site', [ function($scope, ngDialog, CustomslideForm, Customslide, customslide) { Customslide.bindOne(customslide.id, $scope, 'customslide'); Customslide.loadRelations(customslide, 'agenda_item'); - // open edit dialog $scope.openDialog = function (customslide) { ngDialog.open(CustomslideForm.getDialog(customslide)); @@ -1265,30 +1283,24 @@ angular.module('OpenSlidesApp.core.site', [ 'Customslide', 'CustomslideForm', 'Agenda', - function($scope, $state, Customslide, CustomslideForm, Agenda) { + 'AgendaUpdate', + function($scope, $state, Customslide, CustomslideForm, Agenda, AgendaUpdate) { $scope.customslide = {}; $scope.model = {}; $scope.model.showAsAgendaItem = true; // get all form fields $scope.formFields = CustomslideForm.getFormFields(); - // save form $scope.save = function (customslide) { Customslide.create(customslide).then( function(success) { - // find related agenda item - Agenda.find(success.agenda_item_id).then(function(item) { - // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2) - var type = customslide.showAsAgendaItem ? 1 : 2; - // save only if agenda item type is modified - if (item.type != type) { - item.type = type; - Agenda.save(item); - } - }); - $scope.closeThisDialog(); - } - ); + // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, + // see openslides.agenda.models.Item.ITEM_TYPE. + var changes = [{key: 'type', value: (customslide.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: customslide.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id,changes); + }); + $scope.closeThisDialog(); }; } ]) @@ -1299,8 +1311,10 @@ angular.module('OpenSlidesApp.core.site', [ 'Customslide', 'CustomslideForm', 'Agenda', + 'AgendaUpdate', 'customslide', - function($scope, $state, Customslide, CustomslideForm, Agenda, customslide) { + function($scope, $state, Customslide, CustomslideForm, Agenda, AgendaUpdate, customslide) { + Customslide.loadRelations(customslide, 'agenda_item'); $scope.alert = {}; // set initial values for form model by create deep copy of customslide object // so list/detail view is not updated while editing @@ -1311,25 +1325,22 @@ angular.module('OpenSlidesApp.core.site', [ if ($scope.formFields[i].key == "showAsAgendaItem") { // get state from agenda item (hidden/internal or agenda item) $scope.formFields[i].defaultValue = !customslide.agenda_item.is_hidden; + } else if($scope.formFields[i].key == "agenda_parent_item_id") { + $scope.formFields[i].defaultValue = customslide.agenda_item.parent_id; } } // save form $scope.save = function (customslide) { - // inject the changed customslide (copy) object back into DS store - Customslide.inject(customslide); - // save change customslide object on server - Customslide.save(customslide).then( + Customslide.create(customslide).then( function(success) { - // save agenda specific stuff - var type = customslide.showAsAgendaItem ? 1 : 2; - if (customslide.agenda_item.type != type) { - customslide.agenda_item.type = type; - Agenda.save(customslide.agenda_item); - } + // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, + // see openslides.agenda.models.Item.ITEM_TYPE. + var changes = [{key: 'type', value: (customslide.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: customslide.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id,changes); $scope.closeThisDialog(); - }, - function (error) { + }, function (error) { // save error: revert all changes by restore // (refresh) original customslide object from server Customslide.refresh(customslide); diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index fa67c4d0a..c2975893b 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -253,11 +253,16 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid return User.findAll().catch( function () { return null; - } - ); + }); }, workflows: function(Workflow) { return Workflow.findAll(); + }, + items: function(Agenda) { + return Agenda.findAll().catch( + function () { + return null; + }); } } }) @@ -276,6 +281,13 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid } ); }, + items: function(Agenda) { + return Agenda.findAll().catch( + function () { + return null; + } + ); + }, mediafiles: function(Mediafile) { return Mediafile.findAll().catch( function () { @@ -307,6 +319,13 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid return Motion.loadRelations(motion, 'agenda_item'); }); }, + items: function(Agenda) { + return Agenda.findAll().catch( + function() { + return null; + } + ); + } }, preCloseCallback: function() { $state.go('motions.motion.detail', {motion: $stateParams.id}); @@ -384,7 +403,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid 'Tag', 'User', 'Workflow', - function (gettextCatalog, operator, Editor, Category, Config, Mediafile, Tag, User, Workflow) { + 'Agenda', + 'AgendaTree', + function (gettextCatalog, operator, Editor, Category, Config, Mediafile, Tag, User, Workflow, Agenda, AgendaTree) { return { // ngDialog for motion form getDialog: function (motion) { @@ -482,6 +503,17 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid }, hide: !operator.hasPerms('motions.can_manage') }, + { + key: 'agenda_parent_item_id', + type: 'select-single', + templateOptions: { + label: gettextCatalog.getString('Parent item'), + options: AgendaTree.getFlatTree(Agenda.getAll()), + ngOptions: 'item.id as item.getListViewTitle() for item in to.options | notself : model.agenda_item_id', + placeholder: gettextCatalog.getString('Select a parent item ...') + }, + hide: !operator.hasPerms('agenda.can_manage') + }, { key: 'more', type: 'checkbox', @@ -1015,7 +1047,8 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid 'User', 'Workflow', 'Agenda', - function($scope, gettext, Motion, MotionForm, Category, Config, Mediafile, Tag, User, Workflow, Agenda) { + 'AgendaUpdate', + function($scope, gettext, Motion, MotionForm, Category, Config, Mediafile, Tag, User, Workflow, Agenda, AgendaUpdate) { Category.bindAll({}, $scope, 'categories'); Mediafile.bindAll({}, $scope, 'mediafiles'); Tag.bindAll({}, $scope, 'tags'); @@ -1034,16 +1067,11 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid $scope.save = function (motion) { Motion.create(motion).then( function(success) { - // find related agenda item - Agenda.find(success.agenda_item_id).then(function(item) { - // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2) - var type = motion.showAsAgendaItem ? 1 : 2; - // save only if agenda item type is modified - if (item.type != type) { - item.type = type; - Agenda.save(item); - } - }); + // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, + // see openslides.agenda.models.Item.ITEM_TYPE. + var changes = [{key: 'type', value: (motion.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: motion.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id, changes); $scope.closeThisDialog(); } ); @@ -1062,8 +1090,9 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid 'User', 'Workflow', 'Agenda', + 'AgendaUpdate', 'motion', - function($scope, Motion, Category, Config, Mediafile, MotionForm, Tag, User, Workflow, Agenda, motion) { + function($scope, Motion, Category, Config, Mediafile, MotionForm, Tag, User, Workflow, Agenda, AgendaUpdate, motion) { Category.bindAll({}, $scope, 'categories'); Mediafile.bindAll({}, $scope, 'mediafiles'); Tag.bindAll({}, $scope, 'tags'); @@ -1111,6 +1140,10 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid // get saved workflow id from state $scope.formFields[i].defaultValue = motion.state.workflow_id; } + if ($scope.formFields[i].key == "agenda_parent_item_id") { + // get current parent_id of the agenda item + $scope.formFields[i].defaultValue = motion.agenda_item.parent_id; + } } // save motion @@ -1120,14 +1153,14 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid // save change motion object on server Motion.save(motion, { method: 'PATCH' }).then( function(success) { - // check form element and set item type (AGENDA_ITEM = 1, HIDDEN_ITEM = 2) - var type = motion.showAsAgendaItem ? 1 : 2; - // save only if agenda item type is modified - if (motion.agenda_item.type != type) { - motion.agenda_item.type = type; - Agenda.save(motion.agenda_item); - } - $scope.closeThisDialog(); + Agenda.find(success.agenda_item_id).then(function(item) { + // type: Value 1 means a non hidden agenda item, value 2 means a hidden agenda item, + // see openslides.agenda.models.Item.ITEM_TYPE. + var changes = [{key: 'type', value: (motion.showAsAgendaItem ? 1 : 2)}, + {key: 'parent_id', value: motion.agenda_parent_item_id}]; + AgendaUpdate.saveChanges(success.agenda_item_id,changes); + $scope.closeThisDialog(); + }); }, function (error) { // save error: revert all changes by restore