Template improvements for motion blocks.

Fixed $stateProvider to allow camelCase in state name (Fixed #2479)
- Refactor generic templateUrl function.
- Rename MotionBlock templates.
- Rename MotionBlock controller.
This commit is contained in:
Emanuel Schuetze 2016-10-14 15:38:56 +02:00 committed by Emanuel Schütze
parent 700c86a24c
commit 0270c31b32
9 changed files with 242 additions and 73 deletions

View File

@ -165,10 +165,8 @@ angular.module('OpenSlidesApp.core.site', [
}
angular.forEach(views, function(config, name) {
// Sets default values for templateUrl
var patterns = state.name.split('.'),
templateUrl,
// Sets additional default values for templateUrl
var templateUrl,
controller,
defaultControllers = {
create: 'CreateCtrl',
@ -177,22 +175,43 @@ angular.module('OpenSlidesApp.core.site', [
detail: 'DetailCtrl',
};
// templateUrl
if (_.last(patterns).match(/(create|update)/)) {
// When state_patterns is in the form "app.module.create" or
// "app.module.update", use the form template.
templateUrl = 'static/templates/' + patterns[0] + '/' + patterns[1] + '-form.html';
} else {
// Replaces the first point through a slash (the app name)
var appName = state.name.replace('.', '/');
// Replaces any folowing points though a -
templateUrl = 'static/templates/' + appName.replace(/\./g, '-') + '.html';
// Split up state name
// example: "motions.motion.detail.update" -> ['motions', 'motion', 'detail', 'update']
var patterns = state.name.split('.')
// set app and module name from state
// - appName: patterns[0] (e.g. "motions")
// - moduleNames: patterns without first element (e.g. ["motion", "detail", "update"])
var appName = ''
var moduleName = '';
var moduleNames = [];
if (patterns.length > 0) {
appName = patterns[0];
moduleNames = patterns.slice(1);
}
if (moduleNames.length > 0) {
// convert from camcelcase to dash notation
// example: ["motionBlock", "detail"] -> ["motion-block", "detail"]
for (var i = 0; i < moduleNames.length; i++) {
moduleNames[i] = moduleNames[i].replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase();
};
// use special templateUrl for create and update view
// example: ["motion", "detail", "update"] -> "motion-form"
if (_.last(moduleNames).match(/(create|update)/)) {
moduleName = '/' + moduleNames[0] + '-form';
} else {
// convert modelNames array to url string
// example: ["motion-block", "detail"] -> "motion-block-detail"
moduleName = '/' + moduleNames.join('-');
}
}
templateUrl = 'static/templates/' + appName + moduleName + '.html';
config.templateUrl = state.templateUrl || templateUrl;
// controller
if (patterns.length >= 3) {
controller = _.capitalize(patterns[1]) + defaultControllers[_.last(patterns)];
controller = _.upperFirst(patterns[1]) + defaultControllers[_.last(patterns)];
config.controller = state.controller || controller;
}
result[name] = config;
@ -1417,6 +1436,16 @@ angular.module('OpenSlidesApp.core.site', [
}
])
.filter("toArray", function(){
return function(obj) {
var result = [];
angular.forEach(obj, function(val, key) {
result.push(val);
});
return result;
};
})
//Mark all core config strings for translation in Javascript
.config([
'gettext',

View File

@ -58,7 +58,7 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
// Get ngDialog configuration.
getDialog: function (motionBlock) {
return {
template: 'static/templates/motions/motionBlock-form.html',
template: 'static/templates/motions/motion-block-form.html',
controller: (motionBlock) ? 'MotionBlockUpdateCtrl' : 'MotionBlockCreateCtrl',
className: 'ngdialog-theme-default wide-form',
closeByEscape: false,
@ -104,8 +104,7 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
}
])
// TODO: Rename this to MotionBlockListCtrl after $stateProvider is fixed, see #2479.
.controller('MotionblockListCtrl', [
.controller('MotionBlockListCtrl', [
'$scope',
'ngDialog',
'MotionBlock',
@ -123,22 +122,21 @@ angular.module('OpenSlidesApp.motions.motionBlock', [])
$scope.delete = function (motionBlock) {
MotionBlock.destroy(motionBlock.id);
};
// TODO: In template we have filter toggleSort reverse sortColumn header. Use this stuff or remove it.
}
])
// TODO: Rename this to MotionBlockDetailCtrl after $stateProvider is fixed, see #2479.
.controller('MotionblockDetailCtrl', [
.controller('MotionBlockDetailCtrl', [
'$scope',
'ngDialog',
'Motion',
'MotionBlockForm',
'MotionBlock',
'motionBlock',
'Projector',
'ProjectionDefault',
function($scope, ngDialog, MotionBlockForm, MotionBlock, motionBlock, Projector, ProjectionDefault) {
function($scope, ngDialog, Motion, MotionBlockForm, MotionBlock, motionBlock, Projector, ProjectionDefault) {
MotionBlock.bindOne(motionBlock.id, $scope, 'motionBlock');
Motion.bindAll({}, $scope, 'motions');
$scope.$watch(function () {
return Projector.lastModified();
}, function () {

View File

@ -229,6 +229,9 @@ angular.module('OpenSlidesApp.motions.site', [
motionBlock: function(MotionBlock, $stateParams) {
return MotionBlock.find($stateParams.id);
},
motions: function(Motion) {
return Motion.findAll();
},
items: function(Agenda) {
return Agenda.findAll().catch(
function () {
@ -246,7 +249,7 @@ angular.module('OpenSlidesApp.motions.site', [
onEnter: ['$stateParams', '$state', 'ngDialog', 'MotionBlock',
function($stateParams, $state, ngDialog, MotionBlock) {
ngDialog.open({
template: 'static/templates/motions/motionBlock-form.html',
template: 'static/templates/motions/motion-block-form.html',
controller: 'MotionBlockUpdateCtrl',
className: 'ngdialog-theme-default wide-form',
closeByEscape: false,
@ -707,6 +710,7 @@ angular.module('OpenSlidesApp.motions.site', [
'Workflow',
'User',
'Agenda',
'MotionBlock',
'MotionDocxExport',
'MotionContentProvider',
'MotionCatalogContentProvider',
@ -716,11 +720,12 @@ angular.module('OpenSlidesApp.motions.site', [
'HTMLValidizer',
'Projector',
'ProjectionDefault',
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionDocxExport,
MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
function($scope, $state, $http, ngDialog, MotionForm, Motion, Category, Tag, Workflow, User, Agenda, MotionBlock,
MotionDocxExport, MotionContentProvider, MotionCatalogContentProvider, PdfMakeConverter, PdfMakeDocumentProvider,
gettextCatalog, HTMLValidizer, Projector, ProjectionDefault) {
Motion.bindAll({}, $scope, 'motions');
Category.bindAll({}, $scope, 'categories');
MotionBlock.bindAll({}, $scope, 'motionBlocks');
Tag.bindAll({}, $scope, 'tags');
Workflow.bindAll({}, $scope, 'workflows');
User.bindAll({}, $scope, 'users');
@ -743,11 +748,13 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.multiselectFilter = {
state: [],
category: [],
motionBlock: [],
tag: []
};
$scope.getItemId = {
state: function (motion) {return motion.state_id;},
category: function (motion) {return motion.category_id;},
motionBlock: function (motion) {return motion.motion_block_id;},
tag: function (motion) {return motion.tags_id;}
};
// function to operate the multiselectFilter
@ -777,6 +784,10 @@ angular.module('OpenSlidesApp.motions.site', [
if (motion.category) {
category = motion.category.name;
}
var motionBlock = '';
if (motion.motionBlock) {
motionBlock = motion.motionBlock.title;
}
return [
motion.identifier,
motion.getTitle(),
@ -802,6 +813,7 @@ angular.module('OpenSlidesApp.motions.site', [
}
).join(" "),
category,
motionBlock
].join(" ");
};
// for reset-button
@ -809,6 +821,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.multiselectFilter = {
state: [],
category: [],
motionBlock: [],
tag: []
};
if ($scope.filter) {
@ -818,6 +831,7 @@ angular.module('OpenSlidesApp.motions.site', [
$scope.are_filters_set = function () {
return $scope.multiselectFilter.state.length > 0 ||
$scope.multiselectFilter.category.length > 0 ||
$scope.multiselectFilter.motionBlock.length > 0 ||
$scope.multiselectFilter.tag.length > 0 ||
($scope.filter ? $scope.filter.search : false);
};
@ -878,6 +892,14 @@ angular.module('OpenSlidesApp.motions.site', [
}
save(motion);
};
$scope.toggle_motionBlock = function (motion, block) {
if (motion.motion_block_id == block.id) {
motion.motion_block_id = null;
} else {
motion.motion_block_id = block.id;
}
save(motion);
};
// open new/edit dialog
$scope.openDialog = function (motion) {

View File

@ -0,0 +1,76 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="motions.motionBlock.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
<!-- List of speakers -->
<a ui-sref="agenda.item.detail({id: motionBlock.agenda_item_id})" class="btn btn-sm btn-default">
<i class="fa fa-microphone fa-lg"></i>
<translate>List of speakers</translate>
</a>
<!-- project -->
<projector-button model="motionBlock" default-projector-id="defaultProjectorId">
</projector-button>
<!-- edit -->
<a ng-click="openDialog(motionBlock)"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate}}">
<i class="fa fa-pencil"></i>
</a>
</div>
<h1>{{ motionBlock.agenda_item.getTitle() }}</h1>
<h2 translate>Motion block</h2>
</div>
</div>
<div class="details">
<!-- set state button (TODO)-->
<a os-perms="motions.can_manage" class="btn btn-default btn"
ng-bootbox-confirm="{{ 'Are you sure you want to override the state of all motions of this motion block?' | translate }}"
ng-bootbox-confirm-action="" translate>
<i class="fa fa-magic fa-lg"></i>
<translate>Set state for each motion according to their recommendation</translate>
</a>
<div class="row spacer form-group">
<div class="col-sm-4 pull-right">
<input type="text" ng-model="filter.search" class="form-control"
placeholder="{{ 'Filter' | translate }}">
</div>
<div class="col-sm-4 italic">
{{ motionsFiltered.length }} /
{{ motionBlock.motions.length }} {{ "motions" | translate }}
</div>
</div>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th><translate>Motion</translate>
<th><translate>State</translate>
<th><translate>Recommendation</translate>
<tbody>
<tr ng-repeat="motion in motionsFiltered = (motionBlock.motions | filter: filter.search | orderBy: 'identifier')">
<td ng-mouseover="motion.hover=true" ng-mouseleave="motion.hover=false">
<strong>
<a ui-sref="motions.motion.detail({id: motion.id})">{{ motion.identifier }} {{ motion.getTitle() }}</a>
</strong>
<div class="hoverActions" ng-class="{'hiddenDiv': !motion.hover}">
<!-- delete -->
<a href="" class="text-danger"
ng-bootbox-confirm="{{ 'Are you sure you want to remove this motion from motion block?' | translate }}<br>
<b>{{ motion.getTitle() }}</b>"
ng-bootbox-confirm-action="delete(motionBlock)" translate>Remove from motion block</a>
</div>
<td>
<div class="label" ng-class="'label-'+motion.state.css_class">
{{ motion.state.name | translate }}
</div>
<td>
<div class="label" ng-class="'label-'+motion.recommendation.css_class">
{{ motion.recommendation.recommendation_label | translate }}
</div>
</table>
</div>

View File

@ -25,13 +25,9 @@
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th ng-click="toggleSort('name')" class="sortable">
<translate>Name</translate>
<i class="pull-right fa" ng-show="sortColumn === 'name' && header.sortable != false"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
<th><translate>Name</translate>
<tbody>
<tr ng-repeat="motionBlock in motionBlocks | filter: filter.search | orderBy: sortColumn:reverse">
<tr ng-repeat="motionBlock in motionBlocks | filter: filter.search | orderBy: 'title'">
<td ng-mouseover="motionBlock.hover=true" ng-mouseleave="motionBlock.hover=false">
<strong>
<a ui-sref="motions.motionBlock.detail({id: motionBlock.id})">{{ motionBlock.title }}</a>

View File

@ -150,6 +150,12 @@
<h3 ng-if="motion.category" translate>Category</h3>
{{ motion.category.name }}
<!-- Motion block -->
<h3 translate>Motion block</h3>
<a ui-sref="motions.motionBlock.detail({id: motion.motionBlock.id})"
os-perms="motions.can_manage">{{ motion.motionBlock.title }}</a>
<span os-perms="!motions.can_manage">{{ motion.motionBlock.title }}</span>
<!-- Tags -->
<h3 ng-if="motion.tags.length > 0" translate>Tags</h3>
<span ng-repeat="tag in motion.tags">
@ -160,10 +166,6 @@
<h3 ng-if="motion.origin" translate>Origin</h3>
{{ motion.origin }}
<!-- Motion block -->
<h3 translate>Motion block</h3>
{{ motion.motionBlock }}
</div>
<div class="col-md-4">
<h3 ng-if="motion.polls.length > 0" translate>Voting result</h3>

View File

@ -10,7 +10,7 @@
<translate>Categories</translate>
</a>
<a ui-sref="motions.motionBlock.list" os-perms="motions.can_manage" class="btn btn-default btn-sm">
<i class="fa fa-list fa-lg"></i>
<i class="fa fa-th-large fa-lg"></i>
<translate>Motion blocks</translate>
</a>
<a ui-sref="core.tag.list" os-perms="core.can_manage_tags" class="btn btn-default btn-sm">
@ -149,6 +149,25 @@
</li>
</ul>
</span>
<!-- Motion block filter -->
<span class="dropdown" uib-dropdown ng-if="motionBlocks.length > 0">
<span class="pointer" id="dropdownBlock" uib-dropdown-toggle
ng-class="{'bold': multiselectFilter.motionBlock.length > 0, 'disabled': isDeleteMode}"
ng-disabled="isDeleteMode">
<translate>Motion block</translate>
<span class="caret"></span>
</span>
<ul class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownBlock">
<li ng-repeat="block in motionBlocks">
<div class="dropdown-entry pointer"
ng-click="operateMultiselectFilter('motionBlock', block.id)">
<i class="fa fa-check" ng-if="multiselectFilter.motionBlock.indexOf(block.id) > -1"></i>
{{ block.title }}
</div>
</li>
</ul>
</span>
<!-- Tag filter -->
<span class="dropdown" uib-dropdown ng-if="tags.length > 0">
<span class="pointer" id="dropdownTag" uib-dropdown-toggle
@ -223,11 +242,22 @@
</li>
<li>
<!-- category -->
<div class="pointer dropdown-entry" ng-click="toggleSort('category')">
<div class="pointer dropdown-entry" ng-click="toggleSort('category.name')">
<translate>Category</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'category' && header.sortable != false ? 'visible' : 'hidden'}"
ng-style="{'visibility': sortColumn === 'category.name' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
</li>
<li>
<!-- motion block -->
<div class="pointer dropdown-entry" ng-click="toggleSort('motionBlock.title')">
<translate>Motion block</translate>
<span class="spacer-right pull-right"></span>
<i class="pull-right fa"
ng-style="{'visibility': sortColumn === 'motionBlock.title' && header.sortable != false ? 'visible' : 'hidden'}"
ng-class="reverse ? 'fa-sort-desc' : 'fa-sort-asc'">
</i>
</div>
@ -274,6 +304,15 @@
{{ category.name }}
</span>
</span>
<span ng-repeat="motionBlock in motionBlocks" class="pointer spacer-left-lg"
ng-if="multiselectFilter.motionBlock.indexOf(motionBlock.id) > -1"
ng-click="operateMultiselectFilter('motionBlock', motionBlock.id)"
ng-class="{'disabled': isDeleteMode}">
<span class="nobr">
<i class="fa fa-times-circle"></i>
{{ motionBlock.title }}
</span>
</span>
<span ng-repeat="tag in tags" class="pointer spacer-left-lg"
ng-if="multiselectFilter.tag.indexOf(tag.id) > -1"
ng-click="operateMultiselectFilter('tag', tag.id)"
@ -296,7 +335,9 @@
| osFilter: filter.search : getFilterString
| SelectMultipleFilter: multiselectFilter.state : getItemId.state
| SelectMultipleFilter: multiselectFilter.category : getItemId.category
| SelectMultipleFilter: multiselectFilter.motionBlock : getItemId.motionBlock
| SelectMultipleFilter: multiselectFilter.tag : getItemId.tag
| toArray
| orderBy: sortColumn : reverse)">
<!-- select column -->
@ -314,7 +355,7 @@
<div class="col-xs-6 content">
<div class="identifier-col">
<div class="nobr" ng-show="motion.identifier">
{{ motion.identifier }}:
{{ motion.identifier }}
</div>
</div>
<div class="title-col">
@ -415,6 +456,41 @@
{{ motion.category.name }}
</div>
<!-- Motion block dropdown for manage user -->
<div os-perms="motions.can_manage" ng-show="motionBlocks.length > 0"
ng-mouseover="motion.motionBlockHover=true"
ng-mouseleave="motion.motionBlockHover=false">
<span uib-dropdown >
<span id="dropdown-motionBlock{{ motion.id }}" class="pointer"
uib-dropdown-toggle uib-tooltip="{{ 'Set a motion block' | translate }}"
tooltip-class="nobr">
<span ng-if="motion.motionBlock == null" ng-show="motion.hover">
<i class="fa fa-th-large"></i>
<i class="fa fa-plus"></i>
</span>
<span ng-if="motion.motionBlock != null">
<i class="fa fa-th-large spacer-right"></i>
{{ motion.motionBlock.title }}
<i class="fa fa-cog fa-lg spacer-left" ng-show="motion.motionBlockHover"></i>
</span>
</span>
<ul class="dropdown-menu" aria-labelledby="dropdown-motionBlock{{ motion.id }}">
<li ng-repeat="motionBlock in motionBlocks">
<div class="dropdown-entry pointer"
ng-click="toggle_motionBlock(motion, motionBlock)">
<i class="fa fa-check" ng-if="motionBlock.id == motion.motionBlock.id"></i>
{{ motionBlock.title }}
</div>
</li>
</ul>
</span>
</div>
<!-- Motion block string for normal user -->
<div os-perms="!motions.can_manage" ng-show="motion.motionBlock != null">
<i class="fa fa-sitemap spacer-right"></i>
{{ motion.motionBlock.title }}
</div>
<!-- Tag dropdown for manage user -->
<div os-perms="motions.can_manage" ng-show="tags.length > 0"
ng-mouseover="motion.tagHover=true"

View File

@ -1,30 +0,0 @@
<div class="header">
<div class="title">
<div class="submenu">
<a ui-sref="motions.motionBlock.list" class="btn btn-sm btn-default">
<i class="fa fa-angle-double-left fa-lg"></i>
<translate>Back to overview</translate>
</a>
<!-- List of speakers -->
<a ui-sref="agenda.item.detail({id: motionBlock.agenda_item_id})" class="btn btn-sm btn-default">
<i class="fa fa-microphone fa-lg"></i>
<translate>List of speakers</translate>
</a>
<!-- project -->
<projector-button model="motionBlock" default-projector-id="defaultProjectorId">
</projector-button>
<!-- edit -->
<a ng-click="openDialog(motionBlock)"
class="btn btn-default btn-sm"
title="{{ 'Edit' | translate}}">
<i class="fa fa-pencil"></i>
</a>
</div>
<h1>{{ motionBlock.agenda_item.getTitle() }}</h1>
<h2 translate>Motion Block</h2>
</div>
</div>
<div class="details">
{{ motionBlock.motions }}
</div>