define([
'core/js/api/Listenable',
'pages.dt/js/api/ViewGeneratorSession',
'translations.dt/js/spi/TranslatableMixin'
], function(Listenable,
ViewGeneratorSession,
TranslatableMixin) {
'use strict';
/**
* Holds {@link pages.dt/js/api/View View}'s properties. A view keeps its
* properties in a Properties object which is usually accessed by a property
* inspector for modifications and by a
* {@link components.dt/js/spi/generators/Generator Generator} to build
* a specific DOM element according to the property values.
*
* @AbcsExtension stable
* @version 16.3.5
*
* @exports pages.dt/js/api/Properties
* @constructor
* @param {Object} [propertyMap] key-value pair map of property names and values.
* If defined the newly created instance will be initialized with keys and values
* from the map.
*
* @see {@link pages.dt/js/api/View#getProperties View.getProperties} to get view's
* properties object
*
* @example <caption>How to create and use Properties</caption>
* var map = {
* myProp1: 'value 1',
* myProp2: 'value 2'
* };
* // create a initialized instance
* var properties = new Properties(map);
* // will yield 'value 1'
* properties.getValue('myProp1');
* // updated the stored value
* properties.setValue('myProp1', 'modification');
* // returns updated value: 'modification'
* properties.getValue('myProp1');
*/
var Properties = function(propertyMap) {
AbcsLib.checkThis(this);
AbcsLib.checkParameterCount(arguments, 0, 1);
Listenable.call(this);
this._map = propertyMap ? AbcsLib.clone(propertyMap) : {};
this._initSilentProperties();
TranslatableMixin.call(this);
TranslatableMixin.prototype.__initTranslatables.call(this);
};
AbcsLib.mixin(Properties, TranslatableMixin, Listenable);
Properties.EVENT_PROPERTY_REMOVED = 'propertyRemoved';
Properties.EVENT_PROPERTY_SET = 'propertySet';
Properties.EVENT_SILENT_PROPERTY_SET = 'silentPropertySet';
Properties.EVENT_SILENT_PROPERTY_REMOVED = 'silentPropertyRemoved';
Properties._SILENT_PROPERTIES_KEY = '__silentProperties';
Properties._ERROR_REDEFINED_SILENT_PROPERTIES = 'It is not allowed to manually redefine Silent Properties value!';
/**
* Sets silent properties.
*
* @param {Object} [silentProperties] - key-value map of silent properties
* (it is used ONLY if there is NO silent properties map already in {map} or the existing map is empty!)
*
*/
Properties.prototype.setSilentProperties = function (silentProperties) {
this._initSilentProperties(silentProperties);
};
Properties.prototype._initSilentProperties = function (silentProperties) {
if (!this._map[Properties._SILENT_PROPERTIES_KEY] || !Object.keys(this._map[Properties._SILENT_PROPERTIES_KEY]).length) {
this._map[Properties._SILENT_PROPERTIES_KEY] = silentProperties || {};
}
};
Properties.prototype.isEmpty = function() {
return this.getKeys().length === 0;
};
/**
* Gets all keys which are stored in the {@link pages.dt/js/api/Properties Properties}
* object.
*
* <p>One can use this method i.e. to check the existence of the key
* or to iterate the whole keys list. Values for the given key can be obtained
* by the {@link pages.dt/js/api/Properties#getValue Properties.getValue} method then.</p>
*
*
* @returns {String[]} list of Properties's keys
* @see {@link pages.dt/js/api/Properties#getValue Properties.getValue}
*
* @example <caption>Seeks whether the key was defined in the Properties object.</caption>
* // let's assume we are getting properties instance which we want to check
* var keyToCheck = 'coolCustomKey';
* // checks the list of all keys for the keyToCheck availability
* var keyFound = properties.getKeys().indexOf(keyToCheck) !== -1;
*
* @example <caption>Iterates the properties's keys and gather values defined by several specific keys.</caption>
* // let's assume we are getting properties instance which we want to prune
* var result = [];
* properties.getKeys().forEach(function(key) {
* switch (key) {
* case 'special':
* ...
* case 'extra':
* result.push(properties.getValue(key));
* break;
* default:
* // not special keys are no-ops
* }
* });
*/
Properties.prototype.getKeys = function() {
AbcsLib.checkParameterCount(arguments, 0);
return Object.keys(this._map);
};
/**
* Returns the value stored in the {@link pages.dt/js/api/Properties properties}
* object under the given key or <code>undefined</code> if the key does not exist.
*
* @AbcsExtension stable
* @version 16.3.5
*
* @param {String} key property name
* @returns {*} property value, may be <code>undefined</code> if such key doesn't exist
*
* @see {@link pages.dt/js/api/Properties#setValue Properties.setValue} which sets values for keys
*
* @example <caption>Getting values from the Properties object.</caption>
* // creation of the new properties object
* var properties = new Properties({
* 'firstProperty': 'hello',
* 'secondProperty': 'world'
* });
* // will yield 'hello world'
* var welcomeText = properties.getValue('firstProperty')
* + ' ' + properties.getValue('secondProperty');
* // will yield 'undefined' value
* properties.getValue('nonExistingValue');
*/
Properties.prototype.getValue = function(key) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(key, 'key');
if (ViewGeneratorSession.isInSession()) {
return this._getRawValue(key);
} else {
return AbcsLib.clone(this._getRawValue(key));
}
};
Properties.prototype._getRawValue = function (key) {
return this._map[key];
};
/**
* Removes entry from the properties object under the given key.
*
* <p>If the given key (entry) dosn't exist no operation is performed.</p>
*
* @param {String} key property name to be removed
*
* @example <caption>Cleans up all fields of the properties object.</caption>
* // iterates the properties
* properties.getKeys().forEach(function(propertyKey) {
* // removes every entry of the properties instance
* properties.removeEntry(propertyKey);
* });
*/
Properties.prototype.removeEntry = function(key) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(key, 'key');
// events are fired only in case real property removal
if (this._map[key]) {
delete this._map[key];
this.fireEvent(Properties.EVENT_PROPERTY_REMOVED, key);
}
};
/**
* Puts the passed value into the {@link pages.dt/js/api/Properties properties} object
* under the given key. The method returns the old value for the key or <code>undefined</code>
* if the key didn't exist yet.
*
* <p><strong>You should not call this method inside a {@link components.dt/js/spi/generators/Generator Generator}</strong>.
* Setting a property value may trigger another page rebuild and a subsequent
* call to your Generator, so you may introduce an endless loop.</p>
*
* @AbcsExtension stable
* @version 16.3.5
*
* @param {String} key property name used for properties entry identification
* @param {String|translations/js/api/Translatable} value property value to be set into the properties entry
* @returns {*} old property value or <code>undefined</code> if the key didn't exist yet
*
* @see {@link pages.dt/js/api/Properties#getValue Properties.getValue} which gets values for keys
*
* @example <caption>Sets values into the Properties object.</caption>
* // creation of the new properties object
* var properties = new Properties({
* 'firstProperty': 'hello'
* });
* // will yield undefined as the previous value
* properties.setValue('secondProperty', 2);
* // will yield 'hello'
* properties.getValue('firstProperty');
* // will overwrite the 'firstProperty' with the value 'bye'
* var formerValue = properties.setValue('firstProperty', 'bye');
* console.log('Previous \'firstProperty\' value was \'' + formerValue + '\'.');
*/
Properties.prototype.setValue = function(key, value) {
AbcsLib.checkParameterCount(arguments, 2);
AbcsLib.checkDefined(key, 'key');
if (key === Properties._SILENT_PROPERTIES_KEY) {
throw new Error(Properties._ERROR_REDEFINED_SILENT_PROPERTIES);
}
var old = this._getRawValue(key);
if (value !== old) {
this._map[key] = AbcsLib.clone(value);
this.fireEvent(Properties.EVENT_PROPERTY_SET, {key: key, value: value});
}
return old;
};
Properties.prototype.setSilentValue = function(key, value) {
var old = this._getRawSilentValue(key);
if (value !== old) {
this._map[Properties._SILENT_PROPERTIES_KEY][key] = AbcsLib.clone(value);
this.fireEvent(Properties.EVENT_SILENT_PROPERTY_SET, {key: key, value: value});
}
return old;
};
Properties.prototype.getSilentValue = function(key) {
if (ViewGeneratorSession.isInSession()) {
return this._getRawSilentValue(key);
} else {
return AbcsLib.clone(this._getRawSilentValue(key));
}
};
Properties.prototype._getRawSilentValue = function(key) {
return this._map[Properties._SILENT_PROPERTIES_KEY][key];
};
Properties.prototype.removeSilentEntry = function(key) {
delete this._map[Properties._SILENT_PROPERTIES_KEY][key];
this.fireEvent(Properties.EVENT_SILENT_PROPERTY_REMOVED, key);
};
/**
* Gets the serialized Properties object (as JSON).
*
* <p>Serialized version of the {@link pages.dt/js/api/Properties Properties} object is a simple
* hash map containing all properties's key-value entries which can be used also in the Properties
* constructor for creation of a new instance (that leads to the properties's clone).</p>
*
* @AbcsExtension stable
* @version 16.3.5
* @returns {Object} JSON holding all properties's key-value entries
* @see {@link pages.dt/js/api/Properties Properties.constructor} which accepts the definition object
*
* @example <caption>Merges two existing properties instances together.</caption>
* // exports properties as maps
* var definition1 = properties1.getDefinition();
* var definition2 = properties2.getDefinition();
* // uses jquery framework to merge the JSON objects
* var mergedDefinition = $.extend({}, definition1, definition2);
* // creates new properties with all values
* var mergedProperties = new Properties(mergedDefinition);
*/
Properties.prototype.getDefinition = function() {
AbcsLib.checkParameterCount(arguments, 0);
var self = this;
var def = {};
self.getKeys().forEach(function(key) {
def[key] = self.getValue(key);
});
return def;
};
/**
* Temporarily disconnects firing events until the <code>task</code> callback
* finishes.
* @param {Function} task
*/
Properties.prototype.runSilently = function (task) {
var fireEvent = this.fireEvent;
try {
// set fireEvent to no-op in selent mode
this.fireEvent = function () {};
task();
} finally {
this.fireEvent = fireEvent;
}
};
// TranslatableMixin support
/**
* Adds translatable support to the properties object. The method will
* set the translation root of the current properties instance. The
* translation root will prefix every individual translatable key
* stored in the translation metadata table.
*
* @AbcsExtension unstable
* @version 16.3.1
* @see {@link translations/js/api/I18n#key}
* @param {String} translationRoot - the translation root for <b>this</b> properties
*/
Properties.prototype.addTranslationSupport = function (translationRoot) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(translationRoot, 'translation root');
var self = this;
self._setTranslatableRoot(translationRoot);
// When the component root is available, re-scan all translatables and update
// the keys
self.getTranslatableKeys().forEach(function (key) {
self.__updateKey(key, self._map[key]);
});
};
Properties.prototype.copyTranslatablesForNewRoot = function (newTranslationRoot) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(newTranslationRoot, 'newTranslationRoot');
if (this.hasTranslatableRoot()) {
this._copyTranslatablesForNewRoot(newTranslationRoot);
}
};
Properties.prototype._copyTranslatablesForNewRoot = function(newTranslationRoot) {
var self = this;
var oldTranslationRoot = self.getTranslatableRoot();
self._setTranslatableRoot(newTranslationRoot);
self.getTranslatableKeys().forEach(function (key) {
var nlsValue = self._map[key];
if (oldTranslationRoot !== newTranslationRoot && oldTranslationRoot !== TranslatableMixin.__DEFAULT_I18N_ROOT
&& oldTranslationRoot !== TranslatableMixin.__NO_I18N_KEYS_ROOT && !AbcsLib.Translations.isKeyDtOnly(nlsValue.key())) {
self._processNlsValueForNewRoot(nlsValue, key);
} else {
self.__updateKey(key, self._map[key]);
}
});
};
Properties.prototype._processNlsValueForNewRoot = function (nlsValue, key) {
if (nlsValue.hasTranslation()) {
this.setValue(key, this.createTranslatable(key).store(nlsValue.toString()));
} else if (!nlsValue.isEmptyStringWrapper()) {
nlsValue.key(this.__getGloballyUniqueKey(key));
}
};
/**
* Checks if this properties instance has translation support enabled.
*
* @version 16.3.1
*/
Properties.prototype.hasTranslationSupport = function () {
return this.hasTranslatableRoot();
};
Properties.prototype._setTranslatableRoot = function (translatableRoot) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(translatableRoot, 'translatable root');
this.setSilentValue(TranslatableMixin.TRANSLATABLE_ROOT, translatableRoot);
};
/** @inheritdoc **/
Properties.prototype.hasTranslatableRoot = function () {
AbcsLib.checkParameterCount(arguments, 0);
var translatableRoot = this.getSilentValue(TranslatableMixin.TRANSLATABLE_ROOT);
return translatableRoot && TranslatableMixin.__NO_I18N_KEYS_ROOT !== translatableRoot;
};
/** @inheritdoc **/
Properties.prototype.getTranslatableRoot = function () {
AbcsLib.checkParameterCount(arguments, 0);
return this.getSilentValue(TranslatableMixin.TRANSLATABLE_ROOT);
};
/**
* Get a list of all translatable keys.
*
* @private
* @returns {String[]} all translatable keys for the mixin
*/
Properties.prototype.getTranslatableKeys = function () {
var keys = this.getSilentValue(TranslatableMixin.TRANSLATABLE_KEYS) || [];
return keys;
};
return Properties;
});