| /* |
| * Copyright (c) 2014-2018 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| */ |
| import {CheckBoxField, CompositeField, DateField, dates, FormField, HtmlComponent, LogicalGridData, LogicalGridLayoutConfig, scout, SequenceBoxGridConfig, SequenceBoxLayout} from '../../../index'; |
| |
| export default class SequenceBox extends CompositeField { |
| |
| constructor() { |
| super(); |
| this._addWidgetProperties('fields'); |
| this._addCloneProperties(['layoutConfig']); |
| this.logicalGrid = scout.create('scout.HorizontalGrid'); |
| this.layoutConfig = null; |
| this.fields = []; |
| } |
| |
| _init(model) { |
| super._init(model); |
| |
| this._setLayoutConfig(this.layoutConfig); |
| |
| this._initDateFields(); |
| |
| this.setErrorStatus(this.errorStatus); |
| this.setTooltipText(this.tooltipText); |
| this.setMenus(this.menus); |
| this.setMenusVisible(this.menusVisible); |
| } |
| |
| /** |
| * Initialize all DateFields in this SequenceBox with a meaningful autoDate, except fields which already have an autoDate provided by the model. |
| */ |
| _initDateFields() { |
| var dateFields = this._getDateFields(); |
| var newAutoDate = null; |
| for (var i = 0; i < dateFields.length; i++) { |
| var currField = dateFields[i]; |
| if (currField.autoDate) { |
| // is the autoDate already set by the field's model remember to not change this value. |
| currField.hasModelAutoDateSet = true; |
| } |
| if (!currField.hasModelAutoDateSet) { |
| currField.setAutoDate(newAutoDate); |
| } |
| newAutoDate = this._getAutoDateProposal(currField); |
| } |
| } |
| |
| _render() { |
| var field, i; |
| this.addContainer(this.$parent, 'sequence-box'); |
| this.addLabel(); |
| this.addField(this.$parent.makeDiv()); |
| this.addStatus(); |
| this._handleStatus(); |
| this.htmlBody = HtmlComponent.install(this.$field, this.session); |
| this.htmlBody.setLayout(this._createBodyLayout()); |
| for (i = 0; i < this.fields.length; i++) { |
| field = this.fields[i]; |
| field.labelUseUiWidth = true; |
| field.on('propertyChange', this._onFieldPropertyChange.bind(this)); |
| field.render(this.$field); |
| this._modifyLabel(field); |
| |
| // set each children layout data to logical grid data |
| field.setLayoutData(new LogicalGridData(field)); |
| } |
| } |
| |
| _renderProperties() { |
| super._renderProperties(); |
| this._renderLayoutConfig(); |
| } |
| |
| _createBodyLayout() { |
| return new SequenceBoxLayout(this, this.layoutConfig); |
| } |
| |
| /** |
| * @override |
| */ |
| invalidateLogicalGrid(invalidateLayout) { |
| super.invalidateLogicalGrid(false); |
| if (scout.nvl(invalidateLayout, true) && this.rendered) { |
| this.htmlBody.invalidateLayoutTree(); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| _setLogicalGrid(logicalGrid) { |
| super._setLogicalGrid(logicalGrid); |
| if (this.logicalGrid) { |
| this.logicalGrid.setGridConfig(new SequenceBoxGridConfig()); |
| } |
| } |
| |
| setLayoutConfig(layoutConfig) { |
| this.setProperty('layoutConfig', layoutConfig); |
| } |
| |
| _setLayoutConfig(layoutConfig) { |
| if (!layoutConfig) { |
| layoutConfig = new LogicalGridLayoutConfig(); |
| } |
| this._setProperty('layoutConfig', LogicalGridLayoutConfig.ensure(layoutConfig)); |
| } |
| |
| _renderLayoutConfig() { |
| this.layoutConfig.applyToLayout(this.htmlBody.layout); |
| if (this.rendered) { |
| this.htmlBody.invalidateLayoutTree(); |
| } |
| } |
| |
| _onFieldPropertyChange(event) { |
| var visibiltyChanged = (event.propertyName === 'visible'); |
| if (scout.isOneOf(event.propertyName, ['errorStatus', 'tooltipText', 'visible', 'menus', 'menusVisible'])) { |
| this._handleStatus(visibiltyChanged); |
| } |
| if (event.propertyName === 'value') { |
| this._onFieldValueChange(event); |
| } |
| } |
| |
| /** |
| * Moves the status relevant properties from the last visible field to the sequencebox. This makes sure that the fields inside the sequencebox have the same size. |
| */ |
| _handleStatus(visibilityChanged) { |
| if (visibilityChanged && this._lastVisibleField) { |
| // if there is a new last visible field, make sure the status is shown on the previously last one |
| this._lastVisibleField.setSuppressStatus(null); |
| if (this._lastVisibleField.rendered) { |
| this._lastVisibleField._renderErrorStatus(); |
| this._lastVisibleField._renderTooltipText(); |
| this._lastVisibleField._renderMenus(); |
| } |
| } |
| this._lastVisibleField = this._getLastVisibleField(); |
| if (!this._lastVisibleField) { |
| return; |
| } |
| |
| // Update the sequencebox with the status relevant flags |
| this._isOverwritingStatusFromField = true; |
| if (this._lastVisibleField.errorStatus) { |
| this.setErrorStatus(this._lastVisibleField.errorStatus); |
| this._isErrorStatusOverwritten = true; |
| } else { |
| this.setErrorStatus(this.boxErrorStatus); |
| this._isErrorStatusOverwritten = false; |
| } |
| |
| if (this._lastVisibleField.hasStatusTooltip()) { |
| this.setTooltipText(this._lastVisibleField.tooltipText); |
| this._isTooltipTextOverwritten = true; |
| } else { |
| this.setTooltipText(this.boxTooltipText); |
| this._isTooltipTextOverwritten = false; |
| } |
| |
| if (this._lastVisibleField.menus && this._lastVisibleField.menus.length > 0) { |
| // Change owner to make sure menu won't be destroyed when setMenus is called |
| this._updateBoxMenuOwner(this.fieldStatus); |
| this.setMenus(this._lastVisibleField.menus); |
| this.setMenusVisible(this._lastVisibleField.menusVisible); |
| this._isMenusOverwritten = true; |
| } else { |
| this._updateBoxMenuOwner(this); |
| this.setMenus(this.boxMenus); |
| this.setMenusVisible(this.boxMenusVisible); |
| this._isMenusOverwritten = false; |
| } |
| this._isOverwritingStatusFromField = false; |
| |
| // Make sure the last field won't display a status (but shows status CSS class) |
| this._lastVisibleField.setSuppressStatus(FormField.SuppressStatus.ICON); |
| if (visibilityChanged) { |
| // If the last field got invisible, make sure the new last field does not display a status anymore (now done by the seq box) |
| if (this._lastVisibleField.rendered) { |
| this._lastVisibleField._renderErrorStatus(); |
| this._lastVisibleField._renderTooltipText(); |
| this._lastVisibleField._renderMenus(); |
| } |
| } |
| } |
| |
| setErrorStatus(errorStatus) { |
| if (this._isOverwritingStatusFromField && !this._isErrorStatusOverwritten) { |
| // was not overwritten, will be overwritten now -> backup old value |
| this.boxErrorStatus = this.errorStatus; |
| } else if (!this._isOverwritingStatusFromField) { |
| // directly changed on seq box -> update backed-up value |
| this.boxErrorStatus = errorStatus; |
| } |
| if (this._isOverwritingStatusFromField || !this._isErrorStatusOverwritten) { |
| // prevent setting value if directly changed on seq box and is already overwritten |
| super.setErrorStatus(errorStatus); |
| } |
| } |
| |
| setTooltipText(tooltipText) { |
| if (this._isOverwritingStatusFromField && !this._isTooltipTextOverwritten) { |
| // was not overwritten, will be overwritten now -> backup old value |
| this.boxTooltipText = this.tooltipText; |
| } else if (!this._isOverwritingStatusFromField) { |
| // directly changed on seq box -> update backed-up value |
| this.boxTooltipText = tooltipText; |
| } |
| if (this._isOverwritingStatusFromField || !this._isTooltipTextOverwritten) { |
| // prevent setting value if directly changed on seq box and is already overwritten |
| super.setTooltipText(tooltipText); |
| } |
| } |
| |
| setMenus(menus) { |
| // ensure menus are real and not just model objects |
| menus = this._createChildren(menus); |
| |
| if (this._isOverwritingStatusFromField && !this._isMenusOverwritten) { |
| // was not overwritten, will be overwritten now -> backup old value |
| this.boxMenus = this.menus; |
| } else if (!this._isOverwritingStatusFromField) { |
| // directly changed on seq box -> update backed-up value |
| this.boxMenus = menus; |
| } |
| if (this._isOverwritingStatusFromField || !this._isMenusOverwritten) { |
| // prevent setting value if directly changed on seq box and is already overwritten |
| super.setMenus(menus); |
| } |
| } |
| |
| _updateBoxMenuOwner(newOwner) { |
| this.boxMenus.forEach(function(menu) { |
| menu.setOwner(newOwner); |
| }); |
| } |
| |
| setMenusVisible(menusVisible) { |
| if (this._isOverwritingStatusFromField && !this._isMenusOverwritten) { |
| // was not overwritten, will be overwritten now -> backup old value |
| this.boxMenusVisible = this.menusVisible; |
| } else if (!this._isOverwritingStatusFromField) { |
| // directly changed on seq box -> update backed-up value |
| this.boxMenusVisible = menusVisible; |
| } |
| if (this._isOverwritingStatusFromField || !this._isMenusOverwritten) { |
| // prevent setting value if directly changed on seq box and is already overwritten |
| super.setMenusVisible(menusVisible); |
| } |
| } |
| |
| _getLastVisibleField() { |
| var visibleFields = this.fields.filter(function(field) { |
| return field.visible; |
| }); |
| if (visibleFields.length === 0) { |
| return; |
| } |
| |
| return visibleFields[visibleFields.length - 1]; |
| } |
| |
| _onFieldValueChange(event) { |
| if (event.source instanceof DateField) { |
| this._onDateFieldValueChange(event); |
| } |
| } |
| |
| _onDateFieldValueChange(event) { |
| // For a better user experience preselect a meaningful date on all following DateFields in the sequence box. |
| var field = event.source; |
| var dateFields = this._getDateFields(); |
| var newAutoDate = this._getAutoDateProposal(field); |
| for (var i = dateFields.indexOf(field) + 1; i < dateFields.length; i++) { |
| var currField = dateFields[i]; |
| if (!currField.hasModelAutoDateSet) { |
| currField.setAutoDate(newAutoDate); |
| } |
| if (currField.value) { |
| // only update fields in between the current field and the next field with a value set. Otherwise already set autoDates would be overwritten. |
| break; |
| } |
| } |
| } |
| |
| _getDateFields() { |
| return this.fields.filter(function(field) { |
| return field instanceof DateField; |
| }); |
| } |
| |
| _getAutoDateProposal(field) { |
| var newAutoDate = null; |
| // if it's only a time field, add one hour, otherwise add one day |
| if (field && field.value) { |
| if (!field.hasDate && field.hasTime) { |
| newAutoDate = dates.shiftTime(field.value, 1, 0, 0); |
| } else { |
| newAutoDate = dates.shift(field.value, 0, 0, 1); |
| } |
| } |
| return newAutoDate; |
| } |
| |
| // The new sequence-box sets the label to invisible on the model. |
| _modifyLabel(field) { |
| if (field instanceof CheckBoxField) { |
| field.labelVisible = false; |
| } |
| |
| if (field instanceof DateField) { |
| // The DateField has two inputs ($dateField and $timeField), field.$field refers to the composite which is irrelevant here |
| // In order to support aria-labelledby for date fields also, the individual inputs have to be linked with the label rather than the composite |
| if (field.$dateField) { |
| this._linkWithLabel(field.$dateField); |
| } |
| if (field.$timeField) { |
| this._linkWithLabel(field.$timeField); |
| } |
| } else if (field.$field) { // If $field is set depends on the concrete field e.g. a group box does not have a $field |
| this._linkWithLabel(field.$field); |
| } |
| } |
| |
| setFields(fields) { |
| if (this.rendered) { |
| throw new Error('Setting fields is not supported if sequence box is already rendered.'); |
| } |
| this.setProperty('fields', fields); |
| } |
| |
| /** |
| * @override CompositeField.js |
| */ |
| getFields() { |
| return this.fields; |
| } |
| |
| clone(model, options) { |
| var clone = super.clone(model, options); |
| this._deepCloneProperties(clone, 'fields', options); |
| return clone; |
| } |
| } |