/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var App = require('app'); require('views/common/controls_view'); /** * Common view for config widgets * @type {Em.View} */ App.ConfigWidgetView = Em.View.extend(App.SupportsDependentConfigs, App.WidgetPopoverSupport, App.ConvertUnitWidgetViewMixin, App.ServiceConfigCalculateId, { /** * @type {App.ConfigProperty} */ config: null, /** * Determines if user hover on widget-view * @type {boolean} */ isHover: false, /** * Determines if widget controls should be disabled * @type {boolean} */ disabled: false, /** * Determines if widget is editable * It true - show all control-elements (undo, override, finalize etc) for widget * If false - no widget control-elements will be shown * Bound from template * @type {boolean} */ canEdit: true, canNotEdit: Em.computed.not('canEdit'), /** * Config label class attribute. Displays validation status of config. * @type {string} */ configLabelClass: '', /** * defines if widget should be shown * if not, text-field with config value or label "Undefined" should be shown * @type {boolean} */ doNotShowWidget: Em.computed.or('isPropertyUndefined', 'config.showAsTextBox'), /** * defines if property in not defined in selected version * in this case "Undefined" should be shown instead of widget * @type {boolean} */ isPropertyUndefined: Em.computed.equal('config.value', 'Undefined'), /** * Tab where current widget placed * Bound in the template * @type {App.Tab} */ tab: null, /** * Section where current widget placed * Bound in the template * @type {App.Section} */ section: null, /** * Subsection where current widget placed * Bound in the template * @type {App.SubSection} */ subSection: null, /** * Determines if user can switch custom widget-view to the input-field * @type {boolean} */ supportSwitchToTextBox: false, /** * @type {boolean} */ showPencil: Em.computed.and('supportSwitchToTextBox', '!disabled'), /** * Alias to config.isOriginalSCP * Should be used in the templates * Don't use original config.isOriginalSCP in the widget-templates!!! * @type {boolean} */ isOriginalSCPBinding: 'config.isOriginalSCP', /** * Check if property validation failed for overridden property in case when its value is equal to parent * config property. * @type {boolean} */ isOverrideEqualityError: function() { return this.get('config.parentSCP') && this.get('config.parentSCP.value') == this.get('config.value'); }.property('config.isValid'), /** * Alias to config.isComparison * Should be used in the templates * Don't use original config.isComparison in the widget-templates!!! * @type {boolean} */ isComparisonBinding: 'config.isComparison', classNameBindings:['isComparison:compare-mode', 'config.overrides.length:overridden-property'], issueMessage: '', issueView: Em.View.extend({ tagName: 'i', classNames: ['icon-warning-sign'], classNameBindings: ['issueIconClass'], attributeBindings:['issueMessage:data-original-title'], /** * @type {App.ServiceConfigProperty} */ config: null, /** * @type {string} */ issueIconClass: '', /** * @type {string} */ issueMessage: '', didInsertElement: function() { App.tooltip($(this.get('element'))); this.errorLevelObserver(); this.addObserver('issuedConfig.warnMessage', this, this.errorLevelObserver); this.addObserver('issuedConfig.errorMessage', this, this.errorLevelObserver); this.addObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver); }, willDestroyElement: function() { $(this.get('element')).tooltip('destroy'); this.removeObserver('issuedConfig.warnMessage', this, this.errorLevelObserver); this.removeObserver('issuedConfig.errorMessage', this, this.errorLevelObserver); this.removeObserver('parentView.isPropertyUndefined', this, this.errorLevelObserver); }, /** * * @method errorLevelObserver */ errorLevelObserver: function() { var messageLevel = this.get('issuedConfig.errorMessage') ? 'ERROR': this.get('issuedConfig.warnMessage') ? 'WARN' : 'NONE'; if (this.get('parentView.isPropertyUndefined')) { messageLevel = 'NONE'; } var issue = { ERROR: { iconClass: '', message: this.get('issuedConfig.errorMessage'), configLabelClass: 'text-error' }, WARN: { iconClass: 'warning', message: this.get('issuedConfig.warnMessage'), configLabelClass: 'text-warning' }, NONE: { iconClass: 'hide', message: false, configLabelClass: '' } }[messageLevel]; this.set('parentView.configLabelClass', issue.configLabelClass); this.set('issueIconClass', issue.iconClass); this.set('issueMessage', issue.message); this.set('parentView.issueMessage', issue.message); }, /** * @type {App.ServiceConfigProperty} */ issuedConfig: function() { var config = this.get('config'); // check editable override if (!config.get('isEditable') && config.get('isOriginalSCP') && config.get('overrides.length') && config.get('overrides').someProperty('isEditable', true)) { config = config.get('overrides').findProperty('isEditable', true); } else if (config.get('isOriginalSCP') && config.get('isEditable')) { // use original config if it is not valid if (!config.get('isValid')) { return config; // scan overrides for non valid values and use it } else if (config.get('overrides.length') && config.get('overrides').someProperty('isValid', false)) { return config.get('overrides').findProperty('isValid', false); } } return config; }.property('config.isEditable', 'config.overrides.length') }), /** * Config name to display. * @type {String} */ configLabel: Em.computed.firstNotBlank('config.stackConfigProperty.displayName', 'config.displayName', 'config.name'), /** * Error message computed in config property model * @type {String} */ configErrorMessageBinding: 'config.errorMessage', /** * Determines if config-value was changed * @type {boolean} */ valueIsChanged: function () { return !Em.isNone(this.get('config.savedValue')) && this.get('config.value') != this.get('config.savedValue'); }.property('config.value', 'config.savedValue'), /** * Enable/disable widget state * @method toggleWidgetState */ toggleWidgetState: function () { this.set('disabled', !this.get('config.isEditable')); }.observes('config.isEditable'), /** * Reset config-value to its default * @method restoreValue */ restoreValue: function () { var self = this; this.set('config.value', this.get('config.savedValue')); this.sendRequestRorDependentConfigs(this.get('config')).done(function() { self.restoreDependentConfigs(self.get('config')); }); if (this.get('config.supportsFinal')) { this.get('config').set('isFinal', this.get('config.savedIsFinal')); } Em.$('body > .tooltip').remove(); }, /** * set recommendedValue to config * and send request to change dependent configs * @method setRecommendedValue */ setRecommendedValue: function() { var self = this; this.set('config.value', this.get('config.recommendedValue')); this.sendRequestRorDependentConfigs(this.get('config')).done(function() { if (self.get('config.value') === self.get('config.savedValue')) { self.restoreDependentConfigs(self.get('config')); } }); if (this.get('config.supportsFinal')) { this.get('config').set('isFinal', this.get('config.recommendedIsFinal')); } Em.$('body > .tooltip').remove(); }, /** * Determines if override is allowed for config * @type {boolean} */ overrideAllowed: function () { var config = this.get('config'); if (!config) return false; return config.get('isOriginalSCP') && config.get('isPropertyOverridable') && !this.get('config.isComparison'); }.property('config.isOriginalSCP', 'config.isPropertyOverridable', 'config.isComparison'), /** * Determines if undo is allowed for config * @type {boolean} */ undoAllowed: function () { var config = this.get('config'); if (!config) return false; if (!this.get('isOriginalSCP') || this.get('disabled')) return false; return !config.get('cantBeUndone') && config.get('isNotDefaultValue'); }.property('config.cantBeUndone', 'config.isNotDefaultValue', 'isOriginalSCP', 'disabled'), /** * Determines if "final"-button should be shown * @type {boolean} */ showFinalConfig: function () { var config = this.get('config'); return config.get('isFinal') || (!config.get('isNotEditable') && this.get('isHover')); }.property('config.isFinal', 'config.isNotEditable', 'isHover'), /** * * @param {{context: App.ServiceConfigProperty}} event * @method toggleFinalFlag */ toggleFinalFlag: function (event) { var configProperty = event.context; if (configProperty.get('isNotEditable')) { return; } configProperty.toggleProperty('isFinal'); }, /** * sync widget value with config value when dependent properties * have been loaded or changed * @method syncValueWithConfig */ syncValueWithConfig: function() { this.setValue(this.get('config.value')); }.observes('controller.recommendationTimeStamp'), /** * defines if config has same config group as selected * @type {boolean} */ referToSelectedGroup: function() { return this.get('controller.selectedConfigGroup.isDefault') && this.get('config.group') === null || this.get('controller.selectedConfigGroup.name') === this.get('config.group.name'); }.property('controller.selectedConfigGroup.name', 'controller.selectedConfigGroup.isDefault'), didInsertElement: function () { App.tooltip(this.$('[data-toggle=tooltip]'), {placement: 'top'}); App.tooltip($(this.get('element')).find('span')); var self = this; var element = this.$(); if (element) { element.hover(function() { self.set('isHover', true); }, function() { self.set('isHover', false); }); } this.initIncompatibleWidgetAsTextBox(); }, willInsertElement: function() { var configConditions = this.get('config.configConditions'); var configAction = this.get('config.configAction'); if (configConditions && configConditions.length) { this.configValueObserverForAttributes(); //Add Observer to configCondition that depends on another config value var isConditionConfigDependent = configConditions.filterProperty('resource', 'config').length; if (isConditionConfigDependent) { this.addObserver('config.value', this, this.configValueObserverForAttributes); } if (configAction) { this.addObserver('config.value', this, this.configValueObserverForAction); } } }, willDestroyElement: function() { this.$('[data-toggle=tooltip]').tooltip('destroy'); $(this.get('element')).find('span').tooltip('destroy'); if (this.get('config.configConditions')) { this.removeObserver('config.value', this, this.configValueObserverForAttributes); } if (this.get('config.configAction')) { this.removeObserver('config.value', this, this.configValueObserverForAction); } }, configValueObserverForAttributes: function() { var configConditions = this.get('config.configConditions'); var serviceName = this.get('config.serviceName'); var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs'); var isConditionTrue; configConditions.forEach(function(configCondition){ var ifStatement = configCondition.get("if"); if (configCondition.get("resource") === 'config') { isConditionTrue = App.configTheme.calculateConfigCondition(ifStatement, serviceConfigs); if (configCondition.get("type") === 'subsection' || configCondition.get("type") === 'subsectionTab') { this.changeSubsectionAttribute(configCondition, isConditionTrue); } else { this.changeConfigAttribute(configCondition, isConditionTrue); } } else if (configCondition.get("resource") === 'service') { var service = App.Service.find().findProperty('serviceName', ifStatement); var serviceName; if (service) { isConditionTrue = true; } else if (!service && this.get('controller.allSelectedServiceNames') && this.get('controller.allSelectedServiceNames').length) { isConditionTrue = this.get('controller.allSelectedServiceNames').contains(ifStatement); } else { isConditionTrue = false; } this.changeConfigAttribute(configCondition, isConditionTrue); } }, this); }, /** * This is an observer that is fired when a value of a config that is suppose to add/delete a component is changed * @private * @method {configValueObserverForAction} */ configValueObserverForAction: function() { var assignMasterOnStep7Controller = App.router.get('assignMasterOnStep7Controller'); var configAction = this.get('config.configAction'); var serviceName = this.get('config.serviceName'); var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName', serviceName).get('configs'); this.set('config.configActionComponent', null); var hostComponent = { componentName:configAction.get('componentName'), isClient: '', hostName: '', action: '' }; var hostComponentObj = App.HostComponent.find().findProperty('componentName', hostComponent.componentName); var stackComponentObj = App.StackServiceComponent.find(configAction.get('componentName')); if (stackComponentObj) { hostComponent.isClient = stackComponentObj.get('isClient'); } if (hostComponentObj) { hostComponent.hostName = hostComponentObj.get('hostName'); } var isConditionTrue = App.configTheme.calculateConfigCondition(configAction.get("if"), serviceConfigs); var action = isConditionTrue ? configAction.get("then") : configAction.get("else"); hostComponent.action = action; switch (action) { case 'add': // Disable save button until a host is selected var isComponentToBeInstalled = !App.HostComponent.find().someProperty('componentName', hostComponent.componentName); if (isComponentToBeInstalled) { this.set('controller.saveInProgress', true); assignMasterOnStep7Controller.execute(this, 'ADD', hostComponent); } else { assignMasterOnStep7Controller.clearComponentsToBeDeleted(hostComponent.componentName); } break; case 'delete': assignMasterOnStep7Controller.execute(this, 'DELETE', hostComponent); break; } }, /** * * @param configCondition {App.ThemeCondition} * @param isConditionTrue {boolean} */ changeConfigAttribute: function(configCondition, isConditionTrue) { var conditionalConfigName = configCondition.get("configName"); var conditionalConfigFileName = configCondition.get("fileName"); var serviceName = this.get('config.serviceName'); var serviceConfigs = this.get('controller.stepConfigs').findProperty('serviceName',serviceName).get('configs'); var action = isConditionTrue ? configCondition.get("then") : configCondition.get("else"); var valueAttributes = action.property_value_attributes; for (var key in valueAttributes) { if (valueAttributes.hasOwnProperty(key)) { var valueAttribute = App.StackConfigValAttributesMap[key] || key; var conditionalConfig = serviceConfigs.filterProperty('filename',conditionalConfigFileName).findProperty('name', conditionalConfigName); if (conditionalConfig) { conditionalConfig.set(valueAttribute, valueAttributes[key]); } } } }, /** * * @param subsectionCondition {App.ThemeCondition} * @param isConditionTrue {boolean} */ changeSubsectionAttribute: function(subsectionCondition, isConditionTrue) { var subsectionConditionName = subsectionCondition.get('name'); var action = isConditionTrue ? subsectionCondition.get("then") : subsectionCondition.get("else"); if (subsectionCondition.get('id')) { var valueAttributes = action.property_value_attributes; if (valueAttributes && !Em.none(valueAttributes['visible'])) { var themeResource; if (subsectionCondition.get('type') === 'subsection') { themeResource = App.SubSection.find().findProperty('name', subsectionConditionName); } else if (subsectionCondition.get('type') === 'subsectionTab') { themeResource = App.SubSectionTab.find().findProperty('name', subsectionConditionName); } themeResource.set('isHiddenByConfig', !valueAttributes['visible']); themeResource.get('configs').setEach('hiddenBySection', !valueAttributes['visible']); } } }, /** * set widget value same as config value * useful for widgets that work with intermediate config value, not original * for now used in slider widget * @abstract */ setValue: Em.K, /** * Config group bound property. Needed for correct render process in template. * * @returns {App.ConfigGroup|Boolean} */ configGroup: function() { return !this.get('config.group') || this.get('config.group.isDefault') ? false : this.get('config.group'); }.property('config.group.name'), /** * switcher to display config as widget or text field * @method toggleWidgetView */ toggleWidgetView: function() { if (!this.get('isWidgetViewAllowed')) { return false; } if (this.get('config.showAsTextBox')) { this.textBoxToWidget(); } else { this.widgetToTextBox(); } }, /** * switch display of config to text field * @method widgetToTextBox */ widgetToTextBox: function() { this.set("config.showAsTextBox", true); }, /** * switch display of config to widget * @method textBoxToWidget */ textBoxToWidget: function() { if (this.isValueCompatibleWithWidget()) { this.setValue(this.get('config.value')); this.set("config.showAsTextBox", false); } }, /** * check if config value can be converted to config widget value * IMPORTANT! Each config-widget that override this method should use updateWarningsForCompatibilityWithWidget * @returns {boolean} */ isValueCompatibleWithWidget: function() { return (this.get('isOverrideEqualityError') && !this.get('config.isValid')) || this.get('config.isValid') || !this.get('supportSwitchToTextBox'); }, /** * Initialize widget with incompatible value as textbox */ initIncompatibleWidgetAsTextBox : function() { this.get('config').set('showAsTextBox', !this.isValueCompatibleWithWidget()); }, /** * Returns true if raw value can be used by widget or widget view is activated. * @returns {Boolean} */ isWidgetViewAllowed: function() { if (!this.get('config.showAsTextBox')) { return true; } return this.isValueCompatibleWithWidget(); }.property('config.value', 'config.isFinal', 'config.showAsTextBox'), /** * Used in isValueCompatibleWithWidget * Updates issue-parameters if config is in the raw-mode * @param {string} message empty string if value compatible with widget, error-message if value isn't compatible with widget * @method updateWarningsForCompatibilityWithWidget */ updateWarningsForCompatibilityWithWidget: function (message) { this.setProperties({ warnMessage: message, 'config.warnMessage': message, issueMessage: message, configLabelClass: message ? 'text-warning' : '' }); } });