OpenProject is the leading open source project management software.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
openproject/frontend/app/components/wp-edit/wp-edit-field/wp-edit-field.directive.ts

343 lines
9.7 KiB

// -- 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 {WorkPackageEditFormController} from "./../wp-edit-form.directive";
import {WorkPackageEditFieldService} from "./wp-edit-field.service";
import {EditField} from "./wp-edit-field.module";
import {scopedObservable} from "../../../helpers/angular-rx-utils";
import {WorkPackageResource} from "../../api/api-v3/hal-resources/work-package-resource.service";
import {WorkPackageCacheService} from "../../work-packages/work-package-cache.service";
export class WorkPackageEditFieldController {
public formCtrl: WorkPackageEditFormController;
public fieldForm: ng.IFormController;
public fieldName: string;
public fieldType: string;
public fieldIndex: number;
public fieldLabel: string;
public field: EditField;
public errorenous: boolean;
public errors: Array<string>;
public workPackage: WorkPackageResource;
protected _active: boolean = false;
protected _forceFocus: boolean = false;
// Since we load the schema asynchronously
// all fields are initially viewed as uneditable until it is loaded
protected _editable: boolean = false;
private __d__inplaceEditReadValue: JQuery;
constructor(protected wpEditField: WorkPackageEditFieldService,
protected $scope,
protected $element,
protected $timeout,
protected $q,
protected FocusHelper,
protected NotificationsService,
protected ConfigurationService,
protected wpCacheService: WorkPackageCacheService,
protected I18n) {
}
public get active() {
return this._active;
}
public get htmlId() {
return 'wp-' +
this.formCtrl.workPackage.id +
'-inline-edit--field-' +
this.fieldName;
}
public submit() {
if (this.inEditMode) {
return this.formCtrl.updateForm();
}
this.formCtrl.updateWorkPackage()
.finally(() => this.deactivate());
}
public deactivate() {
this._forceFocus = false;
return this._active = false;
}
public activate(forceFocus = false) {
this._forceFocus = forceFocus;
let alreadyActive = this._active;
return this.buildEditField().then(() => {
this._active = this.field.schema.writable;
if (this._active && !alreadyActive) {
this.focusField();
}
return this._active;
});
}
public initializeField() {
// Activate field when creating a work package
// and the schema requires this field
if (this.workPackage.isNew && this.workPackage.requiredValueFor(this.fieldName)) {
this.activate();
var activeField = this.formCtrl.firstActiveField;
if (!activeField || this.formCtrl.fields[activeField].fieldIndex > this.fieldIndex) {
this.formCtrl.firstActiveField = this.fieldName;
}
}
// Mark the td field if it is inline-editable
// We're resolving the non-form schema here since its loaded anyway for the table
this.workPackage.schema.$load().then(schema => {
var fieldSchema = schema[this.fieldName];
this.editable = fieldSchema && fieldSchema.writable;
this.fieldType = fieldSchema && this.wpEditField.fieldType(fieldSchema.type);
this.updateDisplayAttributes();
if (fieldSchema) {
this.fieldLabel = this.fieldLabel || fieldSchema.name;
// Activate the field automatically when in editAllMode
if (this.inEditMode && this.isEditable) {
this.activate();
}
}
});
}
public activateIfEditable(event) {
if (this.isEditable) {
this.handleUserActivate();
}
event.stopImmediatePropagation();
}
public get isEditable(): boolean {
return this._editable && this.workPackage.isEditable;
}
public get inEditMode(): boolean {
return this.formCtrl.inEditMode;
}
public isRequired(): boolean {
return this.workPackage.schema[this.fieldName].required;
}
public isEmpty(): boolean {
return !this.workPackage[this.fieldName];
}
public isChanged(): boolean {
return this.workPackage.$pristine[this.fieldName] !== this.workPackage[this.fieldName];
}
public isErrorenous(): boolean {
return this.errorenous;
}
public isSubmittable(): boolean {
return !(this.inEditMode ||
(this.isRequired() && this.isEmpty()) ||
(this.isErrorenous() && !this.isChanged()));
}
public get errorMessageOnLabel(): string {
if (_.isEmpty(this.errors)) {
return '';
}
else {
return this.I18n.t('js.inplace.errors.messages_on_field',
{ messages: this.errors.join(" ") });
}
}
public set editable(enabled: boolean) {
this._editable = enabled;
}
public shouldFocus() {
return this._forceFocus || this.formCtrl.firstActiveField === this.fieldName;
}
public focusField() {
this.$timeout(_ => this.$scope.$broadcast('updateFocus'));
}
public handleUserActivate() {
this.activate(true).then((active) => {
// Display a generic error if the field turns out not to be editable,
// despite the field being editable.
if (this.isEditable && !active) {
this.NotificationsService.addError(this.I18n.t(
'js.work_packages.error.edit_prohibited',
{attribute: this.field.schema.name}
));
}
});
}
public handleUserBlur(): boolean {
if (!this.isSubmittable()) {
return;
}
this.deactivate();
this.submit();
}
public handleUserCancel() {
if (!this.active || this.inEditMode) {
return;
}
// Close the field
this.reset();
// Keep focus on read value
this._forceFocus = true;
this.focusField();
}
/**
* Avoid clicks within the form to bubble up to the row handler.
* Otherwise, clicks within wp-edit-fields may cause the split / full view to open.
*/
public haltUserFormClick(event) {
event.stopPropagation();
return false;
}
public setErrors(errors) {
this.errorenous = !_.isEmpty(errors);
this.errors = errors;
}
public reset() {
this.workPackage.restoreFromPristine(this.fieldName);
this.fieldForm.$setPristine();
this.deactivate();
}
public onlyInAccessibilityMode(callback) {
if (this.ConfigurationService.accessibilityModeEnabled()) {
callback.apply(this);
}
}
protected buildEditField(): ng.IPromise<any> {
return this.formCtrl.loadSchema().then(schema => {
this.field = <EditField>this.wpEditField.getField(this.workPackage, this.fieldName, schema[this.fieldName]);
this.workPackage.storePristine(this.fieldName);
});
}
protected updateDisplayAttributes() {
this.__d__inplaceEditReadValue = this.__d__inplaceEditReadValue || this.$element.find(".__d__inplace-edit--read-value");
if (this.isEditable) {
this.__d__inplaceEditReadValue.attr("role", "button");
this.__d__inplaceEditReadValue.attr("tabindex", "0");
}
else {
this.__d__inplaceEditReadValue.removeAttr("role");
this.__d__inplaceEditReadValue.removeAttr("tabindex");
}
}
}
function wpEditField(wpCacheService: WorkPackageCacheService) {
function wpEditFieldLink(scope,
element,
attrs,
controllers: [WorkPackageEditFormController, WorkPackageEditFieldController]) {
var formCtrl = controllers[0];
controllers[1].formCtrl = formCtrl;
formCtrl.registerField(scope.vm);
scopedObservable(scope, wpCacheService.loadWorkPackage(formCtrl.workPackage.id))
.subscribe((wp: WorkPackageResource) => {
scope.vm.workPackage = wp;
scope.vm.initializeField();
});
if (formCtrl.workPackage) {
scope.vm.workPackage = formCtrl.workPackage;
scope.vm.initializeField();
}
element.addClass(scope.vm.fieldName);
element.keyup(event => {
if (event.keyCode === 27) {
scope.$evalAsync(() => {
scope.vm.handleUserCancel(true);
});
}
});
}
return {
restrict: 'A',
templateUrl: '/components/wp-edit/wp-edit-field/wp-edit-field.directive.html',
transclude: true,
scope: {
fieldName: '=wpEditField',
fieldLabel: '=?wpEditFieldLabel',
fieldIndex: '=',
columns: '=',
wrapperClasses: '=wpEditFieldWrapperClasses',
displayPlaceholder: '=?',
displayClasses: '=?',
},
require: ['^wpEditForm', 'wpEditField'],
link: wpEditFieldLink,
controller: WorkPackageEditFieldController,
controllerAs: 'vm',
bindToController: true
};
}
//TODO: Use 'openproject.wpEdit' module
angular
.module('openproject')
.directive('wpEditField', wpEditField);