Signed-off-by: Alex Coles <alex@alexbcoles.com> Conflicts: app/assets/javascripts/angular/openproject-app.js app/views/work_packages/_list.html.erbpull/1315/head
commit
f5f98e81de
@ -0,0 +1,70 @@ |
||||
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.$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) { |
||||
QueryService.hideColumns(new Array(columnName)); |
||||
}; |
||||
} |
||||
}; |
||||
}]); |
@ -1 +1,51 @@ |
||||
# Action menu |
||||
|
||||
``` |
||||
<div class="action-menu"> |
||||
<ul class="menu"> |
||||
<li> |
||||
<a href="#"><i class="icon-edit icon-actionmenu"></i>menu item for modal...</a> |
||||
</li> |
||||
|
||||
<li> |
||||
<a href="#"><i class="icon-yes icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
|
||||
<li> |
||||
<a href="#"><i class="icon-copy icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
|
||||
<li class="submenu-item"> |
||||
<a href="#"><i class="icon-priority icon-actionmenu"></i>menu item with sub</a><i class="icon-pulldown-arrow4 icon-submenu"></i> |
||||
</li> |
||||
<li> |
||||
<a href="#"><i class="icon-delete icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
|
||||
<li class="hasnoicon"> |
||||
<a href="#">menu item no icon</a> |
||||
</li> |
||||
|
||||
<li class="dropdown-divider"></li> |
||||
|
||||
<li> |
||||
<a href="#"><i class="icon-time icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
|
||||
</ul> |
||||
</div> |
||||
<div id="submenu" class="action-menu"> |
||||
<ul class="menu"> |
||||
<li> |
||||
<a href="#"><i class="icon-edit icon-actionmenu"></i>menu item for modal...</a> |
||||
</li> |
||||
<li> |
||||
<a href="#"><i class="icon-yes icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
<li> |
||||
<a href="#"><i class="icon-copy icon-actionmenu"></i>menu item</a> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
|
||||
``` |
||||
|
@ -0,0 +1,137 @@ |
||||
//-- 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.
|
||||
//++
|
||||
|
||||
/*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 = '<column-context-menu></column-context-menu>'; |
||||
|
||||
element = angular.element(html); |
||||
rootScope = $rootScope; |
||||
scope = $rootScope.$new(); |
||||
ContextMenuService = _ContextMenuService_; |
||||
|
||||
compile = function() { |
||||
$compile(element)(scope); |
||||
scope.$digest(); |
||||
}; |
||||
})); |
||||
|
||||
describe('element', function() { |
||||
beforeEach(function() { |
||||
compile(); |
||||
}); |
||||
|
||||
it('should render a surrounding div', function() { |
||||
expect(element.prop('tagName')).to.equal('DIV'); |
||||
}); |
||||
|
||||
}); |
||||
|
||||
describe('when the context menu handler of a column is clicked', function() { |
||||
var I18n, QueryService; |
||||
var column = { name: 'status', title: 'Status' }, |
||||
anotherColumn = { name: 'subject', title: 'Subject' }, |
||||
columns = [column, anotherColumn], |
||||
query = Factory.build('Query', { columns: columns }); |
||||
var directiveScope; |
||||
|
||||
beforeEach(inject(function(_QueryService_) { |
||||
QueryService = _QueryService_; |
||||
sinon.stub(QueryService, 'getQuery').returns(query); |
||||
})); |
||||
afterEach(inject(function() { |
||||
QueryService.getQuery.restore(); |
||||
})); |
||||
|
||||
beforeEach(function() { |
||||
compile(); |
||||
|
||||
ContextMenuService.setContext({ column: column, columns: columns }); |
||||
ContextMenuService.open('columnContextMenu'); |
||||
scope.$apply(); |
||||
|
||||
directiveScope = element.children().scope(); |
||||
}); |
||||
|
||||
it('fetches the column from the context handle context', function() { |
||||
expect(directiveScope.column).to.have.property('name').and.contain(column.name); |
||||
}); |
||||
|
||||
describe('and the group by option is clicked', function() { |
||||
beforeEach(function() { |
||||
directiveScope.groupBy(column.name); |
||||
}); |
||||
|
||||
it('changes the query group by', function() { |
||||
expect(query.groupBy).to.equal(column.name); |
||||
}); |
||||
}); |
||||
|
||||
describe('and "move column right" is clicked', function() { |
||||
beforeEach(function() { |
||||
directiveScope.moveRight(column.name); |
||||
}); |
||||
|
||||
it('moves the column right', function() { |
||||
expect(columns[1]).to.equal(column); |
||||
}); |
||||
}); |
||||
|
||||
describe('and "Sort ascending" is clicked', function() { |
||||
var Sortation; |
||||
|
||||
beforeEach(inject(function(_Sortation_) { |
||||
Sortation = _Sortation_; |
||||
query.sortation = new Sortation(); |
||||
directiveScope.sortAscending(column.name); |
||||
})); |
||||
|
||||
it('updates the query sortation', function() { |
||||
expect(query.sortation.getPrimarySortationCriterion()).to.deep.equal({ field: column.name, direction: 'asc' }); |
||||
}); |
||||
}); |
||||
|
||||
describe('and "Hide column" is clicked', function() { |
||||
beforeEach(function() { |
||||
directiveScope.hideColumn(column.name); |
||||
}); |
||||
|
||||
it('removes the column from the query columns', function() { |
||||
expect(query.columns).to.not.include(column); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,28 @@ |
||||
<div id="column-context-menu" class="action-menu" ng-show="opened"> |
||||
<ul class="menu"> |
||||
<li ng-click="groupBy(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.work_packages.query.group_by')"/> {{column.title}}</a> |
||||
</li> |
||||
|
||||
<li ng-click="sortAscending(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.label_sort_by')"/> <span ng-bind="column.title"/> <span ng-bind="I18n.t('js.label_ascending')"/></a> |
||||
</li> |
||||
|
||||
<li ng-click="sortDescending(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.label_sort_by')"/> <span ng-bind="column.title"/> <span ng-bind="I18n.t('js.label_descending')"/></a> |
||||
</li> |
||||
|
||||
<li ng-click="moveLeft(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.label_move_column_left')"/></a> |
||||
</li> |
||||
|
||||
<li ng-click="moveRight(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.label_move_column_right')"/></a> |
||||
</li> |
||||
|
||||
<li ng-click="hideColumn(column.name)"> |
||||
<a href="#"><span ng-bind="I18n.t('js.label_hide_column')"/></a> |
||||
</li> |
||||
|
||||
</ul> |
||||
</div> |
@ -0,0 +1,40 @@ |
||||
require 'spec_helper' |
||||
|
||||
describe "layouts/base" do |
||||
include Redmine::MenuManager::MenuHelper |
||||
helper Redmine::MenuManager::MenuHelper |
||||
let!(:user) { FactoryGirl.create :user } |
||||
let!(:anonymous) { FactoryGirl.create(:anonymous) } |
||||
|
||||
before do |
||||
view.stub(:current_menu_item).and_return("overview") |
||||
view.stub(:default_breadcrumb) |
||||
controller.stub(:default_search_scope) |
||||
end |
||||
|
||||
describe "projects menu visibility" do |
||||
context "when the user is not logged in" do |
||||
before do |
||||
User.stub(:current).and_return anonymous |
||||
view.stub(:current_user).and_return anonymous |
||||
render |
||||
end |
||||
|
||||
it "the projects menu should not be displayed" do |
||||
expect(response).to_not have_text("Projects") |
||||
end |
||||
end |
||||
|
||||
context "when the user is logged in" do |
||||
before do |
||||
User.stub(:current).and_return user |
||||
view.stub(:current_user).and_return user |
||||
render |
||||
end |
||||
|
||||
it "the projects menu should be displayed" do |
||||
expect(response).to have_text("Projects") |
||||
end |
||||
end |
||||
end |
||||
end |
Loading…
Reference in new issue