diff --git a/app/assets/javascripts/angular/directives/work_packages/column-context-menu-directive.js b/app/assets/javascripts/angular/directives/work_packages/column-context-menu-directive.js deleted file mode 100644 index 08b2cf0fce..0000000000 --- a/app/assets/javascripts/angular/directives/work_packages/column-context-menu-directive.js +++ /dev/null @@ -1,72 +0,0 @@ -angular.module('openproject.workPackages.directives') - -.directive('columnContextMenu', [ - 'ContextMenuService', - 'I18n', - 'QueryService', - 'WorkPackagesTableHelper', - 'WorkPackagesTableService', - function(ContextMenuService, I18n, QueryService, WorkPackagesTableHelper, WorkPackagesTableService) { - - - return { - restrict: 'EA', - replace: true, - scope: {}, - templateUrl: '/templates/work_packages/column_context_menu.html', - link: function(scope, element, attrs) { - var contextMenuName = 'columnContextMenu'; - - // Wire up context menu handlers - - ContextMenuService.registerMenuElement(contextMenuName, element); - scope.contextMenu = ContextMenuService.getContextMenu(); - - scope.$watch('contextMenu.opened', function(opened) { - scope.opened = opened && scope.contextMenu.targetMenu === contextMenuName; - }); - scope.$watch('contextMenu.targetMenu', function(target) { - scope.opened = scope.contextMenu.opened && target === contextMenuName; - }); - - // shared context information - - scope.$watch('contextMenu.context.column', function(column) { - scope.column = column; - scope.isGroupable = WorkPackagesTableService.isGroupable(scope.column); - }); - scope.$watch('contextMenu.context.columns', function(columns) { - scope.columns = columns; - }); - - scope.I18n = I18n; - - // context menu actions - - scope.groupBy = function(columnName) { - QueryService.getQuery().groupBy = columnName; - }; - - scope.sortAscending = function(columnName) { - WorkPackagesTableService.sortBy(columnName, 'asc'); - }; - - scope.sortDescending = function(columnName) { - WorkPackagesTableService.sortBy(columnName, 'desc'); - }; - - scope.moveLeft = function(columnName) { - WorkPackagesTableHelper.moveColumnBy(scope.columns, columnName, -1); - }; - - scope.moveRight = function(columnName) { - WorkPackagesTableHelper.moveColumnBy(scope.columns, columnName, 1); - }; - - scope.hideColumn = function(columnName) { - ContextMenuService.close(); - QueryService.hideColumns(new Array(columnName)); - }; - } - }; -}]); diff --git a/app/assets/javascripts/angular/directives/work_packages/sort-header-directive.js b/app/assets/javascripts/angular/directives/work_packages/sort-header-directive.js index 00ad98974d..271ac30223 100644 --- a/app/assets/javascripts/angular/directives/work_packages/sort-header-directive.js +++ b/app/assets/javascripts/angular/directives/work_packages/sort-header-directive.js @@ -28,12 +28,13 @@ angular.module('openproject.workPackages.directives') -.directive('sortHeader', ['I18n', function(I18n){ +.directive('sortHeader', [ + 'I18n', + function(I18n){ return { restrict: 'A', templateUrl: '/templates/work_packages/sort_header.html', - transclude: true, scope: { query: '=', headerName: '=', @@ -41,7 +42,8 @@ angular.module('openproject.workPackages.directives') sortable: '=', locale: '=' }, - link: function(scope, element, attributes) { + require: 'hasDropdownMenu', + link: function(scope, element, attributes, dropdownMenuCtrl) { scope.$watch('query.sortation.sortElements', function(sortElements){ var latestSortElement = sortElements[0]; @@ -54,17 +56,6 @@ angular.module('openproject.workPackages.directives') setFullTitle(); }, true); - scope.$watch('currentSortDirection', function(sort) { - element.toggleClass('active-column', !!sort); - }); - - scope.performSort = function(){ - var targetSortation = scope.query.sortation.getTargetSortationOfHeader(scope.headerName); - - scope.query.setSortation(targetSortation); - scope.currentSortDirection = scope.query.sortation.getDisplayedSortDirectionOfHeader(scope.headerName); - }; - function setFullTitle() { if(!scope.sortable) scope.fullTitle = ''; @@ -72,9 +63,21 @@ angular.module('openproject.workPackages.directives') var sortDirectionText = (scope.currentSortDirection == 'asc') ? I18n.t('js.label_ascending') : I18n.t('js.label_descending'); scope.fullTitle = sortDirectionText + " " + I18n.t('js.label_sorted_by') + ' \"' + scope.headerTitle + '\"'; } else { - scope.fullTitle = (I18n.t('js.label_sort_by') + ' \"' + scope.headerTitle + '\"'); + scope.fullTitle = I18n.t('js.label_open_menu'); } } + + // active-column class setting + + function setActiveColumnClass() { + element.toggleClass('active-column', !!scope.currentSortDirection || scope.dropDownMenuOpened); + } + scope.$watch(dropdownMenuCtrl.opened, function(opened) { + scope.dropDownMenuOpened = opened; + setActiveColumnClass(); + }); + scope.$watch('currentSortDirection', setActiveColumnClass); + } }; }]); diff --git a/app/assets/javascripts/angular/directives/work_packages/work-package-context-menu-directive.js b/app/assets/javascripts/angular/directives/work_packages/work-package-context-menu-directive.js deleted file mode 100644 index c286e4d3dc..0000000000 --- a/app/assets/javascripts/angular/directives/work_packages/work-package-context-menu-directive.js +++ /dev/null @@ -1,106 +0,0 @@ -angular.module('openproject.workPackages.directives') - -.directive('workPackageContextMenu', [ - 'ContextMenuService', - 'WorkPackagesTableHelper', - 'WorkPackageContextMenuHelper', - 'WorkPackageService', - 'WorkPackagesTableService', - 'I18n', - '$window', - function(ContextMenuService, WorkPackagesTableHelper, WorkPackageContextMenuHelper, WorkPackageService, WorkPackagesTableService, I18n, $window) { - return { - restrict: 'EA', - replace: true, - scope: {}, - templateUrl: '/templates/work_packages/work_package_context_menu.html', - link: function(scope, element, attrs) { - var contextMenuName = 'workPackageContextMenu'; - - scope.I18n = I18n; - - scope.hideResourceActions = true; - - // wire up context menu event handler - ContextMenuService.registerMenuElement(contextMenuName, element); - scope.contextMenu = ContextMenuService.getContextMenu(); - - scope.$watch('contextMenu.opened', function(opened) { - scope.opened = opened && scope.contextMenu.targetMenu === contextMenuName; - }); - scope.$watch('contextMenu.targetMenu', function(target) { - scope.opened = scope.contextMenu.opened && target === contextMenuName; - }); - - scope.$watch('contextMenu.context.row', function(row) { - if (row && scope.contextMenu.targetMenu === contextMenuName) { - updateContextMenu(getWorkPackagesFromContext(scope.contextMenu.context)); - } - }); - - scope.triggerContextMenuAction = function(action, link) { - if (action === 'delete') { - deleteSelectedWorkPackages(); - } else { - $window.location.href = link; - } - }; - - function deleteSelectedWorkPackages() { - if (!deleteConfirmed()) return; - - var rows = WorkPackagesTableHelper.getSelectedRows(scope.contextMenu.context.rows); - - WorkPackageService.performBulkDelete(getWorkPackagesFromContext(scope.contextMenu.context)) - .success(function(data, status) { - // TODO wire up to API and processs API response - scope.$emit('flashMessage', { - isError: false, - text: I18n.t('js.work_packages.message_successful_bulk_delete') - }); - - WorkPackagesTableService.removeRows(rows); - }) - .error(function(data, status) { - // TODO wire up to API and processs API response - scope.$emit('flashMessage', { - isError: true, - text: I18n.t('js.work_packages.message_error_during_bulk_delete') - }); - }); - } - - function deleteConfirmed() { - return $window.confirm(I18n.t('js.text_work_packages_destroy_confirmation')); - } - - function updateContextMenu(workPackages) { - scope.permittedActions = WorkPackageContextMenuHelper.getPermittedActions(workPackages); - } - - function getWorkPackagesFromSelectedRows(rows) { - var selectedRows = WorkPackagesTableHelper.getSelectedRows(rows); - - return WorkPackagesTableHelper.getWorkPackagesFromRows(selectedRows); - } - - function getWorkPackagesFromContext(context) { - if (!context.row) return []; - - context.row.checked = true; - - var workPackagefromContext = context.row.object; - var workPackagesfromSelectedRows = getWorkPackagesFromSelectedRows(context.rows); - - if (workPackagesfromSelectedRows.length === 0) { - return [workPackagefromContext]; - } else if (workPackagesfromSelectedRows.indexOf(workPackagefromContext) === -1) { - return [workPackagefromContext].concat(workPackagesfromSelectedRows); - } else { - return workPackagesfromSelectedRows; - } - } - - } - }; -}]); diff --git a/app/assets/javascripts/angular/openproject-app.js b/app/assets/javascripts/angular/openproject-app.js index e402932f98..60f7ba3bcf 100644 --- a/app/assets/javascripts/angular/openproject-app.js +++ b/app/assets/javascripts/angular/openproject-app.js @@ -67,7 +67,8 @@ angular.module('openproject.workPackages', [ 'openproject.workPackages.controllers', 'openproject.workPackages.filters', 'openproject.workPackages.directives', - 'openproject.uiComponents' + 'openproject.uiComponents', + 'ng-context-menu' ]); angular.module('openproject.workPackages.services', []); angular.module('openproject.workPackages.helpers', [ @@ -88,8 +89,7 @@ angular.module('openproject.workPackages.controllers', [ angular.module('openproject.workPackages.directives', [ 'openproject.uiComponents', 'openproject.services', - 'openproject.workPackages.services', - 'ng-context-menu' + 'openproject.workPackages.services' ]); // messages diff --git a/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js b/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js new file mode 100644 index 0000000000..c3ad1f56b5 --- /dev/null +++ b/app/assets/javascripts/angular/ui_components/has-dropdown-menu-directive.js @@ -0,0 +1,131 @@ +angular.module('openproject.uiComponents') + +.directive('hasDropdownMenu', [ + '$injector', + '$window', + '$parse', + function($injector, $window, $parse) { + + function getCssPositionProperties(dropdown, trigger) { + var hOffset = 0, + vOffset = 0; + + // Styling logic taken from jQuery-dropdown plugin: https://github.com/plapier/jquery-dropdown + // (dual MIT/GPL-Licensed) + + // Position the dropdown relative-to-parent or relative-to-document + if (dropdown.hasClass('dropdown-relative')) { + return { + left: dropdown.hasClass('dropdown-anchor-right') ? + trigger.position().left - (dropdown.outerWidth(true) - trigger.outerWidth(true)) - parseInt(trigger.css('margin-right')) + hOffset : + trigger.position().left + parseInt(trigger.css('margin-left')) + hOffset, + top: trigger.position().top + trigger.outerHeight(true) - parseInt(trigger.css('margin-top')) + vOffset + }; + } else { + return { + left: dropdown.hasClass('dropdown-anchor-right') ? + trigger.offset().left - (dropdown.outerWidth() - trigger.outerWidth()) + hOffset : trigger.offset().left + hOffset, + top: trigger.offset().top + trigger.outerHeight() + vOffset + }; + } + } + + return { + restrict: 'A', + controller: [function() { + var dropDownMenuOpened = false; + + this.open = function() { + dropDownMenuOpened = true; + }; + this.close = function() { + dropDownMenuOpened = false; + }; + this.opened = function() { + return dropDownMenuOpened; + }; + }], + link: function(scope, element, attrs, ctrl) { + var contextMenu = $injector.get(attrs.target), + locals = {}, + win = angular.element($window), + menuElement, + triggerOnEvent = attrs.triggerOnEvent || 'click'; + + /* contextMenu is a mandatory attribute and used to bind a specific context + menu to the trigger event + triggerOnEvent allows for binding the event for opening the menu to "click" */ + + // prepare locals, these define properties to be passed on to the context menu scope + var localKeys = attrs.locals.split(',').map(function(local) { + return local.trim(); + }); + angular.forEach(localKeys, function(key) { + locals[key] = scope[key]; + }); + + function toggle() { + active() ? close() : open(); + } + + function active() { + return contextMenu.active() && ctrl.opened(); + } + + function open() { + ctrl.open(); + + contextMenu.open(locals) + .then(function(menuElement) { + menuElement.css(getCssPositionProperties(menuElement, element)); + }); + } + + function close() { + ctrl.close(); + + contextMenu.close(); + } + + element.bind(triggerOnEvent, function(event) { + event.preventDefault(); + event.stopPropagation(); + + scope.$apply(function() { + toggle(); + }); + + scope.$root.$broadcast('openproject.markDropdownsAsClosed', element); + }); + + scope.$on('openproject.markDropdownsAsClosed', function(event, target) { + if (element !== target && ctrl.opened()) { + scope.$apply(ctrl.close); + } + }); + + + win.bind('keyup', function(event) { + if (contextMenu.active() && event.keyCode === 27) { + scope.$apply(function() { + close(); + }); + } + }); + + function handleWindowClickEvent(event) { + if (contextMenu.active() && event.button !== 2) { + + scope.$apply(function() { + close(); + }); + } + } + + // Firefox treats a right-click as a click and a contextmenu event while other browsers + // just treat it as a contextmenu event + win.bind('click', handleWindowClickEvent); + win.bind(triggerOnEvent, handleWindowClickEvent); + } + }; +}]); diff --git a/app/assets/javascripts/angular/work_packages/column-context-menu.js b/app/assets/javascripts/angular/work_packages/column-context-menu.js new file mode 100644 index 0000000000..74224a4b66 --- /dev/null +++ b/app/assets/javascripts/angular/work_packages/column-context-menu.js @@ -0,0 +1,85 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2014 the OpenProject Foundation (OPF) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See doc/COPYRIGHT.rdoc for more details. +//++ + +angular.module('openproject.workPackages') + +.factory('ColumnContextMenu', [ + 'ngContextMenu', + function(ngContextMenu) { + + return ngContextMenu({ + controller: 'ColumnContextMenuController', + controllerAs: 'contextMenu', + templateUrl: '/templates/work_packages/column_context_menu.html' + }); +}]) + +.controller('ColumnContextMenuController', [ + '$scope', + 'ColumnContextMenu', + 'I18n', + 'QueryService', + 'WorkPackagesTableHelper', + 'WorkPackagesTableService', + 'columnsModal', + function($scope, ColumnContextMenu, I18n, QueryService, WorkPackagesTableHelper, WorkPackagesTableService, columnsModal) { + + $scope.I18n = I18n; + $scope.isGroupable = WorkPackagesTableService.isGroupable($scope.column); + + // context menu actions + + $scope.groupBy = function(columnName) { + QueryService.getQuery().groupBy = columnName; + }; + + $scope.sortAscending = function(columnName) { + WorkPackagesTableService.sortBy(columnName || 'id', 'asc'); + }; + + $scope.sortDescending = function(columnName) { + WorkPackagesTableService.sortBy(columnName || 'id', 'desc'); + }; + + $scope.moveLeft = function(columnName) { + WorkPackagesTableHelper.moveColumnBy($scope.columns, columnName, -1); + }; + + $scope.moveRight = function(columnName) { + WorkPackagesTableHelper.moveColumnBy($scope.columns, columnName, 1); + }; + + $scope.hideColumn = function(columnName) { + ColumnContextMenu.close(); + QueryService.hideColumns(new Array(columnName)); + }; + + $scope.insertColumns = function() { + columnsModal.activate(); + }; +}]); diff --git a/app/assets/javascripts/angular/work_packages/work-package-context-menu.js b/app/assets/javascripts/angular/work_packages/work-package-context-menu.js new file mode 100644 index 0000000000..a878384391 --- /dev/null +++ b/app/assets/javascripts/angular/work_packages/work-package-context-menu.js @@ -0,0 +1,117 @@ +//-- copyright +// OpenProject is a project management system. +// Copyright (C) 2012-2014 the OpenProject Foundation (OPF) +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License version 3. +// +// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +// Copyright (C) 2006-2013 Jean-Philippe Lang +// Copyright (C) 2010-2013 the ChiliProject Team +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// +// See doc/COPYRIGHT.rdoc for more details. +//++ + +angular.module('openproject.workPackages') + +.factory('WorkPackageContextMenu', [ + 'ngContextMenu', + function(ngContextMenu) { + + return ngContextMenu({ + controller: 'WorkPackageContextMenuController', + controllerAs: 'contextMenu', + templateUrl: '/templates/work_packages/work_package_context_menu.html' + }); +}]) + +.controller('WorkPackageContextMenuController', [ + '$scope', + 'WorkPackagesTableHelper', + 'WorkPackageContextMenuHelper', + 'WorkPackageService', + 'WorkPackagesTableService', + 'I18n', + '$window', + function($scope, WorkPackagesTableHelper, WorkPackageContextMenuHelper, WorkPackageService, WorkPackagesTableService, I18n, $window) { + + $scope.I18n = I18n; + + $scope.hideResourceActions = true; + + $scope.$watch('row', function() { + $scope.row.checked = true; + $scope.permittedActions = WorkPackageContextMenuHelper.getPermittedActions(getSelectedWorkPackages()); + }); + + + $scope.triggerContextMenuAction = function(action, link) { + if (action === 'delete') { + deleteSelectedWorkPackages(); + } else { + $window.location.href = link; + } + }; + + function deleteSelectedWorkPackages() { + if (!deleteConfirmed()) return; + + var rows = WorkPackagesTableHelper.getSelectedRows($scope.rows); + + WorkPackageService.performBulkDelete(getSelectedWorkPackages()) + .success(function(data, status) { + // TODO wire up to API and processs API response + $scope.$emit('flashMessage', { + isError: false, + text: I18n.t('js.work_packages.message_successful_bulk_delete') + }); + + WorkPackagesTableService.removeRows(rows); + }) + .error(function(data, status) { + // TODO wire up to API and processs API response + $scope.$emit('flashMessage', { + isError: true, + text: I18n.t('js.work_packages.message_error_during_bulk_delete') + }); + }); + } + + function deleteConfirmed() { + return $window.confirm(I18n.t('js.text_work_packages_destroy_confirmation')); + } + + function getWorkPackagesFromSelectedRows() { + var selectedRows = WorkPackagesTableHelper.getSelectedRows($scope.rows); + + return WorkPackagesTableHelper.getWorkPackagesFromRows(selectedRows); + } + + function getSelectedWorkPackages() { + var workPackagefromContext = $scope.row.object; + var workPackagesfromSelectedRows = getWorkPackagesFromSelectedRows(); + + if (workPackagesfromSelectedRows.length === 0) { + return [workPackagefromContext]; + } else if (workPackagesfromSelectedRows.indexOf(workPackagefromContext) === -1) { + return [workPackagefromContext].concat(workPackagesfromSelectedRows); + } else { + return workPackagesfromSelectedRows; + } + } + +}]); diff --git a/app/assets/javascripts/application.js.erb b/app/assets/javascripts/application.js.erb index ddb3472e8b..e3c89db917 100644 --- a/app/assets/javascripts/application.js.erb +++ b/app/assets/javascripts/application.js.erb @@ -66,7 +66,7 @@ //= require angular-truncate //= require angular-feature-flags/dist/featureFlags.js -//= require ng-context-menu +//= require angular-context-menu //= require openproject-ui_components/app/assets/javascripts/angular/ui-components-app //= require ./angular/openproject-app diff --git a/app/assets/stylesheets/content/_context_menu.sass b/app/assets/stylesheets/content/_context_menu.sass index dd4aabf581..e24c053831 100644 --- a/app/assets/stylesheets/content/_context_menu.sass +++ b/app/assets/stylesheets/content/_context_menu.sass @@ -37,6 +37,7 @@ #work-package-context-menu, #column-context-menu &.action-menu position: absolute + z-index: 1000 .hascontextmenu cursor: context-menu diff --git a/app/assets/stylesheets/layout/_drop_down.sass b/app/assets/stylesheets/layout/_drop_down.sass index ef732e9226..2033d0932b 100644 --- a/app/assets/stylesheets/layout/_drop_down.sass +++ b/app/assets/stylesheets/layout/_drop_down.sass @@ -36,6 +36,9 @@ #settingsDropdown, #tasksDropdown margin: 10px 0 0 0 +#column-context-menu + margin: 55px 0 0 0 + .dropdown position: absolute z-index: 9999999 diff --git a/bower.json b/bower.json index 92c712fc6d..b564140c32 100644 --- a/bower.json +++ b/bower.json @@ -22,7 +22,7 @@ "jquery-migrate": "~1.2.1", "momentjs": "~2.6.0", "moment-timezone": "~0.0.6", - "ng-context-menu": "finnlabs/ng-context-menu#context-sharing-with-multiple-targets" + "angular-context-menu": "0.1.1" }, "devDependencies": { "mocha": "~1.14.0", diff --git a/config/locales/de.yml b/config/locales/de.yml index 9f307aca8e..350c3e52c2 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -801,6 +801,7 @@ de: label_not_contains: "enthält nicht" label_not_equals: "ist nicht" label_notify_member_plural: "Aktualisierungen per E-Mail verschicken" + label_open_menu: "Menü öffnen" label_open_work_packages: "offen" label_open_work_packages_plural: "offen" label_optional_description: "Beschreibung" diff --git a/config/locales/en.yml b/config/locales/en.yml index bb11dcb6e7..a42d197d3b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -798,6 +798,7 @@ en: label_not_contains: "doesn't contain" label_not_equals: "is not" label_notify_member_plural: "Email updates" + label_open_menu: "Open menu" label_open_work_packages: "open" label_open_work_packages_plural: "open" label_optional_description: "Description" diff --git a/config/locales/js-de.yml b/config/locales/js-de.yml index 38500a4c2b..f2a7e38df5 100644 --- a/config/locales/js-de.yml +++ b/config/locales/js-de.yml @@ -217,6 +217,7 @@ de: move_column_left: "Spalte nach links" move_column_right: "Spalte nach rechts" hide_column: "Spalte verbergen" + insert_columns: "Spalten hinzufügen ..." filters: "Filter" display_sums: "Summen anzeigen" toolbar: diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index 66ccbd8833..e26964c6da 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -220,6 +220,7 @@ en: move_column_left: "Move column left" move_column_right: "Move column right" hide_column: "Hide column" + insert_columns: "Insert columns ..." filters: "Filters" display_sums: "Display Sums" toolbar: diff --git a/karma.conf.js b/karma.conf.js index f11d4fdd40..290e045137 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -29,8 +29,8 @@ module.exports = function(config) { "vendor/assets/components/angular-truncate/src/truncate.js", "vendor/assets/components/angular-sanitize/angular-sanitize.js", "vendor/assets/components/momentjs/moment.js", + "vendor/assets/components/angular-context-menu/dist/angular-context-menu.js", 'vendor/assets/components/select2/select2.js', - "vendor/assets/components/ng-context-menu/dist/ng-context-menu.js", "vendor/assets/components/openproject-ui_components/app/assets/javascripts/angular/ui-components-app.js", "app/assets/javascripts/angular/openproject-app.js", @@ -102,6 +102,8 @@ module.exports = function(config) { "app/assets/javascripts/angular/controllers/dialogs/share.js", "app/assets/javascripts/angular/controllers/dialogs/sorting.js", + "app/assets/javascripts/angular/work_packages/**/*.js", + 'app/assets/javascripts/autocompleter.js', 'app/assets/javascripts/members_select_boxes.js', 'app/assets/javascripts/openproject.js', @@ -113,7 +115,7 @@ module.exports = function(config) { 'karma/lib/rosie.js', 'karma/tests/test-helper.js', 'karma/factories/*factory.js', - + 'vendor/assets/components/jquery-mockjax/jquery.mockjax.js', 'karma/tests/asset_functions.js', diff --git a/karma/tests/directives/work_packages/sort-header-directive-test.js b/karma/tests/directives/work_packages/sort-header-directive-test.js index ae855a1ef5..97f71606c3 100644 --- a/karma/tests/directives/work_packages/sort-header-directive-test.js +++ b/karma/tests/directives/work_packages/sort-header-directive-test.js @@ -43,9 +43,19 @@ describe('sortHeader Directive', function() { rootScope = $rootScope; scope = $rootScope.$new(); + // Mock hasDropdownManu controller + var dropdownMenuController = function() { + this.open = function() { + return true; + }; + }; + compile = function() { - $compile(element1)(scope); - $compile(element2)(scope); + angular.forEach([element1, element2], function(element){ + element.data('$hasDropdownMenuController', dropdownMenuController); + $compile(element)(scope); + }); + scope.$digest(); }; })); @@ -57,8 +67,10 @@ describe('sortHeader Directive', function() { })); describe('rendering multiple headers', function(){ + var query; + beforeEach(function(){ - var query = new Query({ + query = new Query({ }); query.setSortation(new Sortation([])); scope.query = query; @@ -93,10 +105,12 @@ describe('sortHeader Directive', function() { var link1 = element1.find('span a').first(); expect(link1.hasClass('sort asc')).to.not.be.ok; - link1.click(); + query.sortation.addSortElement({ field: scope.headerName1, direction: 'asc' }); + scope.$digest(); expect(link1.hasClass('sort asc')).to.be.ok; - link1.click(); + query.sortation.addSortElement({ field: scope.headerName1, direction: 'desc' }); + scope.$digest(); expect(link1.hasClass('sort desc')).to.be.ok; }); @@ -108,11 +122,15 @@ describe('sortHeader Directive', function() { scope.$apply(); var link1 = element1.find('span a').first(); - link1.click(); + query.sortation.addSortElement({ field: scope.headerName1, direction: 'asc' }); + scope.$digest(); + expect(link1.hasClass('sort asc')).to.be.ok; var link2 = element2.find('span a').first(); - link2.click(); + query.sortation.addSortElement({ field: scope.headerName2, direction: 'asc' }); + scope.$digest(); + expect(link2.hasClass('sort asc')).to.be.ok; expect(link1.hasClass('sort asc')).to.not.be.ok; }); diff --git a/karma/tests/directives/work_packages/column-context-menu-directive-test.js b/karma/tests/work_packages/column-context-menu-test.js similarity index 62% rename from karma/tests/directives/work_packages/column-context-menu-directive-test.js rename to karma/tests/work_packages/column-context-menu-test.js index e616235ea8..38e23f4c44 100644 --- a/karma/tests/directives/work_packages/column-context-menu-directive-test.js +++ b/karma/tests/work_packages/column-context-menu-test.js @@ -28,37 +28,36 @@ /*jshint expr: true*/ -describe('columnContextMenu Directive', function() { - var compile, element, rootScope, scope; - - beforeEach(angular.mock.module('openproject.workPackages.directives')); - beforeEach(module('templates', 'openproject.models')); - - beforeEach(inject(function($rootScope, $compile, _ContextMenuService_) { - var html; - html = ''; +describe('columnContextMenu', function() { + var container, contextMenu, $rootScope, scope; + + beforeEach(module('ng-context-menu', + 'openproject.workPackages', + 'openproject.workPackages.controllers', + 'openproject.models', + 'templates')); + + beforeEach(function() { + var html = '
'; + container = angular.element(html); + }); - element = angular.element(html); - rootScope = $rootScope; - scope = $rootScope.$new(); - ContextMenuService = _ContextMenuService_; + beforeEach(inject(function(_$rootScope_, _ngContextMenu_, $templateCache) { + $rootScope = _$rootScope_; + ngContextMenu = _ngContextMenu_; - compile = function() { - $compile(element)(scope); - scope.$digest(); - }; - })); + var template = $templateCache.get('/templates/work_packages/column_context_menu.html'); + $templateCache.put('column_context_menu.html', [200, template, {}]); - describe('element', function() { - beforeEach(function() { - compile(); + contextMenu = ngContextMenu({ + controller: 'ColumnContextMenuController', + controllerAs: 'contextMenu', + container: container, + templateUrl: 'column_context_menu.html' }); - it('should render a surrounding div', function() { - expect(element.prop('tagName')).to.equal('DIV'); - }); - - }); + contextMenu.open({x: 0, y: 0}); + })); describe('when the context menu handler of a column is clicked', function() { var I18n, QueryService; @@ -77,22 +76,20 @@ describe('columnContextMenu Directive', function() { })); beforeEach(function() { - compile(); + $rootScope.column = column; + $rootScope.columns = columns; + $rootScope.$digest(); - ContextMenuService.setContext({ column: column, columns: columns }); - ContextMenuService.open('columnContextMenu'); - scope.$apply(); - - directiveScope = element.children().scope(); + scope = container.children().scope(); }); it('fetches the column from the context handle context', function() { - expect(directiveScope.column).to.have.property('name').and.contain(column.name); + expect($rootScope.column).to.have.property('name').and.contain(column.name); }); describe('and the group by option is clicked', function() { beforeEach(function() { - directiveScope.groupBy(column.name); + scope.groupBy(column.name); }); it('changes the query group by', function() { @@ -102,7 +99,7 @@ describe('columnContextMenu Directive', function() { describe('and "move column right" is clicked', function() { beforeEach(function() { - directiveScope.moveRight(column.name); + scope.moveRight(column.name); }); it('moves the column right', function() { @@ -116,7 +113,7 @@ describe('columnContextMenu Directive', function() { beforeEach(inject(function(_Sortation_) { Sortation = _Sortation_; query.sortation = new Sortation(); - directiveScope.sortAscending(column.name); + scope.sortAscending(column.name); })); it('updates the query sortation', function() { @@ -126,12 +123,33 @@ describe('columnContextMenu Directive', function() { describe('and "Hide column" is clicked', function() { beforeEach(function() { - directiveScope.hideColumn(column.name); + scope.hideColumn(column.name); }); it('removes the column from the query columns', function() { expect(query.columns).to.not.include(column); }); }); + + describe('and "Insert columns" is clicked', function() { + var activateFn, columnsModal; + + beforeEach(inject(function(_columnsModal_) { + columnsModal = _columnsModal_; + activateFn = sinon.stub(columnsModal, 'activate'); + })); + afterEach(inject(function() { + columnsModal.activate.restore(); + })); + + beforeEach(function() { + scope.insertColumns(); + }); + + it('opens the columns dialog', function() { + expect(activateFn).to.have.been.called; + }); + }); + }); }); diff --git a/karma/tests/directives/work_packages/work-package-context-menu-directive-test.js b/karma/tests/work_packages/work-package-context-menu-test.js similarity index 66% rename from karma/tests/directives/work_packages/work-package-context-menu-directive-test.js rename to karma/tests/work_packages/work-package-context-menu-test.js index d8290e8e38..c676926949 100644 --- a/karma/tests/directives/work_packages/work-package-context-menu-directive-test.js +++ b/karma/tests/work_packages/work-package-context-menu-test.js @@ -1,3 +1,4 @@ + //-- copyright // OpenProject is a project management system. // Copyright (C) 2012-2014 the OpenProject Foundation (OPF) @@ -28,37 +29,35 @@ /*jshint expr: true*/ -describe('workPackageContextMenu Directive', function() { - var compile, element, rootScope, scope; +describe('workPackageContextMenu', function() { + var container, contextMenu, $rootScope; - beforeEach(angular.mock.module('openproject.workPackages.directives')); - beforeEach(module('templates', 'openproject.models')); + beforeEach(module('ng-context-menu', + 'openproject.workPackages', + 'openproject.models', + 'templates')); - beforeEach(inject(function($rootScope, $compile, _ContextMenuService_) { - var html; - html = ''; + beforeEach(function() { + var html = '
'; + container = angular.element(html); + }); - element = angular.element(html); - rootScope = $rootScope; - scope = $rootScope.$new(); - ContextMenuService = _ContextMenuService_; + beforeEach(inject(function(_$rootScope_, _ngContextMenu_, $templateCache) { + $rootScope = _$rootScope_; + ngContextMenu = _ngContextMenu_; - compile = function() { - $compile(element)(scope); - scope.$digest(); - }; - })); + var template = $templateCache.get('/templates/work_packages/work_package_context_menu.html'); + $templateCache.put('work_package_context_menu.html', [200, template, {}]); - describe('element', function() { - beforeEach(function() { - compile(); + contextMenu = ngContextMenu({ + controller: 'WorkPackageContextMenuController', + controllerAs: 'contextMenu', + container: container, + templateUrl: 'work_package_context_menu.html' }); - it('should render a surrounding div', function() { - expect(element.prop('tagName')).to.equal('DIV'); - }); - - }); + contextMenu.open({x: 0, y: 0}); + })); describe('when the context menu context contains one work package', function() { var I18n; @@ -73,16 +72,6 @@ describe('workPackageContextMenu Directive', function() { }); var directListElements; - beforeEach(function() { - compile(); - - ContextMenuService.setContext({rows: [], row: {object: workPackage}}); - ContextMenuService.open('workPackageContextMenu'); - scope.$apply(); - - directListElements = element.find('.menu > li:not(.folder)'); - }); - beforeEach(inject(function(_I18n_) { I18n = _I18n_; sinon.stub(I18n, 't').withArgs('js.button_' + actions[0]).returns('anything'); @@ -91,6 +80,15 @@ describe('workPackageContextMenu Directive', function() { I18n.t.restore(); })); + beforeEach(function() { + $rootScope.rows = []; + $rootScope.row = {object: workPackage}; + + $rootScope.$digest(); + + directListElements = container.find('.menu > li:not(.folder)'); + }); + it('lists link tags for any permitted action', function(){ expect(directListElements.length).to.equal(2); }); @@ -99,17 +97,17 @@ describe('workPackageContextMenu Directive', function() { expect(directListElements[0].className).to.equal(actions[0]); }); - it('adds an icon from the icon fonts to each list element', function() { - expect(element.find('.' + actions[0] +' a').attr('class')).to.include('icon-' + actions[0]); + it('adds an icon from the icon fonts to each list container', function() { + expect(container.find('.' + actions[0] +' a').attr('class')).to.include('icon-' + actions[0]); }); xit('translates the action name', function() { - expect(element.find('.' + actions[0] +' a').contents()).to.equal('anything'); + expect(container.find('.' + actions[0] +' a').contents()).to.equal('anything'); // TODO find out how to stub I18n.t inside directive }); it('sets the checked property of the row within the context to true', function() { - expect(ContextMenuService.getContextMenu().context.row.checked).to.be.true; + expect($rootScope.row.checked).to.be.true; }); describe('when delete is permitted on a work package', function() { @@ -123,10 +121,11 @@ describe('workPackageContextMenu Directive', function() { }); beforeEach(function() { - ContextMenuService.setContext({rows: [], row: {object: workPackage}}); - compile(); + $rootScope.rows = []; + $rootScope.row = {object: workPackage}; + $rootScope.$digest(); - directListElements = element.find('.menu > li:not(.folder)'); + directListElements = container.find('.menu > li:not(.folder)'); }); it('displays a link triggering deleteWorkPackages within the scope', function() { diff --git a/public/templates/work_packages.list.html b/public/templates/work_packages.list.html index 8e98003418..98d2c4df8c 100644 --- a/public/templates/work_packages.list.html +++ b/public/templates/work_packages.list.html @@ -23,9 +23,6 @@ - - -
diff --git a/public/templates/work_packages/column_context_menu.html b/public/templates/work_packages/column_context_menu.html index 0def064bce..e40359a9dc 100644 --- a/public/templates/work_packages/column_context_menu.html +++ b/public/templates/work_packages/column_context_menu.html @@ -1,13 +1,5 @@ -
-