Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

297 lines
11 KiB

/**
* This mixin enables classes to declare relationships to child elements and provides the
* mechanics for acquiring the {@link Ext.dom.Element elements} and storing them on an object
* instance as properties.
*
* This class is used by {@link Ext.Component components} and {@link Ext.layout.container.Container container layouts} to
* manage their child elements.
*
* A typical component that uses these features might look something like this:
*
* Ext.define('Ext.ux.SomeComponent', {
* extend: 'Ext.Component',
*
* childEls: [
* 'bodyEl'
* ],
*
* renderTpl: [
* '<div id="{id}-bodyEl" data-ref="bodyEl"></div>'
* ],
*
* // ...
* });
*
* The `{@link #childEls}` config lists one or more relationships to child elements managed
* by the component. The items in this array can be objects that more fully specify the
* child. For example, the above could have used this instead to achieve the same result:
*
* childEls: [
* { name: 'bodyEl', itemId: 'bodyEl' }
* ]
*
*
* Unlike a `renderTpl` where there is a single value for an instance, `childEls` are aggregated
* up the class hierarchy so that they are effectively inherited. In other words, if a
* class where to derive from `Ext.ux.SomeComponent` in the example above, it could also
* have a `childEls` property in the same way as `Ext.ux.SomeComponent`.
*
* Ext.define('Ext.ux.AnotherComponent', {
* extend: 'Ext.ux.SomeComponent',
*
* childEls: [
* // 'bodyEl' is inherited
* 'innerEl'
* ],
*
* renderTpl: [
* '<div id="{id}-bodyEl" data-ref="bodyEl">'
* '<div id="{id}-innerEl" data-ref="innerEl"></div>'
* '</div>'
* ],
*
* // ...
* });
*
* **IMPORTANT**
* The `renderTpl` contains both child elements and unites them in the desired markup, but
* the `childEls` only contains the new child element. The `data-ref` attribute must be
* rendered on to child elements that do not use `select` or `selectNode` options. This
* is done for performance reasons on IE8 where element lookup (even by id) is not very
* efficient.
*
* @private
*/
Ext.define('Ext.util.ElementContainer', {
mixinId: 'elementCt',
config: {
/**
* @cfg {Object/String[]/Object[]} childEls
* The canonical form of `childEls` is an object keyed by child's property name
* with values that are objects with the following properties.
*
* - `itemId` - The id to combine with the Component's id that is the id of the
* child element.
* - `id` - The id of the child element.
* - `leaf` - Set to `true` to ignore content when scanning for childEls. This
* should be set on things like the generated content for an `Ext.view.View`.
* - `select`: A selector that will be passed to {@link Ext.dom.Element#method-select}.
* - `selectNode`: A selector that will be passed to {@link Ext.dom.Element#method-selectNode}.
*
* For example:
*
* childEls: {
* button: true,
* buttonText: 'text',
* buttonImage: {
* itemId: 'image'
* }
* }
*
* The above is translated into the following complete form:
*
* childEls: {
* button: {
* name: 'button',
* itemId: 'button'
* },
* buttonText: {
* name: 'buttonText',
* itemId: 'text'
* },
* buttonImage: {
* name: 'buttonImage',
* itemId: 'image'
* }
* }
*
* The above can be provided as an array like so:
*
* childEls: [
* 'button',
* { name: 'buttonText', itemId: 'text' },
* { name: 'buttonImage', itemId: 'image' }
* }
*
* For example, a Component which renders a title and body text:
*
* @example
* Ext.create('Ext.Component', {
* renderTo: Ext.getBody(),
* renderTpl: [
* '<h1 id="{id}-title" data-ref="title">{title}</h1>',
* '<p>{msg}</p>',
* ],
* renderData: {
* title: "Error",
* msg: "Something went wrong"
* },
* childEls: ["title"],
* listeners: {
* afterrender: function(cmp){
* // After rendering the component will have a title property
* cmp.title.setStyle({color: "red"});
* }
* }
* });
*
* When using `select`, the property will be an instance of {@link Ext.CompositeElement}.
* In all other cases, the property will be an {@link Ext.dom.Element} or `null`
* if not found.
*
* Care should be taken when using `select` or `selectNode` to find child elements.
* The following issues should be considered:
*
* - Performance: using selectors can be 10x slower than id lookup.
* - Over-selecting: selectors are applied after the DOM elements for all children
* have been rendered, so selectors can match elements from child components
* (including nested versions of the same component) accidentally.
*
* This above issues are most important when using `select` since it returns multiple
* elements.
*/
childEls: {
$value: {},
cached: true,
lazy: true,
merge: function (newValue, oldValue, target, mixinClass) {
var childEls = oldValue ? Ext.Object.chain(oldValue) : {},
i, val;
// We'd use mergeSets except it assumes array elements are just names.
if (newValue instanceof Array) {
for (i = newValue.length; i--; ) {
val = newValue[i];
if (!mixinClass || !(val in childEls)) {
if (typeof val === 'string') {
childEls[val] = { name: val, itemId: val };
} else {
childEls[val.name] = val;
}
}
}
} else if (newValue) {
if (newValue.constructor === Object) {
for (i in newValue) {
if (!mixinClass || !(i in childEls)) {
val = newValue[i];
if (val === true) {
childEls[i] = { itemId: i };
} else if (typeof val === 'string') {
childEls[i] = { itemId: val };
} else {
childEls[i] = val;
if (!('itemId' in val)) {
val.itemId = i;
}
}
childEls[i].name = i;
}
}
} else {
if (!mixinClass || !(newValue in childEls)) {
childEls[newValue] = { name: newValue, itemId: newValue };
}
}
}
return childEls;
}
}
},
destroy: function () {
var me = this,
childEls = me.getChildEls(),
child, childName;
for (childName in childEls) {
child = me[childName];
if (child) {
if (child.destroy) {
child.component = null;
child.destroy();
}
me[childName] = null;
}
}
},
privates: {
/**
* Called after the mixin is applied. We need to see if `childEls` were used by
* the `targetClass` and apply them to the config.
* @param {Ext.Class} targetClass
* @private
*/
afterClassMixedIn: function (targetClass) {
// When we are mixed in the targetClass may already have specified childEls,
// so check the prototype for any...
var proto = targetClass.prototype,
childEls = proto.childEls;
if (childEls) {
delete proto.childEls;
targetClass.getConfigurator().add({
childEls: childEls
});
}
},
/**
* Sets references to elements inside the component.
* @private
*/
attachChildEls: function (el, owner) {
var me = this,
childEls = me.getChildEls(),
comp = owner || me, // fyi - we are also used by layouts
baseId = comp.id + '-',
unframed = !comp.frame,
childName, elements, entry, k, selector, value, id;
for (childName in childEls) {
// hasOwnProperty is a no-go here since we use prototype chains...
entry = childEls[childName];
if (unframed && entry.frame) {
continue;
}
selector = entry.select;
if (selector) {
value = el.select(selector, true); // a CompositeElement
} else if (!(selector = entry.selectNode)) {
if (!(id = entry.id)) {
// With a normal childEl we want to rely on data-ref to populate
// the cache and *not* use getById since that should never find
// anything we don't already know about.
id = baseId + entry.itemId;
value = Ext.cache[id];// || el.getById(id);
} else {
// With a specified id we may not be so lucky, so check the cache
// first but then fallback to getById.
value = Ext.cache[id] || el.getById(id);
}
} else {
value = el.selectNode(selector, false);
}
if (value) {
if (value.isElement) {
value.component = comp;
} else if (value.isComposite && !value.isLite) {
elements = value.elements;
for (k = elements.length; k--;) {
elements[k].component = comp;
}
}
}
me[childName] = value || null;
}
}
}
});