JavaScript Extension Development API for Oracle Visual Builder Cloud Service - Classic Applications

Source: pages.dt/js/api/Binding.js

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;
});