/** * Given a component hierarchy of this: * * { * xtype: 'panel', * id: 'ContainerA', * layout: 'hbox', * renderTo: Ext.getBody(), * items: [ * { * id: 'ContainerB', * xtype: 'container', * items: [ * { id: 'ComponentA' } * ] * } * ] * } * * The rendering of the above proceeds roughly like this: * * - ContainerA's initComponent calls #render passing the `renderTo` property as the * container argument. * - `render` calls the `getRenderTree` method to get a complete {@link Ext.dom.Helper} spec. * - `getRenderTree` fires the "beforerender" event and calls the #beforeRender * method. Its result is obtained by calling #getElConfig. * - The #getElConfig method uses the `renderTpl` and its render data as the content * of the `autoEl` described element. * - The result of `getRenderTree` is passed to {@link Ext.dom.Helper#append}. * - The `renderTpl` contains calls to render things like docked items, container items * and raw markup (such as the `html` or `tpl` config properties). These calls are to * methods added to the {@link Ext.XTemplate} instance by #setupRenderTpl. * - The #setupRenderTpl method adds methods such as `renderItems`, `renderContent`, etc. * to the template. These are directed to "doRenderItems", "doRenderContent" etc.. * - The #setupRenderTpl calls traverse from components to their {@link Ext.layout.Layout} * object. * - When a container is rendered, it also has a `renderTpl`. This is processed when the * `renderContainer` method is called in the component's `renderTpl`. This call goes to * Ext.layout.container.Container#doRenderContainer. This method repeats this * process for all components in the container. * - After the top-most component's markup is generated and placed in to the DOM, the next * step is to link elements to their components and finish calling the component methods * `onRender` and `afterRender` as well as fire the corresponding events. * - The first step in this is to call #finishRender. This method descends the * component hierarchy and calls `onRender` and fires the `render` event. These calls * are delivered top-down to approximate the timing of these calls/events from previous * versions. * - During the pass, the component's `el` is set. Likewise, the `renderSelectors` and * `childEls` are applied to capture references to the component's elements. * - These calls are also made on the {@link Ext.layout.container.Container} layout to * capture its elements. Both of these classes use {@link Ext.util.ElementContainer} to * handle `childEls` processing. * * @private */Ext.define('Ext.util.Renderable', { mixinId: 'renderable', requires: [ 'Ext.dom.Element' ], frameIdRegex: /[-]frame\d+[TMB][LCR]$/, frameCls: Ext.baseCSSPrefix + 'frame', frameElNames: ['TL', 'TC', 'TR', 'ML', 'MC', 'MR', 'BL', 'BC', 'BR', 'Table'], /* eslint-disable indent, max-len */ frameTpl: [ '<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'before\'); %}</tpl>', '<tpl if="top">', '<tpl if="left"><div id="{fgid}TL" data-ref="frameTL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>{frameElCls}" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}TR" data-ref="frameTR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>{frameElCls}" role="presentation"></tpl>', '<div id="{fgid}TC" data-ref="frameTC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>{frameElCls}" role="presentation"></div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '</tpl>', '<tpl if="left"><div id="{fgid}ML" data-ref="frameML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>{frameElCls}" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}MR" data-ref="frameMR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>{frameElCls}" role="presentation"></tpl>', '<div id="{fgid}Body" data-ref="frameBody" class="{frameBodyCls} {frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>{frameElCls}" role="presentation">', '{%this.applyRenderTpl(out, values)%}', '</div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '<tpl if="bottom">', '<tpl if="left"><div id="{fgid}BL" data-ref="frameBL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>{frameElCls}" role="presentation"></tpl>', '<tpl if="right"><div id="{fgid}BR" data-ref="frameBR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>{frameElCls}" role="presentation"></tpl>', '<div id="{fgid}BC" data-ref="frameBC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>{frameElCls}" role="presentation"></div>', '<tpl if="right"></div></tpl>', '<tpl if="left"></div></tpl>', '</tpl>', '<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'after\'); %}</tpl>' ], frameTableTpl: [ '<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'before\'); %}</tpl>', '<table id="{fgid}Table" data-ref="frameTable" class="{frameCls} ', Ext.baseCSSPrefix + 'table-plain" cellpadding="0" role="presentation">', '<tpl if="top">', '<tr role="presentation">', '<tpl if="left"><td id="{fgid}TL" data-ref="frameTL" class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl>{frameElCls}" role="presentation"></td></tpl>', '<td id="{fgid}TC" data-ref="frameTC" class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl>{frameElCls}" role="presentation"></td>', '<tpl if="right"><td id="{fgid}TR" data-ref="frameTR" class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl>{frameElCls}" role="presentation"></td></tpl>', '</tr>', '</tpl>', '<tr role="presentation">', '<tpl if="left"><td id="{fgid}ML" data-ref="frameML" class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl>{frameElCls}" role="presentation"></td></tpl>', '<td id="{fgid}Body" data-ref="frameBody" class="{frameBodyCls} {frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl>{frameElCls}" style="{mcStyle}" role="presentation">', '{%this.applyRenderTpl(out, values)%}', '</td>', '<tpl if="right"><td id="{fgid}MR" data-ref="frameMR" class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl>{frameElCls}" role="presentation"></td></tpl>', '</tr>', '<tpl if="bottom">', '<tr role="presentation">', '<tpl if="left"><td id="{fgid}BL" data-ref="frameBL" class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl>{frameElCls}" role="presentation"></td></tpl>', '<td id="{fgid}BC" data-ref="frameBC" class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl>{frameElCls}" role="presentation"></td>', '<tpl if="right"><td id="{fgid}BR" data-ref="frameBR" class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl>{frameElCls}" role="presentation"></td></tpl>', '</tr>', '</tpl>', '</table>', '<tpl if="hasTabGuard">{% this.renderTabGuard(out, values, \'after\'); %}</tpl>' ], /* eslint-enable indent, max-len */ /** * @property {Number} _renderState * This property holds one of the following values during the render process: * * * **0** - The component is not rendered. * * **1** - The component has fired beforerender and is about to call beforeRender. * The component has just started rendering. * * **2** - The component has finished the `beforeRender` process and is about to * call `onRender`. This is when `rendering` is set to `true`. * * **3** - The component has started `onRender`. This is when `rendered` is set * to `true`. * * **4** - The component has finished its afterrender process. * * @private * @readonly * @since 5.0.0 */ _renderState: 0, _layerCls: Ext.baseCSSPrefix + 'layer', _fixedLayerCls: Ext.baseCSSPrefix + 'fixed-layer', // Some components will have such roles that do not actively participate // in user interaction, and thus do not need their ARIA attributes updated ariaStaticRoles: { presentation: true, article: true, definition: true, directory: true, document: true, img: true, heading: true, math: true, note: true, banner: true, complementary: true, contentinfo: true, navigation: true, search: true, // When a role is not defined it is akin to static 'undefined': true, 'null': true }, statics: { makeRenderSetter: function(cfg, renderState) { var name = cfg.name; return function(value) { var me = this, bucket = (me.renderConfigs || (me.renderConfigs = {})), pending = bucket[renderState]; if (me._renderState >= renderState) { (cfg.setter || cfg.getSetter()).call(me, value); } else { if (!pending) { bucket[renderState] = pending = {}; } if (!(name in pending)) { pending[name] = me[name]; } me[name] = value; } return me; }; }, processRenderConfig: function(source, configName, state) { // Even though this is not inheritableState, our onClassExtended adds it to // all derived classes so that our "this" pointer is the derived class. var proto = this.prototype, configurator = this.getConfigurator(), Renderable = Ext.util.Renderable, makeSetter = Renderable.makeRenderSetter, renderConfig = source[configName], cachedSetter, cfg, name, setterName; for (name in renderConfig) { cfg = Ext.Config.get(name); if (!proto[setterName = cfg.names.set]) { cachedSetter = (cfg.renderSetter || (cfg.renderSetter = {})); proto[setterName] = cachedSetter[state] || (cachedSetter[state] = makeSetter(cfg, state)); } } delete source[configName]; configurator.add(renderConfig); } }, onClassMixedIn: function(targetClass) { var override = targetClass.override, processRenderConfig = this.processRenderConfig, processOverride = function(body) { if (body.beforeRenderConfig) { this.processRenderConfig(body, 'beforeRenderConfig', 1); } if (body.renderConfig) { this.processRenderConfig(body, 'renderConfig', 3); } override.call(this, body); }, processClass = function(theClass, classBody) { // We need to process overrides for renderConfig declarations: theClass.override = processOverride; // While we are here we add this method (an inheritableStatic basically) theClass.processRenderConfig = processRenderConfig; if (classBody.beforeRenderConfig) { theClass.processRenderConfig(classBody, 'beforeRenderConfig', 1); } if (classBody.renderConfig) { theClass.processRenderConfig(classBody, 'renderConfig', 3); } }; // Process Component itself. processClass(targetClass, targetClass.prototype); // And apply to all Component-derived classes as well: targetClass.onExtended(processClass); }, /** * Allows additional behavior after rendering is complete. At this stage, the * {@link Ext.Component Component's} {@link Ext.Component#getEl Element} will have * been styled according to the configuration, will have had any configured CSS * class names added, and will be in the configured visibility and configured enable * state. * * **Note:** If the Component has a {@link Ext.Component#controller ViewController} * and the controller has an {@link Ext.app.ViewController#afterRender afterRender} * method it will be called passing the Component as the single param. * * @template * @protected */ afterRender: function() { var me = this, data = {}, protoEl = me.protoEl, target = me.el, controller, item, pre, hidden, contentEl; me.finishRenderChildren(); me._renderState = 4; // We need to do the contentEl here because it depends on the layout items (inner/outerCt) // to be rendered before we can put it in if (me.contentEl) { pre = Ext.baseCSSPrefix; hidden = pre + 'hidden-'; contentEl = me.contentEl = Ext.get(me.contentEl); contentEl.component = me; contentEl.removeCls([ pre + 'hidden', hidden + 'display', hidden + 'offsets' ]); me.getContentTarget().appendChild(contentEl.dom); } protoEl.writeTo(data); // Here we apply any styles that were set on the protoEl during the rendering phase // A majority of times this will not happen, but we still need to handle it item = data.removed; if (item) { target.removeCls(item); } item = data.cls; if (item.length) { target.addCls(item); } item = data.style; if (data.style) { target.setStyle(item); } me.protoEl = null; // If this is the outermost Container, lay it out as soon as it is rendered. // Some Components like Ext.LoadMask will lay out themselves and some like // Ext.dd.StatusProxy will even render themselves to a detached document body, // so we allow them to opt out of the Ext layouts. if (!me.ownerCt && !me.skipLayout) { me.updateLayout(); } if (!(me.x && me.y) && (me.pageX || me.pageY)) { me.setPagePosition(me.pageX, me.pageY); } if (me.disableOnRender) { me.onDisable(); } controller = me.controller; if (controller && controller.afterRender) { controller.afterRender(me); } if (me.focusableContainer && me.initFocusableContainer) { me.initFocusableContainer(); } }, afterFirstLayout: function(width, height) { var me = this, x = me.x, y = me.y, alignSpec = me.defaultAlign, alignOffset = me.alignOffset, controller, hasX, hasY, pos, xy; // We only have to set absolute position here if there is no ownerlayout // which should take responsibility. Consider the example of rendered components // outside of a viewport - these might need their positions setting. if (!me.ownerLayout) { hasX = x !== undefined; hasY = y !== undefined; } // For floaters, calculate x and y if they aren't defined by aligning // the sized element to the center of either the container or the ownerCt if (me.floating && !me.preventDefaultAlign && (!hasX || !hasY)) { if (me.floatParent) { pos = me.floatParent.getTargetEl().getViewRegion(); xy = me.el.getAlignToXY( me.alignTarget || me.floatParent.getTargetEl(), alignSpec, alignOffset ); pos.x = xy[0] - pos.x; pos.y = xy[1] - pos.y; } else { xy = me.el.getAlignToXY(me.alignTarget || me.container, alignSpec, alignOffset); pos = me.el.translateXY(xy[0], xy[1]); } x = hasX ? x : pos.x; y = hasY ? y : pos.y; hasX = hasY = true; } if (hasX || hasY) { me.setPosition(x, y); } me.onBoxReady(width, height); controller = me.controller; if (controller && controller.boxReady) { controller.boxReady(me, width, height); } }, /** * Allows additional behavior before rendering. * * **Note:** If the Component has a {@link Ext.Component#controller ViewController} * and the controller has a {@link Ext.app.ViewController#beforeRender beforeRender} * method it will be called passing the Component as the single param. * * @template * @protected */ beforeRender: function() { var me = this, floating = me.floating, layout = me.getComponentLayout(), cls = me.userCls, controller; me._renderState = 1; me.ariaUsesMainElement = me.ariaEl === 'el'; controller = me.controller; if (controller && controller.beforeRender) { controller.beforeRender(me); } // Force bindings to be created me.initBindable(); if (me.renderConfigs) { me.flushRenderConfigs(); } if (cls) { me.addCls(cls); } if (floating) { me.addCls(me.fixed ? me._fixedLayerCls : me._layerCls); cls = floating.cls; if (cls) { me.addCls(cls); } } // Just before rendering, set the frame flag if we are an always-framed component // like Window or Tip. me.frame = me.frame || me.alwaysFramed; if (!layout.initialized) { layout.initLayout(); } // Attempt to set overflow style prior to render if the targetEl can be accessed. // If the targetEl does not exist yet, this will take place in finishRender me.initOverflow(); me.setUI(me.ui); }, /** * @private * Called from the selected frame generation template to insert this Component's inner structure * inside the framing structure. * * When framing is used, a selected frame generation template is used as the primary template * of the #getElConfig instead of the configured {@link Ext.Component#renderTpl renderTpl}. * The renderTpl is invoked by this method which is injected into the framing template. */ doApplyRenderTpl: function(out, values) { // Careful! This method is bolted on to the frameTpl so all we get for context is // the renderData! The "this" pointer is the frameTpl instance! var me = values.$comp, tpl; // Don't do this if the component is already rendered: if (!me.rendered) { tpl = me.initRenderTpl(); tpl.applyOut(values.renderData, out); } }, getElConfig: function() { var me = this, autoEl = me.autoEl, frameInfo = me.getFrameInfo(), config = { tag: 'div', tpl: frameInfo ? me.initFramingTpl(frameInfo.table) : me.initRenderTpl() }, layoutTargetCls = me.layoutTargetCls, protoEl = me.protoEl, ariaRole = me.ariaRole, frameData; me.initStyles(protoEl); // If we're not framing, then just add to the element, otherwise we need to add // it to the frameBody, since it's our targetEl, but we don't have a handle on it yet. if (layoutTargetCls && !frameInfo) { protoEl.addCls(layoutTargetCls); } protoEl.writeTo(config); protoEl.flush(); if (autoEl) { if (Ext.isString(autoEl)) { config.tag = autoEl; } else { Ext.apply(config, autoEl); } } if (ariaRole && me.ariaUsesMainElement) { config.role = ariaRole; if (!me.ariaStaticRoles[ariaRole]) { config['aria-hidden'] = !!me.hidden; config['aria-disabled'] = !!me.disabled; // ariaLabelledBy takes precedence if (me.ariaLabel && !me.ariaLabelledBy) { config['aria-label'] = me.ariaLabel; } // We don't want to handle collapsibleness in subclasses if (me.collapsible) { config['aria-expanded'] = !me.collapsed; } // In some cases we need to force some ARIA attributes // to be rendered on the ariaEl upfront, e.g. certain // state and decorator attributes. It is not semantically // correct to set these in ariaAttributes config, so // we're using ariaRenderAttributes instance config // when available. if (me.ariaRenderAttributes) { Ext.apply(config, me.ariaRenderAttributes); } if (me.config.ariaAttributes) { Ext.apply(config, me.getAriaAttributes()); } } } // It's important to assign the id here as an autoEl.id could have been (wrongly) // applied and this would get things out of sync config.id = me.id; if (config.tpl) { // Use the framingTpl as the main content creating template. // It will call out to this.applyRenderTpl(out, values) if (frameInfo) { config.tplData = frameData = me.getFrameRenderData(); frameData.renderData = me.initRenderData(); } else { config.tplData = me.initRenderData(); } } // After we have gathered all rendering information, this is no longer needed. me.ariaRenderAttributes = null; return config; }, /** * This function takes the position argument passed to onRender and returns a * DOM element that you can use in the insertBefore. * @param {String/Number/Ext.dom.Element/HTMLElement} position Index, element id * or element you want to put this component before. * @return {HTMLElement} DOM element that you can use in the insertBefore */ getInsertPosition: function(position) { // Convert the position to an element to insert before if (position !== undefined) { if (Ext.isNumber(position)) { position = this.container.dom.childNodes[position]; } else { position = Ext.getDom(position); } } return position; }, getRenderTree: function() { var me = this, ret = null; if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { me._renderState = 1; me.beforeRender(); // Flag to let the layout's finishRenderItems and afterFinishRenderItems // know which items to process me.rendering = true; me._renderState = 2; ret = me.getElConfig(); if (me.el) { // Since we are producing a render tree, we produce a "proxy el" that will // sit in the rendered DOM precisely where me.el belongs. We replace the // proxy el in the finishRender phase. ret.id = me.$pid = Ext.id(null, me.el.identifiablePrefix); } // ret['data-cmp'] = me.id; } return ret; }, /** * Initialized the renderData to be used when rendering the renderTpl. * @return {Object} Object with keys and values that are going to be applied to the renderTpl * @protected */ initRenderData: function() { var me = this, ariaRole = me.ariaRole, data, ariaAttr; data = Ext.apply({ $comp: me, id: me.id, ui: me.ui, uiCls: me.uiCls, baseCls: me.baseCls, componentCls: me.componentCls, frame: me.frame, hasTabGuard: !!me.tabGuard, scrollerCls: me.scrollerCls, childElCls: '', // overridden in RTL ariaEl: me.ariaEl }, me.renderData); // This code is similar (in fact, almost identical) to the one in getElConfig; // we duplicate it for performance reasons. if (ariaRole && !me.ariaUsesMainElement) { ariaAttr = { role: ariaRole }; if (!me.ariaStaticRoles[ariaRole]) { ariaAttr['aria-hidden'] = !!me.hidden; ariaAttr['aria-disabled'] = !!me.disabled; // ariaLabelledBy takes precedence if (me.ariaLabel && !me.ariaLabelledBy) { ariaAttr['aria-label'] = me.ariaLabel; } // We don't want to handle collapsibleness in subclasses if (me.collapsible) { ariaAttr['aria-expanded'] = !me.collapsed; } if (me.ariaRenderAttributes) { Ext.apply(ariaAttr, me.ariaRenderAttributes); } if (me.config.ariaAttributes) { Ext.apply(ariaAttr, me.getAriaAttributes()); } } data.ariaAttributes = ariaAttr; } return data; }, /** * Template method called when this Component's DOM structure is created. * * At this point, this Component's (and all descendants') DOM structure *exists* but it has not * been layed out (positioned and sized). * * Subclasses which override this to gain access to the structure at render time should * call the parent class's method before attempting to access any child elements of the * Component. * * @param {Ext.dom.Element} parentNode The parent Element in which this Component's * encapsulating element is contained. * @param {Number} containerIdx The index within the parent Container's child collection * of this Component. * * @template * @protected */ onRender: function(parentNode, containerIdx) { var me = this, x = me.x, y = me.y, lastBox = null, el = me.el, scroller = me.scrollable, width, height; me.applyRenderSelectors(); // Update the scroller very early in first layout so that it has the overflow element // before any 'boxready', onResize, or 'resize' code gets to run. if (scroller && scroller.isScroller) { scroller.setElement(me.getOverflowEl()); // IE browsers don't restore scroll position if the component was scrolled and // then hidden and shown again, so we must do it manually. // See EXTJS-16233. if (Ext.isIE) { me.showListenerIE = Ext.on('show', me.onGlobalShow, me, { destroyable: true }); } } // Flag set on getRenderTree to flag to the layout's postprocessing routine that // the Component is in the process of being rendered and needs postprocessing. me.rendering = null; me.rendered = true; me._renderState = 3; if (me.renderConfigs) { me.flushRenderConfigs(); } // We need to remember these to avoid writing them during the initial layout: if (x != null) { lastBox = { x: x }; } if (y != null) { (lastBox = lastBox || {}).y = y; } // Framed components need their width/height to apply to the frame, which is // best handled in layout at present. if (!me.getFrameInfo()) { width = me.width; height = me.height; if (typeof width === 'number') { lastBox = lastBox || {}; lastBox.width = width; } if (typeof height === 'number') { lastBox = lastBox || {}; lastBox.height = height; } } me.lastBox = el.lastBox = lastBox; }, /** * Renders the Component into the passed HTML element. * * **If you are using a {@link Ext.container.Container Container} object to house this * Component, then do not use the render method.** * * A Container's child Components are rendered by that Container's * {@link Ext.container.Container#layout layout} manager when the Container is first rendered. * * When creating complex UIs, it is important to remember that sizing and positioning * of child items is the responsibility of the Container's * {@link Ext.container.Container#layout layout} manager. If you expect child items to be sized * in response to user interactions, you must configure the Container with a layout manager * which creates and manages the type of layout you have in mind. * * **Omitting the Container's {@link Ext.Container#layout layout} config means that a basic * layout manager is used which does nothing but render child components sequentially into the * Container. No sizing or positioning will be performed in this situation.** * * @param {Ext.dom.Element/HTMLElement/String} [container] The element this Component should be * rendered into. If it is being created from existing markup, this should be omitted. * @param {String/Number} [position] The element ID or DOM node index within the container * **before** which this component will be inserted (defaults to appending to the end * of the container) */ render: function(container, position) { var me = this, el = me.el, ownerLayout = me.ownerLayout, vetoed, tree, nextSibling; if (el && !el.isElement) { me.wrapPrimaryEl(el); // ensure me.el is wrapped el = me.el; } if (!me.skipLayout) { Ext.suspendLayouts(); } container = me.initContainer(container); nextSibling = me.getInsertPosition(position); if (!el) { tree = me.getRenderTree(); // calls beforeRender if (ownerLayout && ownerLayout.transformItemRenderTree) { tree = ownerLayout.transformItemRenderTree(tree); } // tree will be null if a beforerender listener returns false if (tree) { if (nextSibling) { el = Ext.DomHelper.insertBefore(nextSibling, tree); } else { el = Ext.DomHelper.append(container, tree); } me.wrapPrimaryEl(el); // Just rendered a bunch of stuff so fill up the cache with those els we will need. me.cacheRefEls(el); } } else { if (!me.hasListeners.beforerender || me.fireEvent('beforerender', me) !== false) { me.beforeRender(); // We're simulating the above block here as much as possible, but we're already // given an el, so we don't need to create it. We still need to initialize // the renderTpl later. me.needsRenderTpl = me.rendering = true; me._renderState = 2; // Set configured styles on pre-rendered Component's element me.initStyles(el); if (me.allowDomMove !== false) { if (nextSibling) { container.dom.insertBefore(el.dom, nextSibling); } else { container.dom.appendChild(el.dom); } } } else { vetoed = true; } } if (el && !vetoed) { me.finishRender(position); } if (!me.skipLayout) { Ext.resumeLayouts(!me.hidden && !container.isDetachedBody); } }, /** * Ensures that this component is attached to `document.body`. If the component was * rendered to {@link Ext#getDetachedBody}, then it will be appended to `document.body`. * Any configured position is also restored. * @param {Boolean} [runLayout=false] True to run the component's layout. */ ensureAttachedToBody: function(runLayout) { var comp = this, body; while (comp.ownerCt) { comp = comp.ownerCt; } if (comp.container.isDetachedBody) { comp.container = body = Ext.getBody(); body.appendChild(comp.el.dom); if (runLayout) { comp.updateLayout(); } if (typeof comp.x === 'number' || typeof comp.y === 'number') { comp.setPosition(comp.x, comp.y); } } }, //========================================================================= privates: { /** * Sets references to elements inside the component. This applies * {@link Ext.Component#cfg-renderSelectors renderSelectors} as well as * {@link Ext.Component#cfg-childEls childEls}. * @private */ applyRenderSelectors: function() { var me = this, selectors = me.renderSelectors, el = me.el, query, selector; me.attachChildEls(el); // Cache focusEl as a property for speedier lookups if (typeof me.focusEl === 'string') { me.focusEl = me[me.focusEl]; } // For the majority of Components, their ariaEl is going to be their main el. me.ariaEl = me[me.ariaEl] || me.el; // We still support renderSelectors. There are a few places in the framework that // need them and they are a documented part of the API. In fact, we support mixing // childEls and renderSelectors (no reason not to). if (selectors) { for (selector in selectors) { query = selectors[selector]; if (query) { me[selector] = el.selectNode(query, false); } } } }, /** * Ensures that all elements with "data-ref" attributes get loaded into the cache. * This really helps on IE8 where `getElementById` is a search not a lookup. By * populating our cache with one search of the DOM we then have random access to * the elements as we do our `childEls` wire up. * @private */ cacheRefEls: function(el) { el = el || this.el; // eslint-disable-next-line vars-on-top var cache = Ext.cache, El = Ext.dom.Element, dom = el.isElement ? el.dom : el, refs = dom.querySelectorAll('[data-ref]'), len = refs.length, ref, i; for (i = 0; i < len; i++) { ref = refs[i]; if (!cache[ref.id]) { new El(ref); } } }, /** * Handles autoRender. * Floating Components may have an ownerCt. If they are asking to be constrained, * constrain them within that ownerCt, and have their z-index managed locally. * Floating Components are always rendered to document.body * @private */ doAutoRender: function() { var me = this; if (!me.rendered) { if (me.floating) { me.render(me.renderTo || document.body); } else { me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender); } } }, doRenderContent: function(out, renderData) { // Careful! This method is bolted on to the renderTpl so all we get for context is // the renderData! The "this" pointer is the renderTpl instance! var me = renderData.$comp, data = me.data; if (me.html) { Ext.DomHelper.generateMarkup(me.html, out); delete me.html; } if (me.tpl) { // Make sure this.tpl is an instantiated XTemplate if (!me.tpl.isTemplate) { me.tpl = new Ext.XTemplate(me.tpl); } if (data) { me.data = data = data.isEntity ? data.getData(true) : data; // me.tpl[me.tplWriteMode](target, me.data); me.tpl.applyOut(data, out); } } }, doRenderFramingTabGuard: function(out, renderData, position) { // Careful! This method is bolted on to the frameTpl so all we get for context is // the renderData! The "this" pointer is the frameTpl instance! // Container layout implements doRenderTabGuard(), *not* Component itself! var layout = renderData.$comp.layout; // The "renderData" property is placed in scope for the renderTpl, but we don't // want to render tab guards at that level in addition to the framing level: renderData.renderData.$skipTabGuards = true; if (layout && layout.doRenderTabGuard) { layout.doRenderTabGuard.call(this, out, renderData, position); } }, flushRenderConfigs: function() { var me = this, configs = me.renderConfigs, state = me._renderState, bucket, i, name, newConfigs, value; if (configs) { for (i = 0; i <= state; ++i) { bucket = configs[i]; if (bucket) { configs[i] = null; for (name in bucket) { value = bucket[name]; (newConfigs || (newConfigs = {}))[name] = me[name]; me[name] = value; } } } if (newConfigs) { me.setConfig(newConfigs); } } }, /** * This method visits the rendered component tree in a "top-down" order. That is, this * code runs on a parent component before running on a child. This method calls the * {@link #onRender} method of each component. * @param {Number} containerIdx The index into the Container items of this Component. * * @private */ finishRender: function(containerIdx) { var me = this, cache = Ext.cache, // our element cache proxy, first, id, tpl, data, dom, el; // We are typically called w/me.el==null as a child of some ownerCt that is being // rendered. We are also called by render for a normal component (w/o a configured // me.el). In this case, render sets me.el and me.rendering (indirectly). Lastly // we are also called on a component (like a Viewport) that has a configured me.el // (body for a Viewport) when render is called. In this case, it is not flagged as // "me.rendering" yet because it does not produce a renderTree. We use this to know // not to regen the renderTpl. if (!me.el || me.$pid) { if (me.container) { el = cache[me.id]; dom = el ? el.dom : me.container.getById(me.id, true); } else { id = me.$pid || me.id; el = cache[id]; dom = el ? el.dom : Ext.getDom(id); } if (!me.el) { // Typical case: we produced the el during render me.wrapPrimaryEl(dom); } else { // We were configured with an el and created a proxy, so now we can swap // the proxy for me.el: delete me.$pid; if (!me.el.dom) { // make sure me.el is an Element me.wrapPrimaryEl(me.el); } // Insert the configured el before the proxy el: dom.parentNode.insertBefore(me.el.dom, dom); // We need to transplant rendered content from the proxy to the // configured el. This would included the renderTpl of the component // and its layout (innerCt's and such) as well as all child items. // proxy = dom; // hold on to the rendered DOM dom = me.el.dom; // we'll be using the configured el though first = dom.firstChild; // rendered content in proxy goes first while (proxy.firstChild) { dom.insertBefore(proxy.firstChild, first); } // We need the classes rendered on to the proxy as well (things like // "x-panel"): me.el.addCls(proxy.className); Ext.removeNode(proxy); // now proxy can go // TODO - what about style? } } else if (me.needsRenderTpl) { // We were configured with an el and then told to render (e.g., Viewport). We // need to generate the proper DOM. Insert first because the layout system // insists that child Component elements indices match the Component indices. tpl = me.initRenderTpl(); if (tpl) { data = me.initRenderData(); tpl.insertFirst(me.getTargetEl(), data); } // Just rendered a bunch of stuff so fill up the cache with those els we // will need. me.cacheRefEls(); } // else we are rendering me.el.component = me; if (!me.container) { // top-level rendered components will already have me.container set up me.container = Ext.get(me.el.dom.parentNode); } if (me.ctCls) { me.container.addCls(me.ctCls); } // Sets the rendered flag and clears the rendering flag me.onRender(me.container, containerIdx); // If we could not access a target protoEl in beforeRender, // we have to set the overflow styles here. if (!me.overflowInited) { me.initOverflow(); } // Tell the encapsulating element to hide itself in the way the Component // is configured to hide. This means DISPLAY, VISIBILITY or OFFSETS. me.el.setVisibilityMode(Ext.Element[me.hideMode.toUpperCase()]); if (me.overCls) { me.el.hover(me.addOverCls, me.removeOverCls, me); } if (me.hasListeners.render) { me.fireEvent('render', me); } me.afterRender(); // this can cause a layout if (me.hasListeners.afterrender) { me.fireEvent('afterrender', me); } me.initEvents(); if (me.hidden) { // Hiding during the render process should not perform any ancillary // actions that the full hide process does; It is not hiding, // it begins in a hidden state. // So just make the element hidden according to the configured hideMode me.el.hide(); } }, finishRenderChildren: function() { var layout = this.getComponentLayout(); layout.finishRender(); }, getFrameRenderData: function() { var me = this, // we are only called if framing so this has already been determined: frameInfo = me.frameSize, mcStyle = ''; //<feature legacyBrowser> if (me._syncFrameHeight && me.height) { // Buttons need their frame's MC element to have an explicit height in order // for percentage heights to work on elements inside the frame mcStyle = 'height:' + (me.height - frameInfo.height) + 'px'; } //</feature> return { $comp: me, id: me.id, fgid: me.id + '-frame', ui: me.ui, uiCls: me.uiCls, frameCls: me.frameCls, frameBodyCls: me.layoutTargetCls || '', baseCls: me.baseCls, hasTabGuard: me.tabGuard, top: !!frameInfo.top, left: !!frameInfo.left, right: !!frameInfo.right, bottom: !!frameInfo.bottom, mcStyle: mcStyle, // can be optionally set by a subclass or override to be an extra class to // be applied to all framing elements (used by RTL) frameElCls: '' }; }, /** * @private * On render, reads an encoded style attribute, "filter" from the style of this Component's * element. This information is memoized based upon the CSS class name of this Component's * element. Because child Components are rendered as textual HTML as part of the topmost * Container, a dummy div is inserted into the document to receive the document element's * CSS class name, and therefore style attributes. */ getFrameInfo: function() { // If native framing can be used, or this component is not going to be framed, // then do not attempt to read CSS framing info. if (Ext.supports.CSS3BorderRadius || !this.frame) { return false; } // eslint-disable-next-line vars-on-top var me = this, frameInfoCache = me.frameInfoCache, cls = me.getFramingInfoCls() + '-frameInfo', frameInfo = frameInfoCache[cls], styleEl, info, frameTop, frameRight, frameBottom, frameLeft, borderTopWidth, borderRightWidth, borderBottomWidth, borderLeftWidth, paddingTop, paddingRight, paddingBottom, paddingLeft; if (frameInfo == null) { // Get the singleton frame style proxy with our el class name stamped into it. styleEl = Ext.fly(me.getStyleProxy(cls), 'frame-style-el'); info = styleEl.getStyle('font-family'); info = info && info.split('-'); if (info && info.length >= 5) { // The framing data is encoded as // // D=div|T=table // | H=horz|V=vert // | | // | | // [DT][HV]-[T-R-B-L]-[T-R-B-L]-[T-R-B-L] // / / | | \ \ // / / | | \ \ // / / / \ \ \ // / / border-width \ \ // frame-size padding // // The first 2 chars hold the div/table and horizontal/vertical flags. // The 3 sets of TRBL 4-tuples are the CSS3 values for border-radius, // border-width and padding, respectively. // /* eslint-disable no-multi-spaces */ frameTop = parseInt(info[1], 10); frameRight = parseInt(info[2], 10); frameBottom = parseInt(info[3], 10); frameLeft = parseInt(info[4], 10); borderTopWidth = parseInt(info[5], 10) || 0; borderRightWidth = parseInt(info[6], 10) || 0; borderBottomWidth = parseInt(info[7], 10) || 0; borderLeftWidth = parseInt(info[8], 10) || 0; paddingTop = parseInt(info[9], 10) || 0; paddingRight = parseInt(info[10], 10) || 0; paddingBottom = parseInt(info[11], 10) || 0; paddingLeft = parseInt(info[12], 10) || 0; /* eslint-enable no-multi-spaces */ frameInfo = { table: info[0].charAt(0) === 't', vertical: info[0].charAt(1) === 'v', top: frameTop, right: frameRight, bottom: frameBottom, left: frameLeft, // Ext.layout.ContextItem needs width/height width: frameLeft + frameRight, height: frameTop + frameBottom, border: { top: borderTopWidth, right: borderRightWidth, bottom: borderBottomWidth, left: borderLeftWidth, width: borderLeftWidth + borderRightWidth, height: borderTopWidth + borderBottomWidth }, padding: { top: paddingTop, right: paddingRight, bottom: paddingBottom, left: paddingLeft, width: paddingLeft + paddingRight, height: paddingTop + paddingBottom } }; } else { frameInfo = false; } //<debug> // This happens when you set frame: true explicitly without using the x-frame mixin // in sass. // This way IE can't figure out what sizes to use and thus framing can't work. if (me.frame === true && !frameInfo) { Ext.log.error( 'You have set frame: true explicity on this component (' + me.getXType() + ') and it does not have any framing defined in the CSS template. ' + 'In this case IE cannot figure out what sizes to use and thus framing ' + 'on this component will be disabled.' ); } //</debug> frameInfoCache[cls] = frameInfo; } me.frame = !!frameInfo; me.frameSize = frameInfo; return frameInfo; }, getFramingInfoCls: function() { return this.baseCls + '-' + this.ui; }, /** * @private * Returns an offscreen div with the same class name as the element this is being rendered. * This is because child item rendering takes place in a detached div which, being not * part of the document, has no styling. */ getStyleProxy: function(cls) { var result; result = this.styleProxyEl || (Ext.Component.prototype.styleProxyEl = Ext.getBody().createChild({ //<debug> // tell the spec runner to ignore this element // when checking if the dom is clean 'data-sticky': true, //</debug> role: 'presentation', style: { position: 'absolute', top: '-10000px' } }, null, true)); result.className = cls; return result; }, /** * @private */ getFrameTpl: function(table) { return this.lookupTpl(table ? 'frameTableTpl' : 'frameTpl'); }, initContainer: function(container) { var me = this; // If you render a component specifying the el, we get the container // of the el, and make sure we dont move the el around in the dom // during the render if (!container && me.el) { container = me.el.dom.parentNode; me.allowDomMove = false; } me.container = container.dom ? container : Ext.get(container); return me.container; }, initOverflow: function() { var me = this, // Call the style calculation early which sets the scrollFlags property overflowStyle = me.getOverflowStyle(), scrollFlags = me.scrollFlags, overflowEl = me.getOverflowEl(), hasOverflow = (scrollFlags.y || scrollFlags.x); // Not rendered, or the targetEl has been configured as a string, // wait until the call from finishRender if (!hasOverflow || !overflowEl || !overflowEl.isElement) { return; } me.overflowInited = true; overflowEl.setStyle(overflowStyle); }, // Create the framingTpl from the string. // Poke in a reference to applyRenderTpl(frameInfo, out) initFramingTpl: function(table) { var tpl = this.getFrameTpl(table); if (tpl && !tpl.applyRenderTpl) { this.setupFramingTpl(tpl); } return tpl; }, /** * Initializes the renderTpl. * @return {Ext.XTemplate} The renderTpl XTemplate instance. * @private */ initRenderTpl: function() { var tpl = this.lookupTpl('renderTpl'); if (tpl && !tpl.renderContent) { this.setupRenderTpl(tpl); } return tpl; }, /** * @private * Inject a reference to the function which applies the render template * into the framing template. The framing template wraps the content. */ setupFramingTpl: function(frameTpl) { frameTpl.applyRenderTpl = this.doApplyRenderTpl; frameTpl.renderTabGuard = this.doRenderFramingTabGuard; }, setupRenderTpl: function(renderTpl) { renderTpl.renderBody = renderTpl.renderContent = this.doRenderContent; }, /** * Updates the frame elements to match new framing. The current `frameBody` is * preserved by transplanting it into the new frame. All other frame `childEls` * are destroyed and recreated if needed by the new frame. This method cannot * transition from framed to non-framed or vise-versa or between table and div * based framing. * @private */ updateFrame: function() { if (Ext.supports.CSS3BorderRadius || !this.frame) { return; } // eslint-disable-next-line vars-on-top var me = this, dom = me.el.dom, frameTable = me.frameTable, oldFrameBody = me.frameBody, oldFrameBodyDom = oldFrameBody.dom, frameInfo = me.getFrameInfo(), childEls, childElName, div, el, first, frameData, frameDom, frameTpl, i, newBody, newFrameEls; // This is a bit tricky because we will be generating elements with the same // id's (mostly) as our current frame. We have to do most of this work with // the raw DOM nodes due to the duplicate id's (which prevents us from using // an Element wrapper until we resolve the duplicates). // First off, render the new frameTpl to an off-document element. div = document.createElement('div'); frameData = me.getFrameRenderData(); // If the container was configured with tab guards, they were already rendered. frameData.hasTabGuard = false; frameTpl = me.getFrameTpl(frameInfo.table); frameTpl.insertFirst(div, frameData); // Capture the new frameEls (we'll need to update our childEls to these later // once we've destroyed the old ones). newFrameEls = div.querySelectorAll('[data-ref]'); newBody = div.querySelector('[data-ref="frameBody"]'); // Now we can insert the new frameEls before the current frameBody. for (first = oldFrameBodyDom; first.parentNode !== dom;) { first = first.parentNode; } while (div.firstChild) { dom.insertBefore(div.firstChild, first); } // And transplant the oldFrameBody into the new frame newBody.parentNode.replaceChild(oldFrameBodyDom, newBody); oldFrameBodyDom.className = newBody.className; oldFrameBody.setSize(); // clear any size set by layout // Remove the old frame elements, except for frameBody of course: childEls = me.getChildEls(); if (frameTable) { frameTable.destroy(); me.frameTable = null; } for (childElName in childEls) { if (childEls[childElName].frame) { el = me[childElName]; if (el && el !== oldFrameBody) { el.destroy(); me[childElName] = null; } } } // Now we are free to acquire the childEls to the new elements: for (i = newFrameEls.length; i--;) { childElName = (frameDom = newFrameEls[i]).getAttribute('data-ref'); if (childElName !== 'frameBody') { me[childElName] = new Ext.dom.Element(frameDom); } } }, // Cache the frame information object so as not to cause style recalculations frameInfoCache: {} } // private});