/** * Provides a registry of all Components (instances of {@link Ext.Component} or any subclass * thereof) on a page so that they can be easily accessed by {@link Ext.Component component} * {@link Ext.Component#id id} (see {@link #get}, or the convenience method * {@link Ext#getCmp Ext.getCmp}). * * This object also provides a registry of available Component *classes* indexed by a * mnemonic code known as the Component's {@link Ext.Component#xtype xtype}. The `xtype` * provides a way to avoid instantiating child Components when creating a full, nested * config object for a complete Ext page. * * A child Component may be specified simply as a *config object* as long as the correct * `{@link Ext.Component#xtype xtype}` is specified so that if and when the Component * needs rendering, the correct type can be looked up for lazy instantiation. * * @singleton */Ext.define('Ext.ComponentManager', { alternateClassName: 'Ext.ComponentMgr', singleton: true, mixins: [ 'Ext.mixin.Bufferable' ], count: 0, fixReferencesTimer: null, referenceRepairs: 0, typeName: 'xtype', bufferableMethods: { handleDocumentMouseDown: 'asap' }, /** * @private */ constructor: function(config) { var me = this; Ext.apply(me, config); me.all = {}; me.byInstanceId = {}; me.holders = {}; me.onAvailableCallbacks = {}; }, /** * Creates a new Component from the specified config object using the config object's * `xtype` to determine the class to instantiate. * * @param {Object} config A configuration object for the Component you wish to create. * @param {String} [defaultType] The `xtype` to use if the config object does not * contain a `xtype`. (Optional if the config contains a `xtype`). * @return {Ext.Component} The newly instantiated Component. */ create: function(config, defaultType) { if (typeof config === 'string') { return Ext.widget(config); } if (config.isComponent) { return config; } if ('xclass' in config) { return Ext.create(config.xclass, config); } return Ext.widget(config.xtype || defaultType, config); }, /** * Returns an item by id. * @param {String} id The id of the item * @return {Object} The item, undefined if not found. */ get: function(id) { return this.all[id]; }, register: function(component) { var me = this, id = component.getId(), onAvailableCallbacks = me.onAvailableCallbacks; //<debug> if (id === undefined) { Ext.raise('Component id is undefined. Please ensure the component has an id.'); } if (id in me.all) { Ext.raise('Duplicate component id "' + id + '"'); } if (component.$iid in me.byInstanceId) { Ext.raise('Duplicate component instance id "' + component.$iid + '"'); } //</debug> me.all[id] = component; me.byInstanceId[component.$iid] = component; if (component.nameHolder || component.referenceHolder) { me.holders[id] = component; } ++me.count; if (!me.hasFocusListener) { me.installFocusListener(); } onAvailableCallbacks = onAvailableCallbacks && onAvailableCallbacks[id]; if (onAvailableCallbacks && onAvailableCallbacks.length) { me.notifyAvailable(component); } }, unregister: function(component) { var me = this, all = me.all, byInstanceId = me.byInstanceId, holders = me.holders, id = component.getId(); if (id in holders) { // Helps IE since delete may just mark the entry as "free" and not // release the object by clearing the entry value. // TODO find out when IE fixed this holders[id] = null; delete holders[id]; } all[id] = null; delete all[id]; id = component.$iid; byInstanceId[id] = null; delete byInstanceId[id]; --me.count; }, markReferencesDirty: function() { var me = this, holders = me.holders, holder, id; if (!Ext.referencesDirty) { // Clear all collections (no stale entries) for (id in holders) { holder = holders[id]; holder.refs = holder.nameRefs = null; if (holder.invalidateChildDirty) { holder.invalidateChildDirty(); } } Ext.referencesDirty = true; me.fixReferencesTimer = Ext.asap(function() { me.fixReferencesTimer = null; me.fixReferences(); }); } }, fixReferences: function() { var me = this, all = me.all, holders = me.holders, holder, id; if (Ext.referencesDirty) { me.fixReferencesTimer = Ext.unasap(me.fixReferencesTimer); // Falsy value but also !== false so we can tell we're fixing the refs Ext.referencesDirty = 0; ++me.referenceRepairs; for (id in holders) { holder = holders[id]; if (holder.beginSyncChildDirty) { holder.beginSyncChildDirty(); } } for (id in all) { all[id]._fixReference(); } for (id in holders) { holder = holders[id]; if (holder.finishSyncChildDirty) { holder.finishSyncChildDirty(); } } Ext.referencesDirty = false; } }, /** * Registers a function that will be called (a single time) when an item with the specified * id is added to the manager. This will happen on instantiation. * @param {String} id The item id * @param {Function} fn The callback function. Called with a single parameter, the item. * @param {Object} scope The scope ('this' reference) in which the callback is executed. * Defaults to the item. */ onAvailable: function(id, fn, scope) { var me = this, callbacks = me.onAvailableCallbacks, all = me.all, item; if (id in all) { // if already an instance, callback immediately item = all[id]; fn.call(scope || item, item); } else if (id) { // otherwise, queue for dispatch if (!Ext.isArray(callbacks[id])) { callbacks[id] = [ ]; } callbacks[id].push(function(item) { fn.call(scope || item, item); }); } }, /** * @private */ notifyAvailable: function(item) { var callbacks = this.onAvailableCallbacks[item && item.getId()] || []; while (callbacks.length) { (callbacks.shift())(item); } }, /** * Executes the specified function once for each item in the collection. * @param {Function} fn The function to execute. * @param {String} fn.key The key of the item * @param {Number} fn.value The value of the item * @param {Number} fn.length The total number of items in the collection ** Removed * in 5.0 ** * @param {Boolean} fn.return False to cease iteration. * @param {Object} scope The scope to execute in. Defaults to `this`. */ each: function(fn, scope) { Ext.Object.each(this.all, fn, scope); }, /** * Gets the number of items in the collection. * @return {Number} The number of items in the collection. */ getCount: function() { return this.count; }, /** * Returns an array of all components * @return {Array} */ getAll: function() { return Ext.Object.getValues(this.all); }, /** * Return the currently active (focused) Component * * @return {Ext.Component/null} Active Component, or null * @private */ getActiveComponent: function() { return Ext.Component.from(Ext.dom.Element.getActiveElement()); }, // Deliver focus events to Component onGlobalFocus: function(info) { var me = this, event = info.event.chain(), infoCopy = Ext.applyIf({ event: event }, info), to, from, ancestor, target; to = event.toComponent = infoCopy.toComponent = Ext.Component.from(info.toElement); from = event.fromComponent = infoCopy.fromComponent = Ext.Component.from(info.fromElement); ancestor = me.getCommonAncestor(from, to); // Focus moves *within* a component should not cause component focus leave/enter if (to !== from) { if (from && !from.destroyed && !from.isDestructing()) { if (from.handleBlurEvent) { from.handleBlurEvent(infoCopy); } // Call onFocusLeave on the component axis from which focus is exiting for (target = from; target && target !== ancestor; target = target.getRefOwner()) { if (!(target.destroyed || target.destroying)) { event.type = 'focusleave'; target.onFocusLeave(event); } } } if (to && !to.destroyed && !to.isDestructing()) { if (to.handleFocusEvent) { to.handleFocusEvent(infoCopy); } // Call onFocusEnter on the component axis to which focus is entering for (target = to; target && target !== ancestor; target = target.getRefOwner()) { event.type = 'focusenter'; target.onFocusEnter(event); } } } for (target = ancestor; target; target = target.getRefOwner()) { if (!(target.destroying || target.destroyed)) { target.onFocusMove(infoCopy); } } }, getCommonAncestor: function(compA, compB) { if (compA === compB) { return compA; } while (compA && !(compA.isAncestor(compB) || compA === compB)) { compA = compA.getRefOwner(); } return compA; }, privates: { /** * This method reorders the DOM structure of floated components to arrange that the * clicked element is last of its siblings, and therefore on the visual "top" of * the floated component stack. * * This is a Bufferable ASAP method invoked directly from Ext.GlobalEvents. * * We need to use ASAP, not a low priority listener because we need it * to run *after* the browser's default response to the event has been * processed, ie focus consequences. * For example, a Dialog contains a picker field, and the picker field has * its floated picker open and focused. * The user mousedowns on another field in the dialog. The resulting * immediate DOM shuffle to bring the dialog above the picker results * in focus being silently lost. * @param {type} e The mousedown event * @private */ doHandleDocumentMouseDown: function(e) { var floatedSelector = Ext.Widget.prototype.floatedSelector, targetFloated; // If mousedown/pointerdown/touchstart is on a floated Component, ask it to sync // its place in the hierarchy. if (floatedSelector) { targetFloated = Ext.Component.from(e.getTarget(floatedSelector, Ext.getBody())); // If the mousedown is in a floated, move it to top. if (targetFloated) { targetFloated.toFront(true); } } }, installFocusListener: function() { var me = this; Ext.on('focus', me.onGlobalFocus, me); me.hasFocusListener = true; }, clearAll: function() { var me = this; me.all = {}; me.byInstanceId = {}; me.holders = {}; me.onAvailableCallbacks = {}; }, /** * Find the Widget or Component to which the given Element belongs. * * @param {Ext.dom.Element/HTMLElement} el The element from which to start to find * an owning Component. * @param {Ext.dom.Element/HTMLElement} [limit] The element at which to stop upward * searching for an owning Component, or the number of Components to traverse before * giving up. Defaults to the document's HTML element. * @param {String} [selector] An optional {@link Ext.ComponentQuery} selector to * filter the target. * @return {Ext.Widget/Ext.Component} The widget, component or `null`. * * @private * @since 6.5.0 */ from: function(el, limit, selector) { var cache = this.all, depth = 0, target, topmost, cmpId, cmp; if (el && el.isEvent) { el = el.target; } target = Ext.getDom(el); if (typeof limit !== 'number') { topmost = Ext.getDom(limit); limit = Number.MAX_VALUE; } while (target && target.nodeType === 1 && depth < limit && target !== topmost) { cmpId = target.getAttribute('data-componentid') || target.id; if (cmpId) { cmp = cache[cmpId]; if (cmp && (!selector || Ext.ComponentQuery.is(cmp, selector))) { return cmp; } // Increment depth on every *Component* found, not Element depth++; } target = target.parentNode; } return null; } }}, function(ComponentManager) { // Backwards compat: ComponentManager.fromElement = ComponentManager.from; // No components yet, so nothing is dirty. We need this to be false when the first // component is created so that it sets our fixup timer. Ext.referencesDirty = false; Ext.fixReferences = function() { ComponentManager.fixReferences(); }; Ext.markReferencesDirty = function() { ComponentManager.markReferencesDirty(); }; /** * This is shorthand reference to {@link Ext.ComponentManager#get}. * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id} * * @method getCmp * @param {String} id The component {@link Ext.Component#id id} * @return {Ext.Component} The Component, `undefined` if not found, or `null` if a * Class was found. * @member Ext */ Ext.getCmp = function(id) { return ComponentManager.get(id); }; Ext.iidToCmp = function(iid) { return ComponentManager.byInstanceId[iid] || null; }; /** * @private * @deprecated 6.6.0 Inline event handlers are deprecated */ Ext.doEv = function(node, e) { var cmp, method, event; // The event here is a raw browser event, so don't pass the event directly // since from expects an Ext.event.Event cmp = Ext.Component.from(e.target); if (cmp && !cmp.destroying && !cmp.destroyed && cmp.getEventHandlers) { method = cmp.getEventHandlers()[e.type]; if (method && cmp[method]) { event = new Ext.event.Event(e); return cmp[method](event); } } return true; };});