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

Source: components.dt/js/spi/propertyinspectors/PropertyInspector.js

define([
], function (
) {

    'use strict';

    /**
     * A PropertyInspector is a description of how properties of a component
     * can be configured.
     *
     * <p>
     * UI for component configuration is displayed in a panel
     * called Property Inspector (PI), which can contain several tabs, and
     * usually contains at least one tab called 'Properties'. Each of the tabs
     * contains one Knockout template with a view model applied. View models can
     * be shared by several tabs, or each tab can have a dedicated view model.
     * </p><p>
     * When some settings are changed in some of the tabs, the view (for which
     * the PI is opened) should be updated accordingly as soon as possible.
     * (This is most often arranged by updating view's properties.)
     * </p><p>
     * Note: There are several standard PI tab names used in default set of ABCS
     * components. They can be retrieved from {@link module:components.dt/js/spi/propertyinspectors/TabNames TabNames}
     * object to avoid typos and repetition.
     * </p><p>
     * A property inspector can be created inside a {@link components.dt/js/spi/registration/PropertyInspectorProvider}.
     * See example below.
     * </p>
     * @param {components.dt/js/spi/propertyinspectors/PropertyInspector.Tab[]} tabs - Array of tab definitions.
     * @param {components.dt/js/spi/propertyinspectors/PropertyInspector.Controller} [controller] - PI controller.
     *
     * @example
     * <caption>
     * Implementation for two tabs:
     * </caption>
     * define([
     *   'components.dt/js/spi/propertyinspectors/PropertyInspector',
     *   'components.dt/js/spi/propertyinspectors/TabNames',
     *   'text!path/to/mycomponent/mycomponent_pi_data.html',
     *   'text!path/to/mycomponent/mycomponent_pi_properties.html'
     * ], function (
     *   PropertyInspector,
     *   TabNames,
     *   piDataTemplateHtml,
     *   piPropertiesTemplateHtml
     * ) {
     *   'use strict';
     *
     *   var MyPropertyInspectorProvider = function () {
     *   }
     *
     *   MyPropertyInspectorProvider.prototype.getPropertyInspector = function (view) {
     *     if (view.getType() !== 'THE_SUPPORTED_VIEW_TYPE') {
     *       return null;
     *     }
     *     var sharedModel = this._getPIModel(view);
     *     return new PropertyInspector(
     *       [
     *         {
     *           id: 'properties',
     *           title: TabNames.TAB_PROPERTIES,
     *           model: sharedModel,
     *           html: piPropertiesTemplateHtml
     *         },
     *         {
     *           id: 'data',
     *           title: TabNames.TAB_DATA,
     *           model: sharedModel,
     *           html: piDataTemplateHtml
     *         }
     *       ],
     *       {
     *         activate: function () {
     *           sharedModel.start();
     *         },
     *         beforeDeactivate: function () {
     *           return sharedModel.canClosePropertyInspector();
     *         },
     *         deactivate: function () {
     *           sharedModel.end();
     *         }
     *       }
     *   });
     *
     *   // Create Knockout view model for PI for the component view.
     *   //
     *   // (Model in this example is shared by all PI tabs, but we could also
     *   // create dedicated model for each tab.)
     *   //
     *   MyPropertyInspectorProvider.prototype._getPIModel = function (view) {
     *     var model = {};
     *     model.myText = ko.observable(view.getProperties().getValue('my_text'));
     *     model.myText.subscribe(function (value) {
     *       view.getProperties().setValue('my_text', value);
     *     });
     *
     *     model.message = ko.observable();
     *
     *     model.now = ko.observable();
     *
     *     // + some model properties for 'Data' tab.
     *
     *     model.canClosePropertyInspector = function () {
     *       var res = !model.myText() || model.myText().length === 3;
     *       model.message(res ? '' : 'Text must be empty or 3 chars long');
     *       return res;
     *     };
     *     model.start = function () {
     *       model.intervalId = window.setInterval(function () {
     *         model.now(new Date())
     *       }, 1000);
     *     };
     *     model.end = function () {
     *       window.clearInterval(model.intervalId);
     *     }
     *     return model;
     *   }
     *
     *   return new MyPropertyInspectorProvider();
     * });
     *
     * @example
     * <caption>
     * Example Knockout template in file with path
     * path/to/mycomponent/mycomponent_pi_properties.html. The template can be
     * loaded as RequireJS dependency with prefix 'text!', see example above.
     * </caption>
     * <div>
     *   <div data-bind="text: now"></div>
     *   <div>
     *      Text: <input type="text" data-bind="value: myText">
     *   </div>
     *   <div data-bind="text: message"></div>
     * </div>
     *
     * @AbcsExtension stable
     * @exports components.dt/js/spi/propertyinspectors/PropertyInspector
     * @final
     * @constructor
     * @version 16.3.5
     */
    var PropertyInspector = function (tabs, controller) {
        AbcsLib.checkThis(this);
        AbcsLib.checkParameterCount(arguments, 1, 1);
        AbcsLib.checkDefined(tabs, 'tabs');
        if (!(tabs instanceof Array)) {
            throw new Error(PropertyInspector._ERROR_TABS_NOT_ARRAY);
        }
        tabs.forEach(function (tab) {
            AbcsLib.checkDefined(tab.id, 'tab.id');
            if (!/^[\w]+$/.test(tab.id)) {
                throw new Error(PropertyInspector._ERROR_TAB_INVALID_ID(tab.id));
            }
            AbcsLib.checkDefined(tab.title, 'tab.title');
            if (!tab.html && !tab.template) {
                throw new Error(PropertyInspector._ERROR_TEMPLATE_REQUIRED(tab.title));
            }
            AbcsLib.checkObjectLiteral(tab, [
                'id', 'title', 'html', 'template', 'model'
            ]);
            AbcsLib.checkDataType(tab.id, AbcsLib.Type.STRING);
            AbcsLib.checkDataType(tab.title, AbcsLib.Type.STRING);
            if (tab.html) {
                AbcsLib.checkDataType(tab.html, AbcsLib.Type.STRING);
            } else if (tab.template) {
                AbcsLib.checkDataType(tab.template, AbcsLib.Type.STRING);
            }
        });
        if (controller) {
            var allowedKeys = ['activate', 'beforeDeactivate', 'deactivate'];
            AbcsLib.checkObjectLiteral(controller, allowedKeys);
            allowedKeys.forEach(function (k) {
                if (controller[k]) {
                    AbcsLib.checkDataType(controller[k], AbcsLib.Type.FUNCTION);
                }
            });
        }
        this._tabs = tabs;
        this._controller = controller;
    };

    /**
     * Error thrown if invalid 'tabs' argument is passed to the constructor.
     *
     * @type {string}
     */
    PropertyInspector._ERROR_TABS_NOT_ARRAY
            = 'Expected tabs argument to be an array';

    /**
     * Error thrown if tab id contains an invalid character.
     *
     * @param {string} id - Tab id.
     *
     * @returns {string}
     */
    PropertyInspector._ERROR_TAB_INVALID_ID = function (id) {
        return 'Invalid character in tab id "' + id + '", only English '
                + 'alphabet letters, numbers and underscores are allowed.';
    };

    /**
     * Error thrown if tab description doesn't contain required template
     * settings.
     *
     * @param {string} tabTitle
     * @returns {string}
     */
    PropertyInspector._ERROR_TEMPLATE_REQUIRED = function (tabTitle) {
        return 'Tab definition for "' + tabTitle + '" is missing "html" '
                + 'or (deprecated) "template" field.';
    };

    /**
     * Return tab definitions.
     *
     * @returns {module:components.dt/js/spi/propertyinspectors/PropertyInspector.Tab[]}
     */
    PropertyInspector.prototype.getTabs = function () {
        return this._tabs;
    };

    /**
     * Call 'activate' callback from the controller, if defined.
     *
     */
    PropertyInspector.prototype.activate = function () {
        if (this._controller && this._controller.activate) {
            this._controller.activate();
        }
    };

    /**
     * Call 'beforeDeactivate' callback from the controller and return it's
     * result. If the callback is not defined, return true.
     *
     * @returns {Boolean}
     */
    PropertyInspector.prototype.beforeDeactivate = function () {
        if (this._controller && this._controller.beforeDeactivate) {
            return this._controller.beforeDeactivate();
        } else {
            return true;
        }
    };

    /**
     * Call 'deactivate' callback from the controller, if defined.
     *
     */
    PropertyInspector.prototype.deactivate = function () {
        if (this._controller && this._controller.deactivate) {
            this._controller.deactivate();
        }
    };

    /**
     * Description of one tab in Property Inspector panel. Used in constructor
     * for {@link components.dt/js/spi/propertyinspectors/PropertyInspector PropertyInspector}.
     *
     * @AbcsExtension stable
     * @memberOf components.dt/js/spi/propertyinspectors/PropertyInspector
     * @objectLiteral
     * @version 16.3.5
     */
    var Tab = function () {};

    /**
     * Human-readable tab title, possibly translated.
     * <p>
     * Tip: Pre-defined names for standard Property Inspector tabs can be found
     * in {@link components.dt/js/spi/propertyinspectors/TabNames TabNames}.
     * </p>
     *
     * @AbcsExtension stable
     * @version 16.3.5
     * @type {string}
     */
    Tab.prototype.title;

    /**
     * Unique id of this tab (two tabs in a PI cannot have the same id).
     *
     * @AbcsExtension stable
     * @version 16.3.5
     * @type {string}
     */
    Tab.prototype.id;

    /**
     * Knockout view model for the tab.
     *
     * @AbcsExtension stable
     * @version 16.3.5
     * @type {object}
     */
    Tab.prototype.model;

    /**
     * Template name for the tab. The template has to be already loaded.
     *
     * @AbcsExtension stable
     * @version 16.3.5
     * @type {string}
     * @deprecated Deprecated in 17.2.3. Use ["html" field]{@link components.dt/js/spi/propertyinspectors/PropertyInspector.Tab#html} instead.
     */
    Tab.prototype.template;

    /**
     * HTML source fragment to be used as Knockout template for the tab.
     *
     * @AbcsExtension stable
     * @version 17.2.3
     * @type {string}
     */
    Tab.prototype.html;

    /**
     * Definition of callbacks to various events or situations related to PI.
     * Used in constructor for
     * {@link components.dt/js/spi/propertyinspectors/PropertyInspector PropertyInspector}.
     *
     * @AbcsExtension stable
     * @memberOf components.dt/js/spi/propertyinspectors/PropertyInspector
     * @objectLiteral
     * @version 16.3.5
     */
    var Controller = function () {};

    /**
     * Function called right after the templates were rendered in the Property
     * Inspector panel (with applied Knockout bindings for specified models).
     * This callback can be used to register listeners, start animations, or
     * perform additional UI changes.
     *
     * @AbcsExtension stable
     * @version 16.3.5
     */
    Controller.prototype.activate = function () {};

    /**
     * Function called when the PI is going to be closed. The function can
     * return a <code>falsy</code> value to prevent the PI from closing, e.g.
     * if it contains some invalid or unsaved settings.
     *
     * @AbcsExtension stable
     * @version 16.3.5
     * @returns {Boolean}
     */
    Controller.prototype.beforeDeactivate = function () {};

    /**
     * Function called when the PI was closed. This callback is appropriate for
     * removing of listeners and similar cleanup.
     *
     * @AbcsExtension stable
     * @version 16.3.5
     */
    Controller.prototype.deactivate = function () {};

    return PropertyInspector;
});