define([
'core/js/api/Listenable',
'translations.dt/js/spi/TranslatableMixin'
], function(Listenable,
TranslatableMixin) {
'use strict';
/**
* Represents a binding of a view (UI component) to a view model.
* <p>
* Binding is usually created directly when an archetype is beeing created. For
* example list archetype contains one particular binding called 'callOpen' which
* allows client to define what action should happened when she click on the list
* row. The default action is usually opening entity detail with the selected row
* data, but it can teoretically be any custom JS code. Because we don't know what
* exact code will be invoked when the action happened, there is a possibility to
* include {@code $callOpen$} into the HTML template and if the archetype contains
* such binding item, it will be translated to the custom action defined by user.
* </p>
* @AbcsExtension
* @exports pages.dt/js/api/Binding
* @constructor
* @param {object} bindings - the binding definition - map of key-value pairs
*/
var Binding = function(bindings) {
AbcsLib.checkThis(this);
Listenable.apply(this, arguments);
if (bindings && Object.keys(bindings).length > 0) {
this._bindings = bindings;
this._initSilentBindings();
TranslatableMixin.apply(this, arguments);
TranslatableMixin.prototype.__initTranslatables.call(this);
} else {
this._bindings = {};
}
};
AbcsLib.mixin(Binding, TranslatableMixin, Listenable);
Binding.EVENT_BINDING_SET = 'bindingSet';
Binding.EVENT_SILENT_BINDING_SET = 'silentBindingSet';
Binding.EVENT_SILENT_BINDING_REMOVED = 'silentBindingRemoved';
Binding._SILENT_BINDINGS_KEY = '__silentBindings';
Binding._ERROR_REDEFINED_SILENT_BINDINGS = 'It is not allowed to manually redefine Silent Bindings value!';
Binding.prototype._initSilentBindings = function (silentBindings) {
if (!this._bindings[Binding._SILENT_BINDINGS_KEY] || !Object.keys(this._bindings[Binding._SILENT_BINDINGS_KEY]).length) {
this._bindings[Binding._SILENT_BINDINGS_KEY] = silentBindings || {};
}
};
Binding.prototype.getKeys = function() {
return Object.keys(this._bindings);
};
Binding.prototype.getValue = function(key) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(key, 'key');
return AbcsLib.clone(this._getRawValue(key));
};
Binding.prototype._getRawValue = function(key) {
return this._bindings[key];
};
/**
* Sets the binding value.
* @param {String} key binding key
* @param {} value binding value
* @deprecated use {@link pages.dt/js/api/Binding#setValue setValue} instead
* to be consistent with setting {@link pages.dt/js/api/Binding Binding}.
*/
Binding.prototype.putValue = function(key, value) {
AbcsLib.checkParameterCount(arguments, 2);
AbcsLib.checkDefined(key, 'key');
var old = this._getRawValue(key);
if (value !== old) {
this._bindings[key] = AbcsLib.clone(value);
this.fireEvent(Binding.EVENT_BINDING_SET, key, value);
}
};
/**
* Removes entry from the bindings object under the given key.
*
* <p>If the given key (entry) doesn't exist no operation is performed.</p>
*
* @param {String} key binding name to be removed
*/
Binding.prototype.removeEntry = function (key) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(key, 'key');
// events are fired only in case of existing key removal
if (key in this._bindings) {
delete this._bindings[key];
this.fireEvent(Binding.EVENT_BINDING_SET, key);
}
};
Binding.prototype.setValue = function(key, value) {
this.putValue(key, value);
};
/**
* Gets the definition of the binding.
*
* @type {object}
*/
Binding.prototype.getDefinition = function() {
return AbcsLib.clone(this._bindings);
};
// Silent bindings support
/**
* Temporarily disconnects firing events until the <code>task</code> callback
* finishes.
* @param {Function} task
*/
Binding.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;
}
};
Binding.prototype.setSilentValue = function(key, value) {
var old = this._getRawSilentValue(key);
if (value !== old) {
this._bindings[Binding._SILENT_BINDINGS_KEY][key] = AbcsLib.clone(value);
this.fireEvent(Binding.EVENT_SILENT_BINDING_SET, {key: key, value: value});
}
return old;
};
Binding.prototype.getSilentValue = function(key) {
return AbcsLib.clone(this._getRawSilentValue(key));
};
Binding.prototype._getRawSilentValue = function(key) {
this._bindings[Binding._SILENT_BINDINGS_KEY] = this._bindings[Binding._SILENT_BINDINGS_KEY] || {};
return this._bindings[Binding._SILENT_BINDINGS_KEY][key];
};
Binding.prototype.removeSilentEntry = function(key) {
delete this._bindings[Binding._SILENT_BINDINGS_KEY][key];
this.fireEvent(Binding.EVENT_SILENT_BINDING_REMOVED, key);
};
// TranslatableMixin support
/**
* Adds translatable support to the binding object. The method will
* set the translation root of the current binding instance. The
* translation root will prefix every individual translatable key
* stored in the translation metadata table.
*
* @AbcsExtension unstable
* @see {@link translations/js/api/I18n#key}
* @param {String} translationRoot - the translation root for <b>this</b> binding
*/
Binding.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._bindings[key]);
});
};
Binding.prototype.copyTranslatablesForNewRoot = function (newTranslationRoot) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(newTranslationRoot, 'newTranslationRoot');
if (this.hasTranslatableRoot()) {
this._copyTranslatablesForNewRoot(newTranslationRoot);
}
};
Binding.prototype._copyTranslatablesForNewRoot = function(newTranslationRoot) {
var self = this;
var oldTranslationRoot = self.getTranslatableRoot();
self._setTranslatableRoot(newTranslationRoot);
self.getTranslatableKeys().forEach(function (key) {
var nlsValue = self._bindings[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._bindings[key]);
}
});
};
Binding.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 binding instance has translation support enabled.
*
*/
Binding.prototype.hasTranslationSupport = function () {
return this.hasTranslatableRoot();
};
Binding.prototype._setTranslatableRoot = function (translatableRoot) {
AbcsLib.checkParameterCount(arguments, 1);
AbcsLib.checkDefined(translatableRoot, 'translatable root');
this.setSilentValue(TranslatableMixin.TRANSLATABLE_ROOT, translatableRoot);
};
/** @inheritdoc **/
Binding.prototype.hasTranslatableRoot = function () {
AbcsLib.checkParameterCount(arguments, 0);
if (this._bindings[Binding._SILENT_BINDINGS_KEY]) {
var translatableRoot = this.getSilentValue(TranslatableMixin.TRANSLATABLE_ROOT);
return translatableRoot && TranslatableMixin.__NO_I18N_KEYS_ROOT !== translatableRoot;
} else {
return false;
}
};
/** @inheritdoc **/
Binding.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
*/
Binding.prototype.getTranslatableKeys = function () {
var keys = this.getSilentValue(TranslatableMixin.TRANSLATABLE_KEYS) || [];
return keys;
};
return Binding;
});