/**
* 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
});