core/plugin-manager.js

/*
 * Your installation or use of this SugarCRM file is subject to the applicable
 * terms available at
 * http://support.sugarcrm.com/Resources/Master_Subscription_Agreements/.
 * If you do not agree to all of the applicable terms or do not have the
 * authority to bind the entity as an authorized representative, then do not
 * install or use this SugarCRM file.
 *
 * Copyright (C) SugarCRM Inc. All rights reserved.
 */

/**
 * Plugin manager.
 *
 * Example:
 *
 * ```
 * const PluginManager = require('./plugin-manager');
 *
 * PluginManager.register('fast-click-highlight', ['view', 'field'], {
 *     color : "red",
 *     events: {
 *         'click .fast-click-highlighted': 'onClickItem'
 *     },
 *     onClickItem: function(e) {
 *         alert(1)
 *     },
 *
 *     // The onAttach function will be called every time the plugin is
 *     // attached to a new component. It will be executed from the scope of
 *     // the component being attached to.
 *     //Applied after the plugin has been mixed into the component.
 *     onAttach: function(component, plugin) {
 *         this.on('render', function(){
 *             //same as plugin.color and component.$el.css
 *             this.$el.css('color', this.color);
 *         });
 *     }
 * });
 * ```
 *
 * If you want to use the current plugin for a view, you have to declare the
 * plugin in it:
 *
 * ```
 * const ViewManager = require('../view/view-manager');
 * var MyView = ViewManager.View.extend({
 *     initialize: function(options) {},
 *     plugins: ['fast-click-highlight'],
 *      ...,
 * });
 *
 * // or
 *
 * $plugins: [{
 *     'fast-click-highlight': {
 *         events: {
 *             'click article' : 'onClickItem'
 *         }
 *     }
 * }],
 *
 * onClickItem: function() {
 *     alert(2);
 * }
 * ```
 *
 * If you want to disable a plugin, you have to use the `disabledPlugins`
 * property as in the following example:
 *
 * ```
 * var MyView = ViewManager.View.extend({
 *     initialize: function(options) {},
 *     disabledPlugins: ['fast-click-highlight'],
 *     ...,
 * });
 * ```
 * @module Core/PluginManager
 */

/**
 * Gets a plugin by name.
 *
 * @param {string} name The plugin name.
 * @param {string} type The component type. Can be one of the following:
 *   'view', 'layout', 'field', 'model' or 'collection'.
 * @return {Object} The desired plugin object.
 * @private
 */
function get(name, type) {
    if (PluginManager.plugins[type] && PluginManager.plugins[type][name]) {
        return PluginManager.plugins[type][name];
    }
};

/**
 * Checks if a plugin is disabled.
 *
 * @param {View/Component|Data/Bean|Data/BeanCollection} component The
 *   component.
 * @param {string} pluginName The plugin name.
 * @return {boolean} `true` if the plugin is disabled; `false` otherwise.
 * @private
 */
function isPluginDisabled(component, pluginName) {
    return !!_.find(component.disabledPlugins, function(name) {
        return name === pluginName;
    });
};

/**
 * @alias module:Core/PluginManager
 */
const PluginManager = {
    /**
     * A hash map containing all registered plugins.
     */
    plugins: {
        view: {},
        field: {},
        layout: {},
        model: {},
        collection: {}
    },

    /**
     * Attaches a plugin to a view.
     *
     * @param {View/Component|Data/Bean|Data/BeanCollection} component The
     *   component.
     * @param {string} type The component type. Can be one of the following:
     *   'view', 'layout', 'field', 'model' or 'collection'.
     */
    attach: function(component, type) {
        _.each(component.plugins, function(pluginName) {
            var prop = null;
            if (_.isObject(pluginName)) {
                var n = _.keys(pluginName)[0];
                prop = pluginName[n];
                pluginName = n;
            }

            var p = get(pluginName, type);
            if (p && !isPluginDisabled(component, pluginName)) {
                var events = _.extend({}, (_.isFunction(component.events)) ?
                    component.events() : component.events, p.events);

                _.extend(component, p);

                if (prop) {
                    _.extend(component, prop);

                    if (prop.events) {
                        _.extend(events, prop.events);
                    }
                }

                component.events = events;

                //If a plugin has an onAttach function, call it now so that the plugin can initialize
                if (_.isFunction(p.onAttach)) {
                    p.onAttach.call(component, component, p);
                }
            }
        }, this);
    },

    /**
     * Detaches plugins and calls the `onDetach` method on each one.
     *
     * @param {View/Component|Data/Bean|Data/BeanCollection} component The
     *   component.
     * @param {string} type The component type. Can be one of the following:
     *   'view', 'layout', 'field', 'model' or 'collection'.
     */
    detach: function(component, type) {
        _.each(component.plugins, function(name) {
            var plugin = get(name, type);
            if (plugin && _.isFunction(plugin.onDetach)) {
                plugin.onDetach.call(component, component, plugin);
            }
        }, this);
    },

    /**
     * Registers a plugin.
     *
     * @param {string} name The plugin name.
     * @param {string|string[]} validTypes The list of component types this
     *   plugin can be applied to ('view', 'field', 'layout', 'model' and/or
     *   'collection').
     * @param {Object} plugin The plugin object.
     */
    register: function(name, validTypes, plugin) {
        if (!_.isArray(validTypes)) {
            validTypes = [validTypes];
        }

        _.each(validTypes , function(type) {
            this.plugins[type] = this.plugins[type] || {};
            this.plugins[type][name] = plugin;
        }, this);
    },

    _isPluginDisabled: function(component, pluginName) {
        if (!SUGAR.App.config.sidecarCompatMode) {
            SUGAR.App.logger.error('Core.pluginManager#_isPluginDisabled is a private method that you are not allowed ' +
                'to access. Please use only the public API.');
            return;
        }

        SUGAR.App.logger.warn('Core.pluginManager#_isPluginDisabled is a private method that you should not access. ' +
            'You will NOT be allowed to access it in the next release. Please update your code to rely on the public ' +
            'API only.');

       return isPluginDisabled(component, pluginName);
    },

    _get: function(name, type) {
        if (!SUGAR.App.config.sidecarCompatMode) {
            SUGAR.App.logger.error('Core.pluginManager#_get is a private method that you are not allowed ' +
                'to access. Please use only the public API.');
            return;
        }

        SUGAR.App.logger.warn('Core.pluginManager#_get is a private method that you should not access. ' +
            'You will NOT be allowed to access it in the next release. Please update your code to rely on the public ' +
            'API only.');

        return get(name, type);
    }
};

module.exports = PluginManager;