/** * This mixin provides support for a `plugins` config and related API's. * * If this mixin is used for non-Components, the statements regarding the host being a * Component can be translated accordingly. The only requirement on the user of this class * is that the plugins actually used be appropriate for their host. * * While `Ext.Component` in the Classic Toolkit supports `plugins`, it does not use this * class to provide that support. This is due to backwards compatibility in regard to * timing changes this implementation would present. * * **Important:** To ensure plugins are destroyed, call `setPlugins(null)`. * @protected * @since 6.2.0 */Ext.define('Ext.mixin.Pluggable', function (Pluggable) { var EMPTY = []; return { config: { /** * @cfg {Object/String/Object[]/String[]} plugins * An object or array of objects that will provide custom functionality for this * component. If a string is provided or a string is one of the elements of the * array, that string is treated as the `type` alias. For example, "listpaging" * is the type alias for `Ext.plugin.ListPaging`. The full alias includes the * "plugin." prefix (i.e., 'plugin.listpaging'). * * Plugins should derive from `Ext.plugin.Abstract` but this is not required. The * only requirement for a valid plugin is that it contain an `init()` method that * accepts a reference to the owning component. * * When a component is created, if any plugins are available, the component will * call the `{@link Ext.plugin.Abstract#method-init init}` method on each plugin, * passing a reference to itself. Each plugin can then call methods or respond to * events on the component as needed to provide its functionality. * * ## Example code * * A plugin by alias: * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: 'listpaging' * }); * * Multiple plugins by alias: * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: ['listpaging', 'pullrefresh'] * }); * * Single plugin by class name with config options: * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: { * type: 'listpaging', * autoPaging: true * } * }); * * Multiple plugins by type and class name with config options: * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: [{ * xclass: 'Ext.plugin.PullRefresh', * pullRefreshText: 'Pull to refresh...' * }, { * type: 'listpaging', * autoPaging: true * }] * }); * */ plugins: null }, /** * Adds a plugin. For example: * * list.addPlugin('pullrefresh'); * * Or: * * list.addPlugin({ * type: 'pullrefresh', * pullRefreshText: 'Pull to refresh...' * }); * * @param {Object/String/Ext.plugin.Abstract} plugin The plugin or config object or * alias to add. * @since 6.2.0 */ addPlugin: function (plugin) { var me = this, plugins = me.getPlugins(); if (plugins) { plugin = me.createPlugin(plugin); plugin.init(me); plugins.push(plugin); } else { me.setPlugins(plugin); } return plugin; }, /** * Removes and destroys a plugin. * * **Note:** Not all plugins are designed to be removable. Consult the documentation * for the specific plugin in question to be sure. * @param {String/Ext.plugin.Abstract} plugin The plugin or its `id` to remove. * @return {Ext.plugin.Abstract} plugin instance or `null` if not found. * @since 6.2.0 */ destroyPlugin: function (plugin) { return this.removePlugin(plugin, true); }, /** * Retrieves plugin by its `type` alias. For example: * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: ['listpaging', 'pullrefresh'] * }); * * list.findPlugin('pullrefresh').setPullRefreshText('Pull to refresh...'); * * **Note:** See also {@link #getPlugin}. * * @param {String} type The Plugin's `type` as specified by the class's * {@link Ext.Class#cfg-alias alias} configuration. * @return {Ext.plugin.Abstract} plugin instance or `null` if not found. * @since 6.2.0 */ findPlugin: function (type) { var plugins = this.getPlugins(), n = plugins && plugins.length, i, plugin, ret; for (i = 0; i < n && !ret; i++) { plugin = plugins[i]; // Classic used ptype forever, so support it too but Core/Modern just use // type. if (plugin.type === type || plugin.ptype === type) { ret = plugin; } } return ret || null; }, /** * Retrieves a plugin by its `id`. * * var list = Ext.create({ * xtype: 'list', * itemTpl: '<div class="item">{title}</div>', * store: 'Items', * * plugins: { * xclass: 'Ext.plugin.PullRefresh', * id: 'foo' * } * }); * * list.getPlugin('foo').setPullRefreshText('Pull to refresh...'); * * **Note:** See also {@link #findPlugin}. * * @param {String} id The `id` of the plugin. * @return {Ext.plugin.Abstract} plugin instance or `null` if not found. * @since 6.2.0 */ getPlugin: function (id) { var plugins = this.getPlugins(), n = plugins && plugins.length, i, plugin, ret; for (i = 0; i < n && !ret; i++) { plugin = plugins[i]; // Classic used pluginId, so support it too but Core/Modern just use id. if (plugin.id === id || plugin.pluginId === id) { ret = plugin; } } return ret || null; }, /** * Removes and (optionally) destroys a plugin. * * **Note:** Not all plugins are designed to be removable. Consult the documentation * for the specific plugin in question to be sure. * @param {String/Ext.plugin.Abstract} plugin The plugin or its `id` to remove. * @param {Boolean} [destroy] Pass `true` to not call `destroy()` on the plugin. * @return {Ext.plugin.Abstract} plugin instance or `null` if not found. * @since 6.2.0 */ removePlugin: function (plugin, destroy) { var plugins = this.getPlugins(), i = plugins && plugins.length || 0, p; while (i-- > 0) { p = plugins[i]; if (p === plugin || p.id === plugin) { plugins.splice(i, 1); if (destroy) { if (p.destroy) { p.destroy(); } } else if (p.detachCmp) { p.detachCmp(); if (p.setCmp) { p.setCmp(null); } } break; } p = null; } return p; }, privates: { statics: { idSeed: 0 }, /** * Creates a particular plugin type if defined in the `plugins` configuration. * @param {String} type The `type` of the plugin. * @return {Ext.plugin.Abstract} The plugin that was created. * @private * @since 6.2.0 */ activatePlugin: function (type) { var me = this, config = me.initialConfig, plugins = config && config.plugins, ret = null, i, p; if (plugins) { plugins = EMPTY.concat(plugins); // we need an array we can modify for (i = plugins.length; i-- > 0; ) { p = plugins[i]; if (p === type || p.type === type) { me.initialConfig = config = Ext.apply({}, config); config.plugins = plugins; // switch over to our copy // Put the instance in the plugins array so it will be included in // the applyPlugins loop for normal processing of plugins. plugins[i] = ret = me.createPlugin(p); break; } } } return ret; }, /** * Applier for the `plugins` config property. * @param {String[]/Object[]/Ext.plugin.Abstract[]} plugins The new plugins to use. * @param {Ext.plugin.Abstract[]} oldPlugins The existing plugins in use. * @private */ applyPlugins: function (plugins, oldPlugins) { var me = this, oldCount = oldPlugins && oldPlugins.length || 0, count, i, plugin; // Ensure we have an array if we got a single thing or a copy of the array // if we got an array. plugins = plugins ? EMPTY.concat(plugins) : null; count = plugins && plugins.length || 0; // We need to destroy() old plugins that aren't being brought forward in // the new array... // for (i = 0; i < oldCount; ++i) { oldPlugins[i].$dead = true; // so paint the old ones } // Pass #1 (For historical reasons): Create all of the plugins. Prior versions // did this pass first then called init() so we preserve the timings and do // the same. // for (i = 0; i < count; ++i) { plugins[i] = me.createPlugin(plugins[i]); // ensure we have an instance } // Pass #2: Initialize the plugins that have not been and clear $dead for // any returning for the next round. // for (i = 0; i < count; ++i) { plugin = plugins[i]; if (plugin.$dead) { // if (it was in oldPlugins) delete plugin.$dead; // unpaint it (it's a keeper) } else { plugin.init(me); // this one is new to the party } } // Now we can teardown any plugins that aren't coming back. // for (i = 0; i < oldCount; ++i) { if ((plugin = oldPlugins[i]).$dead) { delete plugin.$dead; Ext.destroy(plugin); } } return plugins; }, /** * Converts the provided type or config object into a plugin instance. * @param {String/Object/Ext.plugin.Abstract} config The plugin type, config * object or instance. * @return {Ext.plugin.Abstract} * @private */ createPlugin: function (config) { if (typeof config === 'string') { config = { type: config }; } var ret = config; if (!config.isInstance) { // The owner may be needed by plugin's initConfig so provide it: config.cmp = this; ret = Ext.factory(config, null, null, 'plugin'); // Cleanup the user's config object: delete config.cmp; } if (!ret.id) { ret.id = ++Pluggable.idSeed; } if (ret.setCmp) { ret.setCmp(this); } return ret; } }}});