extjs-gpl
Version:
GPL licensed version of Sencha Ext JS
1,269 lines (1,152 loc) • 230 kB
JavaScript
/**
* @class Ext.dom.Element
* @alternateClassName Ext.Element
* @mixins Ext.util.Positionable
* @mixins Ext.mixin.Observable
*
* Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.
*
* **Note:** The events included in this Class are the ones we've found to be the most commonly used. Many events are
* not listed here due to the expedient rate of change across browsers. For a more comprehensive list, please visit the
* following resources:
*
* + [Mozilla Event Reference Guide](https://developer.mozilla.org/en-US/docs/Web/Events)
* + [W3 Pointer Events](http://www.w3.org/TR/pointerevents/)
* + [W3 Touch Events](http://www.w3.org/TR/touch-events/)
* + [W3 DOM 2 Events](http://www.w3.org/TR/DOM-Level-2-Events/)
* + [W3 DOM 3 Events](http://www.w3.org/TR/DOM-Level-3-Events/)
*
* ## Usage
*
* // by id
* var el = Ext.get("my-div");
*
* // by DOM element reference
* var el = Ext.get(myDivElement);
*
* ## Selecting Descendant Elements
*
* Ext.dom.Element instances can be used to select descendant nodes using CSS selectors.
* There are 3 methods that can be used for this purpose, each with a slightly different
* twist:
*
* - {@link #method-query}
* - {@link #method-selectNode}
* - {@link #method-select}
*
* These methods can accept any valid CSS selector since they all use
* [querySelectorAll](http://www.w3.org/TR/css3-selectors/) under the hood. The primary
* difference between these three methods is their return type:
*
* To get an array of HTMLElement instances matching the selector '.foo' use the query
* method:
*
* element.query('.foo');
*
* This can easily be transformed into an array of Ext.dom.Element instances by setting
* the `asDom` parameter to `false`:
*
* element.query('.foo', false);
*
* If the desired result is only the first matching HTMLElement use the selectNode method:
*
* element.selectNode('.foo');
*
* Once again, the dom node can be wrapped in an Ext.dom.Element by setting the `asDom`
* parameter to `false`:
*
* element.selectNode('.foo', false);
*
* The `select` method is used when the desired return type is a {@link
* Ext.CompositeElementLite CompositeElementLite} or a {@link Ext.CompositeElement
* CompositeElement}. These are collections of elements that can be operated on as a
* group using any of the methods of Ext.dom.Element. The only difference between the two
* is that CompositeElementLite is a collection of HTMLElement instances, while
* CompositeElement is a collection of Ext.dom.Element instances. To retrieve a
* CompositeElementLite that represents a collection of HTMLElements for selector '.foo':
*
* element.select('.foo');
*
* For a {@link Ext.CompositeElement CompositeElement} simply pass `true` as the
* `composite` parameter:
*
* element.select('.foo', true);
*
* The query selection methods can be used even if you don't have a Ext.dom.Element to
* start with For example to select an array of all HTMLElements in the document that match the
* selector '.foo', simply wrap the document object in an Ext.dom.Element instance using
* {@link Ext#fly}:
*
* Ext.fly(document).query('.foo');
*
* # Animations
*
* When an element is manipulated, by default there is no animation.
*
* var el = Ext.get("my-div");
*
* // no animation
* el.setWidth(100);
*
* specified as boolean (true) for default animation effects.
*
* // default animation
* el.setWidth(100, true);
*
* To configure the effects, an object literal with animation options to use as the Element animation configuration
* object can also be specified. Note that the supported Element animation configuration options are a subset of the
* {@link Ext.fx.Anim} animation options specific to Fx effects. The supported Element animation configuration options
* are:
*
* Option Default Description
* --------- -------- ---------------------------------------------
* duration 350 The duration of the animation in milliseconds
* easing easeOut The easing method
* callback none A function to execute when the anim completes
* scope this The scope (this) of the callback function
*
* Usage:
*
* // Element animation options object
* var opt = {
* duration: 1000,
* easing: 'elasticIn',
* callback: this.foo,
* scope: this
* };
* // animation with some options set
* el.setWidth(100, opt);
*
* The Element animation object being used for the animation will be set on the options object as "anim", which allows
* you to stop or manipulate the animation. Here is an example:
*
* // using the "anim" property to get the Anim object
* if(opt.anim.isAnimated()){
* opt.anim.stop();
* }
*/
Ext.define('Ext.dom.Element', function(Element) {
var WIN = window,
DOC = document,
docEl = DOC.documentElement,
TOP = WIN.top,
elementIdCounter,
windowId,
documentId,
WIDTH = 'width',
HEIGHT = 'height',
MIN_WIDTH = 'min-width',
MIN_HEIGHT = 'min-height',
MAX_WIDTH = 'max-width',
MAX_HEIGHT = 'max-height',
TOP = 'top',
RIGHT = 'right',
BOTTOM = 'bottom',
LEFT = 'left',
VISIBILITY = 'visibility',
HIDDEN = 'hidden',
DISPLAY = "display",
NONE = "none",
ZINDEX = "z-index",
POSITION = "position",
RELATIVE = "relative",
STATIC = "static",
SEPARATOR = '-',
wordsRe = /\w/g,
spacesRe = /\s+/,
classNameSplitRegex = /[\s]+/,
transparentRe = /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i,
adjustDirect2DTableRe = /table-row|table-.*-group/,
topRe = /top/i,
borders = {
t: 'border-top-width',
r: 'border-right-width',
b: 'border-bottom-width',
l: 'border-left-width'
},
paddings = {
t: 'padding-top',
r: 'padding-right',
b: 'padding-bottom',
l: 'padding-left'
},
margins = {
t: 'margin-top',
r: 'margin-right',
b: 'margin-bottom',
l: 'margin-left'
},
paddingsTLRB = [paddings.l, paddings.r, paddings.t, paddings.b],
bordersTLRB = [borders.l, borders.r, borders.t, borders.b],
numberRe = /\d+$/,
unitRe = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
defaultUnit = 'px',
camelRe = /(-[a-z])/gi,
cssRe = /([a-z0-9\-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
pxRe = /^\d+(?:\.\d*)?px$/i,
propertyCache = {},
ORIGINALDISPLAY = 'originalDisplay',
camelReplaceFn = function(m, a) {
return a.charAt(1).toUpperCase();
},
clearData = function(node, deep) {
var childNodes, i, len;
// Only Element nodes may have _extData and child nodes to clear.
// IE8 throws an error attempting to set expandos on non-Element nodes.
if (node.nodeType === 1) {
node._extData = null;
if (deep) {
childNodes = node.childNodes;
for (i = 0, len = childNodes.length; i < len; ++i) {
clearData(childNodes[i], deep);
}
}
}
},
visibilityCls = Ext.baseCSSPrefix + 'hidden-visibility',
displayCls = Ext.baseCSSPrefix + 'hidden-display',
offsetsCls = Ext.baseCSSPrefix + 'hidden-offsets',
clipCls = Ext.baseCSSPrefix + 'hidden-clip',
sizedCls = Ext.baseCSSPrefix + 'sized',
unsizedCls = Ext.baseCSSPrefix + 'unsized',
stretchedCls = Ext.baseCSSPrefix + 'stretched',
CREATE_ATTRIBUTES = {
style: 'style',
className: 'className',
cls: 'cls',
classList: 'classList',
text: 'text',
hidden: 'hidden',
html: 'html',
children: 'children'
},
lastFocusChange = 0,
lastKeyboardClose = 0,
editableHasFocus = false,
isVirtualKeyboardOpen = false,
visFly, scrollFly, caFly;
// Cross-origin access might throw an exception
try {
elementIdCounter = TOP.__elementIdCounter__;
}
catch (e) {
TOP = WIN;
}
TOP.__elementIdCounter = elementIdCounter = (TOP.__elementIdCounter__ || 0) + 1;
windowId = 'ext-window-' + elementIdCounter;
documentId = 'ext-document-' + elementIdCounter;
return {
alternateClassName: [ 'Ext.Element' ],
mixins: [
'Ext.util.Positionable',
'Ext.mixin.Observable'
],
requires: [
'Ext.dom.Shadow',
'Ext.dom.Shim',
'Ext.dom.ElementEvent',
'Ext.event.publisher.Dom',
'Ext.event.publisher.Gesture',
'Ext.event.publisher.ElementSize',
'Ext.event.publisher.ElementPaint'
],
uses: [
'Ext.dom.Helper',
'Ext.dom.CompositeElement',
'Ext.dom.Fly',
'Ext.dom.TouchAction',
'Ext.event.publisher.Focus'
],
observableType: 'element',
isElement: true,
skipGarbageCollection: true,
$applyConfigs: true,
identifiablePrefix: 'ext-element-',
styleHooks: {},
validIdRe: Ext.validIdRe,
blockedEvents: Ext.supports.EmulatedMouseOver ? {
// mobile safari emulates a mouseover event on clickable elements such as
// anchors. This event is useless because it runs after touchend. We block
// this event to prevent mouseover handlers from running after tap events. It
// is up to the individual component to determine if it has an analog for
// mouseover, and implement the appropriate event handlers.
mouseover: 1
} : {},
longpressEvents: {
longpress: 1,
taphold: 1
},
/**
* @property {Ext.Component} component
* A reference to the `Component` that owns this element. This is `null` if there
* is no direct owner.
*/
// Mouse events
/**
* @event click
* Fires when a mouse click is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event contextmenu
* Fires when a right click is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event dblclick
* Fires when a mouse double click is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mousedown
* Fires when a mousedown is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mouseup
* Fires when a mouseup is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mouseover
* Fires when a mouseover is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mousemove
* Fires when a mousemove is detected with the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mouseout
* Fires when a mouseout is detected with the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mouseenter
* Fires when the mouse enters the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event mouseleave
* Fires when the mouse leaves the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
// Keyboard events
/**
* @event keypress
* Fires when a keypress is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event keydown
* Fires when a keydown is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event keyup
* Fires when a keyup is detected within the element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
// HTML frame/object events
/**
* @event load
* Fires when the user agent finishes loading all content within the element. Only supported by window, frames,
* objects and images.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event unload
* Fires when the user agent removes all content from a window or frame. For elements, it fires when the target
* element or any of its content has been removed.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event abort
* Fires when an object/image is stopped from loading before completely loaded.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event error
* Fires when an object/image/frame cannot be loaded properly.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event painted
* Fires whenever this Element actually becomes visible (painted) on the screen. This is useful when you need to
* perform 'read' operations on the DOM element, i.e: calculating natural sizes and positioning.
*
* __Note:__ This event is not available to be used with event delegation. Instead `painted` only fires if you explicitly
* add at least one listener to it, for performance reasons.
*
* @param {Ext.dom.Element} this The component instance.
*/
/**
* @event resize
* Important note: For the best performance on mobile devices, use this only when you absolutely need to monitor
* a Element's size.
*
* __Note:__ This event is not available to be used with event delegation. Instead `resize` only fires if you explicitly
* add at least one listener to it, for performance reasons.
*
* @param {Ext.dom.Element} this The component instance.
*/
/**
* @event scroll
* Fires when a document view is scrolled.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
// Form events
/**
* @event select
* Fires when a user selects some text in a text field, including input and textarea.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event change
* Fires when a control loses the input focus and its value has been modified since gaining focus.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event submit
* Fires when a form is submitted.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event reset
* Fires when a form is reset.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event focus
* Fires when an element receives focus either via the pointing device or by tab navigation.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event blur
* Fires when an element loses focus either via the pointing device or by tabbing navigation.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event focusmove
* Fires when focus is moved *within* an element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {Ext.dom.Element} e.target The {@link Ext.dom.Element} element which *recieved* focus.
* @param {Ext.dom.Element} e.relatedTarget The {@link Ext.dom.Element} element which *lost* focus.
* @param {HTMLElement} t The target of the event.
*/
// User Interface events
/**
* @event DOMFocusIn
* Where supported. Similar to HTML focus event, but can be applied to any focusable element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMFocusOut
* Where supported. Similar to HTML blur event, but can be applied to any focusable element.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMActivate
* Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
// DOM Mutation events
/**
* @event DOMSubtreeModified
* Where supported. Fires when the subtree is modified.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMNodeInserted
* Where supported. Fires when a node has been added as a child of another node.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMNodeRemoved
* Where supported. Fires when a descendant node of the element is removed.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMNodeRemovedFromDocument
* Where supported. Fires when a node is being removed from a document.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMNodeInsertedIntoDocument
* Where supported. Fires when a node is being inserted into a document.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMAttrModified
* Where supported. Fires when an attribute has been modified.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @event DOMCharacterDataModified
* Where supported. Fires when the character data has been modified.
* @param {Ext.event.Event} e The {@link Ext.event.Event} encapsulating the DOM event.
* @param {HTMLElement} t The target of the event.
*/
/**
* @constructor
* Creates new Element directly by passing an id or the HTMLElement. This
* constructor should not be called directly. Always use {@link Ext#get Ext.get()}
* or {@link Ext#fly Ext#fly()} instead.
*
* In older versions of Ext JS and Sencha Touch this constructor checked to see if
* there was already an instance of this element in the cache and if so, returned
* the same instance. As of version 5 this behavior has been removed in order to
* avoid a redundant cache lookup since the most common path is for the Element
* constructor to be called from {@link Ext#get Ext.get()}, which has already
* checked for a cache entry.
*
* Correct way of creating a new Ext.dom.Element (or retrieving it from the cache):
*
* var el = Ext.get('foo'); // by id
*
* var el = Ext.get(document.getElementById('foo')); // by DOM reference
*
* Incorrect way of creating a new Ext.dom.Element
*
* var el = new Ext.dom.Element('foo');
*
* For quick and easy access to Ext.dom.Element methods use a flyweight:
*
* Ext.fly('foo').addCls('foo-hovered');
*
* This simply attaches the DOM node with id='foo' to the global flyweight Element
* instance to avoid allocating an extra Ext.dom.Element instance. If, however,
* the Element instance has already been cached by a previous call to Ext.get(),
* then Ext.fly() will return the cached Element instance. For more info see
* {@link Ext#fly}.
*
* @param {String/HTMLElement} element
* @private
*/
constructor: function(dom) {
var me = this,
id;
if (typeof dom === 'string') {
dom = DOC.getElementById(dom);
}
if (!dom) {
//<debug>
Ext.raise("Invalid domNode reference or an id of an existing domNode: " + dom);
//</debug>
return null;
}
//<debug>
if (Ext.cache[dom.id]) {
Ext.raise("Element cache already contains an entry for id '" +
dom.id + "'. Use Ext.get() to create or retrieve Element instances.");
}
//</debug>
/**
* The DOM element
* @property dom
* @type HTMLElement
*/
me.dom = dom;
id = dom.id;
if (id) {
me.id = id;
} else {
id = dom.id = me.getUniqueId();
}
//<debug>
if (!me.validIdRe.test(me.id)) {
Ext.raise('Invalid Element "id": "' + me.id + '"');
}
//</debug>
// set an "el" property that references "this". This allows
// Ext.util.Positionable methods to operate on this.el.dom since it
// gets mixed into both Element and Component
me.el = me;
Ext.cache[id] = me;
me.longpressListenerCount = 0;
me.mixins.observable.constructor.call(me);
},
inheritableStatics: {
/**
* @private
* @static
* @inheritable
*/
cache: Ext.cache = {},
/**
* @property
* @static
* @private
* @inheritable
*/
editableSelector: 'input,textarea,[contenteditable="true"]',
/**
* @property {Number}
* Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
* Use the CSS 'visibility' property to hide the element.
*
* Note that in this mode, {@link Ext.dom.Element#isVisible isVisible} may return true
* for an element even though it actually has a parent element that is hidden. For this
* reason, and in most cases, using the {@link #OFFSETS} mode is a better choice.
* @static
* @inheritable
*/
VISIBILITY: 1,
/**
* @property {Number}
* Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
* Use the CSS 'display' property to hide the element.
* @static
* @inheritable
*/
DISPLAY: 2,
/**
* @property {Number}
* Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
* Use CSS absolute positioning and top/left offsets to hide the element.
* @static
* @inheritable
*/
OFFSETS: 3,
/**
* @property {Number}
* Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}.
* Use CSS `clip` property to reduce element's dimensions to 0px by 0px, effectively
* making it hidden while not being truly invisible. This is useful when an element
* needs to be published to the Assistive Technologies such as screen readers.
* @static
* @inheritable
*/
CLIP: 4,
/**
* @property
* @static
* @inheritable
* @private
* This property indicates a minimum threshold of vertical resize movement for
* virtual keyboard detection.
*
* On some mobile browsers the framework needs to keep track of whether window
* resize events were triggered by the opening or closing of a virtual keyboard
* so that it can prevent unnecessary re-layout of the viewport. It does this
* by detecting resize events in the horizontal direction that occur immediately
* after an editable element is focused or blurred.
*/
minKeyboardHeight: 100,
unitRe: unitRe,
/**
* @property {Boolean}
* @private
* @static
* @inheritable
* True to globally disable the delegated event system. The results of
* setting this to false are unpredictable since the Gesture publisher relies
* on delegated events in order to work correctly. Disabling delegated events
* may cause Gestures to function incorrectly or to stop working completely.
* Use at your own risk!
*/
useDelegatedEvents: true,
/**
* @property {Object}
* @private
* @static
* @inheritable
* The list of valid nodeTypes that are allowed to be wrapped
*/
validNodeTypes: {
1: 1, // ELEMENT_NODE
9: 1 // DOCUMENT_NODE
},
/**
* Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
* @param {Object} size The size to set.
* @param {String} units The units to append to a numeric size value.
* @return {String}
* @private
* @static
* @inheritable
*/
addUnits: function(size, units) {
// Most common case first: Size is set to a number
if (typeof size === 'number') {
return size + (units || defaultUnit);
}
// Values which mean "auto"
// - ""
// - "auto"
// - undefined
// - null
if (size === "" || size === "auto" || size == null) {
return size || '';
}
// less common use case: number formatted as a string. save this case until
// last to avoid regex execution if possible.
if (numberRe.test(size)) {
return size + (units || defaultUnit);
}
// Warn if it's not a valid CSS measurement
if (!unitRe.test(size)) {
//<debug>
Ext.Logger.warn("Warning, size detected (" + size + ") not a valid property value on Element.addUnits.");
//</debug>
return size || '';
}
return size;
},
/**
* @private
* Create method to add support for a DomHelper config. Creates
* and appends elements/children using document.createElement/appendChild.
* This method is used by the modern toolkit for a significant performance gain
* in webkit browsers as opposed to using DomQuery which generates HTML
* markup and sets it as innerHTML.
*
* However, the createElement/appendChild
* method of creating elements is significantly slower in all versions of IE
* at the time of this writing (6 - 11), so classic toolkit should not use this method,
* but should instead use DomHelper methods, or Element methods that use
* DomHelper under the hood (e.g. createChild).
* see https:*fiddle.sencha.com/#fiddle/tj
*
* @static
* @inheritable
*/
create: function(attributes, domNode) {
var me = this,
classes, element, elementStyle, tag, value, name, i, ln, tmp;
attributes = attributes || {};
if (attributes.isElement) {
return domNode ? attributes.dom : attributes;
} else if ('nodeType' in attributes) {
return domNode ? attributes : Ext.get(attributes);
}
if (typeof attributes === 'string') {
return DOC.createTextNode(attributes);
}
tag = attributes.tag;
if (!tag) {
tag = 'div';
}
if (attributes.namespace) {
element = DOC.createElementNS(attributes.namespace, tag);
} else {
element = DOC.createElement(tag);
}
elementStyle = element.style;
for (name in attributes) {
if (name !== 'tag') {
value = attributes[name];
switch (name) {
case CREATE_ATTRIBUTES.style:
if (typeof value === 'string') {
element.setAttribute(name, value);
}
else {
for (i in value) {
if (value.hasOwnProperty(i)) {
elementStyle[i] = value[i];
}
}
}
break;
case CREATE_ATTRIBUTES.className:
case CREATE_ATTRIBUTES.cls:
tmp = value.split(spacesRe);
classes = classes ? classes.concat(tmp) : tmp
break;
case CREATE_ATTRIBUTES.classList:
classes = classes ? classes.concat(value) : value;
break;
case CREATE_ATTRIBUTES.text:
element.textContent = value;
break;
case CREATE_ATTRIBUTES.html:
element.innerHTML = value;
break;
case CREATE_ATTRIBUTES.hidden:
if (classes) {
classes.push(displayCls);
} else {
classes = [displayCls];
}
break;
case CREATE_ATTRIBUTES.children:
if (value != null) {
for (i = 0,ln = value.length; i < ln; i++) {
element.appendChild(me.create(value[i], true));
}
}
break;
default:
if (value != null) { // skip null or undefined values
element.setAttribute(name, value);
}
}
}
}
if (classes) {
element.className = classes.join(' ');
}
if (domNode) {
return element;
} else {
return me.get(element);
}
},
/**
* @static
* @inheritable
* @private
*/
detach: function() {
var dom = this.dom;
if (dom && dom.parentNode && dom.tagName !== 'BODY') {
dom.parentNode.removeChild(dom);
}
return this;
},
/**
* @inheritdoc Ext#fly
* @inheritable
* @static
*/
fly: function(dom, named) {
return Ext.fly(dom, named);
},
/**
* Returns the top Element that is located at the passed coordinates in the current viewport.
* @static
* @inheritable
* @param {Number} x The x coordinate
* @param {Number} y The y coordinate
* @param {Boolean} [asDom=false] `true` to return a DOM element.
* @return {Ext.dom.Element/HTMLElement} The found element.
*/
fromPoint: (function() {
// IE has a weird bug where elementFromPoint can fail on the first call when inside an iframe.
// It seems to happen more consistently on older IE, but sometimes crops up even in IE11.
// This plays havoc especially while running tests.
var elementFromPointBug;
if (Ext.isIE) {
try {
elementFromPointBug = window.self !== window.top;
} catch (e) {
elementFromPointBug = true;
}
}
return function(x, y, asDom) {
var el = null;
el = DOC.elementFromPoint(x, y);
if (!el && elementFromPointBug) {
el = DOC.elementFromPoint(x, y);
}
return asDom ? el : Ext.get(el);
};
})(),
/**
* Returns the top Element that is located at the passed coordinates taking into account
* the scroll position of the document.
* @static
* @inheritable
* @param {Number} x The x coordinate
* @param {Number} y The y coordinate
* @param {Boolean} [asDom=false] `true` to return a DOM element.
* @return {Ext.dom.Element/HTMLElement} The found element.
*
* @since 6.2.0
*/
fromPagePoint: function(x, y, asDom) {
var scroll = Ext.getDoc().getScroll();
return Element.fromPoint(x - scroll.left, y - scroll.top, asDom);
},
/**
* Retrieves Ext.dom.Element objects. {@link Ext#get} is alias for {@link Ext.dom.Element#get}.
*
* **This method does not retrieve {@link Ext.Component Component}s.** This method retrieves Ext.dom.Element
* objects which encapsulate DOM elements. To retrieve a Component by its ID, use {@link Ext.ComponentManager#get}.
*
* When passing an id, it should not include the `#` character that is used for a css selector.
*
* // For an element with id 'foo'
* Ext.get('foo'); // Correct
* Ext.get('#foo'); // Incorrect
*
* Uses simple caching to consistently return the same object. Automatically fixes if an object was recreated with
* the same id via AJAX or DOM.
*
* @param {String/HTMLElement/Ext.dom.Element} el The `id` of the node, a DOM Node or an existing Element.
* @return {Ext.dom.Element} The Element object (or `null` if no matching element was found).
* @static
* @inheritable
*/
get: function(el) {
var me = this,
cache = Ext.cache,
nodeType, dom, id, entry, isDoc, isWin, isValidNodeType;
if (!el) {
return null;
}
//<debug>
function warnDuplicate(id) {
Ext.raise("DOM element with id " + id +
" in Element cache is not the same as element in the DOM. " +
"Make sure to clean up Element instances using destroy()" );
}
//</debug>
// Ext.get(flyweight) must return an Element instance, not the flyweight
if (el.isFly) {
el = el.dom;
}
if (typeof el === 'string') {
id = el;
if (cache.hasOwnProperty(id)) {
entry = cache[id];
if (entry.skipGarbageCollection || !Ext.isGarbage(entry.dom)) {
//<debug>
dom = Ext.getElementById ? Ext.getElementById(id) :
DOC.getElementById(id);
if (dom && (dom !== entry.dom)) {
warnDuplicate(id);
}
//</debug>
return entry;
} else {
entry.destroy();
}
}
if (id === windowId) {
return Element.get(WIN);
} else if (id === documentId) {
return Element.get(DOC);
}
// using Ext.getElementById() allows us to check the detached
// body in addition to the body (Ext JS only).
dom = Ext.getElementById ? Ext.getElementById(id) :
DOC.getElementById(id);
if (dom) {
return new Element(dom);
}
}
nodeType = el.nodeType;
if (nodeType) {
isDoc = (nodeType === 9);
isValidNodeType = me.validNodeTypes[nodeType];
} else {
// if an object has a window property that refers to itself we can
// reasonably assume that it is a window object.
// have to use == instead of === for IE8
isWin = (el.window == el);
}
// check if we have a valid node type or if the el is a window object before
// proceeding. This allows elements, document fragments, and document/window
// objects (even those inside iframes) to be wrapped.
if (isValidNodeType || isWin) {
id = el.id;
if (cache.hasOwnProperty(id)) {
entry = cache[id];
if (entry.skipGarbageCollection || el === entry.dom ||
!Ext.isGarbage(entry.dom)) {
//<debug>
if (el !== entry.dom) {
warnDuplicate(id);
}
//</debug>
return entry;
} else {
entry.destroy();
}
}
if (el === DOC) {
el.id = documentId;
}
// Must use == here, otherwise IE fails to recognize the window
if (el == WIN) {
el.id = windowId;
}
el = new Element(el);
if (isWin || isDoc) {
// document and window objects can never be garbage
el.skipGarbageCollection = true;
}
return el;
}
if (el.isElement) {
return el;
}
if (el.isComposite) {
return el;
}
// Test for iterable.
// Allow the resulting Composite to be based upon an Array or HtmlCollection of nodes.
if (Ext.isIterable(el)) {
return me.select(el);
}
return null;
},
/**
* Returns the active element in the DOM. If the browser supports activeElement
* on the document, this is returned. If not, the focus is tracked and the active
* element is maintained internally.
* @static
* @inheritable
*
* @param {Boolean} asElement Return Ext.Element instance instead of DOM node.
*
* @return {HTMLElement} The active (focused) element in the document.
*/
getActiveElement: function(asElement) {
var active = DOC.activeElement;
// The activeElement can be null, however there also appears to be a very odd
// and inconsistent bug in IE where the activeElement is simply an empty object
// literal. Test if the returned active element has focus, if not, we've hit the bug
// so just default back to the document body.
if (!active || !active.focus) {
active = DOC.body;
}
return asElement ? Ext.get(active) : active;
},
/**
* Retrieves the document height
* @static
* @inheritable
* @return {Number} documentHeight
*/
getDocumentHeight: function() {
return Math.max(!Ext.isStrict ? DOC.body.scrollHeight : docEl.scrollHeight, this.getViewportHeight());
},
/**
* Retrieves the document width
* @static
* @inheritable
* @return {Number} documentWidth
*/
getDocumentWidth: function() {
return Math.max(!Ext.isStrict ? DOC.body.scrollWidth : docEl.scrollWidth, this.getViewportWidth());
},
/**
* Retrieves the current orientation of the window. This is calculated by
* determining if the height is greater than the width.
* @static
* @inheritable
* @return {String} Orientation of window: 'portrait' or 'landscape'
*/
getOrientation: function() {
if (Ext.supports.OrientationChange) {
return (WIN.orientation == 0) ? 'portrait' : 'landscape';
}
return (WIN.innerHeight > WIN.innerWidth) ? 'portrait' : 'landscape';
},
/**
* Retrieves the viewport height of the window.
* @static
* @inheritable
* @return {Number} viewportHeight
*/
getViewportHeight: function() {
var viewportHeight = Element._viewportHeight;
//<feature legacyBrowser>
if (Ext.isIE9m) {
return DOC.documentElement.clientHeight;
}
//</feature>
return (viewportHeight != null) ? viewportHeight : docEl.clientHeight;
},
/**
* Retrieves the viewport width of the window.
* @static
* @inheritable
* @return {Number} viewportWidth
*/
getViewportWidth: function() {
var viewportWidth = Element._viewportWidth;
//<feature legacyBrowser>
if (Ext.isIE9m) {
return DOC.documentElement.clientWidth;
}
//</feature>
return (viewportWidth != null) ? viewportWidth : docEl.clientWidth;
},
/**
* Returns the current zoom level of the viewport as a ratio of page pixels to
* screen pixels.
* @private
* @static
* @return {Number}
*/
getViewportScale: function() {
// on deskop devices, the devicePixel ratio gives us the level of zoom that
// the user specified using ctrl +/- and or by selecting a zoom level from
// the menu.
// On android/iOS devicePixel ratio is a fixed number that represents the
// screen pixel density (e.g. always "2" on apple retina devices)
var top = WIN.top;
return ((Ext.isiOS || Ext.isAndroid) ? 1 :
(top.devicePixelRatio || // modern browsers
top.screen.deviceXDPI / top.screen.logicalXDPI)) // IE10m
* this.getViewportTouchScale();
},
/**
* On touch-screen devices there may be an additional level of zooming
* that occurs when the user performs a pinch or double-tap to zoom
* gesture. This is separate from and in addition to the
* devicePixelRatio. We can detect it by comparing the width
* of the documentElement to window.innerWidth
* @private
*/
getViewportTouchScale: function(forceRead) {
var scale = 1,
hidden = 'hidden',
top = WIN.top,
cachedScale;
if (!forceRead) {
cachedScale = this._viewportTouchScale;
if (cachedScale) {
return cachedScale;
}
}
if (Ext.isIE10p || Ext.isEdge || Ext.isiOS) {
scale = docEl.offsetWidth / WIN.innerWidth;
} else if (Ext.isChromeMobile) {
scale = top.outerWidth / top.innerWidth;
}
return scale;
},
/**
* Retrieves the viewport size of the window.
* @static
* @inheritable
* @return {Object} object containing width and height properties