'use strict'; /* *+------------------------------------------------------------------------+ *| Licensed Materials - Property of IBM *| IBM Cognos Products: Dashboard *| (C) Copyright IBM Corp. 2017, 2020 *| *| US Government Users Restricted Rights - Use, duplication or disclosure *| restricted by GSA ADP Schedule Contract with IBM Corp. *+------------------------------------------------------------------------+ */ define(['jquery', 'underscore', '../../app/nls/StringResources'], function ($, _, StringResources) { var _singletonInstance = null; var PropertiesUtil = function PropertiesUtil() { if (PropertiesUtil.prototype._singletonInstance) { return PropertiesUtil.prototype._singletonInstance; } }; /** Loops through an array of properties to add appropriate callbacks and default values @param {object} properties - the array of properties to process @param {object} widgetInstance - the widget instance which wants to display these properties @return {Promise} resolved once all the properties have been processed **/ PropertiesUtil.prototype.processProperties = function (properties, widgetInstance, dashboardApi) { var _this = this; if (!properties) { return false; } var translationService = dashboardApi.getDashboardCoreSvc('TranslationService'); var removeTranslationIcon = function removeTranslationIcon(property) { // if the property has a tanslationIconNode defined then we were showing the missing translation dot. // Since we're just changed the property make sure the dot gets removed. if (translationService && property.translationIconNode) { translationService.removeTranslationIcon(property.translationIconNode); property.translationIconNode = null; } }; var onChangeCallback = function onChangeCallback(property, propertyName, propertyValue) { if (property.displayMultiplier) { propertyValue = Math.round(propertyValue / property.displayMultiplier); } this._callPropertyOnChangeCallback(property, widgetInstance, propertyName, propertyValue); widgetInstance.onPropertyChange(propertyName, propertyValue); removeTranslationIcon(property); }; var promises = []; properties.forEach(function (property, i) { var customOnChangeCallback = _this._findCallbackFunction(property, 'onChange', widgetInstance); // Ignore hidden properties or the banner if (property.public === false || property.type === 'Banner') { return; } var propertiesOverride = _this._getPropertiesOverride(property, widgetInstance); if (propertiesOverride.removeProperty === true) { properties.splice(i, 1); i--; return; } // Replace any overriden properties $.extend(property, propertiesOverride); // Property UI Controls always need a name property if (property.id && !property.name) { property.name = property.id; } // If the property is tagged as multilingual and we're in translation mode and we're missing the translation for the current locale if (property.multilingual && translationService && translationService.isInTranslationMode()) { var model = widgetInstance.visModelManager || widgetInstance.model; var multilingualAttributeInfo = model.getMultilingualAttribute(property.id); if (!multilingualAttributeInfo) { return; } var multilingualProperty = multilingualAttributeInfo.multilingualProperty; if (multilingualProperty && multilingualProperty.needsTranslation()) { // This will end up being called by PropertyUIControlView after the property has been rendered. property.appendTranslationIcon = translationService.appendTranslationIcon; } } if (property.type === 'IconPicker') { _this._handleIconPickerProperty(property, widgetInstance); } else if (property.items && property.items instanceof Array) { promises.push(_this.processProperties(property.items, widgetInstance, dashboardApi)); } else if (property.type === 'ColorPicker') { promises.push(_this.handleColorPickerProperty(property, widgetInstance, dashboardApi)); } else if (property.type === 'NewPalette' || property.type === 'Palette') { property.type = 'NewPalette'; promises.push(_this.handleNewPaletteProperty(property, widgetInstance, dashboardApi)); } else if (property.type === 'ToggledCombo') { _this._handleToggledComboProperty(property, widgetInstance); } else if (property.module && property.module.indexOf('UiSlider') >= 0) { _this._handleUISlider(property, widgetInstance); } else if (!property.type) { //If there is no type, this is not a property by itself } else { var currentProp = _this._getPropertyValue(widgetInstance, property.name); var currentPropValue = currentProp && currentProp.value ? currentProp.value : currentProp; if (property.type === 'CheckBox' || property.type === 'ToggleButton') { if (currentPropValue === false) { property.checked = false; } else { property.checked = currentPropValue === true || property.defaultValue === true; } } else if (property.type === 'DropDown') { var valueToUse = void 0; /* * Enums - Dropdowns, are difficult because of Vida. Basically, we want * to check if the the current value returned is a valid. If it isn't, * use the property default. If it is, then we should check if it is an * object with property called 'name', which Vida enums do. If it does, * then use the name prop. All else fails, use the current prop value. */ if (currentPropValue !== null && currentPropValue !== undefined) { if (currentPropValue.hasOwnProperty('name')) { valueToUse = currentPropValue.name; } else { valueToUse = currentPropValue; } } else { valueToUse = property.defaultValue; } property.defaultValue = valueToUse; } else { property.value = currentPropValue || currentPropValue === 0 ? currentPropValue : ''; } if (property.displayMultiplier && property.value) { var scaledValue = property.value * property.displayMultiplier; property.value = property.decimalPlaces ? scaledValue.toFixed(property.decimalPlaces) : Math.round(scaledValue); } property.onChange = onChangeCallback.bind(_this, property); } if (customOnChangeCallback) { property.onChange = function (property, propertyName, propertyValue) { customOnChangeCallback(propertyName, propertyValue); removeTranslationIcon(property); }.bind(_this, property); } var customValidatorCallback = _this._findCallbackFunction(property, 'customValidatorCallback', widgetInstance); if (customValidatorCallback) { property.customValidatorCallback = customValidatorCallback; } // Replace any overriden properties once again in case we set defaults $.extend(property, propertiesOverride); }); return Promise.all(promises); }; /** If the property object defines a 'preRenderCallback' property then call it to get any dynamic properties that should be applied to the property UI control @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties @return {object} - returns the member variables (properties) to override **/ PropertiesUtil.prototype._getPropertiesOverride = function (property, widgetInstance) { var propertiesOverride = {}; var callbackFunction = this._findCallbackFunction(property, 'preRenderCallback', widgetInstance); // Call the callback to get any dynamic property info if (callbackFunction) { propertiesOverride = callbackFunction(property, widgetInstance.visModel || widgetInstance.model); } return propertiesOverride; }; /** Callback properties can either be a function or a string representing the name of the function. Use this method to always get back a function object @param {object} property - the property object for which to get any dynamically generated member variables @param {string} propertyName - the widget instance which wants to display these properties @return - the callback function or null **/ PropertiesUtil.prototype._findCallbackFunction = function (property, propertyName, widgetInstance) { if (property && property[propertyName]) { if (typeof property[propertyName] === 'function') { return property[propertyName].bind(property); } else { var functionName = property[propertyName]; var currentVis = widgetInstance.getCurrentVis ? widgetInstance.getCurrentVis() : null; if (typeof widgetInstance[functionName] === 'function') { return widgetInstance[functionName].bind(widgetInstance); } else if (currentVis && typeof currentVis[functionName] === 'function') { return currentVis[functionName].bind(currentVis); } else { throw new Error('preRenderCallback defined but function isn\'t available to call.', property); } } } return null; }; /** A property can define a custom onChange callback. This is mostly used when one property change effects other properties, for example the show/hide legend property to hide/show the legened position property. @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties @param {string} propertyName - the name of the property @param {object} propertyValue - the value of the property **/ PropertiesUtil.prototype._callPropertyOnChangeCallback = function (property, widgetInstance, propertyName, propertyValue) { if (property.onChangeCallback) { property.onChangeCallback(propertyName, propertyValue, widgetInstance.visModel || widgetInstance.model); } }; /** Handles setting the callbacks and dynamic properties of a slider property control @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties **/ PropertiesUtil.prototype._handleUISlider = function (property, widgetInstance) { var currentPropValue = this._getPropertyValue(widgetInstance, property.name); if (currentPropValue || currentPropValue === 0 && property.sliderType === 'percentage') { currentPropValue = parseInt(currentPropValue * 100, 10); } property.start = currentPropValue || currentPropValue === 0 ? [currentPropValue] : [property.defaultValue]; property.startLabels = [property.description || property.label]; property.onChange = function (sliderValues) { // All our sliders currently only have one 'slide' var value = sliderValues[0]; // The value with have the postfix at the end, for example 75% if (property.format && property.format.postfix) { value = value.substring(0, value.indexOf(property.format.postfix)); } if (property.sliderType === 'percentage') { value = value * 0.01; } this._callPropertyOnChangeCallback(property, widgetInstance, property.name, value); widgetInstance.onPropertyChange(property.name, value); }.bind(this); }; /** Handles setting the callbacks and dynamic properties of a toggleCombo (auto refresh) property @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties **/ PropertiesUtil.prototype._handleToggledComboProperty = function (property, widgetInstance) { var propInfo = this._getPropertyValue(widgetInstance, property.name); if (!property.inputOptions) { property.inputOptions = {}; } if (!property.checkBoxOptions) { property.checkBoxOptions = {}; } if (!property.dropDownOptions) { property.dropDownOptions = {}; } property.useToggleButton = true; if (propInfo) { property.dropDownOptions.defaultValue = propInfo.unit; property.inputOptions.value = propInfo.value; property.checkBoxOptions.checked = propInfo.autoRefresh; } else { property.inputOptions.value = property.inputOptions.defaultValue || ''; property.checkBoxOptions.checked = false; } property.validateInput = this._validateToggleComboProperty.bind(this); property.onChange = function (propertyName, propertyValue) { this._callPropertyOnChangeCallback(property, widgetInstance, propertyName, propertyValue); widgetInstance.onPropertyChange(propertyName, propertyValue); }.bind(this); }; /** Handles setting the callbacks and dynamic properties of an IconPicker property @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties **/ PropertiesUtil.prototype._handleIconPickerProperty = function (property, widgetInstance) { var currentProp = this._getPropertyValue(widgetInstance, property.name); var currentPropValue = currentProp && currentProp.value ? currentProp.value : currentProp; if (currentPropValue !== null && currentPropValue !== undefined) { property.values = currentPropValue; } else if (property.defaultValue !== undefined) { property.values = property.defaultValue; } property.onChange = function (propertyName, propertyValue) { var values = propertyValue; if (propertyValue.length && propertyValue.length === 1) { values = propertyValue[0].name; } else if (propertyValue.length) { values = []; propertyValue.forEach(function (value) { values.push(value.name); }); } this._callPropertyOnChangeCallback(property, widgetInstance, propertyName, values); widgetInstance.onPropertyChange(propertyName, values); }.bind(this); }; /** * @param timeUnit - 'seconds', 'minutes', 'hours' * @returns a structure stating the min and max of the specified timeUnit * */ PropertiesUtil.prototype._getMaxMinRefreshValuesForUnits = function (timeUnit) { /* * The browser method used to provide a timed delay between refresh calls has * a max size of a signed integer. We must ensure we stay below this number * depending on the time unit */ var maxIntervalInSeconds = (Math.pow(2, 31) - 1) / 1000; var maxForTimeUnit; var values = {}; switch (timeUnit) { case 'minutes': values.min = 1; maxForTimeUnit = maxIntervalInSeconds / 60; values.max = Math.ceil(maxForTimeUnit - 1); break; case 'hours': values.min = 1; maxForTimeUnit = maxIntervalInSeconds / 3600; values.max = Math.ceil(maxForTimeUnit - 1); break; default: // includes seconds values.min = 5; values.max = Math.ceil(maxIntervalInSeconds - 1); } return values; }; /** Called when the toggleCombo (auto refresh) property changes @param {string} valueOfInput @param {string} valueOfDropDown **/ PropertiesUtil.prototype._validateToggleComboProperty = function (valueOfInput, valueOfDropDown) { if (!valueOfInput || !valueOfDropDown) { return false; } var isValid = $.isNumeric(valueOfInput); if (isValid) { var maxMinValuesForUnit = this._getMaxMinRefreshValuesForUnits(valueOfDropDown); // ensure the valueOfInput is between our current min and max. isValid = valueOfInput >= maxMinValuesForUnit.min && valueOfInput <= maxMinValuesForUnit.max; } return isValid; }; /** Handles setting the callbacks and dynamic properties of a Palette property @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties @param {object} dashboardApi @param {function} [onChangeCallback] - optional to override the default onchange behavior **/ PropertiesUtil.prototype.handleNewPaletteProperty = function (property, widgetInstance, dashboardApi, onChangeCallback) { var _this2 = this; if (!property.paletteType) { throw new Error('Palette property needs a "paletteType"', property); } var colorsService = dashboardApi.getFeature('Colors'); var getPaletteId = function getPaletteId() { var paletteId = _this2._getPropertyValue(widgetInstance, property.name); paletteId = paletteId || property.defaultValue || colorsService.getDefaultPaletteName(property.paletteType); return paletteId; }; var paletteId = getPaletteId(); var continuousPalette = property.paletteType === 'HeatPalette'; var createPaletteType = continuousPalette ? 'continuous' : 'standard'; var isReverse = function isReverse() { var currentOrder = _this2._getPropertyValue(widgetInstance, property.reversePalettePropName); return currentOrder === 'DarkerForLowerValue' && continuousPalette; }; property.menuItems = []; if (continuousPalette) { property.menuItems.push('reverse'); } property.reverse = isReverse(); property.createPaletteType = createPaletteType; return colorsService.getPalette({ paletteId: paletteId, type: property.paletteType, defaultPaletteId: property.defaultValue, addMissingBaseColors: property.addMissingBaseColors, defaultIfNotFound: true, forProperties: true }).then(function (selPalette) { property.palette = selPalette; property.hasSection = true; property.readOnly = true; if (selPalette.my && !selPalette.public) { property.menuItems.push('edit'); } property.onChangePalette = onChangeCallback ? onChangeCallback : function (propertyName, paletteItem) { _this2._callPropertyOnChangeCallback(property, widgetInstance, propertyName, paletteItem.id); widgetInstance.onColorPaletteChanged(_this2.buildPropChangePayload(propertyName, paletteItem.id)).then(function () { widgetInstance.refreshPropertiesPane(); }); }; // Handle selection when creating a palette var onCreatePalette = function onCreatePalette(paletteId) { var prefixCMId = '__CM__'; var cmId = prefixCMId + paletteId; property.onChangePalette(property.name, { id: cmId }); return cmId; }; property.onCreatePalette = onCreatePalette; property.onChange = function (propertyName, paletteItem) { var orderValue = paletteItem.reverse === true ? 'DarkerForLowerValue' : 'DarkerForHigherValue'; _this2._callPropertyOnChangeCallback(property, widgetInstance, property.reversePalettePropName, orderValue); widgetInstance.onHeatScalePaletteChanged(_this2.buildPropChangePayload(property.reversePalettePropName, orderValue)); }; property.changePaletteCb = function () { widgetInstance.dashboardApi.getDashboardSvc('propertiesManager').then(function (propertyManager) { var options = { label: property.linkLabel, overlay: true, width: '320px', content: { module: 'authoring-common/changePaletteView', selectedId: paletteId, onCreatePalette: onCreatePalette, createPaletteType: createPaletteType, name: property.name, getPalettes: function getPalettes() { return colorsService.getPalettes({ type: property.paletteType, includeUserColorPalette: property.includeUserColorPalette, forProperties: true }); }, getPaletteId: getPaletteId.bind(widgetInstance), reverse: isReverse(), onChange: property.onChangePalette, component: 'dashboard' } }; widgetInstance.dashboardApi.prepareGlassOptions(options); propertyManager.addChild(options); }); }; return property; }); }; /** Builds the payload needed for the property change event @param {string} propName - the name of the property changing @param {object} propValue - the new value for the property **/ PropertiesUtil.prototype.buildPropChangePayload = function (propName, propValue) { return { 'category': propName, 'item': propValue, 'transactionId': _.uniqueId('prop_change') }; }; /** Add the ability to a color picker to handle custom colors @param {object} property - Property object @param {object} colorsService - ref to the color service **/ PropertiesUtil.prototype.addCustomColorCapability = function (property, colorsService) { property.createCustomColor = function (hex) { var className = colorsService.addCustomColor(hex); var colorProp = colorsService.createColorDefinition(className, hex, hex); colorProp.type = 'ColorCode'; colorProp.hidden = true; return colorProp; }; if (colorsService.isCustomColor(property.selectedName) && property.items) { var hex = colorsService.getHexColorFromClassName(property.selectedName); var colorProp = colorsService.createColorDefinition(property.selectedName, hex, hex); colorProp.hidden = true; property.items.push(colorProp); } return property; }; /** Checks to see if the palette property is set in the model, if it isn't will return the default palette name for the type of palette being asked for @param {object} widgetInstance - the widget instance which wants to display these properties @param {object} dashboardApi @param {string} palettePropertyName - the name of the property which contains the palette name @param {string} paletteType - the type of palette being asked for **/ PropertiesUtil.prototype._getColorPalettePropertyValue = function (widgetInstance, colorsService, palettePropertyName, paletteType) { var propertyValue = this._getPropertyValue(widgetInstance, palettePropertyName); return propertyValue || colorsService.getDefaultPaletteName(paletteType); }; /** Checks the model and the visModel to see if the property has been set and return the value if found. @param {object} widgetInstance - the widget instance which wants to display these properties @param {string} propertyName - the name of the property @return {object} - the value of the set property or null if the property isn't set in the model **/ PropertiesUtil.prototype._getPropertyValue = function (widgetInstance, propertyName) { if (widgetInstance.model && widgetInstance.model[propertyName] !== undefined) { return widgetInstance.model[propertyName]; } else if (widgetInstance.visModel && widgetInstance.visModel.getPropertyValue(propertyName) !== null) { return widgetInstance.visModel.getPropertyValue(propertyName); } else if (widgetInstance.visAPI && widgetInstance.visAPI.getPropertyValue(propertyName) !== null) { return widgetInstance.visAPI.getPropertyValue(propertyName); } else if (widgetInstance[propertyName] !== 'undefined' && typeof widgetInstance[propertyName] !== 'function') { return widgetInstance[propertyName]; } return null; }; /** Handles setting the callbacks and dynamic properties of a ColorPicker property @param {object} property - the property object for which to get any dynamically generated member variables @param {object} widgetInstance - the widget instance which wants to display these properties @param {object} dashboardApi **/ PropertiesUtil.prototype.handleColorPickerProperty = function (property, widgetInstance, dashboardApi) { var _this3 = this; if (!property.paletteType) { throw new Error('Color picker property needs a "paletteType"', property); } var colorsService = dashboardApi.getFeature('Colors'); property.onChange = function (propertyName, propertyValueInfo) { this._callPropertyOnChangeCallback(property, widgetInstance, property.propertyName, propertyValueInfo.name); widgetInstance.onPropertyChange(propertyName, propertyValueInfo.name); }.bind(this); var currentValue = this._getPropertyValue(widgetInstance, property.name); // when current value is 0, it should keep it property.selectedName = !currentValue && currentValue !== 0 && property.defaultValue ? property.defaultValue : currentValue; return this._getColorPickerColors(property, widgetInstance, dashboardApi).then(function (colors) { // If the colors being show are based off another property, then we need to tweak to payload for backwards compatibility if (property.palettePropertyName) { property.items = []; colors.forEach(function (color, index) { property.items.push({ 'id': index, 'name': index, 'value': color.hexValue, 'type': 'ColorCode', 'label': color.label }); }); } else { property.items = colors; } // if (property.addButton) { property = _this3.addCustomColorCapability(property, colorsService); } // If the current value saved in the model is higher then the number of colors available, default // back to the first color if (property.selectedName > colors.length) { property.selectedName = 0; } else if (!property.selectedName && property.selectedName !== 0 && property.defaultColorIndex !== undefined) { // when selectedName value is 0, it should keep it property.selectedName = property.defaultColorIndex; } else if (property.missingIsLast && property.selectedName) { var selectedColor = _.find(property.items, function (color) { return color.name === property.selectedName; }); if (!selectedColor) { property.selectedName = property.items[property.items.length - 1].name; } } return property; }); }; PropertiesUtil.prototype._getColorPickerColors = function (property, widgetInstance, dashboardApi) { var colorsService = dashboardApi.getFeature('Colors'); if (property.paletteType === 'DashboardColorSet') { return Promise.resolve(colorsService.getDashboardColorSet().slice(0)); } var paletteId = property.palettePropertyName ? this._getColorPalettePropertyValue(widgetInstance, colorsService, property.palettePropertyName, property.paletteType) : colorsService.getColorSetId(); return colorsService.getPaletteColors({ paletteId: paletteId, type: property.paletteType, addMissingBaseColors: property.addMissingBaseColors }).then(function (colors) { return colors.slice(0); }); }; /** Currently only being called by the custom widget since it doesn't use contributions like our other widgets. Returns the default properties to show on the general property tab. **/ PropertiesUtil.prototype.getGeneralProperties = function (widgetInstance, dashboardApi) { var properties = [{ 'id': 'fillColor', 'type': 'ColorPicker', 'label': StringResources.get('propFillColor'), 'showHexValue': true, 'addButton': true, 'open': false, 'ariaLabel': StringResources.get('propFillColor'), 'paletteType': 'ColorPalette', 'defaultValue': 'color1', 'tabName': StringResources.get('tabName_general'), 'sectionName': StringResources.get('sectionName_appearance') }, { 'id': 'borderColor', 'type': 'ColorPicker', 'label': StringResources.get('propBorderColor'), 'open': false, 'showHexValue': true, 'addButton': true, 'ariaLabel': StringResources.get('propBorderColor'), 'paletteType': 'ColorPalette', 'defaultValue': 'transparent', 'tabName': StringResources.get('tabName_general'), 'sectionName': StringResources.get('sectionName_appearance') }]; if (widgetInstance) { return this.processProperties(properties, widgetInstance, dashboardApi).then(function () { return properties; }); } else { return Promise.resolve(properties); } }; /** * Method that takes our propertyUIControl spec and generates properties for our public API. This is temporary until * we clean up our properties logic throughout the code * @param {*} properties * @param {*} widgetInstance * @param {*} dashboardApi */ PropertiesUtil.prototype.propertyUIControlToPublicAPI = function (properties, widgetInstance, dashboardApi) { var _this4 = this; var propertiesList = []; var setValue = function setValue(property, value) { if (dashboardApi.getMode() !== 'authoring') { dashboardApi.getGlassCoreSvc('.Logger').error('Trying to set a property without being in authoring mode.'); return false; } if (property.customValidatorCallback) { var validInfo = property.customValidatorCallback(value); if (validInfo && validInfo.isValid === false) { dashboardApi.getGlassCoreSvc('.Logger').error(validInfo.message); return false; } } if (property.onChange) { if (property.module && property.module.indexOf('UiSlider') >= 0) { property.onChange([value]); } else if (property.type === 'ColorPicker') { var valueToSet = value && value.startsWith('#') ? dashboardApi.getFeature('Colors').addCustomColor(value) : value; property.onChange(property.name, { name: valueToSet }); } else if (property.type === 'NewPalette') { property.onChangePalette(property.name, { id: value }); } else { property.onChange(property.name, value); } return true; } }; var getValue = function getValue(property, widgetInstance) { var propValue = _this4._getPropertyValue(widgetInstance, property.name); if (propValue) { return propValue; } return property.defaultValue !== undefined ? property.defaultValue : null; }; properties.forEach(function (property) { // For now guard against not having an onChange property. This currently happens with complex property where the onChange is // buried inside of the items property. if (property.type === 'Banner' || property.public === false || !property.onChange) { return null; } // Currently only expose the minimum needed for system tests. Once this starts being used for our UI we'll // need to augment what's exposed in the API var propertyApi = { name: property.name, getValue: getValue.bind(_this4, property, widgetInstance), setValue: setValue.bind(_this4, property) }; propertiesList.push(propertyApi); }); return propertiesList; }; var _static = { getInstance: function getInstance() { if (!_singletonInstance) { _singletonInstance = new PropertiesUtil(); } return _singletonInstance; } }; return _static.getInstance(); }); //# sourceMappingURL=PropertiesUtil.js.map