[22851] Table Inline Create (#4290)
* Move the WP create button to the WP buttons directory * Move the keepTab service to the wp-panels directory * Move angular module definitions into separate file * Move the WP create button into a separate file * Use TS in WP create button directive * Refactor the WP create button controller * Move the has-context-menu directive to the components directory * Refactor has-context-menu item focus * Create new button directive and place in table * Export and use wpButton module * Add title to form properties * Add -new modifier to new work package rows * Add WP inline create directive * Reduce error widths to cell span * Save or update work package conditionally * Mark work package as new * Implement the WP inline create button * Style button correctly * Decide when to activate the field for new WPs * Remove padding on -new row * Avoid animation on added -new rows * Avoid writing title when passed from new workPackage * Remove jqMigrate warning * Avoid animations for newly added rows * Add cancel button to inline create row * Track new work packages by custom id If we don't track new work packages, ng-animate will freak out due to the track by expression. We thus track work packages by setting a custom id set from the current date. * Disable focus on errors outside accessibility mode * Add inline create cancel context menu * Conditionally set focus on fields Set focus on the first active fieldpull/4333/head
parent
74e01bf7d9
commit
02cf2b0efe
@ -0,0 +1,67 @@ |
||||
//-- copyright |
||||
// OpenProject is a project management system. |
||||
// Copyright (C) 2012-2015 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. |
||||
//++ |
||||
|
||||
|
||||
.wp-inline-create-button-row td |
||||
padding: 0 |
||||
|
||||
.wp-inline-create-button |
||||
border-bottom: none |
||||
|
||||
a |
||||
width: 100% |
||||
padding: 0.5rem 0 |
||||
display: block |
||||
line-height: 1.6 |
||||
|
||||
&:hover |
||||
background: #e4f7fb |
||||
|
||||
|
||||
.wp-inline-create--add-link |
||||
font-weight: bold |
||||
|
||||
.icon::before |
||||
padding-left: 7px |
||||
padding-right: 10px |
||||
font-size: 11px |
||||
|
||||
&:hover |
||||
text-decoration: none |
||||
|
||||
.wp--row.-new, |
||||
tr.context-menu-selection.-new |
||||
background: #BEF3CA |
||||
|
||||
&:hover |
||||
background: darken(#BEF3CA, 5%) |
||||
|
||||
.wp-table--cell-span:hover |
||||
border-color: #35c53f |
||||
|
||||
|
@ -0,0 +1,172 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
declare var I18n:op.I18n; |
||||
|
||||
// global
|
||||
angular.module('openproject.uiComponents', |
||||
['ui.select', 'ui.router', 'ngSanitize', 'openproject.workPackages.services']) |
||||
.run(['$rootScope', function($rootScope){ |
||||
$rootScope.I18n = I18n; |
||||
}]); |
||||
angular.module('openproject.config', []); |
||||
angular.module( |
||||
'openproject.services', [ |
||||
'openproject.uiComponents', |
||||
'openproject.helpers', |
||||
'openproject.workPackages.config', |
||||
'openproject.workPackages.helpers', |
||||
'openproject.api', |
||||
'angular-cache' |
||||
]); |
||||
angular.module('openproject.helpers', ['openproject.services']); |
||||
angular |
||||
.module('openproject.models', [ |
||||
'openproject.workPackages.config', |
||||
'openproject.services' |
||||
]); |
||||
angular.module('openproject.viewModels', ['openproject.services']); |
||||
|
||||
// timelines
|
||||
angular.module('openproject.timelines', [ |
||||
'openproject.timelines.controllers', |
||||
'openproject.timelines.directives', |
||||
'openproject.uiComponents' |
||||
]); |
||||
angular.module('openproject.timelines.models', ['openproject.helpers']); |
||||
angular |
||||
.module('openproject.timelines.helpers', []); |
||||
angular.module( |
||||
'openproject.timelines.controllers', [ |
||||
'openproject.timelines.models' |
||||
]); |
||||
angular.module('openproject.timelines.services', [ |
||||
'openproject.timelines.models', |
||||
'openproject.timelines.helpers' |
||||
]); |
||||
angular.module('openproject.timelines.directives', [ |
||||
'openproject.timelines.models', |
||||
'openproject.timelines.services', |
||||
'openproject.uiComponents', |
||||
'openproject.helpers' |
||||
]); |
||||
|
||||
// work packages
|
||||
angular.module('openproject.workPackages', [ |
||||
'openproject.workPackages.activities', |
||||
'openproject.workPackages.controllers', |
||||
'openproject.workPackages.filters', |
||||
'openproject.workPackages.directives', |
||||
'openproject.workPackages.tabs', |
||||
'openproject.uiComponents', |
||||
'ng-context-menu', |
||||
'ngFileUpload' |
||||
]); |
||||
angular.module('openproject.workPackages.services', ['openproject.inplace-edit']); |
||||
angular.module( |
||||
'openproject.workPackages.helpers', [ |
||||
'openproject.helpers', |
||||
'openproject.workPackages.services' |
||||
]); |
||||
angular.module('openproject.workPackages.filters', [ |
||||
'openproject.workPackages.helpers' |
||||
]); |
||||
angular.module('openproject.workPackages.config', []); |
||||
angular.module( |
||||
'openproject.workPackages.controllers', [ |
||||
'openproject.models', |
||||
'openproject.viewModels', |
||||
'openproject.workPackages.helpers', |
||||
'openproject.services', |
||||
'openproject.workPackages.config', |
||||
'openproject.layout', |
||||
'btford.modal' |
||||
]); |
||||
angular.module('openproject.workPackages.models', []); |
||||
angular.module( |
||||
'openproject.workPackages.directives', [ |
||||
'openproject.uiComponents', |
||||
'openproject.services', |
||||
'openproject.workPackages.services', |
||||
'openproject.workPackages.models' |
||||
]); |
||||
angular.module('openproject.workPackages.tabs', []); |
||||
angular.module('openproject.workPackages.activities', []); |
||||
|
||||
// messages
|
||||
angular.module('openproject.messages', [ |
||||
'openproject.messages.controllers' |
||||
]); |
||||
angular.module('openproject.messages.controllers', []); |
||||
|
||||
// time entries
|
||||
angular.module('openproject.timeEntries', [ |
||||
'openproject.timeEntries.controllers' |
||||
]); |
||||
angular.module('openproject.timeEntries.controllers', []); |
||||
|
||||
angular.module('openproject.layout', [ |
||||
'openproject.layout.controllers', |
||||
'ui.router' |
||||
]); |
||||
angular.module('openproject.layout.controllers', []); |
||||
|
||||
angular.module('openproject.api', ['restangular', 'openproject.services']); |
||||
|
||||
angular.module('openproject.templates', []); |
||||
|
||||
// refactoring
|
||||
angular.module('openproject.inplace-edit', []); |
||||
angular.module('openproject.responsive', []); |
||||
|
||||
export var wpButtonsModule = |
||||
angular.module('openproject.wpButtons', ['ui.router', 'openproject.services']); |
||||
|
||||
// main app
|
||||
export default angular.module('openproject', [ |
||||
'ui.date', |
||||
'ui.router', |
||||
'openproject.config', |
||||
'openproject.uiComponents', |
||||
'openproject.timelines', |
||||
'openproject.workPackages', |
||||
'openproject.messages', |
||||
'openproject.timeEntries', |
||||
'ngAnimate', |
||||
'ngAria', |
||||
'ngSanitize', |
||||
'truncate', |
||||
'openproject.layout', |
||||
'cgBusy', |
||||
'openproject.api', |
||||
'openproject.templates', |
||||
'monospaced.elastic', |
||||
'openproject.inplace-edit', |
||||
'openproject.wpButtons', |
||||
'openproject.responsive' |
||||
]); |
@ -0,0 +1,64 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
// ++
|
||||
|
||||
import {wpButtonsModule} from '../../../angular-modules'; |
||||
|
||||
export default class WorkPackageCreateButtonController { |
||||
public projectIdentifier:string; |
||||
public text:any; |
||||
public types:any; |
||||
|
||||
protected canCreate:boolean = false; |
||||
|
||||
public get inProjectContext() { |
||||
return !!this.projectIdentifier; |
||||
} |
||||
|
||||
constructor(protected $state, protected I18n, protected ProjectService) { |
||||
this.text = { |
||||
button: I18n.t('js.label_work_package'), |
||||
create: I18n.t('js.label_create_work_package') |
||||
}; |
||||
|
||||
if (this.inProjectContext) { |
||||
this.ProjectService.fetchProjectResource(this.projectIdentifier).then(project => { |
||||
this.canCreate = !!project.links.createWorkPackage; |
||||
}); |
||||
|
||||
this.ProjectService.getProject(this.projectIdentifier).then(project => { |
||||
this.types = project.embedded.types; |
||||
}); |
||||
} |
||||
} |
||||
|
||||
public isDisabled() { |
||||
return !this.inProjectContext || !this.canCreate || this.$state.includes('**.new') || !this.types; |
||||
} |
||||
} |
||||
|
||||
wpButtonsModule.controller('WorkPackageCreateButtonController', WorkPackageCreateButtonController); |
@ -1,8 +1,7 @@ |
||||
<div class="wp-create-button"> |
||||
<button class="button -alt-highlight add-work-package" has-dropdown-menu |
||||
target="typesDropDownMenu" locals="vm" ng-disabled="vm.isDisabled()" |
||||
aria-label="{{ ::vm.text.create }}" aria-haspopup="true" |
||||
onclick="setTimeout(function(){ $(document).getElementsByClassName('menu-item')[0].focus(); }, 100);"> |
||||
aria-label="{{ ::vm.text.create }}" aria-haspopup="true"> |
||||
|
||||
<i class="button--icon icon-add"></i> |
||||
<span class="button--text" ng-bind="::vm.text.button" aria-hidden="true"></span> |
@ -0,0 +1,85 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
// ++
|
||||
|
||||
import {wpButtonsModule} from '../../../angular-modules'; |
||||
import WorkPackageCreateButtonController from '../wp-create-button/wp-create-button.controller'; |
||||
|
||||
class WorkPackageInlineCreateButtonController extends WorkPackageCreateButtonController { |
||||
public columns:any[]; |
||||
public rows:any[]; |
||||
public hidden:boolean = false; |
||||
private _wp; |
||||
|
||||
constructor( |
||||
protected $state, |
||||
protected $rootScope, |
||||
protected $element, |
||||
protected I18n, |
||||
protected ProjectService, |
||||
protected WorkPackageResource, |
||||
protected apiWorkPackages |
||||
) { |
||||
super($state, I18n, ProjectService); |
||||
|
||||
$rootScope.$on('workPackageSaved', (_event, savedWp) => { |
||||
// Add another row
|
||||
if (savedWp === this._wp) { |
||||
this.addWorkPackageRow(); |
||||
} |
||||
}); |
||||
|
||||
$rootScope.$on('inlineWorkPackageCreateCancelled', (_event, index, row) => { |
||||
if (row.object === this._wp) { |
||||
this.rows.splice(index, 1); |
||||
this.show(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public addWorkPackageRow() { |
||||
this.WorkPackageResource.fromCreateForm(this.projectIdentifier).then(wp => { |
||||
this._wp = wp; |
||||
wp.inlineCreated = true; |
||||
this.rows.push({ level: 0, ancestors: [], object: wp, parent: undefined }); |
||||
this.hide(); |
||||
|
||||
|
||||
}); |
||||
} |
||||
|
||||
public hide() { |
||||
return this.hidden = true; |
||||
} |
||||
|
||||
public show() { |
||||
return this.hidden = false; |
||||
} |
||||
} |
||||
|
||||
wpButtonsModule.controller( |
||||
'WorkPackageInlineCreateButtonController', WorkPackageInlineCreateButtonController); |
@ -0,0 +1,13 @@ |
||||
<div |
||||
class="wp-inline-create-button" |
||||
ng-hide="$ctrl.hidden || $ctrl.isDisabled() || !$ctrl.canCreate"> |
||||
<a class="wp-inline-create--add-link" |
||||
ng-click="$ctrl.addWorkPackageRow()" |
||||
ng-disabled="$ctrl.isDisabled()" |
||||
aria-label="{{ ::$ctrl.text.create }}" |
||||
aria-haspopup="true"> |
||||
|
||||
<i class="icon icon-add"></i> |
||||
<span ng-bind="::$ctrl.text.button" aria-hidden="true"></span> |
||||
</a> |
||||
</div> |
@ -0,0 +1,49 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
// ++
|
||||
|
||||
function wpInlineCreateButton() { |
||||
return { |
||||
restrict: 'AE', |
||||
templateUrl: '/components/wp-buttons/wp-inline-create-button/' + |
||||
'wp-inline-create-button.directive.html', |
||||
|
||||
scope: { |
||||
projectIdentifier: '=', |
||||
rows: '=', |
||||
columns: '=' |
||||
}, |
||||
|
||||
bindToController: true, |
||||
controllerAs: '$ctrl', |
||||
controller: 'WorkPackageInlineCreateButtonController' |
||||
} |
||||
} |
||||
|
||||
angular |
||||
.module('openproject.wpButtons') |
||||
.directive('wpInlineCreateButton', wpInlineCreateButton); |
@ -1,6 +1,7 @@ |
||||
<input type="checkbox" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-model="vm.workPackage[vm.fieldName]" |
||||
ng-change="vm.submit()" |
||||
ng-blur="vm.deactivate()" |
||||
focus /> |
||||
focus="vm.shouldFocus()" /> |
||||
|
@ -1,6 +1,7 @@ |
||||
<input type="number" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-model="vm.workPackage[vm.fieldName]" |
||||
duration-value |
||||
ng-blur="vm.deactivate()" |
||||
focus> |
||||
focus="vm.shouldFocus()"> |
||||
|
@ -1,6 +1,7 @@ |
||||
<input type="text" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-model="vm.workPackage[vm.fieldName]" |
||||
ng-blur="vm.deactivate()" |
||||
float-value |
||||
focus /> |
||||
focus="vm.shouldFocus()" /> |
||||
|
@ -1,5 +1,6 @@ |
||||
<input type="number" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-model="vm.workPackage[vm.fieldName]" |
||||
ng-blur="vm.deactivate()" |
||||
focus> |
||||
focus="vm.shouldFocus()"> |
||||
|
@ -1,8 +1,9 @@ |
||||
<select ng-model="vm.workPackage[vm.fieldName]" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-options="value as (value.name || value.value) for value in vm.field.options track by value.href" |
||||
ng-change="vm.submit()" |
||||
ng-blur="vm.deactivate()" |
||||
focus> |
||||
focus="vm.shouldFocus()"> |
||||
<option value="" ng-if="false"></option> |
||||
</select> |
||||
|
@ -1,5 +1,6 @@ |
||||
<input type="text" |
||||
class="wp-inline-edit--field" |
||||
wp-edit-field-requirements="vm.field.schema" |
||||
ng-model="vm.workPackage[vm.fieldName]" |
||||
ng-blur="vm.deactivate()" |
||||
focus> |
||||
focus="vm.shouldFocus()"> |
||||
|
Loading…
Reference in new issue