/** * @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, WIN_TOP = WIN.top, EMPTY = [], 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", wordsRe = /\w/g, spacesRe = /\s+/, classNameSplitRegex = /[\s]+/, transparentRe = /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i, endsQuestionRe = /\?$/, topRe = /top/i, empty = {}, 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' }, selectDir = { b: 'backward', back: 'backward', f: 'forward' }, paddingsTLRB = [paddings.l, paddings.r, paddings.t, paddings.b], bordersTLRB = [borders.l, borders.r, borders.t, borders.b], numberRe = /\d+$/, unitRe = /\d+(px|r?em|%|vh|vw|vmin|vmax|en|ch|ex|pt|in|cm|mm|pc)$/i, defaultUnit = 'px', msRe = /^-ms-/, camelRe = /(-[a-z])/gi, /* eslint-disable-next-line no-useless-escape */ cssRe = /([a-z0-9\-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi, pxRe = /^\d+(?:\.\d*)?px$/i, relativeUnitRe = /(%|r?em|auto|vh|vw|vmin|vmax|ch|ex)$/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); } } } }, toFloat = function(v) { return parseFloat(v) || 0; }, opacityCls = Ext.baseCSSPrefix + 'hidden-opacity', visibilityCls = Ext.baseCSSPrefix + 'hidden-visibility', displayCls = Ext.baseCSSPrefix + 'hidden-display', offsetsCls = Ext.baseCSSPrefix + 'hidden-offsets', clipCls = Ext.baseCSSPrefix + 'hidden-clip', lastFocusChange = 0, lastKeyboardClose = 0, editableHasFocus = false, isVirtualKeyboardOpen = false, inputTypeSelectionSupported = /text|password|search|tel|url/i, visFly, scrollFly, caFly, wrapFly, grannyFly, activeElFly; // We use element ID counter to prevent assigning the same id to top and nested // window and document objects when Ext is running in <iframe>. // Cross-origin access might throw an exception, in which case we can't // reference top window. In IE8 simply getting a property does not throw // but trying to set it does. try { elementIdCounter = WIN_TOP.__elementIdCounter__; WIN_TOP.__elementIdCounter__ = elementIdCounter; } catch (e) { WIN_TOP = WIN; } WIN_TOP.__elementIdCounter__ = elementIdCounter = (WIN_TOP.__elementIdCounter__ || 0) + 1; windowId = 'ext-window-' + elementIdCounter; documentId = 'ext-document-' + elementIdCounter; //<debug> if (Object.freeze) { Object.freeze(EMPTY); } //</debug> 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-', _selectDir: selectDir, styleHooks: { transform: { set: function(dom, value, el) { var result = '', prop; if (typeof value !== 'string') { for (prop in value) { if (result) { result += ' '; } if (prop.indexOf('translate') === 0) { result += prop + '(' + Element.addUnits(value[prop], 'px') + ')'; } else { result += prop + '(' + value[prop] + ')'; } } value = result; } dom.style.transform = value; } } }, 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. * @param {Object} info The element's new size parameters. */ /** * @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. */ /** * 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} dom * @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; if (!(id = dom.id)) { dom.id = id = me.generateAutoId(); } me.id = id; // Uncomment this when debugging orphaned Elements // if (id === 'ext-element-5') debugger; //<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: { /** * @property cache * @private * @static * @inheritable */ cache: Ext.cache = {}, /** * @property editableSelector * @static * @private * @inheritable */ editableSelector: 'input,textarea,[contenteditable="true"]', /** * @property {Number} VISIBILITY * 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} DISPLAY * 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} OFFSETS * 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} CLIP * 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 {Number} OPACITY * Visibility mode constant for use with {@link Ext.dom.Element#setVisibilityMode}. * Use CSS `opacity` property to reduce element's opacity to 0 * @static * @inheritable */ OPACITY: 5, /** * @property minKeyboardHeight * @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} useDelegatedEvents * @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} validNodeTypes * @private * @static * @inheritable * The list of valid nodeTypes that are allowed to be wrapped */ validNodeTypes: { 1: 1, // ELEMENT_NODE 9: 1 // DOCUMENT_NODE }, namespaceURIs: { html: 'http://www.w3.org/1999/xhtml', svg: 'http://www.w3.org/2000/svg' }, selectableCls: Ext.baseCSSPrefix + 'selectable', unselectableCls: Ext.baseCSSPrefix + 'unselectable', /** * Determines the maximum size for all ripples */ maxRippleDiameter: 75, /** * 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> // Don't warn about calc() expressions if (!(Ext.isString(size) && size.indexOf('calc') === 0)) { 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, namespace) { var me = this, classes, element, elementStyle, tag, value, name, i, ln, tmp, ns; 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'; } ns = attributes.namespace || namespace; if (ns) { element = DOC.createElementNS(me.namespaceURIs[ns] || ns, tag); } else { element = DOC.createElement(tag); } elementStyle = element.style; for (name in attributes) { if (name !== 'tag' && name !== 'namespace') { value = attributes[name]; switch (name) { case 'style': if (typeof value === 'string') { element.setAttribute(name, value); } else { for (i in value) { elementStyle[i] = value[i]; } } break; case 'className': case 'cls': tmp = value.split(spacesRe); classes = classes ? classes.concat(tmp) : tmp; break; case 'classList': classes = classes ? classes.concat(value) : value; break; case 'text': element.textContent = value; break; case 'html': element.innerHTML = value; break; case 'hidden': if (classes) { classes.push(displayCls); } else { classes = [displayCls]; } break; case 'children': if (value != null) { for (i = 0, ln = value.length; i < ln; i++) { element.appendChild(me.create(value[i], true, ns)); } } 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); } }, /** * @method fly * @inheritdoc Ext#method-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. * @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. * @static * @inheritable * @method */ 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 || Ext.isEdge) { 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> // eslint-disable-next-line max-len 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); // eslint-disable-line eqeqeq } // 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 (el === DOC) { el.id = id = documentId; } // Must use == here, otherwise IE fails to recognize the window else if (el == WIN) { // eslint-disable-line eqeqeq el.id = id = windowId; } if (cache.hasOwnProperty(id)) { entry = cache[id]; // eslint-disable-next-line max-len if (entry.skipGarbageCollection || el === entry.dom || !Ext.isGarbage(entry.dom)) { //<debug> if (el !== entry.dom) { warnDuplicate(id); } //</debug> return entry; } else { entry.destroy(); } } 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() { // eslint-disable-next-line max-len return Math.max(!Ext.isStrict ? DOC.body.scrollHeight : docEl.scrollHeight, this.getViewportHeight()); }, /** * Retrieves the document width * @static * @inheritable * @return {Number} documentWidth */ getDocumentWidth: function() { // eslint-disable-next-line max-len 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) { /* eslint-disable-next-line eqeqeq */ 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 desktop 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) // WIN_TOP is guarded against cross-frame access in the closure above 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, // WIN_TOP is guarded against cross-frame access in the closure above 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 */ getViewSize: function() { return { width: Element.getViewportWidth(), height: Element.getViewportHeight() }; }, /** * Checks if the passed size has a css unit attached. * @param {String} size The size. * @return {Boolean} `true` if the size has a css unit. * * @since 6.2.1 */ hasUnit: function(size) { return !!(size && unitRe.test(size)); }, /** * Checks if the passed css unit is a relative unit. This includes: * - `auto` * - `%` * - `em` * - `rem` * - `auto` * - `vh` * - `vw` * - `vmin` * - `vmax` * - `ex * - `ch` * @param {String} size The css unit and value. * @return {Boolean} `true` if the value is relative. * * @since 6.2.0 */ isRelativeUnit: function(size) { return !size || relativeUnitRe.test(size); }, /** * Mask iframes when shim is true. See {@link Ext.util.Floating#shim}. * @private */ maskIframes: function() { var iframes = document.getElementsByTagName('iframe'), fly = new Ext.dom.Fly(); Ext.each(iframes, function(iframe) { var myMask; myMask = fly.attach(iframe.parentNode).mask(); myMask.setStyle('background-color', 'transparent'); }); }, /** * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax. * For example: * * - border-width -> borderWidth * - padding-top -> paddingTop * * @static * @inheritable * @param {String} prop The property to normalize * @return {String} The normalized string */ normalize: function(prop) { // For '-ms-foo' we need msFoo // eslint-disable-next-line max-len return propertyCache[prop] || (propertyCache[prop] = prop.replace(msRe, 'ms-').replace(camelRe, camelReplaceFn)); }, /** * @private * @static * @inheritable */ _onWindowFocusChange: function(e) { // Tracks the timestamp of focus entering or leaving an editable element // so that we can compare this timestamp to the time of the next window // resize for the purpose of determining if the virtual keyboard is displayed // see _onWindowResize for more details if (Ext.fly(e.target).is(Element.editableSelector)) { lastFocusChange = new Date(); editableHasFocus = (e.type === 'focusin' || e.type === 'pointerup'); } }, /** * @private * @static * @inheritable */ _onWindowResize: function() { var documentWidth = docEl.clientWidth, documentHeight = docEl.clientHeight, now = new Date(), threshold = 1000, deltaX, deltaY; deltaX = documentWidth - Element._documentWidth; deltaY = documentHeight - Element._documentHeight; Element._documentWidth = documentWidth; Element._documentHeight = documentHeight; // If the focus entered or left an editable element within a brief threshold // of time, then this resize event MAY be due to a virtual keyboard being // shown or hidden. Let's do some additional checking to find out. if (((now - lastFocusChange) < threshold) || ((now - lastKeyboardClose) < threshold)) { // eslint-disable-line max-len // If the resize is ONLY in the vertical direction, and an editable // element has the focus, and the vertical movement was significant, // we can be reasonably certain that the resize event was due to // a virtual keyboard being opened. if (deltaX === 0 && (editableHasFocus && (deltaY <= -Element.minKeyboardHeight))) { // eslint-disable-line max-len isVirtualKeyboardOpen = true; return; } } if (isVirtualKeyboardOpen && (deltaX === 0) && (deltaY >= Element.minKeyboardHeight)) { // eslint-disable-line max-len isVirtualKeyboardOpen = false; // when windows tablets are rotated while keyboard is open, the keyboard closes // and then immediately reopens. Track the timestamp of the last keyboard // close so that we can detect a successive resize event that might indicate // reopening lastKeyboardClose = new Date(); } if (isVirtualKeyboardOpen) { return; } // These cached variables are used by getViewportWidth and getViewportHeight // They do not get updated if we returned early due to detecting that the // resize event was triggered by virtual keyboard. Element._viewportWidth = documentWidth; Element._viewportHeight = documentHeight; }, /** * Parses a number or string representing margin sizes into an object. Supports * CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" * are all valid options and would return the same result) * @static * @inheritable * @param {Number/String} box The encoded margins * @return {Object} An object with margin sizes for top, right, bottom and left * containing the unit */ parseBox: function(box) { var type, parts, ln; box = box || 0; type = typeof box; if (type === 'number') { return { top: box, right: box, bottom: box, left: box }; } else if (type !== 'string') { // If not a number or a string, assume we've been given a box config. return box; } parts = box.split(' '); ln = parts.length; if (ln === 1) { parts[1] = parts[2] = parts[3] = parts[0]; } else if (ln === 2) { parts[2] = parts[0]; parts[3] = parts[1]; } else if (ln === 3) { parts[3] = parts[1]; } return { top: parseFloat(parts[0]) || 0, right: parseFloat(parts[1]) || 0, bottom: parseFloat(parts[2]) || 0, left: parseFloat(parts[3]) || 0 }; }, /** * Converts a CSS string into an object with a property for each style. * * The sample code below would return an object with 2 properties, one * for background-color and one for color. * * var css = 'background-color: red; color: blue;'; * console.log(Ext.dom.Element.parseStyles(css)); * * @static * @inheritable * @param {String} styles A CSS string * @return {Object} styles */ parseStyles: function(styles) { var out = {}, matches; if (styles) { // Since we're using the g flag on the regex, we need to set the lastIndex. // This automatically happens on some implementations, but not others, see: // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp cssRe.lastIndex = 0; while ((matches = cssRe.exec(styles))) { out[matches[1]] = matches[2] || ''; } } return out; }, /** * Selects elements based on the passed CSS selector to enable * {@link Ext.dom.Element Element} methods to be applied to many related * elements in one statement through the returned * {@link Ext.dom.CompositeElementLite CompositeElementLite} object. * @static * @inheritable * @param {String/HTMLElement[]} selector The CSS selector or an array of * elements * @param {Boolean} [composite=false] Return a CompositeElement as opposed to * a CompositeElementLite. Defaults to false. * @param {HTMLElement/String} [root] The root element of the query or id of * the root * @return {Ext.dom.CompositeElementLite/Ext.dom.CompositeElement} */ select: function(selector, composite, root) { return Ext.fly(root || DOC).select(selector, composite); }, /** * Selects child nodes of a given root based on the passed CSS selector. * @static * @inheritable * @param {String} selector The CSS selector. * @param {Boolean} [asDom=true] `false` to return an array of Ext.dom.Element * @param {HTMLElement/String} [root] The root element of the query or id of * the root * @return {HTMLElement[]/Ext.dom.Element[]} An Array of elements that match * the selector. If there are no matches, an empty Array is returned. */ query: function(selector, asDom, root) { return Ext.fly(root || DOC).query(selector, asDom); }, /** * Parses a number or string representing margin sizes into an object. Supports * CSS-style margin declarations (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" * are all valid options and would return the same result) * @static * @inheritable * @param {Number/String/Object} box The encoded margins, or an object with top, right, * @param {String} units The type of units to add * @return {String} An string with unitized (px if units is not specified) metrics for * top, right, bottom and left */ unitizeBox: function(box, units) { var me = this; box = me.parseBox(box); return me.addUnits(box.top, units) + ' ' + me.addUnits(box.right, units) + ' ' + me.addUnits(box.bottom, units) + ' ' + me.addUnits(box.left, units); }, /** * Unmask iframes when shim is true. See {@link Ext.util.Floating#cfg-shim}. * @private */ unmaskIframes: function() { var iframes = document.getElementsByTagName('iframe'), fly = new Ext.dom.Fly(); Ext.each(iframes, function(iframe) { fly.attach(iframe.parentNode).unmask(); }); }, /** * Serializes a DOM form into a url encoded string * @param {Object} form The form * @return {String} The url encoded form * @static * @inheritable */ serializeForm: function(form) { var fElements = form.elements || (DOC.forms[form] || Ext.getDom(form)).elements, hasSubmit = false, encoder = encodeURIComponent, data = '', eLen = fElements.length, element, name, type, options, hasValue, e, o, oLen, opt; for (e = 0; e < eLen; e++) { element = fElements[e]; name = element.name; type = element.type; options = element.options; if (!element.disabled && name) { if (/select-(one|multiple)/i.test(type)) { oLen = options.length; for (o = 0; o < oLen; o++) { opt = options[o]; if (opt.selected) { hasValue = opt.hasAttribute('value'); data += Ext.String.format('{0}={1}&', encoder(name), encoder(hasValue ? opt.value : opt.text)); // eslint-disable-line max-len } } } else if (!(/file|undefined|reset|button/i.test(type))) { if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type === 'submit' && hasSubmit)) { // eslint-disable-line max-len data += encoder(name) + '=' + encoder(element.value) + '&'; hasSubmit = /submit/i.test(type); } } } } return data.substr(0, data.length - 1); }, /** * Returns the common ancestor of the two passed elements. * @static * @inheritable * * @param {Ext.dom.Element/HTMLElement} nodeA * @param {Ext.dom.Element/HTMLElement} nodeB * @param {Boolean} returnDom Pass `true` to return a DOM element. Otherwise an * {@link Ext.dom.Element Element} will be returned. * @return {Ext.dom.Element/HTMLElement} The common ancestor. */ getCommonAncestor: function(nodeA, nodeB, returnDom) { caFly = caFly || new Ext.dom.Fly(); caFly.attach(Ext.getDom(nodeA)); while (!caFly.isAncestor(nodeB)) { if (caFly.dom.parentNode) { caFly.attach(caFly.dom.parentNode); } // If Any of the nodes in in a detached state, have to use the document.body else { caFly.attach(DOC.body); break; } } return returnDom ? caFly.dom : Ext.get(caFly); } }, /** * Enable text selection for this element (normalized across browsers) * @return {Ext.dom.Element} this */ selectable: function() { var me = this; // We clear this property for all browsers, not just Opera. This is so that rendering // templates don't need to condition on Opera when making elements unselectable. me.dom.unselectable = ''; me.removeCls(Element.unselectableCls); me.addCls(Element.selectableCls); return me; }, /** * Disables text selection for this element (normalized across browsers) * @return {Ext.dom.Element} this */ unselectable: function() { // The approach used to disable text selection combines CSS, HTML attributes and DOM // events. Importantly the strategy is designed to be expressible in markup, so that // elements can be rendered unselectable without needing modifications post-render. // e.g.: // // <div class="x-unselectable" unselectable="on"></div> // // Changes to this method may need to be reflected elsewhere, e.g. ProtoElement. var me = this; // The unselectable property (or similar) is supported by various browsers but Opera // is the only browser that doesn't support any of the other techniques. The problem // with it is that it isn't inherited by child elements. Theoretically we could add it // to all children but the performance would be terrible. In certain key locations // (e.g. panel headers) we add unselectable="on" to extra elements during rendering // just for Opera's benefit. if (Ext.isOpera) { me.dom.unselectable = 'on'; } // In Mozilla and WebKit the CSS properties -moz-user-select and -webkit-user-select // prevent a selection originating in an element. These are inherited, which is what // we want. // // In IE we rely on a listener for the selectstart event instead. We don't need to // register a listener on the individual element, instead we use a single listener // and rely on event propagation to listen for the event at the document level. // That listener will walk up the DOM looking for nodes that have either of the classes // x-selectable or x-unselectable. This simulates the CSS inheritance approach. // // IE 10 is expected to support -ms-user-select so the listener may not be required. me.removeCls(Element.selectableCls); me.addCls(Element.unselectableCls); return me; }, // statics statics: { // This selector will be modified at runtime in the _init() method above // to include the elements with saved tabindex in the returned set tabbableSelector: Ext.supports.CSS3NegationSelector ? 'a[href],button,iframe,input,select,textarea,[tabindex]:not([tabindex="-1"]),[contenteditable="true"]' // eslint-disable-line max-len : 'a[href],button,iframe,input,select,textarea,[tabindex],[contenteditable="true"]', // Anchor and link tags are special; they are only naturally focusable (and tabbable) // if they have href attribute, and tabbabledness is further platform/browser specific. // Thus we check it separately in the code. naturallyFocusableTags: { BUTTON: true, IFRAME: true, EMBED: true, INPUT: true, OBJECT: true, SELECT: true, TEXTAREA: true, HTML: Ext.isIE ? true : false, BODY: Ext.isIE ? false : true }, // <object> element is naturally tabbable only in IE8 and below naturallyTabbableTags: { BUTTON: true, IFRAME: true, INPUT: true, SELECT: true, TEXTAREA: true, OBJECT: Ext.isIE8m ? true : false }, inputTags: { INPUT: true, TEXTAREA: true }, tabbableSavedCounterAttribute: 'data-tabindex-counter', tabbableSavedValueAttribute: 'data-tabindex-value', splitCls: function(cls) { if (typeof cls === 'string') { cls = cls.split(spacesRe); } return cls; } }, // statics _init: function(E) { // Allow overriding the attribute name and/or selector; this is // done only once for performance reasons E.tabbableSelector += ',[' + E.tabbableSavedCounterAttribute + ']'; }, /** * Adds the given CSS class(es) to this Element. * @param {String/String[]} names The CSS classes to add separated by space, * or an array of classes * @param {String} [prefix] Prefix to prepend to each class. The separator `-` will be * appended to the prefix. * @param {String} [suffix] Suffix to append to each class. The separator `-` will be * prepended to the suffix. * @return {Ext.dom.Element} this */ addCls: function(names, prefix, suffix) { return this.replaceCls(null, names, prefix, suffix); }, /** * Sets up event handlers to add and remove a css class when the mouse is down and then up * on this element (a click effect) * @param {String} className The class to add * @param {Function} [testFn] A test function to execute before adding the class. The passed * parameter will be the Element instance. If this functions returns false, the class * will not be added. * @param {Object} [scope] The scope to execute the testFn in. * @return {Ext.dom.Element} this */ addClsOnClick: function(className, testFn, scope) { var me = this, hasTest = Ext.isFunction(testFn); me.on("mousedown", function() { if (hasTest && testFn.call(scope || me, me) === false) { return false; } me.addCls(className); Ext.getDoc().on({ mouseup: function() { // In case me was destroyed prior to mouseup if (me.dom) { me.removeCls(className); } }, single: true }); }); return me; }, /** * Sets up event handlers to add and remove a css class when this element has the focus * @param {String} className The class to add * @param {Function} [testFn] A test function to execute before adding the class. The passed * parameter will be the Element instance. If this functions returns false, the class * will not be added. * @param {Object} [scope] The scope to execute the testFn in. * @return {Ext.dom.Element} this */ addClsOnFocus: function(className, testFn, scope) { var me = this, hasTest = Ext.isFunction(testFn); me.on("focus", function() { if (hasTest && testFn.call(scope || me, me) === false) { return false; } me.addCls(className); }); me.on("blur", function() { // In case blur is caused by destruction of me if (me.dom) { me.removeCls(className); } }); return me; }, /** * Sets up event handlers to add and remove a css class when the mouse is over this element * @param {String} className The class to add * @param {Function} [testFn] A test function to execute before adding the class. The passed * parameter will be the Element instance. If this functions returns false, the class * will not be added. * @param {Object} [scope] The scope to execute the testFn in. * @return {Ext.dom.Element} this */ addClsOnOver: function(className, testFn, scope) { var me = this, hasTest = Ext.isFunction(testFn); me.hover( function() { if (hasTest && testFn.call(scope || me, me) === false) { return; } me.addCls(className); }, function() { me.removeCls(className); } ); return me; }, addStyles: function(sides, styles) { var totalSize = 0, sidesArr = (sides || '').match(wordsRe), styleSides = [], len = sidesArr.length, side, i; if (len === 1) { totalSize = parseFloat(this.getStyle(styles[sidesArr[0]])) || 0; } else if (len) { for (i = 0; i < len; i++) { side = sidesArr[i]; styleSides.push(styles[side]); } // Gather all at once, returning a hash styleSides = this.getStyle(styleSides); for (i = 0; i < len; i++) { side = sidesArr[i]; totalSize += parseFloat(styleSides[styles[side]]) || 0; } } return totalSize; }, addUnits: function(size, units) { return Element.addUnits(size, units); }, // The following 3 methods add just enough of an animation api to make the scroller work // in Sencha Touch // TODO: unify touch/ext animations animate: function(animation) { animation = new Ext.fx.Animation(animation); animation.setElement(this); this._activeAnimation = animation; animation.on({ animationend: this._onAnimationEnd, scope: this }); Ext.Animator.run(animation); return animation; }, _onAnimationEnd: function() { this._activeAnimation