hangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegram
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.
4092 lines
159 KiB
4092 lines
159 KiB
/** |
|
* @class Ext.dom.Element |
|
*/ |
|
Ext.define('Ext.overrides.dom.Element', (function() { |
|
var Element, // we cannot do this yet "= Ext.dom.Element" |
|
WIN = window, |
|
DOC = document, |
|
HIDDEN = 'hidden', |
|
ISCLIPPED = 'isClipped', |
|
OVERFLOW = 'overflow', |
|
OVERFLOWX = 'overflow-x', |
|
OVERFLOWY = 'overflow-y', |
|
ORIGINALCLIP = 'originalClip', |
|
HEIGHT = 'height', |
|
WIDTH = 'width', |
|
VISIBILITY = 'visibility', |
|
DISPLAY = 'display', |
|
NONE = 'none', |
|
OFFSETS = 'offsets', |
|
ORIGINALDISPLAY = 'originalDisplay', |
|
VISMODE = 'visibilityMode', |
|
ISVISIBLE = 'isVisible', |
|
OFFSETCLASS = Ext.baseCSSPrefix + 'hidden-offsets', |
|
boxMarkup = [ |
|
'<div class="{0}-tl" role="presentation">', |
|
'<div class="{0}-tr" role="presentation">', |
|
'<div class="{0}-tc" role="presentation"></div>', |
|
'</div>', |
|
'</div>', |
|
'<div class="{0}-ml" role="presentation">', |
|
'<div class="{0}-mr" role="presentation">', |
|
'<div class="{0}-mc" role="presentation"></div>', |
|
'</div>', |
|
'</div>', |
|
'<div class="{0}-bl" role="presentation">', |
|
'<div class="{0}-br" role="presentation">', |
|
'<div class="{0}-bc" role="presentation"></div>', |
|
'</div>', |
|
'</div>' |
|
].join(''), |
|
scriptTagRe = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig, |
|
replaceScriptTagRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, |
|
srcRe = /\ssrc=([\'\"])(.*?)\1/i, |
|
nonSpaceRe = /\S/, |
|
typeRe = /\stype=([\'\"])(.*?)\1/i, |
|
msRe = /^-ms-/, |
|
camelRe = /(-[a-z])/gi, |
|
camelReplaceFn = function(m, a) { |
|
return a.charAt(1).toUpperCase(); |
|
}, |
|
XMASKED = Ext.baseCSSPrefix + "masked", |
|
XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative", |
|
EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg", |
|
bodyRe = /^body/i, |
|
propertyCache = {}, |
|
getDisplay = function(el) { |
|
var data = el.getData(), |
|
display = data[ORIGINALDISPLAY]; |
|
|
|
if (display === undefined) { |
|
data[ORIGINALDISPLAY] = display = ''; |
|
} |
|
return display; |
|
}, |
|
getVisMode = function(el){ |
|
var data = el.getData(), |
|
visMode = data[VISMODE]; |
|
|
|
if (visMode === undefined) { |
|
data[VISMODE] = visMode = Element.VISIBILITY; |
|
} |
|
return visMode; |
|
}, |
|
emptyRange = DOC.createRange ? DOC.createRange() : null, |
|
inputTags = { |
|
INPUT: true, |
|
TEXTAREA: true |
|
}; |
|
|
|
//<feature legacyBrowser> |
|
if (Ext.isIE8) { |
|
var removeNode = Ext.removeNode, // save a reference to the removeNode function defined in sencha-core |
|
garbageBin = DOC.createElement('div'), |
|
destroyQueue = [], |
|
|
|
// prevent memory leaks in IE8 |
|
// see http://social.msdn.microsoft.com/Forums/ie/en-US/c76967f0-dcf8-47d0-8984-8fe1282a94f5/ie-appendchildremovechild-memory-problem?forum=iewebdevelopment |
|
// This function is called to fully destroy an element on a timer so that code following the |
|
// remove call can still access the element. |
|
clearGarbage = Ext.Function.createBuffered(function() { |
|
var len = destroyQueue.length, |
|
i; |
|
|
|
for (i = 0; i < len; i++) { |
|
garbageBin.appendChild(destroyQueue[i]); |
|
} |
|
garbageBin.innerHTML = ''; |
|
destroyQueue.length = 0; |
|
}, 10); |
|
|
|
Ext.removeNode = function(node) { |
|
node = node.dom || node; |
|
removeNode(node); |
|
destroyQueue[destroyQueue.length] = node; |
|
|
|
// Will perform extra IE8 cleanup in 10 milliseconds |
|
// see http://social.msdn.microsoft.com/Forums/ie/en-US/c76967f0-dcf8-47d0-8984-8fe1282a94f5/ie-appendchildremovechild-memory-problem?forum=iewebdevelopment |
|
clearGarbage(); |
|
}; |
|
} |
|
//</feature> |
|
|
|
return { |
|
override: 'Ext.dom.Element', |
|
|
|
mixins: [ |
|
'Ext.util.Animate' |
|
], |
|
|
|
uses: [ |
|
'Ext.dom.GarbageCollector', |
|
'Ext.dom.Fly', |
|
'Ext.event.publisher.MouseEnterLeave', |
|
'Ext.fx.Manager', |
|
'Ext.fx.Anim' |
|
], |
|
|
|
skipGarbageCollection: false, |
|
|
|
_init: function (E) { |
|
Element = E; // now we can poke this into closure scope |
|
}, |
|
|
|
statics: { |
|
selectableCls: Ext.baseCSSPrefix + 'selectable', |
|
unselectableCls: Ext.baseCSSPrefix + 'unselectable', |
|
|
|
/** |
|
* tabIndex attribute name for DOM lookups; needed in IE8 because |
|
* it has a bug: dom.getAttribute('tabindex') will return null |
|
* while dom.getAttribute('tabIndex') will return the actual value. |
|
* IE9+ and all other browsers normalize attribute names to lowercase. |
|
* |
|
* @static |
|
* @private |
|
*/ |
|
tabIndexAttributeName: Ext.isIE8 ? 'tabIndex' : 'tabindex', |
|
|
|
tabbableSelector: '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 |
|
}, |
|
|
|
// <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 |
|
}, |
|
|
|
tabbableSavedFlagAttribute: 'data-tabindexsaved', |
|
tabbableSavedAttribute: 'data-savedtabindex', |
|
|
|
normalize: function(prop) { |
|
if (prop === 'float') { |
|
prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat'; |
|
} |
|
// For '-ms-foo' we need msFoo |
|
return propertyCache[prop] || (propertyCache[prop] = prop.replace(msRe, 'ms-').replace(camelRe, camelReplaceFn)); |
|
}, |
|
|
|
getViewportHeight: function(){ |
|
return Ext.isIE9m ? DOC.documentElement.clientHeight : WIN.innerHeight; |
|
}, |
|
|
|
getViewportWidth: function() { |
|
return (!Ext.isStrict && !Ext.isOpera) ? document.body.clientWidth : |
|
Ext.isIE9m ? DOC.documentElement.clientWidth : WIN.innerWidth; |
|
} |
|
}, |
|
|
|
/** |
|
* 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, |
|
dom = me.dom, |
|
hasTest = Ext.isFunction(testFn); |
|
|
|
me.on("mousedown", function() { |
|
if (hasTest && testFn.call(scope || me, me) === false) { |
|
return false; |
|
} |
|
Ext.fly(dom).addCls(className); |
|
var d = Ext.getDoc(), |
|
fn = function() { |
|
Ext.fly(dom).removeCls(className); |
|
d.removeListener("mouseup", fn); |
|
}; |
|
d.on("mouseup", fn); |
|
}); |
|
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, |
|
dom = me.dom, |
|
hasTest = Ext.isFunction(testFn); |
|
|
|
me.on("focus", function() { |
|
if (hasTest && testFn.call(scope || me, me) === false) { |
|
return false; |
|
} |
|
Ext.fly(dom).addCls(className); |
|
}); |
|
me.on("blur", function() { |
|
Ext.fly(dom).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, |
|
dom = me.dom, |
|
hasTest = Ext.isFunction(testFn); |
|
|
|
me.hover( |
|
function() { |
|
if (hasTest && testFn.call(scope || me, me) === false) { |
|
return; |
|
} |
|
Ext.fly(dom).addCls(className); |
|
}, |
|
function() { |
|
Ext.fly(dom).removeCls(className); |
|
} |
|
); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Convenience method for constructing a KeyMap |
|
* @param {String/Number/Number[]/Object} key Either a string with the keys to listen for, the numeric key code, |
|
* array of key codes or an object with the following options: |
|
* @param {Number/Array} key.key |
|
* @param {Boolean} key.shift |
|
* @param {Boolean} key.ctrl |
|
* @param {Boolean} key.alt |
|
* @param {Function} fn The function to call |
|
* @param {Object} [scope] The scope (`this` reference) in which the specified function is executed. Defaults to this Element. |
|
* @return {Ext.util.KeyMap} The KeyMap created |
|
*/ |
|
addKeyListener: function(key, fn, scope){ |
|
var config; |
|
if(typeof key !== 'object' || Ext.isArray(key)){ |
|
config = { |
|
target: this, |
|
key: key, |
|
fn: fn, |
|
scope: scope |
|
}; |
|
} else { |
|
config = { |
|
target: this, |
|
key : key.key, |
|
shift : key.shift, |
|
ctrl : key.ctrl, |
|
alt : key.alt, |
|
fn: fn, |
|
scope: scope |
|
}; |
|
} |
|
return new Ext.util.KeyMap(config); |
|
}, |
|
|
|
/** |
|
* Creates a KeyMap for this element |
|
* @param {Object} config The KeyMap config. See {@link Ext.util.KeyMap} for more details |
|
* @return {Ext.util.KeyMap} The KeyMap created |
|
*/ |
|
addKeyMap: function(config) { |
|
return new Ext.util.KeyMap(Ext.apply({ |
|
target: this |
|
}, config)); |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
afterAnimate: function() { |
|
var shadow = this.shadow; |
|
|
|
if (shadow && !shadow.disabled && !shadow.animate) { |
|
shadow.show(); |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
anchorAnimX: function(anchor) { |
|
var xName = (anchor === 'l') ? 'right' : 'left'; |
|
this.dom.style[xName] = '0px'; |
|
}, |
|
|
|
/** |
|
* @private |
|
* process the passed fx configuration. |
|
*/ |
|
anim: function(config) { |
|
if (!Ext.isObject(config)) { |
|
return (config) ? {} : false; |
|
} |
|
|
|
var me = this, |
|
duration = config.duration || Ext.fx.Anim.prototype.duration, |
|
easing = config.easing || 'ease', |
|
animConfig; |
|
|
|
if (config.stopAnimation) { |
|
me.stopAnimation(); |
|
} |
|
|
|
Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id)); |
|
|
|
// Clear any 'paused' defaults. |
|
Ext.fx.Manager.setFxDefaults(me.id, { |
|
delay: 0 |
|
}); |
|
|
|
animConfig = { |
|
// Pass the DOM reference. That's tested first so will be converted to an Ext.fx.Target fastest. |
|
target: me.dom, |
|
remove: config.remove, |
|
alternate: config.alternate || false, |
|
duration: duration, |
|
easing: easing, |
|
callback: config.callback, |
|
listeners: config.listeners, |
|
iterations: config.iterations || 1, |
|
scope: config.scope, |
|
block: config.block, |
|
concurrent: config.concurrent, |
|
delay: config.delay || 0, |
|
paused: true, |
|
keyframes: config.keyframes, |
|
from: config.from || {}, |
|
to: Ext.apply({}, config) |
|
}; |
|
Ext.apply(animConfig.to, config.to); |
|
|
|
// Anim API properties - backward compat |
|
delete animConfig.to.to; |
|
delete animConfig.to.from; |
|
delete animConfig.to.remove; |
|
delete animConfig.to.alternate; |
|
delete animConfig.to.keyframes; |
|
delete animConfig.to.iterations; |
|
delete animConfig.to.listeners; |
|
delete animConfig.to.target; |
|
delete animConfig.to.paused; |
|
delete animConfig.to.callback; |
|
delete animConfig.to.scope; |
|
delete animConfig.to.duration; |
|
delete animConfig.to.easing; |
|
delete animConfig.to.concurrent; |
|
delete animConfig.to.block; |
|
delete animConfig.to.stopAnimation; |
|
delete animConfig.to.delay; |
|
return animConfig; |
|
}, |
|
|
|
/** |
|
* Performs custom animation on this Element. |
|
* |
|
* The following properties may be specified in `from`, `to`, and `keyframe` objects: |
|
* |
|
* - `x` - The page X position in pixels. |
|
* |
|
* - `y` - The page Y position in pixels |
|
* |
|
* - `left` - The element's CSS `left` value. Units must be supplied. |
|
* |
|
* - `top` - The element's CSS `top` value. Units must be supplied. |
|
* |
|
* - `width` - The element's CSS `width` value. Units must be supplied. |
|
* |
|
* - `height` - The element's CSS `height` value. Units must be supplied. |
|
* |
|
* - `scrollLeft` - The element's `scrollLeft` value. |
|
* |
|
* - `scrollTop` - The element's `scrollTop` value. |
|
* |
|
* - `opacity` - The element's `opacity` value. This must be a value between `0` and `1`. |
|
* |
|
* **Be aware** that animating an Element which is being used by an Ext Component without in some way informing the |
|
* Component about the changed element state will result in incorrect Component behaviour. This is because the |
|
* Component will be using the old state of the element. To avoid this problem, it is now possible to directly |
|
* animate certain properties of Components. |
|
* |
|
* @param {Object} config Configuration for {@link Ext.fx.Anim}. |
|
* Note that the {@link Ext.fx.Anim#to to} config is required. |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
animate: function(config) { |
|
var me = this, |
|
animId = me.dom.id || Ext.id(me.dom), |
|
listeners, |
|
anim, |
|
end; |
|
|
|
|
|
if (!Ext.fx.Manager.hasFxBlock(animId)) { |
|
// Bit of gymnastics here to ensure our internal listeners get bound first |
|
if (config.listeners) { |
|
listeners = config.listeners; |
|
delete config.listeners; |
|
} |
|
if (config.internalListeners) { |
|
config.listeners = config.internalListeners; |
|
delete config.internalListeners; |
|
} |
|
end = config.autoEnd; |
|
delete config.autoEnd; |
|
anim = new Ext.fx.Anim(me.anim(config)); |
|
anim.on({ |
|
afteranimate: 'afterAnimate', |
|
beforeanimate: 'beforeAnimate', |
|
scope: me, |
|
single: true |
|
}); |
|
if (listeners) { |
|
anim.on(listeners); |
|
} |
|
Ext.fx.Manager.queueFx(anim); |
|
if (end) { |
|
anim.jumpToEnd(); |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
beforeAnimate: function() { |
|
var shadow = this.shadow; |
|
|
|
if (shadow && !shadow.disabled && !shadow.animate) { |
|
shadow.hide(); |
|
} |
|
}, |
|
|
|
/** |
|
* Wraps the specified element with a special 9 element markup/CSS block that renders by default as |
|
* a gray container with a gradient background, rounded corners and a 4-way shadow. |
|
* |
|
* This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button}, |
|
* {@link Ext.panel.Panel} when {@link Ext.panel.Panel#frame frame=true}, {@link Ext.window.Window}). |
|
* The markup is of this form: |
|
* |
|
* <div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div> |
|
* <div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div> |
|
* <div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div> |
|
* |
|
* Example usage: |
|
* |
|
* // Basic box wrap |
|
* Ext.get("foo").boxWrap(); |
|
* |
|
* // You can also add a custom class and use CSS inheritance rules to customize the box look. |
|
* // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example |
|
* // for how to create a custom box wrap style. |
|
* Ext.get("foo").boxWrap().addCls("x-box-blue"); |
|
* |
|
* @param {String} [class='x-box'] A base CSS class to apply to the containing wrapper element. |
|
* Note that there are a number of CSS rules that are dependent on this name to make the overall effect work, |
|
* so if you supply an alternate base class, make sure you also supply all of the necessary rules. |
|
* @return {Ext.dom.Element} The outermost wrapping element of the created box structure. |
|
*/ |
|
boxWrap: function(cls) { |
|
cls = cls || Ext.baseCSSPrefix + 'box'; |
|
var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "' role='presentation'>" + Ext.String.format(boxMarkup, cls) + "</div>")); |
|
el.selectNode('.' + cls + '-mc').appendChild(this.dom); |
|
return el; |
|
}, |
|
|
|
/** |
|
* Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes. |
|
* @param {Boolean} [forceReclean=false] By default the element keeps track if it has been cleaned already |
|
* so you can call this over and over. However, if you update the element and need to force a re-clean, you |
|
* can pass true. |
|
*/ |
|
clean: function(forceReclean) { |
|
var me = this, |
|
dom = me.dom, |
|
data = me.getData(), |
|
n = dom.firstChild, |
|
ni = -1, |
|
nx; |
|
|
|
if (data.isCleaned && forceReclean !== true) { |
|
return me; |
|
} |
|
|
|
while (n) { |
|
nx = n.nextSibling; |
|
if (n.nodeType === 3) { |
|
// Remove empty/whitespace text nodes |
|
if (!(nonSpaceRe.test(n.nodeValue))) { |
|
dom.removeChild(n); |
|
// Combine adjacent text nodes |
|
} else if (nx && nx.nodeType === 3) { |
|
n.appendData(Ext.String.trim(nx.data)); |
|
dom.removeChild(nx); |
|
nx = n.nextSibling; |
|
n.nodeIndex = ++ni; |
|
} |
|
} else { |
|
// Recursively clean |
|
Ext.fly(n, '_clean').clean(); |
|
n.nodeIndex = ++ni; |
|
} |
|
n = nx; |
|
} |
|
|
|
data.isCleaned = true; |
|
return me; |
|
}, |
|
|
|
/** |
|
* Empties this element. Removes all child nodes. |
|
*/ |
|
empty: emptyRange ? function() { |
|
var dom = this.dom; |
|
|
|
if (dom.firstChild) { |
|
emptyRange.setStartBefore(dom.firstChild); |
|
emptyRange.setEndAfter(dom.lastChild); |
|
emptyRange.deleteContents(); |
|
} |
|
} : function() { |
|
var dom = this.dom; |
|
|
|
while (dom.lastChild) { |
|
dom.removeChild(dom.lastChild); |
|
} |
|
}, |
|
|
|
clearListeners: function() { |
|
this.removeAnchor(); |
|
this.callParent(); |
|
}, |
|
|
|
/** |
|
* Clears positioning back to the default when the document was loaded. |
|
* @param {String} [value=''] The value to use for the left, right, top, bottom. |
|
* You could use 'auto'. |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
clearPositioning: function(value) { |
|
value = value || ''; |
|
return this.setStyle({ |
|
left : value, |
|
right : value, |
|
top : value, |
|
bottom : value, |
|
'z-index' : '', |
|
position : 'static' |
|
}); |
|
}, |
|
|
|
/** |
|
* Creates a proxy element of this element |
|
* @param {String/Object} config The class name of the proxy element or a DomHelper config object |
|
* @param {String/HTMLElement} [renderTo] The element or element id to render the proxy to. Defaults to: document.body. |
|
* @param {Boolean} [matchBox=false] True to align and size the proxy to this element now. |
|
* @return {Ext.dom.Element} The new proxy element |
|
*/ |
|
createProxy: function(config, renderTo, matchBox) { |
|
config = (typeof config === 'object') ? config : |
|
{ tag: "div", role: 'presentation', cls: config }; |
|
|
|
var me = this, |
|
proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) : |
|
Ext.DomHelper.insertBefore(me.dom, config, true); |
|
|
|
proxy.setVisibilityMode(Element.DISPLAY); |
|
proxy.hide(); |
|
if (matchBox && me.setBox && me.getBox) { // check to make sure Element_position.js is loaded |
|
proxy.setBox(me.getBox()); |
|
} |
|
return proxy; |
|
}, |
|
|
|
/** |
|
* Clears any opacity settings from this element. Required in some cases for IE. |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
clearOpacity: function() { |
|
return this.setOpacity(''); |
|
}, |
|
|
|
/** |
|
* Store the current overflow setting and clip overflow on the element - use {@link #unclip} to remove |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
clip: function() { |
|
var me = this, |
|
data = me.getData(), |
|
style; |
|
|
|
if (!data[ISCLIPPED]) { |
|
data[ISCLIPPED] = true; |
|
style = me.getStyle([OVERFLOW, OVERFLOWX, OVERFLOWY]); |
|
data[ORIGINALCLIP] = { |
|
o: style[OVERFLOW], |
|
x: style[OVERFLOWX], |
|
y: style[OVERFLOWY] |
|
}; |
|
me.setStyle(OVERFLOW, HIDDEN); |
|
me.setStyle(OVERFLOWX, HIDDEN); |
|
me.setStyle(OVERFLOWY, HIDDEN); |
|
} |
|
return me; |
|
}, |
|
|
|
destroy: function() { |
|
var me = this, |
|
dom = me.dom, |
|
data = me.getData(), |
|
maskEl, maskMsg; |
|
|
|
if (dom && me.isAnimate) { |
|
me.stopAnimation(); |
|
} |
|
|
|
me.callParent(); |
|
|
|
//<feature legacyBrowser> |
|
// prevent memory leaks in IE8 |
|
// see http://social.msdn.microsoft.com/Forums/ie/en-US/c76967f0-dcf8-47d0-8984-8fe1282a94f5/ie-appendchildremovechild-memory-problem?forum=iewebdevelopment |
|
// must not be document, documentElement, body or window object |
|
// Have to use != instead of !== for IE8 or it will not recognize that the window |
|
// objects are equal |
|
if (dom && Ext.isIE8 && (dom.window != dom) && (dom.nodeType !== 9) && |
|
(dom.tagName !== 'BODY') && (dom.tagName !== 'HTML')) { |
|
destroyQueue[destroyQueue.length] = dom; |
|
|
|
|
|
// Will perform extra IE8 cleanup in 10 milliseconds |
|
// see http://social.msdn.microsoft.com/Forums/ie/en-US/c76967f0-dcf8-47d0-8984-8fe1282a94f5/ie-appendchildremovechild-memory-problem?forum=iewebdevelopment |
|
clearGarbage(); |
|
} |
|
//</feature> |
|
|
|
if (data) { |
|
maskEl = data.maskEl; |
|
maskMsg = data.maskMsg; |
|
|
|
if (maskEl) { |
|
maskEl.destroy(); |
|
} |
|
|
|
if (maskMsg) { |
|
maskMsg.destroy(); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Convenience method for setVisibilityMode(Element.DISPLAY). |
|
* @param {String} [display] What to set display to when visible |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
enableDisplayMode : function(display) { |
|
var me = this; |
|
|
|
me.setVisibilityMode(Element.DISPLAY); |
|
|
|
if (display !== undefined) { |
|
me.getData()[ORIGINALDISPLAY] = display; |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Fade an element in (from transparent to opaque). The ending opacity can be specified using the `opacity` |
|
* config option. Usage: |
|
* |
|
* // default: fade in from opacity 0 to 100% |
|
* el.fadeIn(); |
|
* |
|
* // custom: fade in from opacity 0 to 75% over 2 seconds |
|
* el.fadeIn({ opacity: .75, duration: 2000}); |
|
* |
|
* // common config options shown with default values |
|
* el.fadeIn({ |
|
* opacity: 1, //can be any value between 0 and 1 (e.g. .5) |
|
* easing: 'easeOut', |
|
* duration: 500 |
|
* }); |
|
* |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
fadeIn: function(o) { |
|
var me = this, |
|
dom = me.dom; |
|
|
|
me.animate(Ext.apply({}, o, { |
|
opacity: 1, |
|
internalListeners: { |
|
beforeanimate: function(anim){ |
|
// restore any visibility/display that may have |
|
// been applied by a fadeout animation |
|
var el = Ext.fly(dom, '_anim'); |
|
if (el.isStyle('display', 'none')) { |
|
el.setDisplayed(''); |
|
} else { |
|
el.show(); |
|
} |
|
} |
|
} |
|
})); |
|
return this; |
|
}, |
|
|
|
/** |
|
* Fade an element out (from opaque to transparent). The ending opacity can be specified using the `opacity` |
|
* config option. Note that IE may require `useDisplay:true` in order to redisplay correctly. |
|
* Usage: |
|
* |
|
* // default: fade out from the element's current opacity to 0 |
|
* el.fadeOut(); |
|
* |
|
* // custom: fade out from the element's current opacity to 25% over 2 seconds |
|
* el.fadeOut({ opacity: .25, duration: 2000}); |
|
* |
|
* // common config options shown with default values |
|
* el.fadeOut({ |
|
* opacity: 0, //can be any value between 0 and 1 (e.g. .5) |
|
* easing: 'easeOut', |
|
* duration: 500, |
|
* remove: false, |
|
* useDisplay: false |
|
* }); |
|
* |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
fadeOut: function(o) { |
|
var me = this, |
|
dom = me.dom; |
|
|
|
o = Ext.apply({ |
|
opacity: 0, |
|
internalListeners: { |
|
afteranimate: function(anim){ |
|
if (dom && anim.to.opacity === 0) { |
|
var el = Ext.fly(dom, '_anim'); |
|
if (o.useDisplay) { |
|
el.setDisplayed(false); |
|
} else { |
|
el.hide(); |
|
} |
|
} |
|
} |
|
} |
|
}, o); |
|
me.animate(o); |
|
return me; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
fixDisplay: function(){ |
|
var me = this; |
|
if (me.isStyle(DISPLAY, NONE)) { |
|
me.setStyle(VISIBILITY, HIDDEN); |
|
me.setStyle(DISPLAY, getDisplay(me)); // first try reverting to default |
|
if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block |
|
me.setStyle(DISPLAY, "block"); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Shows a ripple of exploding, attenuating borders to draw attention to an Element. Usage: |
|
* |
|
* // default: a single light blue ripple |
|
* el.frame(); |
|
* |
|
* // custom: 3 red ripples lasting 3 seconds total |
|
* el.frame("#ff0000", 3, { duration: 3000 }); |
|
* |
|
* // common config options shown with default values |
|
* el.frame("#C3DAF9", 1, { |
|
* duration: 1000 // duration of each individual ripple. |
|
* // Note: Easing is not configurable and will be ignored if included |
|
* }); |
|
* |
|
* @param {String} [color='#C3DAF9'] The hex color value for the border. |
|
* @param {Number} [count=1] The number of ripples to display. |
|
* @param {Object} [options] Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
frame: function(color, count, obj){ |
|
var me = this, |
|
dom = me.dom, |
|
beforeAnim; |
|
|
|
color = color || '#C3DAF9'; |
|
count = count || 1; |
|
obj = obj || {}; |
|
|
|
beforeAnim = function() { |
|
var el = Ext.fly(dom, '_anim'), |
|
animScope = this, |
|
box, |
|
proxy, proxyAnim; |
|
|
|
el.show(); |
|
box = el.getBox(); |
|
proxy = Ext.getBody().createChild({ |
|
role: 'presentation', |
|
id: el.dom.id + '-anim-proxy', |
|
style: { |
|
position : 'absolute', |
|
'pointer-events': 'none', |
|
'z-index': 35000, |
|
border : '0px solid ' + color |
|
} |
|
}); |
|
|
|
proxyAnim = new Ext.fx.Anim({ |
|
target: proxy, |
|
duration: obj.duration || 1000, |
|
iterations: count, |
|
from: { |
|
top: box.y, |
|
left: box.x, |
|
borderWidth: 0, |
|
opacity: 1, |
|
height: box.height, |
|
width: box.width |
|
}, |
|
to: { |
|
top: box.y - 20, |
|
left: box.x - 20, |
|
borderWidth: 10, |
|
opacity: 0, |
|
height: box.height + 40, |
|
width: box.width + 40 |
|
} |
|
}); |
|
proxyAnim.on('afteranimate', function() { |
|
proxy.destroy(); |
|
// kill the no-op element animation created below |
|
animScope.end(); |
|
}); |
|
}; |
|
|
|
me.animate({ |
|
// See "A Note About Wrapped Animations" at the top of this class: |
|
duration: (Math.max(obj.duration, 500) * 2) || 2000, |
|
listeners: { |
|
beforeanimate: { |
|
fn: beforeAnim |
|
} |
|
}, |
|
callback: obj.callback, |
|
scope: obj.scope |
|
}); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Return the CSS color for the specified CSS attribute. rgb, 3 digit (like `#fff`) |
|
* and valid values are convert to standard 6 digit hex color. |
|
* @param {String} attr The css attribute |
|
* @param {String} defaultValue The default value to use when a valid color isn't found |
|
* @param {String} [prefix] defaults to #. Use an empty string when working with |
|
* color anims. |
|
* @private |
|
*/ |
|
getColor: function(attr, defaultValue, prefix) { |
|
var v = this.getStyle(attr), |
|
color = prefix || prefix === '' ? prefix : '#', |
|
h, len, i=0; |
|
|
|
if (!v || (/transparent|inherit/.test(v))) { |
|
return defaultValue; |
|
} |
|
if (/^r/.test(v)) { |
|
v = v.slice(4, v.length - 1).split(','); |
|
len = v.length; |
|
for (; i<len; i++) { |
|
h = parseInt(v[i], 10); |
|
color += (h < 16 ? '0' : '') + h.toString(16); |
|
} |
|
} else { |
|
v = v.replace('#', ''); |
|
color += v.length === 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v; |
|
} |
|
return(color.length > 5 ? color.toLowerCase() : defaultValue); |
|
}, |
|
|
|
/** |
|
* Gets this element's {@link Ext.ElementLoader ElementLoader} |
|
* @return {Ext.ElementLoader} The loader |
|
*/ |
|
getLoader: function() { |
|
var me = this, |
|
data = me.getData(), |
|
loader = data.loader; |
|
|
|
if (!loader) { |
|
data.loader = loader = new Ext.ElementLoader({ |
|
target: me |
|
}); |
|
} |
|
return loader; |
|
}, |
|
|
|
/** |
|
* Gets an object with all CSS positioning properties. Useful along with |
|
* #setPostioning to get snapshot before performing an update and then restoring |
|
* the element. |
|
* @param {Boolean} [autoPx=false] true to return pixel values for "auto" styles. |
|
* @return {Object} |
|
*/ |
|
getPositioning: function(autoPx){ |
|
var styles = this.getStyle(['left', 'top', 'position', 'z-index']), |
|
dom = this.dom; |
|
|
|
if(autoPx) { |
|
if(styles.left === 'auto') { |
|
styles.left = dom.offsetLeft + 'px'; |
|
} |
|
if(styles.top === 'auto') { |
|
styles.top = dom.offsetTop + 'px'; |
|
} |
|
} |
|
|
|
return styles; |
|
}, |
|
|
|
/** |
|
* Slides the element while fading it out of view. An anchor point can be optionally passed to set the ending point |
|
* of the effect. Usage: |
|
* |
|
* // default: slide the element downward while fading out |
|
* el.ghost(); |
|
* |
|
* // custom: slide the element out to the right with a 2-second duration |
|
* el.ghost('r', { duration: 2000 }); |
|
* |
|
* // common config options shown with default values |
|
* el.ghost('b', { |
|
* easing: 'easeOut', |
|
* duration: 500 |
|
* }); |
|
* |
|
* @param {String} anchor (optional) One of the valid {@link Ext.fx.Anim} anchor positions (defaults to bottom: 'b') |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
ghost: function(anchor, obj) { |
|
var me = this, |
|
dom = me.dom, |
|
beforeAnim; |
|
|
|
anchor = anchor || "b"; |
|
beforeAnim = function() { |
|
var el = Ext.fly(dom, '_anim'), |
|
width = el.getWidth(), |
|
height = el.getHeight(), |
|
xy = el.getXY(), |
|
position = el.getPositioning(), |
|
to = { |
|
opacity: 0 |
|
}; |
|
switch (anchor) { |
|
case 't': |
|
to.y = xy[1] - height; |
|
break; |
|
case 'l': |
|
to.x = xy[0] - width; |
|
break; |
|
case 'r': |
|
to.x = xy[0] + width; |
|
break; |
|
case 'b': |
|
to.y = xy[1] + height; |
|
break; |
|
case 'tl': |
|
to.x = xy[0] - width; |
|
to.y = xy[1] - height; |
|
break; |
|
case 'bl': |
|
to.x = xy[0] - width; |
|
to.y = xy[1] + height; |
|
break; |
|
case 'br': |
|
to.x = xy[0] + width; |
|
to.y = xy[1] + height; |
|
break; |
|
case 'tr': |
|
to.x = xy[0] + width; |
|
to.y = xy[1] - height; |
|
break; |
|
} |
|
this.to = to; |
|
this.on('afteranimate', function () { |
|
var el = Ext.fly(dom, '_anim'); |
|
if (el) { |
|
el.hide(); |
|
el.clearOpacity(); |
|
el.setPositioning(position); |
|
} |
|
}); |
|
}; |
|
|
|
me.animate(Ext.applyIf(obj || {}, { |
|
duration: 500, |
|
easing: 'ease-out', |
|
listeners: { |
|
beforeanimate: beforeAnim |
|
} |
|
})); |
|
return me; |
|
}, |
|
|
|
/** |
|
* @override |
|
* Hide this element - Uses display mode to determine whether to use "display", |
|
* "visibility", or "offsets". See {@link #setVisible}. |
|
* @param {Boolean/Object} [animate] true for the default animation or a standard |
|
* Element animation config object |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
hide: function(animate){ |
|
// hideMode override |
|
if (typeof animate === 'string'){ |
|
this.setVisible(false, animate); |
|
return this; |
|
} |
|
this.setVisible(false, this.anim(animate)); |
|
return this; |
|
}, |
|
|
|
/** |
|
* Highlights the Element by setting a color (applies to the background-color by default, but can be changed using |
|
* the "attr" config option) and then fading back to the original color. If no original color is available, you |
|
* should provide the "endColor" config option which will be cleared after the animation. Usage: |
|
* |
|
* // default: highlight background to yellow |
|
* el.highlight(); |
|
* |
|
* // custom: highlight foreground text to blue for 2 seconds |
|
* el.highlight("0000ff", { attr: 'color', duration: 2000 }); |
|
* |
|
* // common config options shown with default values |
|
* el.highlight("ffff9c", { |
|
* attr: "backgroundColor", //can be any valid CSS property (attribute) that supports a color value |
|
* endColor: (current color) or "ffffff", |
|
* easing: 'easeIn', |
|
* duration: 1000 |
|
* }); |
|
* |
|
* @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # |
|
* (defaults to yellow: 'ffff9c') |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
highlight: function(color, o) { |
|
var me = this, |
|
dom = me.dom, |
|
from = {}, |
|
restore, to, attr, lns, event, fn; |
|
|
|
o = o || {}; |
|
lns = o.listeners || {}; |
|
attr = o.attr || 'backgroundColor'; |
|
from[attr] = color || 'ffff9c'; |
|
|
|
if (!o.to) { |
|
to = {}; |
|
to[attr] = o.endColor || me.getColor(attr, 'ffffff', ''); |
|
} |
|
else { |
|
to = o.to; |
|
} |
|
|
|
// Don't apply directly on lns, since we reference it in our own callbacks below |
|
o.listeners = Ext.apply(Ext.apply({}, lns), { |
|
beforeanimate: function() { |
|
restore = dom.style[attr]; |
|
var el = Ext.fly(dom, '_anim'); |
|
el.clearOpacity(); |
|
el.show(); |
|
|
|
event = lns.beforeanimate; |
|
if (event) { |
|
fn = event.fn || event; |
|
return fn.apply(event.scope || lns.scope || WIN, arguments); |
|
} |
|
}, |
|
afteranimate: function() { |
|
if (dom) { |
|
dom.style[attr] = restore; |
|
} |
|
|
|
event = lns.afteranimate; |
|
if (event) { |
|
fn = event.fn || event; |
|
fn.apply(event.scope || lns.scope || WIN, arguments); |
|
} |
|
} |
|
}); |
|
|
|
me.animate(Ext.apply({}, o, { |
|
duration: 1000, |
|
easing: 'ease-in', |
|
from: from, |
|
to: to |
|
})); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element. |
|
* @param {Function} overFn The function to call when the mouse enters the Element. |
|
* @param {Function} outFn The function to call when the mouse leaves the Element. |
|
* @param {Object} [scope] The scope (`this` reference) in which the functions are executed. Defaults |
|
* to the Element's DOM element. |
|
* @param {Object} [options] Options for the listener. See {@link Ext.util.Observable#addListener the |
|
* options parameter}. |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
hover: function(overFn, outFn, scope, options) { |
|
var me = this; |
|
me.on('mouseenter', overFn, scope || me.dom, options); |
|
me.on('mouseleave', outFn, scope || me.dom, options); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Initializes a {@link Ext.dd.DD} drag drop object for this element. |
|
* @param {String} group The group the DD object is member of |
|
* @param {Object} config The DD config object |
|
* @param {Object} overrides An object containing methods to override/implement on the DD object |
|
* @return {Ext.dd.DD} The DD object |
|
*/ |
|
initDD: function(group, config, overrides){ |
|
var dd = new Ext.dd.DD(Ext.id(this.dom), group, config); |
|
return Ext.apply(dd, overrides); |
|
}, |
|
|
|
/** |
|
* Initializes a {@link Ext.dd.DDProxy} object for this element. |
|
* @param {String} group The group the DDProxy object is member of |
|
* @param {Object} config The DDProxy config object |
|
* @param {Object} overrides An object containing methods to override/implement on the DDProxy object |
|
* @return {Ext.dd.DDProxy} The DDProxy object |
|
*/ |
|
initDDProxy: function(group, config, overrides){ |
|
var dd = new Ext.dd.DDProxy(Ext.id(this.dom), group, config); |
|
return Ext.apply(dd, overrides); |
|
}, |
|
|
|
/** |
|
* Initializes a {@link Ext.dd.DDTarget} object for this element. |
|
* @param {String} group The group the DDTarget object is member of |
|
* @param {Object} config The DDTarget config object |
|
* @param {Object} overrides An object containing methods to override/implement on the DDTarget object |
|
* @return {Ext.dd.DDTarget} The DDTarget object |
|
*/ |
|
initDDTarget: function(group, config, overrides){ |
|
var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config); |
|
return Ext.apply(dd, overrides); |
|
}, |
|
|
|
/** |
|
* Checks whether this element can be focused programmatically or by clicking. |
|
* To check if an element is in the document tab flow, use {@link #isTabbable}. |
|
* |
|
* @return {Boolean} True if the element is focusable |
|
*/ |
|
isFocusable: function() { |
|
var dom = this.dom, |
|
focusable = false, |
|
nodeName; |
|
|
|
if (dom && !dom.disabled) { |
|
nodeName = dom.nodeName; |
|
|
|
/* |
|
* An element is focusable if: |
|
* - It is naturally focusable, or |
|
* - It is an anchor or link with href attribute, or |
|
* - It has a tabIndex, or |
|
* - It is an editing host (contenteditable="true") |
|
* |
|
* Also note that we can't check dom.tabIndex because IE will return 0 |
|
* for elements that have no tabIndex attribute defined, regardless of |
|
* whether they are naturally focusable or not. |
|
*/ |
|
focusable = !!Ext.Element.naturallyFocusableTags[nodeName] || |
|
((nodeName === 'A' || nodeName === 'LINK') && !!dom.href) || |
|
dom.getAttribute('tabindex') != null || |
|
dom.contentEditable === 'true'; |
|
|
|
// In IE8, <input type="hidden"> does not have a corresponding style |
|
// so isVisible() will assume that it's not hidden. |
|
if (Ext.isIE8 && nodeName === 'INPUT' && dom.type === 'hidden') { |
|
focusable = false; |
|
} |
|
|
|
// Invisible elements cannot be focused, so check that as well |
|
focusable = focusable && this.isVisible(true); |
|
} |
|
|
|
return focusable; |
|
}, |
|
|
|
/** |
|
* Returns `true` if this Element is an input field, or is editable in any way. |
|
* @return {Boolean} `true` if this Element is an input field, or is editable in any way. |
|
*/ |
|
isInputField: function() { |
|
var dom = this.dom, |
|
contentEditable = dom.contentEditable; |
|
|
|
// contentEditable will default to inherit if not specified, only check if the |
|
// attribute has been set or explicitly set to true |
|
// http://html5doctor.com/the-contenteditable-attribute/ |
|
// Also skip <input> tags of type="button", we use them for checkboxes |
|
// and radio buttons |
|
if ((inputTags[dom.tagName] && dom.type !== 'button') || |
|
(contentEditable === '' || contentEditable === 'true')) { |
|
return true; |
|
} |
|
return false; |
|
}, |
|
|
|
/** |
|
* Checks whether this element participates in the sequential focus navigation, |
|
* and can be reached by using Tab key. |
|
* |
|
* @return {Boolean} True if the element is tabbable. |
|
*/ |
|
isTabbable: function() { |
|
var dom = this.dom, |
|
tabbable = false, |
|
nodeName, hasIndex, tabIndex; |
|
|
|
if (dom && !dom.disabled) { |
|
nodeName = dom.nodeName; |
|
|
|
// Can't use dom.tabIndex here because IE will return 0 for elements |
|
// that have no tabindex attribute defined, regardless of whether they are |
|
// naturally tabbable or not. |
|
tabIndex = dom.getAttribute('tabindex'); |
|
hasIndex = tabIndex != null; |
|
|
|
tabIndex -= 0; |
|
|
|
// Anchors and links are only naturally tabbable if they have href attribute |
|
// See http://www.w3.org/TR/html5/editing.html#specially-focusable |
|
if (nodeName === 'A' || nodeName === 'LINK') { |
|
if (dom.href) { |
|
// It is also possible to make an anchor untabbable by setting |
|
// tabIndex < 0 on it |
|
tabbable = hasIndex && tabIndex < 0 ? false : true; |
|
} |
|
|
|
// Anchor w/o href is tabbable if it has tabIndex >= 0, |
|
// or if it's editable |
|
else { |
|
if (dom.contentEditable === 'true') { |
|
tabbable = !hasIndex || (hasIndex && tabIndex >= 0) ? true : false; |
|
} |
|
else { |
|
tabbable = hasIndex && tabIndex >= 0 ? true : false; |
|
} |
|
} |
|
} |
|
|
|
// If an element has contenteditable="true" or is naturally tabbable, |
|
// then it is a potential candidate unless its tabIndex is < 0. |
|
else if (dom.contentEditable === 'true' || |
|
Ext.Element.naturallyTabbableTags[nodeName]) { |
|
tabbable = hasIndex && tabIndex < 0 ? false : true; |
|
} |
|
|
|
// That leaves non-editable elements that can only be made tabbable |
|
// by slapping tabIndex >= 0 on them |
|
else { |
|
if (hasIndex && tabIndex >= 0) { |
|
tabbable = true; |
|
} |
|
} |
|
|
|
// In IE8, <input type="hidden"> does not have a corresponding style |
|
// so isVisible() will assume that it's not hidden. |
|
if (Ext.isIE8 && nodeName === 'INPUT' && dom.type === 'hidden') { |
|
tabbable = false; |
|
} |
|
|
|
// Invisible elements can't be tabbed into. If we have a component ref |
|
// we'll also check if the component itself is visible before incurring |
|
// the expense of DOM style reads. |
|
tabbable = tabbable && |
|
(!this.component || this.component.isVisible(true)) && |
|
this.isVisible(true); |
|
} |
|
|
|
return tabbable; |
|
}, |
|
|
|
/** |
|
* Returns true if this element is masked. Also re-centers any displayed message |
|
* within the mask. |
|
* |
|
* @param {Boolean} [deep] Go up the DOM hierarchy to determine if any parent |
|
* element is masked. |
|
* |
|
* @return {Boolean} |
|
*/ |
|
isMasked: function(deep) { |
|
var me = this, |
|
data = me.getData(), |
|
maskEl = data.maskEl, |
|
maskMsg = data.maskMsg, |
|
hasMask = false, |
|
parent; |
|
|
|
if (maskEl && maskEl.isVisible()) { |
|
if (maskMsg) { |
|
maskMsg.center(me); |
|
} |
|
hasMask = true; |
|
} |
|
else if (deep) { |
|
parent = me.findParentNode(); |
|
|
|
if (parent) { |
|
return Ext.fly(parent).isMasked(deep); |
|
} |
|
} |
|
|
|
return hasMask; |
|
}, |
|
|
|
/** |
|
* Returns true if this element is scrollable. |
|
* @return {Boolean} |
|
*/ |
|
isScrollable: function() { |
|
var dom = this.dom; |
|
return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth; |
|
}, |
|
|
|
/** |
|
* Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#method-load} method. |
|
* The method takes the same object parameter as {@link Ext.ElementLoader#method-load} |
|
* @param {Object} options a options object for Ext.ElementLoader {@link Ext.ElementLoader#method-load} |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
load: function(options) { |
|
this.getLoader().load(options); |
|
return this; |
|
}, |
|
|
|
/** |
|
* Puts a mask over this element to disable user interaction. |
|
* This method can only be applied to elements which accept child nodes. Use |
|
* {@link #unmask} to remove the mask. |
|
* |
|
* @param {String} [msg] A message to display in the mask |
|
* @param {String} [msgCls] A css class to apply to the msg element |
|
* @return {Ext.dom.Element} The mask element |
|
*/ |
|
mask: function (msg, msgCls /* private - passed by AbstractComponent.mask to avoid the need to interrogate the DOM to get the height*/, elHeight) { |
|
var me = this, |
|
dom = me.dom, |
|
data = me.getData(), |
|
maskEl = data.maskEl, |
|
maskMsg; |
|
|
|
if (!(bodyRe.test(dom.tagName) && me.getStyle('position') === 'static')) { |
|
me.addCls(XMASKEDRELATIVE); |
|
} |
|
|
|
// We always needs to recreate the mask since the DOM element may have been re-created |
|
if (maskEl) { |
|
maskEl.destroy(); |
|
} |
|
|
|
maskEl = Ext.DomHelper.append(dom, { |
|
role: 'presentation', |
|
cls : Ext.baseCSSPrefix + "mask " + Ext.baseCSSPrefix + "border-box", |
|
children: { |
|
role: 'presentation', |
|
cls : msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG, |
|
cn : { |
|
tag: 'div', |
|
role: 'presentation', |
|
cls: Ext.baseCSSPrefix + 'mask-msg-inner', |
|
cn: { |
|
tag: 'div', |
|
role: 'presentation', |
|
cls: Ext.baseCSSPrefix + 'mask-msg-text', |
|
html: msg || '' |
|
} |
|
} |
|
} |
|
}, true); |
|
maskMsg = Ext.get(maskEl.dom.firstChild); |
|
|
|
data.maskEl = maskEl; |
|
|
|
me.addCls(XMASKED); |
|
maskEl.setDisplayed(true); |
|
|
|
if (typeof msg === 'string') { |
|
maskMsg.setDisplayed(true); |
|
maskMsg.center(me); |
|
} else { |
|
maskMsg.setDisplayed(false); |
|
} |
|
|
|
// When masking the body, don't touch its tabbable state |
|
if (dom === DOC.body) { |
|
maskEl.addCls(Ext.baseCSSPrefix + 'mask-fixed'); |
|
} |
|
else { |
|
me.saveTabbableState(); |
|
} |
|
|
|
me.saveChildrenTabbableState(); |
|
|
|
// ie will not expand full height automatically |
|
if (Ext.isIE9m && dom !== DOC.body && me.isStyle('height', 'auto')) { |
|
maskEl.setSize(undefined, elHeight || me.getHeight()); |
|
} |
|
return maskEl; |
|
}, |
|
|
|
/** |
|
* Monitors this Element for the mouse leaving. Calls the function after the specified delay only if |
|
* the mouse was not moved back into the Element within the delay. If the mouse *was* moved |
|
* back in, the function is not called. |
|
* @param {Number} delay The delay **in milliseconds** to wait for possible mouse re-entry before calling the handler function. |
|
* @param {Function} handler The function to call if the mouse remains outside of this Element for the specified time. |
|
* @param {Object} [scope] The scope (`this` reference) in which the handler function executes. Defaults to this Element. |
|
* @return {Object} The listeners object which was added to this element so that monitoring can be stopped. Example usage: |
|
* |
|
* // Hide the menu if the mouse moves out for 250ms or more |
|
* this.mouseLeaveMonitor = this.menuEl.monitorMouseLeave(250, this.hideMenu, this); |
|
* |
|
* ... |
|
* // Remove mouseleave monitor on menu destroy |
|
* this.menuEl.un(this.mouseLeaveMonitor); |
|
* |
|
*/ |
|
monitorMouseLeave: function(delay, handler, scope) { |
|
var me = this, |
|
timer, |
|
listeners = { |
|
mouseleave: function(e) { |
|
//<feature legacyBrowser> |
|
if (Ext.isIE9m) { |
|
e.enableIEAsync(); |
|
} |
|
//</feature> |
|
timer = Ext.defer(handler, delay, scope || me, [e]); |
|
}, |
|
mouseenter: function() { |
|
clearTimeout(timer); |
|
} |
|
}; |
|
|
|
me.on(listeners); |
|
return listeners; |
|
}, |
|
|
|
/** |
|
* Fades the element out while slowly expanding it in all directions. When the effect is completed, the element will |
|
* be hidden (visibility = 'hidden') but block elements will still take up space in the document. Usage: |
|
* |
|
* // default |
|
* el.puff(); |
|
* |
|
* // common config options shown with default values |
|
* el.puff({ |
|
* easing: 'easeOut', |
|
* duration: 500, |
|
* useDisplay: false |
|
* }); |
|
* |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
puff: function(obj) { |
|
var me = this, |
|
dom = me.dom, |
|
beforeAnim, |
|
box = me.getBox(), |
|
originalStyles = me.getStyle(['width', 'height', 'left', 'right', 'top', 'bottom', 'position', 'z-index', 'font-size', 'opacity'], true); |
|
|
|
obj = Ext.applyIf(obj || {}, { |
|
easing: 'ease-out', |
|
duration: 500, |
|
useDisplay: false |
|
}); |
|
|
|
beforeAnim = function() { |
|
var el = Ext.fly(dom, '_anim'); |
|
|
|
el.clearOpacity(); |
|
el.show(); |
|
this.to = { |
|
width: box.width * 2, |
|
height: box.height * 2, |
|
x: box.x - (box.width / 2), |
|
y: box.y - (box.height /2), |
|
opacity: 0, |
|
fontSize: '200%' |
|
}; |
|
this.on('afteranimate',function() { |
|
var el = Ext.fly(dom, '_anim'); |
|
if (el) { |
|
if (obj.useDisplay) { |
|
el.setDisplayed(false); |
|
} else { |
|
el.hide(); |
|
} |
|
el.setStyle(originalStyles); |
|
Ext.callback(obj.callback, obj.scope); |
|
} |
|
}); |
|
}; |
|
|
|
me.animate({ |
|
duration: obj.duration, |
|
easing: obj.easing, |
|
listeners: { |
|
beforeanimate: { |
|
fn: beforeAnim |
|
} |
|
} |
|
}); |
|
return me; |
|
}, |
|
|
|
/** |
|
* 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; |
|
}, |
|
|
|
//<feature legacyBrowser> |
|
// private |
|
// used to ensure the mouseup event is captured if it occurs outside of the |
|
// window in IE9m. The only reason this method exists, (vs just calling |
|
// el.dom.setCapture() directly) is so that we can override it to emptyFn |
|
// during testing because setCapture() can wreak havoc on emulated mouse events |
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms646262(v=vs.85).aspx |
|
setCapture: function() { |
|
var dom = this.dom; |
|
if (Ext.isIE9m && dom.setCapture) { |
|
dom.setCapture(); |
|
} |
|
}, |
|
//</feature> |
|
|
|
/** |
|
* Sets the CSS display property. Uses originalDisplay if the specified value is a |
|
* boolean true. |
|
* @param {Boolean/String} value Boolean value to display the element using its |
|
* default display, or a string to set the display directly. |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setDisplayed: function(value) { |
|
var me = this; |
|
|
|
if (typeof value === "boolean"){ |
|
value = value ? getDisplay(me) : NONE; |
|
} |
|
me.setStyle(DISPLAY, value); |
|
|
|
if (me.shadow || me.shim) { |
|
me.setUnderlaysVisible(value !== NONE); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Set the height of this Element. |
|
* |
|
* // change the height to 200px and animate with default configuration |
|
* Ext.fly('elementId').setHeight(200, true); |
|
* |
|
* // change the height to 150px and animate with a custom configuration |
|
* Ext.fly('elId').setHeight(150, { |
|
* duration : 500, // animation will have a duration of .5 seconds |
|
* // will change the content to "finished" |
|
* callback: function(){ this.{@link #setHtml}("finished"); } |
|
* }); |
|
* |
|
* @param {Number/String} height The new height. This may be one of: |
|
* |
|
* - A Number specifying the new height in pixels. |
|
* - A String used to set the CSS height style. Animation may **not** be used. |
|
* |
|
* @param {Boolean/Object} [animate] a standard Element animation config object or `true` for |
|
* the default animation (`{duration: 350, easing: 'ease-in'}`) |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setHeight: function(height, animate) { |
|
var me = this; |
|
|
|
if (!animate || !me.anim) { |
|
me.callParent(arguments); |
|
} |
|
else { |
|
if (!Ext.isObject(animate)) { |
|
animate = {}; |
|
} |
|
me.animate(Ext.applyIf({ |
|
to: { |
|
height: height |
|
} |
|
}, animate)); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Removes "vertical" state from this element (reverses everything done |
|
* by {@link #setVertical}). |
|
* @private |
|
*/ |
|
setHorizontal: function() { |
|
var me = this, |
|
cls = me.verticalCls; |
|
|
|
delete me.vertical; |
|
if (cls) { |
|
delete me.verticalCls; |
|
me.removeCls(cls); |
|
} |
|
|
|
// delete the inverted methods and revert to inheriting from the prototype |
|
delete me.setWidth; |
|
delete me.setHeight; |
|
if (!Ext.isIE8) { |
|
delete me.getWidth; |
|
delete me.getHeight; |
|
} |
|
|
|
// revert to inheriting styleHooks from the prototype |
|
delete me.styleHooks; |
|
}, |
|
|
|
/** |
|
* Updates the *text* value of this element. |
|
* Replaces the content of this element with a *single text node* containing the passed text. |
|
* @param {String} text The text to display in this Element. |
|
*/ |
|
updateText: function(text) { |
|
var me = this, |
|
dom, |
|
textNode; |
|
|
|
if (dom) { |
|
textNode = dom.firstChild; |
|
if (!textNode || (textNode.nodeType !== 3 || textNode.nextSibling)) { |
|
textNode = DOC.createTextNode(); |
|
me.empty(); |
|
dom.appendChild(textNode); |
|
} |
|
if (text) { |
|
textNode.data = text; |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Updates the innerHTML of this element, optionally searching for and processing scripts. |
|
* @param {String} html The new HTML |
|
* @param {Boolean} [loadScripts] True to look for and process scripts (defaults to false) |
|
* @param {Function} [callback] For async script loading you can be notified when the update completes |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setHtml: function(html, loadScripts, callback) { |
|
var me = this, |
|
id, |
|
dom, |
|
interval; |
|
|
|
if (!me.dom) { |
|
return me; |
|
} |
|
html = html || ''; |
|
dom = me.dom; |
|
|
|
if (loadScripts !== true) { |
|
dom.innerHTML = html; |
|
Ext.callback(callback, me); |
|
return me; |
|
} |
|
|
|
id = Ext.id(); |
|
html += '<span id="' + id + '" role="presentation"></span>'; |
|
|
|
interval = Ext.interval(function() { |
|
var hd, |
|
match, |
|
attrs, |
|
srcMatch, |
|
typeMatch, |
|
el, |
|
s; |
|
if (!(el = DOC.getElementById(id))) { |
|
return false; |
|
} |
|
clearInterval(interval); |
|
Ext.removeNode(el); |
|
hd = Ext.getHead().dom; |
|
|
|
while ((match = scriptTagRe.exec(html))) { |
|
attrs = match[1]; |
|
srcMatch = attrs ? attrs.match(srcRe) : false; |
|
if (srcMatch && srcMatch[2]) { |
|
s = DOC.createElement("script"); |
|
s.src = srcMatch[2]; |
|
typeMatch = attrs.match(typeRe); |
|
if (typeMatch && typeMatch[2]) { |
|
s.type = typeMatch[2]; |
|
} |
|
hd.appendChild(s); |
|
} else if (match[2] && match[2].length > 0) { |
|
(WIN.execScript || WIN.eval)(match[2]); // jshint ignore:line |
|
} |
|
} |
|
Ext.callback(callback, me); |
|
}, 20); |
|
dom.innerHTML = html.replace(replaceScriptTagRe, ''); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Set the opacity of the element |
|
* @param {Number} opacity The new opacity. 0 = transparent, .5 = 50% visible, 1 = fully visible, etc |
|
* @param {Boolean/Object} [animate] a standard Element animation config object or `true` for |
|
* the default animation (`{duration: 350, easing: 'ease-in'}`) |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setOpacity: function(opacity, animate) { |
|
var me = this; |
|
|
|
if (!me.dom) { |
|
return me; |
|
} |
|
|
|
if (!animate || !me.anim) { |
|
me.setStyle('opacity', opacity); |
|
} |
|
else { |
|
if (typeof animate != 'object') { |
|
animate = { |
|
duration: 350, |
|
easing: 'ease-in' |
|
}; |
|
} |
|
|
|
me.animate(Ext.applyIf({ |
|
to: { |
|
opacity: opacity |
|
} |
|
}, animate)); |
|
} |
|
return me; |
|
}, |
|
|
|
/** |
|
* Set positioning with an object returned by #getPositioning. |
|
* @param {Object} posCfg |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setPositioning: function(pc) { |
|
return this.setStyle(pc); |
|
}, |
|
|
|
/** |
|
* Changes this Element's state to "vertical" (rotated 90 or 270 degrees). |
|
* This involves inverting the getters and setters for height and width, |
|
* and applying hooks for rotating getters and setters for border/margin/padding. |
|
* (getWidth becomes getHeight and vice versa), setStyle and getStyle will |
|
* also return the inverse when height or width are being operated on. |
|
* |
|
* @param {Number} angle the angle of rotation - either 90 or 270 |
|
* @param {String} cls an optional css class that contains the required |
|
* styles for switching the element to vertical orientation. Omit this if |
|
* the element already contains vertical styling. If cls is provided, |
|
* it will be removed from the element when {@link #setHorizontal} is called. |
|
* @private |
|
*/ |
|
setVertical: function(angle, cls) { |
|
var me = this, |
|
proto = Element.prototype; |
|
|
|
me.vertical = true; |
|
if (cls) { |
|
me.addCls(me.verticalCls = cls); |
|
} |
|
|
|
me.setWidth = proto.setHeight; |
|
me.setHeight = proto.setWidth; |
|
if (!Ext.isIE8) { |
|
// In browsers that use CSS3 transforms we must invert getHeight and |
|
// get Width. In IE8 no adjustment is needed because we use |
|
// a BasicImage filter to rotate the element and the element's |
|
// offsetWidth and offsetHeight are automatically inverted. |
|
me.getWidth = proto.getHeight; |
|
me.getHeight = proto.getWidth; |
|
} |
|
|
|
// Switch to using the appropriate vertical style hooks |
|
me.styleHooks = (angle === 270) ? |
|
proto.verticalStyleHooks270 : proto.verticalStyleHooks90; |
|
}, |
|
|
|
/** |
|
* Set the size of this Element. If animation is true, both width and height will be animated concurrently. |
|
* @param {Number/String} width The new width. This may be one of: |
|
* |
|
* - A Number specifying the new width in pixels. |
|
* - A String used to set the CSS width style. Animation may **not** be used. |
|
* - A size object in the format `{width: widthValue, height: heightValue}`. |
|
* |
|
* @param {Number/String} height The new height. This may be one of: |
|
* |
|
* - A Number specifying the new height in pixels. |
|
* - A String used to set the CSS height style. Animation may **not** be used. |
|
* |
|
* @param {Boolean/Object} [animate] a standard Element animation config object or `true` for |
|
* the default animation (`{duration: 350, easing: 'ease-in'}`) |
|
* |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setSize: function(width, height, animate) { |
|
var me = this; |
|
|
|
if (Ext.isObject(width)) { // in case of object from getSize() |
|
animate = height; |
|
height = width.height; |
|
width = width.width; |
|
} |
|
|
|
if (!animate || !me.anim) { |
|
me.dom.style.width = Element.addUnits(width); |
|
me.dom.style.height = Element.addUnits(height); |
|
|
|
if (me.shadow || me.shim) { |
|
me.syncUnderlays(); |
|
} |
|
} |
|
else { |
|
if (animate === true) { |
|
animate = {}; |
|
} |
|
me.animate(Ext.applyIf({ |
|
to: { |
|
width: width, |
|
height: height |
|
} |
|
}, animate)); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use |
|
* the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property. |
|
* @param {Boolean} visible Whether the element is visible |
|
* @param {Boolean/Object} [animate] True for the default animation, or a standard Element animation config object |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setVisible: function(visible, animate) { |
|
var me = this, |
|
dom = me.dom, |
|
visMode = getVisMode(me); |
|
|
|
// hideMode string override |
|
if (typeof animate === 'string') { |
|
switch (animate) { |
|
case DISPLAY: |
|
visMode = Element.DISPLAY; |
|
break; |
|
case VISIBILITY: |
|
visMode = Element.VISIBILITY; |
|
break; |
|
case OFFSETS: |
|
visMode = Element.OFFSETS; |
|
break; |
|
} |
|
me.setVisibilityMode(visMode); |
|
animate = false; |
|
} |
|
|
|
if (!animate || !me.anim) { |
|
if (visMode === Element.DISPLAY) { |
|
return me.setDisplayed(visible); |
|
} else if (visMode === Element.OFFSETS) { |
|
me[visible?'removeCls':'addCls'](OFFSETCLASS); |
|
} else if (visMode === Element.VISIBILITY) { |
|
me.fixDisplay(); |
|
// Show by clearing visibility style. Explicitly setting to "visible" overrides parent visibility setting |
|
dom.style.visibility = visible ? '' : HIDDEN; |
|
} |
|
} else { |
|
// closure for composites |
|
if (visible) { |
|
me.setOpacity(0.01); |
|
me.setVisible(true); |
|
} |
|
if (!Ext.isObject(animate)) { |
|
animate = { |
|
duration: 350, |
|
easing: 'ease-in' |
|
}; |
|
} |
|
me.animate(Ext.applyIf({ |
|
callback: function() { |
|
if (!visible) { |
|
|
|
// Grab the dom again, since the reference may have changed if we use fly |
|
Ext.fly(dom).setVisible(false).setOpacity(1); |
|
} |
|
}, |
|
to: { |
|
opacity: (visible) ? 1 : 0 |
|
} |
|
}, animate)); |
|
} |
|
me.getData()[ISVISIBLE] = visible; |
|
|
|
if (me.shadow || me.shim) { |
|
me.setUnderlaysVisible(visible); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Set the width of this Element. |
|
* |
|
* // change the width to 200px and animate with default configuration |
|
* Ext.fly('elementId').setWidth(200, true); |
|
* |
|
* // change the width to 150px and animate with a custom configuration |
|
* Ext.fly('elId').setWidth(150, { |
|
* duration : 500, // animation will have a duration of .5 seconds |
|
* // will change the content to "finished" |
|
* callback: function(){ this.{@link #setHtml}("finished"); } |
|
* }); |
|
* |
|
* @param {Number/String} width The new width. This may be one of: |
|
* |
|
* - A Number specifying the new width in pixels. |
|
* - A String used to set the CSS width style. Animation may **not** be used. |
|
* |
|
* @param {Boolean/Object} [animate] a standard Element animation config object or `true` for |
|
* the default animation (`{duration: 350, easing: 'ease-in'}`) |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
setWidth: function(width, animate) { |
|
var me = this; |
|
if (!animate || !me.anim) { |
|
me.callParent(arguments); |
|
} |
|
else { |
|
if (!Ext.isObject(animate)) { |
|
animate = {}; |
|
} |
|
me.animate(Ext.applyIf({ |
|
to: { |
|
width: width |
|
} |
|
}, animate)); |
|
} |
|
return me; |
|
}, |
|
|
|
setX: function(x, animate) { |
|
return this.setXY([x, this.getY()], animate); |
|
}, |
|
|
|
setXY: function(xy, animate) { |
|
var me = this; |
|
|
|
if (!animate || !me.anim) { |
|
me.callParent([xy]); |
|
} else { |
|
if (!Ext.isObject(animate)) { |
|
animate = {}; |
|
} |
|
me.animate(Ext.applyIf({ to: { x: xy[0], y: xy[1] } }, animate)); |
|
} |
|
return this; |
|
}, |
|
|
|
setY: function(y, animate) { |
|
return this.setXY([this.getX(), y], animate); |
|
}, |
|
|
|
/** |
|
* Show this element - Uses display mode to determine whether to use "display", |
|
* "visibility", or "offsets". See {@link #setVisible}. |
|
* @param {Boolean/Object} [animate] true for the default animation or a standard |
|
* Element animation config object |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
show: function(animate){ |
|
// hideMode override |
|
if (typeof animate === 'string'){ |
|
this.setVisible(true, animate); |
|
return this; |
|
} |
|
this.setVisible(true, this.anim(animate)); |
|
return this; |
|
}, |
|
|
|
/** |
|
* Slides the element into view. An anchor point can be optionally passed to set the point of origin for the slide |
|
* effect. This function automatically handles wrapping the element with a fixed-size container if needed. See the |
|
* {@link Ext.fx.Anim} class overview for valid anchor point options. Usage: |
|
* |
|
* // default: slide the element in from the top |
|
* el.slideIn(); |
|
* |
|
* // custom: slide the element in from the right with a 2-second duration |
|
* el.slideIn('r', { duration: 2000 }); |
|
* |
|
* // common config options shown with default values |
|
* el.slideIn('t', { |
|
* easing: 'easeOut', |
|
* duration: 500 |
|
* }); |
|
* |
|
* @param {String} anchor (optional) One of the valid {@link Ext.fx.Anim} anchor positions (defaults to top: 't') |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @param {Boolean} options.preserveScroll Set to true if preservation of any descendant elements' |
|
* `scrollTop` values is required. By default the DOM wrapping operation performed by `slideIn` and |
|
* `slideOut` causes the browser to lose all scroll positions. |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
slideIn: function(anchor, obj, slideOut) { |
|
var me = this, |
|
dom = me.dom, |
|
elStyle = dom.style, |
|
beforeAnim, |
|
wrapAnim, |
|
restoreScroll, |
|
wrapDomParentNode; |
|
|
|
anchor = anchor || "t"; |
|
obj = obj || {}; |
|
|
|
beforeAnim = function() { |
|
var animScope = this, |
|
listeners = obj.listeners, |
|
el = Ext.fly(dom, '_anim'), |
|
box, originalStyles, anim, wrap; |
|
|
|
if (!slideOut) { |
|
el.fixDisplay(); |
|
} |
|
|
|
box = el.getBox(); |
|
if ((anchor == 't' || anchor == 'b') && box.height === 0) { |
|
box.height = dom.scrollHeight; |
|
} |
|
else if ((anchor == 'l' || anchor == 'r') && box.width === 0) { |
|
box.width = dom.scrollWidth; |
|
} |
|
|
|
originalStyles = el.getStyle(['width', 'height', 'left', 'right', 'top', 'bottom', 'position', 'z-index'], true); |
|
el.setSize(box.width, box.height); |
|
|
|
// Cache all descendants' scrollTop & scrollLeft values if configured to preserve scroll. |
|
if (obj.preserveScroll) { |
|
restoreScroll = el.cacheScrollValues(); |
|
} |
|
|
|
wrap = el.wrap({ |
|
role: 'presentation', |
|
id: Ext.id() + '-anim-wrap-for-' + el.dom.id, |
|
style: { |
|
visibility: slideOut ? 'visible' : 'hidden' |
|
} |
|
}); |
|
wrapDomParentNode = wrap.dom.parentNode; |
|
wrap.setPositioning(el.getPositioning()); |
|
if (wrap.isStyle('position', 'static')) { |
|
wrap.position('relative'); |
|
} |
|
el.clearPositioning('auto'); |
|
wrap.clip(); |
|
|
|
// The wrap will have reset all descendant scrollTops. Restore them if we cached them. |
|
if (restoreScroll) { |
|
restoreScroll(); |
|
} |
|
|
|
// This element is temporarily positioned absolute within its wrapper. |
|
// Restore to its default, CSS-inherited visibility setting. |
|
// We cannot explicitly poke visibility:visible into its style because that overrides the visibility of the wrap. |
|
el.setStyle({ |
|
visibility: '', |
|
position: 'absolute' |
|
}); |
|
if (slideOut) { |
|
wrap.setSize(box.width, box.height); |
|
} |
|
|
|
switch (anchor) { |
|
case 't': |
|
anim = { |
|
from: { |
|
width: box.width + 'px', |
|
height: '0px' |
|
}, |
|
to: { |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
elStyle.bottom = '0px'; |
|
break; |
|
case 'l': |
|
anim = { |
|
from: { |
|
width: '0px', |
|
height: box.height + 'px' |
|
}, |
|
to: { |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
me.anchorAnimX(anchor); |
|
break; |
|
case 'r': |
|
anim = { |
|
from: { |
|
x: box.x + box.width, |
|
width: '0px', |
|
height: box.height + 'px' |
|
}, |
|
to: { |
|
x: box.x, |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
me.anchorAnimX(anchor); |
|
break; |
|
case 'b': |
|
anim = { |
|
from: { |
|
y: box.y + box.height, |
|
width: box.width + 'px', |
|
height: '0px' |
|
}, |
|
to: { |
|
y: box.y, |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
break; |
|
case 'tl': |
|
anim = { |
|
from: { |
|
x: box.x, |
|
y: box.y, |
|
width: '0px', |
|
height: '0px' |
|
}, |
|
to: { |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
elStyle.bottom = '0px'; |
|
me.anchorAnimX('l'); |
|
break; |
|
case 'bl': |
|
anim = { |
|
from: { |
|
y: box.y + box.height, |
|
width: '0px', |
|
height: '0px' |
|
}, |
|
to: { |
|
y: box.y, |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
me.anchorAnimX('l'); |
|
break; |
|
case 'br': |
|
anim = { |
|
from: { |
|
x: box.x + box.width, |
|
y: box.y + box.height, |
|
width: '0px', |
|
height: '0px' |
|
}, |
|
to: { |
|
x: box.x, |
|
y: box.y, |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
me.anchorAnimX('r'); |
|
break; |
|
case 'tr': |
|
anim = { |
|
from: { |
|
x: box.x + box.width, |
|
width: '0px', |
|
height: '0px' |
|
}, |
|
to: { |
|
x: box.x, |
|
width: box.width + 'px', |
|
height: box.height + 'px' |
|
} |
|
}; |
|
elStyle.bottom = '0px'; |
|
me.anchorAnimX('r'); |
|
break; |
|
} |
|
|
|
wrap.show(); |
|
wrapAnim = Ext.apply({}, obj); |
|
delete wrapAnim.listeners; |
|
wrapAnim = new Ext.fx.Anim(Ext.applyIf(wrapAnim, { |
|
target: wrap, |
|
duration: 500, |
|
easing: 'ease-out', |
|
from: slideOut ? anim.to : anim.from, |
|
to: slideOut ? anim.from : anim.to |
|
})); |
|
|
|
// In the absence of a callback, this listener MUST be added first |
|
wrapAnim.on('afteranimate', function() { |
|
var el = Ext.fly(dom, '_anim'); |
|
|
|
el.setStyle(originalStyles); |
|
if (slideOut) { |
|
if (obj.useDisplay) { |
|
el.setDisplayed(false); |
|
} else { |
|
el.hide(); |
|
} |
|
} |
|
if (wrap.dom) { |
|
if (wrap.dom.parentNode) { |
|
wrap.dom.parentNode.insertBefore(el.dom, wrap.dom); |
|
} else { |
|
wrapDomParentNode.appendChild(el.dom); |
|
} |
|
wrap.destroy(); |
|
} |
|
// The unwrap will have reset all descendant scrollTops. Restore them if we cached them. |
|
if (restoreScroll) { |
|
restoreScroll(); |
|
} |
|
// kill the no-op element animation created below |
|
animScope.end(); |
|
}); |
|
// Add configured listeners after |
|
if (listeners) { |
|
wrapAnim.on(listeners); |
|
} |
|
}; |
|
|
|
me.animate({ |
|
// See "A Note About Wrapped Animations" at the top of this class: |
|
duration: obj.duration ? Math.max(obj.duration, 500) * 2 : 1000, |
|
listeners: { |
|
beforeanimate: beforeAnim // kick off the wrap animation |
|
} |
|
}); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Slides the element out of view. An anchor point can be optionally passed to set the end point for the slide |
|
* effect. When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will |
|
* still take up space in the document. The element must be removed from the DOM using the 'remove' config option if |
|
* desired. This function automatically handles wrapping the element with a fixed-size container if needed. See the |
|
* {@link Ext.fx.Anim} class overview for valid anchor point options. Usage: |
|
* |
|
* // default: slide the element out to the top |
|
* el.slideOut(); |
|
* |
|
* // custom: slide the element out to the right with a 2-second duration |
|
* el.slideOut('r', { duration: 2000 }); |
|
* |
|
* // common config options shown with default values |
|
* el.slideOut('t', { |
|
* easing: 'easeOut', |
|
* duration: 500, |
|
* remove: false, |
|
* useDisplay: false |
|
* }); |
|
* |
|
* @param {String} anchor (optional) One of the valid {@link Ext.fx.Anim} anchor positions (defaults to top: 't') |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
slideOut: function(anchor, o) { |
|
return this.slideIn(anchor, o, true); |
|
}, |
|
|
|
/** |
|
* Stops the specified event(s) from bubbling and optionally prevents the default action |
|
* |
|
* var store = Ext.create('Ext.data.Store', { |
|
* fields: ['name', 'email'], |
|
* data: [{ |
|
* 'name': 'Finn', |
|
* "email": "finn@adventuretime.com" |
|
* }] |
|
* }); |
|
* |
|
* Ext.create('Ext.grid.Panel', { |
|
* title: 'Land of Ooo', |
|
* store: store, |
|
* columns: [{ |
|
* text: 'Name', |
|
* dataIndex: 'name' |
|
* }, { |
|
* text: 'Email <img style="vertical-align:middle;" src="{some-help-image-src}" />', |
|
* dataIndex: 'email', |
|
* flex: 1, |
|
* listeners: { |
|
* render: function(col) { |
|
* // Swallow the click event when the click occurs on the |
|
* // help icon - preventing the sorting of data by that |
|
* // column and instead performing an action specific to |
|
* // the help icon |
|
* var img = col.getEl().down('img'); |
|
* img.swallowEvent(['click', 'mousedown'], true); |
|
* col.on('click', function() { |
|
* // logic to show a help dialog |
|
* console.log('image click handler'); |
|
* }, col); |
|
* } |
|
* } |
|
* }], |
|
* height: 200, |
|
* width: 400, |
|
* renderTo: document.body |
|
* }); |
|
* |
|
* @param {String/String[]} eventName an event / array of events to stop from bubbling |
|
* @param {Boolean} [preventDefault] true to prevent the default action too |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
swallowEvent: function(eventName, preventDefault) { |
|
var me = this, |
|
e, eLen, |
|
fn = function(e) { |
|
e.stopPropagation(); |
|
if (preventDefault) { |
|
e.preventDefault(); |
|
} |
|
}; |
|
|
|
if (Ext.isArray(eventName)) { |
|
eLen = eventName.length; |
|
|
|
for (e = 0; e < eLen; e++) { |
|
me.on(eventName[e], fn); |
|
} |
|
|
|
return me; |
|
} |
|
me.on(eventName, fn); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television). |
|
* When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still |
|
* take up space in the document. The element must be removed from the DOM using the 'remove' config option if |
|
* desired. Usage: |
|
* |
|
* // default |
|
* el.switchOff(); |
|
* |
|
* // all config options shown with default values |
|
* el.switchOff({ |
|
* easing: 'easeIn', |
|
* duration: .3, |
|
* remove: false, |
|
* useDisplay: false |
|
* }); |
|
* |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
switchOff: function(obj) { |
|
var me = this, |
|
dom = me.dom, |
|
beforeAnim; |
|
|
|
obj = Ext.applyIf(obj || {}, { |
|
easing: 'ease-in', |
|
duration: 500, |
|
remove: false, |
|
useDisplay: false |
|
}); |
|
|
|
beforeAnim = function() { |
|
var el = Ext.fly(dom, '_anim'), |
|
animScope = this, |
|
size = el.getSize(), |
|
xy = el.getXY(), |
|
keyframe, position; |
|
|
|
el.clearOpacity(); |
|
el.clip(); |
|
position = el.getPositioning(); |
|
|
|
keyframe = new Ext.fx.Animator({ |
|
target: dom, |
|
duration: obj.duration, |
|
easing: obj.easing, |
|
keyframes: { |
|
33: { |
|
opacity: 0.3 |
|
}, |
|
66: { |
|
height: 1, |
|
y: xy[1] + size.height / 2 |
|
}, |
|
100: { |
|
width: 1, |
|
x: xy[0] + size.width / 2 |
|
} |
|
} |
|
}); |
|
keyframe.on('afteranimate', function() { |
|
var el = Ext.fly(dom, '_anim'); |
|
if (obj.useDisplay) { |
|
el.setDisplayed(false); |
|
} else { |
|
el.hide(); |
|
} |
|
el.clearOpacity(); |
|
el.setPositioning(position); |
|
el.setSize(size); |
|
// kill the no-op element animation created below |
|
animScope.end(); |
|
}); |
|
}; |
|
|
|
me.animate({ |
|
// See "A Note About Wrapped Animations" at the top of this class: |
|
duration: (Math.max(obj.duration, 500) * 2), |
|
listeners: { |
|
beforeanimate: { |
|
fn: beforeAnim |
|
} |
|
}, |
|
callback: obj.callback, |
|
scope: obj.scope |
|
}); |
|
return me; |
|
}, |
|
|
|
/** |
|
* @private. |
|
* Currently used for updating grid cells without modifying DOM structure |
|
* |
|
* Synchronizes content of this Element with the content of the passed element. |
|
* |
|
* Style and CSS class are copied from source into this Element, and contents are synced |
|
* recursively. If a child node is a text node, the textual data is copied. |
|
*/ |
|
syncContent: function(source) { |
|
source = Ext.getDom(source); |
|
var sourceNodes = source.childNodes, |
|
sourceLen = sourceNodes.length, |
|
dest = this.dom, |
|
destNodes = dest.childNodes, |
|
destLen = destNodes.length, |
|
i, destNode, sourceNode, |
|
nodeType, newAttrs, attLen, attName, |
|
elData = dest._extData; |
|
|
|
// Copy top node's attributes across. Use IE-specific method if possible. |
|
// In IE10, there is a problem where the className will not get updated |
|
// in the view, even though the className on the dom element is correct. |
|
// See EXTJSIV-9462 |
|
if (Ext.isIE9m && dest.mergeAttributes) { |
|
dest.mergeAttributes(source, true); |
|
|
|
// EXTJSIV-6803. IE's mergeAttributes appears not to make the source's "src" value available until after the image is ready. |
|
// So programmatically copy any src attribute. |
|
dest.src = source.src; |
|
} else { |
|
newAttrs = source.attributes; |
|
attLen = newAttrs.length; |
|
for (i = 0; i < attLen; i++) { |
|
attName = newAttrs[i].name; |
|
if (attName !== 'id') { |
|
dest.setAttribute(attName, newAttrs[i].value); |
|
} |
|
} |
|
} |
|
|
|
// The element's data is no longer synchronized. We just overwrite it in the DOM |
|
if (elData) { |
|
elData.isSynchronized = false; |
|
} |
|
|
|
// If the number of child nodes does not match, fall back to replacing innerHTML |
|
if (sourceLen !== destLen) { |
|
dest.innerHTML = source.innerHTML; |
|
return; |
|
} |
|
|
|
// Loop through source nodes. |
|
// If there are fewer, we must remove excess |
|
for (i = 0; i < sourceLen; i++) { |
|
sourceNode = sourceNodes[i]; |
|
destNode = destNodes[i]; |
|
nodeType = sourceNode.nodeType; |
|
|
|
// If node structure is out of sync, just drop innerHTML in and return |
|
if (nodeType !== destNode.nodeType || (nodeType === 1 && sourceNode.tagName !== destNode.tagName)) { |
|
dest.innerHTML = source.innerHTML; |
|
return; |
|
} |
|
|
|
// Update text node |
|
if (nodeType === 3) { |
|
destNode.data = sourceNode.data; |
|
} |
|
// Sync element content |
|
else { |
|
if (sourceNode.id && destNode.id !== sourceNode.id) { |
|
destNode.id = sourceNode.id; |
|
} |
|
destNode.style.cssText = sourceNode.style.cssText; |
|
destNode.className = sourceNode.className; |
|
Ext.fly(destNode, '_syncContent').syncContent(sourceNode); |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* Toggles the element's visibility, depending on visibility mode. |
|
* @param {Boolean/Object} [animate] True for the default animation, or a standard Element animation config object |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
toggle: function(animate){ |
|
var me = this; |
|
me.setVisible(!me.isVisible(), me.anim(animate)); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Hides a previously applied mask. |
|
*/ |
|
unmask: function() { |
|
var me = this, |
|
data = me.getData(), |
|
maskEl = data.maskEl, |
|
style; |
|
|
|
if (maskEl) { |
|
style = maskEl.dom.style; |
|
// Remove resource-intensive CSS expressions as soon as they are not required. |
|
if (style.clearExpression) { |
|
style.clearExpression('width'); |
|
style.clearExpression('height'); |
|
} |
|
|
|
if (maskEl) { |
|
maskEl.destroy(); |
|
delete data.maskEl; |
|
} |
|
|
|
me.removeCls([XMASKED, XMASKEDRELATIVE]); |
|
} |
|
|
|
me.restoreChildrenTabbableState(); |
|
|
|
if (me.dom !== DOC.body) { |
|
me.restoreTabbableState(); |
|
} |
|
}, |
|
|
|
/** |
|
* Return clipping (overflow) to original clipping before {@link #clip} was called |
|
* @return {Ext.dom.Element} this |
|
*/ |
|
unclip: function() { |
|
var me = this, |
|
data = me.getData(), |
|
clip; |
|
|
|
if (data[ISCLIPPED]) { |
|
data[ISCLIPPED] = false; |
|
clip = data[ORIGINALCLIP]; |
|
if (clip.o) { |
|
me.setStyle(OVERFLOW, clip.o); |
|
} |
|
if (clip.x) { |
|
me.setStyle(OVERFLOWX, clip.x); |
|
} |
|
if (clip.y) { |
|
me.setStyle(OVERFLOWY, clip.y); |
|
} |
|
} |
|
return me; |
|
}, |
|
|
|
translate: function(x, y, z) { |
|
if (Ext.supports.CssTransforms && !Ext.isIE9m) { |
|
this.callParent(arguments); |
|
} else { |
|
if (x != null) { |
|
this.dom.style.left = x + 'px'; |
|
} |
|
if (y != null) { |
|
this.dom.style.top = y + 'px'; |
|
} |
|
} |
|
}, |
|
|
|
/** |
|
* 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; |
|
}, |
|
|
|
privates: { |
|
/** |
|
* Returns true if this element needs an explicit tabIndex to make it focusable. |
|
* Input fields, text areas, buttons, anchor elements **with an href** etc |
|
* do not need a tabIndex, but structural elements do. |
|
* See http://www.w3.org/TR/html5/editing.html#specially-focusable |
|
* @private |
|
*/ |
|
needsTabIndex: function() { |
|
var dom = this.dom, |
|
nodeName, isFocusable; |
|
|
|
if (dom) { |
|
nodeName = dom.nodeName; |
|
|
|
// Note that the code below is identical to isFocusable(); |
|
// it is intentionally duplicated for performance reasons. |
|
isFocusable = !!Ext.Element.naturallyFocusableTags[nodeName] || |
|
((nodeName === 'A' || nodeName === 'LINK') && !!dom.href) || |
|
dom.getAttribute('tabindex') != null || |
|
dom.contentEditable === 'true'; |
|
|
|
// The result we need is the opposite to what we got |
|
return !isFocusable; |
|
} |
|
}, |
|
|
|
/** |
|
* @private |
|
* The difference between findTabbableElements and selectTabbableElements |
|
* is that find() will include `this` element itself if it is tabbable, while |
|
* select() will only find tabbable children following querySelectorAll() logic. |
|
*/ |
|
findTabbableElements: function(asDom, selector, /* private */ limit, backward) { |
|
asDom = asDom != undefined ? asDom : true; |
|
|
|
var me = this, |
|
selection; |
|
|
|
selection = me.selectTabbableElements(asDom, selector, limit, backward); |
|
|
|
if (me.isTabbable()) { |
|
selection.unshift(asDom ? me.dom : me); |
|
} |
|
|
|
return selection; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
selectTabbableElements: function(asDom, selector, /* private */ limit, backward) { |
|
var selection = [], |
|
nodes, node, el, i, len, to, step, tabIndex; |
|
|
|
asDom = asDom != undefined ? asDom : true; |
|
|
|
nodes = this.dom.querySelectorAll(selector || Ext.Element.tabbableSelector); |
|
len = nodes.length; |
|
|
|
if (!len) { |
|
return selection; |
|
} |
|
|
|
if (backward) { |
|
i = len - 1; |
|
to = 0; |
|
step = -1; |
|
} |
|
else { |
|
i = 0; |
|
to = len - 1; |
|
step = 1; |
|
} |
|
|
|
// We're only interested in the elements that an user can *tab into*, |
|
// not all programmatically focusable elements. So we have to filter |
|
// these out. |
|
for (;; i += step) { |
|
if ((step > 0 && i > to) || (step < 0 && i < to)) { |
|
break; |
|
} |
|
|
|
node = nodes[i]; |
|
|
|
// A node with tabIndex < 0 absolutely can't be tabbable |
|
// so we can save a function call if that is the case. |
|
// Note that we can't use node.tabIndex here because IE |
|
// will return 0 for elements that have no tabindex |
|
// attribute defined, regardless of whether they are |
|
// tabbable or not. |
|
tabIndex = node.getAttribute('tabindex') - 0; // quicker than parseInt |
|
|
|
// tabIndex value may be null for nodes with no tabIndex defined; |
|
// most of those may be naturally tabbable. We don't want to |
|
// check this here, that's isTabbable()'s job and it's not trivial. |
|
// We explicitly check that tabIndex is not negative; the expression |
|
// below is purposeful. |
|
if (!(tabIndex < 0)) { |
|
el = asDom ? Ext.fly(node) : Ext.get(node); |
|
|
|
if (el.isTabbable()) { |
|
selection.push(asDom ? node : el); |
|
} |
|
} |
|
|
|
if (selection.length >= limit) { |
|
return selection; |
|
} |
|
} |
|
|
|
return selection; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
selectFirstTabbableElement: function(asDom, selector) { |
|
var els = this.selectTabbableElements(asDom, selector, 1, false); |
|
|
|
return els[0]; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
selectLastTabbableElement: function(asDom, selector) { |
|
var el = this.selectTabbableElements(true, selector, 1, true)[0]; |
|
|
|
return (asDom !== false) ? el : Ext.get(el); |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
saveTabbableState: function(attribute) { |
|
var tabbableSavedFlagAttribute = Ext.Element.tabbableSavedFlagAttribute, |
|
dom = this.dom; |
|
|
|
// Prevent tabIndex from being munged more than once |
|
if (dom.hasAttribute(tabbableSavedFlagAttribute)) { |
|
return; |
|
} |
|
|
|
attribute = attribute || Ext.Element.tabbableSavedAttribute; |
|
|
|
// tabIndex could be set on both naturally tabbable and generic elements. |
|
// Either way we need to save it to restore later. |
|
if (dom.hasAttribute('tabindex')) { |
|
dom.setAttribute(attribute, dom.getAttribute('tabindex')); |
|
} |
|
|
|
// When no tabIndex is specified, that means a naturally tabbable element. |
|
else { |
|
dom.setAttribute(attribute, 'none'); |
|
} |
|
|
|
// We disable the tabbable state by setting tabIndex to -1. |
|
// The element can still be focused programmatically though. |
|
dom.setAttribute('tabindex', -1); |
|
dom.setAttribute(tabbableSavedFlagAttribute, true); |
|
|
|
return this; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
restoreTabbableState: function(attribute) { |
|
var tabbableSavedFlagAttribute = Ext.Element.tabbableSavedFlagAttribute, |
|
dom = this.dom, |
|
idx; |
|
|
|
attribute = attribute || Ext.Element.tabbableSavedAttribute; |
|
|
|
if (!dom.hasAttribute(tabbableSavedFlagAttribute) || |
|
!dom.hasAttribute(attribute)) { |
|
return; |
|
} |
|
|
|
idx = dom.getAttribute(attribute); |
|
|
|
// That is a naturally tabbable element |
|
if (idx === 'none') { |
|
dom.removeAttribute('tabindex'); |
|
} |
|
else { |
|
dom.setAttribute('tabindex', idx); |
|
} |
|
|
|
dom.removeAttribute(attribute); |
|
dom.removeAttribute(tabbableSavedFlagAttribute); |
|
|
|
return this; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
saveChildrenTabbableState: function(attribute) { |
|
var children, child, i, len; |
|
|
|
if (this.dom) { |
|
children = this.selectTabbableElements(); |
|
|
|
for (i = 0, len = children.length; i < len; i++) { |
|
child = Ext.fly(children[i]); |
|
child.saveTabbableState(attribute); |
|
} |
|
} |
|
|
|
return children; |
|
}, |
|
|
|
/** |
|
* @private |
|
*/ |
|
restoreChildrenTabbableState: function(attribute, children) { |
|
var child, i, len; |
|
|
|
if (this.dom) { |
|
attribute = attribute || Ext.Element.tabbableSavedAttribute; |
|
children = children || this.dom.querySelectorAll('[' + attribute + ']'); |
|
|
|
for (i = 0, len = children.length; i < len; i++) { |
|
child = Ext.fly(children[i]); |
|
child.restoreTabbableState(attribute); |
|
} |
|
} |
|
|
|
return children; |
|
} |
|
}, |
|
|
|
deprecated: { |
|
'4.0': { |
|
methods: { |
|
/** |
|
* Creates a pause before any subsequent queued effects begin. If there are no effects queued after the pause it will |
|
* have no effect. Usage: |
|
* |
|
* el.pause(1); |
|
* |
|
* @deprecated 4.0 Use the `delay` config to {@link #animate} instead. |
|
* @param {Number} seconds The length of time to pause (in seconds) |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
pause: function(ms) { |
|
var me = this; |
|
Ext.fx.Manager.setFxDefaults(me.id, { |
|
delay: ms |
|
}); |
|
return me; |
|
}, |
|
|
|
/** |
|
* Animates the transition of an element's dimensions from a starting height/width to an ending height/width. This |
|
* method is a convenience implementation of {@link #shift}. Usage: |
|
* |
|
* // change height and width to 100x100 pixels |
|
* el.scale(100, 100); |
|
* |
|
* // common config options shown with default values. The height and width will default to |
|
* // the element's existing values if passed as null. |
|
* el.scale( |
|
* [element's width], |
|
* [element's height], { |
|
* easing: 'easeOut', |
|
* duration: 350 |
|
* } |
|
* ); |
|
* |
|
* @deprecated 4.0 Just use {@link #animate} instead. |
|
* @param {Number} width The new width (pass undefined to keep the original width) |
|
* @param {Number} height The new height (pass undefined to keep the original height) |
|
* @param {Object} options (optional) Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
scale: function(w, h, o) { |
|
this.animate(Ext.apply({}, o, { |
|
width: w, |
|
height: h |
|
})); |
|
return this; |
|
}, |
|
|
|
/** |
|
* Animates the transition of any combination of an element's dimensions, xy position and/or opacity. Any of these |
|
* properties not specified in the config object will not be changed. This effect requires that at least one new |
|
* dimension, position or opacity setting must be passed in on the config object in order for the function to have |
|
* any effect. Usage: |
|
* |
|
* // slide the element horizontally to x position 200 while changing the height and opacity |
|
* el.shift({ x: 200, height: 50, opacity: .8 }); |
|
* |
|
* // common config options shown with default values. |
|
* el.shift({ |
|
* width: [element's width], |
|
* height: [element's height], |
|
* x: [element's x position], |
|
* y: [element's y position], |
|
* opacity: [element's opacity], |
|
* easing: 'easeOut', |
|
* duration: 350 |
|
* }); |
|
* |
|
* @deprecated 4.0 Just use {@link #animate} instead. |
|
* @param {Object} options Object literal with any of the {@link Ext.fx.Anim} config options |
|
* @return {Ext.dom.Element} The Element |
|
*/ |
|
shift: function(config) { |
|
this.animate(config); |
|
return this; |
|
} |
|
} |
|
}, |
|
'4.2': { |
|
methods: { |
|
/** |
|
* Sets the position of the element in page coordinates. |
|
* @param {Number} x X value for new position (coordinates are page-based) |
|
* @param {Number} y Y value for new position (coordinates are page-based) |
|
* @param {Boolean/Object} [animate] True for the default animation, or a standard |
|
* Element animation config object |
|
* @return {Ext.dom.Element} this |
|
* @deprecated 4.2.0 Use {@link #setXY} instead. |
|
*/ |
|
moveTo: function(x, y, animate) { |
|
return this.setXY([x, y], animate); |
|
}, |
|
|
|
/** |
|
* Sets the element's position and size in one shot. If animation is true then |
|
* width, height, x and y will be animated concurrently. |
|
* |
|
* @param {Number} x X value for new position (coordinates are page-based) |
|
* @param {Number} y Y value for new position (coordinates are page-based) |
|
* @param {Number/String} width The new width. This may be one of: |
|
* |
|
* - A Number specifying the new width in pixels |
|
* - A String used to set the CSS width style. Animation may **not** be used. |
|
* |
|
* @param {Number/String} height The new height. This may be one of: |
|
* |
|
* - A Number specifying the new height in pixels |
|
* - A String used to set the CSS height style. Animation may **not** be used. |
|
* |
|
* @param {Boolean/Object} [animate] true for the default animation or |
|
* a standard Element animation config object |
|
* |
|
* @return {Ext.dom.Element} this |
|
* @deprecated 4.2.0 Use {@link Ext.util.Positionable#setBox} instead. |
|
*/ |
|
setBounds: function(x, y, width, height, animate) { |
|
return this.setBox({ |
|
x: x, |
|
y: y, |
|
width: width, |
|
height: height |
|
}, animate); |
|
}, |
|
|
|
/** |
|
* Sets the element's left and top positions directly using CSS style |
|
* @param {Number/String} left Number of pixels or CSS string value to |
|
* set as the left CSS property value |
|
* @param {Number/String} top Number of pixels or CSS string value to |
|
* set as the top CSS property value |
|
* @return {Ext.dom.Element} this |
|
* @deprecated 4.2.0 Use {@link #setLocalXY} instead |
|
*/ |
|
setLeftTop: function(left, top) { |
|
var me = this, |
|
style = me.dom.style; |
|
|
|
style.left = Element.addUnits(left); |
|
style.top = Element.addUnits(top); |
|
|
|
if (me.shadow || me.shim) { |
|
me.syncUnderlays(); |
|
} |
|
|
|
return me; |
|
}, |
|
|
|
/** |
|
* Sets the position of the element in page coordinates. |
|
* @param {Number} x X value for new position |
|
* @param {Number} y Y value for new position |
|
* @param {Boolean/Object} [animate] True for the default animation, or a standard |
|
* Element animation config object |
|
* @return {Ext.dom.Element} this |
|
* @deprecated 4.2.0 Use {@link #setXY} instead. |
|
*/ |
|
setLocation: function(x, y, animate) { |
|
return this.setXY([x, y], animate); |
|
} |
|
} |
|
}, |
|
'5.0': { |
|
methods: { |
|
/** |
|
* Returns the value of a namespaced attribute from the element's underlying DOM node. |
|
* @param {String} namespace The namespace in which to look for the attribute |
|
* @param {String} name The attribute name |
|
* @return {String} The attribute value |
|
* @deprecated 5.0.0 Please use {@link #getAttribute} instead. |
|
*/ |
|
getAttributeNS: function(namespace, name) { |
|
return this.getAttribute(name, namespace); |
|
}, |
|
|
|
/** |
|
* Calculates the x, y to center this element on the screen |
|
* @return {Number[]} The x, y values [x, y] |
|
* @deprecated 5.0.0 Use {@link #getAlignToXY} instead. |
|
* el.getAlignToXY(document, 'c-c'); |
|
*/ |
|
getCenterXY: function(){ |
|
return this.getAlignToXY(DOC, 'c-c'); |
|
}, |
|
|
|
/** |
|
* Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders |
|
* when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements |
|
* if a height has not been set using CSS. |
|
* @return {Number} |
|
* @deprecated 5.0.0 use {@link #getHeight} instead |
|
*/ |
|
getComputedHeight: function() { |
|
return Math.max(this.dom.offsetHeight, this.dom.clientHeight) || |
|
parseFloat(this.getStyle(HEIGHT)) || 0; |
|
}, |
|
|
|
/** |
|
* Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders |
|
* when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements |
|
* if a width has not been set using CSS. |
|
* @return {Number} |
|
* @deprecated 5.0.0 use {@link #getWidth} instead. |
|
*/ |
|
getComputedWidth: function() { |
|
return Math.max(this.dom.offsetWidth, this.dom.clientWidth) || |
|
parseFloat(this.getStyle(WIDTH)) || 0; |
|
}, |
|
|
|
/** |
|
* Returns the dimensions of the element available to lay content out in. |
|
* |
|
* getStyleSize utilizes prefers style sizing if present, otherwise it chooses the larger of offsetHeight/clientHeight and |
|
* offsetWidth/clientWidth. To obtain the size excluding scrollbars, use getViewSize. |
|
* |
|
* Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc. |
|
* |
|
* @return {Object} Object describing width and height. |
|
* @return {Number} return.width |
|
* @return {Number} return.height |
|
* @deprecated 5.0.0 Use {@link #getSize} instead. |
|
*/ |
|
getStyleSize: function() { |
|
var me = this, |
|
d = this.dom, |
|
isDoc = (d === DOC || d === DOC.body), |
|
s, |
|
w, h; |
|
|
|
// If the body, use static methods |
|
if (isDoc) { |
|
return { |
|
width : Element.getViewportWidth(), |
|
height : Element.getViewportHeight() |
|
}; |
|
} |
|
|
|
s = me.getStyle(['height', 'width'], true); //seek inline |
|
// Use Styles if they are set |
|
if (s.width && s.width !== 'auto') { |
|
w = parseFloat(s.width); |
|
} |
|
// Use Styles if they are set |
|
if (s.height && s.height !== 'auto') { |
|
h = parseFloat(s.height); |
|
} |
|
// Use getWidth/getHeight if style not set. |
|
return {width: w || me.getWidth(true), height: h || me.getHeight(true)}; |
|
}, |
|
|
|
|
|
/** |
|
* Returns true if this element uses the border-box-sizing model. This method is |
|
* deprecated as of version 5.0 because border-box sizing is forced upon all elements |
|
* via a style sheet rule, and the browsers that do not support border-box (IE6/7 strict |
|
* mode) are no longer supported. |
|
* @deprecated 5.0.0 |
|
* @return {Boolean} |
|
*/ |
|
isBorderBox: function() { |
|
return true; |
|
}, |
|
|
|
/** |
|
* Returns true if display is not "none" |
|
* @return {Boolean} |
|
* @deprecated 5.0.0 use element.isStyle('display', 'none'); |
|
*/ |
|
isDisplayed: function() { |
|
return !this.isStyle('display', 'none'); |
|
}, |
|
|
|
/** |
|
* Checks whether this element can be focused. |
|
* @return {Boolean} True if the element is focusable |
|
* @deprecated 5.0.0 use {@link #isFocusable} instead |
|
*/ |
|
focusable: 'isFocusable' |
|
} |
|
} |
|
} |
|
}; |
|
})(), function() { |
|
var Element = Ext.dom.Element, |
|
proto = Element.prototype, |
|
useDocForId = !Ext.isIE8, |
|
DOC = document, |
|
view = DOC.defaultView, |
|
opacityRe = /alpha\(opacity=(.*)\)/i, |
|
trimRe = /^\s+|\s+$/g, |
|
styleHooks = proto.styleHooks, |
|
supports = Ext.supports, |
|
verticalStyleHooks90, verticalStyleHooks270, edges, k, |
|
edge, borderWidth, getBorderWidth; |
|
|
|
proto._init(Element); |
|
delete proto._init; |
|
|
|
Ext.plainTableCls = Ext.baseCSSPrefix + 'table-plain'; |
|
Ext.plainListCls = Ext.baseCSSPrefix + 'list-plain'; |
|
|
|
// ensure that any methods added by this override are also added to Ext.CompositeElementLite |
|
if (Ext.CompositeElementLite) { |
|
Ext.CompositeElementLite.importElementMethods(); |
|
} |
|
|
|
styleHooks.opacity = { |
|
name: 'opacity', |
|
afterSet: function(dom, value, el) { |
|
var shadow = el.shadow; |
|
if (shadow) { |
|
shadow.setOpacity(value); |
|
} |
|
} |
|
}; |
|
if (!supports.Opacity && Ext.isIE) { |
|
Ext.apply(styleHooks.opacity, { |
|
get: function (dom) { |
|
var filter = dom.style.filter, |
|
match, opacity; |
|
if (filter.match) { |
|
match = filter.match(opacityRe); |
|
if (match) { |
|
opacity = parseFloat(match[1]); |
|
if (!isNaN(opacity)) { |
|
return opacity ? opacity / 100 : 0; |
|
} |
|
} |
|
} |
|
return 1; |
|
}, |
|
set: function (dom, value) { |
|
var style = dom.style, |
|
val = style.filter.replace(opacityRe, '').replace(trimRe, ''); |
|
|
|
style.zoom = 1; // ensure dom.hasLayout |
|
|
|
// value can be a number or '' or null... so treat falsey as no opacity |
|
if (typeof(value) === 'number' && value >= 0 && value < 1) { |
|
value *= 100; |
|
style.filter = val + (val.length ? ' ' : '') + 'alpha(opacity='+value+')'; |
|
} else { |
|
style.filter = val; |
|
} |
|
} |
|
}); |
|
} |
|
|
|
if (!supports.matchesSelector) { |
|
// Match basic tagName.ClassName selector syntax for is implementation |
|
var simpleSelectorRe = /^([a-z]+|\*)?(?:\.([a-z][a-z\-_0-9]*))?$/i, |
|
dashRe = /\-/g, |
|
fragment, |
|
classMatcher = function (tag, cls) { |
|
var classRe = new RegExp('(?:^|\\s+)' +cls.replace(dashRe, '\\-') + '(?:\\s+|$)'); |
|
if (tag && tag !== '*') { |
|
tag = tag.toUpperCase(); |
|
return function (el) { |
|
return el.tagName === tag && classRe.test(el.className); |
|
}; |
|
} |
|
return function (el) { |
|
return classRe.test(el.className); |
|
}; |
|
}, |
|
tagMatcher = function (tag) { |
|
tag = tag.toUpperCase(); |
|
return function (el) { |
|
return el.tagName === tag; |
|
}; |
|
}, |
|
cache = {}; |
|
|
|
proto.matcherCache = cache; |
|
proto.is = function(selector) { |
|
// Empty selector always matches |
|
if (!selector) { |
|
return true; |
|
} |
|
|
|
var dom = this.dom, |
|
cls, match, testFn, root, isOrphan, is, tag; |
|
|
|
// Only Element node types can be matched. |
|
if (dom.nodeType !== 1) { |
|
return false; |
|
} |
|
|
|
if (!(testFn = Ext.isFunction(selector) ? selector : cache[selector])) { |
|
if (!(match = selector.match(simpleSelectorRe))) { |
|
// Not a simple tagName.className selector, do it the hard way |
|
root = dom.parentNode; |
|
|
|
if (!root) { |
|
isOrphan = true; |
|
root = fragment || (fragment = DOC.createDocumentFragment()); |
|
fragment.appendChild(dom); |
|
} |
|
|
|
is = Ext.Array.indexOf(Ext.fly(root, '_is').query(selector), dom) !== -1; |
|
|
|
if (isOrphan) { |
|
fragment.removeChild(dom); |
|
} |
|
return is; |
|
} |
|
|
|
tag = match[1]; |
|
cls = match[2]; |
|
cache[selector] = testFn = cls ? classMatcher(tag, cls) : tagMatcher(tag); |
|
} |
|
|
|
return testFn(dom); |
|
}; |
|
} |
|
|
|
// IE8 needs its own implementation of getStyle because it doesn't support getComputedStyle |
|
if (!view || !view.getComputedStyle) { |
|
proto.getStyle = function (property, inline) { |
|
var me = this, |
|
dom = me.dom, |
|
multiple = typeof property !== 'string', |
|
prop = property, |
|
props = prop, |
|
len = 1, |
|
isInline = inline, |
|
styleHooks = me.styleHooks, |
|
camel, domStyle, values, hook, out, style, i; |
|
|
|
if (multiple) { |
|
values = {}; |
|
prop = props[0]; |
|
i = 0; |
|
if (!(len = props.length)) { |
|
return values; |
|
} |
|
} |
|
|
|
if (!dom || dom.documentElement) { |
|
return values || ''; |
|
} |
|
|
|
domStyle = dom.style; |
|
|
|
if (inline) { |
|
style = domStyle; |
|
} else { |
|
style = dom.currentStyle; |
|
|
|
// fallback to inline style if rendering context not available |
|
if (!style) { |
|
isInline = true; |
|
style = domStyle; |
|
} |
|
} |
|
|
|
do { |
|
hook = styleHooks[prop]; |
|
|
|
if (!hook) { |
|
styleHooks[prop] = hook = { name: Element.normalize(prop) }; |
|
} |
|
|
|
if (hook.get) { |
|
out = hook.get(dom, me, isInline, style); |
|
} else { |
|
camel = hook.name; |
|
out = style[camel]; |
|
} |
|
|
|
if (!multiple) { |
|
return out; |
|
} |
|
|
|
values[prop] = out; |
|
prop = props[++i]; |
|
} while (i < len); |
|
|
|
return values; |
|
}; |
|
} |
|
|
|
// override getStyle for border-*-width |
|
if (Ext.isIE8) { |
|
getBorderWidth = function (dom, el, inline, style) { |
|
if (style[this.styleName] === 'none') { |
|
return '0px'; |
|
} |
|
return style[this.name]; |
|
}; |
|
|
|
edges = ['Top','Right','Bottom','Left']; |
|
k = edges.length; |
|
|
|
while (k--) { |
|
edge = edges[k]; |
|
borderWidth = 'border' + edge + 'Width'; |
|
|
|
styleHooks['border-'+edge.toLowerCase()+'-width'] = styleHooks[borderWidth] = { |
|
name: borderWidth, |
|
styleName: 'border' + edge + 'Style', |
|
get: getBorderWidth |
|
}; |
|
} |
|
} |
|
|
|
Ext.apply(Ext, { |
|
/** |
|
* `true` to automatically uncache orphaned Ext.Elements periodically. If set to |
|
* `false`, the application will be required to clean up orphaned Ext.Elements and |
|
* it's listeners as to not cause memory leakage. |
|
* @member Ext |
|
*/ |
|
enableGarbageCollector: true, |
|
|
|
// In sencha v5 isBorderBox is no longer needed since all supported browsers |
|
// support border-box, but it is hard coded to true for backward compatibility |
|
isBorderBox: true, |
|
|
|
/** |
|
* @property {Boolean} useShims |
|
* @member Ext |
|
* Set to `true` to use a {@link Ext.util.Floating#shim shim} on all floating Components |
|
* and {@link Ext.LoadMask LoadMasks} |
|
*/ |
|
useShims: false, |
|
|
|
/** |
|
* @private |
|
* Returns an HTML div element into which {@link Ext.container.Container#method-remove removed} components |
|
* are placed so that their DOM elements are not garbage collected as detached Dom trees. |
|
* @return {Ext.dom.Element} |
|
* @member Ext |
|
*/ |
|
getDetachedBody: function () { |
|
var detachedEl = Ext.detachedBodyEl; |
|
|
|
if (!detachedEl) { |
|
detachedEl = DOC.createElement('div'); |
|
Ext.detachedBodyEl = detachedEl = new Ext.dom.Fly(detachedEl); |
|
detachedEl.isDetachedBody = true; |
|
} |
|
|
|
return detachedEl; |
|
}, |
|
|
|
getElementById: function (id) { |
|
var el = DOC.getElementById(id), |
|
detachedBodyEl; |
|
|
|
if (!el && (detachedBodyEl = Ext.detachedBodyEl)) { |
|
el = detachedBodyEl.dom.querySelector(Ext.makeIdSelector(id)); |
|
} |
|
|
|
return el; |
|
}, |
|
|
|
/** |
|
* Applies event listeners to elements by selectors when the document is ready. |
|
* The event name is specified with an `@` suffix. |
|
* |
|
* Ext.addBehaviors({ |
|
* // add a listener for click on all anchors in element with id foo |
|
* '#foo a@click': function(e, t){ |
|
* // do something |
|
* }, |
|
* |
|
* // add the same listener to multiple selectors (separated by comma BEFORE the @) |
|
* '#foo a, #bar span.some-class@mouseover': function(){ |
|
* // do something |
|
* } |
|
* }); |
|
* |
|
* @param {Object} obj The list of behaviors to apply |
|
* @member Ext |
|
*/ |
|
addBehaviors: function(o){ |
|
if(!Ext.isReady){ |
|
Ext.onInternalReady(function(){ |
|
Ext.addBehaviors(o); |
|
}); |
|
} else { |
|
var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times |
|
parts, |
|
b, |
|
s; |
|
for (b in o) { |
|
if ((parts = b.split('@'))[1]) { // for Object prototype breakers |
|
s = parts[0]; |
|
if(!cache[s]){ |
|
cache[s] = Ext.fly(document).select(s, true); |
|
} |
|
cache[s].on(parts[1], o[b]); |
|
} |
|
} |
|
cache = null; |
|
} |
|
} |
|
}); |
|
|
|
if (Ext.isIE9m) { |
|
Ext.getElementById = function (id) { |
|
var el = DOC.getElementById(id), |
|
detachedBodyEl; |
|
|
|
if (!el && (detachedBodyEl = Ext.detachedBodyEl)) { |
|
el = detachedBodyEl.dom.all[id]; |
|
} |
|
|
|
return el; |
|
}; |
|
|
|
proto.getById = function (id, asDom) { |
|
var dom = this.dom, |
|
ret = null, |
|
entry, el; |
|
|
|
if (dom) { |
|
// for normal elements getElementById is the best solution, but if the el is |
|
// not part of the document.body, we need to use all[] |
|
el = (useDocForId && DOC.getElementById(id)) || dom.all[id]; |
|
if (el) { |
|
if (asDom) { |
|
ret = el; |
|
} else { |
|
// calling Element.get here is a real hit (2x slower) because it has to |
|
// redetermine that we are giving it a dom el. |
|
entry = Ext.cache[id]; |
|
if (entry) { |
|
if (entry.skipGarbageCollection || !Ext.isGarbage(entry.dom)) { |
|
ret = entry; |
|
} else { |
|
//<debug> |
|
Ext.Error.raise("Stale Element with id '" + el.id + |
|
"' found in Element cache. " + |
|
"Make sure to clean up Element instances using destroy()" ); |
|
//</debug> |
|
entry.destroy(); |
|
} |
|
} |
|
ret = ret || new Ext.Element(el); |
|
} |
|
} |
|
} |
|
|
|
return ret; |
|
}; |
|
} else if (!DOC.querySelector) { |
|
Ext.getDetachedBody = Ext.getBody; |
|
|
|
Ext.getElementById = function (id) { |
|
return DOC.getElementById(id); |
|
}; |
|
|
|
proto.getById = function (id, asDom) { |
|
var dom = DOC.getElementById(id); |
|
return asDom ? dom : (dom ? Ext.get(dom) : null); |
|
}; |
|
} |
|
|
|
if (Ext.isIE && !(Ext.isIE9p && DOC.documentMode >= 9)) { |
|
// Essentially all web browsers (Firefox, Internet Explorer, recent versions of Opera, Safari, Konqueror, and iCab, |
|
// as a non-exhaustive list) return null when the specified attribute does not exist on the specified element. |
|
// The DOM specification says that the correct return value in this case is actually the empty string, and some |
|
// DOM implementations implement this behavior. The implementation of getAttribute in XUL (Gecko) actually follows |
|
// the specification and returns an empty string. Consequently, you should use hasAttribute to check for an attribute's |
|
// existence prior to calling getAttribute() if it is possible that the requested attribute does not exist on the specified element. |
|
// |
|
// https://developer.mozilla.org/en-US/docs/DOM/element.getAttribute |
|
// http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-745549614 |
|
proto.getAttribute = function(name, ns) { |
|
var d = this.dom, |
|
type; |
|
if (ns) { |
|
type = typeof d[ns + ":" + name]; |
|
if (type !== 'undefined' && type !== 'unknown') { |
|
return d[ns + ":" + name] || null; |
|
} |
|
return null; |
|
} |
|
if (name === "for") { |
|
name = "htmlFor"; |
|
} |
|
return d[name] || null; |
|
}; |
|
} |
|
|
|
Ext.onInternalReady(function () { |
|
var transparentRe = /^(?:transparent|(?:rgba[(](?:\s*\d+\s*[,]){3}\s*0\s*[)]))$/i, |
|
bodyCls = [], |
|
//htmlCls = [], |
|
origSetWidth = proto.setWidth, |
|
origSetHeight = proto.setHeight, |
|
origSetSize = proto.setSize, |
|
pxRe = /^\d+(?:\.\d*)?px$/i, |
|
colorStyles, i, name, camel; |
|
|
|
if (supports.FixedTableWidthBug) { |
|
// EXTJSIV-12665 |
|
// https://bugs.webkit.org/show_bug.cgi?id=130239 |
|
// Webkit browsers fail to layout correctly when a form field's width is less |
|
// than the min-width of the body element. The only way to fix it seems to be |
|
// to toggle the display style of the field's element before and after setting |
|
// the width. Note: once the bug has been corrected by toggling the element's |
|
// display, successive calls to setWidth will work without the hack. It's only |
|
// when going from naturally widthed to having an explicit width that the bug |
|
// occurs. |
|
styleHooks.width = { |
|
name: 'width', |
|
set: function(dom, value, el) { |
|
var style = dom.style, |
|
needsFix = el._needsTableWidthFix, |
|
origDisplay = style.display; |
|
|
|
if (needsFix) { |
|
style.display = 'none'; |
|
} |
|
|
|
style.width = value; |
|
|
|
if (needsFix) { |
|
// repaint |
|
dom.scrollWidth; // jshint ignore:line |
|
style.display = origDisplay; |
|
} |
|
} |
|
}; |
|
proto.setWidth = function(width, animate) { |
|
var me = this, |
|
dom = me.dom, |
|
style = dom.style, |
|
needsFix = me._needsTableWidthFix, |
|
origDisplay = style.display; |
|
|
|
if (needsFix && !animate) { |
|
style.display = 'none'; |
|
} |
|
|
|
origSetWidth.call(me, width, animate); |
|
|
|
if (needsFix && !animate) { |
|
// repaint |
|
dom.scrollWidth; // jshint ignore:line |
|
style.display = origDisplay; |
|
} |
|
return me; |
|
}; |
|
proto.setSize = function(width, height, animate) { |
|
var me = this, |
|
dom = me.dom, |
|
style = dom.style, |
|
needsFix = me._needsTableWidthFix, |
|
origDisplay = style.display; |
|
|
|
if (needsFix && !animate) { |
|
style.display = 'none'; |
|
} |
|
|
|
origSetSize.call(me, width, height, animate); |
|
|
|
if (needsFix && !animate) { |
|
// repaint |
|
dom.scrollWidth; // jshint ignore:line |
|
style.display = origDisplay; |
|
} |
|
return me; |
|
}; |
|
} |
|
|
|
//<feature legacyBrowser> |
|
if (Ext.isIE8) { |
|
styleHooks.height = { |
|
name: 'height', |
|
set: function(dom, value, el) { |
|
var component = el.component, |
|
frameInfo, frameBodyStyle; |
|
|
|
if (component && component._syncFrameHeight && this === component.el) { |
|
frameBodyStyle = component.frameBody.dom.style; |
|
if (pxRe.test(value)) { |
|
frameInfo = component.getFrameInfo(); |
|
if (frameInfo) { |
|
frameBodyStyle.height = (parseInt(value, 10) - frameInfo.height) + 'px'; |
|
} |
|
} else if (!value || value === 'auto') { |
|
frameBodyStyle.height = ''; |
|
} |
|
} |
|
|
|
dom.style.height = value; |
|
} |
|
}; |
|
|
|
proto.setHeight = function(height, animate) { |
|
var component = this.component, |
|
frameInfo, frameBodyStyle; |
|
|
|
if (component && component._syncFrameHeight && this === component.el) { |
|
frameBodyStyle = component.frameBody.dom.style; |
|
if (!height || height === 'auto') { |
|
frameBodyStyle.height = ''; |
|
} else { |
|
frameInfo = component.getFrameInfo(); |
|
if (frameInfo) { |
|
frameBodyStyle.height = (height - frameInfo.height) + 'px'; |
|
} |
|
} |
|
} |
|
|
|
return origSetHeight.call(this, height, animate); |
|
}; |
|
|
|
proto.setSize = function(width, height, animate) { |
|
var component = this.component, |
|
frameInfo, frameBodyStyle; |
|
|
|
if (component && component._syncFrameHeight && this === component.el) { |
|
frameBodyStyle = component.frameBody.dom.style; |
|
if (!height || height === 'auto') { |
|
frameBodyStyle.height = ''; |
|
} else { |
|
frameInfo = component.getFrameInfo(); |
|
if (frameInfo) { |
|
frameBodyStyle.height = (height - frameInfo.height) + 'px'; |
|
} |
|
} |
|
} |
|
|
|
return origSetSize.call(this, width, height, animate); |
|
}; |
|
} |
|
//</feature> |
|
|
|
// Element.unselectable relies on this listener to prevent selection in IE. Some other browsers support the event too |
|
// but it is only strictly required for IE. In WebKit this listener causes subtle differences to how the browser handles |
|
// the non-selection, e.g. whether or not the mouse cursor changes when attempting to select text. |
|
Ext.getDoc().on('selectstart', function(ev, dom) { |
|
var selectableCls = Element.selectableCls, |
|
unselectableCls = Element.unselectableCls, |
|
tagName = dom && dom.tagName; |
|
|
|
tagName = tagName && tagName.toLowerCase(); |
|
|
|
// Element.unselectable is not really intended to handle selection within text fields and it is important that |
|
// fields inside menus or panel headers don't inherit the unselectability. In most browsers this is automatic but in |
|
// IE 9 the selectstart event can bubble up from text fields so we have to explicitly handle that case. |
|
if (tagName === 'input' || tagName === 'textarea') { |
|
return; |
|
} |
|
|
|
// Walk up the DOM checking the nodes. This may be 'slow' but selectstart events don't fire very often |
|
while (dom && dom.nodeType === 1 && dom !== DOC.documentElement) { |
|
var el = Ext.fly(dom); |
|
|
|
// If the node has the class x-selectable then stop looking, the text selection is allowed |
|
if (el.hasCls(selectableCls)) { |
|
return; |
|
} |
|
|
|
// If the node has class x-unselectable then the text selection needs to be stopped |
|
if (el.hasCls(unselectableCls)) { |
|
ev.stopEvent(); |
|
return; |
|
} |
|
|
|
dom = dom.parentNode; |
|
} |
|
}); |
|
|
|
function fixTransparent (dom, el, inline, style) { |
|
var value = style[this.name] || ''; |
|
return transparentRe.test(value) ? 'transparent' : value; |
|
} |
|
|
|
/* |
|
* Helper function to create the function that will restore the selection. |
|
*/ |
|
function makeSelectionRestoreFn (activeEl, start, end) { |
|
return function () { |
|
activeEl.selectionStart = start; |
|
activeEl.selectionEnd = end; |
|
}; |
|
} |
|
|
|
/** |
|
* Creates a function to call to clean up problems with the work-around for the |
|
* WebKit RightMargin bug. The work-around is to add "display: 'inline-block'" to |
|
* the element before calling getComputedStyle and then to restore its original |
|
* display value. The problem with this is that it corrupts the selection of an |
|
* INPUT or TEXTAREA element (as in the "I-beam" goes away but the focus remains). |
|
* To cleanup after this, we need to capture the selection of any such element and |
|
* then restore it after we have restored the display style. |
|
* |
|
* @param {HTMLElement} target The top-most element being adjusted. |
|
* @private |
|
*/ |
|
function getRightMarginFixCleaner(target) { |
|
var hasInputBug = supports.DisplayChangeInputSelectionBug, |
|
hasTextAreaBug = supports.DisplayChangeTextAreaSelectionBug, |
|
activeEl, tag, start, end; |
|
|
|
if (hasInputBug || hasTextAreaBug) { |
|
activeEl = Element.getActiveElement(); |
|
tag = activeEl && activeEl.tagName; |
|
|
|
if ((hasTextAreaBug && tag === 'TEXTAREA') || |
|
(hasInputBug && tag === 'INPUT' && activeEl.type === 'text')) { |
|
if (Ext.fly(target).isAncestor(activeEl)) { |
|
start = activeEl.selectionStart; |
|
end = activeEl.selectionEnd; |
|
|
|
if (Ext.isNumber(start) && Ext.isNumber(end)) { // to be safe... |
|
// We don't create the raw closure here inline because that |
|
// will be costly even if we don't want to return it (nested |
|
// function decls and exprs are often instantiated on entry |
|
// regardless of whether execution ever reaches them): |
|
return makeSelectionRestoreFn(activeEl, start, end); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return Ext.emptyFn; // avoid special cases, just return a nop |
|
} |
|
|
|
function fixRightMargin (dom, el, inline, style) { |
|
var result = style.marginRight, |
|
domStyle, display; |
|
|
|
// Ignore cases when the margin is correctly reported as 0, the bug only shows |
|
// numbers larger. |
|
if (result !== '0px') { |
|
domStyle = dom.style; |
|
display = domStyle.display; |
|
domStyle.display = 'inline-block'; |
|
result = (inline ? style : dom.ownerDocument.defaultView.getComputedStyle(dom, null)).marginRight; |
|
domStyle.display = display; |
|
} |
|
|
|
return result; |
|
} |
|
|
|
function fixRightMarginAndInputFocus (dom, el, inline, style) { |
|
var result = style.marginRight, |
|
domStyle, cleaner, display; |
|
|
|
if (result !== '0px') { |
|
domStyle = dom.style; |
|
cleaner = getRightMarginFixCleaner(dom); |
|
display = domStyle.display; |
|
domStyle.display = 'inline-block'; |
|
result = (inline ? style : dom.ownerDocument.defaultView.getComputedStyle(dom, '')).marginRight; |
|
domStyle.display = display; |
|
cleaner(); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
// Fix bug caused by this: https://bugs.webkit.org/show_bug.cgi?id=13343 |
|
if (!supports.RightMargin) { |
|
styleHooks.marginRight = styleHooks['margin-right'] = { |
|
name: 'marginRight', |
|
// TODO - Touch should use conditional compilation here or ensure that the |
|
// underlying Ext.supports flags are set correctly... |
|
get: (supports.DisplayChangeInputSelectionBug || supports.DisplayChangeTextAreaSelectionBug) ? |
|
fixRightMarginAndInputFocus : fixRightMargin |
|
}; |
|
} |
|
|
|
if (!supports.TransparentColor) { |
|
colorStyles = ['background-color', 'border-color', 'color', 'outline-color']; |
|
for (i = colorStyles.length; i--; ) { |
|
name = colorStyles[i]; |
|
camel = Element.normalize(name); |
|
|
|
styleHooks[name] = styleHooks[camel] = { |
|
name: camel, |
|
get: fixTransparent |
|
}; |
|
} |
|
} |
|
|
|
// When elements are rotated 80 or 270 degrees, their border, margin and padding hooks |
|
// need to be rotated as well. |
|
proto.verticalStyleHooks90 = verticalStyleHooks90 = Ext.Object.chain(styleHooks); |
|
proto.verticalStyleHooks270 = verticalStyleHooks270 = Ext.Object.chain(styleHooks); |
|
|
|
verticalStyleHooks90.width = styleHooks.height || { name: 'height' }; |
|
verticalStyleHooks90.height = styleHooks.width || { name: 'width' }; |
|
verticalStyleHooks90['margin-top'] = { name: 'marginLeft' }; |
|
verticalStyleHooks90['margin-right'] = { name: 'marginTop' }; |
|
verticalStyleHooks90['margin-bottom'] = { name: 'marginRight' }; |
|
verticalStyleHooks90['margin-left'] = { name: 'marginBottom' }; |
|
verticalStyleHooks90['padding-top'] = { name: 'paddingLeft' }; |
|
verticalStyleHooks90['padding-right'] = { name: 'paddingTop' }; |
|
verticalStyleHooks90['padding-bottom'] = { name: 'paddingRight' }; |
|
verticalStyleHooks90['padding-left'] = { name: 'paddingBottom' }; |
|
verticalStyleHooks90['border-top'] = { name: 'borderLeft' }; |
|
verticalStyleHooks90['border-right'] = { name: 'borderTop' }; |
|
verticalStyleHooks90['border-bottom'] = { name: 'borderRight' }; |
|
verticalStyleHooks90['border-left'] = { name: 'borderBottom' }; |
|
|
|
verticalStyleHooks270.width = styleHooks.height || { name: 'height' }; |
|
verticalStyleHooks270.height = styleHooks.width || { name: 'width' }; |
|
verticalStyleHooks270['margin-top'] = { name: 'marginRight' }; |
|
verticalStyleHooks270['margin-right'] = { name: 'marginBottom' }; |
|
verticalStyleHooks270['margin-bottom'] = { name: 'marginLeft' }; |
|
verticalStyleHooks270['margin-left'] = { name: 'marginTop' }; |
|
verticalStyleHooks270['padding-top'] = { name: 'paddingRight' }; |
|
verticalStyleHooks270['padding-right'] = { name: 'paddingBottom' }; |
|
verticalStyleHooks270['padding-bottom'] = { name: 'paddingLeft' }; |
|
verticalStyleHooks270['padding-left'] = { name: 'paddingTop' }; |
|
verticalStyleHooks270['border-top'] = { name: 'borderRight' }; |
|
verticalStyleHooks270['border-right'] = { name: 'borderBottom' }; |
|
verticalStyleHooks270['border-bottom'] = { name: 'borderLeft' }; |
|
verticalStyleHooks270['border-left'] = { name: 'borderTop' }; |
|
|
|
/** |
|
* @property {Boolean} scopeCss |
|
* @member Ext |
|
* Set this to true before onReady to prevent any styling from being added to |
|
* the body element. By default a few styles such as font-family, and color |
|
* are added to the body element via a "x-body" class. When this is set to |
|
* `true` the "x-body" class is not added to the body element, but is added |
|
* to the elements of root-level containers instead. |
|
*/ |
|
if (!Ext.scopeCss) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'body'); |
|
} |
|
|
|
if (supports.Touch) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'touch'); |
|
} |
|
|
|
if (Ext.isIE && Ext.isIE9m) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie', |
|
Ext.baseCSSPrefix + 'ie9m'); |
|
|
|
// very often CSS needs to do checks like "IE7+" or "IE6 or 7". To help |
|
// reduce the clutter (since CSS/SCSS cannot do these tests), we add some |
|
// additional classes: |
|
// |
|
// x-ie7p : IE7+ : 7 <= ieVer |
|
// x-ie7m : IE7- : ieVer <= 7 |
|
// x-ie8p : IE8+ : 8 <= ieVer |
|
// x-ie8m : IE8- : ieVer <= 8 |
|
// x-ie9p : IE9+ : 9 <= ieVer |
|
// x-ie78 : IE7 or 8 : 7 <= ieVer <= 8 |
|
// |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie8p'); |
|
|
|
if (Ext.isIE8) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie8'); |
|
} else { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie9', |
|
Ext.baseCSSPrefix + 'ie9p'); |
|
} |
|
|
|
if (Ext.isIE8m) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie8m'); |
|
} |
|
} |
|
|
|
if (Ext.isIE10) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie10'); |
|
} |
|
|
|
if (Ext.isIE11) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'ie11'); |
|
} |
|
|
|
if (Ext.isGecko) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'gecko'); |
|
} |
|
if (Ext.isOpera) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'opera'); |
|
} |
|
if (Ext.isOpera12m) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'opera12m'); |
|
} |
|
if (Ext.isWebKit) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'webkit'); |
|
} |
|
if (Ext.isSafari) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'safari'); |
|
} |
|
if (Ext.isChrome) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'chrome'); |
|
} |
|
if (Ext.isMac) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'mac'); |
|
} |
|
if (Ext.isLinux) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'linux'); |
|
} |
|
if (!supports.CSS3BorderRadius) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'nbr'); |
|
} |
|
if (!supports.CSS3LinearGradient) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'nlg'); |
|
} |
|
if (supports.Touch) { |
|
bodyCls.push(Ext.baseCSSPrefix + 'touch'); |
|
} |
|
//Ext.fly(document.documentElement).addCls(htmlCls); |
|
|
|
Ext.getBody().addCls(bodyCls); |
|
}, null, { priority: 1500 }); // onReady |
|
});
|
|
|