+ var externalLinks = Ext.select("a:external");
+
+ * @markdown
+ */
+ pseudos : {
+ "first-child" : function(c){
+ var r = [], ri = -1, n;
+ for(var i = 0, ci; ci = n = c[i]; i++){
+ while((n = n.previousSibling) && n.nodeType != 1);
+ if(!n){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "last-child" : function(c){
+ var r = [], ri = -1, n;
+ for(var i = 0, ci; ci = n = c[i]; i++){
+ while((n = n.nextSibling) && n.nodeType != 1);
+ if(!n){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "nth-child" : function(c, a) {
+ var r = [], ri = -1,
+ m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
+ f = (m[1] || 1) - 0, l = m[2] - 0;
+ for(var i = 0, n; n = c[i]; i++){
+ var pn = n.parentNode;
+ if (batch != pn._batch) {
+ var j = 0;
+ for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
+ if(cn.nodeType == 1){
+ cn.nodeIndex = ++j;
+ }
+ }
+ pn._batch = batch;
+ }
+ if (f == 1) {
+ if (l == 0 || n.nodeIndex == l){
+ r[++ri] = n;
+ }
+ } else if ((n.nodeIndex + l) % f == 0){
+ r[++ri] = n;
+ }
+ }
+
+ return r;
+ },
+
+ "only-child" : function(c){
+ var r = [], ri = -1;;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(!prev(ci) && !next(ci)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "empty" : function(c){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var cns = ci.childNodes, j = 0, cn, empty = true;
+ while(cn = cns[j]){
+ ++j;
+ if(cn.nodeType == 1 || cn.nodeType == 3){
+ empty = false;
+ break;
+ }
+ }
+ if(empty){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "contains" : function(c, v){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "nodeValue" : function(c, v){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(ci.firstChild && ci.firstChild.nodeValue == v){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "checked" : function(c){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(ci.checked == true){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "not" : function(c, ss){
+ return Ext.DomQuery.filter(c, ss, true);
+ },
+
+ "any" : function(c, selectors){
+ var ss = selectors.split('|'),
+ r = [], ri = -1, s;
+ for(var i = 0, ci; ci = c[i]; i++){
+ for(var j = 0; s = ss[j]; j++){
+ if(Ext.DomQuery.is(ci, s)){
+ r[++ri] = ci;
+ break;
+ }
+ }
+ }
+ return r;
+ },
+
+ "odd" : function(c){
+ return this["nth-child"](c, "odd");
+ },
+
+ "even" : function(c){
+ return this["nth-child"](c, "even");
+ },
+
+ "nth" : function(c, a){
+ return c[a-1] || [];
+ },
+
+ "first" : function(c){
+ return c[0] || [];
+ },
+
+ "last" : function(c){
+ return c[c.length-1] || [];
+ },
+
+ "has" : function(c, ss){
+ var s = Ext.DomQuery.select,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(s(ss, ci).length > 0){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "next" : function(c, ss){
+ var is = Ext.DomQuery.is,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var n = next(ci);
+ if(n && is(n, ss)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "prev" : function(c, ss){
+ var is = Ext.DomQuery.is,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var n = prev(ci);
+ if(n && is(n, ss)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ }
+ }
+ };
+}();
+
+/**
+ * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
+ * @param {String} path The selector/xpath query
+ * @param {Node} root (optional) The start of the query (defaults to document).
+ * @return {Array}
+ * @member Ext
+ * @method query
+ */
+Ext.query = Ext.DomQuery.select;
+
+/**
+ * @class Ext.core.Element
+ * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
+ * <p>All instances of this class inherit the methods of {@link Ext.fx.Anim} making visual effects easily available to all DOM elements.</p>
+ * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
+ * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
+ * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
+ * Usage:<br>
+<pre><code>
+// by id
+var el = Ext.get("my-div");
+
+// by DOM element reference
+var el = Ext.get(myDivElement);
+</code></pre>
+ * <b>Animations</b><br />
+ * <p>When an element is manipulated, by default there is no animation.</p>
+ * <pre><code>
+var el = Ext.get("my-div");
+
+// no animation
+el.setWidth(100);
+ * </code></pre>
+ * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
+ * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
+ * <pre><code>
+// default animation
+el.setWidth(100, true);
+ * </code></pre>
+ *
+ * <p>To configure the effects, an object literal with animation options to use as the Element animation
+ * configuration object can also be specified. Note that the supported Element animation configuration
+ * options are a subset of the {@link Ext.fx.Anim} animation options specific to Fx effects. The supported
+ * Element animation configuration options are:</p>
+<pre>
+Option Default Description
+--------- -------- ---------------------------------------------
+{@link Ext.fx.Anim#duration duration} .35 The duration of the animation in seconds
+{@link Ext.fx.Anim#easing easing} easeOut The easing method
+{@link Ext.fx.Anim#callback callback} none A function to execute when the anim completes
+{@link Ext.fx.Anim#scope scope} this The scope (this) of the callback function
+</pre>
+ *
+ * <pre><code>
+// Element animation options object
+var opt = {
+ {@link Ext.fx.Anim#duration duration}: 1,
+ {@link Ext.fx.Anim#easing easing}: 'elasticIn',
+ {@link Ext.fx.Anim#callback callback}: this.foo,
+ {@link Ext.fx.Anim#scope scope}: this
+};
+// animation with some options set
+el.setWidth(100, opt);
+ * </code></pre>
+ * <p>The Element animation object being used for the animation will be set on the options
+ * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
+ * <pre><code>
+// using the "anim" property to get the Anim object
+if(opt.anim.isAnimated()){
+ opt.anim.stop();
+}
+ * </code></pre>
+ * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
+ * <p><b> Composite (Collections of) Elements</b></p>
+ * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
+ * @constructor Create a new Element directly.
+ * @param {String/HTMLElement} element
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
+ */
+ (function() {
+ var DOC = document,
+ EC = Ext.cache;
+
+ Ext.Element = Ext.core.Element = function(element, forceNew) {
+ var dom = typeof element == "string" ? DOC.getElementById(element) : element,
+ id;
+
+ if (!dom) {
+ return null;
+ }
+
+ id = dom.id;
+
+ if (!forceNew && id && EC[id]) {
+ // element object already exists
+ return EC[id].el;
+ }
+
+ /**
+ * The DOM element
+ * @type HTMLElement
+ */
+ this.dom = dom;
+
+ /**
+ * The DOM element ID
+ * @type String
+ */
+ this.id = id || Ext.id(dom);
+ };
+
+ var DH = Ext.core.DomHelper,
+ El = Ext.core.Element;
+
+
+ El.prototype = {
+ /**
+ * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
+ * @param {Object} o The object with the attributes
+ * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
+ * @return {Ext.core.Element} this
+ */
+ set: function(o, useSet) {
+ var el = this.dom,
+ attr,
+ val;
+ useSet = (useSet !== false) && !!el.setAttribute;
+
+ for (attr in o) {
+ if (o.hasOwnProperty(attr)) {
+ val = o[attr];
+ if (attr == 'style') {
+ DH.applyStyles(el, val);
+ } else if (attr == 'cls') {
+ el.className = val;
+ } else if (useSet) {
+ el.setAttribute(attr, val);
+ } else {
+ el[attr] = val;
+ }
+ }
+ }
+ return this;
+ },
+
+ // Mouse events
+ /**
+ * @event click
+ * Fires when a mouse click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event contextmenu
+ * Fires when a right click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event dblclick
+ * Fires when a mouse double click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mousedown
+ * Fires when a mousedown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseup
+ * Fires when a mouseup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseover
+ * Fires when a mouseover is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mousemove
+ * Fires when a mousemove is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseout
+ * Fires when a mouseout is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseenter
+ * Fires when the mouse enters the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseleave
+ * Fires when the mouse leaves the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ // Keyboard events
+ /**
+ * @event keypress
+ * Fires when a keypress is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keydown
+ * Fires when a keydown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keyup
+ * Fires when a keyup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+
+ // HTML frame/object events
+ /**
+ * @event load
+ * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event unload
+ * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event abort
+ * Fires when an object/image is stopped from loading before completely loaded.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event error
+ * Fires when an object/image/frame cannot be loaded properly.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event resize
+ * Fires when a document view is resized.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event scroll
+ * Fires when a document view is scrolled.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ // Form events
+ /**
+ * @event select
+ * Fires when a user selects some text in a text field, including input and textarea.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event change
+ * Fires when a control loses the input focus and its value has been modified since gaining focus.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event submit
+ * Fires when a form is submitted.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event reset
+ * Fires when a form is reset.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event focus
+ * Fires when an element receives focus either via the pointing device or by tab navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event blur
+ * Fires when an element loses focus either via the pointing device or by tabbing navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ // User Interface events
+ /**
+ * @event DOMFocusIn
+ * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMFocusOut
+ * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMActivate
+ * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ // DOM Mutation events
+ /**
+ * @event DOMSubtreeModified
+ * Where supported. Fires when the subtree is modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInserted
+ * Where supported. Fires when a node has been added as a child of another node.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemoved
+ * Where supported. Fires when a descendant node of the element is removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemovedFromDocument
+ * Where supported. Fires when a node is being removed from a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInsertedIntoDocument
+ * Where supported. Fires when a node is being inserted into a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMAttrModified
+ * Where supported. Fires when an attribute has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMCharacterDataModified
+ * Where supported. Fires when the character data has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ /**
+ * The default unit to append to CSS values where a unit isn't provided (defaults to px).
+ * @type String
+ */
+ defaultUnit: "px",
+
+ /**
+ * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @return {Boolean} True if this element matches the selector, else false
+ */
+ is: function(simpleSelector) {
+ return Ext.DomQuery.is(this.dom, simpleSelector);
+ },
+
+ /**
+ * Tries to focus the element. Any exceptions are caught and ignored.
+ * @param {Number} defer (optional) Milliseconds to defer the focus
+ * @return {Ext.core.Element} this
+ */
+ focus: function(defer,
+ /* private */
+ dom) {
+ var me = this;
+ dom = dom || me.dom;
+ try {
+ if (Number(defer)) {
+ Ext.defer(me.focus, defer, null, [null, dom]);
+ } else {
+ dom.focus();
+ }
+ } catch(e) {}
+ return me;
+ },
+
+ /**
+ * Tries to blur the element. Any exceptions are caught and ignored.
+ * @return {Ext.core.Element} this
+ */
+ blur: function() {
+ try {
+ this.dom.blur();
+ } catch(e) {}
+ return this;
+ },
+
+ /**
+ * Returns the value of the "value" attribute
+ * @param {Boolean} asNumber true to parse the value as a number
+ * @return {String/Number}
+ */
+ getValue: function(asNumber) {
+ var val = this.dom.value;
+ return asNumber ? parseInt(val, 10) : val;
+ },
+
+ /**
+ * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
+ * @param {String} eventName The name of event to handle.
+ * @param {Function} fn The handler function the event invokes. This function is passed
+ * the following parameters:<ul>
+ * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
+ * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
+ * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
+ * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b>.
+ * @param {Object} options (optional) An object containing handler configuration properties.
+ * This may contain any of the following properties:<ul>
+ * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b></div></li>
+ * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
+ * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
+ * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
+ * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
+ * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
+ * <li><b>target</b> Ext.core.Element: <div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
+ * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
+ * <li><b>single</b> Boolean: <div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * </ul><br>
+ * <p>
+ * <b>Combining Options</b><br>
+ * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
+ * addListener. The two are equivalent. Using the options argument, it is possible to combine different
+ * types of listeners:<br>
+ * <br>
+ * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
+ * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
+ * Code:<pre><code>
+el.on('click', this.onClick, this, {
+ single: true,
+ delay: 100,
+ stopEvent : true,
+ forumId: 4
+});</code></pre></p>
+ * <p>
+ * <b>Attaching multiple handlers in 1 call</b><br>
+ * The method also allows for a single argument to be passed which is a config object containing properties
+ * which specify multiple handlers.</p>
+ * <p>
+ * Code:<pre><code>
+el.on({
+ 'click' : {
+ fn: this.onClick,
+ scope: this,
+ delay: 100
+ },
+ 'mouseover' : {
+ fn: this.onMouseOver,
+ scope: this
+ },
+ 'mouseout' : {
+ fn: this.onMouseOut,
+ scope: this
+ }
+});</code></pre>
+ * <p>
+ * Or a shorthand syntax:<br>
+ * Code:<pre><code></p>
+el.on({
+ 'click' : this.onClick,
+ 'mouseover' : this.onMouseOver,
+ 'mouseout' : this.onMouseOut,
+ scope: this
+});
+ * </code></pre></p>
+ * <p><b>delegate</b></p>
+ * <p>This is a configuration option that you can pass along when registering a handler for
+ * an event to assist with event delegation. Event delegation is a technique that is used to
+ * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
+ * for a container element as opposed to each element within a container. By setting this
+ * configuration option to a simple selector, the target element will be filtered to look for
+ * a descendant of the target.
+ * For example:<pre><code>
+// using this markup:
+<div id='elId'>
+ <p id='p1'>paragraph one</p>
+ <p id='p2' class='clickable'>paragraph two</p>
+ <p id='p3'>paragraph three</p>
+</div>
+// utilize event delegation to registering just one handler on the container element:
+el = Ext.get('elId');
+el.on(
+ 'click',
+ function(e,t) {
+ // handle click
+ console.info(t.id); // 'p2'
+ },
+ this,
+ {
+ // filter the target element to be a descendant with the class 'clickable'
+ delegate: '.clickable'
+ }
+);
+ * </code></pre></p>
+ * @return {Ext.core.Element} this
+ */
+ addListener: function(eventName, fn, scope, options) {
+ Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
+ return this;
+ },
+
+ /**
+ * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
+ * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
+ * listener, the same scope must be specified here.
+ * Example:
+ * <pre><code>
+el.removeListener('click', this.handlerFn);
+// or
+el.un('click', this.handlerFn);
+</code></pre>
+ * @param {String} eventName The name of the event from which to remove the handler.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @return {Ext.core.Element} this
+ */
+ removeListener: function(eventName, fn, scope) {
+ Ext.EventManager.un(this.dom, eventName, fn, scope || this);
+ return this;
+ },
+
+ /**
+ * Removes all previous added listeners from this element
+ * @return {Ext.core.Element} this
+ */
+ removeAllListeners: function() {
+ Ext.EventManager.removeAll(this.dom);
+ return this;
+ },
+
+ /**
+ * Recursively removes all previous added listeners from this element and its children
+ * @return {Ext.core.Element} this
+ */
+ purgeAllListeners: function() {
+ Ext.EventManager.purgeElement(this);
+ return this;
+ },
+
+ /**
+ * @private Test if size has a unit, otherwise appends the passed unit string, or the default for this Element.
+ * @param size {Mixed} The size to set
+ * @param units {String} The units to append to a numeric size value
+ */
+ addUnits: function(size, units) {
+
+ // Most common case first: Size is set to a number
+ if (Ext.isNumber(size)) {
+ return size + (units || this.defaultUnit || 'px');
+ }
+
+ // Size set to a value which means "auto"
+ if (size === "" || size == "auto" || size === undefined || size === null) {
+ return size || '';
+ }
+
+ // Otherwise, warn if it's not a valid CSS measurement
+ if (!unitPattern.test(size)) {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn("Warning, size detected as NaN on Element.addUnits.");
+ }
+ return size || '';
+ }
+ return size;
+ },
+
+ /**
+ * Tests various css rules/browsers to determine if this element uses a border box
+ * @return {Boolean}
+ */
+ isBorderBox: function() {
+ return Ext.isBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
+ },
+
+ /**
+ * <p>Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}</p>
+ */
+ remove: function() {
+ var me = this,
+ dom = me.dom;
+
+ if (dom) {
+ delete me.dom;
+ Ext.removeNode(dom);
+ }
+ },
+
+ /**
+ * 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 (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
+ * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
+ * @return {Ext.core.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;
+ },
+
+ /**
+ * Returns true if this element is an ancestor of the passed element
+ * @param {HTMLElement/String} el The element to check
+ * @return {Boolean} True if this element is an ancestor of el, else false
+ */
+ contains: function(el) {
+ return ! el ? false: Ext.core.Element.isAncestor(this.dom, el.dom ? el.dom: el);
+ },
+
+ /**
+ * 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
+ */
+ getAttributeNS: function(ns, name) {
+ return this.getAttribute(name, ns);
+ },
+
+ /**
+ * Returns the value of an attribute from the element's underlying DOM node.
+ * @param {String} name The attribute name
+ * @param {String} namespace (optional) The namespace in which to look for the attribute
+ * @return {String} The attribute value
+ */
+ getAttribute: (Ext.isIE && !(Ext.isIE9 && document.documentMode === 9)) ?
+ 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;
+ }: function(name, ns) {
+ var d = this.dom;
+ if (ns) {
+ return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name);
+ }
+ return d.getAttribute(name) || d[name] || null;
+ },
+
+ /**
+ * Update the innerHTML of this element
+ * @param {String} html The new HTML
+ * @return {Ext.core.Element} this
+ */
+ update: function(html) {
+ if (this.dom) {
+ this.dom.innerHTML = html;
+ }
+ return this;
+ }
+ };
+
+ var ep = El.prototype;
+
+ El.addMethods = function(o) {
+ Ext.apply(ep, o);
+ };
+
+ /**
+ * Appends an event handler (shorthand for {@link #addListener}).
+ * @param {String} eventName The name of event to handle.
+ * @param {Function} fn The handler function the event invokes.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
+ * @param {Object} options (optional) An object containing standard {@link #addListener} options
+ * @member Ext.core.Element
+ * @method on
+ */
+ ep.on = ep.addListener;
+
+ /**
+ * Removes an event handler from this element (see {@link #removeListener} for additional notes).
+ * @param {String} eventName The name of the event from which to remove the handler.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @return {Ext.core.Element} this
+ * @member Ext.core.Element
+ * @method un
+ */
+ ep.un = ep.removeListener;
+
+ /**
+ * Removes all previous added listeners from this element
+ * @return {Ext.core.Element} this
+ * @member Ext.core.Element
+ * @method clearListeners
+ */
+ ep.clearListeners = ep.removeAllListeners;
+
+ /**
+ * Removes this element's dom reference. Note that event and cache removal is handled at {@link Ext#removeNode Ext.removeNode}.
+ * Alias to {@link #remove}.
+ * @member Ext.core.Element
+ * @method destroy
+ */
+ ep.destroy = ep.remove;
+
+ /**
+ * true to automatically adjust width and height settings for box-model issues (default to true)
+ */
+ ep.autoBoxAdjust = true;
+
+ // private
+ var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
+ docEl;
+
+ /**
+ * Retrieves Ext.core.Element objects.
+ * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
+ * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
+ * its ID, use {@link Ext.ComponentManager#get}.</p>
+ * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
+ * object was recreated with the same id via AJAX or DOM.</p>
+ * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
+ * @return {Element} The Element object (or null if no matching element was found)
+ * @static
+ * @member Ext.core.Element
+ * @method get
+ */
+ El.get = function(el) {
+ var ex,
+ elm,
+ id;
+ if (!el) {
+ return null;
+ }
+ if (typeof el == "string") {
+ // element id
+ if (! (elm = DOC.getElementById(el))) {
+ return null;
+ }
+ if (EC[el] && EC[el].el) {
+ ex = EC[el].el;
+ ex.dom = elm;
+ } else {
+ ex = El.addToCache(new El(elm));
+ }
+ return ex;
+ } else if (el.tagName) {
+ // dom element
+ if (! (id = el.id)) {
+ id = Ext.id(el);
+ }
+ if (EC[id] && EC[id].el) {
+ ex = EC[id].el;
+ ex.dom = el;
+ } else {
+ ex = El.addToCache(new El(el));
+ }
+ return ex;
+ } else if (el instanceof El) {
+ if (el != docEl) {
+ // refresh dom element in case no longer valid,
+ // catch case where it hasn't been appended
+ // If an el instance is passed, don't pass to getElementById without some kind of id
+ if (Ext.isIE && (el.id == undefined || el.id == '')) {
+ el.dom = el.dom;
+ } else {
+ el.dom = DOC.getElementById(el.id) || el.dom;
+ }
+ }
+ return el;
+ } else if (el.isComposite) {
+ return el;
+ } else if (Ext.isArray(el)) {
+ return El.select(el);
+ } else if (el == DOC) {
+ // create a bogus element object representing the document object
+ if (!docEl) {
+ var f = function() {};
+ f.prototype = El.prototype;
+ docEl = new f();
+ docEl.dom = DOC;
+ }
+ return docEl;
+ }
+ return null;
+ };
+
+ El.addToCache = function(el, id) {
+ if (el) {
+ id = id || el.id;
+ EC[id] = {
+ el: el,
+ data: {},
+ events: {}
+ };
+ }
+ return el;
+ };
+
+ // private method for getting and setting element data
+ El.data = function(el, key, value) {
+ el = El.get(el);
+ if (!el) {
+ return null;
+ }
+ var c = EC[el.id].data;
+ if (arguments.length == 2) {
+ return c[key];
+ } else {
+ return (c[key] = value);
+ }
+ };
+
+ // private
+ // Garbage collection - uncache elements/purge listeners on orphaned elements
+ // so we don't hold a reference and cause the browser to retain them
+ function garbageCollect() {
+ if (!Ext.enableGarbageCollector) {
+ clearInterval(El.collectorThreadId);
+ } else {
+ var eid,
+ el,
+ d,
+ o;
+
+ for (eid in EC) {
+ if (!EC.hasOwnProperty(eid)) {
+ continue;
+ }
+ o = EC[eid];
+ if (o.skipGarbageCollection) {
+ continue;
+ }
+ el = o.el;
+ d = el.dom;
+ // -------------------------------------------------------
+ // Determining what is garbage:
+ // -------------------------------------------------------
+ // !d
+ // dom node is null, definitely garbage
+ // -------------------------------------------------------
+ // !d.parentNode
+ // no parentNode == direct orphan, definitely garbage
+ // -------------------------------------------------------
+ // !d.offsetParent && !document.getElementById(eid)
+ // display none elements have no offsetParent so we will
+ // also try to look it up by it's id. However, check
+ // offsetParent first so we don't do unneeded lookups.
+ // This enables collection of elements that are not orphans
+ // directly, but somewhere up the line they have an orphan
+ // parent.
+ // -------------------------------------------------------
+ if (!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))) {
+ if (d && Ext.enableListenerCollection) {
+ Ext.EventManager.removeAll(d);
+ }
+ delete EC[eid];
+ }
+ }
+ // Cleanup IE Object leaks
+ if (Ext.isIE) {
+ var t = {};
+ for (eid in EC) {
+ if (!EC.hasOwnProperty(eid)) {
+ continue;
+ }
+ t[eid] = EC[eid];
+ }
+ EC = Ext.cache = t;
+ }
+ }
+ }
+ El.collectorThreadId = setInterval(garbageCollect, 30000);
+
+ var flyFn = function() {};
+ flyFn.prototype = El.prototype;
+
+ // dom is optional
+ El.Flyweight = function(dom) {
+ this.dom = dom;
+ };
+
+ El.Flyweight.prototype = new flyFn();
+ El.Flyweight.prototype.isFlyweight = true;
+ El._flyweights = {};
+
+ /**
+ * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
+ * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
+ * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
+ * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
+ * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
+ * @param {String/HTMLElement} el The dom node or id
+ * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
+ * (e.g. internally Ext uses "_global")
+ * @return {Element} The shared Element object (or null if no matching element was found)
+ * @member Ext.core.Element
+ * @method fly
+ */
+ El.fly = function(el, named) {
+ var ret = null;
+ named = named || '_global';
+ el = Ext.getDom(el);
+ if (el) {
+ (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
+ ret = El._flyweights[named];
+ }
+ return ret;
+ };
+
+ /**
+ * Retrieves Ext.core.Element objects.
+ * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
+ * retrieves Ext.core.Element objects which encapsulate DOM elements. To retrieve a Component by
+ * its ID, use {@link Ext.ComponentManager#get}.</p>
+ * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
+ * object was recreated with the same id via AJAX or DOM.</p>
+ * Shorthand of {@link Ext.core.Element#get}
+ * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
+ * @return {Element} The Element object (or null if no matching element was found)
+ * @member Ext
+ * @method get
+ */
+ Ext.get = El.get;
+
+ /**
+ * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
+ * the dom node can be overwritten by other code. Shorthand of {@link Ext.core.Element#fly}</p>
+ * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
+ * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get Ext.get}
+ * will be more appropriate to take advantage of the caching provided by the Ext.core.Element class.</p>
+ * @param {String/HTMLElement} el The dom node or id
+ * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
+ * (e.g. internally Ext uses "_global")
+ * @return {Element} The shared Element object (or null if no matching element was found)
+ * @member Ext
+ * @method fly
+ */
+ Ext.fly = El.fly;
+
+ // speedy lookup for elements never to box adjust
+ var noBoxAdjust = Ext.isStrict ? {
+ select: 1
+ }: {
+ input: 1,
+ select: 1,
+ textarea: 1
+ };
+ if (Ext.isIE || Ext.isGecko) {
+ noBoxAdjust['button'] = 1;
+ }
+})();
+
+/**
+ * @class Ext.core.Element
+ */
+Ext.core.Element.addMethods({
+ /**
+ * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
+ * @return {HTMLElement} The matching DOM node (or null if no match was found)
+ */
+ findParent : function(simpleSelector, maxDepth, returnEl) {
+ var p = this.dom,
+ b = document.body,
+ depth = 0,
+ stopEl;
+
+ maxDepth = maxDepth || 50;
+ if (isNaN(maxDepth)) {
+ stopEl = Ext.getDom(maxDepth);
+ maxDepth = Number.MAX_VALUE;
+ }
+ while (p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl) {
+ if (Ext.DomQuery.is(p, simpleSelector)) {
+ return returnEl ? Ext.get(p) : p;
+ }
+ depth++;
+ p = p.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to
+ search as a number or element (defaults to 10 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
+ * @return {HTMLElement} The matching DOM node (or null if no match was found)
+ */
+ findParentNode : function(simpleSelector, maxDepth, returnEl) {
+ var p = Ext.fly(this.dom.parentNode, '_internal');
+ return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
+ },
+
+ /**
+ * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
+ * This is a shortcut for findParentNode() that always returns an Ext.core.Element.
+ * @param {String} selector The simple selector to test
+ * @param {Number/Mixed} maxDepth (optional) The max depth to
+ search as a number or element (defaults to 10 || document.body)
+ * @return {Ext.core.Element} The matching DOM node (or null if no match was found)
+ */
+ up : function(simpleSelector, maxDepth) {
+ return this.findParentNode(simpleSelector, maxDepth, true);
+ },
+
+ /**
+ * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @return {CompositeElement/CompositeElement} The composite element
+ */
+ select : function(selector) {
+ return Ext.core.Element.select(selector, false, this.dom);
+ },
+
+ /**
+ * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @return {Array} An array of the matched nodes
+ */
+ query : function(selector) {
+ return Ext.DomQuery.select(selector, this.dom);
+ },
+
+ /**
+ * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
+ * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
+ */
+ down : function(selector, returnDom) {
+ var n = Ext.DomQuery.selectNode(selector, this.dom);
+ return returnDom ? n : Ext.get(n);
+ },
+
+ /**
+ * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
+ * @param {String} selector The CSS selector
+ * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.core.Element (defaults to false)
+ * @return {HTMLElement/Ext.core.Element} The child Ext.core.Element (or DOM node if returnDom = true)
+ */
+ child : function(selector, returnDom) {
+ var node,
+ me = this,
+ id;
+ id = Ext.get(me).id;
+ // Escape . or :
+ id = id.replace(/[\.:]/g, "\\$0");
+ node = Ext.DomQuery.selectNode('#' + id + " > " + selector, me.dom);
+ return returnDom ? node : Ext.get(node);
+ },
+
+ /**
+ * Gets the parent node for this element, optionally chaining up trying to match a selector
+ * @param {String} selector (optional) Find a parent node that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
+ * @return {Ext.core.Element/HTMLElement} The parent node or null
+ */
+ parent : function(selector, returnDom) {
+ return this.matchNode('parentNode', 'parentNode', selector, returnDom);
+ },
+
+ /**
+ * Gets the next sibling, skipping text nodes
+ * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
+ * @return {Ext.core.Element/HTMLElement} The next sibling or null
+ */
+ next : function(selector, returnDom) {
+ return this.matchNode('nextSibling', 'nextSibling', selector, returnDom);
+ },
+
+ /**
+ * Gets the previous sibling, skipping text nodes
+ * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
+ * @return {Ext.core.Element/HTMLElement} The previous sibling or null
+ */
+ prev : function(selector, returnDom) {
+ return this.matchNode('previousSibling', 'previousSibling', selector, returnDom);
+ },
+
+
+ /**
+ * Gets the first child, skipping text nodes
+ * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
+ * @return {Ext.core.Element/HTMLElement} The first child or null
+ */
+ first : function(selector, returnDom) {
+ return this.matchNode('nextSibling', 'firstChild', selector, returnDom);
+ },
+
+ /**
+ * Gets the last child, skipping text nodes
+ * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
+ * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.core.Element
+ * @return {Ext.core.Element/HTMLElement} The last child or null
+ */
+ last : function(selector, returnDom) {
+ return this.matchNode('previousSibling', 'lastChild', selector, returnDom);
+ },
+
+ matchNode : function(dir, start, selector, returnDom) {
+ if (!this.dom) {
+ return null;
+ }
+
+ var n = this.dom[start];
+ while (n) {
+ if (n.nodeType == 1 && (!selector || Ext.DomQuery.is(n, selector))) {
+ return !returnDom ? Ext.get(n) : n;
+ }
+ n = n[dir];
+ }
+ return null;
+ }
+});
+
+/**
+ * @class Ext.core.Element
+ */
+Ext.core.Element.addMethods({
+ /**
+ * Appends the passed element(s) to this element
+ * @param {String/HTMLElement/Array/Element/CompositeElement} el
+ * @return {Ext.core.Element} this
+ */
+ appendChild : function(el) {
+ return Ext.get(el).appendTo(this);
+ },
+
+ /**
+ * Appends this element to the passed element
+ * @param {Mixed} el The new parent element
+ * @return {Ext.core.Element} this
+ */
+ appendTo : function(el) {
+ Ext.getDom(el).appendChild(this.dom);
+ return this;
+ },
+
+ /**
+ * Inserts this element before the passed element in the DOM
+ * @param {Mixed} el The element before which this element will be inserted
+ * @return {Ext.core.Element} this
+ */
+ insertBefore : function(el) {
+ el = Ext.getDom(el);
+ el.parentNode.insertBefore(this.dom, el);
+ return this;
+ },
+
+ /**
+ * Inserts this element after the passed element in the DOM
+ * @param {Mixed} el The element to insert after
+ * @return {Ext.core.Element} this
+ */
+ insertAfter : function(el) {
+ el = Ext.getDom(el);
+ el.parentNode.insertBefore(this.dom, el.nextSibling);
+ return this;
+ },
+
+ /**
+ * Inserts (or creates) an element (or DomHelper config) as the first child of this element
+ * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
+ * @return {Ext.core.Element} The new child
+ */
+ insertFirst : function(el, returnDom) {
+ el = el || {};
+ if (el.nodeType || el.dom || typeof el == 'string') { // element
+ el = Ext.getDom(el);
+ this.dom.insertBefore(el, this.dom.firstChild);
+ return !returnDom ? Ext.get(el) : el;
+ }
+ else { // dh config
+ return this.createChild(el, this.dom.firstChild, returnDom);
+ }
+ },
+
+ /**
+ * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
+ * @param {Mixed/Object/Array} el The id, element to insert or a DomHelper config to create and insert *or* an array of any of those.
+ * @param {String} where (optional) 'before' or 'after' defaults to before
+ * @param {Boolean} returnDom (optional) True to return the .;ll;l,raw DOM element instead of Ext.core.Element
+ * @return {Ext.core.Element} The inserted Element. If an array is passed, the last inserted element is returned.
+ */
+ insertSibling: function(el, where, returnDom){
+ var me = this, rt,
+ isAfter = (where || 'before').toLowerCase() == 'after',
+ insertEl;
+
+ if(Ext.isArray(el)){
+ insertEl = me;
+ Ext.each(el, function(e) {
+ rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
+ if(isAfter){
+ insertEl = rt;
+ }
+ });
+ return rt;
+ }
+
+ el = el || {};
+
+ if(el.nodeType || el.dom){
+ rt = me.dom.parentNode.insertBefore(Ext.getDom(el), isAfter ? me.dom.nextSibling : me.dom);
+ if (!returnDom) {
+ rt = Ext.get(rt);
+ }
+ }else{
+ if (isAfter && !me.dom.nextSibling) {
+ rt = Ext.core.DomHelper.append(me.dom.parentNode, el, !returnDom);
+ } else {
+ rt = Ext.core.DomHelper[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
+ }
+ }
+ return rt;
+ },
+
+ /**
+ * Replaces the passed element with this element
+ * @param {Mixed} el The element to replace
+ * @return {Ext.core.Element} this
+ */
+ replace : function(el) {
+ el = Ext.get(el);
+ this.insertBefore(el);
+ el.remove();
+ return this;
+ },
+
+ /**
+ * Replaces this element with the passed element
+ * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
+ * @return {Ext.core.Element} this
+ */
+ replaceWith: function(el){
+ var me = this;
+
+ if(el.nodeType || el.dom || typeof el == 'string'){
+ el = Ext.get(el);
+ me.dom.parentNode.insertBefore(el, me.dom);
+ }else{
+ el = Ext.core.DomHelper.insertBefore(me.dom, el);
+ }
+
+ delete Ext.cache[me.id];
+ Ext.removeNode(me.dom);
+ me.id = Ext.id(me.dom = el);
+ Ext.core.Element.addToCache(me.isFlyweight ? new Ext.core.Element(me.dom) : me);
+ return me;
+ },
+
+ /**
+ * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
+ * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
+ * automatically generated with the specified attributes.
+ * @param {HTMLElement} insertBefore (optional) a child element of this element
+ * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
+ * @return {Ext.core.Element} The new child element
+ */
+ createChild : function(config, insertBefore, returnDom) {
+ config = config || {tag:'div'};
+ if (insertBefore) {
+ return Ext.core.DomHelper.insertBefore(insertBefore, config, returnDom !== true);
+ }
+ else {
+ return Ext.core.DomHelper[!this.dom.firstChild ? 'insertFirst' : 'append'](this.dom, config, returnDom !== true);
+ }
+ },
+
+ /**
+ * Creates and wraps this element with another element
+ * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
+ * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.core.Element
+ * @return {HTMLElement/Element} The newly created wrapper element
+ */
+ wrap : function(config, returnDom) {
+ var newEl = Ext.core.DomHelper.insertBefore(this.dom, config || {tag: "div"}, !returnDom),
+ d = newEl.dom || newEl;
+
+ d.appendChild(this.dom);
+ return newEl;
+ },
+
+ /**
+ * Inserts an html fragment into this element
+ * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
+ * @param {String} html The HTML fragment
+ * @param {Boolean} returnEl (optional) True to return an Ext.core.Element (defaults to false)
+ * @return {HTMLElement/Ext.core.Element} The inserted node (or nearest related if more than 1 inserted)
+ */
+ insertHtml : function(where, html, returnEl) {
+ var el = Ext.core.DomHelper.insertHtml(where, this.dom, html);
+ return returnEl ? Ext.get(el) : el;
+ }
+});
+
+/**
+ * @class Ext.core.Element
+ */
+(function(){
+ Ext.core.Element.boxMarkup = '<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>';
+ // local style camelizing for speed
+ var supports = Ext.supports,
+ view = document.defaultView,
+ opacityRe = /alpha\(opacity=(.*)\)/i,
+ trimRe = /^\s+|\s+$/g,
+ spacesRe = /\s+/,
+ wordsRe = /\w/g,
+ adjustDirect2DTableRe = /table-row|table-.*-group/,
+ INTERNAL = '_internal',
+ PADDING = 'padding',
+ MARGIN = 'margin',
+ BORDER = 'border',
+ LEFT = '-left',
+ RIGHT = '-right',
+ TOP = '-top',
+ BOTTOM = '-bottom',
+ WIDTH = '-width',
+ MATH = Math,
+ HIDDEN = 'hidden',
+ ISCLIPPED = 'isClipped',
+ OVERFLOW = 'overflow',
+ OVERFLOWX = 'overflow-x',
+ OVERFLOWY = 'overflow-y',
+ ORIGINALCLIP = 'originalClip',
+ // special markup used throughout Ext when box wrapping elements
+ borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
+ paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
+ margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
+ data = Ext.core.Element.data;
+
+ Ext.override(Ext.core.Element, {
+
+ /**
+ * TODO: Look at this
+ */
+ // private ==> used by Fx
+ adjustWidth : function(width) {
+ var me = this,
+ isNum = (typeof width == 'number');
+
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ }
+ return (isNum && width < 0) ? 0 : width;
+ },
+
+ // private ==> used by Fx
+ adjustHeight : function(height) {
+ var me = this,
+ isNum = (typeof height == "number");
+
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ return (isNum && height < 0) ? 0 : height;
+ },
+
+
+ /**
+ * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
+ * @param {String/Array} className The CSS classes to add separated by space, or an array of classes
+ * @return {Ext.core.Element} this
+ */
+ addCls : function(className){
+ var me = this,
+ cls = [],
+ space = ((me.dom.className.replace(trimRe, '') == '') ? "" : " "),
+ i, len, v;
+ if (!Ext.isDefined(className)) {
+ return me;
+ }
+ // Separate case is for speed
+ if (!Ext.isArray(className)) {
+ if (typeof className === 'string') {
+ className = className.replace(trimRe, '').split(spacesRe);
+ if (className.length === 1) {
+ className = className[0];
+ if (!me.hasCls(className)) {
+ me.dom.className += space + className;
+ }
+ } else {
+ this.addCls(className);
+ }
+ }
+ } else {
+ for (i = 0, len = className.length; i < len; i++) {
+ v = className[i];
+ if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
+ cls.push(v);
+ }
+ }
+ if (cls.length) {
+ me.dom.className += space + cls.join(" ");
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Removes one or more CSS classes from the element.
+ * @param {String/Array} className The CSS classes to remove separated by space, or an array of classes
+ * @return {Ext.core.Element} this
+ */
+ removeCls : function(className){
+ var me = this,
+ i, idx, len, cls, elClasses;
+ if (!Ext.isDefined(className)) {
+ return me;
+ }
+ if (!Ext.isArray(className)){
+ className = className.replace(trimRe, '').split(spacesRe);
+ }
+ if (me.dom && me.dom.className) {
+ elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
+ for (i = 0, len = className.length; i < len; i++) {
+ cls = className[i];
+ if (typeof cls == 'string') {
+ cls = cls.replace(trimRe, '');
+ idx = Ext.Array.indexOf(elClasses, cls);
+ if (idx != -1) {
+ elClasses.splice(idx, 1);
+ }
+ }
+ }
+ me.dom.className = elClasses.join(" ");
+ }
+ return me;
+ },
+
+ /**
+ * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
+ * @param {String/Array} className The CSS class to add, or an array of classes
+ * @return {Ext.core.Element} this
+ */
+ radioCls : function(className){
+ var cn = this.dom.parentNode.childNodes,
+ v, i, len;
+ className = Ext.isArray(className) ? className : [className];
+ for (i = 0, len = cn.length; i < len; i++) {
+ v = cn[i];
+ if (v && v.nodeType == 1) {
+ Ext.fly(v, '_internal').removeCls(className);
+ }
+ }
+ return this.addCls(className);
+ },
+
+ /**
+ * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
+ * @param {String} className The CSS class to toggle
+ * @return {Ext.core.Element} this
+ */
+ toggleCls : Ext.supports.ClassList ?
+ function(className) {
+ this.dom.classList.toggle(Ext.String.trim(className));
+ return this;
+ } :
+ function(className) {
+ return this.hasCls(className) ? this.removeCls(className) : this.addCls(className);
+ },
+
+ /**
+ * Checks if the specified CSS class exists on this element's DOM node.
+ * @param {String} className The CSS class to check for
+ * @return {Boolean} True if the class exists, else false
+ */
+ hasCls : Ext.supports.ClassList ?
+ function(className) {
+ if (!className) {
+ return false;
+ }
+ className = className.split(spacesRe);
+ var ln = className.length,
+ i = 0;
+ for (; i < ln; i++) {
+ if (className[i] && this.dom.classList.contains(className[i])) {
+ return true;
+ }
+ }
+ return false;
+ } :
+ function(className){
+ return className && (' ' + this.dom.className + ' ').indexOf(' ' + className + ' ') != -1;
+ },
+
+ /**
+ * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
+ * @param {String} oldClassName The CSS class to replace
+ * @param {String} newClassName The replacement CSS class
+ * @return {Ext.core.Element} this
+ */
+ replaceCls : function(oldClassName, newClassName){
+ return this.removeCls(oldClassName).addCls(newClassName);
+ },
+
+ isStyle : function(style, val) {
+ return this.getStyle(style) == val;
+ },
+
+ /**
+ * Normalizes currentStyle and computedStyle.
+ * @param {String} property The style property whose value is returned.
+ * @return {String} The current value of the style property for this element.
+ */
+ getStyle : function(){
+ return view && view.getComputedStyle ?
+ function(prop){
+ var el = this.dom,
+ v, cs, out, display;
+
+ if(el == document){
+ return null;
+ }
+ prop = Ext.core.Element.normalize(prop);
+ out = (v = el.style[prop]) ? v :
+ (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
+
+ // Ignore cases when the margin is correctly reported as 0, the bug only shows
+ // numbers larger.
+ if(prop == 'marginRight' && out != '0px' && !supports.RightMargin){
+ display = this.getStyle('display');
+ el.style.display = 'inline-block';
+ out = view.getComputedStyle(el, '').marginRight;
+ el.style.display = display;
+ }
+
+ if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.TransparentColor){
+ out = 'transparent';
+ }
+ return out;
+ } :
+ function(prop){
+ var el = this.dom,
+ m, cs;
+
+ if (el == document) {
+ return null;
+ }
+
+ if (prop == 'opacity') {
+ if (el.style.filter.match) {
+ m = el.style.filter.match(opacityRe);
+ if(m){
+ var fv = parseFloat(m[1]);
+ if(!isNaN(fv)){
+ return fv ? fv / 100 : 0;
+ }
+ }
+ }
+ return 1;
+ }
+ prop = Ext.core.Element.normalize(prop);
+ return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
+ };
+ }(),
+
+ /**
+ * 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 (optional) defaults to #. Use an empty string when working with
+ * color anims.
+ */
+ getColor : function(attr, defaultValue, prefix){
+ var v = this.getStyle(attr),
+ color = prefix || prefix === '' ? prefix : '#',
+ h;
+
+ if(!v || (/transparent|inherit/.test(v))) {
+ return defaultValue;
+ }
+ if(/^r/.test(v)){
+ Ext.each(v.slice(4, v.length -1).split(','), function(s){
+ h = parseInt(s, 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);
+ },
+
+ /**
+ * Wrapper for setting style properties, also takes single object parameter of multiple styles.
+ * @param {String/Object} property The style property to be set, or an object of multiple styles.
+ * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
+ * @return {Ext.core.Element} this
+ */
+ setStyle : function(prop, value){
+ var me = this,
+ tmp, style;
+
+ if (!me.dom) {
+ return me;
+ }
+
+ if (!Ext.isObject(prop)) {
+ tmp = {};
+ tmp[prop] = value;
+ prop = tmp;
+ }
+ for (style in prop) {
+ if (prop.hasOwnProperty(style)) {
+ value = Ext.value(prop[style], '');
+ if (style == 'opacity') {
+ me.setOpacity(value);
+ }
+ else {
+ me.dom.style[Ext.core.Element.normalize(style)] = value;
+ }
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Set the opacity of the element
+ * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
+ * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
+ * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
+ * @return {Ext.core.Element} this
+ */
+ setOpacity: function(opacity, animate) {
+ var me = this,
+ dom = me.dom,
+ val,
+ style;
+
+ if (!me.dom) {
+ return me;
+ }
+
+ style = me.dom.style;
+
+ if (!animate || !me.anim) {
+ if (!Ext.supports.Opacity) {
+ opacity = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')': '';
+ val = style.filter.replace(opacityRe, '').replace(trimRe, '');
+
+ style.zoom = 1;
+ style.filter = val + (val.length > 0 ? ' ': '') + opacity;
+ }
+ else {
+ style.opacity = opacity;
+ }
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {
+ duration: 350,
+ easing: 'ease-in'
+ };
+ }
+ me.animate(Ext.applyIf({
+ to: {
+ opacity: opacity
+ }
+ },
+ animate));
+ }
+ return me;
+ },
+
+
+ /**
+ * Clears any opacity settings from this element. Required in some cases for IE.
+ * @return {Ext.core.Element} this
+ */
+ clearOpacity : function(){
+ var style = this.dom.style;
+ if(!Ext.supports.Opacity){
+ if(!Ext.isEmpty(style.filter)){
+ style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
+ }
+ }else{
+ style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
+ }
+ return this;
+ },
+
+ /**
+ * @private
+ * Returns 1 if the browser returns the subpixel dimension rounded to the lowest pixel.
+ * @return {Number} 0 or 1
+ */
+ adjustDirect2DDimension: function(dimension) {
+ var me = this,
+ dom = me.dom,
+ display = me.getStyle('display'),
+ inlineDisplay = dom.style['display'],
+ inlinePosition = dom.style['position'],
+ originIndex = dimension === 'width' ? 0 : 1,
+ floating;
+
+ if (display === 'inline') {
+ dom.style['display'] = 'inline-block';
+ }
+
+ dom.style['position'] = display.match(adjustDirect2DTableRe) ? 'absolute' : 'static';
+
+ // floating will contain digits that appears after the decimal point
+ // if height or width are set to auto we fallback to msTransformOrigin calculation
+ floating = (parseFloat(me.getStyle(dimension)) || parseFloat(dom.currentStyle.msTransformOrigin.split(' ')[originIndex]) * 2) % 1;
+
+ dom.style['position'] = inlinePosition;
+
+ if (display === 'inline') {
+ dom.style['display'] = inlineDisplay;
+ }
+
+ return floating;
+ },
+
+ /**
+ * Returns the offset height of the element
+ * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
+ * @return {Number} The element's height
+ */
+ getHeight: function(contentHeight, preciseHeight) {
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ height, overflow, style, floating;
+
+ // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
+ // We will put the overflow back to it's original value when we are done measuring.
+ if (Ext.isIEQuirks) {
+ style = dom.style;
+ overflow = style.overflow;
+ me.setStyle({ overflow: 'hidden'});
+ }
+
+ height = dom.offsetHeight;
+
+ height = MATH.max(height, hidden ? 0 : dom.clientHeight) || 0;
+
+ // IE9 Direct2D dimension rounding bug
+ if (!hidden && Ext.supports.Direct2DBug) {
+ floating = me.adjustDirect2DDimension('height');
+ if (preciseHeight) {
+ height += floating;
+ }
+ else if (floating > 0 && floating < 0.5) {
+ height++;
+ }
+ }
+
+ if (contentHeight) {
+ height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+
+ if (Ext.isIEQuirks) {
+ me.setStyle({ overflow: overflow});
+ }
+
+ if (height < 0) {
+ height = 0;
+ }
+ return height;
+ },
+
+ /**
+ * Returns the offset width of the element
+ * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
+ * @return {Number} The element's width
+ */
+ getWidth: function(contentWidth, preciseWidth) {
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE && me.isStyle('display', 'none'),
+ rect, width, overflow, style, floating, parentPosition;
+
+ // IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
+ // We will put the overflow back to it's original value when we are done measuring.
+ if (Ext.isIEQuirks) {
+ style = dom.style;
+ overflow = style.overflow;
+ me.setStyle({overflow: 'hidden'});
+ }
+
+ // Fix Opera 10.5x width calculation issues
+ if (Ext.isOpera10_5) {
+ if (dom.parentNode.currentStyle.position === 'relative') {
+ parentPosition = dom.parentNode.style.position;
+ dom.parentNode.style.position = 'static';
+ width = dom.offsetWidth;
+ dom.parentNode.style.position = parentPosition;
+ }
+ width = Math.max(width || 0, dom.offsetWidth);
+
+ // Gecko will in some cases report an offsetWidth that is actually less than the width of the
+ // text contents, because it measures fonts with sub-pixel precision but rounds the calculated
+ // value down. Using getBoundingClientRect instead of offsetWidth allows us to get the precise
+ // subpixel measurements so we can force them to always be rounded up. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=458617
+ } else if (Ext.supports.BoundingClientRect) {
+ rect = dom.getBoundingClientRect();
+ width = rect.right - rect.left;
+ width = preciseWidth ? width : Math.ceil(width);
+ } else {
+ width = dom.offsetWidth;
+ }
+
+ width = MATH.max(width, hidden ? 0 : dom.clientWidth) || 0;
+
+ // IE9 Direct2D dimension rounding bug
+ if (!hidden && Ext.supports.Direct2DBug) {
+ floating = me.adjustDirect2DDimension('width');
+ if (preciseWidth) {
+ width += floating;
+ }
+ else if (floating > 0 && floating < 0.5) {
+ width++;
+ }
+ }
+
+ if (contentWidth) {
+ width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ }
+
+ if (Ext.isIEQuirks) {
+ me.setStyle({ overflow: overflow});
+ }
+
+ if (width < 0) {
+ width = 0;
+ }
+ return width;
+ },
+
+ /**
+ * Set the width of this Element.
+ * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setWidth : function(width, animate){
+ var me = this;
+ width = me.adjustWidth(width);
+ if (!animate || !me.anim) {
+ me.dom.style.width = me.addUnits(width);
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ me.animate(Ext.applyIf({
+ to: {
+ width: width
+ }
+ }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * Set the height of this Element.
+ * <pre><code>
+// 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 : .5, // animation will have a duration of .5 seconds
+ // will change the content to "finished"
+ callback: function(){ this.{@link #update}("finished"); }
+});
+ * </code></pre>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setHeight : function(height, animate){
+ var me = this;
+ height = me.adjustHeight(height);
+ if (!animate || !me.anim) {
+ me.dom.style.height = me.addUnits(height);
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ me.animate(Ext.applyIf({
+ to: {
+ height: height
+ }
+ }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * Gets the width of the border(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
+ * @return {Number} The width of the sides passed added together
+ */
+ getBorderWidth : function(side){
+ return this.addStyles(side, borders);
+ },
+
+ /**
+ * Gets the width of the padding(s) for the specified side(s)
+ * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
+ * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
+ * @return {Number} The padding of the sides passed added together
+ */
+ getPadding : function(side){
+ return this.addStyles(side, paddings);
+ },
+
+ /**
+ * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
+ * @return {Ext.core.Element} this
+ */
+ clip : function(){
+ var me = this,
+ dom = me.dom;
+
+ if(!data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, true);
+ data(dom, ORIGINALCLIP, {
+ o: me.getStyle(OVERFLOW),
+ x: me.getStyle(OVERFLOWX),
+ y: me.getStyle(OVERFLOWY)
+ });
+ me.setStyle(OVERFLOW, HIDDEN);
+ me.setStyle(OVERFLOWX, HIDDEN);
+ me.setStyle(OVERFLOWY, HIDDEN);
+ }
+ return me;
+ },
+
+ /**
+ * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
+ * @return {Ext.core.Element} this
+ */
+ unclip : function(){
+ var me = this,
+ dom = me.dom,
+ clip;
+
+ if(data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, false);
+ clip = data(dom, ORIGINALCLIP);
+ if(o.o){
+ me.setStyle(OVERFLOW, o.o);
+ }
+ if(o.x){
+ me.setStyle(OVERFLOWX, o.x);
+ }
+ if(o.y){
+ me.setStyle(OVERFLOWY, o.y);
+ }
+ }
+ return me;
+ },
+
+ // private
+ addStyles : function(sides, styles){
+ var totalSize = 0,
+ sidesArr = sides.match(wordsRe),
+ i = 0,
+ len = sidesArr.length,
+ side, size;
+ for (; i < len; i++) {
+ side = sidesArr[i];
+ size = side && parseInt(this.getStyle(styles[side]), 10);
+ if (size) {
+ totalSize += MATH.abs(size);
+ }
+ }
+ return totalSize;
+ },
+
+ margins : margins,
+
+ /**
+ * More flexible version of {@link #setStyle} for setting style properties.
+ * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
+ * a function which returns such a specification.
+ * @return {Ext.core.Element} this
+ */
+ applyStyles : function(style){
+ Ext.core.DomHelper.applyStyles(this.dom, style);
+ return this;
+ },
+
+ /**
+ * Returns an object with properties matching the styles requested.
+ * For example, el.getStyles('color', 'font-size', 'width') might return
+ * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
+ * @param {String} style1 A style name
+ * @param {String} style2 A style name
+ * @param {String} etc.
+ * @return {Object} The style object
+ */
+ getStyles : function(){
+ var styles = {},
+ len = arguments.length,
+ i = 0, style;
+
+ for(; i < len; ++i) {
+ style = arguments[i];
+ styles[style] = this.getStyle(style);
+ }
+ return styles;
+ },
+
+ /**
+ * <p>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.</p>
+ * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.button.Button},
+ * {@link Ext.panel.Panel} when <tt>{@link Ext.panel.Panel#frame frame=true}</tt>, {@link Ext.window.Window}). The markup
+ * is of this form:</p>
+ * <pre><code>
+ Ext.core.Element.boxMarkup =
+ '<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>';
+ * </code></pre>
+ * <p>Example usage:</p>
+ * <pre><code>
+ // 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");
+ * </code></pre>
+ * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
+ * (defaults to <tt>'x-box'</tt>). 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.core.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 + "'>" + Ext.String.format(Ext.core.Element.boxMarkup, cls) + "</div>"));
+ Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
+ return el;
+ },
+
+ /**
+ * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
+ * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
+ * </ul></div>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setSize : function(width, height, animate){
+ var me = this;
+ if (Ext.isObject(width)){ // in case of object from getSize()
+ height = width.height;
+ width = width.width;
+ }
+ width = me.adjustWidth(width);
+ height = me.adjustHeight(height);
+ if(!animate || !me.anim){
+ me.dom.style.width = me.addUnits(width);
+ me.dom.style.height = me.addUnits(height);
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ me.animate(Ext.applyIf({
+ to: {
+ width: width,
+ height: height
+ }
+ }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * 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}
+ */
+ getComputedHeight : function(){
+ var me = this,
+ h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
+ if(!h){
+ h = parseFloat(me.getStyle('height')) || 0;
+ if(!me.isBorderBox()){
+ h += me.getFrameWidth('tb');
+ }
+ }
+ return h;
+ },
+
+ /**
+ * 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}
+ */
+ getComputedWidth : function(){
+ var me = this,
+ w = Math.max(me.dom.offsetWidth, me.dom.clientWidth);
+
+ if(!w){
+ w = parseFloat(me.getStyle('width')) || 0;
+ if(!me.isBorderBox()){
+ w += me.getFrameWidth('lr');
+ }
+ }
+ return w;
+ },
+
+ /**
+ * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
+ for more information about the sides.
+ * @param {String} sides
+ * @return {Number}
+ */
+ getFrameWidth : function(sides, onlyContentBox){
+ return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when the mouse is over this element
+ * @param {String} className
+ * @return {Ext.core.Element} this
+ */
+ addClsOnOver : function(className){
+ var dom = this.dom;
+ this.hover(
+ function(){
+ Ext.fly(dom, INTERNAL).addCls(className);
+ },
+ function(){
+ Ext.fly(dom, INTERNAL).removeCls(className);
+ }
+ );
+ return this;
+ },
+
+ /**
+ * Sets up event handlers to add and remove a css class when this element has the focus
+ * @param {String} className
+ * @return {Ext.core.Element} this
+ */
+ addClsOnFocus : function(className){
+ var me = this,
+ dom = me.dom;
+ me.on("focus", function(){
+ Ext.fly(dom, INTERNAL).addCls(className);
+ });
+ me.on("blur", function(){
+ Ext.fly(dom, INTERNAL).removeCls(className);
+ });
+ return me;
+ },
+
+ /**
+ * 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
+ * @return {Ext.core.Element} this
+ */
+ addClsOnClick : function(className){
+ var dom = this.dom;
+ this.on("mousedown", function(){
+ Ext.fly(dom, INTERNAL).addCls(className);
+ var d = Ext.getDoc(),
+ fn = function(){
+ Ext.fly(dom, INTERNAL).removeCls(className);
+ d.removeListener("mouseup", fn);
+ };
+ d.on("mouseup", fn);
+ });
+ return this;
+ },
+
+ /**
+ * <p>Returns the dimensions of the element available to lay content out in.<p>
+ * <p>If the element (or any ancestor element) has CSS style <code>display : none</code>, the dimensions will be zero.</p>
+ * example:<pre><code>
+ var vpSize = Ext.getBody().getViewSize();
+
+ // all Windows created afterwards will have a default value of 90% height and 95% width
+ Ext.Window.override({
+ width: vpSize.width * 0.9,
+ height: vpSize.height * 0.95
+ });
+ // To handle window resizing you would have to hook onto onWindowResize.
+ * </code></pre>
+ *
+ * getViewSize utilizes clientHeight/clientWidth which excludes sizing of scrollbars.
+ * To obtain the size including scrollbars, use getStyleSize
+ *
+ * Sizing of the document body is handled at the adapter level which handles special cases for IE and strict modes, etc.
+ */
+
+ getViewSize : function(){
+ var me = this,
+ dom = me.dom,
+ isDoc = (dom == Ext.getDoc().dom || dom == Ext.getBody().dom),
+ style, overflow, ret;
+
+ // If the body, use static methods
+ if (isDoc) {
+ ret = {
+ width : Ext.core.Element.getViewWidth(),
+ height : Ext.core.Element.getViewHeight()
+ };
+
+ // Else use clientHeight/clientWidth
+ }
+ else {
+ // IE 6 & IE Quirks mode acts more like a max-size measurement unless overflow is hidden during measurement.
+ // We will put the overflow back to it's original value when we are done measuring.
+ if (Ext.isIE6 || Ext.isIEQuirks) {
+ style = dom.style;
+ overflow = style.overflow;
+ me.setStyle({ overflow: 'hidden'});
+ }
+ ret = {
+ width : dom.clientWidth,
+ height : dom.clientHeight
+ };
+ if (Ext.isIE6 || Ext.isIEQuirks) {
+ me.setStyle({ overflow: overflow });
+ }
+ }
+ return ret;
+ },
+
+ /**
+ * <p>Returns the dimensions of the element available to lay content out in.<p>
+ *
+ * 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.
+ */
+
+ getStyleSize : function(){
+ var me = this,
+ doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body),
+ s = d.style,
+ w, h;
+
+ // If the body, use static methods
+ if (isDoc) {
+ return {
+ width : Ext.core.Element.getViewWidth(),
+ height : Ext.core.Element.getViewHeight()
+ };
+ }
+ // Use Styles if they are set
+ if(s.width && s.width != 'auto'){
+ w = parseFloat(s.width);
+ if(me.isBorderBox()){
+ w -= me.getFrameWidth('lr');
+ }
+ }
+ // Use Styles if they are set
+ if(s.height && s.height != 'auto'){
+ h = parseFloat(s.height);
+ if(me.isBorderBox()){
+ h -= me.getFrameWidth('tb');
+ }
+ }
+ // Use getWidth/getHeight if style not set.
+ return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
+ },
+
+ /**
+ * Returns the size of the element.
+ * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
+ * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
+ */
+ getSize : function(contentSize){
+ return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
+ },
+
+ /**
+ * Forces the browser to repaint this element
+ * @return {Ext.core.Element} this
+ */
+ repaint : function(){
+ var dom = this.dom;
+ this.addCls(Ext.baseCSSPrefix + 'repaint');
+ setTimeout(function(){
+ Ext.fly(dom).removeCls(Ext.baseCSSPrefix + 'repaint');
+ }, 1);
+ return this;
+ },
+
+ /**
+ * Disables text selection for this element (normalized across browsers)
+ * @return {Ext.core.Element} this
+ */
+ unselectable : function(){
+ var me = this;
+ me.dom.unselectable = "on";
+
+ me.swallowEvent("selectstart", true);
+ me.applyStyles("-moz-user-select:none;-khtml-user-select:none;");
+ me.addCls(Ext.baseCSSPrefix + 'unselectable');
+
+ return me;
+ },
+
+ /**
+ * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
+ * then it returns the calculated width of the sides (see getPadding)
+ * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
+ * @return {Object/Number}
+ */
+ getMargin : function(side){
+ var me = this,
+ hash = {t:"top", l:"left", r:"right", b: "bottom"},
+ o = {},
+ key;
+
+ if (!side) {
+ for (key in me.margins){
+ o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
+ }
+ return o;
+ } else {
+ return me.addStyles.call(me, side, me.margins);
+ }
+ }
+ });
+})();
+/**
+ * @class Ext.core.Element
+ */
+/**
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
+ * @static
+ * @type Number
+ */
+Ext.core.Element.VISIBILITY = 1;
+/**
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
+ * @static
+ * @type Number
+ */
+Ext.core.Element.DISPLAY = 2;
+
+/**
+ * Visibility mode constant for use with {@link #setVisibilityMode}. Use offsets (x and y positioning offscreen)
+ * to hide element.
+ * @static
+ * @type Number
+ */
+Ext.core.Element.OFFSETS = 3;
+
+
+Ext.core.Element.ASCLASS = 4;
+
+/**
+ * Defaults to 'x-hide-nosize'
+ * @static
+ * @type String
+ */
+Ext.core.Element.visibilityCls = Ext.baseCSSPrefix + 'hide-nosize';
+
+Ext.core.Element.addMethods(function(){
+ var El = Ext.core.Element,
+ OPACITY = "opacity",
+ VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ OFFSETS = "offsets",
+ ASCLASS = "asclass",
+ NONE = "none",
+ NOSIZE = 'nosize',
+ ORIGINALDISPLAY = 'originalDisplay',
+ VISMODE = 'visibilityMode',
+ ISVISIBLE = 'isVisible',
+ data = El.data,
+ getDisplay = function(dom){
+ var d = data(dom, ORIGINALDISPLAY);
+ if(d === undefined){
+ data(dom, ORIGINALDISPLAY, d = '');
+ }
+ return d;
+ },
+ getVisMode = function(dom){
+ var m = data(dom, VISMODE);
+ if(m === undefined){
+ data(dom, VISMODE, m = 1);
+ }
+ return m;
+ };
+
+ return {
+ /**
+ * The element's default display mode (defaults to "")
+ * @type String
+ */
+ originalDisplay : "",
+ visibilityMode : 1,
+
+ /**
+ * Sets the element's visibility mode. When setVisible() is called it
+ * will use this to determine whether to set the visibility or the display property.
+ * @param {Number} visMode Ext.core.Element.VISIBILITY or Ext.core.Element.DISPLAY
+ * @return {Ext.core.Element} this
+ */
+ setVisibilityMode : function(visMode){
+ data(this.dom, VISMODE, visMode);
+ return this;
+ },
+
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @return {Boolean} True if the element is currently visible, else false
+ */
+ isVisible : function() {
+ var me = this,
+ dom = me.dom,
+ visible = data(dom, ISVISIBLE);
+
+ if(typeof visible == 'boolean'){ //return the cached value if registered
+ return visible;
+ }
+ //Determine the current state based on display states
+ visible = !me.isStyle(VISIBILITY, HIDDEN) &&
+ !me.isStyle(DISPLAY, NONE) &&
+ !((getVisMode(dom) == El.ASCLASS) && me.hasCls(me.visibilityCls || El.visibilityCls));
+
+ data(dom, ISVISIBLE, visible);
+ return visible;
+ },
+
+ /**
+ * 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 (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setVisible : function(visible, animate){
+ var me = this, isDisplay, isVisibility, isOffsets, isNosize,
+ dom = me.dom,
+ visMode = getVisMode(dom);
+
+
+ // hideMode string override
+ if (typeof animate == 'string'){
+ switch (animate) {
+ case DISPLAY:
+ visMode = El.DISPLAY;
+ break;
+ case VISIBILITY:
+ visMode = El.VISIBILITY;
+ break;
+ case OFFSETS:
+ visMode = El.OFFSETS;
+ break;
+ case NOSIZE:
+ case ASCLASS:
+ visMode = El.ASCLASS;
+ break;
+ }
+ me.setVisibilityMode(visMode);
+ animate = false;
+ }
+
+ if (!animate || !me.anim) {
+ if(visMode == El.ASCLASS ){
+
+ me[visible?'removeCls':'addCls'](me.visibilityCls || El.visibilityCls);
+
+ } else if (visMode == El.DISPLAY){
+
+ return me.setDisplayed(visible);
+
+ } else if (visMode == El.OFFSETS){
+
+ if (!visible){
+ // Remember position for restoring, if we are not already hidden by offsets.
+ if (!me.hideModeStyles) {
+ me.hideModeStyles = {
+ position: me.getStyle('position'),
+ top: me.getStyle('top'),
+ left: me.getStyle('left')
+ };
+ }
+ me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
+ }
+
+ // Only "restore" as position if we have actually been hidden using offsets.
+ // Calling setVisible(true) on a positioned element should not reposition it.
+ else if (me.hideModeStyles) {
+ me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
+ delete me.hideModeStyles;
+ }
+
+ }else{
+ 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() {
+ visible || me.setVisible(false).setOpacity(1);
+ },
+ to: {
+ opacity: (visible) ? 1 : 0
+ }
+ }, animate));
+ }
+ data(dom, ISVISIBLE, visible); //set logical visibility state
+ return me;
+ },
+
+
+ /**
+ * @private
+ * Determine if the Element has a relevant height and width available based
+ * upon current logical visibility state
+ */
+ hasMetrics : function(){
+ var dom = this.dom;
+ return this.isVisible() || (getVisMode(dom) == El.OFFSETS) || (getVisMode(dom) == El.VISIBILITY);
+ },
+
+ /**
+ * Toggles the element's visibility or display, depending on visibility mode.
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ toggle : function(animate){
+ var me = this;
+ me.setVisible(!me.isVisible(), me.anim(animate));
+ return me;
+ },
+
+ /**
+ * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
+ * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
+ * @return {Ext.core.Element} this
+ */
+ setDisplayed : function(value) {
+ if(typeof value == "boolean"){
+ value = value ? getDisplay(this.dom) : NONE;
+ }
+ this.setStyle(DISPLAY, value);
+ return this;
+ },
+
+ // private
+ fixDisplay : function(){
+ var me = this;
+ if (me.isStyle(DISPLAY, NONE)) {
+ me.setStyle(VISIBILITY, HIDDEN);
+ me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
+ if (me.isStyle(DISPLAY, NONE)) { // if that fails, default to block
+ me.setStyle(DISPLAY, "block");
+ }
+ }
+ },
+
+ /**
+ * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.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;
+ },
+
+ /**
+ * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.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;
+ }
+ };
+}());
+/**
+ * @class Ext.core.Element
+ */
+Ext.applyIf(Ext.core.Element.prototype, {
+ // @private override base Ext.util.Animate mixin for animate for backwards compatibility
+ animate: function(config) {
+ var me = this;
+ if (!me.id) {
+ me = Ext.get(me.dom);
+ }
+ if (Ext.fx.Manager.hasFxBlock(me.id)) {
+ return me;
+ }
+ Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(config)));
+ return this;
+ },
+
+ // @private override base Ext.util.Animate mixin for animate for backwards compatibility
+ 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 = {
+ target: me,
+ 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;
+ },
+
+ /**
+ * 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 Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// 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: 2 });
+
+// common config options shown with default values
+el.slideIn('t', {
+ easing: 'easeOut',
+ duration: 500
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+ slideIn: function(anchor, obj, slideOut) {
+ var me = this,
+ elStyle = me.dom.style,
+ beforeAnim, wrapAnim;
+
+ anchor = anchor || "t";
+ obj = obj || {};
+
+ beforeAnim = function() {
+ var animScope = this,
+ listeners = obj.listeners,
+ box, position, restoreSize, wrap, anim;
+
+ if (!slideOut) {
+ me.fixDisplay();
+ }
+
+ box = me.getBox();
+ if ((anchor == 't' || anchor == 'b') && box.height == 0) {
+ box.height = me.dom.scrollHeight;
+ }
+ else if ((anchor == 'l' || anchor == 'r') && box.width == 0) {
+ box.width = me.dom.scrollWidth;
+ }
+
+ position = me.getPositioning();
+ me.setSize(box.width, box.height);
+
+ wrap = me.wrap({
+ style: {
+ visibility: slideOut ? 'visible' : 'hidden'
+ }
+ });
+ wrap.setPositioning(position);
+ if (wrap.isStyle('position', 'static')) {
+ wrap.position('relative');
+ }
+ me.clearPositioning('auto');
+ wrap.clip();
+
+ // 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.
+ me.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'
+ }
+ };
+ elStyle.right = '0px';
+ 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'
+ }
+ };
+ 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';
+ elStyle.right = '0px';
+ break;
+ case 'bl':
+ anim = {
+ from: {
+ x: box.x + box.width,
+ width: '0px',
+ height: '0px'
+ },
+ to: {
+ x: box.x,
+ width: box.width + 'px',
+ height: box.height + 'px'
+ }
+ };
+ elStyle.right = '0px';
+ 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'
+ }
+ };
+ break;
+ case 'tr':
+ anim = {
+ from: {
+ y: box.y + box.height,
+ width: '0px',
+ height: '0px'
+ },
+ to: {
+ y: box.y,
+ width: box.width + 'px',
+ height: box.height + 'px'
+ }
+ };
+ elStyle.bottom = '0px';
+ break;
+ }
+
+ wrap.show();
+ wrapAnim = Ext.apply({}, obj);
+ delete wrapAnim.listeners;
+ wrapAnim = Ext.create('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() {
+ if (slideOut) {
+ me.setPositioning(position);
+ if (obj.useDisplay) {
+ me.setDisplayed(false);
+ } else {
+ me.hide();
+ }
+ }
+ else {
+ me.clearPositioning();
+ me.setPositioning(position);
+ }
+ if (wrap.dom) {
+ wrap.dom.parentNode.insertBefore(me.dom, wrap.dom);
+ wrap.remove();
+ }
+ me.setSize(box.width, box.height);
+ animScope.end();
+ });
+ // Add configured listeners after
+ if (listeners) {
+ wrapAnim.on(listeners);
+ }
+ };
+
+ me.animate({
+ duration: obj.duration ? obj.duration * 2 : 1000,
+ listeners: {
+ beforeanimate: {
+ fn: beforeAnim
+ },
+ afteranimate: {
+ fn: function() {
+ if (wrapAnim && wrapAnim.running) {
+ wrapAnim.end();
+ }
+ }
+ }
+ }
+ });
+ 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 Fx class overview for valid anchor point options.
+ * Usage:
+ *<pre><code>
+// 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: 2 });
+
+// common config options shown with default values
+el.slideOut('t', {
+ easing: 'easeOut',
+ duration: 500,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+ slideOut: function(anchor, o) {
+ return this.slideIn(anchor, o, true);
+ },
+
+ /**
+ * 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:
+ *<pre><code>
+// default
+el.puff();
+
+// common config options shown with default values
+el.puff({
+ easing: 'easeOut',
+ duration: 500,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+
+ puff: function(obj) {
+ var me = this,
+ beforeAnim;
+ obj = Ext.applyIf(obj || {}, {
+ easing: 'ease-out',
+ duration: 500,
+ useDisplay: false
+ });
+
+ beforeAnim = function() {
+ me.clearOpacity();
+ me.show();
+
+ var box = me.getBox(),
+ fontSize = me.getStyle('fontSize'),
+ position = me.getPositioning();
+ 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() {
+ if (me.dom) {
+ if (obj.useDisplay) {
+ me.setDisplayed(false);
+ } else {
+ me.hide();
+ }
+ me.clearOpacity();
+ me.setPositioning(position);
+ me.setStyle({fontSize: fontSize});
+ }
+ });
+ };
+
+ me.animate({
+ duration: obj.duration,
+ easing: obj.easing,
+ listeners: {
+ beforeanimate: {
+ fn: beforeAnim
+ }
+ }
+ });
+ 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:
+ *<pre><code>
+// default
+el.switchOff();
+
+// all config options shown with default values
+el.switchOff({
+ easing: 'easeIn',
+ duration: .3,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+ switchOff: function(obj) {
+ var me = this,
+ beforeAnim;
+
+ obj = Ext.applyIf(obj || {}, {
+ easing: 'ease-in',
+ duration: 500,
+ remove: false,
+ useDisplay: false
+ });
+
+ beforeAnim = function() {
+ var animScope = this,
+ size = me.getSize(),
+ xy = me.getXY(),
+ keyframe, position;
+ me.clearOpacity();
+ me.clip();
+ position = me.getPositioning();
+
+ keyframe = Ext.create('Ext.fx.Animator', {
+ target: me,
+ 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() {
+ if (obj.useDisplay) {
+ me.setDisplayed(false);
+ } else {
+ me.hide();
+ }
+ me.clearOpacity();
+ me.setPositioning(position);
+ me.setSize(size);
+ animScope.end();
+ });
+ };
+ me.animate({
+ duration: (obj.duration * 2),
+ listeners: {
+ beforeanimate: {
+ fn: beforeAnim
+ }
+ }
+ });
+ return me;
+ },
+
+ /**
+ * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
+ * Usage:
+<pre><code>
+// default: a single light blue ripple
+el.frame();
+
+// custom: 3 red ripples lasting 3 seconds total
+el.frame("#ff0000", 3, { duration: 3 });
+
+// common config options shown with default values
+el.frame("#C3DAF9", 1, {
+ duration: 1 //duration of each individual ripple.
+ // Note: Easing is not configurable and will be ignored if included
+});
+</code></pre>
+ * @param {String} color (optional) The color of the border. Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
+ * @param {Number} count (optional) The number of ripples to display (defaults to 1)
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+ frame : function(color, count, obj){
+ var me = this,
+ beforeAnim;
+
+ color = color || '#C3DAF9';
+ count = count || 1;
+ obj = obj || {};
+
+ beforeAnim = function() {
+ me.show();
+ var animScope = this,
+ box = me.getBox(),
+ proxy = Ext.getBody().createChild({
+ style: {
+ position : 'absolute',
+ 'pointer-events': 'none',
+ 'z-index': 35000,
+ border : '0px solid ' + color
+ }
+ }),
+ proxyAnim;
+ proxyAnim = Ext.create('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.remove();
+ animScope.end();
+ });
+ };
+
+ me.animate({
+ duration: (obj.duration * 2) || 2000,
+ listeners: {
+ beforeanimate: {
+ fn: beforeAnim
+ }
+ }
+ });
+ return me;
+ },
+
+ /**
+ * 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:
+ *<pre><code>
+// 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: 2 });
+
+// common config options shown with default values
+el.ghost('b', {
+ easing: 'easeOut',
+ duration: 500
+});
+</code></pre>
+ * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.core.Element} The Element
+ */
+ ghost: function(anchor, obj) {
+ var me = this,
+ beforeAnim;
+
+ anchor = anchor || "b";
+ beforeAnim = function() {
+ var width = me.getWidth(),
+ height = me.getHeight(),
+ xy = me.getXY(),
+ position = me.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 () {
+ if (me.dom) {
+ me.hide();
+ me.clearOpacity();
+ me.setPositioning(position);
+ }
+ });
+ };
+
+ me.animate(Ext.applyIf(obj || {}, {
+ duration: 500,
+ easing: 'ease-out',
+ listeners: {
+ beforeanimate: {
+ fn: beforeAnim
+ }
+ }
+ }));
+ return me;
+ },
+
+ /**
+ * 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:
+<pre><code>
+// default: highlight background to yellow
+el.highlight();
+
+// custom: highlight foreground text to blue for 2 seconds
+el.highlight("0000ff", { attr: 'color', duration: 2 });
+
+// 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
+});
+</code></pre>
+ * @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 Fx config options
+ * @return {Ext.core.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];
+ me.clearOpacity();
+ me.show();
+
+ event = lns.beforeanimate;
+ if (event) {
+ fn = event.fn || event;
+ return fn.apply(event.scope || lns.scope || window, arguments);
+ }
+ },
+ afteranimate: function() {
+ if (dom) {
+ dom.style[attr] = restore;
+ }
+
+ event = lns.afteranimate;
+ if (event) {
+ fn = event.fn || event;
+ fn.apply(event.scope || lns.scope || window, arguments);
+ }
+ }
+ });
+
+ me.animate(Ext.apply({}, o, {
+ duration: 1000,
+ easing: 'ease-in',
+ from: from,
+ to: to
+ }));
+ return me;
+ },
+
+ /**
+ * @deprecated 4.0
+ * Creates a pause before any subsequent queued effects begin. If there are
+ * no effects queued after the pause it will have no effect.
+ * Usage:
+<pre><code>
+el.pause(1);
+</code></pre>
+ * @param {Number} seconds The length of time to pause (in seconds)
+ * @return {Ext.Element} The Element
+ */
+ pause: function(ms) {
+ var me = this;
+ Ext.fx.Manager.setFxDefaults(me.id, {
+ delay: ms
+ });
+ return me;
+ },
+
+ /**
+ * Fade an element in (from transparent to opaque). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option.
+ * Usage:
+<pre><code>
+// default: fade in from opacity 0 to 100%
+el.fadeIn();
+
+// custom: fade in from opacity 0 to 75% over 2 seconds
+el.fadeIn({ endOpacity: .75, duration: 2});
+
+// common config options shown with default values
+el.fadeIn({
+ endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: 500
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeIn: function(o) {
+ this.animate(Ext.apply({}, o, {
+ opacity: 1
+ }));
+ return this;
+ },
+
+ /**
+ * Fade an element out (from opaque to transparent). The ending opacity can be specified
+ * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
+ * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
+ * Usage:
+<pre><code>
+// 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({ endOpacity: .25, duration: 2});
+
+// common config options shown with default values
+el.fadeOut({
+ endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
+ easing: 'easeOut',
+ duration: 500,
+ remove: false,
+ useDisplay: false
+});
+</code></pre>
+ * @param {Object} options (optional) Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ fadeOut: function(o) {
+ this.animate(Ext.apply({}, o, {
+ opacity: 0
+ }));
+ return this;
+ },
+
+ /**
+ * @deprecated 4.0
+ * 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:
+<pre><code>
+// 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: .35
+ }
+);
+</code></pre>
+ * @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 Fx config options
+ * @return {Ext.Element} The Element
+ */
+ scale: function(w, h, o) {
+ this.animate(Ext.apply({}, o, {
+ width: w,
+ height: h
+ }));
+ return this;
+ },
+
+ /**
+ * @deprecated 4.0
+ * 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:
+<pre><code>
+// 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: .35
+});
+</code></pre>
+ * @param {Object} options Object literal with any of the Fx config options
+ * @return {Ext.Element} The Element
+ */
+ shift: function(config) {
+ this.animate(config);
+ return this;
+ }
+});
+
+/**
+ * @class Ext.core.Element
+ */
+Ext.applyIf(Ext.core.Element, {
+ unitRe: /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
+ camelRe: /(-[a-z])/gi,
+ opacityRe: /alpha\(opacity=(.*)\)/i,
+ cssRe: /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
+ propertyCache: {},
+ defaultUnit : "px",
+ borders: {l: 'border-left-width', r: 'border-right-width', t: 'border-top-width', b: 'border-bottom-width'},
+ paddings: {l: 'padding-left', r: 'padding-right', t: 'padding-top', b: 'padding-bottom'},
+ margins: {l: 'margin-left', r: 'margin-right', t: 'margin-top', b: 'margin-bottom'},
+
+ // Reference the prototype's version of the method. Signatures are identical.
+ addUnits : Ext.core.Element.prototype.addUnits,
+
+ /**
+ * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
+ * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
+ * @static
+ * @param {Number|String} box The encoded margins
+ * @return {Object} An object with margin sizes for top, right, bottom and left
+ */
+ parseBox : function(box) {
+ if (Ext.isObject(box)) {
+ return {
+ top: box.top || 0,
+ right: box.right || 0,
+ bottom: box.bottom || 0,
+ left: box.left || 0
+ };
+ } else {
+ if (typeof box != 'string') {
+ box = box.toString();
+ }
+ var parts = box.split(' '),
+ ln = parts.length;
+
+ if (ln == 1) {
+ parts[1] = parts[2] = parts[3] = parts[0];
+ }
+ else if (ln == 2) {
+ parts[2] = parts[0];
+ parts[3] = parts[1];
+ }
+ else if (ln == 3) {
+ parts[3] = parts[1];
+ }
+
+ return {
+ top :parseFloat(parts[0]) || 0,
+ right :parseFloat(parts[1]) || 0,
+ bottom:parseFloat(parts[2]) || 0,
+ left :parseFloat(parts[3]) || 0
+ };
+ }
+
+ },
+
+ /**
+ * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
+ * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
+ * @static
+ * @param {Number|String} box The encoded margins
+ * @param {String} units The type of units to add
+ * @return {String} An string with unitized (px if units is not specified) metrics for top, right, bottom and left
+ */
+ unitizeBox : function(box, units) {
+ var A = this.addUnits,
+ B = this.parseBox(box);
+
+ return A(B.top, units) + ' ' +
+ A(B.right, units) + ' ' +
+ A(B.bottom, units) + ' ' +
+ A(B.left, units);
+
+ },
+
+ // private
+ camelReplaceFn : function(m, a) {
+ return a.charAt(1).toUpperCase();
+ },
+
+ /**
+ * Normalizes CSS property keys from dash delimited to camel case JavaScript Syntax.
+ * For example:
+ * <ul>
+ * <li>border-width -> borderWidth</li>
+ * <li>padding-top -> paddingTop</li>
+ * </ul>
+ * @static
+ * @param {String} prop The property to normalize
+ * @return {String} The normalized string
+ */
+ normalize : function(prop) {
+ if (prop == 'float') {
+ prop = Ext.supports.Float ? 'cssFloat' : 'styleFloat';
+ }
+ return this.propertyCache[prop] || (this.propertyCache[prop] = prop.replace(this.camelRe, this.camelReplaceFn));
+ },
+
+ /**
+ * Retrieves the document height
+ * @static
+ * @return {Number} documentHeight
+ */
+ getDocumentHeight: function() {
+ return Math.max(!Ext.isStrict ? document.body.scrollHeight : document.documentElement.scrollHeight, this.getViewportHeight());
+ },
+
+ /**
+ * Retrieves the document width
+ * @static
+ * @return {Number} documentWidth
+ */
+ getDocumentWidth: function() {
+ return Math.max(!Ext.isStrict ? document.body.scrollWidth : document.documentElement.scrollWidth, this.getViewportWidth());
+ },
+
+ /**
+ * Retrieves the viewport height of the window.
+ * @static
+ * @return {Number} viewportHeight
+ */
+ getViewportHeight: function(){
+ return window.innerHeight;
+ },
+
+ /**
+ * Retrieves the viewport width of the window.
+ * @static
+ * @return {Number} viewportWidth
+ */
+ getViewportWidth : function() {
+ return window.innerWidth;
+ },
+
+ /**
+ * Retrieves the viewport size of the window.
+ * @static
+ * @return {Object} object containing width and height properties
+ */
+ getViewSize : function() {
+ return {
+ width: window.innerWidth,
+ height: window.innerHeight
+ };
+ },
+
+ /**
+ * Retrieves the current orientation of the window. This is calculated by
+ * determing if the height is greater than the width.
+ * @static
+ * @return {String} Orientation of window: 'portrait' or 'landscape'
+ */
+ getOrientation : function() {
+ if (Ext.supports.OrientationChange) {
+ return (window.orientation == 0) ? 'portrait' : 'landscape';
+ }
+
+ return (window.innerHeight > window.innerWidth) ? 'portrait' : 'landscape';
+ },
+
+ /**
+ * Returns the top Element that is located at the passed coordinates
+ * @static
+ * @param {Number} x The x coordinate
+ * @param {Number} x The y coordinate
+ * @return {String} The found Element
+ */
+ fromPoint: function(x, y) {
+ return Ext.get(document.elementFromPoint(x, y));
+ },
+
+ /**
+ * Converts a CSS string into an object with a property for each style.
+ * <p>
+ * The sample code below would return an object with 2 properties, one
+ * for background-color and one for color.</p>
+ * <pre><code>
+var css = 'background-color: red;color: blue; ';
+console.log(Ext.core.Element.parseStyles(css));
+ * </code></pre>
+ * @static
+ * @param {String} styles A CSS string
+ * @return {Object} styles
+ */
+ parseStyles: function(styles){
+ var out = {},
+ cssRe = this.cssRe,
+ matches;
+
+ if (styles) {
+ // Since we're using the g flag on the regex, we need to set the lastIndex.
+ // This automatically happens on some implementations, but not others, see:
+ // http://stackoverflow.com/questions/2645273/javascript-regular-expression-literal-persists-between-function-calls
+ // http://blog.stevenlevithan.com/archives/fixing-javascript-regexp
+ cssRe.lastIndex = 0;
+ while ((matches = cssRe.exec(styles))) {
+ out[matches[1]] = matches[2];
+ }
+ }
+ return out;
+ }
+});
+
+/**
+ * @class Ext.CompositeElementLite
+ * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
+ * members, or to perform collective actions upon the whole set.</p>
+ * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
+ * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
+ * Example:<pre><code>
+var els = Ext.select("#some-el div.some-class");
+// or select directly from an existing element
+var el = Ext.get('some-el');
+el.select('div.some-class');
+
+els.setWidth(100); // all elements become 100 width
+els.hide(true); // all elements fade out and hide
+// or
+els.setWidth(100).hide(true);
+</code></pre>
+ */
+Ext.CompositeElementLite = function(els, root){
+ /**
+ * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
+ * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
+ * to augment the capabilities of the CompositeElementLite class may use it when adding
+ * methods to the class.</p>
+ * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
+ * following siblings of selected elements, the code would be</p><code><pre>
+Ext.override(Ext.CompositeElementLite, {
+ nextAll: function() {
+ var els = this.elements, i, l = els.length, n, r = [], ri = -1;
+
+// Loop through all elements in this Composite, accumulating
+// an Array of all siblings.
+ for (i = 0; i < l; i++) {
+ for (n = els[i].nextSibling; n; n = n.nextSibling) {
+ r[++ri] = n;
+ }
+ }
+
+// Add all found siblings to this Composite
+ return this.add(r);
+ }
+});</pre></code>
+ * @type Array
+ * @property elements
+ */
+ this.elements = [];
+ this.add(els, root);
+ this.el = new Ext.core.Element.Flyweight();
+};
+
+Ext.CompositeElementLite.prototype = {
+ isComposite: true,
+
+ // private
+ getElement : function(el){
+ // Set the shared flyweight dom property to the current element
+ var e = this.el;
+ e.dom = el;
+ e.id = el.id;
+ return e;
+ },
+
+ // private
+ transformElement : function(el){
+ return Ext.getDom(el);
+ },
+
+ /**
+ * Returns the number of elements in this Composite.
+ * @return Number
+ */
+ getCount : function(){
+ return this.elements.length;
+ },
+ /**
+ * Adds elements to this Composite object.
+ * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
+ * @return {CompositeElement} This Composite object.
+ */
+ add : function(els, root){
+ var me = this,
+ elements = me.elements;
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.core.Element.selectorFunction(els, root);
+ }else if(els.isComposite){
+ els = els.elements;
+ }else if(!Ext.isIterable(els)){
+ els = [els];
+ }
+
+ for(var i = 0, len = els.length; i < len; ++i){
+ elements.push(me.transformElement(els[i]));
+ }
+ return me;
+ },
+
+ invoke : function(fn, args){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ e,
+ i;
+
+ for(i = 0; i < len; i++) {
+ e = els[i];
+ if(e){
+ Ext.core.Element.prototype[fn].apply(me.getElement(e), args);
+ }
+ }
+ return me;
+ },
+ /**
+ * Returns a flyweight Element of the dom element object at the specified index
+ * @param {Number} index
+ * @return {Ext.core.Element}
+ */
+ item : function(index){
+ var me = this,
+ el = me.elements[index],
+ out = null;
+
+ if(el){
+ out = me.getElement(el);
+ }
+ return out;
+ },
+
+ // fixes scope with flyweight
+ addListener : function(eventName, handler, scope, opt){
+ var els = this.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e) {
+ Ext.EventManager.on(e, eventName, handler, scope || e, opt);
+ }
+ }
+ return this;
+ },
+ /**
+ * <p>Calls the passed function for each element in this composite.</p>
+ * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
+ * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
+ * <b>This is the flyweight (shared) Ext.core.Element instance, so if you require a
+ * a reference to the dom node, use el.dom.</b></div></li>
+ * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
+ * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
+ * @return {CompositeElement} this
+ */
+ each : function(fn, scope){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e){
+ e = this.getElement(e);
+ if(fn.call(scope || e, e, me, i) === false){
+ break;
+ }
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Clears this Composite and adds the elements passed.
+ * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
+ * @return {CompositeElement} this
+ */
+ fill : function(els){
+ var me = this;
+ me.elements = [];
+ me.add(els);
+ return me;
+ },
+
+ /**
+ * Filters this composite to only elements that match the passed selector.
+ * @param {String/Function} selector A string CSS selector or a comparison function.
+ * The comparison function will be called with the following arguments:<ul>
+ * <li><code>el</code> : Ext.core.Element<div class="sub-desc">The current DOM element.</div></li>
+ * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
+ * </ul>
+ * @return {CompositeElement} this
+ */
+ filter : function(selector){
+ var els = [],
+ me = this,
+ fn = Ext.isFunction(selector) ? selector
+ : function(el){
+ return el.is(selector);
+ };
+
+ me.each(function(el, self, i) {
+ if (fn(el, i) !== false) {
+ els[els.length] = me.transformElement(el);
+ }
+ });
+
+ me.elements = els;
+ return me;
+ },
+
+ /**
+ * Find the index of the passed element within the composite collection.
+ * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
+ * @return Number The index of the passed Ext.core.Element in the composite collection, or -1 if not found.
+ */
+ indexOf : function(el){
+ return Ext.Array.indexOf(this.elements, this.transformElement(el));
+ },
+
+ /**
+ * Replaces the specified element with the passed element.
+ * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
+ * to replace.
+ * @param {Mixed} replacement The id of an element or the Element itself.
+ * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
+ * @return {CompositeElement} this
+ */
+ replaceElement : function(el, replacement, domReplace){
+ var index = !isNaN(el) ? el : this.indexOf(el),
+ d;
+ if(index > -1){
+ replacement = Ext.getDom(replacement);
+ if(domReplace){
+ d = this.elements[index];
+ d.parentNode.insertBefore(replacement, d);
+ Ext.removeNode(d);
+ }
+ this.elements.splice(index, 1, replacement);
+ }
+ return this;
+ },
+
+ /**
+ * Removes all elements.
+ */
+ clear : function(){
+ this.elements = [];
+ }
+};
+
+Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
+
+/**
+ * @private
+ * Copies all of the functions from Ext.core.Element's prototype onto CompositeElementLite's prototype.
+ * This is called twice - once immediately below, and once again after additional Ext.core.Element
+ * are added in Ext JS
+ */
+Ext.CompositeElementLite.importElementMethods = function() {
+ var fnName,
+ ElProto = Ext.core.Element.prototype,
+ CelProto = Ext.CompositeElementLite.prototype;
+
+ for (fnName in ElProto) {
+ if (typeof ElProto[fnName] == 'function'){
+ (function(fnName) {
+ CelProto[fnName] = CelProto[fnName] || function() {
+ return this.invoke(fnName, arguments);
+ };
+ }).call(CelProto, fnName);
+
+ }
+ }
+};
+
+Ext.CompositeElementLite.importElementMethods();
+
+if(Ext.DomQuery){
+ Ext.core.Element.selectorFunction = Ext.DomQuery.select;
+}
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.core.Element
+ * @method select
+ */
+Ext.core.Element.select = function(selector, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.core.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ Ext.Error.raise({
+ sourceClass: "Ext.core.Element",
+ sourceMethod: "select",
+ selector: selector,
+ root: root,
+ msg: "Invalid selector specified: " + selector
+ });
+ }
+ return new Ext.CompositeElementLite(els);
+};
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext
+ * @method select
+ */
+Ext.select = Ext.core.Element.select;
+
+/**
+ * @class Ext.util.DelayedTask
+ *
+ * The DelayedTask class provides a convenient way to "buffer" the execution of a method,
+ * performing setTimeout where a new timeout cancels the old timeout. When called, the
+ * task will wait the specified time period before executing. If durng that time period,
+ * the task is called again, the original call will be cancelled. This continues so that
+ * the function is only called a single time for each iteration.
+ *
+ * This method is especially useful for things like detecting whether a user has finished
+ * typing in a text field. An example would be performing validation on a keypress. You can
+ * use this class to buffer the keypress events for a certain number of milliseconds, and
+ * perform only if they stop for that amount of time.
+ *
+ * ## Usage
+ *
+ * var task = new Ext.util.DelayedTask(function(){
+ * alert(Ext.getDom('myInputField').value.length);
+ * });
+ *
+ * // Wait 500ms before calling our function. If the user presses another key
+ * // during that 500ms, it will be cancelled and we'll wait another 500ms.
+ * Ext.get('myInputField').on('keypress', function(){
+ * task.{@link #delay}(500);
+ * });
+ *
+ * Note that we are using a DelayedTask here to illustrate a point. The configuration
+ * option `buffer` for {@link Ext.util.Observable#addListener addListener/on} will
+ * also setup a delayed task for you to buffer events.
+ *
+ * @constructor The parameters to this constructor serve as defaults and are not required.
+ * @param {Function} fn (optional) The default function to call.
+ * @param {Object} scope The default scope (The <code><b>this</b></code> reference) in which the
+ * function is called. If not specified, <code>this</code> will refer to the browser window.
+ * @param {Array} args (optional) The default Array of arguments.
+ */
+Ext.util.DelayedTask = function(fn, scope, args) {
+ var me = this,
+ id,
+ call = function() {
+ clearInterval(id);
+ id = null;
+ fn.apply(scope, args || []);
+ };
+
+ /**
+ * Cancels any pending timeout and queues a new one
+ * @param {Number} delay The milliseconds to delay
+ * @param {Function} newFn (optional) Overrides function passed to constructor
+ * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
+ * is specified, <code>this</code> will refer to the browser window.
+ * @param {Array} newArgs (optional) Overrides args passed to constructor
+ */
+ this.delay = function(delay, newFn, newScope, newArgs) {
+ me.cancel();
+ fn = newFn || fn;
+ scope = newScope || scope;
+ args = newArgs || args;
+ id = setInterval(call, delay);
+ };
+
+ /**
+ * Cancel the last queued timeout
+ */
+ this.cancel = function(){
+ if (id) {
+ clearInterval(id);
+ id = null;
+ }
+ };
+};
+Ext.require('Ext.util.DelayedTask', function() {
+
+ Ext.util.Event = Ext.extend(Object, (function() {
+ function createBuffered(handler, listener, o, scope) {
+ listener.task = new Ext.util.DelayedTask();
+ return function() {
+ listener.task.delay(o.buffer, handler, scope, Ext.Array.toArray(arguments));
+ };
+ }
+
+ function createDelayed(handler, listener, o, scope) {
+ return function() {
+ var task = new Ext.util.DelayedTask();
+ if (!listener.tasks) {
+ listener.tasks = [];
+ }
+ listener.tasks.push(task);
+ task.delay(o.delay || 10, handler, scope, Ext.Array.toArray(arguments));
+ };
+ }
+
+ function createSingle(handler, listener, o, scope) {
+ return function() {
+ listener.ev.removeListener(listener.fn, scope);
+ return handler.apply(scope, arguments);
+ };
+ }
+
+ return {
+ isEvent: true,
+
+ constructor: function(observable, name) {
+ this.name = name;
+ this.observable = observable;
+ this.listeners = [];
+ },
+
+ addListener: function(fn, scope, options) {
+ var me = this,
+ listener;
+ scope = scope || me.observable;
+
+ if (!fn) {
+ Ext.Error.raise({
+ sourceClass: Ext.getClassName(this.observable),
+ sourceMethod: "addListener",
+ msg: "The specified callback function is undefined"
+ });
+ }
+
+ if (!me.isListening(fn, scope)) {
+ listener = me.createListener(fn, scope, options);
+ if (me.firing) {
+ // if we are currently firing this event, don't disturb the listener loop
+ me.listeners = me.listeners.slice(0);
+ }
+ me.listeners.push(listener);
+ }
+ },
+
+ createListener: function(fn, scope, o) {
+ o = o || {};
+ scope = scope || this.observable;
+
+ var listener = {
+ fn: fn,
+ scope: scope,
+ o: o,
+ ev: this
+ },
+ handler = fn;
+
+ // The order is important. The 'single' wrapper must be wrapped by the 'buffer' and 'delayed' wrapper
+ // because the event removal that the single listener does destroys the listener's DelayedTask(s)
+ if (o.single) {
+ handler = createSingle(handler, listener, o, scope);
+ }
+ if (o.delay) {
+ handler = createDelayed(handler, listener, o, scope);
+ }
+ if (o.buffer) {
+ handler = createBuffered(handler, listener, o, scope);
+ }
+
+ listener.fireFn = handler;
+ return listener;
+ },
+
+ findListener: function(fn, scope) {
+ var listeners = this.listeners,
+ i = listeners.length,
+ listener,
+ s;
+
+ while (i--) {
+ listener = listeners[i];
+ if (listener) {
+ s = listener.scope;
+ if (listener.fn == fn && (s == scope || s == this.observable)) {
+ return i;
+ }
+ }
+ }
+
+ return - 1;
+ },
+
+ isListening: function(fn, scope) {
+ return this.findListener(fn, scope) !== -1;
+ },
+
+ removeListener: function(fn, scope) {
+ var me = this,
+ index,
+ listener,
+ k;
+ index = me.findListener(fn, scope);
+ if (index != -1) {
+ listener = me.listeners[index];
+
+ if (me.firing) {
+ me.listeners = me.listeners.slice(0);
+ }
+
+ // cancel and remove a buffered handler that hasn't fired yet
+ if (listener.task) {
+ listener.task.cancel();
+ delete listener.task;
+ }
+
+ // cancel and remove all delayed handlers that haven't fired yet
+ k = listener.tasks && listener.tasks.length;
+ if (k) {
+ while (k--) {
+ listener.tasks[k].cancel();
+ }
+ delete listener.tasks;
+ }
+
+ // remove this listener from the listeners array
+ me.listeners.splice(index, 1);
+ return true;
+ }
+
+ return false;
+ },
+
+ // Iterate to stop any buffered/delayed events
+ clearListeners: function() {
+ var listeners = this.listeners,
+ i = listeners.length;
+
+ while (i--) {
+ this.removeListener(listeners[i].fn, listeners[i].scope);
+ }
+ },
+
+ fire: function() {
+ var me = this,
+ listeners = me.listeners,
+ count = listeners.length,
+ i,
+ args,
+ listener;
+
+ if (count > 0) {
+ me.firing = true;
+ for (i = 0; i < count; i++) {
+ listener = listeners[i];
+ args = arguments.length ? Array.prototype.slice.call(arguments, 0) : [];
+ if (listener.o) {
+ args.push(listener.o);
+ }
+ if (listener && listener.fireFn.apply(listener.scope || me.observable, args) === false) {
+ return (me.firing = false);
+ }
+ }
+ }
+ me.firing = false;
+ return true;
+ }
+ };
+ })());
+});
+
+/**
+ * @class Ext.EventManager
+ * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
+ * several useful events directly.
+ * See {@link Ext.EventObject} for more details on normalized event objects.
+ * @singleton
+ */
+Ext.EventManager = {
+
+ // --------------------- onReady ---------------------
+
+ /**
+ * Check if we have bound our global onReady listener
+ * @private
+ */
+ hasBoundOnReady: false,
+
+ /**
+ * Check if fireDocReady has been called
+ * @private
+ */
+ hasFiredReady: false,
+
+ /**
+ * Timer for the document ready event in old IE versions
+ * @private
+ */
+ readyTimeout: null,
+
+ /**
+ * Checks if we have bound an onreadystatechange event
+ * @private
+ */
+ hasOnReadyStateChange: false,
+
+ /**
+ * Holds references to any onReady functions
+ * @private
+ */
+ readyEvent: new Ext.util.Event(),
+
+ /**
+ * Check the ready state for old IE versions
+ * @private
+ * @return {Boolean} True if the document is ready
+ */
+ checkReadyState: function(){
+ var me = Ext.EventManager;
+
+ if(window.attachEvent){
+ // See here for reference: http://javascript.nwbox.com/IEContentLoaded/
+ if (window != top) {
+ return false;
+ }
+ try{
+ document.documentElement.doScroll('left');
+ }catch(e){
+ return false;
+ }
+ me.fireDocReady();
+ return true;
+ }
+ if (document.readyState == 'complete') {
+ me.fireDocReady();
+ return true;
+ }
+ me.readyTimeout = setTimeout(arguments.callee, 2);
+ return false;
+ },
+
+ /**
+ * Binds the appropriate browser event for checking if the DOM has loaded.
+ * @private
+ */
+ bindReadyEvent: function(){
+ var me = Ext.EventManager;
+ if (me.hasBoundOnReady) {
+ return;
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener('DOMContentLoaded', me.fireDocReady, false);
+ // fallback, load will ~always~ fire
+ window.addEventListener('load', me.fireDocReady, false);
+ } else {
+ // check if the document is ready, this will also kick off the scroll checking timer
+ if (!me.checkReadyState()) {
+ document.attachEvent('onreadystatechange', me.checkReadyState);
+ me.hasOnReadyStateChange = true;
+ }
+ // fallback, onload will ~always~ fire
+ window.attachEvent('onload', me.fireDocReady, false);
+ }
+ me.hasBoundOnReady = true;
+ },
+
+ /**
+ * We know the document is loaded, so trigger any onReady events.
+ * @private
+ */
+ fireDocReady: function(){
+ var me = Ext.EventManager;
+
+ // only unbind these events once
+ if (!me.hasFiredReady) {
+ me.hasFiredReady = true;
+
+ if (document.addEventListener) {
+ document.removeEventListener('DOMContentLoaded', me.fireDocReady, false);
+ window.removeEventListener('load', me.fireDocReady, false);
+ } else {
+ if (me.readyTimeout !== null) {
+ clearTimeout(me.readyTimeout);
+ }
+ if (me.hasOnReadyStateChange) {
+ document.detachEvent('onreadystatechange', me.checkReadyState);
+ }
+ window.detachEvent('onload', me.fireDocReady);
+ }
+ Ext.supports.init();
+ }
+ if (!Ext.isReady) {
+ Ext.isReady = true;
+ me.onWindowUnload();
+ me.readyEvent.fire();
+ }
+ },
+
+ /**
+ * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
+ * accessed shorthanded as Ext.onReady().
+ * @param {Function} fn The method the event invokes.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
+ * @param {boolean} options (optional) Options object as passed to {@link Ext.core.Element#addListener}.
+ */
+ onDocumentReady: function(fn, scope, options){
+ options = options || {};
+ var me = Ext.EventManager,
+ readyEvent = me.readyEvent;
+
+ // force single to be true so our event is only ever fired once.
+ options.single = true;
+
+ // Document already loaded, let's just fire it
+ if (Ext.isReady) {
+ readyEvent.addListener(fn, scope, options);
+ readyEvent.fire();
+ } else {
+ options.delay = options.delay || 1;
+ readyEvent.addListener(fn, scope, options);
+ me.bindReadyEvent();
+ }
+ },
+
+
+ // --------------------- event binding ---------------------
+
+ /**
+ * Contains a list of all document mouse downs, so we can ensure they fire even when stopEvent is called.
+ * @private
+ */
+ stoppedMouseDownEvent: new Ext.util.Event(),
+
+ /**
+ * Options to parse for the 4th argument to addListener.
+ * @private
+ */
+ propRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|freezeEvent)$/,
+
+ /**
+ * Get the id of the element. If one has not been assigned, automatically assign it.
+ * @param {Mixed} element The element to get the id for.
+ * @return {String} id
+ */
+ getId : function(element) {
+ var skipGarbageCollection = false,
+ id;
+
+ element = Ext.getDom(element);
+
+ if (element === document || element === window) {
+ id = element === document ? Ext.documentId : Ext.windowId;
+ }
+ else {
+ id = Ext.id(element);
+ }
+ // skip garbage collection for special elements (window, document, iframes)
+ if (element && (element.getElementById || element.navigator)) {
+ skipGarbageCollection = true;
+ }
+
+ if (!Ext.cache[id]){
+ Ext.core.Element.addToCache(new Ext.core.Element(element), id);
+ if (skipGarbageCollection) {
+ Ext.cache[id].skipGarbageCollection = true;
+ }
+ }
+ return id;
+ },
+
+ /**
+ * Convert a "config style" listener into a set of flat arguments so they can be passed to addListener
+ * @private
+ * @param {Object} element The element the event is for
+ * @param {Object} event The event configuration
+ * @param {Object} isRemove True if a removal should be performed, otherwise an add will be done.
+ */
+ prepareListenerConfig: function(element, config, isRemove){
+ var me = this,
+ propRe = me.propRe,
+ key, value, args;
+
+ // loop over all the keys in the object
+ for (key in config) {
+ if (config.hasOwnProperty(key)) {
+ // if the key is something else then an event option
+ if (!propRe.test(key)) {
+ value = config[key];
+ // if the value is a function it must be something like click: function(){}, scope: this
+ // which means that there might be multiple event listeners with shared options
+ if (Ext.isFunction(value)) {
+ // shared options
+ args = [element, key, value, config.scope, config];
+ } else {
+ // if its not a function, it must be an object like click: {fn: function(){}, scope: this}
+ args = [element, key, value.fn, value.scope, value];
+ }
+
+ if (isRemove === true) {
+ me.removeListener.apply(this, args);
+ } else {
+ me.addListener.apply(me, args);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Normalize cross browser event differences
+ * @private
+ * @param {Object} eventName The event name
+ * @param {Object} fn The function to execute
+ * @return {Object} The new event name/function
+ */
+ normalizeEvent: function(eventName, fn){
+ if (/mouseenter|mouseleave/.test(eventName) && !Ext.supports.MouseEnterLeave) {
+ if (fn) {
+ fn = Ext.Function.createInterceptor(fn, this.contains, this);
+ }
+ eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
+ } else if (eventName == 'mousewheel' && !Ext.supports.MouseWheel && !Ext.isOpera){
+ eventName = 'DOMMouseScroll';
+ }
+ return {
+ eventName: eventName,
+ fn: fn
+ };
+ },
+
+ /**
+ * Checks whether the event's relatedTarget is contained inside (or <b>is</b>) the element.
+ * @private
+ * @param {Object} event
+ */
+ contains: function(event){
+ var parent = event.browserEvent.currentTarget,
+ child = this.getRelatedTarget(event);
+
+ if (parent && parent.firstChild) {
+ while (child) {
+ if (child === parent) {
+ return false;
+ }
+ child = child.parentNode;
+ if (child && (child.nodeType != 1)) {
+ child = null;
+ }
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
+ * use {@link Ext.core.Element#addListener} directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The html element or id to assign the event handler to.
+ * @param {String} eventName The name of the event to listen for.
+ * @param {Function} handler The handler function the event invokes. This function is passed
+ * the following parameters:<ul>
+ * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
+ * <li>t : Element<div class="sub-desc">The {@link Ext.core.Element Element} which was the target of the event.
+ * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
+ * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
+ * @param {Object} options (optional) An object containing handler configuration properties.
+ * This may contain any of the following properties:<ul>
+ * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
+ * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
+ * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
+ * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
+ * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
+ * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
+ * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
+ * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
+ * </ul><br>
+ * <p>See {@link Ext.core.Element#addListener} for examples of how to use these options.</p>
+ */
+ addListener: function(element, eventName, fn, scope, options){
+ // Check if we've been passed a "config style" event.
+ if (Ext.isObject(eventName)) {
+ this.prepareListenerConfig(element, eventName);
+ return;
+ }
+
+ var dom = Ext.getDom(element),
+ bind,
+ wrap;
+
+ if (!dom){
+ Ext.Error.raise({
+ sourceClass: 'Ext.EventManager',
+ sourceMethod: 'addListener',
+ targetElement: element,
+ eventName: eventName,
+ msg: 'Error adding "' + eventName + '\" listener for nonexistent element "' + element + '"'
+ });
+ }
+ if (!fn) {
+ Ext.Error.raise({
+ sourceClass: 'Ext.EventManager',
+ sourceMethod: 'addListener',
+ targetElement: element,
+ eventName: eventName,
+ msg: 'Error adding "' + eventName + '\" listener. The handler function is undefined.'
+ });
+ }
+
+ // create the wrapper function
+ options = options || {};
+
+ bind = this.normalizeEvent(eventName, fn);
+ wrap = this.createListenerWrap(dom, eventName, bind.fn, scope, options);
+
+
+ if (dom.attachEvent) {
+ dom.attachEvent('on' + bind.eventName, wrap);
+ } else {
+ dom.addEventListener(bind.eventName, wrap, options.capture || false);
+ }
+
+ if (dom == document && eventName == 'mousedown') {
+ this.stoppedMouseDownEvent.addListener(wrap);
+ }
+
+ // add all required data into the event cache
+ this.getEventListenerCache(dom, eventName).push({
+ fn: fn,
+ wrap: wrap,
+ scope: scope
+ });
+ },
+
+ /**
+ * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
+ * you will use {@link Ext.core.Element#removeListener} directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The id or html element from which to remove the listener.
+ * @param {String} eventName The name of the event.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ */
+ removeListener : function(element, eventName, fn, scope) {
+ // handle our listener config object syntax
+ if (Ext.isObject(eventName)) {
+ this.prepareListenerConfig(element, eventName, true);
+ return;
+ }
+
+ var dom = Ext.getDom(element),
+ cache = this.getEventListenerCache(dom, eventName),
+ bindName = this.normalizeEvent(eventName).eventName,
+ i = cache.length, j,
+ listener, wrap, tasks;
+
+
+ while (i--) {
+ listener = cache[i];
+
+ if (listener && (!fn || listener.fn == fn) && (!scope || listener.scope === scope)) {
+ wrap = listener.wrap;
+
+ // clear buffered calls
+ if (wrap.task) {
+ clearTimeout(wrap.task);
+ delete wrap.task;
+ }
+
+ // clear delayed calls
+ j = wrap.tasks && wrap.tasks.length;
+ if (j) {
+ while (j--) {
+ clearTimeout(wrap.tasks[j]);
+ }
+ delete wrap.tasks;
+ }
+
+ if (dom.detachEvent) {
+ dom.detachEvent('on' + bindName, wrap);
+ } else {
+ dom.removeEventListener(bindName, wrap, false);
+ }
+
+ if (wrap && dom == document && eventName == 'mousedown') {
+ this.stoppedMouseDownEvent.removeListener(wrap);
+ }
+
+ // remove listener from cache
+ cache.splice(i, 1);
+ }
+ }
+ },
+
+ /**
+ * Removes all event handers from an element. Typically you will use {@link Ext.core.Element#removeAllListeners}
+ * directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
+ */
+ removeAll : function(element){
+ var dom = Ext.getDom(element),
+ cache, ev;
+ if (!dom) {
+ return;
+ }
+ cache = this.getElementEventCache(dom);
+
+ for (ev in cache) {
+ if (cache.hasOwnProperty(ev)) {
+ this.removeListener(dom, ev);
+ }
+ }
+ Ext.cache[dom.id].events = {};
+ },
+
+ /**
+ * Recursively removes all previous added listeners from an element and its children. Typically you will use {@link Ext.core.Element#purgeAllListeners}
+ * directly on an Element in favor of calling this version.
+ * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
+ * @param {String} eventName (optional) The name of the event.
+ */
+ purgeElement : function(element, eventName) {
+ var dom = Ext.getDom(element),
+ i = 0, len;
+
+ if(eventName) {
+ this.removeListener(dom, eventName);
+ }
+ else {
+ this.removeAll(dom);
+ }
+
+ if(dom && dom.childNodes) {
+ for(len = element.childNodes.length; i < len; i++) {
+ this.purgeElement(element.childNodes[i], eventName);
+ }
+ }
+ },
+
+ /**
+ * Create the wrapper function for the event
+ * @private
+ * @param {HTMLElement} dom The dom element
+ * @param {String} ename The event name
+ * @param {Function} fn The function to execute
+ * @param {Object} scope The scope to execute callback in
+ * @param {Object} o The options
+ */
+ createListenerWrap : function(dom, ename, fn, scope, options) {
+ options = !Ext.isObject(options) ? {} : options;
+
+ var f = ['if(!Ext) {return;}'],
+ gen;
+
+ if(options.buffer || options.delay || options.freezeEvent) {
+ f.push('e = new Ext.EventObjectImpl(e, ' + (options.freezeEvent ? 'true' : 'false' ) + ');');
+ } else {
+ f.push('e = Ext.EventObject.setEvent(e);');
+ }
+
+ if (options.delegate) {
+ f.push('var t = e.getTarget("' + options.delegate + '", this);');
+ f.push('if(!t) {return;}');
+ } else {
+ f.push('var t = e.target;');
+ }
+
+ if (options.target) {
+ f.push('if(e.target !== options.target) {return;}');
+ }
+
+ if(options.stopEvent) {
+ f.push('e.stopEvent();');
+ } else {
+ if(options.preventDefault) {
+ f.push('e.preventDefault();');
+ }
+ if(options.stopPropagation) {
+ f.push('e.stopPropagation();');
+ }
+ }
+
+ if(options.normalized === false) {
+ f.push('e = e.browserEvent;');
+ }
+
+ if(options.buffer) {
+ f.push('(wrap.task && clearTimeout(wrap.task));');
+ f.push('wrap.task = setTimeout(function(){');
+ }
+
+ if(options.delay) {
+ f.push('wrap.tasks = wrap.tasks || [];');
+ f.push('wrap.tasks.push(setTimeout(function(){');
+ }
+
+ // finally call the actual handler fn
+ f.push('fn.call(scope || dom, e, t, options);');
+
+ if(options.single) {
+ f.push('Ext.EventManager.removeListener(dom, ename, fn, scope);');
+ }
+
+ if(options.delay) {
+ f.push('}, ' + options.delay + '));');
+ }
+
+ if(options.buffer) {
+ f.push('}, ' + options.buffer + ');');
+ }
+
+ gen = Ext.functionFactory('e', 'options', 'fn', 'scope', 'ename', 'dom', 'wrap', 'args', f.join('\n'));
+
+ return function wrap(e, args) {
+ gen.call(dom, e, options, fn, scope, ename, dom, wrap, args);
+ };
+ },
+
+ /**
+ * Get the event cache for a particular element for a particular event
+ * @private
+ * @param {HTMLElement} element The element
+ * @param {Object} eventName The event name
+ * @return {Array} The events for the element
+ */
+ getEventListenerCache : function(element, eventName) {
+ var eventCache = this.getElementEventCache(element);
+ return eventCache[eventName] || (eventCache[eventName] = []);
+ },
+
+ /**
+ * Gets the event cache for the object
+ * @private
+ * @param {HTMLElement} element The element
+ * @return {Object} The event cache for the object
+ */
+ getElementEventCache : function(element) {
+ var elementCache = Ext.cache[this.getId(element)];
+ return elementCache.events || (elementCache.events = {});
+ },
+
+ // --------------------- utility methods ---------------------
+ mouseLeaveRe: /(mouseout|mouseleave)/,
+ mouseEnterRe: /(mouseover|mouseenter)/,
+
+ /**
+ * Stop the event (preventDefault and stopPropagation)
+ * @param {Event} The event to stop
+ */
+ stopEvent: function(event) {
+ this.stopPropagation(event);
+ this.preventDefault(event);
+ },
+
+ /**
+ * Cancels bubbling of the event.
+ * @param {Event} The event to stop bubbling.
+ */
+ stopPropagation: function(event) {
+ event = event.browserEvent || event;
+ if (event.stopPropagation) {
+ event.stopPropagation();
+ } else {
+ event.cancelBubble = true;
+ }
+ },
+
+ /**
+ * Prevents the browsers default handling of the event.
+ * @param {Event} The event to prevent the default
+ */
+ preventDefault: function(event) {
+ event = event.browserEvent || event;
+ if (event.preventDefault) {
+ event.preventDefault();
+ } else {
+ event.returnValue = false;
+ // Some keys events require setting the keyCode to -1 to be prevented
+ try {
+ // all ctrl + X and F1 -> F12
+ if (event.ctrlKey || event.keyCode > 111 && event.keyCode < 124) {
+ event.keyCode = -1;
+ }
+ } catch (e) {
+ // see this outdated document http://support.microsoft.com/kb/934364/en-us for more info
+ }
+ }
+ },
+
+ /**
+ * Gets the related target from the event.
+ * @param {Object} event The event
+ * @return {HTMLElement} The related target.
+ */
+ getRelatedTarget: function(event) {
+ event = event.browserEvent || event;
+ var target = event.relatedTarget;
+ if (!target) {
+ if (this.mouseLeaveRe.test(event.type)) {
+ target = event.toElement;
+ } else if (this.mouseEnterRe.test(event.type)) {
+ target = event.fromElement;
+ }
+ }
+ return this.resolveTextNode(target);
+ },
+
+ /**
+ * Gets the x coordinate from the event
+ * @param {Object} event The event
+ * @return {Number} The x coordinate
+ */
+ getPageX: function(event) {
+ return this.getXY(event)[0];
+ },
+
+ /**
+ * Gets the y coordinate from the event
+ * @param {Object} event The event
+ * @return {Number} The y coordinate
+ */
+ getPageY: function(event) {
+ return this.getXY(event)[1];
+ },
+
+ /**
+ * Gets the x & ycoordinate from the event
+ * @param {Object} event The event
+ * @return {Array} The x/y coordinate
+ */
+ getPageXY: function(event) {
+ event = event.browserEvent || event;
+ var x = event.pageX,
+ y = event.pageY,
+ doc = document.documentElement,
+ body = document.body;
+
+ // pageX/pageY not available (undefined, not null), use clientX/clientY instead
+ if (!x && x !== 0) {
+ x = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
+ y = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
+ }
+ return [x, y];
+ },
+
+ /**
+ * Gets the target of the event.
+ * @param {Object} event The event
+ * @return {HTMLElement} target
+ */
+ getTarget: function(event) {
+ event = event.browserEvent || event;
+ return this.resolveTextNode(event.target || event.srcElement);
+ },
+
+ /**
+ * Resolve any text nodes accounting for browser differences.
+ * @private
+ * @param {HTMLElement} node The node
+ * @return {HTMLElement} The resolved node
+ */
+ // technically no need to browser sniff this, however it makes no sense to check this every time, for every event, whether the string is equal.
+ resolveTextNode: Ext.isGecko ?
+ function(node) {
+ if (!node) {
+ return;
+ }
+ // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
+ var s = HTMLElement.prototype.toString.call(node);
+ if (s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]') {
+ return;
+ }
+ return node.nodeType == 3 ? node.parentNode: node;
+ }: function(node) {
+ return node && node.nodeType == 3 ? node.parentNode: node;
+ },
+
+ // --------------------- custom event binding ---------------------
+
+ // Keep track of the current width/height
+ curWidth: 0,
+ curHeight: 0,
+
+ /**
+ * Adds a listener to be notified when the browser window is resized and provides resize event buffering (100 milliseconds),
+ * passes new viewport width and height to handlers.
+ * @param {Function} fn The handler function the window resize event invokes.
+ * @param {Object} scope The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
+ * @param {boolean} options Options object as passed to {@link Ext.core.Element#addListener}
+ */
+ onWindowResize: function(fn, scope, options){
+ var resize = this.resizeEvent;
+ if(!resize){
+ this.resizeEvent = resize = new Ext.util.Event();
+ this.on(window, 'resize', this.fireResize, this, {buffer: 100});
+ }
+ resize.addListener(fn, scope, options);
+ },
+
+ /**
+ * Fire the resize event.
+ * @private
+ */
+ fireResize: function(){
+ var me = this,
+ w = Ext.core.Element.getViewWidth(),
+ h = Ext.core.Element.getViewHeight();
+
+ //whacky problem in IE where the resize event will sometimes fire even though the w/h are the same.
+ if(me.curHeight != h || me.curWidth != w){
+ me.curHeight = h;
+ me.curWidth = w;
+ me.resizeEvent.fire(w, h);
+ }
+ },
+
+ /**
+ * Removes the passed window resize listener.
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope The scope of handler
+ */
+ removeResizeListener: function(fn, scope){
+ if (this.resizeEvent) {
+ this.resizeEvent.removeListener(fn, scope);
+ }
+ },
+
+ onWindowUnload: function() {
+ var unload = this.unloadEvent;
+ if (!unload) {
+ this.unloadEvent = unload = new Ext.util.Event();
+ this.addListener(window, 'unload', this.fireUnload, this);
+ }
+ },
+
+ /**
+ * Fires the unload event for items bound with onWindowUnload
+ * @private
+ */
+ fireUnload: function() {
+ // wrap in a try catch, could have some problems during unload
+ try {
+ this.removeUnloadListener();
+ // Work around FF3 remembering the last scroll position when refreshing the grid and then losing grid view
+ if (Ext.isGecko3) {
+ var gridviews = Ext.ComponentQuery.query('gridview'),
+ i = 0,
+ ln = gridviews.length;
+ for (; i < ln; i++) {
+ gridviews[i].scrollToTop();
+ }
+ }
+ // Purge all elements in the cache
+ var el,
+ cache = Ext.cache;
+ for (el in cache) {
+ if (cache.hasOwnProperty(el)) {
+ Ext.EventManager.removeAll(el);
+ }
+ }
+ } catch(e) {
+ }
+ },
+
+ /**
+ * Removes the passed window unload listener.
+ * @param {Function} fn The method the event invokes
+ * @param {Object} scope The scope of handler
+ */
+ removeUnloadListener: function(){
+ if (this.unloadEvent) {
+ this.removeListener(window, 'unload', this.fireUnload);
+ }
+ },
+
+ /**
+ * note 1: IE fires ONLY the keydown event on specialkey autorepeat
+ * note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
+ * (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
+ * @private
+ */
+ useKeyDown: Ext.isWebKit ?
+ parseInt(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1], 10) >= 525 :
+ !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera),
+
+ /**
+ * Indicates which event to use for getting key presses.
+ * @return {String} The appropriate event name.
+ */
+ getKeyEvent: function(){
+ return this.useKeyDown ? 'keydown' : 'keypress';
+ }
+};
+
+/**
+ * Alias for {@link Ext.Loader#onReady Ext.Loader.onReady} with withDomReady set to true
+ * @member Ext
+ * @method onReady
+ */
+Ext.onReady = function(fn, scope, options) {
+ Ext.Loader.onReady(fn, scope, true, options);
+};
+
+/**
+ * Alias for {@link Ext.EventManager#onDocumentReady Ext.EventManager.onDocumentReady}
+ * @member Ext
+ * @method onDocumentReady
+ */
+Ext.onDocumentReady = Ext.EventManager.onDocumentReady;
+
+/**
+ * Alias for {@link Ext.EventManager#addListener Ext.EventManager.addListener}
+ * @member Ext.EventManager
+ * @method on
+ */
+Ext.EventManager.on = Ext.EventManager.addListener;
+
+/**
+ * Alias for {@link Ext.EventManager#removeListener Ext.EventManager.removeListener}
+ * @member Ext.EventManager
+ * @method un
+ */
+Ext.EventManager.un = Ext.EventManager.removeListener;
+
+(function(){
+ var initExtCss = function() {
+ // find the body element
+ var bd = document.body || document.getElementsByTagName('body')[0],
+ baseCSSPrefix = Ext.baseCSSPrefix,
+ cls = [],
+ htmlCls = [],
+ html;
+
+ if (!bd) {
+ return false;
+ }
+
+ html = bd.parentNode;
+
+ //Let's keep this human readable!
+ if (Ext.isIE) {
+ cls.push(baseCSSPrefix + 'ie');
+ }
+ if (Ext.isIE6) {
+ cls.push(baseCSSPrefix + 'ie6');
+ }
+ if (Ext.isIE7) {
+ cls.push(baseCSSPrefix + 'ie7');
+ }
+ if (Ext.isIE8) {
+ cls.push(baseCSSPrefix + 'ie8');
+ }
+ if (Ext.isIE9) {
+ cls.push(baseCSSPrefix + 'ie9');
+ }
+ if (Ext.isGecko) {
+ cls.push(baseCSSPrefix + 'gecko');
+ }
+ if (Ext.isGecko3) {
+ cls.push(baseCSSPrefix + 'gecko3');
+ }
+ if (Ext.isGecko4) {
+ cls.push(baseCSSPrefix + 'gecko4');
+ }
+ if (Ext.isOpera) {
+ cls.push(baseCSSPrefix + 'opera');
+ }
+ if (Ext.isWebKit) {
+ cls.push(baseCSSPrefix + 'webkit');
+ }
+ if (Ext.isSafari) {
+ cls.push(baseCSSPrefix + 'safari');
+ }
+ if (Ext.isSafari2) {
+ cls.push(baseCSSPrefix + 'safari2');
+ }
+ if (Ext.isSafari3) {
+ cls.push(baseCSSPrefix + 'safari3');
+ }
+ if (Ext.isSafari4) {
+ cls.push(baseCSSPrefix + 'safari4');
+ }
+ if (Ext.isChrome) {
+ cls.push(baseCSSPrefix + 'chrome');
+ }
+ if (Ext.isMac) {
+ cls.push(baseCSSPrefix + 'mac');
+ }
+ if (Ext.isLinux) {
+ cls.push(baseCSSPrefix + 'linux');
+ }
+ if (!Ext.supports.CSS3BorderRadius) {
+ cls.push(baseCSSPrefix + 'nbr');
+ }
+ if (!Ext.supports.CSS3LinearGradient) {
+ cls.push(baseCSSPrefix + 'nlg');
+ }
+ if (!Ext.scopeResetCSS) {
+ cls.push(baseCSSPrefix + 'reset');
+ }
+
+ // add to the parent to allow for selectors x-strict x-border-box, also set the isBorderBox property correctly
+ if (html) {
+ if (Ext.isStrict && (Ext.isIE6 || Ext.isIE7)) {
+ Ext.isBorderBox = false;
+ }
+ else {
+ Ext.isBorderBox = true;
+ }
+
+ htmlCls.push(baseCSSPrefix + (Ext.isBorderBox ? 'border-box' : 'strict'));
+ if (!Ext.isStrict) {
+ htmlCls.push(baseCSSPrefix + 'quirks');
+ if (Ext.isIE && !Ext.isStrict) {
+ Ext.isIEQuirks = true;
+ }
+ }
+ Ext.fly(html, '_internal').addCls(htmlCls);
+ }
+
+ Ext.fly(bd, '_internal').addCls(cls);
+ return true;
+ };
+
+ Ext.onReady(initExtCss);
+})();
+
+/**
+ * @class Ext.EventObject
+
+Just as {@link Ext.core.Element} wraps around a native DOM node, Ext.EventObject
+wraps the browser's native event-object normalizing cross-browser differences,
+such as which mouse button is clicked, keys pressed, mechanisms to stop
+event-propagation along with a method to prevent default actions from taking place.
+
+For example:
+
+ function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
+ e.preventDefault();
+ var target = e.getTarget(); // same as t (the target HTMLElement)
+ ...
+ }
+
+ var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.core.Element}
+ myDiv.on( // 'on' is shorthand for addListener
+ "click", // perform an action on click of myDiv
+ handleClick // reference to the action handler
+ );
+
+ // other methods to do the same:
+ Ext.EventManager.on("myDiv", 'click', handleClick);
+ Ext.EventManager.addListener("myDiv", 'click', handleClick);
+
+ * @singleton
+ * @markdown
+ */
+Ext.define('Ext.EventObjectImpl', {
+ uses: ['Ext.util.Point'],
+
+ /** Key constant @type Number */
+ BACKSPACE: 8,
+ /** Key constant @type Number */
+ TAB: 9,
+ /** Key constant @type Number */
+ NUM_CENTER: 12,
+ /** Key constant @type Number */
+ ENTER: 13,
+ /** Key constant @type Number */
+ RETURN: 13,
+ /** Key constant @type Number */
+ SHIFT: 16,
+ /** Key constant @type Number */
+ CTRL: 17,
+ /** Key constant @type Number */
+ ALT: 18,
+ /** Key constant @type Number */
+ PAUSE: 19,
+ /** Key constant @type Number */
+ CAPS_LOCK: 20,
+ /** Key constant @type Number */
+ ESC: 27,
+ /** Key constant @type Number */
+ SPACE: 32,
+ /** Key constant @type Number */
+ PAGE_UP: 33,
+ /** Key constant @type Number */
+ PAGE_DOWN: 34,
+ /** Key constant @type Number */
+ END: 35,
+ /** Key constant @type Number */
+ HOME: 36,
+ /** Key constant @type Number */
+ LEFT: 37,
+ /** Key constant @type Number */
+ UP: 38,
+ /** Key constant @type Number */
+ RIGHT: 39,
+ /** Key constant @type Number */
+ DOWN: 40,
+ /** Key constant @type Number */
+ PRINT_SCREEN: 44,
+ /** Key constant @type Number */
+ INSERT: 45,
+ /** Key constant @type Number */
+ DELETE: 46,
+ /** Key constant @type Number */
+ ZERO: 48,
+ /** Key constant @type Number */
+ ONE: 49,
+ /** Key constant @type Number */
+ TWO: 50,
+ /** Key constant @type Number */
+ THREE: 51,
+ /** Key constant @type Number */
+ FOUR: 52,
+ /** Key constant @type Number */
+ FIVE: 53,
+ /** Key constant @type Number */
+ SIX: 54,
+ /** Key constant @type Number */
+ SEVEN: 55,
+ /** Key constant @type Number */
+ EIGHT: 56,
+ /** Key constant @type Number */
+ NINE: 57,
+ /** Key constant @type Number */
+ A: 65,
+ /** Key constant @type Number */
+ B: 66,
+ /** Key constant @type Number */
+ C: 67,
+ /** Key constant @type Number */
+ D: 68,
+ /** Key constant @type Number */
+ E: 69,
+ /** Key constant @type Number */
+ F: 70,
+ /** Key constant @type Number */
+ G: 71,
+ /** Key constant @type Number */
+ H: 72,
+ /** Key constant @type Number */
+ I: 73,
+ /** Key constant @type Number */
+ J: 74,
+ /** Key constant @type Number */
+ K: 75,
+ /** Key constant @type Number */
+ L: 76,
+ /** Key constant @type Number */
+ M: 77,
+ /** Key constant @type Number */
+ N: 78,
+ /** Key constant @type Number */
+ O: 79,
+ /** Key constant @type Number */
+ P: 80,
+ /** Key constant @type Number */
+ Q: 81,
+ /** Key constant @type Number */
+ R: 82,
+ /** Key constant @type Number */
+ S: 83,
+ /** Key constant @type Number */
+ T: 84,
+ /** Key constant @type Number */
+ U: 85,
+ /** Key constant @type Number */
+ V: 86,
+ /** Key constant @type Number */
+ W: 87,
+ /** Key constant @type Number */
+ X: 88,
+ /** Key constant @type Number */
+ Y: 89,
+ /** Key constant @type Number */
+ Z: 90,
+ /** Key constant @type Number */
+ CONTEXT_MENU: 93,
+ /** Key constant @type Number */
+ NUM_ZERO: 96,
+ /** Key constant @type Number */
+ NUM_ONE: 97,
+ /** Key constant @type Number */
+ NUM_TWO: 98,
+ /** Key constant @type Number */
+ NUM_THREE: 99,
+ /** Key constant @type Number */
+ NUM_FOUR: 100,
+ /** Key constant @type Number */
+ NUM_FIVE: 101,
+ /** Key constant @type Number */
+ NUM_SIX: 102,
+ /** Key constant @type Number */
+ NUM_SEVEN: 103,
+ /** Key constant @type Number */
+ NUM_EIGHT: 104,
+ /** Key constant @type Number */
+ NUM_NINE: 105,
+ /** Key constant @type Number */
+ NUM_MULTIPLY: 106,
+ /** Key constant @type Number */
+ NUM_PLUS: 107,
+ /** Key constant @type Number */
+ NUM_MINUS: 109,
+ /** Key constant @type Number */
+ NUM_PERIOD: 110,
+ /** Key constant @type Number */
+ NUM_DIVISION: 111,
+ /** Key constant @type Number */
+ F1: 112,
+ /** Key constant @type Number */
+ F2: 113,
+ /** Key constant @type Number */
+ F3: 114,
+ /** Key constant @type Number */
+ F4: 115,
+ /** Key constant @type Number */
+ F5: 116,
+ /** Key constant @type Number */
+ F6: 117,
+ /** Key constant @type Number */
+ F7: 118,
+ /** Key constant @type Number */
+ F8: 119,
+ /** Key constant @type Number */
+ F9: 120,
+ /** Key constant @type Number */
+ F10: 121,
+ /** Key constant @type Number */
+ F11: 122,
+ /** Key constant @type Number */
+ F12: 123,
+
+ /**
+ * Simple click regex
+ * @private
+ */
+ clickRe: /(dbl)?click/,
+ // safari keypress events for special keys return bad keycodes
+ safariKeys: {
+ 3: 13, // enter
+ 63234: 37, // left
+ 63235: 39, // right
+ 63232: 38, // up
+ 63233: 40, // down
+ 63276: 33, // page up
+ 63277: 34, // page down
+ 63272: 46, // delete
+ 63273: 36, // home
+ 63275: 35 // end
+ },
+ // normalize button clicks, don't see any way to feature detect this.
+ btnMap: Ext.isIE ? {
+ 1: 0,
+ 4: 1,
+ 2: 2
+ } : {
+ 0: 0,
+ 1: 1,
+ 2: 2
+ },
+
+ constructor: function(event, freezeEvent){
+ if (event) {
+ this.setEvent(event.browserEvent || event, freezeEvent);
+ }
+ },
+
+ setEvent: function(event, freezeEvent){
+ var me = this, button, options;
+
+ if (event == me || (event && event.browserEvent)) { // already wrapped
+ return event;
+ }
+ me.browserEvent = event;
+ if (event) {
+ // normalize buttons
+ button = event.button ? me.btnMap[event.button] : (event.which ? event.which - 1 : -1);
+ if (me.clickRe.test(event.type) && button == -1) {
+ button = 0;
+ }
+ options = {
+ type: event.type,
+ button: button,
+ shiftKey: event.shiftKey,
+ // mac metaKey behaves like ctrlKey
+ ctrlKey: event.ctrlKey || event.metaKey || false,
+ altKey: event.altKey,
+ // in getKey these will be normalized for the mac
+ keyCode: event.keyCode,
+ charCode: event.charCode,
+ // cache the targets for the delayed and or buffered events
+ target: Ext.EventManager.getTarget(event),
+ relatedTarget: Ext.EventManager.getRelatedTarget(event),
+ currentTarget: event.currentTarget,
+ xy: (freezeEvent ? me.getXY() : null)
+ };
+ } else {
+ options = {
+ button: -1,
+ shiftKey: false,
+ ctrlKey: false,
+ altKey: false,
+ keyCode: 0,
+ charCode: 0,
+ target: null,
+ xy: [0, 0]
+ };
+ }
+ Ext.apply(me, options);
+ return me;
+ },
+
+ /**
+ * Stop the event (preventDefault and stopPropagation)
+ */
+ stopEvent: function(){
+ this.stopPropagation();
+ this.preventDefault();
+ },
+
+ /**
+ * Prevents the browsers default handling of the event.
+ */
+ preventDefault: function(){
+ if (this.browserEvent) {
+ Ext.EventManager.preventDefault(this.browserEvent);
+ }
+ },
+
+ /**
+ * Cancels bubbling of the event.
+ */
+ stopPropagation: function(){
+ var browserEvent = this.browserEvent;
+
+ if (browserEvent) {
+ if (browserEvent.type == 'mousedown') {
+ Ext.EventManager.stoppedMouseDownEvent.fire(this);
+ }
+ Ext.EventManager.stopPropagation(browserEvent);
+ }
+ },
+
+ /**
+ * Gets the character code for the event.
+ * @return {Number}
+ */
+ getCharCode: function(){
+ return this.charCode || this.keyCode;
+ },
+
+ /**
+ * Returns a normalized keyCode for the event.
+ * @return {Number} The key code
+ */
+ getKey: function(){
+ return this.normalizeKey(this.keyCode || this.charCode);
+ },
+
+ /**
+ * Normalize key codes across browsers
+ * @private
+ * @param {Number} key The key code
+ * @return {Number} The normalized code
+ */
+ normalizeKey: function(key){
+ // can't feature detect this
+ return Ext.isWebKit ? (this.safariKeys[key] || key) : key;
+ },
+
+ /**
+ * Gets the x coordinate of the event.
+ * @return {Number}
+ * @deprecated 4.0 Replaced by {@link #getX}
+ */
+ getPageX: function(){
+ return this.getX();
+ },
+
+ /**
+ * Gets the y coordinate of the event.
+ * @return {Number}
+ * @deprecated 4.0 Replaced by {@link #getY}
+ */
+ getPageY: function(){
+ return this.getY();
+ },
+
+ /**
+ * Gets the x coordinate of the event.
+ * @return {Number}
+ */
+ getX: function() {
+ return this.getXY()[0];
+ },
+
+ /**
+ * Gets the y coordinate of the event.
+ * @return {Number}
+ */
+ getY: function() {
+ return this.getXY()[1];
+ },
+
+ /**
+ * Gets the page coordinates of the event.
+ * @return {Array} The xy values like [x, y]
+ */
+ getXY: function() {
+ if (!this.xy) {
+ // same for XY
+ this.xy = Ext.EventManager.getPageXY(this.browserEvent);
+ }
+ return this.xy;
+ },
+
+ /**
+ * Gets the target for the event.
+ * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
+ * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
+ * @return {HTMLelement}
+ */
+ getTarget : function(selector, maxDepth, returnEl){
+ if (selector) {
+ return Ext.fly(this.target).findParent(selector, maxDepth, returnEl);
+ }
+ return returnEl ? Ext.get(this.target) : this.target;
+ },
+
+ /**
+ * Gets the related target.
+ * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
+ * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 10 || document.body)
+ * @param {Boolean} returnEl (optional) True to return a Ext.core.Element object instead of DOM node
+ * @return {HTMLElement}
+ */
+ getRelatedTarget : function(selector, maxDepth, returnEl){
+ if (selector) {
+ return Ext.fly(this.relatedTarget).findParent(selector, maxDepth, returnEl);
+ }
+ return returnEl ? Ext.get(this.relatedTarget) : this.relatedTarget;
+ },
+
+ /**
+ * Normalizes mouse wheel delta across browsers
+ * @return {Number} The delta
+ */
+ getWheelDelta : function(){
+ var event = this.browserEvent,
+ delta = 0;
+
+ if (event.wheelDelta) { /* IE/Opera. */
+ delta = event.wheelDelta / 120;
+ } else if (event.detail){ /* Mozilla case. */
+ delta = -event.detail / 3;
+ }
+ return delta;
+ },
+
+ /**
+ * Returns true if the target of this event is a child of el. Unless the allowEl parameter is set, it will return false if if the target is el.
+ * Example usage:<pre><code>
+// Handle click on any child of an element
+Ext.getBody().on('click', function(e){
+ if(e.within('some-el')){
+ alert('Clicked on a child of some-el!');
+ }
+});
+
+// Handle click directly on an element, ignoring clicks on child nodes
+Ext.getBody().on('click', function(e,t){
+ if((t.id == 'some-el') && !e.within(t, true)){
+ alert('Clicked directly on some-el!');
+ }
+});
+</code></pre>
+ * @param {Mixed} el The id, DOM element or Ext.core.Element to check
+ * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
+ * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
+ * @return {Boolean}
+ */
+ within : function(el, related, allowEl){
+ if(el){
+ var t = related ? this.getRelatedTarget() : this.getTarget(),
+ result;
+
+ if (t) {
+ result = Ext.fly(el).contains(t);
+ if (!result && allowEl) {
+ result = t == Ext.getDom(el);
+ }
+ return result;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Checks if the key pressed was a "navigation" key
+ * @return {Boolean} True if the press is a navigation keypress
+ */
+ isNavKeyPress : function(){
+ var me = this,
+ k = this.normalizeKey(me.keyCode);
+
+ return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
+ k == me.RETURN ||
+ k == me.TAB ||
+ k == me.ESC;
+ },
+
+ /**
+ * Checks if the key pressed was a "special" key
+ * @return {Boolean} True if the press is a special keypress
+ */
+ isSpecialKey : function(){
+ var k = this.normalizeKey(this.keyCode);
+ return (this.type == 'keypress' && this.ctrlKey) ||
+ this.isNavKeyPress() ||
+ (k == this.BACKSPACE) || // Backspace
+ (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
+ (k >= 44 && k <= 46); // Print Screen, Insert, Delete
+ },
+
+ /**
+ * Returns a point object that consists of the object coordinates.
+ * @return {Ext.util.Point} point
+ */
+ getPoint : function(){
+ var xy = this.getXY();
+ return Ext.create('Ext.util.Point', xy[0], xy[1]);
+ },
+
+ /**
+ * Returns true if the control, meta, shift or alt key was pressed during this event.
+ * @return {Boolean}
+ */
+ hasModifier : function(){
+ return this.ctrlKey || this.altKey || this.shiftKey || this.metaKey;
+ },
+
+ /**
+ * Injects a DOM event using the data in this object and (optionally) a new target.
+ * This is a low-level technique and not likely to be used by application code. The
+ * currently supported event types are:
+ * <p><b>HTMLEvents</b></p>
+ * <ul>
+ * <li>load</li>
+ * <li>unload</li>
+ * <li>select</li>
+ * <li>change</li>
+ * <li>submit</li>
+ * <li>reset</li>
+ * <li>resize</li>
+ * <li>scroll</li>
+ * </ul>
+ * <p><b>MouseEvents</b></p>
+ * <ul>
+ * <li>click</li>
+ * <li>dblclick</li>
+ * <li>mousedown</li>
+ * <li>mouseup</li>
+ * <li>mouseover</li>
+ * <li>mousemove</li>
+ * <li>mouseout</li>
+ * </ul>
+ * <p><b>UIEvents</b></p>
+ * <ul>
+ * <li>focusin</li>
+ * <li>focusout</li>
+ * <li>activate</li>
+ * <li>focus</li>
+ * <li>blur</li>
+ * </ul>
+ * @param {Element/HTMLElement} target If specified, the target for the event. This
+ * is likely to be used when relaying a DOM event. If not specified, {@link #getTarget}
+ * is used to determine the target.
+ */
+ injectEvent: function () {
+ var API,
+ dispatchers = {}; // keyed by event type (e.g., 'mousedown')
+
+ // Good reference: http://developer.yahoo.com/yui/docs/UserAction.js.html
+
+ // IE9 has createEvent, but this code causes major problems with htmleditor (it
+ // blocks all mouse events and maybe more). TODO
+
+ if (!Ext.isIE && document.createEvent) { // if (DOM compliant)
+ API = {
+ createHtmlEvent: function (doc, type, bubbles, cancelable) {
+ var event = doc.createEvent('HTMLEvents');
+
+ event.initEvent(type, bubbles, cancelable);
+ return event;
+ },
+
+ createMouseEvent: function (doc, type, bubbles, cancelable, detail,
+ clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
+ button, relatedTarget) {
+ var event = doc.createEvent('MouseEvents'),
+ view = doc.defaultView || window;
+
+ if (event.initMouseEvent) {
+ event.initMouseEvent(type, bubbles, cancelable, view, detail,
+ clientX, clientY, clientX, clientY, ctrlKey, altKey,
+ shiftKey, metaKey, button, relatedTarget);
+ } else { // old Safari
+ event = doc.createEvent('UIEvents');
+ event.initEvent(type, bubbles, cancelable);
+ event.view = view;
+ event.detail = detail;
+ event.screenX = clientX;
+ event.screenY = clientY;
+ event.clientX = clientX;
+ event.clientY = clientY;
+ event.ctrlKey = ctrlKey;
+ event.altKey = altKey;
+ event.metaKey = metaKey;
+ event.shiftKey = shiftKey;
+ event.button = button;
+ event.relatedTarget = relatedTarget;
+ }
+
+ return event;
+ },
+
+ createUIEvent: function (doc, type, bubbles, cancelable, detail) {
+ var event = doc.createEvent('UIEvents'),
+ view = doc.defaultView || window;
+
+ event.initUIEvent(type, bubbles, cancelable, view, detail);
+ return event;
+ },
+
+ fireEvent: function (target, type, event) {
+ target.dispatchEvent(event);
+ },
+
+ fixTarget: function (target) {
+ // Safari3 doesn't have window.dispatchEvent()
+ if (target == window && !target.dispatchEvent) {
+ return document;
+ }
+
+ return target;
+ }
+ }
+ } else if (document.createEventObject) { // else if (IE)
+ var crazyIEButtons = { 0: 1, 1: 4, 2: 2 };
+
+ API = {
+ createHtmlEvent: function (doc, type, bubbles, cancelable) {
+ var event = doc.createEventObject();
+ event.bubbles = bubbles;
+ event.cancelable = cancelable;
+ return event;
+ },
+
+ createMouseEvent: function (doc, type, bubbles, cancelable, detail,
+ clientX, clientY, ctrlKey, altKey, shiftKey, metaKey,
+ button, relatedTarget) {
+ var event = doc.createEventObject();
+ event.bubbles = bubbles;
+ event.cancelable = cancelable;
+ event.detail = detail;
+ event.screenX = clientX;
+ event.screenY = clientY;
+ event.clientX = clientX;
+ event.clientY = clientY;
+ event.ctrlKey = ctrlKey;
+ event.altKey = altKey;
+ event.shiftKey = shiftKey;
+ event.metaKey = metaKey;
+ event.button = crazyIEButtons[button] || button;
+ event.relatedTarget = relatedTarget; // cannot assign to/fromElement
+ return event;
+ },
+
+ createUIEvent: function (doc, type, bubbles, cancelable, detail) {
+ var event = doc.createEventObject();
+ event.bubbles = bubbles;
+ event.cancelable = cancelable;
+ return event;
+ },
+
+ fireEvent: function (target, type, event) {
+ target.fireEvent('on' + type, event);
+ },
+
+ fixTarget: function (target) {
+ if (target == document) {
+ // IE6,IE7 thinks window==document and doesn't have window.fireEvent()
+ // IE6,IE7 cannot properly call document.fireEvent()
+ return document.documentElement;
+ }
+
+ return target;
+ }
+ };
+ }
+
+ //----------------
+ // HTMLEvents
+
+ Ext.Object.each({
+ load: [false, false],
+ unload: [false, false],
+ select: [true, false],
+ change: [true, false],
+ submit: [true, true],
+ reset: [true, false],
+ resize: [true, false],
+ scroll: [true, false]
+ },
+ function (name, value) {
+ var bubbles = value[0], cancelable = value[1];
+ dispatchers[name] = function (targetEl, srcEvent) {
+ var e = API.createHtmlEvent(name, bubbles, cancelable);
+ API.fireEvent(targetEl, name, e);
+ };
+ });
+
+ //----------------
+ // MouseEvents
+
+ function createMouseEventDispatcher (type, detail) {
+ var cancelable = (type != 'mousemove');
+ return function (targetEl, srcEvent) {
+ var xy = srcEvent.getXY(),
+ e = API.createMouseEvent(targetEl.ownerDocument, type, true, cancelable,
+ detail, xy[0], xy[1], srcEvent.ctrlKey, srcEvent.altKey,
+ srcEvent.shiftKey, srcEvent.metaKey, srcEvent.button,
+ srcEvent.relatedTarget);
+ API.fireEvent(targetEl, type, e);
+ };
+ }
+
+ Ext.each(['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mousemove', 'mouseout'],
+ function (eventName) {
+ dispatchers[eventName] = createMouseEventDispatcher(eventName, 1);
+ });
+
+ //----------------
+ // UIEvents
+
+ Ext.Object.each({
+ focusin: [true, false],
+ focusout: [true, false],
+ activate: [true, true],
+ focus: [false, false],
+ blur: [false, false]
+ },
+ function (name, value) {
+ var bubbles = value[0], cancelable = value[1];
+ dispatchers[name] = function (targetEl, srcEvent) {
+ var e = API.createUIEvent(targetEl.ownerDocument, name, bubbles, cancelable, 1);
+ API.fireEvent(targetEl, name, e);
+ };
+ });
+
+ //---------
+ if (!API) {
+ // not even sure what ancient browsers fall into this category...
+
+ dispatchers = {}; // never mind all those we just built :P
+
+ API = {
+ fixTarget: function (t) {
+ return t;
+ }
+ };
+ }
+
+ function cannotInject (target, srcEvent) {
+ // TODO log something
+ }
+
+ return function (target) {
+ var me = this,
+ dispatcher = dispatchers[me.type] || cannotInject,
+ t = target ? (target.dom || target) : me.getTarget();
+
+ t = API.fixTarget(t);
+ dispatcher(t, me);
+ };
+ }() // call to produce method
+
+}, function() {
+
+Ext.EventObject = new Ext.EventObjectImpl();
+
+});
+
+
+/**
+ * @class Ext.core.Element
+ */
+(function(){
+ var doc = document,
+ isCSS1 = doc.compatMode == "CSS1Compat",
+ ELEMENT = Ext.core.Element,
+ fly = function(el){
+ if (!_fly) {
+ _fly = new Ext.core.Element.Flyweight();
+ }
+ _fly.dom = el;
+ return _fly;
+ }, _fly;
+
+ Ext.apply(ELEMENT, {
+ isAncestor : function(p, c) {
+ var ret = false;
+
+ p = Ext.getDom(p);
+ c = Ext.getDom(c);
+ if (p && c) {
+ if (p.contains) {
+ return p.contains(c);
+ } else if (p.compareDocumentPosition) {
+ return !!(p.compareDocumentPosition(c) & 16);
+ } else {
+ while ((c = c.parentNode)) {
+ ret = c == p || ret;
+ }
+ }
+ }
+ return ret;
+ },
+
+ getViewWidth : function(full) {
+ return full ? ELEMENT.getDocumentWidth() : ELEMENT.getViewportWidth();
+ },
+
+ getViewHeight : function(full) {
+ return full ? ELEMENT.getDocumentHeight() : ELEMENT.getViewportHeight();
+ },
+
+ getDocumentHeight: function() {
+ return Math.max(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, ELEMENT.getViewportHeight());
+ },
+
+ getDocumentWidth: function() {
+ return Math.max(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, ELEMENT.getViewportWidth());
+ },
+
+ getViewportHeight: function(){
+ return Ext.isIE ?
+ (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
+ self.innerHeight;
+ },
+
+ getViewportWidth : function() {
+ return (!Ext.isStrict && !Ext.isOpera) ? doc.body.clientWidth :
+ Ext.isIE ? doc.documentElement.clientWidth : self.innerWidth;
+ },
+
+ getY : function(el) {
+ return ELEMENT.getXY(el)[1];
+ },
+
+ getX : function(el) {
+ return ELEMENT.getXY(el)[0];
+ },
+
+ getXY : function(el) {
+ var p,
+ pe,
+ b,
+ bt,
+ bl,
+ dbd,
+ x = 0,
+ y = 0,
+ scroll,
+ hasAbsolute,
+ bd = (doc.body || doc.documentElement),
+ ret = [0,0];
+
+ el = Ext.getDom(el);
+
+ if(el != bd){
+ hasAbsolute = fly(el).isStyle("position", "absolute");
+
+ if (el.getBoundingClientRect) {
+ b = el.getBoundingClientRect();
+ scroll = fly(document).getScroll();
+ ret = [Math.round(b.left + scroll.left), Math.round(b.top + scroll.top)];
+ } else {
+ p = el;
+
+ while (p) {
+ pe = fly(p);
+ x += p.offsetLeft;
+ y += p.offsetTop;
+
+ hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
+
+ if (Ext.isGecko) {
+ y += bt = parseInt(pe.getStyle("borderTopWidth"), 10) || 0;
+ x += bl = parseInt(pe.getStyle("borderLeftWidth"), 10) || 0;
+
+ if (p != el && !pe.isStyle('overflow','visible')) {
+ x += bl;
+ y += bt;
+ }
+ }
+ p = p.offsetParent;
+ }
+
+ if (Ext.isSafari && hasAbsolute) {
+ x -= bd.offsetLeft;
+ y -= bd.offsetTop;
+ }
+
+ if (Ext.isGecko && !hasAbsolute) {
+ dbd = fly(bd);
+ x += parseInt(dbd.getStyle("borderLeftWidth"), 10) || 0;
+ y += parseInt(dbd.getStyle("borderTopWidth"), 10) || 0;
+ }
+
+ p = el.parentNode;
+ while (p && p != bd) {
+ if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
+ x -= p.scrollLeft;
+ y -= p.scrollTop;
+ }
+ p = p.parentNode;
+ }
+ ret = [x,y];
+ }
+ }
+ return ret;
+ },
+
+ setXY : function(el, xy) {
+ (el = Ext.fly(el, '_setXY')).position();
+
+ var pts = el.translatePoints(xy),
+ style = el.dom.style,
+ pos;
+
+ for (pos in pts) {
+ if (!isNaN(pts[pos])) {
+ style[pos] = pts[pos] + "px";
+ }
+ }
+ },
+
+ setX : function(el, x) {
+ ELEMENT.setXY(el, [x, false]);
+ },
+
+ setY : function(el, y) {
+ ELEMENT.setXY(el, [false, y]);
+ },
+
+ /**
+ * Serializes a DOM form into a url encoded string
+ * @param {Object} form The form
+ * @return {String} The url encoded form
+ */
+ serializeForm: function(form) {
+ var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
+ hasSubmit = false,
+ encoder = encodeURIComponent,
+ name,
+ data = '',
+ type,
+ hasValue;
+
+ Ext.each(fElements, function(element){
+ name = element.name;
+ type = element.type;
+
+ if (!element.disabled && name) {
+ if (/select-(one|multiple)/i.test(type)) {
+ Ext.each(element.options, function(opt){
+ if (opt.selected) {
+ hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
+ data += String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
+ }
+ });
+ } else if (!(/file|undefined|reset|button/i.test(type))) {
+ if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
+ data += encoder(name) + '=' + encoder(element.value) + '&';
+ hasSubmit = /submit/i.test(type);
+ }
+ }
+ }
+ });
+ return data.substr(0, data.length - 1);
+ }
+ });
+})();
+
+/**
+ * @class Ext.core.Element
+ */
+
+Ext.core.Element.addMethods({
+
+ /**
+ * 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 <i>was</i> moved
+ * back in, the function is not called.
+ * @param {Number} delay The delay <b>in milliseconds</b> 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 (<code>this</code> 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:</pre><code>
+// 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);
+</code></pre>
+ */
+ monitorMouseLeave: function(delay, handler, scope) {
+ var me = this,
+ timer,
+ listeners = {
+ mouseleave: function(e) {
+ timer = setTimeout(Ext.Function.bind(handler, scope||me, [e]), delay);
+ },
+ mouseenter: function() {
+ clearTimeout(timer);
+ },
+ freezeEvent: true
+ };
+
+ me.on(listeners);
+ return listeners;
+ },
+
+ /**
+ * Stops the specified event(s) from bubbling and optionally prevents the default action
+ * @param {String/Array} eventName an event / array of events to stop from bubbling
+ * @param {Boolean} preventDefault (optional) true to prevent the default action too
+ * @return {Ext.core.Element} this
+ */
+ swallowEvent : function(eventName, preventDefault) {
+ var me = this;
+ function fn(e) {
+ e.stopPropagation();
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ }
+
+ if (Ext.isArray(eventName)) {
+ Ext.each(eventName, function(e) {
+ me.on(e, fn);
+ });
+ return me;
+ }
+ me.on(eventName, fn);
+ return me;
+ },
+
+ /**
+ * Create an event handler on this element such that when the event fires and is handled by this element,
+ * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
+ * @param {String} eventName The type of event to relay
+ * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
+ * for firing the relayed event
+ */
+ relayEvent : function(eventName, observable) {
+ this.on(eventName, function(e) {
+ observable.fireEvent(eventName, e);
+ });
+ },
+
+ /**
+ * Removes Empty, or whitespace filled text nodes. Combines adjacent text nodes.
+ * @param {Boolean} forceReclean (optional) 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 reclean, you can pass true.
+ */
+ clean : function(forceReclean) {
+ var me = this,
+ dom = me.dom,
+ n = dom.firstChild,
+ nx,
+ ni = -1;
+
+ if (Ext.core.Element.data(dom, 'isCleaned') && forceReclean !== true) {
+ return me;
+ }
+
+ while (n) {
+ nx = n.nextSibling;
+ if (n.nodeType == 3) {
+ // Remove empty/whitespace text nodes
+ if (!(/\S/.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();
+ n.nodeIndex = ++ni;
+ }
+ n = nx;
+ }
+
+ Ext.core.Element.data(dom, 'isCleaned', true);
+ return me;
+ },
+
+ /**
+ * Direct access to the Ext.ElementLoader {@link Ext.ElementLoader#load} method. The method takes the same object
+ * parameter as {@link Ext.ElementLoader#load}
+ * @return {Ext.core.Element} this
+ */
+ load : function(options) {
+ this.getLoader().load(options);
+ return this;
+ },
+
+ /**
+ * Gets this element's {@link Ext.ElementLoader ElementLoader}
+ * @return {Ext.ElementLoader} The loader
+ */
+ getLoader : function() {
+ var dom = this.dom,
+ data = Ext.core.Element.data,
+ loader = data(dom, 'loader');
+
+ if (!loader) {
+ loader = Ext.create('Ext.ElementLoader', {
+ target: this
+ });
+ data(dom, 'loader', loader);
+ }
+ return loader;
+ },
+
+ /**
+ * Update the innerHTML of this element, optionally searching for and processing scripts
+ * @param {String} html The new HTML
+ * @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
+ * @param {Function} callback (optional) For async script loading you can be notified when the update completes
+ * @return {Ext.core.Element} this
+ */
+ update : 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 + '"></span>';
+
+ interval = setInterval(function(){
+ if (!document.getElementById(id)) {
+ return false;
+ }
+ clearInterval(interval);
+ var DOC = document,
+ hd = DOC.getElementsByTagName("head")[0],
+ re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
+ srcRe = /\ssrc=([\'\"])(.*?)\1/i,
+ typeRe = /\stype=([\'\"])(.*?)\1/i,
+ match,
+ attrs,
+ srcMatch,
+ typeMatch,
+ el,
+ s;
+
+ while ((match = re.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) {
+ if (window.execScript) {
+ window.execScript(match[2]);
+ } else {
+ window.eval(match[2]);
+ }
+ }
+ }
+
+ el = DOC.getElementById(id);
+ if (el) {
+ Ext.removeNode(el);
+ }
+ Ext.callback(callback, me);
+ }, 20);
+ dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, '');
+ return me;
+ },
+
+ // inherit docs, overridden so we can add removeAnchor
+ removeAllListeners : function() {
+ this.removeAnchor();
+ Ext.EventManager.removeAll(this.dom);
+ return this;
+ },
+
+ /**
+ * 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 (optional) The element or element id to render the proxy to (defaults to document.body)
+ * @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
+ * @return {Ext.core.Element} The new proxy element
+ */
+ createProxy : function(config, renderTo, matchBox) {
+ config = (typeof config == 'object') ? config : {tag : "div", cls: config};
+
+ var me = this,
+ proxy = renderTo ? Ext.core.DomHelper.append(renderTo, config, true) :
+ Ext.core.DomHelper.insertBefore(me.dom, config, true);
+
+ proxy.setVisibilityMode(Ext.core.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;
+ }
+});
+Ext.core.Element.prototype.clearListeners = Ext.core.Element.prototype.removeAllListeners;
+
+/**
+ * @class Ext.core.Element
+ */
+Ext.core.Element.addMethods({
+ /**
+ * Gets the x,y coordinates specified by the anchor position on the element.
+ * @param {String} anchor (optional) The specified anchor position (defaults to "c"). See {@link #alignTo}
+ * for details on supported anchor positions.
+ * @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
+ * of page coordinates
+ * @param {Object} size (optional) An object containing the size to use for calculating anchor position
+ * {width: (target width), height: (target height)} (defaults to the element's current size)
+ * @return {Array} [x, y] An array containing the element's x and y coordinates
+ */
+ getAnchorXY : function(anchor, local, s){
+ //Passing a different size is useful for pre-calculating anchors,
+ //especially for anchored animations that change the el size.
+ anchor = (anchor || "tl").toLowerCase();
+ s = s || {};
+
+ var me = this,
+ vp = me.dom == document.body || me.dom == document,
+ w = s.width || vp ? Ext.core.Element.getViewWidth() : me.getWidth(),
+ h = s.height || vp ? Ext.core.Element.getViewHeight() : me.getHeight(),
+ xy,
+ r = Math.round,
+ o = me.getXY(),
+ scroll = me.getScroll(),
+ extraX = vp ? scroll.left : !local ? o[0] : 0,
+ extraY = vp ? scroll.top : !local ? o[1] : 0,
+ hash = {
+ c : [r(w * 0.5), r(h * 0.5)],
+ t : [r(w * 0.5), 0],
+ l : [0, r(h * 0.5)],
+ r : [w, r(h * 0.5)],
+ b : [r(w * 0.5), h],
+ tl : [0, 0],
+ bl : [0, h],
+ br : [w, h],
+ tr : [w, 0]
+ };
+
+ xy = hash[anchor];
+ return [xy[0] + extraX, xy[1] + extraY];
+ },
+
+ /**
+ * Anchors an element to another element and realigns it when the window is resized.
+ * @param {Mixed} element The element to align to.
+ * @param {String} position The position to align to.
+ * @param {Array} offsets (optional) Offset the positioning by [x, y]
+ * @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
+ * @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
+ * is a number, it is used as the buffer delay (defaults to 50ms).
+ * @param {Function} callback The function to call after the animation finishes
+ * @return {Ext.core.Element} this
+ */
+ anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
+ var me = this,
+ dom = me.dom,
+ scroll = !Ext.isEmpty(monitorScroll),
+ action = function(){
+ Ext.fly(dom).alignTo(el, alignment, offsets, animate);
+ Ext.callback(callback, Ext.fly(dom));
+ },
+ anchor = this.getAnchor();
+
+ // previous listener anchor, remove it
+ this.removeAnchor();
+ Ext.apply(anchor, {
+ fn: action,
+ scroll: scroll
+ });
+
+ Ext.EventManager.onWindowResize(action, null);
+
+ if(scroll){
+ Ext.EventManager.on(window, 'scroll', action, null,
+ {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
+ }
+ action.call(me); // align immediately
+ return me;
+ },
+
+ /**
+ * Remove any anchor to this element. See {@link #anchorTo}.
+ * @return {Ext.core.Element} this
+ */
+ removeAnchor : function(){
+ var me = this,
+ anchor = this.getAnchor();
+
+ if(anchor && anchor.fn){
+ Ext.EventManager.removeResizeListener(anchor.fn);
+ if(anchor.scroll){
+ Ext.EventManager.un(window, 'scroll', anchor.fn);
+ }
+ delete anchor.fn;
+ }
+ return me;
+ },
+
+ // private
+ getAnchor : function(){
+ var data = Ext.core.Element.data,
+ dom = this.dom;
+ if (!dom) {
+ return;
+ }
+ var anchor = data(dom, '_anchor');
+
+ if(!anchor){
+ anchor = data(dom, '_anchor', {});
+ }
+ return anchor;
+ },
+
+ getAlignVector: function(el, spec, offset) {
+ var me = this,
+ side = {t:"top", l:"left", r:"right", b: "bottom"},
+ thisRegion = me.getRegion(),
+ elRegion;
+
+ el = Ext.get(el);
+ if(!el || !el.dom){
+ Ext.Error.raise({
+ sourceClass: 'Ext.core.Element',
+ sourceMethod: 'getAlignVector',
+ msg: 'Attempted to align an element that doesn\'t exist'
+ });
+ }
+
+ elRegion = el.getRegion();
+ },
+
+ /**
+ * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
+ * supported position values.
+ * @param {Mixed} element The element to align to.
+ * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
+ * @param {Array} offsets (optional) Offset the positioning by [x, y]
+ * @return {Array} [x, y]
+ */
+ getAlignToXY : function(el, p, o){
+ el = Ext.get(el);
+
+ if(!el || !el.dom){
+ Ext.Error.raise({
+ sourceClass: 'Ext.core.Element',
+ sourceMethod: 'getAlignToXY',
+ msg: 'Attempted to align an element that doesn\'t exist'
+ });
+ }
+
+ o = o || [0,0];
+ p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
+
+ var me = this,
+ d = me.dom,
+ a1,
+ a2,
+ x,
+ y,
+ //constrain the aligned el to viewport if necessary
+ w,
+ h,
+ r,
+ dw = Ext.core.Element.getViewWidth() -10, // 10px of margin for ie
+ dh = Ext.core.Element.getViewHeight()-10, // 10px of margin for ie
+ p1y,
+ p1x,
+ p2y,
+ p2x,
+ swapY,
+ swapX,
+ doc = document,
+ docElement = doc.documentElement,
+ docBody = doc.body,
+ scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
+ scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
+ c = false, //constrain to viewport
+ p1 = "",
+ p2 = "",
+ m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
+
+ if(!m){
+ Ext.Error.raise({
+ sourceClass: 'Ext.core.Element',
+ sourceMethod: 'getAlignToXY',
+ el: el,
+ position: p,
+ offset: o,
+ msg: 'Attemmpted to align an element with an invalid position: "' + p + '"'
+ });
+ }
+
+ p1 = m[1];
+ p2 = m[2];
+ c = !!m[3];
+
+ //Subtract the aligned el's internal xy from the target's offset xy
+ //plus custom offset to get the aligned el's new offset xy
+ a1 = me.getAnchorXY(p1, true);
+ a2 = el.getAnchorXY(p2, false);
+
+ x = a2[0] - a1[0] + o[0];
+ y = a2[1] - a1[1] + o[1];
+
+ if(c){
+ w = me.getWidth();
+ h = me.getHeight();
+ r = el.getRegion();
+ //If we are at a viewport boundary and the aligned el is anchored on a target border that is
+ //perpendicular to the vp border, allow the aligned el to slide on that border,
+ //otherwise swap the aligned el to the opposite border of the target.
+ p1y = p1.charAt(0);
+ p1x = p1.charAt(p1.length-1);
+ p2y = p2.charAt(0);
+ p2x = p2.charAt(p2.length-1);
+ swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
+ swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
+
+
+ if (x + w > dw + scrollX) {
+ x = swapX ? r.left-w : dw+scrollX-w;
+ }
+ if (x < scrollX) {
+ x = swapX ? r.right : scrollX;
+ }
+ if (y + h > dh + scrollY) {
+ y = swapY ? r.top-h : dh+scrollY-h;
+ }
+ if (y < scrollY){
+ y = swapY ? r.bottom : scrollY;
+ }
+ }
+ return [x,y];
+ },
+
+ /**
+ * Aligns this element with another element relative to the specified anchor points. If the other element is the
+ * document it aligns it to the viewport.
+ * The position parameter is optional, and can be specified in any one of the following formats:
+ * <ul>
+ * <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
+ * <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
+ * The element being aligned will position its top-left corner (tl) to that point. <i>This method has been
+ * deprecated in favor of the newer two anchor syntax below</i>.</li>
+ * <li><b>Two anchors</b>: If two values from the table below are passed separated by a dash, the first value is used as the
+ * element's anchor point, and the second value is used as the target's anchor point.</li>
+ * </ul>
+ * In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
+ * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
+ * the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
+ * that specified in order to enforce the viewport constraints.
+ * Following are all of the supported anchor positions:
+<pre>
+Value Description
+----- -----------------------------
+tl The top left corner (default)
+t The center of the top edge
+tr The top right corner
+l The center of the left edge
+c In the center of the element
+r The center of the right edge
+bl The bottom left corner
+b The center of the bottom edge
+br The bottom right corner
+</pre>
+Example Usage:
+<pre><code>
+// align el to other-el using the default positioning ("tl-bl", non-constrained)
+el.alignTo("other-el");
+
+// align the top left corner of el with the top right corner of other-el (constrained to viewport)
+el.alignTo("other-el", "tr?");
+
+// align the bottom right corner of el with the center left edge of other-el
+el.alignTo("other-el", "br-l?");
+
+// align the center of el with the bottom left corner of other-el and
+// adjust the x position by -6 pixels (and the y position by 0)
+el.alignTo("other-el", "c-bl", [-6, 0]);
+</code></pre>
+ * @param {Mixed} element The element to align to.
+ * @param {String} position (optional, defaults to "tl-bl?") The position to align to.
+ * @param {Array} offsets (optional) Offset the positioning by [x, y]
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ alignTo : function(element, position, offsets, animate){
+ var me = this;
+ return me.setXY(me.getAlignToXY(element, position, offsets),
+ me.anim && !!animate ? me.anim(animate) : false);
+ },
+
+ // private ==> used outside of core
+ adjustForConstraints : function(xy, parent) {
+ var vector = this.getConstrainVector(parent, xy);
+ if (vector) {
+ xy[0] += vector[0];
+ xy[1] += vector[1];
+ }
+ return xy;
+ },
+
+ /**
+ * <p>Returns the <code>[X, Y]</code> vector by which this element must be translated to make a best attempt
+ * to constrain within the passed constraint. Returns <code>false</code> is this element does not need to be moved.</p>
+ * <p>Priority is given to constraining the top and left within the constraint.</p>
+ * <p>The constraint may either be an existing element into which this element is to be constrained, or
+ * an {@link Ext.util.Region Region} into which this element is to be constrained.</p>
+ * @param constrainTo {Mixed} The Element or {@link Ext.util.Region Region} into which this element is to be constrained.
+ * @param proposedPosition {Array} A proposed <code>[X, Y]</code> position to test for validity and to produce a vector for instead
+ * of using this Element's current position;
+ * @returns {Array} <b>If</b> this element <i>needs</i> to be translated, an <code>[X, Y]</code>
+ * vector by which this element must be translated. Otherwise, <code>false</code>.
+ */
+ getConstrainVector: function(constrainTo, proposedPosition) {
+ if (!(constrainTo instanceof Ext.util.Region)) {
+ constrainTo = Ext.get(constrainTo).getViewRegion();
+ }
+ var thisRegion = this.getRegion(),
+ vector = [0, 0],
+ shadowSize = this.shadow && this.shadow.offset,
+ overflowed = false;
+
+ // Shift this region to occupy the proposed position
+ if (proposedPosition) {
+ thisRegion.translateBy(proposedPosition[0] - thisRegion.x, proposedPosition[1] - thisRegion.y);
+ }
+
+ // Reduce the constrain region to allow for shadow
+ // TODO: Rewrite the Shadow class. When that's done, get the extra for each side from the Shadow.
+ if (shadowSize) {
+ constrainTo.adjust(0, -shadowSize, -shadowSize, shadowSize);
+ }
+
+ // Constrain the X coordinate by however much this Element overflows
+ if (thisRegion.right > constrainTo.right) {
+ overflowed = true;
+ vector[0] = (constrainTo.right - thisRegion.right); // overflowed the right
+ }
+ if (thisRegion.left + vector[0] < constrainTo.left) {
+ overflowed = true;
+ vector[0] = (constrainTo.left - thisRegion.left); // overflowed the left
+ }
+
+ // Constrain the Y coordinate by however much this Element overflows
+ if (thisRegion.bottom > constrainTo.bottom) {
+ overflowed = true;
+ vector[1] = (constrainTo.bottom - thisRegion.bottom); // overflowed the bottom
+ }
+ if (thisRegion.top + vector[1] < constrainTo.top) {
+ overflowed = true;
+ vector[1] = (constrainTo.top - thisRegion.top); // overflowed the top
+ }
+ return overflowed ? vector : false;
+ },
+
+ /**
+ * Calculates the x, y to center this element on the screen
+ * @return {Array} The x, y values [x, y]
+ */
+ getCenterXY : function(){
+ return this.getAlignToXY(document, 'c-c');
+ },
+
+ /**
+ * Centers the Element in either the viewport, or another Element.
+ * @param {Mixed} centerIn (optional) The element in which to center the element.
+ */
+ center : function(centerIn){
+ return this.alignTo(centerIn || document, 'c-c');
+ }
+});
+
+/**
+ * @class Ext.core.Element
+ */
+(function(){
+
+var ELEMENT = Ext.core.Element,
+ LEFT = "left",
+ RIGHT = "right",
+ TOP = "top",
+ BOTTOM = "bottom",
+ POSITION = "position",
+ STATIC = "static",
+ RELATIVE = "relative",
+ AUTO = "auto",
+ ZINDEX = "z-index";
+
+Ext.override(Ext.core.Element, {
+ /**
+ * Gets the current X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The X position of the element
+ */
+ getX : function(){
+ return ELEMENT.getX(this.dom);
+ },
+
+ /**
+ * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Number} The Y position of the element
+ */
+ getY : function(){
+ return ELEMENT.getY(this.dom);
+ },
+
+ /**
+ * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @return {Array} The XY position of the element
+ */
+ getXY : function(){
+ return ELEMENT.getXY(this.dom);
+ },
+
+ /**
+ * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
+ * @param {Mixed} element The element to get the offsets from.
+ * @return {Array} The XY page offsets (e.g. [100, -200])
+ */
+ getOffsetsTo : function(el){
+ var o = this.getXY(),
+ e = Ext.fly(el, '_internal').getXY();
+ return [o[0]-e[0],o[1]-e[1]];
+ },
+
+ /**
+ * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The X position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setX : function(x, animate){
+ return this.setXY([x, this.getY()], animate);
+ },
+
+ /**
+ * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Number} The Y position of the element
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setY : function(y, animate){
+ return this.setXY([this.getX(), y], animate);
+ },
+
+ /**
+ * Sets the element's left position directly using CSS style (instead of {@link #setX}).
+ * @param {String} left The left CSS property value
+ * @return {Ext.core.Element} this
+ */
+ setLeft : function(left){
+ this.setStyle(LEFT, this.addUnits(left));
+ return this;
+ },
+
+ /**
+ * Sets the element's top position directly using CSS style (instead of {@link #setY}).
+ * @param {String} top The top CSS property value
+ * @return {Ext.core.Element} this
+ */
+ setTop : function(top){
+ this.setStyle(TOP, this.addUnits(top));
+ return this;
+ },
+
+ /**
+ * Sets the element's CSS right style.
+ * @param {String} right The right CSS property value
+ * @return {Ext.core.Element} this
+ */
+ setRight : function(right){
+ this.setStyle(RIGHT, this.addUnits(right));
+ return this;
+ },
+
+ /**
+ * Sets the element's CSS bottom style.
+ * @param {String} bottom The bottom CSS property value
+ * @return {Ext.core.Element} this
+ */
+ setBottom : function(bottom){
+ this.setStyle(BOTTOM, this.addUnits(bottom));
+ return this;
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
+ * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setXY: function(pos, animate) {
+ var me = this;
+ if (!animate || !me.anim) {
+ ELEMENT.setXY(me.dom, pos);
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ me.animate(Ext.applyIf({ to: { x: pos[0], y: pos[1] } }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @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 (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setLocation : function(x, y, animate){
+ return this.setXY([x, y], animate);
+ },
+
+ /**
+ * Sets the position of the element in page coordinates, regardless of how the element is positioned.
+ * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
+ * @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 (optional) True for the default animation, or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ moveTo : function(x, y, animate){
+ return this.setXY([x, y], animate);
+ },
+
+ /**
+ * Gets the left X coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getLeft : function(local){
+ return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
+ },
+
+ /**
+ * Gets the right X coordinate of the element (element X position + element width)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getRight : function(local){
+ var me = this;
+ return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
+ },
+
+ /**
+ * Gets the top Y coordinate
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getTop : function(local) {
+ return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
+ },
+
+ /**
+ * Gets the bottom Y coordinate of the element (element Y position + element height)
+ * @param {Boolean} local True to get the local css position instead of page coordinate
+ * @return {Number}
+ */
+ getBottom : function(local){
+ var me = this;
+ return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
+ },
+
+ /**
+ * Initializes positioning on this element. If a desired position is not passed, it will make the
+ * the element positioned relative IF it is not already positioned.
+ * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
+ * @param {Number} zIndex (optional) The zIndex to apply
+ * @param {Number} x (optional) Set the page X position
+ * @param {Number} y (optional) Set the page Y position
+ */
+ position : function(pos, zIndex, x, y) {
+ var me = this;
+
+ if (!pos && me.isStyle(POSITION, STATIC)){
+ me.setStyle(POSITION, RELATIVE);
+ } else if(pos) {
+ me.setStyle(POSITION, pos);
+ }
+ if (zIndex){
+ me.setStyle(ZINDEX, zIndex);
+ }
+ if (x || y) {
+ me.setXY([x || false, y || false]);
+ }
+ },
+
+ /**
+ * Clear positioning back to the default when the document was loaded
+ * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
+ * @return {Ext.core.Element} this
+ */
+ clearPositioning : function(value){
+ value = value || '';
+ this.setStyle({
+ left : value,
+ right : value,
+ top : value,
+ bottom : value,
+ "z-index" : "",
+ position : STATIC
+ });
+ return this;
+ },
+
+ /**
+ * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
+ * snapshot before performing an update and then restoring the element.
+ * @return {Object}
+ */
+ getPositioning : function(){
+ var l = this.getStyle(LEFT);
+ var t = this.getStyle(TOP);
+ return {
+ "position" : this.getStyle(POSITION),
+ "left" : l,
+ "right" : l ? "" : this.getStyle(RIGHT),
+ "top" : t,
+ "bottom" : t ? "" : this.getStyle(BOTTOM),
+ "z-index" : this.getStyle(ZINDEX)
+ };
+ },
+
+ /**
+ * Set positioning with an object returned by getPositioning().
+ * @param {Object} posCfg
+ * @return {Ext.core.Element} this
+ */
+ setPositioning : function(pc){
+ var me = this,
+ style = me.dom.style;
+
+ me.setStyle(pc);
+
+ if(pc.right == AUTO){
+ style.right = "";
+ }
+ if(pc.bottom == AUTO){
+ style.bottom = "";
+ }
+
+ return me;
+ },
+
+ /**
+ * Translates the passed page coordinates into left/top css values for this element
+ * @param {Number/Array} x The page x or an array containing [x, y]
+ * @param {Number} y (optional) The page y, required if x is not an array
+ * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
+ */
+ translatePoints: function(x, y) {
+ if (Ext.isArray(x)) {
+ y = x[1];
+ x = x[0];
+ }
+ var me = this,
+ relative = me.isStyle(POSITION, RELATIVE),
+ o = me.getXY(),
+ left = parseInt(me.getStyle(LEFT), 10),
+ top = parseInt(me.getStyle(TOP), 10);
+
+ if (!Ext.isNumber(left)) {
+ left = relative ? 0 : me.dom.offsetLeft;
+ }
+ if (!Ext.isNumber(top)) {
+ top = relative ? 0 : me.dom.offsetTop;
+ }
+ left = (Ext.isNumber(x)) ? x - o[0] + left : undefined;
+ top = (Ext.isNumber(y)) ? y - o[1] + top : undefined;
+ return {
+ left: left,
+ top: top
+ };
+ },
+
+ /**
+ * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
+ * @param {Object} box The box to fill {x, y, width, height}
+ * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setBox: function(box, adjust, animate) {
+ var me = this,
+ w = box.width,
+ h = box.height;
+ if ((adjust && !me.autoBoxAdjust) && !me.isBorderBox()) {
+ w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ me.setBounds(box.x, box.y, w, h, animate);
+ return me;
+ },
+
+ /**
+ * Return an object defining the area of this Element which can be passed to {@link #setBox} to
+ * set another Element's size/location to match this element.
+ * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
+ * @return {Object} box An object in the format<pre><code>
+{
+ x: <Element's X position>,
+ y: <Element's Y position>,
+ width: <Element's width>,
+ height: <Element's height>,
+ bottom: <Element's lower bound>,
+ right: <Element's rightmost bound>
+}
+</code></pre>
+ * The returned object may also be addressed as an Array where index 0 contains the X position
+ * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
+ */
+ getBox: function(contentBox, local) {
+ var me = this,
+ xy,
+ left,
+ top,
+ getBorderWidth = me.getBorderWidth,
+ getPadding = me.getPadding,
+ l, r, t, b, w, h, bx;
+ if (!local) {
+ xy = me.getXY();
+ } else {
+ left = parseInt(me.getStyle("left"), 10) || 0;
+ top = parseInt(me.getStyle("top"), 10) || 0;
+ xy = [left, top];
+ }
+ w = me.getWidth();
+ h = me.getHeight();
+ if (!contentBox) {
+ bx = {
+ x: xy[0],
+ y: xy[1],
+ 0: xy[0],
+ 1: xy[1],
+ width: w,
+ height: h
+ };
+ } else {
+ l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
+ r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
+ t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
+ b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
+ bx = {
+ x: xy[0] + l,
+ y: xy[1] + t,
+ 0: xy[0] + l,
+ 1: xy[1] + t,
+ width: w - (l + r),
+ height: h - (t + b)
+ };
+ }
+ bx.right = bx.x + bx.width;
+ bx.bottom = bx.y + bx.height;
+ return bx;
+ },
+
+ /**
+ * Move this element relative to its current position.
+ * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
+ * @param {Number} distance How far to move the element in pixels
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ move: function(direction, distance, animate) {
+ var me = this,
+ xy = me.getXY(),
+ x = xy[0],
+ y = xy[1],
+ left = [x - distance, y],
+ right = [x + distance, y],
+ top = [x, y - distance],
+ bottom = [x, y + distance],
+ hash = {
+ l: left,
+ left: left,
+ r: right,
+ right: right,
+ t: top,
+ top: top,
+ up: top,
+ b: bottom,
+ bottom: bottom,
+ down: bottom
+ };
+
+ direction = direction.toLowerCase();
+ me.moveTo(hash[direction][0], hash[direction][1], animate);
+ },
+
+ /**
+ * Quick set left and top adding default units
+ * @param {String} left The left CSS property value
+ * @param {String} top The top CSS property value
+ * @return {Ext.core.Element} this
+ */
+ setLeftTop: function(left, top) {
+ var me = this,
+ style = me.dom.style;
+ style.left = me.addUnits(left);
+ style.top = me.addUnits(top);
+ return me;
+ },
+
+ /**
+ * Returns the region of this element.
+ * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
+ * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
+ */
+ getRegion: function() {
+ return this.getPageBox(true);
+ },
+
+ /**
+ * Returns the <b>content</b> region of this element. That is the region within the borders and padding.
+ * @return {Region} A Ext.util.Region containing "top, left, bottom, right" member data.
+ */
+ getViewRegion: function() {
+ var me = this,
+ isBody = me.dom === document.body,
+ scroll, pos, top, left, width, height;
+
+ // For the body we want to do some special logic
+ if (isBody) {
+ scroll = me.getScroll();
+ left = scroll.left;
+ top = scroll.top;
+ width = Ext.core.Element.getViewportWidth();
+ height = Ext.core.Element.getViewportHeight();
+ }
+ else {
+ pos = me.getXY();
+ left = pos[0] + me.getBorderWidth('l') + me.getPadding('l');
+ top = pos[1] + me.getBorderWidth('t') + me.getPadding('t');
+ width = me.getWidth(true);
+ height = me.getHeight(true);
+ }
+
+ return Ext.create('Ext.util.Region', top, left + width, top + height, left);
+ },
+
+ /**
+ * Return an object defining the area of this Element which can be passed to {@link #setBox} to
+ * set another Element's size/location to match this element.
+ * @param {Boolean} asRegion(optional) If true an Ext.util.Region will be returned
+ * @return {Object} box An object in the format<pre><code>
+{
+ x: <Element's X position>,
+ y: <Element's Y position>,
+ width: <Element's width>,
+ height: <Element's height>,
+ bottom: <Element's lower bound>,
+ right: <Element's rightmost bound>
+}
+</code></pre>
+ * The returned object may also be addressed as an Array where index 0 contains the X position
+ * and index 1 contains the Y position. So the result may also be used for {@link #setXY}
+ */
+ getPageBox : function(getRegion) {
+ var me = this,
+ el = me.dom,
+ isDoc = el === document.body,
+ w = isDoc ? Ext.core.Element.getViewWidth() : el.offsetWidth,
+ h = isDoc ? Ext.core.Element.getViewHeight() : el.offsetHeight,
+ xy = me.getXY(),
+ t = xy[1],
+ r = xy[0] + w,
+ b = xy[1] + h,
+ l = xy[0];
+
+ if (getRegion) {
+ return Ext.create('Ext.util.Region', t, r, b, l);
+ }
+ else {
+ return {
+ left: l,
+ top: t,
+ width: w,
+ height: h,
+ right: r,
+ bottom: b
+ };
+ }
+ },
+
+ /**
+ * 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 {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
+ * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
+ * </ul></div>
+ * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * </ul></div>
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setBounds: function(x, y, width, height, animate) {
+ var me = this;
+ if (!animate || !me.anim) {
+ me.setSize(width, height);
+ me.setLocation(x, y);
+ } else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ me.animate(Ext.applyIf({
+ to: {
+ x: x,
+ y: y,
+ width: me.adjustWidth(width),
+ height: me.adjustHeight(height)
+ }
+ }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
+ * @param {Ext.util.Region} region The region to fill
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Ext.core.Element} this
+ */
+ setRegion: function(region, animate) {
+ return this.setBounds(region.left, region.top, region.right - region.left, region.bottom - region.top, animate);
+ }
+});
+})();
+
+/**
+ * @class Ext.core.Element
+ */
+Ext.override(Ext.core.Element, {
+ /**
+ * Returns true if this element is scrollable.
+ * @return {Boolean}
+ */
+ isScrollable : function(){
+ var dom = this.dom;
+ return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
+ },
+
+ /**
+ * Returns the current scroll position of the element.
+ * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
+ */
+ getScroll : function() {
+ var d = this.dom,
+ doc = document,
+ body = doc.body,
+ docElement = doc.documentElement,
+ l,
+ t,
+ ret;
+
+ if (d == doc || d == body) {
+ if (Ext.isIE && Ext.isStrict) {
+ l = docElement.scrollLeft;
+ t = docElement.scrollTop;
+ } else {
+ l = window.pageXOffset;
+ t = window.pageYOffset;
+ }
+ ret = {
+ left: l || (body ? body.scrollLeft : 0),
+ top : t || (body ? body.scrollTop : 0)
+ };
+ } else {
+ ret = {
+ left: d.scrollLeft,
+ top : d.scrollTop
+ };
+ }
+
+ return ret;
+ },
+
+ /**
+ * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
+ * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
+ * @param {Number} value The new scroll value
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Element} this
+ */
+ scrollTo : function(side, value, animate) {
+ //check if we're scrolling top or left
+ var top = /top/i.test(side),
+ me = this,
+ dom = me.dom,
+ obj = {},
+ prop;
+ if (!animate || !me.anim) {
+ // just setting the value, so grab the direction
+ prop = 'scroll' + (top ? 'Top' : 'Left');
+ dom[prop] = value;
+ }
+ else {
+ if (!Ext.isObject(animate)) {
+ animate = {};
+ }
+ obj['scroll' + (top ? 'Top' : 'Left')] = value;
+ me.animate(Ext.applyIf({
+ to: obj
+ }, animate));
+ }
+ return me;
+ },
+
+ /**
+ * Scrolls this element into view within the passed container.
+ * @param {Mixed} container (optional) The container element to scroll (defaults to document.body). Should be a
+ * string (id), dom node, or Ext.core.Element.
+ * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
+ * @return {Ext.core.Element} this
+ */
+ scrollIntoView : function(container, hscroll) {
+ container = Ext.getDom(container) || Ext.getBody().dom;
+ var el = this.dom,
+ offsets = this.getOffsetsTo(container),
+ // el's box
+ left = offsets[0] + container.scrollLeft,
+ top = offsets[1] + container.scrollTop,
+ bottom = top + el.offsetHeight,
+ right = left + el.offsetWidth,
+ // ct's box
+ ctClientHeight = container.clientHeight,
+ ctScrollTop = parseInt(container.scrollTop, 10),
+ ctScrollLeft = parseInt(container.scrollLeft, 10),
+ ctBottom = ctScrollTop + ctClientHeight,
+ ctRight = ctScrollLeft + container.clientWidth;
+
+ if (el.offsetHeight > ctClientHeight || top < ctScrollTop) {
+ container.scrollTop = top;
+ } else if (bottom > ctBottom) {
+ container.scrollTop = bottom - ctClientHeight;
+ }
+ // corrects IE, other browsers will ignore
+ container.scrollTop = container.scrollTop;
+
+ if (hscroll !== false) {
+ if (el.offsetWidth > container.clientWidth || left < ctScrollLeft) {
+ container.scrollLeft = left;
+ }
+ else if (right > ctRight) {
+ container.scrollLeft = right - container.clientWidth;
+ }
+ container.scrollLeft = container.scrollLeft;
+ }
+ return this;
+ },
+
+ // private
+ scrollChildIntoView : function(child, hscroll) {
+ Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
+ },
+
+ /**
+ * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
+ * within this element's scrollable range.
+ * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
+ * @param {Number} distance How far to scroll the element in pixels
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
+ * @return {Boolean} Returns true if a scroll was triggered or false if the element
+ * was scrolled as far as it could go.
+ */
+ scroll : function(direction, distance, animate) {
+ if (!this.isScrollable()) {
+ return false;
+ }
+ var el = this.dom,
+ l = el.scrollLeft, t = el.scrollTop,
+ w = el.scrollWidth, h = el.scrollHeight,
+ cw = el.clientWidth, ch = el.clientHeight,
+ scrolled = false, v,
+ hash = {
+ l: Math.min(l + distance, w-cw),
+ r: v = Math.max(l - distance, 0),
+ t: Math.max(t - distance, 0),
+ b: Math.min(t + distance, h-ch)
+ };
+ hash.d = hash.b;
+ hash.u = hash.t;
+
+ direction = direction.substr(0, 1);
+ if ((v = hash[direction]) > -1) {
+ scrolled = true;
+ this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.anim(animate));
+ }
+ return scrolled;
+ }
+});
+/**
+ * @class Ext.core.Element
+ */
+Ext.core.Element.addMethods(
+ function() {
+ var VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ NONE = "none",
+ XMASKED = Ext.baseCSSPrefix + "masked",
+ XMASKEDRELATIVE = Ext.baseCSSPrefix + "masked-relative",
+ data = Ext.core.Element.data;
+
+ return {
+ /**
+ * Checks whether the element is currently visible using both visibility and display properties.
+ * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
+ * @return {Boolean} True if the element is currently visible, else false
+ */
+ isVisible : function(deep) {
+ var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
+ p = this.dom.parentNode;
+
+ if (deep !== true || !vis) {
+ return vis;
+ }
+
+ while (p && !(/^body/i.test(p.tagName))) {
+ if (!Ext.fly(p, '_isVisible').isVisible()) {
+ return false;
+ }
+ p = p.parentNode;
+ }
+ return true;
+ },
+
+ /**
+ * Returns true if display is not "none"
+ * @return {Boolean}
+ */
+ isDisplayed : function() {
+ return !this.isStyle(DISPLAY, NONE);
+ },
+
+ /**
+ * Convenience method for setVisibilityMode(Element.DISPLAY)
+ * @param {String} display (optional) What to set display to when visible
+ * @return {Ext.core.Element} this
+ */
+ enableDisplayMode : function(display) {
+ this.setVisibilityMode(Ext.core.Element.DISPLAY);
+
+ if (!Ext.isEmpty(display)) {
+ data(this.dom, 'originalDisplay', display);
+ }
+
+ return this;
+ },
+
+ /**
+ * Puts a mask over this element to disable user interaction. Requires core.css.
+ * This method can only be applied to elements which accept child nodes.
+ * @param {String} msg (optional) A message to display in the mask
+ * @param {String} msgCls (optional) A css class to apply to the msg element
+ * @return {Element} The mask element
+ */
+ mask : function(msg, msgCls) {
+ var me = this,
+ dom = me.dom,
+ setExpression = dom.style.setExpression,
+ dh = Ext.core.DomHelper,
+ EXTELMASKMSG = Ext.baseCSSPrefix + "mask-msg",
+ el,
+ mask;
+
+ if (!(/^body/i.test(dom.tagName) && me.getStyle('position') == 'static')) {
+ me.addCls(XMASKEDRELATIVE);
+ }
+ el = data(dom, 'maskMsg');
+ if (el) {
+ el.remove();
+ }
+ el = data(dom, 'mask');
+ if (el) {
+ el.remove();
+ }
+
+ mask = dh.append(dom, {cls : Ext.baseCSSPrefix + "mask"}, true);
+ data(dom, 'mask', mask);
+
+ me.addCls(XMASKED);
+ mask.setDisplayed(true);
+
+ if (typeof msg == 'string') {
+ var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
+ data(dom, 'maskMsg', mm);
+ mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
+ mm.dom.firstChild.innerHTML = msg;
+ mm.setDisplayed(true);
+ mm.center(me);
+ }
+ // NOTE: CSS expressions are resource intensive and to be used only as a last resort
+ // These expressions are removed as soon as they are no longer necessary - in the unmask method.
+ // In normal use cases an element will be masked for a limited period of time.
+ // Fix for https://sencha.jira.com/browse/EXTJSIV-19.
+ // IE6 strict mode and IE6-9 quirks mode takes off left+right padding when calculating width!
+ if (!Ext.supports.IncludePaddingInWidthCalculation && setExpression) {
+ mask.dom.style.setExpression('width', 'this.parentNode.offsetWidth + "px"');
+ }
+
+ // Some versions and modes of IE subtract top+bottom padding when calculating height.
+ // Different versions from those which make the same error for width!
+ if (!Ext.supports.IncludePaddingInHeightCalculation && setExpression) {
+ mask.dom.style.setExpression('height', 'this.parentNode.offsetHeight + "px"');
+ }
+ // ie will not expand full height automatically
+ else if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
+ mask.setSize(undefined, me.getHeight());
+ }
+ return mask;
+ },
+
+ /**
+ * Removes a previously applied mask.
+ */
+ unmask : function() {
+ var me = this,
+ dom = me.dom,
+ mask = data(dom, 'mask'),
+ maskMsg = data(dom, 'maskMsg');
+
+ if (mask) {
+ // Remove resource-intensive CSS expressions as soon as they are not required.
+ if (mask.dom.style.clearExpression) {
+ mask.dom.style.clearExpression('width');
+ mask.dom.style.clearExpression('height');
+ }
+ if (maskMsg) {
+ maskMsg.remove();
+ data(dom, 'maskMsg', undefined);
+ }
+
+ mask.remove();
+ data(dom, 'mask', undefined);
+ me.removeCls([XMASKED, XMASKEDRELATIVE]);
+ }
+ },
+ /**
+ * Returns true if this element is masked. Also re-centers any displayed message within the mask.
+ * @return {Boolean}
+ */
+ isMasked : function() {
+ var me = this,
+ mask = data(me.dom, 'mask'),
+ maskMsg = data(me.dom, 'maskMsg');
+
+ if (mask && mask.isVisible()) {
+ if (maskMsg) {
+ maskMsg.center(me);
+ }
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Creates an iframe shim for this element to keep selects and other windowed objects from
+ * showing through.
+ * @return {Ext.core.Element} The new shim element
+ */
+ createShim : function() {
+ var el = document.createElement('iframe'),
+ shim;
+
+ el.frameBorder = '0';
+ el.className = Ext.baseCSSPrefix + 'shim';
+ el.src = Ext.SSL_SECURE_URL;
+ shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
+ shim.autoBoxAdjust = false;
+ return shim;
+ }
+ };
+ }()
+);
+/**
+ * @class Ext.core.Element
+ */
+Ext.core.Element.addMethods({
+ /**
+ * Convenience method for constructing a KeyMap
+ * @param {Number/Array/Object/String} 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:
+ * <code>{key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}</code>
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope (<code>this</code> 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 = {
+ key: key,
+ fn: fn,
+ scope: scope
+ };
+ }else{
+ config = {
+ key : key.key,
+ shift : key.shift,
+ ctrl : key.ctrl,
+ alt : key.alt,
+ fn: fn,
+ scope: scope
+ };
+ }
+ return Ext.create('Ext.util.KeyMap', this, 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 Ext.create('Ext.util.KeyMap', this, config);
+ }
+});
+
+//Import the newly-added Ext.core.Element functions into CompositeElementLite. We call this here because
+//Element.keys.js is the last extra Ext.core.Element include in the ext-all.js build
+Ext.CompositeElementLite.importElementMethods();
+
+/**
+ * @class Ext.CompositeElementLite
+ */
+Ext.apply(Ext.CompositeElementLite.prototype, {
+ addElements : function(els, root){
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.core.Element.selectorFunction(els, root);
+ }
+ var yels = this.elements;
+ Ext.each(els, function(e) {
+ yels.push(Ext.get(e));
+ });
+ return this;
+ },
+
+ /**
+ * Returns the first Element
+ * @return {Ext.core.Element}
+ */
+ first : function(){
+ return this.item(0);
+ },
+
+ /**
+ * Returns the last Element
+ * @return {Ext.core.Element}
+ */
+ last : function(){
+ return this.item(this.getCount()-1);
+ },
+
+ /**
+ * Returns true if this composite contains the passed element
+ * @param el {Mixed} The id of an element, or an Ext.core.Element, or an HtmlElement to find within the composite collection.
+ * @return Boolean
+ */
+ contains : function(el){
+ return this.indexOf(el) != -1;
+ },
+
+ /**
+ * Removes the specified element(s).
+ * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
+ * or an array of any of those.
+ * @param {Boolean} removeDom (optional) True to also remove the element from the document
+ * @return {CompositeElement} this
+ */
+ removeElement : function(keys, removeDom){
+ var me = this,
+ els = this.elements,
+ el;
+ Ext.each(keys, function(val){
+ if ((el = (els[val] || els[val = me.indexOf(val)]))) {
+ if(removeDom){
+ if(el.dom){
+ el.remove();
+ }else{
+ Ext.removeNode(el);
+ }
+ }
+ els.splice(val, 1);
+ }
+ });
+ return this;
+ }
+});
+
+/**
+ * @class Ext.CompositeElement
+ * @extends Ext.CompositeElementLite
+ * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
+ * members, or to perform collective actions upon the whole set.</p>
+ * <p>Although they are not listed, this class supports all of the methods of {@link Ext.core.Element} and
+ * {@link Ext.fx.Anim}. The methods from these classes will be performed on all the elements in this collection.</p>
+ * <p>All methods return <i>this</i> and can be chained.</p>
+ * Usage:
+<pre><code>
+var els = Ext.select("#some-el div.some-class", true);
+// or select directly from an existing element
+var el = Ext.get('some-el');
+el.select('div.some-class', true);
+
+els.setWidth(100); // all elements become 100 width
+els.hide(true); // all elements fade out and hide
+// or
+els.setWidth(100).hide(true);
+</code></pre>
+ */
+Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
+
+ constructor : function(els, root){
+ this.elements = [];
+ this.add(els, root);
+ },
+
+ // private
+ getElement : function(el){
+ // In this case just return it, since we already have a reference to it
+ return el;
+ },
+
+ // private
+ transformElement : function(el){
+ return Ext.get(el);
+ }
+
+ /**
+ * Adds elements to this composite.
+ * @param {String/Array} els A string CSS selector, an array of elements or an element
+ * @return {CompositeElement} this
+ */
+
+ /**
+ * Returns the Element object at the specified index
+ * @param {Number} index
+ * @return {Ext.core.Element}
+ */
+
+ /**
+ * Iterates each `element` in this `composite` calling the supplied function using {@link Ext#each Ext.each}.
+ * @param {Function} fn
+
+The function to be called with each
+`element`. If the supplied function returns <tt>false</tt>,
+iteration stops. This function is called with the following arguments:
+
+- `element` : __Ext.core.Element++
+ The element at the current `index` in the `composite`
+
+- `composite` : __Object__
+ This composite.
+
+- `index` : __Number__
+ The current index within the `composite`
+
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the specified function is executed.
+ * Defaults to the <code>element</code> at the current <code>index</code>
+ * within the composite.
+ * @return {CompositeElement} this
+ * @markdown
+ */
+});
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext.core.Element
+ * @method select
+ */
+Ext.core.Element.select = function(selector, unique, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.core.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ Ext.Error.raise({
+ sourceClass: "Ext.core.Element",
+ sourceMethod: "select",
+ selector: selector,
+ unique: unique,
+ root: root,
+ msg: "Invalid selector specified: " + selector
+ });
+ }
+ return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
+};
+
+/**
+ * Selects elements based on the passed CSS selector to enable {@link Ext.core.Element Element} methods
+ * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
+ * {@link Ext.CompositeElementLite CompositeElementLite} object.
+ * @param {String/Array} selector The CSS selector or an array of elements
+ * @param {Boolean} unique (optional) true to create a unique Ext.core.Element for each element (defaults to a shared flyweight object)
+ * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
+ * @return {CompositeElementLite/CompositeElement}
+ * @member Ext
+ * @method select
+ */
+Ext.select = Ext.core.Element.select;
+
+
+/*
+Ext JS - JavaScript Library
+Copyright (c) 2006-2011, Sencha Inc.
+All rights reserved.
+licensing@sencha.com
+*/
+/**
+ * @class Ext.util.Observable
+ * Base class that provides a common interface for publishing events. Subclasses are expected to
+ * to have a property "events" with all the events defined, and, optionally, a property "listeners"
+ * with configured listeners defined.<br>
+ * For example:
+ * <pre><code>
+Employee = Ext.extend(Ext.util.Observable, {
+ constructor: function(config){
+ this.name = config.name;
+ this.addEvents({
+ "fired" : true,
+ "quit" : true
+ });
+
+ // Copy configured listeners into *this* object so that the base class's
+ // constructor will add them.
+ this.listeners = config.listeners;
+
+ // Call our superclass constructor to complete construction process.
+ Employee.superclass.constructor.call(this, config)
+ }
+});
+</code></pre>
+ * This could then be used like this:<pre><code>
+var newEmployee = new Employee({
+ name: employeeName,
+ listeners: {
+ quit: function() {
+ // By default, "this" will be the object that fired the event.
+ alert(this.name + " has quit!");
+ }
+ }
+});
+</code></pre>
+ */
+
+Ext.define('Ext.util.Observable', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.util.Event'],
+
+ statics: {
+ /**
+ * Removes <b>all</b> added captures from the Observable.
+ * @param {Observable} o The Observable to release
+ * @static
+ */
+ releaseCapture: function(o) {
+ o.fireEvent = this.prototype.fireEvent;
+ },
+
+ /**
+ * Starts capture on the specified Observable. All events will be passed
+ * to the supplied function with the event name + standard signature of the event
+ * <b>before</b> the event is fired. If the supplied function returns false,
+ * the event will not fire.
+ * @param {Observable} o The Observable to capture events from.
+ * @param {Function} fn The function to call when an event is fired.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Observable firing the event.
+ * @static
+ */
+ capture: function(o, fn, scope) {
+ o.fireEvent = Ext.Function.createInterceptor(o.fireEvent, fn, scope);
+ },
+
+ /**
+Sets observability on the passed class constructor.
+
+This makes any event fired on any instance of the passed class also fire a single event through
+the __class__ allowing for central handling of events on many instances at once.
+
+Usage:
+
+ Ext.util.Observable.observe(Ext.data.Connection);
+ Ext.data.Connection.on('beforerequest', function(con, options) {
+ console.log('Ajax request made to ' + options.url);
+ });
+
+ * @param {Function} c The class constructor to make observable.
+ * @param {Object} listeners An object containing a series of listeners to add. See {@link #addListener}.
+ * @static
+ * @markdown
+ */
+ observe: function(cls, listeners) {
+ if (cls) {
+ if (!cls.isObservable) {
+ Ext.applyIf(cls, new this());
+ this.capture(cls.prototype, cls.fireEvent, cls);
+ }
+ if (Ext.isObject(listeners)) {
+ cls.on(listeners);
+ }
+ return cls;
+ }
+ }
+ },
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
+ * object during initialization. This should be a valid listeners config object as specified in the
+ * {@link #addListener} example for attaching multiple handlers at once.</p>
+ * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
+ * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
+ * is usually only done when extra value can be added. For example the {@link Ext.view.View DataView}'s
+ * <b><code>{@link Ext.view.View#click click}</code></b> event passing the node clicked on. To access DOM
+ * events directly from a child element of a Component, we need to specify the <code>element</code> option to
+ * identify the Component property to add a DOM listener to:
+ * <pre><code>
+new Ext.panel.Panel({
+ width: 400,
+ height: 200,
+ dockedItems: [{
+ xtype: 'toolbar'
+ }],
+ listeners: {
+ click: {
+ element: 'el', //bind to the underlying el property on the panel
+ fn: function(){ console.log('click el'); }
+ },
+ dblclick: {
+ element: 'body', //bind to the underlying body property on the panel
+ fn: function(){ console.log('dblclick body'); }
+ }
+ }
+});
+</code></pre>
+ * </p>
+ */
+ // @private
+ isObservable: true,
+
+ constructor: function(config) {
+ var me = this;
+
+ Ext.apply(me, config);
+ if (me.listeners) {
+ me.on(me.listeners);
+ delete me.listeners;
+ }
+ me.events = me.events || {};
+
+ if (me.bubbleEvents) {
+ me.enableBubble(me.bubbleEvents);
+ }
+ },
+
+ // @private
+ eventOptionsRe : /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate|element|vertical|horizontal)$/,
+
+ /**
+ * <p>Adds listeners to any Observable object (or Element) which are automatically removed when this Component
+ * is destroyed.
+ * @param {Observable/Element} item The item to which to add a listener/listeners.
+ * @param {Object/String} ename The event name, or an object containing event name properties.
+ * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
+ * is the scope (<code>this</code> reference) in which the handler function is executed.
+ * @param {Object} opt Optional. If the <code>ename</code> parameter was an event name, this
+ * is the {@link Ext.util.Observable#addListener addListener} options.
+ */
+ addManagedListener : function(item, ename, fn, scope, options) {
+ var me = this,
+ managedListeners = me.managedListeners = me.managedListeners || [],
+ config;
+
+ if (Ext.isObject(ename)) {
+ options = ename;
+ for (ename in options) {
+ if (options.hasOwnProperty(ename)) {
+ config = options[ename];
+ if (!me.eventOptionsRe.test(ename)) {
+ me.addManagedListener(item, ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
+ }
+ }
+ }
+ }
+ else {
+ managedListeners.push({
+ item: item,
+ ename: ename,
+ fn: fn,
+ scope: scope,
+ options: options
+ });
+
+ item.on(ename, fn, scope, options);
+ }
+ },
+
+ /**
+ * Removes listeners that were added by the {@link #mon} method.
+ * @param {Observable|Element} item The item from which to remove a listener/listeners.
+ * @param {Object|String} ename The event name, or an object containing event name properties.
+ * @param {Function} fn Optional. If the <code>ename</code> parameter was an event name, this
+ * is the handler function.
+ * @param {Object} scope Optional. If the <code>ename</code> parameter was an event name, this
+ * is the scope (<code>this</code> reference) in which the handler function is executed.
+ */
+ removeManagedListener : function(item, ename, fn, scope) {
+ var me = this,
+ options,
+ config,
+ managedListeners,
+ managedListener,
+ length,
+ i;
+
+ if (Ext.isObject(ename)) {
+ options = ename;
+ for (ename in options) {
+ if (options.hasOwnProperty(ename)) {
+ config = options[ename];
+ if (!me.eventOptionsRe.test(ename)) {
+ me.removeManagedListener(item, ename, config.fn || config, config.scope || options.scope);
+ }
+ }
+ }
+ }
+
+ managedListeners = me.managedListeners ? me.managedListeners.slice() : [];
+ length = managedListeners.length;
+
+ for (i = 0; i < length; i++) {
+ managedListener = managedListeners[i];
+ if (managedListener.item === item && managedListener.ename === ename && (!fn || managedListener.fn === fn) && (!scope || managedListener.scope === scope)) {
+ Ext.Array.remove(me.managedListeners, managedListener);
+ item.un(managedListener.ename, managedListener.fn, managedListener.scope);
+ }
+ }
+ },
+
+ /**
+ * <p>Fires the specified event with the passed parameters (minus the event name).</p>
+ * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
+ * by calling {@link #enableBubble}.</p>
+ * @param {String} eventName The name of the event to fire.
+ * @param {Object...} args Variable number of parameters are passed to handlers.
+ * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
+ */
+ fireEvent: function() {
+ var me = this,
+ args = Ext.Array.toArray(arguments),
+ ename = args[0].toLowerCase(),
+ ret = true,
+ event = me.events[ename],
+ queue = me.eventQueue,
+ parent;
+
+ if (me.eventsSuspended === true) {
+ if (queue) {
+ queue.push(args);
+ }
+ } else if (event && Ext.isObject(event) && event.bubble) {
+ if (event.fire.apply(event, args.slice(1)) === false) {
+ return false;
+ }
+ parent = me.getBubbleTarget && me.getBubbleTarget();
+ if (parent && parent.isObservable) {
+ if (!parent.events[ename] || !Ext.isObject(parent.events[ename]) || !parent.events[ename].bubble) {
+ parent.enableBubble(ename);
+ }
+ return parent.fireEvent.apply(parent, args);
+ }
+ } else if (event && Ext.isObject(event)) {
+ args.shift();
+ ret = event.fire.apply(event, args);
+ }
+ return ret;
+ },
+
+ /**
+ * Appends an event handler to this object.
+ * @param {String} eventName The name of the event to listen for. May also be an object who's property names are event names. See
+ * @param {Function} handler The method the event invokes.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b>
+ * @param {Object} options (optional) An object containing handler configuration.
+ * properties. This may contain any of the following properties:<ul>
+ * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b></div></li>
+ * <li><b>delay</b> : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
+ * <li><b>single</b> : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * <li><b>target</b> : Observable<div class="sub-desc">Only call the handler if the event was fired on the target Observable, <i>not</i>
+ * if the event was bubbled up from a child Observable.</div></li>
+ * <li><b>element</b> : String<div class="sub-desc"><b>This option is only valid for listeners bound to {@link Ext.Component Components}.</b>
+ * The name of a Component property which references an element to add a listener to.
+ * <p>This option is useful during Component construction to add DOM event listeners to elements of {@link Ext.Component Components} which
+ * will exist only after the Component is rendered. For example, to add a click listener to a Panel's body:<pre><code>
+new Ext.panel.Panel({
+ title: 'The title',
+ listeners: {
+ click: this.handlePanelClick,
+ element: 'body'
+ }
+});
+</code></pre></p>
+ * <p>When added in this way, the options available are the options applicable to {@link Ext.core.Element#addListener}</p></div></li>
+ * </ul><br>
+ * <p>
+ * <b>Combining Options</b><br>
+ * Using the options argument, it is possible to combine different types of listeners:<br>
+ * <br>
+ * A delayed, one-time listener.
+ * <pre><code>
+myPanel.on('hide', this.handleClick, this, {
+single: true,
+delay: 100
+});</code></pre>
+ * <p>
+ * <b>Attaching multiple handlers in 1 call</b><br>
+ * The method also allows for a single argument to be passed which is a config object containing properties
+ * which specify multiple events. For example:<pre><code>
+myGridPanel.on({
+ cellClick: this.onCellClick,
+ mouseover: this.onMouseOver,
+ mouseout: this.onMouseOut,
+ scope: this // Important. Ensure "this" is correct during handler execution
+});
+</code></pre>.
+ * <p>
+ */
+ addListener: function(ename, fn, scope, options) {
+ var me = this,
+ config,
+ event;
+
+ if (Ext.isObject(ename)) {
+ options = ename;
+ for (ename in options) {
+ if (options.hasOwnProperty(ename)) {
+ config = options[ename];
+ if (!me.eventOptionsRe.test(ename)) {
+ me.addListener(ename, config.fn || config, config.scope || options.scope, config.fn ? config : options);
+ }
+ }
+ }
+ }
+ else {
+ ename = ename.toLowerCase();
+ me.events[ename] = me.events[ename] || true;
+ event = me.events[ename] || true;
+ if (Ext.isBoolean(event)) {
+ me.events[ename] = event = new Ext.util.Event(me, ename);
+ }
+ event.addListener(fn, scope, Ext.isObject(options) ? options : {});
+ }
+ },
+
+ /**
+ * Removes an event handler.
+ * @param {String} eventName The type of event the handler was associated with.
+ * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope (optional) The scope originally specified for the handler.
+ */
+ removeListener: function(ename, fn, scope) {
+ var me = this,
+ config,
+ event,
+ options;
+
+ if (Ext.isObject(ename)) {
+ options = ename;
+ for (ename in options) {
+ if (options.hasOwnProperty(ename)) {
+ config = options[ename];
+ if (!me.eventOptionsRe.test(ename)) {
+ me.removeListener(ename, config.fn || config, config.scope || options.scope);
+ }
+ }
+ }
+ } else {
+ ename = ename.toLowerCase();
+ event = me.events[ename];
+ if (event.isEvent) {
+ event.removeListener(fn, scope);
+ }
+ }
+ },
+
+ /**
+ * Removes all listeners for this object including the managed listeners
+ */
+ clearListeners: function() {
+ var events = this.events,
+ event,
+ key;
+
+ for (key in events) {
+ if (events.hasOwnProperty(key)) {
+ event = events[key];
+ if (event.isEvent) {
+ event.clearListeners();
+ }
+ }
+ }
+
+ this.clearManagedListeners();
+ },
+
+ purgeListeners : function() {
+ console.warn('Observable: purgeListeners has been deprecated. Please use clearListeners.');
+ return this.clearListeners.apply(this, arguments);
+ },
+
+ /**
+ * Removes all managed listeners for this object.
+ */
+ clearManagedListeners : function() {
+ var managedListeners = this.managedListeners || [],
+ i = 0,
+ len = managedListeners.length,
+ managedListener;
+
+ for (; i < len; i++) {
+ managedListener = managedListeners[i];
+ managedListener.item.un(managedListener.ename, managedListener.fn, managedListener.scope);
+ }
+
+ this.managedListeners = [];
+ },
+
+ purgeManagedListeners : function() {
+ console.warn('Observable: purgeManagedListeners has been deprecated. Please use clearManagedListeners.');
+ return this.clearManagedListeners.apply(this, arguments);
+ },
+
+ /**
+ * Adds the specified events to the list of events which this Observable may fire.
+ * @param {Object/String} o Either an object with event names as properties with a value of <code>true</code>
+ * or the first event name string if multiple event names are being passed as separate parameters.
+ * @param {String} [additional] Optional additional event names if multiple event names are being passed as separate parameters.
+ * Usage:<pre><code>
+this.addEvents('storeloaded', 'storecleared');
+</code></pre>
+ */
+ addEvents: function(o) {
+ var me = this,
+ args,
+ len,
+ i;
+
+ me.events = me.events || {};
+ if (Ext.isString(o)) {
+ args = arguments;
+ i = args.length;
+
+ while (i--) {
+ me.events[args[i]] = me.events[args[i]] || true;
+ }
+ } else {
+ Ext.applyIf(me.events, o);
+ }
+ },
+
+ /**
+ * Checks to see if this object has any listeners for a specified event
+ * @param {String} eventName The name of the event to check for
+ * @return {Boolean} True if the event is being listened for, else false
+ */
+ hasListener: function(ename) {
+ var event = this.events[ename.toLowerCase()];
+ return event && event.isEvent === true && event.listeners.length > 0;
+ },
+
+ /**
+ * Suspend the firing of all events. (see {@link #resumeEvents})
+ * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
+ * after the {@link #resumeEvents} call instead of discarding all suspended events;
+ */
+ suspendEvents: function(queueSuspended) {
+ this.eventsSuspended = true;
+ if (queueSuspended && !this.eventQueue) {
+ this.eventQueue = [];
+ }
+ },
+
+ /**
+ * Resume firing events. (see {@link #suspendEvents})
+ * If events were suspended using the <code><b>queueSuspended</b></code> parameter, then all
+ * events fired during event suspension will be sent to any listeners now.
+ */
+ resumeEvents: function() {
+ var me = this,
+ queued = me.eventQueue || [];
+
+ me.eventsSuspended = false;
+ delete me.eventQueue;
+
+ Ext.each(queued,
+ function(e) {
+ me.fireEvent.apply(me, e);
+ });
+ },
+
+ /**
+ * Relays selected events from the specified Observable as if the events were fired by <code><b>this</b></code>.
+ * @param {Object} origin The Observable whose events this object is to relay.
+ * @param {Array} events Array of event names to relay.
+ */
+ relayEvents : function(origin, events, prefix) {
+ prefix = prefix || '';
+ var me = this,
+ len = events.length,
+ i = 0,
+ oldName,
+ newName;
+
+ for (; i < len; i++) {
+ oldName = events[i].substr(prefix.length);
+ newName = prefix + oldName;
+ me.events[newName] = me.events[newName] || true;
+ origin.on(oldName, me.createRelayer(newName));
+ }
+ },
+
+ /**
+ * @private
+ * Creates an event handling function which refires the event from this object as the passed event name.
+ * @param newName
+ * @returns {Function}
+ */
+ createRelayer: function(newName){
+ var me = this;
+ return function(){
+ return me.fireEvent.apply(me, [newName].concat(Array.prototype.slice.call(arguments, 0, -1)));
+ };
+ },
+
+ /**
+ * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
+ * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
+ * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component#getBubbleTarget}. The default
+ * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
+ * access the required target more quickly.</p>
+ * <p>Example:</p><pre><code>
+Ext.override(Ext.form.field.Base, {
+// Add functionality to Field's initComponent to enable the change event to bubble
+initComponent : Ext.Function.createSequence(Ext.form.field.Base.prototype.initComponent, function() {
+ this.enableBubble('change');
+}),
+
+// We know that we want Field's events to bubble directly to the FormPanel.
+getBubbleTarget : function() {
+ if (!this.formPanel) {
+ this.formPanel = this.findParentByType('form');
+ }
+ return this.formPanel;
+}
+});
+
+var myForm = new Ext.formPanel({
+title: 'User Details',
+items: [{
+ ...
+}],
+listeners: {
+ change: function() {
+ // Title goes red if form has been modified.
+ myForm.header.setStyle('color', 'red');
+ }
+}
+});
+</code></pre>
+ * @param {String/Array} events The event name to bubble, or an Array of event names.
+ */
+ enableBubble: function(events) {
+ var me = this;
+ if (!Ext.isEmpty(events)) {
+ events = Ext.isArray(events) ? events: Ext.Array.toArray(arguments);
+ Ext.each(events,
+ function(ename) {
+ ename = ename.toLowerCase();
+ var ce = me.events[ename] || true;
+ if (Ext.isBoolean(ce)) {
+ ce = new Ext.util.Event(me, ename);
+ me.events[ename] = ce;
+ }
+ ce.bubble = true;
+ });
+ }
+ }
+}, function() {
+ /**
+ * Removes an event handler (shorthand for {@link #removeListener}.)
+ * @param {String} eventName The type of event the handler was associated with.
+ * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope (optional) The scope originally specified for the handler.
+ * @method un
+ */
+
+ /**
+ * Appends an event handler to this object (shorthand for {@link #addListener}.)
+ * @param {String} eventName The type of event to listen for
+ * @param {Function} handler The method the event invokes
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to the object which fired the event.</b>
+ * @param {Object} options (optional) An object containing handler configuration.
+ * @method on
+ */
+
+ this.createAlias({
+ on: 'addListener',
+ un: 'removeListener',
+ mon: 'addManagedListener',
+ mun: 'removeManagedListener'
+ });
+
+ //deprecated, will be removed in 5.0
+ this.observeClass = this.observe;
+
+ Ext.apply(Ext.util.Observable.prototype, function(){
+ // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
+ // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
+ // private
+ function getMethodEvent(method){
+ var e = (this.methodEvents = this.methodEvents || {})[method],
+ returnValue,
+ v,
+ cancel,
+ obj = this;
+
+ if (!e) {
+ this.methodEvents[method] = e = {};
+ e.originalFn = this[method];
+ e.methodName = method;
+ e.before = [];
+ e.after = [];
+
+ var makeCall = function(fn, scope, args){
+ if((v = fn.apply(scope || obj, args)) !== undefined){
+ if (typeof v == 'object') {
+ if(v.returnValue !== undefined){
+ returnValue = v.returnValue;
+ }else{
+ returnValue = v;
+ }
+ cancel = !!v.cancel;
+ }
+ else
+ if (v === false) {
+ cancel = true;
+ }
+ else {
+ returnValue = v;
+ }
+ }
+ };
+
+ this[method] = function(){
+ var args = Array.prototype.slice.call(arguments, 0),
+ b, i, len;
+ returnValue = v = undefined;
+ cancel = false;
+
+ for(i = 0, len = e.before.length; i < len; i++){
+ b = e.before[i];
+ makeCall(b.fn, b.scope, args);
+ if (cancel) {
+ return returnValue;
+ }
+ }
+
+ if((v = e.originalFn.apply(obj, args)) !== undefined){
+ returnValue = v;
+ }
+
+ for(i = 0, len = e.after.length; i < len; i++){
+ b = e.after[i];
+ makeCall(b.fn, b.scope, args);
+ if (cancel) {
+ return returnValue;
+ }
+ }
+ return returnValue;
+ };
+ }
+ return e;
+ }
+
+ return {
+ // these are considered experimental
+ // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
+ // adds an 'interceptor' called before the original method
+ beforeMethod : function(method, fn, scope){
+ getMethodEvent.call(this, method).before.push({
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ // adds a 'sequence' called after the original method
+ afterMethod : function(method, fn, scope){
+ getMethodEvent.call(this, method).after.push({
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ removeMethodListener: function(method, fn, scope){
+ var e = this.getMethodEvent(method),
+ i, len;
+ for(i = 0, len = e.before.length; i < len; i++){
+ if(e.before[i].fn == fn && e.before[i].scope == scope){
+ e.before.splice(i, 1);
+ return;
+ }
+ }
+ for(i = 0, len = e.after.length; i < len; i++){
+ if(e.after[i].fn == fn && e.after[i].scope == scope){
+ e.after.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ toggleEventLogging: function(toggle) {
+ Ext.util.Observable[toggle ? 'capture' : 'releaseCapture'](this, function(en) {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.log(en, arguments);
+ }
+ });
+ }
+ };
+ }());
+});
+
+/**
+ * @class Ext.util.Animate
+ * This animation class is a mixin.
+ *
+ * Ext.util.Animate provides an API for the creation of animated transitions of properties and styles.
+ * This class is used as a mixin and currently applied to {@link Ext.core.Element}, {@link Ext.CompositeElement},
+ * {@link Ext.draw.Sprite}, {@link Ext.draw.CompositeSprite}, and {@link Ext.Component}. Note that Components
+ * have a limited subset of what attributes can be animated such as top, left, x, y, height, width, and
+ * opacity (color, paddings, and margins can not be animated).
+ *
+ * ## Animation Basics
+ *
+ * All animations require three things - `easing`, `duration`, and `to` (the final end value for each property)
+ * you wish to animate. Easing and duration are defaulted values specified below.
+ * Easing describes how the intermediate values used during a transition will be calculated.
+ * {@link Ext.fx.Anim#easing Easing} allows for a transition to change speed over its duration.
+ * You may use the defaults for easing and duration, but you must always set a
+ * {@link Ext.fx.Anim#to to} property which is the end value for all animations.
+ *
+ * Popular element 'to' configurations are:
+ *
+ * - opacity
+ * - x
+ * - y
+ * - color
+ * - height
+ * - width
+ *
+ * Popular sprite 'to' configurations are:
+ *
+ * - translation
+ * - path
+ * - scale
+ * - stroke
+ * - rotation
+ *
+ * The default duration for animations is 250 (which is a 1/4 of a second). Duration is denoted in
+ * milliseconds. Therefore 1 second is 1000, 1 minute would be 60000, and so on. The default easing curve
+ * used for all animations is 'ease'. Popular easing functions are included and can be found in {@link Ext.fx.Anim#easing Easing}.
+ *
+ * For example, a simple animation to fade out an element with a default easing and duration:
+ *
+ * var p1 = Ext.get('myElementId');
+ *
+ * p1.animate({
+ * to: {
+ * opacity: 0
+ * }
+ * });
+ *
+ * To make this animation fade out in a tenth of a second:
+ *
+ * var p1 = Ext.get('myElementId');
+ *
+ * p1.animate({
+ * duration: 100,
+ * to: {
+ * opacity: 0
+ * }
+ * });
+ *
+ * ## Animation Queues
+ *
+ * By default all animations are added to a queue which allows for animation via a chain-style API.
+ * For example, the following code will queue 4 animations which occur sequentially (one right after the other):
+ *
+ * p1.animate({
+ * to: {
+ * x: 500
+ * }
+ * }).animate({
+ * to: {
+ * y: 150
+ * }
+ * }).animate({
+ * to: {
+ * backgroundColor: '#f00' //red
+ * }
+ * }).animate({
+ * to: {
+ * opacity: 0
+ * }
+ * });
+ *
+ * You can change this behavior by calling the {@link Ext.util.Animate#syncFx syncFx} method and all
+ * subsequent animations for the specified target will be run concurrently (at the same time).
+ *
+ * p1.syncFx(); //this will make all animations run at the same time
+ *
+ * p1.animate({
+ * to: {
+ * x: 500
+ * }
+ * }).animate({
+ * to: {
+ * y: 150
+ * }
+ * }).animate({
+ * to: {
+ * backgroundColor: '#f00' //red
+ * }
+ * }).animate({
+ * to: {
+ * opacity: 0
+ * }
+ * });
+ *
+ * This works the same as:
+ *
+ * p1.animate({
+ * to: {
+ * x: 500,
+ * y: 150,
+ * backgroundColor: '#f00' //red
+ * opacity: 0
+ * }
+ * });
+ *
+ * The {@link Ext.util.Animate#stopAnimation stopAnimation} method can be used to stop any
+ * currently running animations and clear any queued animations.
+ *
+ * ## Animation Keyframes
+ *
+ * You can also set up complex animations with {@link Ext.fx.Anim#keyframe keyframe} which follows the
+ * CSS3 Animation configuration pattern. Note rotation, translation, and scaling can only be done for sprites.
+ * The previous example can be written with the following syntax:
+ *
+ * p1.animate({
+ * duration: 1000, //one second total
+ * keyframes: {
+ * 25: { //from 0 to 250ms (25%)
+ * x: 0
+ * },
+ * 50: { //from 250ms to 500ms (50%)
+ * y: 0
+ * },
+ * 75: { //from 500ms to 750ms (75%)
+ * backgroundColor: '#f00' //red
+ * },
+ * 100: { //from 750ms to 1sec
+ * opacity: 0
+ * }
+ * }
+ * });
+ *
+ * ## Animation Events
+ *
+ * Each animation you create has events for {@link Ext.fx.Anim#beforeanimation beforeanimation},
+ * {@link Ext.fx.Anim#afteranimate afteranimate}, and {@link Ext.fx.Anim#lastframe lastframe}.
+ * Keyframed animations adds an additional {@link Ext.fx.Animator#keyframe keyframe} event which
+ * fires for each keyframe in your animation.
+ *
+ * All animations support the {@link Ext.util.Observable#listeners listeners} configuration to attact functions to these events.
+ *
+ * startAnimate: function() {
+ * var p1 = Ext.get('myElementId');
+ * p1.animate({
+ * duration: 100,
+ * to: {
+ * opacity: 0
+ * },
+ * listeners: {
+ * beforeanimate: function() {
+ * // Execute my custom method before the animation
+ * this.myBeforeAnimateFn();
+ * },
+ * afteranimate: function() {
+ * // Execute my custom method after the animation
+ * this.myAfterAnimateFn();
+ * },
+ * scope: this
+ * });
+ * },
+ * myBeforeAnimateFn: function() {
+ * // My custom logic
+ * },
+ * myAfterAnimateFn: function() {
+ * // My custom logic
+ * }
+ *
+ * Due to the fact that animations run asynchronously, you can determine if an animation is currently
+ * running on any target by using the {@link Ext.util.Animate#getActiveAnimation getActiveAnimation}
+ * method. This method will return false if there are no active animations or return the currently
+ * running {@link Ext.fx.Anim} instance.
+ *
+ * In this example, we're going to wait for the current animation to finish, then stop any other
+ * queued animations before we fade our element's opacity to 0:
+ *
+ * var curAnim = p1.getActiveAnimation();
+ * if (curAnim) {
+ * curAnim.on('afteranimate', function() {
+ * p1.stopAnimation();
+ * p1.animate({
+ * to: {
+ * opacity: 0
+ * }
+ * });
+ * });
+ * }
+ *
+ * @docauthor Jamie Avins <jamie@sencha.com>
+ */
+Ext.define('Ext.util.Animate', {
+
+ uses: ['Ext.fx.Manager', 'Ext.fx.Anim'],
+
+ /**
+ * <p>Perform custom animation on this object.<p>
+ * <p>This method is applicable to both the the {@link Ext.Component Component} class and the {@link Ext.core.Element Element} class.
+ * It performs animated transitions of certain properties of this object over a specified timeline.</p>
+ * <p>The sole parameter is an object which specifies start property values, end property values, and properties which
+ * describe the timeline. Of the properties listed below, only <b><code>to</code></b> is mandatory.</p>
+ * <p>Properties include<ul>
+ * <li><code>from</code> <div class="sub-desc">An object which specifies start values for the properties being animated.
+ * If not supplied, properties are animated from current settings. The actual properties which may be animated depend upon
+ * ths object being animated. See the sections below on Element and Component animation.<div></li>
+ * <li><code>to</code> <div class="sub-desc">An object which specifies end values for the properties being animated.</div></li>
+ * <li><code>duration</code><div class="sub-desc">The duration <b>in milliseconds</b> for which the animation will run.</div></li>
+ * <li><code>easing</code> <div class="sub-desc">A string value describing an easing type to modify the rate of change from the default linear to non-linear. Values may be one of:<code><ul>
+ * <li>ease</li>
+ * <li>easeIn</li>
+ * <li>easeOut</li>
+ * <li>easeInOut</li>
+ * <li>backIn</li>
+ * <li>backOut</li>
+ * <li>elasticIn</li>
+ * <li>elasticOut</li>
+ * <li>bounceIn</li>
+ * <li>bounceOut</li>
+ * </ul></code></div></li>
+ * <li><code>keyframes</code> <div class="sub-desc">This is an object which describes the state of animated properties at certain points along the timeline.
+ * it is an object containing properties who's names are the percentage along the timeline being described and who's values specify the animation state at that point.</div></li>
+ * <li><code>listeners</code> <div class="sub-desc">This is a standard {@link Ext.util.Observable#listeners listeners} configuration object which may be used
+ * to inject behaviour at either the <code>beforeanimate</code> event or the <code>afteranimate</code> event.</div></li>
+ * </ul></p>
+ * <h3>Animating an {@link Ext.core.Element Element}</h3>
+ * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
+ * <li><code>x</code> <div class="sub-desc">The page X position in pixels.</div></li>
+ * <li><code>y</code> <div class="sub-desc">The page Y position in pixels</div></li>
+ * <li><code>left</code> <div class="sub-desc">The element's CSS <code>left</code> value. Units must be supplied.</div></li>
+ * <li><code>top</code> <div class="sub-desc">The element's CSS <code>top</code> value. Units must be supplied.</div></li>
+ * <li><code>width</code> <div class="sub-desc">The element's CSS <code>width</code> value. Units must be supplied.</div></li>
+ * <li><code>height</code> <div class="sub-desc">The element's CSS <code>height</code> value. Units must be supplied.</div></li>
+ * <li><code>scrollLeft</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
+ * <li><code>scrollTop</code> <div class="sub-desc">The element's <code>scrollLeft</code> value.</div></li>
+ * <li><code>opacity</code> <div class="sub-desc">The element's <code>opacity</code> value. This must be a value between <code>0</code> and <code>1</code>.</div></li>
+ * </ul>
+ * <p><b>Be aware than 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.</b></p>
+ * <h3>Animating a {@link Ext.Component Component}</h3>
+ * When animating an Element, the following properties may be specified in <code>from</code>, <code>to</code>, and <code>keyframe</code> objects:<ul>
+ * <li><code>x</code> <div class="sub-desc">The Component's page X position in pixels.</div></li>
+ * <li><code>y</code> <div class="sub-desc">The Component's page Y position in pixels</div></li>
+ * <li><code>left</code> <div class="sub-desc">The Component's <code>left</code> value in pixels.</div></li>
+ * <li><code>top</code> <div class="sub-desc">The Component's <code>top</code> value in pixels.</div></li>
+ * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
+ * <li><code>width</code> <div class="sub-desc">The Component's <code>width</code> value in pixels.</div></li>
+ * <li><code>dynamic</code> <div class="sub-desc">Specify as true to update the Component's layout (if it is a Container) at every frame
+ * of the animation. <i>Use sparingly as laying out on every intermediate size change is an expensive operation</i>.</div></li>
+ * </ul>
+ * <p>For example, to animate a Window to a new size, ensuring that its internal layout, and any shadow is correct:</p>
+ * <pre><code>
+myWindow = Ext.create('Ext.window.Window', {
+ title: 'Test Component animation',
+ width: 500,
+ height: 300,
+ layout: {
+ type: 'hbox',
+ align: 'stretch'
+ },
+ items: [{
+ title: 'Left: 33%',
+ margins: '5 0 5 5',
+ flex: 1
+ }, {
+ title: 'Left: 66%',
+ margins: '5 5 5 5',
+ flex: 2
+ }]
+});
+myWindow.show();
+myWindow.header.el.on('click', function() {
+ myWindow.animate({
+ to: {
+ width: (myWindow.getWidth() == 500) ? 700 : 500,
+ height: (myWindow.getHeight() == 300) ? 400 : 300,
+ }
+ });
+});
+</code></pre>
+ * <p>For performance reasons, by default, the internal layout is only updated when the Window reaches its final <code>"to"</code> size. If dynamic updating of the Window's child
+ * Components is required, then configure the animation with <code>dynamic: true</code> and the two child items will maintain their proportions during the animation.</p>
+ * @param {Object} config An object containing properties which describe the animation's start and end states, and the timeline of the animation.
+ * @return {Object} this
+ */
+ animate: function(animObj) {
+ var me = this;
+ if (Ext.fx.Manager.hasFxBlock(me.id)) {
+ return me;
+ }
+ Ext.fx.Manager.queueFx(Ext.create('Ext.fx.Anim', me.anim(animObj)));
+ return this;
+ },
+
+ // @private - process the passed fx configuration.
+ anim: function(config) {
+ if (!Ext.isObject(config)) {
+ return (config) ? {} : false;
+ }
+
+ var me = this;
+
+ if (config.stopAnimation) {
+ me.stopAnimation();
+ }
+
+ Ext.applyIf(config, Ext.fx.Manager.getFxDefaults(me.id));
+
+ return Ext.apply({
+ target: me,
+ paused: true
+ }, config);
+ },
+
+ /**
+ * Stops any running effects and clears this object's internal effects queue if it contains
+ * any additional effects that haven't started yet.
+ * @return {Ext.core.Element} The Element
+ */
+ stopFx: Ext.Function.alias(Ext.util.Animate, 'stopAnimation'),
+
+ /**
+ * @deprecated 4.0 Replaced by {@link #stopAnimation}
+ * Stops any running effects and clears this object's internal effects queue if it contains
+ * any additional effects that haven't started yet.
+ * @return {Ext.core.Element} The Element
+ */
+ stopAnimation: function() {
+ Ext.fx.Manager.stopAnimation(this.id);
+ },
+
+ /**
+ * Ensures that all effects queued after syncFx is called on this object are
+ * run concurrently. This is the opposite of {@link #sequenceFx}.
+ * @return {Ext.core.Element} The Element
+ */
+ syncFx: function() {
+ Ext.fx.Manager.setFxDefaults(this.id, {
+ concurrent: true
+ });
+ },
+
+ /**
+ * Ensures that all effects queued after sequenceFx is called on this object are
+ * run in sequence. This is the opposite of {@link #syncFx}.
+ * @return {Ext.core.Element} The Element
+ */
+ sequenceFx: function() {
+ Ext.fx.Manager.setFxDefaults(this.id, {
+ concurrent: false
+ });
+ },
+
+ /**
+ * @deprecated 4.0 Replaced by {@link #getActiveAnimation}
+ * Returns thq current animation if this object has any effects actively running or queued, else returns false.
+ * @return {Mixed} anim if element has active effects, else false
+ */
+ hasActiveFx: Ext.Function.alias(Ext.util.Animate, 'getActiveAnimation'),
+
+ /**
+ * Returns thq current animation if this object has any effects actively running or queued, else returns false.
+ * @return {Mixed} anim if element has active effects, else false
+ */
+ getActiveAnimation: function() {
+ return Ext.fx.Manager.getActiveAnimation(this.id);
+ }
+});
+
+// Apply Animate mixin manually until Element is defined in the proper 4.x way
+Ext.applyIf(Ext.core.Element.prototype, Ext.util.Animate.prototype);
+/**
+ * @class Ext.state.Provider
+ * <p>Abstract base class for state provider implementations. The provider is responsible
+ * for setting values and extracting values to/from the underlying storage source. The
+ * storage source can vary and the details should be implemented in a subclass. For example
+ * a provider could use a server side database or the browser localstorage where supported.</p>
+ *
+ * <p>This class provides methods for encoding and decoding <b>typed</b> variables including
+ * dates and defines the Provider interface. By default these methods put the value and the
+ * type information into a delimited string that can be stored. These should be overridden in
+ * a subclass if you want to change the format of the encoded value and subsequent decoding.</p>
+ */
+Ext.define('Ext.state.Provider', {
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ /**
+ * @cfg {String} prefix A string to prefix to items stored in the underlying state store.
+ * Defaults to <tt>'ext-'</tt>
+ */
+ prefix: 'ext-',
+
+ constructor : function(config){
+ config = config || {};
+ var me = this;
+ Ext.apply(me, config);
+ /**
+ * @event statechange
+ * Fires when a state change occurs.
+ * @param {Provider} this This state provider
+ * @param {String} key The state key which was changed
+ * @param {String} value The encoded value for the state
+ */
+ me.addEvents("statechange");
+ me.state = {};
+ me.mixins.observable.constructor.call(me);
+ },
+
+ /**
+ * Returns the current value for a key
+ * @param {String} name The key name
+ * @param {Mixed} defaultValue A default value to return if the key's value is not found
+ * @return {Mixed} The state data
+ */
+ get : function(name, defaultValue){
+ return typeof this.state[name] == "undefined" ?
+ defaultValue : this.state[name];
+ },
+
+ /**
+ * Clears a value from the state
+ * @param {String} name The key name
+ */
+ clear : function(name){
+ var me = this;
+ delete me.state[name];
+ me.fireEvent("statechange", me, name, null);
+ },
+
+ /**
+ * Sets the value for a key
+ * @param {String} name The key name
+ * @param {Mixed} value The value to set
+ */
+ set : function(name, value){
+ var me = this;
+ me.state[name] = value;
+ me.fireEvent("statechange", me, name, value);
+ },
+
+ /**
+ * Decodes a string previously encoded with {@link #encodeValue}.
+ * @param {String} value The value to decode
+ * @return {Mixed} The decoded value
+ */
+ decodeValue : function(value){
+
+ // a -> Array
+ // n -> Number
+ // d -> Date
+ // b -> Boolean
+ // s -> String
+ // o -> Object
+ // -> Empty (null)
+
+ var me = this,
+ re = /^(a|n|d|b|s|o|e)\:(.*)$/,
+ matches = re.exec(unescape(value)),
+ all,
+ type,
+ value,
+ keyValue;
+
+ if(!matches || !matches[1]){
+ return; // non state
+ }
+
+ type = matches[1];
+ value = matches[2];
+ switch (type) {
+ case 'e':
+ return null;
+ case 'n':
+ return parseFloat(value);
+ case 'd':
+ return new Date(Date.parse(value));
+ case 'b':
+ return (value == '1');
+ case 'a':
+ all = [];
+ if(value != ''){
+ Ext.each(value.split('^'), function(val){
+ all.push(me.decodeValue(val));
+ }, me);
+ }
+ return all;
+ case 'o':
+ all = {};
+ if(value != ''){
+ Ext.each(value.split('^'), function(val){
+ keyValue = val.split('=');
+ all[keyValue[0]] = me.decodeValue(keyValue[1]);
+ }, me);
+ }
+ return all;
+ default:
+ return value;
+ }
+ },
+
+ /**
+ * Encodes a value including type information. Decode with {@link #decodeValue}.
+ * @param {Mixed} value The value to encode
+ * @return {String} The encoded value
+ */
+ encodeValue : function(value){
+ var flat = '',
+ i = 0,
+ enc,
+ len,
+ key;
+
+ if (value == null) {
+ return 'e:1';
+ } else if(typeof value == 'number') {
+ enc = 'n:' + value;
+ } else if(typeof value == 'boolean') {
+ enc = 'b:' + (value ? '1' : '0');
+ } else if(Ext.isDate(value)) {
+ enc = 'd:' + value.toGMTString();
+ } else if(Ext.isArray(value)) {
+ for (len = value.length; i < len; i++) {
+ flat += this.encodeValue(value[i]);
+ if (i != len - 1) {
+ flat += '^';
+ }
+ }
+ enc = 'a:' + flat;
+ } else if (typeof value == 'object') {
+ for (key in value) {
+ if (typeof value[key] != 'function' && value[key] !== undefined) {
+ flat += key + '=' + this.encodeValue(value[key]) + '^';
+ }
+ }
+ enc = 'o:' + flat.substring(0, flat.length-1);
+ } else {
+ enc = 's:' + value;
+ }
+ return escape(enc);
+ }
+});
+/**
+ * @class Ext.util.HashMap
+ * <p>
+ * Represents a collection of a set of key and value pairs. Each key in the HashMap
+ * must be unique, the same key cannot exist twice. Access to items is provided via
+ * the key only. Sample usage:
+ * <pre><code>
+var map = new Ext.util.HashMap();
+map.add('key1', 1);
+map.add('key2', 2);
+map.add('key3', 3);
+
+map.each(function(key, value, length){
+ console.log(key, value, length);
+});
+ * </code></pre>
+ * </p>
+ *
+ * <p>The HashMap is an unordered class,
+ * there is no guarantee when iterating over the items that they will be in any particular
+ * order. If this is required, then use a {@link Ext.util.MixedCollection}.
+ * </p>
+ * @constructor
+ * @param {Object} config The configuration options
+ */
+Ext.define('Ext.util.HashMap', {
+
+ /**
+ * @cfg {Function} keyFn A function that is used to retrieve a default key for a passed object.
+ * A default is provided that returns the <b>id</b> property on the object. This function is only used
+ * if the add method is called with a single argument.
+ */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ constructor: function(config) {
+ var me = this;
+
+ me.addEvents(
+ /**
+ * @event add
+ * Fires when a new item is added to the hash
+ * @param {Ext.util.HashMap} this.
+ * @param {String} key The key of the added item.
+ * @param {Object} value The value of the added item.
+ */
+ 'add',
+ /**
+ * @event clear
+ * Fires when the hash is cleared.
+ * @param {Ext.util.HashMap} this.
+ */
+ 'clear',
+ /**
+ * @event remove
+ * Fires when an item is removed from the hash.
+ * @param {Ext.util.HashMap} this.
+ * @param {String} key The key of the removed item.
+ * @param {Object} value The value of the removed item.
+ */
+ 'remove',
+ /**
+ * @event replace
+ * Fires when an item is replaced in the hash.
+ * @param {Ext.util.HashMap} this.
+ * @param {String} key The key of the replaced item.
+ * @param {Object} value The new value for the item.
+ * @param {Object} old The old value for the item.
+ */
+ 'replace'
+ );
+
+ me.mixins.observable.constructor.call(me, config);
+ me.clear(true);
+ },
+
+ /**
+ * Gets the number of items in the hash.
+ * @return {Number} The number of items in the hash.
+ */
+ getCount: function() {
+ return this.length;
+ },
+
+ /**
+ * Implementation for being able to extract the key from an object if only
+ * a single argument is passed.
+ * @private
+ * @param {String} key The key
+ * @param {Object} value The value
+ * @return {Array} [key, value]
+ */
+ getData: function(key, value) {
+ // if we have no value, it means we need to get the key from the object
+ if (value === undefined) {
+ value = key;
+ key = this.getKey(value);
+ }
+
+ return [key, value];
+ },
+
+ /**
+ * Extracts the key from an object. This is a default implementation, it may be overridden
+ * @private
+ * @param {Object} o The object to get the key from
+ * @return {String} The key to use.
+ */
+ getKey: function(o) {
+ return o.id;
+ },
+
+ /**
+ * Adds an item to the collection. Fires the {@link #add} event when complete.
+ * @param {String} key <p>The key to associate with the item, or the new item.</p>
+ * <p>If a {@link #getKey} implementation was specified for this HashMap,
+ * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
+ * the HashMap will be able to <i>derive</i> the key for the new item.
+ * In this case just pass the new item in this parameter.</p>
+ * @param {Object} o The item to add.
+ * @return {Object} The item added.
+ */
+ add: function(key, value) {
+ var me = this,
+ data;
+
+ if (arguments.length === 1) {
+ value = key;
+ key = me.getKey(value);
+ }
+
+ if (me.containsKey(key)) {
+ me.replace(key, value);
+ }
+
+ data = me.getData(key, value);
+ key = data[0];
+ value = data[1];
+ me.map[key] = value;
+ ++me.length;
+ me.fireEvent('add', me, key, value);
+ return value;
+ },
+
+ /**
+ * Replaces an item in the hash. If the key doesn't exist, the
+ * {@link #add} method will be used.
+ * @param {String} key The key of the item.
+ * @param {Object} value The new value for the item.
+ * @return {Object} The new value of the item.
+ */
+ replace: function(key, value) {
+ var me = this,
+ map = me.map,
+ old;
+
+ if (!me.containsKey(key)) {
+ me.add(key, value);
+ }
+ old = map[key];
+ map[key] = value;
+ me.fireEvent('replace', me, key, value, old);
+ return value;
+ },
+
+ /**
+ * Remove an item from the hash.
+ * @param {Object} o The value of the item to remove.
+ * @return {Boolean} True if the item was successfully removed.
+ */
+ remove: function(o) {
+ var key = this.findKey(o);
+ if (key !== undefined) {
+ return this.removeAtKey(key);
+ }
+ return false;
+ },
+
+ /**
+ * Remove an item from the hash.
+ * @param {String} key The key to remove.
+ * @return {Boolean} True if the item was successfully removed.
+ */
+ removeAtKey: function(key) {
+ var me = this,
+ value;
+
+ if (me.containsKey(key)) {
+ value = me.map[key];
+ delete me.map[key];
+ --me.length;
+ me.fireEvent('remove', me, key, value);
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Retrieves an item with a particular key.
+ * @param {String} key The key to lookup.
+ * @return {Object} The value at that key. If it doesn't exist, <tt>undefined</tt> is returned.
+ */
+ get: function(key) {
+ return this.map[key];
+ },
+
+ /**
+ * Removes all items from the hash.
+ * @return {Ext.util.HashMap} this
+ */
+ clear: function(/* private */ initial) {
+ var me = this;
+ me.map = {};
+ me.length = 0;
+ if (initial !== true) {
+ me.fireEvent('clear', me);
+ }
+ return me;
+ },
+
+ /**
+ * Checks whether a key exists in the hash.
+ * @param {String} key The key to check for.
+ * @return {Boolean} True if they key exists in the hash.
+ */
+ containsKey: function(key) {
+ return this.map[key] !== undefined;
+ },
+
+ /**
+ * Checks whether a value exists in the hash.
+ * @param {Object} value The value to check for.
+ * @return {Boolean} True if the value exists in the dictionary.
+ */
+ contains: function(value) {
+ return this.containsKey(this.findKey(value));
+ },
+
+ /**
+ * Return all of the keys in the hash.
+ * @return {Array} An array of keys.
+ */
+ getKeys: function() {
+ return this.getArray(true);
+ },
+
+ /**
+ * Return all of the values in the hash.
+ * @return {Array} An array of values.
+ */
+ getValues: function() {
+ return this.getArray(false);
+ },
+
+ /**
+ * Gets either the keys/values in an array from the hash.
+ * @private
+ * @param {Boolean} isKey True to extract the keys, otherwise, the value
+ * @return {Array} An array of either keys/values from the hash.
+ */
+ getArray: function(isKey) {
+ var arr = [],
+ key,
+ map = this.map;
+ for (key in map) {
+ if (map.hasOwnProperty(key)) {
+ arr.push(isKey ? key: map[key]);
+ }
+ }
+ return arr;
+ },
+
+ /**
+ * Executes the specified function once for each item in the hash.
+ * Returning false from the function will cease iteration.
+ *
+ * The paramaters passed to the function are:
+ * <div class="mdetail-params"><ul>
+ * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
+ * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
+ * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the hash</p></li>
+ * </ul></div>
+ * @param {Function} fn The function to execute.
+ * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
+ * @return {Ext.util.HashMap} this
+ */
+ each: function(fn, scope) {
+ // copy items so they may be removed during iteration.
+ var items = Ext.apply({}, this.map),
+ key,
+ length = this.length;
+
+ scope = scope || this;
+ for (key in items) {
+ if (items.hasOwnProperty(key)) {
+ if (fn.call(scope, key, items[key], length) === false) {
+ break;
+ }
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Performs a shallow copy on this hash.
+ * @return {Ext.util.HashMap} The new hash object.
+ */
+ clone: function() {
+ var hash = new this.self(),
+ map = this.map,
+ key;
+
+ hash.suspendEvents();
+ for (key in map) {
+ if (map.hasOwnProperty(key)) {
+ hash.add(key, map[key]);
+ }
+ }
+ hash.resumeEvents();
+ return hash;
+ },
+
+ /**
+ * @private
+ * Find the key for a value.
+ * @param {Object} value The value to find.
+ * @return {Object} The value of the item. Returns <tt>undefined</tt> if not found.
+ */
+ findKey: function(value) {
+ var key,
+ map = this.map;
+
+ for (key in map) {
+ if (map.hasOwnProperty(key) && map[key] === value) {
+ return key;
+ }
+ }
+ return undefined;
+ }
+});
+
+/**
+ * @class Ext.Template
+ * <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
+ * for greater performance.</p>
+ * An instance of this class may be created by passing to the constructor either
+ * a single argument, or multiple arguments:
+ * <div class="mdetail-params"><ul>
+ * <li><b>single argument</b> : String/Array
+ * <div class="sub-desc">
+ * The single argument may be either a String or an Array:<ul>
+ * <li><tt>String</tt> : </li><pre><code>
+var t = new Ext.Template("<div>Hello {0}.</div>");
+t.{@link #append}('some-element', ['foo']);
+ </code></pre>
+ * <li><tt>Array</tt> : </li>
+ * An Array will be combined with <code>join('')</code>.
+<pre><code>
+var t = new Ext.Template([
+ '<div name="{id}">',
+ '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
+ '</div>',
+]);
+t.{@link #compile}();
+t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
+ </code></pre>
+ * </ul></div></li>
+ * <li><b>multiple arguments</b> : String, Object, Array, ...
+ * <div class="sub-desc">
+ * Multiple arguments will be combined with <code>join('')</code>.
+ * <pre><code>
+var t = new Ext.Template(
+ '<div name="{id}">',
+ '<span class="{cls}">{name} {value}</span>',
+ '</div>',
+ // a configuration object:
+ {
+ compiled: true, // {@link #compile} immediately
+ }
+);
+ </code></pre>
+ * <p><b>Notes</b>:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>For a list of available format functions, see {@link Ext.util.Format}.</li>
+ * <li><code>disableFormats</code> reduces <code>{@link #apply}</code> time
+ * when no formatting is required.</li>
+ * </ul></div>
+ * </div></li>
+ * </ul></div>
+ * @param {Mixed} config
+ */
+
+Ext.define('Ext.Template', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.core.DomHelper', 'Ext.util.Format'],
+
+ statics: {
+ /**
+ * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
+ * @param {String/HTMLElement} el A DOM element or its id
+ * @param {Object} config A configuration object
+ * @return {Ext.Template} The created template
+ * @static
+ */
+ from: function(el, config) {
+ el = Ext.getDom(el);
+ return new this(el.value || el.innerHTML, config || '');
+ }
+ },
+
+ /* End Definitions */
+
+ constructor: function(html) {
+ var me = this,
+ args = arguments,
+ buffer = [],
+ i = 0,
+ length = args.length,
+ value;
+
+ me.initialConfig = {};
+
+ if (length > 1) {
+ for (; i < length; i++) {
+ value = args[i];
+ if (typeof value == 'object') {
+ Ext.apply(me.initialConfig, value);
+ Ext.apply(me, value);
+ } else {
+ buffer.push(value);
+ }
+ }
+ html = buffer.join('');
+ } else {
+ if (Ext.isArray(html)) {
+ buffer.push(html.join(''));
+ } else {
+ buffer.push(html);
+ }
+ }
+
+ // @private
+ me.html = buffer.join('');
+
+ if (me.compiled) {
+ me.compile();
+ }
+ },
+ isTemplate: true,
+ /**
+ * @cfg {Boolean} disableFormats true to disable format functions in the template. If the template doesn't contain format functions, setting
+ * disableFormats to true will reduce apply time (defaults to false)
+ */
+ disableFormats: false,
+
+ re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
+ /**
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ * @hide repeat doc
+ */
+ applyTemplate: function(values) {
+ var me = this,
+ useFormat = me.disableFormats !== true,
+ fm = Ext.util.Format,
+ tpl = me;
+
+ if (me.compiled) {
+ return me.compiled(values);
+ }
+ function fn(m, name, format, args) {
+ if (format && useFormat) {
+ if (args) {
+ args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
+ } else {
+ args = [values[name]];
+ }
+ if (format.substr(0, 5) == "this.") {
+ return tpl[format.substr(5)].apply(tpl, args);
+ }
+ else {
+ return fm[format].apply(fm, args);
+ }
+ }
+ else {
+ return values[name] !== undefined ? values[name] : "";
+ }
+ }
+ return me.html.replace(me.re, fn);
+ },
+
+ /**
+ * Sets the HTML used as the template and optionally compiles it.
+ * @param {String} html
+ * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
+ * @return {Ext.Template} this
+ */
+ set: function(html, compile) {
+ var me = this;
+ me.html = html;
+ me.compiled = null;
+ return compile ? me.compile() : me;
+ },
+
+ compileARe: /\\/g,
+ compileBRe: /(\r\n|\n)/g,
+ compileCRe: /'/g,
+ /**
+ * Compiles the template into an internal function, eliminating the RegEx overhead.
+ * @return {Ext.Template} this
+ * @hide repeat doc
+ */
+ compile: function() {
+ var me = this,
+ fm = Ext.util.Format,
+ useFormat = me.disableFormats !== true,
+ body, bodyReturn;
+
+ function fn(m, name, format, args) {
+ if (format && useFormat) {
+ args = args ? ',' + args: "";
+ if (format.substr(0, 5) != "this.") {
+ format = "fm." + format + '(';
+ }
+ else {
+ format = 'this.' + format.substr(5) + '(';
+ }
+ }
+ else {
+ args = '';
+ format = "(values['" + name + "'] == undefined ? '' : ";
+ }
+ return "'," + format + "values['" + name + "']" + args + ") ,'";
+ }
+
+ bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
+ body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
+ eval(body);
+ return me;
+ },
+
+ /**
+ * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
+ * @param {Mixed} el The context element
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
+ * @return {HTMLElement/Ext.core.Element} The new node or Element
+ */
+ insertFirst: function(el, values, returnElement) {
+ return this.doInsert('afterBegin', el, values, returnElement);
+ },
+
+ /**
+ * Applies the supplied values to the template and inserts the new node(s) before el.
+ * @param {Mixed} el The context element
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
+ * @return {HTMLElement/Ext.core.Element} The new node or Element
+ */
+ insertBefore: function(el, values, returnElement) {
+ return this.doInsert('beforeBegin', el, values, returnElement);
+ },
+
+ /**
+ * Applies the supplied values to the template and inserts the new node(s) after el.
+ * @param {Mixed} el The context element
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
+ * @return {HTMLElement/Ext.core.Element} The new node or Element
+ */
+ insertAfter: function(el, values, returnElement) {
+ return this.doInsert('afterEnd', el, values, returnElement);
+ },
+
+ /**
+ * Applies the supplied <code>values</code> to the template and appends
+ * the new node(s) to the specified <code>el</code>.
+ * <p>For example usage {@link #Template see the constructor}.</p>
+ * @param {Mixed} el The context element
+ * @param {Object/Array} values
+ * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
+ * or an object (i.e. <code>{foo: 'bar'}</code>).
+ * @param {Boolean} returnElement (optional) true to return an Ext.core.Element (defaults to undefined)
+ * @return {HTMLElement/Ext.core.Element} The new node or Element
+ */
+ append: function(el, values, returnElement) {
+ return this.doInsert('beforeEnd', el, values, returnElement);
+ },
+
+ doInsert: function(where, el, values, returnEl) {
+ el = Ext.getDom(el);
+ var newNode = Ext.core.DomHelper.insertHtml(where, el, this.applyTemplate(values));
+ return returnEl ? Ext.get(newNode, true) : newNode;
+ },
+
+ /**
+ * Applies the supplied values to the template and overwrites the content of el with the new node(s).
+ * @param {Mixed} el The context element
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
+ * @return {HTMLElement/Ext.core.Element} The new node or Element
+ */
+ overwrite: function(el, values, returnElement) {
+ el = Ext.getDom(el);
+ el.innerHTML = this.applyTemplate(values);
+ return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
+ }
+}, function() {
+
+ /**
+ * Alias for {@link #applyTemplate}
+ * Returns an HTML fragment of this template with the specified <code>values</code> applied.
+ * @param {Object/Array} values
+ * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
+ * or an object (i.e. <code>{foo: 'bar'}</code>).
+ * @return {String} The HTML fragment
+ * @member Ext.Template
+ * @method apply
+ */
+ this.createAlias('apply', 'applyTemplate');
+});
+
+/**
+ * @class Ext.ComponentQuery
+ * @extends Object
+ *
+ * Provides searching of Components within Ext.ComponentManager (globally) or a specific
+ * Ext.container.Container on the document with a similar syntax to a CSS selector.
+ *
+ * Components can be retrieved by using their {@link Ext.Component xtype} with an optional . prefix
+<ul>
+ <li>component or .component</li>
+ <li>gridpanel or .gridpanel</li>
+</ul>
+ *
+ * An itemId or id must be prefixed with a #
+<ul>
+ <li>#myContainer</li>
+</ul>
+ *
+ *
+ * Attributes must be wrapped in brackets
+<ul>
+ <li>component[autoScroll]</li>
+ <li>panel[title="Test"]</li>
+</ul>
+ *
+ * Member expressions from candidate Components may be tested. If the expression returns a <i>truthy</i> value,
+ * the candidate Component will be included in the query:<pre><code>
+var disabledFields = myFormPanel.query("{isDisabled()}");
+</code></pre>
+ *
+ * Pseudo classes may be used to filter results in the same way as in {@link Ext.DomQuery DomQuery}:<code><pre>
+// Function receives array and returns a filtered array.
+Ext.ComponentQuery.pseudos.invalid = function(items) {
+ var i = 0, l = items.length, c, result = [];
+ for (; i < l; i++) {
+ if (!(c = items[i]).isValid()) {
+ result.push(c);
+ }
+ }
+ return result;
+};
+
+var invalidFields = myFormPanel.query('field:invalid');
+if (invalidFields.length) {
+ invalidFields[0].getEl().scrollIntoView(myFormPanel.body);
+ for (var i = 0, l = invalidFields.length; i < l; i++) {
+ invalidFields[i].getEl().frame("red");
+ }
+}
+</pre></code>
+ * <p>
+ * Default pseudos include:<br />
+ * - not
+ * </p>
+ *
+ * Queries return an array of components.
+ * Here are some example queries.
+<pre><code>
+ // retrieve all Ext.Panels in the document by xtype
+ var panelsArray = Ext.ComponentQuery.query('panel');
+
+ // retrieve all Ext.Panels within the container with an id myCt
+ var panelsWithinmyCt = Ext.ComponentQuery.query('#myCt panel');
+
+ // retrieve all direct children which are Ext.Panels within myCt
+ var directChildPanel = Ext.ComponentQuery.query('#myCt > panel');
+
+ // retrieve all gridpanels and listviews
+ var gridsAndLists = Ext.ComponentQuery.query('gridpanel, listview');
+</code></pre>
+
+For easy access to queries based from a particular Container see the {@link Ext.container.Container#query},
+{@link Ext.container.Container#down} and {@link Ext.container.Container#child} methods. Also see
+{@link Ext.Component#up}.
+ * @singleton
+ */
+Ext.define('Ext.ComponentQuery', {
+ singleton: true,
+ uses: ['Ext.ComponentManager']
+}, function() {
+
+ var cq = this,
+
+ // A function source code pattern with a placeholder which accepts an expression which yields a truth value when applied
+ // as a member on each item in the passed array.
+ filterFnPattern = [
+ 'var r = [],',
+ 'i = 0,',
+ 'it = items,',
+ 'l = it.length,',
+ 'c;',
+ 'for (; i < l; i++) {',
+ 'c = it[i];',
+ 'if (c.{0}) {',
+ 'r.push(c);',
+ '}',
+ '}',
+ 'return r;'
+ ].join(''),
+
+ filterItems = function(items, operation) {
+ // Argument list for the operation is [ itemsArray, operationArg1, operationArg2...]
+ // The operation's method loops over each item in the candidate array and
+ // returns an array of items which match its criteria
+ return operation.method.apply(this, [ items ].concat(operation.args));
+ },
+
+ getItems = function(items, mode) {
+ var result = [],
+ i = 0,
+ length = items.length,
+ candidate,
+ deep = mode !== '>';
+
+ for (; i < length; i++) {
+ candidate = items[i];
+ if (candidate.getRefItems) {
+ result = result.concat(candidate.getRefItems(deep));
+ }
+ }
+ return result;
+ },
+
+ getAncestors = function(items) {
+ var result = [],
+ i = 0,
+ length = items.length,
+ candidate;
+ for (; i < length; i++) {
+ candidate = items[i];
+ while (!!(candidate = (candidate.ownerCt || candidate.floatParent))) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ },
+
+ // Filters the passed candidate array and returns only items which match the passed xtype
+ filterByXType = function(items, xtype, shallow) {
+ if (xtype === '*') {
+ return items.slice();
+ }
+ else {
+ var result = [],
+ i = 0,
+ length = items.length,
+ candidate;
+ for (; i < length; i++) {
+ candidate = items[i];
+ if (candidate.isXType(xtype, shallow)) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ }
+ },
+
+ // Filters the passed candidate array and returns only items which have the passed className
+ filterByClassName = function(items, className) {
+ var EA = Ext.Array,
+ result = [],
+ i = 0,
+ length = items.length,
+ candidate;
+ for (; i < length; i++) {
+ candidate = items[i];
+ if (candidate.el ? candidate.el.hasCls(className) : EA.contains(candidate.initCls(), className)) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ },
+
+ // Filters the passed candidate array and returns only items which have the specified property match
+ filterByAttribute = function(items, property, operator, value) {
+ var result = [],
+ i = 0,
+ length = items.length,
+ candidate;
+ for (; i < length; i++) {
+ candidate = items[i];
+ if (!value ? !!candidate[property] : (String(candidate[property]) === value)) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ },
+
+ // Filters the passed candidate array and returns only items which have the specified itemId or id
+ filterById = function(items, id) {
+ var result = [],
+ i = 0,
+ length = items.length,
+ candidate;
+ for (; i < length; i++) {
+ candidate = items[i];
+ if (candidate.getItemId() === id) {
+ result.push(candidate);
+ }
+ }
+ return result;
+ },
+
+ // Filters the passed candidate array and returns only items which the named pseudo class matcher filters in
+ filterByPseudo = function(items, name, value) {
+ return cq.pseudos[name](items, value);
+ },
+
+ // Determines leading mode
+ // > for direct child, and ^ to switch to ownerCt axis
+ modeRe = /^(\s?([>\^])\s?|\s|$)/,
+
+ // Matches a token with possibly (true|false) appended for the "shallow" parameter
+ tokenRe = /^(#)?([\w\-]+|\*)(?:\((true|false)\))?/,
+
+ matchers = [{
+ // Checks for .xtype with possibly (true|false) appended for the "shallow" parameter
+ re: /^\.([\w\-]+)(?:\((true|false)\))?/,
+ method: filterByXType
+ },{
+ // checks for [attribute=value]
+ re: /^(?:[\[](?:@)?([\w\-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]])/,
+ method: filterByAttribute
+ }, {
+ // checks for #cmpItemId
+ re: /^#([\w\-]+)/,
+ method: filterById
+ }, {
+ // checks for :<pseudo_class>(<selector>)
+ re: /^\:([\w\-]+)(?:\(((?:\{[^\}]+\})|(?:(?!\{)[^\s>\/]*?(?!\})))\))?/,
+ method: filterByPseudo
+ }, {
+ // checks for {<member_expression>}
+ re: /^(?:\{([^\}]+)\})/,
+ method: filterFnPattern
+ }];
+
+ /**
+ * @class Ext.ComponentQuery.Query
+ * @extends Object
+ * @private
+ */
+ cq.Query = Ext.extend(Object, {
+ constructor: function(cfg) {
+ cfg = cfg || {};
+ Ext.apply(this, cfg);
+ },
+
+ /**
+ * @private
+ * Executes this Query upon the selected root.
+ * The root provides the initial source of candidate Component matches which are progressively
+ * filtered by iterating through this Query's operations cache.
+ * If no root is provided, all registered Components are searched via the ComponentManager.
+ * root may be a Container who's descendant Components are filtered
+ * root may be a Component with an implementation of getRefItems which provides some nested Components such as the
+ * docked items within a Panel.
+ * root may be an array of candidate Components to filter using this Query.
+ */
+ execute : function(root) {
+ var operations = this.operations,
+ i = 0,
+ length = operations.length,
+ operation,
+ workingItems;
+
+ // no root, use all Components in the document
+ if (!root) {
+ workingItems = Ext.ComponentManager.all.getArray();
+ }
+ // Root is a candidate Array
+ else if (Ext.isArray(root)) {
+ workingItems = root;
+ }
+
+ // We are going to loop over our operations and take care of them
+ // one by one.
+ for (; i < length; i++) {
+ operation = operations[i];
+
+ // The mode operation requires some custom handling.
+ // All other operations essentially filter down our current
+ // working items, while mode replaces our current working
+ // items by getting children from each one of our current
+ // working items. The type of mode determines the type of
+ // children we get. (e.g. > only gets direct children)
+ if (operation.mode === '^') {
+ workingItems = getAncestors(workingItems || [root]);
+ }
+ else if (operation.mode) {
+ workingItems = getItems(workingItems || [root], operation.mode);
+ }
+ else {
+ workingItems = filterItems(workingItems || getItems([root]), operation);
+ }
+
+ // If this is the last operation, it means our current working
+ // items are the final matched items. Thus return them!
+ if (i === length -1) {
+ return workingItems;
+ }
+ }
+ return [];
+ },
+
+ is: function(component) {
+ var operations = this.operations,
+ components = Ext.isArray(component) ? component : [component],
+ originalLength = components.length,
+ lastOperation = operations[operations.length-1],
+ ln, i;
+
+ components = filterItems(components, lastOperation);
+ if (components.length === originalLength) {
+ if (operations.length > 1) {
+ for (i = 0, ln = components.length; i < ln; i++) {
+ if (Ext.Array.indexOf(this.execute(), components[i]) === -1) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+
+ Ext.apply(this, {
+
+ // private cache of selectors and matching ComponentQuery.Query objects
+ cache: {},
+
+ // private cache of pseudo class filter functions
+ pseudos: {
+ not: function(components, selector){
+ var CQ = Ext.ComponentQuery,
+ i = 0,
+ length = components.length,
+ results = [],
+ index = -1,
+ component;
+
+ for(; i < length; ++i) {
+ component = components[i];
+ if (!CQ.is(component, selector)) {
+ results[++index] = component;
+ }
+ }
+ return results;
+ }
+ },
+
+ /**
+ * <p>Returns an array of matched Components from within the passed root object.</p>
+ * <p>This method filters returned Components in a similar way to how CSS selector based DOM
+ * queries work using a textual selector string.</p>
+ * <p>See class summary for details.</p>
+ * @param selector The selector string to filter returned Components
+ * @param root <p>The Container within which to perform the query. If omitted, all Components
+ * within the document are included in the search.</p>
+ * <p>This parameter may also be an array of Components to filter according to the selector.</p>
+ * @returns {Array} The matched Components.
+ * @member Ext.ComponentQuery
+ * @method query
+ */
+ query: function(selector, root) {
+ var selectors = selector.split(','),
+ length = selectors.length,
+ i = 0,
+ results = [],
+ noDupResults = [],
+ dupMatcher = {},
+ query, resultsLn, cmp;
+
+ for (; i < length; i++) {
+ selector = Ext.String.trim(selectors[i]);
+ query = this.cache[selector];
+ if (!query) {
+ this.cache[selector] = query = this.parse(selector);
+ }
+ results = results.concat(query.execute(root));
+ }
+
+ // multiple selectors, potential to find duplicates
+ // lets filter them out.
+ if (length > 1) {
+ resultsLn = results.length;
+ for (i = 0; i < resultsLn; i++) {
+ cmp = results[i];
+ if (!dupMatcher[cmp.id]) {
+ noDupResults.push(cmp);
+ dupMatcher[cmp.id] = true;
+ }
+ }
+ results = noDupResults;
+ }
+ return results;
+ },
+
+ /**
+ * Tests whether the passed Component matches the selector string.
+ * @param component The Component to test
+ * @param selector The selector string to test against.
+ * @return {Boolean} True if the Component matches the selector.
+ * @member Ext.ComponentQuery
+ * @method query
+ */
+ is: function(component, selector) {
+ if (!selector) {
+ return true;
+ }
+ var query = this.cache[selector];
+ if (!query) {
+ this.cache[selector] = query = this.parse(selector);
+ }
+ return query.is(component);
+ },
+
+ parse: function(selector) {
+ var operations = [],
+ length = matchers.length,
+ lastSelector,
+ tokenMatch,
+ matchedChar,
+ modeMatch,
+ selectorMatch,
+ i, matcher, method;
+
+ // We are going to parse the beginning of the selector over and
+ // over again, slicing off the selector any portions we converted into an
+ // operation, until it is an empty string.
+ while (selector && lastSelector !== selector) {
+ lastSelector = selector;
+
+ // First we check if we are dealing with a token like #, * or an xtype
+ tokenMatch = selector.match(tokenRe);
+
+ if (tokenMatch) {
+ matchedChar = tokenMatch[1];
+
+ // If the token is prefixed with a # we push a filterById operation to our stack
+ if (matchedChar === '#') {
+ operations.push({
+ method: filterById,
+ args: [Ext.String.trim(tokenMatch[2])]
+ });
+ }
+ // If the token is prefixed with a . we push a filterByClassName operation to our stack
+ // FIXME: Not enabled yet. just needs \. adding to the tokenRe prefix
+ else if (matchedChar === '.') {
+ operations.push({
+ method: filterByClassName,
+ args: [Ext.String.trim(tokenMatch[2])]
+ });
+ }
+ // If the token is a * or an xtype string, we push a filterByXType
+ // operation to the stack.
+ else {
+ operations.push({
+ method: filterByXType,
+ args: [Ext.String.trim(tokenMatch[2]), Boolean(tokenMatch[3])]
+ });
+ }
+
+ // Now we slice of the part we just converted into an operation
+ selector = selector.replace(tokenMatch[0], '');
+ }
+
+ // If the next part of the query is not a space or > or ^, it means we
+ // are going to check for more things that our current selection
+ // has to comply to.
+ while (!(modeMatch = selector.match(modeRe))) {
+ // Lets loop over each type of matcher and execute it
+ // on our current selector.
+ for (i = 0; selector && i < length; i++) {
+ matcher = matchers[i];
+ selectorMatch = selector.match(matcher.re);
+ method = matcher.method;
+
+ // If we have a match, add an operation with the method
+ // associated with this matcher, and pass the regular
+ // expression matches are arguments to the operation.
+ if (selectorMatch) {
+ operations.push({
+ method: Ext.isString(matcher.method)
+ // Turn a string method into a function by formatting the string with our selector matche expression
+ // A new method is created for different match expressions, eg {id=='textfield-1024'}
+ // Every expression may be different in different selectors.
+ ? Ext.functionFactory('items', Ext.String.format.apply(Ext.String, [method].concat(selectorMatch.slice(1))))
+ : matcher.method,
+ args: selectorMatch.slice(1)
+ });
+ selector = selector.replace(selectorMatch[0], '');
+ break; // Break on match
+ }
+ // Exhausted all matches: It's an error
+ if (i === (length - 1)) {
+ Ext.Error.raise('Invalid ComponentQuery selector: "' + arguments[0] + '"');
+ }
+ }
+ }
+
+ // Now we are going to check for a mode change. This means a space
+ // or a > to determine if we are going to select all the children
+ // of the currently matched items, or a ^ if we are going to use the
+ // ownerCt axis as the candidate source.
+ if (modeMatch[1]) { // Assignment, and test for truthiness!
+ operations.push({
+ mode: modeMatch[2]||modeMatch[1]
+ });
+ selector = selector.replace(modeMatch[0], '');
+ }
+ }
+
+ // Now that we have all our operations in an array, we are going
+ // to create a new Query using these operations.
+ return new cq.Query({
+ operations: operations
+ });
+ }
+ });
+});
+/**
+ * @class Ext.util.Filter
+ * @extends Object
+ * <p>Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply
+ * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the context
+ * of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching on their
+ * records. Example usage:</p>
+<pre><code>
+//set up a fictional MixedCollection containing a few people to filter on
+var allNames = new Ext.util.MixedCollection();
+allNames.addAll([
+ {id: 1, name: 'Ed', age: 25},
+ {id: 2, name: 'Jamie', age: 37},
+ {id: 3, name: 'Abe', age: 32},
+ {id: 4, name: 'Aaron', age: 26},
+ {id: 5, name: 'David', age: 32}
+]);
+
+var ageFilter = new Ext.util.Filter({
+ property: 'age',
+ value : 32
+});
+
+var longNameFilter = new Ext.util.Filter({
+ filterFn: function(item) {
+ return item.name.length > 4;
+ }
+});
+
+//a new MixedCollection with the 3 names longer than 4 characters
+var longNames = allNames.filter(longNameFilter);
+
+//a new MixedCollection with the 2 people of age 24:
+var youngFolk = allNames.filter(ageFilter);
+</code></pre>
+ * @constructor
+ * @param {Object} config Config object
+ */
+Ext.define('Ext.util.Filter', {
+
+ /* Begin Definitions */
+
+ /* End Definitions */
+ /**
+ * @cfg {String} property The property to filter on. Required unless a {@link #filter} is passed
+ */
+
+ /**
+ * @cfg {Function} filterFn A custom filter function which is passed each item in the {@link Ext.util.MixedCollection}
+ * in turn. Should return true to accept each item or false to reject it
+ */
+
+ /**
+ * @cfg {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
+ */
+ anyMatch: false,
+
+ /**
+ * @cfg {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false.
+ * Ignored if anyMatch is true.
+ */
+ exactMatch: false,
+
+ /**
+ * @cfg {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
+ */
+ caseSensitive: false,
+
+ /**
+ * @cfg {String} root Optional root property. This is mostly useful when filtering a Store, in which case we set the
+ * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
+ */
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+
+ //we're aliasing filter to filterFn mostly for API cleanliness reasons, despite the fact it dirties the code here.
+ //Ext.util.Sorter takes a sorterFn property but allows .sort to be called - we do the same here
+ this.filter = this.filter || this.filterFn;
+
+ if (this.filter == undefined) {
+ if (this.property == undefined || this.value == undefined) {
+ // Commented this out temporarily because it stops us using string ids in models. TODO: Remove this once
+ // Model has been updated to allow string ids
+
+ // Ext.Error.raise("A Filter requires either a property or a filterFn to be set");
+ } else {
+ this.filter = this.createFilterFn();
+ }
+
+ this.filterFn = this.filter;
+ }
+ },
+
+ /**
+ * @private
+ * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter
+ */
+ createFilterFn: function() {
+ var me = this,
+ matcher = me.createValueMatcher(),
+ property = me.property;
+
+ return function(item) {
+ return matcher.test(me.getRoot.call(me, item)[property]);
+ };
+ },
+
+ /**
+ * @private
+ * Returns the root property of the given item, based on the configured {@link #root} property
+ * @param {Object} item The item
+ * @return {Object} The root property of the object
+ */
+ getRoot: function(item) {
+ return this.root == undefined ? item : item[this.root];
+ },
+
+ /**
+ * @private
+ * Returns a regular expression based on the given value and matching options
+ */
+ createValueMatcher : function() {
+ var me = this,
+ value = me.value,
+ anyMatch = me.anyMatch,
+ exactMatch = me.exactMatch,
+ caseSensitive = me.caseSensitive,
+ escapeRe = Ext.String.escapeRegex;
+
+ if (!value.exec) { // not a regex
+ value = String(value);
+
+ if (anyMatch === true) {
+ value = escapeRe(value);
+ } else {
+ value = '^' + escapeRe(value);
+ if (exactMatch === true) {
+ value += '$';
+ }
+ }
+ value = new RegExp(value, caseSensitive ? '' : 'i');
+ }
+
+ return value;
+ }
+});
+/**
+ * @class Ext.util.Sorter
+ * @extends Object
+ * Represents a single sorter that can be applied to a Store
+ */
+Ext.define('Ext.util.Sorter', {
+
+ /**
+ * @cfg {String} property The property to sort by. Required unless {@link #sorter} is provided
+ */
+
+ /**
+ * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property}
+ */
+
+ /**
+ * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the
+ * root to 'data' to make the filter pull the {@link #property} out of the data object of each item
+ */
+
+ /**
+ * @cfg {Function} transform A function that will be run on each value before
+ * it is compared in the sorter. The function will receive a single argument,
+ * the value.
+ */
+
+ /**
+ * @cfg {String} direction The direction to sort by. Defaults to ASC
+ */
+ direction: "ASC",
+
+ constructor: function(config) {
+ var me = this;
+
+ Ext.apply(me, config);
+
+ if (me.property == undefined && me.sorterFn == undefined) {
+ Ext.Error.raise("A Sorter requires either a property or a sorter function");
+ }
+
+ me.updateSortFunction();
+ },
+
+ /**
+ * @private
+ * Creates and returns a function which sorts an array by the given property and direction
+ * @return {Function} A function which sorts by the property/direction combination provided
+ */
+ createSortFunction: function(sorterFn) {
+ var me = this,
+ property = me.property,
+ direction = me.direction || "ASC",
+ modifier = direction.toUpperCase() == "DESC" ? -1 : 1;
+
+ //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater,
+ //-1 if object 2 is greater or 0 if they are equal
+ return function(o1, o2) {
+ return modifier * sorterFn.call(me, o1, o2);
+ };
+ },
+
+ /**
+ * @private
+ * Basic default sorter function that just compares the defined property of each object
+ */
+ defaultSorterFn: function(o1, o2) {
+ var me = this,
+ transform = me.transform,
+ v1 = me.getRoot(o1)[me.property],
+ v2 = me.getRoot(o2)[me.property];
+
+ if (transform) {
+ v1 = transform(v1);
+ v2 = transform(v2);
+ }
+
+ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+ },
+
+ /**
+ * @private
+ * Returns the root property of the given item, based on the configured {@link #root} property
+ * @param {Object} item The item
+ * @return {Object} The root property of the object
+ */
+ getRoot: function(item) {
+ return this.root == undefined ? item : item[this.root];
+ },
+
+ // @TODO: Add docs for these three methods
+ setDirection: function(direction) {
+ var me = this;
+ me.direction = direction;
+ me.updateSortFunction();
+ },
+
+ toggle: function() {
+ var me = this;
+ me.direction = Ext.String.toggle(me.direction, "ASC", "DESC");
+ me.updateSortFunction();
+ },
+
+ updateSortFunction: function() {
+ var me = this;
+ me.sort = me.createSortFunction(me.sorterFn || me.defaultSorterFn);
+ }
+});
+/**
+ * @class Ext.ElementLoader
+ * A class used to load remote content to an Element. Sample usage:
+ * <pre><code>
+Ext.get('el').load({
+ url: 'myPage.php',
+ scripts: true,
+ params: {
+ id: 1
+ }
+});
+ * </code></pre>
+ * <p>
+ * In general this class will not be instanced directly, rather the {@link Ext.core.Element#load} method
+ * will be used.
+ * </p>
+ */
+Ext.define('Ext.ElementLoader', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ uses: [
+ 'Ext.data.Connection',
+ 'Ext.Ajax'
+ ],
+
+ statics: {
+ Renderer: {
+ Html: function(loader, response, active){
+ loader.getTarget().update(response.responseText, active.scripts === true);
+ return true;
+ }
+ }
+ },
+
+ /* End Definitions */
+
+ /**
+ * @cfg {String} url The url to retrieve the content from. Defaults to <tt>null</tt>.
+ */
+ url: null,
+
+ /**
+ * @cfg {Object} params Any params to be attached to the Ajax request. These parameters will
+ * be overridden by any params in the load options. Defaults to <tt>null</tt>.
+ */
+ params: null,
+
+ /**
+ * @cfg {Object} baseParams Params that will be attached to every request. These parameters
+ * will not be overridden by any params in the load options. Defaults to <tt>null</tt>.
+ */
+ baseParams: null,
+
+ /**
+ * @cfg {Boolean/Object} autoLoad True to have the loader make a request as soon as it is created. Defaults to <tt>false</tt>.
+ * This argument can also be a set of options that will be passed to {@link #load} is called.
+ */
+ autoLoad: false,
+
+ /**
+ * @cfg {Mixed} target The target element for the loader. It can be the DOM element, the id or an Ext.Element.
+ */
+ target: null,
+
+ /**
+ * @cfg {Mixed} loadMask True or a string to show when the element is loading.
+ */
+ loadMask: false,
+
+ /**
+ * @cfg {Object} ajaxOptions Any additional options to be passed to the request, for example timeout or headers. Defaults to <tt>null</tt>.
+ */
+ ajaxOptions: null,
+
+ /**
+ * @cfg {Boolean} scripts True to parse any inline script tags in the response.
+ */
+ scripts: false,
+
+ /**
+ * @cfg {Function} success A function to be called when a load request is successful.
+ */
+
+ /**
+ * @cfg {Function} failure A function to be called when a load request fails.
+ */
+
+ /**
+ * @cfg {Object} scope The scope to execute the {@link #success} and {@link #failure} functions in.
+ */
+
+ /**
+ * @cfg {Function} renderer A custom function to render the content to the element. The passed parameters
+ * are
+ * <ul>
+ * <li>The loader</li>
+ * <li>The response</li>
+ * <li>The active request</li>
+ * </ul>
+ */
+
+ isLoader: true,
+
+ constructor: function(config) {
+ var me = this,
+ autoLoad;
+
+ config = config || {};
+ Ext.apply(me, config);
+ me.setTarget(me.target);
+ me.addEvents(
+ /**
+ * @event beforeload
+ * Fires before a load request is made to the server.
+ * Returning false from an event listener can prevent the load
+ * from occurring.
+ * @param {Ext.ElementLoader} this
+ * @param {Object} options The options passed to the request
+ */
+ 'beforeload',
+
+ /**
+ * @event exception
+ * Fires after an unsuccessful load.
+ * @param {Ext.ElementLoader} this
+ * @param {Object} response The response from the server
+ * @param {Object} options The options passed to the request
+ */
+ 'exception',
+
+ /**
+ * @event exception
+ * Fires after a successful load.
+ * @param {Ext.ElementLoader} this
+ * @param {Object} response The response from the server
+ * @param {Object} options The options passed to the request
+ */
+ 'load'
+ );
+
+ // don't pass config because we have already applied it.
+ me.mixins.observable.constructor.call(me);
+
+ if (me.autoLoad) {
+ autoLoad = me.autoLoad;
+ if (autoLoad === true) {
+ autoLoad = {};
+ }
+ me.load(autoLoad);
+ }
+ },
+
+ /**
+ * Set an {Ext.Element} as the target of this loader. Note that if the target is changed,
+ * any active requests will be aborted.
+ * @param {Mixed} target The element
+ */
+ setTarget: function(target){
+ var me = this;
+ target = Ext.get(target);
+ if (me.target && me.target != target) {
+ me.abort();
+ }
+ me.target = target;
+ },
+
+ /**
+ * Get the target of this loader.
+ * @return {Ext.Component} target The target, null if none exists.
+ */
+ getTarget: function(){
+ return this.target || null;
+ },
+
+ /**
+ * Aborts the active load request
+ */
+ abort: function(){
+ var active = this.active;
+ if (active !== undefined) {
+ Ext.Ajax.abort(active.request);
+ if (active.mask) {
+ this.removeMask();
+ }
+ delete this.active;
+ }
+ },
+
+ /**
+ * Remove the mask on the target
+ * @private
+ */
+ removeMask: function(){
+ this.target.unmask();
+ },
+
+ /**
+ * Add the mask on the target
+ * @private
+ * @param {Mixed} mask The mask configuration
+ */
+ addMask: function(mask){
+ this.target.mask(mask === true ? null : mask);
+ },
+
+ /**
+ * Load new data from the server.
+ * @param {Object} options The options for the request. They can be any configuration option that can be specified for
+ * the class, with the exception of the target option. Note that any options passed to the method will override any
+ * class defaults.
+ */
+ load: function(options) {
+ if (!this.target) {
+ Ext.Error.raise('A valid target is required when loading content');
+ }
+
+ options = Ext.apply({}, options);
+
+ var me = this,
+ target = me.target,
+ mask = Ext.isDefined(options.loadMask) ? options.loadMask : me.loadMask,
+ params = Ext.apply({}, options.params),
+ ajaxOptions = Ext.apply({}, options.ajaxOptions),
+ callback = options.callback || me.callback,
+ scope = options.scope || me.scope || me,
+ request;
+
+ Ext.applyIf(ajaxOptions, me.ajaxOptions);
+ Ext.applyIf(options, ajaxOptions);
+
+ Ext.applyIf(params, me.params);
+ Ext.apply(params, me.baseParams);
+
+ Ext.applyIf(options, {
+ url: me.url
+ });
+
+ if (!options.url) {
+ Ext.Error.raise('You must specify the URL from which content should be loaded');
+ }
+
+ Ext.apply(options, {
+ scope: me,
+ params: params,
+ callback: me.onComplete
+ });
+
+ if (me.fireEvent('beforeload', me, options) === false) {
+ return;
+ }
+
+ if (mask) {
+ me.addMask(mask);
+ }
+
+ request = Ext.Ajax.request(options);
+ me.active = {
+ request: request,
+ options: options,
+ mask: mask,
+ scope: scope,
+ callback: callback,
+ success: options.success || me.success,
+ failure: options.failure || me.failure,
+ renderer: options.renderer || me.renderer,
+ scripts: Ext.isDefined(options.scripts) ? options.scripts : me.scripts
+ };
+ me.setOptions(me.active, options);
+ },
+
+ /**
+ * Set any additional options on the active request
+ * @private
+ * @param {Object} active The active request
+ * @param {Object} options The initial options
+ */
+ setOptions: Ext.emptyFn,
+
+ /**
+ * Parse the response after the request completes
+ * @private
+ * @param {Object} options Ajax options
+ * @param {Boolean} success Success status of the request
+ * @param {Object} response The response object
+ */
+ onComplete: function(options, success, response) {
+ var me = this,
+ active = me.active,
+ scope = active.scope,
+ renderer = me.getRenderer(active.renderer);
+
+
+ if (success) {
+ success = renderer.call(me, me, response, active);
+ }
+
+ if (success) {
+ Ext.callback(active.success, scope, [me, response, options]);
+ me.fireEvent('load', me, response, options);
+ } else {
+ Ext.callback(active.failure, scope, [me, response, options]);
+ me.fireEvent('exception', me, response, options);
+ }
+ Ext.callback(active.callback, scope, [me, success, response, options]);
+
+ if (active.mask) {
+ me.removeMask();
+ }
+
+ delete me.active;
+ },
+
+ /**
+ * Gets the renderer to use
+ * @private
+ * @param {String/Function} renderer The renderer to use
+ * @return {Function} A rendering function to use.
+ */
+ getRenderer: function(renderer){
+ if (Ext.isFunction(renderer)) {
+ return renderer;
+ }
+ return this.statics().Renderer.Html;
+ },
+
+ /**
+ * Automatically refreshes the content over a specified period.
+ * @param {Number} interval The interval to refresh in ms.
+ * @param {Object} options (optional) The options to pass to the load method. See {@link #load}
+ */
+ startAutoRefresh: function(interval, options){
+ var me = this;
+ me.stopAutoRefresh();
+ me.autoRefresh = setInterval(function(){
+ me.load(options);
+ }, interval);
+ },
+
+ /**
+ * Clears any auto refresh. See {@link #startAutoRefresh}.
+ */
+ stopAutoRefresh: function(){
+ clearInterval(this.autoRefresh);
+ delete this.autoRefresh;
+ },
+
+ /**
+ * Checks whether the loader is automatically refreshing. See {@link #startAutoRefresh}.
+ * @return {Boolean} True if the loader is automatically refreshing
+ */
+ isAutoRefreshing: function(){
+ return Ext.isDefined(this.autoRefresh);
+ },
+
+ /**
+ * Destroys the loader. Any active requests will be aborted.
+ */
+ destroy: function(){
+ var me = this;
+ me.stopAutoRefresh();
+ delete me.target;
+ me.abort();
+ me.clearListeners();
+ }
+});
+
+/**
+ * @class Ext.layout.Layout
+ * @extends Object
+ * @private
+ * Base Layout class - extended by ComponentLayout and ContainerLayout
+ */
+
+Ext.define('Ext.layout.Layout', {
+
+ /* Begin Definitions */
+
+ /* End Definitions */
+
+ isLayout: true,
+ initialized: false,
+
+ statics: {
+ create: function(layout, defaultType) {
+ var type;
+ if (layout instanceof Ext.layout.Layout) {
+ return Ext.createByAlias('layout.' + layout);
+ } else {
+ if (Ext.isObject(layout)) {
+ type = layout.type;
+ }
+ else {
+ type = layout || defaultType;
+ layout = {};
+ }
+ return Ext.createByAlias('layout.' + type, layout || {});
+ }
+ }
+ },
+
+ constructor : function(config) {
+ this.id = Ext.id(null, this.type + '-');
+ Ext.apply(this, config);
+ },
+
+ /**
+ * @private
+ */
+ layout : function() {
+ var me = this;
+ me.layoutBusy = true;
+ me.initLayout();
+
+ if (me.beforeLayout.apply(me, arguments) !== false) {
+ me.layoutCancelled = false;
+ me.onLayout.apply(me, arguments);
+ me.childrenChanged = false;
+ me.owner.needsLayout = false;
+ me.layoutBusy = false;
+ me.afterLayout.apply(me, arguments);
+ }
+ else {
+ me.layoutCancelled = true;
+ }
+ me.layoutBusy = false;
+ me.doOwnerCtLayouts();
+ },
+
+ beforeLayout : function() {
+ this.renderItems(this.getLayoutItems(), this.getRenderTarget());
+ return true;
+ },
+
+ /**
+ * @private
+ * Iterates over all passed items, ensuring they are rendered. If the items are already rendered,
+ * also determines if the items are in the proper place dom.
+ */
+ renderItems : function(items, target) {
+ var ln = items.length,
+ i = 0,
+ item;
+
+ for (; i < ln; i++) {
+ item = items[i];
+ if (item && !item.rendered) {
+ this.renderItem(item, target, i);
+ }
+ else if (!this.isValidParent(item, target, i)) {
+ this.moveItem(item, target, i);
+ }
+ }
+ },
+
+ // @private - Validates item is in the proper place in the dom.
+ isValidParent : function(item, target, position) {
+ var dom = item.el ? item.el.dom : Ext.getDom(item);
+ if (dom && target && target.dom) {
+ if (Ext.isNumber(position) && dom !== target.dom.childNodes[position]) {
+ return false;
+ }
+ return (dom.parentNode == (target.dom || target));
+ }
+ return false;
+ },
+
+ /**
+ * @private
+ * Renders the given Component into the target Element.
+ * @param {Ext.Component} item The Component to render
+ * @param {Ext.core.Element} target The target Element
+ * @param {Number} position The position within the target to render the item to
+ */
+ renderItem : function(item, target, position) {
+ if (!item.rendered) {
+ item.render(target, position);
+ this.configureItem(item);
+ this.childrenChanged = true;
+ }
+ },
+
+ /**
+ * @private
+ * Moved Component to the provided target instead.
+ */
+ moveItem : function(item, target, position) {
+ // Make sure target is a dom element
+ target = target.dom || target;
+ if (typeof position == 'number') {
+ position = target.childNodes[position];
+ }
+ target.insertBefore(item.el.dom, position || null);
+ item.container = Ext.get(target);
+ this.configureItem(item);
+ this.childrenChanged = true;
+ },
+
+ /**
+ * @private
+ * Adds the layout's targetCls if necessary and sets
+ * initialized flag when complete.
+ */
+ initLayout : function() {
+ if (!this.initialized && !Ext.isEmpty(this.targetCls)) {
+ this.getTarget().addCls(this.targetCls);
+ }
+ this.initialized = true;
+ },
+
+ // @private Sets the layout owner
+ setOwner : function(owner) {
+ this.owner = owner;
+ },
+
+ // @private - Returns empty array
+ getLayoutItems : function() {
+ return [];
+ },
+
+ /**
+ * @private
+ * Applies itemCls
+ */
+ configureItem: function(item) {
+ var me = this,
+ el = item.el,
+ owner = me.owner;
+
+ if (me.itemCls) {
+ el.addCls(me.itemCls);
+ }
+ if (owner.itemCls) {
+ el.addCls(owner.itemCls);
+ }
+ },
+
+ // Placeholder empty functions for subclasses to extend
+ onLayout : Ext.emptyFn,
+ afterLayout : Ext.emptyFn,
+ onRemove : Ext.emptyFn,
+ onDestroy : Ext.emptyFn,
+ doOwnerCtLayouts : Ext.emptyFn,
+
+ /**
+ * @private
+ * Removes itemCls
+ */
+ afterRemove : function(item) {
+ var me = this,
+ el = item.el,
+ owner = me.owner;
+
+ if (item.rendered) {
+ if (me.itemCls) {
+ el.removeCls(me.itemCls);
+ }
+ if (owner.itemCls) {
+ el.removeCls(owner.itemCls);
+ }
+ }
+ },
+
+ /*
+ * Destroys this layout. This is a template method that is empty by default, but should be implemented
+ * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
+ * @protected
+ */
+ destroy : function() {
+ if (!Ext.isEmpty(this.targetCls)) {
+ var target = this.getTarget();
+ if (target) {
+ target.removeCls(this.targetCls);
+ }
+ }
+ this.onDestroy();
+ }
+});
+/**
+ * @class Ext.layout.component.Component
+ * @extends Ext.layout.Layout
+ * @private
+ * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Component#componentLayout layout}</b></tt>
+ * configuration property. See <tt><b>{@link Ext.Component#componentLayout}</b></tt> for additional details.</p>
+ */
+
+Ext.define('Ext.layout.component.Component', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.Layout',
+
+ /* End Definitions */
+
+ type: 'component',
+
+ monitorChildren: true,
+
+ initLayout : function() {
+ var me = this,
+ owner = me.owner,
+ ownerEl = owner.el;
+
+ if (!me.initialized) {
+ if (owner.frameSize) {
+ me.frameSize = owner.frameSize;
+ }
+ else {
+ owner.frameSize = me.frameSize = {
+ top: 0,
+ left: 0,
+ bottom: 0,
+ right: 0
+ };
+ }
+ }
+ me.callParent(arguments);
+ },
+
+ beforeLayout : function(width, height, isSetSize, layoutOwner) {
+ this.callParent(arguments);
+
+ var me = this,
+ owner = me.owner,
+ ownerCt = owner.ownerCt,
+ layout = owner.layout,
+ isVisible = owner.isVisible(true),
+ ownerElChild = owner.el.child,
+ layoutCollection;
+
+ /**
+ * Do not layout calculatedSized components for fixedLayouts unless the ownerCt == layoutOwner
+ * fixedLayouts means layouts which are never auto/auto in the sizing that comes from their ownerCt.
+ * Currently 3 layouts MAY be auto/auto (Auto, Border, and Box)
+ * The reason for not allowing component layouts is to stop component layouts from things such as Updater and
+ * form Validation.
+ */
+ if (!isSetSize && !(Ext.isNumber(width) && Ext.isNumber(height)) && ownerCt && ownerCt.layout && ownerCt.layout.fixedLayout && ownerCt != layoutOwner) {
+ me.doContainerLayout();
+ return false;
+ }
+
+ // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
+ // If the owner itself is a directly hidden floater, set the needsLayout object on that for when it is shown.
+ if (!isVisible && (owner.hiddenAncestor || owner.floating)) {
+ if (owner.hiddenAncestor) {
+ layoutCollection = owner.hiddenAncestor.layoutOnShow;
+ layoutCollection.remove(owner);
+ layoutCollection.add(owner);
+ }
+ owner.needsLayout = {
+ width: width,
+ height: height,
+ isSetSize: false
+ };
+ }
+
+ if (isVisible && this.needsLayout(width, height)) {
+ me.rawWidth = width;
+ me.rawHeight = height;
+ return owner.beforeComponentLayout(width, height, isSetSize, layoutOwner);
+ }
+ else {
+ return false;
+ }
+ },
+
+ /**
+ * Check if the new size is different from the current size and only
+ * trigger a layout if it is necessary.
+ * @param {Mixed} width The new width to set.
+ * @param {Mixed} height The new height to set.
+ */
+ needsLayout : function(width, height) {
+ this.lastComponentSize = this.lastComponentSize || {
+ width: -Infinity,
+ height: -Infinity
+ };
+ return (this.childrenChanged || this.lastComponentSize.width !== width || this.lastComponentSize.height !== height);
+ },
+
+ /**
+ * Set the size of any element supporting undefined, null, and values.
+ * @param {Mixed} width The new width to set.
+ * @param {Mixed} height The new height to set.
+ */
+ setElementSize: function(el, width, height) {
+ if (width !== undefined && height !== undefined) {
+ el.setSize(width, height);
+ }
+ else if (height !== undefined) {
+ el.setHeight(height);
+ }
+ else if (width !== undefined) {
+ el.setWidth(width);
+ }
+ },
+
+ /**
+ * Returns the owner component's resize element.
+ * @return {Ext.core.Element}
+ */
+ getTarget : function() {
+ return this.owner.el;
+ },
+
+ /**
+ * <p>Returns the element into which rendering must take place. Defaults to the owner Component's encapsulating element.</p>
+ * May be overridden in Component layout managers which implement an inner element.
+ * @return {Ext.core.Element}
+ */
+ getRenderTarget : function() {
+ return this.owner.el;
+ },
+
+ /**
+ * Set the size of the target element.
+ * @param {Mixed} width The new width to set.
+ * @param {Mixed} height The new height to set.
+ */
+ setTargetSize : function(width, height) {
+ var me = this;
+ me.setElementSize(me.owner.el, width, height);
+
+ if (me.owner.frameBody) {
+ var targetInfo = me.getTargetInfo(),
+ padding = targetInfo.padding,
+ border = targetInfo.border,
+ frameSize = me.frameSize;
+
+ me.setElementSize(me.owner.frameBody,
+ Ext.isNumber(width) ? (width - frameSize.left - frameSize.right - padding.left - padding.right - border.left - border.right) : width,
+ Ext.isNumber(height) ? (height - frameSize.top - frameSize.bottom - padding.top - padding.bottom - border.top - border.bottom) : height
+ );
+ }
+
+ me.autoSized = {
+ width: !Ext.isNumber(width),
+ height: !Ext.isNumber(height)
+ };
+
+ me.lastComponentSize = {
+ width: width,
+ height: height
+ };
+ },
+
+ getTargetInfo : function() {
+ if (!this.targetInfo) {
+ var target = this.getTarget(),
+ body = this.owner.getTargetEl();
+
+ this.targetInfo = {
+ padding: {
+ top: target.getPadding('t'),
+ right: target.getPadding('r'),
+ bottom: target.getPadding('b'),
+ left: target.getPadding('l')
+ },
+ border: {
+ top: target.getBorderWidth('t'),
+ right: target.getBorderWidth('r'),
+ bottom: target.getBorderWidth('b'),
+ left: target.getBorderWidth('l')
+ },
+ bodyMargin: {
+ top: body.getMargin('t'),
+ right: body.getMargin('r'),
+ bottom: body.getMargin('b'),
+ left: body.getMargin('l')
+ }
+ };
+ }
+ return this.targetInfo;
+ },
+
+ // Start laying out UP the ownerCt's layout when flagged to do so.
+ doOwnerCtLayouts: function() {
+ var owner = this.owner,
+ ownerCt = owner.ownerCt,
+ ownerCtComponentLayout, ownerCtContainerLayout;
+
+ if (!ownerCt) {
+ return;
+ }
+
+ ownerCtComponentLayout = ownerCt.componentLayout;
+ ownerCtContainerLayout = ownerCt.layout;
+
+ if (!owner.floating && ownerCtComponentLayout && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
+ if (!ownerCt.suspendLayout && ownerCtContainerLayout && !ownerCtContainerLayout.layoutBusy) {
+ // AutoContainer Layout and Dock with auto in some dimension
+ if (ownerCtContainerLayout.bindToOwnerCtComponent === true) {
+ ownerCt.doComponentLayout();
+ }
+ // Box Layouts
+ else if (ownerCtContainerLayout.bindToOwnerCtContainer === true) {
+ ownerCtContainerLayout.layout();
+ }
+ }
+ }
+ },
+
+ doContainerLayout: function() {
+ var me = this,
+ owner = me.owner,
+ ownerCt = owner.ownerCt,
+ layout = owner.layout,
+ ownerCtComponentLayout;
+
+ // Run the container layout if it exists (layout for child items)
+ // **Unless automatic laying out is suspended, or the layout is currently running**
+ if (!owner.suspendLayout && layout && layout.isLayout && !layout.layoutBusy) {
+ layout.layout();
+ }
+
+ // Tell the ownerCt that it's child has changed and can be re-layed by ignoring the lastComponentSize cache.
+ if (ownerCt && ownerCt.componentLayout) {
+ ownerCtComponentLayout = ownerCt.componentLayout;
+ if (!owner.floating && ownerCtComponentLayout.monitorChildren && !ownerCtComponentLayout.layoutBusy) {
+ ownerCtComponentLayout.childrenChanged = true;
+ }
+ }
+ },
+
+ afterLayout : function(width, height, isSetSize, layoutOwner) {
+ this.doContainerLayout();
+ this.owner.afterComponentLayout(width, height, isSetSize, layoutOwner);
+ }
+});
+
+/**
+ * @class Ext.state.Manager
+ * This is the global state manager. By default all components that are "state aware" check this class
+ * for state information if you don't pass them a custom state provider. In order for this class
+ * to be useful, it must be initialized with a provider when your application initializes. Example usage:
+ <pre><code>
+// in your initialization function
+init : function(){
+ Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
+ var win = new Window(...);
+ win.restoreState();
+}
+ </code></pre>
+ * This class passes on calls from components to the underlying {@link Ext.state.Provider} so that
+ * there is a common interface that can be used without needing to refer to a specific provider instance
+ * in every component.
+ * @singleton
+ * @docauthor Evan Trimboli <evan@sencha.com>
+ */
+Ext.define('Ext.state.Manager', {
+ singleton: true,
+ requires: ['Ext.state.Provider'],
+ constructor: function() {
+ this.provider = Ext.create('Ext.state.Provider');
+ },
+
+
+ /**
+ * Configures the default state provider for your application
+ * @param {Provider} stateProvider The state provider to set
+ */
+ setProvider : function(stateProvider){
+ this.provider = stateProvider;
+ },
+
+ /**
+ * Returns the current value for a key
+ * @param {String} name The key name
+ * @param {Mixed} defaultValue The default value to return if the key lookup does not match
+ * @return {Mixed} The state data
+ */
+ get : function(key, defaultValue){
+ return this.provider.get(key, defaultValue);
+ },
+
+ /**
+ * Sets the value for a key
+ * @param {String} name The key name
+ * @param {Mixed} value The state data
+ */
+ set : function(key, value){
+ this.provider.set(key, value);
+ },
+
+ /**
+ * Clears a value from the state
+ * @param {String} name The key name
+ */
+ clear : function(key){
+ this.provider.clear(key);
+ },
+
+ /**
+ * Gets the currently configured state provider
+ * @return {Provider} The state provider
+ */
+ getProvider : function(){
+ return this.provider;
+ }
+});
+/**
+ * @class Ext.state.Stateful
+ * A mixin for being able to save the state of an object to an underlying
+ * {@link Ext.state.Provider}.
+ */
+Ext.define('Ext.state.Stateful', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.state.Manager'],
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Boolean} stateful
+ * <p>A flag which causes the object to attempt to restore the state of
+ * internal properties from a saved state on startup. The object must have
+ * a <code>{@link #stateId}</code> for state to be managed.
+ * Auto-generated ids are not guaranteed to be stable across page loads and
+ * cannot be relied upon to save and restore the same state for a object.<p>
+ * <p>For state saving to work, the state manager's provider must have been
+ * set to an implementation of {@link Ext.state.Provider} which overrides the
+ * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
+ * methods to save and recall name/value pairs. A built-in implementation,
+ * {@link Ext.state.CookieProvider} is available.</p>
+ * <p>To set the state provider for the current page:</p>
+ * <pre><code>
+Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
+ expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
+}));
+ * </code></pre>
+ * <p>A stateful object attempts to save state when one of the events
+ * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
+ * <p>To save state, a stateful object first serializes its state by
+ * calling <b><code>{@link #getState}</code></b>. By default, this function does
+ * nothing. The developer must provide an implementation which returns an
+ * object hash which represents the restorable state of the object.</p>
+ * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
+ * which uses the configured {@link Ext.state.Provider} to save the object
+ * keyed by the <code>{@link stateId}</code></p>.
+ * <p>During construction, a stateful object attempts to <i>restore</i>
+ * its state by calling {@link Ext.state.Manager#get} passing the
+ * <code>{@link #stateId}</code></p>
+ * <p>The resulting object is passed to <b><code>{@link #applyState}</code></b>.
+ * The default implementation of <code>{@link #applyState}</code> simply copies
+ * properties into the object, but a developer may override this to support
+ * more behaviour.</p>
+ * <p>You can perform extra processing on state save and restore by attaching
+ * handlers to the {@link #beforestaterestore}, {@link #staterestore},
+ * {@link #beforestatesave} and {@link #statesave} events.</p>
+ */
+ stateful: true,
+
+ /**
+ * @cfg {String} stateId
+ * The unique id for this object to use for state management purposes.
+ * <p>See {@link #stateful} for an explanation of saving and restoring state.</p>
+ */
+
+ /**
+ * @cfg {Array} stateEvents
+ * <p>An array of events that, when fired, should trigger this object to
+ * save its state (defaults to none). <code>stateEvents</code> may be any type
+ * of event supported by this object, including browser or custom events
+ * (e.g., <tt>['click', 'customerchange']</tt>).</p>
+ * <p>See <code>{@link #stateful}</code> for an explanation of saving and
+ * restoring object state.</p>
+ */
+
+ /**
+ * @cfg {Number} saveBuffer A buffer to be applied if many state events are fired within
+ * a short period. Defaults to 100.
+ */
+ saveDelay: 100,
+
+ autoGenIdRe: /^((\w+-)|(ext-comp-))\d{4,}$/i,
+
+ constructor: function(config) {
+ var me = this;
+
+ config = config || {};
+ if (Ext.isDefined(config.stateful)) {
+ me.stateful = config.stateful;
+ }
+ if (Ext.isDefined(config.saveDelay)) {
+ me.saveDelay = config.saveDelay;
+ }
+ me.stateId = config.stateId;
+
+ if (!me.stateEvents) {
+ me.stateEvents = [];
+ }
+ if (config.stateEvents) {
+ me.stateEvents.concat(config.stateEvents);
+ }
+ this.addEvents(
+ /**
+ * @event beforestaterestore
+ * Fires before the state of the object is restored. Return false from an event handler to stop the restore.
+ * @param {Ext.state.Stateful} this
+ * @param {Object} state The hash of state values returned from the StateProvider. If this
+ * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
+ * that simply copies property values into this object. The method maybe overriden to
+ * provide custom state restoration.
+ */
+ 'beforestaterestore',
+
+ /**
+ * @event staterestore
+ * Fires after the state of the object is restored.
+ * @param {Ext.state.Stateful} this
+ * @param {Object} state The hash of state values returned from the StateProvider. This is passed
+ * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
+ * object. The method maybe overriden to provide custom state restoration.
+ */
+ 'staterestore',
+
+ /**
+ * @event beforestatesave
+ * Fires before the state of the object is saved to the configured state provider. Return false to stop the save.
+ * @param {Ext.state.Stateful} this
+ * @param {Object} state The hash of state values. This is determined by calling
+ * <b><tt>getState()</tt></b> on the object. This method must be provided by the
+ * developer to return whetever representation of state is required, by default, Ext.state.Stateful
+ * has a null implementation.
+ */
+ 'beforestatesave',
+
+ /**
+ * @event statesave
+ * Fires after the state of the object is saved to the configured state provider.
+ * @param {Ext.state.Stateful} this
+ * @param {Object} state The hash of state values. This is determined by calling
+ * <b><tt>getState()</tt></b> on the object. This method must be provided by the
+ * developer to return whetever representation of state is required, by default, Ext.state.Stateful
+ * has a null implementation.
+ */
+ 'statesave'
+ );
+ me.mixins.observable.constructor.call(me);
+ if (me.stateful !== false) {
+ me.initStateEvents();
+ me.initState();
+ }
+ },
+
+ /**
+ * Initializes any state events for this object.
+ * @private
+ */
+ initStateEvents: function() {
+ this.addStateEvents(this.stateEvents);
+ },
+
+ /**
+ * Add events that will trigger the state to be saved.
+ * @param {String/Array} events The event name or an array of event names.
+ */
+ addStateEvents: function(events){
+ if (!Ext.isArray(events)) {
+ events = [events];
+ }
+
+ var me = this,
+ i = 0,
+ len = events.length;
+
+ for (; i < len; ++i) {
+ me.on(events[i], me.onStateChange, me);
+ }
+ },
+
+ /**
+ * This method is called when any of the {@link #stateEvents} are fired.
+ * @private
+ */
+ onStateChange: function(){
+ var me = this,
+ delay = me.saveDelay;
+
+ if (delay > 0) {
+ if (!me.stateTask) {
+ me.stateTask = Ext.create('Ext.util.DelayedTask', me.saveState, me);
+ }
+ me.stateTask.delay(me.saveDelay);
+ } else {
+ me.saveState();
+ }
+ },
+
+ /**
+ * Saves the state of the object to the persistence store.
+ * @private
+ */
+ saveState: function() {
+ var me = this,
+ id,
+ state;
+
+ if (me.stateful !== false) {
+ id = me.getStateId();
+ if (id) {
+ state = me.getState();
+ if (me.fireEvent('beforestatesave', me, state) !== false) {
+ Ext.state.Manager.set(id, state);
+ me.fireEvent('statesave', me, state);
+ }
+ }
+ }
+ },
+
+ /**
+ * Gets the current state of the object. By default this function returns null,
+ * it should be overridden in subclasses to implement methods for getting the state.
+ * @return {Object} The current state
+ */
+ getState: function(){
+ return null;
+ },
+
+ /**
+ * Applies the state to the object. This should be overridden in subclasses to do
+ * more complex state operations. By default it applies the state properties onto
+ * the current object.
+ * @param {Object} state The state
+ */
+ applyState: function(state) {
+ if (state) {
+ Ext.apply(this, state);
+ }
+ },
+
+ /**
+ * Gets the state id for this object.
+ * @return {String} The state id, null if not found.
+ */
+ getStateId: function() {
+ var me = this,
+ id = me.stateId;
+
+ if (!id) {
+ id = me.autoGenIdRe.test(String(me.id)) ? null : me.id;
+ }
+ return id;
+ },
+
+ /**
+ * Initializes the state of the object upon construction.
+ * @private
+ */
+ initState: function(){
+ var me = this,
+ id = me.getStateId(),
+ state;
+
+ if (me.stateful !== false) {
+ if (id) {
+ state = Ext.state.Manager.get(id);
+ if (state) {
+ state = Ext.apply({}, state);
+ if (me.fireEvent('beforestaterestore', me, state) !== false) {
+ me.applyState(state);
+ me.fireEvent('staterestore', me, state);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Destroys this stateful object.
+ */
+ destroy: function(){
+ var task = this.stateTask;
+ if (task) {
+ task.cancel();
+ }
+ this.clearListeners();
+
+ }
+
+});
+
+/**
+ * @class Ext.AbstractManager
+ * @extends Object
+ * @ignore
+ * Base Manager class
+ */
+
+Ext.define('Ext.AbstractManager', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.util.HashMap'],
+
+ /* End Definitions */
+
+ typeName: 'type',
+
+ constructor: function(config) {
+ Ext.apply(this, config || {});
+
+ /**
+ * Contains all of the items currently managed
+ * @property all
+ * @type Ext.util.MixedCollection
+ */
+ this.all = Ext.create('Ext.util.HashMap');
+
+ this.types = {};
+ },
+
+ /**
+ * Returns an item by id.
+ * For additional details see {@link Ext.util.HashMap#get}.
+ * @param {String} id The id of the item
+ * @return {Mixed} The item, <code>undefined</code> if not found.
+ */
+ get : function(id) {
+ return this.all.get(id);
+ },
+
+ /**
+ * Registers an item to be managed
+ * @param {Mixed} item The item to register
+ */
+ register: function(item) {
+ this.all.add(item);
+ },
+
+ /**
+ * Unregisters an item by removing it from this manager
+ * @param {Mixed} item The item to unregister
+ */
+ unregister: function(item) {
+ this.all.remove(item);
+ },
+
+ /**
+ * <p>Registers a new item constructor, keyed by a type key.
+ * @param {String} type The mnemonic string by which the class may be looked up.
+ * @param {Constructor} cls The new instance class.
+ */
+ registerType : function(type, cls) {
+ this.types[type] = cls;
+ cls[this.typeName] = type;
+ },
+
+ /**
+ * Checks if an item type is registered.
+ * @param {String} type The mnemonic string by which the class may be looked up
+ * @return {Boolean} Whether the type is registered.
+ */
+ isRegistered : function(type){
+ return this.types[type] !== undefined;
+ },
+
+ /**
+ * Creates and returns an instance of whatever this manager manages, based on the supplied type and config object
+ * @param {Object} config The config object
+ * @param {String} defaultType If no type is discovered in the config object, we fall back to this type
+ * @return {Mixed} The instance of whatever this manager is managing
+ */
+ create: function(config, defaultType) {
+ var type = config[this.typeName] || config.type || defaultType,
+ Constructor = this.types[type];
+
+ if (Constructor == undefined) {
+ Ext.Error.raise("The '" + type + "' type has not been registered with this manager");
+ }
+
+ return new Constructor(config);
+ },
+
+ /**
+ * Registers a function that will be called when an item with the specified id is added to the manager. This will happen on instantiation.
+ * @param {String} id The item id
+ * @param {Function} fn The callback function. Called with a single parameter, the item.
+ * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed. Defaults to the item.
+ */
+ onAvailable : function(id, fn, scope){
+ var all = this.all,
+ item;
+
+ if (all.containsKey(id)) {
+ item = all.get(id);
+ fn.call(scope || item, item);
+ } else {
+ all.on('add', function(map, key, item){
+ if (key == id) {
+ fn.call(scope || item, item);
+ all.un('add', fn, scope);
+ }
+ });
+ }
+ },
+
+ /**
+ * Executes the specified function once for each item in the collection.
+ * Returning false from the function will cease iteration.
+ *
+ * The paramaters passed to the function are:
+ * <div class="mdetail-params"><ul>
+ * <li><b>key</b> : String<p class="sub-desc">The key of the item</p></li>
+ * <li><b>value</b> : Number<p class="sub-desc">The value of the item</p></li>
+ * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
+ * </ul></div>
+ * @param {Object} fn The function to execute.
+ * @param {Object} scope The scope to execute in. Defaults to <tt>this</tt>.
+ */
+ each: function(fn, scope){
+ this.all.each(fn, scope || this);
+ },
+
+ /**
+ * Gets the number of items in the collection.
+ * @return {Number} The number of items in the collection.
+ */
+ getCount: function(){
+ return this.all.getCount();
+ }
+});
+
+/**
+ * @class Ext.PluginManager
+ * @extends Ext.AbstractManager
+ * <p>Provides a registry of available Plugin <i>classes</i> indexed by a mnemonic code known as the Plugin's ptype.
+ * The <code>{@link Ext.Component#xtype xtype}</code> provides a way to avoid instantiating child Components
+ * when creating a full, nested config object for a complete Ext page.</p>
+ * <p>A child Component may be specified simply as a <i>config object</i>
+ * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
+ * needs rendering, the correct type can be looked up for lazy instantiation.</p>
+ * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
+ * @singleton
+ */
+Ext.define('Ext.PluginManager', {
+ extend: 'Ext.AbstractManager',
+ alternateClassName: 'Ext.PluginMgr',
+ singleton: true,
+ typeName: 'ptype',
+
+ /**
+ * Creates a new Plugin from the specified config object using the
+ * config object's ptype to determine the class to instantiate.
+ * @param {Object} config A configuration object for the Plugin you wish to create.
+ * @param {Constructor} defaultType The constructor to provide the default Plugin type if
+ * the config object does not contain a <code>ptype</code>. (Optional if the config contains a <code>ptype</code>).
+ * @return {Ext.Component} The newly instantiated Plugin.
+ */
+ //create: function(plugin, defaultType) {
+ // if (plugin instanceof this) {
+ // return plugin;
+ // } else {
+ // var type, config = {};
+ //
+ // if (Ext.isString(plugin)) {
+ // type = plugin;
+ // }
+ // else {
+ // type = plugin[this.typeName] || defaultType;
+ // config = plugin;
+ // }
+ //
+ // return Ext.createByAlias('plugin.' + type, config);
+ // }
+ //},
+
+ create : function(config, defaultType){
+ if (config.init) {
+ return config;
+ } else {
+ return Ext.createByAlias('plugin.' + (config.ptype || defaultType), config);
+ }
+
+ // Prior system supported Singleton plugins.
+ //var PluginCls = this.types[config.ptype || defaultType];
+ //if (PluginCls.init) {
+ // return PluginCls;
+ //} else {
+ // return new PluginCls(config);
+ //}
+ },
+
+ /**
+ * Returns all plugins registered with the given type. Here, 'type' refers to the type of plugin, not its ptype.
+ * @param {String} type The type to search for
+ * @param {Boolean} defaultsOnly True to only return plugins of this type where the plugin's isDefault property is truthy
+ * @return {Array} All matching plugins
+ */
+ findByType: function(type, defaultsOnly) {
+ var matches = [],
+ types = this.types;
+
+ for (var name in types) {
+ if (!types.hasOwnProperty(name)) {
+ continue;
+ }
+ var item = types[name];
+
+ if (item.type == type && (!defaultsOnly || (defaultsOnly === true && item.isDefault))) {
+ matches.push(item);
+ }
+ }
+
+ return matches;
+ }
+}, function() {
+ /**
+ * Shorthand for {@link Ext.PluginManager#registerType}
+ * @param {String} ptype The ptype mnemonic string by which the Plugin class
+ * may be looked up.
+ * @param {Constructor} cls The new Plugin class.
+ * @member Ext
+ * @method preg
+ */
+ Ext.preg = function() {
+ return Ext.PluginManager.registerType.apply(Ext.PluginManager, arguments);
+ };
+});
+
+/**
+ * @class Ext.ComponentManager
+ * @extends Ext.AbstractManager
+ * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
+ * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
+ * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
+ * <p>This object also provides a registry of available Component <i>classes</i>
+ * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
+ * The <code>xtype</code> provides a way to avoid instantiating child Components
+ * when creating a full, nested config object for a complete Ext page.</p>
+ * <p>A child Component may be specified simply as a <i>config object</i>
+ * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
+ * needs rendering, the correct type can be looked up for lazy instantiation.</p>
+ * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
+ * @singleton
+ */
+Ext.define('Ext.ComponentManager', {
+ extend: 'Ext.AbstractManager',
+ alternateClassName: 'Ext.ComponentMgr',
+
+ singleton: true,
+
+ typeName: 'xtype',
+
+ /**
+ * Creates a new Component from the specified config object using the
+ * config object's xtype to determine the class to instantiate.
+ * @param {Object} config A configuration object for the Component you wish to create.
+ * @param {Constructor} defaultType The constructor to provide the default Component type if
+ * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
+ * @return {Ext.Component} The newly instantiated Component.
+ */
+ create: function(component, defaultType){
+ if (component instanceof Ext.AbstractComponent) {
+ return component;
+ }
+ else if (Ext.isString(component)) {
+ return Ext.createByAlias('widget.' + component);
+ }
+ else {
+ var type = component.xtype || defaultType,
+ config = component;
+
+ return Ext.createByAlias('widget.' + type, config);
+ }
+ },
+
+ registerType: function(type, cls) {
+ this.types[type] = cls;
+ cls[this.typeName] = type;
+ cls.prototype[this.typeName] = type;
+ }
+});
+/**
+ * @class Ext.XTemplate
+ * @extends Ext.Template
+ * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
+ * <li>Autofilling arrays using templates and sub-templates</li>
+ * <li>Conditional processing with basic comparison operators</li>
+ * <li>Basic math function support</li>
+ * <li>Execute arbitrary inline code with special built-in template variables</li>
+ * <li>Custom member functions</li>
+ * <li>Many special tags and built-in operators that aren't defined as part of
+ * the API, but are supported in the templates that can be created</li>
+ * </ul></div></p>
+ * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
+ * <li>{@link Ext.view.View}</li>
+ * </ul></div></p>
+ *
+ * The {@link Ext.Template} describes
+ * the acceptable parameters to pass to the constructor. The following
+ * examples demonstrate all of the supported features.</p>
+ *
+ * <div class="mdetail-params"><ul>
+ *
+ * <li><b><u>Sample Data</u></b>
+ * <div class="sub-desc">
+ * <p>This is the data object used for reference in each code example:</p>
+ * <pre><code>
+var data = {
+name: 'Tommy Maintz',
+title: 'Lead Developer',
+company: 'Sencha Inc.',
+email: 'tommy@sencha.com',
+address: '5 Cups Drive',
+city: 'Palo Alto',
+state: 'CA',
+zip: '44102',
+drinks: ['Coffee', 'Soda', 'Water'],
+kids: [{
+ name: 'Joshua',
+ age:3
+ },{
+ name: 'Matthew',
+ age:2
+ },{
+ name: 'Solomon',
+ age:0
+}]
+};
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Auto filling of arrays</u></b>
+ * <div class="sub-desc">
+ * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
+ * to process the provided data object:
+ * <ul>
+ * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
+ * repeating the template block inside the <tt>tpl</tt> tag for each item in the
+ * array.</li>
+ * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
+ * <li>While processing an array, the special variable <tt>{#}</tt>
+ * will provide the current array index + 1 (starts at 1, not 0).</li>
+ * </ul>
+ * </p>
+ * <pre><code>
+<tpl <b>for</b>=".">...</tpl> // loop through array at root node
+<tpl <b>for</b>="foo">...</tpl> // loop through array at foo node
+<tpl <b>for</b>="foo.bar">...</tpl> // loop through array at foo.bar node
+ </code></pre>
+ * Using the sample data above:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Kids: ',
+ '<tpl <b>for</b>=".">', // process the data.kids node
+ '<p>{#}. {name}</p>', // use current array index to autonumber
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
+ </code></pre>
+ * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
+ * to access specified members of the provided data object to populate the template:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Title: {title}</p>',
+ '<p>Company: {company}</p>',
+ '<p>Kids: ',
+ '<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
+ '<p>{name}</p>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data); // pass the root node of the data object
+ </code></pre>
+ * <p>Flat arrays that contain values (and not objects) can be auto-rendered
+ * using the special <b><tt>{.}</tt></b> variable inside a loop. This variable
+ * will represent the value of the array at the current index:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>{name}\'s favorite beverages:</p>',
+ '<tpl for="drinks">',
+ '<div> - {.}</div>',
+ '</tpl>'
+);
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * <p>When processing a sub-template, for example while looping through a child array,
+ * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age &gt; 1">',
+ '<p>{name}</p>',
+ '<p>Dad: {<b>parent</b>.name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Conditional processing with basic comparison operators</u></b>
+ * <div class="sub-desc">
+ * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
+ * to provide conditional checks for deciding whether or not to render specific
+ * parts of the template. Notes:<div class="sub-desc"><ul>
+ * <li>Double quotes must be encoded if used within the conditional</li>
+ * <li>There is no <tt>else</tt> operator — if needed, two opposite
+ * <tt>if</tt> statements should be used.</li>
+ * </ul></div>
+ * <pre><code>
+<tpl if="age > 1 && age < 10">Child</tpl>
+<tpl if="age >= 10 && age < 18">Teenager</tpl>
+<tpl <b>if</b>="this.isGirl(name)">...</tpl>
+<tpl <b>if</b>="id==\'download\'">...</tpl>
+<tpl <b>if</b>="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
+// no good:
+<tpl if="name == "Tommy"">Hello</tpl>
+// encode " if it is part of the condition, e.g.
+<tpl if="name == &quot;Tommy&quot;">Hello</tpl>
+ * </code></pre>
+ * Using the sample data above:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age &gt; 1">',
+ '<p>{name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Basic math support</u></b>
+ * <div class="sub-desc">
+ * <p>The following basic math operators may be applied directly on numeric
+ * data values:</p><pre>
+ * + - * /
+ * </pre>
+ * For example:
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
+ '<p>{#}: {name}</p>', // <-- Auto-number each item
+ '<p>In 5 Years: {age+5}</p>', // <-- Basic math
+ '<p>Dad: {parent.name}</p>',
+ '</tpl>',
+ '</tpl></p>'
+);
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ *
+ * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
+ * <div class="sub-desc">
+ * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
+ * in the scope of the template. There are some special variables available in that code:
+ * <ul>
+ * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
+ * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
+ * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
+ * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
+ * loop you are in (1-based).</li>
+ * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
+ * of the array you are looping.</li>
+ * </ul>
+ * This example demonstrates basic row striping using an inline code block and the
+ * <tt>xindex</tt> variable:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
+ '{name}',
+ '</div>',
+ '</tpl></p>'
+ );
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ * <li><b><u>Template member functions</u></b>
+ * <div class="sub-desc">
+ * <p>One or more member functions can be specified in a configuration
+ * object passed into the XTemplate constructor for more complex processing:</p>
+ * <pre><code>
+var tpl = new Ext.XTemplate(
+ '<p>Name: {name}</p>',
+ '<p>Kids: ',
+ '<tpl for="kids">',
+ '<tpl if="this.isGirl(name)">',
+ '<p>Girl: {name} - {age}</p>',
+ '</tpl>',
+ // use opposite if statement to simulate 'else' processing:
+ '<tpl if="this.isGirl(name) == false">',
+ '<p>Boy: {name} - {age}</p>',
+ '</tpl>',
+ '<tpl if="this.isBaby(age)">',
+ '<p>{name} is a baby!</p>',
+ '</tpl>',
+ '</tpl></p>',
+ {
+ // XTemplate configuration:
+ compiled: true,
+ // member functions:
+ isGirl: function(name){
+ return name == 'Sara Grace';
+ },
+ isBaby: function(age){
+ return age < 1;
+ }
+ }
+);
+tpl.overwrite(panel.body, data);
+ </code></pre>
+ * </div>
+ * </li>
+ *
+ * </ul></div>
+ *
+ * @param {Mixed} config
+ */
+
+Ext.define('Ext.XTemplate', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.Template',
+
+ statics: {
+ /**
+ * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
+ * @param {String/HTMLElement} el A DOM element or its id
+ * @return {Ext.Template} The created template
+ * @static
+ */
+ from: function(el, config) {
+ el = Ext.getDom(el);
+ return new this(el.value || el.innerHTML, config || {});
+ }
+ },
+
+ /* End Definitions */
+
+ argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
+ nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
+ ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
+ execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
+ constructor: function() {
+ this.callParent(arguments);
+
+ var me = this,
+ html = me.html,
+ argsRe = me.argsRe,
+ nameRe = me.nameRe,
+ ifRe = me.ifRe,
+ execRe = me.execRe,
+ id = 0,
+ tpls = [],
+ VALUES = 'values',
+ PARENT = 'parent',
+ XINDEX = 'xindex',
+ XCOUNT = 'xcount',
+ RETURN = 'return ',
+ WITHVALUES = 'with(values){ ',
+ m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
+
+ html = ['<tpl>', html, '</tpl>'].join('');
+
+ while ((m = html.match(argsRe))) {
+ exp = null;
+ fn = null;
+ exec = null;
+ matchName = m[0].match(nameRe);
+ matchIf = m[0].match(ifRe);
+ matchExec = m[0].match(execRe);
+
+ exp = matchIf ? matchIf[1] : null;
+ if (exp) {
+ fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
+ }
+
+ exp = matchExec ? matchExec[1] : null;
+ if (exp) {
+ exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
+ }
+
+ name = matchName ? matchName[1] : null;
+ if (name) {
+ if (name === '.') {
+ name = VALUES;
+ } else if (name === '..') {
+ name = PARENT;
+ }
+ name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
+ }
+
+ tpls.push({
+ id: id,
+ target: name,
+ exec: exec,
+ test: fn,
+ body: m[1] || ''
+ });
+
+ html = html.replace(m[0], '{xtpl' + id + '}');
+ id = id + 1;
+ }
+
+ for (i = tpls.length - 1; i >= 0; --i) {
+ me.compileTpl(tpls[i]);
+ }
+ me.master = tpls[tpls.length - 1];
+ me.tpls = tpls;
+ },
+
+ // @private
+ applySubTemplate: function(id, values, parent, xindex, xcount) {
+ var me = this, t = me.tpls[id];
+ return t.compiled.call(me, values, parent, xindex, xcount);
+ },
+ /**
+ * @cfg {RegExp} codeRe The regular expression used to match code variables (default: matches <tt>{[expression]}</tt>).
+ */
+ codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
+
+ re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
+
+ // @private
+ compileTpl: function(tpl) {
+ var fm = Ext.util.Format,
+ me = this,
+ useFormat = me.disableFormats !== true,
+ body, bodyReturn, evaluatedFn;
+
+ function fn(m, name, format, args, math) {
+ var v;
+ // name is what is inside the {}
+ // Name begins with xtpl, use a Sub Template
+ if (name.substr(0, 4) == 'xtpl') {
+ return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
+ }
+ // name = "." - Just use the values object.
+ if (name == '.') {
+ // filter to not include arrays/objects/nulls
+ v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
+ }
+
+ // name = "#" - Use the xindex
+ else if (name == '#') {
+ v = 'xindex';
+ }
+ else if (name.substr(0, 7) == "parent.") {
+ v = name;
+ }
+ // name has a . in it - Use object literal notation, starting from values
+ else if (name.indexOf('.') != -1) {
+ v = "values." + name;
+ }
+
+ // name is a property of values
+ else {
+ v = "values['" + name + "']";
+ }
+ if (math) {
+ v = '(' + v + math + ')';
+ }
+ if (format && useFormat) {
+ args = args ? ',' + args : "";
+ if (format.substr(0, 5) != "this.") {
+ format = "fm." + format + '(';
+ }
+ else {
+ format = 'this.' + format.substr(5) + '(';
+ }
+ }
+ else {
+ args = '';
+ format = "(" + v + " === undefined ? '' : ";
+ }
+ return "'," + format + v + args + "),'";
+ }
+
+ function codeFn(m, code) {
+ // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
+ return "',(" + code.replace(me.compileARe, "'") + "),'";
+ }
+
+ bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
+ body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
+ eval(body);
+
+ tpl.compiled = function(values, parent, xindex, xcount) {
+ var vs,
+ length,
+ buffer,
+ i;
+
+ if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
+ return '';
+ }
+
+ vs = tpl.target ? tpl.target.call(me, values, parent) : values;
+ if (!vs) {
+ return '';
+ }
+
+ parent = tpl.target ? values : parent;
+ if (tpl.target && Ext.isArray(vs)) {
+ buffer = [];
+ length = vs.length;
+ if (tpl.exec) {
+ for (i = 0; i < length; i++) {
+ buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
+ tpl.exec.call(me, vs[i], parent, i + 1, length);
+ }
+ } else {
+ for (i = 0; i < length; i++) {
+ buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
+ }
+ }
+ return buffer.join('');
+ }
+
+ if (tpl.exec) {
+ tpl.exec.call(me, vs, parent, xindex, xcount);
+ }
+ return evaluatedFn.call(me, vs, parent, xindex, xcount);
+ };
+
+ return this;
+ },
+
+ /**
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ */
+ applyTemplate: function(values) {
+ return this.master.compiled.call(this, values, {}, 1, 1);
+ },
+
+ /**
+ * Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
+ * @return {Function} The compiled function
+ */
+ compile: function() {
+ return this;
+ }
+}, function() {
+ /**
+ * Alias for {@link #applyTemplate}
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ * @member Ext.XTemplate
+ * @method apply
+ */
+ this.createAlias('apply', 'applyTemplate');
+});
+
+/**
+ * @class Ext.util.AbstractMixedCollection
+ */
+Ext.define('Ext.util.AbstractMixedCollection', {
+ requires: ['Ext.util.Filter'],
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ constructor: function(allowFunctions, keyFn) {
+ var me = this;
+
+ me.items = [];
+ me.map = {};
+ me.keys = [];
+ me.length = 0;
+
+ me.addEvents(
+ /**
+ * @event clear
+ * Fires when the collection is cleared.
+ */
+ 'clear',
+
+ /**
+ * @event add
+ * Fires when an item is added to the collection.
+ * @param {Number} index The index at which the item was added.
+ * @param {Object} o The item added.
+ * @param {String} key The key associated with the added item.
+ */
+ 'add',
+
+ /**
+ * @event replace
+ * Fires when an item is replaced in the collection.
+ * @param {String} key he key associated with the new added.
+ * @param {Object} old The item being replaced.
+ * @param {Object} new The new item.
+ */
+ 'replace',
+
+ /**
+ * @event remove
+ * Fires when an item is removed from the collection.
+ * @param {Object} o The item being removed.
+ * @param {String} key (optional) The key associated with the removed item.
+ */
+ 'remove'
+ );
+
+ me.allowFunctions = allowFunctions === true;
+
+ if (keyFn) {
+ me.getKey = keyFn;
+ }
+
+ me.mixins.observable.constructor.call(me);
+ },
+
+ /**
+ * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
+ * function should add function references to the collection. Defaults to
+ * <tt>false</tt>.
+ */
+ allowFunctions : false,
+
+ /**
+ * Adds an item to the collection. Fires the {@link #add} event when complete.
+ * @param {String} key <p>The key to associate with the item, or the new item.</p>
+ * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
+ * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
+ * the MixedCollection will be able to <i>derive</i> the key for the new item.
+ * In this case just pass the new item in this parameter.</p>
+ * @param {Object} o The item to add.
+ * @return {Object} The item added.
+ */
+ add : function(key, obj){
+ var me = this,
+ myObj = obj,
+ myKey = key,
+ old;
+
+ if (arguments.length == 1) {
+ myObj = myKey;
+ myKey = me.getKey(myObj);
+ }
+ if (typeof myKey != 'undefined' && myKey !== null) {
+ old = me.map[myKey];
+ if (typeof old != 'undefined') {
+ return me.replace(myKey, myObj);
+ }
+ me.map[myKey] = myObj;
+ }
+ me.length++;
+ me.items.push(myObj);
+ me.keys.push(myKey);
+ me.fireEvent('add', me.length - 1, myObj, myKey);
+ return myObj;
+ },
+
+ /**
+ * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
+ * simply returns <b><code>item.id</code></b> but you can provide your own implementation
+ * to return a different value as in the following examples:<pre><code>
+// normal way
+var mc = new Ext.util.MixedCollection();
+mc.add(someEl.dom.id, someEl);
+mc.add(otherEl.dom.id, otherEl);
+//and so on
+
+// using getKey
+var mc = new Ext.util.MixedCollection();
+mc.getKey = function(el){
+ return el.dom.id;
+};
+mc.add(someEl);
+mc.add(otherEl);
+
+// or via the constructor
+var mc = new Ext.util.MixedCollection(false, function(el){
+ return el.dom.id;
+});
+mc.add(someEl);
+mc.add(otherEl);
+ * </code></pre>
+ * @param {Object} item The item for which to find the key.
+ * @return {Object} The key for the passed item.
+ */
+ getKey : function(o){
+ return o.id;
+ },
+
+ /**
+ * Replaces an item in the collection. Fires the {@link #replace} event when complete.
+ * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
+ * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
+ * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
+ * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
+ * with one having the same key value, then just pass the replacement item in this parameter.</p>
+ * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
+ * with that key.
+ * @return {Object} The new item.
+ */
+ replace : function(key, o){
+ var me = this,
+ old,
+ index;
+
+ if (arguments.length == 1) {
+ o = arguments[0];
+ key = me.getKey(o);
+ }
+ old = me.map[key];
+ if (typeof key == 'undefined' || key === null || typeof old == 'undefined') {
+ return me.add(key, o);
+ }
+ index = me.indexOfKey(key);
+ me.items[index] = o;
+ me.map[key] = o;
+ me.fireEvent('replace', key, old, o);
+ return o;
+ },
+
+ /**
+ * Adds all elements of an Array or an Object to the collection.
+ * @param {Object/Array} objs An Object containing properties which will be added
+ * to the collection, or an Array of values, each of which are added to the collection.
+ * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
+ * has been set to <tt>true</tt>.
+ */
+ addAll : function(objs){
+ var me = this,
+ i = 0,
+ args,
+ len,
+ key;
+
+ if (arguments.length > 1 || Ext.isArray(objs)) {
+ args = arguments.length > 1 ? arguments : objs;
+ for (len = args.length; i < len; i++) {
+ me.add(args[i]);
+ }
+ } else {
+ for (key in objs) {
+ if (objs.hasOwnProperty(key)) {
+ if (me.allowFunctions || typeof objs[key] != 'function') {
+ me.add(key, objs[key]);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every item in the collection, passing the following arguments:
+ * <div class="mdetail-params"><ul>
+ * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
+ * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
+ * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
+ * </ul></div>
+ * The function should return a boolean value. Returning false from the function will stop the iteration.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current item in the iteration.
+ */
+ each : function(fn, scope){
+ var items = [].concat(this.items), // each safe for removal
+ i = 0,
+ len = items.length,
+ item;
+
+ for (; i < len; i++) {
+ item = items[i];
+ if (fn.call(scope || item, item, i, len) === false) {
+ break;
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every key in the collection, passing each
+ * key, and its associated item as the first two parameters.
+ * @param {Function} fn The function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ */
+ eachKey : function(fn, scope){
+ var keys = this.keys,
+ items = this.items,
+ i = 0,
+ len = keys.length;
+
+ for (; i < len; i++) {
+ fn.call(scope || window, keys[i], items[i], i, len);
+ }
+ },
+
+ /**
+ * Returns the first item in the collection which elicits a true return value from the
+ * passed selection function.
+ * @param {Function} fn The selection function to execute for each item.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ * @return {Object} The first item in the collection which returned true from the selection function.
+ */
+ findBy : function(fn, scope) {
+ var keys = this.keys,
+ items = this.items,
+ i = 0,
+ len = items.length;
+
+ for (; i < len; i++) {
+ if (fn.call(scope || window, items[i], keys[i])) {
+ return items[i];
+ }
+ }
+ return null;
+ },
+
+ find : function() {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.util.MixedCollection: find has been deprecated. Use findBy instead.');
+ }
+ return this.findBy.apply(this, arguments);
+ },
+
+ /**
+ * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
+ * @param {Number} index The index to insert the item at.
+ * @param {String} key The key to associate with the new item, or the item itself.
+ * @param {Object} o (optional) If the second parameter was a key, the new item.
+ * @return {Object} The item inserted.
+ */
+ insert : function(index, key, obj){
+ var me = this,
+ myKey = key,
+ myObj = obj;
+
+ if (arguments.length == 2) {
+ myObj = myKey;
+ myKey = me.getKey(myObj);
+ }
+ if (me.containsKey(myKey)) {
+ me.suspendEvents();
+ me.removeAtKey(myKey);
+ me.resumeEvents();
+ }
+ if (index >= me.length) {
+ return me.add(myKey, myObj);
+ }
+ me.length++;
+ me.items.splice(index, 0, myObj);
+ if (typeof myKey != 'undefined' && myKey !== null) {
+ me.map[myKey] = myObj;
+ }
+ me.keys.splice(index, 0, myKey);
+ me.fireEvent('add', index, myObj, myKey);
+ return myObj;
+ },
+
+ /**
+ * Remove an item from the collection.
+ * @param {Object} o The item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ remove : function(o){
+ return this.removeAt(this.indexOf(o));
+ },
+
+ /**
+ * Remove all items in the passed array from the collection.
+ * @param {Array} items An array of items to be removed.
+ * @return {Ext.util.MixedCollection} this object
+ */
+ removeAll : function(items){
+ Ext.each(items || [], function(item) {
+ this.remove(item);
+ }, this);
+
+ return this;
+ },
+
+ /**
+ * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
+ * @param {Number} index The index within the collection of the item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ removeAt : function(index){
+ var me = this,
+ o,
+ key;
+
+ if (index < me.length && index >= 0) {
+ me.length--;
+ o = me.items[index];
+ me.items.splice(index, 1);
+ key = me.keys[index];
+ if (typeof key != 'undefined') {
+ delete me.map[key];
+ }
+ me.keys.splice(index, 1);
+ me.fireEvent('remove', o, key);
+ return o;
+ }
+ return false;
+ },
+
+ /**
+ * Removed an item associated with the passed key fom the collection.
+ * @param {String} key The key of the item to remove.
+ * @return {Object} The item removed or false if no item was removed.
+ */
+ removeAtKey : function(key){
+ return this.removeAt(this.indexOfKey(key));
+ },
+
+ /**
+ * Returns the number of items in the collection.
+ * @return {Number} the number of items in the collection.
+ */
+ getCount : function(){
+ return this.length;
+ },
+
+ /**
+ * Returns index within the collection of the passed Object.
+ * @param {Object} o The item to find the index of.
+ * @return {Number} index of the item. Returns -1 if not found.
+ */
+ indexOf : function(o){
+ return Ext.Array.indexOf(this.items, o);
+ },
+
+ /**
+ * Returns index within the collection of the passed key.
+ * @param {String} key The key to find the index of.
+ * @return {Number} index of the key.
+ */
+ indexOfKey : function(key){
+ return Ext.Array.indexOf(this.keys, key);
+ },
+
+ /**
+ * Returns the item associated with the passed key OR index.
+ * Key has priority over index. This is the equivalent
+ * of calling {@link #key} first, then if nothing matched calling {@link #getAt}.
+ * @param {String/Number} key The key or index of the item.
+ * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
+ * If an item was found, but is a Class, returns <tt>null</tt>.
+ */
+ get : function(key) {
+ var me = this,
+ mk = me.map[key],
+ item = mk !== undefined ? mk : (typeof key == 'number') ? me.items[key] : undefined;
+ return typeof item != 'function' || me.allowFunctions ? item : null; // for prototype!
+ },
+
+ /**
+ * Returns the item at the specified index.
+ * @param {Number} index The index of the item.
+ * @return {Object} The item at the specified index.
+ */
+ getAt : function(index) {
+ return this.items[index];
+ },
+
+ /**
+ * Returns the item associated with the passed key.
+ * @param {String/Number} key The key of the item.
+ * @return {Object} The item associated with the passed key.
+ */
+ getByKey : function(key) {
+ return this.map[key];
+ },
+
+ /**
+ * Returns true if the collection contains the passed Object as an item.
+ * @param {Object} o The Object to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as an item.
+ */
+ contains : function(o){
+ return Ext.Array.contains(this.items, o);
+ },
+
+ /**
+ * Returns true if the collection contains the passed Object as a key.
+ * @param {String} key The key to look for in the collection.
+ * @return {Boolean} True if the collection contains the Object as a key.
+ */
+ containsKey : function(key){
+ return typeof this.map[key] != 'undefined';
+ },
+
+ /**
+ * Removes all items from the collection. Fires the {@link #clear} event when complete.
+ */
+ clear : function(){
+ var me = this;
+
+ me.length = 0;
+ me.items = [];
+ me.keys = [];
+ me.map = {};
+ me.fireEvent('clear');
+ },
+
+ /**
+ * Returns the first item in the collection.
+ * @return {Object} the first item in the collection..
+ */
+ first : function() {
+ return this.items[0];
+ },
+
+ /**
+ * Returns the last item in the collection.
+ * @return {Object} the last item in the collection..
+ */
+ last : function() {
+ return this.items[this.length - 1];
+ },
+
+ /**
+ * Collects all of the values of the given property and returns their sum
+ * @param {String} property The property to sum by
+ * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
+ * summing fields in records, where the fields are all stored inside the 'data' object
+ * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
+ * @param {Number} end (optional) The record index to end at (defaults to <tt>-1</tt>)
+ * @return {Number} The total
+ */
+ sum: function(property, root, start, end) {
+ var values = this.extractValues(property, root),
+ length = values.length,
+ sum = 0,
+ i;
+
+ start = start || 0;
+ end = (end || end === 0) ? end : length - 1;
+
+ for (i = start; i <= end; i++) {
+ sum += values[i];
+ }
+
+ return sum;
+ },
+
+ /**
+ * Collects unique values of a particular property in this MixedCollection
+ * @param {String} property The property to collect on
+ * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
+ * summing fields in records, where the fields are all stored inside the 'data' object
+ * @param {Boolean} allowBlank (optional) Pass true to allow null, undefined or empty string values
+ * @return {Array} The unique values
+ */
+ collect: function(property, root, allowNull) {
+ var values = this.extractValues(property, root),
+ length = values.length,
+ hits = {},
+ unique = [],
+ value, strValue, i;
+
+ for (i = 0; i < length; i++) {
+ value = values[i];
+ strValue = String(value);
+
+ if ((allowNull || !Ext.isEmpty(value)) && !hits[strValue]) {
+ hits[strValue] = true;
+ unique.push(value);
+ }
+ }
+
+ return unique;
+ },
+
+ /**
+ * @private
+ * Extracts all of the given property values from the items in the MC. Mainly used as a supporting method for
+ * functions like sum and collect.
+ * @param {String} property The property to extract
+ * @param {String} root Optional 'root' property to extract the first argument from. This is used mainly when
+ * extracting field data from Model instances, where the fields are stored inside the 'data' object
+ * @return {Array} The extracted values
+ */
+ extractValues: function(property, root) {
+ var values = this.items;
+
+ if (root) {
+ values = Ext.Array.pluck(values, root);
+ }
+
+ return Ext.Array.pluck(values, property);
+ },
+
+ /**
+ * Returns a range of items in this collection
+ * @param {Number} startIndex (optional) The starting index. Defaults to 0.
+ * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
+ * @return {Array} An array of items
+ */
+ getRange : function(start, end){
+ var me = this,
+ items = me.items,
+ range = [],
+ i;
+
+ if (items.length < 1) {
+ return range;
+ }
+
+ start = start || 0;
+ end = Math.min(typeof end == 'undefined' ? me.length - 1 : end, me.length - 1);
+ if (start <= end) {
+ for (i = start; i <= end; i++) {
+ range[range.length] = items[i];
+ }
+ } else {
+ for (i = start; i >= end; i--) {
+ range[range.length] = items[i];
+ }
+ }
+ return range;
+ },
+
+ /**
+ * <p>Filters the objects in this collection by a set of {@link Ext.util.Filter Filter}s, or by a single
+ * property/value pair with optional parameters for substring matching and case sensitivity. See
+ * {@link Ext.util.Filter Filter} for an example of using Filter objects (preferred). Alternatively,
+ * MixedCollection can be easily filtered by property like this:</p>
+<pre><code>
+//create a simple store with a few people defined
+var people = new Ext.util.MixedCollection();
+people.addAll([
+ {id: 1, age: 25, name: 'Ed'},
+ {id: 2, age: 24, name: 'Tommy'},
+ {id: 3, age: 24, name: 'Arne'},
+ {id: 4, age: 26, name: 'Aaron'}
+]);
+
+//a new MixedCollection containing only the items where age == 24
+var middleAged = people.filter('age', 24);
+</code></pre>
+ *
+ *
+ * @param {Array/String} property A property on your objects, or an array of {@link Ext.util.Filter Filter} objects
+ * @param {String/RegExp} value Either string that the property values
+ * should start with or a RegExp to test against the property
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
+ * @return {MixedCollection} The new filtered collection
+ */
+ filter : function(property, value, anyMatch, caseSensitive) {
+ var filters = [],
+ filterFn;
+
+ //support for the simple case of filtering by property/value
+ if (Ext.isString(property)) {
+ filters.push(Ext.create('Ext.util.Filter', {
+ property : property,
+ value : value,
+ anyMatch : anyMatch,
+ caseSensitive: caseSensitive
+ }));
+ } else if (Ext.isArray(property) || property instanceof Ext.util.Filter) {
+ filters = filters.concat(property);
+ }
+
+ //at this point we have an array of zero or more Ext.util.Filter objects to filter with,
+ //so here we construct a function that combines these filters by ANDing them together
+ filterFn = function(record) {
+ var isMatch = true,
+ length = filters.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ var filter = filters[i],
+ fn = filter.filterFn,
+ scope = filter.scope;
+
+ isMatch = isMatch && fn.call(scope, record);
+ }
+
+ return isMatch;
+ };
+
+ return this.filterBy(filterFn);
+ },
+
+ /**
+ * Filter by a function. Returns a <i>new</i> collection that has been filtered.
+ * The passed function will be called with each object in the collection.
+ * If the function returns true, the value is included otherwise it is filtered.
+ * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
+ * @return {MixedCollection} The new filtered collection
+ */
+ filterBy : function(fn, scope) {
+ var me = this,
+ newMC = new this.self(),
+ keys = me.keys,
+ items = me.items,
+ length = items.length,
+ i;
+
+ newMC.getKey = me.getKey;
+
+ for (i = 0; i < length; i++) {
+ if (fn.call(scope || me, items[i], keys[i])) {
+ newMC.add(keys[i], items[i]);
+ }
+ }
+
+ return newMC;
+ },
+
+ /**
+ * Finds the index of the first matching object in this collection by a specific property/value.
+ * @param {String} property The name of a property on your objects.
+ * @param {String/RegExp} value A string that the property values
+ * should start with or a RegExp to test against the property.
+ * @param {Number} start (optional) The index to start searching at (defaults to 0).
+ * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
+ * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
+ * @return {Number} The matched index or -1
+ */
+ findIndex : function(property, value, start, anyMatch, caseSensitive){
+ if(Ext.isEmpty(value, false)){
+ return -1;
+ }
+ value = this.createValueMatcher(value, anyMatch, caseSensitive);
+ return this.findIndexBy(function(o){
+ return o && value.test(o[property]);
+ }, null, start);
+ },
+
+ /**
+ * Find the index of the first matching object in this collection by a function.
+ * If the function returns <i>true</i> it is considered a match.
+ * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to this MixedCollection.
+ * @param {Number} start (optional) The index to start searching at (defaults to 0).
+ * @return {Number} The matched index or -1
+ */
+ findIndexBy : function(fn, scope, start){
+ var me = this,
+ keys = me.keys,
+ items = me.items,
+ i = start || 0,
+ len = items.length;
+
+ for (; i < len; i++) {
+ if (fn.call(scope || me, items[i], keys[i])) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * Returns a regular expression based on the given value and matching options. This is used internally for finding and filtering,
+ * and by Ext.data.Store#filter
+ * @private
+ * @param {String} value The value to create the regex for. This is escaped using Ext.escapeRe
+ * @param {Boolean} anyMatch True to allow any match - no regex start/end line anchors will be added. Defaults to false
+ * @param {Boolean} caseSensitive True to make the regex case sensitive (adds 'i' switch to regex). Defaults to false.
+ * @param {Boolean} exactMatch True to force exact match (^ and $ characters added to the regex). Defaults to false. Ignored if anyMatch is true.
+ */
+ createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
+ if (!value.exec) { // not a regex
+ var er = Ext.String.escapeRegex;
+ value = String(value);
+
+ if (anyMatch === true) {
+ value = er(value);
+ } else {
+ value = '^' + er(value);
+ if (exactMatch === true) {
+ value += '$';
+ }
+ }
+ value = new RegExp(value, caseSensitive ? '' : 'i');
+ }
+ return value;
+ },
+
+ /**
+ * Creates a shallow copy of this collection
+ * @return {MixedCollection}
+ */
+ clone : function() {
+ var me = this,
+ copy = new this.self(),
+ keys = me.keys,
+ items = me.items,
+ i = 0,
+ len = items.length;
+
+ for(; i < len; i++){
+ copy.add(keys[i], items[i]);
+ }
+ copy.getKey = me.getKey;
+ return copy;
+ }
+});
+
+/**
+ * @class Ext.util.Sortable
+
+A mixin which allows a data component to be sorted. This is used by e.g. {@link Ext.data.Store} and {@link Ext.data.TreeStore}.
+
+**NOTE**: This mixin is mainly for internal library use and most users should not need to use it directly. It
+is more likely you will want to use one of the component classes that import this mixin, such as
+{@link Ext.data.Store} or {@link Ext.data.TreeStore}.
+ * @markdown
+ * @docauthor Tommy Maintz <tommy@sencha.com>
+ */
+Ext.define("Ext.util.Sortable", {
+ /**
+ * @property isSortable
+ * @type Boolean
+ * Flag denoting that this object is sortable. Always true.
+ */
+ isSortable: true,
+
+ /**
+ * The default sort direction to use if one is not specified (defaults to "ASC")
+ * @property defaultSortDirection
+ * @type String
+ */
+ defaultSortDirection: "ASC",
+
+ requires: [
+ 'Ext.util.Sorter'
+ ],
+
+ /**
+ * The property in each item that contains the data to sort. (defaults to null)
+ * @type String
+ */
+ sortRoot: null,
+
+ /**
+ * Performs initialization of this mixin. Component classes using this mixin should call this method
+ * during their own initialization.
+ */
+ initSortable: function() {
+ var me = this,
+ sorters = me.sorters;
+
+ /**
+ * The collection of {@link Ext.util.Sorter Sorters} currently applied to this Store
+ * @property sorters
+ * @type Ext.util.MixedCollection
+ */
+ me.sorters = Ext.create('Ext.util.AbstractMixedCollection', false, function(item) {
+ return item.id || item.property;
+ });
+
+ if (sorters) {
+ me.sorters.addAll(me.decodeSorters(sorters));
+ }
+ },
+
+ /**
+ * <p>Sorts the data in the Store by one or more of its properties. Example usage:</p>
+<pre><code>
+//sort by a single field
+myStore.sort('myField', 'DESC');
+
+//sorting by multiple fields
+myStore.sort([
+ {
+ property : 'age',
+ direction: 'ASC'
+ },
+ {
+ property : 'name',
+ direction: 'DESC'
+ }
+]);
+</code></pre>
+ * <p>Internally, Store converts the passed arguments into an array of {@link Ext.util.Sorter} instances, and delegates the actual
+ * sorting to its internal {@link Ext.util.MixedCollection}.</p>
+ * <p>When passing a single string argument to sort, Store maintains a ASC/DESC toggler per field, so this code:</p>
+<pre><code>
+store.sort('myField');
+store.sort('myField');
+ </code></pre>
+ * <p>Is equivalent to this code, because Store handles the toggling automatically:</p>
+<pre><code>
+store.sort('myField', 'ASC');
+store.sort('myField', 'DESC');
+</code></pre>
+ * @param {String|Array} sorters Either a string name of one of the fields in this Store's configured {@link Ext.data.Model Model},
+ * or an Array of sorter configurations.
+ * @param {String} direction The overall direction to sort the data by. Defaults to "ASC".
+ */
+ sort: function(sorters, direction, where, doSort) {
+ var me = this,
+ sorter, sorterFn,
+ newSorters;
+
+ if (Ext.isArray(sorters)) {
+ doSort = where;
+ where = direction;
+ newSorters = sorters;
+ }
+ else if (Ext.isObject(sorters)) {
+ doSort = where;
+ where = direction;
+ newSorters = [sorters];
+ }
+ else if (Ext.isString(sorters)) {
+ sorter = me.sorters.get(sorters);
+
+ if (!sorter) {
+ sorter = {
+ property : sorters,
+ direction: direction
+ };
+ newSorters = [sorter];
+ }
+ else if (direction === undefined) {
+ sorter.toggle();
+ }
+ else {
+ sorter.setDirection(direction);
+ }
+ }
+
+ if (newSorters && newSorters.length) {
+ newSorters = me.decodeSorters(newSorters);
+ if (Ext.isString(where)) {
+ if (where === 'prepend') {
+ sorters = me.sorters.clone().items;
+
+ me.sorters.clear();
+ me.sorters.addAll(newSorters);
+ me.sorters.addAll(sorters);
+ }
+ else {
+ me.sorters.addAll(newSorters);
+ }
+ }
+ else {
+ me.sorters.clear();
+ me.sorters.addAll(newSorters);
+ }
+
+ if (doSort !== false) {
+ me.onBeforeSort(newSorters);
+ }
+ }
+
+ if (doSort !== false) {
+ sorters = me.sorters.items;
+ if (sorters.length) {
+ //construct an amalgamated sorter function which combines all of the Sorters passed
+ sorterFn = function(r1, r2) {
+ var result = sorters[0].sort(r1, r2),
+ length = sorters.length,
+ i;
+
+ //if we have more than one sorter, OR any additional sorter functions together
+ for (i = 1; i < length; i++) {
+ result = result || sorters[i].sort.call(this, r1, r2);
+ }
+
+ return result;
+ };
+
+ me.doSort(sorterFn);
+ }
+ }
+
+ return sorters;
+ },
+
+ onBeforeSort: Ext.emptyFn,
+
+ /**
+ * @private
+ * Normalizes an array of sorter objects, ensuring that they are all Ext.util.Sorter instances
+ * @param {Array} sorters The sorters array
+ * @return {Array} Array of Ext.util.Sorter objects
+ */
+ decodeSorters: function(sorters) {
+ if (!Ext.isArray(sorters)) {
+ if (sorters === undefined) {
+ sorters = [];
+ } else {
+ sorters = [sorters];
+ }
+ }
+
+ var length = sorters.length,
+ Sorter = Ext.util.Sorter,
+ fields = this.model ? this.model.prototype.fields : null,
+ field,
+ config, i;
+
+ for (i = 0; i < length; i++) {
+ config = sorters[i];
+
+ if (!(config instanceof Sorter)) {
+ if (Ext.isString(config)) {
+ config = {
+ property: config
+ };
+ }
+
+ Ext.applyIf(config, {
+ root : this.sortRoot,
+ direction: "ASC"
+ });
+
+ //support for 3.x style sorters where a function can be defined as 'fn'
+ if (config.fn) {
+ config.sorterFn = config.fn;
+ }
+
+ //support a function to be passed as a sorter definition
+ if (typeof config == 'function') {
+ config = {
+ sorterFn: config
+ };
+ }
+
+ // ensure sortType gets pushed on if necessary
+ if (fields && !config.transform) {
+ field = fields.get(config.property);
+ config.transform = field ? field.sortType : undefined;
+ }
+ sorters[i] = Ext.create('Ext.util.Sorter', config);
+ }
+ }
+
+ return sorters;
+ },
+
+ getSorters: function() {
+ return this.sorters.items;
+ },
+
+ /**
+ * Returns an object describing the current sort state of this Store.
+ * @return {Object} The sort state of the Store. An object with two properties:<ul>
+ * <li><b>field</b> : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
+ * <li><b>direction</b> : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
+ * </ul>
+ * See <tt>{@link #sortInfo}</tt> for additional details.
+ */
+ getSortState : function() {
+ return this.sortInfo;
+ }
+});
+/**
+ * @class Ext.util.MixedCollection
+ * <p>
+ * Represents a collection of a set of key and value pairs. Each key in the MixedCollection
+ * must be unique, the same key cannot exist twice. This collection is ordered, items in the
+ * collection can be accessed by index or via the key. Newly added items are added to
+ * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it
+ * is heavier and provides more functionality. Sample usage:
+ * <pre><code>
+var coll = new Ext.util.MixedCollection();
+coll.add('key1', 'val1');
+coll.add('key2', 'val2');
+coll.add('key3', 'val3');
+
+console.log(coll.get('key1')); // prints 'val1'
+console.log(coll.indexOfKey('key3')); // prints 2
+ * </code></pre>
+ *
+ * <p>
+ * The MixedCollection also has support for sorting and filtering of the values in the collection.
+ * <pre><code>
+var coll = new Ext.util.MixedCollection();
+coll.add('key1', 100);
+coll.add('key2', -100);
+coll.add('key3', 17);
+coll.add('key4', 0);
+var biggerThanZero = coll.filterBy(function(value){
+ return value > 0;
+});
+console.log(biggerThanZero.getCount()); // prints 2
+ * </code></pre>
+ * </p>
+ *
+ * @constructor
+ * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
+ * function should add function references to the collection. Defaults to
+ * <tt>false</tt>.
+ * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
+ * and return the key value for that item. This is used when available to look up the key on items that
+ * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
+ * equivalent to providing an implementation for the {@link #getKey} method.
+ */
+Ext.define('Ext.util.MixedCollection', {
+ extend: 'Ext.util.AbstractMixedCollection',
+ mixins: {
+ sortable: 'Ext.util.Sortable'
+ },
+
+ constructor: function() {
+ var me = this;
+ me.callParent(arguments);
+ me.addEvents('sort');
+ me.mixins.sortable.initSortable.call(me);
+ },
+
+ doSort: function(sorterFn) {
+ this.sortBy(sorterFn);
+ },
+
+ /**
+ * @private
+ * Performs the actual sorting based on a direction and a sorting function. Internally,
+ * this creates a temporary array of all items in the MixedCollection, sorts it and then writes
+ * the sorted array data back into this.items and this.keys
+ * @param {String} property Property to sort by ('key', 'value', or 'index')
+ * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
+ * @param {Function} fn (optional) Comparison function that defines the sort order.
+ * Defaults to sorting by numeric value.
+ */
+ _sort : function(property, dir, fn){
+ var me = this,
+ i, len,
+ dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
+
+ //this is a temporary array used to apply the sorting function
+ c = [],
+ keys = me.keys,
+ items = me.items;
+
+ //default to a simple sorter function if one is not provided
+ fn = fn || function(a, b) {
+ return a - b;
+ };
+
+ //copy all the items into a temporary array, which we will sort
+ for(i = 0, len = items.length; i < len; i++){
+ c[c.length] = {
+ key : keys[i],
+ value: items[i],
+ index: i
+ };
+ }
+
+ //sort the temporary array
+ Ext.Array.sort(c, function(a, b){
+ var v = fn(a[property], b[property]) * dsc;
+ if(v === 0){
+ v = (a.index < b.index ? -1 : 1);
+ }
+ return v;
+ });
+
+ //copy the temporary array back into the main this.items and this.keys objects
+ for(i = 0, len = c.length; i < len; i++){
+ items[i] = c[i].value;
+ keys[i] = c[i].key;
+ }
+
+ me.fireEvent('sort', me);
+ },
+
+ /**
+ * Sorts the collection by a single sorter function
+ * @param {Function} sorterFn The function to sort by
+ */
+ sortBy: function(sorterFn) {
+ var me = this,
+ items = me.items,
+ keys = me.keys,
+ length = items.length,
+ temp = [],
+ i;
+
+ //first we create a copy of the items array so that we can sort it
+ for (i = 0; i < length; i++) {
+ temp[i] = {
+ key : keys[i],
+ value: items[i],
+ index: i
+ };
+ }
+
+ Ext.Array.sort(temp, function(a, b) {
+ var v = sorterFn(a.value, b.value);
+ if (v === 0) {
+ v = (a.index < b.index ? -1 : 1);
+ }
+
+ return v;
+ });
+
+ //copy the temporary array back into the main this.items and this.keys objects
+ for (i = 0; i < length; i++) {
+ items[i] = temp[i].value;
+ keys[i] = temp[i].key;
+ }
+
+ me.fireEvent('sort', me, items, keys);
+ },
+
+ /**
+ * Reorders each of the items based on a mapping from old index to new index. Internally this
+ * just translates into a sort. The 'sort' event is fired whenever reordering has occured.
+ * @param {Object} mapping Mapping from old item index to new item index
+ */
+ reorder: function(mapping) {
+ var me = this,
+ items = me.items,
+ index = 0,
+ length = items.length,
+ order = [],
+ remaining = [],
+ oldIndex;
+
+ me.suspendEvents();
+
+ //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition}
+ for (oldIndex in mapping) {
+ order[mapping[oldIndex]] = items[oldIndex];
+ }
+
+ for (index = 0; index < length; index++) {
+ if (mapping[index] == undefined) {
+ remaining.push(items[index]);
+ }
+ }
+
+ for (index = 0; index < length; index++) {
+ if (order[index] == undefined) {
+ order[index] = remaining.shift();
+ }
+ }
+
+ me.clear();
+ me.addAll(order);
+
+ me.resumeEvents();
+ me.fireEvent('sort', me);
+ },
+
+ /**
+ * Sorts this collection by <b>key</b>s.
+ * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
+ * @param {Function} fn (optional) Comparison function that defines the sort order.
+ * Defaults to sorting by case insensitive string.
+ */
+ sortByKey : function(dir, fn){
+ this._sort('key', dir, fn || function(a, b){
+ var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
+ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+ });
+ }
+});
+
+/**
+ * @class Ext.data.StoreManager
+ * @extends Ext.util.MixedCollection
+ * <p>Contains a collection of all stores that are created that have an identifier.
+ * An identifier can be assigned by setting the {@link Ext.data.AbstractStore#storeId storeId}
+ * property. When a store is in the StoreManager, it can be referred to via it's identifier:
+ * <pre><code>
+Ext.create('Ext.data.Store', {
+ model: 'SomeModel',
+ storeId: 'myStore'
+});
+
+var store = Ext.data.StoreManager.lookup('myStore');
+ * </code></pre>
+ * Also note that the {@link #lookup} method is aliased to {@link Ext#getStore} for convenience.</p>
+ * <p>
+ * If a store is registered with the StoreManager, you can also refer to the store by it's identifier when
+ * registering it with any Component that consumes data from a store:
+ * <pre><code>
+Ext.create('Ext.data.Store', {
+ model: 'SomeModel',
+ storeId: 'myStore'
+});
+
+Ext.create('Ext.view.View', {
+ store: 'myStore',
+ // other configuration here
+});
+ * </code></pre>
+ * </p>
+ * @singleton
+ * @docauthor Evan Trimboli <evan@sencha.com>
+ * TODO: Make this an AbstractMgr
+ */
+Ext.define('Ext.data.StoreManager', {
+ extend: 'Ext.util.MixedCollection',
+ alternateClassName: ['Ext.StoreMgr', 'Ext.data.StoreMgr', 'Ext.StoreManager'],
+ singleton: true,
+ uses: ['Ext.data.ArrayStore'],
+
+ /**
+ * @cfg {Object} listeners @hide
+ */
+
+ /**
+ * Registers one or more Stores with the StoreManager. You do not normally need to register stores
+ * manually. Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
+ * @param {Ext.data.Store} store1 A Store instance
+ * @param {Ext.data.Store} store2 (optional)
+ * @param {Ext.data.Store} etc... (optional)
+ */
+ register : function() {
+ for (var i = 0, s; (s = arguments[i]); i++) {
+ this.add(s);
+ }
+ },
+
+ /**
+ * Unregisters one or more Stores with the StoreManager
+ * @param {String/Object} id1 The id of the Store, or a Store instance
+ * @param {String/Object} id2 (optional)
+ * @param {String/Object} etc... (optional)
+ */
+ unregister : function() {
+ for (var i = 0, s; (s = arguments[i]); i++) {
+ this.remove(this.lookup(s));
+ }
+ },
+
+ /**
+ * Gets a registered Store by id
+ * @param {String/Object} id The id of the Store, or a Store instance, or a store configuration
+ * @return {Ext.data.Store}
+ */
+ lookup : function(store) {
+ // handle the case when we are given an array or an array of arrays.
+ if (Ext.isArray(store)) {
+ var fields = ['field1'],
+ expand = !Ext.isArray(store[0]),
+ data = store,
+ i,
+ len;
+
+ if(expand){
+ data = [];
+ for (i = 0, len = store.length; i < len; ++i) {
+ data.push([store[i]]);
+ }
+ } else {
+ for(i = 2, len = store[0].length; i <= len; ++i){
+ fields.push('field' + i);
+ }
+ }
+ return Ext.create('Ext.data.ArrayStore', {
+ data : data,
+ fields: fields,
+ autoDestroy: true,
+ autoCreated: true,
+ expanded: expand
+ });
+ }
+
+ if (Ext.isString(store)) {
+ // store id
+ return this.get(store);
+ } else {
+ // store instance or store config
+ return Ext.data.AbstractStore.create(store);
+ }
+ },
+
+ // getKey implementation for MixedCollection
+ getKey : function(o) {
+ return o.storeId;
+ }
+}, function() {
+ /**
+ * <p>Creates a new store for the given id and config, then registers it with the {@link Ext.data.StoreManager Store Mananger}.
+ * Sample usage:</p>
+ <pre><code>
+ Ext.regStore('AllUsers', {
+ model: 'User'
+ });
+
+ //the store can now easily be used throughout the application
+ new Ext.List({
+ store: 'AllUsers',
+ ... other config
+ });
+ </code></pre>
+ * @param {String} id The id to set on the new store
+ * @param {Object} config The store config
+ * @param {Constructor} cls The new Component class.
+ * @member Ext
+ * @method regStore
+ */
+ Ext.regStore = function(name, config) {
+ var store;
+
+ if (Ext.isObject(name)) {
+ config = name;
+ } else {
+ config.storeId = name;
+ }
+
+ if (config instanceof Ext.data.Store) {
+ store = config;
+ } else {
+ store = Ext.create('Ext.data.Store', config);
+ }
+
+ return Ext.data.StoreManager.register(store);
+ };
+
+ /**
+ * Gets a registered Store by id (shortcut to {@link #lookup})
+ * @param {String/Object} id The id of the Store, or a Store instance
+ * @return {Ext.data.Store}
+ * @member Ext
+ * @method getStore
+ */
+ Ext.getStore = function(name) {
+ return Ext.data.StoreManager.lookup(name);
+ };
+});
+
+/**
+ * @class Ext.LoadMask
+ * A simple utility class for generically masking elements while loading data. If the {@link #store}
+ * config option is specified, the masking will be automatically synchronized with the store's loading
+ * process and the mask element will be cached for reuse.
+ * <p>Example usage:</p>
+ * <pre><code>
+// Basic mask:
+var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
+myMask.show();
+</code></pre>
+
+ * @constructor
+ * Create a new LoadMask
+ * @param {Mixed} el The element, element ID, or DOM node you wish to mask. Also, may be a Component who's element you wish to mask.
+ * @param {Object} config The config object
+ */
+
+Ext.define('Ext.LoadMask', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.data.StoreManager'],
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Ext.data.Store} store
+ * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
+ * hidden on either load success, or load fail.
+ */
+
+ /**
+ * @cfg {String} msg
+ * The text to display in a centered loading message box (defaults to 'Loading...')
+ */
+ msg : 'Loading...',
+ /**
+ * @cfg {String} msgCls
+ * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
+ */
+ msgCls : Ext.baseCSSPrefix + 'mask-loading',
+
+ /**
+ * @cfg {Boolean} useMsg
+ * Whether or not to use a loading message class or simply mask the bound element.
+ */
+ useMsg: true,
+
+ /**
+ * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
+ * @type Boolean
+ */
+ disabled: false,
+
+ constructor : function(el, config) {
+ var me = this;
+
+ if (el.isComponent) {
+ me.bindComponent(el);
+ } else {
+ me.el = Ext.get(el);
+ }
+ Ext.apply(me, config);
+
+ me.addEvents('beforeshow', 'show', 'hide');
+ if (me.store) {
+ me.bindStore(me.store, true);
+ }
+ me.mixins.observable.constructor.call(me, config);
+ },
+
+ bindComponent: function(comp) {
+ var me = this,
+ listeners = {
+ resize: me.onComponentResize,
+ scope: me
+ };
+
+ if (comp.el) {
+ me.onComponentRender(comp);
+ } else {
+ listeners.render = {
+ fn: me.onComponentRender,
+ scope: me,
+ single: true
+ };
+ }
+ me.mon(comp, listeners);
+ },
+
+ /**
+ * @private
+ * Called if we were configured with a Component, and that Component was not yet rendered. Collects the element to mask.
+ */
+ onComponentRender: function(comp) {
+ this.el = comp.getContentTarget();
+ },
+
+ /**
+ * @private
+ * Called when this LoadMask's Component is resized. The isMasked method also re-centers any displayed message.
+ */
+ onComponentResize: function(comp, w, h) {
+ this.el.isMasked();
+ },
+
+ /**
+ * Changes the data store bound to this LoadMask.
+ * @param {Store} store The store to bind to this LoadMask
+ */
+ bindStore : function(store, initial) {
+ var me = this;
+
+ if (!initial && me.store) {
+ me.mun(me.store, {
+ scope: me,
+ beforeload: me.onBeforeLoad,
+ load: me.onLoad,
+ exception: me.onLoad
+ });
+ if(!store) {
+ me.store = null;
+ }
+ }
+ if (store) {
+ store = Ext.data.StoreManager.lookup(store);
+ me.mon(store, {
+ scope: me,
+ beforeload: me.onBeforeLoad,
+ load: me.onLoad,
+ exception: me.onLoad
+ });
+
+ }
+ me.store = store;
+ if (store && store.isLoading()) {
+ me.onBeforeLoad();
+ }
+ },
+
+ /**
+ * Disables the mask to prevent it from being displayed
+ */
+ disable : function() {
+ var me = this;
+
+ me.disabled = true;
+ if (me.loading) {
+ me.onLoad();
+ }
+ },
+
+ /**
+ * Enables the mask so that it can be displayed
+ */
+ enable : function() {
+ this.disabled = false;
+ },
+
+ /**
+ * Method to determine whether this LoadMask is currently disabled.
+ * @return {Boolean} the disabled state of this LoadMask.
+ */
+ isDisabled : function() {
+ return this.disabled;
+ },
+
+ // private
+ onLoad : function() {
+ var me = this;
+
+ me.loading = false;
+ me.el.unmask();
+ me.fireEvent('hide', me, me.el, me.store);
+ },
+
+ // private
+ onBeforeLoad : function() {
+ var me = this;
+
+ if (!me.disabled && !me.loading && me.fireEvent('beforeshow', me, me.el, me.store) !== false) {
+ if (me.useMsg) {
+ me.el.mask(me.msg, me.msgCls, false);
+ } else {
+ me.el.mask();
+ }
+
+ me.fireEvent('show', me, me.el, me.store);
+ me.loading = true;
+ }
+ },
+
+ /**
+ * Show this LoadMask over the configured Element.
+ */
+ show: function() {
+ this.onBeforeLoad();
+ },
+
+ /**
+ * Hide this LoadMask.
+ */
+ hide: function() {
+ this.onLoad();
+ },
+
+ // private
+ destroy : function() {
+ this.hide();
+ this.clearListeners();
+ }
+});
+
+/**
+ * @class Ext.ComponentLoader
+ * @extends Ext.ElementLoader
+ *
+ * This class is used to load content via Ajax into a {@link Ext.Component}. In general
+ * this class will not be instanced directly, rather a loader configuration will be passed to the
+ * constructor of the {@link Ext.Component}.
+ *
+ * ## HTML Renderer
+ * By default, the content loaded will be processed as raw html. The response text
+ * from the request is taken and added to the component. This can be used in
+ * conjunction with the {@link #scripts} option to execute any inline scripts in
+ * the resulting content. Using this renderer has the same effect as passing the
+ * {@link Ext.Component#html} configuration option.
+ *
+ * ## Data Renderer
+ * This renderer allows content to be added by using JSON data and a {@link Ext.XTemplate}.
+ * The content received from the response is passed to the {@link Ext.Component#update} method.
+ * This content is run through the attached {@link Ext.Component#tpl} and the data is added to
+ * the Component. Using this renderer has the same effect as using the {@link Ext.Component#data}
+ * configuration in conjunction with a {@link Ext.Component#tpl}.
+ *
+ * ## Component Renderer
+ * This renderer can only be used with a {@link Ext.Container} and subclasses. It allows for
+ * Components to be loaded remotely into a Container. The response is expected to be a single/series of
+ * {@link Ext.Component} configuration objects. When the response is received, the data is decoded
+ * and then passed to {@link Ext.Container#add}. Using this renderer has the same effect as specifying
+ * the {@link Ext.Container#items} configuration on a Container.
+ *
+ * ## Custom Renderer
+ * A custom function can be passed to handle any other special case, see the {@link #renderer} option.
+ *
+ * ## Example Usage
+ * new Ext.Component({
+ * tpl: '{firstName} - {lastName}',
+ * loader: {
+ * url: 'myPage.php',
+ * renderer: 'data',
+ * params: {
+ * userId: 1
+ * }
+ * }
+ * });
+ */
+Ext.define('Ext.ComponentLoader', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.ElementLoader',
+
+ statics: {
+ Renderer: {
+ Data: function(loader, response, active){
+ var success = true;
+ try {
+ loader.getTarget().update(Ext.decode(response.responseText));
+ } catch (e) {
+ success = false;
+ }
+ return success;
+ },
+
+ Component: function(loader, response, active){
+ var success = true,
+ target = loader.getTarget(),
+ items = [];
+
+ if (!target.isContainer) {
+ Ext.Error.raise({
+ target: target,
+ msg: 'Components can only be loaded into a container'
+ });
+ }
+
+ try {
+ items = Ext.decode(response.responseText);
+ } catch (e) {
+ success = false;
+ }
+
+ if (success) {
+ if (active.removeAll) {
+ target.removeAll();
+ }
+ target.add(items);
+ }
+ return success;
+ }
+ }
+ },
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Ext.Component/String} target The target {@link Ext.Component} for the loader. Defaults to <tt>null</tt>.
+ * If a string is passed it will be looked up via the id.
+ */
+ target: null,
+
+ /**
+ * @cfg {Mixed} loadMask True or a {@link Ext.LoadMask} configuration to enable masking during loading. Defaults to <tt>false</tt>.
+ */
+ loadMask: false,
+
+ /**
+ * @cfg {Boolean} scripts True to parse any inline script tags in the response. This only used when using the html
+ * {@link #renderer}.
+ */
+
+ /**
+ * @cfg {String/Function} renderer
+
+The type of content that is to be loaded into, which can be one of 3 types:
+
++ **html** : Loads raw html content, see {@link Ext.Component#html}
++ **data** : Loads raw html content, see {@link Ext.Component#data}
++ **component** : Loads child {Ext.Component} instances. This option is only valid when used with a Container.
+
+Defaults to `html`.
+
+Alternatively, you can pass a function which is called with the following parameters.
+
++ loader - Loader instance
++ response - The server response
++ active - The active request
+
+The function must return false is loading is not successful. Below is a sample of using a custom renderer:
+
+ new Ext.Component({
+ loader: {
+ url: 'myPage.php',
+ renderer: function(loader, response, active) {
+ var text = response.responseText;
+ loader.getTarget().update('The response is ' + text);
+ return true;
+ }
+ }
+ });
+ * @markdown
+ */
+ renderer: 'html',
+
+ /**
+ * Set a {Ext.Component} as the target of this loader. Note that if the target is changed,
+ * any active requests will be aborted.
+ * @param {String/Ext.Component} target The component to be the target of this loader. If a string is passed
+ * it will be looked up via its id.
+ */
+ setTarget: function(target){
+ var me = this;
+
+ if (Ext.isString(target)) {
+ target = Ext.getCmp(target);
+ }
+
+ if (me.target && me.target != target) {
+ me.abort();
+ }
+ me.target = target;
+ },
+
+ // inherit docs
+ removeMask: function(){
+ this.target.setLoading(false);
+ },
+
+ /**
+ * Add the mask on the target
+ * @private
+ * @param {Mixed} mask The mask configuration
+ */
+ addMask: function(mask){
+ this.target.setLoading(mask);
+ },
+
+ /**
+ * Get the target of this loader.
+ * @return {Ext.Component} target The target, null if none exists.
+ */
+
+ setOptions: function(active, options){
+ active.removeAll = Ext.isDefined(options.removeAll) ? options.removeAll : this.removeAll;
+ },
+
+ /**
+ * Gets the renderer to use
+ * @private
+ * @param {String/Function} renderer The renderer to use
+ * @return {Function} A rendering function to use.
+ */
+ getRenderer: function(renderer){
+ if (Ext.isFunction(renderer)) {
+ return renderer;
+ }
+
+ var renderers = this.statics().Renderer;
+ switch (renderer) {
+ case 'component':
+ return renderers.Component;
+ case 'data':
+ return renderers.Data;
+ default:
+ return Ext.ElementLoader.Renderer.Html;
+ }
+ }
+});
+
+/**
+ * @class Ext.layout.component.Auto
+ * @extends Ext.layout.component.Component
+ * @private
+ *
+ * <p>The AutoLayout is the default layout manager delegated by {@link Ext.Component} to
+ * render any child Elements when no <tt>{@link Ext.Component#layout layout}</tt> is configured.</p>
+ */
+
+Ext.define('Ext.layout.component.Auto', {
+
+ /* Begin Definitions */
+
+ alias: 'layout.autocomponent',
+
+ extend: 'Ext.layout.component.Component',
+
+ /* End Definitions */
+
+ type: 'autocomponent',
+
+ onLayout : function(width, height) {
+ this.setTargetSize(width, height);
+ }
+});
+/**
+ * @class Ext.AbstractComponent
+ * <p>An abstract base class which provides shared methods for Components across the Sencha product line.</p>
+ * <p>Please refer to sub class's documentation</p>
+ * @constructor
+ */
+
+Ext.define('Ext.AbstractComponent', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable',
+ animate: 'Ext.util.Animate',
+ state: 'Ext.state.Stateful'
+ },
+
+ requires: [
+ 'Ext.PluginManager',
+ 'Ext.ComponentManager',
+ 'Ext.core.Element',
+ 'Ext.core.DomHelper',
+ 'Ext.XTemplate',
+ 'Ext.ComponentQuery',
+ 'Ext.LoadMask',
+ 'Ext.ComponentLoader',
+ 'Ext.EventManager',
+ 'Ext.layout.Layout',
+ 'Ext.layout.component.Auto'
+ ],
+
+ // Please remember to add dependencies whenever you use it
+ // I had to fix these many times already
+ uses: [
+ 'Ext.ZIndexManager'
+ ],
+
+ statics: {
+ AUTO_ID: 1000
+ },
+
+ /* End Definitions */
+
+ isComponent: true,
+
+ getAutoId: function() {
+ return ++Ext.AbstractComponent.AUTO_ID;
+ },
+
+ /**
+ * @cfg {String} id
+ * <p>The <b><u>unique id of this component instance</u></b> (defaults to an {@link #getId auto-assigned id}).</p>
+ * <p>It should not be necessary to use this configuration except for singleton objects in your application.
+ * Components created with an id may be accessed globally using {@link Ext#getCmp Ext.getCmp}.</p>
+ * <p>Instead of using assigned ids, use the {@link #itemId} config, and {@link Ext.ComponentQuery ComponentQuery} which
+ * provides selector-based searching for Sencha Components analogous to DOM querying. The {@link Ext.container.Container Container}
+ * class contains {@link Ext.container.Container#down shortcut methods} to query its descendant Components by selector.</p>
+ * <p>Note that this id will also be used as the element id for the containing HTML element
+ * that is rendered to the page for this component. This allows you to write id-based CSS
+ * rules to style the specific instance of this component uniquely, and also to select
+ * sub-elements using this component's id as the parent.</p>
+ * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see <code>{@link #itemId}</code>.</p>
+ * <p><b>Note</b>: to access the container of a Component see <code>{@link #ownerCt}</code>.</p>
+ */
+
+ /**
+ * @cfg {String} itemId
+ * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
+ * when no object reference is available. Instead of using an <code>{@link #id}</code> with
+ * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
+ * {@link Ext.container.Container}.{@link Ext.container.Container#getComponent getComponent} which will retrieve
+ * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
+ * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
+ * avoiding potential conflicts with {@link Ext.ComponentManager} which requires a <b>unique</b>
+ * <code>{@link #id}</code>.</p>
+ * <pre><code>
+var c = new Ext.panel.Panel({ //
+ {@link Ext.Component#height height}: 300,
+ {@link #renderTo}: document.body,
+ {@link Ext.container.Container#layout layout}: 'auto',
+ {@link Ext.container.Container#items items}: [
+ {
+ itemId: 'p1',
+ {@link Ext.panel.Panel#title title}: 'Panel 1',
+ {@link Ext.Component#height height}: 150
+ },
+ {
+ itemId: 'p2',
+ {@link Ext.panel.Panel#title title}: 'Panel 2',
+ {@link Ext.Component#height height}: 150
+ }
+ ]
+})
+p1 = c.{@link Ext.container.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
+p2 = p1.{@link #ownerCt}.{@link Ext.container.Container#getComponent getComponent}('p2'); // reference via a sibling
+ * </code></pre>
+ * <p>Also see <tt>{@link #id}</tt>, <code>{@link #query}</code>, <code>{@link #down}</code> and <code>{@link #child}</code>.</p>
+ * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
+ */
+
+ /**
+ * This Component's owner {@link Ext.container.Container Container} (defaults to undefined, and is set automatically when
+ * this Component is added to a Container). Read-only.
+ * <p><b>Note</b>: to access items within the Container see <tt>{@link #itemId}</tt>.</p>
+ * @type Ext.Container
+ * @property ownerCt
+ */
+
+ /**
+ * @cfg {Mixed} autoEl
+ * <p>A tag name or {@link Ext.core.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
+ * encapsulate this Component.</p>
+ * <p>You do not normally need to specify this. For the base classes {@link Ext.Component} and {@link Ext.container.Container},
+ * this defaults to <b><tt>'div'</tt></b>. The more complex Sencha classes use a more complex
+ * DOM structure specified by their own {@link #renderTpl}s.</p>
+ * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
+ * different DOM elements. Example usage:</p><pre><code>
+{
+ xtype: 'component',
+ autoEl: {
+ tag: 'img',
+ src: 'http://www.example.com/example.jpg'
+ }
+}, {
+ xtype: 'component',
+ autoEl: {
+ tag: 'blockquote',
+ html: 'autoEl is cool!'
+ }
+}, {
+ xtype: 'container',
+ autoEl: 'ul',
+ cls: 'ux-unordered-list',
+ items: {
+ xtype: 'component',
+ autoEl: 'li',
+ html: 'First list item'
+ }
+}
+</code></pre>
+ */
+
+ /**
+ * @cfg {Mixed} renderTpl
+ * <p>An {@link Ext.XTemplate XTemplate} used to create the internal structure inside this Component's
+ * encapsulating {@link #getEl Element}.</p>
+ * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}
+ * and {@link Ext.container.Container}, this defaults to <b><code>null</code></b> which means that they will be initially rendered
+ * with no internal structure; they render their {@link #getEl Element} empty. The more specialized ExtJS and Touch classes
+ * which use a more complex DOM structure, provide their own template definitions.</p>
+ * <p>This is intended to allow the developer to create application-specific utility Components with customized
+ * internal structure.</p>
+ * <p>Upon rendering, any created child elements may be automatically imported into object properties using the
+ * {@link #renderSelectors} option.</p>
+ */
+ renderTpl: null,
+
+ /**
+ * @cfg {Object} renderSelectors
+
+An object containing properties specifying {@link Ext.DomQuery DomQuery} selectors which identify child elements
+created by the render process.
+
+After the Component's internal structure is rendered according to the {@link #renderTpl}, this object is iterated through,
+and the found Elements are added as properties to the Component using the `renderSelector` property name.
+
+For example, a Component which rendered an image, and description into its element might use the following properties
+coded into its prototype:
+
+ renderTpl: '<img src="{imageUrl}" class="x-image-component-img"><div class="x-image-component-desc">{description}>/div<',
+
+ renderSelectors: {
+ image: 'img.x-image-component-img',
+ descEl: 'div.x-image-component-desc'
+ }
+
+After rendering, the Component would have a property <code>image</code> referencing its child `img` Element,
+and a property `descEl` referencing the `div` Element which contains the description.
+
+ * @markdown
+ */
+
+ /**
+ * @cfg {Mixed} renderTo
+ * <p>Specify the id of the element, a DOM element or an existing Element that this component
+ * will be rendered into.</p><div><ul>
+ * <li><b>Notes</b> : <ul>
+ * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
+ * a {@link Ext.container.Container Container}. It is the responsibility of the
+ * {@link Ext.container.Container Container}'s {@link Ext.container.Container#layout layout manager}
+ * to render and manage its child items.</div>
+ * <div class="sub-desc">When using this config, a call to render() is not required.</div>
+ * </ul></li>
+ * </ul></div>
+ * <p>See <code>{@link #render}</code> also.</p>
+ */
+
+ /**
+ * @cfg {Boolean} frame
+ * <p>Specify as <code>true</code> to have the Component inject framing elements within the Component at render time to
+ * provide a graphical rounded frame around the Component content.</p>
+ * <p>This is only necessary when running on outdated, or non standard-compliant browsers such as Microsoft's Internet Explorer
+ * prior to version 9 which do not support rounded corners natively.</p>
+ * <p>The extra space taken up by this framing is available from the read only property {@link #frameSize}.</p>
+ */
+
+ /**
+ * <p>Read-only property indicating the width of any framing elements which were added within the encapsulating element
+ * to provide graphical, rounded borders. See the {@link #frame} config.</p>
+ * <p> This is an object containing the frame width in pixels for all four sides of the Component containing
+ * the following properties:</p><div class="mdetail-params"><ul>
+ * <li><code>top</code> The width of the top framing element in pixels.</li>
+ * <li><code>right</code> The width of the right framing element in pixels.</li>
+ * <li><code>bottom</code> The width of the bottom framing element in pixels.</li>
+ * <li><code>left</code> The width of the left framing element in pixels.</li>
+ * </ul></div>
+ * @property frameSize
+ * @type {Object}
+ */
+
+ /**
+ * @cfg {String/Object} componentLayout
+ * <p>The sizing and positioning of a Component's internal Elements is the responsibility of
+ * the Component's layout manager which sizes a Component's internal structure in response to the Component being sized.</p>
+ * <p>Generally, developers will not use this configuration as all provided Components which need their internal
+ * elements sizing (Such as {@link Ext.form.field.Base input fields}) come with their own componentLayout managers.</p>
+ * <p>The {@link Ext.layout.container.Auto default layout manager} will be used on instances of the base Ext.Component class
+ * which simply sizes the Component's encapsulating element to the height and width specified in the {@link #setSize} method.</p>
+ */
+
+ /**
+ * @cfg {Mixed} tpl
+ * An <bold>{@link Ext.Template}</bold>, <bold>{@link Ext.XTemplate}</bold>
+ * or an array of strings to form an Ext.XTemplate.
+ * Used in conjunction with the <code>{@link #data}</code> and
+ * <code>{@link #tplWriteMode}</code> configurations.
+ */
+
+ /**
+ * @cfg {Mixed} data
+ * The initial set of data to apply to the <code>{@link #tpl}</code> to
+ * update the content area of the Component.
+ */
+
+ /**
+ * @cfg {String} tplWriteMode The Ext.(X)Template method to use when
+ * updating the content area of the Component. Defaults to <code>'overwrite'</code>
+ * (see <code>{@link Ext.XTemplate#overwrite}</code>).
+ */
+ tplWriteMode: 'overwrite',
+
+ /**
+ * @cfg {String} baseCls
+ * The base CSS class to apply to this components's element. This will also be prepended to
+ * elements within this component like Panel's body will get a class x-panel-body. This means
+ * that if you create a subclass of Panel, and you want it to get all the Panels styling for the
+ * element and the body, you leave the baseCls x-panel and use componentCls to add specific styling for this
+ * component.
+ */
+ baseCls: Ext.baseCSSPrefix + 'component',
+
+ /**
+ * @cfg {String} componentCls
+ * CSS Class to be added to a components root level element to give distinction to it
+ * via styling.
+ */
+
+ /**
+ * @cfg {String} cls
+ * An optional extra CSS class that will be added to this component's Element (defaults to ''). This can be
+ * useful for adding customized styles to the component or any of its children using standard CSS rules.
+ */
+
+ /**
+ * @cfg {String} overCls
+ * An optional extra CSS class that will be added to this component's Element when the mouse moves
+ * over the Element, and removed when the mouse moves out. (defaults to ''). This can be
+ * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
+ */
+
+ /**
+ * @cfg {String} disabledCls
+ * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
+ */
+ disabledCls: Ext.baseCSSPrefix + 'item-disabled',
+
+ /**
+ * @cfg {String/Array} ui
+ * A set style for a component. Can be a string or an Array of multiple strings (UIs)
+ */
+ ui: 'default',
+
+ /**
+ * @cfg {Array} uiCls
+ * An array of of classNames which are currently applied to this component
+ * @private
+ */
+ uiCls: [],
+
+ /**
+ * @cfg {String} style
+ * A custom style specification to be applied to this component's Element. Should be a valid argument to
+ * {@link Ext.core.Element#applyStyles}.
+ * <pre><code>
+ new Ext.panel.Panel({
+ title: 'Some Title',
+ renderTo: Ext.getBody(),
+ width: 400, height: 300,
+ layout: 'form',
+ items: [{
+ xtype: 'textarea',
+ style: {
+ width: '95%',
+ marginBottom: '10px'
+ }
+ },
+ new Ext.button.Button({
+ text: 'Send',
+ minWidth: '100',
+ style: {
+ marginBottom: '10px'
+ }
+ })
+ ]
+ });
+ </code></pre>
+ */
+
+ /**
+ * @cfg {Number} width
+ * The width of this component in pixels.
+ */
+
+ /**
+ * @cfg {Number} height
+ * The height of this component in pixels.
+ */
+
+ /**
+ * @cfg {Number/String} border
+ * Specifies the border for this component. The border can be a single numeric value to apply to all sides or
+ * it can be a CSS style specification for each style, for example: '10 5 3 10'.
+ */
+
+ /**
+ * @cfg {Number/String} padding
+ * Specifies the padding for this component. The padding can be a single numeric value to apply to all sides or
+ * it can be a CSS style specification for each style, for example: '10 5 3 10'.
+ */
+
+ /**
+ * @cfg {Number/String} margin
+ * Specifies the margin for this component. The margin can be a single numeric value to apply to all sides or
+ * it can be a CSS style specification for each style, for example: '10 5 3 10'.
+ */
+
+ /**
+ * @cfg {Boolean} hidden
+ * Defaults to false.
+ */
+ hidden: false,
+
+ /**
+ * @cfg {Boolean} disabled
+ * Defaults to false.
+ */
+ disabled: false,
+
+ /**
+ * @cfg {Boolean} draggable
+ * Allows the component to be dragged.
+ */
+
+ /**
+ * Read-only property indicating whether or not the component can be dragged
+ * @property draggable
+ * @type {Boolean}
+ */
+ draggable: false,
+
+ /**
+ * @cfg {Boolean} floating
+ * Create the Component as a floating and use absolute positioning.
+ * Defaults to false.
+ */
+ floating: false,
+
+ /**
+ * @cfg {String} hideMode
+ * A String which specifies how this Component's encapsulating DOM element will be hidden.
+ * Values may be<div class="mdetail-params"><ul>
+ * <li><code>'display'</code> : The Component will be hidden using the <code>display: none</code> style.</li>
+ * <li><code>'visibility'</code> : The Component will be hidden using the <code>visibility: hidden</code> style.</li>
+ * <li><code>'offsets'</code> : The Component will be hidden by absolutely positioning it out of the visible area of the document. This
+ * is useful when a hidden Component must maintain measurable dimensions. Hiding using <code>display</code> results
+ * in a Component having zero dimensions.</li></ul></div>
+ * Defaults to <code>'display'</code>.
+ */
+ hideMode: 'display',
+
+ /**
+ * @cfg {String} contentEl
+ * <p>Optional. Specify an existing HTML element, or the <code>id</code> of an existing HTML element to use as the content
+ * for this component.</p>
+ * <ul>
+ * <li><b>Description</b> :
+ * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the layout element
+ * of a new component (it simply moves the specified DOM element <i>after the Component is rendered</i> to use as the content.</div></li>
+ * <li><b>Notes</b> :
+ * <div class="sub-desc">The specified HTML element is appended to the layout element of the component <i>after any configured
+ * {@link #html HTML} has been inserted</i>, and so the document will not contain this element at the time the {@link #render} event is fired.</div>
+ * <div class="sub-desc">The specified HTML element used will not participate in any <code><b>{@link Ext.container.Container#layout layout}</b></code>
+ * scheme that the Component may use. It is just HTML. Layouts operate on child <code><b>{@link Ext.container.Container#items items}</b></code>.</div>
+ * <div class="sub-desc">Add either the <code>x-hidden</code> or the <code>x-hide-display</code> CSS class to
+ * prevent a brief flicker of the content before it is rendered to the panel.</div></li>
+ * </ul>
+ */
+
+ /**
+ * @cfg {String/Object} html
+ * An HTML fragment, or a {@link Ext.core.DomHelper DomHelper} specification to use as the layout element
+ * content (defaults to ''). The HTML content is added after the component is rendered,
+ * so the document will not contain this HTML at the time the {@link #render} event is fired.
+ * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
+ */
+
+ /**
+ * @cfg {String} styleHtmlContent
+ * True to automatically style the html inside the content target of this component (body for panels).
+ * Defaults to false.
+ */
+ styleHtmlContent: false,
+
+ /**
+ * @cfg {String} styleHtmlCls
+ * The class that is added to the content target when you set styleHtmlContent to true.
+ * Defaults to 'x-html'
+ */
+ styleHtmlCls: Ext.baseCSSPrefix + 'html',
+
+ /**
+ * @cfg {Number} minHeight
+ * <p>The minimum value in pixels which this Component will set its height to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} minWidth
+ * <p>The minimum value in pixels which this Component will set its width to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} maxHeight
+ * <p>The maximum value in pixels which this Component will set its height to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+ /**
+ * @cfg {Number} maxWidth
+ * <p>The maximum value in pixels which this Component will set its width to.</p>
+ * <p><b>Warning:</b> This will override any size management applied by layout managers.</p>
+ */
+
+ /**
+ * @cfg {Ext.ComponentLoader/Object} loader
+ * A configuration object or an instance of a {@link Ext.ComponentLoader} to load remote
+ * content for this Component.
+ */
+
+ // @private
+ allowDomMove: true,
+
+ /**
+ * @cfg {Boolean} autoShow True to automatically show the component upon creation.
+ * This config option may only be used for {@link #floating} components or components
+ * that use {@link #autoRender}. Defaults to <tt>false</tt>.
+ */
+ autoShow: false,
+
+ /**
+ * @cfg {Mixed} autoRender
+ * <p>This config is intended mainly for {@link #floating} Components which may or may not be shown. Instead
+ * of using {@link #renderTo} in the configuration, and rendering upon construction, this allows a Component
+ * to render itself upon first <i>{@link #show}</i>.</p>
+ * <p>Specify as <code>true</code> to have this Component render to the document body upon first show.</p>
+ * <p>Specify as an element, or the ID of an element to have this Component render to a specific element upon first show.</p>
+ * <p><b>This defaults to <code>true</code> for the {@link Ext.window.Window Window} class.</b></p>
+ */
+ autoRender: false,
+
+ needsLayout: false,
+
+ /**
+ * @cfg {Object/Array} plugins
+ * An object or array of objects that will provide custom functionality for this component. The only
+ * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
+ * When a component is created, if any plugins are available, the component will call the init method on each
+ * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
+ * component as needed to provide its functionality.
+ */
+
+ /**
+ * Read-only property indicating whether or not the component has been rendered.
+ * @property rendered
+ * @type {Boolean}
+ */
+ rendered: false,
+
+ weight: 0,
+
+ trimRe: /^\s+|\s+$/g,
+ spacesRe: /\s+/,
+
+
+ /**
+ * This is an internal flag that you use when creating custom components.
+ * By default this is set to true which means that every component gets a mask when its disabled.
+ * Components like FieldContainer, FieldSet, Field, Button, Tab override this property to false
+ * since they want to implement custom disable logic.
+ * @property maskOnDisable
+ * @type {Boolean}
+ */
+ maskOnDisable: true,
+
+ constructor : function(config) {
+ var me = this,
+ i, len;
+
+ config = config || {};
+ me.initialConfig = config;
+ Ext.apply(me, config);
+
+ me.addEvents(
+ /**
+ * @event beforeactivate
+ * Fires before a Component has been visually activated.
+ * Returning false from an event listener can prevent the activate
+ * from occurring.
+ * @param {Ext.Component} this
+ */
+ 'beforeactivate',
+ /**
+ * @event activate
+ * Fires after a Component has been visually activated.
+ * @param {Ext.Component} this
+ */
+ 'activate',
+ /**
+ * @event beforedeactivate
+ * Fires before a Component has been visually deactivated.
+ * Returning false from an event listener can prevent the deactivate
+ * from occurring.
+ * @param {Ext.Component} this
+ */
+ 'beforedeactivate',
+ /**
+ * @event deactivate
+ * Fires after a Component has been visually deactivated.
+ * @param {Ext.Component} this
+ */
+ 'deactivate',
+ /**
+ * @event added
+ * Fires after a Component had been added to a Container.
+ * @param {Ext.Component} this
+ * @param {Ext.container.Container} container Parent Container
+ * @param {Number} pos position of Component
+ */
+ 'added',
+ /**
+ * @event disable
+ * Fires after the component is disabled.
+ * @param {Ext.Component} this
+ */
+ 'disable',
+ /**
+ * @event enable
+ * Fires after the component is enabled.
+ * @param {Ext.Component} this
+ */
+ 'enable',
+ /**
+ * @event beforeshow
+ * Fires before the component is shown when calling the {@link #show} method.
+ * Return false from an event handler to stop the show.
+ * @param {Ext.Component} this
+ */
+ 'beforeshow',
+ /**
+ * @event show
+ * Fires after the component is shown when calling the {@link #show} method.
+ * @param {Ext.Component} this
+ */
+ 'show',
+ /**
+ * @event beforehide
+ * Fires before the component is hidden when calling the {@link #hide} method.
+ * Return false from an event handler to stop the hide.
+ * @param {Ext.Component} this
+ */
+ 'beforehide',
+ /**
+ * @event hide
+ * Fires after the component is hidden.
+ * Fires after the component is hidden when calling the {@link #hide} method.
+ * @param {Ext.Component} this
+ */
+ 'hide',
+ /**
+ * @event removed
+ * Fires when a component is removed from an Ext.container.Container
+ * @param {Ext.Component} this
+ * @param {Ext.container.Container} ownerCt Container which holds the component
+ */
+ 'removed',
+ /**
+ * @event beforerender
+ * Fires before the component is {@link #rendered}. Return false from an
+ * event handler to stop the {@link #render}.
+ * @param {Ext.Component} this
+ */
+ 'beforerender',
+ /**
+ * @event render
+ * Fires after the component markup is {@link #rendered}.
+ * @param {Ext.Component} this
+ */
+ 'render',
+ /**
+ * @event afterrender
+ * <p>Fires after the component rendering is finished.</p>
+ * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
+ * by any afterRender method defined for the Component.</p>
+ * @param {Ext.Component} this
+ */
+ 'afterrender',
+ /**
+ * @event beforedestroy
+ * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
+ * @param {Ext.Component} this
+ */
+ 'beforedestroy',
+ /**
+ * @event destroy
+ * Fires after the component is {@link #destroy}ed.
+ * @param {Ext.Component} this
+ */
+ 'destroy',
+ /**
+ * @event resize
+ * Fires after the component is resized.
+ * @param {Ext.Component} this
+ * @param {Number} adjWidth The box-adjusted width that was set
+ * @param {Number} adjHeight The box-adjusted height that was set
+ */
+ 'resize',
+ /**
+ * @event move
+ * Fires after the component is moved.
+ * @param {Ext.Component} this
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ */
+ 'move'
+ );
+
+ me.getId();
+
+ me.mons = [];
+ me.additionalCls = [];
+ me.renderData = me.renderData || {};
+ me.renderSelectors = me.renderSelectors || {};
+
+ if (me.plugins) {
+ me.plugins = [].concat(me.plugins);
+ for (i = 0, len = me.plugins.length; i < len; i++) {
+ me.plugins[i] = me.constructPlugin(me.plugins[i]);
+ }
+ }
+
+ me.initComponent();
+
+ // ititComponent gets a chance to change the id property before registering
+ Ext.ComponentManager.register(me);
+
+ // Dont pass the config so that it is not applied to 'this' again
+ me.mixins.observable.constructor.call(me);
+ me.mixins.state.constructor.call(me, config);
+
+ // Move this into Observable?
+ if (me.plugins) {
+ me.plugins = [].concat(me.plugins);
+ for (i = 0, len = me.plugins.length; i < len; i++) {
+ me.plugins[i] = me.initPlugin(me.plugins[i]);
+ }
+ }
+
+ me.loader = me.getLoader();
+
+ if (me.renderTo) {
+ me.render(me.renderTo);
+ }
+
+ if (me.autoShow) {
+ me.show();
+ }
+
+ if (Ext.isDefined(me.disabledClass)) {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.Component: disabledClass has been deprecated. Please use disabledCls.');
+ }
+ me.disabledCls = me.disabledClass;
+ delete me.disabledClass;
+ }
+ },
+
+ initComponent: Ext.emptyFn,
+
+ show: Ext.emptyFn,
+
+ animate: function(animObj) {
+ var me = this,
+ to;
+
+ animObj = animObj || {};
+ to = animObj.to || {};
+
+ if (Ext.fx.Manager.hasFxBlock(me.id)) {
+ return me;
+ }
+ // Special processing for animating Component dimensions.
+ if (!animObj.dynamic && (to.height || to.width)) {
+ var curWidth = me.getWidth(),
+ w = curWidth,
+ curHeight = me.getHeight(),
+ h = curHeight,
+ needsResize = false;
+
+ if (to.height && to.height > curHeight) {
+ h = to.height;
+ needsResize = true;
+ }
+ if (to.width && to.width > curWidth) {
+ w = to.width;
+ needsResize = true;
+ }
+
+ // If any dimensions are being increased, we must resize the internal structure
+ // of the Component, but then clip it by sizing its encapsulating element back to original dimensions.
+ // The animation will then progressively reveal the larger content.
+ if (needsResize) {
+ var clearWidth = !Ext.isNumber(me.width),
+ clearHeight = !Ext.isNumber(me.height);
+
+ me.componentLayout.childrenChanged = true;
+ me.setSize(w, h, me.ownerCt);
+ me.el.setSize(curWidth, curHeight);
+ if (clearWidth) {
+ delete me.width;
+ }
+ if (clearHeight) {
+ delete me.height;
+ }
+ }
+ }
+ return me.mixins.animate.animate.apply(me, arguments);
+ },
+
+ /**
+ * <p>This method finds the topmost active layout who's processing will eventually determine the size and position of this
+ * Component.<p>
+ * <p>This method is useful when dynamically adding Components into Containers, and some processing must take place after the
+ * final sizing and positioning of the Component has been performed.</p>
+ * @returns
+ */
+ findLayoutController: function() {
+ return this.findParentBy(function(c) {
+ // Return true if we are at the root of the Container tree
+ // or this Container's layout is busy but the next one up is not.
+ return !c.ownerCt || (c.layout.layoutBusy && !c.ownerCt.layout.layoutBusy);
+ });
+ },
+
+ onShow : function() {
+ // Layout if needed
+ var needsLayout = this.needsLayout;
+ if (Ext.isObject(needsLayout)) {
+ this.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
+ }
+ },
+
+ constructPlugin: function(plugin) {
+ if (plugin.ptype && typeof plugin.init != 'function') {
+ plugin.cmp = this;
+ plugin = Ext.PluginManager.create(plugin);
+ }
+ else if (typeof plugin == 'string') {
+ plugin = Ext.PluginManager.create({
+ ptype: plugin,
+ cmp: this
+ });
+ }
+ return plugin;
+ },
+
+
+ // @private
+ initPlugin : function(plugin) {
+ plugin.init(this);
+
+ return plugin;
+ },
+
+ /**
+ * Handles autoRender.
+ * Floating Components may have an ownerCt. If they are asking to be constrained, constrain them within that
+ * ownerCt, and have their z-index managed locally. Floating Components are always rendered to document.body
+ */
+ doAutoRender: function() {
+ var me = this;
+ if (me.floating) {
+ me.render(document.body);
+ } else {
+ me.render(Ext.isBoolean(me.autoRender) ? Ext.getBody() : me.autoRender);
+ }
+ },
+
+ // @private
+ render : function(container, position) {
+ var me = this;
+
+ if (!me.rendered && me.fireEvent('beforerender', me) !== false) {
+ // If this.el is defined, we want to make sure we are dealing with
+ // an Ext Element.
+ if (me.el) {
+ me.el = Ext.get(me.el);
+ }
+
+ // Perform render-time processing for floating Components
+ if (me.floating) {
+ me.onFloatRender();
+ }
+
+ container = me.initContainer(container);
+
+ me.onRender(container, position);
+
+ // Tell the encapsulating element to hide itself in the way the Component is configured to hide
+ // This means DISPLAY, VISIBILITY or OFFSETS.
+ me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
+
+ if (me.overCls) {
+ me.el.hover(me.addOverCls, me.removeOverCls, me);
+ }
+
+ me.fireEvent('render', me);
+
+ me.initContent();
+
+ me.afterRender(container);
+ me.fireEvent('afterrender', me);
+
+ me.initEvents();
+
+ if (me.hidden) {
+ // Hiding during the render process should not perform any ancillary
+ // actions that the full hide process does; It is not hiding, it begins in a hidden state.'
+ // So just make the element hidden according to the configured hideMode
+ me.el.hide();
+ }
+
+ if (me.disabled) {
+ // pass silent so the event doesn't fire the first time.
+ me.disable(true);
+ }
+ }
+ return me;
+ },
+
+ // @private
+ onRender : function(container, position) {
+ var me = this,
+ el = me.el,
+ cls = me.initCls(),
+ styles = me.initStyles(),
+ renderTpl, renderData, i;
+
+ position = me.getInsertPosition(position);
+
+ if (!el) {
+ if (position) {
+ el = Ext.core.DomHelper.insertBefore(position, me.getElConfig(), true);
+ }
+ else {
+ el = Ext.core.DomHelper.append(container, me.getElConfig(), true);
+ }
+ }
+ else if (me.allowDomMove !== false) {
+ if (position) {
+ container.dom.insertBefore(el.dom, position);
+ } else {
+ container.dom.appendChild(el.dom);
+ }
+ }
+
+ if (Ext.scopeResetCSS && !me.ownerCt) {
+ // If this component's el is the body element, we add the reset class to the html tag
+ if (el.dom == Ext.getBody().dom) {
+ el.parent().addCls(Ext.baseCSSPrefix + 'reset');
+ }
+ else {
+ // Else we wrap this element in an element that adds the reset class.
+ me.resetEl = el.wrap({
+ cls: Ext.baseCSSPrefix + 'reset'
+ });
+ }
+ }
+
+ el.addCls(cls);
+ el.setStyle(styles);
+
+ // Here we check if the component has a height set through style or css.
+ // If it does then we set the this.height to that value and it won't be
+ // considered an auto height component
+ // if (this.height === undefined) {
+ // var height = el.getHeight();
+ // // This hopefully means that the panel has an explicit height set in style or css
+ // if (height - el.getPadding('tb') - el.getBorderWidth('tb') > 0) {
+ // this.height = height;
+ // }
+ // }
+
+ me.el = el;
+
+ me.rendered = true;
+ me.addUIToElement(true);
+ //loop through all exisiting uiCls and update the ui in them
+ for (i = 0; i < me.uiCls.length; i++) {
+ me.addUIClsToElement(me.uiCls[i], true);
+ }
+ me.rendered = false;
+ me.initFrame();
+
+ renderTpl = me.initRenderTpl();
+ if (renderTpl) {
+ renderData = me.initRenderData();
+ renderTpl.append(me.getTargetEl(), renderData);
+ }
+
+ me.applyRenderSelectors();
+
+ me.rendered = true;
+
+ me.setUI(me.ui);
+ },
+
+ // @private
+ afterRender : function() {
+ var me = this,
+ pos,
+ xy;
+
+ me.getComponentLayout();
+
+ // Set the size if a size is configured, or if this is the outermost Container
+ if (!me.ownerCt || (me.height || me.width)) {
+ me.setSize(me.width, me.height);
+ }
+
+ // For floaters, calculate x and y if they aren't defined by aligning
+ // the sized element to the center of either the the container or the ownerCt
+ if (me.floating && (me.x === undefined || me.y === undefined)) {
+ if (me.floatParent) {
+ xy = me.el.getAlignToXY(me.floatParent.getTargetEl(), 'c-c');
+ pos = me.floatParent.getTargetEl().translatePoints(xy[0], xy[1]);
+ } else {
+ xy = me.el.getAlignToXY(me.container, 'c-c');
+ pos = me.container.translatePoints(xy[0], xy[1]);
+ }
+ me.x = me.x === undefined ? pos.left: me.x;
+ me.y = me.y === undefined ? pos.top: me.y;
+ }
+
+ if (Ext.isDefined(me.x) || Ext.isDefined(me.y)) {
+ me.setPosition(me.x, me.y);
+ }
+
+ if (me.styleHtmlContent) {
+ me.getTargetEl().addCls(me.styleHtmlCls);
+ }
+ },
+
+ frameCls: Ext.baseCSSPrefix + 'frame',
+
+ frameTpl: [
+ '<tpl if="top">',
+ '<tpl if="left"><div class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
+ '<tpl if="right"><div class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
+ '<div class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></div>',
+ '<tpl if="right"></div></tpl>',
+ '<tpl if="left"></div></tpl>',
+ '</tpl>',
+ '<tpl if="left"><div class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></tpl>',
+ '<tpl if="right"><div class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-right: {frameWidth}px" role="presentation"></tpl>',
+ '<div class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" role="presentation"></div>',
+ '<tpl if="right"></div></tpl>',
+ '<tpl if="left"></div></tpl>',
+ '<tpl if="bottom">',
+ '<tpl if="left"><div class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></tpl>',
+ '<tpl if="right"><div class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-right: {frameWidth}px" role="presentation"></tpl>',
+ '<div class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></div>',
+ '<tpl if="right"></div></tpl>',
+ '<tpl if="left"></div></tpl>',
+ '</tpl>'
+ ],
+
+ frameTableTpl: [
+ '<table><tbody>',
+ '<tpl if="top">',
+ '<tr>',
+ '<tpl if="left"><td class="{frameCls}-tl {baseCls}-tl {baseCls}-{ui}-tl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tl</tpl></tpl>" style="background-position: {tl}; padding-left:{frameWidth}px" role="presentation"></td></tpl>',
+ '<td class="{frameCls}-tc {baseCls}-tc {baseCls}-{ui}-tc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tc</tpl></tpl>" style="background-position: {tc}; height: {frameWidth}px" role="presentation"></td>',
+ '<tpl if="right"><td class="{frameCls}-tr {baseCls}-tr {baseCls}-{ui}-tr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-tr</tpl></tpl>" style="background-position: {tr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
+ '</tr>',
+ '</tpl>',
+ '<tr>',
+ '<tpl if="left"><td class="{frameCls}-ml {baseCls}-ml {baseCls}-{ui}-ml<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-ml</tpl></tpl>" style="background-position: {ml}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
+ '<td class="{frameCls}-mc {baseCls}-mc {baseCls}-{ui}-mc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mc</tpl></tpl>" style="background-position: 0 0;" role="presentation"></td>',
+ '<tpl if="right"><td class="{frameCls}-mr {baseCls}-mr {baseCls}-{ui}-mr<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-mr</tpl></tpl>" style="background-position: {mr}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
+ '</tr>',
+ '<tpl if="bottom">',
+ '<tr>',
+ '<tpl if="left"><td class="{frameCls}-bl {baseCls}-bl {baseCls}-{ui}-bl<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bl</tpl></tpl>" style="background-position: {bl}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
+ '<td class="{frameCls}-bc {baseCls}-bc {baseCls}-{ui}-bc<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-bc</tpl></tpl>" style="background-position: {bc}; height: {frameWidth}px" role="presentation"></td>',
+ '<tpl if="right"><td class="{frameCls}-br {baseCls}-br {baseCls}-{ui}-br<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-{parent.ui}-{.}-br</tpl></tpl>" style="background-position: {br}; padding-left: {frameWidth}px" role="presentation"></td></tpl>',
+ '</tr>',
+ '</tpl>',
+ '</tbody></table>'
+ ],
+
+ /**
+ * @private
+ */
+ initFrame : function() {
+ if (Ext.supports.CSS3BorderRadius) {
+ return false;
+ }
+
+ var me = this,
+ frameInfo = me.getFrameInfo(),
+ frameWidth = frameInfo.width,
+ frameTpl = me.getFrameTpl(frameInfo.table);
+
+ if (me.frame) {
+ // Here we render the frameTpl to this component. This inserts the 9point div or the table framing.
+ frameTpl.insertFirst(me.el, Ext.apply({}, {
+ ui: me.ui,
+ uiCls: me.uiCls,
+ frameCls: me.frameCls,
+ baseCls: me.baseCls,
+ frameWidth: frameWidth,
+ top: !!frameInfo.top,
+ left: !!frameInfo.left,
+ right: !!frameInfo.right,
+ bottom: !!frameInfo.bottom
+ }, me.getFramePositions(frameInfo)));
+
+ // The frameBody is returned in getTargetEl, so that layouts render items to the correct target.=
+ me.frameBody = me.el.down('.' + me.frameCls + '-mc');
+
+ // Add the render selectors for each of the frame elements
+ Ext.apply(me.renderSelectors, {
+ frameTL: '.' + me.baseCls + '-tl',
+ frameTC: '.' + me.baseCls + '-tc',
+ frameTR: '.' + me.baseCls + '-tr',
+ frameML: '.' + me.baseCls + '-ml',
+ frameMC: '.' + me.baseCls + '-mc',
+ frameMR: '.' + me.baseCls + '-mr',
+ frameBL: '.' + me.baseCls + '-bl',
+ frameBC: '.' + me.baseCls + '-bc',
+ frameBR: '.' + me.baseCls + '-br'
+ });
+ }
+ },
+
+ updateFrame: function() {
+ if (Ext.supports.CSS3BorderRadius) {
+ return false;
+ }
+
+ var me = this,
+ wasTable = this.frameSize && this.frameSize.table,
+ oldFrameTL = this.frameTL,
+ oldFrameBL = this.frameBL,
+ oldFrameML = this.frameML,
+ oldFrameMC = this.frameMC,
+ newMCClassName;
+
+ this.initFrame();
+
+ if (oldFrameMC) {
+ if (me.frame) {
+ // Reapply render selectors
+ delete me.frameTL;
+ delete me.frameTC;
+ delete me.frameTR;
+ delete me.frameML;
+ delete me.frameMC;
+ delete me.frameMR;
+ delete me.frameBL;
+ delete me.frameBC;
+ delete me.frameBR;
+ this.applyRenderSelectors();
+
+ // Store the class names set on the new mc
+ newMCClassName = this.frameMC.dom.className;
+
+ // Replace the new mc with the old mc
+ oldFrameMC.insertAfter(this.frameMC);
+ this.frameMC.remove();
+
+ // Restore the reference to the old frame mc as the framebody
+ this.frameBody = this.frameMC = oldFrameMC;
+
+ // Apply the new mc classes to the old mc element
+ oldFrameMC.dom.className = newMCClassName;
+
+ // Remove the old framing
+ if (wasTable) {
+ me.el.query('> table')[1].remove();
+ }
+ else {
+ if (oldFrameTL) {
+ oldFrameTL.remove();
+ }
+ if (oldFrameBL) {
+ oldFrameBL.remove();
+ }
+ oldFrameML.remove();
+ }
+ }
+ else {
+ // We were framed but not anymore. Move all content from the old frame to the body
+
+ }
+ }
+ else if (me.frame) {
+ this.applyRenderSelectors();
+ }
+ },
+
+ getFrameInfo: function() {
+ if (Ext.supports.CSS3BorderRadius) {
+ return false;
+ }
+
+ var me = this,
+ left = me.el.getStyle('background-position-x'),
+ top = me.el.getStyle('background-position-y'),
+ info, frameInfo = false, max;
+
+ // Some browsers dont support background-position-x and y, so for those
+ // browsers let's split background-position into two parts.
+ if (!left && !top) {
+ info = me.el.getStyle('background-position').split(' ');
+ left = info[0];
+ top = info[1];
+ }
+
+ // We actually pass a string in the form of '[type][tl][tr]px [type][br][bl]px' as
+ // the background position of this.el from the css to indicate to IE that this component needs
+ // framing. We parse it here and change the markup accordingly.
+ if (parseInt(left, 10) >= 1000000 && parseInt(top, 10) >= 1000000) {
+ max = Math.max;
+
+ frameInfo = {
+ // Table markup starts with 110, div markup with 100.
+ table: left.substr(0, 3) == '110',
+
+ // Determine if we are dealing with a horizontal or vertical component
+ vertical: top.substr(0, 3) == '110',
+
+ // Get and parse the different border radius sizes
+ top: max(left.substr(3, 2), left.substr(5, 2)),
+ right: max(left.substr(5, 2), top.substr(3, 2)),
+ bottom: max(top.substr(3, 2), top.substr(5, 2)),
+ left: max(top.substr(5, 2), left.substr(3, 2))
+ };
+
+ frameInfo.width = max(frameInfo.top, frameInfo.right, frameInfo.bottom, frameInfo.left);
+
+ // Just to be sure we set the background image of the el to none.
+ me.el.setStyle('background-image', 'none');
+ }
+
+ // This happens when you set frame: true explicitly without using the x-frame mixin in sass.
+ // This way IE can't figure out what sizes to use and thus framing can't work.
+ if (me.frame === true && !frameInfo) {
+ Ext.Error.raise("You have set frame: true explicity on this component while it doesn't have any " +
+ "framing defined in the CSS template. In this case IE can't figure out what sizes " +
+ "to use and thus framing on this component will be disabled.");
+ }
+
+ me.frame = me.frame || !!frameInfo;
+ me.frameSize = frameInfo || false;
+
+ return frameInfo;
+ },
+
+ getFramePositions: function(frameInfo) {
+ var me = this,
+ frameWidth = frameInfo.width,
+ dock = me.dock,
+ positions, tc, bc, ml, mr;
+
+ if (frameInfo.vertical) {
+ tc = '0 -' + (frameWidth * 0) + 'px';
+ bc = '0 -' + (frameWidth * 1) + 'px';
+
+ if (dock && dock == "right") {
+ tc = 'right -' + (frameWidth * 0) + 'px';
+ bc = 'right -' + (frameWidth * 1) + 'px';
+ }
+
+ positions = {
+ tl: '0 -' + (frameWidth * 0) + 'px',
+ tr: '0 -' + (frameWidth * 1) + 'px',
+ bl: '0 -' + (frameWidth * 2) + 'px',
+ br: '0 -' + (frameWidth * 3) + 'px',
+
+ ml: '-' + (frameWidth * 1) + 'px 0',
+ mr: 'right 0',
+
+ tc: tc,
+ bc: bc
+ };
+ } else {
+ ml = '-' + (frameWidth * 0) + 'px 0';
+ mr = 'right 0';
+
+ if (dock && dock == "bottom") {
+ ml = 'left bottom';
+ mr = 'right bottom';
+ }
+
+ positions = {
+ tl: '0 -' + (frameWidth * 2) + 'px',
+ tr: 'right -' + (frameWidth * 3) + 'px',
+ bl: '0 -' + (frameWidth * 4) + 'px',
+ br: 'right -' + (frameWidth * 5) + 'px',
+
+ ml: ml,
+ mr: mr,
+
+ tc: '0 -' + (frameWidth * 0) + 'px',
+ bc: '0 -' + (frameWidth * 1) + 'px'
+ };
+ }
+
+ return positions;
+ },
+
+ /**
+ * @private
+ */
+ getFrameTpl : function(table) {
+ return table ? this.getTpl('frameTableTpl') : this.getTpl('frameTpl');
+ },
+
+ /**
+ * <p>Creates an array of class names from the configurations to add to this Component's <code>el</code> on render.</p>
+ * <p>Private, but (possibly) used by ComponentQuery for selection by class name if Component is not rendered.</p>
+ * @return {Array} An array of class names with which the Component's element will be rendered.
+ * @private
+ */
+ initCls: function() {
+ var me = this,
+ cls = [];
+
+ cls.push(me.baseCls);
+
+ if (Ext.isDefined(me.cmpCls)) {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
+ }
+ me.componentCls = me.cmpCls;
+ delete me.cmpCls;
+ }
+
+ if (me.componentCls) {
+ cls.push(me.componentCls);
+ } else {
+ me.componentCls = me.baseCls;
+ }
+ if (me.cls) {
+ cls.push(me.cls);
+ delete me.cls;
+ }
+
+ return cls.concat(me.additionalCls);
+ },
+
+ /**
+ * Sets the UI for the component. This will remove any existing UIs on the component. It will also
+ * loop through any uiCls set on the component and rename them so they include the new UI
+ * @param {String} ui The new UI for the component
+ */
+ setUI: function(ui) {
+ var me = this,
+ oldUICls = Ext.Array.clone(me.uiCls),
+ newUICls = [],
+ cls,
+ i;
+
+ //loop through all exisiting uiCls and update the ui in them
+ for (i = 0; i < oldUICls.length; i++) {
+ cls = oldUICls[i];
+
+ me.removeClsWithUI(cls);
+ newUICls.push(cls);
+ }
+
+ //remove the UI from the element
+ me.removeUIFromElement();
+
+ //set the UI
+ me.ui = ui;
+
+ //add the new UI to the elemend
+ me.addUIToElement();
+
+ //loop through all exisiting uiCls and update the ui in them
+ for (i = 0; i < newUICls.length; i++) {
+ cls = newUICls[i];
+
+ me.addClsWithUI(cls);
+ }
+ },
+
+ /**
+ * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds
+ * to all elements of this component.
+ * @param {String/Array} cls A string or an array of strings to add to the uiCls
+ */
+ addClsWithUI: function(cls) {
+ var me = this,
+ i;
+
+ if (!Ext.isArray(cls)) {
+ cls = [cls];
+ }
+
+ for (i = 0; i < cls.length; i++) {
+ if (cls[i] && !me.hasUICls(cls[i])) {
+ me.uiCls = Ext.Array.clone(me.uiCls);
+ me.uiCls.push(cls[i]);
+ me.addUIClsToElement(cls[i]);
+ }
+ }
+ },
+
+ /**
+ * Removes a cls to the uiCls array, which will also call {@link #removeUIClsToElement} and removes
+ * it from all elements of this component.
+ * @param {String/Array} cls A string or an array of strings to remove to the uiCls
+ */
+ removeClsWithUI: function(cls) {
+ var me = this,
+ i;
+
+ if (!Ext.isArray(cls)) {
+ cls = [cls];
+ }
+
+ for (i = 0; i < cls.length; i++) {
+ if (cls[i] && me.hasUICls(cls[i])) {
+ me.uiCls = Ext.Array.remove(me.uiCls, cls[i]);
+ me.removeUIClsFromElement(cls[i]);
+ }
+ }
+ },
+
+ /**
+ * Checks if there is currently a specified uiCls
+ * @param {String} cls The cls to check
+ */
+ hasUICls: function(cls) {
+ var me = this,
+ uiCls = me.uiCls || [];
+
+ return Ext.Array.contains(uiCls, cls);
+ },
+
+ /**
+ * Method which adds a specified UI + uiCls to the components element.
+ * Can be overridden to remove the UI from more than just the components element.
+ * @param {String} ui The UI to remove from the element
+ * @private
+ */
+ addUIClsToElement: function(cls, force) {
+ var me = this;
+
+ me.addCls(Ext.baseCSSPrefix + cls);
+ me.addCls(me.baseCls + '-' + cls);
+ me.addCls(me.baseCls + '-' + me.ui + '-' + cls);
+
+ if (!force && me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
+ // define each element of the frame
+ var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
+ i, el;
+
+ // loop through each of them, and if they are defined add the ui
+ for (i = 0; i < els.length; i++) {
+ el = me['frame' + els[i].toUpperCase()];
+
+ if (el && el.dom) {
+ el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
+ el.addCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method which removes a specified UI + uiCls from the components element.
+ * The cls which is added to the element will be: `this.baseCls + '-' + ui`
+ * @param {String} ui The UI to add to the element
+ * @private
+ */
+ removeUIClsFromElement: function(cls, force) {
+ var me = this;
+
+ me.removeCls(Ext.baseCSSPrefix + cls);
+ me.removeCls(me.baseCls + '-' + cls);
+ me.removeCls(me.baseCls + '-' + me.ui + '-' + cls);
+
+ if (!force &&me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
+ // define each element of the frame
+ var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
+ i, el;
+
+ // loop through each of them, and if they are defined add the ui
+ for (i = 0; i < els.length; i++) {
+ el = me['frame' + els[i].toUpperCase()];
+ if (el && el.dom) {
+ el.removeCls(me.baseCls + '-' + me.ui + '-' + cls + '-' + els[i]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method which adds a specified UI to the components element.
+ * @private
+ */
+ addUIToElement: function(force) {
+ var me = this;
+
+ me.addCls(me.baseCls + '-' + me.ui);
+
+ if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
+ // define each element of the frame
+ var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
+ i, el;
+
+ // loop through each of them, and if they are defined add the ui
+ for (i = 0; i < els.length; i++) {
+ el = me['frame' + els[i].toUpperCase()];
+
+ if (el) {
+ el.addCls(me.baseCls + '-' + me.ui + '-' + els[i]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Method which removes a specified UI from the components element.
+ * @private
+ */
+ removeUIFromElement: function() {
+ var me = this;
+
+ me.removeCls(me.baseCls + '-' + me.ui);
+
+ if (me.rendered && me.frame && !Ext.supports.CSS3BorderRadius) {
+ // define each element of the frame
+ var els = ['tl', 'tc', 'tr', 'ml', 'mc', 'mr', 'bl', 'bc', 'br'],
+ i, el;
+
+ // loop through each of them, and if they are defined add the ui
+ for (i = 0; i < els.length; i++) {
+ el = me['frame' + els[i].toUpperCase()];
+ if (el) {
+ el.removeCls(me.baseCls + '-' + me.ui + '-' + els[i]);
+ }
+ }
+ }
+ },
+
+ getElConfig : function() {
+ var result = this.autoEl || {tag: 'div'};
+ result.id = this.id;
+ return result;
+ },
+
+ /**
+ * This function takes the position argument passed to onRender and returns a
+ * DOM element that you can use in the insertBefore.
+ * @param {String/Number/Element/HTMLElement} position Index, element id or element you want
+ * to put this component before.
+ * @return {HTMLElement} DOM element that you can use in the insertBefore
+ */
+ getInsertPosition: function(position) {
+ // Convert the position to an element to insert before
+ if (position !== undefined) {
+ if (Ext.isNumber(position)) {
+ position = this.container.dom.childNodes[position];
+ }
+ else {
+ position = Ext.getDom(position);
+ }
+ }
+
+ return position;
+ },
+
+ /**
+ * Adds ctCls to container.
+ * @return {Ext.core.Element} The initialized container
+ * @private
+ */
+ initContainer: function(container) {
+ var me = this;
+
+ // If you render a component specifying the el, we get the container
+ // of the el, and make sure we dont move the el around in the dom
+ // during the render
+ if (!container && me.el) {
+ container = me.el.dom.parentNode;
+ me.allowDomMove = false;
+ }
+
+ me.container = Ext.get(container);
+
+ if (me.ctCls) {
+ me.container.addCls(me.ctCls);
+ }
+
+ return me.container;
+ },
+
+ /**
+ * Initialized the renderData to be used when rendering the renderTpl.
+ * @return {Object} Object with keys and values that are going to be applied to the renderTpl
+ * @private
+ */
+ initRenderData: function() {
+ var me = this;
+
+ return Ext.applyIf(me.renderData, {
+ ui: me.ui,
+ uiCls: me.uiCls,
+ baseCls: me.baseCls,
+ componentCls: me.componentCls,
+ frame: me.frame
+ });
+ },
+
+ /**
+ * @private
+ */
+ getTpl: function(name) {
+ var prototype = this.self.prototype,
+ ownerPrototype;
+
+ if (this.hasOwnProperty(name)) {
+ if (!(this[name] instanceof Ext.XTemplate)) {
+ this[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', this[name]);
+ }
+
+ return this[name];
+ }
+
+ if (!(prototype[name] instanceof Ext.XTemplate)) {
+ ownerPrototype = prototype;
+
+ do {
+ if (ownerPrototype.hasOwnProperty(name)) {
+ ownerPrototype[name] = Ext.ClassManager.dynInstantiate('Ext.XTemplate', ownerPrototype[name]);
+ break;
+ }
+
+ ownerPrototype = ownerPrototype.superclass;
+ } while (ownerPrototype);
+ }
+
+ return prototype[name];
+ },
+
+ /**
+ * Initializes the renderTpl.
+ * @return {Ext.XTemplate} The renderTpl XTemplate instance.
+ * @private
+ */
+ initRenderTpl: function() {
+ return this.getTpl('renderTpl');
+ },
+
+ /**
+ * Function description
+ * @return {String} A CSS style string with style, padding, margin and border.
+ * @private
+ */
+ initStyles: function() {
+ var style = {},
+ me = this,
+ Element = Ext.core.Element;
+
+ if (Ext.isString(me.style)) {
+ style = Element.parseStyles(me.style);
+ } else {
+ style = Ext.apply({}, me.style);
+ }
+
+ // Convert the padding, margin and border properties from a space seperated string
+ // into a proper style string
+ if (me.padding !== undefined) {
+ style.padding = Element.unitizeBox((me.padding === true) ? 5 : me.padding);
+ }
+
+ if (me.margin !== undefined) {
+ style.margin = Element.unitizeBox((me.margin === true) ? 5 : me.margin);
+ }
+
+ delete me.style;
+ return style;
+ },
+
+ /**
+ * Initializes this components contents. It checks for the properties
+ * html, contentEl and tpl/data.
+ * @private
+ */
+ initContent: function() {
+ var me = this,
+ target = me.getTargetEl(),
+ contentEl,
+ pre;
+
+ if (me.html) {
+ target.update(Ext.core.DomHelper.markup(me.html));
+ delete me.html;
+ }
+
+ if (me.contentEl) {
+ contentEl = Ext.get(me.contentEl);
+ pre = Ext.baseCSSPrefix;
+ contentEl.removeCls([pre + 'hidden', pre + 'hide-display', pre + 'hide-offsets', pre + 'hide-nosize']);
+ target.appendChild(contentEl.dom);
+ }
+
+ if (me.tpl) {
+ // Make sure this.tpl is an instantiated XTemplate
+ if (!me.tpl.isTemplate) {
+ me.tpl = Ext.create('Ext.XTemplate', me.tpl);
+ }
+
+ if (me.data) {
+ me.tpl[me.tplWriteMode](target, me.data);
+ delete me.data;
+ }
+ }
+ },
+
+ // @private
+ initEvents : function() {
+ var me = this,
+ afterRenderEvents = me.afterRenderEvents,
+ property, listeners;
+ if (afterRenderEvents) {
+ for (property in afterRenderEvents) {
+ if (afterRenderEvents.hasOwnProperty(property)) {
+ listeners = afterRenderEvents[property];
+ if (me[property] && me[property].on) {
+ me.mon(me[property], listeners);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Sets references to elements inside the component. E.g body -> x-panel-body
+ * @private
+ */
+ applyRenderSelectors: function() {
+ var selectors = this.renderSelectors || {},
+ el = this.el.dom,
+ selector;
+
+ for (selector in selectors) {
+ if (selectors.hasOwnProperty(selector) && selectors[selector]) {
+ this[selector] = Ext.get(Ext.DomQuery.selectNode(selectors[selector], el));
+ }
+ }
+ },
+
+ /**
+ * Tests whether this Component matches the selector string.
+ * @param {String} selector The selector string to test against.
+ * @return {Boolean} True if this Component matches the selector.
+ */
+ is: function(selector) {
+ return Ext.ComponentQuery.is(this, selector);
+ },
+
+ /**
+ * <p>Walks up the <code>ownerCt</code> axis looking for an ancestor Container which matches
+ * the passed simple selector.</p>
+ * <p>Example:<pre><code>
+var owningTabPanel = grid.up('tabpanel');
+</code></pre>
+ * @param {String} selector Optional. The simple selector to test.
+ * @return {Container} The matching ancestor Container (or <code>undefined</code> if no match was found).
+ */
+ up: function(selector) {
+ var result = this.ownerCt;
+ if (selector) {
+ for (; result; result = result.ownerCt) {
+ if (Ext.ComponentQuery.is(result, selector)) {
+ return result;
+ }
+ }
+ }
+ return result;
+ },
+
+ /**
+ * <p>Returns the next sibling of this Component.</p>
+ * <p>Optionally selects the next sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
+ * <p>May also be refered to as <code><b>next()</b></code></p>
+ * <p>Note that this is limited to siblings, and if no siblings of the item match, <code>null</code> is returned. Contrast with {@link #nextNode}</p>
+ * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following items.
+ * @returns The next sibling (or the next sibling which matches the selector). Returns null if there is no matching sibling.
+ */
+ nextSibling: function(selector) {
+ var o = this.ownerCt, it, last, idx, c;
+ if (o) {
+ it = o.items;
+ idx = it.indexOf(this) + 1;
+ if (idx) {
+ if (selector) {
+ for (last = it.getCount(); idx < last; idx++) {
+ if ((c = it.getAt(idx)).is(selector)) {
+ return c;
+ }
+ }
+ } else {
+ if (idx < it.getCount()) {
+ return it.getAt(idx);
+ }
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * <p>Returns the previous sibling of this Component.</p>
+ * <p>Optionally selects the previous sibling which matches the passed {@link Ext.ComponentQuery ComponentQuery} selector.</p>
+ * <p>May also be refered to as <code><b>prev()</b></code></p>
+ * <p>Note that this is limited to siblings, and if no siblings of the item match, <code>null</code> is returned. Contrast with {@link #previousNode}</p>
+ * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding items.
+ * @returns The previous sibling (or the previous sibling which matches the selector). Returns null if there is no matching sibling.
+ */
+ previousSibling: function(selector) {
+ var o = this.ownerCt, it, idx, c;
+ if (o) {
+ it = o.items;
+ idx = it.indexOf(this);
+ if (idx != -1) {
+ if (selector) {
+ for (--idx; idx >= 0; idx--) {
+ if ((c = it.getAt(idx)).is(selector)) {
+ return c;
+ }
+ }
+ } else {
+ if (idx) {
+ return it.getAt(--idx);
+ }
+ }
+ }
+ }
+ return null;
+ },
+
+ /**
+ * <p>Returns the previous node in the Component tree in tree traversal order.</p>
+ * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
+ * walk the tree in reverse order to attempt to find a match. Contrast with {@link #previousSibling}.</p>
+ * @param {String} selector Optional. A {@link Ext.ComponentQuery ComponentQuery} selector to filter the preceding nodes.
+ * @returns The previous node (or the previous node which matches the selector). Returns null if there is no matching node.
+ */
+ previousNode: function(selector, includeSelf) {
+ var node = this,
+ result,
+ it, len, i;
+
+ // If asked to include self, test me
+ if (includeSelf && node.is(selector)) {
+ return node;
+ }
+
+ result = this.prev(selector);
+ if (result) {
+ return result;
+ }
+
+ if (node.ownerCt) {
+ for (it = node.ownerCt.items.items, i = Ext.Array.indexOf(it, node) - 1; i > -1; i--) {
+ if (it[i].query) {
+ result = it[i].query(selector);
+ result = result[result.length - 1];
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return node.ownerCt.previousNode(selector, true);
+ }
+ },
+
+ /**
+ * <p>Returns the next node in the Component tree in tree traversal order.</p>
+ * <p>Note that this is not limited to siblings, and if invoked upon a node with no matching siblings, will
+ * walk the tree to attempt to find a match. Contrast with {@link #pnextSibling}.</p>
+ * @param {String} selector Optional A {@link Ext.ComponentQuery ComponentQuery} selector to filter the following nodes.
+ * @returns The next node (or the next node which matches the selector). Returns null if there is no matching node.
+ */
+ nextNode: function(selector, includeSelf) {
+ var node = this,
+ result,
+ it, len, i;
+
+ // If asked to include self, test me
+ if (includeSelf && node.is(selector)) {
+ return node;
+ }
+
+ result = this.next(selector);
+ if (result) {
+ return result;
+ }
+
+ if (node.ownerCt) {
+ for (it = node.ownerCt.items, i = it.indexOf(node) + 1, it = it.items, len = it.length; i < len; i++) {
+ if (it[i].down) {
+ result = it[i].down(selector);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ return node.ownerCt.nextNode(selector);
+ }
+ },
+
+ /**
+ * Retrieves the id of this component.
+ * Will autogenerate an id if one has not already been set.
+ */
+ getId : function() {
+ return this.id || (this.id = 'ext-comp-' + (this.getAutoId()));
+ },
+
+ getItemId : function() {
+ return this.itemId || this.id;
+ },
+
+ /**
+ * Retrieves the top level element representing this component.
+ */
+ getEl : function() {
+ return this.el;
+ },
+
+ /**
+ * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
+ * @private
+ */
+ getTargetEl: function() {
+ return this.frameBody || this.el;
+ },
+
+ /**
+ * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
+ * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
+ * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
+ * to participate in determination of inherited xtypes.</b></p>
+ * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var t = new Ext.form.field.Text();
+var isText = t.isXType('textfield'); // true
+var isBoxSubclass = t.isXType('field'); // true, descended from Ext.form.field.Base
+var isBoxInstance = t.isXType('field', true); // false, not a direct Ext.form.field.Base instance
+</code></pre>
+ * @param {String} xtype The xtype to check for this Component
+ * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
+ * the default), or true to check whether this Component is directly of the specified xtype.
+ * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
+ */
+ isXType: function(xtype, shallow) {
+ //assume a string by default
+ if (Ext.isFunction(xtype)) {
+ xtype = xtype.xtype;
+ //handle being passed the class, e.g. Ext.Component
+ } else if (Ext.isObject(xtype)) {
+ xtype = xtype.statics().xtype;
+ //handle being passed an instance
+ }
+
+ return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1: this.self.xtype == xtype;
+ },
+
+ /**
+ * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
+ * available xtypes, see the {@link Ext.Component} header.</p>
+ * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
+ * to participate in determination of inherited xtypes.</b></p>
+ * <p>Example usage:</p>
+ * <pre><code>
+var t = new Ext.form.field.Text();
+alert(t.getXTypes()); // alerts 'component/field/textfield'
+</code></pre>
+ * @return {String} The xtype hierarchy string
+ */
+ getXTypes: function() {
+ var self = this.self,
+ xtypes = [],
+ parentPrototype = this,
+ xtype;
+
+ if (!self.xtypes) {
+ while (parentPrototype && Ext.getClass(parentPrototype)) {
+ xtype = Ext.getClass(parentPrototype).xtype;
+
+ if (xtype !== undefined) {
+ xtypes.unshift(xtype);
+ }
+
+ parentPrototype = parentPrototype.superclass;
+ }
+
+ self.xtypeChain = xtypes;
+ self.xtypes = xtypes.join('/');
+ }
+
+ return self.xtypes;
+ },
+
+ /**
+ * Update the content area of a component.
+ * @param {Mixed} htmlOrData
+ * If this component has been configured with a template via the tpl config
+ * then it will use this argument as data to populate the template.
+ * If this component was not configured with a template, the components
+ * content area will be updated via Ext.core.Element update
+ * @param {Boolean} loadScripts
+ * (optional) Only legitimate when using the html configuration. Defaults to false
+ * @param {Function} callback
+ * (optional) Only legitimate when using the html configuration. Callback to execute when scripts have finished loading
+ */
+ update : function(htmlOrData, loadScripts, cb) {
+ var me = this;
+
+ if (me.tpl && !Ext.isString(htmlOrData)) {
+ me.data = htmlOrData;
+ if (me.rendered) {
+ me.tpl[me.tplWriteMode](me.getTargetEl(), htmlOrData || {});
+ }
+ } else {
+ me.html = Ext.isObject(htmlOrData) ? Ext.core.DomHelper.markup(htmlOrData) : htmlOrData;
+ if (me.rendered) {
+ me.getTargetEl().update(me.html, loadScripts, cb);
+ }
+ }
+
+ if (me.rendered) {
+ me.doComponentLayout();
+ }
+ },
+
+ /**
+ * Convenience function to hide or show this component by boolean.
+ * @param {Boolean} visible True to show, false to hide
+ * @return {Ext.Component} this
+ */
+ setVisible : function(visible) {
+ return this[visible ? 'show': 'hide']();
+ },
+
+ /**
+ * Returns true if this component is visible.
+ * @param {Boolean} deep. <p>Optional. Pass <code>true</code> to interrogate the visibility status of all
+ * parent Containers to determine whether this Component is truly visible to the user.</p>
+ * <p>Generally, to determine whether a Component is hidden, the no argument form is needed. For example
+ * when creating dynamically laid out UIs in a hidden Container before showing them.</p>
+ * @return {Boolean} True if this component is visible, false otherwise.
+ */
+ isVisible: function(deep) {
+ var me = this,
+ child = me,
+ visible = !me.hidden,
+ ancestor = me.ownerCt;
+
+ // Clear hiddenOwnerCt property
+ me.hiddenAncestor = false;
+ if (me.destroyed) {
+ return false;
+ }
+
+ if (deep && visible && me.rendered && ancestor) {
+ while (ancestor) {
+ // If any ancestor is hidden, then this is hidden.
+ // If an ancestor Panel (only Panels have a collapse method) is collapsed,
+ // then its layoutTarget (body) is hidden, so this is hidden unless its within a
+ // docked item; they are still visible when collapsed (Unless they themseves are hidden)
+ if (ancestor.hidden || (ancestor.collapsed &&
+ !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
+ // Store hiddenOwnerCt property if needed
+ me.hiddenAncestor = ancestor;
+ visible = false;
+ break;
+ }
+ child = ancestor;
+ ancestor = ancestor.ownerCt;
+ }
+ }
+ return visible;
+ },
+
+ /**
+ * Enable the component
+ * @param {Boolean} silent
+ * Passing false will supress the 'enable' event from being fired.
+ */
+ enable: function(silent) {
+ var me = this;
+
+ if (me.rendered) {
+ me.el.removeCls(me.disabledCls);
+ me.el.dom.disabled = false;
+ me.onEnable();
+ }
+
+ me.disabled = false;
+
+ if (silent !== true) {
+ me.fireEvent('enable', me);
+ }
+
+ return me;
+ },
+
+ /**
+ * Disable the component.
+ * @param {Boolean} silent
+ * Passing true, will supress the 'disable' event from being fired.
+ */
+ disable: function(silent) {
+ var me = this;
+
+ if (me.rendered) {
+ me.el.addCls(me.disabledCls);
+ me.el.dom.disabled = true;
+ me.onDisable();
+ }
+
+ me.disabled = true;
+
+ if (silent !== true) {
+ me.fireEvent('disable', me);
+ }
+
+ return me;
+ },
+
+ // @private
+ onEnable: function() {
+ if (this.maskOnDisable) {
+ this.el.unmask();
+ }
+ },
+
+ // @private
+ onDisable : function() {
+ if (this.maskOnDisable) {
+ this.el.mask();
+ }
+ },
+
+ /**
+ * Method to determine whether this Component is currently disabled.
+ * @return {Boolean} the disabled state of this Component.
+ */
+ isDisabled : function() {
+ return this.disabled;
+ },
+
+ /**
+ * Enable or disable the component.
+ * @param {Boolean} disabled
+ */
+ setDisabled : function(disabled) {
+ return this[disabled ? 'disable': 'enable']();
+ },
+
+ /**
+ * Method to determine whether this Component is currently set to hidden.
+ * @return {Boolean} the hidden state of this Component.
+ */
+ isHidden : function() {
+ return this.hidden;
+ },
+
+ /**
+ * Adds a CSS class to the top level element representing this component.
+ * @param {String} cls The CSS class name to add
+ * @return {Ext.Component} Returns the Component to allow method chaining.
+ */
+ addCls : function(className) {
+ var me = this;
+ if (!className) {
+ return me;
+ }
+ if (!Ext.isArray(className)){
+ className = className.replace(me.trimRe, '').split(me.spacesRe);
+ }
+ if (me.rendered) {
+ me.el.addCls(className);
+ }
+ else {
+ me.additionalCls = Ext.Array.unique(me.additionalCls.concat(className));
+ }
+ return me;
+ },
+
+ /**
+ * @deprecated 4.0 Replaced by {link:#addCls}
+ * Adds a CSS class to the top level element representing this component.
+ * @param {String} cls The CSS class name to add
+ * @return {Ext.Component} Returns the Component to allow method chaining.
+ */
+ addClass : function() {
+ return this.addCls.apply(this, arguments);
+ },
+
+ /**
+ * Removes a CSS class from the top level element representing this component.
+ * @returns {Ext.Component} Returns the Component to allow method chaining.
+ */
+ removeCls : function(className) {
+ var me = this;
+
+ if (!className) {
+ return me;
+ }
+ if (!Ext.isArray(className)){
+ className = className.replace(me.trimRe, '').split(me.spacesRe);
+ }
+ if (me.rendered) {
+ me.el.removeCls(className);
+ }
+ else if (me.additionalCls.length) {
+ Ext.each(className, function(cls) {
+ Ext.Array.remove(me.additionalCls, cls);
+ });
+ }
+ return me;
+ },
+
+ removeClass : function() {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.Component: removeClass has been deprecated. Please use removeCls.');
+ }
+ return this.removeCls.apply(this, arguments);
+ },
+
+ addOverCls: function() {
+ var me = this;
+ if (!me.disabled) {
+ me.el.addCls(me.overCls);
+ }
+ },
+
+ removeOverCls: function() {
+ this.el.removeCls(this.overCls);
+ },
+
+ addListener : function(element, listeners, scope, options) {
+ var me = this,
+ fn,
+ option;
+
+ if (Ext.isString(element) && (Ext.isObject(listeners) || options && options.element)) {
+ if (options.element) {
+ fn = listeners;
+
+ listeners = {};
+ listeners[element] = fn;
+ element = options.element;
+ if (scope) {
+ listeners.scope = scope;
+ }
+
+ for (option in options) {
+ if (options.hasOwnProperty(option)) {
+ if (me.eventOptionsRe.test(option)) {
+ listeners[option] = options[option];
+ }
+ }
+ }
+ }
+
+ // At this point we have a variable called element,
+ // and a listeners object that can be passed to on
+ if (me[element] && me[element].on) {
+ me.mon(me[element], listeners);
+ } else {
+ me.afterRenderEvents = me.afterRenderEvents || {};
+ me.afterRenderEvents[element] = listeners;
+ }
+ }
+
+ return me.mixins.observable.addListener.apply(me, arguments);
+ },
+
+ // @TODO: implement removelistener to support the dom event stuff
+
+ /**
+ * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
+ * @return {Ext.container.Container} the Container which owns this Component.
+ */
+ getBubbleTarget : function() {
+ return this.ownerCt;
+ },
+
+ /**
+ * Method to determine whether this Component is floating.
+ * @return {Boolean} the floating state of this component.
+ */
+ isFloating : function() {
+ return this.floating;
+ },
+
+ /**
+ * Method to determine whether this Component is draggable.
+ * @return {Boolean} the draggable state of this component.
+ */
+ isDraggable : function() {
+ return !!this.draggable;
+ },
+
+ /**
+ * Method to determine whether this Component is droppable.
+ * @return {Boolean} the droppable state of this component.
+ */
+ isDroppable : function() {
+ return !!this.droppable;
+ },
+
+ /**
+ * @private
+ * Method to manage awareness of when components are added to their
+ * respective Container, firing an added event.
+ * References are established at add time rather than at render time.
+ * @param {Ext.container.Container} container Container which holds the component
+ * @param {number} pos Position at which the component was added
+ */
+ onAdded : function(container, pos) {
+ this.ownerCt = container;
+ this.fireEvent('added', this, container, pos);
+ },
+
+ /**
+ * @private
+ * Method to manage awareness of when components are removed from their
+ * respective Container, firing an removed event. References are properly
+ * cleaned up after removing a component from its owning container.
+ */
+ onRemoved : function() {
+ var me = this;
+
+ me.fireEvent('removed', me, me.ownerCt);
+ delete me.ownerCt;
+ },
+
+ // @private
+ beforeDestroy : Ext.emptyFn,
+ // @private
+ // @private
+ onResize : Ext.emptyFn,
+
+ /**
+ * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
+ * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
+ * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style.</li>
+ * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
+ * <li><code>undefined</code> to leave the width unchanged.</li>
+ * </ul></div>
+ * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
+ * This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
+ * <li><code>undefined</code> to leave the height unchanged.</li>
+ * </ul></div>
+ * @return {Ext.Component} this
+ */
+ setSize : function(width, height) {
+ var me = this,
+ layoutCollection;
+
+ // support for standard size objects
+ if (Ext.isObject(width)) {
+ height = width.height;
+ width = width.width;
+ }
+
+ // Constrain within configured maxima
+ if (Ext.isNumber(width)) {
+ width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
+ }
+ if (Ext.isNumber(height)) {
+ height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
+ }
+
+ if (!me.rendered || !me.isVisible()) {
+ // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
+ if (me.hiddenAncestor) {
+ layoutCollection = me.hiddenAncestor.layoutOnShow;
+ layoutCollection.remove(me);
+ layoutCollection.add(me);
+ }
+ me.needsLayout = {
+ width: width,
+ height: height,
+ isSetSize: true
+ };
+ if (!me.rendered) {
+ me.width = (width !== undefined) ? width : me.width;
+ me.height = (height !== undefined) ? height : me.height;
+ }
+ return me;
+ }
+ me.doComponentLayout(width, height, true);
+
+ return me;
+ },
+
+ setCalculatedSize : function(width, height, ownerCt) {
+ var me = this,
+ layoutCollection;
+
+ // support for standard size objects
+ if (Ext.isObject(width)) {
+ ownerCt = width.ownerCt;
+ height = width.height;
+ width = width.width;
+ }
+
+ // Constrain within configured maxima
+ if (Ext.isNumber(width)) {
+ width = Ext.Number.constrain(width, me.minWidth, me.maxWidth);
+ }
+ if (Ext.isNumber(height)) {
+ height = Ext.Number.constrain(height, me.minHeight, me.maxHeight);
+ }
+
+ if (!me.rendered || !me.isVisible()) {
+ // If an ownerCt is hidden, add my reference onto the layoutOnShow stack. Set the needsLayout flag.
+ if (me.hiddenAncestor) {
+ layoutCollection = me.hiddenAncestor.layoutOnShow;
+ layoutCollection.remove(me);
+ layoutCollection.add(me);
+ }
+ me.needsLayout = {
+ width: width,
+ height: height,
+ isSetSize: false,
+ ownerCt: ownerCt
+ };
+ return me;
+ }
+ me.doComponentLayout(width, height, false, ownerCt);
+
+ return me;
+ },
+
+ /**
+ * This method needs to be called whenever you change something on this component that requires the Component's
+ * layout to be recalculated.
+ * @return {Ext.container.Container} this
+ */
+ doComponentLayout : function(width, height, isSetSize, ownerCt) {
+ var me = this,
+ componentLayout = me.getComponentLayout();
+
+ // collapsed state is not relevant here, so no testing done.
+ // Only Panels have a collapse method, and that just sets the width/height such that only
+ // a single docked Header parallel to the collapseTo side are visible, and the Panel body is hidden.
+ if (me.rendered && componentLayout) {
+ width = (width !== undefined) ? width : me.width;
+ height = (height !== undefined) ? height : me.height;
+ if (isSetSize) {
+ me.width = width;
+ me.height = height;
+ }
+
+ componentLayout.layout(width, height, isSetSize, ownerCt);
+ }
+ return me;
+ },
+
+ // @private
+ setComponentLayout : function(layout) {
+ var currentLayout = this.componentLayout;
+ if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
+ currentLayout.setOwner(null);
+ }
+ this.componentLayout = layout;
+ layout.setOwner(this);
+ },
+
+ getComponentLayout : function() {
+ var me = this;
+
+ if (!me.componentLayout || !me.componentLayout.isLayout) {
+ me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
+ }
+ return me.componentLayout;
+ },
+
+ /**
+ * @param {Number} adjWidth The box-adjusted width that was set
+ * @param {Number} adjHeight The box-adjusted height that was set
+ * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
+ * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
+ */
+ afterComponentLayout: function(width, height, isSetSize, layoutOwner) {
+ this.fireEvent('resize', this, width, height);
+ },
+
+ /**
+ * Occurs before componentLayout is run. Returning false from this method will prevent the componentLayout
+ * from being executed.
+ * @param {Number} adjWidth The box-adjusted width that was set
+ * @param {Number} adjHeight The box-adjusted height that was set
+ * @param {Boolean} isSetSize Whether or not the height/width are stored on the component permanently
+ * @param {Ext.Component} layoutOwner Component which sent the layout. Only used when isSetSize is false.
+ */
+ beforeComponentLayout: function(width, height, isSetSize, layoutOwner) {
+ return true;
+ },
+
+ /**
+ * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}.
+ * This method fires the {@link #move} event.
+ * @param {Number} left The new left
+ * @param {Number} top The new top
+ * @return {Ext.Component} this
+ */
+ setPosition : function(x, y) {
+ var me = this;
+
+ if (Ext.isObject(x)) {
+ y = x.y;
+ x = x.x;
+ }
+
+ if (!me.rendered) {
+ return me;
+ }
+
+ if (x !== undefined || y !== undefined) {
+ me.el.setBox(x, y);
+ me.onPosition(x, y);
+ me.fireEvent('move', me, x, y);
+ }
+ return me;
+ },
+
+ /* @private
+ * Called after the component is moved, this method is empty by default but can be implemented by any
+ * subclass that needs to perform custom logic after a move occurs.
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ */
+ onPosition: Ext.emptyFn,
+
+ /**
+ * Sets the width of the component. This method fires the {@link #resize} event.
+ * @param {Number} width The new width to setThis may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS width style.</li>
+ * </ul></div>
+ * @return {Ext.Component} this
+ */
+ setWidth : function(width) {
+ return this.setSize(width);
+ },
+
+ /**
+ * Sets the height of the component. This method fires the {@link #resize} event.
+ * @param {Number} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
+ * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.core.Element#defaultUnit}s (by default, pixels).</li>
+ * <li>A String used to set the CSS height style.</li>
+ * <li><i>undefined</i> to leave the height unchanged.</li>
+ * </ul></div>
+ * @return {Ext.Component} this
+ */
+ setHeight : function(height) {
+ return this.setSize(undefined, height);
+ },
+
+ /**
+ * Gets the current size of the component's underlying element.
+ * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
+ */
+ getSize : function() {
+ return this.el.getSize();
+ },
+
+ /**
+ * Gets the current width of the component's underlying element.
+ * @return {Number}
+ */
+ getWidth : function() {
+ return this.el.getWidth();
+ },
+
+ /**
+ * Gets the current height of the component's underlying element.
+ * @return {Number}
+ */
+ getHeight : function() {
+ return this.el.getHeight();
+ },
+
+ /**
+ * Gets the {@link Ext.ComponentLoader} for this Component.
+ * @return {Ext.ComponentLoader} The loader instance, null if it doesn't exist.
+ */
+ getLoader: function(){
+ var me = this,
+ autoLoad = me.autoLoad ? (Ext.isObject(me.autoLoad) ? me.autoLoad : {url: me.autoLoad}) : null,
+ loader = me.loader || autoLoad;
+
+ if (loader) {
+ if (!loader.isLoader) {
+ me.loader = Ext.create('Ext.ComponentLoader', Ext.apply({
+ target: me,
+ autoLoad: autoLoad
+ }, loader));
+ } else {
+ loader.setTarget(me);
+ }
+ return me.loader;
+
+ }
+ return null;
+ },
+
+ /**
+ * This method allows you to show or hide a LoadMask on top of this component.
+ * @param {Boolean/Object/String} load True to show the default LoadMask, a config object
+ * that will be passed to the LoadMask constructor, or a message String to show. False to
+ * hide the current LoadMask.
+ * @param {Boolean} targetEl True to mask the targetEl of this Component instead of the this.el.
+ * For example, setting this to true on a Panel will cause only the body to be masked. (defaults to false)
+ * @return {Ext.LoadMask} The LoadMask instance that has just been shown.
+ */
+ setLoading : function(load, targetEl) {
+ var me = this,
+ config;
+
+ if (me.rendered) {
+ if (load !== false && !me.collapsed) {
+ if (Ext.isObject(load)) {
+ config = load;
+ }
+ else if (Ext.isString(load)) {
+ config = {msg: load};
+ }
+ else {
+ config = {};
+ }
+ me.loadMask = me.loadMask || Ext.create('Ext.LoadMask', targetEl ? me.getTargetEl() : me.el, config);
+ me.loadMask.show();
+ } else if (me.loadMask) {
+ Ext.destroy(me.loadMask);
+ me.loadMask = null;
+ }
+ }
+
+ return me.loadMask;
+ },
+
+ /**
+ * Sets the dock position of this component in its parent panel. Note that
+ * this only has effect if this item is part of the dockedItems collection
+ * of a parent that has a DockLayout (note that any Panel has a DockLayout
+ * by default)
+ * @return {Component} this
+ */
+ setDocked : function(dock, layoutParent) {
+ var me = this;
+
+ me.dock = dock;
+ if (layoutParent && me.ownerCt && me.rendered) {
+ me.ownerCt.doComponentLayout();
+ }
+ return me;
+ },
+
+ onDestroy : function() {
+ var me = this;
+
+ if (me.monitorResize && Ext.EventManager.resizeEvent) {
+ Ext.EventManager.resizeEvent.removeListener(me.setSize, me);
+ }
+ Ext.destroy(me.componentLayout, me.loadMask);
+ },
+
+ /**
+ * Destroys the Component.
+ */
+ destroy : function() {
+ var me = this;
+
+ if (!me.isDestroyed) {
+ if (me.fireEvent('beforedestroy', me) !== false) {
+ me.destroying = true;
+ me.beforeDestroy();
+
+ if (me.floating) {
+ delete me.floatParent;
+ // A zIndexManager is stamped into a *floating* Component when it is added to a Container.
+ // If it has no zIndexManager at render time, it is assigned to the global Ext.WindowManager instance.
+ if (me.zIndexManager) {
+ me.zIndexManager.unregister(me);
+ }
+ } else if (me.ownerCt && me.ownerCt.remove) {
+ me.ownerCt.remove(me, false);
+ }
+
+ if (me.rendered) {
+ me.el.remove();
+ }
+
+ me.onDestroy();
+
+ // Attempt to destroy all plugins
+ Ext.destroy(me.plugins);
+
+ Ext.ComponentManager.unregister(me);
+ me.fireEvent('destroy', me);
+
+ me.mixins.state.destroy.call(me);
+
+ me.clearListeners();
+ me.destroying = false;
+ me.isDestroyed = true;
+ }
+ }
+ },
+
+ /**
+ * Retrieves a plugin by its pluginId which has been bound to this
+ * component.
+ * @returns {Ext.AbstractPlugin} pluginInstance
+ */
+ getPlugin: function(pluginId) {
+ var i = 0,
+ plugins = this.plugins,
+ ln = plugins.length;
+ for (; i < ln; i++) {
+ if (plugins[i].pluginId === pluginId) {
+ return plugins[i];
+ }
+ }
+ },
+
+ /**
+ * Determines whether this component is the descendant of a particular container.
+ * @param {Ext.Container} container
+ * @returns {Boolean} isDescendant
+ */
+ isDescendantOf: function(container) {
+ return !!this.findParentBy(function(p){
+ return p === container;
+ });
+ }
+}, function() {
+ this.createAlias({
+ on: 'addListener',
+ prev: 'previousSibling',
+ next: 'nextSibling'
+ });
+});
+
+/**
+ * @class Ext.AbstractPlugin
+ * @extends Object
+ *
+ * Plugins are injected
+ */
+Ext.define('Ext.AbstractPlugin', {
+ disabled: false,
+
+ constructor: function(config) {
+ if (!config.cmp && Ext.global.console) {
+ Ext.global.console.warn("Attempted to attach a plugin ");
+ }
+ Ext.apply(this, config);
+ },
+
+ getCmp: function() {
+ return this.cmp;
+ },
+
+ /**
+ * The init method is invoked after initComponent has been run for the
+ * component which we are injecting the plugin into.
+ */
+ init: Ext.emptyFn,
+
+ /**
+ * The destroy method is invoked by the owning Component at the time the Component is being destroyed.
+ * Use this method to clean up an resources.
+ */
+ destroy: Ext.emptyFn,
+
+ /**
+ * Enable the plugin and set the disabled flag to false.
+ */
+ enable: function() {
+ this.disabled = false;
+ },
+
+ /**
+ * Disable the plugin and set the disabled flag to true.
+ */
+ disable: function() {
+ this.disabled = true;
+ }
+});
+
+/**
+ * @class Ext.data.Connection
+ * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
+ * to a configured URL, or to a URL specified at request time.
+ *
+ * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
+ * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
+ * in the request options object, or an {@link #requestcomplete event listener}.
+ *
+ * <p><u>File Uploads</u></p>
+ *
+ * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
+ * Instead the form is submitted in the standard manner with the DOM <form> element temporarily modified to have its
+ * target set to refer to a dynamically generated, hidden <iframe> which is inserted into the document but removed
+ * after the return data has been gathered.
+ *
+ * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
+ * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
+ * insert the text unchanged into the document body.
+ *
+ * Characters which are significant to an HTML parser must be sent as HTML entities, so encode "<" as "&lt;", "&" as
+ * "&amp;" etc.
+ *
+ * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
+ * responseText property in order to conform to the requirements of event handlers and callbacks.
+ *
+ * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
+ * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
+ * packet content.
+ *
+ * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
+ */
+Ext.define('Ext.data.Connection', {
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ statics: {
+ requestId: 0
+ },
+
+ url: null,
+ async: true,
+ method: null,
+ username: '',
+ password: '',
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ disableCaching: true,
+
+ /**
+ * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
+ * through a cache buster. Defaults to '_dc'
+ * @type String
+ */
+ disableCachingParam: '_dc',
+
+ /**
+ * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
+ */
+ timeout : 30000,
+
+ /**
+ * @param {Object} extraParams (Optional) Any parameters to be appended to the request.
+ */
+
+ useDefaultHeader : true,
+ defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
+ useDefaultXhrHeader : true,
+ defaultXhrHeader : 'XMLHttpRequest',
+
+ constructor : function(config) {
+ config = config || {};
+ Ext.apply(this, config);
+
+ this.addEvents(
+ /**
+ * @event beforerequest
+ * Fires before a network request is made to retrieve a data object.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ 'beforerequest',
+ /**
+ * @event requestcomplete
+ * Fires if the request was successfully completed.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ 'requestcomplete',
+ /**
+ * @event requestexception
+ * Fires if an error HTTP status was returned from the server.
+ * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
+ * for details of HTTP status codes.
+ * @param {Connection} conn This Connection object.
+ * @param {Object} response The XHR object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
+ * for details.
+ * @param {Object} options The options config object passed to the {@link #request} method.
+ */
+ 'requestexception'
+ );
+ this.requests = {};
+ this.mixins.observable.constructor.call(this);
+ },
+
+ /**
+ * <p>Sends an HTTP request to a remote server.</p>
+ * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
+ * return before the response has been received. Process any returned data
+ * in a callback function.</p>
+ * <pre><code>
+Ext.Ajax.request({
+url: 'ajax_demo/sample.json',
+success: function(response, opts) {
+ var obj = Ext.decode(response.responseText);
+ console.dir(obj);
+},
+failure: function(response, opts) {
+ console.log('server-side failure with status code ' + response.status);
+}
+});
+ * </code></pre>
+ * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
+ * @param {Object} options An object which may contain the following properties:<ul>
+ * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
+ * which to send the request, or a function to call which returns a URL string. The scope of the
+ * function is specified by the <tt>scope</tt> option. Defaults to the configured
+ * <tt>{@link #url}</tt>.</div></li>
+ * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
+ * An object containing properties which are used as parameters to the
+ * request, a url encoded string or a function to call to get either. The scope of the function
+ * is specified by the <tt>scope</tt> option.</div></li>
+ * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
+ * for the request. Defaults to the configured method, or if no method was configured,
+ * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
+ * the method name is case-sensitive and should be all caps.</div></li>
+ * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
+ * function to be called upon receipt of the HTTP response. The callback is
+ * called regardless of success or failure and is passed the following
+ * parameters:<ul>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
+ * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
+ * accessing elements of the response.</div></li>
+ * </ul></div></li>
+ * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon success of the request. The callback is passed the following
+ * parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
+ * to be called upon failure of the request. The callback is passed the
+ * following parameters:<ul>
+ * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
+ * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
+ * </ul></div></li>
+ * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
+ * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
+ * specified as functions from which to draw values, then this also serves as the scope for those function calls.
+ * Defaults to the browser window.</div></li>
+ * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
+ * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
+ * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
+ * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
+ * with the <tt>form</tt> option</b>.
+ * <p>True if the form object is a file upload (will be set automatically if the form was
+ * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
+ * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
+ * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
+ * DOM <tt><form></tt> element temporarily modified to have its
+ * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
+ * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
+ * but removed after the return data has been gathered.</p>
+ * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
+ * server is using JSON to send the return object, then the
+ * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
+ * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
+ * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
+ * is created containing a <tt>responseText</tt> property in order to conform to the
+ * requirements of event handlers and callbacks.</p>
+ * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
+ * and some server technologies (notably JEE) may require some custom processing in order to
+ * retrieve parameter names and parameter values from the packet content.</p>
+ * </div></li>
+ * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
+ * headers to set for the request.</div></li>
+ * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
+ * to use for the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
+ * data to use as the post. Note: This will be used instead of params for the post
+ * data. Any params will be appended to the URL.</div></li>
+ * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
+ * to add a unique cache-buster param to GET requests.</div></li>
+ * </ul></p>
+ * <p>The options object may also contain any other property which might be needed to perform
+ * postprocessing in a callback because it is passed to callback functions.</p>
+ * @return {Object} request The request object. This may be used
+ * to cancel the request.
+ */
+ request : function(options) {
+ options = options || {};
+ var me = this,
+ scope = options.scope || window,
+ username = options.username || me.username,
+ password = options.password || me.password || '',
+ async,
+ requestOptions,
+ request,
+ headers,
+ xhr;
+
+ if (me.fireEvent('beforerequest', me, options) !== false) {
+
+ requestOptions = me.setOptions(options, scope);
+
+ if (this.isFormUpload(options) === true) {
+ this.upload(options.form, requestOptions.url, requestOptions.data, options);
+ return null;
+ }
+
+ // if autoabort is set, cancel the current transactions
+ if (options.autoAbort === true || me.autoAbort) {
+ me.abort();
+ }
+
+ // create a connection object
+ xhr = this.getXhrInstance();
+
+ async = options.async !== false ? (options.async || me.async) : false;
+
+ // open the request
+ if (username) {
+ xhr.open(requestOptions.method, requestOptions.url, async, username, password);
+ } else {
+ xhr.open(requestOptions.method, requestOptions.url, async);
+ }
+
+ headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
+
+ // create the transaction object
+ request = {
+ id: ++Ext.data.Connection.requestId,
+ xhr: xhr,
+ headers: headers,
+ options: options,
+ async: async,
+ timeout: setTimeout(function() {
+ request.timedout = true;
+ me.abort(request);
+ }, options.timeout || me.timeout)
+ };
+ me.requests[request.id] = request;
+
+ // bind our statechange listener
+ if (async) {
+ xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
+ }
+
+ // start the request!
+ xhr.send(requestOptions.data);
+ if (!async) {
+ return this.onComplete(request);
+ }
+ return request;
+ } else {
+ Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
+ return null;
+ }
+ },
+
+ /**
+ * Upload a form using a hidden iframe.
+ * @param {Mixed} form The form to upload
+ * @param {String} url The url to post to
+ * @param {String} params Any extra parameters to pass
+ * @param {Object} options The initial options
+ */
+ upload: function(form, url, params, options){
+ form = Ext.getDom(form);
+ options = options || {};
+
+ var id = Ext.id(),
+ frame = document.createElement('iframe'),
+ hiddens = [],
+ encoding = 'multipart/form-data',
+ buf = {
+ target: form.target,
+ method: form.method,
+ encoding: form.encoding,
+ enctype: form.enctype,
+ action: form.action
+ }, hiddenItem;
+
+ /*
+ * Originally this behaviour was modified for Opera 10 to apply the secure URL after
+ * the frame had been added to the document. It seems this has since been corrected in
+ * Opera so the behaviour has been reverted, the URL will be set before being added.
+ */
+ Ext.fly(frame).set({
+ id: id,
+ name: id,
+ cls: Ext.baseCSSPrefix + 'hide-display',
+ src: Ext.SSL_SECURE_URL
+ });
+
+ document.body.appendChild(frame);
+
+ // This is required so that IE doesn't pop the response up in a new window.
+ if (document.frames) {
+ document.frames[id].name = id;
+ }
+
+ Ext.fly(form).set({
+ target: id,
+ method: 'POST',
+ enctype: encoding,
+ encoding: encoding,
+ action: url || buf.action
+ });
+
+ // add dynamic params
+ if (params) {
+ Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
+ hiddenItem = document.createElement('input');
+ Ext.fly(hiddenItem).set({
+ type: 'hidden',
+ value: value,
+ name: name
+ });
+ form.appendChild(hiddenItem);
+ hiddens.push(hiddenItem);
+ });
+ }
+
+ Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
+ form.submit();
+
+ Ext.fly(form).set(buf);
+ Ext.each(hiddens, function(h) {
+ Ext.removeNode(h);
+ });
+ },
+
+ onUploadComplete: function(frame, options){
+ var me = this,
+ // bogus response object
+ response = {
+ responseText: '',
+ responseXML: null
+ }, doc, firstChild;
+
+ try {
+ doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
+ if (doc) {
+ if (doc.body) {
+ if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
+ response.responseText = firstChild.value;
+ } else {
+ response.responseText = doc.body.innerHTML;
+ }
+ }
+ //in IE the document may still have a body even if returns XML.
+ response.responseXML = doc.XMLDocument || doc;
+ }
+ } catch (e) {
+ }
+
+ me.fireEvent('requestcomplete', me, response, options);
+
+ Ext.callback(options.success, options.scope, [response, options]);
+ Ext.callback(options.callback, options.scope, [options, true, response]);
+
+ setTimeout(function(){
+ Ext.removeNode(frame);
+ }, 100);
+ },
+
+ /**
+ * Detect whether the form is intended to be used for an upload.
+ * @private
+ */
+ isFormUpload: function(options){
+ var form = this.getForm(options);
+ if (form) {
+ return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
+ }
+ return false;
+ },
+
+ /**
+ * Get the form object from options.
+ * @private
+ * @param {Object} options The request options
+ * @return {HTMLElement} The form, null if not passed
+ */
+ getForm: function(options){
+ return Ext.getDom(options.form) || null;
+ },
+
+ /**
+ * Set various options such as the url, params for the request
+ * @param {Object} options The initial options
+ * @param {Object} scope The scope to execute in
+ * @return {Object} The params for the request
+ */
+ setOptions: function(options, scope){
+ var me = this,
+ params = options.params || {},
+ extraParams = me.extraParams,
+ urlParams = options.urlParams,
+ url = options.url || me.url,
+ jsonData = options.jsonData,
+ method,
+ disableCache,
+ data;
+
+
+ // allow params to be a method that returns the params object
+ if (Ext.isFunction(params)) {
+ params = params.call(scope, options);
+ }
+
+ // allow url to be a method that returns the actual url
+ if (Ext.isFunction(url)) {
+ url = url.call(scope, options);
+ }
+
+ url = this.setupUrl(options, url);
+
+ if (!url) {
+ Ext.Error.raise({
+ options: options,
+ msg: 'No URL specified'
+ });
+ }
+
+ // check for xml or json data, and make sure json data is encoded
+ data = options.rawData || options.xmlData || jsonData || null;
+ if (jsonData && !Ext.isPrimitive(jsonData)) {
+ data = Ext.encode(data);
+ }
+
+ // make sure params are a url encoded string and include any extraParams if specified
+ if (Ext.isObject(params)) {
+ params = Ext.Object.toQueryString(params);
+ }
+
+ if (Ext.isObject(extraParams)) {
+ extraParams = Ext.Object.toQueryString(extraParams);
+ }
+
+ params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
+
+ urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
+
+ params = this.setupParams(options, params);
+
+ // decide the proper method for this request
+ method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
+ this.setupMethod(options, method);
+
+
+ disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
+ // if the method is get append date to prevent caching
+ if (method === 'GET' && disableCache) {
+ url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
+ }
+
+ // if the method is get or there is json/xml data append the params to the url
+ if ((method == 'GET' || data) && params) {
+ url = Ext.urlAppend(url, params);
+ params = null;
+ }
+
+ // allow params to be forced into the url
+ if (urlParams) {
+ url = Ext.urlAppend(url, urlParams);
+ }
+
+ return {
+ url: url,
+ method: method,
+ data: data || params || null
+ };
+ },
+
+ /**
+ * Template method for overriding url
+ * @private
+ * @param {Object} options
+ * @param {String} url
+ * @return {String} The modified url
+ */
+ setupUrl: function(options, url){
+ var form = this.getForm(options);
+ if (form) {
+ url = url || form.action;
+ }
+ return url;
+ },
+
+
+ /**
+ * Template method for overriding params
+ * @private
+ * @param {Object} options
+ * @param {String} params
+ * @return {String} The modified params
+ */
+ setupParams: function(options, params) {
+ var form = this.getForm(options),
+ serializedForm;
+ if (form && !this.isFormUpload(options)) {
+ serializedForm = Ext.core.Element.serializeForm(form);
+ params = params ? (params + '&' + serializedForm) : serializedForm;
+ }
+ return params;
+ },
+
+ /**
+ * Template method for overriding method
+ * @private
+ * @param {Object} options
+ * @param {String} method
+ * @return {String} The modified method
+ */
+ setupMethod: function(options, method){
+ if (this.isFormUpload(options)) {
+ return 'POST';
+ }
+ return method;
+ },
+
+ /**
+ * Setup all the headers for the request
+ * @private
+ * @param {Object} xhr The xhr object
+ * @param {Object} options The options for the request
+ * @param {Object} data The data for the request
+ * @param {Object} params The params for the request
+ */
+ setupHeaders: function(xhr, options, data, params){
+ var me = this,
+ headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
+ contentType = me.defaultPostHeader,
+ jsonData = options.jsonData,
+ xmlData = options.xmlData,
+ key,
+ header;
+
+ if (!headers['Content-Type'] && (data || params)) {
+ if (data) {
+ if (options.rawData) {
+ contentType = 'text/plain';
+ } else {
+ if (xmlData && Ext.isDefined(xmlData)) {
+ contentType = 'text/xml';
+ } else if (jsonData && Ext.isDefined(jsonData)) {
+ contentType = 'application/json';
+ }
+ }
+ }
+ headers['Content-Type'] = contentType;
+ }
+
+ if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
+ headers['X-Requested-With'] = me.defaultXhrHeader;
+ }
+ // set up all the request headers on the xhr object
+ try{
+ for (key in headers) {
+ if (headers.hasOwnProperty(key)) {
+ header = headers[key];
+ xhr.setRequestHeader(key, header);
+ }
+
+ }
+ } catch(e) {
+ me.fireEvent('exception', key, header);
+ }
+ return headers;
+ },
+
+ /**
+ * Creates the appropriate XHR transport for the browser.
+ * @private
+ */
+ getXhrInstance: (function(){
+ var options = [function(){
+ return new XMLHttpRequest();
+ }, function(){
+ return new ActiveXObject('MSXML2.XMLHTTP.3.0');
+ }, function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ }, function(){
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ }], i = 0,
+ len = options.length,
+ xhr;
+
+ for(; i < len; ++i) {
+ try{
+ xhr = options[i];
+ xhr();
+ break;
+ }catch(e){}
+ }
+ return xhr;
+ })(),
+
+ /**
+ * Determine whether this object has a request outstanding.
+ * @param {Object} request (Optional) defaults to the last transaction
+ * @return {Boolean} True if there is an outstanding request.
+ */
+ isLoading : function(request) {
+ if (!(request && request.xhr)) {
+ return false;
+ }
+ // if there is a connection and readyState is not 0 or 4
+ var state = request.xhr.readyState;
+ return !(state === 0 || state == 4);
+ },
+
+ /**
+ * Aborts any outstanding request.
+ * @param {Object} request (Optional) defaults to the last request
+ */
+ abort : function(request) {
+ var me = this,
+ requests = me.requests,
+ id;
+
+ if (request && me.isLoading(request)) {
+ /**
+ * Clear out the onreadystatechange here, this allows us
+ * greater control, the browser may/may not fire the function
+ * depending on a series of conditions.
+ */
+ request.xhr.onreadystatechange = null;
+ request.xhr.abort();
+ me.clearTimeout(request);
+ if (!request.timedout) {
+ request.aborted = true;
+ }
+ me.onComplete(request);
+ me.cleanup(request);
+ } else if (!request) {
+ for(id in requests) {
+ if (requests.hasOwnProperty(id)) {
+ me.abort(requests[id]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Fires when the state of the xhr changes
+ * @private
+ * @param {Object} request The request
+ */
+ onStateChange : function(request) {
+ if (request.xhr.readyState == 4) {
+ this.clearTimeout(request);
+ this.onComplete(request);
+ this.cleanup(request);
+ }
+ },
+
+ /**
+ * Clear the timeout on the request
+ * @private
+ * @param {Object} The request
+ */
+ clearTimeout: function(request){
+ clearTimeout(request.timeout);
+ delete request.timeout;
+ },
+
+ /**
+ * Clean up any left over information from the request
+ * @private
+ * @param {Object} The request
+ */
+ cleanup: function(request){
+ request.xhr = null;
+ delete request.xhr;
+ },
+
+ /**
+ * To be called when the request has come back from the server
+ * @private
+ * @param {Object} request
+ * @return {Object} The response
+ */
+ onComplete : function(request) {
+ var me = this,
+ options = request.options,
+ result = me.parseStatus(request.xhr.status),
+ success = result.success,
+ response;
+
+ if (success) {
+ response = me.createResponse(request);
+ me.fireEvent('requestcomplete', me, response, options);
+ Ext.callback(options.success, options.scope, [response, options]);
+ } else {
+ if (result.isException || request.aborted || request.timedout) {
+ response = me.createException(request);
+ } else {
+ response = me.createResponse(request);
+ }
+ me.fireEvent('requestexception', me, response, options);
+ Ext.callback(options.failure, options.scope, [response, options]);
+ }
+ Ext.callback(options.callback, options.scope, [options, success, response]);
+ delete me.requests[request.id];
+ return response;
+ },
+
+ /**
+ * Check if the response status was successful
+ * @param {Number} status The status code
+ * @return {Object} An object containing success/status state
+ */
+ parseStatus: function(status) {
+ // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
+ status = status == 1223 ? 204 : status;
+
+ var success = (status >= 200 && status < 300) || status == 304,
+ isException = false;
+
+ if (!success) {
+ switch (status) {
+ case 12002:
+ case 12029:
+ case 12030:
+ case 12031:
+ case 12152:
+ case 13030:
+ isException = true;
+ break;
+ }
+ }
+ return {
+ success: success,
+ isException: isException
+ };
+ },
+
+ /**
+ * Create the response object
+ * @private
+ * @param {Object} request
+ */
+ createResponse : function(request) {
+ var xhr = request.xhr,
+ headers = {},
+ lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
+ count = lines.length,
+ line, index, key, value, response;
+
+ while (count--) {
+ line = lines[count];
+ index = line.indexOf(':');
+ if(index >= 0) {
+ key = line.substr(0, index).toLowerCase();
+ if (line.charAt(index + 1) == ' ') {
+ ++index;
+ }
+ headers[key] = line.substr(index + 1);
+ }
+ }
+
+ request.xhr = null;
+ delete request.xhr;
+
+ response = {
+ request: request,
+ requestId : request.id,
+ status : xhr.status,
+ statusText : xhr.statusText,
+ getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
+ getAllResponseHeaders : function(){ return headers; },
+ responseText : xhr.responseText,
+ responseXML : xhr.responseXML
+ };
+
+ // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
+ // functions created with getResponseHeader/getAllResponseHeaders
+ xhr = null;
+ return response;
+ },
+
+ /**
+ * Create the exception object
+ * @private
+ * @param {Object} request
+ */
+ createException : function(request) {
+ return {
+ request : request,
+ requestId : request.id,
+ status : request.aborted ? -1 : 0,
+ statusText : request.aborted ? 'transaction aborted' : 'communication failure',
+ aborted: request.aborted,
+ timedout: request.timedout
+ };
+ }
+});
+
+/**
+ * @class Ext.Ajax
+ * @singleton
+ * @markdown
+ * @extends Ext.data.Connection
+
+A singleton instance of an {@link Ext.data.Connection}. This class
+is used to communicate with your server side code. It can be used as follows:
+
+ Ext.Ajax.request({
+ url: 'page.php',
+ params: {
+ id: 1
+ },
+ success: function(response){
+ var text = response.responseText;
+ // process server response here
+ }
+ });
+
+Default options for all requests can be set be changing a property on the Ext.Ajax class:
+
+ Ext.Ajax.timeout = 60000; // 60 seconds
+
+Any options specified in the request method for the Ajax request will override any
+defaults set on the Ext.Ajax class. In the code sample below, the timeout for the
+request will be 60 seconds.
+
+ Ext.Ajax.timeout = 120000; // 120 seconds
+ Ext.Ajax.request({
+ url: 'page.aspx',
+ timeout: 60000
+ });
+
+In general, this class will be used for all Ajax requests in your application.
+The main reason for creating a separate {@link Ext.data.Connection} is for a
+series of requests that share common settings that are different to all other
+requests in the application.
+
+ */
+Ext.define('Ext.Ajax', {
+ extend: 'Ext.data.Connection',
+ singleton: true,
+
+ /**
+ * @cfg {String} url @hide
+ */
+ /**
+ * @cfg {Object} extraParams @hide
+ */
+ /**
+ * @cfg {Object} defaultHeaders @hide
+ */
+ /**
+ * @cfg {String} method (Optional) @hide
+ */
+ /**
+ * @cfg {Number} timeout (Optional) @hide
+ */
+ /**
+ * @cfg {Boolean} autoAbort (Optional) @hide
+ */
+
+ /**
+ * @cfg {Boolean} disableCaching (Optional) @hide
+ */
+
+ /**
+ * @property disableCaching
+ * True to add a unique cache-buster param to GET requests. (defaults to true)
+ * @type Boolean
+ */
+ /**
+ * @property url
+ * The default URL to be used for requests to the server. (defaults to undefined)
+ * If the server receives all requests through one URL, setting this once is easier than
+ * entering it on every request.
+ * @type String
+ */
+ /**
+ * @property extraParams
+ * An object containing properties which are used as extra parameters to each request made
+ * by this object (defaults to undefined). Session information and other data that you need
+ * to pass with each request are commonly put here.
+ * @type Object
+ */
+ /**
+ * @property defaultHeaders
+ * An object containing request headers which are added to each request made by this object
+ * (defaults to undefined).
+ * @type Object
+ */
+ /**
+ * @property method
+ * The default HTTP method to be used for requests. Note that this is case-sensitive and
+ * should be all caps (defaults to undefined; if not set but params are present will use
+ * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
+ * @type String
+ */
+ /**
+ * @property timeout
+ * The timeout in milliseconds to be used for requests. (defaults to 30000)
+ * @type Number
+ */
+
+ /**
+ * @property autoAbort
+ * Whether a new request should abort any pending requests. (defaults to false)
+ * @type Boolean
+ */
+ autoAbort : false
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Association
+ * @extends Object
+ *
+ * <p>Associations enable you to express relationships between different {@link Ext.data.Model Models}. Let's say we're
+ * writing an ecommerce system where Users can make Orders - there's a relationship between these Models that we can
+ * express like this:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'name', 'email'],
+
+ hasMany: {model: 'Order', name: 'orders'}
+});
+
+Ext.define('Order', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'user_id', 'status', 'price'],
+
+ belongsTo: 'User'
+});
+</code></pre>
+ *
+ * <p>We've set up two models - User and Order - and told them about each other. You can set up as many associations on
+ * each Model as you need using the two default types - {@link Ext.data.HasManyAssociation hasMany} and
+ * {@link Ext.data.BelongsToAssociation belongsTo}. There's much more detail on the usage of each of those inside their
+ * documentation pages. If you're not familiar with Models already, {@link Ext.data.Model there is plenty on those too}.</p>
+ *
+ * <p><u>Further Reading</u></p>
+ *
+ * <ul style="list-style-type: disc; padding-left: 20px;">
+ * <li>{@link Ext.data.HasManyAssociation hasMany associations}
+ * <li>{@link Ext.data.BelongsToAssociation belongsTo associations}
+ * <li>{@link Ext.data.Model using Models}
+ * </ul>
+ *
+ * <b>Self association models</b>
+ * <p>We can also have models that create parent/child associations between the same type. Below is an example, where
+ * groups can be nested inside other groups:</p>
+ * <pre><code>
+
+// Server Data
+{
+ "groups": {
+ "id": 10,
+ "parent_id": 100,
+ "name": "Main Group",
+ "parent_group": {
+ "id": 100,
+ "parent_id": null,
+ "name": "Parent Group"
+ },
+ "child_groups": [{
+ "id": 2,
+ "parent_id": 10,
+ "name": "Child Group 1"
+ },{
+ "id": 3,
+ "parent_id": 10,
+ "name": "Child Group 2"
+ },{
+ "id": 4,
+ "parent_id": 10,
+ "name": "Child Group 3"
+ }]
+ }
+}
+
+// Client code
+Ext.define('Group', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'parent_id', 'name'],
+ proxy: {
+ type: 'ajax',
+ url: 'data.json',
+ reader: {
+ type: 'json',
+ root: 'groups'
+ }
+ },
+ associations: [{
+ type: 'hasMany',
+ model: 'Group',
+ primaryKey: 'id',
+ foreignKey: 'parent_id',
+ autoLoad: true,
+ associationKey: 'child_groups' // read child data from child_groups
+ }, {
+ type: 'belongsTo',
+ model: 'Group',
+ primaryKey: 'id',
+ foreignKey: 'parent_id',
+ autoLoad: true,
+ associationKey: 'parent_group' // read parent data from parent_group
+ }]
+});
+
+
+Ext.onReady(function(){
+
+ Group.load(10, {
+ success: function(group){
+ console.log(group.getGroup().get('name'));
+
+ group.groups().each(function(rec){
+ console.log(rec.get('name'));
+ });
+ }
+ });
+
+});
+ * </code></pre>
+ *
+ * @constructor
+ * @param {Object} config Optional config object
+ */
+Ext.define('Ext.data.Association', {
+ /**
+ * @cfg {String} ownerModel The string name of the model that owns the association. Required
+ */
+
+ /**
+ * @cfg {String} associatedModel The string name of the model that is being associated with. Required
+ */
+
+ /**
+ * @cfg {String} primaryKey The name of the primary key on the associated model. Defaults to 'id'.
+ * In general this will be the {@link Ext.data.Model#idProperty} of the Model.
+ */
+ primaryKey: 'id',
+
+ /**
+ * @cfg {Ext.data.reader.Reader} reader A special reader to read associated data
+ */
+
+ /**
+ * @cfg {String} associationKey The name of the property in the data to read the association from.
+ * Defaults to the name of the associated model.
+ */
+
+ defaultReaderType: 'json',
+
+ statics: {
+ create: function(association){
+ if (!association.isAssociation) {
+ if (Ext.isString(association)) {
+ association = {
+ type: association
+ };
+ }
+
+ switch (association.type) {
+ case 'belongsTo':
+ return Ext.create('Ext.data.BelongsToAssociation', association);
+ case 'hasMany':
+ return Ext.create('Ext.data.HasManyAssociation', association);
+ //TODO Add this back when it's fixed
+// case 'polymorphic':
+// return Ext.create('Ext.data.PolymorphicAssociation', association);
+ default:
+ Ext.Error.raise('Unknown Association type: "' + association.type + '"');
+ }
+ }
+ return association;
+ }
+ },
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+
+ var types = Ext.ModelManager.types,
+ ownerName = config.ownerModel,
+ associatedName = config.associatedModel,
+ ownerModel = types[ownerName],
+ associatedModel = types[associatedName],
+ ownerProto;
+
+ if (ownerModel === undefined) {
+ Ext.Error.raise("The configured ownerModel was not valid (you tried " + ownerName + ")");
+ }
+ if (associatedModel === undefined) {
+ Ext.Error.raise("The configured associatedModel was not valid (you tried " + associatedName + ")");
+ }
+
+ this.ownerModel = ownerModel;
+ this.associatedModel = associatedModel;
+
+ /**
+ * The name of the model that 'owns' the association
+ * @property ownerName
+ * @type String
+ */
+
+ /**
+ * The name of the model is on the other end of the association (e.g. if a User model hasMany Orders, this is 'Order')
+ * @property associatedName
+ * @type String
+ */
+
+ Ext.applyIf(this, {
+ ownerName : ownerName,
+ associatedName: associatedName
+ });
+ },
+
+ /**
+ * Get a specialized reader for reading associated data
+ * @return {Ext.data.reader.Reader} The reader, null if not supplied
+ */
+ getReader: function(){
+ var me = this,
+ reader = me.reader,
+ model = me.associatedModel;
+
+ if (reader) {
+ if (Ext.isString(reader)) {
+ reader = {
+ type: reader
+ };
+ }
+ if (reader.isReader) {
+ reader.setModel(model);
+ } else {
+ Ext.applyIf(reader, {
+ model: model,
+ type : me.defaultReaderType
+ });
+ }
+ me.reader = Ext.createByAlias('reader.' + reader.type, reader);
+ }
+ return me.reader || null;
+ }
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.ModelManager
+ * @extends Ext.AbstractManager
+
+The ModelManager keeps track of all {@link Ext.data.Model} types defined in your application.
+
+__Creating Model Instances__
+Model instances can be created by using the {@link #create} function. It is also possible to do
+this by using the Model type directly. The following snippets are equivalent:
+
+ Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['first', 'last']
+ });
+
+ // method 1, create through the manager
+ Ext.ModelManager.create({
+ first: 'Ed',
+ last: 'Spencer'
+ }, 'User');
+
+ // method 2, create on the type directly
+ new User({
+ first: 'Ed',
+ last: 'Spencer'
+ });
+
+__Accessing Model Types__
+A reference to a Model type can be obtained by using the {@link #getModel} function. Since models types
+are normal classes, you can access the type directly. The following snippets are equivalent:
+
+ Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['first', 'last']
+ });
+
+ // method 1, access model type through the manager
+ var UserType = Ext.ModelManager.getModel('User');
+
+ // method 2, reference the type directly
+ var UserType = User;
+
+ * @markdown
+ * @singleton
+ */
+Ext.define('Ext.ModelManager', {
+ extend: 'Ext.AbstractManager',
+ alternateClassName: 'Ext.ModelMgr',
+ requires: ['Ext.data.Association'],
+
+ singleton: true,
+
+ typeName: 'mtype',
+
+ /**
+ * Private stack of associations that must be created once their associated model has been defined
+ * @property associationStack
+ * @type Array
+ */
+ associationStack: [],
+
+ /**
+ * Registers a model definition. All model plugins marked with isDefault: true are bootstrapped
+ * immediately, as are any addition plugins defined in the model config.
+ * @private
+ */
+ registerType: function(name, config) {
+ var proto = config.prototype,
+ model;
+ if (proto && proto.isModel) {
+ // registering an already defined model
+ model = config;
+ } else {
+ // passing in a configuration
+ if (!config.extend) {
+ config.extend = 'Ext.data.Model';
+ }
+ model = Ext.define(name, config);
+ }
+ this.types[name] = model;
+ return model;
+ },
+
+ /**
+ * @private
+ * Private callback called whenever a model has just been defined. This sets up any associations
+ * that were waiting for the given model to be defined
+ * @param {Function} model The model that was just created
+ */
+ onModelDefined: function(model) {
+ var stack = this.associationStack,
+ length = stack.length,
+ create = [],
+ association, i, created;
+
+ for (i = 0; i < length; i++) {
+ association = stack[i];
+
+ if (association.associatedModel == model.modelName) {
+ create.push(association);
+ }
+ }
+
+ for (i = 0, length = create.length; i < length; i++) {
+ created = create[i];
+ this.types[created.ownerModel].prototype.associations.add(Ext.data.Association.create(created));
+ Ext.Array.remove(stack, created);
+ }
+ },
+
+ /**
+ * Registers an association where one of the models defined doesn't exist yet.
+ * The ModelManager will check when new models are registered if it can link them
+ * together
+ * @private
+ * @param {Ext.data.Association} association The association
+ */
+ registerDeferredAssociation: function(association){
+ this.associationStack.push(association);
+ },
+
+ /**
+ * Returns the {@link Ext.data.Model} for a given model name
+ * @param {String/Object} id The id of the model or the model instance.
+ */
+ getModel: function(id) {
+ var model = id;
+ if (typeof model == 'string') {
+ model = this.types[model];
+ }
+ return model;
+ },
+
+ /**
+ * Creates a new instance of a Model using the given data.
+ * @param {Object} data Data to initialize the Model's fields with
+ * @param {String} name The name of the model to create
+ * @param {Number} id Optional unique id of the Model instance (see {@link Ext.data.Model})
+ */
+ create: function(config, name, id) {
+ var con = typeof name == 'function' ? name : this.types[name || config.name];
+
+ return new con(config, id);
+ }
+}, function() {
+
+ /**
+ * Creates a new Model class from the specified config object. See {@link Ext.data.Model} for full examples.
+ *
+ * @param {Object} config A configuration object for the Model you wish to create.
+ * @return {Ext.data.Model} The newly registered Model
+ * @member Ext
+ * @method regModel
+ */
+ Ext.regModel = function() {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.regModel has been deprecated. Models can now be created by extending Ext.data.Model: Ext.define("MyModel", {extend: "Ext.data.Model", fields: []});.');
+ }
+ return this.ModelManager.registerType.apply(this.ModelManager, arguments);
+ };
+});
+
+/**
+ * @class Ext.app.Controller
+ * @constructor
+ *
+ * Controllers are the glue that binds an application together. All they really do is listen for events (usually from
+ * views) and take some action. Here's how we might create a Controller to manage Users:
+ *
+ * Ext.define('MyApp.controller.Users', {
+ * extend: 'Ext.app.Controller',
+ *
+ * init: function() {
+ * console.log('Initialized Users! This happens before the Application launch function is called');
+ * }
+ * });
+ *
+ * The init function is a special method that is called when your application boots. It is called before the
+ * {@link Ext.app.Application Application}'s launch function is executed so gives a hook point to run any code before
+ * your Viewport is created.
+ *
+ * The init function is a great place to set up how your controller interacts with the view, and is usually used in
+ * conjunction with another Controller function - {@link Ext.app.Controller#control control}. The control function
+ * makes it easy to listen to events on your view classes and take some action with a handler function. Let's update
+ * our Users controller to tell us when the panel is rendered:
+ *
+ * Ext.define('MyApp.controller.Users', {
+ * extend: 'Ext.app.Controller',
+ *
+ * init: function() {
+ * this.control({
+ * 'viewport > panel': {
+ * render: this.onPanelRendered
+ * }
+ * });
+ * },
+ *
+ * onPanelRendered: function() {
+ * console.log('The panel was rendered');
+ * }
+ * });
+ *
+ * We've updated the init function to use this.control to set up listeners on views in our application. The control
+ * function uses the new ComponentQuery engine to quickly and easily get references to components on the page. If you
+ * are not familiar with ComponentQuery yet, be sure to check out THIS GUIDE for a full explanation. In brief though,
+ * it allows us to pass a CSS-like selector that will find every matching component on the page.
+ *
+ * In our init function above we supplied 'viewport > panel', which translates to "find me every Panel that is a direct
+ * child of a Viewport". We then supplied an object that maps event names (just 'render' in this case) to handler
+ * functions. The overall effect is that whenever any component that matches our selector fires a 'render' event, our
+ * onPanelRendered function is called.
+ *
+ * <u>Using refs</u>
+ *
+ * One of the most useful parts of Controllers is the new ref system. These use the new {@link Ext.ComponentQuery} to
+ * make it really easy to get references to Views on your page. Let's look at an example of this now:
+ *
+ * Ext.define('MyApp.controller.Users', {
+ extend: 'Ext.app.Controller',
+
+ refs: [
+ {
+ ref: 'list',
+ selector: 'grid'
+ }
+ ],
+
+ init: function() {
+ this.control({
+ 'button': {
+ click: this.refreshGrid
+ }
+ });
+ },
+
+ refreshGrid: function() {
+ this.getList().store.load();
+ }
+ });
+ *
+ * This example assumes the existence of a {@link Ext.grid.Panel Grid} on the page, which contains a single button to
+ * refresh the Grid when clicked. In our refs array, we set up a reference to the grid. There are two parts to this -
+ * the 'selector', which is a {@link Ext.ComponentQuery ComponentQuery} selector which finds any grid on the page and
+ * assigns it to the reference 'list'.
+ *
+ * By giving the reference a name, we get a number of things for free. The first is the getList function that we use in
+ * the refreshGrid method above. This is generated automatically by the Controller based on the name of our ref, which
+ * was capitalized and prepended with get to go from 'list' to 'getList'.
+ *
+ * The way this works is that the first time getList is called by your code, the ComponentQuery selector is run and the
+ * first component that matches the selector ('grid' in this case) will be returned. All future calls to getList will
+ * use a cached reference to that grid. Usually it is advised to use a specific ComponentQuery selector that will only
+ * match a single View in your application (in the case above our selector will match any grid on the page).
+ *
+ * Bringing it all together, our init function is called when the application boots, at which time we call this.control
+ * to listen to any click on a {@link Ext.button.Button button} and call our refreshGrid function (again, this will
+ * match any button on the page so we advise a more specific selector than just 'button', but have left it this way for
+ * simplicity). When the button is clicked we use out getList function to refresh the grid.
+ *
+ * You can create any number of refs and control any number of components this way, simply adding more functions to
+ * your Controller as you go. For an example of real-world usage of Controllers see the Feed Viewer example in the
+ * examples/app/feed-viewer folder in the SDK download.
+ *
+ * <u>Generated getter methods</u>
+ *
+ * Refs aren't the only thing that generate convenient getter methods. Controllers often have to deal with Models and
+ * Stores so the framework offers a couple of easy ways to get access to those too. Let's look at another example:
+ *
+ * Ext.define('MyApp.controller.Users', {
+ extend: 'Ext.app.Controller',
+
+ models: ['User'],
+ stores: ['AllUsers', 'AdminUsers'],
+
+ init: function() {
+ var User = this.getUserModel(),
+ allUsers = this.getAllUsersStore();
+
+ var ed = new User({name: 'Ed'});
+ allUsers.add(ed);
+ }
+ });
+ *
+ * By specifying Models and Stores that the Controller cares about, it again dynamically loads them from the appropriate
+ * locations (app/model/User.js, app/store/AllUsers.js and app/store/AdminUsers.js in this case) and creates getter
+ * functions for them all. The example above will create a new User model instance and add it to the AllUsers Store.
+ * Of course, you could do anything in this function but in this case we just did something simple to demonstrate the
+ * functionality.
+ *
+ * <u>Further Reading</u>
+ *
+ * For more information about writing Ext JS 4 applications, please see the <a href="../guide/application_architecture">
+ * application architecture guide</a>. Also see the {@link Ext.app.Application} documentation.
+ *
+ * @markdown
+ * @docauthor Ed Spencer
+ */
+Ext.define('Ext.app.Controller', {
+ /**
+ * @cfg {Object} id The id of this controller. You can use this id when dispatching.
+ */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ onClassExtended: function(cls, data) {
+ var className = Ext.getClassName(cls),
+ match = className.match(/^(.*)\.controller\./);
+
+ if (match !== null) {
+ var namespace = Ext.Loader.getPrefix(className) || match[1],
+ onBeforeClassCreated = data.onBeforeClassCreated,
+ requires = [],
+ modules = ['model', 'view', 'store'],
+ prefix;
+
+ data.onBeforeClassCreated = function(cls, data) {
+ var i, ln, module,
+ items, j, subLn, item;
+
+ for (i = 0,ln = modules.length; i < ln; i++) {
+ module = modules[i];
+
+ items = Ext.Array.from(data[module + 's']);
+
+ for (j = 0,subLn = items.length; j < subLn; j++) {
+ item = items[j];
+
+ prefix = Ext.Loader.getPrefix(item);
+
+ if (prefix === '' || prefix === item) {
+ requires.push(namespace + '.' + module + '.' + item);
+ }
+ else {
+ requires.push(item);
+ }
+ }
+ }
+
+ Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
+ };
+ }
+ },
+
+ constructor: function(config) {
+ this.mixins.observable.constructor.call(this, config);
+
+ Ext.apply(this, config || {});
+
+ this.createGetters('model', this.models);
+ this.createGetters('store', this.stores);
+ this.createGetters('view', this.views);
+
+ if (this.refs) {
+ this.ref(this.refs);
+ }
+ },
+
+ // Template method
+ init: function(application) {},
+ // Template method
+ onLaunch: function(application) {},
+
+ createGetters: function(type, refs) {
+ type = Ext.String.capitalize(type);
+ Ext.Array.each(refs, function(ref) {
+ var fn = 'get',
+ parts = ref.split('.');
+
+ // Handle namespaced class names. E.g. feed.Add becomes getFeedAddView etc.
+ Ext.Array.each(parts, function(part) {
+ fn += Ext.String.capitalize(part);
+ });
+ fn += type;
+
+ if (!this[fn]) {
+ this[fn] = Ext.Function.pass(this['get' + type], [ref], this);
+ }
+ // Execute it right away
+ this[fn](ref);
+ },
+ this);
+ },
+
+ ref: function(refs) {
+ var me = this;
+ refs = Ext.Array.from(refs);
+ Ext.Array.each(refs, function(info) {
+ var ref = info.ref,
+ fn = 'get' + Ext.String.capitalize(ref);
+ if (!me[fn]) {
+ me[fn] = Ext.Function.pass(me.getRef, [ref, info], me);
+ }
+ });
+ },
+
+ getRef: function(ref, info, config) {
+ this.refCache = this.refCache || {};
+ info = info || {};
+ config = config || {};
+
+ Ext.apply(info, config);
+
+ if (info.forceCreate) {
+ return Ext.ComponentManager.create(info, 'component');
+ }
+
+ var me = this,
+ selector = info.selector,
+ cached = me.refCache[ref];
+
+ if (!cached) {
+ me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
+ if (!cached && info.autoCreate) {
+ me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
+ }
+ if (cached) {
+ cached.on('beforedestroy', function() {
+ me.refCache[ref] = null;
+ });
+ }
+ }
+
+ return cached;
+ },
+
+ control: function(selectors, listeners) {
+ this.application.control(selectors, listeners, this);
+ },
+
+ getController: function(name) {
+ return this.application.getController(name);
+ },
+
+ getStore: function(name) {
+ return this.application.getStore(name);
+ },
+
+ getModel: function(model) {
+ return this.application.getModel(model);
+ },
+
+ getView: function(view) {
+ return this.application.getView(view);
+ }
+});
+
+/**
+ * @class Ext.data.SortTypes
+ * This class defines a series of static methods that are used on a
+ * {@link Ext.data.Field} for performing sorting. The methods cast the
+ * underlying values into a data type that is appropriate for sorting on
+ * that particular field. If a {@link Ext.data.Field#type} is specified,
+ * the sortType will be set to a sane default if the sortType is not
+ * explicitly defined on the field. The sortType will make any necessary
+ * modifications to the value and return it.
+ * <ul>
+ * <li><b>asText</b> - Removes any tags and converts the value to a string</li>
+ * <li><b>asUCText</b> - Removes any tags and converts the value to an uppercase string</li>
+ * <li><b>asUCText</b> - Converts the value to an uppercase string</li>
+ * <li><b>asDate</b> - Converts the value into Unix epoch time</li>
+ * <li><b>asFloat</b> - Converts the value to a floating point number</li>
+ * <li><b>asInt</b> - Converts the value to an integer number</li>
+ * </ul>
+ * <p>
+ * It is also possible to create a custom sortType that can be used throughout
+ * an application.
+ * <pre><code>
+Ext.apply(Ext.data.SortTypes, {
+ asPerson: function(person){
+ // expects an object with a first and last name property
+ return person.lastName.toUpperCase() + person.firstName.toLowerCase();
+ }
+});
+
+Ext.define('Employee', {
+ extend: 'Ext.data.Model',
+ fields: [{
+ name: 'person',
+ sortType: 'asPerson'
+ }, {
+ name: 'salary',
+ type: 'float' // sortType set to asFloat
+ }]
+});
+ * </code></pre>
+ * </p>
+ * @singleton
+ * @docauthor Evan Trimboli <evan@sencha.com>
+ */
+Ext.define('Ext.data.SortTypes', {
+
+ singleton: true,
+
+ /**
+ * Default sort that does nothing
+ * @param {Mixed} s The value being converted
+ * @return {Mixed} The comparison value
+ */
+ none : function(s) {
+ return s;
+ },
+
+ /**
+ * The regular expression used to strip tags
+ * @type {RegExp}
+ * @property
+ */
+ stripTagsRE : /<\/?[^>]+>/gi,
+
+ /**
+ * Strips all HTML tags to sort on text only
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
+ */
+ asText : function(s) {
+ return String(s).replace(this.stripTagsRE, "");
+ },
+
+ /**
+ * Strips all HTML tags to sort on text only - Case insensitive
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
+ */
+ asUCText : function(s) {
+ return String(s).toUpperCase().replace(this.stripTagsRE, "");
+ },
+
+ /**
+ * Case insensitive string
+ * @param {Mixed} s The value being converted
+ * @return {String} The comparison value
+ */
+ asUCString : function(s) {
+ return String(s).toUpperCase();
+ },
+
+ /**
+ * Date sorting
+ * @param {Mixed} s The value being converted
+ * @return {Number} The comparison value
+ */
+ asDate : function(s) {
+ if(!s){
+ return 0;
+ }
+ if(Ext.isDate(s)){
+ return s.getTime();
+ }
+ return Date.parse(String(s));
+ },
+
+ /**
+ * Float sorting
+ * @param {Mixed} s The value being converted
+ * @return {Float} The comparison value
+ */
+ asFloat : function(s) {
+ var val = parseFloat(String(s).replace(/,/g, ""));
+ return isNaN(val) ? 0 : val;
+ },
+
+ /**
+ * Integer sorting
+ * @param {Mixed} s The value being converted
+ * @return {Number} The comparison value
+ */
+ asInt : function(s) {
+ var val = parseInt(String(s).replace(/,/g, ""), 10);
+ return isNaN(val) ? 0 : val;
+ }
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Errors
+ * @extends Ext.util.MixedCollection
+ *
+ * <p>Wraps a collection of validation error responses and provides convenient functions for
+ * accessing and errors for specific fields.</p>
+ *
+ * <p>Usually this class does not need to be instantiated directly - instances are instead created
+ * automatically when {@link Ext.data.Model#validate validate} on a model instance:</p>
+ *
+<pre><code>
+//validate some existing model instance - in this case it returned 2 failures messages
+var errors = myModel.validate();
+
+errors.isValid(); //false
+
+errors.length; //2
+errors.getByField('name'); // [{field: 'name', error: 'must be present'}]
+errors.getByField('title'); // [{field: 'title', error: 'is too short'}]
+</code></pre>
+ */
+Ext.define('Ext.data.Errors', {
+ extend: 'Ext.util.MixedCollection',
+
+ /**
+ * Returns true if there are no errors in the collection
+ * @return {Boolean}
+ */
+ isValid: function() {
+ return this.length === 0;
+ },
+
+ /**
+ * Returns all of the errors for the given field
+ * @param {String} fieldName The field to get errors for
+ * @return {Array} All errors for the given field
+ */
+ getByField: function(fieldName) {
+ var errors = [],
+ error, field, i;
+
+ for (i = 0; i < this.length; i++) {
+ error = this.items[i];
+
+ if (error.field == fieldName) {
+ errors.push(error);
+ }
+ }
+
+ return errors;
+ }
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Operation
+ * @extends Object
+ *
+ * <p>Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}.
+ * Operation objects are used to enable communication between Stores and Proxies. Application
+ * developers should rarely need to interact with Operation objects directly.</p>
+ *
+ * <p>Several Operations can be batched together in a {@link Ext.data.Batch batch}.</p>
+ *
+ * @constructor
+ * @param {Object} config Optional config object
+ */
+Ext.define('Ext.data.Operation', {
+ /**
+ * @cfg {Boolean} synchronous True if this Operation is to be executed synchronously (defaults to true). This
+ * property is inspected by a {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in
+ * parallel or not.
+ */
+ synchronous: true,
+
+ /**
+ * @cfg {String} action The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'
+ */
+ action: undefined,
+
+ /**
+ * @cfg {Array} filters Optional array of filter objects. Only applies to 'read' actions.
+ */
+ filters: undefined,
+
+ /**
+ * @cfg {Array} sorters Optional array of sorter objects. Only applies to 'read' actions.
+ */
+ sorters: undefined,
+
+ /**
+ * @cfg {Object} group Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
+ */
+ group: undefined,
+
+ /**
+ * @cfg {Number} start The start index (offset), used in paging when running a 'read' action.
+ */
+ start: undefined,
+
+ /**
+ * @cfg {Number} limit The number of records to load. Used on 'read' actions when paging is being used.
+ */
+ limit: undefined,
+
+ /**
+ * @cfg {Ext.data.Batch} batch The batch that this Operation is a part of (optional)
+ */
+ batch: undefined,
+
+ /**
+ * Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
+ * @property started
+ * @type Boolean
+ * @private
+ */
+ started: false,
+
+ /**
+ * Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
+ * @property running
+ * @type Boolean
+ * @private
+ */
+ running: false,
+
+ /**
+ * Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
+ * @property complete
+ * @type Boolean
+ * @private
+ */
+ complete: false,
+
+ /**
+ * Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
+ * or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
+ * {@link #wasSuccessful} to query success status.
+ * @property success
+ * @type Boolean
+ * @private
+ */
+ success: undefined,
+
+ /**
+ * Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
+ * @property exception
+ * @type Boolean
+ * @private
+ */
+ exception: false,
+
+ /**
+ * The error object passed when {@link #setException} was called. This could be any object or primitive.
+ * @property error
+ * @type Mixed
+ * @private
+ */
+ error: undefined,
+
+ constructor: function(config) {
+ Ext.apply(this, config || {});
+ },
+
+ /**
+ * Marks the Operation as started
+ */
+ setStarted: function() {
+ this.started = true;
+ this.running = true;
+ },
+
+ /**
+ * Marks the Operation as completed
+ */
+ setCompleted: function() {
+ this.complete = true;
+ this.running = false;
+ },
+
+ /**
+ * Marks the Operation as successful
+ */
+ setSuccessful: function() {
+ this.success = true;
+ },
+
+ /**
+ * Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
+ * @param {Mixed} error Optional error string/object
+ */
+ setException: function(error) {
+ this.exception = true;
+ this.success = false;
+ this.running = false;
+ this.error = error;
+ },
+
+ /**
+ * Returns true if this Operation encountered an exception (see also {@link #getError})
+ * @return {Boolean} True if there was an exception
+ */
+ hasException: function() {
+ return this.exception === true;
+ },
+
+ /**
+ * Returns the error string or object that was set using {@link #setException}
+ * @return {Mixed} The error object
+ */
+ getError: function() {
+ return this.error;
+ },
+
+ /**
+ * Returns an array of Ext.data.Model instances as set by the Proxy.
+ * @return {Array} Any loaded Records
+ */
+ getRecords: function() {
+ var resultSet = this.getResultSet();
+
+ return (resultSet === undefined ? this.records : resultSet.records);
+ },
+
+ /**
+ * Returns the ResultSet object (if set by the Proxy). This object will contain the {@link Ext.data.Model model} instances
+ * as well as meta data such as number of instances fetched, number available etc
+ * @return {Ext.data.ResultSet} The ResultSet object
+ */
+ getResultSet: function() {
+ return this.resultSet;
+ },
+
+ /**
+ * Returns true if the Operation has been started. Note that the Operation may have started AND completed,
+ * see {@link #isRunning} to test if the Operation is currently running.
+ * @return {Boolean} True if the Operation has started
+ */
+ isStarted: function() {
+ return this.started === true;
+ },
+
+ /**
+ * Returns true if the Operation has been started but has not yet completed.
+ * @return {Boolean} True if the Operation is currently running
+ */
+ isRunning: function() {
+ return this.running === true;
+ },
+
+ /**
+ * Returns true if the Operation has been completed
+ * @return {Boolean} True if the Operation is complete
+ */
+ isComplete: function() {
+ return this.complete === true;
+ },
+
+ /**
+ * Returns true if the Operation has completed and was successful
+ * @return {Boolean} True if successful
+ */
+ wasSuccessful: function() {
+ return this.isComplete() && this.success === true;
+ },
+
+ /**
+ * @private
+ * Associates this Operation with a Batch
+ * @param {Ext.data.Batch} batch The batch
+ */
+ setBatch: function(batch) {
+ this.batch = batch;
+ },
+
+ /**
+ * Checks whether this operation should cause writing to occur.
+ * @return {Boolean} Whether the operation should cause a write to occur.
+ */
+ allowWrite: function() {
+ return this.action != 'read';
+ }
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.validations
+ * @extends Object
+ *
+ * <p>This singleton contains a set of validation functions that can be used to validate any type
+ * of data. They are most often used in {@link Ext.data.Model Models}, where they are automatically
+ * set up and executed.</p>
+ */
+Ext.define('Ext.data.validations', {
+ singleton: true,
+
+ /**
+ * The default error message used when a presence validation fails
+ * @property presenceMessage
+ * @type String
+ */
+ presenceMessage: 'must be present',
+
+ /**
+ * The default error message used when a length validation fails
+ * @property lengthMessage
+ * @type String
+ */
+ lengthMessage: 'is the wrong length',
+
+ /**
+ * The default error message used when a format validation fails
+ * @property formatMessage
+ * @type Boolean
+ */
+ formatMessage: 'is the wrong format',
+
+ /**
+ * The default error message used when an inclusion validation fails
+ * @property inclusionMessage
+ * @type String
+ */
+ inclusionMessage: 'is not included in the list of acceptable values',
+
+ /**
+ * The default error message used when an exclusion validation fails
+ * @property exclusionMessage
+ * @type String
+ */
+ exclusionMessage: 'is not an acceptable value',
+
+ /**
+ * Validates that the given value is present
+ * @param {Object} config Optional config object
+ * @param {Mixed} value The value to validate
+ * @return {Boolean} True if validation passed
+ */
+ presence: function(config, value) {
+ if (value === undefined) {
+ value = config;
+ }
+
+ return !!value;
+ },
+
+ /**
+ * Returns true if the given value is between the configured min and max values
+ * @param {Object} config Optional config object
+ * @param {String} value The value to validate
+ * @return {Boolean} True if the value passes validation
+ */
+ length: function(config, value) {
+ if (value === undefined) {
+ return false;
+ }
+
+ var length = value.length,
+ min = config.min,
+ max = config.max;
+
+ if ((min && length < min) || (max && length > max)) {
+ return false;
+ } else {
+ return true;
+ }
+ },
+
+ /**
+ * Returns true if the given value passes validation against the configured {@link #matcher} regex
+ * @param {Object} config Optional config object
+ * @param {String} value The value to validate
+ * @return {Boolean} True if the value passes the format validation
+ */
+ format: function(config, value) {
+ return !!(config.matcher && config.matcher.test(value));
+ },
+
+ /**
+ * Validates that the given value is present in the configured {@link #list}
+ * @param {String} value The value to validate
+ * @return {Boolean} True if the value is present in the list
+ */
+ inclusion: function(config, value) {
+ return config.list && Ext.Array.indexOf(config.list,value) != -1;
+ },
+
+ /**
+ * Validates that the given value is present in the configured {@link #list}
+ * @param {Object} config Optional config object
+ * @param {String} value The value to validate
+ * @return {Boolean} True if the value is not present in the list
+ */
+ exclusion: function(config, value) {
+ return config.list && Ext.Array.indexOf(config.list,value) == -1;
+ }
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.ResultSet
+ * @extends Object
+ *
+ * <p>Simple wrapper class that represents a set of records returned by a Proxy.</p>
+ *
+ * @constructor
+ * Creates the new ResultSet
+ */
+Ext.define('Ext.data.ResultSet', {
+ /**
+ * @cfg {Boolean} loaded
+ * True if the records have already been loaded. This is only meaningful when dealing with
+ * SQL-backed proxies
+ */
+ loaded: true,
+
+ /**
+ * @cfg {Number} count
+ * The number of records in this ResultSet. Note that total may differ from this number
+ */
+ count: 0,
+
+ /**
+ * @cfg {Number} total
+ * The total number of records reported by the data source. This ResultSet may form a subset of
+ * those records (see count)
+ */
+ total: 0,
+
+ /**
+ * @cfg {Boolean} success
+ * True if the ResultSet loaded successfully, false if any errors were encountered
+ */
+ success: false,
+
+ /**
+ * @cfg {Array} records The array of record instances. Required
+ */
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+
+ /**
+ * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.total - use that instead
+ * @property totalRecords
+ * @type Mixed
+ */
+ this.totalRecords = this.total;
+
+ if (config.count === undefined) {
+ this.count = this.records.length;
+ }
+ }
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.writer.Writer
+ * @extends Object
+ *
+ * <p>Base Writer class used by most subclasses of {@link Ext.data.proxy.Server}. This class is
+ * responsible for taking a set of {@link Ext.data.Operation} objects and a {@link Ext.data.Request}
+ * object and modifying that request based on the Operations.</p>
+ *
+ * <p>For example a Ext.data.writer.Json would format the Operations and their {@link Ext.data.Model}
+ * instances based on the config options passed to the JsonWriter's constructor.</p>
+ *
+ * <p>Writers are not needed for any kind of local storage - whether via a
+ * {@link Ext.data.proxy.WebStorage Web Storage proxy} (see {@link Ext.data.proxy.LocalStorage localStorage}
+ * and {@link Ext.data.proxy.SessionStorage sessionStorage}) or just in memory via a
+ * {@link Ext.data.proxy.Memory MemoryProxy}.</p>
+ *
+ * @constructor
+ * @param {Object} config Optional config object
+ */
+Ext.define('Ext.data.writer.Writer', {
+ alias: 'writer.base',
+ alternateClassName: ['Ext.data.DataWriter', 'Ext.data.Writer'],
+
+ /**
+ * @cfg {Boolean} writeAllFields True to write all fields from the record to the server. If set to false it
+ * will only send the fields that were modified. Defaults to <tt>true</tt>. Note that any fields that have
+ * {@link Ext.data.Field#persist} set to false will still be ignored.
+ */
+ writeAllFields: true,
+
+ /**
+ * @cfg {String} nameProperty This property is used to read the key for each value that will be sent to the server.
+ * For example:
+ * <pre><code>
+Ext.define('Person', {
+ extend: 'Ext.data.Model',
+ fields: [{
+ name: 'first',
+ mapping: 'firstName'
+ }, {
+ name: 'last',
+ mapping: 'lastName'
+ }, {
+ name: 'age'
+ }]
+});
+new Ext.data.writer.Writer({
+ writeAllFields: true,
+ nameProperty: 'mapping'
+});
+
+// This will be sent to the server
+{
+ firstName: 'first name value',
+ lastName: 'last name value',
+ age: 1
+}
+
+ * </code></pre>
+ * Defaults to <tt>name</tt>. If the value is not present, the field name will always be used.
+ */
+ nameProperty: 'name',
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+ },
+
+ /**
+ * Prepares a Proxy's Ext.data.Request object
+ * @param {Ext.data.Request} request The request object
+ * @return {Ext.data.Request} The modified request object
+ */
+ write: function(request) {
+ var operation = request.operation,
+ records = operation.records || [],
+ len = records.length,
+ i = 0,
+ data = [];
+
+ for (; i < len; i++) {
+ data.push(this.getRecordData(records[i]));
+ }
+ return this.writeRecords(request, data);
+ },
+
+ /**
+ * Formats the data for each record before sending it to the server. This
+ * method should be overridden to format the data in a way that differs from the default.
+ * @param {Object} record The record that we are writing to the server.
+ * @return {Object} An object literal of name/value keys to be written to the server.
+ * By default this method returns the data property on the record.
+ */
+ getRecordData: function(record) {
+ var isPhantom = record.phantom === true,
+ writeAll = this.writeAllFields || isPhantom,
+ nameProperty = this.nameProperty,
+ fields = record.fields,
+ data = {},
+ changes,
+ name,
+ field,
+ key;
+
+ if (writeAll) {
+ fields.each(function(field){
+ if (field.persist) {
+ name = field[nameProperty] || field.name;
+ data[name] = record.get(field.name);
+ }
+ });
+ } else {
+ // Only write the changes
+ changes = record.getChanges();
+ for (key in changes) {
+ if (changes.hasOwnProperty(key)) {
+ field = fields.get(key);
+ name = field[nameProperty] || field.name;
+ data[name] = changes[key];
+ }
+ }
+ if (!isPhantom) {
+ // always include the id for non phantoms
+ data[record.idProperty] = record.getId();
+ }
+ }
+ return data;
+ }
+});
+
+/**
+ * @class Ext.util.Floating
+ * A mixin to add floating capability to a Component
+ */
+Ext.define('Ext.util.Floating', {
+
+ uses: ['Ext.Layer', 'Ext.window.Window'],
+
+ /**
+ * @cfg {Boolean} focusOnToFront
+ * Specifies whether the floated component should be automatically {@link #focus focused} when it is
+ * {@link #toFront brought to the front}. Defaults to true.
+ */
+ focusOnToFront: true,
+
+ /**
+ * @cfg {String/Boolean} shadow Specifies whether the floating component should be given a shadow. Set to
+ * <tt>true</tt> to automatically create an {@link Ext.Shadow}, or a string indicating the
+ * shadow's display {@link Ext.Shadow#mode}. Set to <tt>false</tt> to disable the shadow.
+ * (Defaults to <tt>'sides'</tt>.)
+ */
+ shadow: 'sides',
+
+ constructor: function(config) {
+ this.floating = true;
+ this.el = Ext.create('Ext.Layer', Ext.apply({}, config, {
+ hideMode: this.hideMode,
+ hidden: this.hidden,
+ shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
+ shadowOffset: this.shadowOffset,
+ constrain: false,
+ shim: this.shim === false ? false : undefined
+ }), this.el);
+ },
+
+ onFloatRender: function() {
+ var me = this;
+ me.zIndexParent = me.getZIndexParent();
+ me.setFloatParent(me.ownerCt);
+ delete me.ownerCt;
+
+ if (me.zIndexParent) {
+ me.zIndexParent.registerFloatingItem(me);
+ } else {
+ Ext.WindowManager.register(me);
+ }
+ },
+
+ setFloatParent: function(floatParent) {
+ var me = this;
+
+ // Remove listeners from previous floatParent
+ if (me.floatParent) {
+ me.mun(me.floatParent, {
+ hide: me.onFloatParentHide,
+ show: me.onFloatParentShow,
+ scope: me
+ });
+ }
+
+ me.floatParent = floatParent;
+
+ // Floating Components as children of Containers must hide when their parent hides.
+ if (floatParent) {
+ me.mon(me.floatParent, {
+ hide: me.onFloatParentHide,
+ show: me.onFloatParentShow,
+ scope: me
+ });
+ }
+
+ // If a floating Component is configured to be constrained, but has no configured
+ // constrainTo setting, set its constrainTo to be it's ownerCt before rendering.
+ if ((me.constrain || me.constrainHeader) && !me.constrainTo) {
+ me.constrainTo = floatParent ? floatParent.getTargetEl() : me.container;
+ }
+ },
+
+ onFloatParentHide: function() {
+ this.showOnParentShow = this.isVisible();
+ this.hide();
+ },
+
+ onFloatParentShow: function() {
+ if (this.showOnParentShow) {
+ delete this.showOnParentShow;
+ this.show();
+ }
+ },
+
+ /**
+ * @private
+ * <p>Finds the ancestor Container responsible for allocating zIndexes for the passed Component.</p>
+ * <p>That will be the outermost floating Container (a Container which has no ownerCt and has floating:true).</p>
+ * <p>If we have no ancestors, or we walk all the way up to the document body, there's no zIndexParent,
+ * and the global Ext.WindowManager will be used.</p>
+ */
+ getZIndexParent: function() {
+ var p = this.ownerCt,
+ c;
+
+ if (p) {
+ while (p) {
+ c = p;
+ p = p.ownerCt;
+ }
+ if (c.floating) {
+ return c;
+ }
+ }
+ },
+
+ // private
+ // z-index is managed by the zIndexManager and may be overwritten at any time.
+ // Returns the next z-index to be used.
+ // If this is a Container, then it will have rebased any managed floating Components,
+ // and so the next available z-index will be approximately 10000 above that.
+ setZIndex: function(index) {
+ var me = this;
+ this.el.setZIndex(index);
+
+ // Next item goes 10 above;
+ index += 10;
+
+ // When a Container with floating items has its z-index set, it rebases any floating items it is managing.
+ // The returned value is a round number approximately 10000 above the last z-index used.
+ if (me.floatingItems) {
+ index = Math.floor(me.floatingItems.setBase(index) / 100) * 100 + 10000;
+ }
+ return index;
+ },
+
+ /**
+ * <p>Moves this floating Component into a constrain region.</p>
+ * <p>By default, this Component is constrained to be within the container it was added to, or the element
+ * it was rendered to.</p>
+ * <p>An alternative constraint may be passed.</p>
+ * @param {Mixed} constrainTo Optional. The Element or {@link Ext.util.Region Region} into which this Component is to be constrained.
+ */
+ doConstrain: function(constrainTo) {
+ var me = this,
+ constrainEl,
+ vector,
+ xy;
+
+ if (me.constrain || me.constrainHeader) {
+ if (me.constrainHeader) {
+ constrainEl = me.header.el;
+ } else {
+ constrainEl = me.el;
+ }
+ vector = constrainEl.getConstrainVector(constrainTo || (me.floatParent && me.floatParent.getTargetEl()) || me.container);
+ if (vector) {
+ xy = me.getPosition();
+ xy[0] += vector[0];
+ xy[1] += vector[1];
+ me.setPosition(xy);
+ }
+ }
+ },
+
+ /**
+ * Aligns this floating Component to the specified element
+ * @param {Mixed} element The element or {@link Ext.Component} to align to. If passing a component, it must
+ * be a omponent instance. If a string id is passed, it will be used as an element id.
+ * @param {String} position (optional, defaults to "tl-bl?") The position to align to (see {@link Ext.core.Element#alignTo} for more details).
+ * @param {Array} offsets (optional) Offset the positioning by [x, y]
+ * @return {Component} this
+ */
+ alignTo: function(element, position, offsets) {
+ if (element.isComponent) {
+ element = element.getEl();
+ }
+ var xy = this.el.getAlignToXY(element, position, offsets);
+ this.setPagePosition(xy);
+ return this;
+ },
+
+ /**
+ * <p>Brings this floating Component to the front of any other visible, floating Components managed by the same {@link Ext.ZIndexManager ZIndexManager}</p>
+ * <p>If this Component is modal, inserts the modal mask just below this Component in the z-index stack.</p>
+ * @param {Boolean} preventFocus (optional) Specify <code>true</code> to prevent the Component from being focused.
+ * @return {Component} this
+ */
+ toFront: function(preventFocus) {
+ var me = this;
+
+ // Find the floating Component which provides the base for this Component's zIndexing.
+ // That must move to front to then be able to rebase its zIndex stack and move this to the front
+ if (me.zIndexParent) {
+ me.zIndexParent.toFront(true);
+ }
+ if (me.zIndexManager.bringToFront(me)) {
+ if (!Ext.isDefined(preventFocus)) {
+ preventFocus = !me.focusOnToFront;
+ }
+ if (!preventFocus) {
+ // Kick off a delayed focus request.
+ // If another floating Component is toFronted before the delay expires
+ // this will not receive focus.
+ me.focus(false, true);
+ }
+ }
+ return me;
+ },
+
+ /**
+ * <p>This method is called internally by {@link Ext.ZIndexManager} to signal that a floating
+ * Component has either been moved to the top of its zIndex stack, or pushed from the top of its zIndex stack.</p>
+ * <p>If a <i>Window</i> is superceded by another Window, deactivating it hides its shadow.</p>
+ * <p>This method also fires the {@link #activate} or {@link #deactivate} event depending on which action occurred.</p>
+ * @param {Boolean} active True to activate the Component, false to deactivate it (defaults to false)
+ * @param {Component} newActive The newly active Component which is taking over topmost zIndex position.
+ */
+ setActive: function(active, newActive) {
+ if (active) {
+ if ((this instanceof Ext.window.Window) && !this.maximized) {
+ this.el.enableShadow(true);
+ }
+ this.fireEvent('activate', this);
+ } else {
+ // Only the *Windows* in a zIndex stack share a shadow. All other types of floaters
+ // can keep their shadows all the time
+ if ((this instanceof Ext.window.Window) && (newActive instanceof Ext.window.Window)) {
+ this.el.disableShadow();
+ }
+ this.fireEvent('deactivate', this);
+ }
+ },
+
+ /**
+ * Sends this Component to the back of (lower z-index than) any other visible windows
+ * @return {Component} this
+ */
+ toBack: function() {
+ this.zIndexManager.sendToBack(this);
+ return this;
+ },
+
+ /**
+ * Center this Component in its container.
+ * @return {Component} this
+ */
+ center: function() {
+ var xy = this.el.getAlignToXY(this.container, 'c-c');
+ this.setPagePosition(xy);
+ return this;
+ },
+
+ // private
+ syncShadow : function(){
+ if (this.floating) {
+ this.el.sync(true);
+ }
+ },
+
+ // private
+ fitContainer: function() {
+ var parent = this.floatParent,
+ container = parent ? parent.getTargetEl() : this.container,
+ size = container.getViewSize(false);
+
+ this.setSize(size);
+ }
+});
+/**
+ * @class Ext.layout.container.AbstractContainer
+ * @extends Ext.layout.Layout
+ * Please refer to sub classes documentation
+ */
+
+Ext.define('Ext.layout.container.AbstractContainer', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.Layout',
+
+ /* End Definitions */
+
+ type: 'container',
+
+ fixedLayout: true,
+
+ // @private
+ managedHeight: true,
+ // @private
+ managedWidth: true,
+
+ /**
+ * @cfg {Boolean} bindToOwnerCtComponent
+ * Flag to notify the ownerCt Component on afterLayout of a change
+ */
+ bindToOwnerCtComponent: false,
+
+ /**
+ * @cfg {Boolean} bindToOwnerCtContainer
+ * Flag to notify the ownerCt Container on afterLayout of a change
+ */
+ bindToOwnerCtContainer: false,
+
+ /**
+ * @cfg {String} itemCls
+ * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
+ * customized styles to the container or any of its children using standard CSS rules. See
+ * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.</p>
+ * </p>
+ */
+
+ isManaged: function(dimension) {
+ dimension = Ext.String.capitalize(dimension);
+ var me = this,
+ child = me,
+ managed = me['managed' + dimension],
+ ancestor = me.owner.ownerCt;
+
+ if (ancestor && ancestor.layout) {
+ while (ancestor && ancestor.layout) {
+ if (managed === false || ancestor.layout['managed' + dimension] === false) {
+ managed = false;
+ break;
+ }
+ ancestor = ancestor.ownerCt;
+ }
+ }
+ return managed;
+ },
+
+ layout: function() {
+ var me = this,
+ owner = me.owner;
+ if (Ext.isNumber(owner.height) || owner.isViewport) {
+ me.managedHeight = false;
+ }
+ if (Ext.isNumber(owner.width) || owner.isViewport) {
+ me.managedWidth = false;
+ }
+ me.callParent(arguments);
+ },
+
+ /**
+ * Set the size of an item within the Container. We should always use setCalculatedSize.
+ * @private
+ */
+ setItemSize: function(item, width, height) {
+ if (Ext.isObject(width)) {
+ height = width.height;
+ width = width.width;
+ }
+ item.setCalculatedSize(width, height, this.owner);
+ },
+
+ /**
+ * <p>Returns an array of child components either for a render phase (Performed in the beforeLayout method of the layout's
+ * base class), or the layout phase (onLayout).</p>
+ * @return {Array} of child components
+ */
+ getLayoutItems: function() {
+ return this.owner && this.owner.items && this.owner.items.items || [];
+ },
+
+ afterLayout: function() {
+ this.owner.afterLayout(this);
+ },
+ /**
+ * Returns the owner component's resize element.
+ * @return {Ext.core.Element}
+ */
+ getTarget: function() {
+ return this.owner.getTargetEl();
+ },
+ /**
+ * <p>Returns the element into which rendering must take place. Defaults to the owner Container's {@link Ext.AbstractComponent#targetEl}.</p>
+ * May be overridden in layout managers which implement an inner element.
+ * @return {Ext.core.Element}
+ */
+ getRenderTarget: function() {
+ return this.owner.getTargetEl();
+ }
+});
+
+/**
+ * @class Ext.ZIndexManager
+ * <p>A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
+ * and Component activation behavior, including masking below the active (topmost) Component.</p>
+ * <p>{@link Ext.Component#floating Floating} Components which are rendered directly into the document (Such as {@link Ext.window.Window Window}s which are
+ * {@link Ext.Component#show show}n are managed by a {@link Ext.WindowManager global instance}.</p>
+ * <p>{@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating} <i>Containers</i>
+ * (For example a {Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window}, or a {@link Ext.menu.Menu Menu}),
+ * are managed by a ZIndexManager owned by that floating Container. So ComboBox dropdowns within Windows will have managed z-indices
+ * guaranteed to be correct, relative to the Window.</p>
+ * @constructor
+ */
+Ext.define('Ext.ZIndexManager', {
+
+ alternateClassName: 'Ext.WindowGroup',
+
+ statics: {
+ zBase : 9000
+ },
+
+ constructor: function(container) {
+ var me = this;
+
+ me.list = {};
+ me.zIndexStack = [];
+ me.front = null;
+
+ if (container) {
+
+ // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
+ if (container.isContainer) {
+ container.on('resize', me._onContainerResize, me);
+ me.zseed = Ext.Number.from(container.getEl().getStyle('zIndex'), me.getNextZSeed());
+ // The containing element we will be dealing with (eg masking) is the content target
+ me.targetEl = container.getTargetEl();
+ me.container = container;
+ }
+ // This is the ZIndexManager for a DOM element
+ else {
+ Ext.EventManager.onWindowResize(me._onContainerResize, me);
+ me.zseed = me.getNextZSeed();
+ me.targetEl = Ext.get(container);
+ }
+ }
+ // No container passed means we are the global WindowManager. Our target is the doc body.
+ // DOM must be ready to collect that ref.
+ else {
+ Ext.EventManager.onWindowResize(me._onContainerResize, me);
+ me.zseed = me.getNextZSeed();
+ Ext.onDocumentReady(function() {
+ me.targetEl = Ext.getBody();
+ });
+ }
+ },
+
+ getNextZSeed: function() {
+ return (Ext.ZIndexManager.zBase += 10000);
+ },
+
+ setBase: function(baseZIndex) {
+ this.zseed = baseZIndex;
+ return this.assignZIndices();
+ },
+
+ // private
+ assignZIndices: function() {
+ var a = this.zIndexStack,
+ len = a.length,
+ i = 0,
+ zIndex = this.zseed,
+ comp;
+
+ for (; i < len; i++) {
+ comp = a[i];
+ if (comp && !comp.hidden) {
+
+ // Setting the zIndex of a Component returns the topmost zIndex consumed by
+ // that Component.
+ // If it's just a plain floating Component such as a BoundList, then the
+ // return value is the passed value plus 10, ready for the next item.
+ // If a floating *Container* has its zIndex set, it re-orders its managed
+ // floating children, starting from that new base, and returns a value 10000 above
+ // the highest zIndex which it allocates.
+ zIndex = comp.setZIndex(zIndex);
+ }
+ }
+ this._activateLast();
+ return zIndex;
+ },
+
+ // private
+ _setActiveChild: function(comp) {
+ if (comp != this.front) {
+
+ if (this.front) {
+ this.front.setActive(false, comp);
+ }
+ this.front = comp;
+ if (comp) {
+ comp.setActive(true);
+ if (comp.modal) {
+ this._showModalMask(comp.el.getStyle('zIndex') - 4);
+ }
+ }
+ }
+ },
+
+ // private
+ _activateLast: function(justHidden) {
+ var comp,
+ lastActivated = false,
+ i;
+
+ // Go down through the z-index stack.
+ // Activate the next visible one down.
+ // Keep going down to find the next visible modal one to shift the modal mask down under
+ for (i = this.zIndexStack.length-1; i >= 0; --i) {
+ comp = this.zIndexStack[i];
+ if (!comp.hidden) {
+ if (!lastActivated) {
+ this._setActiveChild(comp);
+ lastActivated = true;
+ }
+
+ // Move any modal mask down to just under the next modal floater down the stack
+ if (comp.modal) {
+ this._showModalMask(comp.el.getStyle('zIndex') - 4);
+ return;
+ }
+ }
+ }
+
+ // none to activate, so there must be no modal mask.
+ // And clear the currently active property
+ this._hideModalMask();
+ if (!lastActivated) {
+ this._setActiveChild(null);
+ }
+ },
+
+ _showModalMask: function(zIndex) {
+ if (!this.mask) {
+ this.mask = this.targetEl.createChild({
+ cls: Ext.baseCSSPrefix + 'mask'
+ });
+ this.mask.setVisibilityMode(Ext.core.Element.DISPLAY);
+ this.mask.on('click', this._onMaskClick, this);
+ }
+ Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
+ this.mask.setSize(this.targetEl.getViewSize(true));
+ this.mask.setStyle('zIndex', zIndex);
+ this.mask.show();
+ },
+
+ _hideModalMask: function() {
+ if (this.mask) {
+ Ext.getBody().removeCls(Ext.baseCSSPrefix + 'body-masked');
+ this.mask.hide();
+ }
+ },
+
+ _onMaskClick: function() {
+ if (this.front) {
+ this.front.focus();
+ }
+ },
+
+ _onContainerResize: function() {
+ if (this.mask && this.mask.isVisible()) {
+ this.mask.setSize(this.targetEl.getViewSize(true));
+ }
+ },
+
+ /**
+ * <p>Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
+ * need to be called under normal circumstances. Floating Components (such as Windows, BoundLists and Menus) are automatically registered
+ * with a {@link Ext.Component#zIndexManager zIndexManager} at render time.</p>
+ * <p>Where this may be useful is moving Windows between two ZIndexManagers. For example,
+ * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
+ * ZIndexManager in the desktop sample app:</p><code><pre>
+MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
+</pre></code>
+ * @param {Component} comp The Component to register.
+ */
+ register : function(comp) {
+ if (comp.zIndexManager) {
+ comp.zIndexManager.unregister(comp);
+ }
+ comp.zIndexManager = this;
+
+ this.list[comp.id] = comp;
+ this.zIndexStack.push(comp);
+ comp.on('hide', this._activateLast, this);
+ },
+
+ /**
+ * <p>Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
+ * need to be called. Components are automatically unregistered upon destruction.
+ * See {@link #register}.</p>
+ * @param {Component} comp The Component to unregister.
+ */
+ unregister : function(comp) {
+ delete comp.zIndexManager;
+ if (this.list && this.list[comp.id]) {
+ delete this.list[comp.id];
+ comp.un('hide', this._activateLast);
+ Ext.Array.remove(this.zIndexStack, comp);
+
+ // Destruction requires that the topmost visible floater be activated. Same as hiding.
+ this._activateLast(comp);
+ }
+ },
+
+ /**
+ * Gets a registered Component by id.
+ * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
+ * @return {Ext.Component}
+ */
+ get : function(id) {
+ return typeof id == "object" ? id : this.list[id];
+ },
+
+ /**
+ * Brings the specified Component to the front of any other active Components in this ZIndexManager.
+ * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
+ * @return {Boolean} True if the dialog was brought to the front, else false
+ * if it was already in front
+ */
+ bringToFront : function(comp) {
+ comp = this.get(comp);
+ if (comp != this.front) {
+ Ext.Array.remove(this.zIndexStack, comp);
+ this.zIndexStack.push(comp);
+ this.assignZIndices();
+ return true;
+ }
+ if (comp.modal) {
+ Ext.getBody().addCls(Ext.baseCSSPrefix + 'body-masked');
+ this.mask.setSize(Ext.core.Element.getViewWidth(true), Ext.core.Element.getViewHeight(true));
+ this.mask.show();
+ }
+ return false;
+ },
+
+ /**
+ * Sends the specified Component to the back of other active Components in this ZIndexManager.
+ * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
+ * @return {Ext.Component} The Component
+ */
+ sendToBack : function(comp) {
+ comp = this.get(comp);
+ Ext.Array.remove(this.zIndexStack, comp);
+ this.zIndexStack.unshift(comp);
+ this.assignZIndices();
+ return comp;
+ },
+
+ /**
+ * Hides all Components managed by this ZIndexManager.
+ */
+ hideAll : function() {
+ for (var id in this.list) {
+ if (this.list[id].isComponent && this.list[id].isVisible()) {
+ this.list[id].hide();
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Temporarily hides all currently visible managed Components. This is for when
+ * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
+ * they should all be hidden just for the duration of the drag.
+ */
+ hide: function() {
+ var i = 0,
+ ln = this.zIndexStack.length,
+ comp;
+
+ this.tempHidden = [];
+ for (; i < ln; i++) {
+ comp = this.zIndexStack[i];
+ if (comp.isVisible()) {
+ this.tempHidden.push(comp);
+ comp.hide();
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Restores temporarily hidden managed Components to visibility.
+ */
+ show: function() {
+ var i = 0,
+ ln = this.tempHidden.length,
+ comp,
+ x,
+ y;
+
+ for (; i < ln; i++) {
+ comp = this.tempHidden[i];
+ x = comp.x;
+ y = comp.y;
+ comp.show();
+ comp.setPosition(x, y);
+ }
+ delete this.tempHidden;
+ },
+
+ /**
+ * Gets the currently-active Component in this ZIndexManager.
+ * @return {Ext.Component} The active Component
+ */
+ getActive : function() {
+ return this.front;
+ },
+
+ /**
+ * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
+ * The function should accept a single {@link Ext.Component} reference as its only argument and should
+ * return true if the Component matches the search criteria, otherwise it should return false.
+ * @param {Function} fn The search function
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Component being tested.
+ * that gets passed to the function if not specified)
+ * @return {Array} An array of zero or more matching windows
+ */
+ getBy : function(fn, scope) {
+ var r = [],
+ i = 0,
+ len = this.zIndexStack.length,
+ comp;
+
+ for (; i < len; i++) {
+ comp = this.zIndexStack[i];
+ if (fn.call(scope||comp, comp) !== false) {
+ r.push(comp);
+ }
+ }
+ return r;
+ },
+
+ /**
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
+ * Component as the only parameter. Returning false from the function will stop the iteration.
+ * @param {Function} fn The function to execute for each item
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Component in the iteration.
+ */
+ each : function(fn, scope) {
+ var comp;
+ for (var id in this.list) {
+ comp = this.list[id];
+ if (comp.isComponent && fn.call(scope || comp, comp) === false) {
+ return;
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
+ * Component as the only parameter. Returning false from the function will stop the iteration.
+ * The components are passed to the function starting at the bottom and proceeding to the top.
+ * @param {Function} fn The function to execute for each item
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
+ * is executed. Defaults to the current Component in the iteration.
+ */
+ eachBottomUp: function (fn, scope) {
+ var comp,
+ stack = this.zIndexStack,
+ i, n;
+
+ for (i = 0, n = stack.length ; i < n; i++) {
+ comp = stack[i];
+ if (comp.isComponent && fn.call(scope || comp, comp) === false) {
+ return;
+ }
+ }
+ },
+
+ /**
+ * Executes the specified function once for every Component in this ZIndexManager, passing each
+ * Component as the only parameter. Returning false from the function will stop the iteration.
+ * The components are passed to the function starting at the top and proceeding to the bottom.
+ * @param {Function} fn The function to execute for each item
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function
+ * is executed. Defaults to the current Component in the iteration.
+ */
+ eachTopDown: function (fn, scope) {
+ var comp,
+ stack = this.zIndexStack,
+ i;
+
+ for (i = stack.length ; i-- > 0; ) {
+ comp = stack[i];
+ if (comp.isComponent && fn.call(scope || comp, comp) === false) {
+ return;
+ }
+ }
+ },
+
+ destroy: function() {
+ delete this.zIndexStack;
+ delete this.list;
+ delete this.container;
+ delete this.targetEl;
+ }
+}, function() {
+ /**
+ * @class Ext.WindowManager
+ * @extends Ext.ZIndexManager
+ * <p>The default global floating Component group that is available automatically.</p>
+ * <p>This manages instances of floating Components which were rendered programatically without
+ * being added to a {@link Ext.container.Container Container}, and for floating Components which were added into non-floating Containers.</p>
+ * <p><i>Floating</i> Containers create their own instance of ZIndexManager, and floating Components added at any depth below
+ * there are managed by that ZIndexManager.</p>
+ * @singleton
+ */
+ Ext.WindowManager = Ext.WindowMgr = new this();
+});
+
+/**
+ * @class Ext.layout.container.boxOverflow.None
+ * @extends Object
+ * @private
+ * Base class for Box Layout overflow handlers. These specialized classes are invoked when a Box Layout
+ * (either an HBox or a VBox) has child items that are either too wide (for HBox) or too tall (for VBox)
+ * for its container.
+ */
+Ext.define('Ext.layout.container.boxOverflow.None', {
+
+ alternateClassName: 'Ext.layout.boxOverflow.None',
+
+ constructor: function(layout, config) {
+ this.layout = layout;
+ Ext.apply(this, config || {});
+ },
+
+ handleOverflow: Ext.emptyFn,
+
+ clearOverflow: Ext.emptyFn,
+
+ /**
+ * @private
+ * Normalizes an item reference, string id or numerical index into a reference to the item
+ * @param {Ext.Component|String|Number} item The item reference, id or index
+ * @return {Ext.Component} The item
+ */
+ getItem: function(item) {
+ return this.layout.owner.getComponent(item);
+ }
+});
+/**
+ * @class Ext.util.KeyMap
+ * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
+ * The constructor accepts the same config object as defined by {@link #addBinding}.
+ * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
+ * combination it will call the function with this signature (if the match is a multi-key
+ * combination the callback will still be called only once): (String key, Ext.EventObject e)
+ * A KeyMap can also handle a string representation of keys.<br />
+ * Usage:
+ <pre><code>
+// map one key by key code
+var map = new Ext.util.KeyMap("my-element", {
+ key: 13, // or Ext.EventObject.ENTER
+ fn: myHandler,
+ scope: myObject
+});
+
+// map multiple keys to one action by string
+var map = new Ext.util.KeyMap("my-element", {
+ key: "a\r\n\t",
+ fn: myHandler,
+ scope: myObject
+});
+
+// map multiple keys to multiple actions by strings and array of codes
+var map = new Ext.util.KeyMap("my-element", [
+ {
+ key: [10,13],
+ fn: function(){ alert("Return was pressed"); }
+ }, {
+ key: "abc",
+ fn: function(){ alert('a, b or c was pressed'); }
+ }, {
+ key: "\t",
+ ctrl:true,
+ shift:true,
+ fn: function(){ alert('Control + shift + tab was pressed.'); }
+ }
+]);
+</code></pre>
+ * <b>Note: A KeyMap starts enabled</b>
+ * @constructor
+ * @param {Mixed} el The element to bind to
+ * @param {Object} binding The binding (see {@link #addBinding})
+ * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
+ */
+Ext.define('Ext.util.KeyMap', {
+ alternateClassName: 'Ext.KeyMap',
+
+ constructor: function(el, binding, eventName){
+ var me = this;
+
+ Ext.apply(me, {
+ el: Ext.get(el),
+ eventName: eventName || me.eventName,
+ bindings: []
+ });
+ if (binding) {
+ me.addBinding(binding);
+ }
+ me.enable();
+ },
+
+ eventName: 'keydown',
+
+ /**
+ * Add a new binding to this KeyMap. The following config object properties are supported:
+ * <pre>
+Property Type Description
+---------- --------------- ----------------------------------------------------------------------
+key String/Array A single keycode or an array of keycodes to handle
+shift Boolean True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
+ctrl Boolean True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
+alt Boolean True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
+handler Function The function to call when KeyMap finds the expected key combination
+fn Function Alias of handler (for backwards-compatibility)
+scope Object The scope of the callback function
+defaultEventAction String A default action to apply to the event. Possible values are: stopEvent, stopPropagation, preventDefault. If no value is set no action is performed.
+</pre>
+ *
+ * Usage:
+ * <pre><code>
+// Create a KeyMap
+var map = new Ext.util.KeyMap(document, {
+ key: Ext.EventObject.ENTER,
+ fn: handleKey,
+ scope: this
+});
+
+//Add a new binding to the existing KeyMap later
+map.addBinding({
+ key: 'abc',
+ shift: true,
+ fn: handleKey,
+ scope: this
+});
+</code></pre>
+ * @param {Object/Array} binding A single KeyMap config or an array of configs
+ */
+ addBinding : function(binding){
+ if (Ext.isArray(binding)) {
+ Ext.each(binding, this.addBinding, this);
+ return;
+ }
+
+ var keyCode = binding.key,
+ processed = false,
+ key,
+ keys,
+ keyString,
+ i,
+ len;
+
+ if (Ext.isString(keyCode)) {
+ keys = [];
+ keyString = keyCode.toLowerCase();
+
+ for (i = 0, len = keyString.length; i < len; ++i){
+ keys.push(keyString.charCodeAt(i));
+ }
+ keyCode = keys;
+ processed = true;
+ }
+
+ if (!Ext.isArray(keyCode)) {
+ keyCode = [keyCode];
+ }
+
+ if (!processed) {
+ for (i = 0, len = keyCode.length; i < len; ++i) {
+ key = keyCode[i];
+ if (Ext.isString(key)) {
+ keyCode[i] = key.toLowerCase().charCodeAt(0);
+ }
+ }
+ }
+
+ this.bindings.push(Ext.apply({
+ keyCode: keyCode
+ }, binding));
+ },
+
+ /**
+ * Process any keydown events on the element
+ * @private
+ * @param {Ext.EventObject} event
+ */
+ handleKeyDown: function(event) {
+ if (this.enabled) { //just in case
+ var bindings = this.bindings,
+ i = 0,
+ len = bindings.length;
+
+ event = this.processEvent(event);
+ for(; i < len; ++i){
+ this.processBinding(bindings[i], event);
+ }
+ }
+ },
+
+ /**
+ * Ugly hack to allow this class to be tested. Currently WebKit gives
+ * no way to raise a key event properly with both
+ * a) A keycode
+ * b) The alt/ctrl/shift modifiers
+ * So we have to simulate them here. Yuk!
+ * This is a stub method intended to be overridden by tests.
+ * More info: https://bugs.webkit.org/show_bug.cgi?id=16735
+ * @private
+ */
+ processEvent: function(event){
+ return event;
+ },
+
+ /**
+ * Process a particular binding and fire the handler if necessary.
+ * @private
+ * @param {Object} binding The binding information
+ * @param {Ext.EventObject} event
+ */
+ processBinding: function(binding, event){
+ if (this.checkModifiers(binding, event)) {
+ var key = event.getKey(),
+ handler = binding.fn || binding.handler,
+ scope = binding.scope || this,
+ keyCode = binding.keyCode,
+ defaultEventAction = binding.defaultEventAction,
+ i,
+ len,
+ keydownEvent = new Ext.EventObjectImpl(event);
+
+
+ for (i = 0, len = keyCode.length; i < len; ++i) {
+ if (key === keyCode[i]) {
+ if (handler.call(scope, key, event) !== true && defaultEventAction) {
+ keydownEvent[defaultEventAction]();
+ }
+ break;
+ }
+ }
+ }
+ },
+
+ /**
+ * Check if the modifiers on the event match those on the binding
+ * @private
+ * @param {Object} binding
+ * @param {Ext.EventObject} event
+ * @return {Boolean} True if the event matches the binding
+ */
+ checkModifiers: function(binding, e){
+ var keys = ['shift', 'ctrl', 'alt'],
+ i = 0,
+ len = keys.length,
+ val, key;
+
+ for (; i < len; ++i){
+ key = keys[i];
+ val = binding[key];
+ if (!(val === undefined || (val === e[key + 'Key']))) {
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Shorthand for adding a single key listener
+ * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
+ * following options:
+ * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
+ */
+ on: function(key, fn, scope) {
+ var keyCode, shift, ctrl, alt;
+ if (Ext.isObject(key) && !Ext.isArray(key)) {
+ keyCode = key.key;
+ shift = key.shift;
+ ctrl = key.ctrl;
+ alt = key.alt;
+ } else {
+ keyCode = key;
+ }
+ this.addBinding({
+ key: keyCode,
+ shift: shift,
+ ctrl: ctrl,
+ alt: alt,
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ /**
+ * Returns true if this KeyMap is enabled
+ * @return {Boolean}
+ */
+ isEnabled : function(){
+ return this.enabled;
+ },
+
+ /**
+ * Enables this KeyMap
+ */
+ enable: function(){
+ if(!this.enabled){
+ this.el.on(this.eventName, this.handleKeyDown, this);
+ this.enabled = true;
+ }
+ },
+
+ /**
+ * Disable this KeyMap
+ */
+ disable: function(){
+ if(this.enabled){
+ this.el.removeListener(this.eventName, this.handleKeyDown, this);
+ this.enabled = false;
+ }
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled : function(disabled){
+ if (disabled) {
+ this.disable();
+ } else {
+ this.enable();
+ }
+ },
+
+ /**
+ * Destroys the KeyMap instance and removes all handlers.
+ * @param {Boolean} removeEl True to also remove the attached element
+ */
+ destroy: function(removeEl){
+ var me = this;
+
+ me.bindings = [];
+ me.disable();
+ if (removeEl === true) {
+ me.el.remove();
+ }
+ delete me.el;
+ }
+});
+/**
+ * @class Ext.util.ClickRepeater
+ * @extends Ext.util.Observable
+ *
+ * A wrapper class which can be applied to any element. Fires a "click" event while the
+ * mouse is pressed. The interval between firings may be specified in the config but
+ * defaults to 20 milliseconds.
+ *
+ * Optionally, a CSS class may be applied to the element during the time it is pressed.
+ *
+ * @constructor
+ * @param {Mixed} el The element to listen on
+ * @param {Object} config
+ */
+
+Ext.define('Ext.util.ClickRepeater', {
+ extend: 'Ext.util.Observable',
+
+ constructor : function(el, config){
+ this.el = Ext.get(el);
+ this.el.unselectable();
+
+ Ext.apply(this, config);
+
+ this.addEvents(
+ /**
+ * @event mousedown
+ * Fires when the mouse button is depressed.
+ * @param {Ext.util.ClickRepeater} this
+ * @param {Ext.EventObject} e
+ */
+ "mousedown",
+ /**
+ * @event click
+ * Fires on a specified interval during the time the element is pressed.
+ * @param {Ext.util.ClickRepeater} this
+ * @param {Ext.EventObject} e
+ */
+ "click",
+ /**
+ * @event mouseup
+ * Fires when the mouse key is released.
+ * @param {Ext.util.ClickRepeater} this
+ * @param {Ext.EventObject} e
+ */
+ "mouseup"
+ );
+
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
+
+ // allow inline handler
+ if(this.handler){
+ this.on("click", this.handler, this.scope || this);
+ }
+
+ this.callParent();
+ },
+
+ /**
+ * @cfg {Mixed} el The element to act as a button.
+ */
+
+ /**
+ * @cfg {String} pressedCls A CSS class name to be applied to the element while pressed.
+ */
+
+ /**
+ * @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
+ * "interval" and "delay" are ignored.
+ */
+
+ /**
+ * @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
+ */
+ interval : 20,
+
+ /**
+ * @cfg {Number} delay The initial delay before the repeating event begins firing.
+ * Similar to an autorepeat key delay.
+ */
+ delay: 250,
+
+ /**
+ * @cfg {Boolean} preventDefault True to prevent the default click event
+ */
+ preventDefault : true,
+ /**
+ * @cfg {Boolean} stopDefault True to stop the default click event
+ */
+ stopDefault : false,
+
+ timer : 0,
+
+ /**
+ * Enables the repeater and allows events to fire.
+ */
+ enable: function(){
+ if(this.disabled){
+ this.el.on('mousedown', this.handleMouseDown, this);
+ if (Ext.isIE){
+ this.el.on('dblclick', this.handleDblClick, this);
+ }
+ if(this.preventDefault || this.stopDefault){
+ this.el.on('click', this.eventOptions, this);
+ }
+ }
+ this.disabled = false;
+ },
+
+ /**
+ * Disables the repeater and stops events from firing.
+ */
+ disable: function(/* private */ force){
+ if(force || !this.disabled){
+ clearTimeout(this.timer);
+ if(this.pressedCls){
+ this.el.removeCls(this.pressedCls);
+ }
+ Ext.getDoc().un('mouseup', this.handleMouseUp, this);
+ this.el.removeAllListeners();
+ }
+ this.disabled = true;
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled: function(disabled){
+ this[disabled ? 'disable' : 'enable']();
+ },
+
+ eventOptions: function(e){
+ if(this.preventDefault){
+ e.preventDefault();
+ }
+ if(this.stopDefault){
+ e.stopEvent();
+ }
+ },
+
+ // private
+ destroy : function() {
+ this.disable(true);
+ Ext.destroy(this.el);
+ this.clearListeners();
+ },
+
+ handleDblClick : function(e){
+ clearTimeout(this.timer);
+ this.el.blur();
+
+ this.fireEvent("mousedown", this, e);
+ this.fireEvent("click", this, e);
+ },
+
+ // private
+ handleMouseDown : function(e){
+ clearTimeout(this.timer);
+ this.el.blur();
+ if(this.pressedCls){
+ this.el.addCls(this.pressedCls);
+ }
+ this.mousedownTime = new Date();
+
+ Ext.getDoc().on("mouseup", this.handleMouseUp, this);
+ this.el.on("mouseout", this.handleMouseOut, this);
+
+ this.fireEvent("mousedown", this, e);
+ this.fireEvent("click", this, e);
+
+ // Do not honor delay or interval if acceleration wanted.
+ if (this.accelerate) {
+ this.delay = 400;
+ }
+
+ // Re-wrap the event object in a non-shared object, so it doesn't lose its context if
+ // the global shared EventObject gets a new Event put into it before the timer fires.
+ e = new Ext.EventObjectImpl(e);
+
+ this.timer = Ext.defer(this.click, this.delay || this.interval, this, [e]);
+ },
+
+ // private
+ click : function(e){
+ this.fireEvent("click", this, e);
+ this.timer = Ext.defer(this.click, this.accelerate ?
+ this.easeOutExpo(Ext.Date.getElapsed(this.mousedownTime),
+ 400,
+ -390,
+ 12000) :
+ this.interval, this, [e]);
+ },
+
+ easeOutExpo : function (t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+ // private
+ handleMouseOut : function(){
+ clearTimeout(this.timer);
+ if(this.pressedCls){
+ this.el.removeCls(this.pressedCls);
+ }
+ this.el.on("mouseover", this.handleMouseReturn, this);
+ },
+
+ // private
+ handleMouseReturn : function(){
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ if(this.pressedCls){
+ this.el.addCls(this.pressedCls);
+ }
+ this.click();
+ },
+
+ // private
+ handleMouseUp : function(e){
+ clearTimeout(this.timer);
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ this.el.un("mouseout", this.handleMouseOut, this);
+ Ext.getDoc().un("mouseup", this.handleMouseUp, this);
+ if(this.pressedCls){
+ this.el.removeCls(this.pressedCls);
+ }
+ this.fireEvent("mouseup", this, e);
+ }
+});
+
+/**
+ * Component layout for buttons
+ * @class Ext.layout.component.Button
+ * @extends Ext.layout.component.Component
+ * @private
+ */
+Ext.define('Ext.layout.component.Button', {
+
+ /* Begin Definitions */
+
+ alias: ['layout.button'],
+
+ extend: 'Ext.layout.component.Component',
+
+ /* End Definitions */
+
+ type: 'button',
+
+ cellClsRE: /-btn-(tl|br)\b/,
+ htmlRE: /<.*>/,
+
+ beforeLayout: function() {
+ return this.callParent(arguments) || this.lastText !== this.owner.text;
+ },
+
+ /**
+ * Set the dimensions of the inner <button> element to match the
+ * component dimensions.
+ */
+ onLayout: function(width, height) {
+ var me = this,
+ isNum = Ext.isNumber,
+ owner = me.owner,
+ ownerEl = owner.el,
+ btnEl = owner.btnEl,
+ btnInnerEl = owner.btnInnerEl,
+ minWidth = owner.minWidth,
+ maxWidth = owner.maxWidth,
+ ownerWidth, btnFrameWidth, metrics;
+
+ me.getTargetInfo();
+ me.callParent(arguments);
+
+ btnInnerEl.unclip();
+ me.setTargetSize(width, height);
+
+ if (!isNum(width)) {
+ // In IE7 strict mode button elements with width:auto get strange extra side margins within
+ // the wrapping table cell, but they go away if the width is explicitly set. So we measure
+ // the size of the text and set the width to match.
+ if (owner.text && Ext.isIE7 && Ext.isStrict && btnEl && btnEl.getWidth() > 20) {
+ btnFrameWidth = me.btnFrameWidth;
+ metrics = Ext.util.TextMetrics.measure(btnInnerEl, owner.text);
+ ownerEl.setWidth(metrics.width + btnFrameWidth + me.adjWidth);
+ btnEl.setWidth(metrics.width + btnFrameWidth);
+ btnInnerEl.setWidth(metrics.width + btnFrameWidth);
+ } else {
+ // Remove any previous fixed widths
+ ownerEl.setWidth(null);
+ btnEl.setWidth(null);
+ btnInnerEl.setWidth(null);
+ }
+
+ // Handle maxWidth/minWidth config
+ if (minWidth || maxWidth) {
+ ownerWidth = ownerEl.getWidth();
+ if (minWidth && (ownerWidth < minWidth)) {
+ me.setTargetSize(minWidth, height);
+ }
+ else if (maxWidth && (ownerWidth > maxWidth)) {
+ btnInnerEl.clip();
+ me.setTargetSize(maxWidth, height);
+ }
+ }
+ }
+
+ this.lastText = owner.text;
+ },
+
+ setTargetSize: function(width, height) {
+ var me = this,
+ owner = me.owner,
+ isNum = Ext.isNumber,
+ btnInnerEl = owner.btnInnerEl,
+ btnWidth = (isNum(width) ? width - me.adjWidth : width),
+ btnHeight = (isNum(height) ? height - me.adjHeight : height),
+ btnFrameHeight = me.btnFrameHeight,
+ text = owner.getText(),
+ textHeight;
+
+ me.callParent(arguments);
+ me.setElementSize(owner.btnEl, btnWidth, btnHeight);
+ me.setElementSize(btnInnerEl, btnWidth, btnHeight);
+ if (isNum(btnHeight)) {
+ btnInnerEl.setStyle('line-height', btnHeight - btnFrameHeight + 'px');
+ }
+
+ // Button text may contain markup that would force it to wrap to more than one line (e.g. 'Button<br>Label').
+ // When this happens, we cannot use the line-height set above for vertical centering; we instead reset the
+ // line-height to normal, measure the rendered text height, and add padding-top to center the text block
+ // vertically within the button's height. This is more expensive than the basic line-height approach so
+ // we only do it if the text contains markup.
+ if (text && this.htmlRE.test(text)) {
+ btnInnerEl.setStyle('line-height', 'normal');
+ textHeight = Ext.util.TextMetrics.measure(btnInnerEl, text).height;
+ btnInnerEl.setStyle('padding-top', me.btnFrameTop + Math.max(btnInnerEl.getHeight() - btnFrameHeight - textHeight, 0) / 2 + 'px');
+ me.setElementSize(btnInnerEl, btnWidth, btnHeight);
+ }
+ },
+
+ getTargetInfo: function() {
+ var me = this,
+ owner = me.owner,
+ ownerEl = owner.el,
+ frameSize = me.frameSize,
+ frameBody = owner.frameBody,
+ btnWrap = owner.btnWrap,
+ innerEl = owner.btnInnerEl;
+
+ if (!('adjWidth' in me)) {
+ Ext.apply(me, {
+ // Width adjustment must take into account the arrow area. The btnWrap is the <em> which has padding to accommodate the arrow.
+ adjWidth: frameSize.left + frameSize.right + ownerEl.getBorderWidth('lr') + ownerEl.getPadding('lr') +
+ btnWrap.getPadding('lr') + (frameBody ? frameBody.getFrameWidth('lr') : 0),
+ adjHeight: frameSize.top + frameSize.bottom + ownerEl.getBorderWidth('tb') + ownerEl.getPadding('tb') +
+ btnWrap.getPadding('tb') + (frameBody ? frameBody.getFrameWidth('tb') : 0),
+ btnFrameWidth: innerEl.getFrameWidth('lr'),
+ btnFrameHeight: innerEl.getFrameWidth('tb'),
+ btnFrameTop: innerEl.getFrameWidth('t')
+ });
+ }
+
+ return me.callParent();
+ }
+});
+/**
+ * @class Ext.util.TextMetrics
+ * <p>
+ * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
+ * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
+ * should not contain any HTML, otherwise it may not be measured correctly.</p>
+ * <p>The measurement works by copying the relevant CSS styles that can affect the font related display,
+ * then checking the size of an element that is auto-sized. Note that if the text is multi-lined, you must
+ * provide a <b>fixed width</b> when doing the measurement.</p>
+ *
+ * <p>
+ * If multiple measurements are being done on the same element, you create a new instance to initialize
+ * to avoid the overhead of copying the styles to the element repeatedly.
+ * </p>
+ */
+Ext.define('Ext.util.TextMetrics', {
+ statics: {
+ shared: null,
+ /**
+ * Measures the size of the specified text
+ * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
+ * that can affect the size of the rendered text
+ * @param {String} text The text to measure
+ * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
+ * in order to accurately measure the text height
+ * @return {Object} An object containing the text's size {width: (width), height: (height)}
+ */
+ measure: function(el, text, fixedWidth){
+ var me = this,
+ shared = me.shared;
+
+ if(!shared){
+ shared = me.shared = new me(el, fixedWidth);
+ }
+ shared.bind(el);
+ shared.setFixedWidth(fixedWidth || 'auto');
+ return shared.getSize(text);
+ },
+
+ /**
+ * Destroy the TextMetrics instance created by {@link #measure}.
+ */
+ destroy: function(){
+ var me = this;
+ Ext.destroy(me.shared);
+ me.shared = null;
+ }
+ },
+
+ /**
+ * @constructor
+ * @param {Mixed} bindTo The element to bind to.
+ * @param {Number} fixedWidth A fixed width to apply to the measuring element.
+ */
+ constructor: function(bindTo, fixedWidth){
+ var measure = this.measure = Ext.getBody().createChild({
+ cls: 'x-textmetrics'
+ });
+ this.el = Ext.get(bindTo);
+
+ measure.position('absolute');
+ measure.setLeftTop(-1000, -1000);
+ measure.hide();
+
+ if (fixedWidth) {
+ measure.setWidth(fixedWidth);
+ }
+ },
+
+ /**
+ * <p><b>Only available on the instance returned from {@link #createInstance}, <u>not</u> on the singleton.</b></p>
+ * Returns the size of the specified text based on the internal element's style and width properties
+ * @param {String} text The text to measure
+ * @return {Object} An object containing the text's size {width: (width), height: (height)}
+ */
+ getSize: function(text){
+ var measure = this.measure,
+ size;
+
+ measure.update(text);
+ size = measure.getSize();
+ measure.update('');
+ return size;
+ },
+
+ /**
+ * Binds this TextMetrics instance to a new element
+ * @param {Mixed} el The element
+ */
+ bind: function(el){
+ var me = this;
+
+ me.el = Ext.get(el);
+ me.measure.setStyle(
+ me.el.getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
+ );
+ },
+
+ /**
+ * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
+ * to set a fixed width in order to accurately measure the text height.
+ * @param {Number} width The width to set on the element
+ */
+ setFixedWidth : function(width){
+ this.measure.setWidth(width);
+ },
+
+ /**
+ * Returns the measured width of the specified text
+ * @param {String} text The text to measure
+ * @return {Number} width The width in pixels
+ */
+ getWidth : function(text){
+ this.measure.dom.style.width = 'auto';
+ return this.getSize(text).width;
+ },
+
+ /**
+ * Returns the measured height of the specified text
+ * @param {String} text The text to measure
+ * @return {Number} height The height in pixels
+ */
+ getHeight : function(text){
+ return this.getSize(text).height;
+ },
+
+ /**
+ * Destroy this instance
+ */
+ destroy: function(){
+ var me = this;
+ me.measure.remove();
+ delete me.el;
+ delete me.measure;
+ }
+}, function(){
+ Ext.core.Element.addMethods({
+ /**
+ * Returns the width in pixels of the passed text, or the width of the text in this Element.
+ * @param {String} text The text to measure. Defaults to the innerHTML of the element.
+ * @param {Number} min (Optional) The minumum value to return.
+ * @param {Number} max (Optional) The maximum value to return.
+ * @return {Number} The text width in pixels.
+ * @member Ext.core.Element getTextWidth
+ */
+ getTextWidth : function(text, min, max){
+ return Ext.Number.constrain(Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width, min || 0, max || 1000000);
+ }
+ });
+});
+
+/**
+ * @class Ext.layout.container.boxOverflow.Scroller
+ * @extends Ext.layout.container.boxOverflow.None
+ * @private
+ */
+Ext.define('Ext.layout.container.boxOverflow.Scroller', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.container.boxOverflow.None',
+ requires: ['Ext.util.ClickRepeater', 'Ext.core.Element'],
+ alternateClassName: 'Ext.layout.boxOverflow.Scroller',
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Boolean} animateScroll
+ * True to animate the scrolling of items within the layout (defaults to true, ignored if enableScroll is false)
+ */
+ animateScroll: false,
+
+ /**
+ * @cfg {Number} scrollIncrement
+ * The number of pixels to scroll by on scroller click (defaults to 24)
+ */
+ scrollIncrement: 20,
+
+ /**
+ * @cfg {Number} wheelIncrement
+ * The number of pixels to increment on mouse wheel scrolling (defaults to <tt>3</tt>).
+ */
+ wheelIncrement: 10,
+
+ /**
+ * @cfg {Number} scrollRepeatInterval
+ * Number of milliseconds between each scroll while a scroller button is held down (defaults to 20)
+ */
+ scrollRepeatInterval: 60,
+
+ /**
+ * @cfg {Number} scrollDuration
+ * Number of milliseconds that each scroll animation lasts (defaults to 400)
+ */
+ scrollDuration: 400,
+
+ /**
+ * @cfg {String} beforeCtCls
+ * CSS class added to the beforeCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the leftmost edge of the Container
+ */
+
+ /**
+ * @cfg {String} afterCtCls
+ * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the rightmost edge of the Container
+ */
+
+ /**
+ * @cfg {String} scrollerCls
+ * CSS class added to both scroller elements if enableScroll is used
+ */
+ scrollerCls: Ext.baseCSSPrefix + 'box-scroller',
+
+ /**
+ * @cfg {String} beforeScrollerCls
+ * CSS class added to the left scroller element if enableScroll is used
+ */
+
+ /**
+ * @cfg {String} afterScrollerCls
+ * CSS class added to the right scroller element if enableScroll is used
+ */
+
+ constructor: function(layout, config) {
+ this.layout = layout;
+ Ext.apply(this, config || {});
+
+ this.addEvents(
+ /**
+ * @event scroll
+ * @param {Ext.layout.container.boxOverflow.Scroller} scroller The layout scroller
+ * @param {Number} newPosition The new position of the scroller
+ * @param {Boolean/Object} animate If animating or not. If true, it will be a animation configuration, else it will be false
+ */
+ 'scroll'
+ );
+ },
+
+ initCSSClasses: function() {
+ var me = this,
+ layout = me.layout;
+
+ if (!me.CSSinitialized) {
+ me.beforeCtCls = me.beforeCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelBefore;
+ me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-scroller-' + layout.parallelAfter;
+ me.beforeScrollerCls = me.beforeScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelBefore;
+ me.afterScrollerCls = me.afterScrollerCls || Ext.baseCSSPrefix + layout.owner.getXType() + '-scroll-' + layout.parallelAfter;
+ me.CSSinitializes = true;
+ }
+ },
+
+ handleOverflow: function(calculations, targetSize) {
+ var me = this,
+ layout = me.layout,
+ methodName = 'get' + layout.parallelPrefixCap,
+ newSize = {};
+
+ me.initCSSClasses();
+ me.callParent(arguments);
+ this.createInnerElements();
+ this.showScrollers();
+ newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
+ newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - (me.beforeCt[methodName]() + me.afterCt[methodName]());
+ return { targetSize: newSize };
+ },
+
+ /**
+ * @private
+ * Creates the beforeCt and afterCt elements if they have not already been created
+ */
+ createInnerElements: function() {
+ var me = this,
+ target = me.layout.getRenderTarget();
+
+ //normal items will be rendered to the innerCt. beforeCt and afterCt allow for fixed positioning of
+ //special items such as scrollers or dropdown menu triggers
+ if (!me.beforeCt) {
+ target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
+ me.beforeCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.beforeCtCls}, 'before');
+ me.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + me.afterCtCls}, 'after');
+ me.createWheelListener();
+ }
+ },
+
+ /**
+ * @private
+ * Sets up an listener to scroll on the layout's innerCt mousewheel event
+ */
+ createWheelListener: function() {
+ this.layout.innerCt.on({
+ scope : this,
+ mousewheel: function(e) {
+ e.stopEvent();
+
+ this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
+ }
+ });
+ },
+
+ /**
+ * @private
+ */
+ clearOverflow: function() {
+ this.hideScrollers();
+ },
+
+ /**
+ * @private
+ * Shows the scroller elements in the beforeCt and afterCt. Creates the scrollers first if they are not already
+ * present.
+ */
+ showScrollers: function() {
+ this.createScrollers();
+ this.beforeScroller.show();
+ this.afterScroller.show();
+ this.updateScrollButtons();
+
+ this.layout.owner.addClsWithUI('scroller');
+ },
+
+ /**
+ * @private
+ * Hides the scroller elements in the beforeCt and afterCt
+ */
+ hideScrollers: function() {
+ if (this.beforeScroller != undefined) {
+ this.beforeScroller.hide();
+ this.afterScroller.hide();
+
+ this.layout.owner.removeClsWithUI('scroller');
+ }
+ },
+
+ /**
+ * @private
+ * Creates the clickable scroller elements and places them into the beforeCt and afterCt
+ */
+ createScrollers: function() {
+ if (!this.beforeScroller && !this.afterScroller) {
+ var before = this.beforeCt.createChild({
+ cls: Ext.String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
+ });
+
+ var after = this.afterCt.createChild({
+ cls: Ext.String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
+ });
+
+ before.addClsOnOver(this.beforeScrollerCls + '-hover');
+ after.addClsOnOver(this.afterScrollerCls + '-hover');
+
+ before.setVisibilityMode(Ext.core.Element.DISPLAY);
+ after.setVisibilityMode(Ext.core.Element.DISPLAY);
+
+ this.beforeRepeater = Ext.create('Ext.util.ClickRepeater', before, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollLeft,
+ scope : this
+ });
+
+ this.afterRepeater = Ext.create('Ext.util.ClickRepeater', after, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollRight,
+ scope : this
+ });
+
+ /**
+ * @property beforeScroller
+ * @type Ext.core.Element
+ * The left scroller element. Only created when needed.
+ */
+ this.beforeScroller = before;
+
+ /**
+ * @property afterScroller
+ * @type Ext.core.Element
+ * The left scroller element. Only created when needed.
+ */
+ this.afterScroller = after;
+ }
+ },
+
+ /**
+ * @private
+ */
+ destroy: function() {
+ Ext.destroy(this.beforeRepeater, this.afterRepeater, this.beforeScroller, this.afterScroller, this.beforeCt, this.afterCt);
+ },
+
+ /**
+ * @private
+ * Scrolls left or right by the number of pixels specified
+ * @param {Number} delta Number of pixels to scroll to the right by. Use a negative number to scroll left
+ */
+ scrollBy: function(delta, animate) {
+ this.scrollTo(this.getScrollPosition() + delta, animate);
+ },
+
+ /**
+ * @private
+ * @return {Object} Object passed to scrollTo when scrolling
+ */
+ getScrollAnim: function() {
+ return {
+ duration: this.scrollDuration,
+ callback: this.updateScrollButtons,
+ scope : this
+ };
+ },
+
+ /**
+ * @private
+ * Enables or disables each scroller button based on the current scroll position
+ */
+ updateScrollButtons: function() {
+ if (this.beforeScroller == undefined || this.afterScroller == undefined) {
+ return;
+ }
+
+ var beforeMeth = this.atExtremeBefore() ? 'addCls' : 'removeCls',
+ afterMeth = this.atExtremeAfter() ? 'addCls' : 'removeCls',
+ beforeCls = this.beforeScrollerCls + '-disabled',
+ afterCls = this.afterScrollerCls + '-disabled';
+
+ this.beforeScroller[beforeMeth](beforeCls);
+ this.afterScroller[afterMeth](afterCls);
+ this.scrolling = false;
+ },
+
+ /**
+ * @private
+ * Returns true if the innerCt scroll is already at its left-most point
+ * @return {Boolean} True if already at furthest left point
+ */
+ atExtremeBefore: function() {
+ return this.getScrollPosition() === 0;
+ },
+
+ /**
+ * @private
+ * Scrolls to the left by the configured amount
+ */
+ scrollLeft: function() {
+ this.scrollBy(-this.scrollIncrement, false);
+ },
+
+ /**
+ * @private
+ * Scrolls to the right by the configured amount
+ */
+ scrollRight: function() {
+ this.scrollBy(this.scrollIncrement, false);
+ },
+
+ /**
+ * Returns the current scroll position of the innerCt element
+ * @return {Number} The current scroll position
+ */
+ getScrollPosition: function(){
+ var layout = this.layout;
+ return parseInt(layout.innerCt.dom['scroll' + layout.parallelBeforeCap], 10) || 0;
+ },
+
+ /**
+ * @private
+ * Returns the maximum value we can scrollTo
+ * @return {Number} The max scroll value
+ */
+ getMaxScrollPosition: function() {
+ var layout = this.layout;
+ return layout.innerCt.dom['scroll' + layout.parallelPrefixCap] - this.layout.innerCt['get' + layout.parallelPrefixCap]();
+ },
+
+ /**
+ * @private
+ * Returns true if the innerCt scroll is already at its right-most point
+ * @return {Boolean} True if already at furthest right point
+ */
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollPosition();
+ },
+
+ /**
+ * @private
+ * Scrolls to the given position. Performs bounds checking.
+ * @param {Number} position The position to scroll to. This is constrained.
+ * @param {Boolean} animate True to animate. If undefined, falls back to value of this.animateScroll
+ */
+ scrollTo: function(position, animate) {
+ var me = this,
+ layout = me.layout,
+ oldPosition = me.getScrollPosition(),
+ newPosition = Ext.Number.constrain(position, 0, me.getMaxScrollPosition());
+
+ if (newPosition != oldPosition && !me.scrolling) {
+ if (animate == undefined) {
+ animate = me.animateScroll;
+ }
+
+ layout.innerCt.scrollTo(layout.parallelBefore, newPosition, animate ? me.getScrollAnim() : false);
+ if (animate) {
+ me.scrolling = true;
+ } else {
+ me.scrolling = false;
+ me.updateScrollButtons();
+ }
+
+ me.fireEvent('scroll', me, newPosition, animate ? me.getScrollAnim() : false);
+ }
+ },
+
+ /**
+ * Scrolls to the given component.
+ * @param {String|Number|Ext.Component} item The item to scroll to. Can be a numerical index, component id
+ * or a reference to the component itself.
+ * @param {Boolean} animate True to animate the scrolling
+ */
+ scrollToItem: function(item, animate) {
+ var me = this,
+ layout = me.layout,
+ visibility,
+ box,
+ newPos;
+
+ item = me.getItem(item);
+ if (item != undefined) {
+ visibility = this.getItemVisibility(item);
+ if (!visibility.fullyVisible) {
+ box = item.getBox(true, true);
+ newPos = box[layout.parallelPosition];
+ if (visibility.hiddenEnd) {
+ newPos -= (this.layout.innerCt['get' + layout.parallelPrefixCap]() - box[layout.parallelPrefix]);
+ }
+ this.scrollTo(newPos, animate);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * For a given item in the container, return an object with information on whether the item is visible
+ * with the current innerCt scroll value.
+ * @param {Ext.Component} item The item
+ * @return {Object} Values for fullyVisible, hiddenStart and hiddenEnd
+ */
+ getItemVisibility: function(item) {
+ var me = this,
+ box = me.getItem(item).getBox(true, true),
+ layout = me.layout,
+ itemStart = box[layout.parallelPosition],
+ itemEnd = itemStart + box[layout.parallelPrefix],
+ scrollStart = me.getScrollPosition(),
+ scrollEnd = scrollStart + layout.innerCt['get' + layout.parallelPrefixCap]();
+
+ return {
+ hiddenStart : itemStart < scrollStart,
+ hiddenEnd : itemEnd > scrollEnd,
+ fullyVisible: itemStart > scrollStart && itemEnd < scrollEnd
+ };
+ }
+});
+/**
+ * @class Ext.util.Offset
+ * @ignore
+ */
+Ext.define('Ext.util.Offset', {
+
+ /* Begin Definitions */
+
+ statics: {
+ fromObject: function(obj) {
+ return new this(obj.x, obj.y);
+ }
+ },
+
+ /* End Definitions */
+
+ constructor: function(x, y) {
+ this.x = (x != null && !isNaN(x)) ? x : 0;
+ this.y = (y != null && !isNaN(y)) ? y : 0;
+
+ return this;
+ },
+
+ copy: function() {
+ return new Ext.util.Offset(this.x, this.y);
+ },
+
+ copyFrom: function(p) {
+ this.x = p.x;
+ this.y = p.y;
+ },
+
+ toString: function() {
+ return "Offset[" + this.x + "," + this.y + "]";
+ },
+
+ equals: function(offset) {
+ if(!(offset instanceof this.statics())) {
+ Ext.Error.raise('Offset must be an instance of Ext.util.Offset');
+ }
+
+ return (this.x == offset.x && this.y == offset.y);
+ },
+
+ round: function(to) {
+ if (!isNaN(to)) {
+ var factor = Math.pow(10, to);
+ this.x = Math.round(this.x * factor) / factor;
+ this.y = Math.round(this.y * factor) / factor;
+ } else {
+ this.x = Math.round(this.x);
+ this.y = Math.round(this.y);
+ }
+ },
+
+ isZero: function() {
+ return this.x == 0 && this.y == 0;
+ }
+});
+
+/**
+ * @class Ext.util.KeyNav
+ * <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
+ * navigation keys to function calls that will get called when the keys are pressed, providing an easy
+ * way to implement custom navigation schemes for any UI component.</p>
+ * <p>The following are all of the possible keys that can be implemented: enter, space, left, right, up, down, tab, esc,
+ * pageUp, pageDown, del, backspace, home, end. Usage:</p>
+ <pre><code>
+var nav = new Ext.util.KeyNav("my-element", {
+ "left" : function(e){
+ this.moveLeft(e.ctrlKey);
+ },
+ "right" : function(e){
+ this.moveRight(e.ctrlKey);
+ },
+ "enter" : function(e){
+ this.save();
+ },
+ scope : this
+});
+</code></pre>
+ * @constructor
+ * @param {Mixed} el The element to bind to
+ * @param {Object} config The config
+ */
+Ext.define('Ext.util.KeyNav', {
+
+ alternateClassName: 'Ext.KeyNav',
+
+ requires: ['Ext.util.KeyMap'],
+
+ statics: {
+ keyOptions: {
+ left: 37,
+ right: 39,
+ up: 38,
+ down: 40,
+ space: 32,
+ pageUp: 33,
+ pageDown: 34,
+ del: 46,
+ backspace: 8,
+ home: 36,
+ end: 35,
+ enter: 13,
+ esc: 27,
+ tab: 9
+ }
+ },
+
+ constructor: function(el, config){
+ this.setConfig(el, config || {});
+ },
+
+ /**
+ * Sets up a configuration for the KeyNav.
+ * @private
+ * @param {Mixed} el The element to bind to
+ * @param {Object}A configuration object as specified in the constructor.
+ */
+ setConfig: function(el, config) {
+ if (this.map) {
+ this.map.destroy();
+ }
+
+ var map = Ext.create('Ext.util.KeyMap', el, null, this.getKeyEvent('forceKeyDown' in config ? config.forceKeyDown : this.forceKeyDown)),
+ keys = Ext.util.KeyNav.keyOptions,
+ scope = config.scope || this,
+ key;
+
+ this.map = map;
+ for (key in keys) {
+ if (keys.hasOwnProperty(key)) {
+ if (config[key]) {
+ map.addBinding({
+ scope: scope,
+ key: keys[key],
+ handler: Ext.Function.bind(this.handleEvent, scope, [config[key]], true),
+ defaultEventAction: config.defaultEventAction || this.defaultEventAction
+ });
+ }
+ }
+ }
+
+ map.disable();
+ if (!config.disabled) {
+ map.enable();
+ }
+ },
+
+ /**
+ * Method for filtering out the map argument
+ * @private
+ * @param {Ext.util.KeyMap} map
+ * @param {Ext.EventObject} event
+ * @param {Object} options Contains the handler to call
+ */
+ handleEvent: function(map, event, handler){
+ return handler.call(this, event);
+ },
+
+ /**
+ * @cfg {Boolean} disabled
+ * True to disable this KeyNav instance (defaults to false)
+ */
+ disabled: false,
+
+ /**
+ * @cfg {String} defaultEventAction
+ * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
+ * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
+ * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
+ */
+ defaultEventAction: "stopEvent",
+
+ /**
+ * @cfg {Boolean} forceKeyDown
+ * Handle the keydown event instead of keypress (defaults to false). KeyNav automatically does this for IE since
+ * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
+ * handle keydown instead of keypress.
+ */
+ forceKeyDown: false,
+
+ /**
+ * Destroy this KeyNav (this is the same as calling disable).
+ * @param {Boolean} removeEl True to remove the element associated with this KeyNav.
+ */
+ destroy: function(removeEl){
+ this.map.destroy(removeEl);
+ delete this.map;
+ },
+
+ /**
+ * Enable this KeyNav
+ */
+ enable: function() {
+ this.map.enable();
+ this.disabled = false;
+ },
+
+ /**
+ * Disable this KeyNav
+ */
+ disable: function() {
+ this.map.disable();
+ this.disabled = true;
+ },
+
+ /**
+ * Convenience function for setting disabled/enabled by boolean.
+ * @param {Boolean} disabled
+ */
+ setDisabled : function(disabled){
+ this.map.setDisabled(disabled);
+ this.disabled = disabled;
+ },
+
+ /**
+ * Determines the event to bind to listen for keys. Depends on the {@link #forceKeyDown} setting,
+ * as well as the useKeyDown option on the EventManager.
+ * @return {String} The type of event to listen for.
+ */
+ getKeyEvent: function(forceKeyDown){
+ return (forceKeyDown || Ext.EventManager.useKeyDown) ? 'keydown' : 'keypress';
+ }
+});
+
+/**
+ * @class Ext.fx.Queue
+ * Animation Queue mixin to handle chaining and queueing by target.
+ * @private
+ */
+
+Ext.define('Ext.fx.Queue', {
+
+ requires: ['Ext.util.HashMap'],
+
+ constructor: function() {
+ this.targets = Ext.create('Ext.util.HashMap');
+ this.fxQueue = {};
+ },
+
+ // @private
+ getFxDefaults: function(targetId) {
+ var target = this.targets.get(targetId);
+ if (target) {
+ return target.fxDefaults;
+ }
+ return {};
+ },
+
+ // @private
+ setFxDefaults: function(targetId, obj) {
+ var target = this.targets.get(targetId);
+ if (target) {
+ target.fxDefaults = Ext.apply(target.fxDefaults || {}, obj);
+ }
+ },
+
+ // @private
+ stopAnimation: function(targetId) {
+ var me = this,
+ queue = me.getFxQueue(targetId),
+ ln = queue.length;
+ while (ln) {
+ queue[ln - 1].end();
+ ln--;
+ }
+ },
+
+ /**
+ * @private
+ * Returns current animation object if the element has any effects actively running or queued, else returns false.
+ */
+ getActiveAnimation: function(targetId) {
+ var queue = this.getFxQueue(targetId);
+ return (queue && !!queue.length) ? queue[0] : false;
+ },
+
+ // @private
+ hasFxBlock: function(targetId) {
+ var queue = this.getFxQueue(targetId);
+ return queue && queue[0] && queue[0].block;
+ },
+
+ // @private get fx queue for passed target, create if needed.
+ getFxQueue: function(targetId) {
+ if (!targetId) {
+ return false;
+ }
+ var me = this,
+ queue = me.fxQueue[targetId],
+ target = me.targets.get(targetId);
+
+ if (!target) {
+ return false;
+ }
+
+ if (!queue) {
+ me.fxQueue[targetId] = [];
+ // GarbageCollector will need to clean up Elements since they aren't currently observable
+ if (target.type != 'element') {
+ target.target.on('destroy', function() {
+ me.fxQueue[targetId] = [];
+ });
+ }
+ }
+ return me.fxQueue[targetId];
+ },
+
+ // @private
+ queueFx: function(anim) {
+ var me = this,
+ target = anim.target,
+ queue, ln;
+
+ if (!target) {
+ return;
+ }
+
+ queue = me.getFxQueue(target.getId());
+ ln = queue.length;
+
+ if (ln) {
+ if (anim.concurrent) {
+ anim.paused = false;
+ }
+ else {
+ queue[ln - 1].on('afteranimate', function() {
+ anim.paused = false;
+ });
+ }
+ }
+ else {
+ anim.paused = false;
+ }
+ anim.on('afteranimate', function() {
+ Ext.Array.remove(queue, anim);
+ if (anim.remove) {
+ if (target.type == 'element') {
+ var el = Ext.get(target.id);
+ if (el) {
+ el.remove();
+ }
+ }
+ }
+ }, this);
+ queue.push(anim);
+ }
+});
+/**
+ * @class Ext.fx.target.Target
+
+This class specifies a generic target for an animation. It provides a wrapper around a
+series of different types of objects to allow for a generic animation API.
+A target can be a single object or a Composite object containing other objects that are
+to be animated. This class and it's subclasses are generally not created directly, the
+underlying animation will create the appropriate Ext.fx.target.Target object by passing
+the instance to be animated.
+
+The following types of objects can be animated:
+- {@link #Ext.fx.target.Component Components}
+- {@link #Ext.fx.target.Element Elements}
+- {@link #Ext.fx.target.Sprite Sprites}
+
+ * @markdown
+ * @abstract
+ * @constructor
+ * @param {Mixed} target The object to be animated
+ */
+
+Ext.define('Ext.fx.target.Target', {
+
+ isAnimTarget: true,
+
+ constructor: function(target) {
+ this.target = target;
+ this.id = this.getId();
+ },
+
+ getId: function() {
+ return this.target.id;
+ }
+});
+
+/**
+ * @class Ext.fx.target.Sprite
+ * @extends Ext.fx.target.Target
+
+This class represents a animation target for a {@link Ext.draw.Sprite}. In general this class will not be
+created directly, the {@link Ext.draw.Sprite} will be passed to the animation and
+and the appropriate target will be created.
+
+ * @markdown
+ */
+
+Ext.define('Ext.fx.target.Sprite', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.fx.target.Target',
+
+ /* End Definitions */
+
+ type: 'draw',
+
+ getFromPrim: function(sprite, attr) {
+ var o;
+ if (attr == 'translate') {
+ o = {
+ x: sprite.attr.translation.x || 0,
+ y: sprite.attr.translation.y || 0
+ };
+ }
+ else if (attr == 'rotate') {
+ o = {
+ degrees: sprite.attr.rotation.degrees || 0,
+ x: sprite.attr.rotation.x,
+ y: sprite.attr.rotation.y
+ };
+ }
+ else {
+ o = sprite.attr[attr];
+ }
+ return o;
+ },
+
+ getAttr: function(attr, val) {
+ return [[this.target, val != undefined ? val : this.getFromPrim(this.target, attr)]];
+ },
+
+ setAttr: function(targetData) {
+ var ln = targetData.length,
+ spriteArr = [],
+ attrs, attr, attrArr, attPtr, spritePtr, idx, value, i, j, x, y, ln2;
+ for (i = 0; i < ln; i++) {
+ attrs = targetData[i].attrs;
+ for (attr in attrs) {
+ attrArr = attrs[attr];
+ ln2 = attrArr.length;
+ for (j = 0; j < ln2; j++) {
+ spritePtr = attrArr[j][0];
+ attPtr = attrArr[j][1];
+ if (attr === 'translate') {
+ value = {
+ x: attPtr.x,
+ y: attPtr.y
+ };
+ }
+ else if (attr === 'rotate') {
+ x = attPtr.x;
+ if (isNaN(x)) {
+ x = null;
+ }
+ y = attPtr.y;
+ if (isNaN(y)) {
+ y = null;
+ }
+ value = {
+ degrees: attPtr.degrees,
+ x: x,
+ y: y
+ };
+ }
+ else if (attr === 'width' || attr === 'height' || attr === 'x' || attr === 'y') {
+ value = parseFloat(attPtr);
+ }
+ else {
+ value = attPtr;
+ }
+ idx = Ext.Array.indexOf(spriteArr, spritePtr);
+ if (idx == -1) {
+ spriteArr.push([spritePtr, {}]);
+ idx = spriteArr.length - 1;
+ }
+ spriteArr[idx][1][attr] = value;
+ }
+ }
+ }
+ ln = spriteArr.length;
+ for (i = 0; i < ln; i++) {
+ spritePtr = spriteArr[i];
+ spritePtr[0].setAttributes(spritePtr[1]);
+ }
+ this.target.redraw();
+ }
+});
+
+/**
+ * @class Ext.fx.target.CompositeSprite
+ * @extends Ext.fx.target.Sprite
+
+This class represents a animation target for a {@link Ext.draw.CompositeSprite}. It allows
+each {@link Ext.draw.Sprite} in the group to be animated as a whole. In general this class will not be
+created directly, the {@link Ext.draw.CompositeSprite} will be passed to the animation and
+and the appropriate target will be created.
+
+ * @markdown
+ */
+
+Ext.define('Ext.fx.target.CompositeSprite', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.fx.target.Sprite',
+
+ /* End Definitions */
+
+ getAttr: function(attr, val) {
+ var out = [],
+ target = this.target;
+ target.each(function(sprite) {
+ out.push([sprite, val != undefined ? val : this.getFromPrim(sprite, attr)]);
+ }, this);
+ return out;
+ }
+});
+
+/**
+ * @class Ext.fx.target.Component
+ * @extends Ext.fx.target.Target
+ *
+ * This class represents a animation target for a {@link Ext.Component}. In general this class will not be
+ * created directly, the {@link Ext.Component} will be passed to the animation and
+ * and the appropriate target will be created.
+ */
+Ext.define('Ext.fx.target.Component', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.fx.target.Target',
+
+ /* End Definitions */
+
+ type: 'component',
+
+ // Methods to call to retrieve unspecified "from" values from a target Component
+ getPropMethod: {
+ top: function() {
+ return this.getPosition(true)[1];
+ },
+ left: function() {
+ return this.getPosition(true)[0];
+ },
+ x: function() {
+ return this.getPosition()[0];
+ },
+ y: function() {
+ return this.getPosition()[1];
+ },
+ height: function() {
+ return this.getHeight();
+ },
+ width: function() {
+ return this.getWidth();
+ },
+ opacity: function() {
+ return this.el.getStyle('opacity');
+ }
+ },
+
+ compMethod: {
+ top: 'setPosition',
+ left: 'setPosition',
+ x: 'setPagePosition',
+ y: 'setPagePosition',
+ height: 'setSize',
+ width: 'setSize',
+ opacity: 'setOpacity'
+ },
+
+ // Read the named attribute from the target Component. Use the defined getter for the attribute
+ getAttr: function(attr, val) {
+ return [[this.target, val !== undefined ? val : this.getPropMethod[attr].call(this.target)]];
+ },
+
+ setAttr: function(targetData, isFirstFrame, isLastFrame) {
+ var me = this,
+ target = me.target,
+ ln = targetData.length,
+ attrs, attr, o, i, j, meth, targets, left, top, w, h;
+ for (i = 0; i < ln; i++) {
+ attrs = targetData[i].attrs;
+ for (attr in attrs) {
+ targets = attrs[attr].length;
+ meth = {
+ setPosition: {},
+ setPagePosition: {},
+ setSize: {},
+ setOpacity: {}
+ };
+ for (j = 0; j < targets; j++) {
+ o = attrs[attr][j];
+ // We REALLY want a single function call, so push these down to merge them: eg
+ // meth.setPagePosition.target = <targetComponent>
+ // meth.setPagePosition['x'] = 100
+ // meth.setPagePosition['y'] = 100
+ meth[me.compMethod[attr]].target = o[0];
+ meth[me.compMethod[attr]][attr] = o[1];
+ }
+ if (meth.setPosition.target) {
+ o = meth.setPosition;
+ left = (o.left === undefined) ? undefined : parseInt(o.left, 10);
+ top = (o.top === undefined) ? undefined : parseInt(o.top, 10);
+ o.target.setPosition(left, top);
+ }
+ if (meth.setPagePosition.target) {
+ o = meth.setPagePosition;
+ o.target.setPagePosition(o.x, o.y);
+ }
+ if (meth.setSize.target) {
+ o = meth.setSize;
+ // Dimensions not being animated MUST NOT be autosized. They must remain at current value.
+ w = (o.width === undefined) ? o.target.getWidth() : parseInt(o.width, 10);
+ h = (o.height === undefined) ? o.target.getHeight() : parseInt(o.height, 10);
+
+ // Only set the size of the Component on the last frame, or if the animation was
+ // configured with dynamic: true.
+ // In other cases, we just set the target element size.
+ // This will result in either clipping if animating a reduction in size, or the revealing of
+ // the inner elements of the Component if animating an increase in size.
+ // Component's animate function initially resizes to the larger size before resizing the
+ // outer element to clip the contents.
+ if (isLastFrame || me.dynamic) {
+ o.target.componentLayout.childrenChanged = true;
+
+ // Flag if we are being called by an animating layout: use setCalculatedSize
+ if (me.layoutAnimation) {
+ o.target.setCalculatedSize(w, h);
+ } else {
+ o.target.setSize(w, h);
+ }
+ }
+ else {
+ o.target.el.setSize(w, h);
+ }
+ }
+ if (meth.setOpacity.target) {
+ o = meth.setOpacity;
+ o.target.el.setStyle('opacity', o.opacity);
+ }
+ }
+ }
+ }
+});
+
+/**
+ * @class Ext.fx.CubicBezier
+ * @ignore
+ */
+Ext.define('Ext.fx.CubicBezier', {
+
+ /* Begin Definitions */
+
+ singleton: true,
+
+ /* End Definitions */
+
+ cubicBezierAtTime: function(t, p1x, p1y, p2x, p2y, duration) {
+ var cx = 3 * p1x,
+ bx = 3 * (p2x - p1x) - cx,
+ ax = 1 - cx - bx,
+ cy = 3 * p1y,
+ by = 3 * (p2y - p1y) - cy,
+ ay = 1 - cy - by;
+ function sampleCurveX(t) {
+ return ((ax * t + bx) * t + cx) * t;
+ }
+ function solve(x, epsilon) {
+ var t = solveCurveX(x, epsilon);
+ return ((ay * t + by) * t + cy) * t;
+ }
+ function solveCurveX(x, epsilon) {
+ var t0, t1, t2, x2, d2, i;
+ for (t2 = x, i = 0; i < 8; i++) {
+ x2 = sampleCurveX(t2) - x;
+ if (Math.abs(x2) < epsilon) {
+ return t2;
+ }
+ d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
+ if (Math.abs(d2) < 1e-6) {
+ break;
+ }
+ t2 = t2 - x2 / d2;
+ }
+ t0 = 0;
+ t1 = 1;
+ t2 = x;
+ if (t2 < t0) {
+ return t0;
+ }
+ if (t2 > t1) {
+ return t1;
+ }
+ while (t0 < t1) {
+ x2 = sampleCurveX(t2);
+ if (Math.abs(x2 - x) < epsilon) {
+ return t2;
+ }
+ if (x > x2) {
+ t0 = t2;
+ } else {
+ t1 = t2;
+ }
+ t2 = (t1 - t0) / 2 + t0;
+ }
+ return t2;
+ }
+ return solve(t, 1 / (200 * duration));
+ },
+
+ cubicBezier: function(x1, y1, x2, y2) {
+ var fn = function(pos) {
+ return Ext.fx.CubicBezier.cubicBezierAtTime(pos, x1, y1, x2, y2, 1);
+ };
+ fn.toCSS3 = function() {
+ return 'cubic-bezier(' + [x1, y1, x2, y2].join(',') + ')';
+ };
+ fn.reverse = function() {
+ return Ext.fx.CubicBezier.cubicBezier(1 - x2, 1 - y2, 1 - x1, 1 - y1);
+ };
+ return fn;
+ }
+});
+/**
+ * @class Ext.draw.Color
+ * @extends Object
+ *
+ * Represents an RGB color and provides helper functions get
+ * color components in HSL color space.
+ */
+Ext.define('Ext.draw.Color', {
+
+ /* Begin Definitions */
+
+ /* End Definitions */
+
+ colorToHexRe: /(.*?)rgb\((\d+),\s*(\d+),\s*(\d+)\)/,
+ rgbRe: /\s*rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)\s*/,
+ hexRe: /\s*#([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)([0-9a-fA-F][0-9a-fA-F]?)\s*/,
+
+ /**
+ * @cfg {Number} lightnessFactor
+ *
+ * The default factor to compute the lighter or darker color. Defaults to 0.2.
+ */
+ lightnessFactor: 0.2,
+
+ /**
+ * @constructor
+ * @param {Number} red Red component (0..255)
+ * @param {Number} green Green component (0..255)
+ * @param {Number} blue Blue component (0..255)
+ */
+ constructor : function(red, green, blue) {
+ var me = this,
+ clamp = Ext.Number.constrain;
+ me.r = clamp(red, 0, 255);
+ me.g = clamp(green, 0, 255);
+ me.b = clamp(blue, 0, 255);
+ },
+
+ /**
+ * Get the red component of the color, in the range 0..255.
+ * @return {Number}
+ */
+ getRed: function() {
+ return this.r;
+ },
+
+ /**
+ * Get the green component of the color, in the range 0..255.
+ * @return {Number}
+ */
+ getGreen: function() {
+ return this.g;
+ },
+
+ /**
+ * Get the blue component of the color, in the range 0..255.
+ * @return {Number}
+ */
+ getBlue: function() {
+ return this.b;
+ },
+
+ /**
+ * Get the RGB values.
+ * @return {Array}
+ */
+ getRGB: function() {
+ var me = this;
+ return [me.r, me.g, me.b];
+ },
+
+ /**
+ * Get the equivalent HSL components of the color.
+ * @return {Array}
+ */
+ getHSL: function() {
+ var me = this,
+ r = me.r / 255,
+ g = me.g / 255,
+ b = me.b / 255,
+ max = Math.max(r, g, b),
+ min = Math.min(r, g, b),
+ delta = max - min,
+ h,
+ s = 0,
+ l = 0.5 * (max + min);
+
+ // min==max means achromatic (hue is undefined)
+ if (min != max) {
+ s = (l < 0.5) ? delta / (max + min) : delta / (2 - max - min);
+ if (r == max) {
+ h = 60 * (g - b) / delta;
+ } else if (g == max) {
+ h = 120 + 60 * (b - r) / delta;
+ } else {
+ h = 240 + 60 * (r - g) / delta;
+ }
+ if (h < 0) {
+ h += 360;
+ }
+ if (h >= 360) {
+ h -= 360;
+ }
+ }
+ return [h, s, l];
+ },
+
+ /**
+ * Return a new color that is lighter than this color.
+ * @param {Number} factor Lighter factor (0..1), default to 0.2
+ * @return Ext.draw.Color
+ */
+ getLighter: function(factor) {
+ var hsl = this.getHSL();
+ factor = factor || this.lightnessFactor;
+ hsl[2] = Ext.Number.constrain(hsl[2] + factor, 0, 1);
+ return this.fromHSL(hsl[0], hsl[1], hsl[2]);
+ },
+
+ /**
+ * Return a new color that is darker than this color.
+ * @param {Number} factor Darker factor (0..1), default to 0.2
+ * @return Ext.draw.Color
+ */
+ getDarker: function(factor) {
+ factor = factor || this.lightnessFactor;
+ return this.getLighter(-factor);
+ },
+
+ /**
+ * Return the color in the hex format, i.e. '#rrggbb'.
+ * @return {String}
+ */
+ toString: function() {
+ var me = this,
+ round = Math.round,
+ r = round(me.r).toString(16),
+ g = round(me.g).toString(16),
+ b = round(me.b).toString(16);
+ r = (r.length == 1) ? '0' + r : r;
+ g = (g.length == 1) ? '0' + g : g;
+ b = (b.length == 1) ? '0' + b : b;
+ return ['#', r, g, b].join('');
+ },
+
+ /**
+ * Convert a color to hexadecimal format.
+ *
+ * @param {String|Array} color The color value (i.e 'rgb(255, 255, 255)', 'color: #ffffff').
+ * Can also be an Array, in this case the function handles the first member.
+ * @returns {String} The color in hexadecimal format.
+ */
+ toHex: function(color) {
+ if (Ext.isArray(color)) {
+ color = color[0];
+ }
+ if (!Ext.isString(color)) {
+ return '';
+ }
+ if (color.substr(0, 1) === '#') {
+ return color;
+ }
+ var digits = this.colorToHexRe.exec(color);
+
+ if (Ext.isArray(digits)) {
+ var red = parseInt(digits[2], 10),
+ green = parseInt(digits[3], 10),
+ blue = parseInt(digits[4], 10),
+ rgb = blue | (green << 8) | (red << 16);
+ return digits[1] + '#' + ("000000" + rgb.toString(16)).slice(-6);
+ }
+ else {
+ return '';
+ }
+ },
+
+ /**
+ * Parse the string and create a new color.
+ *
+ * Supported formats: '#rrggbb', '#rgb', and 'rgb(r,g,b)'.
+ *
+ * If the string is not recognized, an undefined will be returned instead.
+ *
+ * @param {String} str Color in string.
+ * @returns Ext.draw.Color
+ */
+ fromString: function(str) {
+ var values, r, g, b,
+ parse = parseInt;
+
+ if ((str.length == 4 || str.length == 7) && str.substr(0, 1) === '#') {
+ values = str.match(this.hexRe);
+ if (values) {
+ r = parse(values[1], 16) >> 0;
+ g = parse(values[2], 16) >> 0;
+ b = parse(values[3], 16) >> 0;
+ if (str.length == 4) {
+ r += (r * 16);
+ g += (g * 16);
+ b += (b * 16);
+ }
+ }
+ }
+ else {
+ values = str.match(this.rgbRe);
+ if (values) {
+ r = values[1];
+ g = values[2];
+ b = values[3];
+ }
+ }
+
+ return (typeof r == 'undefined') ? undefined : Ext.create('Ext.draw.Color', r, g, b);
+ },
+
+ /**
+ * Returns the gray value (0 to 255) of the color.
+ *
+ * The gray value is calculated using the formula r*0.3 + g*0.59 + b*0.11.
+ *
+ * @returns {Number}
+ */
+ getGrayscale: function() {
+ // http://en.wikipedia.org/wiki/Grayscale#Converting_color_to_grayscale
+ return this.r * 0.3 + this.g * 0.59 + this.b * 0.11;
+ },
+
+ /**
+ * Create a new color based on the specified HSL values.
+ *
+ * @param {Number} h Hue component (0..359)
+ * @param {Number} s Saturation component (0..1)
+ * @param {Number} l Lightness component (0..1)
+ * @returns Ext.draw.Color
+ */
+ fromHSL: function(h, s, l) {
+ var C, X, m, i, rgb = [],
+ abs = Math.abs,
+ floor = Math.floor;
+
+ if (s == 0 || h == null) {
+ // achromatic
+ rgb = [l, l, l];
+ }
+ else {
+ // http://en.wikipedia.org/wiki/HSL_and_HSV#From_HSL
+ // C is the chroma
+ // X is the second largest component
+ // m is the lightness adjustment
+ h /= 60;
+ C = s * (1 - abs(2 * l - 1));
+ X = C * (1 - abs(h - 2 * floor(h / 2) - 1));
+ m = l - C / 2;
+ switch (floor(h)) {
+ case 0:
+ rgb = [C, X, 0];
+ break;
+ case 1:
+ rgb = [X, C, 0];
+ break;
+ case 2:
+ rgb = [0, C, X];
+ break;
+ case 3:
+ rgb = [0, X, C];
+ break;
+ case 4:
+ rgb = [X, 0, C];
+ break;
+ case 5:
+ rgb = [C, 0, X];
+ break;
+ }
+ rgb = [rgb[0] + m, rgb[1] + m, rgb[2] + m];
+ }
+ return Ext.create('Ext.draw.Color', rgb[0] * 255, rgb[1] * 255, rgb[2] * 255);
+ }
+}, function() {
+ var prototype = this.prototype;
+
+ //These functions are both static and instance. TODO: find a more elegant way of copying them
+ this.addStatics({
+ fromHSL: function() {
+ return prototype.fromHSL.apply(prototype, arguments);
+ },
+ fromString: function() {
+ return prototype.fromString.apply(prototype, arguments);
+ },
+ toHex: function() {
+ return prototype.toHex.apply(prototype, arguments);
+ }
+ });
+});
+
+/**
+ * @class Ext.dd.StatusProxy
+ * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. This is the
+ * default drag proxy used by all Ext.dd components.
+ * @constructor
+ * @param {Object} config
+ */
+Ext.define('Ext.dd.StatusProxy', {
+ animRepair: false,
+
+ constructor: function(config){
+ Ext.apply(this, config);
+ this.id = this.id || Ext.id();
+ this.proxy = Ext.createWidget('component', {
+ floating: true,
+ id: this.id,
+ html: '<div class="' + Ext.baseCSSPrefix + 'dd-drop-icon"></div>' +
+ '<div class="' + Ext.baseCSSPrefix + 'dd-drag-ghost"></div>',
+ cls: Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed,
+ shadow: !config || config.shadow !== false,
+ renderTo: document.body
+ });
+
+ this.el = this.proxy.el;
+ this.el.show();
+ this.el.setVisibilityMode(Ext.core.Element.VISIBILITY);
+ this.el.hide();
+
+ this.ghost = Ext.get(this.el.dom.childNodes[1]);
+ this.dropStatus = this.dropNotAllowed;
+ },
+ /**
+ * @cfg {String} dropAllowed
+ * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
+ */
+ dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
+ /**
+ * @cfg {String} dropNotAllowed
+ * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
+ */
+ dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
+
+ /**
+ * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
+ * over the current target element.
+ * @param {String} cssClass The css class for the new drop status indicator image
+ */
+ setStatus : function(cssClass){
+ cssClass = cssClass || this.dropNotAllowed;
+ if(this.dropStatus != cssClass){
+ this.el.replaceCls(this.dropStatus, cssClass);
+ this.dropStatus = cssClass;
+ }
+ },
+
+ /**
+ * Resets the status indicator to the default dropNotAllowed value
+ * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
+ */
+ reset : function(clearGhost){
+ this.el.dom.className = Ext.baseCSSPrefix + 'dd-drag-proxy ' + this.dropNotAllowed;
+ this.dropStatus = this.dropNotAllowed;
+ if(clearGhost){
+ this.ghost.update("");
+ }
+ },
+
+ /**
+ * Updates the contents of the ghost element
+ * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
+ * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
+ */
+ update : function(html){
+ if(typeof html == "string"){
+ this.ghost.update(html);
+ }else{
+ this.ghost.update("");
+ html.style.margin = "0";
+ this.ghost.dom.appendChild(html);
+ }
+ var el = this.ghost.dom.firstChild;
+ if(el){
+ Ext.fly(el).setStyle('float', 'none');
+ }
+ },
+
+ /**
+ * Returns the underlying proxy {@link Ext.Layer}
+ * @return {Ext.Layer} el
+ */
+ getEl : function(){
+ return this.el;
+ },
+
+ /**
+ * Returns the ghost element
+ * @return {Ext.core.Element} el
+ */
+ getGhost : function(){
+ return this.ghost;
+ },
+
+ /**
+ * Hides the proxy
+ * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
+ */
+ hide : function(clear) {
+ this.proxy.hide();
+ if (clear) {
+ this.reset(true);
+ }
+ },
+
+ /**
+ * Stops the repair animation if it's currently running
+ */
+ stop : function(){
+ if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
+ this.anim.stop();
+ }
+ },
+
+ /**
+ * Displays this proxy
+ */
+ show : function() {
+ this.proxy.show();
+ this.proxy.toFront();
+ },
+
+ /**
+ * Force the Layer to sync its shadow and shim positions to the element
+ */
+ sync : function(){
+ this.proxy.el.sync();
+ },
+
+ /**
+ * Causes the proxy to return to its position of origin via an animation. Should be called after an
+ * invalid drop operation by the item being dragged.
+ * @param {Array} xy The XY position of the element ([x, y])
+ * @param {Function} callback The function to call after the repair is complete.
+ * @param {Object} scope The scope (<code>this</code> reference) in which the callback function is executed. Defaults to the browser window.
+ */
+ repair : function(xy, callback, scope){
+ this.callback = callback;
+ this.scope = scope;
+ if (xy && this.animRepair !== false) {
+ this.el.addCls(Ext.baseCSSPrefix + 'dd-drag-repair');
+ this.el.hideUnders(true);
+ this.anim = this.el.animate({
+ duration: this.repairDuration || 500,
+ easing: 'ease-out',
+ to: {
+ x: xy[0],
+ y: xy[1]
+ },
+ stopAnimation: true,
+ callback: this.afterRepair,
+ scope: this
+ });
+ } else {
+ this.afterRepair();
+ }
+ },
+
+ // private
+ afterRepair : function(){
+ this.hide(true);
+ if(typeof this.callback == "function"){
+ this.callback.call(this.scope || this);
+ }
+ this.callback = null;
+ this.scope = null;
+ },
+
+ destroy: function(){
+ Ext.destroy(this.ghost, this.proxy, this.el);
+ }
+});
+/**
+ * @class Ext.panel.Proxy
+ * @extends Object
+ * A custom drag proxy implementation specific to {@link Ext.panel.Panel}s. This class
+ * is primarily used internally for the Panel's drag drop implementation, and
+ * should never need to be created directly.
+ * @constructor
+ * @param panel The {@link Ext.panel.Panel} to proxy for
+ * @param config Configuration options
+ */
+Ext.define('Ext.panel.Proxy', {
+
+ alternateClassName: 'Ext.dd.PanelProxy',
+
+ constructor: function(panel, config){
+ /**
+ * @property panel
+ * @type Ext.panel.Panel
+ */
+ this.panel = panel;
+ this.id = this.panel.id +'-ddproxy';
+ Ext.apply(this, config);
+ },
+
+ /**
+ * @cfg {Boolean} insertProxy True to insert a placeholder proxy element
+ * while dragging the panel, false to drag with no proxy (defaults to true).
+ * Most Panels are not absolute positioned and therefore we need to reserve
+ * this space.
+ */
+ insertProxy: true,
+
+ // private overrides
+ setStatus: Ext.emptyFn,
+ reset: Ext.emptyFn,
+ update: Ext.emptyFn,
+ stop: Ext.emptyFn,
+ sync: Ext.emptyFn,
+
+ /**
+ * Gets the proxy's element
+ * @return {Element} The proxy's element
+ */
+ getEl: function(){
+ return this.ghost.el;
+ },
+
+ /**
+ * Gets the proxy's ghost Panel
+ * @return {Panel} The proxy's ghost Panel
+ */
+ getGhost: function(){
+ return this.ghost;
+ },
+
+ /**
+ * Gets the proxy element. This is the element that represents where the
+ * Panel was before we started the drag operation.
+ * @return {Element} The proxy's element
+ */
+ getProxy: function(){
+ return this.proxy;
+ },
+
+ /**
+ * Hides the proxy
+ */
+ hide : function(){
+ if (this.ghost) {
+ if (this.proxy) {
+ this.proxy.remove();
+ delete this.proxy;
+ }
+
+ // Unghost the Panel, do not move the Panel to where the ghost was
+ this.panel.unghost(null, false);
+ delete this.ghost;
+ }
+ },
+
+ /**
+ * Shows the proxy
+ */
+ show: function(){
+ if (!this.ghost) {
+ var panelSize = this.panel.getSize();
+ this.panel.el.setVisibilityMode(Ext.core.Element.DISPLAY);
+ this.ghost = this.panel.ghost();
+ if (this.insertProxy) {
+ // bc Panels aren't absolute positioned we need to take up the space
+ // of where the panel previously was
+ this.proxy = this.panel.el.insertSibling({cls: Ext.baseCSSPrefix + 'panel-dd-spacer'});
+ this.proxy.setSize(panelSize);
+ }
+ }
+ },
+
+ // private
+ repair: function(xy, callback, scope) {
+ this.hide();
+ if (typeof callback == "function") {
+ callback.call(scope || this);
+ }
+ },
+
+ /**
+ * Moves the proxy to a different position in the DOM. This is typically
+ * called while dragging the Panel to keep the proxy sync'd to the Panel's
+ * location.
+ * @param {HTMLElement} parentNode The proxy's parent DOM node
+ * @param {HTMLElement} before (optional) The sibling node before which the
+ * proxy should be inserted (defaults to the parent's last child if not
+ * specified)
+ */
+ moveProxy : function(parentNode, before){
+ if (this.proxy) {
+ parentNode.insertBefore(this.proxy.dom, before);
+ }
+ }
+});
+/**
+ * @class Ext.layout.component.AbstractDock
+ * @extends Ext.layout.component.Component
+ * @private
+ * This ComponentLayout handles docking for Panels. It takes care of panels that are
+ * part of a ContainerLayout that sets this Panel's size and Panels that are part of
+ * an AutoContainerLayout in which this panel get his height based of the CSS or
+ * or its content.
+ */
+
+Ext.define('Ext.layout.component.AbstractDock', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.component.Component',
+
+ /* End Definitions */
+
+ type: 'dock',
+
+ /**
+ * @private
+ * @property autoSizing
+ * @type boolean
+ * This flag is set to indicate this layout may have an autoHeight/autoWidth.
+ */
+ autoSizing: true,
+
+ beforeLayout: function() {
+ var returnValue = this.callParent(arguments);
+ if (returnValue !== false && (!this.initializedBorders || this.childrenChanged) && (!this.owner.border || this.owner.manageBodyBorders)) {
+ this.handleItemBorders();
+ this.initializedBorders = true;
+ }
+ return returnValue;
+ },
+
+ handleItemBorders: function() {
+ var owner = this.owner,
+ body = owner.body,
+ docked = this.getLayoutItems(),
+ borders = {
+ top: [],
+ right: [],
+ bottom: [],
+ left: []
+ },
+ oldBorders = this.borders,
+ opposites = {
+ top: 'bottom',
+ right: 'left',
+ bottom: 'top',
+ left: 'right'
+ },
+ i, ln, item, dock, side;
+
+ for (i = 0, ln = docked.length; i < ln; i++) {
+ item = docked[i];
+ dock = item.dock;
+
+ if (item.ignoreBorderManagement) {
+ continue;
+ }
+
+ if (!borders[dock].satisfied) {
+ borders[dock].push(item);
+ borders[dock].satisfied = true;
+ }
+
+ if (!borders.top.satisfied && opposites[dock] !== 'top') {
+ borders.top.push(item);
+ }
+ if (!borders.right.satisfied && opposites[dock] !== 'right') {
+ borders.right.push(item);
+ }
+ if (!borders.bottom.satisfied && opposites[dock] !== 'bottom') {
+ borders.bottom.push(item);
+ }
+ if (!borders.left.satisfied && opposites[dock] !== 'left') {
+ borders.left.push(item);
+ }
+ }
+
+ if (oldBorders) {
+ for (side in oldBorders) {
+ if (oldBorders.hasOwnProperty(side)) {
+ ln = oldBorders[side].length;
+ if (!owner.manageBodyBorders) {
+ for (i = 0; i < ln; i++) {
+ oldBorders[side][i].removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
+ }
+ if (!oldBorders[side].satisfied && !owner.bodyBorder) {
+ body.removeCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
+ }
+ }
+ else if (oldBorders[side].satisfied) {
+ body.setStyle('border-' + side + '-width', '');
+ }
+ }
+ }
+ }
+
+ for (side in borders) {
+ if (borders.hasOwnProperty(side)) {
+ ln = borders[side].length;
+ if (!owner.manageBodyBorders) {
+ for (i = 0; i < ln; i++) {
+ borders[side][i].addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
+ }
+ if ((!borders[side].satisfied && !owner.bodyBorder) || owner.bodyBorder === false) {
+ body.addCls(Ext.baseCSSPrefix + 'docked-noborder-' + side);
+ }
+ }
+ else if (borders[side].satisfied) {
+ body.setStyle('border-' + side + '-width', '1px');
+ }
+ }
+ }
+
+ this.borders = borders;
+ },
+
+ /**
+ * @protected
+ * @param {Ext.Component} owner The Panel that owns this DockLayout
+ * @param {Ext.core.Element} target The target in which we are going to render the docked items
+ * @param {Array} args The arguments passed to the ComponentLayout.layout method
+ */
+ onLayout: function(width, height) {
+ var me = this,
+ owner = me.owner,
+ body = owner.body,
+ layout = owner.layout,
+ target = me.getTarget(),
+ autoWidth = false,
+ autoHeight = false,
+ padding, border, frameSize;
+
+ // We start of by resetting all the layouts info
+ var info = me.info = {
+ boxes: [],
+ size: {
+ width: width,
+ height: height
+ },
+ bodyBox: {}
+ };
+
+ Ext.applyIf(info, me.getTargetInfo());
+
+ // We need to bind to the ownerCt whenever we do not have a user set height or width.
+ if (owner && owner.ownerCt && owner.ownerCt.layout && owner.ownerCt.layout.isLayout) {
+ if (!Ext.isNumber(owner.height) || !Ext.isNumber(owner.width)) {
+ owner.ownerCt.layout.bindToOwnerCtComponent = true;
+ }
+ else {
+ owner.ownerCt.layout.bindToOwnerCtComponent = false;
+ }
+ }
+
+ // Determine if we have an autoHeight or autoWidth.
+ if (height === undefined || height === null || width === undefined || width === null) {
+ padding = info.padding;
+ border = info.border;
+ frameSize = me.frameSize;
+
+ // Auto-everything, clear out any style height/width and read from css
+ if ((height === undefined || height === null) && (width === undefined || width === null)) {
+ autoHeight = true;
+ autoWidth = true;
+ me.setTargetSize(null);
+ me.setBodyBox({width: null, height: null});
+ }
+ // Auto-height
+ else if (height === undefined || height === null) {
+ autoHeight = true;
+ // Clear any sizing that we already set in a previous layout
+ me.setTargetSize(width);
+ me.setBodyBox({width: width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right, height: null});
+ // Auto-width
+ }
+ else {
+ autoWidth = true;
+ // Clear any sizing that we already set in a previous layout
+ me.setTargetSize(null, height);
+ me.setBodyBox({width: null, height: height - padding.top - padding.bottom - border.top - border.bottom - frameSize.top - frameSize.bottom});
+ }
+
+ // Run the container
+ if (layout && layout.isLayout) {
+ // Auto-Sized so have the container layout notify the component layout.
+ layout.bindToOwnerCtComponent = true;
+ layout.layout();
+
+ // If this is an autosized container layout, then we must compensate for a
+ // body that is being autosized. We do not want to adjust the body's size
+ // to accommodate the dock items, but rather we will want to adjust the
+ // target's size.
+ //
+ // This is necessary because, particularly in a Box layout, all child items
+ // are set with absolute dimensions that are not flexible to the size of its
+ // innerCt/target. So once they are laid out, they are sized for good. By
+ // shrinking the body box to accommodate dock items, we're merely cutting off
+ // parts of the body. Not good. Instead, the target's size should expand
+ // to fit the dock items in. This is valid because the target container is
+ // suppose to be autosized to fit everything accordingly.
+ info.autoSizedCtLayout = layout.autoSize === true;
+ }
+
+ // The dockItems method will add all the top and bottom docked items height
+ // to the info.panelSize height. That's why we have to call setSize after
+ // we dock all the items to actually set the panel's width and height.
+ // We have to do this because the panel body and docked items will be position
+ // absolute which doesn't stretch the panel.
+ me.dockItems(autoWidth, autoHeight);
+ me.setTargetSize(info.size.width, info.size.height);
+ }
+ else {
+ me.setTargetSize(width, height);
+ me.dockItems();
+ }
+ me.callParent(arguments);
+ },
+
+ /**
+ * @protected
+ * This method will first update all the information about the docked items,
+ * body dimensions and position, the panel's total size. It will then
+ * set all these values on the docked items and panel body.
+ * @param {Array} items Array containing all the docked items
+ * @param {Boolean} autoBoxes Set this to true if the Panel is part of an
+ * AutoContainerLayout
+ */
+ dockItems : function(autoWidth, autoHeight) {
+ this.calculateDockBoxes(autoWidth, autoHeight);
+
+ // Both calculateAutoBoxes and calculateSizedBoxes are changing the
+ // information about the body, panel size, and boxes for docked items
+ // inside a property called info.
+ var info = this.info,
+ boxes = info.boxes,
+ ln = boxes.length,
+ dock, i;
+
+ // We are going to loop over all the boxes that were calculated
+ // and set the position of each item the box belongs to.
+ for (i = 0; i < ln; i++) {
+ dock = boxes[i];
+ dock.item.setPosition(dock.x, dock.y);
+ if ((autoWidth || autoHeight) && dock.layout && dock.layout.isLayout) {
+ // Auto-Sized so have the container layout notify the component layout.
+ dock.layout.bindToOwnerCtComponent = true;
+ }
+ }
+
+ // Don't adjust body width/height if the target is using an auto container layout.
+ // But, we do want to adjust the body size if the container layout is auto sized.
+ if (!info.autoSizedCtLayout) {
+ if (autoWidth) {
+ info.bodyBox.width = null;
+ }
+ if (autoHeight) {
+ info.bodyBox.height = null;
+ }
+ }
+
+ // If the bodyBox has been adjusted because of the docked items
+ // we will update the dimensions and position of the panel's body.
+ this.setBodyBox(info.bodyBox);
+ },
+
+ /**
+ * @protected
+ * This method will set up some initial information about the panel size and bodybox
+ * and then loop over all the items you pass it to take care of stretching, aligning,
+ * dock position and all calculations involved with adjusting the body box.
+ * @param {Array} items Array containing all the docked items we have to layout
+ */
+ calculateDockBoxes : function(autoWidth, autoHeight) {
+ // We want to use the Panel's el width, and the Panel's body height as the initial
+ // size we are going to use in calculateDockBoxes. We also want to account for
+ // the border of the panel.
+ var me = this,
+ target = me.getTarget(),
+ items = me.getLayoutItems(),
+ owner = me.owner,
+ bodyEl = owner.body,
+ info = me.info,
+ size = info.size,
+ ln = items.length,
+ padding = info.padding,
+ border = info.border,
+ frameSize = me.frameSize,
+ item, i, box, rect;
+
+ // If this Panel is inside an AutoContainerLayout, we will base all the calculations
+ // around the height of the body and the width of the panel.
+ if (autoHeight) {
+ size.height = bodyEl.getHeight() + padding.top + border.top + padding.bottom + border.bottom + frameSize.top + frameSize.bottom;
+ }
+ else {
+ size.height = target.getHeight();
+ }
+ if (autoWidth) {
+ size.width = bodyEl.getWidth() + padding.left + border.left + padding.right + border.right + frameSize.left + frameSize.right;
+ }
+ else {
+ size.width = target.getWidth();
+ }
+
+ info.bodyBox = {
+ x: padding.left + frameSize.left,
+ y: padding.top + frameSize.top,
+ width: size.width - padding.left - border.left - padding.right - border.right - frameSize.left - frameSize.right,
+ height: size.height - border.top - padding.top - border.bottom - padding.bottom - frameSize.top - frameSize.bottom
+ };
+
+ // Loop over all the docked items
+ for (i = 0; i < ln; i++) {
+ item = items[i];
+ // The initBox method will take care of stretching and alignment
+ // In some cases it will also layout the dock items to be able to
+ // get a width or height measurement
+ box = me.initBox(item);
+
+ if (autoHeight === true) {
+ box = me.adjustAutoBox(box, i);
+ }
+ else {
+ box = me.adjustSizedBox(box, i);
+ }
+
+ // Save our box. This allows us to loop over all docked items and do all
+ // calculations first. Then in one loop we will actually size and position
+ // all the docked items that have changed.
+ info.boxes.push(box);
+ }
+ },
+
+ /**
+ * @protected
+ * This method will adjust the position of the docked item and adjust the body box
+ * accordingly.
+ * @param {Object} box The box containing information about the width and height
+ * of this docked item
+ * @param {Number} index The index position of this docked item
+ * @return {Object} The adjusted box
+ */
+ adjustSizedBox : function(box, index) {
+ var bodyBox = this.info.bodyBox,
+ frameSize = this.frameSize,
+ info = this.info,
+ padding = info.padding,
+ pos = box.type,
+ border = info.border;
+
+ switch (pos) {
+ case 'top':
+ box.y = bodyBox.y;
+ break;
+
+ case 'left':
+ box.x = bodyBox.x;
+ break;
+
+ case 'bottom':
+ box.y = (bodyBox.y + bodyBox.height) - box.height;
+ break;
+
+ case 'right':
+ box.x = (bodyBox.x + bodyBox.width) - box.width;
+ break;
+ }
+
+ if (box.ignoreFrame) {
+ if (pos == 'bottom') {
+ box.y += (frameSize.bottom + padding.bottom + border.bottom);
+ }
+ else {
+ box.y -= (frameSize.top + padding.top + border.top);
+ }
+ if (pos == 'right') {
+ box.x += (frameSize.right + padding.right + border.right);
+ }
+ else {
+ box.x -= (frameSize.left + padding.left + border.left);
+ }
+ }
+
+ // If this is not an overlaying docked item, we have to adjust the body box
+ if (!box.overlay) {
+ switch (pos) {
+ case 'top':
+ bodyBox.y += box.height;
+ bodyBox.height -= box.height;
+ break;
+
+ case 'left':
+ bodyBox.x += box.width;
+ bodyBox.width -= box.width;
+ break;
+
+ case 'bottom':
+ bodyBox.height -= box.height;
+ break;
+
+ case 'right':
+ bodyBox.width -= box.width;
+ break;
+ }
+ }
+ return box;
+ },
+
+ /**
+ * @protected
+ * This method will adjust the position of the docked item inside an AutoContainerLayout
+ * and adjust the body box accordingly.
+ * @param {Object} box The box containing information about the width and height
+ * of this docked item
+ * @param {Number} index The index position of this docked item
+ * @return {Object} The adjusted box
+ */
+ adjustAutoBox : function (box, index) {
+ var info = this.info,
+ bodyBox = info.bodyBox,
+ size = info.size,
+ boxes = info.boxes,
+ boxesLn = boxes.length,
+ pos = box.type,
+ frameSize = this.frameSize,
+ padding = info.padding,
+ border = info.border,
+ autoSizedCtLayout = info.autoSizedCtLayout,
+ ln = (boxesLn < index) ? boxesLn : index,
+ i, adjustBox;
+
+ if (pos == 'top' || pos == 'bottom') {
+ // This can affect the previously set left and right and bottom docked items
+ for (i = 0; i < ln; i++) {
+ adjustBox = boxes[i];
+ if (adjustBox.stretched && adjustBox.type == 'left' || adjustBox.type == 'right') {
+ adjustBox.height += box.height;
+ }
+ else if (adjustBox.type == 'bottom') {
+ adjustBox.y += box.height;
+ }
+ }
+ }
+
+ switch (pos) {
+ case 'top':
+ box.y = bodyBox.y;
+ if (!box.overlay) {
+ bodyBox.y += box.height;
+ }
+ size.height += box.height;
+ break;
+
+ case 'bottom':
+ box.y = (bodyBox.y + bodyBox.height);
+ size.height += box.height;
+ break;
+
+ case 'left':
+ box.x = bodyBox.x;
+ if (!box.overlay) {
+ bodyBox.x += box.width;
+ if (autoSizedCtLayout) {
+ size.width += box.width;
+ } else {
+ bodyBox.width -= box.width;
+ }
+ }
+ break;
+
+ case 'right':
+ if (!box.overlay) {
+ if (autoSizedCtLayout) {
+ size.width += box.width;
+ } else {
+ bodyBox.width -= box.width;
+ }
+ }
+ box.x = (bodyBox.x + bodyBox.width);
+ break;
+ }
+
+ if (box.ignoreFrame) {
+ if (pos == 'bottom') {
+ box.y += (frameSize.bottom + padding.bottom + border.bottom);
+ }
+ else {
+ box.y -= (frameSize.top + padding.top + border.top);
+ }
+ if (pos == 'right') {
+ box.x += (frameSize.right + padding.right + border.right);
+ }
+ else {
+ box.x -= (frameSize.left + padding.left + border.left);
+ }
+ }
+ return box;
+ },
+
+ /**
+ * @protected
+ * This method will create a box object, with a reference to the item, the type of dock
+ * (top, left, bottom, right). It will also take care of stretching and aligning of the
+ * docked items.
+ * @param {Ext.Component} item The docked item we want to initialize the box for
+ * @return {Object} The initial box containing width and height and other useful information
+ */
+ initBox : function(item) {
+ var me = this,
+ bodyBox = me.info.bodyBox,
+ horizontal = (item.dock == 'top' || item.dock == 'bottom'),
+ owner = me.owner,
+ frameSize = me.frameSize,
+ info = me.info,
+ padding = info.padding,
+ border = info.border,
+ box = {
+ item: item,
+ overlay: item.overlay,
+ type: item.dock,
+ offsets: Ext.core.Element.parseBox(item.offsets || {}),
+ ignoreFrame: item.ignoreParentFrame
+ };
+ // First we are going to take care of stretch and align properties for all four dock scenarios.
+ if (item.stretch !== false) {
+ box.stretched = true;
+ if (horizontal) {
+ box.x = bodyBox.x + box.offsets.left;
+ box.width = bodyBox.width - (box.offsets.left + box.offsets.right);
+ if (box.ignoreFrame) {
+ box.width += (frameSize.left + frameSize.right + border.left + border.right + padding.left + padding.right);
+ }
+ item.setCalculatedSize(box.width - item.el.getMargin('lr'), undefined, owner);
+ }
+ else {
+ box.y = bodyBox.y + box.offsets.top;
+ box.height = bodyBox.height - (box.offsets.bottom + box.offsets.top);
+ if (box.ignoreFrame) {
+ box.height += (frameSize.top + frameSize.bottom + border.top + border.bottom + padding.top + padding.bottom);
+ }
+ item.setCalculatedSize(undefined, box.height - item.el.getMargin('tb'), owner);
+
+ // At this point IE will report the left/right-docked toolbar as having a width equal to the
+ // container's full width. Forcing a repaint kicks it into shape so it reports the correct width.
+ if (!Ext.supports.ComputedStyle) {
+ item.el.repaint();
+ }
+ }
+ }
+ else {
+ item.doComponentLayout();
+ box.width = item.getWidth() - (box.offsets.left + box.offsets.right);
+ box.height = item.getHeight() - (box.offsets.bottom + box.offsets.top);
+ box.y += box.offsets.top;
+ if (horizontal) {
+ box.x = (item.align == 'right') ? bodyBox.width - box.width : bodyBox.x;
+ box.x += box.offsets.left;
+ }
+ }
+
+ // If we haven't calculated the width or height of the docked item yet
+ // do so, since we need this for our upcoming calculations
+ if (box.width == undefined) {
+ box.width = item.getWidth() + item.el.getMargin('lr');
+ }
+ if (box.height == undefined) {
+ box.height = item.getHeight() + item.el.getMargin('tb');
+ }
+
+ return box;
+ },
+
+ /**
+ * @protected
+ * Returns an array containing all the <b>visible</b> docked items inside this layout's owner Panel
+ * @return {Array} An array containing all the <b>visible</b> docked items of the Panel
+ */
+ getLayoutItems : function() {
+ var it = this.owner.getDockedItems(),
+ ln = it.length,
+ i = 0,
+ result = [];
+ for (; i < ln; i++) {
+ if (it[i].isVisible(true)) {
+ result.push(it[i]);
+ }
+ }
+ return result;
+ },
+
+ /**
+ * @protected
+ * Render the top and left docked items before any existing DOM nodes in our render target,
+ * and then render the right and bottom docked items after. This is important, for such things
+ * as tab stops and ARIA readers, that the DOM nodes are in a meaningful order.
+ * Our collection of docked items will already be ordered via Panel.getDockedItems().
+ */
+ renderItems: function(items, target) {
+ var cns = target.dom.childNodes,
+ cnsLn = cns.length,
+ ln = items.length,
+ domLn = 0,
+ i, j, cn, item;
+
+ // Calculate the number of DOM nodes in our target that are not our docked items
+ for (i = 0; i < cnsLn; i++) {
+ cn = Ext.get(cns[i]);
+ for (j = 0; j < ln; j++) {
+ item = items[j];
+ if (item.rendered && (cn.id == item.el.id || cn.down('#' + item.el.id))) {
+ break;
+ }
+ }
+
+ if (j === ln) {
+ domLn++;
+ }
+ }
+
+ // Now we go through our docked items and render/move them
+ for (i = 0, j = 0; i < ln; i++, j++) {
+ item = items[i];
+
+ // If we're now at the right/bottom docked item, we jump ahead in our
+ // DOM position, just past the existing DOM nodes.
+ //
+ // TODO: This is affected if users provide custom weight values to their
+ // docked items, which puts it out of (t,l,r,b) order. Avoiding a second
+ // sort operation here, for now, in the name of performance. getDockedItems()
+ // needs the sort operation not just for this layout-time rendering, but
+ // also for getRefItems() to return a logical ordering (FocusManager, CQ, et al).
+ if (i === j && (item.dock === 'right' || item.dock === 'bottom')) {
+ j += domLn;
+ }
+
+ // Same logic as Layout.renderItems()
+ if (item && !item.rendered) {
+ this.renderItem(item, target, j);
+ }
+ else if (!this.isValidParent(item, target, j)) {
+ this.moveItem(item, target, j);
+ }
+ }
+ },
+
+ /**
+ * @protected
+ * This function will be called by the dockItems method. Since the body is positioned absolute,
+ * we need to give it dimensions and a position so that it is in the middle surrounded by
+ * docked items
+ * @param {Object} box An object containing new x, y, width and height values for the
+ * Panel's body
+ */
+ setBodyBox : function(box) {
+ var me = this,
+ owner = me.owner,
+ body = owner.body,
+ info = me.info,
+ bodyMargin = info.bodyMargin,
+ padding = info.padding,
+ border = info.border,
+ frameSize = me.frameSize;
+
+ // Panel collapse effectively hides the Panel's body, so this is a no-op.
+ if (owner.collapsed) {
+ return;
+ }
+
+ if (Ext.isNumber(box.width)) {
+ box.width -= bodyMargin.left + bodyMargin.right;
+ }
+
+ if (Ext.isNumber(box.height)) {
+ box.height -= bodyMargin.top + bodyMargin.bottom;
+ }
+
+ me.setElementSize(body, box.width, box.height);
+ if (Ext.isNumber(box.x)) {
+ body.setLeft(box.x - padding.left - frameSize.left);
+ }
+ if (Ext.isNumber(box.y)) {
+ body.setTop(box.y - padding.top - frameSize.top);
+ }
+ },
+
+ /**
+ * @protected
+ * We are overriding the Ext.layout.Layout configureItem method to also add a class that
+ * indicates the position of the docked item. We use the itemCls (x-docked) as a prefix.
+ * An example of a class added to a dock: right item is x-docked-right
+ * @param {Ext.Component} item The item we are configuring
+ */
+ configureItem : function(item, pos) {
+ this.callParent(arguments);
+
+ item.addCls(Ext.baseCSSPrefix + 'docked');
+ item.addClsWithUI('docked-' + item.dock);
+ },
+
+ afterRemove : function(item) {
+ this.callParent(arguments);
+ if (this.itemCls) {
+ item.el.removeCls(this.itemCls + '-' + item.dock);
+ }
+ var dom = item.el.dom;
+
+ if (!item.destroying && dom) {
+ dom.parentNode.removeChild(dom);
+ }
+ this.childrenChanged = true;
+ }
+});
+/**
+ * @class Ext.app.EventBus
+ * @private
+ *
+ * Class documentation for the MVC classes will be present before 4.0 final, in the mean time please refer to the MVC
+ * guide
+ */
+Ext.define('Ext.app.EventBus', {
+ requires: [
+ 'Ext.util.Event'
+ ],
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ constructor: function() {
+ this.mixins.observable.constructor.call(this);
+
+ this.bus = {};
+
+ var me = this;
+ Ext.override(Ext.Component, {
+ fireEvent: function(ev) {
+ if (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false) {
+ return me.dispatch.call(me, ev, this, arguments);
+ }
+ return false;
+ }
+ });
+ },
+
+ dispatch: function(ev, target, args) {
+ var bus = this.bus,
+ selectors = bus[ev],
+ selector, controllers, id, events, event, i, ln;
+
+ if (selectors) {
+ // Loop over all the selectors that are bound to this event
+ for (selector in selectors) {
+ // Check if the target matches the selector
+ if (target.is(selector)) {
+ // Loop over all the controllers that are bound to this selector
+ controllers = selectors[selector];
+ for (id in controllers) {
+ // Loop over all the events that are bound to this selector on this controller
+ events = controllers[id];
+ for (i = 0, ln = events.length; i < ln; i++) {
+ event = events[i];
+ // Fire the event!
+ return event.fire.apply(event, Array.prototype.slice.call(args, 1));
+ }
+ }
+ }
+ }
+ }
+ },
+
+ control: function(selectors, listeners, controller) {
+ var bus = this.bus,
+ selector, fn;
+
+ if (Ext.isString(selectors)) {
+ selector = selectors;
+ selectors = {};
+ selectors[selector] = listeners;
+ this.control(selectors, null, controller);
+ return;
+ }
+
+ Ext.Object.each(selectors, function(selector, listeners) {
+ Ext.Object.each(listeners, function(ev, listener) {
+ var options = {},
+ scope = controller,
+ event = Ext.create('Ext.util.Event', controller, ev);
+
+ // Normalize the listener
+ if (Ext.isObject(listener)) {
+ options = listener;
+ listener = options.fn;
+ scope = options.scope || controller;
+ delete options.fn;
+ delete options.scope;
+ }
+
+ event.addListener(listener, scope, options);
+
+ // Create the bus tree if it is not there yet
+ bus[ev] = bus[ev] || {};
+ bus[ev][selector] = bus[ev][selector] || {};
+ bus[ev][selector][controller.id] = bus[ev][selector][controller.id] || [];
+
+ // Push our listener in our bus
+ bus[ev][selector][controller.id].push(event);
+ });
+ });
+ }
+});
+/**
+ * @class Ext.data.Types
+ * <p>This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.<p/>
+ * <p>The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to
+ * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties
+ * of this class.</p>
+ * <p>Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE.
+ * each type definition must contain three properties:</p>
+ * <div class="mdetail-params"><ul>
+ * <li><code>convert</code> : <i>Function</i><div class="sub-desc">A function to convert raw data values from a data block into the data
+ * to be stored in the Field. The function is passed the collowing parameters:
+ * <div class="mdetail-params"><ul>
+ * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
+ * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
+ * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
+ * Depending on the Reader type, this could be an Array ({@link Ext.data.reader.Array ArrayReader}), an object
+ * ({@link Ext.data.reader.Json JsonReader}), or an XML element.</div></li>
+ * </ul></div></div></li>
+ * <li><code>sortType</code> : <i>Function</i> <div class="sub-desc">A function to convert the stored data into comparable form, as defined by {@link Ext.data.SortTypes}.</div></li>
+ * <li><code>type</code> : <i>String</i> <div class="sub-desc">A textual data type name.</div></li>
+ * </ul></div>
+ * <p>For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
+ * which contained the properties <code>lat</code> and <code>long</code>, you would define a new data type like this:</p>
+ *<pre><code>
+// Add a new Field data type which stores a VELatLong object in the Record.
+Ext.data.Types.VELATLONG = {
+ convert: function(v, data) {
+ return new VELatLong(data.lat, data.long);
+ },
+ sortType: function(v) {
+ return v.Latitude; // When sorting, order by latitude
+ },
+ type: 'VELatLong'
+};
+</code></pre>
+ * <p>Then, when declaring a Model, use <pre><code>
+var types = Ext.data.Types; // allow shorthand type access
+Ext.define('Unit',
+ extend: 'Ext.data.Model',
+ fields: [
+ { name: 'unitName', mapping: 'UnitName' },
+ { name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
+ { name: 'latitude', mapping: 'lat', type: types.FLOAT },
+ { name: 'latitude', mapping: 'lat', type: types.FLOAT },
+ { name: 'position', type: types.VELATLONG }
+ ]
+});
+</code></pre>
+ * @singleton
+ */
+Ext.define('Ext.data.Types', {
+ singleton: true,
+ requires: ['Ext.data.SortTypes']
+}, function() {
+ var st = Ext.data.SortTypes;
+
+ Ext.apply(Ext.data.Types, {
+ /**
+ * @type Regexp
+ * @property stripRe
+ * A regular expression for stripping non-numeric characters from a numeric value. Defaults to <tt>/[\$,%]/g</tt>.
+ * This should be overridden for localization.
+ */
+ stripRe: /[\$,%]/g,
+
+ /**
+ * @type Object.
+ * @property AUTO
+ * This data type means that no conversion is applied to the raw data before it is placed into a Record.
+ */
+ AUTO: {
+ convert: function(v) {
+ return v;
+ },
+ sortType: st.none,
+ type: 'auto'
+ },
+
+ /**
+ * @type Object.
+ * @property STRING
+ * This data type means that the raw data is converted into a String before it is placed into a Record.
+ */
+ STRING: {
+ convert: function(v) {
+ var defaultValue = this.useNull ? null : '';
+ return (v === undefined || v === null) ? defaultValue : String(v);
+ },
+ sortType: st.asUCString,
+ type: 'string'
+ },
+
+ /**
+ * @type Object.
+ * @property INT
+ * This data type means that the raw data is converted into an integer before it is placed into a Record.
+ * <p>The synonym <code>INTEGER</code> is equivalent.</p>
+ */
+ INT: {
+ convert: function(v) {
+ return v !== undefined && v !== null && v !== '' ?
+ parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
+ },
+ sortType: st.none,
+ type: 'int'
+ },
+
+ /**
+ * @type Object.
+ * @property FLOAT
+ * This data type means that the raw data is converted into a number before it is placed into a Record.
+ * <p>The synonym <code>NUMBER</code> is equivalent.</p>
+ */
+ FLOAT: {
+ convert: function(v) {
+ return v !== undefined && v !== null && v !== '' ?
+ parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
+ },
+ sortType: st.none,
+ type: 'float'
+ },
+
+ /**
+ * @type Object.
+ * @property BOOL
+ * <p>This data type means that the raw data is converted into a boolean before it is placed into
+ * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
+ * <p>The synonym <code>BOOLEAN</code> is equivalent.</p>
+ */
+ BOOL: {
+ convert: function(v) {
+ if (this.useNull && v === undefined || v === null || v === '') {
+ return null;
+ }
+ return v === true || v === 'true' || v == 1;
+ },
+ sortType: st.none,
+ type: 'bool'
+ },
+
+ /**
+ * @type Object.
+ * @property DATE
+ * This data type means that the raw data is converted into a Date before it is placed into a Record.
+ * The date format is specified in the constructor of the {@link Ext.data.Field} to which this type is
+ * being applied.
+ */
+ DATE: {
+ convert: function(v) {
+ var df = this.dateFormat;
+ if (!v) {
+ return null;
+ }
+ if (Ext.isDate(v)) {
+ return v;
+ }
+ if (df) {
+ if (df == 'timestamp') {
+ return new Date(v*1000);
+ }
+ if (df == 'time') {
+ return new Date(parseInt(v, 10));
+ }
+ return Ext.Date.parse(v, df);
+ }
+
+ var parsed = Date.parse(v);
+ return parsed ? new Date(parsed) : null;
+ },
+ sortType: st.asDate,
+ type: 'date'
+ }
+ });
+
+ Ext.apply(Ext.data.Types, {
+ /**
+ * @type Object.
+ * @property BOOLEAN
+ * <p>This data type means that the raw data is converted into a boolean before it is placed into
+ * a Record. The string "true" and the number 1 are converted to boolean <code>true</code>.</p>
+ * <p>The synonym <code>BOOL</code> is equivalent.</p>
+ */
+ BOOLEAN: this.BOOL,
+
+ /**
+ * @type Object.
+ * @property INTEGER
+ * This data type means that the raw data is converted into an integer before it is placed into a Record.
+ * <p>The synonym <code>INT</code> is equivalent.</p>
+ */
+ INTEGER: this.INT,
+
+ /**
+ * @type Object.
+ * @property NUMBER
+ * This data type means that the raw data is converted into a number before it is placed into a Record.
+ * <p>The synonym <code>FLOAT</code> is equivalent.</p>
+ */
+ NUMBER: this.FLOAT
+ });
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Field
+ * @extends Object
+ *
+ * <p>Fields are used to define what a Model is. They aren't instantiated directly - instead, when we create a class
+ * that extends {@link Ext.data.Model}, it will automatically create a Field instance for each field configured in a
+ * {@link Ext.data.Model Model}. For example, we might set up a model like this:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'name', 'email',
+ {name: 'age', type: 'int'},
+ {name: 'gender', type: 'string', defaultValue: 'Unknown'}
+ ]
+});
+</code></pre>
+ *
+ * <p>Four fields will have been created for the User Model - name, email, age and gender. Note that we specified a
+ * couple of different formats here; if we only pass in the string name of the field (as with name and email), the
+ * field is set up with the 'auto' type. It's as if we'd done this instead:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'name', type: 'auto'},
+ {name: 'email', type: 'auto'},
+ {name: 'age', type: 'int'},
+ {name: 'gender', type: 'string', defaultValue: 'Unknown'}
+ ]
+});
+</code></pre>
+ *
+ * <p><u>Types and conversion</u></p>
+ *
+ * <p>The {@link #type} is important - it's used to automatically convert data passed to the field into the correct
+ * format. In our example above, the name and email fields used the 'auto' type and will just accept anything that is
+ * passed into them. The 'age' field had an 'int' type however, so if we passed 25.4 this would be rounded to 25.</p>
+ *
+ * <p>Sometimes a simple type isn't enough, or we want to perform some processing when we load a Field's data. We can
+ * do this using a {@link #convert} function. Here, we're going to create a new field based on another:</p>
+ *
+<code><pre>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'name', 'email',
+ {name: 'age', type: 'int'},
+ {name: 'gender', type: 'string', defaultValue: 'Unknown'},
+
+ {
+ name: 'firstName',
+ convert: function(value, record) {
+ var fullName = record.get('name'),
+ splits = fullName.split(" "),
+ firstName = splits[0];
+
+ return firstName;
+ }
+ }
+ ]
+});
+</code></pre>
+ *
+ * <p>Now when we create a new User, the firstName is populated automatically based on the name:</p>
+ *
+<code><pre>
+var ed = Ext.ModelManager.create({name: 'Ed Spencer'}, 'User');
+
+console.log(ed.get('firstName')); //logs 'Ed', based on our convert function
+</code></pre>
+ *
+ * <p>In fact, if we log out all of the data inside ed, we'll see this:</p>
+ *
+<code><pre>
+console.log(ed.data);
+
+//outputs this:
+{
+ age: 0,
+ email: "",
+ firstName: "Ed",
+ gender: "Unknown",
+ name: "Ed Spencer"
+}
+</code></pre>
+ *
+ * <p>The age field has been given a default of zero because we made it an int type. As an auto field, email has
+ * defaulted to an empty string. When we registered the User model we set gender's {@link #defaultValue} to 'Unknown'
+ * so we see that now. Let's correct that and satisfy ourselves that the types work as we expect:</p>
+ *
+<code><pre>
+ed.set('gender', 'Male');
+ed.get('gender'); //returns 'Male'
+
+ed.set('age', 25.4);
+ed.get('age'); //returns 25 - we wanted an int, not a float, so no decimal places allowed
+</code></pre>
+ *
+ */
+Ext.define('Ext.data.Field', {
+ requires: ['Ext.data.Types', 'Ext.data.SortTypes'],
+ alias: 'data.field',
+
+ constructor : function(config) {
+ if (Ext.isString(config)) {
+ config = {name: config};
+ }
+ Ext.apply(this, config);
+
+ var types = Ext.data.Types,
+ st = this.sortType,
+ t;
+
+ if (this.type) {
+ if (Ext.isString(this.type)) {
+ this.type = types[this.type.toUpperCase()] || types.AUTO;
+ }
+ } else {
+ this.type = types.AUTO;
+ }
+
+ // named sortTypes are supported, here we look them up
+ if (Ext.isString(st)) {
+ this.sortType = Ext.data.SortTypes[st];
+ } else if(Ext.isEmpty(st)) {
+ this.sortType = this.type.sortType;
+ }
+
+ if (!this.convert) {
+ this.convert = this.type.convert;
+ }
+ },
+
+ /**
+ * @cfg {String} name
+ * The name by which the field is referenced within the Model. This is referenced by, for example,
+ * the <code>dataIndex</code> property in column definition objects passed to {@link Ext.grid.property.HeaderContainer}.
+ * <p>Note: In the simplest case, if no properties other than <code>name</code> are required, a field
+ * definition may consist of just a String for the field name.</p>
+ */
+
+ /**
+ * @cfg {Mixed} type
+ * (Optional) The data type for automatic conversion from received data to the <i>stored</i> value if <code>{@link Ext.data.Field#convert convert}</code>
+ * has not been specified. This may be specified as a string value. Possible values are
+ * <div class="mdetail-params"><ul>
+ * <li>auto (Default, implies no conversion)</li>
+ * <li>string</li>
+ * <li>int</li>
+ * <li>float</li>
+ * <li>boolean</li>
+ * <li>date</li></ul></div>
+ * <p>This may also be specified by referencing a member of the {@link Ext.data.Types} class.</p>
+ * <p>Developers may create their own application-specific data types by defining new members of the
+ * {@link Ext.data.Types} class.</p>
+ */
+
+ /**
+ * @cfg {Function} convert
+ * (Optional) A function which converts the value provided by the Reader into an object that will be stored
+ * in the Model. It is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
+ * the configured <code>{@link Ext.data.Field#defaultValue defaultValue}</code>.</div></li>
+ * <li><b>rec</b> : Ext.data.Model<div class="sub-desc">The data object containing the Model as read so far by the
+ * Reader. Note that the Model may not be fully populated at this point as the fields are read in the order that
+ * they are defined in your {@link #fields} array.</div></li>
+ * </ul></div>
+ * <pre><code>
+// example of convert function
+function fullName(v, record){
+ return record.name.last + ', ' + record.name.first;
+}
+
+function location(v, record){
+ return !record.city ? '' : (record.city + ', ' + record.state);
+}
+
+Ext.define('Dude', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'fullname', convert: fullName},
+ {name: 'firstname', mapping: 'name.first'},
+ {name: 'lastname', mapping: 'name.last'},
+ {name: 'city', defaultValue: 'homeless'},
+ 'state',
+ {name: 'location', convert: location}
+ ]
+});
+
+// create the data store
+var store = new Ext.data.Store({
+ reader: {
+ type: 'json',
+ model: 'Dude',
+ idProperty: 'key',
+ root: 'daRoot',
+ totalProperty: 'total'
+ }
+});
+
+var myData = [
+ { key: 1,
+ name: { first: 'Fat', last: 'Albert' }
+ // notice no city, state provided in data object
+ },
+ { key: 2,
+ name: { first: 'Barney', last: 'Rubble' },
+ city: 'Bedrock', state: 'Stoneridge'
+ },
+ { key: 3,
+ name: { first: 'Cliff', last: 'Claven' },
+ city: 'Boston', state: 'MA'
+ }
+];
+ * </code></pre>
+ */
+ /**
+ * @cfg {String} dateFormat
+ * <p>(Optional) Used when converting received data into a Date when the {@link #type} is specified as <code>"date"</code>.</p>
+ * <p>A format string for the {@link Ext.Date#parse Ext.Date.parse} function, or "timestamp" if the
+ * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
+ * javascript millisecond timestamp. See {@link Date}</p>
+ */
+ dateFormat: null,
+
+ /**
+ * @cfg {Boolean} useNull
+ * <p>(Optional) Use when converting received data into a Number type (either int or float). If the value cannot be parsed,
+ * null will be used if useNull is true, otherwise the value will be 0. Defaults to <tt>false</tt>
+ */
+ useNull: false,
+
+ /**
+ * @cfg {Mixed} defaultValue
+ * (Optional) The default value used <b>when a Model is being created by a {@link Ext.data.reader.Reader Reader}</b>
+ * when the item referenced by the <code>{@link Ext.data.Field#mapping mapping}</code> does not exist in the data
+ * object (i.e. undefined). (defaults to "")
+ */
+ defaultValue: "",
+ /**
+ * @cfg {String/Number} mapping
+ * <p>(Optional) A path expression for use by the {@link Ext.data.reader.Reader} implementation
+ * that is creating the {@link Ext.data.Model Model} to extract the Field value from the data object.
+ * If the path expression is the same as the field name, the mapping may be omitted.</p>
+ * <p>The form of the mapping expression depends on the Reader being used.</p>
+ * <div class="mdetail-params"><ul>
+ * <li>{@link Ext.data.reader.Json}<div class="sub-desc">The mapping is a string containing the javascript
+ * expression to reference the data from an element of the data item's {@link Ext.data.reader.Json#root root} Array. Defaults to the field name.</div></li>
+ * <li>{@link Ext.data.reader.Xml}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
+ * item relative to the DOM element that represents the {@link Ext.data.reader.Xml#record record}. Defaults to the field name.</div></li>
+ * <li>{@link Ext.data.reader.Array}<div class="sub-desc">The mapping is a number indicating the Array index
+ * of the field's value. Defaults to the field specification's Array position.</div></li>
+ * </ul></div>
+ * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
+ * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
+ * return the desired data.</p>
+ */
+ mapping: null,
+ /**
+ * @cfg {Function} sortType
+ * (Optional) A function which converts a Field's value to a comparable value in order to ensure
+ * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
+ * sort example:<pre><code>
+// current sort after sort we want
+// +-+------+ +-+------+
+// |1|First | |1|First |
+// |2|Last | |3|Second|
+// |3|Second| |2|Last |
+// +-+------+ +-+------+
+
+sortType: function(value) {
+ switch (value.toLowerCase()) // native toLowerCase():
+ {
+ case 'first': return 1;
+ case 'second': return 2;
+ default: return 3;
+ }
+}
+ * </code></pre>
+ */
+ sortType : null,
+ /**
+ * @cfg {String} sortDir
+ * (Optional) Initial direction to sort (<code>"ASC"</code> or <code>"DESC"</code>). Defaults to
+ * <code>"ASC"</code>.
+ */
+ sortDir : "ASC",
+ /**
+ * @cfg {Boolean} allowBlank
+ * @private
+ * (Optional) Used for validating a {@link Ext.data.Model model}, defaults to <code>true</code>.
+ * An empty value here will cause {@link Ext.data.Model}.{@link Ext.data.Model#isValid isValid}
+ * to evaluate to <code>false</code>.
+ */
+ allowBlank : true,
+
+ /**
+ * @cfg {Boolean} persist
+ * False to exclude this field from the {@link Ext.data.Model#modified} fields in a model. This
+ * will also exclude the field from being written using a {@link Ext.data.writer.Writer}. This option
+ * is useful when model fields are used to keep state on the client but do not need to be persisted
+ * to the server. Defaults to <tt>true</tt>.
+ */
+ persist: true
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.reader.Reader
+ * @extends Object
+ *
+ * <p>Readers are used to interpret data to be loaded into a {@link Ext.data.Model Model} instance or a {@link Ext.data.Store Store}
+ * - usually in response to an AJAX request. This is normally handled transparently by passing some configuration to either the
+ * {@link Ext.data.Model Model} or the {@link Ext.data.Store Store} in question - see their documentation for further details.</p>
+ *
+ * <p><u>Loading Nested Data</u></p>
+ *
+ * <p>Readers have the ability to automatically load deeply-nested data objects based on the {@link Ext.data.Association associations}
+ * configured on each Model. Below is an example demonstrating the flexibility of these associations in a fictional CRM system which
+ * manages a User, their Orders, OrderItems and Products. First we'll define the models:
+ *
+<pre><code>
+Ext.define("User", {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'id', 'name'
+ ],
+
+ hasMany: {model: 'Order', name: 'orders'},
+
+ proxy: {
+ type: 'rest',
+ url : 'users.json',
+ reader: {
+ type: 'json',
+ root: 'users'
+ }
+ }
+});
+
+Ext.define("Order", {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'id', 'total'
+ ],
+
+ hasMany : {model: 'OrderItem', name: 'orderItems', associationKey: 'order_items'},
+ belongsTo: 'User'
+});
+
+Ext.define("OrderItem", {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'id', 'price', 'quantity', 'order_id', 'product_id'
+ ],
+
+ belongsTo: ['Order', {model: 'Product', associationKey: 'product'}]
+});
+
+Ext.define("Product", {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'id', 'name'
+ ],
+
+ hasMany: 'OrderItem'
+});
+</code></pre>
+ *
+ * <p>This may be a lot to take in - basically a User has many Orders, each of which is composed of several OrderItems. Finally,
+ * each OrderItem has a single Product. This allows us to consume data like this:</p>
+ *
+<pre><code>
+{
+ "users": [
+ {
+ "id": 123,
+ "name": "Ed",
+ "orders": [
+ {
+ "id": 50,
+ "total": 100,
+ "order_items": [
+ {
+ "id" : 20,
+ "price" : 40,
+ "quantity": 2,
+ "product" : {
+ "id": 1000,
+ "name": "MacBook Pro"
+ }
+ },
+ {
+ "id" : 21,
+ "price" : 20,
+ "quantity": 3,
+ "product" : {
+ "id": 1001,
+ "name": "iPhone"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+}
+</code></pre>
+ *
+ * <p>The JSON response is deeply nested - it returns all Users (in this case just 1 for simplicity's sake), all of the Orders
+ * for each User (again just 1 in this case), all of the OrderItems for each Order (2 order items in this case), and finally
+ * the Product associated with each OrderItem. Now we can read the data and use it as follows:
+ *
+<pre><code>
+var store = new Ext.data.Store({
+ model: "User"
+});
+
+store.load({
+ callback: function() {
+ //the user that was loaded
+ var user = store.first();
+
+ console.log("Orders for " + user.get('name') + ":")
+
+ //iterate over the Orders for each User
+ user.orders().each(function(order) {
+ console.log("Order ID: " + order.getId() + ", which contains items:");
+
+ //iterate over the OrderItems for each Order
+ order.orderItems().each(function(orderItem) {
+ //we know that the Product data is already loaded, so we can use the synchronous getProduct
+ //usually, we would use the asynchronous version (see {@link Ext.data.BelongsToAssociation})
+ var product = orderItem.getProduct();
+
+ console.log(orderItem.get('quantity') + ' orders of ' + product.get('name'));
+ });
+ });
+ }
+});
+</code></pre>
+ *
+ * <p>Running the code above results in the following:</p>
+ *
+<pre><code>
+Orders for Ed:
+Order ID: 50, which contains items:
+2 orders of MacBook Pro
+3 orders of iPhone
+</code></pre>
+ *
+ * @constructor
+ * @param {Object} config Optional config object
+ */
+Ext.define('Ext.data.reader.Reader', {
+ requires: ['Ext.data.ResultSet'],
+ alternateClassName: ['Ext.data.Reader', 'Ext.data.DataReader'],
+
+ /**
+ * @cfg {String} idProperty Name of the property within a row object
+ * that contains a record identifier value. Defaults to <tt>The id of the model</tt>.
+ * If an idProperty is explicitly specified it will override that of the one specified
+ * on the model
+ */
+
+ /**
+ * @cfg {String} totalProperty Name of the property from which to
+ * retrieve the total number of records in the dataset. This is only needed
+ * if the whole dataset is not passed in one go, but is being paged from
+ * the remote server. Defaults to <tt>total</tt>.
+ */
+ totalProperty: 'total',
+
+ /**
+ * @cfg {String} successProperty Name of the property from which to
+ * retrieve the success attribute. Defaults to <tt>success</tt>. See
+ * {@link Ext.data.proxy.Proxy}.{@link Ext.data.proxy.Proxy#exception exception}
+ * for additional information.
+ */
+ successProperty: 'success',
+
+ /**
+ * @cfg {String} root <b>Required</b>. The name of the property
+ * which contains the Array of row objects. Defaults to <tt>undefined</tt>.
+ * An exception will be thrown if the root property is undefined. The data
+ * packet value for this property should be an empty array to clear the data
+ * or show no data.
+ */
+ root: '',
+
+ /**
+ * @cfg {String} messageProperty The name of the property which contains a response message.
+ * This property is optional.
+ */
+
+ /**
+ * @cfg {Boolean} implicitIncludes True to automatically parse models nested within other models in a response
+ * object. See the Ext.data.reader.Reader intro docs for full explanation. Defaults to true.
+ */
+ implicitIncludes: true,
+
+ isReader: true,
+
+ constructor: function(config) {
+ var me = this;
+
+ Ext.apply(me, config || {});
+ me.fieldCount = 0;
+ me.model = Ext.ModelManager.getModel(config.model);
+ if (me.model) {
+ me.buildExtractors();
+ }
+ },
+
+ /**
+ * Sets a new model for the reader.
+ * @private
+ * @param {Object} model The model to set.
+ * @param {Boolean} setOnProxy True to also set on the Proxy, if one is configured
+ */
+ setModel: function(model, setOnProxy) {
+ var me = this;
+
+ me.model = Ext.ModelManager.getModel(model);
+ me.buildExtractors(true);
+
+ if (setOnProxy && me.proxy) {
+ me.proxy.setModel(me.model, true);
+ }
+ },
+
+ /**
+ * Reads the given response object. This method normalizes the different types of response object that may be passed
+ * to it, before handing off the reading of records to the {@link #readRecords} function.
+ * @param {Object} response The response object. This may be either an XMLHttpRequest object or a plain JS object
+ * @return {Ext.data.ResultSet} The parsed ResultSet object
+ */
+ read: function(response) {
+ var data = response;
+
+ if (response && response.responseText) {
+ data = this.getResponseData(response);
+ }
+
+ if (data) {
+ return this.readRecords(data);
+ } else {
+ return this.nullResultSet;
+ }
+ },
+
+ /**
+ * Abstracts common functionality used by all Reader subclasses. Each subclass is expected to call
+ * this function before running its own logic and returning the Ext.data.ResultSet instance. For most
+ * Readers additional processing should not be needed.
+ * @param {Mixed} data The raw data object
+ * @return {Ext.data.ResultSet} A ResultSet object
+ */
+ readRecords: function(data) {
+ var me = this;
+
+ /*
+ * We check here whether the number of fields has changed since the last read.
+ * This works around an issue when a Model is used for both a Tree and another
+ * source, because the tree decorates the model with extra fields and it causes
+ * issues because the readers aren't notified.
+ */
+ if (me.fieldCount !== me.getFields().length) {
+ me.buildExtractors(true);
+ }
+
+ /**
+ * The raw data object that was last passed to readRecords. Stored for further processing if needed
+ * @property rawData
+ * @type Mixed
+ */
+ me.rawData = data;
+
+ data = me.getData(data);
+
+ // If we pass an array as the data, we dont use getRoot on the data.
+ // Instead the root equals to the data.
+ var root = Ext.isArray(data) ? data : me.getRoot(data),
+ success = true,
+ recordCount = 0,
+ total, value, records, message;
+
+ if (root) {
+ total = root.length;
+ }
+
+ if (me.totalProperty) {
+ value = parseInt(me.getTotal(data), 10);
+ if (!isNaN(value)) {
+ total = value;
+ }
+ }
+
+ if (me.successProperty) {
+ value = me.getSuccess(data);
+ if (value === false || value === 'false') {
+ success = false;
+ }
+ }
+
+ if (me.messageProperty) {
+ message = me.getMessage(data);
+ }
+
+ if (root) {
+ records = me.extractData(root);
+ recordCount = records.length;
+ } else {
+ recordCount = 0;
+ records = [];
+ }
+
+ return Ext.create('Ext.data.ResultSet', {
+ total : total || recordCount,
+ count : recordCount,
+ records: records,
+ success: success,
+ message: message
+ });
+ },
+
+ /**
+ * Returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
+ * @param {Object[]/Object} data-root from server response
+ * @private
+ */
+ extractData : function(root) {
+ var me = this,
+ values = [],
+ records = [],
+ Model = me.model,
+ i = 0,
+ length = root.length,
+ idProp = me.getIdProperty(),
+ node, id, record;
+
+ if (!root.length && Ext.isObject(root)) {
+ root = [root];
+ length = 1;
+ }
+
+ for (; i < length; i++) {
+ node = root[i];
+ values = me.extractValues(node);
+ id = me.getId(node);
+
+
+ record = new Model(values, id);
+ record.raw = node;
+ records.push(record);
+
+ if (me.implicitIncludes) {
+ me.readAssociated(record, node);
+ }
+ }
+
+ return records;
+ },
+
+ /**
+ * @private
+ * Loads a record's associations from the data object. This prepopulates hasMany and belongsTo associations
+ * on the record provided.
+ * @param {Ext.data.Model} record The record to load associations for
+ * @param {Mixed} data The data object
+ * @return {String} Return value description
+ */
+ readAssociated: function(record, data) {
+ var associations = record.associations.items,
+ i = 0,
+ length = associations.length,
+ association, associationData, proxy, reader;
+
+ for (; i < length; i++) {
+ association = associations[i];
+ associationData = this.getAssociatedDataRoot(data, association.associationKey || association.name);
+
+ if (associationData) {
+ reader = association.getReader();
+ if (!reader) {
+ proxy = association.associatedModel.proxy;
+ // if the associated model has a Reader already, use that, otherwise attempt to create a sensible one
+ if (proxy) {
+ reader = proxy.getReader();
+ } else {
+ reader = new this.constructor({
+ model: association.associatedName
+ });
+ }
+ }
+ association.read(record, reader, associationData);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Used internally by {@link #readAssociated}. Given a data object (which could be json, xml etc) for a specific
+ * record, this should return the relevant part of that data for the given association name. This is only really
+ * needed to support the XML Reader, which has to do a query to get the associated data object
+ * @param {Mixed} data The raw data object
+ * @param {String} associationName The name of the association to get data for (uses associationKey if present)
+ * @return {Mixed} The root
+ */
+ getAssociatedDataRoot: function(data, associationName) {
+ return data[associationName];
+ },
+
+ getFields: function() {
+ return this.model.prototype.fields.items;
+ },
+
+ /**
+ * @private
+ * Given an object representing a single model instance's data, iterates over the model's fields and
+ * builds an object with the value for each field.
+ * @param {Object} data The data object to convert
+ * @return {Object} Data object suitable for use with a model constructor
+ */
+ extractValues: function(data) {
+ var fields = this.getFields(),
+ i = 0,
+ length = fields.length,
+ output = {},
+ field, value;
+
+ for (; i < length; i++) {
+ field = fields[i];
+ value = this.extractorFunctions[i](data);
+
+ output[field.name] = value;
+ }
+
+ return output;
+ },
+
+ /**
+ * @private
+ * By default this function just returns what is passed to it. It can be overridden in a subclass
+ * to return something else. See XmlReader for an example.
+ * @param {Object} data The data object
+ * @return {Object} The normalized data object
+ */
+ getData: function(data) {
+ return data;
+ },
+
+ /**
+ * @private
+ * This will usually need to be implemented in a subclass. Given a generic data object (the type depends on the type
+ * of data we are reading), this function should return the object as configured by the Reader's 'root' meta data config.
+ * See XmlReader's getRoot implementation for an example. By default the same data object will simply be returned.
+ * @param {Mixed} data The data object
+ * @return {Mixed} The same data object
+ */
+ getRoot: function(data) {
+ return data;
+ },
+
+ /**
+ * Takes a raw response object (as passed to this.read) and returns the useful data segment of it. This must be implemented by each subclass
+ * @param {Object} response The responce object
+ * @return {Object} The useful data from the response
+ */
+ getResponseData: function(response) {
+ Ext.Error.raise("getResponseData must be implemented in the Ext.data.reader.Reader subclass");
+ },
+
+ /**
+ * @private
+ * Reconfigures the meta data tied to this Reader
+ */
+ onMetaChange : function(meta) {
+ var fields = meta.fields,
+ newModel;
+
+ Ext.apply(this, meta);
+
+ if (fields) {
+ newModel = Ext.define("Ext.data.reader.Json-Model" + Ext.id(), {
+ extend: 'Ext.data.Model',
+ fields: fields
+ });
+ this.setModel(newModel, true);
+ } else {
+ this.buildExtractors(true);
+ }
+ },
+
+ /**
+ * Get the idProperty to use for extracting data
+ * @private
+ * @return {String} The id property
+ */
+ getIdProperty: function(){
+ var prop = this.idProperty;
+ if (Ext.isEmpty(prop)) {
+ prop = this.model.prototype.idProperty;
+ }
+ return prop;
+ },
+
+ /**
+ * @private
+ * This builds optimized functions for retrieving record data and meta data from an object.
+ * Subclasses may need to implement their own getRoot function.
+ * @param {Boolean} force True to automatically remove existing extractor functions first (defaults to false)
+ */
+ buildExtractors: function(force) {
+ var me = this,
+ idProp = me.getIdProperty(),
+ totalProp = me.totalProperty,
+ successProp = me.successProperty,
+ messageProp = me.messageProperty,
+ accessor;
+
+ if (force === true) {
+ delete me.extractorFunctions;
+ }
+
+ if (me.extractorFunctions) {
+ return;
+ }
+
+ //build the extractors for all the meta data
+ if (totalProp) {
+ me.getTotal = me.createAccessor(totalProp);
+ }
+
+ if (successProp) {
+ me.getSuccess = me.createAccessor(successProp);
+ }
+
+ if (messageProp) {
+ me.getMessage = me.createAccessor(messageProp);
+ }
+
+ if (idProp) {
+ accessor = me.createAccessor(idProp);
+
+ me.getId = function(record) {
+ var id = accessor.call(me, record);
+ return (id === undefined || id === '') ? null : id;
+ };
+ } else {
+ me.getId = function() {
+ return null;
+ };
+ }
+ me.buildFieldExtractors();
+ },
+
+ /**
+ * @private
+ */
+ buildFieldExtractors: function() {
+ //now build the extractors for all the fields
+ var me = this,
+ fields = me.getFields(),
+ ln = fields.length,
+ i = 0,
+ extractorFunctions = [],
+ field, map;
+
+ for (; i < ln; i++) {
+ field = fields[i];
+ map = (field.mapping !== undefined && field.mapping !== null) ? field.mapping : field.name;
+
+ extractorFunctions.push(me.createAccessor(map));
+ }
+ me.fieldCount = ln;
+
+ me.extractorFunctions = extractorFunctions;
+ }
+}, function() {
+ Ext.apply(this, {
+ // Private. Empty ResultSet to return when response is falsy (null|undefined|empty string)
+ nullResultSet: Ext.create('Ext.data.ResultSet', {
+ total : 0,
+ count : 0,
+ records: [],
+ success: true
+ })
+ });
+});
+/**
+ * @author Ed Spencer
+ * @class Ext.data.reader.Json
+ * @extends Ext.data.reader.Reader
+ *
+ * <p>The JSON Reader is used by a Proxy to read a server response that is sent back in JSON format. This usually
+ * happens as a result of loading a Store - for example we might create something like this:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'name', 'email']
+});
+
+var store = new Ext.data.Store({
+ model: 'User',
+ proxy: {
+ type: 'ajax',
+ url : 'users.json',
+ reader: {
+ type: 'json'
+ }
+ }
+});
+</code></pre>
+ *
+ * <p>The example above creates a 'User' model. Models are explained in the {@link Ext.data.Model Model} docs if you're
+ * not already familiar with them.</p>
+ *
+ * <p>We created the simplest type of JSON Reader possible by simply telling our {@link Ext.data.Store Store}'s
+ * {@link Ext.data.proxy.Proxy Proxy} that we want a JSON Reader. The Store automatically passes the configured model to the
+ * Store, so it is as if we passed this instead:
+ *
+<pre><code>
+reader: {
+ type : 'json',
+ model: 'User'
+}
+</code></pre>
+ *
+ * <p>The reader we set up is ready to read data from our server - at the moment it will accept a response like this:</p>
+ *
+<pre><code>
+[
+ {
+ "id": 1,
+ "name": "Ed Spencer",
+ "email": "ed@sencha.com"
+ },
+ {
+ "id": 2,
+ "name": "Abe Elias",
+ "email": "abe@sencha.com"
+ }
+]
+</code></pre>
+ *
+ * <p><u>Reading other JSON formats</u></p>
+ *
+ * <p>If you already have your JSON format defined and it doesn't look quite like what we have above, you can usually
+ * pass JsonReader a couple of configuration options to make it parse your format. For example, we can use the
+ * {@link #root} configuration to parse data that comes back like this:</p>
+ *
+<pre><code>
+{
+ "users": [
+ {
+ "id": 1,
+ "name": "Ed Spencer",
+ "email": "ed@sencha.com"
+ },
+ {
+ "id": 2,
+ "name": "Abe Elias",
+ "email": "abe@sencha.com"
+ }
+ ]
+}
+</code></pre>
+ *
+ * <p>To parse this we just pass in a {@link #root} configuration that matches the 'users' above:</p>
+ *
+<pre><code>
+reader: {
+ type: 'json',
+ root: 'users'
+}
+</code></pre>
+ *
+ * <p>Sometimes the JSON structure is even more complicated. Document databases like CouchDB often provide metadata
+ * around each record inside a nested structure like this:</p>
+ *
+<pre><code>
+{
+ "total": 122,
+ "offset": 0,
+ "users": [
+ {
+ "id": "ed-spencer-1",
+ "value": 1,
+ "user": {
+ "id": 1,
+ "name": "Ed Spencer",
+ "email": "ed@sencha.com"
+ }
+ }
+ ]
+}
+</code></pre>
+ *
+ * <p>In the case above the record data is nested an additional level inside the "users" array as each "user" item has
+ * additional metadata surrounding it ('id' and 'value' in this case). To parse data out of each "user" item in the
+ * JSON above we need to specify the {@link #record} configuration like this:</p>
+ *
+<pre><code>
+reader: {
+ type : 'json',
+ root : 'users',
+ record: 'user'
+}
+</code></pre>
+ *
+ * <p><u>Response metadata</u></p>
+ *
+ * <p>The server can return additional data in its response, such as the {@link #totalProperty total number of records}
+ * and the {@link #successProperty success status of the response}. These are typically included in the JSON response
+ * like this:</p>
+ *
+<pre><code>
+{
+ "total": 100,
+ "success": true,
+ "users": [
+ {
+ "id": 1,
+ "name": "Ed Spencer",
+ "email": "ed@sencha.com"
+ }
+ ]
+}
+</code></pre>
+ *
+ * <p>If these properties are present in the JSON response they can be parsed out by the JsonReader and used by the
+ * Store that loaded it. We can set up the names of these properties by specifying a final pair of configuration
+ * options:</p>
+ *
+<pre><code>
+reader: {
+ type : 'json',
+ root : 'users',
+ totalProperty : 'total',
+ successProperty: 'success'
+}
+</code></pre>
+ *
+ * <p>These final options are not necessary to make the Reader work, but can be useful when the server needs to report
+ * an error or if it needs to indicate that there is a lot of data available of which only a subset is currently being
+ * returned.</p>
+ */
+Ext.define('Ext.data.reader.Json', {
+ extend: 'Ext.data.reader.Reader',
+ alternateClassName: 'Ext.data.JsonReader',
+ alias : 'reader.json',
+
+ root: '',
+
+ /**
+ * @cfg {String} record The optional location within the JSON response that the record data itself can be found at.
+ * See the JsonReader intro docs for more details. This is not often needed and defaults to undefined.
+ */
+
+ /**
+ * @cfg {Boolean} useSimpleAccessors True to ensure that field names/mappings are treated as literals when
+ * reading values. Defalts to <tt>false</tt>.
+ * For example, by default, using the mapping "foo.bar.baz" will try and read a property foo from the root, then a property bar
+ * from foo, then a property baz from bar. Setting the simple accessors to true will read the property with the name
+ * "foo.bar.baz" direct from the root object.
+ */
+ useSimpleAccessors: false,
+
+ /**
+ * Reads a JSON object and returns a ResultSet. Uses the internal getTotal and getSuccess extractors to
+ * retrieve meta data from the response, and extractData to turn the JSON data into model instances.
+ * @param {Object} data The raw JSON data
+ * @return {Ext.data.ResultSet} A ResultSet containing model instances and meta data about the results
+ */
+ readRecords: function(data) {
+ //this has to be before the call to super because we use the meta data in the superclass readRecords
+ if (data.metaData) {
+ this.onMetaChange(data.metaData);
+ }
+
+ /**
+ * DEPRECATED - will be removed in Ext JS 5.0. This is just a copy of this.rawData - use that instead
+ * @property jsonData
+ * @type Mixed
+ */
+ this.jsonData = data;
+ return this.callParent([data]);
+ },
+
+ //inherit docs
+ getResponseData: function(response) {
+ try {
+ var data = Ext.decode(response.responseText);
+ }
+ catch (ex) {
+ Ext.Error.raise({
+ response: response,
+ json: response.responseText,
+ parseError: ex,
+ msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
+ });
+ }
+ if (!data) {
+ Ext.Error.raise('JSON object not found');
+ }
+
+ return data;
+ },
+
+ //inherit docs
+ buildExtractors : function() {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (me.root) {
+ me.getRoot = me.createAccessor(me.root);
+ } else {
+ me.getRoot = function(root) {
+ return root;
+ };
+ }
+ },
+
+ /**
+ * @private
+ * We're just preparing the data for the superclass by pulling out the record objects we want. If a {@link #record}
+ * was specified we have to pull those out of the larger JSON object, which is most of what this function is doing
+ * @param {Object} root The JSON root node
+ * @return {Array} The records
+ */
+ extractData: function(root) {
+ var recordName = this.record,
+ data = [],
+ length, i;
+
+ if (recordName) {
+ length = root.length;
+
+ for (i = 0; i < length; i++) {
+ data[i] = root[i][recordName];
+ }
+ } else {
+ data = root;
+ }
+ return this.callParent([data]);
+ },
+
+ /**
+ * @private
+ * Returns an accessor function for the given property string. Gives support for properties such as the following:
+ * 'someProperty'
+ * 'some.property'
+ * 'some["property"]'
+ * This is used by buildExtractors to create optimized extractor functions when casting raw data into model instances.
+ */
+ createAccessor: function() {
+ var re = /[\[\.]/;
+
+ return function(expr) {
+ if (Ext.isEmpty(expr)) {
+ return Ext.emptyFn;
+ }
+ if (Ext.isFunction(expr)) {
+ return expr;
+ }
+ if (this.useSimpleAccessors !== true) {
+ var i = String(expr).search(re);
+ if (i >= 0) {
+ return Ext.functionFactory('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
+ }
+ }
+ return function(obj) {
+ return obj[expr];
+ };
+ };
+ }()
+});
+/**
+ * @class Ext.data.writer.Json
+ * @extends Ext.data.writer.Writer
+ * @ignore
+ */
+Ext.define('Ext.data.writer.Json', {
+ extend: 'Ext.data.writer.Writer',
+ alternateClassName: 'Ext.data.JsonWriter',
+ alias: 'writer.json',
+
+ /**
+ * @cfg {String} root The key under which the records in this Writer will be placed. Defaults to <tt>undefined</tt>.
+ * Example generated request, using root: 'records':
+<pre><code>
+{'records': [{name: 'my record'}, {name: 'another record'}]}
+</code></pre>
+ */
+ root: undefined,
+
+ /**
+ * @cfg {Boolean} encode True to use Ext.encode() on the data before sending. Defaults to <tt>false</tt>.
+ * The encode option should only be set to true when a {@link #root} is defined, because the values will be
+ * sent as part of the request parameters as opposed to a raw post. The root will be the name of the parameter
+ * sent to the server.
+ */
+ encode: false,
+
+ /**
+ * @cfg {Boolean} allowSingle False to ensure that records are always wrapped in an array, even if there is only
+ * one record being sent. When there is more than one record, they will always be encoded into an array.
+ * Defaults to <tt>true</tt>. Example:
+ * <pre><code>
+// with allowSingle: true
+"root": {
+ "first": "Mark",
+ "last": "Corrigan"
+}
+
+// with allowSingle: false
+"root": [{
+ "first": "Mark",
+ "last": "Corrigan"
+}]
+ * </code></pre>
+ */
+ allowSingle: true,
+
+ //inherit docs
+ writeRecords: function(request, data) {
+ var root = this.root;
+
+ if (this.allowSingle && data.length == 1) {
+ // convert to single object format
+ data = data[0];
+ }
+
+ if (this.encode) {
+ if (root) {
+ // sending as a param, need to encode
+ request.params[root] = Ext.encode(data);
+ } else {
+ Ext.Error.raise('Must specify a root when using encode');
+ }
+ } else {
+ // send as jsonData
+ request.jsonData = request.jsonData || {};
+ if (root) {
+ request.jsonData[root] = data;
+ } else {
+ request.jsonData = data;
+ }
+ }
+ return request;
+ }
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.proxy.Proxy
+ *
+ * <p>Proxies are used by {@link Ext.data.Store Stores} to handle the loading and saving of {@link Ext.data.Model Model} data.
+ * Usually developers will not need to create or interact with proxies directly.</p>
+ * <p><u>Types of Proxy</u></p>
+ *
+ * <p>There are two main types of Proxy - {@link Ext.data.proxy.Client Client} and {@link Ext.data.proxy.Server Server}. The Client proxies
+ * save their data locally and include the following subclasses:</p>
+ *
+ * <ul style="list-style-type: disc; padding-left: 25px">
+ * <li>{@link Ext.data.proxy.LocalStorage LocalStorageProxy} - saves its data to localStorage if the browser supports it</li>
+ * <li>{@link Ext.data.proxy.SessionStorage SessionStorageProxy} - saves its data to sessionStorage if the browsers supports it</li>
+ * <li>{@link Ext.data.proxy.Memory MemoryProxy} - holds data in memory only, any data is lost when the page is refreshed</li>
+ * </ul>
+ *
+ * <p>The Server proxies save their data by sending requests to some remote server. These proxies include:</p>
+ *
+ * <ul style="list-style-type: disc; padding-left: 25px">
+ * <li>{@link Ext.data.proxy.Ajax Ajax} - sends requests to a server on the same domain</li>
+ * <li>{@link Ext.data.proxy.JsonP JsonP} - uses JSON-P to send requests to a server on a different domain</li>
+ * <li>{@link Ext.data.proxy.Direct Direct} - uses {@link Ext.direct} to send requests</li>
+ * </ul>
+ *
+ * <p>Proxies operate on the principle that all operations performed are either Create, Read, Update or Delete. These four operations
+ * are mapped to the methods {@link #create}, {@link #read}, {@link #update} and {@link #destroy} respectively. Each Proxy subclass
+ * implements these functions.</p>
+ *
+ * <p>The CRUD methods each expect an {@link Ext.data.Operation Operation} object as the sole argument. The Operation encapsulates
+ * information about the action the Store wishes to perform, the {@link Ext.data.Model model} instances that are to be modified, etc.
+ * See the {@link Ext.data.Operation Operation} documentation for more details. Each CRUD method also accepts a callback function to be
+ * called asynchronously on completion.</p>
+ *
+ * <p>Proxies also support batching of Operations via a {@link Ext.data.Batch batch} object, invoked by the {@link #batch} method.</p>
+ *
+ * @constructor
+ * Creates the Proxy
+ * @param {Object} config Optional config object
+ */
+Ext.define('Ext.data.proxy.Proxy', {
+ alias: 'proxy.proxy',
+ alternateClassName: ['Ext.data.DataProxy', 'Ext.data.Proxy'],
+ requires: [
+ 'Ext.data.reader.Json',
+ 'Ext.data.writer.Json'
+ ],
+ uses: [
+ 'Ext.data.Batch',
+ 'Ext.data.Operation',
+ 'Ext.data.Model'
+ ],
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ /**
+ * @cfg {String} batchOrder
+ * Comma-separated ordering 'create', 'update' and 'destroy' actions when batching. Override this
+ * to set a different order for the batched CRUD actions to be executed in. Defaults to 'create,update,destroy'
+ */
+ batchOrder: 'create,update,destroy',
+
+ /**
+ * @cfg {Boolean} batchActions True to batch actions of a particular type when synchronizing the store.
+ * Defaults to <tt>true</tt>.
+ */
+ batchActions: true,
+
+ /**
+ * @cfg {String} defaultReaderType The default registered reader type. Defaults to 'json'
+ * @private
+ */
+ defaultReaderType: 'json',
+
+ /**
+ * @cfg {String} defaultWriterType The default registered writer type. Defaults to 'json'
+ * @private
+ */
+ defaultWriterType: 'json',
+
+ /**
+ * @cfg {String/Ext.data.Model} model The name of the Model to tie to this Proxy. Can be either the string name of
+ * the Model, or a reference to the Model constructor. Required.
+ */
+
+ isProxy: true,
+
+ constructor: function(config) {
+ config = config || {};
+
+ if (config.model === undefined) {
+ delete config.model;
+ }
+
+ this.mixins.observable.constructor.call(this, config);
+
+ if (this.model !== undefined && !(this.model instanceof Ext.data.Model)) {
+ this.setModel(this.model);
+ }
+ },
+
+ /**
+ * Sets the model associated with this proxy. This will only usually be called by a Store
+ * @param {String|Ext.data.Model} model The new model. Can be either the model name string,
+ * or a reference to the model's constructor
+ * @param {Boolean} setOnStore Sets the new model on the associated Store, if one is present
+ */
+ setModel: function(model, setOnStore) {
+ this.model = Ext.ModelManager.getModel(model);
+
+ var reader = this.reader,
+ writer = this.writer;
+
+ this.setReader(reader);
+ this.setWriter(writer);
+
+ if (setOnStore && this.store) {
+ this.store.setModel(this.model);
+ }
+ },
+
+ /**
+ * Returns the model attached to this Proxy
+ * @return {Ext.data.Model} The model
+ */
+ getModel: function() {
+ return this.model;
+ },
+
+ /**
+ * Sets the Proxy's Reader by string, config object or Reader instance
+ * @param {String|Object|Ext.data.reader.Reader} reader The new Reader, which can be either a type string, a configuration object
+ * or an Ext.data.reader.Reader instance
+ * @return {Ext.data.reader.Reader} The attached Reader object
+ */
+ setReader: function(reader) {
+ var me = this;
+
+ if (reader === undefined || typeof reader == 'string') {
+ reader = {
+ type: reader
+ };
+ }
+
+ if (reader.isReader) {
+ reader.setModel(me.model);
+ } else {
+ Ext.applyIf(reader, {
+ proxy: me,
+ model: me.model,
+ type : me.defaultReaderType
+ });
+
+ reader = Ext.createByAlias('reader.' + reader.type, reader);
+ }
+
+ me.reader = reader;
+ return me.reader;
+ },
+
+ /**
+ * Returns the reader currently attached to this proxy instance
+ * @return {Ext.data.reader.Reader} The Reader instance
+ */
+ getReader: function() {
+ return this.reader;
+ },
+
+ /**
+ * Sets the Proxy's Writer by string, config object or Writer instance
+ * @param {String|Object|Ext.data.writer.Writer} writer The new Writer, which can be either a type string, a configuration object
+ * or an Ext.data.writer.Writer instance
+ * @return {Ext.data.writer.Writer} The attached Writer object
+ */
+ setWriter: function(writer) {
+ if (writer === undefined || typeof writer == 'string') {
+ writer = {
+ type: writer
+ };
+ }
+
+ if (!(writer instanceof Ext.data.writer.Writer)) {
+ Ext.applyIf(writer, {
+ model: this.model,
+ type : this.defaultWriterType
+ });
+
+ writer = Ext.createByAlias('writer.' + writer.type, writer);
+ }
+
+ this.writer = writer;
+
+ return this.writer;
+ },
+
+ /**
+ * Returns the writer currently attached to this proxy instance
+ * @return {Ext.data.writer.Writer} The Writer instance
+ */
+ getWriter: function() {
+ return this.writer;
+ },
+
+ /**
+ * Performs the given create operation.
+ * @param {Ext.data.Operation} operation The Operation to perform
+ * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
+ * @param {Object} scope Scope to execute the callback function in
+ */
+ create: Ext.emptyFn,
+
+ /**
+ * Performs the given read operation.
+ * @param {Ext.data.Operation} operation The Operation to perform
+ * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
+ * @param {Object} scope Scope to execute the callback function in
+ */
+ read: Ext.emptyFn,
+
+ /**
+ * Performs the given update operation.
+ * @param {Ext.data.Operation} operation The Operation to perform
+ * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
+ * @param {Object} scope Scope to execute the callback function in
+ */
+ update: Ext.emptyFn,
+
+ /**
+ * Performs the given destroy operation.
+ * @param {Ext.data.Operation} operation The Operation to perform
+ * @param {Function} callback Callback function to be called when the Operation has completed (whether successful or not)
+ * @param {Object} scope Scope to execute the callback function in
+ */
+ destroy: Ext.emptyFn,
+
+ /**
+ * Performs a batch of {@link Ext.data.Operation Operations}, in the order specified by {@link #batchOrder}. Used internally by
+ * {@link Ext.data.Store}'s {@link Ext.data.Store#sync sync} method. Example usage:
+ * <pre><code>
+ * myProxy.batch({
+ * create : [myModel1, myModel2],
+ * update : [myModel3],
+ * destroy: [myModel4, myModel5]
+ * });
+ * </code></pre>
+ * Where the myModel* above are {@link Ext.data.Model Model} instances - in this case 1 and 2 are new instances and have not been
+ * saved before, 3 has been saved previously but needs to be updated, and 4 and 5 have already been saved but should now be destroyed.
+ * @param {Object} operations Object containing the Model instances to act upon, keyed by action name
+ * @param {Object} listeners Optional listeners object passed straight through to the Batch - see {@link Ext.data.Batch}
+ * @return {Ext.data.Batch} The newly created Ext.data.Batch object
+ */
+ batch: function(operations, listeners) {
+ var me = this,
+ batch = Ext.create('Ext.data.Batch', {
+ proxy: me,
+ listeners: listeners || {}
+ }),
+ useBatch = me.batchActions,
+ records;
+
+ Ext.each(me.batchOrder.split(','), function(action) {
+ records = operations[action];
+ if (records) {
+ if (useBatch) {
+ batch.add(Ext.create('Ext.data.Operation', {
+ action: action,
+ records: records
+ }));
+ } else {
+ Ext.each(records, function(record){
+ batch.add(Ext.create('Ext.data.Operation', {
+ action : action,
+ records: [record]
+ }));
+ });
+ }
+ }
+ }, me);
+
+ batch.start();
+ return batch;
+ }
+}, function() {
+ // Ext.data.proxy.ProxyMgr.registerType('proxy', this);
+
+ //backwards compatibility
+ Ext.data.DataProxy = this;
+ // Ext.deprecate('platform', '2.0', function() {
+ // Ext.data.DataProxy = this;
+ // }, this);
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.proxy.Server
+ * @extends Ext.data.proxy.Proxy
+ *
+ * <p>ServerProxy is a superclass of {@link Ext.data.proxy.JsonP JsonPProxy} and {@link Ext.data.proxy.Ajax AjaxProxy},
+ * and would not usually be used directly.</p>
+ *
+ * <p>ServerProxy should ideally be named HttpProxy as it is a superclass for all HTTP proxies - for Ext JS 4.x it has been
+ * called ServerProxy to enable any 3.x applications that reference the HttpProxy to continue to work (HttpProxy is now an
+ * alias of AjaxProxy).</p>
+ */
+Ext.define('Ext.data.proxy.Server', {
+ extend: 'Ext.data.proxy.Proxy',
+ alias : 'proxy.server',
+ alternateClassName: 'Ext.data.ServerProxy',
+ uses : ['Ext.data.Request'],
+
+ /**
+ * @cfg {String} url The URL from which to request the data object.
+ */
+
+ /**
+ * @cfg {Object/String/Ext.data.reader.Reader} reader The Ext.data.reader.Reader to use to decode the server's response. This can
+ * either be a Reader instance, a config object or just a valid Reader type name (e.g. 'json', 'xml').
+ */
+
+ /**
+ * @cfg {Object/String/Ext.data.writer.Writer} writer The Ext.data.writer.Writer to use to encode any request sent to the server.
+ * This can either be a Writer instance, a config object or just a valid Writer type name (e.g. 'json', 'xml').
+ */
+
+ /**
+ * @cfg {String} pageParam The name of the 'page' parameter to send in a request. Defaults to 'page'. Set this to
+ * undefined if you don't want to send a page parameter
+ */
+ pageParam: 'page',
+
+ /**
+ * @cfg {String} startParam The name of the 'start' parameter to send in a request. Defaults to 'start'. Set this
+ * to undefined if you don't want to send a start parameter
+ */
+ startParam: 'start',
+
+ /**
+ * @cfg {String} limitParam The name of the 'limit' parameter to send in a request. Defaults to 'limit'. Set this
+ * to undefined if you don't want to send a limit parameter
+ */
+ limitParam: 'limit',
+
+ /**
+ * @cfg {String} groupParam The name of the 'group' parameter to send in a request. Defaults to 'group'. Set this
+ * to undefined if you don't want to send a group parameter
+ */
+ groupParam: 'group',
+
+ /**
+ * @cfg {String} sortParam The name of the 'sort' parameter to send in a request. Defaults to 'sort'. Set this
+ * to undefined if you don't want to send a sort parameter
+ */
+ sortParam: 'sort',
+
+ /**
+ * @cfg {String} filterParam The name of the 'filter' parameter to send in a request. Defaults to 'filter'. Set
+ * this to undefined if you don't want to send a filter parameter
+ */
+ filterParam: 'filter',
+
+ /**
+ * @cfg {String} directionParam The name of the direction parameter to send in a request. <strong>This is only used when simpleSortMode is set to true.</strong>
+ * Defaults to 'dir'.
+ */
+ directionParam: 'dir',
+
+ /**
+ * @cfg {Boolean} simpleSortMode Enabling simpleSortMode in conjunction with remoteSort will only send one sort property and a direction when a remote sort is requested.
+ * The directionParam and sortParam will be sent with the property name and either 'ASC' or 'DESC'
+ */
+ simpleSortMode: false,
+
+ /**
+ * @cfg {Boolean} noCache (optional) Defaults to true. Disable caching by adding a unique parameter
+ * name to the request.
+ */
+ noCache : true,
+
+ /**
+ * @cfg {String} cacheString The name of the cache param added to the url when using noCache (defaults to "_dc")
+ */
+ cacheString: "_dc",
+
+ /**
+ * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
+ */
+ timeout : 30000,
+
+ /**
+ * @cfg {Object} api
+ * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
+ * Defaults to:<pre><code>
+api: {
+ read : undefined,
+ create : undefined,
+ update : undefined,
+ destroy : undefined
+}
+ * </code></pre>
+ * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
+ * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
+ * configured {@link Ext.data.Store}.{@link Ext.data.proxy.Server#url url}.</p><br>
+ * <p>For example:</p>
+ * <pre><code>
+api: {
+ load : '/controller/load',
+ create : '/controller/new',
+ save : '/controller/update',
+ destroy : '/controller/destroy_action'
+}
+ * </code></pre>
+ * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
+ * will be directed to the configured <tt>{@link Ext.data.proxy.Server#url url}</tt>.</p>
+ */
+
+ /**
+ * @ignore
+ */
+ constructor: function(config) {
+ var me = this;
+
+ config = config || {};
+ this.addEvents(
+ /**
+ * @event exception
+ * Fires when the server returns an exception
+ * @param {Ext.data.proxy.Proxy} this
+ * @param {Object} response The response from the AJAX request
+ * @param {Ext.data.Operation} operation The operation that triggered request
+ */
+ 'exception'
+ );
+ me.callParent([config]);
+
+ /**
+ * @cfg {Object} extraParams Extra parameters that will be included on every request. Individual requests with params
+ * of the same name will override these params when they are in conflict.
+ */
+ me.extraParams = config.extraParams || {};
+
+ me.api = config.api || {};
+
+ //backwards compatibility, will be deprecated in 5.0
+ me.nocache = me.noCache;
+ },
+
+ //in a ServerProxy all four CRUD operations are executed in the same manner, so we delegate to doRequest in each case
+ create: function() {
+ return this.doRequest.apply(this, arguments);
+ },
+
+ read: function() {
+ return this.doRequest.apply(this, arguments);
+ },
+
+ update: function() {
+ return this.doRequest.apply(this, arguments);
+ },
+
+ destroy: function() {
+ return this.doRequest.apply(this, arguments);
+ },
+
+ /**
+ * Creates and returns an Ext.data.Request object based on the options passed by the {@link Ext.data.Store Store}
+ * that this Proxy is attached to.
+ * @param {Ext.data.Operation} operation The {@link Ext.data.Operation Operation} object to execute
+ * @return {Ext.data.Request} The request object
+ */
+ buildRequest: function(operation) {
+ var params = Ext.applyIf(operation.params || {}, this.extraParams || {}),
+ request;
+
+ //copy any sorters, filters etc into the params so they can be sent over the wire
+ params = Ext.applyIf(params, this.getParams(params, operation));
+
+ if (operation.id && !params.id) {
+ params.id = operation.id;
+ }
+
+ request = Ext.create('Ext.data.Request', {
+ params : params,
+ action : operation.action,
+ records : operation.records,
+ operation: operation,
+ url : operation.url
+ });
+
+ request.url = this.buildUrl(request);
+
+ /*
+ * Save the request on the Operation. Operations don't usually care about Request and Response data, but in the
+ * ServerProxy and any of its subclasses we add both request and response as they may be useful for further processing
+ */
+ operation.request = request;
+
+ return request;
+ },
+
+ /**
+ *
+ */
+ processResponse: function(success, operation, request, response, callback, scope){
+ var me = this,
+ reader,
+ result,
+ records,
+ length,
+ mc,
+ record,
+ i;
+
+ if (success === true) {
+ reader = me.getReader();
+ result = reader.read(me.extractResponseData(response));
+ records = result.records;
+ length = records.length;
+
+ if (result.success !== false) {
+ mc = Ext.create('Ext.util.MixedCollection', true, function(r) {return r.getId();});
+ mc.addAll(operation.records);
+ for (i = 0; i < length; i++) {
+ record = mc.get(records[i].getId());
+
+ if (record) {
+ record.beginEdit();
+ record.set(record.data);
+ record.endEdit(true);
+ }
+ }
+
+ //see comment in buildRequest for why we include the response object here
+ Ext.apply(operation, {
+ response: response,
+ resultSet: result
+ });
+
+ operation.setCompleted();
+ operation.setSuccessful();
+ } else {
+ operation.setException(result.message);
+ me.fireEvent('exception', this, response, operation);
+ }
+ } else {
+ me.setException(operation, response);
+ me.fireEvent('exception', this, response, operation);
+ }
+
+ //this callback is the one that was passed to the 'read' or 'write' function above
+ if (typeof callback == 'function') {
+ callback.call(scope || me, operation);
+ }
+
+ me.afterRequest(request, success);
+ },
+
+ /**
+ * Sets up an exception on the operation
+ * @private
+ * @param {Ext.data.Operation} operation The operation
+ * @param {Object} response The response
+ */
+ setException: function(operation, response){
+ operation.setException({
+ status: response.status,
+ statusText: response.statusText
+ });
+ },
+
+ /**
+ * Template method to allow subclasses to specify how to get the response for the reader.
+ * @private
+ * @param {Object} response The server response
+ * @return {Mixed} The response data to be used by the reader
+ */
+ extractResponseData: function(response){
+ return response;
+ },
+
+ /**
+ * Encode any values being sent to the server. Can be overridden in subclasses.
+ * @private
+ * @param {Array} An array of sorters/filters.
+ * @return {Mixed} The encoded value
+ */
+ applyEncoding: function(value){
+ return Ext.encode(value);
+ },
+
+ /**
+ * Encodes the array of {@link Ext.util.Sorter} objects into a string to be sent in the request url. By default,
+ * this simply JSON-encodes the sorter data
+ * @param {Array} sorters The array of {@link Ext.util.Sorter Sorter} objects
+ * @return {String} The encoded sorters
+ */
+ encodeSorters: function(sorters) {
+ var min = [],
+ length = sorters.length,
+ i = 0;
+
+ for (; i < length; i++) {
+ min[i] = {
+ property : sorters[i].property,
+ direction: sorters[i].direction
+ };
+ }
+ return this.applyEncoding(min);
+
+ },
+
+ /**
+ * Encodes the array of {@link Ext.util.Filter} objects into a string to be sent in the request url. By default,
+ * this simply JSON-encodes the filter data
+ * @param {Array} sorters The array of {@link Ext.util.Filter Filter} objects
+ * @return {String} The encoded filters
+ */
+ encodeFilters: function(filters) {
+ var min = [],
+ length = filters.length,
+ i = 0;
+
+ for (; i < length; i++) {
+ min[i] = {
+ property: filters[i].property,
+ value : filters[i].value
+ };
+ }
+ return this.applyEncoding(min);
+ },
+
+ /**
+ * @private
+ * Copy any sorters, filters etc into the params so they can be sent over the wire
+ */
+ getParams: function(params, operation) {
+ params = params || {};
+
+ var me = this,
+ isDef = Ext.isDefined,
+ groupers = operation.groupers,
+ sorters = operation.sorters,
+ filters = operation.filters,
+ page = operation.page,
+ start = operation.start,
+ limit = operation.limit,
+
+ simpleSortMode = me.simpleSortMode,
+
+ pageParam = me.pageParam,
+ startParam = me.startParam,
+ limitParam = me.limitParam,
+ groupParam = me.groupParam,
+ sortParam = me.sortParam,
+ filterParam = me.filterParam,
+ directionParam = me.directionParam;
+
+ if (pageParam && isDef(page)) {
+ params[pageParam] = page;
+ }
+
+ if (startParam && isDef(start)) {
+ params[startParam] = start;
+ }
+
+ if (limitParam && isDef(limit)) {
+ params[limitParam] = limit;
+ }
+
+ if (groupParam && groupers && groupers.length > 0) {
+ // Grouper is a subclass of sorter, so we can just use the sorter method
+ params[groupParam] = me.encodeSorters(groupers);
+ }
+
+ if (sortParam && sorters && sorters.length > 0) {
+ if (simpleSortMode) {
+ params[sortParam] = sorters[0].property;
+ params[directionParam] = sorters[0].direction;
+ } else {
+ params[sortParam] = me.encodeSorters(sorters);
+ }
+
+ }
+
+ if (filterParam && filters && filters.length > 0) {
+ params[filterParam] = me.encodeFilters(filters);
+ }
+
+ return params;
+ },
+
+ /**
+ * Generates a url based on a given Ext.data.Request object. By default, ServerProxy's buildUrl will
+ * add the cache-buster param to the end of the url. Subclasses may need to perform additional modifications
+ * to the url.
+ * @param {Ext.data.Request} request The request object
+ * @return {String} The url
+ */
+ buildUrl: function(request) {
+ var me = this,
+ url = me.getUrl(request);
+
+ if (!url) {
+ Ext.Error.raise("You are using a ServerProxy but have not supplied it with a url.");
+ }
+
+ if (me.noCache) {
+ url = Ext.urlAppend(url, Ext.String.format("{0}={1}", me.cacheString, Ext.Date.now()));
+ }
+
+ return url;
+ },
+
+ /**
+ * Get the url for the request taking into account the order of priority,
+ * - The request
+ * - The api
+ * - The url
+ * @private
+ * @param {Ext.data.Request} request The request
+ * @return {String} The url
+ */
+ getUrl: function(request){
+ return request.url || this.api[request.action] || this.url;
+ },
+
+ /**
+ * In ServerProxy subclasses, the {@link #create}, {@link #read}, {@link #update} and {@link #destroy} methods all pass
+ * through to doRequest. Each ServerProxy subclass must implement the doRequest method - see {@link Ext.data.proxy.JsonP}
+ * and {@link Ext.data.proxy.Ajax} for examples. This method carries the same signature as each of the methods that delegate to it.
+ * @param {Ext.data.Operation} operation The Ext.data.Operation object
+ * @param {Function} callback The callback function to call when the Operation has completed
+ * @param {Object} scope The scope in which to execute the callback
+ */
+ doRequest: function(operation, callback, scope) {
+ Ext.Error.raise("The doRequest function has not been implemented on your Ext.data.proxy.Server subclass. See src/data/ServerProxy.js for details");
+ },
+
+ /**
+ * Optional callback function which can be used to clean up after a request has been completed.
+ * @param {Ext.data.Request} request The Request object
+ * @param {Boolean} success True if the request was successful
+ */
+ afterRequest: Ext.emptyFn,
+
+ onDestroy: function() {
+ Ext.destroy(this.reader, this.writer);
+ }
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.proxy.Ajax
+ * @extends Ext.data.proxy.Server
+ *
+ * <p>AjaxProxy is one of the most widely-used ways of getting data into your application. It uses AJAX requests to
+ * load data from the server, usually to be placed into a {@link Ext.data.Store Store}. Let's take a look at a typical
+ * setup. Here we're going to set up a Store that has an AjaxProxy. To prepare, we'll also set up a
+ * {@link Ext.data.Model Model}:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'name', 'email']
+});
+
+//The Store contains the AjaxProxy as an inline configuration
+var store = new Ext.data.Store({
+ model: 'User',
+ proxy: {
+ type: 'ajax',
+ url : 'users.json'
+ }
+});
+
+store.load();
+</code></pre>
+ *
+ * <p>Our example is going to load user data into a Store, so we start off by defining a {@link Ext.data.Model Model}
+ * with the fields that we expect the server to return. Next we set up the Store itself, along with a {@link #proxy}
+ * configuration. This configuration was automatically turned into an Ext.data.proxy.Ajax instance, with the url we
+ * specified being passed into AjaxProxy's constructor. It's as if we'd done this:</p>
+ *
+<pre><code>
+new Ext.data.proxy.Ajax({
+ url: 'users.json',
+ model: 'User',
+ reader: 'json'
+});
+</code></pre>
+ *
+ * <p>A couple of extra configurations appeared here - {@link #model} and {@link #reader}. These are set by default
+ * when we create the proxy via the Store - the Store already knows about the Model, and Proxy's default
+ * {@link Ext.data.reader.Reader Reader} is {@link Ext.data.reader.Json JsonReader}.</p>
+ *
+ * <p>Now when we call store.load(), the AjaxProxy springs into action, making a request to the url we configured
+ * ('users.json' in this case). As we're performing a read, it sends a GET request to that url (see {@link #actionMethods}
+ * to customize this - by default any kind of read will be sent as a GET request and any kind of write will be sent as a
+ * POST request).</p>
+ *
+ * <p><u>Limitations</u></p>
+ *
+ * <p>AjaxProxy cannot be used to retrieve data from other domains. If your application is running on http://domainA.com
+ * it cannot load data from http://domainB.com because browsers have a built-in security policy that prohibits domains
+ * talking to each other via AJAX.</p>
+ *
+ * <p>If you need to read data from another domain and can't set up a proxy server (some software that runs on your own
+ * domain's web server and transparently forwards requests to http://domainB.com, making it look like they actually came
+ * from http://domainA.com), you can use {@link Ext.data.proxy.JsonP} and a technique known as JSON-P (JSON with
+ * Padding), which can help you get around the problem so long as the server on http://domainB.com is set up to support
+ * JSON-P responses. See {@link Ext.data.proxy.JsonP JsonPProxy}'s introduction docs for more details.</p>
+ *
+ * <p><u>Readers and Writers</u></p>
+ *
+ * <p>AjaxProxy can be configured to use any type of {@link Ext.data.reader.Reader Reader} to decode the server's response. If
+ * no Reader is supplied, AjaxProxy will default to using a {@link Ext.data.reader.Json JsonReader}. Reader configuration
+ * can be passed in as a simple object, which the Proxy automatically turns into a {@link Ext.data.reader.Reader Reader}
+ * instance:</p>
+ *
+<pre><code>
+var proxy = new Ext.data.proxy.Ajax({
+ model: 'User',
+ reader: {
+ type: 'xml',
+ root: 'users'
+ }
+});
+
+proxy.getReader(); //returns an {@link Ext.data.reader.Xml XmlReader} instance based on the config we supplied
+</code></pre>
+ *
+ * <p><u>Url generation</u></p>
+ *
+ * <p>AjaxProxy automatically inserts any sorting, filtering, paging and grouping options into the url it generates for
+ * each request. These are controlled with the following configuration options:</p>
+ *
+ * <ul style="list-style-type: disc; padding-left: 20px;">
+ * <li>{@link #pageParam} - controls how the page number is sent to the server
+ * (see also {@link #startParam} and {@link #limitParam})</li>
+ * <li>{@link #sortParam} - controls how sort information is sent to the server</li>
+ * <li>{@link #groupParam} - controls how grouping information is sent to the server</li>
+ * <li>{@link #filterParam} - controls how filter information is sent to the server</li>
+ * </ul>
+ *
+ * <p>Each request sent by AjaxProxy is described by an {@link Ext.data.Operation Operation}. To see how we can
+ * customize the generated urls, let's say we're loading the Proxy with the following Operation:</p>
+ *
+<pre><code>
+var operation = new Ext.data.Operation({
+ action: 'read',
+ page : 2
+});
+</code></pre>
+ *
+ * <p>Now we'll issue the request for this Operation by calling {@link #read}:</p>
+ *
+<pre><code>
+var proxy = new Ext.data.proxy.Ajax({
+ url: '/users'
+});
+
+proxy.read(operation); //GET /users?page=2
+</code></pre>
+ *
+ * <p>Easy enough - the Proxy just copied the page property from the Operation. We can customize how this page data is
+ * sent to the server:</p>
+ *
+<pre><code>
+var proxy = new Ext.data.proxy.Ajax({
+ url: '/users',
+ pagePage: 'pageNumber'
+});
+
+proxy.read(operation); //GET /users?pageNumber=2
+</code></pre>
+ *
+ * <p>Alternatively, our Operation could have been configured to send start and limit parameters instead of page:</p>
+ *
+<pre><code>
+var operation = new Ext.data.Operation({
+ action: 'read',
+ start : 50,
+ limit : 25
+});
+
+var proxy = new Ext.data.proxy.Ajax({
+ url: '/users'
+});
+
+proxy.read(operation); //GET /users?start=50&limit=25
+</code></pre>
+ *
+ * <p>Again we can customize this url:</p>
+ *
+<pre><code>
+var proxy = new Ext.data.proxy.Ajax({
+ url: '/users',
+ startParam: 'startIndex',
+ limitParam: 'limitIndex'
+});
+
+proxy.read(operation); //GET /users?startIndex=50&limitIndex=25
+</code></pre>
+ *
+ * <p>AjaxProxy will also send sort and filter information to the server. Let's take a look at how this looks with a
+ * more expressive Operation object:</p>
+ *
+<pre><code>
+var operation = new Ext.data.Operation({
+ action: 'read',
+ sorters: [
+ new Ext.util.Sorter({
+ property : 'name',
+ direction: 'ASC'
+ }),
+ new Ext.util.Sorter({
+ property : 'age',
+ direction: 'DESC'
+ })
+ ],
+ filters: [
+ new Ext.util.Filter({
+ property: 'eyeColor',
+ value : 'brown'
+ })
+ ]
+});
+</code></pre>
+ *
+ * <p>This is the type of object that is generated internally when loading a {@link Ext.data.Store Store} with sorters
+ * and filters defined. By default the AjaxProxy will JSON encode the sorters and filters, resulting in something like
+ * this (note that the url is escaped before sending the request, but is left unescaped here for clarity):</p>
+ *
+<pre><code>
+var proxy = new Ext.data.proxy.Ajax({
+ url: '/users'
+});
+
+proxy.read(operation); //GET /users?sort=[{"property":"name","direction":"ASC"},{"property":"age","direction":"DESC"}]&filter=[{"property":"eyeColor","value":"brown"}]
+</code></pre>
+ *
+ * <p>We can again customize how this is created by supplying a few configuration options. Let's say our server is set
+ * up to receive sorting information is a format like "sortBy=name#ASC,age#DESC". We can configure AjaxProxy to provide
+ * that format like this:</p>
+ *
+ <pre><code>
+ var proxy = new Ext.data.proxy.Ajax({
+ url: '/users',
+ sortParam: 'sortBy',
+ filterParam: 'filterBy',
+
+ //our custom implementation of sorter encoding - turns our sorters into "name#ASC,age#DESC"
+ encodeSorters: function(sorters) {
+ var length = sorters.length,
+ sortStrs = [],
+ sorter, i;
+
+ for (i = 0; i < length; i++) {
+ sorter = sorters[i];
+
+ sortStrs[i] = sorter.property + '#' + sorter.direction
+ }
+
+ return sortStrs.join(",");
+ }
+ });
+
+ proxy.read(operation); //GET /users?sortBy=name#ASC,age#DESC&filterBy=[{"property":"eyeColor","value":"brown"}]
+ </code></pre>
+ *
+ * <p>We can also provide a custom {@link #encodeFilters} function to encode our filters.</p>
+ *
+ * @constructor
+ *
+ * <p>Note that if this HttpProxy is being used by a {@link Ext.data.Store Store}, then the
+ * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
+ * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
+ * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
+ * used to pass parameters known at instantiation time.</p>
+ *
+ * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
+ * the request.</p>
+ */
+Ext.define('Ext.data.proxy.Ajax', {
+ requires: ['Ext.util.MixedCollection', 'Ext.Ajax'],
+ extend: 'Ext.data.proxy.Server',
+ alias: 'proxy.ajax',
+ alternateClassName: ['Ext.data.HttpProxy', 'Ext.data.AjaxProxy'],
+
+ /**
+ * @property actionMethods
+ * Mapping of action name to HTTP request method. In the basic AjaxProxy these are set to 'GET' for 'read' actions and 'POST'
+ * for 'create', 'update' and 'destroy' actions. The {@link Ext.data.proxy.Rest} maps these to the correct RESTful methods.
+ */
+ actionMethods: {
+ create : 'POST',
+ read : 'GET',
+ update : 'POST',
+ destroy: 'POST'
+ },
+
+ /**
+ * @cfg {Object} headers Any headers to add to the Ajax request. Defaults to <tt>undefined</tt>.
+ */
+
+ /**
+ * @ignore
+ */
+ doRequest: function(operation, callback, scope) {
+ var writer = this.getWriter(),
+ request = this.buildRequest(operation, callback, scope);
+
+ if (operation.allowWrite()) {
+ request = writer.write(request);
+ }
+
+ Ext.apply(request, {
+ headers : this.headers,
+ timeout : this.timeout,
+ scope : this,
+ callback : this.createRequestCallback(request, operation, callback, scope),
+ method : this.getMethod(request),
+ disableCaching: false // explicitly set it to false, ServerProxy handles caching
+ });
+
+ Ext.Ajax.request(request);
+
+ return request;
+ },
+
+ /**
+ * Returns the HTTP method name for a given request. By default this returns based on a lookup on {@link #actionMethods}.
+ * @param {Ext.data.Request} request The request object
+ * @return {String} The HTTP method to use (should be one of 'GET', 'POST', 'PUT' or 'DELETE')
+ */
+ getMethod: function(request) {
+ return this.actionMethods[request.action];
+ },
+
+ /**
+ * @private
+ * TODO: This is currently identical to the JsonPProxy version except for the return function's signature. There is a lot
+ * of code duplication inside the returned function so we need to find a way to DRY this up.
+ * @param {Ext.data.Request} request The Request object
+ * @param {Ext.data.Operation} operation The Operation being executed
+ * @param {Function} callback The callback function to be called when the request completes. This is usually the callback
+ * passed to doRequest
+ * @param {Object} scope The scope in which to execute the callback function
+ * @return {Function} The callback function
+ */
+ createRequestCallback: function(request, operation, callback, scope) {
+ var me = this;
+
+ return function(options, success, response) {
+ me.processResponse(success, operation, request, response, callback, scope);
+ };
+ }
+}, function() {
+ //backwards compatibility, remove in Ext JS 5.0
+ Ext.data.HttpProxy = this;
+});
+
+/**
+ * @author Ed Spencer
+ * @class Ext.data.Model
+ *
+ * <p>A Model represents some object that your application manages. For example, one might define a Model for Users, Products,
+ * Cars, or any other real-world object that we want to model in the system. Models are registered via the {@link Ext.ModelManager model manager},
+ * and are used by {@link Ext.data.Store stores}, which are in turn used by many of the data-bound components in Ext.</p>
+ *
+ * <p>Models are defined as a set of fields and any arbitrary methods and properties relevant to the model. For example:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'name', type: 'string'},
+ {name: 'age', type: 'int'},
+ {name: 'phone', type: 'string'},
+ {name: 'alive', type: 'boolean', defaultValue: true}
+ ],
+
+ changeName: function() {
+ var oldName = this.get('name'),
+ newName = oldName + " The Barbarian";
+
+ this.set('name', newName);
+ }
+});
+</code></pre>
+*
+* <p>The fields array is turned into a {@link Ext.util.MixedCollection MixedCollection} automatically by the {@link Ext.ModelManager ModelManager}, and all
+* other functions and properties are copied to the new Model's prototype.</p>
+*
+* <p>Now we can create instances of our User model and call any model logic we defined:</p>
+*
+<pre><code>
+var user = Ext.ModelManager.create({
+ name : 'Conan',
+ age : 24,
+ phone: '555-555-5555'
+}, 'User');
+
+user.changeName();
+user.get('name'); //returns "Conan The Barbarian"
+</code></pre>
+ *
+ * <p><u>Validations</u></p>
+ *
+ * <p>Models have built-in support for validations, which are executed against the validator functions in
+ * {@link Ext.data.validations} ({@link Ext.data.validations see all validation functions}). Validations are easy to add to models:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'name', type: 'string'},
+ {name: 'age', type: 'int'},
+ {name: 'phone', type: 'string'},
+ {name: 'gender', type: 'string'},
+ {name: 'username', type: 'string'},
+ {name: 'alive', type: 'boolean', defaultValue: true}
+ ],
+
+ validations: [
+ {type: 'presence', field: 'age'},
+ {type: 'length', field: 'name', min: 2},
+ {type: 'inclusion', field: 'gender', list: ['Male', 'Female']},
+ {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
+ {type: 'format', field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
+ ]
+});
+</code></pre>
+ *
+ * <p>The validations can be run by simply calling the {@link #validate} function, which returns a {@link Ext.data.Errors}
+ * object:</p>
+ *
+<pre><code>
+var instance = Ext.ModelManager.create({
+ name: 'Ed',
+ gender: 'Male',
+ username: 'edspencer'
+}, 'User');
+
+var errors = instance.validate();
+</code></pre>
+ *
+ * <p><u>Associations</u></p>
+ *
+ * <p>Models can have associations with other Models via {@link Ext.data.BelongsToAssociation belongsTo} and
+ * {@link Ext.data.HasManyAssociation hasMany} associations. For example, let's say we're writing a blog administration
+ * application which deals with Users, Posts and Comments. We can express the relationships between these models like this:</p>
+ *
+<pre><code>
+Ext.define('Post', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'user_id'],
+
+ belongsTo: 'User',
+ hasMany : {model: 'Comment', name: 'comments'}
+});
+
+Ext.define('Comment', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'user_id', 'post_id'],
+
+ belongsTo: 'Post'
+});
+
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id'],
+
+ hasMany: [
+ 'Post',
+ {model: 'Comment', name: 'comments'}
+ ]
+});
+</code></pre>
+ *
+ * <p>See the docs for {@link Ext.data.BelongsToAssociation} and {@link Ext.data.HasManyAssociation} for details on the usage
+ * and configuration of associations. Note that associations can also be specified like this:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id'],
+
+ associations: [
+ {type: 'hasMany', model: 'Post', name: 'posts'},
+ {type: 'hasMany', model: 'Comment', name: 'comments'}
+ ]
+});
+</code></pre>
+ *
+ * <p><u>Using a Proxy</u></p>
+ *
+ * <p>Models are great for representing types of data and relationships, but sooner or later we're going to want to
+ * load or save that data somewhere. All loading and saving of data is handled via a {@link Ext.data.proxy.Proxy Proxy},
+ * which can be set directly on the Model:</p>
+ *
+<pre><code>
+Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: ['id', 'name', 'email'],
+
+ proxy: {
+ type: 'rest',
+ url : '/users'
+ }
+});
+</code></pre>
+ *
+ * <p>Here we've set up a {@link Ext.data.proxy.Rest Rest Proxy}, which knows how to load and save data to and from a
+ * RESTful backend. Let's see how this works:</p>
+ *
+<pre><code>
+var user = Ext.ModelManager.create({name: 'Ed Spencer', email: 'ed@sencha.com'}, 'User');
+
+user.save(); //POST /users
+</code></pre>
+ *
+ * <p>Calling {@link #save} on the new Model instance tells the configured RestProxy that we wish to persist this
+ * Model's data onto our server. RestProxy figures out that this Model hasn't been saved before because it doesn't
+ * have an id, and performs the appropriate action - in this case issuing a POST request to the url we configured
+ * (/users). We configure any Proxy on any Model and always follow this API - see {@link Ext.data.proxy.Proxy} for a full
+ * list.</p>
+ *
+ * <p>Loading data via the Proxy is equally easy:</p>
+ *
+<pre><code>
+//get a reference to the User model class
+var User = Ext.ModelManager.getModel('User');
+
+//Uses the configured RestProxy to make a GET request to /users/123
+User.load(123, {
+ success: function(user) {
+ console.log(user.getId()); //logs 123
+ }
+});
+</code></pre>
+ *
+ * <p>Models can also be updated and destroyed easily:</p>
+ *
+<pre><code>
+//the user Model we loaded in the last snippet:
+user.set('name', 'Edward Spencer');
+
+//tells the Proxy to save the Model. In this case it will perform a PUT request to /users/123 as this Model already has an id
+user.save({
+ success: function() {
+ console.log('The User was updated');
+ }
+});
+
+//tells the Proxy to destroy the Model. Performs a DELETE request to /users/123
+user.destroy({
+ success: function() {
+ console.log('The User was destroyed!');
+ }
+});
+</code></pre>
+ *
+ * <p><u>Usage in Stores</u></p>
+ *
+ * <p>It is very common to want to load a set of Model instances to be displayed and manipulated in the UI. We do this
+ * by creating a {@link Ext.data.Store Store}:</p>
+ *
+<pre><code>
+var store = new Ext.data.Store({
+ model: 'User'
+});
+
+//uses the Proxy we set up on Model to load the Store data
+store.load();
+</code></pre>
+ *
+ * <p>A Store is just a collection of Model instances - usually loaded from a server somewhere. Store can also maintain
+ * a set of added, updated and removed Model instances to be synchronized with the server via the Proxy. See the
+ * {@link Ext.data.Store Store docs} for more information on Stores.</p>
+ *
+ * @constructor
+ * @param {Object} data An object containing keys corresponding to this model's fields, and their associated values
+ * @param {Number} id Optional unique ID to assign to this model instance
+ */
+Ext.define('Ext.data.Model', {
+ alternateClassName: 'Ext.data.Record',
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: [
+ 'Ext.ModelManager',
+ 'Ext.data.Field',
+ 'Ext.data.Errors',
+ 'Ext.data.Operation',
+ 'Ext.data.validations',
+ 'Ext.data.proxy.Ajax',
+ 'Ext.util.MixedCollection'
+ ],
+
+ onClassExtended: function(cls, data) {
+ var onBeforeClassCreated = data.onBeforeClassCreated;
+
+ data.onBeforeClassCreated = function(cls, data) {
+ var me = this,
+ name = Ext.getClassName(cls),
+ prototype = cls.prototype,
+ superCls = cls.prototype.superclass,
+
+ validations = data.validations || [],
+ fields = data.fields || [],
+ associations = data.associations || [],
+ belongsTo = data.belongsTo,
+ hasMany = data.hasMany,
+
+ fieldsMixedCollection = new Ext.util.MixedCollection(false, function(field) {
+ return field.name;
+ }),
+
+ associationsMixedCollection = new Ext.util.MixedCollection(false, function(association) {
+ return association.name;
+ }),
+
+ superValidations = superCls.validations,
+ superFields = superCls.fields,
+ superAssociations = superCls.associations,
+
+ association, i, ln,
+ dependencies = [];
+
+ // Save modelName on class and its prototype
+ cls.modelName = name;
+ prototype.modelName = name;
+
+ // Merge the validations of the superclass and the new subclass
+ if (superValidations) {
+ validations = superValidations.concat(validations);
+ }
+
+ data.validations = validations;
+
+ // Merge the fields of the superclass and the new subclass
+ if (superFields) {
+ fields = superFields.items.concat(fields);
+ }
+
+ for (i = 0, ln = fields.length; i < ln; ++i) {
+ fieldsMixedCollection.add(new Ext.data.Field(fields[i]));
+ }
+
+ data.fields = fieldsMixedCollection;
+
+ //associations can be specified in the more convenient format (e.g. not inside an 'associations' array).
+ //we support that here
+ if (belongsTo) {
+ belongsTo = Ext.Array.from(belongsTo);
+
+ for (i = 0, ln = belongsTo.length; i < ln; ++i) {
+ association = belongsTo[i];
+
+ if (!Ext.isObject(association)) {
+ association = {model: association};
+ }
+
+ association.type = 'belongsTo';
+ associations.push(association);
+ }
+
+ delete data.belongsTo;
+ }
+
+ if (hasMany) {
+ hasMany = Ext.Array.from(hasMany);
+ for (i = 0, ln = hasMany.length; i < ln; ++i) {
+ association = hasMany[i];
+
+ if (!Ext.isObject(association)) {
+ association = {model: association};
+ }
+
+ association.type = 'hasMany';
+ associations.push(association);
+ }
+
+ delete data.hasMany;
+ }
+
+ if (superAssociations) {
+ associations = superAssociations.items.concat(associations);
+ }
+
+ for (i = 0, ln = associations.length; i < ln; ++i) {
+ dependencies.push('association.' + associations[i].type.toLowerCase());
+ }
+
+ if (data.proxy) {
+ if (typeof data.proxy === 'string') {
+ dependencies.push('proxy.' + data.proxy);
+ }
+ else if (typeof data.proxy.type === 'string') {
+ dependencies.push('proxy.' + data.proxy.type);
+ }
+ }
+
+ Ext.require(dependencies, function() {
+ Ext.ModelManager.registerType(name, cls);
+
+ for (i = 0, ln = associations.length; i < ln; ++i) {
+ association = associations[i];
+
+ Ext.apply(association, {
+ ownerModel: name,
+ associatedModel: association.model
+ });
+
+ if (Ext.ModelManager.getModel(association.model) === undefined) {
+ Ext.ModelManager.registerDeferredAssociation(association);
+ } else {
+ associationsMixedCollection.add(Ext.data.Association.create(association));
+ }
+ }
+
+ data.associations = associationsMixedCollection;
+
+ onBeforeClassCreated.call(me, cls, data);
+
+ cls.setProxy(cls.prototype.proxy || cls.prototype.defaultProxyType);
+
+ // Fire the onModelDefined template method on ModelManager
+ Ext.ModelManager.onModelDefined(cls);
+ });
+ }
+ },
+
+ inheritableStatics: {
+ /**
+ * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
+ * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
+ * @static
+ */
+ setProxy: function(proxy) {
+ //make sure we have an Ext.data.proxy.Proxy object
+ if (!proxy.isProxy) {
+ if (typeof proxy == "string") {
+ proxy = {
+ type: proxy
+ };
+ }
+ proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
+ }
+ proxy.setModel(this);
+ this.proxy = this.prototype.proxy = proxy;
+
+ return proxy;
+ },
+
+ /**
+ * Returns the configured Proxy for this Model
+ * @return {Ext.data.proxy.Proxy} The proxy
+ */
+ getProxy: function() {
+ return this.proxy;
+ },
+
+ /**
+ * <b>Static</b>. Asynchronously loads a model instance by id. Sample usage:
+ <pre><code>
+ MyApp.User = Ext.define('User', {
+ extend: 'Ext.data.Model',
+ fields: [
+ {name: 'id', type: 'int'},
+ {name: 'name', type: 'string'}
+ ]
+ });
+
+ MyApp.User.load(10, {
+ scope: this,
+ failure: function(record, operation) {
+ //do something if the load failed
+ },
+ success: function(record, operation) {
+ //do something if the load succeeded
+ },
+ callback: function(record, operation) {
+ //do something whether the load succeeded or failed
+ }
+ });
+ </code></pre>
+ * @param {Number} id The id of the model to load
+ * @param {Object} config Optional config object containing success, failure and callback functions, plus optional scope
+ * @member Ext.data.Model
+ * @method load
+ * @static
+ */
+ load: function(id, config) {
+ config = Ext.apply({}, config);
+ config = Ext.applyIf(config, {
+ action: 'read',
+ id : id
+ });
+
+ var operation = Ext.create('Ext.data.Operation', config),
+ scope = config.scope || this,
+ record = null,
+ callback;
+
+ callback = function(operation) {
+ if (operation.wasSuccessful()) {
+ record = operation.getRecords()[0];
+ Ext.callback(config.success, scope, [record, operation]);
+ } else {
+ Ext.callback(config.failure, scope, [record, operation]);
+ }
+ Ext.callback(config.callback, scope, [record, operation]);
+ };
+
+ this.proxy.read(operation, callback, this);
+ }
+ },
+
+ statics: {
+ PREFIX : 'ext-record',
+ AUTO_ID: 1,
+ EDIT : 'edit',
+ REJECT : 'reject',
+ COMMIT : 'commit',
+
+ /**
+ * Generates a sequential id. This method is typically called when a record is {@link #create}d
+ * and {@link #Record no id has been specified}. The id will automatically be assigned
+ * to the record. The returned id takes the form:
+ * <tt>{PREFIX}-{AUTO_ID}</tt>.<div class="mdetail-params"><ul>
+ * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.PREFIX</tt>
+ * (defaults to <tt>'ext-record'</tt>)</p></li>
+ * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Model.AUTO_ID</tt>
+ * (defaults to <tt>1</tt> initially)</p></li>
+ * </ul></div>
+ * @param {Ext.data.Model} rec The record being created. The record does not exist, it's a {@link #phantom}.
+ * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
+ * @static
+ */
+ id: function(rec) {
+ var id = [this.PREFIX, '-', this.AUTO_ID++].join('');
+ rec.phantom = true;
+ rec.internalId = id;
+ return id;
+ }
+ },
+
+ /**
+ * Internal flag used to track whether or not the model instance is currently being edited. Read-only
+ * @property editing
+ * @type Boolean
+ */
+ editing : false,
+
+ /**
+ * Readonly flag - true if this Record has been modified.
+ * @type Boolean
+ */
+ dirty : false,
+
+ /**
+ * @cfg {String} persistanceProperty The property on this Persistable object that its data is saved to.
+ * Defaults to 'data' (e.g. all persistable data resides in this.data.)
+ */
+ persistanceProperty: 'data',
+
+ evented: false,
+ isModel: true,
+
+ /**
+ * <tt>true</tt> when the record does not yet exist in a server-side database (see
+ * {@link #setDirty}). Any record which has a real database pk set as its id property
+ * is NOT a phantom -- it's real.
+ * @property phantom
+ * @type {Boolean}
+ */
+ phantom : false,
+
+ /**
+ * @cfg {String} idProperty The name of the field treated as this Model's unique id (defaults to 'id').
+ */
+ idProperty: 'id',
+
+ /**
+ * The string type of the default Model Proxy. Defaults to 'ajax'
+ * @property defaultProxyType
+ * @type String
+ */
+ defaultProxyType: 'ajax',
+
+ /**
+ * An array of the fields defined on this model
+ * @property fields
+ * @type {Array}
+ */
+
+ constructor: function(data, id) {
+ data = data || {};
+
+ var me = this,
+ fields,
+ length,
+ field,
+ name,
+ i,
+ isArray = Ext.isArray(data),
+ newData = isArray ? {} : null; // to hold mapped array data if needed
+
+ /**
+ * An internal unique ID for each Model instance, used to identify Models that don't have an ID yet
+ * @property internalId
+ * @type String
+ * @private
+ */
+ me.internalId = (id || id === 0) ? id : Ext.data.Model.id(me);
+
+ Ext.applyIf(me, {
+ data: {}
+ });
+
+ /**
+ * Key: value pairs of all fields whose values have changed
+ * @property modified
+ * @type Object
+ */
+ me.modified = {};
+
+ me[me.persistanceProperty] = {};
+
+ me.mixins.observable.constructor.call(me);
+
+ //add default field values if present
+ fields = me.fields.items;
+ length = fields.length;
+
+ for (i = 0; i < length; i++) {
+ field = fields[i];
+ name = field.name;
+
+ if (isArray){
+ // Have to map array data so the values get assigned to the named fields
+ // rather than getting set as the field names with undefined values.
+ newData[name] = data[i];
+ }
+ else if (data[name] === undefined) {
+ data[name] = field.defaultValue;
+ }
+ }
+
+ me.set(newData || data);
+ // clear any dirty/modified since we're initializing
+ me.dirty = false;
+ me.modified = {};
+
+ if (me.getId()) {
+ me.phantom = false;
+ }
+
+ if (typeof me.init == 'function') {
+ me.init();
+ }
+
+ me.id = me.modelName + '-' + me.internalId;
+
+ Ext.ModelManager.register(me);
+ },
+
+ /**
+ * Returns the value of the given field
+ * @param {String} fieldName The field to fetch the value for
+ * @return {Mixed} The value
+ */
+ get: function(field) {
+ return this[this.persistanceProperty][field];
+ },
+
+ /**
+ * Sets the given field to the given value, marks the instance as dirty
+ * @param {String|Object} fieldName The field to set, or an object containing key/value pairs
+ * @param {Mixed} value The value to set
+ */
+ set: function(fieldName, value) {
+ var me = this,
+ fields = me.fields,
+ modified = me.modified,
+ convertFields = [],
+ field, key, i, currentValue;
+
+ /*
+ * If we're passed an object, iterate over that object. NOTE: we pull out fields with a convert function and
+ * set those last so that all other possible data is set before the convert function is called
+ */
+ if (arguments.length == 1 && Ext.isObject(fieldName)) {
+ for (key in fieldName) {
+ if (fieldName.hasOwnProperty(key)) {
+
+ //here we check for the custom convert function. Note that if a field doesn't have a convert function,
+ //we default it to its type's convert function, so we have to check that here. This feels rather dirty.
+ field = fields.get(key);
+ if (field && field.convert !== field.type.convert) {
+ convertFields.push(key);
+ continue;
+ }
+
+ me.set(key, fieldName[key]);
+ }
+ }
+
+ for (i = 0; i < convertFields.length; i++) {
+ field = convertFields[i];
+ me.set(field, fieldName[field]);
+ }
+
+ } else {
+ if (fields) {
+ field = fields.get(fieldName);
+
+ if (field && field.convert) {
+ value = field.convert(value, me);
+ }
+ }
+ currentValue = me.get(fieldName);
+ me[me.persistanceProperty][fieldName] = value;
+
+ if (field && field.persist && !me.isEqual(currentValue, value)) {
+ me.dirty = true;
+ me.modified[fieldName] = currentValue;
+ }
+
+ if (!me.editing) {
+ me.afterEdit();
+ }
+ }
+ },
+
+ /**
+ * Checks if two values are equal, taking into account certain
+ * special factors, for example dates.
+ * @private
+ * @param {Object} a The first value
+ * @param {Object} b The second value
+ * @return {Boolean} True if the values are equal
+ */
+ isEqual: function(a, b){
+ if (Ext.isDate(a) && Ext.isDate(b)) {
+ return a.getTime() === b.getTime();
+ }
+ return a === b;
+ },
+
+ /**
+ * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
+ * are relayed to the containing store. When an edit has begun, it must be followed
+ * by either {@link #endEdit} or {@link #cancelEdit}.
+ */
+ beginEdit : function(){
+ var me = this;
+ if (!me.editing) {
+ me.editing = true;
+ me.dirtySave = me.dirty;
+ me.dataSave = Ext.apply({}, me[me.persistanceProperty]);
+ me.modifiedSave = Ext.apply({}, me.modified);
+ }
+ },
+
+ /**
+ * Cancels all changes made in the current edit operation.
+ */
+ cancelEdit : function(){
+ var me = this;
+ if (me.editing) {
+ me.editing = false;
+ // reset the modified state, nothing changed since the edit began
+ me.modified = me.modifiedSave;
+ me[me.persistanceProperty] = me.dataSave;
+ me.dirty = me.dirtySave;
+ delete me.modifiedSave;
+ delete me.dataSave;
+ delete me.dirtySave;
+ }
+ },
+
+ /**
+ * End an edit. If any data was modified, the containing store is notified
+ * (ie, the store's <code>update</code> event will fire).
+ * @param {Boolean} silent True to not notify the store of the change
+ */
+ endEdit : function(silent){
+ var me = this;
+ if (me.editing) {
+ me.editing = false;
+ delete me.modifiedSave;
+ delete me.dataSave;
+ delete me.dirtySave;
+ if (silent !== true && me.dirty) {
+ me.afterEdit();
+ }
+ }
+ },
+
+ /**
+ * Gets a hash of only the fields that have been modified since this Model was created or commited.
+ * @return Object
+ */
+ getChanges : function(){
+ var modified = this.modified,
+ changes = {},
+ field;
+
+ for (field in modified) {
+ if (modified.hasOwnProperty(field)){
+ changes[field] = this.get(field);
+ }
+ }
+
+ return changes;
+ },
+
+ /**
+ * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
+ * since the load or last commit.
+ * @param {String} fieldName {@link Ext.data.Field#name}
+ * @return {Boolean}
+ */
+ isModified : function(fieldName) {
+ return this.modified.hasOwnProperty(fieldName);
+ },
+
+ /**
+ * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
+ * is used interally when adding <code>{@link #phantom}</code> records to a
+ * {@link Ext.data.Store#writer writer enabled store}.</p>
+ * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
+ * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
+ * have a create action composed for it during {@link Ext.data.Store#save store save}
+ * operations.</p>
+ */
+ setDirty : function() {
+ var me = this,
+ name;
+
+ me.dirty = true;
+
+ me.fields.each(function(field) {
+ if (field.persist) {
+ name = field.name;
+ me.modified[name] = me.get(name);
+ }
+ }, me);
+ },
+
+ markDirty : function() {
+ if (Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.warn('Ext.data.Model: markDirty has been deprecated. Use setDirty instead.');
+ }
+ return this.setDirty.apply(this, arguments);
+ },
+
+ /**
+ * Usually called by the {@link Ext.data.Store} to which this model instance has been {@link #join joined}.
+ * Rejects all changes made to the model instance since either creation, or the last commit operation.
+ * Modified fields are reverted to their original values.
+ * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
+ * to have their code notified of reject operations.</p>
+ * @param {Boolean} silent (optional) True to skip notification of the owning
+ * store of the change (defaults to false)
+ */
+ reject : function(silent) {
+ var me = this,
+ modified = me.modified,
+ field;
+
+ for (field in modified) {
+ if (modified.hasOwnProperty(field)) {
+ if (typeof modified[field] != "function") {
+ me[me.persistanceProperty][field] = modified[field];
+ }
+ }
+ }
+
+ me.dirty = false;
+ me.editing = false;
+ me.modified = {};
+
+ if (silent !== true) {
+ me.afterReject();
+ }
+ },
+
+ /**
+ * Usually called by the {@link Ext.data.Store} which owns the model instance.
+ * Commits all changes made to the instance since either creation or the last commit operation.
+ * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
+ * to have their code notified of commit operations.</p>
+ * @param {Boolean} silent (optional) True to skip notification of the owning
+ * store of the change (defaults to false)
+ */
+ commit : function(silent) {
+ var me = this;
+
+ me.dirty = false;
+ me.editing = false;
+
+ me.modified = {};
+
+ if (silent !== true) {
+ me.afterCommit();
+ }
+ },
+
+ /**
+ * Creates a copy (clone) of this Model instance.
+ * @param {String} id (optional) A new id, defaults to the id
+ * of the instance being copied. See <code>{@link #id}</code>.
+ * To generate a phantom instance with a new id use:<pre><code>
+var rec = record.copy(); // clone the record
+Ext.data.Model.id(rec); // automatically generate a unique sequential id
+ * </code></pre>
+ * @return {Record}
+ */
+ copy : function(newId) {
+ var me = this;
+
+ return new me.self(Ext.apply({}, me[me.persistanceProperty]), newId || me.internalId);
+ },
+
+ /**
+ * Sets the Proxy to use for this model. Accepts any options that can be accepted by {@link Ext#createByAlias Ext.createByAlias}
+ * @param {String/Object/Ext.data.proxy.Proxy} proxy The proxy
+ * @static
+ */
+ setProxy: function(proxy) {
+ //make sure we have an Ext.data.proxy.Proxy object
+ if (!proxy.isProxy) {
+ if (typeof proxy === "string") {
+ proxy = {
+ type: proxy
+ };
+ }
+ proxy = Ext.createByAlias("proxy." + proxy.type, proxy);
+ }
+ proxy.setModel(this.self);
+ this.proxy = proxy;
+
+ return proxy;
+ },
+
+ /**
+ * Returns the configured Proxy for this Model
+ * @return {Ext.data.proxy.Proxy} The proxy
+ */
+ getProxy: function() {
+ return this.proxy;
+ },
+
+ /**
+ * Validates the current data against all of its configured {@link #validations} and returns an
+ * {@link Ext.data.Errors Errors} object
+ * @return {Ext.data.Errors} The errors object
+ */
+ validate: function() {
+ var errors = Ext.create('Ext.data.Errors'),
+ validations = this.validations,
+ validators = Ext.data.validations,
+ length, validation, field, valid, type, i;
+
+ if (validations) {
+ length = validations.length;
+
+ for (i = 0; i < length; i++) {
+ validation = validations[i];
+ field = validation.field || validation.name;
+ type = validation.type;
+ valid = validators[type](validation, this.get(field));
+
+ if (!valid) {
+ errors.add({
+ field : field,
+ message: validation.message || validators[type + 'Message']
+ });
+ }
+ }
+ }
+
+ return errors;
+ },
+
+ /**
+ * Checks if the model is valid. See {@link #validate}.
+ * @return {Boolean} True if the model is valid.
+ */
+ isValid: function(){
+ return this.validate().isValid();
+ },
+
+ /**
+ * Saves the model instance using the configured proxy
+ * @param {Object} options Options to pass to the proxy
+ * @return {Ext.data.Model} The Model instance
+ */
+ save: function(options) {
+ options = Ext.apply({}, options);
+
+ var me = this,
+ action = me.phantom ? 'create' : 'update',
+ record = null,
+ scope = options.scope || me,
+ operation,
+ callback;
+
+ Ext.apply(options, {
+ records: [me],
+ action : action
+ });
+
+ operation = Ext.create('Ext.data.Operation', options);
+
+ callback = function(operation) {
+ if (operation.wasSuccessful()) {
+ record = operation.getRecords()[0];
+ //we need to make sure we've set the updated data here. Ideally this will be redundant once the
+ //ModelCache is in place
+ me.set(record.data);
+ record.dirty = false;
+
+ Ext.callback(options.success, scope, [record, operation]);
+ } else {
+ Ext.callback(options.failure, scope, [record, operation]);
+ }
+
+ Ext.callback(options.callback, scope, [record, operation]);
+ };
+
+ me.getProxy()[action](operation, callback, me);
+
+ return me;
+ },
+
+ /**
+ * Destroys the model using the configured proxy
+ * @param {Object} options Options to pass to the proxy
+ * @return {Ext.data.Model} The Model instance
+ */
+ destroy: function(options){
+ options = Ext.apply({}, options);
+
+ var me = this,
+ record = null,
+ scope = options.scope || me,
+ operation,
+ callback;
+
+ Ext.apply(options, {
+ records: [me],
+ action : 'destroy'
+ });
+
+ operation = Ext.create('Ext.data.Operation', options);
+ callback = function(operation) {
+ if (operation.wasSuccessful()) {
+ Ext.callback(options.success, scope, [record, operation]);
+ } else {
+ Ext.callback(options.failure, scope, [record, operation]);
+ }
+ Ext.callback(options.callback, scope, [record, operation]);
+ };
+
+ me.getProxy().destroy(operation, callback, me);
+ return me;
+ },
+
+ /**
+ * Returns the unique ID allocated to this model instance as defined by {@link #idProperty}
+ * @return {Number} The id
+ */
+ getId: function() {
+ return this.get(this.idProperty);
+ },
+
+ /**
+ * Sets the model instance's id field to the given id
+ * @param {Number} id The new id
+ */
+ setId: function(id) {
+ this.set(this.idProperty, id);
+ },
+
+ /**
+ * Tells this model instance that it has been added to a store
+ * @param {Ext.data.Store} store The store that the model has been added to
+ */
+ join : function(store) {
+ /**
+ * The {@link Ext.data.Store} to which this Record belongs.
+ * @property store
+ * @type {Ext.data.Store}
+ */
+ this.store = store;
+ },
+
+ /**
+ * Tells this model instance that it has been removed from the store
+ */
+ unjoin: function() {
+ delete this.store;
+ },
+
+ /**
+ * @private
+ * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+ * afterEdit method is called
+ */
+ afterEdit : function() {
+ this.callStore('afterEdit');
+ },
+
+ /**
+ * @private
+ * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+ * afterReject method is called
+ */
+ afterReject : function() {
+ this.callStore("afterReject");
+ },
+
+ /**
+ * @private
+ * If this Model instance has been {@link #join joined} to a {@link Ext.data.Store store}, the store's
+ * afterCommit method is called
+ */
+ afterCommit: function() {
+ this.callStore('afterCommit');
+ },
+
+ /**
+ * @private
+ * Helper function used by afterEdit, afterReject and afterCommit. Calls the given method on the
+ * {@link Ext.data.Store store} that this instance has {@link #join joined}, if any. The store function
+ * will always be called with the model instance as its single argument.
+ * @param {String} fn The function to call on the store
+ */
+ callStore: function(fn) {
+ var store = this.store;
+
+ if (store !== undefined && typeof store[fn] == "function") {
+ store[fn](this);
+ }
+ },
+
+ /**
+ * Gets all of the data from this Models *loaded* associations.
+ * It does this recursively - for example if we have a User which
+ * hasMany Orders, and each Order hasMany OrderItems, it will return an object like this:
+ * {
+ * orders: [
+ * {
+ * id: 123,
+ * status: 'shipped',
+ * orderItems: [
+ * ...
+ * ]
+ * }
+ * ]
+ * }
+ * @return {Object} The nested data set for the Model's loaded associations
+ */
+ getAssociatedData: function(){
+ return this.prepareAssociatedData(this, [], null);
+ },
+
+ /**
+ * @private
+ * This complex-looking method takes a given Model instance and returns an object containing all data from
+ * all of that Model's *loaded* associations. See (@link #getAssociatedData}
+ * @param {Ext.data.Model} record The Model instance
+ * @param {Array} ids PRIVATE. The set of Model instance internalIds that have already been loaded
+ * @param {String} associationType (optional) The name of the type of association to limit to.
+ * @return {Object} The nested data set for the Model's loaded associations
+ */
+ prepareAssociatedData: function(record, ids, associationType) {
+ //we keep track of all of the internalIds of the models that we have loaded so far in here
+ var associations = record.associations.items,
+ associationCount = associations.length,
+ associationData = {},
+ associatedStore, associatedName, associatedRecords, associatedRecord,
+ associatedRecordCount, association, id, i, j, type, allow;
+
+ for (i = 0; i < associationCount; i++) {
+ association = associations[i];
+ type = association.type;
+ allow = true;
+ if (associationType) {
+ allow = type == associationType;
+ }
+ if (allow && type == 'hasMany') {
+
+ //this is the hasMany store filled with the associated data
+ associatedStore = record[association.storeName];
+
+ //we will use this to contain each associated record's data
+ associationData[association.name] = [];
+
+ //if it's loaded, put it into the association data
+ if (associatedStore && associatedStore.data.length > 0) {
+ associatedRecords = associatedStore.data.items;
+ associatedRecordCount = associatedRecords.length;
+
+ //now we're finally iterating over the records in the association. We do this recursively
+ for (j = 0; j < associatedRecordCount; j++) {
+ associatedRecord = associatedRecords[j];
+ // Use the id, since it is prefixed with the model name, guaranteed to be unique
+ id = associatedRecord.id;
+
+ //when we load the associations for a specific model instance we add it to the set of loaded ids so that
+ //we don't load it twice. If we don't do this, we can fall into endless recursive loading failures.
+ if (Ext.Array.indexOf(ids, id) == -1) {
+ ids.push(id);
+
+ associationData[association.name][j] = associatedRecord.data;
+ Ext.apply(associationData[association.name][j], this.prepareAssociatedData(associatedRecord, ids, type));
+ }
+ }
+ }
+ } else if (allow && type == 'belongsTo') {
+ associatedRecord = record[association.instanceName];
+ if (associatedRecord !== undefined) {
+ id = associatedRecord.id;
+ if (Ext.Array.indexOf(ids, id) == -1) {
+ ids.push(id);
+ associationData[association.name] = associatedRecord.data;
+ Ext.apply(associationData[association.name], this.prepareAssociatedData(associatedRecord, ids, type));
+ }
+ }
+ }
+ }
+
+ return associationData;
+ }
+});
+
+/**
+ * @class Ext.Component
+ * @extends Ext.AbstractComponent
+ * <p>Base class for all Ext components. All subclasses of Component may participate in the automated
+ * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.container.Container Container} class.
+ * Components may be added to a Container through the {@link Ext.container.Container#items items} config option at the time the Container is created,
+ * or they may be added dynamically via the {@link Ext.container.Container#add add} method.</p>
+ * <p>The Component base class has built-in support for basic hide/show and enable/disable and size control behavior.</p>
+ * <p>All Components are registered with the {@link Ext.ComponentManager} on construction so that they can be referenced at any time via
+ * {@link Ext#getCmp Ext.getCmp}, passing the {@link #id}.</p>
+ * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component.</p>
+ * <p>See the <a href="http://sencha.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
+ * and to either extend or augment ExtJs base classes to create custom Components.</p>
+ * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
+ * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
+ * <pre>
+xtype Class
+------------- ------------------
+button {@link Ext.button.Button}
+buttongroup {@link Ext.container.ButtonGroup}
+colorpalette {@link Ext.picker.Color}
+component {@link Ext.Component}
+container {@link Ext.container.Container}
+cycle {@link Ext.button.Cycle}
+dataview {@link Ext.view.View}
+datepicker {@link Ext.picker.Date}
+editor {@link Ext.Editor}
+editorgrid {@link Ext.grid.plugin.Editing}
+grid {@link Ext.grid.Panel}
+multislider {@link Ext.slider.Multi}
+panel {@link Ext.panel.Panel}
+progress {@link Ext.ProgressBar}
+slider {@link Ext.slider.Single}
+spacer {@link Ext.toolbar.Spacer}
+splitbutton {@link Ext.button.Split}
+tabpanel {@link Ext.tab.Panel}
+treepanel {@link Ext.tree.Panel}
+viewport {@link Ext.container.Viewport}
+window {@link Ext.window.Window}
+
+Toolbar components
+---------------------------------------
+paging {@link Ext.toolbar.Paging}
+toolbar {@link Ext.toolbar.Toolbar}
+tbfill {@link Ext.toolbar.Fill}
+tbitem {@link Ext.toolbar.Item}
+tbseparator {@link Ext.toolbar.Separator}
+tbspacer {@link Ext.toolbar.Spacer}
+tbtext {@link Ext.toolbar.TextItem}
+
+Menu components
+---------------------------------------
+menu {@link Ext.menu.Menu}
+menucheckitem {@link Ext.menu.CheckItem}
+menuitem {@link Ext.menu.Item}
+menuseparator {@link Ext.menu.Separator}
+menutextitem {@link Ext.menu.Item}
+
+Form components
+---------------------------------------
+form {@link Ext.form.Panel}
+checkbox {@link Ext.form.field.Checkbox}
+combo {@link Ext.form.field.ComboBox}
+datefield {@link Ext.form.field.Date}
+displayfield {@link Ext.form.field.Display}
+field {@link Ext.form.field.Base}
+fieldset {@link Ext.form.FieldSet}
+hidden {@link Ext.form.field.Hidden}
+htmleditor {@link Ext.form.field.HtmlEditor}
+label {@link Ext.form.Label}
+numberfield {@link Ext.form.field.Number}
+radio {@link Ext.form.field.Radio}
+radiogroup {@link Ext.form.RadioGroup}
+textarea {@link Ext.form.field.TextArea}
+textfield {@link Ext.form.field.Text}
+timefield {@link Ext.form.field.Time}
+trigger {@link Ext.form.field.Trigger}
+
+Chart components
+---------------------------------------
+chart {@link Ext.chart.Chart}
+barchart {@link Ext.chart.series.Bar}
+columnchart {@link Ext.chart.series.Column}
+linechart {@link Ext.chart.series.Line}
+piechart {@link Ext.chart.series.Pie}
+
+</pre><p>
+ * It should not usually be necessary to instantiate a Component because there are provided subclasses which implement specialized Component
+ * use cases which over most application needs. However it is possible to instantiate a base Component, and it will be renderable,
+ * or will particpate in layouts as the child item of a Container:
+{@img Ext.Component/Ext.Component.png Ext.Component component}
+<pre><code>
+ Ext.create('Ext.Component', {
+ html: 'Hello world!',
+ width: 300,
+ height: 200,
+ padding: 20,
+ style: {
+ color: '#FFFFFF',
+ backgroundColor:'#000000'
+ },
+ renderTo: Ext.getBody()
+ });
+</code></pre>
+ *</p>
+ *<p>The Component above creates its encapsulating <code>div</code> upon render, and use the configured HTML as content. More complex
+ * internal structure may be created using the {@link #renderTpl} configuration, although to display database-derived mass
+ * data, it is recommended that an ExtJS data-backed Component such as a {Ext.view.DataView DataView}, or {Ext.grid.Panel GridPanel},
+ * or {@link Ext.tree.Panel TreePanel} be used.</p>
+ * @constructor
+ * @param {Ext.core.Element/String/Object} config The configuration options may be specified as either:
+ * <div class="mdetail-params"><ul>
+ * <li><b>an element</b> :
+ * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
+ * <li><b>a string</b> :
+ * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
+ * <li><b>anything else</b> :
+ * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
+ * </ul></div>
+ */
+
+Ext.define('Ext.Component', {
+
+ /* Begin Definitions */
+
+ alias: ['widget.component', 'widget.box'],
+
+ extend: 'Ext.AbstractComponent',
+
+ requires: [
+ 'Ext.util.DelayedTask'
+ ],
+
+ uses: [
+ 'Ext.Layer',
+ 'Ext.resizer.Resizer',
+ 'Ext.util.ComponentDragger'
+ ],
+
+ mixins: {
+ floating: 'Ext.util.Floating'
+ },
+
+ statics: {
+ // Collapse/expand directions
+ DIRECTION_TOP: 'top',
+ DIRECTION_RIGHT: 'right',
+ DIRECTION_BOTTOM: 'bottom',
+ DIRECTION_LEFT: 'left'
+ },
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Mixed} resizable
+ * <p>Specify as <code>true</code> to apply a {@link Ext.resizer.Resizer Resizer} to this Component
+ * after rendering.</p>
+ * <p>May also be specified as a config object to be passed to the constructor of {@link Ext.resizer.Resizer Resizer}
+ * to override any defaults. By default the Component passes its minimum and maximum size, and uses
+ * <code>{@link Ext.resizer.Resizer#dynamic}: false</code></p>
+ */
+
+ /**
+ * @cfg {String} resizeHandles
+ * A valid {@link Ext.resizer.Resizer} handles config string (defaults to 'all'). Only applies when resizable = true.
+ */
+ resizeHandles: 'all',
+
+ /**
+ * @cfg {Boolean} autoScroll
+ * <code>true</code> to use overflow:'auto' on the components layout element and show scroll bars automatically when
+ * necessary, <code>false</code> to clip any overflowing content (defaults to <code>false</code>).
+ */
+
+ /**
+ * @cfg {Boolean} floating
+ * <p>Specify as true to float the Component outside of the document flow using CSS absolute positioning.</p>
+ * <p>Components such as {@link Ext.window.Window Window}s and {@link Ext.menu.Menu Menu}s are floating
+ * by default.</p>
+ * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will register themselves with the global
+ * {@link Ext.WindowManager ZIndexManager}</p>
+ * <h3 class="pa">Floating Components as child items of a Container</h3>
+ * <p>A floating Component may be used as a child item of a Container. This just allows the floating Component to seek a ZIndexManager by
+ * examining the ownerCt chain.</p>
+ * <p>When configured as floating, Components acquire, at render time, a {@link Ext.ZIndexManager ZIndexManager} which manages a stack
+ * of related floating Components. The ZIndexManager brings a single floating Component to the top of its stack when
+ * the Component's {@link #toFront} method is called.</p>
+ * <p>The ZIndexManager is found by traversing up the {@link #ownerCt} chain to find an ancestor which itself is floating. This is so that
+ * descendant floating Components of floating <i>Containers</i> (Such as a ComboBox dropdown within a Window) can have its zIndex managed relative
+ * to any siblings, but always <b>above</b> that floating ancestor Container.</p>
+ * <p>If no floating ancestor is found, a floating Component registers itself with the default {@link Ext.WindowManager ZIndexManager}.</p>
+ * <p>Floating components <i>do not participate in the Container's layout</i>. Because of this, they are not rendered until you explicitly
+ * {@link #show} them.</p>
+ * <p>After rendering, the ownerCt reference is deleted, and the {@link #floatParent} property is set to the found floating ancestor Container.
+ * If no floating ancestor Container was found the {@link #floatParent} property will not be set.</p>
+ */
+ floating: false,
+
+ /**
+ * @cfg {Boolean} toFrontOnShow
+ * <p>True to automatically call {@link #toFront} when the {@link #show} method is called
+ * on an already visible, floating component (default is <code>true</code>).</p>
+ */
+ toFrontOnShow: true,
+
+ /**
+ * <p>Optional. Only present for {@link #floating} Components after they have been rendered.</p>
+ * <p>A reference to the ZIndexManager which is managing this Component's z-index.</p>
+ * <p>The {@link Ext.ZIndexManager ZIndexManager} maintains a stack of floating Component z-indices, and also provides a single modal
+ * mask which is insert just beneath the topmost visible modal floating Component.</p>
+ * <p>Floating Components may be {@link #toFront brought to the front} or {@link #toBack sent to the back} of the z-index stack.</p>
+ * <p>This defaults to the global {@link Ext.WindowManager ZIndexManager} for floating Components that are programatically
+ * {@link Ext.Component#render rendered}.</p>
+ * <p>For {@link #floating} Components which are added to a Container, the ZIndexManager is acquired from the first ancestor Container found
+ * which is floating, or if not found the global {@link Ext.WindowManager ZIndexManager} is used.</p>
+ * <p>See {@link #floating} and {@link #floatParent}</p>
+ * @property zIndexManager
+ * @type Ext.ZIndexManager
+ */
+
+ /**
+ * <p>Optional. Only present for {@link #floating} Components which were inserted as descendant items of floating Containers.</p>
+ * <p>Floating Components that are programatically {@link Ext.Component#render rendered} will not have a <code>floatParent</code> property.</p>
+ * <p>For {@link #floating} Components which are child items of a Container, the floatParent will be the floating ancestor Container which is
+ * responsible for the base z-index value of all its floating descendants. It provides a {@link Ext.ZIndexManager ZIndexManager} which provides
+ * z-indexing services for all its descendant floating Components.</p>
+ * <p>For example, the dropdown {@link Ext.view.BoundList BoundList} of a ComboBox which is in a Window will have the Window as its
+ * <code>floatParent</code></p>
+ * <p>See {@link #floating} and {@link #zIndexManager}</p>
+ * @property floatParent
+ * @type Ext.Container
+ */
+
+ /**
+ * @cfg {Mixed} draggable
+ * <p>Specify as true to make a {@link #floating} Component draggable using the Component's encapsulating element as the drag handle.</p>
+ * <p>This may also be specified as a config object for the {@link Ext.util.ComponentDragger ComponentDragger} which is instantiated to perform dragging.</p>
+ * <p>For example to create a Component which may only be dragged around using a certain internal element as the drag handle,
+ * use the delegate option:</p>
+ * <code><pre>
+new Ext.Component({
+ constrain: true,
+ floating:true,
+ style: {
+ backgroundColor: '#fff',
+ border: '1px solid black'
+ },
+ html: '<h1 style="cursor:move">The title</h1><p>The content</p>',
+ draggable: {
+ delegate: 'h1'
+ }
+}).show();
+</pre></code>
+ */
+
+ /**
+ * @cfg {Boolean} maintainFlex
+ * <p><b>Only valid when a sibling element of a {@link Ext.resizer.Splitter Splitter} within a {@link Ext.layout.container.VBox VBox} or
+ * {@link Ext.layout.container.HBox HBox} layout.</b></p>
+ * <p>Specifies that if an immediate sibling Splitter is moved, the Component on the <i>other</i> side is resized, and this
+ * Component maintains its configured {@link Ext.layout.container.Box#flex flex} value.</p>
+ */
+
+ hideMode: 'display',
+ // Deprecate 5.0
+ hideParent: false,
+
+ ariaRole: 'presentation',
+
+ bubbleEvents: [],
+
+ actionMode: 'el',
+ monPropRe: /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
+
+ //renderTpl: new Ext.XTemplate(
+ // '<div id="{id}" class="{baseCls} {cls} {cmpCls}<tpl if="typeof ui !== \'undefined\'"> {uiBase}-{ui}</tpl>"<tpl if="typeof style !== \'undefined\'"> style="{style}"</tpl>></div>', {
+ // compiled: true,
+ // disableFormats: true
+ // }
+ //),
+ constructor: function(config) {
+ config = config || {};
+ if (config.initialConfig) {
+
+ // Being initialized from an Ext.Action instance...
+ if (config.isAction) {
+ this.baseAction = config;
+ }
+ config = config.initialConfig;
+ // component cloning / action set up
+ }
+ else if (config.tagName || config.dom || Ext.isString(config)) {
+ // element object
+ config = {
+ applyTo: config,
+ id: config.id || config
+ };
+ }
+
+ this.callParent([config]);
+
+ // If we were configured from an instance of Ext.Action, (or configured with a baseAction option),
+ // register this Component as one of its items
+ if (this.baseAction){
+ this.baseAction.addComponent(this);
+ }
+ },
+
+ initComponent: function() {
+ var me = this;
+
+ if (me.listeners) {
+ me.on(me.listeners);
+ delete me.listeners;
+ }
+ me.enableBubble(me.bubbleEvents);
+ me.mons = [];
+ },
+
+ // private
+ afterRender: function() {
+ var me = this,
+ resizable = me.resizable;
+
+ if (me.floating) {
+ me.makeFloating(me.floating);
+ } else {
+ me.el.setVisibilityMode(Ext.core.Element[me.hideMode.toUpperCase()]);
+ }
+
+ me.setAutoScroll(me.autoScroll);
+ me.callParent();
+
+ if (!(me.x && me.y) && (me.pageX || me.pageY)) {
+ me.setPagePosition(me.pageX, me.pageY);
+ }
+
+ if (resizable) {
+ me.initResizable(resizable);
+ }
+
+ if (me.draggable) {
+ me.initDraggable();
+ }
+
+ me.initAria();
+ },
+
+ initAria: function() {
+ var actionEl = this.getActionEl(),
+ role = this.ariaRole;
+ if (role) {
+ actionEl.dom.setAttribute('role', role);
+ }
+ },
+
+ /**
+ * Sets the overflow on the content element of the component.
+ * @param {Boolean} scroll True to allow the Component to auto scroll.
+ * @return {Ext.Component} this
+ */
+ setAutoScroll : function(scroll){
+ var me = this,
+ targetEl;
+ scroll = !!scroll;
+ if (me.rendered) {
+ targetEl = me.getTargetEl();
+ targetEl.setStyle('overflow', scroll ? 'auto' : '');
+ if (scroll && (Ext.isIE6 || Ext.isIE7)) {
+ // The scrollable container element must be non-statically positioned or IE6/7 will make
+ // positioned children stay in place rather than scrolling with the rest of the content
+ targetEl.position();
+ }
+ }
+ me.autoScroll = scroll;
+ return me;
+ },
+
+ // private
+ makeFloating : function(cfg){
+ this.mixins.floating.constructor.call(this, cfg);
+ },
+
+ initResizable: function(resizable) {
+ resizable = Ext.apply({
+ target: this,
+ dynamic: false,
+ constrainTo: this.constrainTo,
+ handles: this.resizeHandles
+ }, resizable);
+ resizable.target = this;
+ this.resizer = Ext.create('Ext.resizer.Resizer', resizable);
+ },
+
+ getDragEl: function() {
+ return this.el;
+ },
+
+ initDraggable: function() {
+ var me = this,
+ ddConfig = Ext.applyIf({
+ el: this.getDragEl(),
+ constrainTo: me.constrainTo || (me.floatParent ? me.floatParent.getTargetEl() : me.el.dom.parentNode)
+ }, this.draggable);
+
+ // Add extra configs if Component is specified to be constrained
+ if (me.constrain || me.constrainDelegate) {
+ ddConfig.constrain = me.constrain;
+ ddConfig.constrainDelegate = me.constrainDelegate;
+ }
+
+ this.dd = Ext.create('Ext.util.ComponentDragger', this, ddConfig);
+ },
+
+ /**
+ * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}.
+ * This method fires the {@link #move} event.
+ * @param {Number} left The new left
+ * @param {Number} top The new top
+ * @param {Mixed} animate If true, the Component is <i>animated</i> into its new position. You may also pass an animation configuration.
+ * @return {Ext.Component} this
+ */
+ setPosition: function(x, y, animate) {
+ var me = this,
+ el = me.el,
+ to = {},
+ adj, adjX, adjY, xIsNumber, yIsNumber;
+
+ if (Ext.isArray(x)) {
+ animate = y;
+ y = x[1];
+ x = x[0];
+ }
+ me.x = x;
+ me.y = y;
+
+ if (!me.rendered) {
+ return me;
+ }
+
+ adj = me.adjustPosition(x, y);
+ adjX = adj.x;
+ adjY = adj.y;
+ xIsNumber = Ext.isNumber(adjX);
+ yIsNumber = Ext.isNumber(adjY);
+
+ if (xIsNumber || yIsNumber) {
+ if (animate) {
+ if (xIsNumber) {
+ to.left = adjX;
+ }
+ if (yIsNumber) {
+ to.top = adjY;
+ }
+
+ me.stopAnimation();
+ me.animate(Ext.apply({
+ duration: 1000,
+ listeners: {
+ afteranimate: Ext.Function.bind(me.afterSetPosition, me, [adjX, adjY])
+ },
+ to: to
+ }, animate));
+ }
+ else {
+ if (!xIsNumber) {
+ el.setTop(adjY);
+ }
+ else if (!yIsNumber) {
+ el.setLeft(adjX);
+ }
+ else {
+ el.setLeftTop(adjX, adjY);
+ }
+ me.afterSetPosition(adjX, adjY);
+ }
+ }
+ return me;
+ },
+
+ /**
+ * @private Template method called after a Component has been positioned.
+ */
+ afterSetPosition: function(ax, ay) {
+ this.onPosition(ax, ay);
+ this.fireEvent('move', this, ax, ay);
+ },
+
+ showAt: function(x, y, animate) {
+ // A floating Component is positioned relative to its ownerCt if any.
+ if (this.floating) {
+ this.setPosition(x, y, animate);
+ } else {
+ this.setPagePosition(x, y, animate);
+ }
+ this.show();
+ },
+
+ /**
+ * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
+ * This method fires the {@link #move} event.
+ * @param {Number} x The new x position
+ * @param {Number} y The new y position
+ * @param {Mixed} animate If passed, the Component is <i>animated</i> into its new position. If this parameter
+ * is a number, it is used as the animation duration in milliseconds.
+ * @return {Ext.Component} this
+ */
+ setPagePosition: function(x, y, animate) {
+ var me = this,
+ p;
+
+ if (Ext.isArray(x)) {
+ y = x[1];
+ x = x[0];
+ }
+ me.pageX = x;
+ me.pageY = y;
+ if (me.floating && me.floatParent) {
+ // Floating Components being positioned in their ownerCt have to be made absolute
+ p = me.floatParent.getTargetEl().getViewRegion();
+ if (Ext.isNumber(x) && Ext.isNumber(p.left)) {
+ x -= p.left;
+ }
+ if (Ext.isNumber(y) && Ext.isNumber(p.top)) {
+ y -= p.top;
+ }
+ me.setPosition(x, y, animate);
+ }
+ else {
+ p = me.el.translatePoints(x, y);
+ me.setPosition(p.left, p.top, animate);
+ }
+ return me;
+ },
+
+ /**
+ * Gets the current box measurements of the component's underlying element.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
+ * @return {Object} box An object in the format {x, y, width, height}
+ */
+ getBox : function(local){
+ var pos = this.getPosition(local);
+ var s = this.getSize();
+ s.x = pos[0];
+ s.y = pos[1];
+ return s;
+ },
+
+ /**
+ * Sets the current box measurements of the component's underlying element.
+ * @param {Object} box An object in the format {x, y, width, height}
+ * @return {Ext.Component} this
+ */
+ updateBox : function(box){
+ this.setSize(box.width, box.height);
+ this.setPagePosition(box.x, box.y);
+ return this;
+ },
+
+ // Include margins
+ getOuterSize: function() {
+ var el = this.el;
+ return {
+ width: el.getWidth() + el.getMargin('lr'),
+ height: el.getHeight() + el.getMargin('tb')
+ };
+ },
+
+ // private
+ adjustSize: function(w, h) {
+ if (this.autoWidth) {
+ w = 'auto';
+ }
+
+ if (this.autoHeight) {
+ h = 'auto';
+ }
+
+ return {
+ width: w,
+ height: h
+ };
+ },
+
+ // private
+ adjustPosition: function(x, y) {
+
+ // Floating Components being positioned in their ownerCt have to be made absolute
+ if (this.floating && this.floatParent) {
+ var o = this.floatParent.getTargetEl().getViewRegion();
+ x += o.left;
+ y += o.top;
+ }
+
+ return {
+ x: x,
+ y: y
+ };
+ },
+
+ /**
+ * Gets the current XY position of the component's underlying element.
+ * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
+ * @return {Array} The XY position of the element (e.g., [100, 200])
+ */
+ getPosition: function(local) {
+ var el = this.el,
+ xy;
+
+ if (local === true) {
+ return [el.getLeft(true), el.getTop(true)];
+ }
+ xy = this.xy || el.getXY();
+
+ // Floating Components in an ownerCt have to have their positions made relative
+ if (this.floating && this.floatParent) {
+ var o = this.floatParent.getTargetEl().getViewRegion();
+ xy[0] -= o.left;
+ xy[1] -= o.top;
+ }
+ return xy;
+ },
+
+ // Todo: add in xtype prefix support
+ getId: function() {
+ return this.id || (this.id = (this.getXType() || 'ext-comp') + '-' + this.getAutoId());
+ },
+
+ onEnable: function() {
+ var actionEl = this.getActionEl();
+ actionEl.dom.removeAttribute('aria-disabled');
+ actionEl.dom.disabled = false;
+ this.callParent();
+ },
+
+ onDisable: function() {
+ var actionEl = this.getActionEl();
+ actionEl.dom.setAttribute('aria-disabled', true);
+ actionEl.dom.disabled = true;
+ this.callParent();
+ },
+
+ /**
+ * <p>Shows this Component, rendering it first if {@link #autoRender} or {{@link "floating} are <code>true</code>.</p>
+ * <p>After being shown, a {@link #floating} Component (such as a {@link Ext.window.Window}), is activated it and brought to the front of
+ * its {@link #ZIndexManager z-index stack}.</p>
+ * @param {String/Element} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
+ * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
+ * with <code>floating: true</code>.</b> The target from which the Component should
+ * animate from while opening (defaults to null with no animation)
+ * @param {Function} callback (optional) A callback function to call after the Component is displayed. Only necessary if animation was specified.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
+ * @return {Component} this
+ */
+ show: function(animateTarget, cb, scope) {
+ if (this.rendered && this.isVisible()) {
+ if (this.toFrontOnShow && this.floating) {
+ this.toFront();
+ }
+ } else if (this.fireEvent('beforeshow', this) !== false) {
+ this.hidden = false;
+
+ // Render on first show if there is an autoRender config, or if this is a floater (Window, Menu, BoundList etc).
+ if (!this.rendered && (this.autoRender || this.floating)) {
+ this.doAutoRender();
+ }
+ if (this.rendered) {
+ this.beforeShow();
+ this.onShow.apply(this, arguments);
+
+ // Notify any owning Container unless it's suspended.
+ // Floating Components do not participate in layouts.
+ if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
+ this.ownerCt.doLayout();
+ }
+ this.afterShow.apply(this, arguments);
+ }
+ }
+ return this;
+ },
+
+ beforeShow: Ext.emptyFn,
+
+ // Private. Override in subclasses where more complex behaviour is needed.
+ onShow: function() {
+ var me = this;
+
+ me.el.show();
+ if (this.floating && this.constrain) {
+ this.doConstrain();
+ }
+ me.callParent(arguments);
+ },
+
+ afterShow: function(animateTarget, cb, scope) {
+ var me = this,
+ fromBox,
+ toBox,
+ ghostPanel;
+
+ // Default to configured animate target if none passed
+ animateTarget = animateTarget || me.animateTarget;
+
+ // Need to be able to ghost the Component
+ if (!me.ghost) {
+ animateTarget = null;
+ }
+ // If we're animating, kick of an animation of the ghost from the target to the *Element* current box
+ if (animateTarget) {
+ animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
+ toBox = me.el.getBox();
+ fromBox = animateTarget.getBox();
+ fromBox.width += 'px';
+ fromBox.height += 'px';
+ toBox.width += 'px';
+ toBox.height += 'px';
+ me.el.addCls(Ext.baseCSSPrefix + 'hide-offsets');
+ ghostPanel = me.ghost();
+ ghostPanel.el.stopAnimation();
+
+ ghostPanel.el.animate({
+ from: fromBox,
+ to: toBox,
+ listeners: {
+ afteranimate: function() {
+ delete ghostPanel.componentLayout.lastComponentSize;
+ me.unghost();
+ me.el.removeCls(Ext.baseCSSPrefix + 'hide-offsets');
+ if (me.floating) {
+ me.toFront();
+ }
+ Ext.callback(cb, scope || me);
+ }
+ }
+ });
+ }
+ else {
+ if (me.floating) {
+ me.toFront();
+ }
+ Ext.callback(cb, scope || me);
+ }
+ me.fireEvent('show', me);
+ },
+
+ /**
+ * Hides this Component, setting it to invisible using the configured {@link #hideMode}.
+ * @param {String/Element/Component} animateTarget Optional, and <b>only valid for {@link #floating} Components such as
+ * {@link Ext.window.Window Window}s or {@link Ext.tip.ToolTip ToolTip}s, or regular Components which have been configured
+ * with <code>floating: true</code>.</b>.
+ * The target to which the Component should animate while hiding (defaults to null with no animation)
+ * @param {Function} callback (optional) A callback function to call after the Component is hidden.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Component.
+ * @return {Ext.Component} this
+ */
+ hide: function() {
+
+ // Clear the flag which is set if a floatParent was hidden while this is visible.
+ // If a hide operation was subsequently called, that pending show must be hidden.
+ this.showOnParentShow = false;
+
+ if (!(this.rendered && !this.isVisible()) && this.fireEvent('beforehide', this) !== false) {
+ this.hidden = true;
+ if (this.rendered) {
+ this.onHide.apply(this, arguments);
+
+ // Notify any owning Container unless it's suspended.
+ // Floating Components do not participate in layouts.
+ if (this.ownerCt && !this.floating && !(this.ownerCt.suspendLayout || this.ownerCt.layout.layoutBusy)) {
+ this.ownerCt.doLayout();
+ }
+ }
+ }
+ return this;
+ },
+
+ // Possibly animate down to a target element.
+ onHide: function(animateTarget, cb, scope) {
+ var me = this,
+ ghostPanel,
+ toBox;
+
+ // Default to configured animate target if none passed
+ animateTarget = animateTarget || me.animateTarget;
+
+ // Need to be able to ghost the Component
+ if (!me.ghost) {
+ animateTarget = null;
+ }
+ // If we're animating, kick off an animation of the ghost down to the target
+ if (animateTarget) {
+ animateTarget = animateTarget.el ? animateTarget.el : Ext.get(animateTarget);
+ ghostPanel = me.ghost();
+ ghostPanel.el.stopAnimation();
+ toBox = animateTarget.getBox();
+ toBox.width += 'px';
+ toBox.height += 'px';
+ ghostPanel.el.animate({
+ to: toBox,
+ listeners: {
+ afteranimate: function() {
+ delete ghostPanel.componentLayout.lastComponentSize;
+ ghostPanel.el.hide();
+ me.afterHide(cb, scope);
+ }
+ }
+ });
+ }
+ me.el.hide();
+ if (!animateTarget) {
+ me.afterHide(cb, scope);
+ }
+ },
+
+ afterHide: function(cb, scope) {
+ Ext.callback(cb, scope || this);
+ this.fireEvent('hide', this);
+ },
+
+ /**
+ * @private
+ * Template method to contribute functionality at destroy time.
+ */
+ onDestroy: function() {
+ var me = this;
+
+ // Ensure that any ancillary components are destroyed.
+ if (me.rendered) {
+ Ext.destroy(
+ me.proxy,
+ me.resizer
+ );
+ // Different from AbstractComponent
+ if (me.actionMode == 'container' || me.removeMode == 'container') {
+ me.container.remove();
+ }
+ }
+ me.callParent();
+ },
+
+ deleteMembers: function() {
+ var args = arguments,
+ len = args.length,
+ i = 0;
+ for (; i < len; ++i) {
+ delete this[args[i]];
+ }
+ },
+
+ /**
+ * Try to focus this component.
+ * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
+ * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds).
+ * @return {Ext.Component} this
+ */
+ focus: function(selectText, delay) {
+ var me = this,
+ focusEl;
+
+ if (delay) {
+ me.focusTask.delay(Ext.isNumber(delay) ? delay: 10, null, me, [selectText, false]);
+ return me;
+ }
+
+ if (me.rendered && !me.isDestroyed) {
+ // getFocusEl could return a Component.
+ focusEl = me.getFocusEl();
+ focusEl.focus();
+ if (focusEl.dom && selectText === true) {
+ focusEl.dom.select();
+ }
+
+ // Focusing a floating Component brings it to the front of its stack.
+ // this is performed by its zIndexManager. Pass preventFocus true to avoid recursion.
+ if (me.floating) {
+ me.toFront(true);
+ }
+ }
+ return me;
+ },
+
+ /**
+ * @private
+ * Returns the focus holder element associated with this Component. By default, this is the Component's encapsulating
+ * element. Subclasses which use embedded focusable elements (such as Window and Button) should override this for use
+ * by the {@link #focus} method.
+ * @returns {Ext.core.Element} the focus holing element.
+ */
+ getFocusEl: function() {
+ return this.el;
+ },
+
+ // private
+ blur: function() {
+ if (this.rendered) {
+ this.getFocusEl().blur();
+ }
+ return this;
+ },
+
+ getEl: function() {
+ return this.el;
+ },
+
+ // Deprecate 5.0
+ getResizeEl: function() {
+ return this.el;
+ },
+
+ // Deprecate 5.0
+ getPositionEl: function() {
+ return this.el;
+ },
+
+ // Deprecate 5.0
+ getActionEl: function() {
+ return this.el;
+ },
+
+ // Deprecate 5.0
+ getVisibilityEl: function() {
+ return this.el;
+ },
+
+ // Deprecate 5.0
+ onResize: Ext.emptyFn,
+
+ // private
+ getBubbleTarget: function() {
+ return this.ownerCt;
+ },
+
+ // private
+ getContentTarget: function() {
+ return this.el;
+ },
+
+ /**
+ * Clone the current component using the original config values passed into this instance by default.
+ * @param {Object} overrides A new config containing any properties to override in the cloned version.
+ * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
+ * @return {Ext.Component} clone The cloned copy of this component
+ */
+ cloneConfig: function(overrides) {
+ overrides = overrides || {};
+ var id = overrides.id || Ext.id();
+ var cfg = Ext.applyIf(overrides, this.initialConfig);
+ cfg.id = id;
+
+ var self = Ext.getClass(this);
+
+ // prevent dup id
+ return new self(cfg);
+ },
+
+ /**
+ * Gets the xtype for this component as registered with {@link Ext.ComponentManager}. For a list of all
+ * available xtypes, see the {@link Ext.Component} header. Example usage:
+ * <pre><code>
+var t = new Ext.form.field.Text();
+alert(t.getXType()); // alerts 'textfield'
+</code></pre>
+ * @return {String} The xtype
+ */
+ getXType: function() {
+ return this.self.xtype;
+ },
+
+ /**
+ * Find a container above this component at any level by a custom function. If the passed function returns
+ * true, the container will be returned.
+ * @param {Function} fn The custom function to call with the arguments (container, this component).
+ * @return {Ext.container.Container} The first Container for which the custom function returns true
+ */
+ findParentBy: function(fn) {
+ var p;
+
+ // Iterate up the ownerCt chain until there's no ownerCt, or we find an ancestor which matches using the selector function.
+ for (p = this.ownerCt; p && !fn(p, this); p = p.ownerCt);
+ return p || null;
+ },
+
+ /**
+ * <p>Find a container above this component at any level by xtype or class</p>
+ * <p>See also the {@link Ext.Component#up up} method.</p>
+ * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
+ * @return {Ext.container.Container} The first Container which matches the given xtype or class
+ */
+ findParentByType: function(xtype) {
+ return Ext.isFunction(xtype) ?
+ this.findParentBy(function(p) {
+ return p.constructor === xtype;
+ })
+ :
+ this.up(xtype);
+ },
+
+ /**
+ * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
+ * function call will be the scope provided or the current component. The arguments to the function
+ * will be the args provided or the current component. If the function returns false at any point,
+ * the bubble is stopped.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function (defaults to current node)
+ * @param {Array} args (optional) The args to call the function with (default to passing the current component)
+ * @return {Ext.Component} this
+ */
+ bubble: function(fn, scope, args) {
+ var p = this;
+ while (p) {
+ if (fn.apply(scope || p, args || [p]) === false) {
+ break;
+ }
+ p = p.ownerCt;
+ }
+ return this;
+ },
+
+ getProxy: function() {
+ if (!this.proxy) {
+ this.proxy = this.el.createProxy(Ext.baseCSSPrefix + 'proxy-el', Ext.getBody(), true);
+ }
+ return this.proxy;
+ }
+
+}, function() {
+
+ // A single focus delayer for all Components.
+ this.prototype.focusTask = Ext.create('Ext.util.DelayedTask', this.prototype.focus);
+
+});
+
+/**
+* @class Ext.layout.container.Container
+* @extends Ext.layout.container.AbstractContainer
+* @private
+* <p>This class is intended to be extended or created via the <tt><b>{@link Ext.container.Container#layout layout}</b></tt>
+* configuration property. See <tt><b>{@link Ext.container.Container#layout}</b></tt> for additional details.</p>
+*/
+Ext.define('Ext.layout.container.Container', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.container.AbstractContainer',
+ alternateClassName: 'Ext.layout.ContainerLayout',
+
+ /* End Definitions */
+
+ layoutItem: function(item, box) {
+ box = box || {};
+ if (item.componentLayout.initialized !== true) {
+ this.setItemSize(item, box.width || item.width || undefined, box.height || item.height || undefined);
+ // item.doComponentLayout(box.width || item.width || undefined, box.height || item.height || undefined);
+ }
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.getTarget(),
+ ret;
+
+ if (target) {
+ ret = target.getViewSize();
+
+ // IE in will sometimes return a width of 0 on the 1st pass of getViewSize.
+ // Use getStyleSize to verify the 0 width, the adjustment pass will then work properly
+ // with getViewSize
+ if (Ext.isIE && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ beforeLayout: function() {
+ if (this.owner.beforeLayout(arguments) !== false) {
+ return this.callParent(arguments);
+ }
+ else {
+ return false;
+ }
+ },
+
+ afterLayout: function() {
+ this.owner.afterLayout(arguments);
+ this.callParent(arguments);
+ },
+
+ /**
+ * @protected
+ * Returns all items that are rendered
+ * @return {Array} All matching items
+ */
+ getRenderedItems: function() {
+ var me = this,
+ target = me.getTarget(),
+ items = me.getLayoutItems(),
+ ln = items.length,
+ renderedItems = [],
+ i, item;
+
+ for (i = 0; i < ln; i++) {
+ item = items[i];
+ if (item.rendered && me.isValidParent(item, target, i)) {
+ renderedItems.push(item);
+ }
+ }
+
+ return renderedItems;
+ },
+
+ /**
+ * @protected
+ * Returns all items that are both rendered and visible
+ * @return {Array} All matching items
+ */
+ getVisibleItems: function() {
+ var target = this.getTarget(),
+ items = this.getLayoutItems(),
+ ln = items.length,
+ visibleItems = [],
+ i, item;
+
+ for (i = 0; i < ln; i++) {
+ item = items[i];
+ if (item.rendered && this.isValidParent(item, target, i) && item.hidden !== true) {
+ visibleItems.push(item);
+ }
+ }
+
+ return visibleItems;
+ }
+});
+/**
+ * @class Ext.layout.container.Auto
+ * @extends Ext.layout.container.Container
+ *
+ * <p>The AutoLayout is the default layout manager delegated by {@link Ext.container.Container} to
+ * render any child Components when no <tt>{@link Ext.container.Container#layout layout}</tt> is configured into
+ * a <tt>{@link Ext.container.Container Container}.</tt>. AutoLayout provides only a passthrough of any layout calls
+ * to any child containers.</p>
+ * {@img Ext.layout.container.Auto/Ext.layout.container.Auto.png Ext.layout.container.Auto container layout}
+ * Example usage:
+ Ext.create('Ext.Panel', {
+ width: 500,
+ height: 280,
+ title: "AutoLayout Panel",
+ layout: 'auto',
+ renderTo: document.body,
+ items: [{
+ xtype: 'panel',
+ title: 'Top Inner Panel',
+ width: '75%',
+ height: 90
+ },{
+ xtype: 'panel',
+ title: 'Bottom Inner Panel',
+ width: '75%',
+ height: 90
+ }]
+ });
+ */
+
+Ext.define('Ext.layout.container.Auto', {
+
+ /* Begin Definitions */
+
+ alias: ['layout.auto', 'layout.autocontainer'],
+
+ extend: 'Ext.layout.container.Container',
+
+ /* End Definitions */
+
+ type: 'autocontainer',
+
+ fixedLayout: false,
+
+ bindToOwnerCtComponent: true,
+
+ // @private
+ onLayout : function(owner, target) {
+ var me = this,
+ items = me.getLayoutItems(),
+ ln = items.length,
+ i;
+
+ // Ensure the Container is only primed with the clear element if there are child items.
+ if (ln) {
+ // Auto layout uses natural HTML flow to arrange the child items.
+ // To ensure that all browsers (I'm looking at you IE!) add the bottom margin of the last child to the
+ // containing element height, we create a zero-sized element with style clear:both to force a "new line"
+ if (!me.clearEl) {
+ me.clearEl = me.getRenderTarget().createChild({
+ cls: Ext.baseCSSPrefix + 'clear',
+ role: 'presentation'
+ });
+ }
+
+ // Auto layout allows CSS to size its child items.
+ for (i = 0; i < ln; i++) {
+ me.setItemSize(items[i]);
+ }
+ }
+ }
+});
+/**
+ * @class Ext.container.AbstractContainer
+ * @extends Ext.Component
+ * <p>An abstract base class which provides shared methods for Containers across the Sencha product line.</p>
+ * Please refer to sub class's documentation
+ */
+Ext.define('Ext.container.AbstractContainer', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.Component',
+
+ requires: [
+ 'Ext.util.MixedCollection',
+ 'Ext.layout.container.Auto',
+ 'Ext.ZIndexManager'
+ ],
+
+ /* End Definitions */
+ /**
+ * @cfg {String/Object} layout
+ * <p><b>*Important</b>: In order for child items to be correctly sized and
+ * positioned, typically a layout manager <b>must</b> be specified through
+ * the <code>layout</code> configuration option.</p>
+ * <br><p>The sizing and positioning of child {@link #items} is the responsibility of
+ * the Container's layout manager which creates and manages the type of layout
+ * you have in mind. For example:</p>
+ * <p>If the {@link #layout} configuration is not explicitly specified for
+ * a general purpose container (e.g. Container or Panel) the
+ * {@link Ext.layout.container.Auto default layout manager} will be used
+ * which does nothing but render child components sequentially into the
+ * Container (no sizing or positioning will be performed in this situation).</p>
+ * <br><p><b><code>layout</code></b> may be specified as either as an Object or
+ * as a String:</p><div><ul class="mdetail-params">
+ *
+ * <li><u>Specify as an Object</u></li>
+ * <div><ul class="mdetail-params">
+ * <li>Example usage:</li>
+ * <pre><code>
+layout: {
+ type: 'vbox',
+ align: 'left'
+}
+ </code></pre>
+ *
+ * <li><code><b>type</b></code></li>
+ * <br/><p>The layout type to be used for this container. If not specified,
+ * a default {@link Ext.layout.container.Auto} will be created and used.</p>
+ * <br/><p>Valid layout <code>type</code> values are:</p>
+ * <div class="sub-desc"><ul class="mdetail-params">
+ * <li><code><b>{@link Ext.layout.container.Auto Auto}</b></code> <b>Default</b></li>
+ * <li><code><b>{@link Ext.layout.container.Card card}</b></code></li>
+ * <li><code><b>{@link Ext.layout.container.Fit fit}</b></code></li>
+ * <li><code><b>{@link Ext.layout.container.HBox hbox}</b></code></li>
+ * <li><code><b>{@link Ext.layout.container.VBox vbox}</b></code></li>
+ * <li><code><b>{@link Ext.layout.container.Anchor anchor}</b></code></li>
+ * <li><code><b>{@link Ext.layout.container.Table table}</b></code></li>
+ * </ul></div>
+ *
+ * <li>Layout specific configuration properties</li>
+ * <br/><p>Additional layout specific configuration properties may also be
+ * specified. For complete details regarding the valid config options for
+ * each layout type, see the layout class corresponding to the <code>type</code>
+ * specified.</p>
+ *
+ * </ul></div>
+ *
+ * <li><u>Specify as a String</u></li>
+ * <div><ul class="mdetail-params">
+ * <li>Example usage:</li>
+ * <pre><code>
+layout: {
+ type: 'vbox',
+ padding: '5',
+ align: 'left'
+}
+ </code></pre>
+ * <li><code><b>layout</b></code></li>
+ * <br/><p>The layout <code>type</code> to be used for this container (see list
+ * of valid layout type values above).</p><br/>
+ * <br/><p>Additional layout specific configuration properties. For complete
+ * details regarding the valid config options for each layout type, see the
+ * layout class corresponding to the <code>layout</code> specified.</p>
+ * </ul></div></ul></div>
+ */
+
+ /**
+ * @cfg {String/Number} activeItem
+ * A string component id or the numeric index of the component that should be initially activated within the
+ * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
+ * item in the container's collection). activeItem only applies to layout styles that can display
+ * items one at a time (like {@link Ext.layout.container.Card} and {@link Ext.layout.container.Fit}).
+ */
+ /**
+ * @cfg {Object/Array} items
+ * <p>A single item, or an array of child Components to be added to this container</p>
+ * <p><b>Unless configured with a {@link #layout}, a Container simply renders child Components serially into
+ * its encapsulating element and performs no sizing or positioning upon them.</b><p>
+ * <p>Example:</p>
+ * <pre><code>
+// specifying a single item
+items: {...},
+layout: 'fit', // The single items is sized to fit
+
+// specifying multiple items
+items: [{...}, {...}],
+layout: 'hbox', // The items are arranged horizontally
+ </code></pre>
+ * <p>Each item may be:</p>
+ * <ul>
+ * <li>A {@link Ext.Component Component}</li>
+ * <li>A Component configuration object</li>
+ * </ul>
+ * <p>If a configuration object is specified, the actual type of Component to be
+ * instantiated my be indicated by using the {@link Ext.Component#xtype xtype} option.</p>
+ * <p>Every Component class has its own {@link Ext.Component#xtype xtype}.</p>
+ * <p>If an {@link Ext.Component#xtype xtype} is not explicitly
+ * specified, the {@link #defaultType} for the Container is used, which by default is usually <code>panel</code>.</p>
+ * <p><b>Notes</b>:</p>
+ * <p>Ext uses lazy rendering. Child Components will only be rendered
+ * should it become necessary. Items are automatically laid out when they are first
+ * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</p>
+ * <p>Do not specify <code>{@link Ext.panel.Panel#contentEl contentEl}</code> or
+ * <code>{@link Ext.panel.Panel#html html}</code> with <code>items</code>.</p>
+ */
+ /**
+ * @cfg {Object|Function} defaults
+ * <p>This option is a means of applying default settings to all added items whether added through the {@link #items}
+ * config or via the {@link #add} or {@link #insert} methods.</p>
+ * <p>If an added item is a config object, and <b>not</b> an instantiated Component, then the default properties are
+ * unconditionally applied. If the added item <b>is</b> an instantiated Component, then the default properties are
+ * applied conditionally so as not to override existing properties in the item.</p>
+ * <p>If the defaults option is specified as a function, then the function will be called using this Container as the
+ * scope (<code>this</code> reference) and passing the added item as the first parameter. Any resulting object
+ * from that call is then applied to the item as default properties.</p>
+ * <p>For example, to automatically apply padding to the body of each of a set of
+ * contained {@link Ext.panel.Panel} items, you could pass: <code>defaults: {bodyStyle:'padding:15px'}</code>.</p>
+ * <p>Usage:</p><pre><code>
+defaults: { // defaults are applied to items, not the container
+ autoScroll:true
+},
+items: [
+ {
+ xtype: 'panel', // defaults <b>do not</b> have precedence over
+ id: 'panel1', // options in config objects, so the defaults
+ autoScroll: false // will not be applied here, panel1 will be autoScroll:false
+ },
+ new Ext.panel.Panel({ // defaults <b>do</b> have precedence over options
+ id: 'panel2', // options in components, so the defaults
+ autoScroll: false // will be applied here, panel2 will be autoScroll:true.
+ })
+]</code></pre>
+ */
+
+ /** @cfg {Boolean} suspendLayout
+ * If true, suspend calls to doLayout. Useful when batching multiple adds to a container and not passing them
+ * as multiple arguments or an array.
+ */
+ suspendLayout : false,
+
+ /** @cfg {Boolean} autoDestroy
+ * If true the container will automatically destroy any contained component that is removed from it, else
+ * destruction must be handled manually.
+ * Defaults to true.
+ */
+ autoDestroy : true,
+
+ /** @cfg {String} defaultType
+ * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
+ * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
+ * <p>Defaults to <code>'panel'</code>.</p>
+ */
+ defaultType: 'panel',
+
+ isContainer : true,
+
+ baseCls: Ext.baseCSSPrefix + 'container',
+
+ /**
+ * @cfg {Array} bubbleEvents
+ * <p>An array of events that, when fired, should be bubbled to any parent container.
+ * See {@link Ext.util.Observable#enableBubble}.
+ * Defaults to <code>['add', 'remove']</code>.
+ */
+ bubbleEvents: ['add', 'remove'],
+
+ // @private
+ initComponent : function(){
+ var me = this;
+ me.addEvents(
+ /**
+ * @event afterlayout
+ * Fires when the components in this container are arranged by the associated layout manager.
+ * @param {Ext.container.Container} this
+ * @param {ContainerLayout} layout The ContainerLayout implementation for this container
+ */
+ 'afterlayout',
+ /**
+ * @event beforeadd
+ * Fires before any {@link Ext.Component} is added or inserted into the container.
+ * A handler can return false to cancel the add.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} component The component being added
+ * @param {Number} index The index at which the component will be added to the container's items collection
+ */
+ 'beforeadd',
+ /**
+ * @event beforeremove
+ * Fires before any {@link Ext.Component} is removed from the container. A handler can return
+ * false to cancel the remove.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} component The component being removed
+ */
+ 'beforeremove',
+ /**
+ * @event add
+ * @bubbles
+ * Fires after any {@link Ext.Component} is added or inserted into the container.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} component The component that was added
+ * @param {Number} index The index at which the component was added to the container's items collection
+ */
+ 'add',
+ /**
+ * @event remove
+ * @bubbles
+ * Fires after any {@link Ext.Component} is removed from the container.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} component The component that was removed
+ */
+ 'remove',
+ /**
+ * @event beforecardswitch
+ * Fires before this container switches the active card. This event
+ * is only available if this container uses a CardLayout. Note that
+ * TabPanel and Carousel both get a CardLayout by default, so both
+ * will have this event.
+ * A handler can return false to cancel the card switch.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} newCard The card that will be switched to
+ * @param {Ext.Component} oldCard The card that will be switched from
+ * @param {Number} index The index of the card that will be switched to
+ * @param {Boolean} animated True if this cardswitch will be animated
+ */
+ 'beforecardswitch',
+ /**
+ * @event cardswitch
+ * Fires after this container switches the active card. If the card
+ * is switched using an animation, this event will fire after the
+ * animation has finished. This event is only available if this container
+ * uses a CardLayout. Note that TabPanel and Carousel both get a CardLayout
+ * by default, so both will have this event.
+ * @param {Ext.container.Container} this
+ * @param {Ext.Component} newCard The card that has been switched to
+ * @param {Ext.Component} oldCard The card that has been switched from
+ * @param {Number} index The index of the card that has been switched to
+ * @param {Boolean} animated True if this cardswitch was animated
+ */
+ 'cardswitch'
+ );
+
+ // layoutOnShow stack
+ me.layoutOnShow = Ext.create('Ext.util.MixedCollection');
+ me.callParent();
+ me.initItems();
+ },
+
+ // @private
+ initItems : function() {
+ var me = this,
+ items = me.items;
+
+ /**
+ * The MixedCollection containing all the child items of this container.
+ * @property items
+ * @type Ext.util.MixedCollection
+ */
+ me.items = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
+
+ if (items) {
+ if (!Ext.isArray(items)) {
+ items = [items];
+ }
+
+ me.add(items);
+ }
+ },
+
+ // @private
+ afterRender : function() {
+ this.getLayout();
+ this.callParent();
+ },
+
+ // @private
+ setLayout : function(layout) {
+ var currentLayout = this.layout;
+
+ if (currentLayout && currentLayout.isLayout && currentLayout != layout) {
+ currentLayout.setOwner(null);
+ }
+
+ this.layout = layout;
+ layout.setOwner(this);
+ },
+
+ /**
+ * Returns the {@link Ext.layout.container.AbstractContainer layout} instance currently associated with this Container.
+ * If a layout has not been instantiated yet, that is done first
+ * @return {Ext.layout.container.AbstractContainer} The layout
+ */
+ getLayout : function() {
+ var me = this;
+ if (!me.layout || !me.layout.isLayout) {
+ me.setLayout(Ext.layout.Layout.create(me.layout, 'autocontainer'));
+ }
+
+ return me.layout;
+ },
+
+ /**
+ * Manually force this container's layout to be recalculated. The framwork uses this internally to refresh layouts
+ * form most cases.
+ * @return {Ext.container.Container} this
+ */
+ doLayout : function() {
+ var me = this,
+ layout = me.getLayout();
+
+ if (me.rendered && layout && !me.suspendLayout) {
+ // If either dimension is being auto-set, then it requires a ComponentLayout to be run.
+ if ((!Ext.isNumber(me.width) || !Ext.isNumber(me.height)) && me.componentLayout.type !== 'autocomponent') {
+ // Only run the ComponentLayout if it is not already in progress
+ if (me.componentLayout.layoutBusy !== true) {
+ me.doComponentLayout();
+ if (me.componentLayout.layoutCancelled === true) {
+ layout.layout();
+ }
+ }
+ }
+ // Both dimensions defined, run a ContainerLayout
+ else {
+ // Only run the ContainerLayout if it is not already in progress
+ if (layout.layoutBusy !== true) {
+ layout.layout();
+ }
+ }
+ }
+
+ return me;
+ },
+
+ // @private
+ afterLayout : function(layout) {
+ this.fireEvent('afterlayout', this, layout);
+ },
+
+ // @private
+ prepareItems : function(items, applyDefaults) {
+ if (!Ext.isArray(items)) {
+ items = [items];
+ }
+
+ // Make sure defaults are applied and item is initialized
+ var i = 0,
+ len = items.length,
+ item;
+
+ for (; i < len; i++) {
+ item = items[i];
+ if (applyDefaults) {
+ item = this.applyDefaults(item);
+ }
+ items[i] = this.lookupComponent(item);
+ }
+ return items;
+ },
+
+ // @private
+ applyDefaults : function(config) {
+ var defaults = this.defaults;
+
+ if (defaults) {
+ if (Ext.isFunction(defaults)) {
+ defaults = defaults.call(this, config);
+ }
+
+ if (Ext.isString(config)) {
+ config = Ext.ComponentManager.get(config);
+ Ext.applyIf(config, defaults);
+ } else if (!config.isComponent) {
+ Ext.applyIf(config, defaults);
+ } else {
+ Ext.applyIf(config, defaults);
+ }
+ }
+
+ return config;
+ },
+
+ // @private
+ lookupComponent : function(comp) {
+ return Ext.isString(comp) ? Ext.ComponentManager.get(comp) : this.createComponent(comp);
+ },
+
+ // @private
+ createComponent : function(config, defaultType) {
+ // // add in ownerCt at creation time but then immediately
+ // // remove so that onBeforeAdd can handle it
+ // var component = Ext.create(Ext.apply({ownerCt: this}, config), defaultType || this.defaultType);
+ //
+ // delete component.initialConfig.ownerCt;
+ // delete component.ownerCt;
+
+ return Ext.ComponentManager.create(config, defaultType || this.defaultType);
+ },
+
+ // @private - used as the key lookup function for the items collection
+ getComponentId : function(comp) {
+ return comp.getItemId();
+ },
+
+ /**
+
+Adds {@link Ext.Component Component}(s) to this Container.
+
+##Description:##
+
+- Fires the {@link #beforeadd} event before adding.
+- The Container's {@link #defaults default config values} will be applied
+ accordingly (see `{@link #defaults}` for details).
+- Fires the `{@link #add}` event after the component has been added.
+
+##Notes:##
+
+If the Container is __already rendered__ when `add`
+is called, it will render the newly added Component into its content area.
+
+__**If**__ the Container was configured with a size-managing {@link #layout} manager, the Container
+will recalculate its internal layout at this time too.
+
+Note that the default layout manager simply renders child Components sequentially into the content area and thereafter performs no sizing.
+
+If adding multiple new child Components, pass them as an array to the `add` method, so that only one layout recalculation is performed.
+
+ tb = new {@link Ext.toolbar.Toolbar}({
+ renderTo: document.body
+ }); // toolbar is rendered
+ tb.add([{text:'Button 1'}, {text:'Button 2'}]); // add multiple items. ({@link #defaultType} for {@link Ext.toolbar.Toolbar Toolbar} is 'button')
+
+##Warning:##
+
+Components directly managed by the BorderLayout layout manager
+may not be removed or added. See the Notes for {@link Ext.layout.container.Border BorderLayout}
+for more details.
+
+ * @param {...Object/Array} Component
+ * Either one or more Components to add or an Array of Components to add.
+ * See `{@link #items}` for additional information.
+ *
+ * @return {Ext.Component/Array} The Components that were added.
+ * @markdown
+ */
+ add : function() {
+ var me = this,
+ args = Array.prototype.slice.call(arguments),
+ hasMultipleArgs,
+ items,
+ results = [],
+ i,
+ ln,
+ item,
+ index = -1,
+ cmp;
+
+ if (typeof args[0] == 'number') {
+ index = args.shift();
+ }
+
+ hasMultipleArgs = args.length > 1;
+ if (hasMultipleArgs || Ext.isArray(args[0])) {
+
+ items = hasMultipleArgs ? args : args[0];
+ // Suspend Layouts while we add multiple items to the container
+ me.suspendLayout = true;
+ for (i = 0, ln = items.length; i < ln; i++) {
+ item = items[i];
+
+ if (!item) {
+ Ext.Error.raise("Trying to add a null item as a child of Container with itemId/id: " + me.getItemId());
+ }
+
+ if (index != -1) {
+ item = me.add(index + i, item);
+ } else {
+ item = me.add(item);
+ }
+ results.push(item);
+ }
+ // Resume Layouts now that all items have been added and do a single layout for all the items just added
+ me.suspendLayout = false;
+ me.doLayout();
+ return results;
+ }
+
+ cmp = me.prepareItems(args[0], true)[0];
+
+ // Floating Components are not added into the items collection
+ // But they do get an upward ownerCt link so that they can traverse
+ // up to their z-index parent.
+ if (cmp.floating) {
+ cmp.onAdded(me, index);
+ } else {
+ index = (index !== -1) ? index : me.items.length;
+ if (me.fireEvent('beforeadd', me, cmp, index) !== false && me.onBeforeAdd(cmp) !== false) {
+ me.items.insert(index, cmp);
+ cmp.onAdded(me, index);
+ me.onAdd(cmp, index);
+ me.fireEvent('add', me, cmp, index);
+ }
+ me.doLayout();
+ }
+ return cmp;
+ },
+
+ /**
+ * @private
+ * <p>Called by Component#doAutoRender</p>
+ * <p>Register a Container configured <code>floating: true</code> with this Container's {@link Ext.ZIndexManager ZIndexManager}.</p>
+ * <p>Components added in ths way will not participate in the layout, but will be rendered
+ * upon first show in the way that {@link Ext.window.Window Window}s are.</p>
+ * <p></p>
+ */
+ registerFloatingItem: function(cmp) {
+ var me = this;
+ if (!me.floatingItems) {
+ me.floatingItems = Ext.create('Ext.ZIndexManager', me);
+ }
+ me.floatingItems.register(cmp);
+ },
+
+ onAdd : Ext.emptyFn,
+ onRemove : Ext.emptyFn,
+
+ /**
+ * Inserts a Component into this Container at a specified index. Fires the
+ * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
+ * Component has been inserted.
+ * @param {Number} index The index at which the Component will be inserted
+ * into the Container's items collection
+ * @param {Ext.Component} component The child Component to insert.<br><br>
+ * Ext uses lazy rendering, and will only render the inserted Component should
+ * it become necessary.<br><br>
+ * A Component config object may be passed in order to avoid the overhead of
+ * constructing a real Component object if lazy rendering might mean that the
+ * inserted Component will not be rendered immediately. To take advantage of
+ * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
+ * property to the registered type of the Component wanted.<br><br>
+ * For a list of all available xtypes, see {@link Ext.Component}.
+ * @return {Ext.Component} component The Component (or config object) that was
+ * inserted with the Container's default config values applied.
+ */
+ insert : function(index, comp) {
+ return this.add(index, comp);
+ },
+
+ /**
+ * Moves a Component within the Container
+ * @param {Number} fromIdx The index the Component you wish to move is currently at.
+ * @param {Number} toIdx The new index for the Component.
+ * @return {Ext.Component} component The Component (or config object) that was moved.
+ */
+ move : function(fromIdx, toIdx) {
+ var items = this.items,
+ item;
+ item = items.removeAt(fromIdx);
+ if (item === false) {
+ return false;
+ }
+ items.insert(toIdx, item);
+ this.doLayout();
+ return item;
+ },
+
+ // @private
+ onBeforeAdd : function(item) {
+ var me = this;
+
+ if (item.ownerCt) {
+ item.ownerCt.remove(item, false);
+ }
+
+ if (me.border === false || me.border === 0) {
+ item.border = (item.border === true);
+ }
+ },
+
+ /**
+ * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
+ * the {@link #remove} event after the component has been removed.
+ * @param {Component/String} component The component reference or id to remove.
+ * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
+ * Defaults to the value of this Container's {@link #autoDestroy} config.
+ * @return {Ext.Component} component The Component that was removed.
+ */
+ remove : function(comp, autoDestroy) {
+ var me = this,
+ c = me.getComponent(comp);
+ if (Ext.isDefined(Ext.global.console) && !c) {
+ console.warn("Attempted to remove a component that does not exist. Ext.container.Container: remove takes an argument of the component to remove. cmp.remove() is incorrect usage.");
+ }
+
+ if (c && me.fireEvent('beforeremove', me, c) !== false) {
+ me.doRemove(c, autoDestroy);
+ me.fireEvent('remove', me, c);
+ }
+
+ return c;
+ },
+
+ // @private
+ doRemove : function(component, autoDestroy) {
+ var me = this,
+ layout = me.layout,
+ hasLayout = layout && me.rendered;
+
+ me.items.remove(component);
+ component.onRemoved();
+
+ if (hasLayout) {
+ layout.onRemove(component);
+ }
+
+ me.onRemove(component, autoDestroy);
+
+ if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
+ component.destroy();
+ }
+
+ if (hasLayout && !autoDestroy) {
+ layout.afterRemove(component);
+ }
+
+ if (!me.destroying) {
+ me.doLayout();
+ }
+ },
+
+ /**
+ * Removes all components from this container.
+ * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
+ * Defaults to the value of this Container's {@link #autoDestroy} config.
+ * @return {Array} Array of the destroyed components
+ */
+ removeAll : function(autoDestroy) {
+ var me = this,
+ removeItems = me.items.items.slice(),
+ items = [],
+ i = 0,
+ len = removeItems.length,
+ item;
+
+ // Suspend Layouts while we remove multiple items from the container
+ me.suspendLayout = true;
+ for (; i < len; i++) {
+ item = removeItems[i];
+ me.remove(item, autoDestroy);
+
+ if (item.ownerCt !== me) {
+ items.push(item);
+ }
+ }
+
+ // Resume Layouts now that all items have been removed and do a single layout
+ me.suspendLayout = false;
+ me.doLayout();
+ return items;
+ },
+
+ // Used by ComponentQuery to retrieve all of the items
+ // which can potentially be considered a child of this Container.
+ // This should be overriden by components which have child items
+ // that are not contained in items. For example dockedItems, menu, etc
+ // IMPORTANT note for maintainers:
+ // Items are returned in tree traversal order. Each item is appended to the result array
+ // followed by the results of that child's getRefItems call.
+ // Floating child items are appended after internal child items.
+ getRefItems : function(deep) {
+ var me = this,
+ items = me.items.items,
+ len = items.length,
+ i = 0,
+ item,
+ result = [];
+
+ for (; i < len; i++) {
+ item = items[i];
+ result.push(item);
+ if (deep && item.getRefItems) {
+ result.push.apply(result, item.getRefItems(true));
+ }
+ }
+
+ // Append floating items to the list.
+ // These will only be present after they are rendered.
+ if (me.floatingItems && me.floatingItems.accessList) {
+ result.push.apply(result, me.floatingItems.accessList);
+ }
+
+ return result;
+ },
+
+ /**
+ * Cascades down the component/container heirarchy from this component (passed in the first call), calling the specified function with
+ * each component. The scope (<code>this</code> reference) of the
+ * function call will be the scope provided or the current component. The arguments to the function
+ * will be the args provided or the current component. If the function returns false at any point,
+ * the cascade is stopped on that branch.
+ * @param {Function} fn The function to call
+ * @param {Object} scope (optional) The scope of the function (defaults to current component)
+ * @param {Array} args (optional) The args to call the function with. The current component always passed as the last argument.
+ * @return {Ext.Container} this
+ */
+ cascade : function(fn, scope, origArgs){
+ var me = this,
+ cs = me.items ? me.items.items : [],
+ len = cs.length,
+ i = 0,
+ c,
+ args = origArgs ? origArgs.concat(me) : [me],
+ componentIndex = args.length - 1;
+
+ if (fn.apply(scope || me, args) !== false) {
+ for(; i < len; i++){
+ c = cs[i];
+ if (c.cascade) {
+ c.cascade(fn, scope, origArgs);
+ } else {
+ args[componentIndex] = c;
+ fn.apply(scope || cs, args);
+ }
+ }
+ }
+ return this;
+ },
+
+ /**
+ * Examines this container's <code>{@link #items}</code> <b>property</b>
+ * and gets a direct child component of this container.
+ * @param {String/Number} comp This parameter may be any of the following:
+ * <div><ul class="mdetail-params">
+ * <li>a <b><code>String</code></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
+ * or <code>{@link Ext.Component#id id}</code> of the child component </li>
+ * <li>a <b><code>Number</code></b> : representing the position of the child component
+ * within the <code>{@link #items}</code> <b>property</b></li>
+ * </ul></div>
+ * <p>For additional information see {@link Ext.util.MixedCollection#get}.
+ * @return Ext.Component The component (if found).
+ */
+ getComponent : function(comp) {
+ if (Ext.isObject(comp)) {
+ comp = comp.getItemId();
+ }
+
+ return this.items.get(comp);
+ },
+
+ /**
+ * Retrieves all descendant components which match the passed selector.
+ * Executes an Ext.ComponentQuery.query using this container as its root.
+ * @param {String} selector Selector complying to an Ext.ComponentQuery selector
+ * @return {Array} Ext.Component's which matched the selector
+ */
+ query : function(selector) {
+ return Ext.ComponentQuery.query(selector, this);
+ },
+
+ /**
+ * Retrieves the first direct child of this container which matches the passed selector.
+ * The passed in selector must comply with an Ext.ComponentQuery selector.
+ * @param {String} selector An Ext.ComponentQuery selector
+ * @return Ext.Component
+ */
+ child : function(selector) {
+ return this.query('> ' + selector)[0] || null;
+ },
+
+ /**
+ * Retrieves the first descendant of this container which matches the passed selector.
+ * The passed in selector must comply with an Ext.ComponentQuery selector.
+ * @param {String} selector An Ext.ComponentQuery selector
+ * @return Ext.Component
+ */
+ down : function(selector) {
+ return this.query(selector)[0] || null;
+ },
+
+ // inherit docs
+ show : function() {
+ this.callParent(arguments);
+ this.performDeferredLayouts();
+ return this;
+ },
+
+ // Lay out any descendant containers who queued a layout operation during the time this was hidden
+ // This is also called by Panel after it expands because descendants of a collapsed Panel allso queue any layout ops.
+ performDeferredLayouts: function() {
+ var layoutCollection = this.layoutOnShow,
+ ln = layoutCollection.getCount(),
+ i = 0,
+ needsLayout,
+ item;
+
+ for (; i < ln; i++) {
+ item = layoutCollection.get(i);
+ needsLayout = item.needsLayout;
+
+ if (Ext.isObject(needsLayout)) {
+ item.doComponentLayout(needsLayout.width, needsLayout.height, needsLayout.isSetSize, needsLayout.ownerCt);
+ }
+ }
+ layoutCollection.clear();
+ },
+
+ //@private
+ // Enable all immediate children that was previously disabled
+ onEnable: function() {
+ Ext.Array.each(this.query('[isFormField]'), function(item) {
+ if (item.resetDisable) {
+ item.enable();
+ delete item.resetDisable;
+ }
+ });
+ this.callParent();
+ },
+
+ // @private
+ // Disable all immediate children that was previously disabled
+ onDisable: function() {
+ Ext.Array.each(this.query('[isFormField]'), function(item) {
+ if (item.resetDisable !== false && !item.disabled) {
+ item.disable();
+ item.resetDisable = true;
+ }
+ });
+ this.callParent();
+ },
+
+ /**
+ * Occurs before componentLayout is run. Returning false from this method will prevent the containerLayout
+ * from being executed.
+ */
+ beforeLayout: function() {
+ return true;
+ },
+
+ // @private
+ beforeDestroy : function() {
+ var me = this,
+ items = me.items,
+ c;
+
+ if (items) {
+ while ((c = items.first())) {
+ me.doRemove(c, true);
+ }
+ }
+
+ Ext.destroy(
+ me.layout,
+ me.floatingItems
+ );
+ me.callParent();
+ }
+});
+/**
+ * @class Ext.container.Container
+ * @extends Ext.container.AbstractContainer
+ * <p>Base class for any {@link Ext.Component} that may contain other Components. Containers handle the
+ * basic behavior of containing items, namely adding, inserting and removing items.</p>
+ *
+ * <p>The most commonly used Container classes are {@link Ext.panel.Panel}, {@link Ext.window.Window} and {@link Ext.tab.Panel}.
+ * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
+ * Container to be encapsulated by an HTML element to your specifications by using the
+ * <code><b>{@link Ext.Component#autoEl autoEl}</b></code> config option.</p>
+ *
+ * {@img Ext.Container/Ext.Container.png Ext.Container component}
+ * <p>The code below illustrates how to explicitly create a Container:<pre><code>
+// explicitly create a Container
+Ext.create('Ext.container.Container', {
+ layout: {
+ type: 'hbox'
+ },
+ width: 400,
+ renderTo: Ext.getBody(),
+ border: 1,
+ style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
+ defaults: {
+ labelWidth: 80,
+ // implicitly create Container by specifying xtype
+ xtype: 'datefield',
+ flex: 1,
+ style: {
+ padding: '10px'
+ }
+ },
+ items: [{
+ xtype: 'datefield',
+ name: 'startDate',
+ fieldLabel: 'Start date'
+ },{
+ xtype: 'datefield',
+ name: 'endDate',
+ fieldLabel: 'End date'
+ }]
+});
+</code></pre></p>
+ *
+ * <p><u><b>Layout</b></u></p>
+ * <p>Container classes delegate the rendering of child Components to a layout
+ * manager class which must be configured into the Container using the
+ * <code><b>{@link #layout}</b></code> configuration property.</p>
+ * <p>When either specifying child <code>{@link #items}</code> of a Container,
+ * or dynamically {@link #add adding} Components to a Container, remember to
+ * consider how you wish the Container to arrange those child elements, and
+ * whether those child elements need to be sized using one of Ext's built-in
+ * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
+ * {@link Ext.layout.container.Auto Auto} scheme which only
+ * renders child components, appending them one after the other inside the
+ * Container, and <b>does not apply any sizing</b> at all.</p>
+ * <p>A common mistake is when a developer neglects to specify a
+ * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
+ * TreePanels are added to Containers for which no <code><b>{@link #layout}</b></code>
+ * has been specified). If a Container is left to use the default
+ * {Ext.layout.container.Auto Auto} scheme, none of its
+ * child components will be resized, or changed in any way when the Container
+ * is resized.</p>
+ * <p>Certain layout managers allow dynamic addition of child components.
+ * Those that do include {@link Ext.layout.container.Card},
+ * {@link Ext.layout.container.Anchor}, {@link Ext.layout.container.VBox}, {@link Ext.layout.container.HBox}, and
+ * {@link Ext.layout.container.Table}. For example:<pre><code>
+// Create the GridPanel.
+var myNewGrid = new Ext.grid.Panel({
+ store: myStore,
+ headers: myHeaders,
+ title: 'Results', // the title becomes the title of the tab
+});
+
+myTabPanel.add(myNewGrid); // {@link Ext.tab.Panel} implicitly uses {@link Ext.layout.container.Card Card}
+myTabPanel.{@link Ext.tab.Panel#setActiveTab setActiveTab}(myNewGrid);
+ * </code></pre></p>
+ * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
+ * a TabPanel uses {@link Ext.layout.container.Card} as its layout manager which
+ * means all its child items are sized to {@link Ext.layout.container.Fit fit}
+ * exactly into its client area.
+ * <p><b><u>Overnesting is a common problem</u></b>.
+ * An example of overnesting occurs when a GridPanel is added to a TabPanel
+ * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
+ * <code><b>{@link #layout}</b></code> specified) and then add that wrapping Panel
+ * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
+ * Component which can be added directly to a Container. If the wrapping Panel
+ * has no <code><b>{@link #layout}</b></code> configuration, then the overnested
+ * GridPanel will not be sized as expected.<p>
+ *
+ * <p><u><b>Adding via remote configuration</b></u></p>
+ *
+ * <p>A server side script can be used to add Components which are generated dynamically on the server.
+ * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
+ * based on certain parameters:
+ * </p><pre><code>
+// execute an Ajax request to invoke server side script:
+Ext.Ajax.request({
+ url: 'gen-invoice-grid.php',
+ // send additional parameters to instruct server script
+ params: {
+ startDate: Ext.getCmp('start-date').getValue(),
+ endDate: Ext.getCmp('end-date').getValue()
+ },
+ // process the response object to add it to the TabPanel:
+ success: function(xhr) {
+ var newComponent = eval(xhr.responseText); // see discussion below
+ myTabPanel.add(newComponent); // add the component to the TabPanel
+ myTabPanel.setActiveTab(newComponent);
+ },
+ failure: function() {
+ Ext.Msg.alert("Grid create failed", "Server communication failure");
+ }
+});
+</code></pre>
+ * <p>The server script needs to return a JSON representation of a configuration object, which, when decoded
+ * will return a config object with an {@link Ext.Component#xtype xtype}. The server might return the following
+ * JSON:</p><pre><code>
+{
+ "xtype": 'grid',
+ "title": 'Invoice Report',
+ "store": {
+ "model": 'Invoice',
+ "proxy": {
+ "type": 'ajax',
+ "url": 'get-invoice-data.php',
+ "reader": {
+ "type": 'json'
+ "record": 'transaction',
+ "idProperty": 'id',
+ "totalRecords": 'total'
+ })
+ },
+ "autoLoad": {
+ "params": {
+ "startDate": '01/01/2008',
+ "endDate": '01/31/2008'
+ }
+ }
+ },
+ "headers": [
+ {"header": "Customer", "width": 250, "dataIndex": 'customer', "sortable": true},
+ {"header": "Invoice Number", "width": 120, "dataIndex": 'invNo', "sortable": true},
+ {"header": "Invoice Date", "width": 100, "dataIndex": 'date', "renderer": Ext.util.Format.dateRenderer('M d, y'), "sortable": true},
+ {"header": "Value", "width": 120, "dataIndex": 'value', "renderer": 'usMoney', "sortable": true}
+ ]
+}
+</code></pre>
+ * <p>When the above code fragment is passed through the <code>eval</code> function in the success handler
+ * of the Ajax request, the result will be a config object which, when added to a Container, will cause instantiation
+ * of a GridPanel. <b>Be sure that the Container is configured with a layout which sizes and positions the child items to your requirements.</b></p>
+ * <p>Note: since the code above is <i>generated</i> by a server script, the <code>autoLoad</code> params for
+ * the Store, the user's preferred date format, the metadata to allow generation of the Model layout, and the ColumnModel
+ * can all be generated into the code since these are all known on the server.</p>
+ *
+ * @xtype container
+ */
+Ext.define('Ext.container.Container', {
+ extend: 'Ext.container.AbstractContainer',
+ alias: 'widget.container',
+ alternateClassName: 'Ext.Container',
+
+ /**
+ * Return the immediate child Component in which the passed element is located.
+ * @param el The element to test.
+ * @return {Component} The child item which contains the passed element.
+ */
+ getChildByElement: function(el) {
+ var item,
+ itemEl,
+ i = 0,
+ it = this.items.items,
+ ln = it.length;
+
+ el = Ext.getDom(el);
+ for (; i < ln; i++) {
+ item = it[i];
+ itemEl = item.getEl();
+ if ((itemEl.dom === el) || itemEl.contains(el)) {
+ return item;
+ }
+ }
+ return null;
+ }
+});
+
+/**
+ * @class Ext.toolbar.Fill
+ * @extends Ext.Component
+ * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
+ * the right-justified button container.
+ *
+ * {@img Ext.toolbar.Fill/Ext.toolbar.Fill.png Toolbar Fill}
+ * Example usage:
+<pre><code>
+ Ext.create('Ext.panel.Panel', {
+ title: 'Toolbar Fill Example',
+ width: 300,
+ height: 200,
+ tbar : [
+ 'Item 1',
+ {xtype: 'tbfill'}, // or '->'
+ 'Item 2'
+ ],
+ renderTo: Ext.getBody()
+ });
+</code></pre>
+ * @constructor
+ * Creates a new Fill
+ * @xtype tbfill
+ */
+Ext.define('Ext.toolbar.Fill', {
+ extend: 'Ext.Component',
+ alias: 'widget.tbfill',
+ alternateClassName: 'Ext.Toolbar.Fill',
+ isFill : true,
+ flex: 1
+});
+/**
+ * @class Ext.toolbar.Item
+ * @extends Ext.Component
+ * The base class that other non-interacting Toolbar Item classes should extend in order to
+ * get some basic common toolbar item functionality.
+ * @constructor
+ * Creates a new Item
+ * @param {HTMLElement} el
+ * @xtype tbitem
+ */
+Ext.define('Ext.toolbar.Item', {
+ extend: 'Ext.Component',
+ alias: 'widget.tbitem',
+ alternateClassName: 'Ext.Toolbar.Item',
+ enable:Ext.emptyFn,
+ disable:Ext.emptyFn,
+ focus:Ext.emptyFn
+ /**
+ * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
+ */
+});
+/**
+ * @class Ext.toolbar.Separator
+ * @extends Ext.toolbar.Item
+ * A simple class that adds a vertical separator bar between toolbar items
+ * (css class:<tt>'x-toolbar-separator'</tt>).
+ * {@img Ext.toolbar.Separator/Ext.toolbar.Separator.png Toolbar Separator}
+ * Example usage:
+ * <pre><code>
+ Ext.create('Ext.panel.Panel', {
+ title: 'Toolbar Seperator Example',
+ width: 300,
+ height: 200,
+ tbar : [
+ 'Item 1',
+ {xtype: 'tbseparator'}, // or '-'
+ 'Item 2'
+ ],
+ renderTo: Ext.getBody()
+ });
+</code></pre>
+ * @constructor
+ * Creates a new Separator
+ * @xtype tbseparator
+ */
+Ext.define('Ext.toolbar.Separator', {
+ extend: 'Ext.toolbar.Item',
+ alias: 'widget.tbseparator',
+ alternateClassName: 'Ext.Toolbar.Separator',
+ baseCls: Ext.baseCSSPrefix + 'toolbar-separator',
+ focusable: false
+});
+/**
+ * @class Ext.menu.Manager
+ * Provides a common registry of all menus on a page.
+ * @singleton
+ */
+Ext.define('Ext.menu.Manager', {
+ singleton: true,
+ requires: [
+ 'Ext.util.MixedCollection',
+ 'Ext.util.KeyMap'
+ ],
+ alternateClassName: 'Ext.menu.MenuMgr',
+
+ uses: ['Ext.menu.Menu'],
+
+ menus: {},
+ groups: {},
+ attached: false,
+ lastShow: new Date(),
+
+ init: function() {
+ var me = this;
+
+ me.active = Ext.create('Ext.util.MixedCollection');
+ Ext.getDoc().addKeyListener(27, function() {
+ if (me.active.length > 0) {
+ me.hideAll();
+ }
+ }, me);
+ },
+
+ /**
+ * Hides all menus that are currently visible
+ * @return {Boolean} success True if any active menus were hidden.
+ */
+ hideAll: function() {
+ var active = this.active,
+ c;
+ if (active && active.length > 0) {
+ c = active.clone();
+ c.each(function(m) {
+ m.hide();
+ });
+ return true;
+ }
+ return false;
+ },
+
+ onHide: function(m) {
+ var me = this,
+ active = me.active;
+ active.remove(m);
+ if (active.length < 1) {
+ Ext.getDoc().un('mousedown', me.onMouseDown, me);
+ me.attached = false;
+ }
+ },
+
+ onShow: function(m) {
+ var me = this,
+ active = me.active,
+ last = active.last(),
+ attached = me.attached,
+ menuEl = m.getEl(),
+ zIndex;
+
+ me.lastShow = new Date();
+ active.add(m);
+ if (!attached) {
+ Ext.getDoc().on('mousedown', me.onMouseDown, me);
+ me.attached = true;
+ }
+ m.toFront();
+ },
+
+ onBeforeHide: function(m) {
+ if (m.activeChild) {
+ m.activeChild.hide();
+ }
+ if (m.autoHideTimer) {
+ clearTimeout(m.autoHideTimer);
+ delete m.autoHideTimer;
+ }
+ },
+
+ onBeforeShow: function(m) {
+ var active = this.active,
+ parentMenu = m.parentMenu;
+
+ active.remove(m);
+ if (!parentMenu && !m.allowOtherMenus) {
+ this.hideAll();
+ }
+ else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
+ parentMenu.activeChild.hide();
+ }
+ },
+
+ // private
+ onMouseDown: function(e) {
+ var me = this,
+ active = me.active,
+ lastShow = me.lastShow;
+
+ if (Ext.Date.getElapsed(lastShow) > 50 && active.length > 0 && !e.getTarget('.' + Ext.baseCSSPrefix + 'menu')) {
+ me.hideAll();
+ }
+ },
+
+ // private
+ register: function(menu) {
+ var me = this;
+
+ if (!me.active) {
+ me.init();
+ }
+
+ if (menu.floating) {
+ me.menus[menu.id] = menu;
+ menu.on({
+ beforehide: me.onBeforeHide,
+ hide: me.onHide,
+ beforeshow: me.onBeforeShow,
+ show: me.onShow,
+ scope: me
+ });
+ }
+ },
+
+ /**
+ * Returns a {@link Ext.menu.Menu} object
+ * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
+ * be used to generate and return a new Menu this.
+ * @return {Ext.menu.Menu} The specified menu, or null if none are found
+ */
+ get: function(menu) {
+ var menus = this.menus;
+
+ if (typeof menu == 'string') { // menu id
+ if (!menus) { // not initialized, no menus to return
+ return null;
+ }
+ return menus[menu];
+ } else if (menu.isMenu) { // menu instance
+ return menu;
+ } else if (Ext.isArray(menu)) { // array of menu items
+ return Ext.create('Ext.menu.Menu', {items:menu});
+ } else { // otherwise, must be a config
+ return Ext.ComponentManager.create(menu, 'menu');
+ }
+ },
+
+ // private
+ unregister: function(menu) {
+ var me = this,
+ menus = me.menus,
+ active = me.active;
+
+ delete menus[menu.id];
+ active.remove(menu);
+ menu.un({
+ beforehide: me.onBeforeHide,
+ hide: me.onHide,
+ beforeshow: me.onBeforeShow,
+ show: me.onShow,
+ scope: me
+ });
+ },
+
+ // private
+ registerCheckable: function(menuItem) {
+ var groups = this.groups,
+ groupId = menuItem.group;
+
+ if (groupId) {
+ if (!groups[groupId]) {
+ groups[groupId] = [];
+ }
+
+ groups[groupId].push(menuItem);
+ }
+ },
+
+ // private
+ unregisterCheckable: function(menuItem) {
+ var groups = this.groups,
+ groupId = menuItem.group;
+
+ if (groupId) {
+ Ext.Array.remove(groups[groupId], menuItem);
+ }
+ },
+
+ onCheckChange: function(menuItem, state) {
+ var groups = this.groups,
+ groupId = menuItem.group,
+ i = 0,
+ group, ln, curr;
+
+ if (groupId && state) {
+ group = groups[groupId];
+ ln = group.length;
+ for (; i < ln; i++) {
+ curr = group[i];
+ if (curr != menuItem) {
+ curr.setChecked(false);
+ }
+ }
+ }
+ }
+});
+/**
+ * @class Ext.button.Button
+ * @extends Ext.Component
+
+Create simple buttons with this component. Customisations include {@link #config-iconAlign aligned}
+{@link #config-iconCls icons}, {@link #config-menu dropdown menus}, {@link #config-tooltip tooltips}
+and {@link #config-scale sizing options}. Specify a {@link #config-handler handler} to run code when
+a user clicks the button, or use {@link #config-listeners listeners} for other events such as
+{@link #events-mouseover mouseover}.
+
+{@img Ext.button.Button/Ext.button.Button1.png Ext.button.Button component}
+Example usage:
+
+ Ext.create('Ext.Button', {
+ text: 'Click me',
+ renderTo: Ext.getBody(),
+ handler: function() {
+ alert('You clicked the button!')
+ }
+ });
+
+The {@link #handler} configuration can also be updated dynamically using the {@link #setHandler} method.
+Example usage:
+
+ Ext.create('Ext.Button', {
+ text : 'Dyanmic Handler Button',
+ renderTo: Ext.getBody(),
+ handler : function() {
+ //this button will spit out a different number every time you click it.
+ //so firstly we must check if that number is already set:
+ if (this.clickCount) {
+ //looks like the property is already set, so lets just add 1 to that number and alert the user
+ this.clickCount++;
+ alert('You have clicked the button "' + this.clickCount + '" times.\n\nTry clicking it again..');
+ } else {
+ //if the clickCount property is not set, we will set it and alert the user
+ this.clickCount = 1;
+ alert('You just clicked the button for the first time!\n\nTry pressing it again..');
+ }
+ }
+ });
+
+A button within a container:
+
+ Ext.create('Ext.Container', {
+ renderTo: Ext.getBody(),
+ items : [
+ {
+ xtype: 'button',
+ text : 'My Button'
+ }
+ ]
+ });
+
+A useful option of Button is the {@link #scale} configuration. This configuration has three different options:
+* `'small'`
+* `'medium'`
+* `'large'`
+
+{@img Ext.button.Button/Ext.button.Button2.png Ext.button.Button component}
+Example usage:
+
+ Ext.create('Ext.Button', {
+ renderTo: document.body,
+ text : 'Click me',
+ scale : 'large'
+ });
+
+Buttons can also be toggled. To enable this, you simple set the {@link #enableToggle} property to `true`.
+{@img Ext.button.Button/Ext.button.Button3.png Ext.button.Button component}
+Example usage:
+
+ Ext.create('Ext.Button', {
+ renderTo: Ext.getBody(),
+ text: 'Click Me',
+ enableToggle: true
+ });
+
+You can assign a menu to a button by using the {@link #menu} configuration. This standard configuration can either be a reference to a {@link Ext.menu.Menu menu}
+object, a {@link Ext.menu.Menu menu} id or a {@link Ext.menu.Menu menu} config blob. When assigning a menu to a button, an arrow is automatically added to the button.
+You can change the alignment of the arrow using the {@link #arrowAlign} configuration on button.
+{@img Ext.button.Button/Ext.button.Button4.png Ext.button.Button component}
+Example usage:
+
+ Ext.create('Ext.Button', {
+ text : 'Menu button',
+ renderTo : Ext.getBody(),
+ arrowAlign: 'bottom',
+ menu : [
+ {text: 'Item 1'},
+ {text: 'Item 2'},
+ {text: 'Item 3'},
+ {text: 'Item 4'}
+ ]
+ });
+
+Using listeners, you can easily listen to events fired by any component, using the {@link #listeners} configuration or using the {@link #addListener} method.
+Button has a variety of different listeners:
+* `click`
+* `toggle`
+* `mouseover`
+* `mouseout`
+* `mouseshow`
+* `menuhide`
+* `menutriggerover`
+* `menutriggerout`
+
+Example usage:
+
+ Ext.create('Ext.Button', {
+ text : 'Button',
+ renderTo : Ext.getBody(),
+ listeners: {
+ click: function() {
+ //this == the button, as we are in the local scope
+ this.setText('I was clicked!');
+ },
+ mouseover: function() {
+ //set a new config which says we moused over, if not already set
+ if (!this.mousedOver) {
+ this.mousedOver = true;
+ alert('You moused over a button!\n\nI wont do this again.');
+ }
+ }
+ }
+ });
+
+ * @constructor
+ * Create a new button
+ * @param {Object} config The config object
+ * @xtype button
+ * @markdown
+ * @docauthor Robert Dougan <rob@sencha.com>
+ */
+Ext.define('Ext.button.Button', {
+
+ /* Begin Definitions */
+ alias: 'widget.button',
+ extend: 'Ext.Component',
+
+ requires: [
+ 'Ext.menu.Manager',
+ 'Ext.util.ClickRepeater',
+ 'Ext.layout.component.Button',
+ 'Ext.util.TextMetrics',
+ 'Ext.util.KeyMap'
+ ],
+
+ alternateClassName: 'Ext.Button',
+ /* End Definitions */
+
+ isButton: true,
+ componentLayout: 'button',
+
+ /**
+ * Read-only. True if this button is hidden
+ * @type Boolean
+ */
+ hidden: false,
+
+ /**
+ * Read-only. True if this button is disabled
+ * @type Boolean
+ */
+ disabled: false,
+
+ /**
+ * Read-only. True if this button is pressed (only if enableToggle = true)
+ * @type Boolean
+ */
+ pressed: false,
+
+ /**
+ * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
+ */
+
+ /**
+ * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
+ * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
+ */
+
+ /**
+ * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
+ * The handler is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
+ * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
+ * </ul></div>
+ */
+
+ /**
+ * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
+ * See also {@link Ext.panel.Panel}.<tt>{@link Ext.panel.Panel#minButtonWidth minButtonWidth}</tt>.
+ */
+
+ /**
+ * @cfg {String/Object} tooltip The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config object
+ */
+
+ /**
+ * @cfg {Boolean} hidden True to start hidden (defaults to false)
+ */
+
+ /**
+ * @cfg {Boolean} disabled True to start disabled (defaults to false)
+ */
+
+ /**
+ * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
+ */
+
+ /**
+ * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
+ */
+
+ /**
+ * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
+ * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
+ */
+
+ /**
+ * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
+ */
+
+ /**
+ * @cfg {Boolean} allowDepress
+ * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
+ */
+
+ /**
+ * @cfg {Boolean} enableToggle
+ * True to enable pressed/not pressed toggling (defaults to false)
+ */
+ enableToggle: false,
+
+ /**
+ * @cfg {Function} toggleHandler
+ * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
+ * <li><b>button</b> : Ext.button.Button<div class="sub-desc">this Button object</div></li>
+ * <li><b>state</b> : Boolean<div class="sub-desc">The next state of the Button, true means pressed.</div></li>
+ * </ul>
+ */
+
+ /**
+ * @cfg {Mixed} menu
+ * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
+ */
+
+ /**
+ * @cfg {String} menuAlign
+ * The position to align the menu to (see {@link Ext.core.Element#alignTo} for more details, defaults to 'tl-bl?').
+ */
+ menuAlign: 'tl-bl?',
+
+ /**
+ * @cfg {String} overflowText If used in a {@link Ext.toolbar.Toolbar Toolbar}, the
+ * text to be used if this item is shown in the overflow menu. See also
+ * {@link Ext.toolbar.Item}.<code>{@link Ext.toolbar.Item#overflowText overflowText}</code>.
+ */
+
+ /**
+ * @cfg {String} iconCls
+ * A css class which sets a background image to be used as the icon for this button
+ */
+
+ /**
+ * @cfg {String} type
+ * submit, reset or button - defaults to 'button'
+ */
+ type: 'button',
+
+ /**
+ * @cfg {String} clickEvent
+ * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
+ * Defaults to <tt>'click'</tt>.
+ */
+ clickEvent: 'click',
+
+ /**
+ * @cfg {Boolean} preventDefault
+ * True to prevent the default action when the {@link #clickEvent} is processed. Defaults to true.
+ */
+ preventDefault: true,
+
+ /**
+ * @cfg {Boolean} handleMouseEvents
+ * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
+ */
+ handleMouseEvents: true,
+
+ /**
+ * @cfg {String} tooltipType
+ * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
+ */
+ tooltipType: 'qtip',
+
+ /**
+ * @cfg {String} baseCls
+ * The base CSS class to add to all buttons. (Defaults to 'x-btn')
+ */
+ baseCls: Ext.baseCSSPrefix + 'btn',
+
+ /**
+ * @cfg {String} pressedCls
+ * The CSS class to add to a button when it is in the pressed state. (Defaults to 'x-btn-pressed')
+ */
+ pressedCls: 'pressed',
+
+ /**
+ * @cfg {String} overCls
+ * The CSS class to add to a button when it is in the over (hovered) state. (Defaults to 'x-btn-over')
+ */
+ overCls: 'over',
+
+ /**
+ * @cfg {String} focusCls
+ * The CSS class to add to a button when it is in the focussed state. (Defaults to 'x-btn-focus')
+ */
+ focusCls: 'focus',
+
+ /**
+ * @cfg {String} menuActiveCls
+ * The CSS class to add to a button when it's menu is active. (Defaults to 'x-btn-menu-active')
+ */
+ menuActiveCls: 'menu-active',
+
+ ariaRole: 'button',
+
+ // inherited
+ renderTpl:
+ '<em class="{splitCls}">' +
+ '<tpl if="href">' +
+ '<a href="{href}" target="{target}"<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="link">' +
+ '<span class="{baseCls}-inner">{text}</span>' +
+ '</a>' +
+ '</tpl>' +
+ '<tpl if="!href">' +
+ '<button type="{type}" hidefocus="true"' +
+ // the autocomplete="off" is required to prevent Firefox from remembering
+ // the button's disabled state between page reloads.
+ '<tpl if="tabIndex"> tabIndex="{tabIndex}"</tpl> role="button" autocomplete="off">' +
+ '<span class="{baseCls}-inner" style="{innerSpanStyle}">{text}</span>' +
+ '</button>' +
+ '</tpl>' +
+ '</em>' ,
+
+ /**
+ * @cfg {String} scale
+ * <p>(Optional) The size of the Button. Three values are allowed:</p>
+ * <ul class="mdetail-params">
+ * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
+ * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
+ * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
+ * </ul>
+ * <p>Defaults to <b><tt>'small'</tt></b>.</p>
+ */
+ scale: 'small',
+
+ /**
+ * @private An array of allowed scales.
+ */
+ allowedScales: ['small', 'medium', 'large'],
+
+ /**
+ * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the
+ * <code>{@link #handler}</code> and <code>{@link #toggleHandler}</code> is
+ * executed. Defaults to this Button.
+ */
+
+ /**
+ * @cfg {String} iconAlign
+ * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
+ * <ul class="mdetail-params">
+ * <li>'top'<div class="sub-desc"></div></li>
+ * <li>'right'<div class="sub-desc"></div></li>
+ * <li>'bottom'<div class="sub-desc"></div></li>
+ * <li>'left'<div class="sub-desc"></div></li>
+ * </ul>
+ * <p>Defaults to <b><tt>'left'</tt></b>.</p>
+ */
+ iconAlign: 'left',
+
+ /**
+ * @cfg {String} arrowAlign
+ * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
+ * Two values are allowed:</p>
+ * <ul class="mdetail-params">
+ * <li>'right'<div class="sub-desc"></div></li>
+ * <li>'bottom'<div class="sub-desc"></div></li>
+ * </ul>
+ * <p>Defaults to <b><tt>'right'</tt></b>.</p>
+ */
+ arrowAlign: 'right',
+
+ /**
+ * @cfg {String} arrowCls
+ * <p>(Optional) The className used for the inner arrow element if the button has a menu.</p>
+ */
+ arrowCls: 'arrow',
+
+ /**
+ * @cfg {Ext.Template} template (Optional)
+ * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
+ * Instances, or subclasses which need a different DOM structure may provide a different
+ * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
+ * @type Ext.Template
+ * @property template
+ */
+
+ /**
+ * @cfg {String} cls
+ * A CSS class string to apply to the button's main element.
+ */
+
+ /**
+ * @property menu
+ * @type Menu
+ * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
+ */
+
+ /**
+ * @cfg {Boolean} autoWidth
+ * By default, if a width is not specified the button will attempt to stretch horizontally to fit its content.
+ * If the button is being managed by a width sizing layout (hbox, fit, anchor), set this to false to prevent
+ * the button from doing this automatic sizing.
+ * Defaults to <tt>undefined</tt>.
+ */
+
+ maskOnDisable: false,
+
+ // inherit docs
+ initComponent: function() {
+ var me = this;
+ me.callParent(arguments);
+
+ me.addEvents(
+ /**
+ * @event click
+ * Fires when this button is clicked
+ * @param {Button} this
+ * @param {EventObject} e The click event
+ */
+ 'click',
+
+ /**
+ * @event toggle
+ * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
+ * @param {Button} this
+ * @param {Boolean} pressed
+ */
+ 'toggle',
+
+ /**
+ * @event mouseover
+ * Fires when the mouse hovers over the button
+ * @param {Button} this
+ * @param {Event} e The event object
+ */
+ 'mouseover',
+
+ /**
+ * @event mouseout
+ * Fires when the mouse exits the button
+ * @param {Button} this
+ * @param {Event} e The event object
+ */
+ 'mouseout',
+
+ /**
+ * @event menushow
+ * If this button has a menu, this event fires when it is shown
+ * @param {Button} this
+ * @param {Menu} menu
+ */
+ 'menushow',
+
+ /**
+ * @event menuhide
+ * If this button has a menu, this event fires when it is hidden
+ * @param {Button} this
+ * @param {Menu} menu
+ */
+ 'menuhide',
+
+ /**
+ * @event menutriggerover
+ * If this button has a menu, this event fires when the mouse enters the menu triggering element
+ * @param {Button} this
+ * @param {Menu} menu
+ * @param {EventObject} e
+ */
+ 'menutriggerover',
+
+ /**
+ * @event menutriggerout
+ * If this button has a menu, this event fires when the mouse leaves the menu triggering element
+ * @param {Button} this
+ * @param {Menu} menu
+ * @param {EventObject} e
+ */
+ 'menutriggerout'
+ );
+
+ if (me.menu) {
+ // Flag that we'll have a splitCls
+ me.split = true;
+
+ // retrieve menu by id or instantiate instance if needed
+ me.menu = Ext.menu.Manager.get(me.menu);
+ me.menu.ownerCt = me;
+ }
+
+ // Accept url as a synonym for href
+ if (me.url) {
+ me.href = me.url;
+ }
+
+ // preventDefault defaults to false for links
+ if (me.href && !me.hasOwnProperty('preventDefault')) {
+ me.preventDefault = false;
+ }
+
+ if (Ext.isString(me.toggleGroup)) {
+ me.enableToggle = true;
+ }
+
+ },
+
+ // private
+ initAria: function() {
+ this.callParent();
+ var actionEl = this.getActionEl();
+ if (this.menu) {
+ actionEl.dom.setAttribute('aria-haspopup', true);
+ }
+ },
+
+ // inherit docs
+ getActionEl: function() {
+ return this.btnEl;
+ },
+
+ // inherit docs
+ getFocusEl: function() {
+ return this.btnEl;
+ },
+
+ // private
+ setButtonCls: function() {
+ var me = this,
+ el = me.el,
+ cls = [];
+
+ if (me.useSetClass) {
+ if (!Ext.isEmpty(me.oldCls)) {
+ me.removeClsWithUI(me.oldCls);
+ me.removeClsWithUI(me.pressedCls);
+ }
+
+ // Check whether the button has an icon or not, and if it has an icon, what is th alignment
+ if (me.iconCls || me.icon) {
+ if (me.text) {
+ cls.push('icon-text-' + me.iconAlign);
+ } else {
+ cls.push('icon');
+ }
+ } else if (me.text) {
+ cls.push('noicon');
+ }
+
+ me.oldCls = cls;
+ me.addClsWithUI(cls);
+ me.addClsWithUI(me.pressed ? me.pressedCls : null);
+ }
+ },
+
+ // private
+ onRender: function(ct, position) {
+ // classNames for the button
+ var me = this,
+ repeater, btn;
+
+ // Apply the renderData to the template args
+ Ext.applyIf(me.renderData, me.getTemplateArgs());
+
+ // Extract the button and the button wrapping element
+ Ext.applyIf(me.renderSelectors, {
+ btnEl : me.href ? 'a' : 'button',
+ btnWrap: 'em',
+ btnInnerEl: '.' + me.baseCls + '-inner'
+ });
+
+ if (me.scale) {
+ me.ui = me.ui + '-' + me.scale;
+ }
+
+ // Render internal structure
+ me.callParent(arguments);
+
+ // If it is a split button + has a toolip for the arrow
+ if (me.split && me.arrowTooltip) {
+ me.arrowEl.dom[me.tooltipType] = me.arrowTooltip;
+ }
+
+ // Add listeners to the focus and blur events on the element
+ me.mon(me.btnEl, {
+ scope: me,
+ focus: me.onFocus,
+ blur : me.onBlur
+ });
+
+ // Set btn as a local variable for easy access
+ btn = me.el;
+
+ if (me.icon) {
+ me.setIcon(me.icon);
+ }
+
+ if (me.iconCls) {
+ me.setIconCls(me.iconCls);
+ }
+
+ if (me.tooltip) {
+ me.setTooltip(me.tooltip, true);
+ }
+
+ // Add the mouse events to the button
+ if (me.handleMouseEvents) {
+ me.mon(btn, {
+ scope: me,
+ mouseover: me.onMouseOver,
+ mouseout: me.onMouseOut,
+ mousedown: me.onMouseDown
+ });
+
+ if (me.split) {
+ me.mon(btn, {
+ mousemove: me.onMouseMove,
+ scope: me
+ });
+ }
+ }
+
+ // Check if the button has a menu
+ if (me.menu) {
+ me.mon(me.menu, {
+ scope: me,
+ show: me.onMenuShow,
+ hide: me.onMenuHide
+ });
+
+ me.keyMap = Ext.create('Ext.util.KeyMap', me.el, {
+ key: Ext.EventObject.DOWN,
+ handler: me.onDownKey,
+ scope: me
+ });
+ }
+
+ // Check if it is a repeat button
+ if (me.repeat) {
+ repeater = Ext.create('Ext.util.ClickRepeater', btn, Ext.isObject(me.repeat) ? me.repeat: {});
+ me.mon(repeater, 'click', me.onRepeatClick, me);
+ } else {
+ me.mon(btn, me.clickEvent, me.onClick, me);
+ }
+
+ // Register the button in the toggle manager
+ Ext.ButtonToggleManager.register(me);
+ },
+
+ /**
+ * <p>This method returns an object which provides substitution parameters for the {@link #renderTpl XTemplate} used
+ * to create this Button's DOM structure.</p>
+ * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
+ * own implementation of this method.</p>
+ * <p>The default implementation which provides data for the default {@link #template} returns an Object containing the
+ * following properties:</p><div class="mdetail-params"><ul>
+ * <li><code>type</code> : The <button>'s {@link #type}</li>
+ * <li><code>splitCls</code> : A CSS class to determine the presence and position of an arrow icon. (<code>'x-btn-arrow'</code> or <code>'x-btn-arrow-bottom'</code> or <code>''</code>)</li>
+ * <li><code>cls</code> : A CSS class name applied to the Button's main <tbody> element which determines the button's scale and icon alignment.</li>
+ * <li><code>text</code> : The {@link #text} to display ion the Button.</li>
+ * <li><code>tabIndex</code> : The tab index within the input flow.</li>
+ * </ul></div>
+ * @return {Array} Substitution data for a Template.
+ */
+ getTemplateArgs: function() {
+ var me = this,
+ persistentPadding = me.getPersistentBtnPadding(),
+ innerSpanStyle = '';
+
+ // Create negative margin offsets to counteract persistent button padding if needed
+ if (Math.max.apply(Math, persistentPadding) > 0) {
+ innerSpanStyle = 'margin:' + Ext.Array.map(persistentPadding, function(pad) {
+ return -pad + 'px';
+ }).join(' ');
+ }
+
+ return {
+ href : me.getHref(),
+ target : me.target || '_blank',
+ type : me.type,
+ splitCls : me.getSplitCls(),
+ cls : me.cls,
+ text : me.text || ' ',
+ tabIndex : me.tabIndex,
+ innerSpanStyle: innerSpanStyle
+ };
+ },
+
+ /**
+ * @private
+ * If there is a configured href for this Button, returns the href with parameters appended.
+ * @returns The href string with parameters appended.
+ */
+ getHref: function() {
+ var me = this;
+ return me.href ? Ext.urlAppend(me.href, me.params + Ext.Object.toQueryString(Ext.apply(Ext.apply({}, me.baseParams)))) : false;
+ },
+
+ /**
+ * <p><b>Only valid if the Button was originally configured with a {@link #url}</b></p>
+ * <p>Sets the href of the link dynamically according to the params passed, and any {@link #baseParams} configured.</p>
+ * @param {Object} Parameters to use in the href URL.
+ */
+ setParams: function(p) {
+ this.params = p;
+ this.btnEl.dom.href = this.getHref();
+ },
+
+ getSplitCls: function() {
+ var me = this;
+ return me.split ? (me.baseCls + '-' + me.arrowCls) + ' ' + (me.baseCls + '-' + me.arrowCls + '-' + me.arrowAlign) : '';
+ },
+
+ // private
+ afterRender: function() {
+ var me = this;
+ me.useSetClass = true;
+ me.setButtonCls();
+ me.doc = Ext.getDoc();
+ this.callParent(arguments);
+ },
+
+ /**
+ * Sets the CSS class that provides a background image to use as the button's icon. This method also changes
+ * the value of the {@link #iconCls} config internally.
+ * @param {String} cls The CSS class providing the icon image
+ * @return {Ext.button.Button} this
+ */
+ setIconCls: function(cls) {
+ var me = this,
+ btnInnerEl = me.btnInnerEl;
+ if (btnInnerEl) {
+ // Remove the previous iconCls from the button
+ btnInnerEl.removeCls(me.iconCls);
+ btnInnerEl.addCls(cls || '');
+ me.setButtonCls();
+ }
+ me.iconCls = cls;
+ return me;
+ },
+
+ /**
+ * Sets the tooltip for this Button.
+ * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
+ * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
+ * <li><b>Object</b> : A configuration object for {@link Ext.tip.QuickTipManager#register}.</li>
+ * </ul></div>
+ * @return {Ext.button.Button} this
+ */
+ setTooltip: function(tooltip, initial) {
+ var me = this;
+
+ if (me.rendered) {
+ if (!initial) {
+ me.clearTip();
+ }
+ if (Ext.isObject(tooltip)) {
+ Ext.tip.QuickTipManager.register(Ext.apply({
+ target: me.btnEl.id
+ },
+ tooltip));
+ me.tooltip = tooltip;
+ } else {
+ me.btnEl.dom.setAttribute('data-' + this.tooltipType, tooltip);
+ }
+ } else {
+ me.tooltip = tooltip;
+ }
+ return me;
+ },
+
+ // private
+ getRefItems: function(deep){
+ var menu = this.menu,
+ items;
+
+ if (menu) {
+ items = menu.getRefItems(deep);
+ items.unshift(menu);
+ }
+ return items || [];
+ },
+
+ // private
+ clearTip: function() {
+ if (Ext.isObject(this.tooltip)) {
+ Ext.tip.QuickTipManager.unregister(this.btnEl);
+ }
+ },
+
+ // private
+ beforeDestroy: function() {
+ var me = this;
+ if (me.rendered) {
+ me.clearTip();
+ }
+ if (me.menu && me.destroyMenu !== false) {
+ Ext.destroy(me.btnEl, me.btnInnerEl, me.menu);
+ }
+ Ext.destroy(me.repeater);
+ },
+
+ // private
+ onDestroy: function() {
+ var me = this;
+ if (me.rendered) {
+ me.doc.un('mouseover', me.monitorMouseOver, me);
+ me.doc.un('mouseup', me.onMouseUp, me);
+ delete me.doc;
+ delete me.btnEl;
+ delete me.btnInnerEl;
+ Ext.ButtonToggleManager.unregister(me);
+
+ Ext.destroy(me.keyMap);
+ delete me.keyMap;
+ }
+ me.callParent();
+ },
+
+ /**
+ * Assigns this Button's click handler
+ * @param {Function} handler The function to call when the button is clicked
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
+ * Defaults to this Button.
+ * @return {Ext.button.Button} this
+ */
+ setHandler: function(handler, scope) {
+ this.handler = handler;
+ this.scope = scope;
+ return this;
+ },
+
+ /**
+ * Sets this Button's text
+ * @param {String} text The button text
+ * @return {Ext.button.Button} this
+ */
+ setText: function(text) {
+ var me = this;
+ me.text = text;
+ if (me.el) {
+ me.btnInnerEl.update(text || ' ');
+ me.setButtonCls();
+ }
+ me.doComponentLayout();
+ return me;
+ },
+
+ /**
+ * Sets the background image (inline style) of the button. This method also changes
+ * the value of the {@link #icon} config internally.
+ * @param {String} icon The path to an image to display in the button
+ * @return {Ext.button.Button} this
+ */
+ setIcon: function(icon) {
+ var me = this,
+ btnInnerEl = me.btnInnerEl;
+ me.icon = icon;
+ if (btnInnerEl) {
+ btnInnerEl.setStyle('background-image', icon ? 'url(' + icon + ')': '');
+ me.setButtonCls();
+ }
+ return me;
+ },
+
+ /**
+ * Gets the text for this Button
+ * @return {String} The button text
+ */
+ getText: function() {
+ return this.text;
+ },
+
+ /**
+ * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
+ * @param {Boolean} state (optional) Force a particular state
+ * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
+ * @return {Ext.button.Button} this
+ */
+ toggle: function(state, suppressEvent) {
+ var me = this;
+ state = state === undefined ? !me.pressed: !!state;
+ if (state !== me.pressed) {
+ if (me.rendered) {
+ me[state ? 'addClsWithUI': 'removeClsWithUI'](me.pressedCls);
+ }
+ me.btnEl.dom.setAttribute('aria-pressed', state);
+ me.pressed = state;
+ if (!suppressEvent) {
+ me.fireEvent('toggle', me, state);
+ Ext.callback(me.toggleHandler, me.scope || me, [me, state]);
+ }
+ }
+ return me;
+ },
+
+ /**
+ * Show this button's menu (if it has one)
+ */
+ showMenu: function() {
+ var me = this;
+ if (me.rendered && me.menu) {
+ if (me.tooltip) {
+ Ext.tip.QuickTipManager.getQuickTip().cancelShow(me.btnEl);
+ }
+ if (me.menu.isVisible()) {
+ me.menu.hide();
+ }
+
+ me.menu.showBy(me.el, me.menuAlign);
+ }
+ return me;
+ },
+
+ /**
+ * Hide this button's menu (if it has one)
+ */
+ hideMenu: function() {
+ if (this.hasVisibleMenu()) {
+ this.menu.hide();
+ }
+ return this;
+ },
+
+ /**
+ * Returns true if the button has a menu and it is visible
+ * @return {Boolean}
+ */
+ hasVisibleMenu: function() {
+ var menu = this.menu;
+ return menu && menu.rendered && menu.isVisible();
+ },
+
+ // private
+ onRepeatClick: function(repeat, e) {
+ this.onClick(e);
+ },
+
+ // private
+ onClick: function(e) {
+ var me = this;
+ if (me.preventDefault || (me.disabled && me.getHref()) && e) {
+ e.preventDefault();
+ }
+ if (e.button !== 0) {
+ return;
+ }
+ if (!me.disabled) {
+ if (me.enableToggle && (me.allowDepress !== false || !me.pressed)) {
+ me.toggle();
+ }
+ if (me.menu && !me.hasVisibleMenu() && !me.ignoreNextClick) {
+ me.showMenu();
+ }
+ me.fireEvent('click', me, e);
+ if (me.handler) {
+ me.handler.call(me.scope || me, me, e);
+ }
+ me.onBlur();
+ }
+ },
+
+ /**
+ * @private mouseover handler called when a mouseover event occurs anywhere within the encapsulating element.
+ * The targets are interrogated to see what is being entered from where.
+ * @param e
+ */
+ onMouseOver: function(e) {
+ var me = this;
+ if (!me.disabled && !e.within(me.el, true, true)) {
+ me.onMouseEnter(e);
+ }
+ },
+
+ /**
+ * @private mouseout handler called when a mouseout event occurs anywhere within the encapsulating element -
+ * or the mouse leaves the encapsulating element.
+ * The targets are interrogated to see what is being exited to where.
+ * @param e
+ */
+ onMouseOut: function(e) {
+ var me = this;
+ if (!e.within(me.el, true, true)) {
+ if (me.overMenuTrigger) {
+ me.onMenuTriggerOut(e);
+ }
+ me.onMouseLeave(e);
+ }
+ },
+
+ /**
+ * @private mousemove handler called when the mouse moves anywhere within the encapsulating element.
+ * The position is checked to determine if the mouse is entering or leaving the trigger area. Using
+ * mousemove to check this is more resource intensive than we'd like, but it is necessary because
+ * the trigger area does not line up exactly with sub-elements so we don't always get mouseover/out
+ * events when needed. In the future we should consider making the trigger a separate element that
+ * is absolutely positioned and sized over the trigger area.
+ */
+ onMouseMove: function(e) {
+ var me = this,
+ el = me.el,
+ over = me.overMenuTrigger,
+ overlap, btnSize;
+
+ if (me.split) {
+ if (me.arrowAlign === 'right') {
+ overlap = e.getX() - el.getX();
+ btnSize = el.getWidth();
+ } else {
+ overlap = e.getY() - el.getY();
+ btnSize = el.getHeight();
+ }
+
+ if (overlap > (btnSize - me.getTriggerSize())) {
+ if (!over) {
+ me.onMenuTriggerOver(e);
+ }
+ } else {
+ if (over) {
+ me.onMenuTriggerOut(e);
+ }
+ }
+ }
+ },
+
+ /**
+ * @private Measures the size of the trigger area for menu and split buttons. Will be a width for
+ * a right-aligned trigger and a height for a bottom-aligned trigger. Cached after first measurement.
+ */
+ getTriggerSize: function() {
+ var me = this,
+ size = me.triggerSize,
+ side, sideFirstLetter, undef;
+
+ if (size === undef) {
+ side = me.arrowAlign;
+ sideFirstLetter = side.charAt(0);
+ size = me.triggerSize = me.el.getFrameWidth(sideFirstLetter) + me.btnWrap.getFrameWidth(sideFirstLetter) + (me.frameSize && me.frameSize[side] || 0);
+ }
+ return size;
+ },
+
+ /**
+ * @private virtual mouseenter handler called when it is detected that the mouseout event
+ * signified the mouse entering the encapsulating element.
+ * @param e
+ */
+ onMouseEnter: function(e) {
+ var me = this;
+ me.addClsWithUI(me.overCls);
+ me.fireEvent('mouseover', me, e);
+ },
+
+ /**
+ * @private virtual mouseleave handler called when it is detected that the mouseover event
+ * signified the mouse entering the encapsulating element.
+ * @param e
+ */
+ onMouseLeave: function(e) {
+ var me = this;
+ me.removeClsWithUI(me.overCls);
+ me.fireEvent('mouseout', me, e);
+ },
+
+ /**
+ * @private virtual mouseenter handler called when it is detected that the mouseover event
+ * signified the mouse entering the arrow area of the button - the <em>.
+ * @param e
+ */
+ onMenuTriggerOver: function(e) {
+ var me = this;
+ me.overMenuTrigger = true;
+ me.fireEvent('menutriggerover', me, me.menu, e);
+ },
+
+ /**
+ * @private virtual mouseleave handler called when it is detected that the mouseout event
+ * signified the mouse leaving the arrow area of the button - the <em>.
+ * @param e
+ */
+ onMenuTriggerOut: function(e) {
+ var me = this;
+ delete me.overMenuTrigger;
+ me.fireEvent('menutriggerout', me, me.menu, e);
+ },
+
+ // inherit docs
+ enable : function(silent) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ me.removeClsWithUI('disabled');
+
+ return me;
+ },
+
+ // inherit docs
+ disable : function(silent) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ me.addClsWithUI('disabled');
+
+ return me;
+ },
+
+ /**
+ * Method to change the scale of the button. See {@link #scale} for allowed configurations.
+ * @param {String} scale The scale to change to.
+ */
+ setScale: function(scale) {
+ var me = this,
+ ui = me.ui.replace('-' + me.scale, '');
+
+ //check if it is an allowed scale
+ if (!Ext.Array.contains(me.allowedScales, scale)) {
+ throw('#setScale: scale must be an allowed scale (' + me.allowedScales.join(', ') + ')');
+ }
+
+ me.scale = scale;
+ me.setUI(ui);
+ },
+
+ // inherit docs
+ setUI: function(ui) {
+ var me = this;
+
+ //we need to append the scale to the UI, if not already done
+ if (me.scale && !ui.match(me.scale)) {
+ ui = ui + '-' + me.scale;
+ }
+
+ me.callParent([ui]);
+
+ // Set all the state classNames, as they need to include the UI
+ // me.disabledCls += ' ' + me.baseCls + '-' + me.ui + '-disabled';
+ },
+
+ // private
+ onFocus: function(e) {
+ var me = this;
+ if (!me.disabled) {
+ me.addClsWithUI(me.focusCls);
+ }
+ },
+
+ // private
+ onBlur: function(e) {
+ var me = this;
+ me.removeClsWithUI(me.focusCls);
+ },
+
+ // private
+ onMouseDown: function(e) {
+ var me = this;
+ if (!me.disabled && e.button === 0) {
+ me.addClsWithUI(me.pressedCls);
+ me.doc.on('mouseup', me.onMouseUp, me);
+ }
+ },
+ // private
+ onMouseUp: function(e) {
+ var me = this;
+ if (e.button === 0) {
+ if (!me.pressed) {
+ me.removeClsWithUI(me.pressedCls);
+ }
+ me.doc.un('mouseup', me.onMouseUp, me);
+ }
+ },
+ // private
+ onMenuShow: function(e) {
+ var me = this;
+ me.ignoreNextClick = 0;
+ me.addClsWithUI(me.menuActiveCls);
+ me.fireEvent('menushow', me, me.menu);
+ },
+
+ // private
+ onMenuHide: function(e) {
+ var me = this;
+ me.removeClsWithUI(me.menuActiveCls);
+ me.ignoreNextClick = Ext.defer(me.restoreClick, 250, me);
+ me.fireEvent('menuhide', me, me.menu);
+ },
+
+ // private
+ restoreClick: function() {
+ this.ignoreNextClick = 0;
+ },
+
+ // private
+ onDownKey: function() {
+ var me = this;
+
+ if (!me.disabled) {
+ if (me.menu) {
+ me.showMenu();
+ }
+ }
+ },
+
+ /**
+ * @private Some browsers (notably Safari and older Chromes on Windows) add extra "padding" inside the button
+ * element that cannot be removed. This method returns the size of that padding with a one-time detection.
+ * @return Array [top, right, bottom, left]
+ */
+ getPersistentBtnPadding: function() {
+ var cls = Ext.button.Button,
+ padding = cls.persistentPadding,
+ btn, leftTop, btnEl, btnInnerEl;
+
+ if (!padding) {
+ padding = cls.persistentPadding = [0, 0, 0, 0]; //set early to prevent recursion
+
+ if (!Ext.isIE) { //short-circuit IE as it sometimes gives false positive for padding
+ // Create auto-size button offscreen and measure its insides
+ btn = Ext.create('Ext.button.Button', {
+ renderTo: Ext.getBody(),
+ text: 'test',
+ style: 'position:absolute;top:-999px;'
+ });
+ btnEl = btn.btnEl;
+ btnInnerEl = btn.btnInnerEl;
+ btnEl.setSize(null, null); //clear any hard dimensions on the button el to see what it does naturally
+
+ leftTop = btnInnerEl.getOffsetsTo(btnEl);
+ padding[0] = leftTop[1];
+ padding[1] = btnEl.getWidth() - btnInnerEl.getWidth() - leftTop[0];
+ padding[2] = btnEl.getHeight() - btnInnerEl.getHeight() - leftTop[1];
+ padding[3] = leftTop[0];
+
+ btn.destroy();
+ }
+ }
+
+ return padding;
+ }
+
+}, function() {
+ var groups = {},
+ g, i, l;
+
+ function toggleGroup(btn, state) {
+ if (state) {
+ g = groups[btn.toggleGroup];
+ for (i = 0, l = g.length; i < l; i++) {
+ if (g[i] !== btn) {
+ g[i].toggle(false);
+ }
+ }
+ }
+ }
+ // Private utility class used by Button
+ Ext.ButtonToggleManager = {
+ register: function(btn) {
+ if (!btn.toggleGroup) {
+ return;
+ }
+ var group = groups[btn.toggleGroup];
+ if (!group) {
+ group = groups[btn.toggleGroup] = [];
+ }
+ group.push(btn);
+ btn.on('toggle', toggleGroup);
+ },
+
+ unregister: function(btn) {
+ if (!btn.toggleGroup) {
+ return;
+ }
+ var group = groups[btn.toggleGroup];
+ if (group) {
+ Ext.Array.remove(group, btn);
+ btn.un('toggle', toggleGroup);
+ }
+ },
+
+ /**
+ * Gets the pressed button in the passed group or null
+ * @param {String} group
+ * @return Button
+ */
+ getPressed: function(group) {
+ var g = groups[group],
+ i = 0,
+ len;
+ if (g) {
+ for (len = g.length; i < len; i++) {
+ if (g[i].pressed === true) {
+ return g[i];
+ }
+ }
+ }
+ return null;
+ }
+ };
+});
+
+/**
+ * @class Ext.layout.container.boxOverflow.Menu
+ * @extends Ext.layout.container.boxOverflow.None
+ * @private
+ */
+Ext.define('Ext.layout.container.boxOverflow.Menu', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.layout.container.boxOverflow.None',
+ requires: ['Ext.toolbar.Separator', 'Ext.button.Button'],
+ alternateClassName: 'Ext.layout.boxOverflow.Menu',
+
+ /* End Definitions */
+
+ /**
+ * @cfg {String} afterCtCls
+ * CSS class added to the afterCt element. This is the element that holds any special items such as scrollers,
+ * which must always be present at the rightmost edge of the Container
+ */
+
+ /**
+ * @property noItemsMenuText
+ * @type String
+ * HTML fragment to render into the toolbar overflow menu if there are no items to display
+ */
+ noItemsMenuText : '<div class="' + Ext.baseCSSPrefix + 'toolbar-no-items">(None)</div>',
+
+ constructor: function(layout) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ // Before layout, we need to re-show all items which we may have hidden due to a previous overflow.
+ layout.beforeLayout = Ext.Function.createInterceptor(layout.beforeLayout, this.clearOverflow, this);
+
+ me.afterCtCls = me.afterCtCls || Ext.baseCSSPrefix + 'box-menu-' + layout.parallelAfter;
+ /**
+ * @property menuItems
+ * @type Array
+ * Array of all items that are currently hidden and should go into the dropdown menu
+ */
+ me.menuItems = [];
+ },
+
+ handleOverflow: function(calculations, targetSize) {
+ var me = this,
+ layout = me.layout,
+ methodName = 'get' + layout.parallelPrefixCap,
+ newSize = {},
+ posArgs = [null, null];
+
+ me.callParent(arguments);
+ this.createMenu(calculations, targetSize);
+ newSize[layout.perpendicularPrefix] = targetSize[layout.perpendicularPrefix];
+ newSize[layout.parallelPrefix] = targetSize[layout.parallelPrefix] - me.afterCt[methodName]();
+
+ // Center the menuTrigger button.
+ // TODO: Should we emulate align: 'middle' like this, or should we 'stretchmax' the menuTrigger?
+ posArgs[layout.perpendicularSizeIndex] = (calculations.meta.maxSize - me.menuTrigger['get' + layout.perpendicularPrefixCap]()) / 2;
+ me.menuTrigger.setPosition.apply(me.menuTrigger, posArgs);
+
+ return { targetSize: newSize };
+ },
+
+ /**
+ * @private
+ * Called by the layout, when it determines that there is no overflow.
+ * Also called as an interceptor to the layout's onLayout method to reshow
+ * previously hidden overflowing items.
+ */
+ clearOverflow: function(calculations, targetSize) {
+ var me = this,
+ newWidth = targetSize ? targetSize.width + (me.afterCt ? me.afterCt.getWidth() : 0) : 0,
+ items = me.menuItems,
+ i = 0,
+ length = items.length,
+ item;
+
+ me.hideTrigger();
+ for (; i < length; i++) {
+ items[i].show();
+ }
+ items.length = 0;
+
+ return targetSize ? {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ }
+ } : null;
+ },
+
+ /**
+ * @private
+ */
+ showTrigger: function() {
+ this.menuTrigger.show();
+ },
+
+ /**
+ * @private
+ */
+ hideTrigger: function() {
+ if (this.menuTrigger != undefined) {
+ this.menuTrigger.hide();
+ }
+ },
+
+ /**
+ * @private
+ * Called before the overflow menu is shown. This constructs the menu's items, caching them for as long as it can.
+ */
+ beforeMenuShow: function(menu) {
+ var me = this,
+ items = me.menuItems,
+ i = 0,
+ len = items.length,
+ item,
+ prev;
+
+ var needsSep = function(group, prev){
+ return group.isXType('buttongroup') && !(prev instanceof Ext.toolbar.Separator);
+ };
+
+ me.clearMenu();
+ menu.removeAll();
+
+ for (; i < len; i++) {
+ item = items[i];
+
+ // Do not show a separator as a first item
+ if (!i && (item instanceof Ext.toolbar.Separator)) {
+ continue;
+ }
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
+ }
+
+ me.addComponentToMenu(menu, item);
+ prev = item;
+ }
+
+ // put something so the menu isn't empty if no compatible items found
+ if (menu.items.length < 1) {
+ menu.add(me.noItemsMenuText);
+ }
+ },
+
+ /**
+ * @private
+ * Returns a menu config for a given component. This config is used to create a menu item
+ * to be added to the expander menu
+ * @param {Ext.Component} component The component to create the config for
+ * @param {Boolean} hideOnClick Passed through to the menu item
+ */
+ createMenuConfig : function(component, hideOnClick) {
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick,
+ destroyMenu: false
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
+ }
+ });
+ }
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+ return config;
+ },
+
+ /**
+ * @private
+ * Adds the given Toolbar item to the given menu. Buttons inside a buttongroup are added individually.
+ * @param {Ext.menu.Menu} menu The menu to add to
+ * @param {Ext.Component} component The component to add
+ */
+ addComponentToMenu : function(menu, component) {
+ var me = this;
+ if (component instanceof Ext.toolbar.Separator) {
+ menu.add('-');
+ } else if (component.isComponent) {
+ if (component.isXType('splitbutton')) {
+ menu.add(me.createMenuConfig(component, true));
+
+ } else if (component.isXType('button')) {
+ menu.add(me.createMenuConfig(component, !component.menu));
+
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ me.addComponentToMenu(menu, item);
+ });
+ } else {
+ menu.add(Ext.create(Ext.getClassName(component), me.createMenuConfig(component)));
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Deletes the sub-menu of each item in the expander menu. Submenus are created for items such as
+ * splitbuttons and buttongroups, where the Toolbar item cannot be represented by a single menu item
+ */
+ clearMenu : function() {
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item) {
+ if (item.menu) {
+ delete item.menu;
+ }
+ });
+ }
+ },
+
+ /**
+ * @private
+ * Creates the overflow trigger and menu used when enableOverflow is set to true and the items
+ * in the layout are too wide to fit in the space available
+ */
+ createMenu: function(calculations, targetSize) {
+ var me = this,
+ layout = me.layout,
+ startProp = layout.parallelBefore,
+ sizeProp = layout.parallelPrefix,
+ available = targetSize[sizeProp],
+ boxes = calculations.boxes,
+ i = 0,
+ len = boxes.length,
+ box;
+
+ if (!me.menuTrigger) {
+ me.createInnerElements();
+
+ /**
+ * @private
+ * @property menu
+ * @type Ext.menu.Menu
+ * The expand menu - holds items for every item that cannot be shown
+ * because the container is currently not large enough.
+ */
+ me.menu = Ext.create('Ext.menu.Menu', {
+ hideMode: 'offsets',
+ listeners: {
+ scope: me,
+ beforeshow: me.beforeMenuShow
+ }
+ });
+
+ /**
+ * @private
+ * @property menuTrigger
+ * @type Ext.button.Button
+ * The expand button which triggers the overflow menu to be shown
+ */
+ me.menuTrigger = Ext.create('Ext.button.Button', {
+ ownerCt : me.layout.owner, // To enable the Menu to ascertain a valid zIndexManager owner in the same tree
+ iconCls : Ext.baseCSSPrefix + layout.owner.getXType() + '-more-icon',
+ ui : layout.owner instanceof Ext.toolbar.Toolbar ? 'default-toolbar' : 'default',
+ menu : me.menu,
+ getSplitCls: function() { return '';},
+ renderTo: me.afterCt
+ });
+ }
+ me.showTrigger();
+ available -= me.afterCt.getWidth();
+
+ // Hide all items which are off the end, and store them to allow them to be restored
+ // before each layout operation.
+ me.menuItems.length = 0;
+ for (; i < len; i++) {
+ box = boxes[i];
+ if (box[startProp] + box[sizeProp] > available) {
+ me.menuItems.push(box.component);
+ box.component.hide();
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Creates the beforeCt, innerCt and afterCt elements if they have not already been created
+ * @param {Ext.container.Container} container The Container attached to this Layout instance
+ * @param {Ext.core.Element} target The target Element
+ */
+ createInnerElements: function() {
+ var me = this,
+ target = me.layout.getRenderTarget();
+
+ if (!this.afterCt) {
+ target.addCls(Ext.baseCSSPrefix + me.layout.direction + '-box-overflow-body');
+ this.afterCt = target.insertSibling({cls: Ext.layout.container.Box.prototype.innerCls + ' ' + this.afterCtCls}, 'before');
+ }
+ },
+
+ /**
+ * @private
+ */
+ destroy: function() {
+ Ext.destroy(this.menu, this.menuTrigger);
+ }
+});
+/**
+ * @class Ext.util.Region
+ * @extends Object
+ *
+ * Represents a rectangular region and provides a number of utility methods
+ * to compare regions.
+ */
+
+Ext.define('Ext.util.Region', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.util.Offset'],
+
+ statics: {
+ /**
+ * @static
+ * @param {Mixed} el A string, DomElement or Ext.core.Element representing an element
+ * on the page.
+ * @returns {Ext.util.Region} region
+ * Retrieves an Ext.util.Region for a particular element.
+ */
+ getRegion: function(el) {
+ return Ext.fly(el).getPageBox(true);
+ },
+
+ /**
+ * @static
+ * @param {Object} o An object with top, right, bottom, left properties
+ * @return {Ext.util.Region} region The region constructed based on the passed object
+ */
+ from: function(o) {
+ return new this(o.top, o.right, o.bottom, o.left);
+ }
+ },
+
+ /* End Definitions */
+
+ /**
+ * @constructor
+ * @param {Number} top Top
+ * @param {Number} right Right
+ * @param {Number} bottom Bottom
+ * @param {Number} left Left
+ */
+ constructor : function(t, r, b, l) {
+ var me = this;
+ me.y = me.top = me[1] = t;
+ me.right = r;
+ me.bottom = b;
+ me.x = me.left = me[0] = l;
+ },
+
+ /**
+ * Checks if this region completely contains the region that is passed in.
+ * @param {Ext.util.Region} region
+ */
+ contains : function(region) {
+ var me = this;
+ return (region.x >= me.x &&
+ region.right <= me.right &&
+ region.y >= me.y &&
+ region.bottom <= me.bottom);
+
+ },
+
+ /**
+ * Checks if this region intersects the region passed in.
+ * @param {Ext.util.Region} region
+ * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection.
+ */
+ intersect : function(region) {
+ var me = this,
+ t = Math.max(me.y, region.y),
+ r = Math.min(me.right, region.right),
+ b = Math.min(me.bottom, region.bottom),
+ l = Math.max(me.x, region.x);
+
+ if (b > t && r > l) {
+ return new this.self(t, r, b, l);
+ }
+ else {
+ return false;
+ }
+ },
+
+ /**
+ * Returns the smallest region that contains the current AND targetRegion.
+ * @param {Ext.util.Region} region
+ */
+ union : function(region) {
+ var me = this,
+ t = Math.min(me.y, region.y),
+ r = Math.max(me.right, region.right),
+ b = Math.max(me.bottom, region.bottom),
+ l = Math.min(me.x, region.x);
+
+ return new this.self(t, r, b, l);
+ },
+
+ /**
+ * Modifies the current region to be constrained to the targetRegion.
+ * @param {Ext.util.Region} targetRegion
+ */
+ constrainTo : function(r) {
+ var me = this,
+ constrain = Ext.Number.constrain;
+ me.top = me.y = constrain(me.top, r.y, r.bottom);
+ me.bottom = constrain(me.bottom, r.y, r.bottom);
+ me.left = me.x = constrain(me.left, r.x, r.right);
+ me.right = constrain(me.right, r.x, r.right);
+ return me;
+ },
+
+ /**
+ * Modifies the current region to be adjusted by offsets.
+ * @param {Number} top top offset
+ * @param {Number} right right offset
+ * @param {Number} bottom bottom offset
+ * @param {Number} left left offset
+ */
+ adjust : function(t, r, b, l) {
+ var me = this;
+ me.top = me.y += t;
+ me.left = me.x += l;
+ me.right += r;
+ me.bottom += b;
+ return me;
+ },
+
+ /**
+ * Get the offset amount of a point outside the region
+ * @param {String} axis optional
+ * @param {Ext.util.Point} p the point
+ * @return {Ext.util.Offset}
+ */
+ getOutOfBoundOffset: function(axis, p) {
+ if (!Ext.isObject(axis)) {
+ if (axis == 'x') {
+ return this.getOutOfBoundOffsetX(p);
+ } else {
+ return this.getOutOfBoundOffsetY(p);
+ }
+ } else {
+ p = axis;
+ var d = Ext.create('Ext.util.Offset');
+ d.x = this.getOutOfBoundOffsetX(p.x);
+ d.y = this.getOutOfBoundOffsetY(p.y);
+ return d;
+ }
+
+ },
+
+ /**
+ * Get the offset amount on the x-axis
+ * @param {Number} p the offset
+ * @return {Number}
+ */
+ getOutOfBoundOffsetX: function(p) {
+ if (p <= this.x) {
+ return this.x - p;
+ } else if (p >= this.right) {
+ return this.right - p;
+ }
+
+ return 0;
+ },
+
+ /**
+ * Get the offset amount on the y-axis
+ * @param {Number} p the offset
+ * @return {Number}
+ */
+ getOutOfBoundOffsetY: function(p) {
+ if (p <= this.y) {
+ return this.y - p;
+ } else if (p >= this.bottom) {
+ return this.bottom - p;
+ }
+
+ return 0;
+ },
+
+ /**
+ * Check whether the point / offset is out of bound
+ * @param {String} axis optional
+ * @param {Ext.util.Point/Number} p the point / offset
+ * @return {Boolean}
+ */
+ isOutOfBound: function(axis, p) {
+ if (!Ext.isObject(axis)) {
+ if (axis == 'x') {
+ return this.isOutOfBoundX(p);
+ } else {
+ return this.isOutOfBoundY(p);
+ }
+ } else {
+ p = axis;
+ return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y));
+ }
+ },
+
+ /**
+ * Check whether the offset is out of bound in the x-axis
+ * @param {Number} p the offset
+ * @return {Boolean}
+ */
+ isOutOfBoundX: function(p) {
+ return (p < this.x || p > this.right);
+ },
+
+ /**
+ * Check whether the offset is out of bound in the y-axis
+ * @param {Number} p the offset
+ * @return {Boolean}
+ */
+ isOutOfBoundY: function(p) {
+ return (p < this.y || p > this.bottom);
+ },
+
+ /*
+ * Restrict a point within the region by a certain factor.
+ * @param {String} axis Optional
+ * @param {Ext.util.Point/Ext.util.Offset/Object} p
+ * @param {Number} factor
+ * @return {Ext.util.Point/Ext.util.Offset/Object/Number}
+ */
+ restrict: function(axis, p, factor) {
+ if (Ext.isObject(axis)) {
+ var newP;
+
+ factor = p;
+ p = axis;
+
+ if (p.copy) {
+ newP = p.copy();
+ }
+ else {
+ newP = {
+ x: p.x,
+ y: p.y
+ };
+ }
+
+ newP.x = this.restrictX(p.x, factor);
+ newP.y = this.restrictY(p.y, factor);
+ return newP;
+ } else {
+ if (axis == 'x') {
+ return this.restrictX(p, factor);
+ } else {
+ return this.restrictY(p, factor);
+ }
+ }
+ },
+
+ /*
+ * Restrict an offset within the region by a certain factor, on the x-axis
+ * @param {Number} p
+ * @param {Number} factor The factor, optional, defaults to 1
+ * @return
+ */
+ restrictX : function(p, factor) {
+ if (!factor) {
+ factor = 1;
+ }
+
+ if (p <= this.x) {
+ p -= (p - this.x) * factor;
+ }
+ else if (p >= this.right) {
+ p -= (p - this.right) * factor;
+ }
+ return p;
+ },
+
+ /*
+ * Restrict an offset within the region by a certain factor, on the y-axis
+ * @param {Number} p
+ * @param {Number} factor The factor, optional, defaults to 1
+ */
+ restrictY : function(p, factor) {
+ if (!factor) {
+ factor = 1;
+ }
+
+ if (p <= this.y) {
+ p -= (p - this.y) * factor;
+ }
+ else if (p >= this.bottom) {
+ p -= (p - this.bottom) * factor;
+ }
+ return p;
+ },
+
+ /*
+ * Get the width / height of this region
+ * @return {Object} an object with width and height properties
+ */
+ getSize: function() {
+ return {
+ width: this.right - this.x,
+ height: this.bottom - this.y
+ };
+ },
+
+ /**
+ * Copy a new instance
+ * @return {Ext.util.Region}
+ */
+ copy: function() {
+ return new this.self(this.y, this.right, this.bottom, this.x);
+ },
+
+ /**
+ * Copy the values of another Region to this Region
+ * @param {Region} The region to copy from.
+ * @return {Ext.util.Point} this This point
+ */
+ copyFrom: function(p) {
+ var me = this;
+ me.top = me.y = me[1] = p.y;
+ me.right = p.right;
+ me.bottom = p.bottom;
+ me.left = me.x = me[0] = p.x;
+
+ return this;
+ },
+
+ /**
+ * Dump this to an eye-friendly string, great for debugging
+ * @return {String}
+ */
+ toString: function() {
+ return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]";
+ },
+
+
+ /**
+ * Translate this region by the given offset amount
+ * @param {Ext.util.Offset/Object} offset Object containing the <code>x</code> and <code>y</code> properties.
+ * Or the x value is using the two argument form.
+ * @param {Number} The y value unless using an Offset object.
+ * @return {Ext.util.Region} this This Region
+ */
+ translateBy: function(x, y) {
+ if (arguments.length == 1) {
+ y = x.y;
+ x = x.x;
+ }
+ var me = this;
+ me.top = me.y += y;
+ me.right += x;
+ me.bottom += y;
+ me.left = me.x += x;
+
+ return me;
+ },
+
+ /**
+ * Round all the properties of this region
+ * @return {Ext.util.Region} this This Region
+ */
+ round: function() {
+ var me = this;
+ me.top = me.y = Math.round(me.y);
+ me.right = Math.round(me.right);
+ me.bottom = Math.round(me.bottom);
+ me.left = me.x = Math.round(me.x);
+
+ return me;
+ },
+
+ /**
+ * Check whether this region is equivalent to the given region
+ * @param {Ext.util.Region} region The region to compare with
+ * @return {Boolean}
+ */
+ equals: function(region) {
+ return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left);
+ }
+});
+
+/*
+ * This is a derivative of the similarly named class in the YUI Library.
+ * The original license:
+ * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License:
+ * http://developer.yahoo.net/yui/license.txt
+ */
+
+
+/**
+ * @class Ext.dd.DragDropManager
+ * DragDropManager is a singleton that tracks the element interaction for
+ * all DragDrop items in the window. Generally, you will not call
+ * this class directly, but it does have helper methods that could
+ * be useful in your DragDrop implementations.
+ * @singleton
+ */
+Ext.define('Ext.dd.DragDropManager', {
+ singleton: true,
+
+ requires: ['Ext.util.Region'],
+
+ uses: ['Ext.tip.QuickTipManager'],
+
+ // shorter ClassName, to save bytes and use internally
+ alternateClassName: ['Ext.dd.DragDropMgr', 'Ext.dd.DDM'],
+
+ /**
+ * Two dimensional Array of registered DragDrop objects. The first
+ * dimension is the DragDrop item group, the second the DragDrop
+ * object.
+ * @property ids
+ * @type String[]
+ * @private
+ * @static
+ */
+ ids: {},
+
+ /**
+ * Array of element ids defined as drag handles. Used to determine
+ * if the element that generated the mousedown event is actually the
+ * handle and not the html element itself.
+ * @property handleIds
+ * @type String[]
+ * @private
+ * @static
+ */
+ handleIds: {},
+
+ /**
+ * the DragDrop object that is currently being dragged
+ * @property dragCurrent
+ * @type DragDrop
+ * @private
+ * @static
+ **/
+ dragCurrent: null,
+
+ /**
+ * the DragDrop object(s) that are being hovered over
+ * @property dragOvers
+ * @type Array
+ * @private
+ * @static
+ */
+ dragOvers: {},
+
+ /**
+ * the X distance between the cursor and the object being dragged
+ * @property deltaX
+ * @type int
+ * @private
+ * @static
+ */
+ deltaX: 0,
+
+ /**
+ * the Y distance between the cursor and the object being dragged
+ * @property deltaY
+ * @type int
+ * @private
+ * @static
+ */
+ deltaY: 0,
+
+ /**
+ * Flag to determine if we should prevent the default behavior of the
+ * events we define. By default this is true, but this can be set to
+ * false if you need the default behavior (not recommended)
+ * @property preventDefault
+ * @type boolean
+ * @static
+ */
+ preventDefault: true,
+
+ /**
+ * Flag to determine if we should stop the propagation of the events
+ * we generate. This is true by default but you may want to set it to
+ * false if the html element contains other features that require the
+ * mouse click.
+ * @property stopPropagation
+ * @type boolean
+ * @static
+ */
+ stopPropagation: true,
+
+ /**
+ * Internal flag that is set to true when drag and drop has been
+ * intialized
+ * @property initialized
+ * @private
+ * @static
+ */
+ initialized: false,
+
+ /**
+ * All drag and drop can be disabled.
+ * @property locked
+ * @private
+ * @static
+ */
+ locked: false,
+
+ /**
+ * Called the first time an element is registered.
+ * @method init
+ * @private
+ * @static
+ */
+ init: function() {
+ this.initialized = true;
+ },
+
+ /**
+ * In point mode, drag and drop interaction is defined by the
+ * location of the cursor during the drag/drop
+ * @property POINT
+ * @type int
+ * @static
+ */
+ POINT: 0,
+
+ /**
+ * In intersect mode, drag and drop interaction is defined by the
+ * overlap of two or more drag and drop objects.
+ * @property INTERSECT
+ * @type int
+ * @static
+ */
+ INTERSECT: 1,
+
+ /**
+ * The current drag and drop mode. Default: POINT
+ * @property mode
+ * @type int
+ * @static
+ */
+ mode: 0,
+
+ /**
+ * Runs method on all drag and drop objects
+ * @method _execOnAll
+ * @private
+ * @static
+ */
+ _execOnAll: function(sMethod, args) {
+ for (var i in this.ids) {
+ for (var j in this.ids[i]) {
+ var oDD = this.ids[i][j];
+ if (! this.isTypeOfDD(oDD)) {
+ continue;
+ }
+ oDD[sMethod].apply(oDD, args);
+ }
+ }
+ },
+
+ /**
+ * Drag and drop initialization. Sets up the global event handlers
+ * @method _onLoad
+ * @private
+ * @static
+ */
+ _onLoad: function() {
+
+ this.init();
+
+ var Event = Ext.EventManager;
+ Event.on(document, "mouseup", this.handleMouseUp, this, true);
+ Event.on(document, "mousemove", this.handleMouseMove, this, true);
+ Event.on(window, "unload", this._onUnload, this, true);
+ Event.on(window, "resize", this._onResize, this, true);
+ // Event.on(window, "mouseout", this._test);
+
+ },
+
+ /**
+ * Reset constraints on all drag and drop objs
+ * @method _onResize
+ * @private
+ * @static
+ */
+ _onResize: function(e) {
+ this._execOnAll("resetConstraints", []);
+ },
+
+ /**
+ * Lock all drag and drop functionality
+ * @method lock
+ * @static
+ */
+ lock: function() { this.locked = true; },
+
+ /**
+ * Unlock all drag and drop functionality
+ * @method unlock
+ * @static
+ */
+ unlock: function() { this.locked = false; },
+
+ /**
+ * Is drag and drop locked?
+ * @method isLocked
+ * @return {boolean} True if drag and drop is locked, false otherwise.
+ * @static
+ */
+ isLocked: function() { return this.locked; },
+
+ /**
+ * Location cache that is set for all drag drop objects when a drag is
+ * initiated, cleared when the drag is finished.
+ * @property locationCache
+ * @private
+ * @static
+ */
+ locationCache: {},
+
+ /**
+ * Set useCache to false if you want to force object the lookup of each
+ * drag and drop linked element constantly during a drag.
+ * @property useCache
+ * @type boolean
+ * @static
+ */
+ useCache: true,
+
+ /**
+ * The number of pixels that the mouse needs to move after the
+ * mousedown before the drag is initiated. Default=3;
+ * @property clickPixelThresh
+ * @type int
+ * @static
+ */
+ clickPixelThresh: 3,
+
+ /**
+ * The number of milliseconds after the mousedown event to initiate the
+ * drag if we don't get a mouseup event. Default=350
+ * @property clickTimeThresh
+ * @type int
+ * @static
+ */
+ clickTimeThresh: 350,
+
+ /**
+ * Flag that indicates that either the drag pixel threshold or the
+ * mousdown time threshold has been met
+ * @property dragThreshMet
+ * @type boolean
+ * @private
+ * @static
+ */
+ dragThreshMet: false,
+
+ /**
+ * Timeout used for the click time threshold
+ * @property clickTimeout
+ * @type Object
+ * @private
+ * @static
+ */
+ clickTimeout: null,
+
+ /**
+ * The X position of the mousedown event stored for later use when a
+ * drag threshold is met.
+ * @property startX
+ * @type int
+ * @private
+ * @static
+ */
+ startX: 0,
+
+ /**
+ * The Y position of the mousedown event stored for later use when a
+ * drag threshold is met.
+ * @property startY
+ * @type int
+ * @private
+ * @static
+ */
+ startY: 0,
+
+ /**
+ * Each DragDrop instance must be registered with the DragDropManager.
+ * This is executed in DragDrop.init()
+ * @method regDragDrop
+ * @param {DragDrop} oDD the DragDrop object to register
+ * @param {String} sGroup the name of the group this element belongs to
+ * @static
+ */
+ regDragDrop: function(oDD, sGroup) {
+ if (!this.initialized) { this.init(); }
+
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+ this.ids[sGroup][oDD.id] = oDD;
+ },
+
+ /**
+ * Removes the supplied dd instance from the supplied group. Executed
+ * by DragDrop.removeFromGroup, so don't call this function directly.
+ * @method removeDDFromGroup
+ * @private
+ * @static
+ */
+ removeDDFromGroup: function(oDD, sGroup) {
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+
+ var obj = this.ids[sGroup];
+ if (obj && obj[oDD.id]) {
+ delete obj[oDD.id];
+ }
+ },
+
+ /**
+ * Unregisters a drag and drop item. This is executed in
+ * DragDrop.unreg, use that method instead of calling this directly.
+ * @method _remove
+ * @private
+ * @static
+ */
+ _remove: function(oDD) {
+ for (var g in oDD.groups) {
+ if (g && this.ids[g] && this.ids[g][oDD.id]) {
+ delete this.ids[g][oDD.id];
+ }
+ }
+ delete this.handleIds[oDD.id];
+ },
+
+ /**
+ * Each DragDrop handle element must be registered. This is done
+ * automatically when executing DragDrop.setHandleElId()
+ * @method regHandle
+ * @param {String} sDDId the DragDrop id this element is a handle for
+ * @param {String} sHandleId the id of the element that is the drag
+ * handle
+ * @static
+ */
+ regHandle: function(sDDId, sHandleId) {
+ if (!this.handleIds[sDDId]) {
+ this.handleIds[sDDId] = {};
+ }
+ this.handleIds[sDDId][sHandleId] = sHandleId;
+ },
+
+ /**
+ * Utility function to determine if a given element has been
+ * registered as a drag drop item.
+ * @method isDragDrop
+ * @param {String} id the element id to check
+ * @return {boolean} true if this element is a DragDrop item,
+ * false otherwise
+ * @static
+ */
+ isDragDrop: function(id) {
+ return ( this.getDDById(id) ) ? true : false;
+ },
+
+ /**
+ * Returns the drag and drop instances that are in all groups the
+ * passed in instance belongs to.
+ * @method getRelated
+ * @param {DragDrop} p_oDD the obj to get related data for
+ * @param {boolean} bTargetsOnly if true, only return targetable objs
+ * @return {DragDrop[]} the related instances
+ * @static
+ */
+ getRelated: function(p_oDD, bTargetsOnly) {
+ var oDDs = [];
+ for (var i in p_oDD.groups) {
+ for (var j in this.ids[i]) {
+ var dd = this.ids[i][j];
+ if (! this.isTypeOfDD(dd)) {
+ continue;
+ }
+ if (!bTargetsOnly || dd.isTarget) {
+ oDDs[oDDs.length] = dd;
+ }
+ }
+ }
+
+ return oDDs;
+ },
+
+ /**
+ * Returns true if the specified dd target is a legal target for
+ * the specifice drag obj
+ * @method isLegalTarget
+ * @param {DragDrop} oDD the drag obj
+ * @param {DragDrop} oTargetDD the target
+ * @return {boolean} true if the target is a legal target for the
+ * dd obj
+ * @static
+ */
+ isLegalTarget: function (oDD, oTargetDD) {
+ var targets = this.getRelated(oDD, true);
+ for (var i=0, len=targets.length;i<len;++i) {
+ if (targets[i].id == oTargetDD.id) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * My goal is to be able to transparently determine if an object is
+ * typeof DragDrop, and the exact subclass of DragDrop. typeof
+ * returns "object", oDD.constructor.toString() always returns
+ * "DragDrop" and not the name of the subclass. So for now it just
+ * evaluates a well-known variable in DragDrop.
+ * @method isTypeOfDD
+ * @param {Object} the object to evaluate
+ * @return {boolean} true if typeof oDD = DragDrop
+ * @static
+ */
+ isTypeOfDD: function (oDD) {
+ return (oDD && oDD.__ygDragDrop);
+ },
+
+ /**
+ * Utility function to determine if a given element has been
+ * registered as a drag drop handle for the given Drag Drop object.
+ * @method isHandle
+ * @param {String} id the element id to check
+ * @return {boolean} true if this element is a DragDrop handle, false
+ * otherwise
+ * @static
+ */
+ isHandle: function(sDDId, sHandleId) {
+ return ( this.handleIds[sDDId] &&
+ this.handleIds[sDDId][sHandleId] );
+ },
+
+ /**
+ * Returns the DragDrop instance for a given id
+ * @method getDDById
+ * @param {String} id the id of the DragDrop object
+ * @return {DragDrop} the drag drop object, null if it is not found
+ * @static
+ */
+ getDDById: function(id) {
+ for (var i in this.ids) {
+ if (this.ids[i][id]) {
+ return this.ids[i][id];
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Fired after a registered DragDrop object gets the mousedown event.
+ * Sets up the events required to track the object being dragged
+ * @method handleMouseDown
+ * @param {Event} e the event
+ * @param oDD the DragDrop object being dragged
+ * @private
+ * @static
+ */
+ handleMouseDown: function(e, oDD) {
+ if(Ext.tip.QuickTipManager){
+ Ext.tip.QuickTipManager.ddDisable();
+ }
+ if(this.dragCurrent){
+ // the original browser mouseup wasn't handled (e.g. outside FF browser window)
+ // so clean up first to avoid breaking the next drag
+ this.handleMouseUp(e);
+ }
+
+ this.currentTarget = e.getTarget();
+ this.dragCurrent = oDD;
+
+ var el = oDD.getEl();
+
+ // track start position
+ this.startX = e.getPageX();
+ this.startY = e.getPageY();
+
+ this.deltaX = this.startX - el.offsetLeft;
+ this.deltaY = this.startY - el.offsetTop;
+
+ this.dragThreshMet = false;
+
+ this.clickTimeout = setTimeout(
+ function() {
+ var DDM = Ext.dd.DragDropManager;
+ DDM.startDrag(DDM.startX, DDM.startY);
+ },
+ this.clickTimeThresh );
+ },
+
+ /**
+ * Fired when either the drag pixel threshol or the mousedown hold
+ * time threshold has been met.
+ * @method startDrag
+ * @param x {int} the X position of the original mousedown
+ * @param y {int} the Y position of the original mousedown
+ * @static
+ */
+ startDrag: function(x, y) {
+ clearTimeout(this.clickTimeout);
+ if (this.dragCurrent) {
+ this.dragCurrent.b4StartDrag(x, y);
+ this.dragCurrent.startDrag(x, y);
+ }
+ this.dragThreshMet = true;
+ },
+
+ /**
+ * Internal function to handle the mouseup event. Will be invoked
+ * from the context of the document.
+ * @method handleMouseUp
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ handleMouseUp: function(e) {
+
+ if(Ext.tip.QuickTipManager){
+ Ext.tip.QuickTipManager.ddEnable();
+ }
+ if (! this.dragCurrent) {
+ return;
+ }
+
+ clearTimeout(this.clickTimeout);
+
+ if (this.dragThreshMet) {
+ this.fireEvents(e, true);
+ } else {
+ }
+
+ this.stopDrag(e);
+
+ this.stopEvent(e);
+ },
+
+ /**
+ * Utility to stop event propagation and event default, if these
+ * features are turned on.
+ * @method stopEvent
+ * @param {Event} e the event as returned by this.getEvent()
+ * @static
+ */
+ stopEvent: function(e){
+ if(this.stopPropagation) {
+ e.stopPropagation();
+ }
+
+ if (this.preventDefault) {
+ e.preventDefault();
+ }
+ },
+
+ /**
+ * Internal function to clean up event handlers after the drag
+ * operation is complete
+ * @method stopDrag
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ stopDrag: function(e) {
+ // Fire the drag end event for the item that was dragged
+ if (this.dragCurrent) {
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4EndDrag(e);
+ this.dragCurrent.endDrag(e);
+ }
+
+ this.dragCurrent.onMouseUp(e);
+ }
+
+ this.dragCurrent = null;
+ this.dragOvers = {};
+ },
+
+ /**
+ * Internal function to handle the mousemove event. Will be invoked
+ * from the context of the html element.
+ *
+ * @TODO figure out what we can do about mouse events lost when the
+ * user drags objects beyond the window boundary. Currently we can
+ * detect this in internet explorer by verifying that the mouse is
+ * down during the mousemove event. Firefox doesn't give us the
+ * button state on the mousemove event.
+ * @method handleMouseMove
+ * @param {Event} e the event
+ * @private
+ * @static
+ */
+ handleMouseMove: function(e) {
+ if (! this.dragCurrent) {
+ return true;
+ }
+ // var button = e.which || e.button;
+
+ // check for IE mouseup outside of page boundary
+ if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
+ this.stopEvent(e);
+ return this.handleMouseUp(e);
+ }
+
+ if (!this.dragThreshMet) {
+ var diffX = Math.abs(this.startX - e.getPageX());
+ var diffY = Math.abs(this.startY - e.getPageY());
+ if (diffX > this.clickPixelThresh ||
+ diffY > this.clickPixelThresh) {
+ this.startDrag(this.startX, this.startY);
+ }
+ }
+
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4Drag(e);
+ this.dragCurrent.onDrag(e);
+ if(!this.dragCurrent.moveOnly){
+ this.fireEvents(e, false);
+ }
+ }
+
+ this.stopEvent(e);
+
+ return true;
+ },
+
+ /**
+ * Iterates over all of the DragDrop elements to find ones we are
+ * hovering over or dropping on
+ * @method fireEvents
+ * @param {Event} e the event
+ * @param {boolean} isDrop is this a drop op or a mouseover op?
+ * @private
+ * @static
+ */
+ fireEvents: function(e, isDrop) {
+ var dc = this.dragCurrent;
+
+ // If the user did the mouse up outside of the window, we could
+ // get here even though we have ended the drag.
+ if (!dc || dc.isLocked()) {
+ return;
+ }
+
+ var pt = e.getPoint();
+
+ // cache the previous dragOver array
+ var oldOvers = [];
+
+ var outEvts = [];
+ var overEvts = [];
+ var dropEvts = [];
+ var enterEvts = [];
+
+ // Check to see if the object(s) we were hovering over is no longer
+ // being hovered over so we can fire the onDragOut event
+ for (var i in this.dragOvers) {
+
+ var ddo = this.dragOvers[i];
+
+ if (! this.isTypeOfDD(ddo)) {
+ continue;
+ }
+
+ if (! this.isOverTarget(pt, ddo, this.mode)) {
+ outEvts.push( ddo );
+ }
+
+ oldOvers[i] = true;
+ delete this.dragOvers[i];
+ }
+
+ for (var sGroup in dc.groups) {
+
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+
+ for (i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+ if (! this.isTypeOfDD(oDD)) {
+ continue;
+ }
+
+ if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
+ if (this.isOverTarget(pt, oDD, this.mode)) {
+ // look for drop interactions
+ if (isDrop) {
+ dropEvts.push( oDD );
+ // look for drag enter and drag over interactions
+ } else {
+
+ // initial drag over: dragEnter fires
+ if (!oldOvers[oDD.id]) {
+ enterEvts.push( oDD );
+ // subsequent drag overs: dragOver fires
+ } else {
+ overEvts.push( oDD );
+ }
+
+ this.dragOvers[oDD.id] = oDD;
+ }
+ }
+ }
+ }
+ }
+
+ if (this.mode) {
+ if (outEvts.length) {
+ dc.b4DragOut(e, outEvts);
+ dc.onDragOut(e, outEvts);
+ }
+
+ if (enterEvts.length) {
+ dc.onDragEnter(e, enterEvts);
+ }
+
+ if (overEvts.length) {
+ dc.b4DragOver(e, overEvts);
+ dc.onDragOver(e, overEvts);
+ }
+
+ if (dropEvts.length) {
+ dc.b4DragDrop(e, dropEvts);
+ dc.onDragDrop(e, dropEvts);
+ }
+
+ } else {
+ // fire dragout events
+ var len = 0;
+ for (i=0, len=outEvts.length; i<len; ++i) {
+ dc.b4DragOut(e, outEvts[i].id);
+ dc.onDragOut(e, outEvts[i].id);
+ }
+
+ // fire enter events
+ for (i=0,len=enterEvts.length; i<len; ++i) {
+ // dc.b4DragEnter(e, oDD.id);
+ dc.onDragEnter(e, enterEvts[i].id);
+ }
+
+ // fire over events
+ for (i=0,len=overEvts.length; i<len; ++i) {
+ dc.b4DragOver(e, overEvts[i].id);
+ dc.onDragOver(e, overEvts[i].id);
+ }
+
+ // fire drop events
+ for (i=0, len=dropEvts.length; i<len; ++i) {
+ dc.b4DragDrop(e, dropEvts[i].id);
+ dc.onDragDrop(e, dropEvts[i].id);
+ }
+
+ }
+
+ // notify about a drop that did not find a target
+ if (isDrop && !dropEvts.length) {
+ dc.onInvalidDrop(e);
+ }
+
+ },
+
+ /**
+ * Helper function for getting the best match from the list of drag
+ * and drop objects returned by the drag and drop events when we are
+ * in INTERSECT mode. It returns either the first object that the
+ * cursor is over, or the object that has the greatest overlap with
+ * the dragged element.
+ * @method getBestMatch
+ * @param {DragDrop[]} dds The array of drag and drop objects
+ * targeted
+ * @return {DragDrop} The best single match
+ * @static
+ */
+ getBestMatch: function(dds) {
+ var winner = null;
+ // Return null if the input is not what we expect
+ //if (!dds || !dds.length || dds.length == 0) {
+ // winner = null;
+ // If there is only one item, it wins
+ //} else if (dds.length == 1) {
+
+ var len = dds.length;
+
+ if (len == 1) {
+ winner = dds[0];
+ } else {
+ // Loop through the targeted items
+ for (var i=0; i<len; ++i) {
+ var dd = dds[i];
+ // If the cursor is over the object, it wins. If the
+ // cursor is over multiple matches, the first one we come
+ // to wins.
+ if (dd.cursorIsOver) {
+ winner = dd;
+ break;
+ // Otherwise the object with the most overlap wins
+ } else {
+ if (!winner ||
+ winner.overlap.getArea() < dd.overlap.getArea()) {
+ winner = dd;
+ }
+ }
+ }
+ }
+
+ return winner;
+ },
+
+ /**
+ * Refreshes the cache of the top-left and bottom-right points of the
+ * drag and drop objects in the specified group(s). This is in the
+ * format that is stored in the drag and drop instance, so typical
+ * usage is:
+ * <code>
+ * Ext.dd.DragDropManager.refreshCache(ddinstance.groups);
+ * </code>
+ * Alternatively:
+ * <code>
+ * Ext.dd.DragDropManager.refreshCache({group1:true, group2:true});
+ * </code>
+ * @TODO this really should be an indexed array. Alternatively this
+ * method could accept both.
+ * @method refreshCache
+ * @param {Object} groups an associative array of groups to refresh
+ * @static
+ */
+ refreshCache: function(groups) {
+ for (var sGroup in groups) {
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+ for (var i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+
+ if (this.isTypeOfDD(oDD)) {
+ // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
+ var loc = this.getLocation(oDD);
+ if (loc) {
+ this.locationCache[oDD.id] = loc;
+ } else {
+ delete this.locationCache[oDD.id];
+ // this will unregister the drag and drop object if
+ // the element is not in a usable state
+ // oDD.unreg();
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * This checks to make sure an element exists and is in the DOM. The
+ * main purpose is to handle cases where innerHTML is used to remove
+ * drag and drop objects from the DOM. IE provides an 'unspecified
+ * error' when trying to access the offsetParent of such an element
+ * @method verifyEl
+ * @param {HTMLElement} el the element to check
+ * @return {boolean} true if the element looks usable
+ * @static
+ */
+ verifyEl: function(el) {
+ if (el) {
+ var parent;
+ if(Ext.isIE){
+ try{
+ parent = el.offsetParent;
+ }catch(e){}
+ }else{
+ parent = el.offsetParent;
+ }
+ if (parent) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Returns a Region object containing the drag and drop element's position
+ * and size, including the padding configured for it
+ * @method getLocation
+ * @param {DragDrop} oDD the drag and drop object to get the
+ * location for
+ * @return {Ext.util.Region} a Region object representing the total area
+ * the element occupies, including any padding
+ * the instance is configured for.
+ * @static
+ */
+ getLocation: function(oDD) {
+ if (! this.isTypeOfDD(oDD)) {
+ return null;
+ }
+
+ //delegate getLocation method to the
+ //drag and drop target.
+ if (oDD.getRegion) {
+ return oDD.getRegion();
+ }
+
+ var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
+
+ try {
+ pos= Ext.core.Element.getXY(el);
+ } catch (e) { }
+
+ if (!pos) {
+ return null;
+ }
+
+ x1 = pos[0];
+ x2 = x1 + el.offsetWidth;
+ y1 = pos[1];
+ y2 = y1 + el.offsetHeight;
+
+ t = y1 - oDD.padding[0];
+ r = x2 + oDD.padding[1];
+ b = y2 + oDD.padding[2];
+ l = x1 - oDD.padding[3];
+
+ return Ext.create('Ext.util.Region', t, r, b, l);
+ },
+
+ /**
+ * Checks the cursor location to see if it over the target
+ * @method isOverTarget
+ * @param {Ext.util.Point} pt The point to evaluate
+ * @param {DragDrop} oTarget the DragDrop object we are inspecting
+ * @return {boolean} true if the mouse is over the target
+ * @private
+ * @static
+ */
+ isOverTarget: function(pt, oTarget, intersect) {
+ // use cache if available
+ var loc = this.locationCache[oTarget.id];
+ if (!loc || !this.useCache) {
+ loc = this.getLocation(oTarget);
+ this.locationCache[oTarget.id] = loc;
+
+ }
+
+ if (!loc) {
+ return false;
+ }
+
+ oTarget.cursorIsOver = loc.contains( pt );
+
+ // DragDrop is using this as a sanity check for the initial mousedown
+ // in this case we are done. In POINT mode, if the drag obj has no
+ // contraints, we are also done. Otherwise we need to evaluate the
+ // location of the target as related to the actual location of the
+ // dragged element.
+ var dc = this.dragCurrent;
+ if (!dc || !dc.getTargetCoord ||
+ (!intersect && !dc.constrainX && !dc.constrainY)) {
+ return oTarget.cursorIsOver;
+ }
+
+ oTarget.overlap = null;
+
+ // Get the current location of the drag element, this is the
+ // location of the mouse event less the delta that represents
+ // where the original mousedown happened on the element. We
+ // need to consider constraints and ticks as well.
+ var pos = dc.getTargetCoord(pt.x, pt.y);
+
+ var el = dc.getDragEl();
+ var curRegion = Ext.create('Ext.util.Region', pos.y,
+ pos.x + el.offsetWidth,
+ pos.y + el.offsetHeight,
+ pos.x );
+
+ var overlap = curRegion.intersect(loc);
+
+ if (overlap) {
+ oTarget.overlap = overlap;
+ return (intersect) ? true : oTarget.cursorIsOver;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ * unload event handler
+ * @method _onUnload
+ * @private
+ * @static
+ */
+ _onUnload: function(e, me) {
+ Ext.dd.DragDropManager.unregAll();
+ },
+
+ /**
+ * Cleans up the drag and drop events and objects.
+ * @method unregAll
+ * @private
+ * @static
+ */
+ unregAll: function() {
+
+ if (this.dragCurrent) {
+ this.stopDrag();
+ this.dragCurrent = null;
+ }
+
+ this._execOnAll("unreg", []);
+
+ for (var i in this.elementCache) {
+ delete this.elementCache[i];
+ }
+
+ this.elementCache = {};
+ this.ids = {};
+ },
+
+ /**
+ * A cache of DOM elements
+ * @property elementCache
+ * @private
+ * @static
+ */
+ elementCache: {},
+
+ /**
+ * Get the wrapper for the DOM element specified
+ * @method getElWrapper
+ * @param {String} id the id of the element to get
+ * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
+ * @private
+ * @deprecated This wrapper isn't that useful
+ * @static
+ */
+ getElWrapper: function(id) {
+ var oWrapper = this.elementCache[id];
+ if (!oWrapper || !oWrapper.el) {
+ oWrapper = this.elementCache[id] =
+ new this.ElementWrapper(Ext.getDom(id));
+ }
+ return oWrapper;
+ },
+
+ /**
+ * Returns the actual DOM element
+ * @method getElement
+ * @param {String} id the id of the elment to get
+ * @return {Object} The element
+ * @deprecated use Ext.lib.Ext.getDom instead
+ * @static
+ */
+ getElement: function(id) {
+ return Ext.getDom(id);
+ },
+
+ /**
+ * Returns the style property for the DOM element (i.e.,
+ * document.getElById(id).style)
+ * @method getCss
+ * @param {String} id the id of the elment to get
+ * @return {Object} The style property of the element
+ * @static
+ */
+ getCss: function(id) {
+ var el = Ext.getDom(id);
+ return (el) ? el.style : null;
+ },
+
+ /**
+ * Inner class for cached elements
+ * @class Ext.dd.DragDropManager.ElementWrapper
+ * @for DragDropManager
+ * @private
+ * @deprecated
+ */
+ ElementWrapper: function(el) {
+ /**
+ * The element
+ * @property el
+ */
+ this.el = el || null;
+ /**
+ * The element id
+ * @property id
+ */
+ this.id = this.el && el.id;
+ /**
+ * A reference to the style property
+ * @property css
+ */
+ this.css = this.el && el.style;
+ },
+
+ /**
+ * Returns the X position of an html element
+ * @method getPosX
+ * @param el the element for which to get the position
+ * @return {int} the X coordinate
+ * @for DragDropManager
+ * @static
+ */
+ getPosX: function(el) {
+ return Ext.core.Element.getX(el);
+ },
+
+ /**
+ * Returns the Y position of an html element
+ * @method getPosY
+ * @param el the element for which to get the position
+ * @return {int} the Y coordinate
+ * @static
+ */
+ getPosY: function(el) {
+ return Ext.core.Element.getY(el);
+ },
+
+ /**
+ * Swap two nodes. In IE, we use the native method, for others we
+ * emulate the IE behavior
+ * @method swapNode
+ * @param n1 the first node to swap
+ * @param n2 the other node to swap
+ * @static
+ */
+ swapNode: function(n1, n2) {
+ if (n1.swapNode) {
+ n1.swapNode(n2);
+ } else {
+ var p = n2.parentNode;
+ var s = n2.nextSibling;
+
+ if (s == n1) {
+ p.insertBefore(n1, n2);
+ } else if (n2 == n1.nextSibling) {
+ p.insertBefore(n2, n1);
+ } else {
+ n1.parentNode.replaceChild(n2, n1);
+ p.insertBefore(n1, s);
+ }
+ }
+ },
+
+ /**
+ * Returns the current scroll position
+ * @method getScroll
+ * @private
+ * @static
+ */
+ getScroll: function () {
+ var doc = window.document,
+ docEl = doc.documentElement,
+ body = doc.body,
+ top = 0,
+ left = 0;
+
+ if (Ext.isGecko4) {
+ top = window.scrollYOffset;
+ left = window.scrollXOffset;
+ } else {
+ if (docEl && (docEl.scrollTop || docEl.scrollLeft)) {
+ top = docEl.scrollTop;
+ left = docEl.scrollLeft;
+ } else if (body) {
+ top = body.scrollTop;
+ left = body.scrollLeft;
+ }
+ }
+ return {
+ top: top,
+ left: left
+ };
+ },
+
+ /**
+ * Returns the specified element style property
+ * @method getStyle
+ * @param {HTMLElement} el the element
+ * @param {string} styleProp the style property
+ * @return {string} The value of the style property
+ * @static
+ */
+ getStyle: function(el, styleProp) {
+ return Ext.fly(el).getStyle(styleProp);
+ },
+
+ /**
+ * Gets the scrollTop
+ * @method getScrollTop
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollTop: function () {
+ return this.getScroll().top;
+ },
+
+ /**
+ * Gets the scrollLeft
+ * @method getScrollLeft
+ * @return {int} the document's scrollTop
+ * @static
+ */
+ getScrollLeft: function () {
+ return this.getScroll().left;
+ },
+
+ /**
+ * Sets the x/y position of an element to the location of the
+ * target element.
+ * @method moveToEl
+ * @param {HTMLElement} moveEl The element to move
+ * @param {HTMLElement} targetEl The position reference element
+ * @static
+ */
+ moveToEl: function (moveEl, targetEl) {
+ var aCoord = Ext.core.Element.getXY(targetEl);
+ Ext.core.Element.setXY(moveEl, aCoord);
+ },
+
+ /**
+ * Numeric array sort function
+ * @method numericSort
+ * @static
+ */
+ numericSort: function(a, b) {
+ return (a - b);
+ },
+
+ /**
+ * Internal counter
+ * @property _timeoutCount
+ * @private
+ * @static
+ */
+ _timeoutCount: 0,
+
+ /**
+ * Trying to make the load order less important. Without this we get
+ * an error if this file is loaded before the Event Utility.
+ * @method _addListeners
+ * @private
+ * @static
+ */
+ _addListeners: function() {
+ if ( document ) {
+ this._onLoad();
+ } else {
+ if (this._timeoutCount > 2000) {
+ } else {
+ setTimeout(this._addListeners, 10);
+ if (document && document.body) {
+ this._timeoutCount += 1;
+ }
+ }
+ }
+ },
+
+ /**
+ * Recursively searches the immediate parent and all child nodes for
+ * the handle element in order to determine wheter or not it was
+ * clicked.
+ * @method handleWasClicked
+ * @param node the html element to inspect
+ * @static
+ */
+ handleWasClicked: function(node, id) {
+ if (this.isHandle(id, node.id)) {
+ return true;
+ } else {
+ // check to see if this is a text node child of the one we want
+ var p = node.parentNode;
+
+ while (p) {
+ if (this.isHandle(id, p.id)) {
+ return true;
+ } else {
+ p = p.parentNode;
+ }
+ }
+ }
+
+ return false;
+ }
+}, function() {
+ this._addListeners();
+});
+
+/**
+ * @class Ext.layout.container.Box
+ * @extends Ext.layout.container.Container
+ * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
+ */
+
+Ext.define('Ext.layout.container.Box', {
+
+ /* Begin Definitions */
+
+ alias: ['layout.box'],
+ extend: 'Ext.layout.container.Container',
+ alternateClassName: 'Ext.layout.BoxLayout',
+
+ requires: [
+ 'Ext.layout.container.boxOverflow.None',
+ 'Ext.layout.container.boxOverflow.Menu',
+ 'Ext.layout.container.boxOverflow.Scroller',
+ 'Ext.util.Format',
+ 'Ext.dd.DragDropManager'
+ ],
+
+ /* End Definitions */
+
+ /**
+ * @cfg {Mixed} animate
+ * <p>If truthy, child Component are <i>animated</i> into position whenever the Container
+ * is layed out. If this option is numeric, it is used as the animation duration in milliseconds.</p>
+ * <p>May be set as a property at any time.</p>
+ */
+
+ /**
+ * @cfg {Object} defaultMargins
+ * <p>If the individual contained items do not have a <tt>margins</tt>
+ * property specified or margin specified via CSS, the default margins from this property will be
+ * applied to each item.</p>
+ * <br><p>This property may be specified as an object containing margins
+ * to apply in the format:</p><pre><code>
+{
+ top: (top margin),
+ right: (right margin),
+ bottom: (bottom margin),
+ left: (left margin)
+}</code></pre>
+ * <p>This property may also be specified as a string containing
+ * space-separated, numeric margin values. The order of the sides associated
+ * with each value matches the way CSS processes margin values:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to:</p><pre><code>
+ * {top:0, right:0, bottom:0, left:0}
+ * </code></pre>
+ */
+ defaultMargins: {
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0
+ },
+
+ /**
+ * @cfg {String} padding
+ * <p>Sets the padding to be applied to all child items managed by this layout.</p>
+ * <p>This property must be specified as a string containing
+ * space-separated, numeric padding values. The order of the sides associated
+ * with each value matches the way CSS processes padding values:</p>
+ * <div class="mdetail-params"><ul>
+ * <li>If there is only one value, it applies to all sides.</li>
+ * <li>If there are two values, the top and bottom borders are set to the
+ * first value and the right and left are set to the second.</li>
+ * <li>If there are three values, the top is set to the first value, the left
+ * and right are set to the second, and the bottom is set to the third.</li>
+ * <li>If there are four values, they apply to the top, right, bottom, and
+ * left, respectively.</li>
+ * </ul></div>
+ * <p>Defaults to: <code>"0"</code></p>
+ */
+ padding: '0',
+ // documented in subclasses
+ pack: 'start',
+
+ /**
+ * @cfg {String} pack
+ * Controls how the child items of the container are packed together. Acceptable configuration values
+ * for this property are:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
+ * <b>left</b> side of container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
+ * <b>mid-width</b> of container</div></li>
+ * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
+ * side of container</div></li>
+ * </ul></div>
+ */
+ /**
+ * @cfg {Number} flex
+ * This configuration option is to be applied to <b>child <tt>items</tt></b> of the container managed
+ * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
+ * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
+ * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
+ * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
+ */
+
+ type: 'box',
+ scrollOffset: 0,
+ itemCls: Ext.baseCSSPrefix + 'box-item',
+ targetCls: Ext.baseCSSPrefix + 'box-layout-ct',
+ innerCls: Ext.baseCSSPrefix + 'box-inner',
+
+ bindToOwnerCtContainer: true,
+
+ fixedLayout: false,
+
+ // availableSpaceOffset is used to adjust the availableWidth, typically used
+ // to reserve space for a scrollbar
+ availableSpaceOffset: 0,
+
+ // whether or not to reserve the availableSpaceOffset in layout calculations
+ reserveOffset: true,
+
+ /**
+ * @cfg {Boolean} clearInnerCtOnLayout
+ */
+ clearInnerCtOnLayout: false,
+
+ flexSortFn: function (a, b) {
+ var maxParallelPrefix = 'max' + this.parallelPrefixCap,
+ infiniteValue = Infinity;
+ a = a.component[maxParallelPrefix] || infiniteValue;
+ b = b.component[maxParallelPrefix] || infiniteValue;
+ // IE 6/7 Don't like Infinity - Infinity...
+ if (!isFinite(a) && !isFinite(b)) {
+ return false;
+ }
+ return a - b;
+ },
+
+ // Sort into *descending* order.
+ minSizeSortFn: function(a, b) {
+ return b.available - a.available;
+ },
+
+ constructor: function(config) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ // The sort function needs access to properties in this, so must be bound.
+ me.flexSortFn = Ext.Function.bind(me.flexSortFn, me);
+
+ me.initOverflowHandler();
+ },
+
+ /**
+ * @private
+ * Returns the current size and positioning of the passed child item.
+ * @param {Component} child The child Component to calculate the box for
+ * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
+ */
+ getChildBox: function(child) {
+ child = child.el || this.owner.getComponent(child).el;
+ return {
+ left: child.getLeft(true),
+ top: child.getTop(true),
+ width: child.getWidth(),
+ height: child.getHeight()
+ };
+ },
+
+ /**
+ * @private
+ * Calculates the size and positioning of the passed child item.
+ * @param {Component} child The child Component to calculate the box for
+ * @return {Object} Object containing box measurements for the child. Properties are left,top,width,height.
+ */
+ calculateChildBox: function(child) {
+ var me = this,
+ boxes = me.calculateChildBoxes(me.getVisibleItems(), me.getLayoutTargetSize()).boxes,
+ ln = boxes.length,
+ i = 0;
+
+ child = me.owner.getComponent(child);
+ for (; i < ln; i++) {
+ if (boxes[i].component === child) {
+ return boxes[i];
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Calculates the size and positioning of each item in the box. This iterates over all of the rendered,
+ * visible items and returns a height, width, top and left for each, as well as a reference to each. Also
+ * returns meta data such as maxSize which are useful when resizing layout wrappers such as this.innerCt.
+ * @param {Array} visibleItems The array of all rendered, visible items to be calculated for
+ * @param {Object} targetSize Object containing target size and height
+ * @return {Object} Object containing box measurements for each child, plus meta data
+ */
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var me = this,
+ math = Math,
+ mmax = math.max,
+ infiniteValue = Infinity,
+ undefinedValue,
+
+ parallelPrefix = me.parallelPrefix,
+ parallelPrefixCap = me.parallelPrefixCap,
+ perpendicularPrefix = me.perpendicularPrefix,
+ perpendicularPrefixCap = me.perpendicularPrefixCap,
+ parallelMinString = 'min' + parallelPrefixCap,
+ perpendicularMinString = 'min' + perpendicularPrefixCap,
+ perpendicularMaxString = 'max' + perpendicularPrefixCap,
+
+ parallelSize = targetSize[parallelPrefix] - me.scrollOffset,
+ perpendicularSize = targetSize[perpendicularPrefix],
+ padding = me.padding,
+ parallelOffset = padding[me.parallelBefore],
+ paddingParallel = parallelOffset + padding[me.parallelAfter],
+ perpendicularOffset = padding[me.perpendicularLeftTop],
+ paddingPerpendicular = perpendicularOffset + padding[me.perpendicularRightBottom],
+ availPerpendicularSize = mmax(0, perpendicularSize - paddingPerpendicular),
+
+ isStart = me.pack == 'start',
+ isCenter = me.pack == 'center',
+ isEnd = me.pack == 'end',
+
+ constrain = Ext.Number.constrain,
+ visibleCount = visibleItems.length,
+ nonFlexSize = 0,
+ totalFlex = 0,
+ desiredSize = 0,
+ minimumSize = 0,
+ maxSize = 0,
+ boxes = [],
+ minSizes = [],
+ calculatedWidth,
+
+ i, child, childParallel, childPerpendicular, childMargins, childSize, minParallel, tmpObj, shortfall,
+ tooNarrow, availableSpace, minSize, item, length, itemIndex, box, oldSize, newSize, reduction, diff,
+ flexedBoxes, remainingSpace, remainingFlex, flexedSize, parallelMargins, calcs, offset,
+ perpendicularMargins, stretchSize;
+
+ //gather the total flex of all flexed items and the width taken up by fixed width items
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childPerpendicular = child[perpendicularPrefix];
+ me.layoutItem(child);
+ childMargins = child.margins;
+ parallelMargins = childMargins[me.parallelBefore] + childMargins[me.parallelAfter];
+
+ // Create the box description object for this child item.
+ tmpObj = {
+ component: child,
+ margins: childMargins
+ };
+
+ // flex and not 'auto' width
+ if (child.flex) {
+ totalFlex += child.flex;
+ childParallel = undefinedValue;
+ }
+ // Not flexed or 'auto' width or undefined width
+ else {
+ if (!(child[parallelPrefix] && childPerpendicular)) {
+ childSize = child.getSize();
+ }
+ childParallel = child[parallelPrefix] || childSize[parallelPrefix];
+ childPerpendicular = childPerpendicular || childSize[perpendicularPrefix];
+ }
+
+ nonFlexSize += parallelMargins + (childParallel || 0);
+ desiredSize += parallelMargins + (child.flex ? child[parallelMinString] || 0 : childParallel);
+ minimumSize += parallelMargins + (child[parallelMinString] || childParallel || 0);
+
+ // Max height for align - force layout of non-laid out subcontainers without a numeric height
+ if (typeof childPerpendicular != 'number') {
+ // Clear any static sizing and revert to flow so we can get a proper measurement
+ // child['set' + perpendicularPrefixCap](null);
+ childPerpendicular = child['get' + perpendicularPrefixCap]();
+ }
+
+ // Track the maximum perpendicular size for use by the stretch and stretchmax align config values.
+ maxSize = mmax(maxSize, childPerpendicular + childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom]);
+
+ tmpObj[parallelPrefix] = childParallel || undefinedValue;
+ tmpObj[perpendicularPrefix] = childPerpendicular || undefinedValue;
+ boxes.push(tmpObj);
+ }
+ shortfall = desiredSize - parallelSize;
+ tooNarrow = minimumSize > parallelSize;
+
+ //the space available to the flexed items
+ availableSpace = mmax(0, parallelSize - nonFlexSize - paddingParallel - (me.reserveOffset ? me.availableSpaceOffset : 0));
+
+ if (tooNarrow) {
+ for (i = 0; i < visibleCount; i++) {
+ box = boxes[i];
+ minSize = visibleItems[i][parallelMinString] || visibleItems[i][parallelPrefix] || box[parallelPrefix];
+ box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
+ box[parallelPrefix] = minSize;
+ }
+ }
+ else {
+ //all flexed items should be sized to their minimum size, other items should be shrunk down until
+ //the shortfall has been accounted for
+ if (shortfall > 0) {
+ /*
+ * When we have a shortfall but are not tooNarrow, we need to shrink the width of each non-flexed item.
+ * Flexed items are immediately reduced to their minWidth and anything already at minWidth is ignored.
+ * The remaining items are collected into the minWidths array, which is later used to distribute the shortfall.
+ */
+ for (i = 0; i < visibleCount; i++) {
+ item = visibleItems[i];
+ minSize = item[parallelMinString] || 0;
+
+ //shrink each non-flex tab by an equal amount to make them all fit. Flexed items are all
+ //shrunk to their minSize because they're flexible and should be the first to lose size
+ if (item.flex) {
+ box = boxes[i];
+ box.dirtySize = box.dirtySize || box[parallelPrefix] != minSize;
+ box[parallelPrefix] = minSize;
+ }
+ else {
+ minSizes.push({
+ minSize: minSize,
+ available: boxes[i][parallelPrefix] - minSize,
+ index: i
+ });
+ }
+ }
+
+ //sort by descending amount of width remaining before minWidth is reached
+ Ext.Array.sort(minSizes, me.minSizeSortFn);
+
+ /*
+ * Distribute the shortfall (difference between total desired size of all items and actual size available)
+ * between the non-flexed items. We try to distribute the shortfall evenly, but apply it to items with the
+ * smallest difference between their size and minSize first, so that if reducing the size by the average
+ * amount would make that item less than its minSize, we carry the remainder over to the next item.
+ */
+ for (i = 0, length = minSizes.length; i < length; i++) {
+ itemIndex = minSizes[i].index;
+
+ if (itemIndex == undefinedValue) {
+ continue;
+ }
+ item = visibleItems[itemIndex];
+ minSize = minSizes[i].minSize;
+
+ box = boxes[itemIndex];
+ oldSize = box[parallelPrefix];
+ newSize = mmax(minSize, oldSize - math.ceil(shortfall / (length - i)));
+ reduction = oldSize - newSize;
+
+ box.dirtySize = box.dirtySize || box[parallelPrefix] != newSize;
+ box[parallelPrefix] = newSize;
+ shortfall -= reduction;
+ }
+ }
+ else {
+ remainingSpace = availableSpace;
+ remainingFlex = totalFlex;
+ flexedBoxes = [];
+
+ // Create an array containing *just the flexed boxes* for allocation of remainingSpace
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ if (isStart && child.flex) {
+ flexedBoxes.push(boxes[Ext.Array.indexOf(visibleItems, child)]);
+ }
+ }
+ // The flexed boxes need to be sorted in ascending order of maxSize to work properly
+ // so that unallocated space caused by maxWidth being less than flexed width
+ // can be reallocated to subsequent flexed boxes.
+ Ext.Array.sort(flexedBoxes, me.flexSortFn);
+
+ // Calculate the size of each flexed item, and attempt to set it.
+ for (i = 0; i < flexedBoxes.length; i++) {
+ calcs = flexedBoxes[i];
+ child = calcs.component;
+ childMargins = calcs.margins;
+
+ flexedSize = math.ceil((child.flex / remainingFlex) * remainingSpace);
+
+ // Implement maxSize and minSize check
+ flexedSize = Math.max(child['min' + parallelPrefixCap] || 0, math.min(child['max' + parallelPrefixCap] || infiniteValue, flexedSize));
+
+ // Remaining space has already had all parallel margins subtracted from it, so just subtract consumed size
+ remainingSpace -= flexedSize;
+ remainingFlex -= child.flex;
+
+ calcs.dirtySize = calcs.dirtySize || calcs[parallelPrefix] != flexedSize;
+ calcs[parallelPrefix] = flexedSize;
+ }
+ }
+ }
+
+ if (isCenter) {
+ parallelOffset += availableSpace / 2;
+ }
+ else if (isEnd) {
+ parallelOffset += availableSpace;
+ }
+
+ // Fix for left and right docked Components in a dock component layout. This is for docked Headers and docked Toolbars.
+ // Older Microsoft browsers do not size a position:absolute element's width to match its content.
+ // So in this case, in the updateInnerCtSize method we may need to adjust the size of the owning Container's element explicitly based upon
+ // the discovered max width. So here we put a calculatedWidth property in the metadata to facilitate this.
+ if (me.owner.dock && (Ext.isIE6 || Ext.isIE7 || Ext.isIEQuirks) && !me.owner.width && me.direction == 'vertical') {
+
+ calculatedWidth = maxSize + me.owner.el.getPadding('lr') + me.owner.el.getBorderWidth('lr');
+ if (me.owner.frameSize) {
+ calculatedWidth += me.owner.frameSize.left + me.owner.frameSize.right;
+ }
+ // If the owning element is not sized, calculate the available width to center or stretch in based upon maxSize
+ availPerpendicularSize = Math.min(availPerpendicularSize, targetSize.width = maxSize + padding.left + padding.right);
+ }
+
+ //finally, calculate the left and top position of each item
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = calcs.margins;
+
+ perpendicularMargins = childMargins[me.perpendicularLeftTop] + childMargins[me.perpendicularRightBottom];
+
+ // Advance past the "before" margin
+ parallelOffset += childMargins[me.parallelBefore];
+
+ calcs[me.parallelBefore] = parallelOffset;
+ calcs[me.perpendicularLeftTop] = perpendicularOffset + childMargins[me.perpendicularLeftTop];
+
+ if (me.align == 'stretch') {
+ stretchSize = constrain(availPerpendicularSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
+ calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
+ calcs[perpendicularPrefix] = stretchSize;
+ }
+ else if (me.align == 'stretchmax') {
+ stretchSize = constrain(maxSize - perpendicularMargins, child[perpendicularMinString] || 0, child[perpendicularMaxString] || infiniteValue);
+ calcs.dirtySize = calcs.dirtySize || calcs[perpendicularPrefix] != stretchSize;
+ calcs[perpendicularPrefix] = stretchSize;
+ }
+ else if (me.align == me.alignCenteringString) {
+ // When calculating a centered position within the content box of the innerCt, the width of the borders must be subtracted from
+ // the size to yield the space available to center within.
+ // The updateInnerCtSize method explicitly adds the border widths to the set size of the innerCt.
+ diff = mmax(availPerpendicularSize, maxSize) - me.innerCt.getBorderWidth(me.perpendicularLT + me.perpendicularRB) - calcs[perpendicularPrefix];
+ if (diff > 0) {
+ calcs[me.perpendicularLeftTop] = perpendicularOffset + Math.round(diff / 2);
+ }
+ }
+
+ // Advance past the box size and the "after" margin
+ parallelOffset += (calcs[parallelPrefix] || 0) + childMargins[me.parallelAfter];
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ calculatedWidth: calculatedWidth,
+ maxSize: maxSize,
+ nonFlexSize: nonFlexSize,
+ desiredSize: desiredSize,
+ minimumSize: minimumSize,
+ shortfall: shortfall,
+ tooNarrow: tooNarrow
+ }
+ };
+ },
+
+ /**
+ * @private
+ */
+ initOverflowHandler: function() {
+ var handler = this.overflowHandler;
+
+ if (typeof handler == 'string') {
+ handler = {
+ type: handler
+ };
+ }
+
+ var handlerType = 'None';
+ if (handler && handler.type != undefined) {
+ handlerType = handler.type;
+ }
+
+ var constructor = Ext.layout.container.boxOverflow[handlerType];
+ if (constructor[this.type]) {
+ constructor = constructor[this.type];
+ }
+
+ this.overflowHandler = Ext.create('Ext.layout.container.boxOverflow.' + handlerType, this, handler);
+ },
+
+ /**
+ * @private
+ * Runs the child box calculations and caches them in childBoxCache. Subclasses can used these cached values
+ * when laying out
+ */
+ onLayout: function() {
+ this.callParent();
+ // Clear the innerCt size so it doesn't influence the child items.
+ if (this.clearInnerCtOnLayout === true && this.adjustmentPass !== true) {
+ this.innerCt.setSize(null, null);
+ }
+
+ var me = this,
+ targetSize = me.getLayoutTargetSize(),
+ items = me.getVisibleItems(),
+ calcs = me.calculateChildBoxes(items, targetSize),
+ boxes = calcs.boxes,
+ meta = calcs.meta,
+ handler, method, results;
+
+ if (me.autoSize && calcs.meta.desiredSize) {
+ targetSize[me.parallelPrefix] = calcs.meta.desiredSize;
+ }
+
+ //invoke the overflow handler, if one is configured
+ if (meta.shortfall > 0) {
+ handler = me.overflowHandler;
+ method = meta.tooNarrow ? 'handleOverflow': 'clearOverflow';
+
+ results = handler[method](calcs, targetSize);
+
+ if (results) {
+ if (results.targetSize) {
+ targetSize = results.targetSize;
+ }
+
+ if (results.recalculate) {
+ items = me.getVisibleItems(owner);
+ calcs = me.calculateChildBoxes(items, targetSize);
+ boxes = calcs.boxes;
+ }
+ }
+ } else {
+ me.overflowHandler.clearOverflow();
+ }
+
+ /**
+ * @private
+ * @property layoutTargetLastSize
+ * @type Object
+ * Private cache of the last measured size of the layout target. This should never be used except by
+ * BoxLayout subclasses during their onLayout run.
+ */
+ me.layoutTargetLastSize = targetSize;
+
+ /**
+ * @private
+ * @property childBoxCache
+ * @type Array
+ * Array of the last calculated height, width, top and left positions of each visible rendered component
+ * within the Box layout.
+ */
+ me.childBoxCache = calcs;
+
+ me.updateInnerCtSize(targetSize, calcs);
+ me.updateChildBoxes(boxes);
+ me.handleTargetOverflow(targetSize);
+ },
+
+ /**
+ * Resizes and repositions each child component
+ * @param {Array} boxes The box measurements
+ */
+ updateChildBoxes: function(boxes) {
+ var me = this,
+ i = 0,
+ length = boxes.length,
+ animQueue = [],
+ dd = Ext.dd.DDM.getDDById(me.innerCt.id), // Any DD active on this layout's element (The BoxReorderer plugin does this.)
+ oldBox, newBox, changed, comp, boxAnim, animCallback;
+
+ for (; i < length; i++) {
+ newBox = boxes[i];
+ comp = newBox.component;
+
+ // If a Component is being drag/dropped, skip positioning it.
+ // Accomodate the BoxReorderer plugin: Its current dragEl must not be positioned by the layout
+ if (dd && (dd.getDragEl() === comp.el.dom)) {
+ continue;
+ }
+
+ changed = false;
+
+ oldBox = me.getChildBox(comp);
+
+ // If we are animating, we build up an array of Anim config objects, one for each
+ // child Component which has any changed box properties. Those with unchanged
+ // properties are not animated.
+ if (me.animate) {
+ // Animate may be a config object containing callback.
+ animCallback = me.animate.callback || me.animate;
+ boxAnim = {
+ layoutAnimation: true, // Component Target handler must use set*Calculated*Size
+ target: comp,
+ from: {},
+ to: {},
+ listeners: {}
+ };
+ // Only set from and to properties when there's a change.
+ // Perform as few Component setter methods as possible.
+ // Temporarily set the property values that we are not animating
+ // so that doComponentLayout does not auto-size them.
+ if (!isNaN(newBox.width) && (newBox.width != oldBox.width)) {
+ changed = true;
+ // boxAnim.from.width = oldBox.width;
+ boxAnim.to.width = newBox.width;
+ }
+ if (!isNaN(newBox.height) && (newBox.height != oldBox.height)) {
+ changed = true;
+ // boxAnim.from.height = oldBox.height;
+ boxAnim.to.height = newBox.height;
+ }
+ if (!isNaN(newBox.left) && (newBox.left != oldBox.left)) {
+ changed = true;
+ // boxAnim.from.left = oldBox.left;
+ boxAnim.to.left = newBox.left;
+ }
+ if (!isNaN(newBox.top) && (newBox.top != oldBox.top)) {
+ changed = true;
+ // boxAnim.from.top = oldBox.top;
+ boxAnim.to.top = newBox.top;
+ }
+ if (changed) {
+ animQueue.push(boxAnim);
+ }
+ } else {
+ if (newBox.dirtySize) {
+ if (newBox.width !== oldBox.width || newBox.height !== oldBox.height) {
+ me.setItemSize(comp, newBox.width, newBox.height);
+ }
+ }
+ // Don't set positions to NaN
+ if (isNaN(newBox.left) || isNaN(newBox.top)) {
+ continue;
+ }
+ comp.setPosition(newBox.left, newBox.top);
+ }
+ }
+
+ // Kick off any queued animations
+ length = animQueue.length;
+ if (length) {
+
+ // A function which cleans up when a Component's animation is done.
+ // The last one to finish calls the callback.
+ var afterAnimate = function(anim) {
+ // When we've animated all changed boxes into position, clear our busy flag and call the callback.
+ length -= 1;
+ if (!length) {
+ me.layoutBusy = false;
+ if (Ext.isFunction(animCallback)) {
+ animCallback();
+ }
+ }
+ };
+
+ var beforeAnimate = function() {
+ me.layoutBusy = true;
+ };
+
+ // Start each box animation off
+ for (i = 0, length = animQueue.length; i < length; i++) {
+ boxAnim = animQueue[i];
+
+ // Clean up the Component after. Clean up the *layout* after the last animation finishes
+ boxAnim.listeners.afteranimate = afterAnimate;
+
+ // The layout is busy during animation, and may not be called, so set the flag when the first animation begins
+ if (!i) {
+ boxAnim.listeners.beforeanimate = beforeAnimate;
+ }
+ if (me.animate.duration) {
+ boxAnim.duration = me.animate.duration;
+ }
+ comp = boxAnim.target;
+ delete boxAnim.target;
+ // Stop any currently running animation
+ comp.stopAnimation();
+ comp.animate(boxAnim);
+ }
+ }
+ },
+
+ /**
+ * @private
+ * Called by onRender just before the child components are sized and positioned. This resizes the innerCt
+ * to make sure all child items fit within it. We call this before sizing the children because if our child
+ * items are larger than the previous innerCt size the browser will insert scrollbars and then remove them
+ * again immediately afterwards, giving a performance hit.
+ * Subclasses should provide an implementation.
+ * @param {Object} currentSize The current height and width of the innerCt
+ * @param {Array} calculations The new box calculations of all items to be laid out
+ */
+ updateInnerCtSize: function(tSize, calcs) {
+ var me = this,
+ mmax = Math.max,
+ align = me.align,
+ padding = me.padding,
+ width = tSize.width,
+ height = tSize.height,
+ meta = calcs.meta,
+ innerCtWidth,
+ innerCtHeight;
+
+ if (me.direction == 'horizontal') {
+ innerCtWidth = width;
+ innerCtHeight = meta.maxSize + padding.top + padding.bottom + me.innerCt.getBorderWidth('tb');
+
+ if (align == 'stretch') {
+ innerCtHeight = height;
+ }
+ else if (align == 'middle') {
+ innerCtHeight = mmax(height, innerCtHeight);
+ }
+ } else {
+ innerCtHeight = height;
+ innerCtWidth = meta.maxSize + padding.left + padding.right + me.innerCt.getBorderWidth('lr');
+
+ if (align == 'stretch') {
+ innerCtWidth = width;
+ }
+ else if (align == 'center') {
+ innerCtWidth = mmax(width, innerCtWidth);
+ }
+ }
+ me.getRenderTarget().setSize(innerCtWidth || undefined, innerCtHeight || undefined);
+
+ // If a calculated width has been found (and this only happens for auto-width vertical docked Components in old Microsoft browsers)
+ // then, if the Component has not assumed the size of its content, set it to do so.
+ if (meta.calculatedWidth && me.owner.el.getWidth() > meta.calculatedWidth) {
+ me.owner.el.setWidth(meta.calculatedWidth);
+ }
+
+ if (me.innerCt.dom.scrollTop) {
+ me.innerCt.dom.scrollTop = 0;
+ }
+ },
+
+ /**
+ * @private
+ * This should be called after onLayout of any BoxLayout subclass. If the target's overflow is not set to 'hidden',
+ * we need to lay out a second time because the scrollbars may have modified the height and width of the layout
+ * target. Having a Box layout inside such a target is therefore not recommended.
+ * @param {Object} previousTargetSize The size and height of the layout target before we just laid out
+ * @param {Ext.container.Container} container The container
+ * @param {Ext.core.Element} target The target element
+ * @return True if the layout overflowed, and was reflowed in a secondary onLayout call.
+ */
+ handleTargetOverflow: function(previousTargetSize) {
+ var target = this.getTarget(),
+ overflow = target.getStyle('overflow'),
+ newTargetSize;
+
+ if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
+ newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height) {
+ this.adjustmentPass = true;
+ this.onLayout();
+ return true;
+ }
+ }
+
+ delete this.adjustmentPass;
+ },
+
+ // private
+ isValidParent : function(item, target, position) {
+ // Note: Box layouts do not care about order within the innerCt element because it's an absolutely positioning layout
+ // We only care whether the item is a direct child of the innerCt element.
+ var itemEl = item.el ? item.el.dom : Ext.getDom(item);
+ return (itemEl && this.innerCt && itemEl.parentNode === this.innerCt.dom) || false;
+ },
+
+ // Overridden method from AbstractContainer.
+ // Used in the base AbstractLayout.beforeLayout method to render all items into.
+ getRenderTarget: function() {
+ if (!this.innerCt) {
+ // the innerCt prevents wrapping and shuffling while the container is resizing
+ this.innerCt = this.getTarget().createChild({
+ cls: this.innerCls,
+ role: 'presentation'
+ });
+ this.padding = Ext.util.Format.parseBox(this.padding);
+ }
+ return this.innerCt;
+ },
+
+ // private
+ renderItem: function(item, target) {
+ this.callParent(arguments);
+ var me = this,
+ itemEl = item.getEl(),
+ style = itemEl.dom.style,
+ margins = item.margins || item.margin;
+
+ // Parse the item's margin/margins specification
+ if (margins) {
+ if (Ext.isString(margins) || Ext.isNumber(margins)) {
+ margins = Ext.util.Format.parseBox(margins);
+ } else {
+ Ext.applyIf(margins, {top: 0, right: 0, bottom: 0, left: 0});
+ }
+ } else {
+ margins = Ext.apply({}, me.defaultMargins);
+ }
+
+ // Add any before/after CSS margins to the configured margins, and zero the CSS margins
+ margins.top += itemEl.getMargin('t');
+ margins.right += itemEl.getMargin('r');
+ margins.bottom += itemEl.getMargin('b');
+ margins.left += itemEl.getMargin('l');
+ style.marginTop = style.marginRight = style.marginBottom = style.marginLeft = '0';
+
+ // Item must reference calculated margins.
+ item.margins = margins;
+ },
+
+ /**
+ * @private
+ */
+ destroy: function() {
+ Ext.destroy(this.overflowHandler);
+ this.callParent(arguments);
+ }
+});
+/**
+ * @class Ext.layout.container.HBox
+ * @extends Ext.layout.container.Box
+ * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
+ * space between child items containing a numeric <code>flex</code> configuration.</p>
+ * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
+ * {@img Ext.layout.container.HBox/Ext.layout.container.HBox.png Ext.layout.container.HBox container layout}
+ * Example usage:
+ Ext.create('Ext.Panel', {
+ width: 500,
+ height: 300,
+ title: "HBoxLayout Panel",
+ layout: {
+ type: 'hbox',
+ align: 'stretch'
+ },
+ renderTo: document.body,
+ items: [{
+ xtype: 'panel',
+ title: 'Inner Panel One',
+ flex: 2
+ },{
+ xtype: 'panel',
+ title: 'Inner Panel Two',
+ flex: 1
+ },{
+ xtype: 'panel',
+ title: 'Inner Panel Three',
+ flex: 1
+ }]
+ });
+ */
+Ext.define('Ext.layout.container.HBox', {
+
+ /* Begin Definitions */
+
+ alias: ['layout.hbox'],
+ extend: 'Ext.layout.container.Box',
+ alternateClassName: 'Ext.layout.HBoxLayout',
+
+ /* End Definitions */
+
+ /**
+ * @cfg {String} align
+ * Controls how the child items of the container are aligned. Acceptable configuration values for this
+ * property are:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
+ * at the <b>top</b> of the container</div></li>
+ * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically in the
+ * <b>middle</b> of the container</div></li>
+ * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
+ * the height of the container</div></li>
+ * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
+ * the height of the largest item.</div></li>
+ * </ul></div>
+ */
+ align: 'top', // top, middle, stretch, strechmax
+
+ //@private
+ alignCenteringString: 'middle',
+
+ type : 'hbox',
+
+ direction: 'horizontal',
+
+ // When creating an argument list to setSize, use this order
+ parallelSizeIndex: 0,
+ perpendicularSizeIndex: 1,
+
+ parallelPrefix: 'width',
+ parallelPrefixCap: 'Width',
+ parallelLT: 'l',
+ parallelRB: 'r',
+ parallelBefore: 'left',
+ parallelBeforeCap: 'Left',
+ parallelAfter: 'right',
+ parallelPosition: 'x',
+
+ perpendicularPrefix: 'height',
+ perpendicularPrefixCap: 'Height',
+ perpendicularLT: 't',
+ perpendicularRB: 'b',
+ perpendicularLeftTop: 'top',
+ perpendicularRightBottom: 'bottom',
+ perpendicularPosition: 'y'
+});
+/**
+ * @class Ext.layout.container.VBox
+ * @extends Ext.layout.container.Box
+ * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
+ * space between child items containing a numeric <code>flex</code> configuration.</p>
+ * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
+ * {@img Ext.layout.container.VBox/Ext.layout.container.VBox.png Ext.layout.container.VBox container layout}
+ * Example usage:
+ Ext.create('Ext.Panel', {
+ width: 500,
+ height: 400,
+ title: "VBoxLayout Panel",
+ layout: {
+ type: 'vbox',
+ align: 'center'
+ },
+ renderTo: document.body,
+ items: [{
+ xtype: 'panel',
+ title: 'Inner Panel One',
+ width: 250,
+ flex: 2
+ },{
+ xtype: 'panel',
+ title: 'Inner Panel Two',
+ width: 250,
+ flex: 4
+ },{
+ xtype: 'panel',
+ title: 'Inner Panel Three',
+ width: '50%',
+ flex: 4
+ }]
+ });
+ */
+Ext.define('Ext.layout.container.VBox', {
+
+ /* Begin Definitions */
+
+ alias: ['layout.vbox'],
+ extend: 'Ext.layout.container.Box',
+ alternateClassName: 'Ext.layout.VBoxLayout',
+
+ /* End Definitions */
+
+ /**
+ * @cfg {String} align
+ * Controls how the child items of the container are aligned. Acceptable configuration values for this
+ * property are:
+ * <div class="mdetail-params"><ul>
+ * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
+ * at the <b>left</b> side of the container</div></li>
+ * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
+ * <b>mid-width</b> of the container</div></li>
+ * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
+ * the width of the container</div></li>
+ * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
+ * the size of the largest item.</div></li>
+ * </ul></div>
+ */
+ align : 'left', // left, center, stretch, strechmax
+
+ //@private
+ alignCenteringString: 'center',
+
+ type: 'vbox',
+
+ direction: 'vertical',
+
+ // When creating an argument list to setSize, use this order
+ parallelSizeIndex: 1,
+ perpendicularSizeIndex: 0,
+
+ parallelPrefix: 'height',
+ parallelPrefixCap: 'Height',
+ parallelLT: 't',
+ parallelRB: 'b',
+ parallelBefore: 'top',
+ parallelBeforeCap: 'Top',
+ parallelAfter: 'bottom',
+ parallelPosition: 'y',
+
+ perpendicularPrefix: 'width',
+ perpendicularPrefixCap: 'Width',
+ perpendicularLT: 'l',
+ perpendicularRB: 'r',
+ perpendicularLeftTop: 'left',
+ perpendicularRightBottom: 'right',
+ perpendicularPosition: 'x'
+});
+/**
+ * @class Ext.FocusManager
+
+The FocusManager is responsible for globally:
+
+1. Managing component focus
+2. Providing basic keyboard navigation
+3. (optional) Provide a visual cue for focused components, in the form of a focus ring/frame.
+
+To activate the FocusManager, simply call {@link #enable `Ext.FocusManager.enable();`}. In turn, you may
+deactivate the FocusManager by subsequently calling {@link #disable `Ext.FocusManager.disable();`}. The
+FocusManager is disabled by default.
+
+To enable the optional focus frame, pass `true` or `{focusFrame: true}` to {@link #enable}.
+
+Another feature of the FocusManager is to provide basic keyboard focus navigation scoped to any {@link Ext.container.Container}
+that would like to have navigation between its child {@link Ext.Component}'s. The {@link Ext.container.Container} can simply
+call {@link #subscribe Ext.FocusManager.subscribe} to take advantage of this feature, and can at any time call
+{@link #unsubscribe Ext.FocusManager.unsubscribe} to turn the navigation off.
+
+ * @singleton
+ * @markdown
+ * @author Jarred Nicholls <jarred@sencha.com>
+ * @docauthor Jarred Nicholls <jarred@sencha.com>
+ */
+Ext.define('Ext.FocusManager', {
+ singleton: true,
+ alternateClassName: 'Ext.FocusMgr',
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: [
+ 'Ext.ComponentManager',
+ 'Ext.ComponentQuery',
+ 'Ext.util.HashMap',
+ 'Ext.util.KeyNav'
+ ],
+
+ /**
+ * @property {Boolean} enabled
+ * Whether or not the FocusManager is currently enabled
+ */
+ enabled: false,
+
+ /**
+ * @property {Ext.Component} focusedCmp
+ * The currently focused component. Defaults to `undefined`.
+ * @markdown
+ */
+
+ focusElementCls: Ext.baseCSSPrefix + 'focus-element',
+
+ focusFrameCls: Ext.baseCSSPrefix + 'focus-frame',
+
+ /**
+ * @property {Array} whitelist
+ * A list of xtypes that should ignore certain navigation input keys and
+ * allow for the default browser event/behavior. These input keys include:
+ *
+ * 1. Backspace
+ * 2. Delete
+ * 3. Left
+ * 4. Right
+ * 5. Up
+ * 6. Down
+ *
+ * The FocusManager will not attempt to navigate when a component is an xtype (or descendents thereof)
+ * that belongs to this whitelist. E.g., an {@link Ext.form.field.Text} should allow
+ * the user to move the input cursor left and right, and to delete characters, etc.
+ *
+ * This whitelist currently defaults to `['textfield']`.
+ * @markdown
+ */
+ whitelist: [
+ 'textfield'
+ ],
+
+ tabIndexWhitelist: [
+ 'a',
+ 'button',
+ 'embed',
+ 'frame',
+ 'iframe',
+ 'img',
+ 'input',
+ 'object',
+ 'select',
+ 'textarea'
+ ],
+
+ constructor: function() {
+ var me = this,
+ CQ = Ext.ComponentQuery;
+
+ me.addEvents(
+ /**
+ * @event beforecomponentfocus
+ * Fires before a component becomes focused. Return `false` to prevent
+ * the component from gaining focus.
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ * @param {Ext.Component} cmp The component that is being focused
+ * @param {Ext.Component} previousCmp The component that was previously focused,
+ * or `undefined` if there was no previously focused component.
+ * @markdown
+ */
+ 'beforecomponentfocus',
+
+ /**
+ * @event componentfocus
+ * Fires after a component becomes focused.
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ * @param {Ext.Component} cmp The component that has been focused
+ * @param {Ext.Component} previousCmp The component that was previously focused,
+ * or `undefined` if there was no previously focused component.
+ * @markdown
+ */
+ 'componentfocus',
+
+ /**
+ * @event disable
+ * Fires when the FocusManager is disabled
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ */
+ 'disable',
+
+ /**
+ * @event enable
+ * Fires when the FocusManager is enabled
+ * @param {Ext.FocusManager} fm A reference to the FocusManager singleton
+ */
+ 'enable'
+ );
+
+ // Setup KeyNav that's bound to document to catch all
+ // unhandled/bubbled key events for navigation
+ me.keyNav = Ext.create('Ext.util.KeyNav', Ext.getDoc(), {
+ disabled: true,
+ scope: me,
+
+ backspace: me.focusLast,
+ enter: me.navigateIn,
+ esc: me.navigateOut,
+ tab: me.navigateSiblings
+
+ //space: me.navigateIn,
+ //del: me.focusLast,
+ //left: me.navigateSiblings,
+ //right: me.navigateSiblings,
+ //down: me.navigateSiblings,
+ //up: me.navigateSiblings
+ });
+
+ me.focusData = {};
+ me.subscribers = Ext.create('Ext.util.HashMap');
+ me.focusChain = {};
+
+ // Setup some ComponentQuery pseudos
+ Ext.apply(CQ.pseudos, {
+ focusable: function(cmps) {
+ var len = cmps.length,
+ results = [],
+ i = 0,
+ c,
+
+ isFocusable = function(x) {
+ return x && x.focusable !== false && CQ.is(x, '[rendered]:not([destroying]):not([isDestroyed]):not([disabled]){isVisible(true)}{el && c.el.dom && c.el.isVisible()}');
+ };
+
+ for (; i < len; i++) {
+ c = cmps[i];
+ if (isFocusable(c)) {
+ results.push(c);
+ }
+ }
+
+ return results;
+ },
+
+ nextFocus: function(cmps, idx, step) {
+ step = step || 1;
+ idx = parseInt(idx, 10);
+
+ var len = cmps.length,
+ i = idx + step,
+ c;
+
+ for (; i != idx; i += step) {
+ if (i >= len) {
+ i = 0;
+ } else if (i < 0) {
+ i = len - 1;
+ }
+
+ c = cmps[i];
+ if (CQ.is(c, ':focusable')) {
+ return [c];
+ } else if (c.placeholder && CQ.is(c.placeholder, ':focusable')) {
+ return [c.placeholder];
+ }
+ }
+
+ return [];
+ },
+
+ prevFocus: function(cmps, idx) {
+ return this.nextFocus(cmps, idx, -1);
+ },
+
+ root: function(cmps) {
+ var len = cmps.length,
+ results = [],
+ i = 0,
+ c;
+
+ for (; i < len; i++) {
+ c = cmps[i];
+ if (!c.ownerCt) {
+ results.push(c);
+ }
+ }
+
+ return results;
+ }
+ });
+ },
+
+ /**
+ * Adds the specified xtype to the {@link #whitelist}.
+ * @param {String/Array} xtype Adds the xtype(s) to the {@link #whitelist}.
+ */
+ addXTypeToWhitelist: function(xtype) {
+ var me = this;
+
+ if (Ext.isArray(xtype)) {
+ Ext.Array.forEach(xtype, me.addXTypeToWhitelist, me);
+ return;
+ }
+
+ if (!Ext.Array.contains(me.whitelist, xtype)) {
+ me.whitelist.push(xtype);
+ }
+ },
+
+ clearComponent: function(cmp) {
+ clearTimeout(this.cmpFocusDelay);
+ if (!cmp.isDestroyed) {
+ cmp.blur();
+ }
+ },
+
+ /**
+ * Disables the FocusManager by turning of all automatic focus management and keyboard navigation
+ */
+ disable: function() {
+ var me = this;
+
+ if (!me.enabled) {
+ return;
+ }
+
+ delete me.options;
+ me.enabled = false;
+
+ Ext.ComponentManager.all.un('add', me.onComponentCreated, me);
+
+ me.removeDOM();
+
+ // Stop handling key navigation
+ me.keyNav.disable();
+
+ // disable focus for all components
+ me.setFocusAll(false);
+
+ me.fireEvent('disable', me);
+ },
+
+ /**
+ * Enables the FocusManager by turning on all automatic focus management and keyboard navigation
+ * @param {Boolean/Object} options Either `true`/`false` to turn on the focus frame, or an object of the following options:
+ - focusFrame : Boolean
+ `true` to show the focus frame around a component when it is focused. Defaults to `false`.
+ * @markdown
+ */
+ enable: function(options) {
+ var me = this;
+
+ if (options === true) {
+ options = { focusFrame: true };
+ }
+ me.options = options = options || {};
+
+ if (me.enabled) {
+ return;
+ }
+
+ // Handle components that are newly added after we are enabled
+ Ext.ComponentManager.all.on('add', me.onComponentCreated, me);
+
+ me.initDOM(options);
+
+ // Start handling key navigation
+ me.keyNav.enable();
+
+ // enable focus for all components
+ me.setFocusAll(true, options);
+
+ // Finally, let's focus our global focus el so we start fresh
+ me.focusEl.focus();
+ delete me.focusedCmp;
+
+ me.enabled = true;
+ me.fireEvent('enable', me);
+ },
+
+ focusLast: function(e) {
+ var me = this;
+
+ if (me.isWhitelisted(me.focusedCmp)) {
+ return true;
+ }
+
+ // Go back to last focused item
+ if (me.previousFocusedCmp) {
+ me.previousFocusedCmp.focus();
+ }
+ },
+
+ getRootComponents: function() {
+ var me = this,
+ CQ = Ext.ComponentQuery,
+ inline = CQ.query(':focusable:root:not([floating])'),
+ floating = CQ.query(':focusable:root[floating]');
+
+ // Floating items should go to the top of our root stack, and be ordered
+ // by their z-index (highest first)
+ floating.sort(function(a, b) {
+ return a.el.getZIndex() > b.el.getZIndex();
+ });
+
+ return floating.concat(inline);
+ },
+
+ initDOM: function(options) {
+ var me = this,
+ sp = ' ',
+ cls = me.focusFrameCls;
+
+ if (!Ext.isReady) {
+ Ext.onReady(me.initDOM, me);
+ return;
+ }
+
+ // Create global focus element
+ if (!me.focusEl) {
+ me.focusEl = Ext.getBody().createChild({
+ tabIndex: '-1',
+ cls: me.focusElementCls,
+ html: sp
+ });
+ }
+
+ // Create global focus frame
+ if (!me.focusFrame && options.focusFrame) {
+ me.focusFrame = Ext.getBody().createChild({
+ cls: cls,
+ children: [
+ { cls: cls + '-top' },
+ { cls: cls + '-bottom' },
+ { cls: cls + '-left' },
+ { cls: cls + '-right' }
+ ],
+ style: 'top: -100px; left: -100px;'
+ });
+ me.focusFrame.setVisibilityMode(Ext.core.Element.DISPLAY);
+ me.focusFrameWidth = me.focusFrame.child('.' + cls + '-top').getHeight();
+ me.focusFrame.hide().setLeftTop(0, 0);
+ }
+ },
+
+ isWhitelisted: function(cmp) {
+ return cmp && Ext.Array.some(this.whitelist, function(x) {
+ return cmp.isXType(x);
+ });
+ },
+
+ navigateIn: function(e) {
+ var me = this,
+ focusedCmp = me.focusedCmp,
+ rootCmps,
+ firstChild;
+
+ if (!focusedCmp) {
+ // No focus yet, so focus the first root cmp on the page
+ rootCmps = me.getRootComponents();
+ if (rootCmps.length) {
+ rootCmps[0].focus();
+ }
+ } else {
+ // Drill into child ref items of the focused cmp, if applicable.
+ // This works for any Component with a getRefItems implementation.
+ firstChild = Ext.ComponentQuery.query('>:focusable', focusedCmp)[0];
+ if (firstChild) {
+ firstChild.focus();
+ } else {
+ // Let's try to fire a click event, as if it came from the mouse
+ if (Ext.isFunction(focusedCmp.onClick)) {
+ e.button = 0;
+ focusedCmp.onClick(e);
+ focusedCmp.focus();
+ }
+ }
+ }
+ },
+
+ navigateOut: function(e) {
+ var me = this,
+ parent;
+
+ if (!me.focusedCmp || !(parent = me.focusedCmp.up(':focusable'))) {
+ me.focusEl.focus();
+ return;
+ }
+
+ parent.focus();
+ },
+
+ navigateSiblings: function(e, source, parent) {
+ var me = this,
+ src = source || me,
+ key = e.getKey(),
+ EO = Ext.EventObject,
+ goBack = e.shiftKey || key == EO.LEFT || key == EO.UP,
+ checkWhitelist = key == EO.LEFT || key == EO.RIGHT || key == EO.UP || key == EO.DOWN,
+ nextSelector = goBack ? 'prev' : 'next',
+ idx, next, focusedCmp;
+
+ focusedCmp = (src.focusedCmp && src.focusedCmp.comp) || src.focusedCmp;
+ if (!focusedCmp && !parent) {
+ return;
+ }
+
+ if (checkWhitelist && me.isWhitelisted(focusedCmp)) {
+ return true;
+ }
+
+ parent = parent || focusedCmp.up();
+ if (parent) {
+ idx = focusedCmp ? Ext.Array.indexOf(parent.getRefItems(), focusedCmp) : -1;
+ next = Ext.ComponentQuery.query('>:' + nextSelector + 'Focus(' + idx + ')', parent)[0];
+ if (next && focusedCmp !== next) {
+ next.focus();
+ return next;
+ }
+ }
+ },
+
+ onComponentBlur: function(cmp, e) {
+ var me = this;
+
+ if (me.focusedCmp === cmp) {
+ me.previousFocusedCmp = cmp;
+ delete me.focusedCmp;
+ }
+
+ if (me.focusFrame) {
+ me.focusFrame.hide();
+ }
+ },
+
+ onComponentCreated: function(hash, id, cmp) {
+ this.setFocus(cmp, true, this.options);
+ },
+
+ onComponentDestroy: function(cmp) {
+ this.setFocus(cmp, false);
+ },
+
+ onComponentFocus: function(cmp, e) {
+ var me = this,
+ chain = me.focusChain;
+
+ if (!Ext.ComponentQuery.is(cmp, ':focusable')) {
+ me.clearComponent(cmp);
+
+ // Check our focus chain, so we don't run into a never ending recursion
+ // If we've attempted (unsuccessfully) to focus this component before,
+ // then we're caught in a loop of child->parent->...->child and we
+ // need to cut the loop off rather than feed into it.
+ if (chain[cmp.id]) {
+ return;
+ }
+
+ // Try to focus the parent instead
+ var parent = cmp.up();
+ if (parent) {
+ // Add component to our focus chain to detect infinite focus loop
+ // before we fire off an attempt to focus our parent.
+ // See the comments above.
+ chain[cmp.id] = true;
+ parent.focus();
+ }
+
+ return;
+ }
+
+ // Clear our focus chain when we have a focusable component
+ me.focusChain = {};
+
+ // Defer focusing for 90ms so components can do a layout/positioning
+ // and give us an ability to buffer focuses
+ clearTimeout(me.cmpFocusDelay);
+ if (arguments.length !== 2) {
+ me.cmpFocusDelay = Ext.defer(me.onComponentFocus, 90, me, [cmp, e]);
+ return;
+ }
+
+ if (me.fireEvent('beforecomponentfocus', me, cmp, me.previousFocusedCmp) === false) {
+ me.clearComponent(cmp);
+ return;
+ }
+
+ me.focusedCmp = cmp;
+
+ // If we have a focus frame, show it around the focused component
+ if (me.shouldShowFocusFrame(cmp)) {
+ var cls = '.' + me.focusFrameCls + '-',
+ ff = me.focusFrame,
+ fw = me.focusFrameWidth,
+ box = cmp.el.getPageBox(),
+
+ // Size the focus frame's t/b/l/r according to the box
+ // This leaves a hole in the middle of the frame so user
+ // interaction w/ the mouse can continue
+ bt = box.top,
+ bl = box.left,
+ bw = box.width,
+ bh = box.height,
+ ft = ff.child(cls + 'top'),
+ fb = ff.child(cls + 'bottom'),
+ fl = ff.child(cls + 'left'),
+ fr = ff.child(cls + 'right');
+
+ ft.setWidth(bw - 2).setLeftTop(bl + 1, bt);
+ fb.setWidth(bw - 2).setLeftTop(bl + 1, bt + bh - fw);
+ fl.setHeight(bh - 2).setLeftTop(bl, bt + 1);
+ fr.setHeight(bh - 2).setLeftTop(bl + bw - fw, bt + 1);
+
+ ff.show();
+ }
+
+ me.fireEvent('componentfocus', me, cmp, me.previousFocusedCmp);
+ },
+
+ onComponentHide: function(cmp) {
+ var me = this,
+ CQ = Ext.ComponentQuery,
+ cmpHadFocus = false,
+ focusedCmp,
+ parent;
+
+ if (me.focusedCmp) {
+ focusedCmp = CQ.query('[id=' + me.focusedCmp.id + ']', cmp)[0];
+ cmpHadFocus = me.focusedCmp.id === cmp.id || focusedCmp;
+
+ if (focusedCmp) {
+ me.clearComponent(focusedCmp);
+ }
+ }
+
+ me.clearComponent(cmp);
+
+ if (cmpHadFocus) {
+ parent = CQ.query('^:focusable', cmp)[0];
+ if (parent) {
+ parent.focus();
+ }
+ }
+ },
+
+ removeDOM: function() {
+ var me = this;
+
+ // If we are still enabled globally, or there are still subscribers
+ // then we will halt here, since our DOM stuff is still being used
+ if (me.enabled || me.subscribers.length) {
+ return;
+ }
+
+ Ext.destroy(
+ me.focusEl,
+ me.focusFrame
+ );
+ delete me.focusEl;
+ delete me.focusFrame;
+ delete me.focusFrameWidth;
+ },
+
+ /**
+ * Removes the specified xtype from the {@link #whitelist}.
+ * @param {String/Array} xtype Removes the xtype(s) from the {@link #whitelist}.
+ */
+ removeXTypeFromWhitelist: function(xtype) {
+ var me = this;
+
+ if (Ext.isArray(xtype)) {
+ Ext.Array.forEach(xtype, me.removeXTypeFromWhitelist, me);
+ return;
+ }
+
+ Ext.Array.remove(me.whitelist, xtype);
+ },
+
+ setFocus: function(cmp, focusable, options) {
+ var me = this,
+ el, dom, data,
+
+ needsTabIndex = function(n) {
+ return !Ext.Array.contains(me.tabIndexWhitelist, n.tagName.toLowerCase())
+ && n.tabIndex <= 0;
+ };
+
+ options = options || {};
+
+ // Come back and do this after the component is rendered
+ if (!cmp.rendered) {
+ cmp.on('afterrender', Ext.pass(me.setFocus, arguments, me), me, { single: true });
+ return;
+ }
+
+ el = cmp.getFocusEl();
+ dom = el.dom;
+
+ // Decorate the component's focus el for focus-ability
+ if ((focusable && !me.focusData[cmp.id]) || (!focusable && me.focusData[cmp.id])) {
+ if (focusable) {
+ data = {
+ focusFrame: options.focusFrame
+ };
+
+ // Only set -1 tabIndex if we need it
+ // inputs, buttons, and anchor tags do not need it,
+ // and neither does any DOM that has it set already
+ // programmatically or in markup.
+ if (needsTabIndex(dom)) {
+ data.tabIndex = dom.tabIndex;
+ dom.tabIndex = -1;
+ }
+
+ el.on({
+ focus: data.focusFn = Ext.bind(me.onComponentFocus, me, [cmp], 0),
+ blur: data.blurFn = Ext.bind(me.onComponentBlur, me, [cmp], 0),
+ scope: me
+ });
+ cmp.on({
+ hide: me.onComponentHide,
+ close: me.onComponentHide,
+ beforedestroy: me.onComponentDestroy,
+ scope: me
+ });
+
+ me.focusData[cmp.id] = data;
+ } else {
+ data = me.focusData[cmp.id];
+ if ('tabIndex' in data) {
+ dom.tabIndex = data.tabIndex;
+ }
+ el.un('focus', data.focusFn, me);
+ el.un('blur', data.blurFn, me);
+ cmp.un('hide', me.onComponentHide, me);
+ cmp.un('close', me.onComponentHide, me);
+ cmp.un('beforedestroy', me.onComponentDestroy, me);
+
+ delete me.focusData[cmp.id];
+ }
+ }
+ },
+
+ setFocusAll: function(focusable, options) {
+ var me = this,
+ cmps = Ext.ComponentManager.all.getArray(),
+ len = cmps.length,
+ cmp,
+ i = 0;
+
+ for (; i < len; i++) {
+ me.setFocus(cmps[i], focusable, options);
+ }
+ },
+
+ setupSubscriberKeys: function(container, keys) {
+ var me = this,
+ el = container.getFocusEl(),
+ scope = keys.scope,
+ handlers = {
+ backspace: me.focusLast,
+ enter: me.navigateIn,
+ esc: me.navigateOut,
+ scope: me
+ },
+
+ navSiblings = function(e) {
+ if (me.focusedCmp === container) {
+ // Root the sibling navigation to this container, so that we
+ // can automatically dive into the container, rather than forcing
+ // the user to hit the enter key to dive in.
+ return me.navigateSiblings(e, me, container);
+ } else {
+ return me.navigateSiblings(e);
+ }
+ };
+
+ Ext.iterate(keys, function(key, cb) {
+ handlers[key] = function(e) {
+ var ret = navSiblings(e);
+
+ if (Ext.isFunction(cb) && cb.call(scope || container, e, ret) === true) {
+ return true;
+ }
+
+ return ret;
+ };
+ }, me);
+
+ return Ext.create('Ext.util.KeyNav', el, handlers);
+ },
+
+ shouldShowFocusFrame: function(cmp) {
+ var me = this,
+ opts = me.options || {};
+
+ if (!me.focusFrame || !cmp) {
+ return false;
+ }
+
+ // Global trumps
+ if (opts.focusFrame) {
+ return true;
+ }
+
+ if (me.focusData[cmp.id].focusFrame) {
+ return true;
+ }
+
+ return false;
+ },
+
+ /**
+ * Subscribes an {@link Ext.container.Container} to provide basic keyboard focus navigation between its child {@link Ext.Component}'s.
+ * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} on which to enable keyboard functionality and focus management.
+ * @param {Boolean/Object} options An object of the following options:
+ - keys : Array/Object
+ An array containing the string names of navigation keys to be supported. The allowed values are:
+
+ - 'left'
+ - 'right'
+ - 'up'
+ - 'down'
+
+ Or, an object containing those key names as keys with `true` or a callback function as their value. A scope may also be passed. E.g.:
+
+ {
+ left: this.onLeftKey,
+ right: this.onRightKey,
+ scope: this
+ }
+
+ - focusFrame : Boolean (optional)
+ `true` to show the focus frame around a component when it is focused. Defaults to `false`.
+ * @markdown
+ */
+ subscribe: function(container, options) {
+ var me = this,
+ EA = Ext.Array,
+ data = {},
+ subs = me.subscribers,
+
+ // Recursively add focus ability as long as a descendent container isn't
+ // itself subscribed to the FocusManager, or else we'd have unwanted side
+ // effects for subscribing a descendent container twice.
+ safeSetFocus = function(cmp) {
+ if (cmp.isContainer && !subs.containsKey(cmp.id)) {
+ EA.forEach(cmp.query('>'), safeSetFocus);
+ me.setFocus(cmp, true, options);
+ cmp.on('add', data.onAdd, me);
+ } else if (!cmp.isContainer) {
+ me.setFocus(cmp, true, options);
+ }
+ };
+
+ // We only accept containers
+ if (!container || !container.isContainer) {
+ return;
+ }
+
+ if (!container.rendered) {
+ container.on('afterrender', Ext.pass(me.subscribe, arguments, me), me, { single: true });
+ return;
+ }
+
+ // Init the DOM, incase this is the first time it will be used
+ me.initDOM(options);
+
+ // Create key navigation for subscriber based on keys option
+ data.keyNav = me.setupSubscriberKeys(container, options.keys);
+
+ // We need to keep track of components being added to our subscriber
+ // and any containers nested deeply within it (omg), so let's do that.
+ // Components that are removed are globally handled.
+ // Also keep track of destruction of our container for auto-unsubscribe.
+ data.onAdd = function(ct, cmp, idx) {
+ safeSetFocus(cmp);
+ };
+ container.on('beforedestroy', me.unsubscribe, me);
+
+ // Now we setup focusing abilities for the container and all its components
+ safeSetFocus(container);
+
+ // Add to our subscribers list
+ subs.add(container.id, data);
+ },
+
+ /**
+ * Unsubscribes an {@link Ext.container.Container} from keyboard focus management.
+ * @param {Ext.container.Container} container A reference to the {@link Ext.container.Container} to unsubscribe from the FocusManager.
+ * @markdown
+ */
+ unsubscribe: function(container) {
+ var me = this,
+ EA = Ext.Array,
+ subs = me.subscribers,
+ data,
+
+ // Recursively remove focus ability as long as a descendent container isn't
+ // itself subscribed to the FocusManager, or else we'd have unwanted side
+ // effects for unsubscribing an ancestor container.
+ safeSetFocus = function(cmp) {
+ if (cmp.isContainer && !subs.containsKey(cmp.id)) {
+ EA.forEach(cmp.query('>'), safeSetFocus);
+ me.setFocus(cmp, false);
+ cmp.un('add', data.onAdd, me);
+ } else if (!cmp.isContainer) {
+ me.setFocus(cmp, false);
+ }
+ };
+
+ if (!container || !subs.containsKey(container.id)) {
+ return;
+ }
+
+ data = subs.get(container.id);
+ data.keyNav.destroy();
+ container.un('beforedestroy', me.unsubscribe, me);
+ subs.removeAtKey(container.id);
+ safeSetFocus(container);
+ me.removeDOM();
+ }
+});
+/**
+ * @class Ext.toolbar.Toolbar
+ * @extends Ext.container.Container
+
+Basic Toolbar class. Although the {@link Ext.container.Container#defaultType defaultType} for Toolbar is {@link Ext.button.Button button}, Toolbar
+elements (child items for the Toolbar container) may be virtually any type of Component. Toolbar elements can be created explicitly via their
+constructors, or implicitly via their xtypes, and can be {@link #add}ed dynamically.
+
+__Some items have shortcut strings for creation:__
+
+| Shortcut | xtype | Class | Description |
+|:---------|:--------------|:------------------------------|:---------------------------------------------------|
+| `->` | `tbspacer` | {@link Ext.toolbar.Fill} | begin using the right-justified button container |
+| `-` | `tbseparator` | {@link Ext.toolbar.Separator} | add a vertical separator bar between toolbar items |
+| ` ` | `tbspacer` | {@link Ext.toolbar.Spacer} | add horiztonal space between elements |
+
+{@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar1.png Toolbar component}
+Example usage:
+
+ Ext.create('Ext.toolbar.Toolbar", {
+ renderTo: document.body,
+ width : 500,
+ items: [
+ {
+ // xtype: 'button', // default for Toolbars
+ text: 'Button'
+ },
+ {
+ xtype: 'splitbutton',
+ text : 'Split Button'
+ },
+ // begin using the right-justified button container
+ '->', // same as {xtype: 'tbfill'}, // Ext.toolbar.Fill
+ {
+ xtype : 'textfield',
+ name : 'field1',
+ emptyText: 'enter search term'
+ },
+ // add a vertical separator bar between toolbar items
+ '-', // same as {xtype: 'tbseparator'} to create Ext.toolbar.Separator
+ 'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.toolbar.TextItem
+ {xtype: 'tbspacer'},// same as ' ' to create Ext.toolbar.Spacer
+ 'text 2',
+ {xtype: 'tbspacer', width: 50}, // add a 50px space
+ 'text 3'
+ ]
+ });
+
+Toolbars have {@link #enable} and {@link #disable} methods which when called, will enable/disable all items within your toolbar.
+
+{@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar2.png Toolbar component}
+Example usage:
+
+ Ext.create('Ext.toolbar.Toolbar', {
+ renderTo: document.body,
+ width : 400,
+ items: [
+ {
+ text: 'Button'
+ },
+ {
+ xtype: 'splitbutton',
+ text : 'Split Button'
+ },
+ '->',
+ {
+ xtype : 'textfield',
+ name : 'field1',
+ emptyText: 'enter search term'
+ }
+ ]
+ });
+
+{@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar3.png Toolbar component}
+Example usage:
+
+ var enableBtn = Ext.create('Ext.button.Button', {
+ text : 'Enable All Items',
+ disabled: true,
+ scope : this,
+ handler : function() {
+ //disable the enable button and enable the disable button
+ enableBtn.disable();
+ disableBtn.enable();
+
+ //enable the toolbar
+ toolbar.enable();
+ }
+ });
+
+ var disableBtn = Ext.create('Ext.button.Button', {
+ text : 'Disable All Items',
+ scope : this,
+ handler : function() {
+ //enable the enable button and disable button
+ disableBtn.disable();
+ enableBtn.enable();
+
+ //disable the toolbar
+ toolbar.disable();
+ }
+ });
+
+ var toolbar = Ext.create('Ext.toolbar.Toolbar', {
+ renderTo: document.body,
+ width : 400,
+ margin : '5 0 0 0',
+ items : [enableBtn, disableBtn]
+ });
+
+Adding items to and removing items from a toolbar is as simple as calling the {@link #add} and {@link #remove} methods. There is also a {@link #removeAll} method
+which remove all items within the toolbar.
+
+{@img Ext.toolbar.Toolbar/Ext.toolbar.Toolbar4.png Toolbar component}
+Example usage:
+
+ var toolbar = Ext.create('Ext.toolbar.Toolbar', {
+ renderTo: document.body,
+ width : 700,
+ items: [
+ {
+ text: 'Example Button'
+ }
+ ]
+ });
+
+ var addedItems = [];
+
+ Ext.create('Ext.toolbar.Toolbar', {
+ renderTo: document.body,
+ width : 700,
+ margin : '5 0 0 0',
+ items : [
+ {
+ text : 'Add a button',
+ scope : this,
+ handler: function() {
+ var text = prompt('Please enter the text for your button:');
+ addedItems.push(toolbar.add({
+ text: text
+ }));
+ }
+ },
+ {
+ text : 'Add a text item',
+ scope : this,
+ handler: function() {
+ var text = prompt('Please enter the text for your item:');
+ addedItems.push(toolbar.add(text));
+ }
+ },
+ {
+ text : 'Add a toolbar seperator',
+ scope : this,
+ handler: function() {
+ addedItems.push(toolbar.add('-'));
+ }
+ },
+ {
+ text : 'Add a toolbar spacer',
+ scope : this,
+ handler: function() {
+ addedItems.push(toolbar.add('->'));
+ }
+ },
+ '->',
+ {
+ text : 'Remove last inserted item',
+ scope : this,
+ handler: function() {
+ if (addedItems.length) {
+ toolbar.remove(addedItems.pop());
+ } else if (toolbar.items.length) {
+ toolbar.remove(toolbar.items.last());
+ } else {
+ alert('No items in the toolbar');
+ }
+ }
+ },
+ {
+ text : 'Remove all items',
+ scope : this,
+ handler: function() {
+ toolbar.removeAll();
+ }
+ }
+ ]
+ });
+
+ * @constructor
+ * Creates a new Toolbar
+ * @param {Object/Array} config A config object or an array of buttons to <code>{@link #add}</code>
+ * @xtype toolbar
+ * @docauthor Robert Dougan <rob@sencha.com>
+ * @markdown
+ */
+Ext.define('Ext.toolbar.Toolbar', {
+ extend: 'Ext.container.Container',
+ requires: [
+ 'Ext.toolbar.Fill',
+ 'Ext.layout.container.HBox',
+ 'Ext.layout.container.VBox',
+ 'Ext.FocusManager'
+ ],
+ uses: [
+ 'Ext.toolbar.Separator'
+ ],
+ alias: 'widget.toolbar',
+ alternateClassName: 'Ext.Toolbar',
+
+ isToolbar: true,
+ baseCls : Ext.baseCSSPrefix + 'toolbar',
+ ariaRole : 'toolbar',
+
+ defaultType: 'button',
+
+ /**
+ * @cfg {Boolean} vertical
+ * Set to `true` to make the toolbar vertical. The layout will become a `vbox`.
+ * (defaults to `false`)
+ */
+ vertical: false,
+
+ /**
+ * @cfg {String/Object} layout
+ * This class assigns a default layout (<code>layout:'<b>hbox</b>'</code>).
+ * Developers <i>may</i> override this configuration option if another layout
+ * is required (the constructor must be passed a configuration object in this
+ * case instead of an array).
+ * See {@link Ext.container.Container#layout} for additional information.
+ */
+
+ /**
+ * @cfg {Boolean} enableOverflow
+ * Defaults to false. Configure <code>true</code> to make the toolbar provide a button
+ * which activates a dropdown Menu to show items which overflow the Toolbar's width.
+ */
+ enableOverflow: false,
+
+ // private
+ trackMenus: true,
+
+ itemCls: Ext.baseCSSPrefix + 'toolbar-item',
+
+ initComponent: function() {
+ var me = this,
+ keys;
+
+ // check for simplified (old-style) overflow config:
+ if (!me.layout && me.enableOverflow) {
+ me.layout = { overflowHandler: 'Menu' };
+ }
+
+ if (me.dock === 'right' || me.dock === 'left') {
+ me.vertical = true;
+ }
+
+ me.layout = Ext.applyIf(Ext.isString(me.layout) ? {
+ type: me.layout
+ } : me.layout || {}, {
+ type: me.vertical ? 'vbox' : 'hbox',
+ align: me.vertical ? 'stretchmax' : 'middle'
+ });
+
+ if (me.vertical) {
+ me.addClsWithUI('vertical');
+ }
+
+ // @TODO: remove this hack and implement a more general solution
+ if (me.ui === 'footer') {
+ me.ignoreBorderManagement = true;
+ }
+
+ me.callParent();
+
+ /**
+ * @event overflowchange
+ * Fires after the overflow state has changed.
+ * @param {Object} c The Container
+ * @param {Boolean} lastOverflow overflow state
+ */
+ me.addEvents('overflowchange');
+
+ // Subscribe to Ext.FocusManager for key navigation
+ keys = me.vertical ? ['up', 'down'] : ['left', 'right'];
+ Ext.FocusManager.subscribe(me, {
+ keys: keys
+ });
+ },
+
+ /**
+ * <p>Adds element(s) to the toolbar -- this function takes a variable number of
+ * arguments of mixed type and adds them to the toolbar.</p>
+ * <br><p><b>Note</b>: See the notes within {@link Ext.container.Container#add}.</p>
+ * @param {Mixed} arg1 The following types of arguments are all valid:<br />
+ * <ul>
+ * <li>{@link Ext.button.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
+ * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
+ * <li>Field: Any form field (equivalent to {@link #addField})</li>
+ * <li>Item: Any subclass of {@link Ext.toolbar.Item} (equivalent to {@link #addItem})</li>
+ * <li>String: Any generic string (gets wrapped in a {@link Ext.toolbar.TextItem}, equivalent to {@link #addText}).
+ * Note that there are a few special strings that are treated differently as explained next.</li>
+ * <li>'-': Creates a separator element (equivalent to {@link #addSeparator})</li>
+ * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
+ * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
+ * </ul>
+ * @param {Mixed} arg2
+ * @param {Mixed} etc.
+ * @method add
+ */
+
+ // private
+ lookupComponent: function(c) {
+ if (Ext.isString(c)) {
+ var shortcut = Ext.toolbar.Toolbar.shortcuts[c];
+ if (shortcut) {
+ c = {
+ xtype: shortcut
+ };
+ } else {
+ c = {
+ xtype: 'tbtext',
+ text: c
+ };
+ }
+ this.applyDefaults(c);
+ }
+ return this.callParent(arguments);
+ },
+
+ // private
+ applyDefaults: function(c) {
+ if (!Ext.isString(c)) {
+ c = this.callParent(arguments);
+ var d = this.internalDefaults;
+ if (c.events) {
+ Ext.applyIf(c.initialConfig, d);
+ Ext.apply(c, d);
+ } else {
+ Ext.applyIf(c, d);
+ }
+ }
+ return c;
+ },
+
+ // private
+ trackMenu: function(item, remove) {
+ if (this.trackMenus && item.menu) {
+ var method = remove ? 'mun' : 'mon',
+ me = this;
+
+ me[method](item, 'menutriggerover', me.onButtonTriggerOver, me);
+ me[method](item, 'menushow', me.onButtonMenuShow, me);
+ me[method](item, 'menuhide', me.onButtonMenuHide, me);
+ }
+ },
+
+ // private
+ constructButton: function(item) {
+ return item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
+ },
+
+ // private
+ onBeforeAdd: function(component) {
+ if (component.is('field') || (component.is('button') && this.ui != 'footer')) {
+ component.ui = component.ui + '-toolbar';
+ }
+
+ // Any separators needs to know if is vertical or not
+ if (component instanceof Ext.toolbar.Separator) {
+ component.setUI((this.vertical) ? 'vertical' : 'horizontal');
+ }
+
+ this.callParent(arguments);
+ },
+
+ // private
+ onAdd: function(component) {
+ this.callParent(arguments);
+
+ this.trackMenu(component);
+ if (this.disabled) {
+ component.disable();
+ }
+ },
+
+ // private
+ onRemove: function(c) {
+ this.callParent(arguments);
+ this.trackMenu(c, true);
+ },
+
+ // private
+ onButtonTriggerOver: function(btn){
+ if (this.activeMenuBtn && this.activeMenuBtn != btn) {
+ this.activeMenuBtn.hideMenu();
+ btn.showMenu();
+ this.activeMenuBtn = btn;
+ }
+ },
+
+ // private
+ onButtonMenuShow: function(btn) {
+ this.activeMenuBtn = btn;
+ },
+
+ // private
+ onButtonMenuHide: function(btn) {
+ delete this.activeMenuBtn;
+ }
+}, function() {
+ this.shortcuts = {
+ '-' : 'tbseparator',
+ ' ' : 'tbspacer',
+ '->': 'tbfill'
+ };
+});
+/**
+ * @class Ext.panel.AbstractPanel
+ * @extends Ext.container.Container
+ * <p>A base class which provides methods common to Panel classes across the Sencha product range.</p>
+ * <p>Please refer to sub class's documentation</p>
+ * @constructor
+ * @param {Object} config The config object
+ */
+Ext.define('Ext.panel.AbstractPanel', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.container.Container',
+
+ requires: ['Ext.util.MixedCollection', 'Ext.core.Element', 'Ext.toolbar.Toolbar'],
+
+ /* End Definitions */
+
+ /**
+ * @cfg {String} baseCls
+ * The base CSS class to apply to this panel's element (defaults to <code>'x-panel'</code>).
+ */
+ baseCls : Ext.baseCSSPrefix + 'panel',
+
+ /**
+ * @cfg {Number/String} bodyPadding
+ * A shortcut for setting a padding style on the body element. The value can either be
+ * a number to be applied to all sides, or a normal css string describing padding.
+ * Defaults to <code>undefined</code>.
+ */
+
+ /**
+ * @cfg {Boolean} bodyBorder
+ * A shortcut to add or remove the border on the body of a panel. This only applies to a panel which has the {@link #frame} configuration set to `true`.
+ * Defaults to <code>undefined</code>.
+ */
+
+ /**
+ * @cfg {String/Object/Function} bodyStyle
+ * Custom CSS styles to be applied to the panel's body element, which can be supplied as a valid CSS style string,
+ * an object containing style property name/value pairs or a function that returns such a string or object.
+ * For example, these two formats are interpreted to be equivalent:<pre><code>
+bodyStyle: 'background:#ffc; padding:10px;'
+
+bodyStyle: {
+ background: '#ffc',
+ padding: '10px'
+}
+ * </code></pre>
+ */
+
+ /**
+ * @cfg {String/Array} bodyCls
+ * A CSS class, space-delimited string of classes, or array of classes to be applied to the panel's body element.
+ * The following examples are all valid:<pre><code>
+bodyCls: 'foo'
+bodyCls: 'foo bar'
+bodyCls: ['foo', 'bar']
+ * </code></pre>
+ */
+
+ isPanel: true,
+
+ componentLayout: 'dock',
+
+ renderTpl: ['<div class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl> {baseCls}-body-{ui}<tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
+
+ // TODO: Move code examples into product-specific files. The code snippet below is Touch only.
+ /**
+ * @cfg {Object/Array} dockedItems
+ * A component or series of components to be added as docked items to this panel.
+ * The docked items can be docked to either the top, right, left or bottom of a panel.
+ * This is typically used for things like toolbars or tab bars:
+ * <pre><code>
+var panel = new Ext.panel.Panel({
+ fullscreen: true,
+ dockedItems: [{
+ xtype: 'toolbar',
+ dock: 'top',
+ items: [{
+ text: 'Docked to the top'
+ }]
+ }]
+});</code></pre>
+ */
+
+ border: true,
+
+ initComponent : function() {
+ var me = this;
+
+ me.addEvents(
+ /**
+ * @event bodyresize
+ * Fires after the Panel has been resized.
+ * @param {Ext.panel.Panel} p the Panel which has been resized.
+ * @param {Number} width The Panel body's new width.
+ * @param {Number} height The Panel body's new height.
+ */
+ 'bodyresize'
+ // // inherited
+ // 'activate',
+ // // inherited
+ // 'deactivate'
+ );
+
+ Ext.applyIf(me.renderSelectors, {
+ body: '.' + me.baseCls + '-body'
+ });
+
+ //!frame
+ //!border
+
+ if (me.frame && me.border && me.bodyBorder === undefined) {
+ me.bodyBorder = false;
+ }
+ if (me.frame && me.border && (me.bodyBorder === false || me.bodyBorder === 0)) {
+ me.manageBodyBorders = true;
+ }
+
+ me.callParent();
+ },
+
+ // @private
+ initItems : function() {
+ var me = this,
+ items = me.dockedItems;
+
+ me.callParent();
+ me.dockedItems = Ext.create('Ext.util.MixedCollection', false, me.getComponentId);
+ if (items) {
+ me.addDocked(items);
+ }
+ },
+
+ /**
+ * Finds a docked component by id, itemId or position. Also see {@link #getDockedItems}
+ * @param {String/Number} comp The id, itemId or position of the docked component (see {@link #getComponent} for details)
+ * @return {Ext.Component} The docked component (if found)
+ */
+ getDockedComponent: function(comp) {
+ if (Ext.isObject(comp)) {
+ comp = comp.getItemId();
+ }
+ return this.dockedItems.get(comp);
+ },
+
+ /**
+ * Attempts a default component lookup (see {@link Ext.container.Container#getComponent}). If the component is not found in the normal
+ * items, the dockedItems are searched and the matched component (if any) returned (see {@loink #getDockedComponent}). Note that docked
+ * items will only be matched by component id or itemId -- if you pass a numeric index only non-docked child components will be searched.
+ * @param {String/Number} comp The component id, itemId or position to find
+ * @return {Ext.Component} The component (if found)
+ */
+ getComponent: function(comp) {
+ var component = this.callParent(arguments);
+ if (component === undefined && !Ext.isNumber(comp)) {
+ // If the arg is a numeric index skip docked items
+ component = this.getDockedComponent(comp);
+ }
+ return component;
+ },
+
+ /**
+ * Parses the {@link bodyStyle} config if available to create a style string that will be applied to the body element.
+ * This also includes {@link bodyPadding} and {@link bodyBorder} if available.
+ * @return {String} A CSS style string with body styles, padding and border.
+ * @private
+ */
+ initBodyStyles: function() {
+ var me = this,
+ bodyStyle = me.bodyStyle,
+ styles = [],
+ Element = Ext.core.Element,
+ prop;
+
+ if (Ext.isFunction(bodyStyle)) {
+ bodyStyle = bodyStyle();
+ }
+ if (Ext.isString(bodyStyle)) {
+ styles = bodyStyle.split(';');
+ } else {
+ for (prop in bodyStyle) {
+ if (bodyStyle.hasOwnProperty(prop)) {
+ styles.push(prop + ':' + bodyStyle[prop]);
+ }
+ }
+ }
+
+ if (me.bodyPadding !== undefined) {
+ styles.push('padding: ' + Element.unitizeBox((me.bodyPadding === true) ? 5 : me.bodyPadding));
+ }
+ if (me.frame && me.bodyBorder) {
+ if (!Ext.isNumber(me.bodyBorder)) {
+ me.bodyBorder = 1;
+ }
+ styles.push('border-width: ' + Element.unitizeBox(me.bodyBorder));
+ }
+ delete me.bodyStyle;
+ return styles.length ? styles.join(';') : undefined;
+ },
+
+ /**
+ * Parse the {@link bodyCls} config if available to create a comma-delimited string of
+ * CSS classes to be applied to the body element.
+ * @return {String} The CSS class(es)
+ * @private
+ */
+ initBodyCls: function() {
+ var me = this,
+ cls = '',
+ bodyCls = me.bodyCls;
+
+ if (bodyCls) {
+ Ext.each(bodyCls, function(v) {
+ cls += " " + v;
+ });
+ delete me.bodyCls;
+ }
+ return cls.length > 0 ? cls : undefined;
+ },
+
+ /**
+ * Initialized the renderData to be used when rendering the renderTpl.
+ * @return {Object} Object with keys and values that are going to be applied to the renderTpl
+ * @private
+ */
+ initRenderData: function() {
+ return Ext.applyIf(this.callParent(), {
+ bodyStyle: this.initBodyStyles(),
+ bodyCls: this.initBodyCls()
+ });
+ },
+
+ /**
+ * Adds docked item(s) to the panel.
+ * @param {Object/Array} component The Component or array of components to add. The components
+ * must include a 'dock' parameter on each component to indicate where it should be docked ('top', 'right',
+ * 'bottom', 'left').
+ * @param {Number} pos (optional) The index at which the Component will be added
+ */
+ addDocked : function(items, pos) {
+ var me = this,
+ i = 0,
+ item, length;
+
+ items = me.prepareItems(items);
+ length = items.length;
+
+ for (; i < length; i++) {
+ item = items[i];
+ item.dock = item.dock || 'top';
+
+ // Allow older browsers to target docked items to style without borders
+ if (me.border === false) {
+ // item.cls = item.cls || '' + ' ' + me.baseCls + '-noborder-docked-' + item.dock;
+ }
+
+ if (pos !== undefined) {
+ me.dockedItems.insert(pos + i, item);
+ }
+ else {
+ me.dockedItems.add(item);
+ }
+ item.onAdded(me, i);
+ me.onDockedAdd(item);
+ }
+ if (me.rendered) {
+ me.doComponentLayout();
+ }
+ return items;
+ },
+
+ // Placeholder empty functions
+ onDockedAdd : Ext.emptyFn,
+ onDockedRemove : Ext.emptyFn,
+
+ /**
+ * Inserts docked item(s) to the panel at the indicated position.
+ * @param {Number} pos The index at which the Component will be inserted
+ * @param {Object/Array} component. The Component or array of components to add. The components
+ * must include a 'dock' paramater on each component to indicate where it should be docked ('top', 'right',
+ * 'bottom', 'left').
+ */
+ insertDocked : function(pos, items) {
+ this.addDocked(items, pos);
+ },
+
+ /**
+ * Removes the docked item from the panel.
+ * @param {Ext.Component} item. The Component to remove.
+ * @param {Boolean} autoDestroy (optional) Destroy the component after removal.
+ */
+ removeDocked : function(item, autoDestroy) {
+ var me = this,
+ layout,
+ hasLayout;
+
+ if (!me.dockedItems.contains(item)) {
+ return item;
+ }
+
+ layout = me.componentLayout;
+ hasLayout = layout && me.rendered;
+
+ if (hasLayout) {
+ layout.onRemove(item);
+ }
+
+ me.dockedItems.remove(item);
+ item.onRemoved();
+ me.onDockedRemove(item);
+
+ if (autoDestroy === true || (autoDestroy !== false && me.autoDestroy)) {
+ item.destroy();
+ }
+
+ if (hasLayout && !autoDestroy) {
+ layout.afterRemove(item);
+ }
+
+ if (!this.destroying) {
+ me.doComponentLayout();
+ }
+
+ return item;
+ },
+
+ /**
+ * Retrieve an array of all currently docked Components.
+ * @param {String} cqSelector A {@link Ext.ComponentQuery ComponentQuery} selector string to filter the returned items.
+ * @return {Array} An array of components.
+ */
+ getDockedItems : function(cqSelector) {
+ var me = this,
+ // Start with a weight of 1, so users can provide <= 0 to come before top items
+ // Odd numbers, so users can provide a weight to come in between if desired
+ defaultWeight = { top: 1, left: 3, right: 5, bottom: 7 },
+ dockedItems;
+
+ if (me.dockedItems && me.dockedItems.items.length) {
+ // Allow filtering of returned docked items by CQ selector.
+ if (cqSelector) {
+ dockedItems = Ext.ComponentQuery.query(cqSelector, me.dockedItems.items);
+ } else {
+ dockedItems = me.dockedItems.items.slice();
+ }
+
+ Ext.Array.sort(dockedItems, function(a, b) {
+ // Docked items are ordered by their visual representation by default (t,l,r,b)
+ // TODO: Enforce position ordering, and have weights be sub-ordering within positions?
+ var aw = a.weight || defaultWeight[a.dock],
+ bw = b.weight || defaultWeight[b.dock];
+ if (Ext.isNumber(aw) && Ext.isNumber(bw)) {
+ return aw - bw;
+ }
+ return 0;
+ });
+
+ return dockedItems;
+ }
+ return [];
+ },
+
+ // inherit docs
+ addUIClsToElement: function(cls, force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.addCls(Ext.baseCSSPrefix + cls);
+ me.body.addCls(me.baseCls + '-body-' + cls);
+ me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
+ }
+ },
+
+ // inherit docs
+ removeUIClsFromElement: function(cls, force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.removeCls(Ext.baseCSSPrefix + cls);
+ me.body.removeCls(me.baseCls + '-body-' + cls);
+ me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
+ }
+ },
+
+ // inherit docs
+ addUIToElement: function(force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.addCls(me.baseCls + '-body-' + me.ui);
+ }
+ },
+
+ // inherit docs
+ removeUIFromElement: function() {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (me.rendered) {
+ me.body.removeCls(me.baseCls + '-body-' + me.ui);
+ }
+ },
+
+ // @private
+ getTargetEl : function() {
+ return this.body;
+ },
+
+ getRefItems: function(deep) {
+ var items = this.callParent(arguments),
+ // deep fetches all docked items, and their descendants using '*' selector and then '* *'
+ dockedItems = this.getDockedItems(deep ? '*,* *' : undefined),
+ ln = dockedItems.length,
+ i = 0,
+ item;
+
+ // Find the index where we go from top/left docked items to right/bottom docked items
+ for (; i < ln; i++) {
+ item = dockedItems[i];
+ if (item.dock === 'right' || item.dock === 'bottom') {
+ break;
+ }
+ }
+
+ // Return docked items in the top/left position before our container items, and
+ // return right/bottom positioned items after our container items.
+ // See AbstractDock.renderItems() for more information.
+ return dockedItems.splice(0, i).concat(items).concat(dockedItems);
+ },
+
+ beforeDestroy: function(){
+ var docked = this.dockedItems,
+ c;
+
+ if (docked) {
+ while ((c = docked.first())) {
+ this.removeDocked(c, true);
+ }
+ }
+ this.callParent();
+ },
+
+ setBorder: function(border) {
+ var me = this;
+ me.border = (border !== undefined) ? border : true;
+ if (me.rendered) {
+ me.doComponentLayout();
+ }
+ }
+});
+/**
+ * @class Ext.panel.Header
+ * @extends Ext.container.Container
+ * Simple header class which is used for on {@link Ext.panel.Panel} and {@link Ext.window.Window}
+ * @xtype header
+ */
+Ext.define('Ext.panel.Header', {
+ extend: 'Ext.container.Container',
+ uses: ['Ext.panel.Tool', 'Ext.draw.Component', 'Ext.util.CSS'],
+ alias: 'widget.header',
+
+ isHeader : true,
+ defaultType : 'tool',
+ indicateDrag : false,
+ weight : -1,
+
+ renderTpl: ['<div class="{baseCls}-body<tpl if="bodyCls"> {bodyCls}</tpl><tpl if="uiCls"><tpl for="uiCls"> {parent.baseCls}-body-{parent.ui}-{.}</tpl></tpl>"<tpl if="bodyStyle"> style="{bodyStyle}"</tpl>></div>'],
+
+ initComponent: function() {
+ var me = this,
+ rule,
+ style,
+ titleTextEl,
+ ui;
+
+ me.indicateDragCls = me.baseCls + '-draggable';
+ me.title = me.title || ' ';
+ me.tools = me.tools || [];
+ me.items = me.items || [];
+ me.orientation = me.orientation || 'horizontal';
+ me.dock = (me.dock) ? me.dock : (me.orientation == 'horizontal') ? 'top' : 'left';
+
+ //add the dock as a ui
+ //this is so we support top/right/left/bottom headers
+ me.addClsWithUI(me.orientation);
+ me.addClsWithUI(me.dock);
+
+ Ext.applyIf(me.renderSelectors, {
+ body: '.' + me.baseCls + '-body'
+ });
+
+ // Add Icon
+ if (!Ext.isEmpty(me.iconCls)) {
+ me.initIconCmp();
+ me.items.push(me.iconCmp);
+ }
+
+ // Add Title
+ if (me.orientation == 'vertical') {
+ // Hack for IE6/7's inability to display an inline-block
+ if (Ext.isIE6 || Ext.isIE7) {
+ me.width = this.width || 24;
+ } else if (Ext.isIEQuirks) {
+ me.width = this.width || 25;
+ }
+
+ me.layout = {
+ type : 'vbox',
+ align: 'center',
+ clearInnerCtOnLayout: true,
+ bindToOwnerCtContainer: false
+ };
+ me.textConfig = {
+ cls: me.baseCls + '-text',
+ type: 'text',
+ text: me.title,
+ rotate: {
+ degrees: 90
+ }
+ };
+ ui = me.ui;
+ if (Ext.isArray(ui)) {
+ ui = ui[0];
+ }
+ rule = Ext.util.CSS.getRule('.' + me.baseCls + '-text-' + ui);
+ if (rule) {
+ style = rule.style;
+ }
+ if (style) {
+ Ext.apply(me.textConfig, {
+ 'font-family': style.fontFamily,
+ 'font-weight': style.fontWeight,
+ 'font-size': style.fontSize,
+ fill: style.color
+ });
+ }
+ me.titleCmp = Ext.create('Ext.draw.Component', {
+ ariaRole : 'heading',
+ focusable: false,
+ viewBox: false,
+ autoSize: true,
+ margins: '5 0 0 0',
+ items: [ me.textConfig ],
+ renderSelectors: {
+ textEl: '.' + me.baseCls + '-text'
+ }
+ });
+ } else {
+ me.layout = {
+ type : 'hbox',
+ align: 'middle',
+ clearInnerCtOnLayout: true,
+ bindToOwnerCtContainer: false
+ };
+ me.titleCmp = Ext.create('Ext.Component', {
+ xtype : 'component',
+ ariaRole : 'heading',
+ focusable: false,
+ renderTpl : ['<span class="{cls}-text {cls}-text-{ui}">{title}</span>'],
+ renderData: {
+ title: me.title,
+ cls : me.baseCls,
+ ui : me.ui
+ },
+ renderSelectors: {
+ textEl: '.' + me.baseCls + '-text'
+ }
+ });
+ }
+ me.items.push(me.titleCmp);
+
+ // Spacer ->
+ me.items.push({
+ xtype: 'component',
+ html : ' ',
+ flex : 1,
+ focusable: false
+ });
+
+ // Add Tools
+ me.items = me.items.concat(me.tools);
+ this.callParent();
+ },
+
+ initIconCmp: function() {
+ this.iconCmp = Ext.create('Ext.Component', {
+ focusable: false,
+ renderTpl : ['<img alt="" src="{blank}" class="{cls}-icon {iconCls}"/>'],
+ renderData: {
+ blank : Ext.BLANK_IMAGE_URL,
+ cls : this.baseCls,
+ iconCls: this.iconCls,
+ orientation: this.orientation
+ },
+ renderSelectors: {
+ iconEl: '.' + this.baseCls + '-icon'
+ },
+ iconCls: this.iconCls
+ });
+ },
+
+ afterRender: function() {
+ var me = this;
+
+ me.el.unselectable();
+ if (me.indicateDrag) {
+ me.el.addCls(me.indicateDragCls);
+ }
+ me.mon(me.el, {
+ click: me.onClick,
+ scope: me
+ });
+ me.callParent();
+ },
+
+ afterLayout: function() {
+ var me = this;
+ me.callParent(arguments);
+
+ // IE7 needs a forced repaint to make the top framing div expand to full width
+ if (Ext.isIE7) {
+ me.el.repaint();
+ }
+ },
+
+ // inherit docs
+ addUIClsToElement: function(cls, force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.addCls(me.baseCls + '-body-' + cls);
+ me.body.addCls(me.baseCls + '-body-' + me.ui + '-' + cls);
+ }
+ },
+
+ // inherit docs
+ removeUIClsFromElement: function(cls, force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.removeCls(me.baseCls + '-body-' + cls);
+ me.body.removeCls(me.baseCls + '-body-' + me.ui + '-' + cls);
+ }
+ },
+
+ // inherit docs
+ addUIToElement: function(force) {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (!force && me.rendered) {
+ me.body.addCls(me.baseCls + '-body-' + me.ui);
+ }
+
+ if (!force && me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
+ me.titleCmp.textEl.addCls(me.baseCls + '-text-' + me.ui);
+ }
+ },
+
+ // inherit docs
+ removeUIFromElement: function() {
+ var me = this;
+
+ me.callParent(arguments);
+
+ if (me.rendered) {
+ me.body.removeCls(me.baseCls + '-body-' + me.ui);
+ }
+
+ if (me.titleCmp && me.titleCmp.rendered && me.titleCmp.textEl) {
+ me.titleCmp.textEl.removeCls(me.baseCls + '-text-' + me.ui);
+ }
+ },
+
+ onClick: function(e) {
+ if (!e.getTarget(Ext.baseCSSPrefix + 'tool')) {
+ this.fireEvent('click', e);
+ }
+ },
+
+ getTargetEl: function() {
+ return this.body || this.frameBody || this.el;
+ },
+
+ /**
+ * Sets the title of the header.
+ * @param {String} title The title to be set
+ */
+ setTitle: function(title) {
+ var me = this;
+ if (me.rendered) {
+ if (me.titleCmp.rendered) {
+ if (me.titleCmp.surface) {
+ me.title = title || '';
+ var sprite = me.titleCmp.surface.items.items[0],
+ surface = me.titleCmp.surface;
+
+ surface.remove(sprite);
+ me.textConfig.type = 'text';
+ me.textConfig.text = title;
+ sprite = surface.add(me.textConfig);
+ sprite.setAttributes({
+ rotate: {
+ degrees: 90
+ }
+ }, true);
+ me.titleCmp.autoSizeSurface();
+ } else {
+ me.title = title || ' ';
+ me.titleCmp.textEl.update(me.title);
+ }
+ } else {
+ me.titleCmp.on({
+ render: function() {
+ me.setTitle(title);
+ },
+ single: true
+ });
+ }
+ } else {
+ me.on({
+ render: function() {
+ me.layout.layout();
+ me.setTitle(title);
+ },
+ single: true
+ });
+ }
+ },
+
+ /**
+ * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
+ * icon class if one has already been set and fire the {@link #iconchange} event after completion.
+ * @param {String} cls The new CSS class name
+ */
+ setIconCls: function(cls) {
+ this.iconCls = cls;
+ if (!this.iconCmp) {
+ this.initIconCmp();
+ this.insert(0, this.iconCmp);
+ }
+ else {
+ if (!cls || !cls.length) {
+ this.iconCmp.destroy();
+ }
+ else {
+ var iconCmp = this.iconCmp,
+ el = iconCmp.iconEl;
+
+ el.removeCls(iconCmp.iconCls);
+ el.addCls(cls);
+ iconCmp.iconCls = cls;
+ }
+ }
+ },
+
+ /**
+ * Add a tool to the header
+ * @param {Object} tool
+ */
+ addTool: function(tool) {
+ this.tools.push(this.add(tool));
+ },
+
+ /**
+ * @private
+ * Set up the tools.<tool type> link in the owning Panel.
+ * Bind the tool to its owning Panel.
+ * @param component
+ * @param index
+ */
+ onAdd: function(component, index) {
+ this.callParent([arguments]);
+ if (component instanceof Ext.panel.Tool) {
+ component.bindTo(this.ownerCt);
+ this.tools[component.type] = component;
+ }
+ }
+});
+
+/**
+ * @class Ext.fx.target.Element
+ * @extends Ext.fx.target.Target
+ *
+ * This class represents a animation target for an {@link Ext.core.Element}. In general this class will not be
+ * created directly, the {@link Ext.core.Element} will be passed to the animation and
+ * and the appropriate target will be created.
+ */
+Ext.define('Ext.fx.target.Element', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.fx.target.Target',
+
+ /* End Definitions */
+
+ type: 'element',
+
+ getElVal: function(el, attr, val) {
+ if (val == undefined) {
+ if (attr === 'x') {
+ val = el.getX();
+ }
+ else if (attr === 'y') {
+ val = el.getY();
+ }
+ else if (attr === 'scrollTop') {
+ val = el.getScroll().top;
+ }
+ else if (attr === 'scrollLeft') {
+ val = el.getScroll().left;
+ }
+ else if (attr === 'height') {
+ val = el.getHeight();
+ }
+ else if (attr === 'width') {
+ val = el.getWidth();
+ }
+ else {
+ val = el.getStyle(attr);
+ }
+ }
+ return val;
+ },
+
+ getAttr: function(attr, val) {
+ var el = this.target;
+ return [[ el, this.getElVal(el, attr, val)]];
+ },
+
+ setAttr: function(targetData) {
+ var target = this.target,
+ ln = targetData.length,
+ attrs, attr, o, i, j, ln2, element, value;
+ for (i = 0; i < ln; i++) {
+ attrs = targetData[i].attrs;
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ ln2 = attrs[attr].length;
+ for (j = 0; j < ln2; j++) {
+ o = attrs[attr][j];
+ element = o[0];
+ value = o[1];
+ if (attr === 'x') {
+ element.setX(value);
+ }
+ else if (attr === 'y') {
+ element.setY(value);
+ }
+ else if (attr === 'scrollTop') {
+ element.scrollTo('top', value);
+ }
+ else if (attr === 'scrollLeft') {
+ element.scrollTo('left',value);
+ }
+ else {
+ element.setStyle(attr, value);
+ }
+ }
+ }
+ }
+ }
+ }
+});
+
+/**
+ * @class Ext.fx.target.CompositeElement
+ * @extends Ext.fx.target.Element
+ *
+ * This class represents a animation target for a {@link Ext.CompositeElement}. It allows
+ * each {@link Ext.core.Element} in the group to be animated as a whole. In general this class will not be
+ * created directly, the {@link Ext.CompositeElement} will be passed to the animation and
+ * and the appropriate target will be created.
+ */
+Ext.define('Ext.fx.target.CompositeElement', {
+
+ /* Begin Definitions */
+
+ extend: 'Ext.fx.target.Element',
+
+ /* End Definitions */
+
+ isComposite: true,
+
+ constructor: function(target) {
+ target.id = target.id || Ext.id(null, 'ext-composite-');
+ this.callParent([target]);
+ },
+
+ getAttr: function(attr, val) {
+ var out = [],
+ target = this.target;
+ target.each(function(el) {
+ out.push([el, this.getElVal(el, attr, val)]);
+ }, this);
+ return out;
+ }
+});
+
+/**
+ * @class Ext.fx.Manager
+ * Animation Manager which keeps track of all current animations and manages them on a frame by frame basis.
+ * @private
+ * @singleton
+ */
+
+Ext.define('Ext.fx.Manager', {
+
+ /* Begin Definitions */
+
+ singleton: true,
+
+ requires: ['Ext.util.MixedCollection',
+ 'Ext.fx.target.Element',
+ 'Ext.fx.target.CompositeElement',
+ 'Ext.fx.target.Sprite',
+ 'Ext.fx.target.CompositeSprite',
+ 'Ext.fx.target.Component'],
+
+ mixins: {
+ queue: 'Ext.fx.Queue'
+ },
+
+ /* End Definitions */
+
+ constructor: function() {
+ this.items = Ext.create('Ext.util.MixedCollection');
+ this.mixins.queue.constructor.call(this);
+
+ // this.requestAnimFrame = (function() {
+ // var raf = window.requestAnimationFrame ||
+ // window.webkitRequestAnimationFrame ||
+ // window.mozRequestAnimationFrame ||
+ // window.oRequestAnimationFrame ||
+ // window.msRequestAnimationFrame;
+ // if (raf) {
+ // return function(callback, element) {
+ // raf(callback);
+ // };
+ // }
+ // else {
+ // return function(callback, element) {
+ // window.setTimeout(callback, Ext.fx.Manager.interval);
+ // };
+ // }
+ // })();
+ },
+
+ /**
+ * @cfg {Number} interval Default interval in miliseconds to calculate each frame. Defaults to 16ms (~60fps)
+ */
+ interval: 16,
+
+ /**
+ * @cfg {Boolean} forceJS Turn off to not use CSS3 transitions when they are available
+ */
+ forceJS: true,
+
+ // @private Target factory
+ createTarget: function(target) {
+ var me = this,
+ useCSS3 = !me.forceJS && Ext.supports.Transitions,
+ targetObj;
+
+ me.useCSS3 = useCSS3;
+
+ // dom id
+ if (Ext.isString(target)) {
+ target = Ext.get(target);
+ }
+ // dom element
+ if (target && target.tagName) {
+ target = Ext.get(target);
+ targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
+ me.targets.add(targetObj);
+ return targetObj;
+ }
+ if (Ext.isObject(target)) {
+ // Element
+ if (target.dom) {
+ targetObj = Ext.create('Ext.fx.target.' + 'Element' + (useCSS3 ? 'CSS' : ''), target);
+ }
+ // Element Composite
+ else if (target.isComposite) {
+ targetObj = Ext.create('Ext.fx.target.' + 'CompositeElement' + (useCSS3 ? 'CSS' : ''), target);
+ }
+ // Draw Sprite
+ else if (target.isSprite) {
+ targetObj = Ext.create('Ext.fx.target.Sprite', target);
+ }
+ // Draw Sprite Composite
+ else if (target.isCompositeSprite) {
+ targetObj = Ext.create('Ext.fx.target.CompositeSprite', target);
+ }
+ // Component
+ else if (target.isComponent) {
+ targetObj = Ext.create('Ext.fx.target.Component', target);
+ }
+ else if (target.isAnimTarget) {
+ return target;
+ }
+ else {
+ return null;
+ }
+ me.targets.add(targetObj);
+ return targetObj;
+ }
+ else {
+ return null;
+ }
+ },
+
+ /**
+ * Add an Anim to the manager. This is done automatically when an Anim instance is created.
+ * @param {Ext.fx.Anim} anim
+ */
+ addAnim: function(anim) {
+ var items = this.items,
+ task = this.task;
+ // var me = this,
+ // items = me.items,
+ // cb = function() {
+ // if (items.length) {
+ // me.task = true;
+ // me.runner();
+ // me.requestAnimFrame(cb);
+ // }
+ // else {
+ // me.task = false;
+ // }
+ // };
+
+ items.add(anim);
+
+ // Start the timer if not already running
+ if (!task && items.length) {
+ task = this.task = {
+ run: this.runner,
+ interval: this.interval,
+ scope: this
+ };
+ Ext.TaskManager.start(task);
+ }
+
+ // //Start the timer if not already running
+ // if (!me.task && items.length) {
+ // me.requestAnimFrame(cb);
+ // }
+ },
+
+ /**
+ * Remove an Anim from the manager. This is done automatically when an Anim ends.
+ * @param {Ext.fx.Anim} anim
+ */
+ removeAnim: function(anim) {
+ // this.items.remove(anim);
+ var items = this.items,
+ task = this.task;
+ items.remove(anim);
+ // Stop the timer if there are no more managed Anims
+ if (task && !items.length) {
+ Ext.TaskManager.stop(task);
+ delete this.task;
+ }
+ },
+
+ /**
+ * @private
+ * Filter function to determine which animations need to be started
+ */
+ startingFilter: function(o) {
+ return o.paused === false && o.running === false && o.iterations > 0;
+ },
+
+ /**
+ * @private
+ * Filter function to determine which animations are still running
+ */
+ runningFilter: function(o) {
+ return o.paused === false && o.running === true && o.isAnimator !== true;
+ },
+
+ /**
+ * @private
+ * Runner function being called each frame
+ */
+ runner: function() {
+ var me = this,
+ items = me.items;
+
+ me.targetData = {};
+ me.targetArr = {};
+
+ // Single timestamp for all animations this interval
+ me.timestamp = new Date();
+
+ // Start any items not current running
+ items.filterBy(me.startingFilter).each(me.startAnim, me);
+
+ // Build the new attributes to be applied for all targets in this frame
+ items.filterBy(me.runningFilter).each(me.runAnim, me);
+
+ // Apply all the pending changes to their targets
+ me.applyPendingAttrs();
+ },
+
+ /**
+ * @private
+ * Start the individual animation (initialization)
+ */
+ startAnim: function(anim) {
+ anim.start(this.timestamp);
+ },
+
+ /**
+ * @private
+ * Run the individual animation for this frame
+ */
+ runAnim: function(anim) {
+ if (!anim) {
+ return;
+ }
+ var me = this,
+ targetId = anim.target.getId(),
+ useCSS3 = me.useCSS3 && anim.target.type == 'element',
+ elapsedTime = me.timestamp - anim.startTime,
+ target, o;
+
+ this.collectTargetData(anim, elapsedTime, useCSS3);
+
+ // For CSS3 animation, we need to immediately set the first frame's attributes without any transition
+ // to get a good initial state, then add the transition properties and set the final attributes.
+ if (useCSS3) {
+ // Flush the collected attributes, without transition
+ anim.target.setAttr(me.targetData[targetId], true);
+
+ // Add the end frame data
+ me.targetData[targetId] = [];
+ me.collectTargetData(anim, anim.duration, useCSS3);
+
+ // Pause the animation so runAnim doesn't keep getting called
+ anim.paused = true;
+
+ target = anim.target.target;
+ // We only want to attach an event on the last element in a composite
+ if (anim.target.isComposite) {
+ target = anim.target.target.last();
+ }
+
+ // Listen for the transitionend event
+ o = {};
+ o[Ext.supports.CSS3TransitionEnd] = anim.lastFrame;
+ o.scope = anim;
+ o.single = true;
+ target.on(o);
+ }
+ // For JS animation, trigger the lastFrame handler if this is the final frame
+ else if (elapsedTime >= anim.duration) {
+ me.applyPendingAttrs(true);
+ delete me.targetData[targetId];
+ delete me.targetArr[targetId];
+ anim.lastFrame();
+ }
+ },
+
+ /**
+ * Collect target attributes for the given Anim object at the given timestamp
+ * @param {Ext.fx.Anim} anim The Anim instance
+ * @param {Number} timestamp Time after the anim's start time
+ */
+ collectTargetData: function(anim, elapsedTime, useCSS3) {
+ var targetId = anim.target.getId(),
+ targetData = this.targetData[targetId],
+ data;
+
+ if (!targetData) {
+ targetData = this.targetData[targetId] = [];
+ this.targetArr[targetId] = anim.target;
+ }
+
+ data = {
+ duration: anim.duration,
+ easing: (useCSS3 && anim.reverse) ? anim.easingFn.reverse().toCSS3() : anim.easing,
+ attrs: {}
+ };
+ Ext.apply(data.attrs, anim.runAnim(elapsedTime));
+ targetData.push(data);
+ },
+
+ /**
+ * @private
+ * Apply all pending attribute changes to their targets
+ */
+ applyPendingAttrs: function(isLastFrame) {
+ var targetData = this.targetData,
+ targetArr = this.targetArr,
+ targetId;
+ for (targetId in targetData) {
+ if (targetData.hasOwnProperty(targetId)) {
+ targetArr[targetId].setAttr(targetData[targetId], false, isLastFrame);
+ }
+ }
+ }
+});
+
+/**
+ * @class Ext.fx.Animator
+ * Animation instance
+
+This class is used to run keyframe based animations, which follows the CSS3 based animation structure.
+Keyframe animations differ from typical from/to animations in that they offer the ability to specify values
+at various points throughout the animation.
+
+__Using Keyframes__
+The {@link #keyframes} option is the most important part of specifying an animation when using this
+class. A key frame is a point in a particular animation. We represent this as a percentage of the
+total animation duration. At each key frame, we can specify the target values at that time. Note that
+you *must* specify the values at 0% and 100%, the start and ending values. There is also a {@link keyframe}
+event that fires after each key frame is reached.
+
+__Example Usage__
+In the example below, we modify the values of the element at each fifth throughout the animation.
+
+ Ext.create('Ext.fx.Animator', {
+ target: Ext.getBody().createChild({
+ style: {
+ width: '100px',
+ height: '100px',
+ 'background-color': 'red'
+ }
+ }),
+ duration: 10000, // 10 seconds
+ keyframes: {
+ 0: {
+ opacity: 1,
+ backgroundColor: 'FF0000'
+ },
+ 20: {
+ x: 30,
+ opacity: 0.5
+ },
+ 40: {
+ x: 130,
+ backgroundColor: '0000FF'
+ },
+ 60: {
+ y: 80,
+ opacity: 0.3
+ },
+ 80: {
+ width: 200,
+ y: 200
+ },
+ 100: {
+ opacity: 1,
+ backgroundColor: '00FF00'
+ }
+ }
+ });
+
+ * @markdown
+ */
+Ext.define('Ext.fx.Animator', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.fx.Manager'],
+
+ /* End Definitions */
+
+ isAnimator: true,
+
+ /**
+ * @cfg {Number} duration
+ * Time in milliseconds for the animation to last. Defaults to 250.
+ */
+ duration: 250,
+
+ /**
+ * @cfg {Number} delay
+ * Time to delay before starting the animation. Defaults to 0.
+ */
+ delay: 0,
+
+ /* private used to track a delayed starting time */
+ delayStart: 0,
+
+ /**
+ * @cfg {Boolean} dynamic
+ * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
+ */
+ dynamic: false,
+
+ /**
+ * @cfg {String} easing
+
+This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
+speed over its duration.
+
+- backIn
+- backOut
+- bounceIn
+- bounceOut
+- ease
+- easeIn
+- easeOut
+- easeInOut
+- elasticIn
+- elasticOut
+- cubic-bezier(x1, y1, x2, y2)
+
+Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
+as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
+
+ * @markdown
+ */
+ easing: 'ease',
+
+ /**
+ * Flag to determine if the animation has started
+ * @property running
+ * @type boolean
+ */
+ running: false,
+
+ /**
+ * Flag to determine if the animation is paused. Only set this to true if you need to
+ * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
+ * @property paused
+ * @type boolean
+ */
+ paused: false,
+
+ /**
+ * @private
+ */
+ damper: 1,
+
+ /**
+ * @cfg {Number} iterations
+ * Number of times to execute the animation. Defaults to 1.
+ */
+ iterations: 1,
+
+ /**
+ * Current iteration the animation is running.
+ * @property currentIteration
+ * @type int
+ */
+ currentIteration: 0,
+
+ /**
+ * Current keyframe step of the animation.
+ * @property keyframeStep
+ * @type Number
+ */
+ keyframeStep: 0,
+
+ /**
+ * @private
+ */
+ animKeyFramesRE: /^(from|to|\d+%?)$/,
+
+ /**
+ * @cfg {Ext.fx.target} target
+ * The Ext.fx.target to apply the animation to. If not specified during initialization, this can be passed to the applyAnimator
+ * method to apply the same animation to many targets.
+ */
+
+ /**
+ * @cfg {Object} keyframes
+ * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
+ * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
+ * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
+ * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
+ * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
+ <pre><code>
+keyframes : {
+ '0%': {
+ left: 100
+ },
+ '40%': {
+ left: 150
+ },
+ '60%': {
+ left: 75
+ },
+ '100%': {
+ left: 100
+ }
+}
+ </code></pre>
+ */
+ constructor: function(config) {
+ var me = this;
+ config = Ext.apply(me, config || {});
+ me.config = config;
+ me.id = Ext.id(null, 'ext-animator-');
+ me.addEvents(
+ /**
+ * @event beforeanimate
+ * Fires before the animation starts. A handler can return false to cancel the animation.
+ * @param {Ext.fx.Animator} this
+ */
+ 'beforeanimate',
+ /**
+ * @event keyframe
+ * Fires at each keyframe.
+ * @param {Ext.fx.Animator} this
+ * @param {Number} keyframe step number
+ */
+ 'keyframe',
+ /**
+ * @event afteranimate
+ * Fires when the animation is complete.
+ * @param {Ext.fx.Animator} this
+ * @param {Date} startTime
+ */
+ 'afteranimate'
+ );
+ me.mixins.observable.constructor.call(me, config);
+ me.timeline = [];
+ me.createTimeline(me.keyframes);
+ if (me.target) {
+ me.applyAnimator(me.target);
+ Ext.fx.Manager.addAnim(me);
+ }
+ },
+
+ /**
+ * @private
+ */
+ sorter: function (a, b) {
+ return a.pct - b.pct;
+ },
+
+ /**
+ * @private
+ * Takes the given keyframe configuration object and converts it into an ordered array with the passed attributes per keyframe
+ * or applying the 'to' configuration to all keyframes. Also calculates the proper animation duration per keyframe.
+ */
+ createTimeline: function(keyframes) {
+ var me = this,
+ attrs = [],
+ to = me.to || {},
+ duration = me.duration,
+ prevMs, ms, i, ln, pct, anim, nextAnim, attr;
+
+ for (pct in keyframes) {
+ if (keyframes.hasOwnProperty(pct) && me.animKeyFramesRE.test(pct)) {
+ attr = {attrs: Ext.apply(keyframes[pct], to)};
+ // CSS3 spec allow for from/to to be specified.
+ if (pct == "from") {
+ pct = 0;
+ }
+ else if (pct == "to") {
+ pct = 100;
+ }
+ // convert % values into integers
+ attr.pct = parseInt(pct, 10);
+ attrs.push(attr);
+ }
+ }
+ // Sort by pct property
+ Ext.Array.sort(attrs, me.sorter);
+ // Only an end
+ //if (attrs[0].pct) {
+ // attrs.unshift({pct: 0, attrs: element.attrs});
+ //}
+
+ ln = attrs.length;
+ for (i = 0; i < ln; i++) {
+ prevMs = (attrs[i - 1]) ? duration * (attrs[i - 1].pct / 100) : 0;
+ ms = duration * (attrs[i].pct / 100);
+ me.timeline.push({
+ duration: ms - prevMs,
+ attrs: attrs[i].attrs
+ });
+ }
+ },
+
+ /**
+ * Applies animation to the Ext.fx.target
+ * @private
+ * @param target
+ * @type string/object
+ */
+ applyAnimator: function(target) {
+ var me = this,
+ anims = [],
+ timeline = me.timeline,
+ reverse = me.reverse,
+ ln = timeline.length,
+ anim, easing, damper, initial, attrs, lastAttrs, i;
+
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ for (i = 0; i < ln; i++) {
+ anim = timeline[i];
+ attrs = anim.attrs;
+ easing = attrs.easing || me.easing;
+ damper = attrs.damper || me.damper;
+ delete attrs.easing;
+ delete attrs.damper;
+ anim = Ext.create('Ext.fx.Anim', {
+ target: target,
+ easing: easing,
+ damper: damper,
+ duration: anim.duration,
+ paused: true,
+ to: attrs
+ });
+ anims.push(anim);
+ }
+ me.animations = anims;
+ me.target = anim.target;
+ for (i = 0; i < ln - 1; i++) {
+ anim = anims[i];
+ anim.nextAnim = anims[i + 1];
+ anim.on('afteranimate', function() {
+ this.nextAnim.paused = false;
+ });
+ anim.on('afteranimate', function() {
+ this.fireEvent('keyframe', this, ++this.keyframeStep);
+ }, me);
+ }
+ anims[ln - 1].on('afteranimate', function() {
+ this.lastFrame();
+ }, me);
+ }
+ },
+
+ /*
+ * @private
+ * Fires beforeanimate and sets the running flag.
+ */
+ start: function(startTime) {
+ var me = this,
+ delay = me.delay,
+ delayStart = me.delayStart,
+ delayDelta;
+ if (delay) {
+ if (!delayStart) {
+ me.delayStart = startTime;
+ return;
+ }
+ else {
+ delayDelta = startTime - delayStart;
+ if (delayDelta < delay) {
+ return;
+ }
+ else {
+ // Compensate for frame delay;
+ startTime = new Date(delayStart.getTime() + delay);
+ }
+ }
+ }
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ me.startTime = startTime;
+ me.running = true;
+ me.animations[me.keyframeStep].paused = false;
+ }
+ },
+
+ /*
+ * @private
+ * Perform lastFrame cleanup and handle iterations
+ * @returns a hash of the new attributes.
+ */
+ lastFrame: function() {
+ var me = this,
+ iter = me.iterations,
+ iterCount = me.currentIteration;
+
+ iterCount++;
+ if (iterCount < iter) {
+ me.startTime = new Date();
+ me.currentIteration = iterCount;
+ me.keyframeStep = 0;
+ me.applyAnimator(me.target);
+ me.animations[me.keyframeStep].paused = false;
+ }
+ else {
+ me.currentIteration = 0;
+ me.end();
+ }
+ },
+
+ /*
+ * Fire afteranimate event and end the animation. Usually called automatically when the
+ * animation reaches its final frame, but can also be called manually to pre-emptively
+ * stop and destroy the running animation.
+ */
+ end: function() {
+ var me = this;
+ me.fireEvent('afteranimate', me, me.startTime, new Date() - me.startTime);
+ }
+});
+/**
+ * @class Ext.fx.Easing
+ *
+This class contains a series of function definitions used to modify values during an animation.
+They describe how the intermediate values used during a transition will be calculated. It allows for a transition to change
+speed over its duration. The following options are available:
+
+- linear The default easing type
+- backIn
+- backOut
+- bounceIn
+- bounceOut
+- ease
+- easeIn
+- easeOut
+- easeInOut
+- elasticIn
+- elasticOut
+- cubic-bezier(x1, y1, x2, y2)
+
+Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
+as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
+ * @markdown
+ * @singleton
+ */
+Ext.ns('Ext.fx');
+
+Ext.require('Ext.fx.CubicBezier', function() {
+ var math = Math,
+ pi = math.PI,
+ pow = math.pow,
+ sin = math.sin,
+ sqrt = math.sqrt,
+ abs = math.abs,
+ backInSeed = 1.70158;
+ Ext.fx.Easing = {
+ // ease: Ext.fx.CubicBezier.cubicBezier(0.25, 0.1, 0.25, 1),
+ // linear: Ext.fx.CubicBezier.cubicBezier(0, 0, 1, 1),
+ // 'ease-in': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
+ // 'ease-out': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
+ // 'ease-in-out': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1),
+ // 'easeIn': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 1, 1),
+ // 'easeOut': Ext.fx.CubicBezier.cubicBezier(0, 0.58, 1, 1),
+ // 'easeInOut': Ext.fx.CubicBezier.cubicBezier(0.42, 0, 0.58, 1)
+ };
+
+ Ext.apply(Ext.fx.Easing, {
+ linear: function(n) {
+ return n;
+ },
+ ease: function(n) {
+ var q = 0.07813 - n / 2,
+ alpha = -0.25,
+ Q = sqrt(0.0066 + q * q),
+ x = Q - q,
+ X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
+ y = -Q - q,
+ Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
+ t = X + Y + 0.25;
+ return pow(1 - t, 2) * 3 * t * 0.1 + (1 - t) * 3 * t * t + t * t * t;
+ },
+ easeIn: function (n) {
+ return pow(n, 1.7);
+ },
+ easeOut: function (n) {
+ return pow(n, 0.48);
+ },
+ easeInOut: function(n) {
+ var q = 0.48 - n / 1.04,
+ Q = sqrt(0.1734 + q * q),
+ x = Q - q,
+ X = pow(abs(x), 1/3) * (x < 0 ? -1 : 1),
+ y = -Q - q,
+ Y = pow(abs(y), 1/3) * (y < 0 ? -1 : 1),
+ t = X + Y + 0.5;
+ return (1 - t) * 3 * t * t + t * t * t;
+ },
+ backIn: function (n) {
+ return n * n * ((backInSeed + 1) * n - backInSeed);
+ },
+ backOut: function (n) {
+ n = n - 1;
+ return n * n * ((backInSeed + 1) * n + backInSeed) + 1;
+ },
+ elasticIn: function (n) {
+ if (n === 0 || n === 1) {
+ return n;
+ }
+ var p = 0.3,
+ s = p / 4;
+ return pow(2, -10 * n) * sin((n - s) * (2 * pi) / p) + 1;
+ },
+ elasticOut: function (n) {
+ return 1 - Ext.fx.Easing.elasticIn(1 - n);
+ },
+ bounceIn: function (n) {
+ return 1 - Ext.fx.Easing.bounceOut(1 - n);
+ },
+ bounceOut: function (n) {
+ var s = 7.5625,
+ p = 2.75,
+ l;
+ if (n < (1 / p)) {
+ l = s * n * n;
+ } else {
+ if (n < (2 / p)) {
+ n -= (1.5 / p);
+ l = s * n * n + 0.75;
+ } else {
+ if (n < (2.5 / p)) {
+ n -= (2.25 / p);
+ l = s * n * n + 0.9375;
+ } else {
+ n -= (2.625 / p);
+ l = s * n * n + 0.984375;
+ }
+ }
+ }
+ return l;
+ }
+ });
+ Ext.apply(Ext.fx.Easing, {
+ 'back-in': Ext.fx.Easing.backIn,
+ 'back-out': Ext.fx.Easing.backOut,
+ 'ease-in': Ext.fx.Easing.easeIn,
+ 'ease-out': Ext.fx.Easing.easeOut,
+ 'elastic-in': Ext.fx.Easing.elasticIn,
+ 'elastic-out': Ext.fx.Easing.elasticIn,
+ 'bounce-in': Ext.fx.Easing.bounceIn,
+ 'bounce-out': Ext.fx.Easing.bounceOut,
+ 'ease-in-out': Ext.fx.Easing.easeInOut
+ });
+});
+/*
+ * @class Ext.draw.Draw
+ * Base Drawing class. Provides base drawing functions.
+ */
+
+Ext.define('Ext.draw.Draw', {
+ /* Begin Definitions */
+
+ singleton: true,
+
+ requires: ['Ext.draw.Color'],
+
+ /* End Definitions */
+
+ pathToStringRE: /,?([achlmqrstvxz]),?/gi,
+ pathCommandRE: /([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,
+ pathValuesRE: /(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,
+ stopsRE: /^(\d+%?)$/,
+ radian: Math.PI / 180,
+
+ availableAnimAttrs: {
+ along: "along",
+ blur: null,
+ "clip-rect": "csv",
+ cx: null,
+ cy: null,
+ fill: "color",
+ "fill-opacity": null,
+ "font-size": null,
+ height: null,
+ opacity: null,
+ path: "path",
+ r: null,
+ rotation: "csv",
+ rx: null,
+ ry: null,
+ scale: "csv",
+ stroke: "color",
+ "stroke-opacity": null,
+ "stroke-width": null,
+ translation: "csv",
+ width: null,
+ x: null,
+ y: null
+ },
+
+ is: function(o, type) {
+ type = String(type).toLowerCase();
+ return (type == "object" && o === Object(o)) ||
+ (type == "undefined" && typeof o == type) ||
+ (type == "null" && o === null) ||
+ (type == "array" && Array.isArray && Array.isArray(o)) ||
+ (Object.prototype.toString.call(o).toLowerCase().slice(8, -1)) == type;
+ },
+
+ ellipsePath: function(sprite) {
+ var attr = sprite.attr;
+ return Ext.String.format("M{0},{1}A{2},{3},0,1,1,{0},{4}A{2},{3},0,1,1,{0},{1}z", attr.x, attr.y - attr.ry, attr.rx, attr.ry, attr.y + attr.ry);
+ },
+
+ rectPath: function(sprite) {
+ var attr = sprite.attr;
+ if (attr.radius) {
+ return Ext.String.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z", attr.x + attr.radius, attr.y, attr.width - attr.radius * 2, attr.radius, -attr.radius, attr.height - attr.radius * 2, attr.radius * 2 - attr.width, attr.radius * 2 - attr.height);
+ }
+ else {
+ return Ext.String.format("M{0},{1}l{2},0,0,{3},{4},0z", attr.x, attr.y, attr.width, attr.height, -attr.width);
+ }
+ },
+
+ path2string: function () {
+ return this.join(",").replace(Ext.draw.Draw.pathToStringRE, "$1");
+ },
+
+ parsePathString: function (pathString) {
+ if (!pathString) {
+ return null;
+ }
+ var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, q: 4, s: 4, t: 2, v: 1, z: 0},
+ data = [],
+ me = this;
+ if (me.is(pathString, "array") && me.is(pathString[0], "array")) { // rough assumption
+ data = me.pathClone(pathString);
+ }
+ if (!data.length) {
+ String(pathString).replace(me.pathCommandRE, function (a, b, c) {
+ var params = [],
+ name = b.toLowerCase();
+ c.replace(me.pathValuesRE, function (a, b) {
+ b && params.push(+b);
+ });
+ if (name == "m" && params.length > 2) {
+ data.push([b].concat(params.splice(0, 2)));
+ name = "l";
+ b = (b == "m") ? "l" : "L";
+ }
+ while (params.length >= paramCounts[name]) {
+ data.push([b].concat(params.splice(0, paramCounts[name])));
+ if (!paramCounts[name]) {
+ break;
+ }
+ }
+ });
+ }
+ data.toString = me.path2string;
+ return data;
+ },
+
+ mapPath: function (path, matrix) {
+ if (!matrix) {
+ return path;
+ }
+ var x, y, i, ii, j, jj, pathi;
+ path = this.path2curve(path);
+ for (i = 0, ii = path.length; i < ii; i++) {
+ pathi = path[i];
+ for (j = 1, jj = pathi.length; j < jj-1; j += 2) {
+ x = matrix.x(pathi[j], pathi[j + 1]);
+ y = matrix.y(pathi[j], pathi[j + 1]);
+ pathi[j] = x;
+ pathi[j + 1] = y;
+ }
+ }
+ return path;
+ },
+
+ pathClone: function(pathArray) {
+ var res = [],
+ j,
+ jj,
+ i,
+ ii;
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
+ pathArray = this.parsePathString(pathArray);
+ }
+ for (i = 0, ii = pathArray.length; i < ii; i++) {
+ res[i] = [];
+ for (j = 0, jj = pathArray[i].length; j < jj; j++) {
+ res[i][j] = pathArray[i][j];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ pathToAbsolute: function (pathArray) {
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) { // rough assumption
+ pathArray = this.parsePathString(pathArray);
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ start = 0,
+ i,
+ ii,
+ r,
+ pa,
+ j,
+ jj,
+ k,
+ kk;
+ if (pathArray[0][0] == "M") {
+ x = +pathArray[0][1];
+ y = +pathArray[0][2];
+ mx = x;
+ my = y;
+ start++;
+ res[0] = ["M", x, y];
+ }
+ for (i = start, ii = pathArray.length; i < ii; i++) {
+ r = res[i] = [];
+ pa = pathArray[i];
+ if (pa[0] != pa[0].toUpperCase()) {
+ r[0] = pa[0].toUpperCase();
+ switch (r[0]) {
+ case "A":
+ r[1] = pa[1];
+ r[2] = pa[2];
+ r[3] = pa[3];
+ r[4] = pa[4];
+ r[5] = pa[5];
+ r[6] = +(pa[6] + x);
+ r[7] = +(pa[7] + y);
+ break;
+ case "V":
+ r[1] = +pa[1] + y;
+ break;
+ case "H":
+ r[1] = +pa[1] + x;
+ break;
+ case "M":
+ mx = +pa[1] + x;
+ my = +pa[2] + y;
+ default:
+ for (j = 1, jj = pa.length; j < jj; j++) {
+ r[j] = +pa[j] + ((j % 2) ? x : y);
+ }
+ }
+ } else {
+ for (k = 0, kk = pa.length; k < kk; k++) {
+ res[i][k] = pa[k];
+ }
+ }
+ switch (r[0]) {
+ case "Z":
+ x = mx;
+ y = my;
+ break;
+ case "H":
+ x = r[1];
+ break;
+ case "V":
+ y = r[1];
+ break;
+ case "M":
+ mx = res[i][res[i].length - 2];
+ my = res[i][res[i].length - 1];
+ default:
+ x = res[i][res[i].length - 2];
+ y = res[i][res[i].length - 1];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ pathToRelative: function (pathArray) {
+ if (!this.is(pathArray, "array") || !this.is(pathArray && pathArray[0], "array")) {
+ pathArray = this.parsePathString(pathArray);
+ }
+ var res = [],
+ x = 0,
+ y = 0,
+ mx = 0,
+ my = 0,
+ start = 0;
+ if (pathArray[0][0] == "M") {
+ x = pathArray[0][1];
+ y = pathArray[0][2];
+ mx = x;
+ my = y;
+ start++;
+ res.push(["M", x, y]);
+ }
+ for (var i = start, ii = pathArray.length; i < ii; i++) {
+ var r = res[i] = [],
+ pa = pathArray[i];
+ if (pa[0] != pa[0].toLowerCase()) {
+ r[0] = pa[0].toLowerCase();
+ switch (r[0]) {
+ case "a":
+ r[1] = pa[1];
+ r[2] = pa[2];
+ r[3] = pa[3];
+ r[4] = pa[4];
+ r[5] = pa[5];
+ r[6] = +(pa[6] - x).toFixed(3);
+ r[7] = +(pa[7] - y).toFixed(3);
+ break;
+ case "v":
+ r[1] = +(pa[1] - y).toFixed(3);
+ break;
+ case "m":
+ mx = pa[1];
+ my = pa[2];
+ default:
+ for (var j = 1, jj = pa.length; j < jj; j++) {
+ r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
+ }
+ }
+ } else {
+ r = res[i] = [];
+ if (pa[0] == "m") {
+ mx = pa[1] + x;
+ my = pa[2] + y;
+ }
+ for (var k = 0, kk = pa.length; k < kk; k++) {
+ res[i][k] = pa[k];
+ }
+ }
+ var len = res[i].length;
+ switch (res[i][0]) {
+ case "z":
+ x = mx;
+ y = my;
+ break;
+ case "h":
+ x += +res[i][len - 1];
+ break;
+ case "v":
+ y += +res[i][len - 1];
+ break;
+ default:
+ x += +res[i][len - 2];
+ y += +res[i][len - 1];
+ }
+ }
+ res.toString = this.path2string;
+ return res;
+ },
+
+ //Returns a path converted to a set of curveto commands
+ path2curve: function (path) {
+ var me = this,
+ points = me.pathToAbsolute(path),
+ ln = points.length,
+ attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ i, seg, segLn, point;
+
+ for (i = 0; i < ln; i++) {
+ points[i] = me.command2curve(points[i], attrs);
+ if (points[i].length > 7) {
+ points[i].shift();
+ point = points[i];
+ while (point.length) {
+ points.splice(i++, 0, ["C"].concat(point.splice(0, 6)));
+ }
+ points.splice(i, 1);
+ ln = points.length;
+ }
+ seg = points[i];
+ segLn = seg.length;
+ attrs.x = seg[segLn - 2];
+ attrs.y = seg[segLn - 1];
+ attrs.bx = parseFloat(seg[segLn - 4]) || attrs.x;
+ attrs.by = parseFloat(seg[segLn - 3]) || attrs.y;
+ }
+ return points;
+ },
+
+ interpolatePaths: function (path, path2) {
+ var me = this,
+ p = me.pathToAbsolute(path),
+ p2 = me.pathToAbsolute(path2),
+ attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
+ fixArc = function (pp, i) {
+ if (pp[i].length > 7) {
+ pp[i].shift();
+ var pi = pp[i];
+ while (pi.length) {
+ pp.splice(i++, 0, ["C"].concat(pi.splice(0, 6)));
+ }
+ pp.splice(i, 1);
+ ii = Math.max(p.length, p2.length || 0);
+ }
+ },
+ fixM = function (path1, path2, a1, a2, i) {
+ if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
+ path2.splice(i, 0, ["M", a2.x, a2.y]);
+ a1.bx = 0;
+ a1.by = 0;
+ a1.x = path1[i][1];
+ a1.y = path1[i][2];
+ ii = Math.max(p.length, p2.length || 0);
+ }
+ };
+ for (var i = 0, ii = Math.max(p.length, p2.length || 0); i < ii; i++) {
+ p[i] = me.command2curve(p[i], attrs);
+ fixArc(p, i);
+ (p2[i] = me.command2curve(p2[i], attrs2));
+ fixArc(p2, i);
+ fixM(p, p2, attrs, attrs2, i);
+ fixM(p2, p, attrs2, attrs, i);
+ var seg = p[i],
+ seg2 = p2[i],
+ seglen = seg.length,
+ seg2len = seg2.length;
+ attrs.x = seg[seglen - 2];
+ attrs.y = seg[seglen - 1];
+ attrs.bx = parseFloat(seg[seglen - 4]) || attrs.x;
+ attrs.by = parseFloat(seg[seglen - 3]) || attrs.y;
+ attrs2.bx = (parseFloat(seg2[seg2len - 4]) || attrs2.x);
+ attrs2.by = (parseFloat(seg2[seg2len - 3]) || attrs2.y);
+ attrs2.x = seg2[seg2len - 2];
+ attrs2.y = seg2[seg2len - 1];
+ }
+ return [p, p2];
+ },
+
+ //Returns any path command as a curveto command based on the attrs passed
+ command2curve: function (pathCommand, d) {
+ var me = this;
+ if (!pathCommand) {
+ return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
+ }
+ if (pathCommand[0] != "T" && pathCommand[0] != "Q") {
+ d.qx = d.qy = null;
+ }
+ switch (pathCommand[0]) {
+ case "M":
+ d.X = pathCommand[1];
+ d.Y = pathCommand[2];
+ break;
+ case "A":
+ pathCommand = ["C"].concat(me.arc2curve.apply(me, [d.x, d.y].concat(pathCommand.slice(1))));
+ break;
+ case "S":
+ pathCommand = ["C", d.x + (d.x - (d.bx || d.x)), d.y + (d.y - (d.by || d.y))].concat(pathCommand.slice(1));
+ break;
+ case "T":
+ d.qx = d.x + (d.x - (d.qx || d.x));
+ d.qy = d.y + (d.y - (d.qy || d.y));
+ pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, d.qx, d.qy, pathCommand[1], pathCommand[2]));
+ break;
+ case "Q":
+ d.qx = pathCommand[1];
+ d.qy = pathCommand[2];
+ pathCommand = ["C"].concat(me.quadratic2curve(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[3], pathCommand[4]));
+ break;
+ case "L":
+ pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], pathCommand[2], pathCommand[1], pathCommand[2]);
+ break;
+ case "H":
+ pathCommand = ["C"].concat(d.x, d.y, pathCommand[1], d.y, pathCommand[1], d.y);
+ break;
+ case "V":
+ pathCommand = ["C"].concat(d.x, d.y, d.x, pathCommand[1], d.x, pathCommand[1]);
+ break;
+ case "Z":
+ pathCommand = ["C"].concat(d.x, d.y, d.X, d.Y, d.X, d.Y);
+ break;
+ }
+ return pathCommand;
+ },
+
+ quadratic2curve: function (x1, y1, ax, ay, x2, y2) {
+ var _13 = 1 / 3,
+ _23 = 2 / 3;
+ return [
+ _13 * x1 + _23 * ax,
+ _13 * y1 + _23 * ay,
+ _13 * x2 + _23 * ax,
+ _13 * y2 + _23 * ay,
+ x2,
+ y2
+ ];
+ },
+
+ rotate: function (x, y, rad) {
+ var cos = Math.cos(rad),
+ sin = Math.sin(rad),
+ X = x * cos - y * sin,
+ Y = x * sin + y * cos;
+ return {x: X, y: Y};
+ },
+
+ arc2curve: function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
+ // for more information of where this Math came from visit:
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ var me = this,
+ PI = Math.PI,
+ radian = me.radian,
+ _120 = PI * 120 / 180,
+ rad = radian * (+angle || 0),
+ res = [],
+ math = Math,
+ mcos = math.cos,
+ msin = math.sin,
+ msqrt = math.sqrt,
+ mabs = math.abs,
+ masin = math.asin,
+ xy, cos, sin, x, y, h, rx2, ry2, k, cx, cy, f1, f2, df, c1, s1, c2, s2,
+ t, hx, hy, m1, m2, m3, m4, newres, i, ln, f2old, x2old, y2old;
+ if (!recursive) {
+ xy = me.rotate(x1, y1, -rad);
+ x1 = xy.x;
+ y1 = xy.y;
+ xy = me.rotate(x2, y2, -rad);
+ x2 = xy.x;
+ y2 = xy.y;
+ cos = mcos(radian * angle);
+ sin = msin(radian * angle);
+ x = (x1 - x2) / 2;
+ y = (y1 - y2) / 2;
+ h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
+ if (h > 1) {
+ h = msqrt(h);
+ rx = h * rx;
+ ry = h * ry;
+ }
+ rx2 = rx * rx;
+ ry2 = ry * ry;
+ k = (large_arc_flag == sweep_flag ? -1 : 1) *
+ msqrt(mabs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x)));
+ cx = k * rx * y / ry + (x1 + x2) / 2;
+ cy = k * -ry * x / rx + (y1 + y2) / 2;
+ f1 = masin(((y1 - cy) / ry).toFixed(7));
+ f2 = masin(((y2 - cy) / ry).toFixed(7));
+
+ f1 = x1 < cx ? PI - f1 : f1;
+ f2 = x2 < cx ? PI - f2 : f2;
+ if (f1 < 0) {
+ f1 = PI * 2 + f1;
+ }
+ if (f2 < 0) {
+ f2 = PI * 2 + f2;
+ }
+ if (sweep_flag && f1 > f2) {
+ f1 = f1 - PI * 2;
+ }
+ if (!sweep_flag && f2 > f1) {
+ f2 = f2 - PI * 2;
+ }
+ }
+ else {
+ f1 = recursive[0];
+ f2 = recursive[1];
+ cx = recursive[2];
+ cy = recursive[3];
+ }
+ df = f2 - f1;
+ if (mabs(df) > _120) {
+ f2old = f2;
+ x2old = x2;
+ y2old = y2;
+ f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
+ x2 = cx + rx * mcos(f2);
+ y2 = cy + ry * msin(f2);
+ res = me.arc2curve(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
+ }
+ df = f2 - f1;
+ c1 = mcos(f1);
+ s1 = msin(f1);
+ c2 = mcos(f2);
+ s2 = msin(f2);
+ t = math.tan(df / 4);
+ hx = 4 / 3 * rx * t;
+ hy = 4 / 3 * ry * t;
+ m1 = [x1, y1];
+ m2 = [x1 + hx * s1, y1 - hy * c1];
+ m3 = [x2 + hx * s2, y2 - hy * c2];
+ m4 = [x2, y2];
+ m2[0] = 2 * m1[0] - m2[0];
+ m2[1] = 2 * m1[1] - m2[1];
+ if (recursive) {
+ return [m2, m3, m4].concat(res);
+ }
+ else {
+ res = [m2, m3, m4].concat(res).join().split(",");
+ newres = [];
+ ln = res.length;
+ for (i = 0; i < ln; i++) {
+ newres[i] = i % 2 ? me.rotate(res[i - 1], res[i], rad).y : me.rotate(res[i], res[i + 1], rad).x;
+ }
+ return newres;
+ }
+ },
+
+ rotatePoint: function (x, y, alpha, cx, cy) {
+ if (!alpha) {
+ return {
+ x: x,
+ y: y
+ };
+ }
+ cx = cx || 0;
+ cy = cy || 0;
+ x = x - cx;
+ y = y - cy;
+ alpha = alpha * this.radian;
+ var cos = Math.cos(alpha),
+ sin = Math.sin(alpha);
+ return {
+ x: x * cos - y * sin + cx,
+ y: x * sin + y * cos + cy
+ };
+ },
+
+ rotateAndTranslatePath: function (sprite) {
+ var alpha = sprite.rotation.degrees,
+ cx = sprite.rotation.x,
+ cy = sprite.rotation.y,
+ dx = sprite.translation.x,
+ dy = sprite.translation.y,
+ path,
+ i,
+ p,
+ xy,
+ j,
+ res = [];
+ if (!alpha && !dx && !dy) {
+ return this.pathToAbsolute(sprite.attr.path);
+ }
+ dx = dx || 0;
+ dy = dy || 0;
+ path = this.pathToAbsolute(sprite.attr.path);
+ for (i = path.length; i--;) {
+ p = res[i] = path[i].slice();
+ if (p[0] == "A") {
+ xy = this.rotatePoint(p[6], p[7], alpha, cx, cy);
+ p[6] = xy.x + dx;
+ p[7] = xy.y + dy;
+ } else {
+ j = 1;
+ while (p[j + 1] != null) {
+ xy = this.rotatePoint(p[j], p[j + 1], alpha, cx, cy);
+ p[j] = xy.x + dx;
+ p[j + 1] = xy.y + dy;
+ j += 2;
+ }
+ }
+ }
+ return res;
+ },
+
+ pathDimensions: function (path) {
+ if (!path || !(path + "")) {
+ return {x: 0, y: 0, width: 0, height: 0};
+ }
+ path = this.path2curve(path);
+ var x = 0,
+ y = 0,
+ X = [],
+ Y = [],
+ p,
+ i,
+ ii,
+ xmin,
+ ymin,
+ dim;
+ for (i = 0, ii = path.length; i < ii; i++) {
+ p = path[i];
+ if (p[0] == "M") {
+ x = p[1];
+ y = p[2];
+ X.push(x);
+ Y.push(y);
+ }
+ else {
+ dim = this.curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
+ X = X.concat(dim.min.x, dim.max.x);
+ Y = Y.concat(dim.min.y, dim.max.y);
+ x = p[5];
+ y = p[6];
+ }
+ }
+ xmin = Math.min.apply(0, X);
+ ymin = Math.min.apply(0, Y);
+ return {
+ x: xmin,
+ y: ymin,
+ path: path,
+ width: Math.max.apply(0, X) - xmin,
+ height: Math.max.apply(0, Y) - ymin
+ };
+ },
+
+ intersect: function(subjectPolygon, clipPolygon) {
+ var cp1, cp2, s, e, point;
+ var inside = function(p) {
+ return (cp2[0]-cp1[0]) * (p[1]-cp1[1]) > (cp2[1]-cp1[1]) * (p[0]-cp1[0]);
+ };
+ var intersection = function() {
+ var p = [];
+ var dcx = cp1[0]-cp2[0],
+ dcy = cp1[1]-cp2[1],
+ dpx = s[0]-e[0],
+ dpy = s[1]-e[1],
+ n1 = cp1[0]*cp2[1] - cp1[1]*cp2[0],
+ n2 = s[0]*e[1] - s[1]*e[0],
+ n3 = 1 / (dcx*dpy - dcy*dpx);
+
+ p[0] = (n1*dpx - n2*dcx) * n3;
+ p[1] = (n1*dpy - n2*dcy) * n3;
+ return p;
+ };
+ var outputList = subjectPolygon;
+ cp1 = clipPolygon[clipPolygon.length -1];
+ for (var i = 0, l = clipPolygon.length; i < l; ++i) {
+ cp2 = clipPolygon[i];
+ var inputList = outputList;
+ outputList = [];
+ s = inputList[inputList.length -1];
+ for (var j = 0, ln = inputList.length; j < ln; j++) {
+ e = inputList[j];
+ if (inside(e)) {
+ if (!inside(s)) {
+ outputList.push(intersection());
+ }
+ outputList.push(e);
+ } else if (inside(s)) {
+ outputList.push(intersection());
+ }
+ s = e;
+ }
+ cp1 = cp2;
+ }
+ return outputList;
+ },
+
+ curveDim: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
+ var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
+ b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
+ c = p1x - c1x,
+ t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a,
+ y = [p1y, p2y],
+ x = [p1x, p2x],
+ dot;
+ if (Math.abs(t1) > 1e12) {
+ t1 = 0.5;
+ }
+ if (Math.abs(t2) > 1e12) {
+ t2 = 0.5;
+ }
+ if (t1 > 0 && t1 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
+ b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
+ c = p1y - c1y;
+ t1 = (-b + Math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ t2 = (-b - Math.sqrt(b * b - 4 * a * c)) / 2 / a;
+ if (Math.abs(t1) > 1e12) {
+ t1 = 0.5;
+ }
+ if (Math.abs(t2) > 1e12) {
+ t2 = 0.5;
+ }
+ if (t1 > 0 && t1 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ if (t2 > 0 && t2 < 1) {
+ dot = this.findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
+ x.push(dot.x);
+ y.push(dot.y);
+ }
+ return {
+ min: {x: Math.min.apply(0, x), y: Math.min.apply(0, y)},
+ max: {x: Math.max.apply(0, x), y: Math.max.apply(0, y)}
+ };
+ },
+
+ getAnchors: function (p1x, p1y, p2x, p2y, p3x, p3y, value) {
+ value = value || 4;
+ var l = Math.min(Math.sqrt(Math.pow(p1x - p2x, 2) + Math.pow(p1y - p2y, 2)) / value, Math.sqrt(Math.pow(p3x - p2x, 2) + Math.pow(p3y - p2y, 2)) / value),
+ a = Math.atan((p2x - p1x) / Math.abs(p2y - p1y)),
+ b = Math.atan((p3x - p2x) / Math.abs(p2y - p3y)),
+ pi = Math.PI;
+ a = p1y < p2y ? pi - a : a;
+ b = p3y < p2y ? pi - b : b;
+ var alpha = pi / 2 - ((a + b) % (pi * 2)) / 2;
+ alpha > pi / 2 && (alpha -= pi);
+ var dx1 = l * Math.sin(alpha + a),
+ dy1 = l * Math.cos(alpha + a),
+ dx2 = l * Math.sin(alpha + b),
+ dy2 = l * Math.cos(alpha + b),
+ out = {
+ x1: p2x - dx1,
+ y1: p2y + dy1,
+ x2: p2x + dx2,
+ y2: p2y + dy2
+ };
+ return out;
+ },
+
+ /* Smoothing function for a path. Converts a path into cubic beziers. Value defines the divider of the distance between points.
+ * Defaults to a value of 4.
+ */
+ smooth: function (originalPath, value) {
+ var path = this.path2curve(originalPath),
+ newp = [path[0]],
+ x = path[0][1],
+ y = path[0][2],
+ j,
+ points,
+ i = 1,
+ ii = path.length,
+ beg = 1,
+ mx = x,
+ my = y,
+ cx = 0,
+ cy = 0;
+ for (; i < ii; i++) {
+ var pathi = path[i],
+ pathil = pathi.length,
+ pathim = path[i - 1],
+ pathiml = pathim.length,
+ pathip = path[i + 1],
+ pathipl = pathip && pathip.length;
+ if (pathi[0] == "M") {
+ mx = pathi[1];
+ my = pathi[2];
+ j = i + 1;
+ while (path[j][0] != "C") {
+ j++;
+ }
+ cx = path[j][5];
+ cy = path[j][6];
+ newp.push(["M", mx, my]);
+ beg = newp.length;
+ x = mx;
+ y = my;
+ continue;
+ }
+ if (pathi[pathil - 2] == mx && pathi[pathil - 1] == my && (!pathip || pathip[0] == "M")) {
+ var begl = newp[beg].length;
+ points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], mx, my, newp[beg][begl - 2], newp[beg][begl - 1], value);
+ newp[beg][1] = points.x2;
+ newp[beg][2] = points.y2;
+ }
+ else if (!pathip || pathip[0] == "M") {
+ points = {
+ x1: pathi[pathil - 2],
+ y1: pathi[pathil - 1]
+ };
+ } else {
+ points = this.getAnchors(pathim[pathiml - 2], pathim[pathiml - 1], pathi[pathil - 2], pathi[pathil - 1], pathip[pathipl - 2], pathip[pathipl - 1], value);
+ }
+ newp.push(["C", x, y, points.x1, points.y1, pathi[pathil - 2], pathi[pathil - 1]]);
+ x = points.x2;
+ y = points.y2;
+ }
+ return newp;
+ },
+
+ findDotAtSegment: function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
+ var t1 = 1 - t;
+ return {
+ x: Math.pow(t1, 3) * p1x + Math.pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + Math.pow(t, 3) * p2x,
+ y: Math.pow(t1, 3) * p1y + Math.pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + Math.pow(t, 3) * p2y
+ };
+ },
+
+ snapEnds: function (from, to, stepsMax) {
+ var step = (to - from) / stepsMax,
+ level = Math.floor(Math.log(step) / Math.LN10) + 1,
+ m = Math.pow(10, level),
+ cur,
+ modulo = Math.round((step % m) * Math.pow(10, 2 - level)),
+ interval = [[0, 15], [20, 4], [30, 2], [40, 4], [50, 9], [60, 4], [70, 2], [80, 4], [100, 15]],
+ stepCount = 0,
+ value,
+ weight,
+ i,
+ topValue,
+ topWeight = 1e9,
+ ln = interval.length;
+ cur = from = Math.floor(from / m) * m;
+ for (i = 0; i < ln; i++) {
+ value = interval[i][0];
+ weight = (value - modulo) < 0 ? 1e6 : (value - modulo) / interval[i][1];
+ if (weight < topWeight) {
+ topValue = value;
+ topWeight = weight;
+ }
+ }
+ step = Math.floor(step * Math.pow(10, -level)) * Math.pow(10, level) + topValue * Math.pow(10, level - 2);
+ while (cur < to) {
+ cur += step;
+ stepCount++;
+ }
+ to = +cur.toFixed(10);
+ return {
+ from: from,
+ to: to,
+ power: level,
+ step: step,
+ steps: stepCount
+ };
+ },
+
+ sorter: function (a, b) {
+ return a.offset - b.offset;
+ },
+
+ rad: function(degrees) {
+ return degrees % 360 * Math.PI / 180;
+ },
+
+ degrees: function(radian) {
+ return radian * 180 / Math.PI % 360;
+ },
+
+ withinBox: function(x, y, bbox) {
+ bbox = bbox || {};
+ return (x >= bbox.x && x <= (bbox.x + bbox.width) && y >= bbox.y && y <= (bbox.y + bbox.height));
+ },
+
+ parseGradient: function(gradient) {
+ var me = this,
+ type = gradient.type || 'linear',
+ angle = gradient.angle || 0,
+ radian = me.radian,
+ stops = gradient.stops,
+ stopsArr = [],
+ stop,
+ vector,
+ max,
+ stopObj;
+
+ if (type == 'linear') {
+ vector = [0, 0, Math.cos(angle * radian), Math.sin(angle * radian)];
+ max = 1 / (Math.max(Math.abs(vector[2]), Math.abs(vector[3])) || 1);
+ vector[2] *= max;
+ vector[3] *= max;
+ if (vector[2] < 0) {
+ vector[0] = -vector[2];
+ vector[2] = 0;
+ }
+ if (vector[3] < 0) {
+ vector[1] = -vector[3];
+ vector[3] = 0;
+ }
+ }
+
+ for (stop in stops) {
+ if (stops.hasOwnProperty(stop) && me.stopsRE.test(stop)) {
+ stopObj = {
+ offset: parseInt(stop, 10),
+ color: Ext.draw.Color.toHex(stops[stop].color) || '#ffffff',
+ opacity: stops[stop].opacity || 1
+ };
+ stopsArr.push(stopObj);
+ }
+ }
+ // Sort by pct property
+ Ext.Array.sort(stopsArr, me.sorter);
+ if (type == 'linear') {
+ return {
+ id: gradient.id,
+ type: type,
+ vector: vector,
+ stops: stopsArr
+ };
+ }
+ else {
+ return {
+ id: gradient.id,
+ type: type,
+ centerX: gradient.centerX,
+ centerY: gradient.centerY,
+ focalX: gradient.focalX,
+ focalY: gradient.focalY,
+ radius: gradient.radius,
+ vector: vector,
+ stops: stopsArr
+ };
+ }
+ }
+});
+
+/**
+ * @class Ext.fx.PropertyHandler
+ * @ignore
+ */
+Ext.define('Ext.fx.PropertyHandler', {
+
+ /* Begin Definitions */
+
+ requires: ['Ext.draw.Draw'],
+
+ statics: {
+ defaultHandler: {
+ pixelDefaults: ['width', 'height', 'top', 'left'],
+ unitRE: /^(-?\d*\.?\d*){1}(em|ex|px|in|cm|mm|pt|pc|%)*$/,
+
+ computeDelta: function(from, end, damper, initial, attr) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var match = this.unitRE.exec(from),
+ start, units;
+ if (match) {
+ from = match[1];
+ units = match[2];
+ if (!units && Ext.Array.contains(this.pixelDefaults, attr)) {
+ units = 'px';
+ }
+ }
+ from = +from || 0;
+
+ match = this.unitRE.exec(end);
+ if (match) {
+ end = match[1];
+ units = match[2] || units;
+ }
+ end = +end || 0;
+ start = (initial != null) ? initial : from;
+ return {
+ from: from,
+ delta: (end - start) * damper,
+ units: units
+ };
+ },
+
+ get: function(from, end, damper, initialFrom, attr) {
+ var ln = from.length,
+ out = [],
+ i, initial, res, j, len;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ if (Ext.isArray(from[i][1]) && Ext.isArray(end)) {
+ res = [];
+ j = 0;
+ len = from[i][1].length;
+ for (; j < len; j++) {
+ res.push(this.computeDelta(from[i][1][j], end[j], damper, initial, attr));
+ }
+ out.push([from[i][0], res]);
+ }
+ else {
+ out.push([from[i][0], this.computeDelta(from[i][1], end, damper, initial, attr)]);
+ }
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, val, res, len, j;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ if (Ext.isArray(val)) {
+ res = [];
+ j = 0;
+ len = val.length;
+ for (; j < len; j++) {
+ res.push(val[j].from + (val[j].delta * easing) + (val[j].units || 0));
+ }
+ out.push([values[i][0], res]);
+ } else {
+ out.push([values[i][0], val.from + (val.delta * easing) + (val.units || 0)]);
+ }
+ }
+ return out;
+ }
+ },
+ color: {
+ rgbRE: /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
+ hexRE: /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
+ hex3RE: /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
+
+ parseColor : function(color, damper) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var base,
+ out = false,
+ match;
+
+ Ext.each([this.hexRE, this.rgbRE, this.hex3RE], function(re, idx) {
+ base = (idx % 2 == 0) ? 16 : 10;
+ match = re.exec(color);
+ if (match && match.length == 4) {
+ if (idx == 2) {
+ match[1] += match[1];
+ match[2] += match[2];
+ match[3] += match[3];
+ }
+ out = {
+ red: parseInt(match[1], base),
+ green: parseInt(match[2], base),
+ blue: parseInt(match[3], base)
+ };
+ return false;
+ }
+ });
+ return out || color;
+ },
+
+ computeDelta: function(from, end, damper, initial) {
+ from = this.parseColor(from);
+ end = this.parseColor(end, damper);
+ var start = initial ? initial : from,
+ tfrom = typeof start,
+ tend = typeof end;
+ //Extra check for when the color string is not recognized.
+ if (tfrom == 'string' || tfrom == 'undefined'
+ || tend == 'string' || tend == 'undefined') {
+ return end || start;
+ }
+ return {
+ from: from,
+ delta: {
+ red: Math.round((end.red - start.red) * damper),
+ green: Math.round((end.green - start.green) * damper),
+ blue: Math.round((end.blue - start.blue) * damper)
+ }
+ };
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var ln = start.length,
+ out = [],
+ i, initial;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, val, parsedString, from, delta;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ if (val) {
+ from = val.from;
+ delta = val.delta;
+ //multiple checks to reformat the color if it can't recognized by computeDelta.
+ val = (typeof val == 'object' && 'red' in val)?
+ 'rgb(' + val.red + ', ' + val.green + ', ' + val.blue + ')' : val;
+ val = (typeof val == 'object' && val.length)? val[0] : val;
+ if (typeof val == 'undefined') {
+ return [];
+ }
+ parsedString = typeof val == 'string'? val :
+ 'rgb(' + [
+ (from.red + Math.round(delta.red * easing)) % 256,
+ (from.green + Math.round(delta.green * easing)) % 256,
+ (from.blue + Math.round(delta.blue * easing)) % 256
+ ].join(',') + ')';
+ out.push([
+ values[i][0],
+ parsedString
+ ]);
+ }
+ }
+ return out;
+ }
+ },
+ object: {
+ interpolate: function(prop, damper) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var out = {},
+ p;
+ for(p in prop) {
+ out[p] = parseInt(prop[p], 10) * damper;
+ }
+ return out;
+ },
+
+ computeDelta: function(from, end, damper, initial) {
+ from = this.interpolate(from);
+ end = this.interpolate(end, damper);
+ var start = initial ? initial : from,
+ delta = {},
+ p;
+
+ for(p in end) {
+ delta[p] = end[p] - start[p];
+ }
+ return {
+ from: from,
+ delta: delta
+ };
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var ln = start.length,
+ out = [],
+ i, initial;
+ for (i = 0; i < ln; i++) {
+ if (initialFrom) {
+ initial = initialFrom[i][1].from;
+ }
+ out.push([start[i][0], this.computeDelta(start[i][1], end, damper, initial)]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ outObject = {},
+ i, from, delta, val, p;
+ for (i = 0; i < ln; i++) {
+ val = values[i][1];
+ from = val.from;
+ delta = val.delta;
+ for (p in from) {
+ outObject[p] = Math.round(from[p] + delta[p] * easing);
+ }
+ out.push([
+ values[i][0],
+ outObject
+ ]);
+ }
+ return out;
+ }
+ },
+
+ path: {
+ computeDelta: function(from, end, damper, initial) {
+ damper = (typeof damper == 'number') ? damper : 1;
+ var start;
+ from = +from || 0;
+ end = +end || 0;
+ start = (initial != null) ? initial : from;
+ return {
+ from: from,
+ delta: (end - start) * damper
+ };
+ },
+
+ forcePath: function(path) {
+ if (!Ext.isArray(path) && !Ext.isArray(path[0])) {
+ path = Ext.draw.Draw.parsePathString(path);
+ }
+ return path;
+ },
+
+ get: function(start, end, damper, initialFrom) {
+ var endPath = this.forcePath(end),
+ out = [],
+ startLn = start.length,
+ startPathLn, pointsLn, i, deltaPath, initial, j, k, path, startPath;
+ for (i = 0; i < startLn; i++) {
+ startPath = this.forcePath(start[i][1]);
+
+ deltaPath = Ext.draw.Draw.interpolatePaths(startPath, endPath);
+ startPath = deltaPath[0];
+ endPath = deltaPath[1];
+
+ startPathLn = startPath.length;
+ path = [];
+ for (j = 0; j < startPathLn; j++) {
+ deltaPath = [startPath[j][0]];
+ pointsLn = startPath[j].length;
+ for (k = 1; k < pointsLn; k++) {
+ initial = initialFrom && initialFrom[0][1][j][k].from;
+ deltaPath.push(this.computeDelta(startPath[j][k], endPath[j][k], damper, initial));
+ }
+ path.push(deltaPath);
+ }
+ out.push([start[i][0], path]);
+ }
+ return out;
+ },
+
+ set: function(values, easing) {
+ var ln = values.length,
+ out = [],
+ i, j, k, newPath, calcPath, deltaPath, deltaPathLn, pointsLn;
+ for (i = 0; i < ln; i++) {
+ deltaPath = values[i][1];
+ newPath = [];
+ deltaPathLn = deltaPath.length;
+ for (j = 0; j < deltaPathLn; j++) {
+ calcPath = [deltaPath[j][0]];
+ pointsLn = deltaPath[j].length;
+ for (k = 1; k < pointsLn; k++) {
+ calcPath.push(deltaPath[j][k].from + deltaPath[j][k].delta * easing);
+ }
+ newPath.push(calcPath.join(','));
+ }
+ out.push([values[i][0], newPath.join(',')]);
+ }
+ return out;
+ }
+ }
+ /* End Definitions */
+ }
+}, function() {
+ Ext.each([
+ 'outlineColor',
+ 'backgroundColor',
+ 'borderColor',
+ 'borderTopColor',
+ 'borderRightColor',
+ 'borderBottomColor',
+ 'borderLeftColor',
+ 'fill',
+ 'stroke'
+ ], function(prop) {
+ this[prop] = this.color;
+ }, this);
+});
+/**
+ * @class Ext.fx.Anim
+ *
+ * This class manages animation for a specific {@link #target}. The animation allows
+ * animation of various properties on the target, such as size, position, color and others.
+ *
+ * ## Starting Conditions
+ * The starting conditions for the animation are provided by the {@link #from} configuration.
+ * Any/all of the properties in the {@link #from} configuration can be specified. If a particular
+ * property is not defined, the starting value for that property will be read directly from the target.
+ *
+ * ## End Conditions
+ * The ending conditions for the animation are provided by the {@link #to} configuration. These mark
+ * the final values once the animations has finished. The values in the {@link #from} can mirror
+ * those in the {@link #to} configuration to provide a starting point.
+ *
+ * ## Other Options
+ * - {@link #duration}: Specifies the time period of the animation.
+ * - {@link #easing}: Specifies the easing of the animation.
+ * - {@link #iterations}: Allows the animation to repeat a number of times.
+ * - {@link #alternate}: Used in conjunction with {@link #iterations}, reverses the direction every second iteration.
+ *
+ * ## Example Code
+ *
+ * var myComponent = Ext.create('Ext.Component', {
+ * renderTo: document.body,
+ * width: 200,
+ * height: 200,
+ * style: 'border: 1px solid red;'
+ * });
+ *
+ * new Ext.fx.Anim({
+ * target: myComponent,
+ * duration: 1000,
+ * from: {
+ * width: 400 //starting width 400
+ * },
+ * to: {
+ * width: 300, //end width 300
+ * height: 300 // end width 300
+ * }
+ * });
+ */
+Ext.define('Ext.fx.Anim', {
+
+ /* Begin Definitions */
+
+ mixins: {
+ observable: 'Ext.util.Observable'
+ },
+
+ requires: ['Ext.fx.Manager', 'Ext.fx.Animator', 'Ext.fx.Easing', 'Ext.fx.CubicBezier', 'Ext.fx.PropertyHandler'],
+
+ /* End Definitions */
+
+ isAnimation: true,
+ /**
+ * @cfg {Number} duration
+ * Time in milliseconds for a single animation to last. Defaults to 250. If the {@link #iterations} property is
+ * specified, then each animate will take the same duration for each iteration.
+ */
+ duration: 250,
+
+ /**
+ * @cfg {Number} delay
+ * Time to delay before starting the animation. Defaults to 0.
+ */
+ delay: 0,
+
+ /* private used to track a delayed starting time */
+ delayStart: 0,
+
+ /**
+ * @cfg {Boolean} dynamic
+ * Currently only for Component Animation: Only set a component's outer element size bypassing layouts. Set to true to do full layouts for every frame of the animation. Defaults to false.
+ */
+ dynamic: false,
+
+ /**
+ * @cfg {String} easing
+This describes how the intermediate values used during a transition will be calculated. It allows for a transition to change
+speed over its duration.
+
+ -backIn
+ -backOut
+ -bounceIn
+ -bounceOut
+ -ease
+ -easeIn
+ -easeOut
+ -easeInOut
+ -elasticIn
+ -elasticOut
+ -cubic-bezier(x1, y1, x2, y2)
+
+Note that cubic-bezier will create a custom easing curve following the CSS3 transition-timing-function specification `{@link http://www.w3.org/TR/css3-transitions/#transition-timing-function_tag}`. The four values specify points P1 and P2 of the curve
+as (x1, y1, x2, y2). All values must be in the range [0, 1] or the definition is invalid.
+ * @markdown
+ */
+ easing: 'ease',
+
+ /**
+ * @cfg {Object} keyframes
+ * Animation keyframes follow the CSS3 Animation configuration pattern. 'from' is always considered '0%' and 'to'
+ * is considered '100%'.<b>Every keyframe declaration must have a keyframe rule for 0% and 100%, possibly defined using
+ * "from" or "to"</b>. A keyframe declaration without these keyframe selectors is invalid and will not be available for
+ * animation. The keyframe declaration for a keyframe rule consists of properties and values. Properties that are unable to
+ * be animated are ignored in these rules, with the exception of 'easing' which can be changed at each keyframe. For example:
+ <pre><code>
+keyframes : {
+ '0%': {
+ left: 100
+ },
+ '40%': {
+ left: 150
+ },
+ '60%': {
+ left: 75
+ },
+ '100%': {
+ left: 100
+ }
+}
+ </code></pre>
+ */
+
+ /**
+ * @private
+ */
+ damper: 1,
+
+ /**
+ * @private
+ */
+ bezierRE: /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
+
+ /**
+ * Run the animation from the end to the beginning
+ * Defaults to false.
+ * @cfg {Boolean} reverse
+ */
+ reverse: false,
+
+ /**
+ * Flag to determine if the animation has started
+ * @property running
+ * @type boolean
+ */
+ running: false,
+
+ /**
+ * Flag to determine if the animation is paused. Only set this to true if you need to
+ * keep the Anim instance around to be unpaused later; otherwise call {@link #end}.
+ * @property paused
+ * @type boolean
+ */
+ paused: false,
+
+ /**
+ * Number of times to execute the animation. Defaults to 1.
+ * @cfg {int} iterations
+ */
+ iterations: 1,
+
+ /**
+ * Used in conjunction with iterations to reverse the animation each time an iteration completes.
+ * @cfg {Boolean} alternate
+ * Defaults to false.
+ */
+ alternate: false,
+
+ /**
+ * Current iteration the animation is running.
+ * @property currentIteration
+ * @type int
+ */
+ currentIteration: 0,
+
+ /**
+ * Starting time of the animation.
+ * @property startTime
+ * @type Date
+ */
+ startTime: 0,
+
+ /**
+ * Contains a cache of the interpolators to be used.
+ * @private
+ * @property propHandlers
+ * @type Object
+ */
+
+ /**
+ * @cfg {String/Object} target
+ * The {@link Ext.fx.target.Target} to apply the animation to. This should only be specified when creating an Ext.fx.Anim directly.
+ * The target does not need to be a {@link Ext.fx.target.Target} instance, it can be the underlying object. For example, you can
+ * pass a Component, Element or Sprite as the target and the Anim will create the appropriate {@link Ext.fx.target.Target} object
+ * automatically.
+ */
+
+ /**
+ * @cfg {Object} from
+ * An object containing property/value pairs for the beginning of the animation. If not specified, the current state of the
+ * Ext.fx.target will be used. For example:
+<pre><code>
+from : {
+ opacity: 0, // Transparent
+ color: '#ffffff', // White
+ left: 0
+}
+</code></pre>
+ */
+
+ /**
+ * @cfg {Object} to
+ * An object containing property/value pairs for the end of the animation. For example:
+ <pre><code>
+ to : {
+ opacity: 1, // Opaque
+ color: '#00ff00', // Green
+ left: 500
+ }
+ </code></pre>
+ */
+
+ // @private
+ constructor: function(config) {
+ var me = this;
+ config = config || {};
+ // If keyframes are passed, they really want an Animator instead.
+ if (config.keyframes) {
+ return Ext.create('Ext.fx.Animator', config);
+ }
+ config = Ext.apply(me, config);
+ if (me.from === undefined) {
+ me.from = {};
+ }
+ me.propHandlers = {};
+ me.config = config;
+ me.target = Ext.fx.Manager.createTarget(me.target);
+ me.easingFn = Ext.fx.Easing[me.easing];
+ me.target.dynamic = me.dynamic;
+
+ // If not a pre-defined curve, try a cubic-bezier
+ if (!me.easingFn) {
+ me.easingFn = String(me.easing).match(me.bezierRE);
+ if (me.easingFn && me.easingFn.length == 5) {
+ var curve = me.easingFn;
+ me.easingFn = Ext.fx.cubicBezier(+curve[1], +curve[2], +curve[3], +curve[4]);
+ }
+ }
+ me.id = Ext.id(null, 'ext-anim-');
+ Ext.fx.Manager.addAnim(me);
+ me.addEvents(
+ /**
+ * @event beforeanimate
+ * Fires before the animation starts. A handler can return false to cancel the animation.
+ * @param {Ext.fx.Anim} this
+ */
+ 'beforeanimate',
+ /**
+ * @event afteranimate
+ * Fires when the animation is complete.
+ * @param {Ext.fx.Anim} this
+ * @param {Date} startTime
+ */
+ 'afteranimate',
+ /**
+ * @event lastframe
+ * Fires when the animation's last frame has been set.
+ * @param {Ext.fx.Anim} this
+ * @param {Date} startTime
+ */
+ 'lastframe'
+ );
+ me.mixins.observable.constructor.call(me, config);
+ if (config.callback) {
+ me.on('afteranimate', config.callback, config.scope);
+ }
+ return me;
+ },
+
+ /**
+ * @private
+ * Helper to the target
+ */
+ setAttr: function(attr, value) {
+ return Ext.fx.Manager.items.get(this.id).setAttr(this.target, attr, value);
+ },
+
+ /*
+ * @private
+ * Set up the initial currentAttrs hash.
+ */
+ initAttrs: function() {
+ var me = this,
+ from = me.from,
+ to = me.to,
+ initialFrom = me.initialFrom || {},
+ out = {},
+ start, end, propHandler, attr;
+
+ for (attr in to) {
+ if (to.hasOwnProperty(attr)) {
+ start = me.target.getAttr(attr, from[attr]);
+ end = to[attr];
+ // Use default (numeric) property handler
+ if (!Ext.fx.PropertyHandler[attr]) {
+ if (Ext.isObject(end)) {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.object;
+ } else {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler.defaultHandler;
+ }
+ }
+ // Use custom handler
+ else {
+ propHandler = me.propHandlers[attr] = Ext.fx.PropertyHandler[attr];
+ }
+ out[attr] = propHandler.get(start, end, me.damper, initialFrom[attr], attr);
+ }
+ }
+ me.currentAttrs = out;
+ },
+
+ /*
+ * @private
+ * Fires beforeanimate and sets the running flag.
+ */
+ start: function(startTime) {
+ var me = this,
+ delay = me.delay,
+ delayStart = me.delayStart,
+ delayDelta;
+ if (delay) {
+ if (!delayStart) {
+ me.delayStart = startTime;
+ return;
+ }
+ else {
+ delayDelta = startTime - delayStart;
+ if (delayDelta < delay) {
+ return;
+ }
+ else {
+ // Compensate for frame delay;
+ startTime = new Date(delayStart.getTime() + delay);
+ }
+ }
+ }
+ if (me.fireEvent('beforeanimate', me) !== false) {
+ me.startTime = startTime;
+ if (!me.paused && !me.currentAttrs) {
+ me.initAttrs();
+ }
+ me.running = true;
+ }
+ },
+
+ /*
+ * @private
+ * Calculate attribute value at the passed timestamp.
+ * @returns a hash of the new attributes.
+ */
+ runAnim: function(elapsedTime) {
+ var me = this,
+ attrs = me.currentAttrs,
+ duration = me.duration,
+ easingFn = me.easingFn,
+ propHandlers = me.propHandlers,
+ ret = {},
+ easing, values, attr, lastFrame;
+
+ if (elapsedTime >= duration) {
+ elapsedTime = duration;
+ lastFrame = true;
+ }
+ if (me.reverse) {
+ elapsedTime = duration - elapsedTime;
+ }
+
+ for (attr in attrs) {
+ if (attrs.hasOwnProperty(attr)) {
+ values = attrs[attr];
+ easing = lastFrame ? 1 : easingFn(elapsedTime / duration);
+ ret[attr] = propHandlers[attr].set(values, easing);
+ }
+ }
+ return ret;
+ },
+
+ /*
+ * @private
+ * Perform lastFrame cleanup and handle iterations
+ * @returns a hash of the new attributes.
+ */
+ lastFrame: function() {
+ var me = this,
+ iter = me.iterations,
+ iterCount = me.currentIteration;
+
+ iterCount++;
+ if (iterCount < iter) {
+ if (me.alternate) {
+ me.reverse = !me.reverse;
+ }
+ me.startTime = new Date();
+ me.currentIteration = iterCount;
+ // Turn off paused for CSS3 Transitions
+ me.paused = false;
+ }
+ else {
+ me.currentIteration = 0;
+ me.end();
+ me.fireEvent('lastframe', me, me.startTime);
+ }
+ },
+
+ /*
+ * Fire afteranimate event and end the animation. Usually called automatically when the
+ * animation reaches its final frame, but can also be called manually to pre-emptively
+ * stop and destroy the running animation.
+ */
+ end: function() {
+ var me = this;
+ me.startTime = 0;
+ me.paused = false;
+ me.running = false;
+ Ext.fx.Manager.removeAnim(me);
+ me.fireEvent('afteranimate', me, me.startTime);
+ }
+});
+// Set flag to indicate that Fx is available. Class might not be available immediately.
+Ext.enableFx = true;
+
+/*
+ * This is a derivative of the similarly named class in the YUI Library.
+ * The original license:
+ * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
+ * Code licensed under the BSD License:
+ * http://developer.yahoo.net/yui/license.txt
+ */
+
+
+/**
+ * @class Ext.dd.DragDrop
+ * Defines the interface and base operation of items that that can be
+ * dragged or can be drop targets. It was designed to be extended, overriding
+ * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
+ * Up to three html elements can be associated with a DragDrop instance:
+ * <ul>
+ * <li>linked element: the element that is passed into the constructor.
+ * This is the element which defines the boundaries for interaction with
+ * other DragDrop objects.</li>
+ * <li>handle element(s): The drag operation only occurs if the element that
+ * was clicked matches a handle element. By default this is the linked
+ * element, but there are times that you will want only a portion of the
+ * linked element to initiate the drag operation, and the setHandleElId()
+ * method provides a way to define this.</li>
+ * <li>drag element: this represents the element that would be moved along
+ * with the cursor during a drag operation. By default, this is the linked
+ * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
+ * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
+ * </li>
+ * </ul>
+ * This class should not be instantiated until the onload event to ensure that
+ * the associated elements are available.
+ * The following would define a DragDrop obj that would interact with any
+ * other DragDrop obj in the "group1" group:
+ * <pre>
+ * dd = new Ext.dd.DragDrop("div1", "group1");
+ * </pre>
+ * Since none of the event handlers have been implemented, nothing would
+ * actually happen if you were to run the code above. Normally you would
+ * override this class or one of the default implementations, but you can
+ * also override the methods you want on an instance of the class...
+ * <pre>
+ * dd.onDragDrop = function(e, id) {
+ * alert("dd was dropped on " + id);
+ * }
+ * </pre>
+ * @constructor
+ * @param {String} id of the element that is linked to this instance
+ * @param {String} sGroup the group of related DragDrop objects
+ * @param {object} config an object containing configurable attributes
+ * Valid properties for DragDrop:
+ * padding, isTarget, maintainOffset, primaryButtonOnly
+ */
+
+Ext.define('Ext.dd.DragDrop', {
+ requires: ['Ext.dd.DragDropManager'],
+ constructor: function(id, sGroup, config) {
+ if(id) {
+ this.init(id, sGroup, config);
+ }
+ },
+
+ /**
+ * Set to false to enable a DragDrop object to fire drag events while dragging
+ * over its own Element. Defaults to true - DragDrop objects do not by default
+ * fire drag events to themselves.
+ * @property ignoreSelf
+ * @type Boolean
+ */
+
+ /**
+ * The id of the element associated with this object. This is what we
+ * refer to as the "linked element" because the size and position of
+ * this element is used to determine when the drag and drop objects have
+ * interacted.
+ * @property id
+ * @type String
+ */
+ id: null,
+
+ /**
+ * Configuration attributes passed into the constructor
+ * @property config
+ * @type object
+ */
+ config: null,
+
+ /**
+ * The id of the element that will be dragged. By default this is same
+ * as the linked element, but could be changed to another element. Ex:
+ * Ext.dd.DDProxy
+ * @property dragElId
+ * @type String
+ * @private
+ */
+ dragElId: null,
+
+ /**
+ * The ID of the element that initiates the drag operation. By default
+ * this is the linked element, but could be changed to be a child of this
+ * element. This lets us do things like only starting the drag when the
+ * header element within the linked html element is clicked.
+ * @property handleElId
+ * @type String
+ * @private
+ */
+ handleElId: null,
+
+ /**
+ * An object who's property names identify HTML tags to be considered invalid as drag handles.
+ * A non-null property value identifies the tag as invalid. Defaults to the
+ * following value which prevents drag operations from being initiated by <a> elements:<pre><code>
+{
+ A: "A"
+}</code></pre>
+ * @property invalidHandleTypes
+ * @type Object
+ */
+ invalidHandleTypes: null,
+
+ /**
+ * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
+ * A non-null property value identifies the ID as invalid. For example, to prevent
+ * dragging from being initiated on element ID "foo", use:<pre><code>
+{
+ foo: true
+}</code></pre>
+ * @property invalidHandleIds
+ * @type Object
+ */
+ invalidHandleIds: null,
+
+ /**
+ * An Array of CSS class names for elements to be considered in valid as drag handles.
+ * @property invalidHandleClasses
+ * @type Array
+ */
+ invalidHandleClasses: null,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageX
+ * @type int
+ * @private
+ */
+ startPageX: 0,
+
+ /**
+ * The linked element's absolute X position at the time the drag was
+ * started
+ * @property startPageY
+ * @type int
+ * @private
+ */
+ startPageY: 0,
+
+ /**
+ * The group defines a logical collection of DragDrop objects that are
+ * related. Instances only get events when interacting with other
+ * DragDrop object in the same group. This lets us define multiple
+ * groups using a single DragDrop subclass if we want.
+ * @property groups
+ * @type object An object in the format {'group1':true, 'group2':true}
+ */
+ groups: null,
+
+ /**
+ * Individual drag/drop instances can be locked. This will prevent
+ * onmousedown start drag.
+ * @property locked
+ * @type boolean
+ * @private
+ */
+ locked: false,
+
+ /**
+ * Lock this instance
+ * @method lock
+ */
+ lock: function() {
+ this.locked = true;
+ },
+
+ /**
+ * When set to true, other DD objects in cooperating DDGroups do not receive
+ * notification events when this DD object is dragged over them. Defaults to false.
+ * @property moveOnly
+ * @type boolean
+ */
+ moveOnly: false,
+
+ /**
+ * Unlock this instace
+ * @method unlock
+ */
+ unlock: function() {
+ this.locked = false;
+ },
+
+ /**
+ * By default, all instances can be a drop target. This can be disabled by
+ * setting isTarget to false.
+ * @property isTarget
+ * @type boolean
+ */
+ isTarget: true,
+
+ /**
+ * The padding configured for this drag and drop object for calculating
+ * the drop zone intersection with this object.
+ * @property padding
+ * @type int[] An array containing the 4 padding values: [top, right, bottom, left]
+ */
+ padding: null,
+
+ /**
+ * Cached reference to the linked element
+ * @property _domRef
+ * @private
+ */
+ _domRef: null,
+
+ /**
+ * Internal typeof flag
+ * @property __ygDragDrop
+ * @private
+ */
+ __ygDragDrop: true,
+
+ /**
+ * Set to true when horizontal contraints are applied
+ * @property constrainX
+ * @type boolean
+ * @private
+ */
+ constrainX: false,
+
+ /**
+ * Set to true when vertical contraints are applied
+ * @property constrainY
+ * @type boolean
+ * @private
+ */
+ constrainY: false,
+
+ /**
+ * The left constraint
+ * @property minX
+ * @type int
+ * @private
+ */
+ minX: 0,
+
+ /**
+ * The right constraint
+ * @property maxX
+ * @type int
+ * @private
+ */
+ maxX: 0,
+
+ /**
+ * The up constraint
+ * @property minY
+ * @type int
+ * @private
+ */
+ minY: 0,
+
+ /**
+ * The down constraint
+ * @property maxY
+ * @type int
+ * @private
+ */
+ maxY: 0,
+
+ /**
+ * Maintain offsets when we resetconstraints. Set to true when you want
+ * the position of the element relative to its parent to stay the same
+ * when the page changes
+ *
+ * @property maintainOffset
+ * @type boolean
+ */
+ maintainOffset: false,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * horizontal graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property xTicks
+ * @type int[]
+ */
+ xTicks: null,
+
+ /**
+ * Array of pixel locations the element will snap to if we specified a
+ * vertical graduation/interval. This array is generated automatically
+ * when you define a tick interval.
+ * @property yTicks
+ * @type int[]
+ */
+ yTicks: null,
+
+ /**
+ * By default the drag and drop instance will only respond to the primary
+ * button click (left button for a right-handed mouse). Set to true to
+ * allow drag and drop to start with any mouse click that is propogated
+ * by the browser
+ * @property primaryButtonOnly
+ * @type boolean
+ */
+ primaryButtonOnly: true,
+
+ /**
+ * The available property is false until the linked dom element is accessible.
+ * @property available
+ * @type boolean
+ */
+ available: false,
+
+ /**
+ * By default, drags can only be initiated if the mousedown occurs in the
+ * region the linked element is. This is done in part to work around a
+ * bug in some browsers that mis-report the mousedown if the previous
+ * mouseup happened outside of the window. This property is set to true
+ * if outer handles are defined.
+ *
+ * @property hasOuterHandles
+ * @type boolean
+ * @default false
+ */
+ hasOuterHandles: false,
+
+ /**
+ * Code that executes immediately before the startDrag event
+ * @method b4StartDrag
+ * @private
+ */
+ b4StartDrag: function(x, y) { },
+
+ /**
+ * Abstract method called after a drag/drop object is clicked
+ * and the drag or mousedown time thresholds have beeen met.
+ * @method startDrag
+ * @param {int} X click location
+ * @param {int} Y click location
+ */
+ startDrag: function(x, y) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDrag event
+ * @method b4Drag
+ * @private
+ */
+ b4Drag: function(e) { },
+
+ /**
+ * Abstract method called during the onMouseMove event while dragging an
+ * object.
+ * @method onDrag
+ * @param {Event} e the mousemove event
+ */
+ onDrag: function(e) { /* override this */ },
+
+ /**
+ * Abstract method called when this element fist begins hovering over
+ * another DragDrop obj
+ * @method onDragEnter
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of one or more
+ * dragdrop items being hovered over.
+ */
+ onDragEnter: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOver event
+ * @method b4DragOver
+ * @private
+ */
+ b4DragOver: function(e) { },
+
+ /**
+ * Abstract method called when this element is hovering over another
+ * DragDrop obj
+ * @method onDragOver
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this is hovering over. In INTERSECT mode, an array of dd items
+ * being hovered over.
+ */
+ onDragOver: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragOut event
+ * @method b4DragOut
+ * @private
+ */
+ b4DragOut: function(e) { },
+
+ /**
+ * Abstract method called when we are no longer hovering over an element
+ * @method onDragOut
+ * @param {Event} e the mousemove event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this was hovering over. In INTERSECT mode, an array of dd items
+ * that the mouse is no longer over.
+ */
+ onDragOut: function(e, id) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the onDragDrop event
+ * @method b4DragDrop
+ * @private
+ */
+ b4DragDrop: function(e) { },
+
+ /**
+ * Abstract method called when this item is dropped on another DragDrop
+ * obj
+ * @method onDragDrop
+ * @param {Event} e the mouseup event
+ * @param {String|DragDrop[]} id In POINT mode, the element
+ * id this was dropped on. In INTERSECT mode, an array of dd items this
+ * was dropped on.
+ */
+ onDragDrop: function(e, id) { /* override this */ },
+
+ /**
+ * Abstract method called when this item is dropped on an area with no
+ * drop target
+ * @method onInvalidDrop
+ * @param {Event} e the mouseup event
+ */
+ onInvalidDrop: function(e) { /* override this */ },
+
+ /**
+ * Code that executes immediately before the endDrag event
+ * @method b4EndDrag
+ * @private
+ */
+ b4EndDrag: function(e) { },
+
+ /**
+ * Fired when we are done dragging the object
+ * @method endDrag
+ * @param {Event} e the mouseup event
+ */
+ endDrag: function(e) { /* override this */ },
+
+ /**
+ * Code executed immediately before the onMouseDown event
+ * @method b4MouseDown
+ * @param {Event} e the mousedown event
+ * @private
+ */
+ b4MouseDown: function(e) { },
+
+ /**
+ * Event handler that fires when a drag/drop obj gets a mousedown
+ * @method onMouseDown
+ * @param {Event} e the mousedown event
+ */
+ onMouseDown: function(e) { /* override this */ },
+
+ /**
+ * Event handler that fires when a drag/drop obj gets a mouseup
+ * @method onMouseUp
+ * @param {Event} e the mouseup event
+ */
+ onMouseUp: function(e) { /* override this */ },
+
+ /**
+ * Override the onAvailable method to do what is needed after the initial
+ * position was determined.
+ * @method onAvailable
+ */
+ onAvailable: function () {
+ },
+
+ /**
+ * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
+ * @type Object
+ */
+ defaultPadding: {
+ left: 0,
+ right: 0,
+ top: 0,
+ bottom: 0
+ },
+
+ /**
+ * Initializes the drag drop object's constraints to restrict movement to a certain element.
+ *
+ * Usage:
+ <pre><code>
+ var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
+ { dragElId: "existingProxyDiv" });
+ dd.startDrag = function(){
+ this.constrainTo("parent-id");
+ };
+ </code></pre>
+ * Or you can initalize it using the {@link Ext.core.Element} object:
+ <pre><code>
+ Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
+ startDrag : function(){
+ this.constrainTo("parent-id");
+ }
+ });
+ </code></pre>
+ * @param {Mixed} constrainTo The element to constrain to.
+ * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
+ * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
+ * an object containing the sides to pad. For example: {right:10, bottom:10}
+ * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
+ */
+ constrainTo : function(constrainTo, pad, inContent){
+ if(Ext.isNumber(pad)){
+ pad = {left: pad, right:pad, top:pad, bottom:pad};
+ }
+ pad = pad || this.defaultPadding;
+ var b = Ext.get(this.getEl()).getBox(),
+ ce = Ext.get(constrainTo),
+ s = ce.getScroll(),
+ c,
+ cd = ce.dom;
+ if(cd == document.body){
+ c = { x: s.left, y: s.top, width: Ext.core.Element.getViewWidth(), height: Ext.core.Element.getViewHeight()};
+ }else{
+ var xy = ce.getXY();
+ c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
+ }
+
+
+ var topSpace = b.y - c.y,
+ leftSpace = b.x - c.x;
+
+ this.resetConstraints();
+ this.setXConstraint(leftSpace - (pad.left||0), // left
+ c.width - leftSpace - b.width - (pad.right||0), //right
+ this.xTickSize
+ );
+ this.setYConstraint(topSpace - (pad.top||0), //top
+ c.height - topSpace - b.height - (pad.bottom||0), //bottom
+ this.yTickSize
+ );
+ },
+
+ /**
+ * Returns a reference to the linked element
+ * @method getEl
+ * @return {HTMLElement} the html element
+ */
+ getEl: function() {
+ if (!this._domRef) {
+ this._domRef = Ext.getDom(this.id);
+ }
+
+ return this._domRef;
+ },
+
+ /**
+ * Returns a reference to the actual element to drag. By default this is
+ * the same as the html element, but it can be assigned to another
+ * element. An example of this can be found in Ext.dd.DDProxy
+ * @method getDragEl
+ * @return {HTMLElement} the html element
+ */
+ getDragEl: function() {
+ return Ext.getDom(this.dragElId);
+ },
+
+ /**
+ * Sets up the DragDrop object. Must be called in the constructor of any
+ * Ext.dd.DragDrop subclass
+ * @method init
+ * @param id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {object} config configuration attributes
+ */
+ init: function(id, sGroup, config) {
+ this.initTarget(id, sGroup, config);
+ Ext.EventManager.on(this.id, "mousedown", this.handleMouseDown, this);
+ // Ext.EventManager.on(this.id, "selectstart", Event.preventDefault);
+ },
+
+ /**
+ * Initializes Targeting functionality only... the object does not
+ * get a mousedown handler.
+ * @method initTarget
+ * @param id the id of the linked element
+ * @param {String} sGroup the group of related items
+ * @param {object} config configuration attributes
+ */
+ initTarget: function(id, sGroup, config) {
+
+ // configuration attributes
+ this.config = config || {};
+
+ // create a local reference to the drag and drop manager
+ this.DDMInstance = Ext.dd.DragDropManager;
+ // initialize the groups array
+ this.groups = {};
+
+ // assume that we have an element reference instead of an id if the
+ // parameter is not a string
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+
+ // set the id
+ this.id = id;
+
+ // add to an interaction group
+ this.addToGroup((sGroup) ? sGroup : "default");
+
+ // We don't want to register this as the handle with the manager
+ // so we just set the id rather than calling the setter.
+ this.handleElId = id;
+
+ // the linked element is the element that gets dragged by default
+ this.setDragElId(id);
+
+ // by default, clicked anchors will not start drag operations.
+ this.invalidHandleTypes = { A: "A" };
+ this.invalidHandleIds = {};
+ this.invalidHandleClasses = [];
+
+ this.applyConfig();
+
+ this.handleOnAvailable();
+ },
+
+ /**
+ * Applies the configuration parameters that were passed into the constructor.
+ * This is supposed to happen at each level through the inheritance chain. So
+ * a DDProxy implentation will execute apply config on DDProxy, DD, and
+ * DragDrop in order to get all of the parameters that are available in
+ * each object.
+ * @method applyConfig
+ */
+ applyConfig: function() {
+
+ // configurable properties:
+ // padding, isTarget, maintainOffset, primaryButtonOnly
+ this.padding = this.config.padding || [0, 0, 0, 0];
+ this.isTarget = (this.config.isTarget !== false);
+ this.maintainOffset = (this.config.maintainOffset);
+ this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
+
+ },
+
+ /**
+ * Executed when the linked element is available
+ * @method handleOnAvailable
+ * @private
+ */
+ handleOnAvailable: function() {
+ this.available = true;
+ this.resetConstraints();
+ this.onAvailable();
+ },
+
+ /**
+ * Configures the padding for the target zone in px. Effectively expands
+ * (or reduces) the virtual object size for targeting calculations.
+ * Supports css-style shorthand; if only one parameter is passed, all sides
+ * will have that padding, and if only two are passed, the top and bottom
+ * will have the first param, the left and right the second.
+ * @method setPadding
+ * @param {int} iTop Top pad
+ * @param {int} iRight Right pad
+ * @param {int} iBot Bot pad
+ * @param {int} iLeft Left pad
+ */
+ setPadding: function(iTop, iRight, iBot, iLeft) {
+ // this.padding = [iLeft, iRight, iTop, iBot];
+ if (!iRight && 0 !== iRight) {
+ this.padding = [iTop, iTop, iTop, iTop];
+ } else if (!iBot && 0 !== iBot) {
+ this.padding = [iTop, iRight, iTop, iRight];
+ } else {
+ this.padding = [iTop, iRight, iBot, iLeft];
+ }
+ },
+
+ /**
+ * Stores the initial placement of the linked element.
+ * @method setInitPosition
+ * @param {int} diffX the X offset, default 0
+ * @param {int} diffY the Y offset, default 0
+ */
+ setInitPosition: function(diffX, diffY) {
+ var el = this.getEl();
+
+ if (!this.DDMInstance.verifyEl(el)) {
+ return;
+ }
+
+ var dx = diffX || 0;
+ var dy = diffY || 0;
+
+ var p = Ext.core.Element.getXY( el );
+
+ this.initPageX = p[0] - dx;
+ this.initPageY = p[1] - dy;
+
+ this.lastPageX = p[0];
+ this.lastPageY = p[1];
+
+ this.setStartPosition(p);
+ },
+
+ /**
+ * Sets the start position of the element. This is set when the obj
+ * is initialized, the reset when a drag is started.
+ * @method setStartPosition
+ * @param pos current position (from previous lookup)
+ * @private
+ */
+ setStartPosition: function(pos) {
+ var p = pos || Ext.core.Element.getXY( this.getEl() );
+ this.deltaSetXY = null;
+
+ this.startPageX = p[0];
+ this.startPageY = p[1];
+ },
+
+ /**
+ * Add this instance to a group of related drag/drop objects. All
+ * instances belong to at least one group, and can belong to as many
+ * groups as needed.
+ * @method addToGroup
+ * @param sGroup {string} the name of the group
+ */
+ addToGroup: function(sGroup) {
+ this.groups[sGroup] = true;
+ this.DDMInstance.regDragDrop(this, sGroup);
+ },
+
+ /**
+ * Remove's this instance from the supplied interaction group
+ * @method removeFromGroup
+ * @param {string} sGroup The group to drop
+ */
+ removeFromGroup: function(sGroup) {
+ if (this.groups[sGroup]) {
+ delete this.groups[sGroup];
+ }
+
+ this.DDMInstance.removeDDFromGroup(this, sGroup);
+ },
+
+ /**
+ * Allows you to specify that an element other than the linked element
+ * will be moved with the cursor during a drag
+ * @method setDragElId
+ * @param id {string} the id of the element that will be used to initiate the drag
+ */
+ setDragElId: function(id) {
+ this.dragElId = id;
+ },
+
+ /**
+ * Allows you to specify a child of the linked element that should be
+ * used to initiate the drag operation. An example of this would be if
+ * you have a content div with text and links. Clicking anywhere in the
+ * content area would normally start the drag operation. Use this method
+ * to specify that an element inside of the content div is the element
+ * that starts the drag operation.
+ * @method setHandleElId
+ * @param id {string} the id of the element that will be used to
+ * initiate the drag.
+ */
+ setHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.handleElId = id;
+ this.DDMInstance.regHandle(this.id, id);
+ },
+
+ /**
+ * Allows you to set an element outside of the linked element as a drag
+ * handle
+ * @method setOuterHandleElId
+ * @param id the id of the element that will be used to initiate the drag
+ */
+ setOuterHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ Ext.EventManager.on(id, "mousedown", this.handleMouseDown, this);
+ this.setHandleElId(id);
+
+ this.hasOuterHandles = true;
+ },
+
+ /**
+ * Remove all drag and drop hooks for this element
+ * @method unreg
+ */
+ unreg: function() {
+ Ext.EventManager.un(this.id, "mousedown", this.handleMouseDown, this);
+ this._domRef = null;
+ this.DDMInstance._remove(this);
+ },
+
+ destroy : function(){
+ this.unreg();
+ },
+
+ /**
+ * Returns true if this instance is locked, or the drag drop mgr is locked
+ * (meaning that all drag/drop is disabled on the page.)
+ * @method isLocked
+ * @return {boolean} true if this obj or all drag/drop is locked, else
+ * false
+ */
+ isLocked: function() {
+ return (this.DDMInstance.isLocked() || this.locked);
+ },
+
+ /**
+ * Fired when this object is clicked
+ * @method handleMouseDown
+ * @param {Event} e
+ * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
+ * @private
+ */
+ handleMouseDown: function(e, oDD){
+ if (this.primaryButtonOnly && e.button != 0) {
+ return;
+ }
+
+ if (this.isLocked()) {
+ return;
+ }
+
+ this.DDMInstance.refreshCache(this.groups);
+
+ var pt = e.getPoint();
+ if (!this.hasOuterHandles && !this.DDMInstance.isOverTarget(pt, this) ) {
+ } else {
+ if (this.clickValidator(e)) {
+ // set the initial element position
+ this.setStartPosition();
+ this.b4MouseDown(e);
+ this.onMouseDown(e);
+
+ this.DDMInstance.handleMouseDown(e, this);
+
+ this.DDMInstance.stopEvent(e);
+ } else {
+
+
+ }
+ }
+ },
+
+ clickValidator: function(e) {
+ var target = e.getTarget();
+ return ( this.isValidHandleChild(target) &&
+ (this.id == this.handleElId ||
+ this.DDMInstance.handleWasClicked(target, this.id)) );
+ },
+
+ /**
+ * Allows you to specify a tag name that should not start a drag operation
+ * when clicked. This is designed to facilitate embedding links within a
+ * drag handle that do something other than start the drag.
+ * @method addInvalidHandleType
+ * @param {string} tagName the type of element to exclude
+ */
+ addInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+ this.invalidHandleTypes[type] = type;
+ },
+
+ /**
+ * Lets you to specify an element id for a child of a drag handle
+ * that should not initiate a drag
+ * @method addInvalidHandleId
+ * @param {string} id the element id of the element you wish to ignore
+ */
+ addInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.invalidHandleIds[id] = id;
+ },
+
+ /**
+ * Lets you specify a css class of elements that will not initiate a drag
+ * @method addInvalidHandleClass
+ * @param {string} cssClass the class of the elements you wish to ignore
+ */
+ addInvalidHandleClass: function(cssClass) {
+ this.invalidHandleClasses.push(cssClass);
+ },
+
+ /**
+ * Unsets an excluded tag name set by addInvalidHandleType
+ * @method removeInvalidHandleType
+ * @param {string} tagName the type of element to unexclude
+ */
+ removeInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+ // this.invalidHandleTypes[type] = null;
+ delete this.invalidHandleTypes[type];
+ },
+
+ /**
+ * Unsets an invalid handle id
+ * @method removeInvalidHandleId
+ * @param {string} id the id of the element to re-enable
+ */
+ removeInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ delete this.invalidHandleIds[id];
+ },
+
+ /**
+ * Unsets an invalid css class
+ * @method removeInvalidHandleClass
+ * @param {string} cssClass the class of the element(s) you wish to
+ * re-enable
+ */
+ removeInvalidHandleClass: function(cssClass) {
+ for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
+ if (this.invalidHandleClasses[i] == cssClass) {
+ delete this.invalidHandleClasses[i];
+ }