X-Git-Url: http://git.ithinksw.org/extjs.git/blobdiff_plain/c930e9176a5a85509c5b0230e2bff5c22a591432..25ef3491bd9ae007ff1fc2b0d7943e6eaaccf775:/ext-all-debug.js diff --git a/ext-all-debug.js b/ext-all-debug.js index 0f649b55..d38b19f1 100644 --- a/ext-all-debug.js +++ b/ext-all-debug.js @@ -1,380 +1,418 @@ /*! - * Ext JS Library 3.0.0 + * Ext JS Library 3.0.3 * Copyright(c) 2006-2009 Ext JS, LLC * licensing@extjs.com * http://www.extjs.com/license */ -/** - * @class Ext.DomHelper - *

The DomHelper class provides a layer of abstraction from DOM and transparently supports creating - * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates - * from your DOM building code.

- * - *

DomHelper element specification object

- *

A specification object is used when creating elements. Attributes of this object - * are assumed to be element attributes, except for 4 special attributes: - *

- * - *

Insertion methods

- *

Commonly used insertion methods: - *

- * - *

Example

- *

This is an example, where an unordered list with 3 children items is appended to an existing - * element with id 'my-div':
-


-var dh = Ext.DomHelper; // create shorthand alias
-// specification object
-var spec = {
-    id: 'my-ul',
-    tag: 'ul',
-    cls: 'my-list',
-    // append children after creating
-    children: [     // may also specify 'cn' instead of 'children'
-        {tag: 'li', id: 'item0', html: 'List Item 0'},
-        {tag: 'li', id: 'item1', html: 'List Item 1'},
-        {tag: 'li', id: 'item2', html: 'List Item 2'}
-    ]
-};
-var list = dh.append(
-    'my-div', // the context element 'my-div' can either be the id or the actual node
-    spec      // the specification object
-);
- 

- *

Element creation specification parameters in this class may also be passed as an Array of - * specification objects. This can be used to insert multiple sibling nodes into an existing - * container very efficiently. For example, to add more list items to the example above:


-dh.append('my-ul', [
-    {tag: 'li', id: 'item3', html: 'List Item 3'},
-    {tag: 'li', id: 'item4', html: 'List Item 4'}
-]);
- * 

- * - *

Templating

- *

The real power is in the built-in templating. Instead of creating or appending any elements, - * {@link #createTemplate} returns a Template object which can be used over and over to - * insert new elements. Revisiting the example above, we could utilize templating this time: - *


-// create the node
-var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
-// get template
-var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
-
-for(var i = 0; i < 5, i++){
-    tpl.append(list, [i]); // use template to append to the actual node
-}
- * 

- *

An example using a template:


-var html = '{2}';
-
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
-tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
- * 

- * - *

The same example using named parameters:


-var html = '{text}';
-
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.append('blog-roll', {
-    id: 'link1',
-    url: 'http://www.jackslocum.com/',
-    text: "Jack's Site"
-});
-tpl.append('blog-roll', {
-    id: 'link2',
-    url: 'http://www.dustindiaz.com/',
-    text: "Dustin's Site"
-});
- * 

- * - *

Compiling Templates

- *

Templates are applied using regular expressions. The performance is great, but if - * you are adding a bunch of DOM elements using the same template, you can increase - * performance even further by {@link Ext.Template#compile "compiling"} the template. - * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and - * broken up at the different variable points and a dynamic function is created and eval'ed. - * The generated function performs string concatenation of these parts and the passed - * variables instead of using regular expressions. - *


-var html = '{text}';
-
-var tpl = new Ext.DomHelper.createTemplate(html);
-tpl.compile();
-
-//... use template like normal
- * 

- * - *

Performance Boost

- *

DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead - * of DOM can significantly boost performance.

- *

Element creation specification parameters may also be strings. If {@link #useDom} is false, - * then the string is used as innerHTML. If {@link #useDom} is true, a string specification - * results in the creation of a text node. Usage:

- *

-Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
- * 
- * @singleton - */ -Ext.DomHelper = function(){ - var tempTableEl = null, - emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i, - tableRe = /^table|tbody|tr|td$/i, - pub, - // kill repeat to save bytes - afterbegin = "afterbegin", - afterend = "afterend", - beforebegin = "beforebegin", - beforeend = "beforeend", - ts = '', - te = '
', - tbs = ts+'', - tbe = ''+te, - trs = tbs + '', - tre = ''+tbe; - - // private - function doInsert(el, o, returnElement, pos, sibling, append){ - var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o)); - return returnElement ? Ext.get(newNode, true) : newNode; - } - - // build as innerHTML where available - function createHtml(o){ - var b = "", - attr, - val, - key, - keyVal, - cn; - - if(typeof o == 'string'){ - b = o; - } else if (Ext.isArray(o)) { - Ext.each(o, function(v) { - b += createHtml(v); - }); - } else { - b += "<" + (o.tag = o.tag || "div"); - Ext.iterate(o, function(attr, val){ - if(!/tag|children|cn|html$/i.test(attr)){ - if (Ext.isObject(val)) { - b += " " + attr + "='"; - Ext.iterate(val, function(key, keyVal){ - b += key + ":" + keyVal + ";"; - }); - b += "'"; - }else{ - b += " " + ({cls : "class", htmlFor : "for"}[attr] || attr) + "='" + val + "'"; - } - } - }); - // Now either just close the tag or try to add children and close the tag. - if (emptyTags.test(o.tag)) { - b += "/>"; - } else { - b += ">"; - if ((cn = o.children || o.cn)) { - b += createHtml(cn); - } else if(o.html){ - b += o.html; - } - b += ""; - } - } - return b; - } - - function ieTable(depth, s, h, e){ - tempTableEl.innerHTML = [s, h, e].join(''); - var i = -1, - el = tempTableEl; - while(++i < depth){ - el = el.firstChild; - } - return el; - } - - /** - * @ignore - * Nasty code for IE's broken table implementation - */ - function insertIntoTable(tag, where, el, html) { - var node, - before; - - tempTableEl = tempTableEl || document.createElement('div'); - - if(tag == 'td' && (where == afterbegin || where == beforeend) || - !/td|tr|tbody/i.test(tag) && (where == beforebegin || where == afterend)) { - return; - } - before = where == beforebegin ? el : - where == afterend ? el.nextSibling : - where == afterbegin ? el.firstChild : null; - - if (where == beforebegin || where == afterend) { - el = el.parentNode; - } - - if (tag == 'td' || (tag == "tr" && (where == beforeend || where == afterbegin))) { - node = ieTable(4, trs, html, tre); - } else if ((tag == "tbody" && (where == beforeend || where == afterbegin)) || - (tag == "tr" && (where == beforebegin || where == afterend))) { - node = ieTable(3, tbs, html, tbe); - } else { - node = ieTable(2, ts, html, te); - } - el.insertBefore(node, before); - return node; - } - - - pub = { - /** - * Returns the markup for the passed Element(s) config. - * @param {Object} o The DOM object spec (and children) - * @return {String} - */ - markup : function(o){ - return createHtml(o); - }, - - /** - * Inserts an HTML fragment into the DOM. - * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd. - * @param {HTMLElement} el The context element - * @param {String} html The HTML fragmenet - * @return {HTMLElement} The new node - */ - insertHtml : function(where, el, html){ - var hash = {}, - hashVal, - setStart, - range, - frag, - rangeEl, - rs; - - where = where.toLowerCase(); - // add these here because they are used in both branches of the condition. - hash[beforebegin] = ['BeforeBegin', 'previousSibling']; - hash[afterend] = ['AfterEnd', 'nextSibling']; - - if (el.insertAdjacentHTML) { - if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){ - return rs; - } - // add these two to the hash. - hash[afterbegin] = ['AfterBegin', 'firstChild']; - hash[beforeend] = ['BeforeEnd', 'lastChild']; - if ((hashVal = hash[where])) { - el.insertAdjacentHTML(hashVal[0], html); - return el[hashVal[1]]; - } - } else { - range = el.ownerDocument.createRange(); - setStart = "setStart" + (/end/i.test(where) ? "After" : "Before"); - if (hash[where]) { - range[setStart](el); - frag = range.createContextualFragment(html); - el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling); - return el[(where == beforebegin ? "previous" : "next") + "Sibling"]; - } else { - rangeEl = (where == afterbegin ? "first" : "last") + "Child"; - if (el.firstChild) { - range[setStart](el[rangeEl]); - frag = range.createContextualFragment(html); - if(where == afterbegin){ - el.insertBefore(frag, el.firstChild); - }else{ - el.appendChild(frag); - } - } else { - el.innerHTML = html; - } - return el[rangeEl]; - } - } - throw 'Illegal insertion point -> "' + where + '"'; - }, - - /** - * Creates new DOM element(s) and inserts them before el. - * @param {Mixed} el The context element - * @param {Object/String} o The DOM object spec (and children) or raw HTML blob - * @param {Boolean} returnElement (optional) true to return a Ext.Element - * @return {HTMLElement/Ext.Element} The new node - */ - insertBefore : function(el, o, returnElement){ - return doInsert(el, o, returnElement, beforebegin); - }, - - /** - * Creates new DOM element(s) and inserts them after el. - * @param {Mixed} el The context element - * @param {Object} o The DOM object spec (and children) - * @param {Boolean} returnElement (optional) true to return a Ext.Element - * @return {HTMLElement/Ext.Element} The new node - */ - insertAfter : function(el, o, returnElement){ - return doInsert(el, o, returnElement, afterend, "nextSibling"); - }, - - /** - * Creates new DOM element(s) and inserts them as the first child of el. - * @param {Mixed} el The context element - * @param {Object/String} o The DOM object spec (and children) or raw HTML blob - * @param {Boolean} returnElement (optional) true to return a Ext.Element - * @return {HTMLElement/Ext.Element} The new node - */ - insertFirst : function(el, o, returnElement){ - return doInsert(el, o, returnElement, afterbegin, "firstChild"); - }, - - /** - * Creates new DOM element(s) and appends them to el. - * @param {Mixed} el The context element - * @param {Object/String} o The DOM object spec (and children) or raw HTML blob - * @param {Boolean} returnElement (optional) true to return a Ext.Element - * @return {HTMLElement/Ext.Element} The new node - */ - append : function(el, o, returnElement){ - return doInsert(el, o, returnElement, beforeend, "", true); - }, - - /** - * Creates new DOM element(s) and overwrites the contents of el with them. - * @param {Mixed} el The context element - * @param {Object/String} o The DOM object spec (and children) or raw HTML blob - * @param {Boolean} returnElement (optional) true to return a Ext.Element - * @return {HTMLElement/Ext.Element} The new node - */ - overwrite : function(el, o, returnElement){ - el = Ext.getDom(el); - el.innerHTML = createHtml(o); - return returnElement ? Ext.get(el.firstChild) : el.firstChild; - }, - - createHtml : createHtml - }; - return pub; +/** + * @class Ext.DomHelper + *

The DomHelper class provides a layer of abstraction from DOM and transparently supports creating + * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates + * from your DOM building code.

+ * + *

DomHelper element specification object

+ *

A specification object is used when creating elements. Attributes of this object + * are assumed to be element attributes, except for 4 special attributes: + *

+ * + *

Insertion methods

+ *

Commonly used insertion methods: + *

+ * + *

Example

+ *

This is an example, where an unordered list with 3 children items is appended to an existing + * element with id 'my-div':
+


+var dh = Ext.DomHelper; // create shorthand alias
+// specification object
+var spec = {
+    id: 'my-ul',
+    tag: 'ul',
+    cls: 'my-list',
+    // append children after creating
+    children: [     // may also specify 'cn' instead of 'children'
+        {tag: 'li', id: 'item0', html: 'List Item 0'},
+        {tag: 'li', id: 'item1', html: 'List Item 1'},
+        {tag: 'li', id: 'item2', html: 'List Item 2'}
+    ]
+};
+var list = dh.append(
+    'my-div', // the context element 'my-div' can either be the id or the actual node
+    spec      // the specification object
+);
+ 

+ *

Element creation specification parameters in this class may also be passed as an Array of + * specification objects. This can be used to insert multiple sibling nodes into an existing + * container very efficiently. For example, to add more list items to the example above:


+dh.append('my-ul', [
+    {tag: 'li', id: 'item3', html: 'List Item 3'},
+    {tag: 'li', id: 'item4', html: 'List Item 4'}
+]);
+ * 

+ * + *

Templating

+ *

The real power is in the built-in templating. Instead of creating or appending any elements, + * {@link #createTemplate} returns a Template object which can be used over and over to + * insert new elements. Revisiting the example above, we could utilize templating this time: + *


+// create the node
+var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
+// get template
+var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
+
+for(var i = 0; i < 5, i++){
+    tpl.append(list, [i]); // use template to append to the actual node
+}
+ * 

+ *

An example using a template:


+var html = '{2}';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
+tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
+ * 

+ * + *

The same example using named parameters:


+var html = '{text}';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.append('blog-roll', {
+    id: 'link1',
+    url: 'http://www.jackslocum.com/',
+    text: "Jack's Site"
+});
+tpl.append('blog-roll', {
+    id: 'link2',
+    url: 'http://www.dustindiaz.com/',
+    text: "Dustin's Site"
+});
+ * 

+ * + *

Compiling Templates

+ *

Templates are applied using regular expressions. The performance is great, but if + * you are adding a bunch of DOM elements using the same template, you can increase + * performance even further by {@link Ext.Template#compile "compiling"} the template. + * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and + * broken up at the different variable points and a dynamic function is created and eval'ed. + * The generated function performs string concatenation of these parts and the passed + * variables instead of using regular expressions. + *


+var html = '{text}';
+
+var tpl = new Ext.DomHelper.createTemplate(html);
+tpl.compile();
+
+//... use template like normal
+ * 

+ * + *

Performance Boost

+ *

DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead + * of DOM can significantly boost performance.

+ *

Element creation specification parameters may also be strings. If {@link #useDom} is false, + * then the string is used as innerHTML. If {@link #useDom} is true, a string specification + * results in the creation of a text node. Usage:

+ *

+Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
+ * 
+ * @singleton + */ +Ext.DomHelper = function(){ + var tempTableEl = null, + emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i, + tableRe = /^table|tbody|tr|td$/i, + pub, + // kill repeat to save bytes + afterbegin = 'afterbegin', + afterend = 'afterend', + beforebegin = 'beforebegin', + beforeend = 'beforeend', + ts = '', + te = '
', + tbs = ts+'', + tbe = ''+te, + trs = tbs + '', + tre = ''+tbe; + + // private + function doInsert(el, o, returnElement, pos, sibling, append){ + var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o)); + return returnElement ? Ext.get(newNode, true) : newNode; + } + + // build as innerHTML where available + function createHtml(o){ + var b = '', + attr, + val, + key, + keyVal, + cn; + + if(Ext.isString(o)){ + b = o; + } else if (Ext.isArray(o)) { + Ext.each(o, function(v) { + b += createHtml(v); + }); + } else { + b += '<' + (o.tag = o.tag || 'div'); + Ext.iterate(o, function(attr, val){ + if(!/tag|children|cn|html$/i.test(attr)){ + if (Ext.isObject(val)) { + b += ' ' + attr + '="'; + Ext.iterate(val, function(key, keyVal){ + b += key + ':' + keyVal + ';'; + }); + b += '"'; + }else{ + b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"'; + } + } + }); + // Now either just close the tag or try to add children and close the tag. + if (emptyTags.test(o.tag)) { + b += '/>'; + } else { + b += '>'; + if ((cn = o.children || o.cn)) { + b += createHtml(cn); + } else if(o.html){ + b += o.html; + } + b += ''; + } + } + return b; + } + + function ieTable(depth, s, h, e){ + tempTableEl.innerHTML = [s, h, e].join(''); + var i = -1, + el = tempTableEl, + ns; + while(++i < depth){ + el = el.firstChild; + } +// If the result is multiple siblings, then encapsulate them into one fragment. + if(ns = el.nextSibling){ + var df = document.createDocumentFragment(); + while(el){ + ns = el.nextSibling; + df.appendChild(el); + el = ns; + } + el = df; + } + return el; + } + + /** + * @ignore + * Nasty code for IE's broken table implementation + */ + function insertIntoTable(tag, where, el, html) { + var node, + before; + + tempTableEl = tempTableEl || document.createElement('div'); + + if(tag == 'td' && (where == afterbegin || where == beforeend) || + !/td|tr|tbody/i.test(tag) && (where == beforebegin || where == afterend)) { + return; + } + before = where == beforebegin ? el : + where == afterend ? el.nextSibling : + where == afterbegin ? el.firstChild : null; + + if (where == beforebegin || where == afterend) { + el = el.parentNode; + } + + if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) { + node = ieTable(4, trs, html, tre); + } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) || + (tag == 'tr' && (where == beforebegin || where == afterend))) { + node = ieTable(3, tbs, html, tbe); + } else { + node = ieTable(2, ts, html, te); + } + el.insertBefore(node, before); + return node; + } + + + pub = { + /** + * Returns the markup for the passed Element(s) config. + * @param {Object} o The DOM object spec (and children) + * @return {String} + */ + markup : function(o){ + return createHtml(o); + }, + + /** + * Applies a style specification to an element. + * @param {String/HTMLElement} el The element to apply styles to + * @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. + */ + applyStyles : function(el, styles){ + if(styles){ + var i = 0, + len, + style; + + el = Ext.fly(el); + if(Ext.isFunction(styles)){ + styles = styles.call(); + } + if(Ext.isString(styles)){ + styles = styles.trim().split(/\s*(?::|;)\s*/); + for(len = styles.length; i < len;){ + el.setStyle(styles[i++], styles[i++]); + } + }else if (Ext.isObject(styles)){ + el.setStyle(styles); + } + } + }, + + /** + * Inserts an HTML fragment into the DOM. + * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd. + * @param {HTMLElement} el The context element + * @param {String} html The HTML fragment + * @return {HTMLElement} The new node + */ + insertHtml : function(where, el, html){ + var hash = {}, + hashVal, + setStart, + range, + frag, + rangeEl, + rs; + + where = where.toLowerCase(); + // add these here because they are used in both branches of the condition. + hash[beforebegin] = ['BeforeBegin', 'previousSibling']; + hash[afterend] = ['AfterEnd', 'nextSibling']; + + if (el.insertAdjacentHTML) { + if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){ + return rs; + } + // add these two to the hash. + hash[afterbegin] = ['AfterBegin', 'firstChild']; + hash[beforeend] = ['BeforeEnd', 'lastChild']; + if ((hashVal = hash[where])) { + el.insertAdjacentHTML(hashVal[0], html); + return el[hashVal[1]]; + } + } else { + range = el.ownerDocument.createRange(); + setStart = 'setStart' + (/end/i.test(where) ? 'After' : 'Before'); + if (hash[where]) { + range[setStart](el); + frag = range.createContextualFragment(html); + el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling); + return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling']; + } else { + rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child'; + if (el.firstChild) { + range[setStart](el[rangeEl]); + frag = range.createContextualFragment(html); + if(where == afterbegin){ + el.insertBefore(frag, el.firstChild); + }else{ + el.appendChild(frag); + } + } else { + el.innerHTML = html; + } + return el[rangeEl]; + } + } + throw 'Illegal insertion point -> "' + where + '"'; + }, + + /** + * Creates new DOM element(s) and inserts them before el. + * @param {Mixed} el The context element + * @param {Object/String} o The DOM object spec (and children) or raw HTML blob + * @param {Boolean} returnElement (optional) true to return a Ext.Element + * @return {HTMLElement/Ext.Element} The new node + */ + insertBefore : function(el, o, returnElement){ + return doInsert(el, o, returnElement, beforebegin); + }, + + /** + * Creates new DOM element(s) and inserts them after el. + * @param {Mixed} el The context element + * @param {Object} o The DOM object spec (and children) + * @param {Boolean} returnElement (optional) true to return a Ext.Element + * @return {HTMLElement/Ext.Element} The new node + */ + insertAfter : function(el, o, returnElement){ + return doInsert(el, o, returnElement, afterend, 'nextSibling'); + }, + + /** + * Creates new DOM element(s) and inserts them as the first child of el. + * @param {Mixed} el The context element + * @param {Object/String} o The DOM object spec (and children) or raw HTML blob + * @param {Boolean} returnElement (optional) true to return a Ext.Element + * @return {HTMLElement/Ext.Element} The new node + */ + insertFirst : function(el, o, returnElement){ + return doInsert(el, o, returnElement, afterbegin, 'firstChild'); + }, + + /** + * Creates new DOM element(s) and appends them to el. + * @param {Mixed} el The context element + * @param {Object/String} o The DOM object spec (and children) or raw HTML blob + * @param {Boolean} returnElement (optional) true to return a Ext.Element + * @return {HTMLElement/Ext.Element} The new node + */ + append : function(el, o, returnElement){ + return doInsert(el, o, returnElement, beforeend, '', true); + }, + + /** + * Creates new DOM element(s) and overwrites the contents of el with them. + * @param {Mixed} el The context element + * @param {Object/String} o The DOM object spec (and children) or raw HTML blob + * @param {Boolean} returnElement (optional) true to return a Ext.Element + * @return {HTMLElement/Ext.Element} The new node + */ + overwrite : function(el, o, returnElement){ + el = Ext.getDom(el); + el.innerHTML = createHtml(o); + return returnElement ? Ext.get(el.firstChild) : el.firstChild; + }, + + createHtml : createHtml + }; + return pub; }();/** * @class Ext.DomHelper */ @@ -436,7 +474,7 @@ function(){ } } }); - pub.applyStyles(el, o.style); + Ext.DomHelper.applyStyles(el, o.style); if ((cn = o.children || o.cn)) { createDom(cn, el); @@ -464,33 +502,6 @@ function(){ /** True to force the use of DOM instead of html fragments @type Boolean */ useDom : false, - /** - * Applies a style specification to an element. - * @param {String/HTMLElement} el The element to apply styles to - * @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. - */ - applyStyles : function(el, styles){ - if(styles){ - var i = 0, - len, - style; - - el = Ext.fly(el); - if(Ext.isFunction(styles)){ - styles = styles.call(); - } - if(Ext.isString(styles)){ - styles = styles.trim().split(/\s*(?::|;)\s*/); - for(len = styles.length; i < len;){ - el.setStyle(styles[i++], styles[i++]); - } - }else if (Ext.isObject(styles)){ - el.setStyle(styles); - } - } - }, - /** * Creates new DOM element(s) and inserts them before el. * @param {Mixed} el The context element @@ -549,19 +560,58 @@ function(){ return pub; }());/** * @class Ext.Template - * Represents an HTML fragment template. Templates can be precompiled for greater performance. - * For a list of available format functions, see {@link Ext.util.Format}.
- * Usage: + *

Represents an HTML fragment template. Templates may be {@link #compile precompiled} + * for greater performance.

+ *

For example usage {@link #Template see the constructor}.

+ * + * @constructor + * An instance of this class may be created by passing to the constructor either + * a single argument, or multiple arguments: + *
+ * @param {Mixed} config */ Ext.Template = function(html){ var me = this, @@ -583,14 +633,35 @@ Ext.Template = function(html){ /**@private*/ me.html = html; + /** + * @cfg {Boolean} compiled Specify true to compile the template + * immediately (see {@link #compile}). + * Defaults to false. + */ if (me.compiled) { me.compile(); } }; Ext.Template.prototype = { /** - * 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'}) + * @cfg {RegExp} re The regular expression used to match template variables. + * Defaults to:

+     * re : /\{([\w-]+)\}/g                                     // for Ext Core
+     * re : /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g      // for Ext JS
+     * 
+ */ + re : /\{([\w-]+)\}/g, + /** + * See {@link #re}. + * @type RegExp + * @property re + */ + + /** + * Returns an HTML fragment of this template with the specified values applied. + * @param {Object/Array} values + * The template values. Can be an array if the params are numeric (i.e. {0}) + * or an object (i.e. {foo: 'bar'}). * @return {String} The HTML fragment */ applyTemplate : function(values){ @@ -616,13 +687,6 @@ Ext.Template.prototype = { return compile ? me.compile() : me; }, - /** - * The regular expression used to match template variables - * @type RegExp - * @property - */ - re : /\{([\w-]+)\}/g, - /** * Compiles the template into an internal function, eliminating the RegEx overhead. * @return {Ext.Template} this @@ -676,10 +740,14 @@ Ext.Template.prototype = { }, /** - * Applies the supplied values to the template and appends the new node(s) to el. + * Applies the supplied values to the template and appends + * the new node(s) to the specified el. + *

For example usage {@link #Template see the constructor}.

* @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.Element (defaults to undefined) + * @param {Object/Array} values + * The template values. Can be an array if the params are numeric (i.e. {0}) + * or an object (i.e. {foo: 'bar'}). + * @param {Boolean} returnElement (optional) true to return an Ext.Element (defaults to undefined) * @return {HTMLElement/Ext.Element} The new node or Element */ append : function(el, values, returnElement){ @@ -707,8 +775,10 @@ Ext.Template.prototype = { }; /** * 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'}) + * Returns an HTML fragment of this template with the specified values applied. + * @param {Object/Array} values + * The template values. Can be an array if the params are numeric (i.e. {0}) + * or an object (i.e. {foo: 'bar'}). * @return {String} The HTML fragment * @member Ext.Template * @method apply @@ -729,6 +799,39 @@ Ext.Template.from = function(el, config){ * @class Ext.Template */ Ext.apply(Ext.Template.prototype, { + /** + * @cfg {Boolean} disableFormats Specify true to disable format + * functions in the template. If the template does not contain + * {@link Ext.util.Format format functions}, setting disableFormats + * to true will reduce {@link #apply} time. Defaults to false. + *

+var t = new Ext.Template(
+    '<div name="{id}">',
+        '<span class="{cls}">{name} {value}</span>',
+    '</div>',
+    {
+        compiled: true,      // {@link #compile} immediately
+        disableFormats: true // reduce {@link #apply} time since no formatting
+    }    
+);
+     * 
+ * For a list of available format functions, see {@link Ext.util.Format}. + */ + disableFormats : false, + /** + * See {@link #disableFormats}. + * @type Boolean + * @property disableFormats + */ + + /** + * The regular expression used to match template variables + * @type RegExp + * @property + * @hide repeat doc + */ + 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'}) @@ -771,21 +874,6 @@ Ext.apply(Ext.Template.prototype, { return me.html.replace(me.re, fn); }, - /** - * true to disable format functions (defaults to false) - * @type Boolean - * @property - */ - disableFormats : false, - - /** - * The regular expression used to match template variables - * @type RegExp - * @property - * @hide repeat doc - */ - re : /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g, - /** * Compiles the template into an internal function, eliminating the RegEx overhead. * @return {Ext.Template} this @@ -1457,8 +1545,29 @@ Ext.DomQuery = function(){ }, /** - * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array) - * and the argument (if any) supplied in the selector. + *

Object hash of "pseudo class" filter functions which are used when filtering selections. Each function is passed + * two parameters:

+ *

A filter function returns an Array of DOM elements which conform to the pseudo class.

+ *

In addition to the provided pseudo classes listed above such as first-child and nth-child, + * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.

+ *

For example, to filter <a> elements to only return links to external resources:

+ *
+Ext.DomQuery.pseudos.external = function(c, v){
+    var r = [], ri = -1;
+    for(var i = 0, ci; ci = c[i]; i++){
+//      Include in result set only if it's a link to an external resource
+        if(ci.hostname != location.hostname){
+            r[++ri] = ci;
+        }
+    }
+    return r;
+};
+ * Then external links could be gathered with the following statement:
+var externalLinks = Ext.select("a:external");
+
*/ pseudos : { "first-child" : function(c){ @@ -1765,216 +1874,221 @@ var combo = new Ext.form.ComboBox({ me.events = e || {}; }; -EXTUTIL.Observable.prototype = function(){ - var filterOptRe = /^(?:scope|delay|buffer|single)$/, toLower = function(s){ - return s.toLowerCase(); - }; - - return { - /** - *

Fires the specified event with the passed parameters (minus the event name).

- *

An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) - * by calling {@link #enableBubble}.

- * @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. - */ +EXTUTIL.Observable.prototype = { + // private + filterOptRe : /^(?:scope|delay|buffer|single)$/, - fireEvent : function(){ - var a = TOARRAY(arguments), - ename = toLower(a[0]), - me = this, - ret = TRUE, - ce = me.events[ename], - q, - c; - if (me.eventsSuspended === TRUE) { - if (q = me.suspendedEventsQueue) { - q.push(a); - } + /** + *

Fires the specified event with the passed parameters (minus the event name).

+ *

An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget}) + * by calling {@link #enableBubble}.

+ * @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 a = TOARRAY(arguments), + ename = a[0].toLowerCase(), + me = this, + ret = TRUE, + ce = me.events[ename], + q, + c; + if (me.eventsSuspended === TRUE) { + if (q = me.eventQueue) { + q.push(a); } - else if(ISOBJECT(ce) && ce.bubble){ - if(ce.fire.apply(ce, a.slice(1)) === FALSE) { - return FALSE; - } - c = me.getBubbleTarget && me.getBubbleTarget(); - if(c && c.enableBubble) { + } + else if(ISOBJECT(ce) && ce.bubble){ + if(ce.fire.apply(ce, a.slice(1)) === FALSE) { + return FALSE; + } + c = me.getBubbleTarget && me.getBubbleTarget(); + if(c && c.enableBubble) { + if(!c.events[ename] || !Ext.isObject(c.events[ename]) || !c.events[ename].bubble) { c.enableBubble(ename); - return c.fireEvent.apply(c, a); } + return c.fireEvent.apply(c, a); } - else { - if (ISOBJECT(ce)) { - a.shift(); - ret = ce.fire.apply(ce, a); - } + } + else { + if (ISOBJECT(ce)) { + a.shift(); + ret = ce.fire.apply(ce, a); } - return ret; - }, + } + return ret; + }, - /** - * Appends an event handler to this object. - * @param {String} eventName The name of the event to listen for. - * @param {Function} handler The method the event invokes. - * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed. - * If omitted, defaults to the object which fired the event. - * @param {Object} options (optional) An object containing handler configuration. - * properties. This may contain any of the following properties:
- *

- * Combining Options
- * Using the options argument, it is possible to combine different types of listeners:
- *
- * A delayed, one-time listener. - *


+    /**
+     * Appends an event handler to this object.
+     * @param {String}   eventName The name of the event to listen for.
+     * @param {Function} handler The method the event invokes.
+     * @param {Object}   scope (optional) The scope (this reference) in which the handler function is executed.
+     * If omitted, defaults to the object which fired the event.
+     * @param {Object}   options (optional) An object containing handler configuration.
+     * properties. This may contain any of the following properties:
+ *

+ * Combining Options
+ * Using the options argument, it is possible to combine different types of listeners:
+ *
+ * A delayed, one-time listener. + *


 myDataView.on('click', this.onClick, this, {
-    single: true,
-    delay: 100
-});
- *

- * Attaching multiple handlers in 1 call
- * The method also allows for a single argument to be passed which is a config object containing properties - * which specify multiple handlers. - *

- *


-myGridPanel.on({
-    'click' : {
-        fn: this.onClick,
-        scope: this,
-        delay: 100
-    },
-    'mouseover' : {
-        fn: this.onMouseOver,
-        scope: this
-    },
-    'mouseout' : {
-        fn: this.onMouseOut,
-        scope: this
-    }
+single: true,
+delay: 100
 });
*

- * Or a shorthand syntax:
+ * Attaching multiple handlers in 1 call
+ * The method also allows for a single argument to be passed which is a config object containing properties + * which specify multiple handlers. + *

*


 myGridPanel.on({
-    'click' : this.onClick,
-    'mouseover' : this.onMouseOver,
-    'mouseout' : this.onMouseOut,
-     scope: this
+'click' : {
+    fn: this.onClick,
+    scope: this,
+    delay: 100
+},
+'mouseover' : {
+    fn: this.onMouseOver,
+    scope: this
+},
+'mouseout' : {
+    fn: this.onMouseOut,
+    scope: this
+}
 });
- */ - addListener : function(eventName, fn, scope, o){ - var me = this, - e, - oe, - isF, - ce; - if (ISOBJECT(eventName)) { - o = eventName; - for (e in o){ - oe = o[e]; - if (!filterOptRe.test(e)) { - me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o); - } - } - } else { - eventName = toLower(eventName); - ce = me.events[eventName] || TRUE; - if (typeof ce == "boolean") { - me.events[eventName] = ce = new EXTUTIL.Event(me, eventName); + *

+ * Or a shorthand syntax:
+ *


+myGridPanel.on({
+'click' : this.onClick,
+'mouseover' : this.onMouseOver,
+'mouseout' : this.onMouseOut,
+ scope: this
+});
+ */ + addListener : function(eventName, fn, scope, o){ + var me = this, + e, + oe, + isF, + ce; + if (ISOBJECT(eventName)) { + o = eventName; + for (e in o){ + oe = o[e]; + if (!me.filterOptRe.test(e)) { + me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o); } - ce.addListener(fn, scope, ISOBJECT(o) ? o : {}); } - }, - - /** - * Removes an event handler. - * @param {String} eventName The type of event the handler was associated with. - * @param {Function} handler The handler to remove. This must be a reference to the function passed into the {@link #addListener} call. - * @param {Object} scope (optional) The scope originally specified for the handler. - */ - removeListener : function(eventName, fn, scope){ - var ce = this.events[toLower(eventName)]; - if (ISOBJECT(ce)) { - ce.removeListener(fn, scope); + } else { + eventName = eventName.toLowerCase(); + ce = me.events[eventName] || TRUE; + if (Ext.isBoolean(ce)) { + me.events[eventName] = ce = new EXTUTIL.Event(me, eventName); } - }, + ce.addListener(fn, scope, ISOBJECT(o) ? o : {}); + } + }, - /** - * Removes all listeners for this object - */ - purgeListeners : function(){ - var events = this.events, - evt, - key; - for(key in events){ - evt = events[key]; - if(ISOBJECT(evt)){ - evt.clearListeners(); - } - } - }, + /** + * Removes an event handler. + * @param {String} eventName The type of event the handler was associated with. + * @param {Function} handler The handler to remove. This must be a reference to the function passed into the {@link #addListener} call. + * @param {Object} scope (optional) The scope originally specified for the handler. + */ + removeListener : function(eventName, fn, scope){ + var ce = this.events[eventName.toLowerCase()]; + if (ISOBJECT(ce)) { + ce.removeListener(fn, scope); + } + }, - /** - * Used to define events on this Observable - * @param {Object} object The object with the events defined - */ - addEvents : function(o){ - var me = this; - me.events = me.events || {}; - if (typeof o == 'string') { - EACH(arguments, function(a) { - me.events[a] = me.events[a] || TRUE; - }); - } else { - Ext.applyIf(me.events, o); + /** + * Removes all listeners for this object + */ + purgeListeners : function(){ + var events = this.events, + evt, + key; + for(key in events){ + evt = events[key]; + if(ISOBJECT(evt)){ + evt.clearListeners(); } - }, + } + }, - /** - * 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(eventName){ - var e = this.events[eventName]; - return ISOBJECT(e) && e.listeners.length > 0; - }, + /** + * 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 true + * or the first event name string if multiple event names are being passed as separate parameters. + * @param {string} Optional. Event name if multiple event names are being passed as separate parameters. + * Usage:

+this.addEvents('storeloaded', 'storecleared');
+
+ */ + addEvents : function(o){ + var me = this; + me.events = me.events || {}; + if (Ext.isString(o)) { + EACH(arguments, function(a) { + me.events[a] = me.events[a] || TRUE; + }); + } else { + Ext.applyIf(me.events, o); + } + }, - /** - * 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.suspendedEventsQueue = []; - } - }, + /** + * 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(eventName){ + var e = this.events[eventName]; + return ISOBJECT(e) && e.listeners.length > 0; + }, - /** - * Resume firing events. (see {@link #suspendEvents}) - * If events were suspended using the queueSuspended parameter, then all - * events fired during event suspension will be sent to any listeners now. - */ - resumeEvents : function(){ - var me = this; - me.eventsSuspended = !delete me.suspendedEventQueue; - EACH(me.suspendedEventsQueue, function(e) { - me.fireEvent.apply(me, e); - }); + /** + * 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 queueSuspended 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; + EACH(queued, function(e) { + me.fireEvent.apply(me, e); + }); } -}(); +}; var OBSERVABLE = EXTUTIL.Observable.prototype; /** @@ -2247,21 +2361,56 @@ Ext.apply(Ext.util.Observable.prototype, function(){ }, /** - * Used to enable bubbling of events - * @param {Object} events + *

Enables events fired by this Observable to bubble up an owner hierarchy by calling + * this.getBubbleTarget() if present. There is no implementation in the Observable base class.

+ *

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.

+ *

Example:


+Ext.override(Ext.form.Field, {
+//  Add functionality to Field's initComponent to enable the change event to bubble
+    initComponent: Ext.form.Field.prototype.initComponent.createSequence(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");
+        }
+    }
+});
+
+ * @param {Object} events The event name to bubble, or an Array of event names. */ enableBubble: function(events){ var me = this; - events = Ext.isArray(events) ? events : Ext.toArray(arguments); - Ext.each(events, function(ename){ - ename = ename.toLowerCase(); - var ce = me.events[ename] || true; - if (typeof ce == "boolean") { - ce = new Ext.util.Event(me, ename); - me.events[ename] = ce; - } - ce.bubble = true; - }); + if(!Ext.isEmpty(events)){ + events = Ext.isArray(events) ? events : Ext.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; + }); + } } }; }()); @@ -2309,21 +2458,21 @@ Ext.util.Observable.observeClass = function(c){ */ Ext.EventManager = function(){ var docReadyEvent, - docReadyProcId, - docReadyState = false, - E = Ext.lib.Event, - D = Ext.lib.Dom, - DOC = document, - WINDOW = window, - IEDEFERED = "ie-deferred-loader", - DOMCONTENTLOADED = "DOMContentLoaded", - elHash = {}, - propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; + docReadyProcId, + docReadyState = false, + E = Ext.lib.Event, + D = Ext.lib.Dom, + DOC = document, + WINDOW = window, + IEDEFERED = "ie-deferred-loader", + DOMCONTENTLOADED = "DOMContentLoaded", + elHash = {}, + propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; /// There is some jquery work around stuff here that isn't needed in Ext Core. - function addListener(el, ename, fn, wrap, scope){ + function addListener(el, ename, fn, wrap, scope){ var id = Ext.id(el), - es = elHash[id] = elHash[id] || {}; + es = elHash[id] = elHash[id] || {}; (es[ename] = es[ename] || []).push([fn, wrap, scope]); E.on(el, ename, wrap); @@ -2331,10 +2480,10 @@ Ext.EventManager = function(){ // this is a workaround for jQuery and should somehow be removed from Ext Core in the future // without breaking ExtJS. if(ename == "mousewheel" && el.addEventListener){ // workaround for jQuery - var args = ["DOMMouseScroll", wrap, false]; - el.addEventListener.apply(el, args); + var args = ["DOMMouseScroll", wrap, false]; + el.addEventListener.apply(el, args); E.on(window, 'unload', function(){ - el.removeEventListener.apply(el, args); + el.removeEventListener.apply(el, args); }); } if(ename == "mousedown" && el == document){ // fix stopped mousedowns on the document @@ -2366,8 +2515,8 @@ Ext.EventManager = function(){ }; function initDocReady(){ - var COMPLETE = "complete"; - + var COMPLETE = "complete"; + docReadyEvent = new Ext.util.Event(); if (Ext.isGecko || Ext.isOpera) { DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false); @@ -2391,7 +2540,7 @@ Ext.EventManager = function(){ function createTargeted(h, o){ return function(){ - var args = Ext.toArray(arguments); + var args = Ext.toArray(arguments); if(o.target == Ext.EventObject.setEvent(args[0]).target){ h.apply(this, args); } @@ -2425,8 +2574,8 @@ Ext.EventManager = function(){ function listen(element, ename, opt, fn, scope){ var o = !Ext.isObject(opt) ? {} : opt, - el = Ext.getDom(element); - + el = Ext.getDom(element); + fn = fn || o.fn; scope = scope || o.scope; @@ -2480,112 +2629,115 @@ Ext.EventManager = function(){ }; var pub = { - /** - * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will - * use {@link Ext.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 type of event to listen for - * @param {Function} handler The handler function the event invokes This function is passed - * the following parameters: - * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed. Defaults to the Element. - * @param {Object} options (optional) An object containing handler configuration properties. - * This may contain any of the following properties:
- *

See {@link Ext.Element#addListener} for examples of how to use these options.

- */ - addListener : function(element, eventName, fn, scope, options){ + /** + * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will + * use {@link Ext.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: + * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed. Defaults to the Element. + * @param {Object} options (optional) An object containing handler configuration properties. + * This may contain any of the following properties:
+ *

See {@link Ext.Element#addListener} for examples of how to use these options.

+ */ + addListener : function(element, eventName, fn, scope, options){ if(Ext.isObject(eventName)){ - var o = eventName, e, val; + var o = eventName, e, val; for(e in o){ - val = o[e]; - if(!propRe.test(e)){ - if(Ext.isFunction(val)){ - // shared options - listen(element, e, o, val, o.scope); - }else{ - // individual options - listen(element, e, val); - } + val = o[e]; + if(!propRe.test(e)){ + if(Ext.isFunction(val)){ + // shared options + listen(element, e, o, val, o.scope); + }else{ + // individual options + listen(element, e, val); + } } } } else { - listen(element, eventName, options, fn, scope); - } + listen(element, eventName, options, fn, scope); + } }, /** * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically * you will use {@link Ext.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 event - * @param {String} eventName The type of event - * @param {Function} fn The handler function to remove + * @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. This must be a reference to the function passed into the {@link #addListener} call. + * @param {Object} scope If a scope (this reference) was specified when the listener was added, + * then this must refer to the same object. */ removeListener : function(element, eventName, fn, scope){ var el = Ext.getDom(element), id = Ext.id(el), - wrap; - - Ext.each((elHash[id] || {})[eventName], function (v,i,a) { - if (Ext.isArray(v) && v[0] == fn && (!scope || v[2] == scope)) { - E.un(el, eventName, wrap = v[1]); - a.splice(i,1); - return false; - } - }); + wrap; + + Ext.each((elHash[id] || {})[eventName], function (v,i,a) { + if (Ext.isArray(v) && v[0] == fn && (!scope || v[2] == scope)) { + E.un(el, eventName, wrap = v[1]); + a.splice(i,1); + return false; + } + }); // jQuery workaround that should be removed from Ext Core - if(eventName == "mousewheel" && el.addEventListener && wrap){ - el.removeEventListener("DOMMouseScroll", wrap, false); - } - - if(eventName == "mousedown" && el == DOC && wrap){ // fix stopped mousedowns on the document - Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap); - } + if(eventName == "mousewheel" && el.addEventListener && wrap){ + el.removeEventListener("DOMMouseScroll", wrap, false); + } + + if(eventName == "mousedown" && el == DOC && wrap){ // fix stopped mousedowns on the document + Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap); + } }, /** * Removes all event handers from an element. Typically you will use {@link Ext.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 the event + * @param {String/HTMLElement} el The id or html element from which to remove all event handlers. */ removeAll : function(el){ - var id = Ext.id(el = Ext.getDom(el)), - es = elHash[id], - ename; - - for(ename in es){ - if(es.hasOwnProperty(ename)){ - Ext.each(es[ename], function(v) { - E.un(el, ename, v.wrap); - }); - } - } - elHash[id] = null; + var id = Ext.id(el = Ext.getDom(el)), + es = elHash[id], + ename; + + for(ename in es){ + if(es.hasOwnProperty(ename)){ + Ext.each(es[ename], function(v) { + E.un(el, ename, v.wrap); + }); + } + } + elHash[id] = null; }, /** - * Fires when the document is ready (before onload and before images are loaded). Can be + * 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) An object that becomes the scope of the handler - * @param {boolean} options (optional) An object containing standard {@link #addListener} options + * @param {Function} fn The method the event invokes. + * @param {Object} scope (optional) The scope (this reference) in which the handler function executes. Defaults to the browser window. + * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options + * {single: true} be used so that the handler is removed on first invocation. */ onDocumentReady : function(fn, scope, options){ if(docReadyState){ // if it already fired @@ -2595,8 +2747,8 @@ Ext.EventManager = function(){ } else { if(!docReadyEvent) initDocReady(); options = options || {}; - options.delay = options.delay || 1; - docReadyEvent.addListener(fn, scope, options); + options.delay = options.delay || 1; + docReadyEvent.addListener(fn, scope, options); } }, @@ -2605,10 +2757,9 @@ Ext.EventManager = function(){ /** * Appends an event handler to an element. Shorthand for {@link #addListener}. * @param {String/HTMLElement} el The html element or id to assign the event handler to - * @param {String} eventName The type of event to listen for - * @param {Function} handler The handler function the event invokes - * @param {Object} scope (optional) The scope in which to execute the handler - * function (the handler function's "this" context) + * @param {String} eventName The name of the event to listen for. + * @param {Function} handler The handler function the event invokes. + * @param {Object} scope (optional) (this reference) in which the handler function executes. Defaults to the Element. * @param {Object} options (optional) An object containing standard {@link #addListener} options * @member Ext.EventManager * @method on @@ -2616,10 +2767,11 @@ Ext.EventManager = function(){ pub.on = pub.addListener; /** * Removes an event handler from an element. Shorthand for {@link #removeListener}. - * @param {String/HTMLElement} el The id or html element from which to remove the event - * @param {String} eventName The type of event - * @param {Function} fn The handler function to remove - * @return {Boolean} True if a listener was actually removed, else false + * @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. This must be a reference to the function passed into the {@link #on} call. + * @param {Object} scope If a scope (this reference) was specified when the listener was added, + * then this must refer to the same object. * @member Ext.EventManager * @method un */ @@ -2629,10 +2781,11 @@ Ext.EventManager = function(){ return pub; }(); /** - * Fires when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}. - * @param {Function} fn The method the event invokes - * @param {Object} scope An object that becomes the scope of the handler - * @param {boolean} options (optional) An object containing standard {@link #addListener} options + * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}. + * @param {Function} fn The method the event invokes. + * @param {Object} scope (optional) The scope (this reference) in which the handler function executes. Defaults to the browser window. + * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options + * {single: true} be used so that the handler is removed on first invocation. * @member Ext * @method onReady */ @@ -2707,21 +2860,21 @@ Ext.EventManager.addListener("myDiv", 'click', handleClick); */ Ext.EventObject = function(){ var E = Ext.lib.Event, - // 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 - btnMap = Ext.isIE ? {1:0,4:1,2:2} : + // 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 + btnMap = Ext.isIE ? {1:0,4:1,2:2} : (Ext.isWebKit ? {1:0,2:1,3:2} : {0:0,1:1,2:2}); Ext.EventObjectImpl = function(e){ @@ -2733,7 +2886,7 @@ Ext.EventObject = function(){ Ext.EventObjectImpl.prototype = { /** @private */ setEvent : function(e){ - var me = this; + var me = this; if(e == me || (e && e.browserEvent)){ // already wrapped return e; } @@ -2773,7 +2926,7 @@ Ext.EventObject = function(){ * Stop the event (preventDefault and stopPropagation) */ stopEvent : function(){ - var me = this; + var me = this; if(me.browserEvent){ if(me.browserEvent.type == 'mousedown'){ Ext.EventManager.stoppedMouseDownEvent.fire(me); @@ -2795,7 +2948,7 @@ Ext.EventObject = function(){ * Cancels bubbling of the event. */ stopPropagation : function(){ - var me = this; + var me = this; if(me.browserEvent){ if(me.browserEvent.type == 'mousedown'){ Ext.EventManager.stoppedMouseDownEvent.fire(me); @@ -2819,11 +2972,11 @@ Ext.EventObject = function(){ getKey : function(){ return this.normalizeKey(this.keyCode || this.charCode) }, - - // private - normalizeKey: function(k){ - return Ext.isSafari ? (safariKeys[k] || k) : k; - }, + + // private + normalizeKey: function(k){ + return Ext.isSafari ? (safariKeys[k] || k) : k; + }, /** * Gets the x coordinate of the event. @@ -2883,37 +3036,37 @@ Ext.EventObject = function(){ } 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:

-		// 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!');
-			}
-		});
-		
- * @param {Mixed} el The id, DOM element or Ext.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){ + + /** + * 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:

+        // 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!');
+            }
+        });
+        
+ * @param {Mixed} el The id, DOM element or Ext.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 = this[related ? "getRelatedTarget" : "getTarget"](); - return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t)); + var t = this[related ? "getRelatedTarget" : "getTarget"](); + return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t)); } return false; - } - }; + } + }; return new Ext.EventObjectImpl(); }();/** @@ -2932,8 +3085,8 @@ Ext.apply(Ext.EventManager, function(){ // 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) - useKeydown = Ext.isSafari ? - Ext.num(navigator.userAgent.toLowerCase().match(/version\/(\d+\.\d)/)[1] || 2) >= 3.1 : + useKeydown = Ext.isWebKit ? + Ext.num(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1]) >= 525 : !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera); return { @@ -3371,7 +3524,7 @@ El.prototype = { } } if(o.style){ - Ext.DomHelper.applyStyles(el, o.style); + DH.applyStyles(el, o.style); } return this; }, @@ -3384,6 +3537,13 @@ El.prototype = { * @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. @@ -3683,11 +3843,11 @@ El.prototype = { /** * Appends an event handler to this element. The shorthand version {@link #on} is equivalent. - * @param {String} eventName The type of event to handle + * @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: @@ -3801,10 +3961,10 @@ el.removeListener('click', this.handlerFn); // or el.un('click', this.handlerFn);
- * @param {String} eventName the type of event to remove - * @param {Function} fn the method the event invokes - * @param {Object} scope (optional) The scope (The this reference) of the handler function. Defaults - * to this Element. + * @param {String} eventName The name of the event from which to remove the handler. + * @param {Function} fn The handler function to remove. This must be a reference to the function passed into the {@link #addListener} call. + * @param {Object} scope If a scope (this reference) was specified when the listener was added, + * then this must refer to the same object. * @return {Ext.Element} this */ removeListener : function(eventName, fn, scope){ @@ -3868,16 +4028,19 @@ el.un('click', this.handlerFn); dom = me.dom; me.removeAllListeners(); - delete El.cache[dom.id]; - delete El.dataCache[dom.id] - Ext.removeNode(dom); + if (dom) { + delete me.dom; + delete El.cache[dom.id]; + delete El.dataCache[dom.id]; + 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 (this reference) in which the functions are executed. Defaults to the Element's DOM element. + * @param {Object} scope (optional) The scope (this 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 options parameter}. * @return {Ext.Element} this */ @@ -3933,7 +4096,9 @@ el.un('click', this.handlerFn); * @return {Ext.Element} this */ update : function(html) { - this.dom.innerHTML = html; + if (this.dom) { + this.dom.innerHTML = html; + } return this; } }; @@ -3946,9 +4111,9 @@ El.addMethods = function(o){ /** * Appends an event handler (shorthand for {@link #addListener}). - * @param {String} eventName The type of event to handle - * @param {Function} fn The handler function the event invokes - * @param {Object} scope (optional) The scope (this element) of the handler function + * @param {String} eventName The name of event to handle. + * @param {Function} fn The handler function the event invokes. + * @param {Object} scope (optional) The scope (this reference) in which the handler function is executed. * @param {Object} options (optional) An object containing standard {@link #addListener} options * @member Ext.Element * @method on @@ -3957,10 +4122,10 @@ ep.on = ep.addListener; /** * Removes an event handler from this element (see {@link #removeListener} for additional notes). - * @param {String} eventName the type of event to remove - * @param {Function} fn the method the event invokes - * @param {Object} scope (optional) The scope (The this reference) of the handler function. Defaults - * to this Element. + * @param {String} eventName The name of the event from which to remove the handler. + * @param {Function} fn The handler function to remove. This must be a reference to the function passed into the {@link #addListener} call. + * @param {Object} scope If a scope (this reference) was specified when the listener was added, + * then this must refer to the same object. * @return {Ext.Element} this * @member Ext.Element * @method un @@ -4053,7 +4218,7 @@ El.data = function(el, key, value){ if(arguments.length == 2){ return c[key]; }else{ - c[key] = value; + return (c[key] = value); } }; @@ -4907,10 +5072,7 @@ Ext.Element.addMethods( function() { var GETDOM = Ext.getDom, GET = Ext.get, - DH = Ext.DomHelper, - isEl = function(el){ - return (el.nodeType || el.dom || typeof el == 'string'); - }; + DH = Ext.DomHelper; return { /** @@ -4959,14 +5121,14 @@ function() { */ insertFirst: function(el, returnDom){ el = el || {}; - if(isEl(el)){ // element + if(el.nodeType || el.dom || typeof el == 'string'){ // element el = GETDOM(el); this.dom.insertBefore(el, this.dom.firstChild); return !returnDom ? GET(el) : el; }else{ // dh config return this.createChild(el, this.dom.firstChild, returnDom); } - }, + }, /** * Replaces the passed element with this element @@ -4988,7 +5150,7 @@ function() { replaceWith: function(el){ var me = this, Element = Ext.Element; - if(isEl(el)){ + if(el.nodeType || el.dom || typeof el == 'string'){ el = GETDOM(el); me.dom.parentNode.insertBefore(el, me.dom); }else{ @@ -5085,556 +5247,565 @@ Ext.apply(Ext.Element.prototype, function() { return rt; } }; -}());/** +}());/** + * @class Ext.Element + */ +Ext.Element.addMethods(function(){ + // local style camelizing for speed + var propCache = {}, + camelRe = /(-[a-z])/gi, + classReCache = {}, + view = document.defaultView, + propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat', + opacityRe = /alpha\(opacity=(.*)\)/i, + trimRe = /^\s+|\s+$/g, + EL = Ext.Element, + 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.Element.data; + + + // private + function camelFn(m, a) { + return a.charAt(1).toUpperCase(); + } + + function chkCache(prop) { + return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn)); + } + + return { + // private ==> used by Fx + adjustWidth : function(width) { + var me = this; + var isNum = Ext.isNumber(width); + 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; + var isNum = Ext.isNumber(height); + 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 class to add, or an array of classes + * @return {Ext.Element} this + */ + addClass : function(className){ + var me = this; + Ext.each(className, function(v) { + me.dom.className += (!me.hasClass(v) && v ? " " + v : ""); + }); + 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.Element} this + */ + radioClass : function(className){ + Ext.each(this.dom.parentNode.childNodes, function(v) { + if(v.nodeType == 1) { + Ext.fly(v, '_internal').removeClass(className); + } + }); + return this.addClass(className); + }, + + /** + * Removes one or more CSS classes from the element. + * @param {String/Array} className The CSS class to remove, or an array of classes + * @return {Ext.Element} this + */ + removeClass : function(className){ + var me = this; + if (me.dom && me.dom.className) { + Ext.each(className, function(v) { + me.dom.className = me.dom.className.replace( + classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"), + " "); + }); + } + return me; + }, + + /** + * 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.Element} this + */ + toggleClass : function(className){ + return this.hasClass(className) ? this.removeClass(className) : this.addClass(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 + */ + hasClass : 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.Element} this + */ + replaceClass : function(oldClassName, newClassName){ + return this.removeClass(oldClassName).addClass(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; + if(el == document) return null; + prop = chkCache(prop); + out = (v = el.style[prop]) ? v : + (cs = view.getComputedStyle(el, "")) ? cs[prop] : null; + + // Webkit returns rgb values for transparent. + if(Ext.isWebKit && out == 'rgba(0, 0, 0, 0)'){ + 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) { + if(m = el.style.filter.match(opacityRe)){ + var fv = parseFloat(m[1]); + if(!isNaN(fv)){ + return fv ? fv / 100 : 0; + } + } + } + return 1; + } + prop = chkCache(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 = Ext.isDefined(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.Element} this + */ + setStyle : function(prop, value){ + var tmp, + style, + camel; + if (!Ext.isObject(prop)) { + tmp = {}; + tmp[prop] = value; + prop = tmp; + } + for (style in prop) { + value = prop[style]; + style == 'opacity' ? + this.setOpacity(value) : + this.dom.style[chkCache(style)] = value; + } + return this; + }, + + /** + * 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 true for + * the default animation ({duration: .35, easing: 'easeIn'}) + * @return {Ext.Element} this + */ + setOpacity : function(opacity, animate){ + var me = this, + s = me.dom.style; + + if(!animate || !me.anim){ + if(Ext.isIE){ + var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '', + val = s.filter.replace(opacityRe, '').replace(trimRe, ''); + + s.zoom = 1; + s.filter = val + (val.length > 0 ? ' ' : '') + opac; + }else{ + s.opacity = opacity; + } + }else{ + me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn'); + } + return me; + }, + + /** + * Clears any opacity settings from this element. Required in some cases for IE. + * @return {Ext.Element} this + */ + clearOpacity : function(){ + var style = this.dom.style; + if(Ext.isIE){ + if(!Ext.isEmpty(style.filter)){ + style.filter = style.filter.replace(opacityRe, '').replace(trimRe, ''); + } + }else{ + style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = ''; + } + return this; + }, + + /** + * 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){ + var me = this, + dom = me.dom, + hidden = Ext.isIE && me.isStyle('display', 'none'), + h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0; + + h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb"); + return h < 0 ? 0 : h; + }, + + /** + * 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){ + var me = this, + dom = me.dom, + hidden = Ext.isIE && me.isStyle('display', 'none'), + w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0; + w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr"); + return w < 0 ? 0 : w; + }, + + /** + * Set the width of this Element. + * @param {Mixed} width The new width. This may be one of:
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object + * @return {Ext.Element} this + */ + setWidth : function(width, animate){ + var me = this; + width = me.adjustWidth(width); + !animate || !me.anim ? + me.dom.style.width = me.addUnits(width) : + me.anim({width : {to : width}}, me.preanim(arguments, 1)); + return me; + }, + + /** + * Set the height of this Element. + *

+// change the height to 200px and animate with default configuration
+Ext.fly('elementId').setHeight(200, true);
+
+// change the height to 150px and animate with a custom configuration
+Ext.fly('elId').setHeight(150, {
+    duration : .5, // animation will have a duration of .5 seconds
+    // will change the content to "finished"
+    callback: function(){ this.{@link #update}("finished"); } 
+});
+         * 
+ * @param {Mixed} height The new height. This may be one of:
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object + * @return {Ext.Element} this + */ + setHeight : function(height, animate){ + var me = this; + height = me.adjustHeight(height); + !animate || !me.anim ? + me.dom.style.height = me.addUnits(height) : + me.anim({height : {to : height}}, me.preanim(arguments, 1)); + 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 'lr' would get the border left width + the border right 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 'lr' would get the padding left + the padding right. + * @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 {@link #unclip} to remove + * @return {Ext.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 {@link #clip} was called + * @return {Ext.Element} this + */ + unclip : function(){ + var me = this, + dom = me.dom; + + if(data(dom, ISCLIPPED)){ + data(dom, ISCLIPPED, false); + var o = 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 val = 0; + + Ext.each(sides.match(/\w/g), function(s) { + if (s = parseInt(this.getStyle(styles[s]), 10)) { + val += MATH.abs(s); + } + }, + this); + return val; + }, + + margins : margins + } +}() +); +/** * @class Ext.Element */ -Ext.Element.addMethods(function(){ - // local style camelizing for speed - var propCache = {}, - camelRe = /(-[a-z])/gi, - classReCache = {}, - view = document.defaultView, - propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat', - opacityRe = /alpha\(opacity=(.*)\)/i, - trimRe = /^\s+|\s+$/g, - EL = Ext.Element, - 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.Element.data; - - - // private - function camelFn(m, a) { - return a.charAt(1).toUpperCase(); - } - - // private (needs to be called => addStyles.call(this, sides, styles)) - function addStyles(sides, styles){ - var val = 0; - - Ext.each(sides.match(/\w/g), function(s) { - if (s = parseInt(this.getStyle(styles[s]), 10)) { - val += MATH.abs(s); - } - }, - this); - return val; - } - function chkCache(prop) { - return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn)); +// special markup used throughout Ext when box wrapping elements +Ext.Element.boxMarkup = '
'; + +Ext.Element.addMethods(function(){ + var INTERNAL = "_internal"; + return { + /** + * 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.Element} this + */ + applyStyles : function(style){ + Ext.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 ret = {}; + Ext.each(arguments, function(v) { + ret[v] = this.getStyle(v); + }, + this); + return ret; + }, + + getStyleSize : function(){ + var me = this, + w, + h, + d = this.dom, + s = d.style; + if(s.width && s.width != 'auto'){ + w = parseInt(s.width, 10); + if(me.isBorderBox()){ + w -= me.getFrameWidth('lr'); + } + } + if(s.height && s.height != 'auto'){ + h = parseInt(s.height, 10); + if(me.isBorderBox()){ + h -= me.getFrameWidth('tb'); + } + } + return {width: w || me.getWidth(true), height: h || me.getHeight(true)}; + }, + + // private ==> used by ext full + setOverflow : function(v){ + var dom = this.dom; + if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug + dom.style.overflow = 'hidden'; + (function(){dom.style.overflow = 'auto';}).defer(1); + }else{ + dom.style.overflow = v; + } + }, + + /** + *

Wraps the specified element with a special 9 element markup/CSS block that renders by default as + * a gray container with a gradient background, rounded corners and a 4-way shadow.

+ *

This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button}, + * {@link Ext.Panel} when {@link Ext.Panel#frame frame=true}, {@link Ext.Window}). The markup + * is of this form:

+ *

+Ext.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>';
+		* 
+ *

Example usage:

+ *

+// Basic box wrap
+Ext.get("foo").boxWrap();
+
+// You can also add a custom class and use CSS inheritance rules to customize the box look.
+// 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
+// for how to create a custom box wrap style.
+Ext.get("foo").boxWrap().addClass("x-box-blue");
+		* 
+ * @param {String} class (optional) A base CSS class to apply to the containing wrapper element + * (defaults to 'x-box'). 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.Element} this + */ + boxWrap : function(cls){ + cls = cls || 'x-box'; + var el = Ext.get(this.insertHtml("beforeBegin", "
" + String.format(Ext.Element.boxMarkup, cls) + "
")); //String.format('
'+Ext.Element.boxMarkup+'
', cls))); + Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom); + return el; + }, - } - - return { - // private ==> used by Fx - adjustWidth : function(width) { - var me = this; - var 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; - var 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 class to add, or an array of classes - * @return {Ext.Element} this - */ - addClass : function(className){ - var me = this; - Ext.each(className, function(v) { - me.dom.className += (!me.hasClass(v) && v ? " " + v : ""); - }); - 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.Element} this - */ - radioClass : function(className){ - Ext.each(this.dom.parentNode.childNodes, function(v) { - if(v.nodeType == 1) { - Ext.fly(v, '_internal').removeClass(className); - } - }); - return this.addClass(className); - }, - /** - * Removes one or more CSS classes from the element. - * @param {String/Array} className The CSS class to remove, or an array of classes - * @return {Ext.Element} this - */ - removeClass : function(className){ - var me = this; - if (me.dom.className) { - Ext.each(className, function(v) { - me.dom.className = me.dom.className.replace( - classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"), - " "); - }); - } - return me; - }, - - /** - * 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.Element} this - */ - toggleClass : function(className){ - return this.hasClass(className) ? this.removeClass(className) : this.addClass(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 - */ - hasClass : 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.Element} this - */ - replaceClass : function(oldClassName, newClassName){ - return this.removeClass(oldClassName).addClass(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; - if(el == document) return null; - prop = chkCache(prop); - return (v = el.style[prop]) ? v : - (cs = view.getComputedStyle(el, "")) ? cs[prop] : null; - } : - function(prop){ - var el = this.dom, - m, - cs; - - if(el == document) return null; - if (prop == 'opacity') { - if (el.style.filter.match) { - if(m = el.style.filter.match(opacityRe)){ - var fv = parseFloat(m[1]); - if(!isNaN(fv)){ - return fv ? fv / 100 : 0; - } - } - } - return 1; - } - prop = chkCache(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 || '#', - 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.Element} this - */ - setStyle : function(prop, value){ - var tmp, - style, - camel; - if (!Ext.isObject(prop)) { - tmp = {}; - tmp[prop] = value; - prop = tmp; - } - for (style in prop) { - value = prop[style]; - style == 'opacity' ? - this.setOpacity(value) : - this.dom.style[chkCache(style)] = value; - } - return this; - }, - - /** - * 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 true for - * the default animation ({duration: .35, easing: 'easeIn'}) - * @return {Ext.Element} this - */ - setOpacity : function(opacity, animate){ - var me = this, - s = me.dom.style; - - if(!animate || !me.anim){ - if(Ext.isIE){ - var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '', - val = s.filter.replace(opacityRe, '').replace(trimRe, ''); - - s.zoom = 1; - s.filter = val + (val.length > 0 ? ' ' : '') + opac; - }else{ - s.opacity = opacity; - } - }else{ - me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn'); - } - return me; - }, - - /** - * Clears any opacity settings from this element. Required in some cases for IE. - * @return {Ext.Element} this - */ - clearOpacity : function(){ - var style = this.dom.style; - if(Ext.isIE){ - if(!Ext.isEmpty(style.filter)){ - style.filter = style.filter.replace(opacityRe, '').replace(trimRe, ''); - } - }else{ - style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = ''; - } - return this; - }, - - /** - * 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){ - var me = this, - dom = me.dom, - h = MATH.max(dom.offsetHeight, dom.clientHeight) || 0; - h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb"); - return h < 0 ? 0 : h; - }, - - /** - * 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){ - var me = this, - dom = me.dom, - w = MATH.max(dom.offsetWidth, dom.clientWidth) || 0; - w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr"); - return w < 0 ? 0 : w; - }, - - /** - * Set the width of this Element. - * @param {Mixed} width The new width. This may be one of:
- * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object - * @return {Ext.Element} this - */ - setWidth : function(width, animate){ - var me = this; - width = me.adjustWidth(width); - !animate || !me.anim ? - me.dom.style.width = me.addUnits(width) : - me.anim({width : {to : width}}, me.preanim(arguments, 1)); - return me; - }, - - /** - * Set the height of this Element. - *

-// change the height to 200px and animate with default configuration
-Ext.fly('elementId').setHeight(200, true);
-
-// change the height to 150px and animate with a custom configuration
-Ext.fly('elId').setHeight(150, {
-    duration : .5, // animation will have a duration of .5 seconds
-    // will change the content to "finished"
-    callback: function(){ this.{@link #update}("finished"); } 
-});
-         * 
- * @param {Mixed} height The new height. This may be one of:
- * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object - * @return {Ext.Element} this - */ - setHeight : function(height, animate){ - var me = this; - height = me.adjustHeight(height); - !animate || !me.anim ? - me.dom.style.height = me.addUnits(height) : - me.anim({height : {to : height}}, me.preanim(arguments, 1)); - 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 'lr' would get the border left width + the border right width. - * @return {Number} The width of the sides passed added together - */ - getBorderWidth : function(side){ - return addStyles.call(this, 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 'lr' would get the padding left + the padding right. - * @return {Number} The padding of the sides passed added together - */ - getPadding : function(side){ - return addStyles.call(this, side, paddings); - }, - - /** - * Store the current overflow setting and clip overflow on the element - use {@link #unclip} to remove - * @return {Ext.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 {@link #clip} was called - * @return {Ext.Element} this - */ - unclip : function(){ - var me = this, - dom = me.dom; - - if(data(dom, ISCLIPPED)){ - data(dom, ISCLIPPED, false); - var o = 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; - }, - - addStyles : addStyles, - margins : margins - } -}() -);/** - * @class Ext.Element - */ - -// special markup used throughout Ext when box wrapping elements -Ext.Element.boxMarkup = '
'; - -Ext.Element.addMethods(function(){ - var INTERNAL = "_internal"; - return { - /** - * 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.Element} this - */ - applyStyles : function(style){ - Ext.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 ret = {}; - Ext.each(arguments, function(v) { - ret[v] = this.getStyle(v); - }, - this); - return ret; - }, - - getStyleSize : function(){ - var me = this, - w, - h, - d = this.dom, - s = d.style; - if(s.width && s.width != 'auto'){ - w = parseInt(s.width, 10); - if(me.isBorderBox()){ - w -= me.getFrameWidth('lr'); - } - } - if(s.height && s.height != 'auto'){ - h = parseInt(s.height, 10); - if(me.isBorderBox()){ - h -= me.getFrameWidth('tb'); - } - } - return {width: w || me.getWidth(true), height: h || me.getHeight(true)}; - }, - - // private ==> used by ext full - setOverflow : function(v){ - var dom = this.dom; - if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug - dom.style.overflow = 'hidden'; - (function(){dom.style.overflow = 'auto';}).defer(1); - }else{ - dom.style.overflow = v; - } - }, - - /** - *

Wraps the specified element with a special 9 element markup/CSS block that renders by default as - * a gray container with a gradient background, rounded corners and a 4-way shadow.

- *

This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button}, - * {@link Ext.Panel} when {@link Ext.Panel#frame frame=true}, {@link Ext.Window}). The markup - * is of this form:

- *

-Ext.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>';
-		* 
- *

Example usage:

- *

-// Basic box wrap
-Ext.get("foo").boxWrap();
-
-// You can also add a custom class and use CSS inheritance rules to customize the box look.
-// 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
-// for how to create a custom box wrap style.
-Ext.get("foo").boxWrap().addClass("x-box-blue");
-		* 
- * @param {String} class (optional) A base CSS class to apply to the containing wrapper element - * (defaults to 'x-box'). 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.Element} this - */ - boxWrap : function(cls){ - cls = cls || 'x-box'; - var el = Ext.get(this.insertHtml("beforeBegin", "
" + String.format(Ext.Element.boxMarkup, cls) + "
")); //String.format('
'+Ext.Element.boxMarkup+'
', cls))); - 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:
- * @param {Mixed} height The new height. This may be one of:
- * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object + * 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:
+ * @param {Mixed} height The new height. This may be one of:
+ * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object * @return {Ext.Element} this */ setSize : function(width, height, animate){ @@ -5843,10 +6014,6 @@ var D = Ext.lib.Dom, AUTO = "auto", ZINDEX = "z-index"; -function animTest(args, animate, i) { - return this.preanim && !!animate ? this.preanim(args, i) : false -} - Ext.Element.addMethods({ /** * 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). @@ -5890,7 +6057,7 @@ Ext.Element.addMethods({ * @return {Ext.Element} this */ setX : function(x, animate){ - return this.setXY([x, this.getY()], animTest.call(this, arguments, animate, 1)); + return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1)); }, /** @@ -5900,7 +6067,7 @@ Ext.Element.addMethods({ * @return {Ext.Element} this */ setY : function(y, animate){ - return this.setXY([this.getX(), y], animTest.call(this, arguments, animate, 1)); + return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1)); }, /** @@ -5969,7 +6136,7 @@ Ext.Element.addMethods({ * @return {Ext.Element} this */ setLocation : function(x, y, animate){ - return this.setXY([x, y], animTest.call(this, arguments, animate, 2)); + return this.setXY([x, y], this.animTest(arguments, animate, 2)); }, /** @@ -5981,7 +6148,7 @@ Ext.Element.addMethods({ * @return {Ext.Element} this */ moveTo : function(x, y, animate){ - return this.setXY([x, y], animTest.call(this, arguments, animate, 2)); + return this.setXY([x, y], this.animTest(arguments, animate, 2)); }, /** @@ -6122,7 +6289,9 @@ Ext.Element.addMethods({ return {left: (x - o[0] + l), top: (y - o[1] + t)}; }, - animTest : animTest + animTest : function(args, animate, i) { + return !!animate && this.preanim ? this.preanim(args, i) : false; + } }); })();/** * @class Ext.Element @@ -6345,15 +6514,15 @@ Ext.Element.addMethods({ * @return {Element} this */ scrollTo : function(side, value, animate){ - var tester = /top/i, - prop = "scroll" + (tester.test(side) ? "Top" : "Left"), - me = this, - dom = me.dom; + var top = /top/i.test(side), //check if we're scrolling top or left + prop = 'scroll' + (top ? 'Left' : 'Top'), // if scrolling top, we need to grab scrollLeft, if left, scrollTop + me = this, + dom = me.dom; if (!animate || !me.anim) { dom[prop] = value; } else { - me.anim({scroll: {to: tester.test(prop) ? [dom[prop], value] : [value, dom[prop]]}}, - me.preanim(arguments, 2), 'scroll'); + me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}}, + me.preanim(arguments, 2), 'scroll'); } return me; }, @@ -6487,7 +6656,7 @@ Ext.Element.addMethods(function(){ /** * 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 visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY + * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY * @return {Ext.Element} this */ setVisibilityMode : function(visMode){ @@ -6890,9 +7059,7 @@ function(){ shim; el.frameBorder = '0'; el.className = 'ext-shim'; - if(Ext.isIE && Ext.isSecure){ - el.src = Ext.SSL_SECURE_URL; - } + el.src = Ext.SSL_SECURE_URL; shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom)); shim.autoBoxAdjust = false; return shim; @@ -7031,7 +7198,7 @@ br The bottom right corner * The callback is intended for any additional code that should run once a particular effect has completed. The Element * being operated upon is passed as the first parameter. * - * @cfg {Object} scope The scope of the {@link #callback} function + * @cfg {Object} scope The scope (this reference) in which the {@link #callback} function is executed. Defaults to the browser window. * * @cfg {String} easing A valid Ext.lib.Easing value for the effect:

+ * + * + * + *
  • Conditional processing with basic comparison operators + *
    + *

    The tpl tag and the if operator are used + * to provide conditional checks for deciding whether or not to render specific + * parts of the template. Notes:

      + *
    • Double quotes must be encoded if used within the conditional
    • + *
    • There is no else operator — if needed, two opposite + * if statements should be used.
    • + *
    + *
    
    +<tpl if="age > 1 && age < 10">Child</tpl>
    +<tpl if="age >= 10 && age < 18">Teenager</tpl>
    +<tpl if="this.isGirl(name)">...</tpl>
    +<tpl if="id==\'download\'">...</tpl>
    +<tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
    +// no good:
    +<tpl if="name == "Jack"">Hello</tpl>
    +// encode " if it is part of the condition, e.g.
    +<tpl if="name == &quot;Jack&quot;">Hello</tpl>
    + * 
    + * Using the sample data above: *
    
     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 if="age > 1">',
    +            '<p>{name}</p>',
             '</tpl>',
         '</tpl></p>'
     );
     tpl.overwrite(panel.body, data);
    -
    - *

    Auto-rendering of flat arrays
    Flat arrays that contain values (and not objects) can be auto-rendered - * using the special {.} variable inside a loop. This variable will represent the value of - * the array at the current index:

    - *
    
    -var tpl = new Ext.XTemplate(
    -    '<p>{name}\'s favorite beverages:</p>',
    -    '<tpl for="drinks">',
    -       '<div> - {.}</div>',
    -    '</tpl>'
    -);
    -tpl.overwrite(panel.body, data);
    -
    - *

    Basic conditional logic
    Using the tpl tag and the if - * operator you can provide conditional checks for deciding whether or not to render specific parts of the template. - * Note that there is no else operator — if needed, you should use two opposite if statements. - * Properly-encoded attributes are required as seen in the following example:

    + * + *
    + *
  • + * + * + *
  • Basic math support + *
    + *

    The following basic math operators may be applied directly on numeric + * data values:

    + * + - * /
    + * 
    + * For example: *
    
     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>',
    +            '<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);
     
    - *

    Ability to execute arbitrary inline code
    In an XTemplate, anything between {[ ... ]} is considered - * code to be executed in the scope of the template. There are some special variables available in that code: + *

    + *
  • + * + * + *
  • Execute arbitrary inline code with special built-in template variables + *
    + *

    Anything between {[ ... ]} is considered code to be executed + * in the scope of the template. There are some special variables available in that code: *

    - * This example demonstrates basic row striping using an inline code block and the xindex variable:

    + * This example demonstrates basic row striping using an inline code block and the + * xindex variable:

    *
    
     var tpl = new Ext.XTemplate(
         '<p>Name: {name}</p>',
    @@ -12159,8 +12493,13 @@ var tpl = new Ext.XTemplate(
         '</tpl></p>'
     );
     tpl.overwrite(panel.body, data);
    -
    - *

    Template member functions
    One or more member functions can be defined directly on the config + * + *

    + *
  • + * + *
  • Template member functions + *
    + *

    One or more member functions can be specified in a configuration * object passed into the XTemplate constructor for more complex processing:

    *
    
     var tpl = new Ext.XTemplate(
    @@ -12170,25 +12509,35 @@ var tpl = new Ext.XTemplate(
             '<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>', {
    -     isGirl: function(name){
    -         return name == 'Sara Grace';
    -     },
    -     isBaby: function(age){
    -        return age < 1;
    -     }
    -});
    +    '</tpl></p>',
    +    {
    +        // XTemplate configuration:
    +        compiled: true,
    +        disableFormats: true,
    +        // member functions:
    +        isGirl: function(name){
    +            return name == 'Sara Grace';
    +        },
    +        isBaby: function(age){
    +            return age < 1;
    +        }
    +    }
    +);
     tpl.overwrite(panel.body, data);
    -
    - * @constructor - * @param {String/Array/Object} parts The HTML fragment or an array of fragments to join(""), or multiple arguments - * to join("") that can also include a config object + * + *
    + *
  • + * + * + * + * @param {Mixed} config */ Ext.XTemplate = function(){ Ext.XTemplate.superclass.constructor.apply(this, arguments); @@ -12323,7 +12672,8 @@ Ext.extend(Ext.XTemplate, Ext.Template, { } function codeFn(m, code){ - return "'"+ sep +'('+code+')'+sep+"'"; + // Single quotes get escaped when the template is compiled, however we want to undo this when running code. + return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'"; } // branched to use + in gecko and [].join() in others @@ -12476,7 +12826,7 @@ Ext.util.CSS = function(){ try{// try catch for cross domain access issue var ssRules = ss.cssRules || ss.rules; for(var j = ssRules.length-1; j >= 0; --j){ - rules[ssRules[j].selectorText] = ssRules[j]; + rules[ssRules[j].selectorText.toLowerCase()] = ssRules[j]; } }catch(e){} }, @@ -12508,11 +12858,11 @@ Ext.util.CSS = function(){ getRule : function(selector, refreshCache){ var rs = this.getRules(refreshCache); if(!Ext.isArray(selector)){ - return rs[selector]; + return rs[selector.toLowerCase()]; } for(var i = 0; i < selector.length; i++){ if(rs[selector[i]]){ - return rs[selector[i]]; + return rs[selector[i].toLowerCase()]; } } return null; @@ -12791,15 +13141,6 @@ Ext.KeyNav.prototype = { */ forceKeyDown : false, - // private - prepareEvent : function(e){ - var k = e.getKey(); - var h = this.keyToHandler[k]; - if(Ext.isSafari2 && h && k >= 37 && k <= 40){ - e.stopEvent(); - } - }, - // private relay : function(e){ var k = e.getKey(); @@ -12845,38 +13186,46 @@ Ext.KeyNav.prototype = { 27 : "esc", 9 : "tab" }, + + stopKeyUp: function(e) { + var k = e.getKey(); + + if (k >= 37 && k <= 40) { + // *** bugfix - safari 2.x fires 2 keyup events on cursor keys + // *** (note: this bugfix sacrifices the "keyup" event originating from keyNav elements in Safari 2) + e.stopEvent(); + } + }, /** * Enable this KeyNav */ - enable: function(){ - if(this.disabled){ - // ie won't do special keys on keypress, no one else will repeat keys with keydown - // the EventObject will normalize Safari automatically - if(this.isKeydown()){ - this.el.on("keydown", this.relay, this); - }else{ - this.el.on("keydown", this.prepareEvent, this); - this.el.on("keypress", this.relay, this); + enable: function() { + if (this.disabled) { + if (Ext.isSafari2) { + // call stopKeyUp() on "keyup" event + this.el.on('keyup', this.stopKeyUp, this); } - this.disabled = false; - } - }, + + this.el.on(this.isKeydown()? 'keydown' : 'keypress', this.relay, this); + this.disabled = false; + } + }, /** * Disable this KeyNav */ - disable: function(){ - if(!this.disabled){ - if(this.isKeydown()){ - this.el.un("keydown", this.relay, this); - }else{ - this.el.un("keydown", this.prepareEvent, this); - this.el.un("keypress", this.relay, this); + disable: function() { + if (!this.disabled) { + if (Ext.isSafari2) { + // remove "keyup" event handler + this.el.un('keyup', this.stopKeyUp, this); } - this.disabled = true; - } - }, + + this.el.un(this.isKeydown()? 'keydown' : 'keypress', this.relay, this); + this.disabled = true; + } + }, /** * Convenience function for setting disabled/enabled by boolean. @@ -13261,8 +13610,8 @@ Ext.util.Cookies = { * Create a cookie with the specified name and value. Additional settings * for the cookie may be optionally specified (for example: expiration, * access restriction, SSL). - * @param {Object} name - * @param {Object} value + * @param {String} name The name of the cookie to set. + * @param {Mixed} value The value to set for the cookie. * @param {Object} expires (Optional) Specify an expiration date the * cookie is to persist until. Note that the specified Date object will * be converted to Greenwich Mean Time (GMT). @@ -13297,7 +13646,7 @@ Ext.util.Cookies = { *
    
          * var validStatus = Ext.util.Cookies.get("valid");
          * 
    - * @param {Object} name The name of the cookie to get + * @param {String} name The name of the cookie to get * @return {Mixed} Returns the cookie value for the specified name; * null if the cookie name does not exist. */ @@ -13322,8 +13671,8 @@ Ext.util.Cookies = { /** * Removes a cookie with the provided name from the browser - * if found. - * @param {Object} name The name of the cookie to remove + * if found by setting its expiration date to sometime in the past. + * @param {String} name The name of the cookie to remove */ clear : function(name){ if(Ext.util.Cookies.get(name)){ @@ -13422,12 +13771,12 @@ Ext.apply(Ext.Error.prototype, { * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).

    *

    This object also provides a registry of available Component classes * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}. - * The {@link Ext.Component#xtype xtype} provides a way to avoid instantiating child Components + * The {@link Ext.Component#xtype xtype} provides a way to avoid instantiating child Components * when creating a full, nested config object for a complete Ext page.

    *

    A child Component may be specified simply as a config object - * as long as the correct {@link Ext.Component#xtype xtype} is specified so that if and when the Component + * as long as the correct {@link Ext.Component#xtype xtype} is specified so that if and when the Component * needs rendering, the correct type can be looked up for lazy instantiation.

    - *

    For a list of all available {@link Ext.Component#xtype xtypes}, see {@link Ext.Component}.

    + *

    For a list of all available {@link Ext.Component#xtype xtypes}, see {@link Ext.Component}.

    * @singleton */ Ext.ComponentMgr = function(){ @@ -13456,7 +13805,7 @@ Ext.ComponentMgr = function(){ * Returns a component by {@link Ext.Component#id id}. * For additional details see {@link Ext.util.MixedCollection#get}. * @param {String} id The component {@link Ext.Component#id id} - * @return Ext.Component The Component, undefined if not found, or null if a + * @return Ext.Component The Component, undefined if not found, or null if a * Class was found. */ get : function(id){ @@ -13514,7 +13863,7 @@ Ext.ComponentMgr = function(){ * config object's {@link Ext.component#xtype 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 xtype. (Optional if the config contains a xtype). + * the config object does not contain a xtype. (Optional if the config contains a xtype). * @return {Ext.Component} The newly instantiated Component. */ create : function(config, defaultType){ @@ -13540,7 +13889,7 @@ Ext.ComponentMgr = function(){ * config object's {@link Ext.component#ptype 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 ptype. (Optional if the config contains a ptype). + * the config object does not contain a ptype. (Optional if the config contains a ptype). * @return {Ext.Component} The newly instantiated Plugin. */ createPlugin : function(config, defaultType){ @@ -13567,8 +13916,18 @@ Ext.reg = Ext.ComponentMgr.registerType; // this will be called a lot internally * @method preg */ Ext.preg = Ext.ComponentMgr.registerPlugin; -Ext.create = Ext.ComponentMgr.create; /** + * Shorthand for {@link Ext.ComponentMgr#create} + * Creates a new Component from the specified config object using the + * config object's {@link Ext.component#xtype 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 xtype. (Optional if the config contains a xtype). + * @return {Ext.Component} The newly instantiated Component. + * @member Ext + * @method create + */ +Ext.create = Ext.ComponentMgr.create;/** * @class Ext.Component * @extends Ext.util.Observable *

    Base class for all Ext components. All subclasses of Component may participate in the automated @@ -13856,7 +14215,7 @@ Ext.Component = function(config){ Ext.Component.AUTO_ID = 1000; Ext.extend(Ext.Component, Ext.util.Observable, { - // Configs below are used for all Components when rendered by FormLayout. + // Configs below are used for all Components when rendered by FormLayout. /** * @cfg {String} fieldLabel

    The label text to display next to this Component (defaults to '').

    *

    Note: this config is only used when this Component is rendered by a Container which @@ -13955,7 +14314,11 @@ new Ext.FormPanel({ *

    See {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} also.

    */ /** - * @cfg {String} itemCls

    An additional CSS class to apply to the div wrapping the form item + * @cfg {String} itemCls + *

    Note: this config is only used when this Component is rendered by a Container which + * has been configured to use the {@link Ext.layout.FormLayout FormLayout} layout manager (e.g. + * {@link Ext.form.FormPanel} or specifying layout:'form').


    + *

    An additional CSS class to apply to the div wrapping the form item * element of this field. If supplied, itemCls at the field level will override * the default itemCls supplied at the container level. The value specified for * itemCls will be added to the default class ('x-form-item').

    @@ -13965,27 +14328,27 @@ new Ext.FormPanel({ * any other element within the markup for the field.

    *

    Note: see the note for {@link #fieldLabel}.


    * Example use:
    
    -// Apply a style to the field's label:
    +// Apply a style to the field's label:
     <style>
         .required .x-form-item-label {font-weight:bold;color:red;}
     </style>
     
     new Ext.FormPanel({
    -	height: 100,
    -	renderTo: Ext.getBody(),
    -	items: [{
    -		xtype: 'textfield',
    -		fieldLabel: 'Name',
    -		itemCls: 'required' //this label will be styled
    -	},{
    -		xtype: 'textfield',
    -		fieldLabel: 'Favorite Color'
    -	}]
    +    height: 100,
    +    renderTo: Ext.getBody(),
    +    items: [{
    +        xtype: 'textfield',
    +        fieldLabel: 'Name',
    +        itemCls: 'required' //this label will be styled
    +    },{
    +        xtype: 'textfield',
    +        fieldLabel: 'Favorite Color'
    +    }]
     });
     
    */ - // Configs below are used for all Components when rendered by AnchorLayout. + // Configs below are used for all Components when rendered by AnchorLayout. /** * @cfg {String} anchor

    Note: this config is only used when this Component is rendered * by a Container which has been configured to use an {@link Ext.layout.AnchorLayout AnchorLayout} @@ -14324,26 +14687,26 @@ new Ext.Panel({ * @property el */ /** - * The component's owner {@link Ext.Container} (defaults to undefined, and is set automatically when - * the component is added to a container). Read-only. - *

    Note: to access items within the container see {@link #itemId}.

    + * This Component's owner {@link Ext.Container Container} (defaults to undefined, and is set automatically when + * this Component is added to a Container). Read-only. + *

    Note: to access items within the Container see {@link #itemId}.

    * @type Ext.Container * @property ownerCt */ /** * True if this component is hidden. Read-only. * @type Boolean - * @property + * @property hidden */ /** * True if this component is disabled. Read-only. * @type Boolean - * @property + * @property disabled */ /** * True if this component has been rendered. Read-only. * @type Boolean - * @property + * @property rendered */ rendered : false, @@ -14555,7 +14918,7 @@ var myGrid = new Ext.grid.EditorGridPanel({ }, // private - applyState : function(state, config){ + applyState : function(state){ if(state){ Ext.apply(this, state); } @@ -14662,19 +15025,22 @@ var myGrid = new Ext.grid.EditorGridPanel({ * */ destroy : function(){ - if(this.fireEvent('beforedestroy', this) !== false){ - this.beforeDestroy(); - if(this.rendered){ - this.el.removeAllListeners(); - this.el.remove(); - if(this.actionMode == 'container' || this.removeMode == 'container'){ - this.container.remove(); + if(!this.isDestroyed){ + if(this.fireEvent('beforedestroy', this) !== false){ + this.beforeDestroy(); + if(this.rendered){ + this.el.removeAllListeners(); + this.el.remove(); + if(this.actionMode == 'container' || this.removeMode == 'container'){ + this.container.remove(); + } } + this.onDestroy(); + Ext.ComponentMgr.unregister(this); + this.fireEvent('destroy', this); + this.purgeListeners(); + this.isDestroyed = true; } - this.onDestroy(); - Ext.ComponentMgr.unregister(this); - this.fireEvent('destroy', this); - this.purgeListeners(); } }, @@ -14829,7 +15195,7 @@ new Ext.Panel({ // private onShow : function(){ - this.getVisibiltyEl().removeClass('x-hide-' + this.hideMode); + this.getVisibilityEl().removeClass('x-hide-' + this.hideMode); }, /** @@ -14857,11 +15223,11 @@ new Ext.Panel({ // private onHide : function(){ - this.getVisibiltyEl().addClass('x-hide-' + this.hideMode); + this.getVisibilityEl().addClass('x-hide-' + this.hideMode); }, // private - getVisibiltyEl : function(){ + getVisibilityEl : function(){ return this.hideParent ? this.container : this.getActionEl(); }, @@ -14879,7 +15245,7 @@ new Ext.Panel({ * @return {Boolean} True if this component is visible, false otherwise. */ isVisible : function(){ - return this.rendered && this.getVisibiltyEl().isVisible(); + return this.rendered && this.getVisibilityEl().isVisible(); }, /** @@ -15009,16 +15375,20 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' }, this); this.mons = []; }, - - // internal function for auto removal of assigned event handlers on destruction - mon : function(item, ename, fn, scope, opt){ + + // private + createMons: function(){ if(!this.mons){ this.mons = []; this.on('beforedestroy', this.clearMons, this, {single: true}); } + }, + // internal function for auto removal of assigned event handlers on destruction + mon : function(item, ename, fn, scope, opt){ + this.createMons(); if(Ext.isObject(ename)){ - var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; + var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/; var o = ename; for(var e in o){ @@ -15027,22 +15397,21 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' } if(Ext.isFunction(o[e])){ // shared options - this.mons.push({ - item: item, ename: e, fn: o[e], scope: o.scope - }); - item.on(e, o[e], o.scope, o); + this.mons.push({ + item: item, ename: e, fn: o[e], scope: o.scope + }); + item.on(e, o[e], o.scope, o); }else{ // individual options - this.mons.push({ - item: item, ename: e, fn: o[e], scope: o.scope - }); - item.on(e, o[e]); + this.mons.push({ + item: item, ename: e, fn: o[e], scope: o.scope + }); + item.on(e, o[e]); } } return; } - this.mons.push({ item: item, ename: ename, fn: fn, scope: scope }); @@ -15052,6 +15421,7 @@ alert(t.getXTypes()); // alerts 'component/box/field/textfield' // protected, opposite of mon mun : function(item, ename, fn, scope){ var found, mon; + this.createMons(); for(var i = 0, len = this.mons.length; i < len; ++i){ mon = this.mons[i]; if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){ @@ -16022,6 +16392,14 @@ var myImage = new Ext.BoxComponent({ */ Ext.BoxComponent = Ext.extend(Ext.Component, { + // tabTip config is used when a BoxComponent is a child of a TabPanel + /** + * @cfg {String} tabTip + *

    Note: this config is only used when this BoxComponent is a child item of a TabPanel.

    + * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over + * the associated tab selector element. {@link Ext.QuickTips}.init() + * must be called in order for the tips to render. + */ // Configs below are used for all Components when rendered by BorderLayout. /** * @cfg {String} region

    Note: this config is only used when this BoxComponent is rendered @@ -16421,19 +16799,14 @@ var myPanel = new Ext.Panel({ }, // private - onRender : function(ct, position){ - Ext.BoxComponent.superclass.onRender.call(this, ct, position); + afterRender : function(){ + Ext.BoxComponent.superclass.afterRender.call(this); if(this.resizeEl){ this.resizeEl = Ext.get(this.resizeEl); } if(this.positionEl){ this.positionEl = Ext.get(this.positionEl); } - }, - - // private - afterRender : function(){ - Ext.BoxComponent.superclass.afterRender.call(this); this.boxReady = true; this.setSize(this.width, this.height); if(this.x || this.y){ @@ -16934,908 +17307,967 @@ Ext.SplitBar.TOP = 3; * @type Number */ Ext.SplitBar.BOTTOM = 4; +/** + * @class Ext.Container + * @extends Ext.BoxComponent + *

    Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the + * basic behavior of containing items, namely adding, inserting and removing items.

    + * + *

    The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}. + * 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 + * {@link Ext.Component#autoEl autoEl} config option. This is a useful technique when creating + * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels} + * for example.

    + * + *

    The code below illustrates both how to explicitly create a Container, and how to implicitly + * create one using the 'container' xtype:

    
    +// explicitly create a Container
    +var embeddedColumns = new Ext.Container({
    +    autoEl: 'div',  // This is the default
    +    layout: 'column',
    +    defaults: {
    +        // implicitly create Container by specifying xtype
    +        xtype: 'container',
    +        autoEl: 'div', // This is the default.
    +        layout: 'form',
    +        columnWidth: 0.5,
    +        style: {
    +            padding: '10px'
    +        }
    +    },
    +//  The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
    +    items: [{
    +        items: {
    +            xtype: 'datefield',
    +            name: 'startDate',
    +            fieldLabel: 'Start date'
    +        }
    +    }, {
    +        items: {
    +            xtype: 'datefield',
    +            name: 'endDate',
    +            fieldLabel: 'End date'
    +        }
    +    }]
    +});

    + * + *

    Layout

    + *

    Container classes delegate the rendering of child Components to a layout + * manager class which must be configured into the Container using the + * {@link #layout} configuration property.

    + *

    When either specifying child {@link #items} 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 + * {@link #layout} schemes. By default, Containers use the + * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only + * renders child components, appending them one after the other inside the + * Container, and does not apply any sizing at all.

    + *

    A common mistake is when a developer neglects to specify a + * {@link #layout} (e.g. widgets like GridPanels or + * TreePanels are added to Containers for which no {@link #layout} + * has been specified). If a Container is left to use the default + * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its + * child components will be resized, or changed in any way when the Container + * is resized.

    + *

    Certain layout managers allow dynamic addition of child components. + * Those that do include {@link Ext.layout.CardLayout}, + * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and + * {@link Ext.layout.TableLayout}. For example:

    
    +//  Create the GridPanel.
    +var myNewGrid = new Ext.grid.GridPanel({
    +    store: myStore,
    +    columns: myColumnModel,
    +    title: 'Results', // the title becomes the title of the tab
    +});
    +
    +myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
    +myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
    + * 

    + *

    The example above adds a newly created GridPanel to a TabPanel. Note that + * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which + * means all its child items are sized to {@link Ext.layout.FitLayout fit} + * exactly into its client area. + *

    Overnesting is a common problem. + * An example of overnesting occurs when a GridPanel is added to a TabPanel + * by wrapping the GridPanel inside a wrapping Panel (that has no + * {@link #layout} specified) and then add that wrapping Panel + * to the TabPanel. The point to realize is that a GridPanel is a + * Component which can be added directly to a Container. If the wrapping Panel + * has no {@link #layout} configuration, then the overnested + * GridPanel will not be sized as expected.

    + * + *

    Adding via remote configuration

    + * + *

    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: + *

    
    +// 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");
    +    }
    +});
    +
    + *

    The server script needs to return an executable Javascript statement which, when processed + * using eval(), will return either a config object with an {@link Ext.Component#xtype xtype}, + * or an instantiated Component. The server might return this for example:

    
    +(function() {
    +    function formatDate(value){
    +        return value ? value.dateFormat('M d, Y') : '';
    +    };
    +
    +    var store = new Ext.data.Store({
    +        url: 'get-invoice-data.php',
    +        baseParams: {
    +            startDate: '01/01/2008',
    +            endDate: '01/31/2008'
    +        },
    +        reader: new Ext.data.JsonReader({
    +            record: 'transaction',
    +            idProperty: 'id',
    +            totalRecords: 'total'
    +        }, [
    +           'customer',
    +           'invNo',
    +           {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
    +           {name: 'value', type: 'float'}
    +        ])
    +    });
    +
    +    var grid = new Ext.grid.GridPanel({
    +        title: 'Invoice Report',
    +        bbar: new Ext.PagingToolbar(store),
    +        store: store,
    +        columns: [
    +            {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: formatDate, sortable: true},
    +            {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
    +        ],
    +    });
    +    store.load();
    +    return grid;  // return instantiated component
    +})();
    +
    + *

    When the above code fragment is passed through the eval function in the success handler + * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function + * runs, and returns the instantiated grid component.

    + *

    Note: since the code above is generated by a server script, the baseParams for + * the Store, the metadata to allow generation of the Record layout, and the ColumnModel + * can all be generated into the code since these are all known on the server.

    + * + * @xtype container + */ +Ext.Container = Ext.extend(Ext.BoxComponent, { + /** + * @cfg {Boolean} monitorResize + * True to automatically monitor window resize events to handle anything that is sensitive to the current size + * of the viewport. This value is typically managed by the chosen {@link #layout} and should not need + * to be set manually. + */ + /** + * @cfg {String/Object} layout + *

    *Important: In order for child items to be correctly sized and + * positioned, typically a layout manager must be specified through + * the layout configuration option.

    + *

    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:

    
    +new Ext.Window({
    +    width:300, height: 300,
    +    layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
    +    items: [{
    +        title: 'Panel inside a Window'
    +    }]
    +}).show();
    +     * 
    + *

    If the {@link #layout} configuration is not explicitly specified for + * a general purpose container (e.g. Container or Panel) the + * {@link Ext.layout.ContainerLayout 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). + * Some container classes implicitly specify a default layout + * (e.g. FormPanel specifies layout:'form'). Other specific + * purpose classes internally specify/manage their internal layout (e.g. + * GridPanel, TabPanel, TreePanel, Toolbar, Menu, etc.).

    + *

    layout may be specified as either as an Object or + * as a String:

    + */ + /** + * @cfg {Object} layoutConfig + * This is a config object containing properties specific to the chosen + * {@link #layout} if {@link #layout} + * has been specified as a string.

    + */ + /** + * @cfg {Boolean/Number} bufferResize + * When set to true (50 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer + * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers + * with a large quantity of sub-components for which frequent layout calls would be expensive. Defaults to 50. + */ + bufferResize: 50, + + /** + * @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.AccordionLayout}, {@link Ext.layout.CardLayout} and + * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}. + */ + /** + * @cfg {Object/Array} items + *
    ** IMPORTANT: be sure to {@link #layout specify a layout} if needed ! **
    + *

    A single item, or an array of child Components to be added to this container, + * for example:

    + *
    
    +// specifying a single item
    +items: {...},
    +layout: 'fit',    // specify a layout!
    +
    +// specifying multiple items
    +items: [{...}, {...}],
    +layout: 'anchor', // specify a layout!
    +     * 
    + *

    Each item may be:

    + *
    + *

    Notes:

    + *
    + */ + /** + * @cfg {Object} defaults + *

    A config object that will be applied to all components added to this container either via the {@link #items} + * config or via the {@link #add} or {@link #insert} methods. The defaults config can contain any + * number of name/value property pairs to be added to each item, and should be valid for the types of items + * being added to the container. For example, to automatically apply padding to the body of each of a set of + * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}.


    + *

    Note: defaults will not be applied to config objects if the option is already specified. + * For example:

    
    +defaults: {               // defaults are applied to items, not the container
    +    autoScroll:true
    +},
    +items: [
    +    {
    +        xtype: 'panel',   // defaults do not 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({       // defaults do have precedence over options
    +        id: 'panel2',     // options in components, so the defaults
    +        autoScroll: false // will be applied here, panel2 will be autoScroll:true.
    +    })
    +]
    +     * 
    + */ + + + /** @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 {Boolean} forceLayout + * If true the container will force a layout initially even if hidden or collapsed. This option + * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false). + */ + forceLayout: false, + + /** @cfg {Boolean} hideBorders + * True to hide the borders of each contained component, false to defer to the component's existing + * border settings (defaults to false). + */ + /** @cfg {String} defaultType + *

    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.

    + *

    Defaults to 'panel', except {@link Ext.menu.Menu} which defaults to 'menuitem', + * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'.

    + */ + defaultType : 'panel', + + /** @cfg {String} resizeEvent + * The event to listen to for resizing in layouts. Defaults to 'resize'. + */ + resizeEvent: 'resize', + + /** + * @cfg {Array} bubbleEvents + *

    An array of events that, when fired, should be bubbled to any parent container. + * Defaults to ['add', 'remove']. + */ + bubbleEvents: ['add', 'remove'], + + // private + initComponent : function(){ + Ext.Container.superclass.initComponent.call(this); + + this.addEvents( + /** + * @event afterlayout + * Fires when the components in this container are arranged by the associated layout manager. + * @param {Ext.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} 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} 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} 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} this + * @param {Ext.Component} component The component that was removed + */ + 'remove' + ); + + this.enableBubble(this.bubbleEvents); + + /** + * The collection of components in this container as a {@link Ext.util.MixedCollection} + * @type MixedCollection + * @property items + */ + var items = this.items; + if(items){ + delete this.items; + this.add(items); + } + }, + + // private + initItems : function(){ + if(!this.items){ + this.items = new Ext.util.MixedCollection(false, this.getComponentId); + this.getLayout(); // initialize the layout + } + }, + + // private + setLayout : function(layout){ + if(this.layout && this.layout != layout){ + this.layout.setContainer(null); + } + this.initItems(); + this.layout = layout; + layout.setContainer(this); + }, + + afterRender: function(){ + Ext.Container.superclass.afterRender.call(this); + if(!this.layout){ + this.layout = 'auto'; + } + if(Ext.isObject(this.layout) && !this.layout.layout){ + this.layoutConfig = this.layout; + this.layout = this.layoutConfig.type; + } + if(Ext.isString(this.layout)){ + this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig); + } + this.setLayout(this.layout); + + if(this.activeItem !== undefined){ + var item = this.activeItem; + delete this.activeItem; + this.layout.setActiveItem(item); + } + if(!this.ownerCt){ + // force a layout if no ownerCt is set + this.doLayout(false, true); + } + if(this.monitorResize === true){ + Ext.EventManager.onWindowResize(this.doLayout, this, [false]); + } + }, + + /** + *

    Returns the Element to be used to contain the child Components of this Container.

    + *

    An implementation is provided which returns the Container's {@link #getEl Element}, but + * if there is a more complex structure to a Container, this may be overridden to return + * the element into which the {@link #layout layout} renders child Components.

    + * @return {Ext.Element} The Element to render child Components into. + */ + getLayoutTarget : function(){ + return this.el; + }, + + // 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 : + *

    + *

    Notes : + *

    + * @param {Object/Array} component + *

    Either a single component or an Array of components to add. See + * {@link #items} for additional information.

    + * @param {Object} (Optional) component_2 + * @param {Object} (Optional) component_n + * @return {Ext.Component} component The Component (or config object) that was added. + */ + add : function(comp){ + this.initItems(); + var args = arguments.length > 1; + if(args || Ext.isArray(comp)){ + Ext.each(args ? arguments : comp, function(c){ + this.add(c); + }, this); + return; + } + var c = this.lookupComponent(this.applyDefaults(comp)); + var pos = this.items.length; + if(this.fireEvent('beforeadd', this, c, pos) !== false && this.onBeforeAdd(c) !== false){ + this.items.add(c); + c.ownerCt = this; + this.onAdd(c); + this.fireEvent('add', this, c, pos); + } + return c; + }, + + onAdd : function(c){ + // Empty template method + }, + + /** + * 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.

    + * Ext uses lazy rendering, and will only render the inserted Component should + * it become necessary.

    + * 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.

    + * 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){ + this.initItems(); + var a = arguments, len = a.length; + if(len > 2){ + for(var i = len-1; i >= 1; --i) { + this.insert(index, a[i]); + } + return; + } + var c = this.lookupComponent(this.applyDefaults(comp)); + index = Math.min(index, this.items.length); + if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){ + if(c.ownerCt == this){ + this.items.remove(c); + } + this.items.insert(index, c); + c.ownerCt = this; + this.onAdd(c); + this.fireEvent('add', this, c, index); + } + return c; + }, + + // private + applyDefaults : function(c){ + if(this.defaults){ + if(Ext.isString(c)){ + c = Ext.ComponentMgr.get(c); + Ext.apply(c, this.defaults); + }else if(!c.events){ + Ext.applyIf(c, this.defaults); + }else{ + Ext.apply(c, this.defaults); + } + } + return c; + }, + + // private + onBeforeAdd : function(item){ + if(item.ownerCt){ + item.ownerCt.remove(item, false); + } + if(this.hideBorders === true){ + 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){ + this.initItems(); + var c = this.getComponent(comp); + if(c && this.fireEvent('beforeremove', this, c) !== false){ + delete c.ownerCt; + if(this.layout && this.rendered){ + this.layout.onRemove(c); + } + this.onRemove(c); + this.items.remove(c); + if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){ + c.destroy(); + } + this.fireEvent('remove', this, c); + } + return c; + }, + + onRemove: function(c){ + // Empty template method + }, + + /** + * 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){ + this.initItems(); + var item, rem = [], items = []; + this.items.each(function(i){ + rem.push(i); + }); + for (var i = 0, len = rem.length; i < len; ++i){ + item = rem[i]; + this.remove(item, autoDestroy); + if(item.ownerCt !== this){ + items.push(item); + } + } + return items; + }, + + /** + * Examines this container's {@link #items} property + * and gets a direct child component of this container. + * @param {String/Number} comp This parameter may be any of the following: + *
    + *

    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); + }, + + // private + lookupComponent : function(comp){ + if(Ext.isString(comp)){ + return Ext.ComponentMgr.get(comp); + }else if(!comp.events){ + return this.createComponent(comp); + } + return comp; + }, + + // private + createComponent : function(config){ + return Ext.create(config, this.defaultType); + }, + + // private + canLayout: function() { + var el = this.getVisibilityEl(); + return el && !el.isStyle("display", "none"); + }, + + + /** + * Force this container's layout to be recalculated. A call to this function is required after adding a new component + * to an already rendered container, or possibly after changing sizing/position properties of child components. + * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto + * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer) + * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden. + * @return {Ext.Container} this + */ + doLayout: function(shallow, force){ + var rendered = this.rendered; + forceLayout = force || this.forceLayout; + + if(!this.canLayout() || this.collapsed){ + this.deferLayout = this.deferLayout || !shallow; + if(!forceLayout){ + return; + } + shallow = shallow && !this.deferLayout; + } else { + delete this.deferLayout; + } + if(rendered && this.layout){ + this.layout.layout(); + } + if(shallow !== true && this.items){ + var cs = this.items.items; + for(var i = 0, len = cs.length; i < len; i++){ + var c = cs[i]; + if(c.doLayout){ + c.doLayout(false, forceLayout); + } + } + } + if(rendered){ + this.onLayout(shallow, forceLayout); + } + // Initial layout completed + this.hasLayout = true; + delete this.forceLayout; + }, + + //private + onLayout : Ext.emptyFn, + + // private + shouldBufferLayout: function(){ + /* + * Returns true if the container should buffer a layout. + * This is true only if the container has previously been laid out + * and has a parent container that is pending a layout. + */ + var hl = this.hasLayout; + if(this.ownerCt){ + // Only ever buffer if we've laid out the first time and we have one pending. + return hl ? !this.hasLayoutPending() : false; + } + // Never buffer initial layout + return hl; + }, + + // private + hasLayoutPending: function(){ + // Traverse hierarchy to see if any parent container has a pending layout. + var pending = false; + this.ownerCt.bubble(function(c){ + if(c.layoutPending){ + pending = true; + return false; + } + }); + return pending; + }, + + onShow : function(){ + Ext.Container.superclass.onShow.call(this); + if(this.deferLayout !== undefined){ + this.doLayout(true); + } + }, + + /** + * Returns the layout currently in use by the container. If the container does not currently have a layout + * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout. + * @return {ContainerLayout} layout The container's layout + */ + getLayout : function(){ + if(!this.layout){ + var layout = new Ext.layout.ContainerLayout(this.layoutConfig); + this.setLayout(layout); + } + return this.layout; + }, + + // private + beforeDestroy : function(){ + if(this.items){ + Ext.destroy.apply(Ext, this.items.items); + } + if(this.monitorResize){ + Ext.EventManager.removeResizeListener(this.doLayout, this); + } + Ext.destroy(this.layout); + Ext.Container.superclass.beforeDestroy.call(this); + }, + + /** + * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (this) 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.Container} 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; + }, + + /** + * Cascades down the component/container heirarchy from this component (called first), calling the specified function with + * each component. The scope (this) 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 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 (defaults to passing the current component) + * @return {Ext.Container} this + */ + cascade : function(fn, scope, args){ + if(fn.apply(scope || this, args || [this]) !== false){ + if(this.items){ + var cs = this.items.items; + for(var i = 0, len = cs.length; i < len; i++){ + if(cs[i].cascade){ + cs[i].cascade(fn, scope, args); + }else{ + fn.apply(scope || cs[i], args || [cs[i]]); + } + } + } + } + return this; + }, + + /** + * Find a component under this container at any level by id + * @param {String} id + * @return Ext.Component + */ + findById : function(id){ + var m, ct = this; + this.cascade(function(c){ + if(ct != c && c.id === id){ + m = c; + return false; + } + }); + return m || null; + }, + + /** + * Find a component under this container at any level by xtype or class + * @param {String/Class} xtype The xtype string for a component, or the class of the component directly + * @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 {Array} Array of Ext.Components + */ + findByType : function(xtype, shallow){ + return this.findBy(function(c){ + return c.isXType(xtype, shallow); + }); + }, + + /** + * Find a component under this container at any level by property + * @param {String} prop + * @param {String} value + * @return {Array} Array of Ext.Components + */ + find : function(prop, value){ + return this.findBy(function(c){ + return c[prop] === value; + }); + }, + + /** + * Find a component under this container at any level by a custom function. If the passed function returns + * true, the component will be included in the results. The passed function is called with the arguments (component, this container). + * @param {Function} fn The function to call + * @param {Object} scope (optional) + * @return {Array} Array of Ext.Components + */ + findBy : function(fn, scope){ + var m = [], ct = this; + this.cascade(function(c){ + if(ct != c && fn.call(scope || c, c, ct) === true){ + m.push(c); + } + }); + return m; + }, + + /** + * Get a component contained by this container (alias for items.get(key)) + * @param {String/Number} key The index or id of the component + * @return {Ext.Component} Ext.Component + */ + get : function(key){ + return this.items.get(key); + } +}); + +Ext.Container.LAYOUTS = {}; +Ext.reg('container', Ext.Container); /** - * @class Ext.Container - * @extends Ext.BoxComponent - *

    Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the - * basic behavior of containing items, namely adding, inserting and removing items.

    - * - *

    The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}. - * 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 - * {@link Ext.Component#autoEl autoEl} config option. This is a useful technique when creating - * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels} - * for example.

    - * - *

    The code below illustrates both how to explicitly create a Container, and how to implicitly - * create one using the 'container' xtype:

    
    -// explicitly create a Container
    -var embeddedColumns = new Ext.Container({
    -    autoEl: 'div',  // This is the default
    -    layout: 'column',
    -    defaults: {
    -        // implicitly create Container by specifying xtype
    -        xtype: 'container',
    -        autoEl: 'div', // This is the default.
    -        layout: 'form',
    -        columnWidth: 0.5,
    -        style: {
    -            padding: '10px'
    -        }
    -    },
    -//  The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
    -    items: [{
    -        items: {
    -            xtype: 'datefield',
    -            name: 'startDate',
    -            fieldLabel: 'Start date'
    -        }
    -    }, {
    -        items: {
    -            xtype: 'datefield',
    -            name: 'endDate',
    -            fieldLabel: 'End date'
    -        }
    -    }]
    -});

    - * - *

    Layout

    - *

    Container classes delegate the rendering of child Components to a layout - * manager class which must be configured into the Container using the - * {@link #layout} configuration property.

    - *

    When either specifying child {@link #items} 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 - * {@link #layout} schemes. By default, Containers use the - * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only - * renders child components, appending them one after the other inside the - * Container, and does not apply any sizing at all.

    - *

    A common mistake is when a developer neglects to specify a - * {@link #layout} (e.g. widgets like GridPanels or - * TreePanels are added to Containers for which no {@link #layout} - * has been specified). If a Container is left to use the default - * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its - * child components will be resized, or changed in any way when the Container - * is resized.

    - *

    Certain layout managers allow dynamic addition of child components. - * Those that do include {@link Ext.layout.CardLayout}, - * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and - * {@link Ext.layout.TableLayout}. For example:

    
    -//  Create the GridPanel.
    -var myNewGrid = new Ext.grid.GridPanel({
    -    store: myStore,
    -    columns: myColumnModel,
    -    title: 'Results', // the title becomes the title of the tab
    -});
    -
    -myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
    -myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
    - * 

    - *

    The example above adds a newly created GridPanel to a TabPanel. Note that - * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which - * means all its child items are sized to {@link Ext.layout.FitLayout fit} - * exactly into its client area. - *

    Overnesting is a common problem. - * An example of overnesting occurs when a GridPanel is added to a TabPanel - * by wrapping the GridPanel inside a wrapping Panel (that has no - * {@link #layout} specified) and then add that wrapping Panel - * to the TabPanel. The point to realize is that a GridPanel is a - * Component which can be added directly to a Container. If the wrapping Panel - * has no {@link #layout} configuration, then the overnested - * GridPanel will not be sized as expected.

    - - * - *

    Adding via remote configuration

    - * - *

    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: - *

    
    -// 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");
    -    }
    -});
    -
    - *

    The server script needs to return an executable Javascript statement which, when processed - * using eval(), will return either a config object with an {@link Ext.Component#xtype xtype}, - * or an instantiated Component. The server might return this for example:

    
    -(function() {
    -    function formatDate(value){
    -        return value ? value.dateFormat('M d, Y') : '';
    -    };
    -
    -    var store = new Ext.data.Store({
    -        url: 'get-invoice-data.php',
    -        baseParams: {
    -            startDate: '01/01/2008',
    -            endDate: '01/31/2008'
    -        },
    -        reader: new Ext.data.JsonReader({
    -            record: 'transaction',
    -            idProperty: 'id',
    -            totalRecords: 'total'
    -        }, [
    -           'customer',
    -           'invNo',
    -           {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
    -           {name: 'value', type: 'float'}
    -        ])
    -    });
    -
    -    var grid = new Ext.grid.GridPanel({
    -        title: 'Invoice Report',
    -        bbar: new Ext.PagingToolbar(store),
    -        store: store,
    -        columns: [
    -            {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: formatDate, sortable: true},
    -            {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
    -        ],
    -    });
    -    store.load();
    -    return grid;  // return instantiated component
    -})();
    -
    - *

    When the above code fragment is passed through the eval function in the success handler - * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function - * runs, and returns the instantiated grid component.

    - *

    Note: since the code above is generated by a server script, the baseParams for - * the Store, the metadata to allow generation of the Record layout, and the ColumnModel - * can all be generated into the code since these are all known on the server.

    - * - * @xtype container - */ -Ext.Container = Ext.extend(Ext.BoxComponent, { - /** - * @cfg {Boolean} monitorResize - * True to automatically monitor window resize events to handle anything that is sensitive to the current size - * of the viewport. This value is typically managed by the chosen {@link #layout} and should not need - * to be set manually. - */ - /** - * @cfg {String/Object} layout - * When creating complex UIs, it is important to remember that sizing and - * positioning of child items is the responsibility of the Container's - * layout manager. If you expect child items to be sized in response to - * user interactions, you must specify a layout manager which - * creates and manages the type of layout you have in mind. For example:
    
    -new Ext.Window({
    -    width:300, height: 300,
    -    layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
    -    items: [{
    -        title: 'Panel inside a Window'
    -    }]
    -}).show();
    -     * 
    - *

    Omitting the {@link #layout} config means that the - * {@link Ext.layout.ContainerLayout 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).

    - *

    The layout manager class for this container may be specified as either as an - * Object or as a String:

    - *
    - */ - /** - * @cfg {Object} layoutConfig - * This is a config object containing properties specific to the chosen - * {@link #layout} if {@link #layout} - * has been specified as a string.

    - */ - /** - * @cfg {Boolean/Number} bufferResize - * When set to true (100 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer - * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers - * with a large quantity of sub-components for which frequent layout calls would be expensive. - */ - bufferResize: 100, - - /** - * @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.AccordionLayout}, {@link Ext.layout.CardLayout} and - * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}. - */ - /** - * @cfg {Object/Array} items - *
    ** IMPORTANT: be sure to specify a {@link #layout} ! **
    - *

    A single item, or an array of child Components to be added to this container, - * for example:

    - *
    
    -// specifying a single item
    -items: {...},
    -layout: 'fit',    // specify a layout!
    -
    -// specifying multiple items
    -items: [{...}, {...}],
    -layout: 'anchor', // specify a layout!
    -     * 
    - *

    Each item may be:

    - *
    - *

    Notes:

    - *
    - */ - /** - * @cfg {Object} defaults - *

    A config object that will be applied to all components added to this container either via the {@link #items} - * config or via the {@link #add} or {@link #insert} methods. The defaults config can contain any - * number of name/value property pairs to be added to each item, and should be valid for the types of items - * being added to the container. For example, to automatically apply padding to the body of each of a set of - * contained {@link Ext.Panel} items, you could pass: defaults: {bodyStyle:'padding:15px'}.


    - *

    Note: defaults will not be applied to config objects if the option is already specified. - * For example:

    
    -defaults: {               // defaults are applied to items, not the container
    -    autoScroll:true
    -},
    -items: [
    -    {
    -        xtype: 'panel',   // defaults do not 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({       // defaults do have precedence over options
    -        id: 'panel2',     // options in components, so the defaults
    -        autoScroll: false // will be applied here, panel2 will be autoScroll:true.
    -    })
    -]
    -     * 
    - */ - - - /** @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 {Boolean} forceLayout - * If true the container will force a layout initially even if hidden or collapsed. This option - * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false). - */ - forceLayout: false, - - /** @cfg {Boolean} hideBorders - * True to hide the borders of each contained component, false to defer to the component's existing - * border settings (defaults to false). - */ - /** @cfg {String} defaultType - *

    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.

    - *

    Defaults to 'panel', except {@link Ext.menu.Menu} which defaults to 'menuitem', - * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to 'button'.

    - */ - defaultType : 'panel', - - // private - initComponent : function(){ - Ext.Container.superclass.initComponent.call(this); - - this.addEvents( - /** - * @event afterlayout - * Fires when the components in this container are arranged by the associated layout manager. - * @param {Ext.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} 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} 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} 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} this - * @param {Ext.Component} component The component that was removed - */ - 'remove' - ); - - this.enableBubble('add', 'remove'); - - /** - * The collection of components in this container as a {@link Ext.util.MixedCollection} - * @type MixedCollection - * @property items - */ - var items = this.items; - if(items){ - delete this.items; - if(Ext.isArray(items) && items.length > 0){ - this.add.apply(this, items); - }else{ - this.add(items); - } - } - }, - - // private - initItems : function(){ - if(!this.items){ - this.items = new Ext.util.MixedCollection(false, this.getComponentId); - this.getLayout(); // initialize the layout - } - }, - - // private - setLayout : function(layout){ - if(this.layout && this.layout != layout){ - this.layout.setContainer(null); - } - this.initItems(); - this.layout = layout; - layout.setContainer(this); - }, - - // private - render : function(){ - Ext.Container.superclass.render.apply(this, arguments); - if(this.layout){ - if(Ext.isObject(this.layout) && !this.layout.layout){ - this.layoutConfig = this.layout; - this.layout = this.layoutConfig.type; - } - if(typeof this.layout == 'string'){ - this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig); - } - this.setLayout(this.layout); - - if(this.activeItem !== undefined){ - var item = this.activeItem; - delete this.activeItem; - this.layout.setActiveItem(item); - } - } - if(!this.ownerCt){ - // force a layout if no ownerCt is set - this.doLayout(false, true); - } - if(this.monitorResize === true){ - Ext.EventManager.onWindowResize(this.doLayout, this, [false]); - } - }, - - /** - *

    Returns the Element to be used to contain the child Components of this Container.

    - *

    An implementation is provided which returns the Container's {@link #getEl Element}, but - * if there is a more complex structure to a Container, this may be overridden to return - * the element into which the {@link #layout layout} renders child Components.

    - * @return {Ext.Element} The Element to render child Components into. - */ - getLayoutTarget : function(){ - return this.el; - }, - - // 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 : - *

    - *

    Notes : - *

    - * @param {Object/Array} component - *

    Either a single component or an Array of components to add. See - * {@link #items} for additional information.

    - * @param {Object} (Optional) component_2 - * @param {Object} (Optional) component_n - * @return {Ext.Component} component The Component (or config object) that was added. - */ - add : function(comp){ - this.initItems(); - var args = arguments.length > 1; - if(args || Ext.isArray(comp)){ - Ext.each(args ? arguments : comp, function(c){ - this.add(c); - }, this); - return; - } - var c = this.lookupComponent(this.applyDefaults(comp)); - var pos = this.items.length; - if(this.fireEvent('beforeadd', this, c, pos) !== false && this.onBeforeAdd(c) !== false){ - this.items.add(c); - c.ownerCt = this; - this.fireEvent('add', this, c, pos); - } - return c; - }, - - /** - * 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.

    - * Ext uses lazy rendering, and will only render the inserted Component should - * it become necessary.

    - * 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.

    - * 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){ - this.initItems(); - var a = arguments, len = a.length; - if(len > 2){ - for(var i = len-1; i >= 1; --i) { - this.insert(index, a[i]); - } - return; - } - var c = this.lookupComponent(this.applyDefaults(comp)); - - if(c.ownerCt == this && this.items.indexOf(c) < index){ - --index; - } - - if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){ - this.items.insert(index, c); - c.ownerCt = this; - this.fireEvent('add', this, c, index); - } - return c; - }, - - // private - applyDefaults : function(c){ - if(this.defaults){ - if(typeof c == 'string'){ - c = Ext.ComponentMgr.get(c); - Ext.apply(c, this.defaults); - }else if(!c.events){ - Ext.applyIf(c, this.defaults); - }else{ - Ext.apply(c, this.defaults); - } - } - return c; - }, - - // private - onBeforeAdd : function(item){ - if(item.ownerCt){ - item.ownerCt.remove(item, false); - } - if(this.hideBorders === true){ - 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){ - this.initItems(); - var c = this.getComponent(comp); - if(c && this.fireEvent('beforeremove', this, c) !== false){ - this.items.remove(c); - delete c.ownerCt; - if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){ - c.destroy(); - } - if(this.layout && this.layout.activeItem == c){ - delete this.layout.activeItem; - } - this.fireEvent('remove', this, c); - } - return c; - }, - - /** - * 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){ - this.initItems(); - var item, rem = [], items = []; - this.items.each(function(i){ - rem.push(i); - }); - for (var i = 0, len = rem.length; i < len; ++i){ - item = rem[i]; - this.remove(item, autoDestroy); - if(item.ownerCt !== this){ - items.push(item); - } - } - return items; - }, - - /** - * Examines this container's {@link #items} property - * and gets a direct child component of this container. - * @param {String/Number} comp This parameter may be any of the following: - *
    - *

    For additional information see {@link Ext.util.MixedCollection#get}. - * @return Ext.Component The component (if found). - */ - getComponent : function(comp){ - if(Ext.isObject(comp)){ - return comp; - } - return this.items.get(comp); - }, - - // private - lookupComponent : function(comp){ - if(typeof comp == 'string'){ - return Ext.ComponentMgr.get(comp); - }else if(!comp.events){ - return this.createComponent(comp); - } - return comp; - }, - - // private - createComponent : function(config){ - return Ext.create(config, this.defaultType); - }, - - /** - * Force this container's layout to be recalculated. A call to this function is required after adding a new component - * to an already rendered container, or possibly after changing sizing/position properties of child components. - * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto - * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer) - * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden. - * @return {Ext.Container} this - */ - doLayout: function(shallow, force){ - var rendered = this.rendered, - forceLayout = this.forceLayout; - - if(!this.isVisible() || this.collapsed){ - this.deferLayout = this.deferLayout || !shallow; - if(!(force || forceLayout)){ - return; - } - shallow = shallow && !this.deferLayout; - } else { - delete this.deferLayout; - } - if(rendered && this.layout){ - this.layout.layout(); - } - if(shallow !== true && this.items){ - var cs = this.items.items; - for(var i = 0, len = cs.length; i < len; i++){ - var c = cs[i]; - if(c.doLayout){ - c.forceLayout = forceLayout; - c.doLayout(); - } - } - } - if(rendered){ - this.onLayout(shallow, force); - } - delete this.forceLayout; - }, - - //private - onLayout : Ext.emptyFn, - - onShow : function(){ - Ext.Container.superclass.onShow.call(this); - if(this.deferLayout !== undefined){ - this.doLayout(true); - } - }, - - /** - * Returns the layout currently in use by the container. If the container does not currently have a layout - * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout. - * @return {ContainerLayout} layout The container's layout - */ - getLayout : function(){ - if(!this.layout){ - var layout = new Ext.layout.ContainerLayout(this.layoutConfig); - this.setLayout(layout); - } - return this.layout; - }, - - // private - beforeDestroy : function(){ - if(this.items){ - Ext.destroy.apply(Ext, this.items.items); - } - if(this.monitorResize){ - Ext.EventManager.removeResizeListener(this.doLayout, this); - } - Ext.destroy(this.layout); - Ext.Container.superclass.beforeDestroy.call(this); - }, - - /** - * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (this) 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.Container} 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; - }, - - /** - * Cascades down the component/container heirarchy from this component (called first), calling the specified function with - * each component. The scope (this) 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 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 (defaults to passing the current component) - * @return {Ext.Container} this - */ - cascade : function(fn, scope, args){ - if(fn.apply(scope || this, args || [this]) !== false){ - if(this.items){ - var cs = this.items.items; - for(var i = 0, len = cs.length; i < len; i++){ - if(cs[i].cascade){ - cs[i].cascade(fn, scope, args); - }else{ - fn.apply(scope || cs[i], args || [cs[i]]); - } - } - } - } - return this; - }, - - /** - * Find a component under this container at any level by id - * @param {String} id - * @return Ext.Component - */ - findById : function(id){ - var m, ct = this; - this.cascade(function(c){ - if(ct != c && c.id === id){ - m = c; - return false; - } - }); - return m || null; - }, - - /** - * Find a component under this container at any level by xtype or class - * @param {String/Class} xtype The xtype string for a component, or the class of the component directly - * @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 {Array} Array of Ext.Components - */ - findByType : function(xtype, shallow){ - return this.findBy(function(c){ - return c.isXType(xtype, shallow); - }); - }, - - /** - * Find a component under this container at any level by property - * @param {String} prop - * @param {String} value - * @return {Array} Array of Ext.Components - */ - find : function(prop, value){ - return this.findBy(function(c){ - return c[prop] === value; - }); - }, - - /** - * Find a component under this container at any level by a custom function. If the passed function returns - * true, the component will be included in the results. The passed function is called with the arguments (component, this container). - * @param {Function} fn The function to call - * @param {Object} scope (optional) - * @return {Array} Array of Ext.Components - */ - findBy : function(fn, scope){ - var m = [], ct = this; - this.cascade(function(c){ - if(ct != c && fn.call(scope || c, c, ct) === true){ - m.push(c); - } - }); - return m; - }, - - /** - * Get a component contained by this container (alias for items.get(key)) - * @param {String/Number} key The index or id of the component - * @return {Ext.Component} Ext.Component - */ - get : function(key){ - return this.items.get(key); - } -}); - -Ext.Container.LAYOUTS = {}; -Ext.reg('container', Ext.Container); -/** - * @class Ext.layout.ContainerLayout - *

    The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to - * render any child Components when no {@link Ext.Container#layout layout} is configured into - * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout - * classes in Ext. It simply renders all child Components into the Container, performing no sizing or - * positioning services. To utilize a layout that provides sizing and positioning of child Components, - * specify an appropriate {@link Ext.Container#layout layout}.

    - *

    This class is intended to be extended or created via the {@link Ext.Container#layout layout} - * configuration property. See {@link Ext.Container#layout} for additional details.

    - */ -Ext.layout.ContainerLayout = function(config){ - Ext.apply(this, config); -}; + * @class Ext.layout.ContainerLayout + *

    The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to + * render any child Components when no {@link Ext.Container#layout layout} is configured into + * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout + * classes in Ext. It simply renders all child Components into the Container, performing no sizing or + * positioning services. To utilize a layout that provides sizing and positioning of child Components, + * specify an appropriate {@link Ext.Container#layout layout}.

    + *

    This class is intended to be extended or created via the {@link Ext.Container#layout layout} + * configuration property. See {@link Ext.Container#layout} for additional details.

    + */ +Ext.layout.ContainerLayout = function(config){ + Ext.apply(this, config); +}; Ext.layout.ContainerLayout.prototype = { /** @@ -17911,7 +18343,7 @@ Ext.layout.ContainerLayout.prototype = { c.render(target, position); this.configureItem(c, position); }else if(c && !this.isValidParent(c, target)){ - if(typeof position == 'number'){ + if(Ext.isNumber(position)){ position = target.dom.childNodes[position]; } target.dom.insertBefore(c.getDomPositionEl().dom, position || null); @@ -17929,47 +18361,60 @@ Ext.layout.ContainerLayout.prototype = { if (this.renderHidden && c != this.activeItem) { c.hide(); } - if(c.doLayout){ - c.doLayout(false, this.forceLayout); + if(c.doLayout && this.forceLayout){ + c.doLayout(false, true); + } + }, + + onRemove: function(c){ + if(this.activeItem == c){ + delete this.activeItem; + } + if(c.rendered && this.extraCls){ + var t = c.getPositionEl ? c.getPositionEl() : c; + t.removeClass(this.extraCls); } }, // private onResize: function(){ - if(this.container.collapsed){ + var ct = this.container, + b; + + if(ct.collapsed){ return; } - var b = this.container.bufferResize; - if(b){ - if(!this.resizeTask){ - this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this); - this.resizeBuffer = typeof b == 'number' ? b : 100; + if(b = ct.bufferResize){ + // Only allow if we should buffer the layout + if(ct.shouldBufferLayout()){ + if(!this.resizeTask){ + this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this); + this.resizeBuffer = Ext.isNumber(b) ? b : 50; + } + ct.layoutPending = true; + this.resizeTask.delay(this.resizeBuffer); } - this.resizeTask.delay(this.resizeBuffer); }else{ - this.runLayout(); + ct.doLayout(); } }, // private runLayout: function(){ - this.layout(); - this.container.onLayout(); + var ct = this.container; + ct.doLayout(); + delete ct.layoutPending; }, // private setContainer : function(ct){ if(this.monitorResize && ct != this.container){ - if(this.container){ - this.container.un('resize', this.onResize, this); - this.container.un('bodyresize', this.onResize, this); + var old = this.container; + if(old){ + old.un(old.resizeEvent, this.onResize, this); } if(ct){ - ct.on({ - scope: this, - resize: this.onResize, - bodyresize: this.onResize - }); + ct.on(ct.resizeEvent, this.onResize, this); } } this.container = ct; @@ -17977,7 +18422,7 @@ Ext.layout.ContainerLayout.prototype = { // private parseMargins : function(v){ - if(typeof v == 'number'){ + if(Ext.isNumber(v)){ v = v.toString(); } var ms = v.split(' '); @@ -18003,7 +18448,7 @@ Ext.layout.ContainerLayout.prototype = { }, /** - * The {@link Template Ext.Template} used by Field rendering layout classes (such as + * The {@link Ext.Template Ext.Template} used by Field rendering layout classes (such as * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped, * labeled and styled form Field. A default Template is supplied, but this may be * overriden to create custom field structures. The template processes values returned from @@ -18173,10 +18618,11 @@ Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, { if(this.activeItem){ this.activeItem.hide(); } + var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered); this.activeItem = item; item.show(); - this.container.doLayout(); - if(this.layoutOnCardChange && item.doLayout){ + this.layout(); + if(layout){ item.doLayout(); } } @@ -18607,8 +19053,8 @@ Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, { } c.collapsed = false; if(!c.rendered){ - c.cls = c.cls ? c.cls +' x-border-panel' : 'x-border-panel'; c.render(target, i); + c.getDomPositionEl().addClass('x-border-panel'); } this[pos] = pos != 'center' && c.split ? new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) : @@ -19718,7 +20164,33 @@ Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, { * @type String * @property labelStyle */ + + /** + * @cfg {Boolean} trackLabels + * True to show/hide the field label when the field is hidden. Defaults to false. + */ + trackLabels: false, + + onRemove: function(c){ + Ext.layout.FormLayout.superclass.onRemove.call(this, c); + if(this.trackLabels && !this.isHide(c)){ + c.un('show', this.onFieldShow, this); + c.un('hide', this.onFieldHide, this); + } + // check for itemCt, since we may be removing a fieldset or something similar + var el = c.getPositionEl(), + ct = c.getItemCt && c.getItemCt(); + if(c.rendered && ct){ + el.insertAfter(ct); + Ext.destroy(ct); + Ext.destroyMembers(c, 'label', 'itemCt'); + if(c.customItemCt){ + Ext.destroyMembers(c, 'getItemCt', 'customItemCt'); + } + } + }, + // private setContainer : function(ct){ Ext.layout.FormLayout.superclass.setContainer.call(this, ct); @@ -19727,25 +20199,43 @@ Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, { } if(ct.hideLabels){ - this.labelStyle = "display:none"; - this.elementStyle = "padding-left:0;"; - this.labelAdjust = 0; + Ext.apply(this, { + labelStyle: 'display:none', + elementStyle: 'padding-left:0;', + labelAdjust: 0 + }); }else{ this.labelSeparator = ct.labelSeparator || this.labelSeparator; ct.labelWidth = ct.labelWidth || 100; - if(typeof ct.labelWidth == 'number'){ - var pad = (typeof ct.labelPad == 'number' ? ct.labelPad : 5); - this.labelAdjust = ct.labelWidth+pad; - this.labelStyle = "width:"+ct.labelWidth+"px;"; - this.elementStyle = "padding-left:"+(ct.labelWidth+pad)+'px'; + if(Ext.isNumber(ct.labelWidth)){ + var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5; + Ext.apply(this, { + labelAdjust: ct.labelWidth + pad, + labelStyle: 'width:' + ct.labelWidth + 'px;', + elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px' + }); } if(ct.labelAlign == 'top'){ - this.labelStyle = "width:auto;"; - this.labelAdjust = 0; - this.elementStyle = "padding-left:0;"; + Ext.apply(this, { + labelStyle: 'width:auto;', + labelAdjust: 0, + elementStyle: 'padding-left:0;' + }); } } }, + + isHide: function(c){ + return c.hideLabel || this.container.hideLabels; + }, + + onFieldShow: function(c){ + c.getItemCt().removeClass('x-hide-' + c.hideMode); + }, + + onFieldHide: function(c){ + c.getItemCt().addClass('x-hide-' + c.hideMode); + }, //private getLabelStyle: function(s){ @@ -19797,17 +20287,43 @@ new Ext.Template( // private renderItem : function(c, position, target){ - if(c && !c.rendered && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){ + if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){ var args = this.getTemplateArgs(c); - if(typeof position == 'number'){ + if(Ext.isNumber(position)){ position = target.dom.childNodes[position] || null; } if(position){ - this.fieldTpl.insertBefore(position, args); + c.itemCt = this.fieldTpl.insertBefore(position, args, true); }else{ - this.fieldTpl.append(target, args); + c.itemCt = this.fieldTpl.append(target, args, true); + } + if(!c.rendered){ + c.render('x-form-el-' + c.id); + }else if(!this.isValidParent(c, target)){ + Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl()); + } + if(!c.getItemCt){ + // Non form fields don't have getItemCt, apply it here + // This will get cleaned up in onRemove + Ext.apply(c, { + getItemCt: function(){ + return c.itemCt; + }, + customItemCt: true + }); } - c.render('x-form-el-'+c.id); + c.label = c.getItemCt().child('label.x-form-item-label'); + if(this.trackLabels && !this.isHide(c)){ + if(c.hidden){ + this.onFieldHide(c); + } + c.on({ + scope: this, + show: this.onFieldShow, + hide: this.onFieldHide + }); + } + this.configureItem(c); }else { Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments); } @@ -19843,22 +20359,33 @@ new Ext.Template( return { id: field.id, label: field.fieldLabel, - labelStyle: field.labelStyle||this.labelStyle||'', + labelStyle: this.getLabelStyle(field.labelStyle), elementStyle: this.elementStyle||'', - labelSeparator: noLabelSep ? '' : (typeof field.labelSeparator == 'undefined' ? this.labelSeparator : field.labelSeparator), + labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator), itemCls: (field.itemCls||this.container.itemCls||'') + (field.hideLabel ? ' x-hide-label' : ''), clearCls: field.clearCls || 'x-form-clear-left' }; }, // private - adjustWidthAnchor : function(value, comp){ - return value - (comp.isFormField || comp.fieldLabel ? (comp.hideLabel ? 0 : this.labelAdjust) : 0); + adjustWidthAnchor: function(value, c){ + if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){ + var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict); + return value - this.labelAdjust + (adjust ? -3 : 0); + } + return value; + }, + + adjustHeightAnchor : function(value, c){ + if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){ + return value - c.label.getHeight(); + } + return value; }, // private isValidParent : function(c, target){ - return true; + return target && this.container.getEl().contains(c.getDomPositionEl()); } /** @@ -19870,8 +20397,9 @@ new Ext.Template( Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;/** * @class Ext.layout.AccordionLayout * @extends Ext.layout.FitLayout - *

    This is a layout that contains multiple panels in an expandable accordion style such that only - * one panel can be open at any given time. Each panel has built-in support for expanding and collapsing. + *

    This is a layout that manages multiple Panels in an expandable accordion style such that only + * one Panel can be expanded at any given time. Each Panel has built-in support for expanding and collapsing.

    + *

    Note: Only Ext.Panels and all subclasses of Ext.Panel may be used in an accordion layout Container.

    *

    This class is intended to be extended or created via the {@link Ext.Container#layout layout} * configuration property. See {@link Ext.Container#layout} for additional details.

    *

    Example usage:

    @@ -19980,6 +20508,14 @@ Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, { c.header.addClass('x-accordion-hd'); c.on('beforeexpand', this.beforeExpand, this); }, + + onRemove: function(c){ + Ext.layout.AccordionLayout.superclass.onRemove.call(this, c); + if(c.rendered){ + c.header.removeClass('x-accordion-hd'); + } + c.un('beforeexpand', this.beforeExpand, this); + }, // private beforeExpand : function(p, anim){ @@ -20206,16 +20742,18 @@ Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, { renderItem : function(c, position, target){ if(c && !c.rendered){ c.render(this.getNextCell(c)); - if(this.extraCls){ - var t = c.getPositionEl ? c.getPositionEl() : c; - t.addClass(this.extraCls); - } + this.configureItem(c, position); + }else if(c && !this.isValidParent(c, target)){ + var container = this.getNextCell(c); + container.insertBefore(c.getDomPositionEl().dom, null); + c.container = Ext.get(container); + this.configureItem(c, position); } }, // private isValidParent : function(c, target){ - return true; + return c.getDomPositionEl().up('table', 5).dom.parentNode === (target.dom || target); } /** @@ -20338,8 +20876,20 @@ Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { defaultMargins : {left:0,top:0,right:0,bottom:0}, /** * @cfg {String} padding - * Defaults to '0'. Sets the padding to be applied to all child items managed by this - * container's layout. + *

    Sets the padding to be applied to all child items managed by this layout.

    + *

    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:

    + *
    + *

    Defaults to: "0"

    */ padding : '0', // documented in subclasses @@ -20351,6 +20901,13 @@ Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { extraCls : 'x-box-item', ctCls : 'x-box-layout-ct', innerCls : 'x-box-inner', + + constructor : function(config){ + Ext.layout.BoxLayout.superclass.constructor.call(this, config); + if(Ext.isString(this.defaultMargins)){ + this.defaultMargins = this.parseMargins(this.defaultMargins); + } + }, // private isValidParent : function(c, target){ @@ -20374,7 +20931,7 @@ Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { // private renderItem : function(c){ - if(typeof c.margins == 'string'){ + if(Ext.isString(c.margins)){ c.margins = this.parseMargins(c.margins); }else if(!c.margins){ c.margins = this.defaultMargins; @@ -20405,7 +20962,9 @@ Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, { /** * @class Ext.layout.VBoxLayout * @extends Ext.layout.BoxLayout - * A layout that arranges items vertically + *

    A layout that arranges items vertically down a Container. This layout optionally divides available vertical + * space between child items containing a numeric flex configuration.

    + * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option. */ Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, { /** @@ -20453,8 +21012,8 @@ Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, { var cs = this.getItems(ct), cm, ch, margin, size = this.getTargetSize(target), - w = size.width - target.getPadding('lr') - this.scrollOffset, - h = size.height - target.getPadding('tb'), + w = size.width - target.getPadding('lr'), + h = size.height - target.getPadding('tb') - this.scrollOffset, l = this.padding.left, t = this.padding.top, isStart = this.pack == 'start', isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1, @@ -20560,7 +21119,9 @@ Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout; /** * @class Ext.layout.HBoxLayout * @extends Ext.layout.BoxLayout - * A layout that arranges items horizontally + *

    A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal + * space between child items containing a numeric flex configuration.

    + * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option. */ Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, { /** @@ -20837,7 +21398,7 @@ Ext.reg('viewport', Ext.Viewport);/** * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.

    *

    When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether - * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout} schemes. By + * those child elements need to be sized using one of Ext's built-in {@link Ext.Container#layout layout} schemes. By * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders * child components, appending them one after the other inside the Container, and does not apply any sizing * at all.

    @@ -20856,7 +21417,7 @@ Ext.Panel = Ext.extend(Ext.Container, { /** * The Panel's header {@link Ext.Element Element}. Read-only. *

    This Element is used to house the {@link #title} and {@link #tools}

    - *

    Note: see the Note for {@link Ext.Component#el el} also.

    + *

    Note: see the Note for {@link Ext.Component#el el} also.

    * @type Ext.Element * @property header */ @@ -20869,7 +21430,7 @@ Ext.Panel = Ext.extend(Ext.Container, { *

    If this Panel is intended to be used as the host of a Layout (See {@link #layout} * then the body Element must not be loaded or changed - it is under the control * of the Panel's Layout. - *

    Note: see the Note for {@link Ext.Component#el el} also.

    + *

    Note: see the Note for {@link Ext.Component#el el} also.

    * @type Ext.Element * @property body */ @@ -20889,8 +21450,8 @@ Ext.Panel = Ext.extend(Ext.Container, { *

    A {@link Ext.DomHelper DomHelper} element specification object may be specified for any * Panel Element.

    *

    By default, the Default element in the table below will be used for the html markup to - * create a child element with the commensurate Default class name (baseCls will be - * replaced by {@link #baseCls}):

    + * create a child element with the commensurate Default class name (baseCls will be + * replaced by {@link #baseCls}):

    *
          * Panel      Default  Default             Custom      Additional       Additional
          * Element    element  class               element     class            style
    @@ -20926,51 +21487,51 @@ new Ext.Panel({
         footerStyle:    'background-color:red' // see {@link #bodyStyle}
     });
          * 
    - *

    The example above also explicitly creates a {@link #footer} with custom markup and + *

    The example above also explicitly creates a {@link #footer} with custom markup and * styling applied.

    */ /** * @cfg {Object} headerCfg *

    A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #header} Element. See {@link #bodyCfg} also.

    + * of this Panel's {@link #header} Element. See {@link #bodyCfg} also.

    */ /** * @cfg {Object} bwrapCfg *

    A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg} also.

    + * of this Panel's {@link #bwrap} Element. See {@link #bodyCfg} also.

    */ /** * @cfg {Object} tbarCfg *

    A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #tbar} Element. See {@link #bodyCfg} also.

    + * of this Panel's {@link #tbar} Element. See {@link #bodyCfg} also.

    */ /** * @cfg {Object} bbarCfg *

    A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #bbar} Element. See {@link #bodyCfg} also.

    + * of this Panel's {@link #bbar} Element. See {@link #bodyCfg} also.

    */ /** * @cfg {Object} footerCfg *

    A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure - * of this Panel's {@link #footer} Element. See {@link #bodyCfg} also.

    + * of this Panel's {@link #footer} Element. See {@link #bodyCfg} also.

    */ /** * @cfg {Boolean} closable * Panels themselves do not directly support being closed, but some Panel subclasses do (like - * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true - * to enable closing in such situations. Defaults to false. + * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify true + * to enable closing in such situations. Defaults to false. */ /** * The Panel's footer {@link Ext.Element Element}. Read-only. - *

    This Element is used to house the Panel's {@link #buttons} or {@link #fbar}.

    - *

    Note: see the Note for {@link Ext.Component#el el} also.

    + *

    This Element is used to house the Panel's {@link #buttons} or {@link #fbar}.

    + *

    Note: see the Note for {@link Ext.Component#el el} also.

    * @type Ext.Element * @property footer */ /** * @cfg {Mixed} applyTo *

    The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in - * the document that specifies some panel-specific structural markup. When applyTo is used, + * the document that specifies some panel-specific structural markup. When applyTo is used, * constituent parts of the panel can be specified by CSS class name within the main element, and the panel * will automatically create those components from that markup. Any required components not specified in the * markup will be autogenerated if necessary.

    @@ -21001,7 +21562,7 @@ new Ext.Panel({ *

    The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render. * To access the bottom toolbar after render, use {@link #getBottomToolbar}.

    - *

    Note: Although a Toolbar may contain Field components, these will not be updated by a load + *

    Note: Although a Toolbar may contain Field components, these will not be updated by a load * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and * so are not scanned to collect form items. However, the values will be submitted because form * submission parameters are collected from the DOM tree.

    @@ -21010,8 +21571,8 @@ new Ext.Panel({ *

    A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of * {@link Ext.Button Button}s/{@link Ext.Button Button} configs, describing a {@link Ext.Toolbar Toolbar} to be rendered into this Panel's footer element.

    *

    After render, the fbar property will be an {@link Ext.Toolbar Toolbar} instance.

    - *

    If {@link #buttons} are specified, they will supersede the fbar configuration property.

    - * The Panel's {@link #buttonAlign} configuration affects the layout of these items, for example: + *

    If {@link #buttons} are specified, they will supersede the fbar configuration property.

    + * The Panel's {@link #buttonAlign} configuration affects the layout of these items, for example: *
    
     var w = new Ext.Window({
         height: 250,
    @@ -21023,7 +21584,7 @@ var w = new Ext.Window({
                 text: 'bbar Right'
             }]
         }),
    -    {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use "-", and "->"
    +    {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use '-', and '->'
                                       // to control the alignment of fbar items
         fbar: [{
             text: 'fbar Left'
    @@ -21032,40 +21593,40 @@ var w = new Ext.Window({
         }]
     }).show();
          * 
    - *

    Note: Although a Toolbar may contain Field components, these will not be updated by a load + *

    Note: Although a Toolbar may contain Field components, these will not be updated by a load * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and * so are not scanned to collect form items. However, the values will be submitted because form * submission parameters are collected from the DOM tree.

    */ /** * @cfg {Boolean} header - * true to create the Panel's header element explicitly, false to skip creating - * it. If a {@link #title} is set the header will be created automatically, otherwise it will not. - * If a {@link #title} is set but header is explicitly set to false, the header + * true to create the Panel's header element explicitly, false to skip creating + * it. If a {@link #title} is set the header will be created automatically, otherwise it will not. + * If a {@link #title} is set but header is explicitly set to false, the header * will not be rendered. */ /** * @cfg {Boolean} footer - * true to create the footer element explicitly, false to skip creating it. The footer - * will be created automatically if {@link #buttons} or a {@link #fbar} have - * been configured. See {@link #bodyCfg} for an example. + * true to create the footer element explicitly, false to skip creating it. The footer + * will be created automatically if {@link #buttons} or a {@link #fbar} have + * been configured. See {@link #bodyCfg} for an example. */ /** * @cfg {String} title * The title text to be used as innerHTML (html tags are accepted) to display in the panel - * {@link #header} (defaults to ''). When a title is specified the - * {@link #header} element will automatically be created and displayed unless - * {@link #header} is explicitly set to false. If you do not want to specify a - * title at config time, but you may want one later, you must either specify a non-empty - * title (a blank space ' ' will do) or header:true so that the container + * {@link #header} (defaults to ''). When a title is specified the + * {@link #header} element will automatically be created and displayed unless + * {@link #header} is explicitly set to false. If you do not want to specify a + * title at config time, but you may want one later, you must either specify a non-empty + * title (a blank space ' ' will do) or header:true so that the container * element will get created. */ /** * @cfg {Array} buttons - * buttons will be used as {@link Ext.Container#items items} for the toolbar in - * the footer ({@link #fbar}). Typically the value of this configuration property will be + * buttons will be used as {@link Ext.Container#items items} for the toolbar in + * the footer ({@link #fbar}). Typically the value of this configuration property will be * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects. - * If an item is configured with minWidth or the Panel is configured with minButtonWidth, + * If an item is configured with minWidth or the Panel is configured with minButtonWidth, * that width will be applied to the item. */ /** @@ -21078,7 +21639,7 @@ var w = new Ext.Window({ */ /** * @cfg {Boolean} frame - * false by default to render with plain 1px square borders. true to render with + * false by default to render with plain 1px square borders. true to render with * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}). *

    The template generated for each condition is depicted below:

    
          *
    @@ -21150,33 +21711,33 @@ var w = new Ext.Window({
         /**
          * @cfg {Array} tools
          * An array of tool button configs to be added to the header tool area. When rendered, each tool is
    -     * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type>
    +     * stored as an {@link Ext.Element Element} referenced by a public property called tools.<tool-type>
          * 

    Each tool config may contain the following properties: *

      *
    • id : String
      Required. The type - * of tool to create. By default, this assigns a CSS class of the form x-tool-<tool-type> to the + * of tool to create. By default, this assigns a CSS class of the form x-tool-<tool-type> to the * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below. * The developer may implement custom tools by supplying alternate CSS rules and background images: *
        - *
        toggle (Created by default when {@link #collapsible} is true)
        - *
        close
        - *
        minimize
        - *
        maximize
        - *
        restore
        - *
        gear
        - *
        pin
        - *
        unpin
        - *
        right
        - *
        left
        - *
        up
        - *
        down
        - *
        refresh
        - *
        minus
        - *
        plus
        - *
        help
        - *
        search
        - *
        save
        - *
        print
        + *
        toggle (Created by default when {@link #collapsible} is true)
        + *
        close
        + *
        minimize
        + *
        maximize
        + *
        restore
        + *
        gear
        + *
        pin
        + *
        unpin
        + *
        right
        + *
        left
        + *
        up
        + *
        down
        + *
        refresh
        + *
        minus
        + *
        plus
        + *
        help
        + *
        search
        + *
        save
        + *
        print
        *
    • *
    • handler : Function
      Required. The function to * call when clicked. Arguments passed are:
        @@ -21214,7 +21775,7 @@ tools:[{ } }]
    - *

    For the custom id of 'help' define two relevant css classes with a link to + *

    For the custom id of 'help' define two relevant css classes with a link to * a 15x15 image:

    *
    
     .x-tool-help {background-image: url(images/help.png);}
    @@ -21249,7 +21810,7 @@ var win = new Ext.Window({
         height:300,
         closeAction:'hide'
     });
    - *

    Note that the CSS class "x-tool-pdf" should have an associated style rule which provides an + *

    Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an * appropriate background image, something like:

    
         a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
    @@ -21257,56 +21818,56 @@ var win = new Ext.Window({
          */
         /**
          * @cfg {Boolean} hideCollapseTool
    -     * true to hide the expand/collapse toggle button when {@link #collapsible} == true,
    -     * false to display it (defaults to false).
    +     * true to hide the expand/collapse toggle button when {@link #collapsible} == true,
    +     * false to display it (defaults to false).
          */
         /**
          * @cfg {Boolean} titleCollapse
    -     * true to allow expanding and collapsing the panel (when {@link #collapsible} = true)
    -     * by clicking anywhere in the header bar, false) to allow it only by clicking to tool button
    -     * (defaults to false)). If this panel is a child item of a border layout also see the
    +     * true to allow expanding and collapsing the panel (when {@link #collapsible} = true)
    +     * by clicking anywhere in the header bar, false) to allow it only by clicking to tool button
    +     * (defaults to false)). If this panel is a child item of a border layout also see the
          * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
    -     * {@link Ext.layout.BorderLayout.Region#floatable floatable} config option.
    +     * {@link Ext.layout.BorderLayout.Region#floatable floatable} config option.
          */
         /**
          * @cfg {Boolean} autoScroll
    -     * true to use overflow:'auto' on the panel's body element and show scroll bars automatically when
    -     * necessary, false to clip any overflowing content (defaults to false).
    +     * true to use overflow:'auto' on the panel's body element and show scroll bars automatically when
    +     * necessary, false to clip any overflowing content (defaults to false).
          */
         /**
          * @cfg {Mixed} floating
          * 

    This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this * configuration property are:

      - *
    • false : Default.
      Display the panel inline where it is + *
    • false : Default.
      Display the panel inline where it is * rendered.
    • - *
    • true :
      Float the panel (absolute position it with automatic + *
    • true :
      Float the panel (absolute position it with automatic * shimming and shadow).
        *
        Setting floating to true will create an Ext.Layer for this panel and display the * panel at negative offsets so that it is hidden.
        *
        Since the panel will be absolute positioned, the position must be set explicitly - * after render (e.g., myPanel.setPosition(100,100);).
        + * after render (e.g., myPanel.setPosition(100,100);).
      *
      Note: when floating a panel you should always assign a fixed width, * otherwise it will be auto width and will expand to fill to the right edge of the viewport.
      *
    - *
  • {@link Ext.Layer object} :
    The specified object will be used + *
  • {@link Ext.Layer object} :
    The specified object will be used * as the configuration object for the {@link Ext.Layer} that will be created.
  • * */ /** * @cfg {Boolean/String} shadow - * true (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the - * panel, false to display no shadow (defaults to 'sides'). Note that this option - * only applies when {@link #floating} = true. + * true (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the + * panel, false to display no shadow (defaults to 'sides'). Note that this option + * only applies when {@link #floating} = true. */ /** * @cfg {Number} shadowOffset - * The number of pixels to offset the shadow if displayed (defaults to 4). Note that this - * option only applies when {@link #floating} = true. + * The number of pixels to offset the shadow if displayed (defaults to 4). Note that this + * option only applies when {@link #floating} = true. */ /** * @cfg {Boolean} shim - * false to disable the iframe shim in browsers which need one (defaults to true). - * Note that this option only applies when {@link #floating} = true. + * false to disable the iframe shim in browsers which need one (defaults to true). + * Note that this option only applies when {@link #floating} = true. */ /** * @cfg {String/Object} html @@ -21317,36 +21878,34 @@ var win = new Ext.Window({ */ /** * @cfg {String} contentEl - *

    Specify the id of an existing HTML node to use as the panel's body content - * (defaults to '').

      - *
    • Description :
        + *

        Optional. Specify an existing HTML element, or the id of an existing HTML element to use as this Panel's + * {@link #body} content.

        + *
          + *
        • Description : *
          This config option is used to take an existing HTML element and place it in the body * of a new panel (it simply moves the specified DOM element into the body element of the Panel - * when the Panel is rendered to use as the content (it is not going to be the - * actual panel itself).
          - *
        - *
      • Notes :
          - *
          The specified HTML Element is appended to the Panel's {@link #body} Element by the - * Panel's {@link #afterRender} method after any configured {@link #html HTML} has - * been inserted, and so the document will not contain this HTML at the time the + * after the Panel is rendered to use as the content (it is not going to be the actual panel itself).
          + *
        • Notes : + *
          The specified HTML element is appended to the Panel's {@link #body} Element by the + * Panel's afterRender method after any configured {@link #html HTML} has + * been inserted, and so the document will not contain this element at the time the * {@link #render} event is fired.
          - *
          The specified HTML element used will not participate in any layout scheme that the - * Panel may use. It's just HTML. Layouts operate on child items.
          - *
          Add either the x-hidden or the x-hide-display CSS class to - * prevent a brief flicker of the content before it is rendered to the panel.
          - *
      • - *
    + *
    The specified HTML element used will not participate in any {@link Ext.Container#layout layout} + * scheme that the Panel may use. It is just HTML. Layouts operate on child {@link Ext.Container#items items}.
    + *
    Add either the x-hidden or the x-hide-display CSS class to + * prevent a brief flicker of the content before it is rendered to the panel.
    + * */ /** * @cfg {Object/Array} keys * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding} - * used to assign custom key handling to this panel (defaults to null). + * used to assign custom key handling to this panel (defaults to null). */ /** * @cfg {Boolean/Object} draggable - *

    true to enable dragging of this Panel (defaults to false).

    + *

    true to enable dragging of this Panel (defaults to false).

    *

    For custom drag/drop implementations, an Ext.Panel.DD config could also be passed - * in this config instead of true. Ext.Panel.DD is an internal, undocumented class which + * in this config instead of true. Ext.Panel.DD is an internal, undocumented class which * moves a proxy Element around in place of the Panel's element, but provides no other behaviour * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.: @@ -21388,15 +21947,9 @@ new Ext.Panel({ }).show();

    */ - /** - * @cfg {String} tabTip - * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over - * the tab of a Ext.Panel which is an item of a {@link Ext.TabPanel}. {@link Ext.QuickTips}.init() - * must be called in order for the tips to render. - */ /** * @cfg {Boolean} disabled - * Render this panel disabled (default is false). An important note when using the disabled + * Render this panel disabled (default is false). An important note when using the disabled * config on panels is that IE will often fail to initialize the disabled mask element correectly if * the panel's layout has not yet completed by the time the Panel is disabled during the render process. * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize @@ -21417,10 +21970,10 @@ new Ext.Panel({ */ /** * @cfg {Boolean} autoHeight - * true to use height:'auto', false to use fixed height (defaults to false). - * Note: Setting autoHeight:true means that the browser will manage the panel's height + * true to use height:'auto', false to use fixed height (defaults to false). + * Note: Setting autoHeight: true means that the browser will manage the panel's height * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that - * manages dimensions (fit, border, etc.) then setting autoHeight:true + * manages dimensions (fit, border, etc.) then setting autoHeight: true * can cause issues with scrolling and will not generally work as expected since the panel will take * on the height of its contents rather than the height required by the Ext layout. */ @@ -21428,64 +21981,64 @@ new Ext.Panel({ /** * @cfg {String} baseCls - * The base CSS class to apply to this panel's element (defaults to 'x-panel'). - *

    Another option available by default is to specify 'x-plain' which strips all styling + * The base CSS class to apply to this panel's element (defaults to 'x-panel'). + *

    Another option available by default is to specify 'x-plain' which strips all styling * except for required attributes for Ext layouts to function (e.g. overflow:hidden). - * See {@link #unstyled} also.

    + * See {@link #unstyled} also.

    */ baseCls : 'x-panel', /** * @cfg {String} collapsedCls * A CSS class to add to the panel's element after it has been collapsed (defaults to - * 'x-panel-collapsed'). + * 'x-panel-collapsed'). */ collapsedCls : 'x-panel-collapsed', /** * @cfg {Boolean} maskDisabled - * true to mask the panel when it is {@link #disabled}, false to not mask it (defaults - * to true). Either way, the panel will always tell its contained elements to disable themselves + * true to mask the panel when it is {@link #disabled}, false to not mask it (defaults + * to true). Either way, the panel will always tell its contained elements to disable themselves * when it is disabled, but masking the panel can provide an additional visual cue that the panel is * disabled. */ maskDisabled : true, /** * @cfg {Boolean} animCollapse - * true to animate the transition when the panel is collapsed, false to skip the - * animation (defaults to true if the {@link Ext.Fx} class is available, otherwise false). + * true to animate the transition when the panel is collapsed, false to skip the + * animation (defaults to true if the {@link Ext.Fx} class is available, otherwise false). */ animCollapse : Ext.enableFx, /** * @cfg {Boolean} headerAsText - * true to display the panel {@link #title} in the {@link #header}, - * false to hide it (defaults to true). + * true to display the panel {@link #title} in the {@link #header}, + * false to hide it (defaults to true). */ headerAsText : true, /** * @cfg {String} buttonAlign - * The alignment of any {@link #buttons} added to this panel. Valid values are 'right', - * 'left' and 'center' (defaults to 'right'). + * The alignment of any {@link #buttons} added to this panel. Valid values are 'right', + * 'left' and 'center' (defaults to 'right'). */ buttonAlign : 'right', /** * @cfg {Boolean} collapsed - * true to render the panel collapsed, false to render it expanded (defaults to - * false). + * true to render the panel collapsed, false to render it expanded (defaults to + * false). */ collapsed : false, /** * @cfg {Boolean} collapseFirst - * true to make sure the collapse/expand toggle button always renders first (to the left of) - * any other tools in the panel's title bar, false to render it last (defaults to true). + * true to make sure the collapse/expand toggle button always renders first (to the left of) + * any other tools in the panel's title bar, false to render it last (defaults to true). */ collapseFirst : true, /** * @cfg {Number} minButtonWidth - * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75) + * Minimum width in pixels of all {@link #buttons} in this panel (defaults to 75) */ minButtonWidth : 75, /** * @cfg {Boolean} unstyled - * Overrides the {@link #baseCls} setting to {@link #baseCls} = 'x-plain' which renders + * Overrides the {@link #baseCls} setting to {@link #baseCls} = 'x-plain' which renders * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden). */ /** @@ -21495,23 +22048,28 @@ new Ext.Panel({ * make sure a structural element is rendered even if not specified at config time (for example, you may want * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this * list will allocate the required placeholders in the panel when it is rendered. Valid values are
      - *
    • header
    • - *
    • tbar (top bar)
    • - *
    • body
    • - *
    • bbar (bottom bar)
    • - *
    • footer
    • + *
    • header
    • + *
    • tbar (top bar)
    • + *
    • body
    • + *
    • bbar (bottom bar)
    • + *
    • footer
    • *
    - * Defaults to 'body'. + * Defaults to 'body'. */ elements : 'body', /** * @cfg {Boolean} preventBodyReset - * Defaults to false. When set to true, an extra css class 'x-panel-normal' + * Defaults to false. When set to true, an extra css class 'x-panel-normal' * will be added to the panel's element, effectively applying css styles suggested by the W3C * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's body element (not the header, * footer, etc.). */ preventBodyReset : false, + + /** @cfg {String} resizeEvent + * The event to listen to for resizing in layouts. Defaults to 'bodyresize'. + */ + resizeEvent: 'bodyresize', // protected - these could be used to customize the behavior of the window, // but changing them would not be useful without further mofifications and @@ -21658,21 +22216,21 @@ new Ext.Panel({ this.elements += ',footer'; var btns = this.buttons; /** - * This Panel's Array of buttons as created from the {@link #buttons} + * This Panel's Array of buttons as created from the {@link #buttons} * config property. Read only. * @type Array * @property buttons */ this.buttons = []; - for(var i = 0, len = btns.length; i < len; i++) { - if(btns[i].render){ // button instance - this.buttons.push(btns[i]); - }else if(btns[i].xtype){ - this.buttons.push(Ext.create(btns[i], 'button')); + Ext.each(btns, function(btn){ + if(btn.render){ // button instance + this.buttons.push(btn); + }else if(btn.xtype){ + this.buttons.push(Ext.create(btn, 'button')); }else{ - this.addButton(btns[i]); + this.addButton(btn); } - } + }, this); } if(this.fbar){ this.elements += ',footer'; @@ -21713,7 +22271,25 @@ new Ext.Panel({ var el = this.el, d = el.dom, - bw; + bw, + ts; + + + if(this.collapsible && !this.hideCollapseTool){ + this.tools = this.tools ? this.tools.slice(0) : []; + this.tools[this.collapseFirst?'unshift':'push']({ + id: 'toggle', + handler : this.toggleCollapse, + scope: this + }); + } + + if(this.tools){ + ts = this.tools; + this.elements += (this.header !== false) ? ',header' : ''; + } + this.tools = {}; + el.addClass(this.baseCls); if(d.firstChild){ // existing markup this.header = el.down('.'+this.headerCls); @@ -21760,6 +22336,13 @@ new Ext.Panel({ if(!this.footer){ this.bwrap.dom.lastChild.className += ' x-panel-nofooter'; } + /* + * Store a reference to this element so: + * a) We aren't looking it up all the time + * b) The last element is reported incorrectly when using a loadmask + */ + this.ft = Ext.get(this.bwrap.dom.lastChild); + this.mc = Ext.get(this.bwrap.dom.firstChild.firstChild.firstChild); }else{ this.createElement('header', d); this.createElement('bwrap', d); @@ -21779,7 +22362,7 @@ new Ext.Panel({ } } - if(this.padding !== undefined) { + if(Ext.isDefined(this.padding)){ this.body.setStyle('padding', this.body.addUnits(this.padding)); } @@ -21824,26 +22407,12 @@ new Ext.Panel({ this.makeFloating(this.floating); } - if(this.collapsible){ - this.tools = this.tools ? this.tools.slice(0) : []; - if(!this.hideCollapseTool){ - this.tools[this.collapseFirst?'unshift':'push']({ - id: 'toggle', - handler : this.toggleCollapse, - scope: this - }); - } - if(this.titleCollapse && this.header){ - this.mon(this.header, 'click', this.toggleCollapse, this); - this.header.setStyle('cursor', 'pointer'); - } + if(this.collapsible && this.titleCollapse && this.header){ + this.mon(this.header, 'click', this.toggleCollapse, this); + this.header.setStyle('cursor', 'pointer'); } - if(this.tools){ - var ts = this.tools; - this.tools = {}; + if(ts){ this.addTool.apply(this, ts); - }else{ - this.tools = {}; } if(this.buttons && this.buttons.length > 0){ @@ -21890,13 +22459,6 @@ new Ext.Panel({ this.bottomToolbar.render(this.bbar); this.toolbars.push(this.bottomToolbar); } - Ext.each(this.toolbars, function(tb){ - tb.on({ - scope: this, - afterlayout: this.syncHeight, - remove: this.syncHeight - }); - }, this); }, /** @@ -21912,12 +22474,12 @@ new Ext.Panel({ this.header.addClass('x-panel-icon'); this.header.replaceClass(old, this.iconCls); }else{ - var hd = this.header.dom; - var img = hd.firstChild && String(hd.firstChild.tagName).toLowerCase() == 'img' ? hd.firstChild : null; + var hd = this.header, + img = hd.child('img.x-panel-inline-icon'); if(img){ Ext.fly(img).replaceClass(old, this.iconCls); }else{ - Ext.DomHelper.insertBefore(hd.firstChild, { + Ext.DomHelper.insertBefore(hd.dom.firstChild, { tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls }); } @@ -21931,7 +22493,7 @@ new Ext.Panel({ this.floating = true; this.el = new Ext.Layer( Ext.isObject(cfg) ? cfg : { - shadow: this.shadow !== undefined ? this.shadow : 'sides', + shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides', shadowOffset: this.shadowOffset, constrain:false, shim: this.shim === false ? false : undefined @@ -21940,7 +22502,7 @@ new Ext.Panel({ }, /** - * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}) section of the panel. + * Returns the {@link Ext.Toolbar toolbar} from the top ({@link #tbar}) section of the panel. * @return {Ext.Toolbar} The toolbar */ getTopToolbar : function(){ @@ -21948,7 +22510,7 @@ new Ext.Panel({ }, /** - * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. + * Returns the {@link Ext.Toolbar toolbar} from the bottom ({@link #bbar}) section of the panel. * @return {Ext.Toolbar} The toolbar */ getBottomToolbar : function(){ @@ -21971,7 +22533,7 @@ new Ext.Panel({ minWidth: this.minButtonWidth, hideParent:true }; - if(typeof config == "string"){ + if(Ext.isString(config)){ bc.text = config; }else{ Ext.apply(bc, config); @@ -21986,7 +22548,17 @@ new Ext.Panel({ // private addTool : function(){ - if(!this[this.toolTarget]) { // no where to render tools! + if(!this.rendered){ + if(!this.tools){ + this.tools = []; + } + Ext.each(arguments, function(arg){ + this.tools.push(arg) + }, this); + return; + } + // nowhere to render tools! + if(!this[this.toolTarget]){ return; } if(!this.toolTemplate){ @@ -22026,35 +22598,31 @@ new Ext.Panel({ } }, - onLayout : function(){ - if(this.toolbars.length > 0){ - this.duringLayout = true; + onLayout : function(shallow, force){ + if(this.hasLayout && this.toolbars.length > 0){ Ext.each(this.toolbars, function(tb){ - tb.doLayout(); + tb.doLayout(undefined, force); }); - delete this.duringLayout; this.syncHeight(); } }, syncHeight : function(){ - if(!(this.autoHeight || this.duringLayout)){ - var last = this.lastSize; - if(last && !Ext.isEmpty(last.height)){ - var old = last.height, h = this.el.getHeight(); - if(old != 'auto' && old != h){ - var bd = this.body, bdh = bd.getHeight(); - h = Math.max(bdh + old - h, 0); - if(bdh > 0 && bdh != h){ - bd.setHeight(h); - if(Ext.isIE && h <= 0){ - return; - } - var sz = bd.getSize(); - this.fireEvent('bodyresize', sz.width, sz.height); - } - } - } + var h = this.toolbarHeight, + bd = this.body, + lsh = this.lastSize.height; + + if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){ + return; + } + + + if(h != this.getToolbarHeight()){ + h = Math.max(0, this.adjustBodyHeight(lsh - this.getFrameHeight())); + bd.setHeight(h); + sz = bd.getSize(); + this.toolbarHeight = this.getToolbarHeight(); + this.onBodyResize(sz.width, sz.height); } }, @@ -22141,6 +22709,19 @@ new Ext.Panel({ if(this.draggable){ this.initDraggable(); } + if(this.toolbars.length > 0){ + Ext.each(this.toolbars, function(tb){ + tb.doLayout(); + tb.on({ + scope: this, + afterlayout: this.syncHeight, + remove: this.syncHeight + }); + }, this); + if(!this.ownerCt){ + this.syncHeight(); + } + } }, // private @@ -22153,21 +22734,25 @@ new Ext.Panel({ * @type Ext.dd.DragSource. * @property dd */ - this.dd = new Ext.Panel.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable); + this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable); }, // private - beforeEffect : function(){ + beforeEffect : function(anim){ if(this.floating){ this.el.beforeAction(); } - this.el.addClass('x-panel-animated'); + if(anim !== false){ + this.el.addClass('x-panel-animated'); + } }, // private - afterEffect : function(){ + afterEffect : function(anim){ this.syncShadow(); - this.el.removeClass('x-panel-animated'); + if(anim !== false){ + this.el.removeClass('x-panel-animated'); + } }, // private - wraps up an animation param with internal callbacks @@ -22202,7 +22787,7 @@ new Ext.Panel({ return; } var doAnim = animate === true || (animate !== false && this.animCollapse); - this.beforeEffect(); + this.beforeEffect(doAnim); this.onCollapse(doAnim, animate); return this; }, @@ -22215,15 +22800,15 @@ new Ext.Panel({ this.collapseDefaults)); }else{ this[this.collapseEl].hide(); - this.afterCollapse(); + this.afterCollapse(false); } }, // private - afterCollapse : function(){ + afterCollapse : function(anim){ this.collapsed = true; this.el.addClass(this.collapsedCls); - this.afterEffect(); + this.afterEffect(anim); this.fireEvent('collapse', this); }, @@ -22240,7 +22825,7 @@ new Ext.Panel({ } var doAnim = animate === true || (animate !== false && this.animCollapse); this.el.removeClass(this.collapsedCls); - this.beforeEffect(); + this.beforeEffect(doAnim); this.onExpand(doAnim, animate); return this; }, @@ -22253,15 +22838,15 @@ new Ext.Panel({ this.expandDefaults)); }else{ this[this.collapseEl].show(); - this.afterExpand(); + this.afterExpand(false); } }, // private - afterExpand : function(){ + afterExpand : function(anim){ this.collapsed = false; - this.afterEffect(); - if(this.deferLayout !== undefined){ + this.afterEffect(anim); + if(Ext.isDefined(this.deferLayout)){ this.doLayout(true); } this.fireEvent('expand', this); @@ -22296,9 +22881,9 @@ new Ext.Panel({ // private onResize : function(w, h){ - if(w !== undefined || h !== undefined){ + if(Ext.isDefined(w) || Ext.isDefined(h)){ if(!this.collapsed){ - if(typeof w == 'number'){ + if(Ext.isNumber(w)){ w = this.adjustBodyWidth(w - this.getFrameWidth()); if(this.tbar){ this.tbar.setWidth(w); @@ -22341,7 +22926,7 @@ new Ext.Panel({ this.body.setWidth(w); } - if(typeof h == 'number'){ + if(Ext.isNumber(h)){ h = Math.max(0, this.adjustBodyHeight(h - this.getFrameHeight())); this.body.setHeight(h); }else if(h == 'auto'){ @@ -22362,10 +22947,26 @@ new Ext.Panel({ }, this, {single:true}); } } - this.fireEvent('bodyresize', this, w, h); + this.onBodyResize(w, h); } this.syncShadow(); }, + + // private + onBodyResize: function(w, h){ + this.fireEvent('bodyresize', this, w, h); + }, + + // private + getToolbarHeight: function(){ + var h = 0; + if(this.rendered){ + Ext.each(this.toolbars, function(tb){ + h += tb.getHeight(); + }, this); + } + return h; + }, // private adjustBodyHeight : function(h){ @@ -22388,13 +22989,12 @@ new Ext.Panel({ * @return {Number} The frame width */ getFrameWidth : function(){ - var w = this.el.getFrameWidth('lr')+this.bwrap.getFrameWidth('lr'); + var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr'); if(this.frame){ var l = this.bwrap.dom.firstChild; w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r')); - var mc = this.bwrap.dom.firstChild.firstChild.firstChild; - w += Ext.fly(mc).getFrameWidth('lr'); + w += this.mc.getFrameWidth('lr'); } return w; }, @@ -22405,16 +23005,12 @@ new Ext.Panel({ * @return {Number} The frame height */ getFrameHeight : function(){ - var h = this.el.getFrameWidth('tb')+this.bwrap.getFrameWidth('tb'); + var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb'); h += (this.tbar ? this.tbar.getHeight() : 0) + (this.bbar ? this.bbar.getHeight() : 0); if(this.frame){ - var hd = this.el.dom.firstChild; - var ft = this.bwrap.dom.lastChild; - h += (hd.offsetHeight + ft.offsetHeight); - var mc = this.bwrap.dom.firstChild.firstChild.firstChild; - h += Ext.fly(mc).getFrameWidth('tb'); + h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb'); }else{ h += (this.header ? this.header.getHeight() : 0) + (this.footer ? this.footer.getHeight() : 0); @@ -22455,8 +23051,8 @@ new Ext.Panel({ /** *

    Sets the title text for the panel and optionally the {@link #iconCls icon class}.

    *

    In order to be able to set the title, a header element must have been created - * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}, - * or configuring it with {@link #header}: true.

    + * for the Panel. This is triggered either by configuring the Panel with a non-blank {@link #title}, + * or configuring it with {@link #header}: true.

    * @param {String} title The title text to set * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel */ @@ -22485,13 +23081,13 @@ new Ext.Panel({ * @param {Object/String/Function} config A config object containing any of the following options:
    
     panel.load({
    -    url: "your-url.php",
    -    params: {param1: "foo", param2: "bar"}, // or a URL encoded string
    +    url: 'your-url.php',
    +    params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
         callback: yourFunction,
         scope: yourObject, // optional scope for the callback
         discardUrl: false,
         nocache: false,
    -    text: "Loading...",
    +    text: 'Loading...',
         timeout: 30,
         scripts: false
     });
    @@ -22516,6 +23112,8 @@ panel.load({
                 }
             }
             Ext.Element.uncache(
    +            this.ft,
    +            this.mc,
                 this.header,
                 this.tbar,
                 this.bbar,
    @@ -22533,7 +23131,11 @@ panel.load({
                     Ext.destroy(this.buttons[b]);
                 }
             }
    -        Ext.destroy(this.toolbars);
    +        if(this.rendered){
    +            Ext.destroy(this.toolbars);
    +        }else{
    +            Ext.destroy(this.topToolbar, this.bottomToolbar);
    +        }
             Ext.Panel.superclass.beforeDestroy.call(this);
         },
     
    @@ -22626,8 +23228,8 @@ Ext.extend(Ext.Editor, Ext.Component, {
          */
         /**
          * @cfg {Boolean/String} autoSize
    -     * True for the editor to automatically adopt the size of the element being edited, "width" to adopt the width only,
    -     * or "height" to adopt the height only (defaults to false)
    +     * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only,
    +     * or "height" to adopt the height only, "none" to always use the field dimensions. (defaults to false)
          */
         /**
          * @cfg {Boolean} revertInvalid
    @@ -22668,13 +23270,13 @@ Ext.extend(Ext.Editor, Ext.Component, {
          */
         swallowKeys : true,
         /**
    -     * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false)
    +     * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to true.
          */
    -    completeOnEnter : false,
    +    completeOnEnter : true,
         /**
    -     * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false)
    +     * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to true.
          */
    -    cancelOnEsc : false,
    +    cancelOnEsc : true,
         /**
          * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
          */
    @@ -22756,35 +23358,41 @@ Ext.extend(Ext.Editor, Ext.Component, {
                 this.field.msgTarget = 'qtip';
             }
             this.field.inEditor = true;
    -        this.field.render(this.el);
    -        if(Ext.isGecko){
    -            this.field.el.dom.setAttribute('autocomplete', 'off');
    +        this.mon(this.field, {
    +            scope: this,
    +            blur: this.onBlur,
    +            specialkey: this.onSpecialKey
    +        });
    +        if(this.field.grow){
    +            this.mon(this.field, "autosize", this.el.sync,  this.el, {delay:1});
             }
    -        this.mon(this.field, "specialkey", this.onSpecialKey, this);
    +        this.field.render(this.el).show();
    +        this.field.getEl().dom.name = '';
             if(this.swallowKeys){
    -            this.field.el.swallowEvent(['keydown','keypress']);
    -        }
    -        this.field.show();
    -        this.mon(this.field, "blur", this.onBlur, this);
    -        if(this.field.grow){
    -        	this.mon(this.field, "autosize", this.el.sync,  this.el, {delay:1});
    +            this.field.el.swallowEvent([
    +                'keypress', // *** Opera
    +                'keydown'   // *** all other browsers
    +            ]);
             }
         },
     
         // private
         onSpecialKey : function(field, e){
    -        var key = e.getKey();
    -        if(this.completeOnEnter && key == e.ENTER){
    +        var key = e.getKey(),
    +            complete = this.completeOnEnter && key == e.ENTER,
    +            cancel = this.cancelOnEsc && key == e.ESC;
    +        if(complete || cancel){
                 e.stopEvent();
    -            this.completeEdit();
    -        }else if(this.cancelOnEsc && key == e.ESC){
    -            this.cancelEdit();
    -        }else{
    -            this.fireEvent('specialkey', field, e);
    -        }
    -        if(this.field.triggerBlur && (key == e.ENTER || key == e.ESC || key == e.TAB)){
    -            this.field.triggerBlur();
    +            if(complete){
    +                this.completeEdit();
    +            }else{
    +                this.cancelEdit();
    +            }
    +            if(field.triggerBlur){
    +                field.triggerBlur(); 
    +            }
             }
    +        this.fireEvent('specialkey', field, e);
         },
     
         /**
    @@ -22802,30 +23410,34 @@ Ext.extend(Ext.Editor, Ext.Component, {
             if(!this.rendered){
                 this.render(this.parentEl || document.body);
             }
    -        if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){
    -            return;
    +        if(this.fireEvent("beforestartedit", this, this.boundEl, v) !== false){
    +            this.startValue = v;
    +            this.field.setValue(v);
    +            this.doAutoSize();
    +            this.el.alignTo(this.boundEl, this.alignment);
    +            this.editing = true;
    +            this.show();
             }
    -        this.startValue = v;
    -        this.field.setValue(v);
    -        this.doAutoSize();
    -        this.el.alignTo(this.boundEl, this.alignment);
    -        this.editing = true;
    -        this.show();
         },
     
         // private
         doAutoSize : function(){
             if(this.autoSize){
    -            var sz = this.boundEl.getSize();
    +            var sz = this.boundEl.getSize(),
    +                fs = this.field.getSize();
    +
                 switch(this.autoSize){
                     case "width":
    -                    this.setSize(sz.width,  "");
    -                break;
    +                    this.setSize(sz.width, fs.height);
    +                    break;
                     case "height":
    -                    this.setSize("",  sz.height);
    -                break;
    +                    this.setSize(fs.width, sz.height);
    +                    break;
    +                case "none":
    +                    this.setSize(fs.width, fs.height);
    +                    break;
                     default:
    -                    this.setSize(sz.width,  sz.height);
    +                    this.setSize(sz.width, sz.height);
                 }
             }
         },
    @@ -22889,22 +23501,10 @@ Ext.extend(Ext.Editor, Ext.Component, {
             if(this.hideEl !== false){
                 this.boundEl.hide();
             }
    -        this.field.show();
    -        if(Ext.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time
    -            this.fixIEFocus = true;
    -            this.deferredFocus.defer(50, this);
    -        }else{
    -            this.field.focus();
    -        }
    +        this.field.show().focus(false, true);
             this.fireEvent("startedit", this.boundEl, this.startValue);
         },
     
    -    deferredFocus : function(){
    -        if(this.editing){
    -            this.field.focus();
    -        }
    -    },
    -
         /**
          * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
          * reverted to the original starting value.
    @@ -23003,7 +23603,7 @@ Ext.ColorPalette = function(config){
         );
     
         if(this.handler){
    -        this.on("select", this.handler, this.scope, true);
    +        this.on('select', this.handler, this.scope, true);
         }
     };
     Ext.extend(Ext.ColorPalette, Ext.Component, {
    @@ -23012,18 +23612,18 @@ Ext.extend(Ext.ColorPalette, Ext.Component, {
     	 */
         /**
          * @cfg {String} itemCls
    -     * The CSS class to apply to the containing element (defaults to "x-color-palette")
    +     * The CSS class to apply to the containing element (defaults to 'x-color-palette')
          */
    -    itemCls : "x-color-palette",
    +    itemCls : 'x-color-palette',
         /**
          * @cfg {String} value
          * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol).  Note that
          * the hex codes are case-sensitive.
          */
         value : null,
    -    clickEvent:'click',
    +    clickEvent :'click',
         // private
    -    ctype: "Ext.ColorPalette",
    +    ctype : 'Ext.ColorPalette',
     
         /**
          * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
    @@ -23038,30 +23638,44 @@ Ext.extend(Ext.ColorPalette, Ext.Component, {
          * 

    You can override individual colors if needed:

    *
    
     var cp = new Ext.ColorPalette();
    -cp.colors[0] = "FF0000";  // change the first box to red
    +cp.colors[0] = 'FF0000';  // change the first box to red
     
    Or you can provide a custom array of your own for complete control:
    
     var cp = new Ext.ColorPalette();
    -cp.colors = ["000000", "993300", "333300"];
    +cp.colors = ['000000', '993300', '333300'];
     
    * @type Array */ colors : [ - "000000", "993300", "333300", "003300", "003366", "000080", "333399", "333333", - "800000", "FF6600", "808000", "008000", "008080", "0000FF", "666699", "808080", - "FF0000", "FF9900", "99CC00", "339966", "33CCCC", "3366FF", "800080", "969696", - "FF00FF", "FFCC00", "FFFF00", "00FF00", "00FFFF", "00CCFF", "993366", "C0C0C0", - "FF99CC", "FFCC99", "FFFF99", "CCFFCC", "CCFFFF", "99CCFF", "CC99FF", "FFFFFF" + '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333', + '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080', + 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696', + 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0', + 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF' ], + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this palette. + * The handler is passed the following parameters:
      + *
    • palette : ColorPalette
      The {@link #palette Ext.ColorPalette}.
    • + *
    • color : String
      The 6-digit color hex code (without the # symbol).
    • + *
    + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this ColorPalette instance. + */ + // private onRender : function(container, position){ var t = this.tpl || new Ext.XTemplate( ' ' ); - var el = document.createElement("div"); + var el = document.createElement('div'); el.id = this.getId(); el.className = this.itemCls; t.overwrite(el, this.colors); @@ -23097,15 +23711,15 @@ cp.colors = ["000000", "993300", "333300"]; * @param {String} color A valid 6-digit color hex code (# will be stripped if included) */ select : function(color){ - color = color.replace("#", ""); + color = color.replace('#', ''); if(color != this.value || this.allowReselect){ var el = this.el; if(this.value){ - el.child("a.color-"+this.value).removeClass("x-color-palette-sel"); + el.child('a.color-'+this.value).removeClass('x-color-palette-sel'); } - el.child("a.color-"+color).addClass("x-color-palette-sel"); + el.child('a.color-'+color).addClass('x-color-palette-sel'); this.value = color; - this.fireEvent("select", this, color); + this.fireEvent('select', this, color); } } @@ -23113,7 +23727,8 @@ cp.colors = ["000000", "993300", "333300"]; * @cfg {String} autoEl @hide */ }); -Ext.reg('colorpalette', Ext.ColorPalette);/** +Ext.reg('colorpalette', Ext.ColorPalette); +/** * @class Ext.DatePicker * @extends Ext.Component * Simple date picker class. @@ -23138,6 +23753,19 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { * The text to display on the cancel button (defaults to 'Cancel') */ cancelText : 'Cancel', + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this picker. + * The handler is passed the following parameters:
      + *
    • picker : DatePicker
      The Ext.DatePicker.
    • + *
    • date : Date
      The selected date.
    • + *
    + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this DatePicker instance. + */ /** * @cfg {String} todayTip * The tooltip to display for the button that selects the current date (defaults to '{current date} (Spacebar)') @@ -23244,7 +23872,7 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { Ext.DatePicker.superclass.initComponent.call(this); this.value = this.value ? - this.value.clearTime() : new Date().clearTime(); + this.value.clearTime(true) : new Date().clearTime(); this.addEvents( /** @@ -23329,11 +23957,8 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { * @param {Date} value The date to set */ setValue : function(value){ - var old = this.value; this.value = value.clearTime(true); - if(this.el){ - this.update(this.value); - } + this.update(this.value); }, /** @@ -23346,9 +23971,7 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { // private focus : function(){ - if(this.el){ - this.update(this.activeDate); - } + this.update(this.activeDate); }, // private @@ -23363,7 +23986,7 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { }, // private - onDisable: function(){ + onDisable : function(){ Ext.DatePicker.superclass.onDisable.call(this); this.doDisabled(true); if(Ext.isIE && !Ext.isIE8){ @@ -23378,7 +24001,7 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { }, // private - doDisabled: function(disabled){ + doDisabled : function(disabled){ this.keyNav.setDisabled(disabled); this.prevRepeater.setDisabled(disabled); this.nextRepeater.setDisabled(disabled); @@ -23717,142 +24340,142 @@ Ext.DatePicker = Ext.extend(Ext.BoxComponent, { // private update : function(date, forceRefresh){ - var vd = this.activeDate, vis = this.isVisible(); - this.activeDate = date; - if(!forceRefresh && vd && this.el){ - var t = date.getTime(); - if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ - this.cells.removeClass('x-date-selected'); - this.cells.each(function(c){ - if(c.dom.firstChild.dateValue == t){ - c.addClass('x-date-selected'); - if(vis){ - Ext.fly(c.dom.firstChild).focus(50); - } - return false; - } - }); - return; - } - } - var days = date.getDaysInMonth(); - var firstOfMonth = date.getFirstDateOfMonth(); - var startingPos = firstOfMonth.getDay()-this.startDay; - - if(startingPos <= this.startDay){ - startingPos += 7; - } - - var pm = date.add('mo', -1); - var prevStart = pm.getDaysInMonth()-startingPos; - - var cells = this.cells.elements; - var textEls = this.textNodes; - days += startingPos; - - // convert everything to numbers so it's fast - var day = 86400000; - var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(); - var today = new Date().clearTime().getTime(); - var sel = date.clearTime().getTime(); - var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY; - var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY; - var ddMatch = this.disabledDatesRE; - var ddText = this.disabledDatesText; - var ddays = this.disabledDays ? this.disabledDays.join('') : false; - var ddaysText = this.disabledDaysText; - var format = this.format; - - if(this.showToday){ - var td = new Date().clearTime(); - var disable = (td < min || td > max || - (ddMatch && format && ddMatch.test(td.dateFormat(format))) || - (ddays && ddays.indexOf(td.getDay()) != -1)); - - if(!this.disabled){ - this.todayBtn.setDisabled(disable); - this.todayKeyListener[disable ? 'disable' : 'enable'](); - } - } - - var setCellClass = function(cal, cell){ - cell.title = ''; - var t = d.getTime(); - cell.firstChild.dateValue = t; - if(t == today){ - cell.className += ' x-date-today'; - cell.title = cal.todayText; - } - if(t == sel){ - cell.className += ' x-date-selected'; - if(vis){ - Ext.fly(cell.firstChild).focus(50); - } - } - // disabling - if(t < min) { - cell.className = ' x-date-disabled'; - cell.title = cal.minText; - return; - } - if(t > max) { - cell.className = ' x-date-disabled'; - cell.title = cal.maxText; - return; - } - if(ddays){ - if(ddays.indexOf(d.getDay()) != -1){ - cell.title = ddaysText; - cell.className = ' x-date-disabled'; - } - } - if(ddMatch && format){ - var fvalue = d.dateFormat(format); - if(ddMatch.test(fvalue)){ - cell.title = ddText.replace('%0', fvalue); - cell.className = ' x-date-disabled'; - } - } - }; - - var i = 0; - for(; i < startingPos; i++) { - textEls[i].innerHTML = (++prevStart); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-prevday'; - setCellClass(this, cells[i]); - } - for(; i < days; i++){ - var intDay = i - startingPos + 1; - textEls[i].innerHTML = (intDay); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-active'; - setCellClass(this, cells[i]); - } - var extraDays = 0; - for(; i < 42; i++) { - textEls[i].innerHTML = (++extraDays); - d.setDate(d.getDate()+1); - cells[i].className = 'x-date-nextday'; - setCellClass(this, cells[i]); - } - - this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); - - if(!this.internalRender){ - var main = this.el.dom.firstChild; - var w = main.offsetWidth; - this.el.setWidth(w + this.el.getBorderWidth('lr')); - Ext.fly(main).setWidth(w); - this.internalRender = true; - // opera does not respect the auto grow header center column - // then, after it gets a width opera refuses to recalculate - // without a second pass - if(Ext.isOpera && !this.secondPass){ - main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; - this.secondPass = true; - this.update.defer(10, this, [date]); - } + if(this.rendered){ + var vd = this.activeDate, vis = this.isVisible(); + this.activeDate = date; + if(!forceRefresh && vd && this.el){ + var t = date.getTime(); + if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){ + this.cells.removeClass('x-date-selected'); + this.cells.each(function(c){ + if(c.dom.firstChild.dateValue == t){ + c.addClass('x-date-selected'); + if(vis){ + Ext.fly(c.dom.firstChild).focus(50); + } + return false; + } + }); + return; + } + } + var days = date.getDaysInMonth(), + firstOfMonth = date.getFirstDateOfMonth(), + startingPos = firstOfMonth.getDay()-this.startDay; + + if(startingPos < 0){ + startingPos += 7; + } + days += startingPos; + + var pm = date.add('mo', -1), + prevStart = pm.getDaysInMonth()-startingPos, + cells = this.cells.elements, + textEls = this.textNodes, + // convert everything to numbers so it's fast + day = 86400000, + d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(), + today = new Date().clearTime().getTime(), + sel = date.clearTime(true).getTime(), + min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY, + max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY, + ddMatch = this.disabledDatesRE, + ddText = this.disabledDatesText, + ddays = this.disabledDays ? this.disabledDays.join('') : false, + ddaysText = this.disabledDaysText, + format = this.format; + + if(this.showToday){ + var td = new Date().clearTime(), + disable = (td < min || td > max || + (ddMatch && format && ddMatch.test(td.dateFormat(format))) || + (ddays && ddays.indexOf(td.getDay()) != -1)); + + if(!this.disabled){ + this.todayBtn.setDisabled(disable); + this.todayKeyListener[disable ? 'disable' : 'enable'](); + } + } + + var setCellClass = function(cal, cell){ + cell.title = ''; + var t = d.getTime(); + cell.firstChild.dateValue = t; + if(t == today){ + cell.className += ' x-date-today'; + cell.title = cal.todayText; + } + if(t == sel){ + cell.className += ' x-date-selected'; + if(vis){ + Ext.fly(cell.firstChild).focus(50); + } + } + // disabling + if(t < min) { + cell.className = ' x-date-disabled'; + cell.title = cal.minText; + return; + } + if(t > max) { + cell.className = ' x-date-disabled'; + cell.title = cal.maxText; + return; + } + if(ddays){ + if(ddays.indexOf(d.getDay()) != -1){ + cell.title = ddaysText; + cell.className = ' x-date-disabled'; + } + } + if(ddMatch && format){ + var fvalue = d.dateFormat(format); + if(ddMatch.test(fvalue)){ + cell.title = ddText.replace('%0', fvalue); + cell.className = ' x-date-disabled'; + } + } + }; + + var i = 0; + for(; i < startingPos; i++) { + textEls[i].innerHTML = (++prevStart); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-prevday'; + setCellClass(this, cells[i]); + } + for(; i < days; i++){ + var intDay = i - startingPos + 1; + textEls[i].innerHTML = (intDay); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-active'; + setCellClass(this, cells[i]); + } + var extraDays = 0; + for(; i < 42; i++) { + textEls[i].innerHTML = (++extraDays); + d.setDate(d.getDate()+1); + cells[i].className = 'x-date-nextday'; + setCellClass(this, cells[i]); + } + + this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear()); + + if(!this.internalRender){ + var main = this.el.dom.firstChild, + w = main.offsetWidth; + this.el.setWidth(w + this.el.getBorderWidth('lr')); + Ext.fly(main).setWidth(w); + this.internalRender = true; + // opera does not respect the auto grow header center column + // then, after it gets a width opera refuses to recalculate + // without a second pass + if(Ext.isOpera && !this.secondPass){ + main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px'; + this.secondPass = true; + this.update.defer(10, this, [date]); + } + } } }, @@ -24606,6 +25229,7 @@ myAction.on('complete', function(){ this.waitTimer = Ext.TaskMgr.start({ run: function(i){ var inc = o.increment || 10; + i -= 1; this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate); }, interval: o.interval || 1000, @@ -25222,7 +25846,7 @@ Ext.dd.DragDrop.prototype = { c = { x: s.left, y: s.top, width: Ext.lib.Dom.getViewWidth(), height: Ext.lib.Dom.getViewHeight()}; }else{ var xy = ce.getXY(); - c = {x : xy[0]+s.left, y: xy[1]+s.top, width: cd.clientWidth, height: cd.clientHeight}; + c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight}; } @@ -29255,7 +29879,8 @@ Ext.Element.addMethods({ var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config); return Ext.apply(dd, overrides); } -});/** +}); +/** * @class Ext.data.Api * @extends Object * Ext.data.Api is a singleton designed to manage the data API including methods @@ -29435,10 +30060,86 @@ new Ext.data.HttpProxy({ for (var verb in this.restActions) { proxy.api[this.actions[verb]].method = this.restActions[verb]; } + // TODO: perhaps move this interceptor elsewhere? like into DataProxy, perhaps? Placed here + // to satisfy initial 3.0 final release of REST features. + proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) { + var reader = o.reader; + var res = new Ext.data.Response({ + action: action, + raw: response + }); + + switch (response.status) { + case 200: // standard 200 response, send control back to HttpProxy#onWrite + return true; + break; + case 201: // entity created but no response returned + //res[reader.meta.successProperty] = true; + res.success = true; + break; + case 204: // no-content. Create a fake response. + //res[reader.meta.successProperty] = true; + //res[reader.meta.root] = null; + res.success = true; + res.data = null; + break; + default: + return true; + break; + } + /* + if (res[reader.meta.successProperty] === true) { + this.fireEvent("write", this, action, res[reader.meta.root], res, rs, o.request.arg); + } else { + this.fireEvent('exception', this, 'remote', action, o, res, rs); + } + */ + if (res.success === true) { + this.fireEvent("write", this, action, res.data, res, rs, o.request.arg); + } else { + this.fireEvent('exception', this, 'remote', action, o, res, rs); + } + o.request.callback.call(o.request.scope, res.data, res, res.success); + + return false; // <-- false to prevent intercepted function from running. + }, proxy); } }; })(); +/** + * Ext.data.Response + * Experimental. Do not use directly. + */ +Ext.data.Response = function(params, response) { + Ext.apply(this, params, { + raw: response + }); +}; +Ext.data.Response.prototype = { + message : null, + success : false, + status : null, + root : null, + raw : null, + + getMessage : function() { + return this.message; + }, + getSuccess : function() { + return this.success; + }, + getStatus : function() { + return this.status + }, + getRoot : function() { + return this.root; + }, + getRawResponse : function() { + return this.raw; + } +}; + /** * @class Ext.data.Api.Error * @extends Ext.Error @@ -29459,6 +30160,8 @@ Ext.apply(Ext.data.Api.Error.prototype, { 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"' } }); + + /** * @class Ext.data.SortTypes @@ -29560,8 +30263,9 @@ Ext.data.SortTypes = { * {@link #data} and {@link #id} properties.

    *

    Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.

    * @constructor - * This constructor should not be used to create Record objects. Instead, use {@link #create} to - * generate a subclass of Ext.data.Record configured with information about its constituent fields. + *

    This constructor should not be used to create Record objects. Instead, use {@link #create} to + * generate a subclass of Ext.data.Record configured with information about its constituent fields.

    + *

    The generated constructor has the same signature as this constructor.

    * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's * fields. If not specified the {@link Ext.data.Field#defaultValue defaultValue} * for each field will be assigned. @@ -29612,7 +30316,7 @@ myStore.{@link Ext.data.Store#add add}(myNewRecord);
    * @method create * @return {function} A constructor which is used to create new Records according - * to the definition. The constructor has the same signature as {@link #Ext.data.Record}. + * to the definition. The constructor has the same signature as {@link #Record}. * @static */ Ext.data.Record.create = function(o){ @@ -30064,6 +30768,9 @@ var recId = 100; // provide unique id for the record var r = new myStore.recordType(defaultData, ++recId); // create new record myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add}) * + *

    Writing Data

    + *

    And new in Ext version 3, use the new {@link Ext.data.DataWriter DataWriter} to create an automated, Writable Store + * along with RESTful features. * @constructor * Creates a new Store. * @param {Object} config A config object containing the objects needed for the Store to access data, @@ -30092,7 +30799,7 @@ Ext.data.Store = function(config){ } Ext.apply(this, config); - + this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames); if(this.url && !this.proxy){ @@ -30110,7 +30817,8 @@ Ext.data.Store = function(config){ this.recordType = this.reader.recordType; } if(this.reader.onMetaChange){ - this.reader.onMetaChange = this.onMetaChange.createDelegate(this); + //this.reader.onMetaChange = this.onMetaChange.createDelegate(this); + this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this); } if (this.writer) { // writer passed this.writer.meta = this.reader.meta; @@ -30242,6 +30950,7 @@ var grid = new Ext.grid.EditorGridPanel({ * @event clear * Fires when the data cache has been cleared. * @param {Store} this + * @param {Record[]} The records that were cleared. */ 'clear', /** @@ -30283,7 +30992,7 @@ var grid = new Ext.grid.EditorGridPanel({ 'loadexception', /** * @event beforewrite - * @param {DataProxy} this + * @param {Ext.data.Store} store * @param {String} action [Ext.data.Api.actions.create|update|destroy] * @param {Record/Array[Record]} rs * @param {Object} options The loading options that were specified. Edit options.params to add Http parameters to the request. (see {@link #save} for details) @@ -30293,9 +31002,8 @@ var grid = new Ext.grid.EditorGridPanel({ /** * @event write * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action. - * Success or failure of the action is available in the result['successProperty'] property. - * The server-code might set the successProperty to false if a database validation - * failed, for example. + * Success of the action is determined in the result['successProperty']property (NOTE for RESTful stores, + * a simple 20x response is sufficient for the actions "destroy" and "update". The "create" action should should return 200 along with a database pk). * @param {Ext.data.Store} store * @param {String} action [Ext.data.Api.actions.create|update|destroy] * @param {Object} result The 'data' picked-out out of the response for convenience. @@ -30314,7 +31022,8 @@ var grid = new Ext.grid.EditorGridPanel({ scope: this, add: this.createRecords, remove: this.destroyRecord, - update: this.updateRecord + update: this.updateRecord, + clear: this.onClear }); } @@ -30492,7 +31201,7 @@ sortInfo: { * internally be set to false.

    */ restful: false, - + /** * @cfg {Object} paramNames *

    An object containing properties which specify the names of the paging and @@ -30512,7 +31221,7 @@ sortInfo: { * the parameter names to use in its {@link #load requests}. */ paramNames : undefined, - + /** * @cfg {Object} defaultParamNames * Provides the default values for the {@link #paramNames} property. To globally modify the parameters @@ -30529,13 +31238,17 @@ sortInfo: { * Destroys the store. */ destroy : function(){ - if(this.storeId){ - Ext.StoreMgr.unregister(this); + if(!this.isDestroyed){ + if(this.storeId){ + Ext.StoreMgr.unregister(this); + } + this.clearData(); + this.data = null; + Ext.destroy(this.proxy); + this.reader = this.writer = null; + this.purgeListeners(); + this.isDestroyed = true; } - this.data = null; - Ext.destroy(this.proxy); - this.reader = this.writer = null; - this.purgeListeners(); }, /** @@ -30578,6 +31291,7 @@ sortInfo: { remove : function(record){ var index = this.data.indexOf(record); if(index > -1){ + record.join(null); this.data.removeAt(index); if(this.pruneModifiedRecords){ this.modified.remove(record); @@ -30601,14 +31315,25 @@ sortInfo: { * Remove all Records from the Store and fires the {@link #clear} event. */ removeAll : function(){ - this.data.clear(); + var items = []; + this.each(function(rec){ + items.push(rec); + }); + this.clearData(); if(this.snapshot){ this.snapshot.clear(); } if(this.pruneModifiedRecords){ this.modified = []; } - this.fireEvent('clear', this); + this.fireEvent('clear', this, items); + }, + + // private + onClear: function(store, records){ + Ext.each(records, function(rec, index){ + this.destroyRecord(this, rec, index); + }, this); }, /** @@ -30680,6 +31405,14 @@ sortInfo: { this.lastOptions = o; }, + // private + clearData: function(){ + this.data.each(function(rec) { + rec.join(null); + }); + this.data.clear(); + }, + /** *

    Loads the Record cache from the configured {@link #proxy} using the configured {@link #reader}.

    *

    Notes:

      @@ -30811,10 +31544,12 @@ sortInfo: { var doRequest = true; if (action === 'read') { + Ext.applyIf(options.params, this.baseParams); doRequest = this.fireEvent('beforeload', this, options); } else { - // if Writer is configured as listful, force single-recoord rs to be [{}} instead of {} + // if Writer is configured as listful, force single-record rs to be [{}] instead of {} + // TODO Move listful rendering into DataWriter where the @cfg is defined. Should be easy now. if (this.writer.listful === true && this.restful !== true) { rs = (Ext.isArray(rs)) ? rs : [rs]; } @@ -30831,10 +31566,11 @@ sortInfo: { // Send request to proxy. var params = Ext.apply({}, options.params, this.baseParams); if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) { - params.xaction = action; + params.xaction = action; // <-- really old, probaby unecessary. } - // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions. We'll flip it now - // and send the value into DataProxy#request, since it's the value which maps to the DataProxy#api + // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions. + // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to + // the user's configured DataProxy#api this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options); } return doRequest; @@ -30917,7 +31653,7 @@ sortInfo: { var actions = Ext.data.Api.actions; return (action == 'read') ? this.loadRecords : function(data, response, success) { // calls: onCreateRecords | onUpdateRecords | onDestroyRecords - this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, data); + this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data)); // If success === false here, exception will have been called in DataProxy if (success === true) { this.fireEvent('write', this, action, data, response, rs); @@ -30927,6 +31663,7 @@ sortInfo: { // Clears records from modified array after an exception event. // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized? + // TODO remove this method? clearModified : function(rs) { if (Ext.isArray(rs)) { for (var n=rs.length-1;n>=0;n--) { @@ -30987,7 +31724,7 @@ sortInfo: { // @protected onDestroyRecords proxy callback for destroy action onDestroyRecords : function(success, rs, data) { // splice each rec out of this.removed - rs = (rs instanceof Ext.data.Record) ? [rs] : rs; + rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs); for (var i=0,len=rs.length;i _phid, so we can remap in Store#onCreateRecords - rs.id = data[this.meta.idProperty]; - rs.data = values; + rs.id = this.getId(data); + rs.data = data; rs.commit(); } }, /** * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save. - * You must return a complete new record from the server. If you don't, your local record's missing fields - * will be populated with the default values specified in your Ext.data.Record.create specification. Without a defaultValue, - * local fields will be populated with empty string "". So return your entire record's data after both remote create and update. - * In addition, you must return record-data from the server in the same order received. - * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed as the record receives - * a fresh new data-hash. + * If returning data from multiple-records after a batch-update, you must return record-data from the server in + * the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be + * suppressed as the record receives fresh new data-hash * @param {Record/Record[]} rs * @param {Object/Object[]} data */ @@ -31835,18 +32611,13 @@ Ext.data.DataReader.prototype = { } } else { - // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on. + // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on. if (Ext.isArray(data) && data.length == 1) { data = data.shift(); } - if (!this.isData(data)) { - // TODO: create custom Exception class to return record in thrown exception. Allow exception-handler the choice - // to commit or not rather than blindly rs.commit() here. - rs.commit(); - throw new Ext.data.DataReader.Error('update', rs); + if (this.isData(data)) { + rs.data = Ext.apply(rs.data, data); } - this.buildExtractors(); - rs.data = this.extractValues(Ext.apply(rs.data, data), rs.fields.items, rs.fields.items.length); rs.commit(); } }, @@ -31858,7 +32629,15 @@ Ext.data.DataReader.prototype = { * @return {Boolean} */ isData : function(data) { - return (data && Ext.isObject(data) && !Ext.isEmpty(data[this.meta.idProperty])) ? true : false; + return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false; + }, + + // private function a store will createSequence upon + onMetaChange : function(meta){ + delete this.ef; + this.meta = meta; + this.recordType = Ext.data.Record.create(meta.fields); + this.buildExtractors(); } }; @@ -31883,6 +32662,40 @@ Ext.apply(Ext.data.DataReader.Error.prototype, { }); +/** + * Ext.data.Response + * A generic response class to normalize response-handling internally to the framework. + * TODO move to own file, add to jsb. + */ +Ext.data.Response = function(params) { + Ext.apply(this, params); +}; +Ext.data.Response.prototype = { + /** + * @property {String} action {@link Ext.data.Api#actions} + */ + action: undefined, + /** + * @property {Boolean} success + */ + success : undefined, + /** + * @property {String} message + */ + message : undefined, + /** + * @property {Array/Object} data + */ + data: undefined, + /** + * @property {Object} raw The raw response returned from server-code + */ + raw: undefined, + /** + * @property {Ext.data.Record/Ext.data.Record[]} record(s) related to the Request action + */ + records: undefined +} /** * @class Ext.data.DataWriter *

      Ext.data.DataWriter facilitates create, update, and destroy actions between @@ -31929,14 +32742,8 @@ var store = new Ext.data.Store({ * using {@link Ext.data.Record#create}. */ Ext.data.DataWriter = function(config){ - /** - * This DataWriter's configured metadata as passed to the constructor. - * @type Mixed - * @property meta - */ Ext.apply(this, config); }; - Ext.data.DataWriter.prototype = { /** @@ -31961,7 +32768,18 @@ Ext.data.DataWriter.prototype = { * @param {Record/Record[]} rs The recordset write. */ write : function(action, params, rs) { - this.render(action, rs, params, this[action](rs)); + var data = [], + renderer = action + 'Record'; + // TODO implement @cfg listful here + if (Ext.isArray(rs)) { + Ext.each(rs, function(rec){ + data.push(this[renderer](rec)); + }, this); + } + else if (rs instanceof Ext.data.Record) { + data = this[renderer](rs); + } + this.render(action, rs, params, data); }, /** @@ -31975,84 +32793,17 @@ Ext.data.DataWriter.prototype = { render : Ext.emptyFn, /** - * update - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - update : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = [], - ids = []; - Ext.each(rs, function(val){ - ids.push(val.id); - data.push(this.updateRecord(val)); - }, this); - params[this.meta.idProperty] = ids; - params[this.meta.root] = data; - } - else if (rs instanceof Ext.data.Record) { - params[this.meta.idProperty] = rs.id; - params[this.meta.root] = this.updateRecord(rs); - } - return params; - }, - - /** - * @cfg {Function} saveRecord Abstract method that should be implemented in all subclasses - * (e.g.: {@link Ext.data.JsonWriter#saveRecord JsonWriter.saveRecord} + * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses + * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord} */ updateRecord : Ext.emptyFn, - /** - * create - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - create : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = []; - Ext.each(rs, function(val){ - data.push(this.createRecord(val)); - }, this); - params[this.meta.root] = data; - } - else if (rs instanceof Ext.data.Record) { - params[this.meta.root] = this.createRecord(rs); - } - return params; - }, - /** * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord}) */ createRecord : Ext.emptyFn, - /** - * destroy - * @param {Object} p Params-hash to apply result to. - * @param {Record/Record[]} rs Record(s) to write - * @private - */ - destroy : function(rs) { - var params = {}; - if (Ext.isArray(rs)) { - var data = [], - ids = []; - Ext.each(rs, function(val){ - data.push(this.destroyRecord(val)); - }, this); - params[this.meta.root] = data; - } else if (rs instanceof Ext.data.Record) { - params[this.meta.root] = this.destroyRecord(rs); - } - return params; - }, - /** * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord}) @@ -32060,7 +32811,7 @@ Ext.data.DataWriter.prototype = { destroyRecord : Ext.emptyFn, /** - * Converts a Record to a hash + * Converts a Record to a hash. * @param {Record} * @private */ @@ -32074,7 +32825,16 @@ Ext.data.DataWriter.prototype = { data[m.mapping ? m.mapping : m.name] = value; } }); - data[this.meta.idProperty] = rec.id; + // we don't want to write Ext auto-generated id to hash. Careful not to remove it on Models not having auto-increment pk though. + // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty. + // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix. + if (rec.phantom) { + if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) { + delete data[this.meta.idProperty]; + } + } else { + data[this.meta.idProperty] = rec.id + } return data; } };/** @@ -32140,7 +32900,27 @@ api: { update : undefined, destroy : undefined } - + * + *

      The url is built based upon the action being executed [load|create|save|destroy] + * using the commensurate {@link #api} property, or if undefined default to the + * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.


      + *

      For example:

      + *
      
      +api: {
      +    load :    '/controller/load',
      +    create :  '/controller/new',  // Server MUST return idProperty of new record
      +    save :    '/controller/update',
      +    destroy : '/controller/destroy_action'
      +}
      +
      +// Alternatively, one can use the object-form to specify each API-action
      +api: {
      +    load: {url: 'read.php', method: 'GET'},
      +    create: 'create.php',
      +    destroy: 'destroy.php',
      +    save: 'update.php'
      +}
      +     * 
      *

      If the specific URL for a given CRUD action is undefined, the CRUD action request * will be directed to the configured {@link Ext.data.Connection#url url}.

      *

      Note: To modify the URL for an action dynamically the appropriate API @@ -32158,22 +32938,9 @@ myStore.on({ // permanent, applying this URL for all subsequent requests. store.proxy.setUrl('changed1.php', true); - // manually set the private connection URL. - // Warning: Accessing the private URL property should be avoided. - // Use the public method {@link Ext.data.HttpProxy#setUrl setUrl} instead, shown above. - // It should be noted that changing the URL like this will affect - // the URL for just this request. Subsequent requests will use the - // API or URL defined in your initial proxy configuration. - store.proxy.conn.url = 'changed1.php'; - - // proxy URL will be superseded by API (only if proxy created to use ajax): - // It should be noted that proxy API changes are permanent and will - // be used for all subsequent requests. - store.proxy.api.load = 'changed2.php'; - - // However, altering the proxy API should be done using the public - // method {@link Ext.data.DataProxy#setApi setApi} instead. - store.proxy.setApi('load', 'changed2.php'); + // Altering the proxy API should be done using the public + // method {@link Ext.data.DataProxy#setApi setApi}. + store.proxy.setApi('read', 'changed2.php'); // Or set the entire API with a config-object. // When using the config-object option, you must redefine the entire @@ -32190,23 +32957,17 @@ myStore.on({ * *

      */ - // Prepare the proxy api. Ensures all API-actions are defined with the Object-form. - try { - Ext.data.Api.prepare(this); - } catch (e) { - if (e instanceof Ext.data.Api.Error) { - e.toConsole(); - } - } this.addEvents( /** * @event exception - *

      Fires if an exception occurs in the Proxy during a remote request. - * This event is relayed through a corresponding - * {@link Ext.data.Store}.{@link Ext.data.Store#exception exception}, - * so any Store instance may observe this event. - * This event can be fired for one of two reasons:

      + *

      Fires if an exception occurs in the Proxy during a remote request. This event is relayed + * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception}, + * so any Store instance may observe this event.

      + *

      In addition to being fired through the DataProxy instance that raised the event, this event is also fired + * through the Ext.data.DataProxy class to allow for centralized processing of exception events from all + * DataProxies by attaching a listener to the Ext.data.Proxy class itself.

      + *

      This event can be fired for one of two reasons:

      *
        *
      • remote-request failed :
        * The server did not return status === 200. @@ -32290,7 +33051,10 @@ myStore.on({ 'loadexception', /** * @event beforewrite - * Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy + *

        Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy

        + *

        In addition to being fired through the DataProxy instance that raised the event, this event is also fired + * through the Ext.data.DataProxy class to allow for centralized processing of beforewrite events from all + * DataProxies by attaching a listener to the Ext.data.Proxy class itself.

        * @param {DataProxy} this The proxy for the request * @param {String} action [Ext.data.Api.actions.create|update|destroy] * @param {Record/Array[Record]} rs The Record(s) to create|update|destroy. @@ -32299,7 +33063,10 @@ myStore.on({ 'beforewrite', /** * @event write - * Fires before the request-callback is called + *

        Fires before the request-callback is called

        + *

        In addition to being fired through the DataProxy instance that raised the event, this event is also fired + * through the Ext.data.DataProxy class to allow for centralized processing of write events from all + * DataProxies by attaching a listener to the Ext.data.Proxy class itself.

        * @param {DataProxy} this The proxy that sent the request * @param {String} action [Ext.data.Api.actions.create|upate|destroy] * @param {Object} data The data object extracted from the server-response @@ -32310,6 +33077,17 @@ myStore.on({ 'write' ); Ext.data.DataProxy.superclass.constructor.call(this); + + // Prepare the proxy api. Ensures all API-actions are defined with the Object-form. + try { + Ext.data.Api.prepare(this); + } catch (e) { + if (e instanceof Ext.data.Api.Error) { + e.toConsole(); + } + } + // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening + Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']); }; Ext.extend(Ext.data.DataProxy, Ext.util.Observable, { @@ -32328,7 +33106,7 @@ store: new Ext.data.Store({ ... )} * - * There is no {@link #api} specified in the configuration of the proxy, + * If there is no {@link #api} specified in the configuration of the proxy, * all requests will be marshalled to a single RESTful url (/users) so the serverside * framework can inspect the HTTP Method and act accordingly: *
        @@ -32338,6 +33116,18 @@ GET      /users     read
         PUT      /users/23  update
         DESTROY  /users/23  delete
              * 

        + *

        If set to true, a {@link Ext.data.Record#phantom non-phantom} record's + * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails, + * Merb and Django) support segment based urls where the segments in the URL follow the + * Model-View-Controller approach:

        
        +     * someSite.com/controller/action/id
        +     * 
        + * Where the segments in the url are typically:
          + *
        • The first segment : represents the controller class that should be invoked.
        • + *
        • The second segment : represents the class function, or method, that should be called.
        • + *
        • The third segment : represents the ID (a variable typically passed to the method).
        • + *

        + *

        Refer to {@link Ext.data.DataProxy#api} for additional information.

        */ restful: false, @@ -32453,11 +33243,16 @@ proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url'); throw new Ext.data.Api.Error('invalid-url', action); } + // look for urls having "provides" suffix (from Rails/Merb and others...), + // e.g.: /users.json, /users.xml, etc + // with restful routes, we need urls like: + // PUT /users/1.json + // DELETE /users/1.json var format = null; - var m = url.match(/(.*)(\.\w+)$/); // <-- look for urls with "provides" suffix, e.g.: /users.json, /users.xml, etc + var m = url.match(/(.*)(\.json|\.xml|\.html)$/); if (m) { - format = m[2]; - url = m[1]; + format = m[2]; // eg ".json" + url = m[1]; // eg: "/users" } // prettyUrls is deprectated in favor of restful-config if ((this.prettyUrls === true || this.restful === true) && record instanceof Ext.data.Record && !record.phantom) { @@ -32477,6 +33272,11 @@ proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url'); } }); +// Apply the Observable prototype to the DataProxy class so that proxy instances can relay their +// events to the class. Allows for centralized listening of all proxy instances upon the DataProxy class. +Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype); +Ext.util.Observable.call(Ext.data.DataProxy); + /** * @class Ext.data.DataProxy.Error * @extends Ext.Error @@ -32498,6 +33298,8 @@ Ext.apply(Ext.data.DataProxy.Error.prototype, { 'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.' } }); + + /** * @class Ext.data.ScriptTagProxy * @extends Ext.data.DataProxy @@ -32696,23 +33498,23 @@ Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, { * @param {Object} res The server response * @private */ - onWrite : function(action, trans, res, rs) { + onWrite : function(action, trans, response, rs) { var reader = trans.reader; try { // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions. - reader.readResponse(action, res); + var res = reader.readResponse(action, response); } catch (e) { this.fireEvent('exception', this, 'response', action, trans, res, e); trans.callback.call(trans.scope||window, null, res, false); return; } - if(!res[reader.meta.successProperty] === true){ + if(!res.success === true){ this.fireEvent('exception', this, 'remote', action, trans, res, rs); trans.callback.call(trans.scope||window, null, res, false); return; } - this.fireEvent("write", this, action, res[reader.meta.root], res, rs, trans.arg ); - trans.callback.call(trans.scope||window, res[reader.meta.root], res, true); + this.fireEvent("write", this, action, res.data, res, rs, trans.arg ); + trans.callback.call(trans.scope||window, res.data, res, true); }, // private @@ -32804,13 +33606,13 @@ Ext.data.HttpProxy = function(conn){ // nullify the connection url. The url param has been copied to 'this' above. The connection // url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the - // connection url during beforeaction events (ie: beforeload, beforesave, etc). The connection url will be nullified - // after each request as well. Url is always re-defined during doRequest. + // connection url during beforeaction events (ie: beforeload, beforewrite, etc). + // Url is always re-defined during doRequest. this.conn.url = null; this.useAjax = !conn || !conn.events; - //private. A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy] + // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy] var actions = Ext.data.Api.actions; this.activeRequest = {}; for (var verb in actions) { @@ -32819,40 +33621,6 @@ Ext.data.HttpProxy = function(conn){ }; Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, { - /** - * @cfg {Boolean} restful - *

        If set to true, a {@link Ext.data.Record#phantom non-phantom} record's - * {@link Ext.data.Record#id id} will be appended to the url (defaults to false).


        - *

        The url is built based upon the action being executed [load|create|save|destroy] - * using the commensurate {@link #api} property, or if undefined default to the - * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.


        - *

        Some MVC (e.g., Ruby on Rails, Merb and Django) support this style of segment based urls - * where the segments in the URL follow the Model-View-Controller approach.

        
        -     * someSite.com/controller/action/id
        -     * 
        - * Where the segments in the url are typically:
          - *
        • The first segment : represents the controller class that should be invoked.
        • - *
        • The second segment : represents the class function, or method, that should be called.
        • - *
        • The third segment : represents the ID (a variable typically passed to the method).
        • - *

        - *

        For example:

        - *
        
        -api: {
        -    load :    '/controller/load',
        -    create :  '/controller/new',  // Server MUST return idProperty of new record
        -    save :    '/controller/update',
        -    destroy : '/controller/destroy_action'
        -}
        -
        -// Alternatively, one can use the object-form to specify each API-action
        -api: {
        -    load: {url: 'read.php', method: 'GET'},
        -    create: 'create.php',
        -    destroy: 'destroy.php',
        -    save: 'update.php'
        -}
        -     */
        -
             /**
              * Return the {@link Ext.data.Connection} object being used by this Proxy.
              * @return {Connection} The Connection object. This object may be used to subscribe to events on
        @@ -32876,6 +33644,7 @@ api: {
                 this.conn.url = url;
                 if (makePermanent === true) {
                     this.url = url;
        +            this.api = null;
                     Ext.data.Api.prepare(this);
                 }
             },
        @@ -32909,10 +33678,14 @@ api: {
                     callback : this.createCallback(action, rs),
                     scope: this
                 };
        -        // Sample the request data:  If it's an object, then it hasn't been json-encoded yet.
        -        // Transmit data using jsonData config of Ext.Ajax.request
        -        if (typeof(params[reader.meta.root]) === 'object') {
        -            o.jsonData = params;
        +
        +        // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
        +        // Use std HTTP params otherwise.
        +        // TODO wrap into 1 Ext.apply now?
        +        if (params.jsonData) {
        +            o.jsonData = params.jsonData;
        +        } else if (params.xmlData) {
        +            o.xmlData = params.xmlData;
                 } else {
                     o.params = params || {};
                 }
        @@ -32922,7 +33695,7 @@ api: {
                 if (this.conn.url === null) {
                     this.conn.url = this.buildUrl(action, rs);
                 }
        -        else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) {
        +        else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) { // <-- user must have intervened with #setApi or #setUrl
                     this.conn.url += '/' + rs.id;
                 }
                 if(this.useAjax){
        @@ -32931,7 +33704,10 @@ api: {
         
                     // If a currently running request is found for this action, abort it.
                     if (this.activeRequest[action]) {
        +                ////
                         // Disabled aborting activeRequest while implementing REST.  activeRequest[action] will have to become an array
        +                // TODO ideas anyone?
        +                //
                         //Ext.Ajax.abort(this.activeRequest[action]);
                     }
                     this.activeRequest[action] = Ext.Ajax.request(o);
        @@ -32955,6 +33731,7 @@ api: {
                     if (!success) {
                         if (action === Ext.data.Api.actions.read) {
                             // @deprecated: fire loadexception for backwards compat.
        +                    // TODO remove in 3.1
                             this.fireEvent('loadexception', this, o, response);
                         }
                         this.fireEvent('exception', this, 'response', action, o, response);
        @@ -32974,6 +33751,9 @@ api: {
              * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
              * @param {Object} o The request transaction object
              * @param {Object} res The server response
        +     * @fires loadexception (deprecated)
        +     * @fires exception
        +     * @fires load
              * @private
              */
             onRead : function(action, o, response) {
        @@ -32982,13 +33762,16 @@ api: {
                     result = o.reader.read(response);
                 }catch(e){
                     // @deprecated: fire old loadexception for backwards-compat.
        +            // TODO remove in 3.1
                     this.fireEvent('loadexception', this, o, response, e);
        +
                     this.fireEvent('exception', this, 'response', action, o, response, e);
                     o.request.callback.call(o.request.scope, null, o.request.arg, false);
                     return;
                 }
                 if (result.success === false) {
                     // @deprecated: fire old loadexception for backwards-compat.
        +            // TODO remove in 3.1
                     this.fireEvent('loadexception', this, o, response);
         
                     // Get DataReader read-back a response-object to pass along to exception event
        @@ -32998,6 +33781,9 @@ api: {
                 else {
                     this.fireEvent('load', this, o, o.request.arg);
                 }
        +        // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
        +        // the calls to request.callback(...) in each will have to be made identical.
        +        // NOTE reader.readResponse does not currently return Ext.data.Response
                 o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
             },
             /**
        @@ -33005,6 +33791,8 @@ api: {
              * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
              * @param {Object} trans The request transaction object
              * @param {Object} res The server response
        +     * @fires exception
        +     * @fires write
              * @private
              */
             onWrite : function(action, o, response, rs) {
        @@ -33017,12 +33805,15 @@ api: {
                     o.request.callback.call(o.request.scope, null, o.request.arg, false);
                     return;
                 }
        -        if (res[reader.meta.successProperty] === false) {
        +        if (res.success === false) {
                     this.fireEvent('exception', this, 'remote', action, o, res, rs);
                 } else {
        -            this.fireEvent('write', this, action, res[reader.meta.root], res, rs, o.request.arg);
        +            this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
                 }
        -        o.request.callback.call(o.request.scope, res[reader.meta.root], res, res[reader.meta.successProperty]);
        +        // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
        +        // the calls to request.callback(...) in each will have to be made similar.
        +        // NOTE reader.readResponse does not currently return Ext.data.Response
        +        o.request.callback.call(o.request.scope, res.data, res, res.success);
             },
         
             // inherit docs
        @@ -33108,14 +33899,16 @@ Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
          */
         Ext.data.JsonWriter = function(config) {
             Ext.data.JsonWriter.superclass.constructor.call(this, config);
        +
             // careful to respect "returnJson", renamed to "encode"
        +    // TODO: remove after v3 final release
             if (this.returnJson != undefined) {
                 this.encode = this.returnJson;
             }
         }
         Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
             /**
        -     * @cfg {Boolean} returnJson Deprecated.  Use {@link Ext.data.JsonWriter#encode} instead.
        +     * @cfg {Boolean} returnJson Deprecated.  Use {@link Ext.data.JsonWriter#encode} instead.
              */
             returnJson : undefined,
             /**
        @@ -33125,7 +33918,8 @@ Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
              * its own json-encoding.  In addition, if you're using {@link Ext.data.HttpProxy}, setting to false
              * will cause HttpProxy to transmit data using the jsonData configuration-params of {@link Ext.Ajax#request}
              * instead of params.  When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
        -     * tuned to expect data through the jsonData mechanism.  In those cases, one will want to set encode: false
        +     * tuned to expect data through the jsonData mechanism.  In those cases, one will want to set encode: false, as in
        +     * let the lower-level connection object (eg: Ext.Ajax) do the encoding.
              */
             encode : true,
         
        @@ -33137,36 +33931,37 @@ Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
              * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty"
              */
             render : function(action, rs, params, data) {
        -        Ext.apply(params, data);
        -
        -        if (this.encode === true) { // <-- @deprecated returnJson
        -            if (Ext.isArray(rs) && data[this.meta.idProperty]) {
        -                params[this.meta.idProperty] = Ext.encode(params[this.meta.idProperty]);
        -            }
        -            params[this.meta.root] = Ext.encode(params[this.meta.root]);
        +        if (this.encode === true) {
        +            params[this.meta.root] = Ext.encode(data);
        +        } else {
        +            params.jsonData = {};
        +            params.jsonData[this.meta.root] = data;
                 }
             },
             /**
        -     * createRecord
        +     * Implements abstract Ext.data.DataWriter#createRecord
              * @protected
              * @param {Ext.data.Record} rec
        +     * @return {Object}
              */
             createRecord : function(rec) {
        -        return this.toHash(rec);
        +       return this.toHash(rec);
             },
             /**
        -     * updateRecord
        +     * Implements abstract Ext.data.DataWriter#updateRecord
              * @protected
              * @param {Ext.data.Record} rec
        +     * @return {Object}
              */
             updateRecord : function(rec) {
                 return this.toHash(rec);
         
             },
             /**
        -     * destroyRecord
        +     * Implements abstract Ext.data.DataWriter#destroyRecord
              * @protected
              * @param {Ext.data.Record} rec
        +     * @return {Object}
              */
             destroyRecord : function(rec) {
                 return rec.id;
        @@ -33174,90 +33969,101 @@ Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
         });/**
          * @class Ext.data.JsonReader
          * @extends Ext.data.DataReader
        - * 

        Data reader class to create an Array of {@link Ext.data.Record} objects from a JSON response - * based on mappings in a provided {@link Ext.data.Record} constructor.

        + *

        Data reader class to create an Array of {@link Ext.data.Record} objects + * from a JSON packet based on mappings in a provided {@link Ext.data.Record} + * constructor.

        *

        Example code:

        *
        
        -var Employee = Ext.data.Record.create([
        -    {name: 'firstname'},                  // map the Record's "firstname" field to the row object's key of the same name
        -    {name: 'job', mapping: 'occupation'}  // map the Record's "job" field to the row object's "occupation" key
        -]);
        -var myReader = new Ext.data.JsonReader(
        -    {                             // The metadata property, with configuration options:
        -        totalProperty: "results", //   the property which contains the total dataset size (optional)
        -        root: "rows",             //   the property which contains an Array of record data objects
        -        idProperty: "id"          //   the property within each row object that provides an ID for the record (optional)
        -    },
        -    Employee  // {@link Ext.data.Record} constructor that provides mapping for JSON object
        -);
        +var myReader = new Ext.data.JsonReader({
        +    // metadata configuration options:
        +    {@link #idProperty}: 'id'
        +    {@link #root}: 'rows',
        +    {@link #totalProperty}: 'results',
        +    {@link Ext.data.DataReader#messageProperty}: "msg"  // The element within the response that provides a user-feedback message (optional)
        +
        +    // the fields config option will internally create an {@link Ext.data.Record}
        +    // constructor that provides mapping for reading the record data objects
        +    {@link Ext.data.DataReader#fields fields}: [
        +        // map Record's 'firstname' field to data object's key of same name
        +        {name: 'name'},
        +        // map Record's 'job' field to data object's 'occupation' key
        +        {name: 'job', mapping: 'occupation'}
        +    ]
        +});
         
        *

        This would consume a JSON data object of the form:

        
         {
        -    results: 2,  // Reader's configured totalProperty
        -    rows: [      // Reader's configured root
        -        { id: 1, firstname: 'Bill', occupation: 'Gardener' },         // a row object
        -        { id: 2, firstname: 'Ben' , occupation: 'Horticulturalist' }  // another row object
        +    results: 2000, // Reader's configured {@link #totalProperty}
        +    rows: [        // Reader's configured {@link #root}
        +        // record data objects:
        +        { {@link #idProperty id}: 1, firstname: 'Bill', occupation: 'Gardener' },
        +        { {@link #idProperty id}: 2, firstname: 'Ben' , occupation: 'Horticulturalist' },
        +        ...
             ]
         }
         
        *

        Automatic configuration using metaData

        - *

        It is possible to change a JsonReader's metadata at any time by including a metaData - * property in the JSON data object. If the JSON data object has a metaData property, a - * {@link Ext.data.Store Store} object using this Reader will reconfigure itself to use the newly provided - * field definition and fire its {@link Ext.data.Store#metachange metachange} event. The metachange event - * handler may interrogate the metaData property to perform any configuration required. - * Note that reconfiguring a Store potentially invalidates objects which may refer to Fields or Records - * which no longer exist.

        - *

        The metaData property in the JSON data object may contain:

        - *
          - *
        • any of the configuration options for this class
        • - *
        • a {@link Ext.data.Record#fields fields} property which the JsonReader will - * use as an argument to the {@link Ext.data.Record#create data Record create method} in order to - * configure the layout of the Records it will produce.
        • - *
        • a {@link Ext.data.Store#sortInfo sortInfo} property which the JsonReader will - * use to set the {@link Ext.data.Store}'s {@link Ext.data.Store#sortInfo sortInfo} property
        • - *
        • any user-defined properties needed
        • - *
        - *

        To use this facility to send the same data as the example above (without having to code the creation - * of the Record constructor), you would create the JsonReader like this:

        
        + * 

        It is possible to change a JsonReader's metadata at any time by including + * a metaData property in the JSON data object. If the JSON data + * object has a metaData property, a {@link Ext.data.Store Store} + * object using this Reader will reconfigure itself to use the newly provided + * field definition and fire its {@link Ext.data.Store#metachange metachange} + * event. The metachange event handler may interrogate the metaData + * property to perform any configuration required.

        + *

        Note that reconfiguring a Store potentially invalidates objects which may + * refer to Fields or Records which no longer exist.

        + *

        To use this facility you would create the JsonReader like this:

        
         var myReader = new Ext.data.JsonReader();
         
        - *

        The first data packet from the server would configure the reader by containing a - * metaData property and the data. For example, the JSON data object might take - * the form:

        -
        
        + * 

        The first data packet from the server would configure the reader by + * containing a metaData property and the data. For + * example, the JSON data object might take the form:

        
         {
             metaData: {
        -        idProperty: 'id',
        -        root: 'rows',
        -        totalProperty: 'results',
        -        fields: [
        -            {name: 'name'},
        -            {name: 'job', mapping: 'occupation'}
        +        "{@link #idProperty}": "id",
        +        "{@link #root}": "rows",
        +        "{@link #totalProperty}": "results"
        +        "{@link #successProperty}": "success",
        +        "{@link Ext.data.DataReader#fields fields}": [
        +            {"name": "name"},
        +            {"name": "job", "mapping": "occupation"}
                 ],
        -        sortInfo: {field: 'name', direction:'ASC'}, // used by store to set its sortInfo
        -        foo: 'bar' // custom property
        -    },
        -    results: 2,
        -    rows: [ // an Array
        -        { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
        -        { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' }
        +        // used by store to set its sortInfo
        +        "sortInfo":{
        +           "field": "name",
        +           "direction": "ASC"
        +        },
        +        // {@link Ext.PagingToolbar paging data} (if applicable)
        +        "start": 0,
        +        "limit": 2,
        +        // custom property
        +        "foo": "bar"
        +    },
        +    // Reader's configured {@link #successProperty}
        +    "success": true,
        +    // Reader's configured {@link #totalProperty}
        +    "results": 2000,
        +    // Reader's configured {@link #root}
        +    // (this data simulates 2 results {@link Ext.PagingToolbar per page})
        +    "rows": [ // *Note: this must be an Array
        +        { "id": 1, "name": "Bill", "occupation": "Gardener" },
        +        { "id": 2, "name":  "Ben", "occupation": "Horticulturalist" }
             ]
         }
        -
        - * @cfg {String} totalProperty [total] 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 total. - * @cfg {String} successProperty [success] Name of the property from which to - * retrieve the success attribute. Defaults to success. See - * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} - * for additional information. - * @cfg {String} root [undefined] Required. The name of the property - * which contains the Array of row objects. Defaults to undefined. - * 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. - * @cfg {String} idProperty [id] Name of the property within a row object that contains a record identifier value. Defaults to id + *
        + *

        The metaData property in the JSON data object should contain:

        + *
          + *
        • any of the configuration options for this class
        • + *
        • a {@link Ext.data.Record#fields fields} property which + * the JsonReader will use as an argument to the + * {@link Ext.data.Record#create data Record create method} in order to + * configure the layout of the Records it will produce.
        • + *
        • a {@link Ext.data.Store#sortInfo sortInfo} property + * which the JsonReader will use to set the {@link Ext.data.Store}'s + * {@link Ext.data.Store#sortInfo sortInfo} property
        • + *
        • any custom properties needed
        • + *
        + * * @constructor * Create a new JsonReader * @param {Object} meta Metadata configuration options. @@ -33268,8 +34074,29 @@ var myReader = new Ext.data.JsonReader(); */ Ext.data.JsonReader = function(meta, recordType){ meta = meta || {}; - - // default idProperty, successProperty & totalProperty -> "id", "success", "total" + /** + * @cfg {String} idProperty [id] Name of the property within a row object + * that contains a record identifier value. Defaults to id + */ + /** + * @cfg {String} successProperty [success] Name of the property from which to + * retrieve the success attribute. Defaults to success. See + * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception} + * for additional information. + */ + /** + * @cfg {String} totalProperty [total] 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 total. + */ + /** + * @cfg {String} root [undefined] Required. The name of the property + * which contains the Array of row objects. Defaults to undefined. + * 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. + */ Ext.applyIf(meta, { idProperty: 'id', successProperty: 'success', @@ -33295,39 +34122,50 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, { var json = response.responseText; var o = Ext.decode(json); if(!o) { - throw {message: "JsonReader.read: Json object not found"}; + throw {message: 'JsonReader.read: Json object not found'}; } return this.readRecords(o); }, - // private function a store will implement - onMetaChange : function(meta, recordType, o){ - - }, - /** - * @ignore + * Decode a json response from server. + * @param {String} action [Ext.data.Api.actions.create|read|update|destroy] + * @param {Object} response + * TODO: refactor code between JsonReader#readRecords, #readResponse into 1 method. + * there's ugly duplication going on due to maintaining backwards compat. with 2.0. It's time to do this. */ - simpleAccess: function(obj, subsc) { - return obj[subsc]; - }, + readResponse : function(action, response) { + var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response; + if(!o) { + throw new Ext.data.JsonReader.Error('response'); + } - /** - * @ignore - */ - getJsonAccessor: function(){ - var re = /[\[\.]/; - return function(expr) { - try { - return(re.test(expr)) ? - new Function("obj", "return obj." + expr) : - function(obj){ - return obj[expr]; - }; - } catch(e){} - return Ext.emptyFn; - }; - }(), + var root = this.getRoot(o); + if (action === Ext.data.Api.actions.create) { + var def = Ext.isDefined(root); + if (def && Ext.isEmpty(root)) { + throw new Ext.data.JsonReader.Error('root-empty', this.meta.root); + } + else if (!def) { + throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root); + } + } + + // instantiate response object + var res = new Ext.data.Response({ + action: action, + success: this.getSuccess(o), + data: this.extractData(root), + message: this.getMessage(o), + raw: o + }); + + // blow up if no successProperty + if (Ext.isEmpty(res.success)) { + throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty); + } + return res; + }, /** * Create a data block containing Ext.data.Records from a JSON object. @@ -33345,16 +34183,11 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, { */ this.jsonData = o; if(o.metaData){ - delete this.ef; - this.meta = o.metaData; - this.recordType = Ext.data.Record.create(o.metaData.fields); - this.onMetaChange(this.meta, this.recordType, o); + this.onMetaChange(o.metaData); } var s = this.meta, Record = this.recordType, f = Record.prototype.fields, fi = f.items, fl = f.length, v; - // Generate extraction functions for the totalProperty, the root, the id, and for each field - this.buildExtractors(); var root = this.getRoot(o), c = root.length, totalRecords = c, success = true; if(s.totalProperty){ v = parseInt(this.getTotal(o), 10); @@ -33369,16 +34202,10 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, { } } - var records = []; - for(var i = 0; i < c; i++){ - var n = root[i]; - var record = new Record(this.extractValues(n, fi, fl), this.getId(n)); - record.json = n; - records[i] = record; - } + // TODO return Ext.data.Response instance instead. @see #readResponse return { success : success, - records : records, + records : this.extractData(root, true), // <-- true to return [Ext.data.Record] totalRecords : totalRecords }; }, @@ -33392,17 +34219,20 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, { f = Record.prototype.fields, fi = f.items, fl = f.length; if(s.totalProperty) { - this.getTotal = this.getJsonAccessor(s.totalProperty); + this.getTotal = this.createAccessor(s.totalProperty); } if(s.successProperty) { - this.getSuccess = this.getJsonAccessor(s.successProperty); + this.getSuccess = this.createAccessor(s.successProperty); + } + if (s.messageProperty) { + this.getMessage = this.createAccessor(s.messageProperty); } - this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;}; + this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;}; if (s.id || s.idProperty) { - var g = this.getJsonAccessor(s.id || s.idProperty); + var g = this.createAccessor(s.id || s.idProperty); this.getId = function(rec) { var r = g(rec); - return (r === undefined || r === "") ? null : r; + return (r === undefined || r === '') ? null : r; }; } else { this.getId = function(){return null;}; @@ -33411,47 +34241,85 @@ Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, { for(var i = 0; i < fl; i++){ f = fi[i]; var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name; - ef.push(this.getJsonAccessor(map)); + ef.push(this.createAccessor(map)); } this.ef = ef; }, - // private extractValues - extractValues: function(data, items, len) { - var f, values = {}; - for(var j = 0; j < len; j++){ - f = items[j]; - var v = this.ef[j](data); - values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data); - } - return values; + /** + * @ignore + * TODO This isn't used anywhere?? Don't we want to use this where possible instead of complex #createAccessor? + */ + simpleAccess : function(obj, subsc) { + return obj[subsc]; }, /** - * Decode a json response from server. - * @param {String} action [Ext.data.Api.actions.create|read|update|destroy] - * @param {Object} response + * @ignore */ - readResponse : function(action, response) { - var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response; - if(!o) { - throw new Ext.data.JsonReader.Error('response'); - } - if (Ext.isEmpty(o[this.meta.successProperty])) { - throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty); - } - // TODO, separate empty and undefined exceptions. - if ((action === Ext.data.Api.actions.create || action === Ext.data.Api.actions.update)) { - if (Ext.isEmpty(o[this.meta.root])) { - throw new Ext.data.JsonReader.Error('root-emtpy', this.meta.root); + createAccessor : function(){ + var re = /[\[\.]/; + return function(expr) { + try { + return(re.test(expr)) ? + new Function('obj', 'return obj.' + expr) : + function(obj){ + return obj[expr]; + }; + } catch(e){} + return Ext.emptyFn; + }; + }(), + + /** + * returns extracted, type-cast rows of data. Iterates to call #extractValues for each row + * @param {Object[]/Object} data-root from server response + * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record + * @private + */ + extractData : function(root, returnRecords) { + var rs = undefined; + if (this.isData(root)) { + root = [root]; + } + if (Ext.isArray(root)) { + var f = this.recordType.prototype.fields, + fi = f.items, + fl = f.length, + rs = []; + if (returnRecords === true) { + var Record = this.recordType; + for (var i = 0; i < root.length; i++) { + var n = root[i]; + var record = new Record(this.extractValues(n, fi, fl), this.getId(n)); + record.json = n; + rs.push(record); + } } - else if (o[this.meta.root] === undefined) { - throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root); + else { + for (var i = 0; i < root.length; i++) { + rs.push(this.extractValues(root[i], fi, fl)); + } } } - // make sure extraction functions are defined. - this.ef = this.buildExtractors(); - return o; + return rs; + }, + + /** + * type-casts a single row of raw-data from server + * @param {Object} data + * @param {Array} items + * @param {Integer} len + * @private + */ + extractValues : function(data, items, len) { + var f, values = {}; + for(var j = 0; j < len; j++){ + f = items[j]; + var v = this.ef[j](data); + values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data); + } + return values; } }); @@ -33468,12 +34336,11 @@ Ext.data.JsonReader.Error = Ext.extend(Ext.Error, { }); Ext.apply(Ext.data.JsonReader.Error.prototype, { lang: { - 'response': "An error occurred while json-decoding your server response", + 'response': 'An error occurred while json-decoding your server response', 'successProperty-response': 'Could not locate your "successProperty" in your server response. Please review your JsonReader config to ensure the config-property "successProperty" matches the property in your server-response. See the JsonReader docs.', - 'root-undefined-response': 'Could not locate your "root" property in your server response. Please review your JsonReader config to ensure the config-property "root" matches the property your server-response. See the JsonReader docs.', 'root-undefined-config': 'Your JsonReader was configured without a "root" property. Please review your JsonReader config and make sure to define the root property. See the JsonReader docs.', 'idProperty-undefined' : 'Your JsonReader was configured without an "idProperty" Please review your JsonReader configuration and ensure the "idProperty" is set (e.g.: "id"). See the JsonReader docs.', - 'root-emtpy': 'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.' + 'root-empty': 'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.' } }); /** @@ -33541,13 +34408,13 @@ Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, { var root = this.getRoot(o); - for(var i = 0; i < root.length; i++) { - var n = root[i]; - var values = {}; - var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null); + for(var i = 0, len = root.length; i < len; i++) { + var n = root[i], + values = {}, + id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null); for(var j = 0, jlen = fields.length; j < jlen; j++) { - var f = fields.items[j]; - var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j; + var f = fields.items[j], + k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j; v = n[k] !== undefined ? n[k] : f.defaultValue; v = f.convert(v, n); values[f.name] = v; @@ -33647,7 +34514,7 @@ var store = new Ext.data.JsonStore({ storeId: 'myStore', // reader configs root: 'images', - idProperty: 'name', + idProperty: 'name', fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}] }); *

        @@ -33660,7 +34527,7 @@ var store = new Ext.data.JsonStore({ } *
        * An object literal of this form could also be used as the {@link #data} config option.

        - *

        *Note: Although not listed here, this class accepts all of the configuration options of + *

        *Note: Although not listed here, this class accepts all of the configuration options of * {@link Ext.data.JsonReader JsonReader}.

        * @constructor * @param {Object} config @@ -33681,40 +34548,129 @@ Ext.reg('jsonstore', Ext.data.JsonStore);/** * @extends Ext.data.DataWriter * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action via XML. */ -Ext.data.XmlWriter = Ext.extend(Ext.data.DataWriter, { +Ext.data.XmlWriter = function(params) { + Ext.data.XmlWriter.superclass.constructor.apply(this, arguments); + this.tpl = new Ext.XTemplate(this.tpl).compile(); +}; +Ext.extend(Ext.data.XmlWriter, Ext.data.DataWriter, { + /** + * @cfg {String} root [records] The name of the root element when writing multiple records to the server. Each + * xml-record written to the server will be wrapped in an element named after {@link Ext.data.XmlReader#record} property. + * eg: +
        +<?xml version="1.0" encoding="UTF-8"?>
        +<user><first>Barney</first></user>
        +
        + * However, when multiple records are written in a batch-operation, these records must be wrapped in a containing + * Element. + * eg: +
        +<?xml version="1.0" encoding="UTF-8"?>
        +    <records>
        +        <first>Barney</first></user>
        +        <records><first>Barney</first></user>
        +    </records>
        +
        + * Defaults to records + */ + root: 'records', + /** + * @cfg {String} xmlVersion [1.0] The version written to header of xml documents. +
        <?xml version="1.0" encoding="ISO-8859-15"?>
        + */ + xmlVersion : '1.0', + /** + * @cfg {String} xmlEncoding [ISO-8859-15] The encoding written to header of xml documents. +
        <?xml version="1.0" encoding="ISO-8859-15"?>
        + */ + xmlEncoding: 'ISO-8859-15', + /** + * @cfg {String} tpl The xml template. Defaults to +
        +<?xml version="{version}" encoding="{encoding}"?>
        +    <tpl if="{[values.nodes.length>1]}"><{root}}>',
        +    <tpl for="records">
        +        <{parent.record}>
        +        <tpl for="fields">
        +            <{name}>{value}</{name}>
        +        </tpl>
        +        </{parent.record}>
        +    </tpl>
        +    <tpl if="{[values.records.length>1]}"></{root}}></tpl>
        +
        + */ + // Break up encoding here in case it's being included by some kind of page that will parse it (eg. PHP) + tpl: '<' + '?xml version="{version}" encoding="{encoding}"?' + '><{documentRoot}><{name}>{value}<{root}><{parent.record}><{name}>{value}', + /** * Final action of a write event. Apply the written data-object to params. * @param {String} action [Ext.data.Api.create|read|update|destroy] - * @param {Record[]} rs + * @param {Ext.data.Record/Ext.data.Record[]} rs * @param {Object} http params - * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty" + * @param {Object/Object[]} rendered data. */ render : function(action, rs, params, data) { - // no impl. + params.xmlData = this.tpl.applyTemplate({ + version: this.xmlVersion, + encoding: this.xmlEncoding, + record: this.meta.record, + root: this.root, + records: (Ext.isArray(rs)) ? data : [data] + }); + }, + + /** + * Converts an Ext.data.Record to xml + * @param {Ext.data.Record} rec + * @return {String} rendered xml-element + * @private + */ + toXml : function(data) { + var fields = []; + Ext.iterate(data, function(k, v) { + fields.push({ + name: k, + value: v + }); + },this); + return { + fields: fields + }; }, + /** * createRecord * @param {Ext.data.Record} rec + * @return {String} xml element + * @private */ createRecord : function(rec) { - // no impl + return this.toXml(this.toHash(rec)); }, + /** * updateRecord * @param {Ext.data.Record} rec + * @return {String} xml element + * @private */ updateRecord : function(rec) { - // no impl. + return this.toXml(this.toHash(rec)); }, /** * destroyRecord * @param {Ext.data.Record} rec + * @return {String} xml element */ destroyRecord : function(rec) { - // no impl + var data = {}; + data[this.meta.idProperty] = rec.id; + return this.toXml(data); } -});/** +}); + +/** * @class Ext.data.XmlReader * @extends Ext.data.DataReader *

        Data reader class to create an Array of {@link Ext.data.Record} objects from an XML document @@ -33728,9 +34684,10 @@ var Employee = Ext.data.Record.create([ {name: 'occupation'} // This field will use "occupation" as the mapping. ]); var myReader = new Ext.data.XmlReader({ - totalRecords: "results", // The element which contains the total dataset size (optional) + totalProperty: "results", // The element which contains the total dataset size (optional) record: "row", // The repeated element which contains row information - id: "id" // The element within the row that provides an ID for the record (optional) + idProperty: "id" // The element within the row that provides an ID for the record (optional) + messageProperty: "msg" // The element within the response that provides a user-feedback message (optional) }, Employee); *

        @@ -33751,11 +34708,12 @@ var myReader = new Ext.data.XmlReader({ </row> </dataset> - * @cfg {String} totalRecords The DomQuery path from which to retrieve the total number of records + * @cfg {String} totalProperty The DomQuery path 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. * @cfg {String} record The DomQuery path to the repeated element which contains record information. - * @cfg {String} success The DomQuery path to the success attribute used by forms. + * @cfg {String} record The DomQuery path to the repeated element which contains record information. + * @cfg {String} successProperty The DomQuery path to the success attribute used by forms. * @cfg {String} idPath The DomQuery path relative from the record element to the element that contains * a record identifier value. * @constructor @@ -33766,6 +34724,10 @@ var myReader = new Ext.data.XmlReader({ */ Ext.data.XmlReader = function(meta, recordType){ meta = meta || {}; + + // backwards compat, convert idPath to idProperty + meta.idProperty = meta.idProperty || meta.idPath; + Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields); }; Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, { @@ -33796,36 +34758,22 @@ Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, { * @type XMLDocument */ this.xmlData = doc; - var root = doc.documentElement || doc; - var q = Ext.DomQuery; - var recordType = this.recordType, fields = recordType.prototype.fields; - var sid = this.meta.idPath || this.meta.id; - var totalRecords = 0, success = true; - if(this.meta.totalRecords){ - totalRecords = q.selectNumber(this.meta.totalRecords, root, 0); - } - - if(this.meta.success){ - var sv = q.selectValue(this.meta.success, root, true); - success = sv !== false && sv !== 'false'; - } - var records = []; - var ns = q.select(this.meta.record, root); - for(var i = 0, len = ns.length; i < len; i++) { - var n = ns[i]; - var values = {}; - var id = sid ? q.selectValue(sid, n) : undefined; - for(var j = 0, jlen = fields.length; j < jlen; j++){ - var f = fields.items[j]; - var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue); - v = f.convert(v, n); - values[f.name] = v; - } - var record = new recordType(values, id); - record.node = n; - records[records.length] = record; + + var root = doc.documentElement || doc, + q = Ext.DomQuery, + totalRecords = 0, + success = true; + + if(this.meta.totalProperty){ + totalRecords = this.getTotal(root, 0); + } + if(this.meta.successProperty){ + success = this.getSuccess(root); } + var records = this.extractData(q.select(this.meta.record, root), true); // <-- true to return Ext.data.Record[] + + // TODO return Ext.data.Response instance. @see #readResponse return { success : success, records : records, @@ -33833,8 +34781,167 @@ Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, { }; }, - // TODO: implement readResponse for XmlReader - readResponse : Ext.emptyFn + /** + * Decode a json response from server. + * @param {String} action [{@link Ext.data.Api#actions} create|read|update|destroy] + * @param {Ext.data.Response} response Returns an instance of {@link Ext.data.Response} + */ + readResponse : function(action, response) { + var q = Ext.DomQuery, + doc = response.responseXML; + + var res = new Ext.data.Response({ + action: action, + success : this.getSuccess(doc), + message: this.getMessage(doc), + data: this.extractData(q.select(this.meta.record, doc) || q.select(this.meta.root, doc)), + raw: doc + }); + + if (Ext.isEmpty(res.success)) { + throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty); + } + + if (action === Ext.data.Api.actions.create) { + var def = Ext.isDefined(res.data); + if (def && Ext.isEmpty(res.data)) { + throw new Ext.data.JsonReader.Error('root-empty', this.meta.root); + } + else if (!def) { + throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root); + } + } + return res; + }, + + getSuccess : function() { + return true; + }, + + /** + * build response-data extractor functions. + * @private + * @ignore + */ + buildExtractors : function() { + if(this.ef){ + return; + } + var s = this.meta, + Record = this.recordType, + f = Record.prototype.fields, + fi = f.items, + fl = f.length; + + if(s.totalProperty) { + this.getTotal = this.createAccessor(s.totalProperty); + } + if(s.successProperty) { + this.getSuccess = this.createAccessor(s.successProperty); + } + if (s.messageProperty) { + this.getMessage = this.createAccessor(s.messageProperty); + } + this.getRoot = function(res) { + return (!Ext.isEmpty(res[this.meta.record])) ? res[this.meta.record] : res[this.meta.root]; + } + if (s.idPath || s.idProperty) { + var g = this.createAccessor(s.idPath || s.idProperty); + this.getId = function(rec) { + var id = g(rec) || rec.id; + return (id === undefined || id === '') ? null : id; + }; + } else { + this.getId = function(){return null;}; + } + var ef = []; + for(var i = 0; i < fl; i++){ + f = fi[i]; + var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name; + ef.push(this.createAccessor(map)); + } + this.ef = ef; + }, + + /** + * Creates a function to return some particular key of data from a response. + * @param {String} key + * @return {Function} + * @private + * @ignore + */ + createAccessor : function(){ + var q = Ext.DomQuery; + return function(key) { + switch(key) { + case this.meta.totalProperty: + return function(root, def){ + return q.selectNumber(key, root, def); + } + break; + case this.meta.successProperty: + return function(root, def) { + var sv = q.selectValue(key, root, true); + var success = sv !== false && sv !== 'false'; + return success; + } + break; + default: + return function(root, def) { + return q.selectValue(key, root, def); + } + break; + } + }; + }(), + + /** + * Extracts rows of record-data from server. iterates and calls #extractValues + * TODO I don't care much for method-names of #extractData, #extractValues. + * @param {Array} root + * @param {Boolean} returnRecords When true, will return instances of Ext.data.Record; otherwise just hashes. + * @private + * @ignore + */ + extractData : function(root, returnRecords) { + var Record = this.recordType, + records = [], + f = Record.prototype.fields, + fi = f.items, + fl = f.length; + if (returnRecords === true) { + for (var i = 0, len = root.length; i < len; i++) { + var data = root[i], + record = new Record(this.extractValues(data, fi, fl), this.getId(data)); + + record.node = data; + records.push(record); + } + } else { + for (var i = 0, len = root.length; i < len; i++) { + records.push(this.extractValues(root[i], fi, fl)); + } + } + return records; + }, + + /** + * extracts values and type-casts a row of data from server, extracted by #extractData + * @param {Hash} data + * @param {Ext.data.Field[]} items + * @param {Number} len + * @private + * @ignore + */ + extractValues : function(data, items, len) { + var f, values = {}; + for(var j = 0; j < len; j++){ + f = items[j]; + var v = this.ef[j](data); + values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data); + } + return values; + } });/** * @class Ext.data.XmlStore * @extends Ext.data.Store @@ -34083,31 +35190,35 @@ paramOrder: 'param1|param2|param' switch (action) { case Ext.data.Api.actions.create: - args.push(params[reader.meta.root]); // <-- create(Hash) + args.push(params.jsonData[reader.meta.root]); // <-- create(Hash) break; case Ext.data.Api.actions.read: - if(this.paramOrder){ - for(var i = 0, len = this.paramOrder.length; i < len; i++){ - args.push(params[this.paramOrder[i]]); + // If the method has no parameters, ignore the paramOrder/paramsAsHash. + if(directFn.directCfg.method.len > 0){ + if(this.paramOrder){ + for(var i = 0, len = this.paramOrder.length; i < len; i++){ + args.push(params[this.paramOrder[i]]); + } + }else if(this.paramsAsHash){ + args.push(params); } - }else if(this.paramsAsHash){ - args.push(params); } break; case Ext.data.Api.actions.update: - args.push(params[reader.meta.idProperty]); // <-- save(Integer/Integer[], Hash/Hash[]) - args.push(params[reader.meta.root]); + args.push(params.jsonData[reader.meta.root]); // <-- update(Hash/Hash[]) break; case Ext.data.Api.actions.destroy: - args.push(params[reader.meta.root]); // <-- destroy(Int/Int[]) + args.push(params.jsonData[reader.meta.root]); // <-- destroy(Int/Int[]) break; } var trans = { params : params || {}, - callback : callback, - scope : scope, - arg : options, + request: { + callback : callback, + scope : scope, + arg : options + }, reader: reader }; @@ -34124,7 +35235,7 @@ paramOrder: 'param1|param2|param' this.fireEvent("loadexception", this, trans, res, null); } this.fireEvent('exception', this, 'remote', action, trans, res, null); - trans.callback.call(trans.scope, null, trans.arg, false); + trans.request.callback.call(trans.request.scope, null, trans.request.arg, false); return; } if (action === Ext.data.Api.actions.read) { @@ -34151,11 +35262,11 @@ paramOrder: 'param1|param2|param' this.fireEvent("loadexception", this, trans, res, ex); this.fireEvent('exception', this, 'response', action, trans, res, ex); - trans.callback.call(trans.scope, null, trans.arg, false); + trans.request.callback.call(trans.request.scope, null, trans.request.arg, false); return; } - this.fireEvent("load", this, res, trans.arg); - trans.callback.call(trans.scope, records, trans.arg, true); + this.fireEvent("load", this, res, trans.request.arg); + trans.request.callback.call(trans.request.scope, records, trans.request.arg, true); }, /** * Callback for write actions @@ -34165,8 +35276,9 @@ paramOrder: 'param1|param2|param' * @private */ onWrite : function(action, trans, result, res, rs) { - this.fireEvent("write", this, action, result, res, rs, trans.arg); - trans.callback.call(trans.scope, result, res, true); + var data = trans.reader.extractData(result); + this.fireEvent("write", this, action, data, res, rs, trans.request.arg); + trans.request.callback.call(trans.request.scope, data, res, true); } }); @@ -34881,9 +35993,15 @@ TestAction.multiply( /** * @cfg {Number} maxRetries - * Number of times to re-attempt delivery on failure of a call. + * Number of times to re-attempt delivery on failure of a call. Defaults to 1. */ maxRetries: 1, + + /** + * @cfg {Number} timeout + * The timeout to use for each request. Defaults to undefined. + */ + timeout: undefined, constructor : function(config){ Ext.direct.RemotingProvider.superclass.constructor.call(this, config); @@ -34896,7 +36014,7 @@ TestAction.multiply( * @param {Ext.direct.RemotingProvider} provider * @param {Ext.Direct.Transaction} transaction */ - 'beforecall', + 'beforecall', /** * @event call * Fires immediately after the request to the server-side is sent. This does @@ -34906,7 +36024,7 @@ TestAction.multiply( */ 'call' ); - this.namespace = (typeof this.namespace === 'string') ? Ext.ns(this.namespace) : this.namespace || window; + this.namespace = (Ext.isString(this.namespace)) ? Ext.ns(this.namespace) : this.namespace || window; this.transactions = {}; this.callBuffer = []; }, @@ -34915,8 +36033,8 @@ TestAction.multiply( initAPI : function(){ var o = this.actions; for(var c in o){ - var cls = this.namespace[c] || (this.namespace[c] = {}); - var ms = o[c]; + var cls = this.namespace[c] || (this.namespace[c] = {}), + ms = o[c]; for(var i = 0, len = ms.length; i < len; i++){ var m = ms[i]; cls[m.name] = this.createMethod(c, m); @@ -34950,8 +36068,8 @@ TestAction.multiply( if(success){ var events = this.getEvents(xhr); for(var i = 0, len = events.length; i < len; i++){ - var e = events[i]; - var t = this.getTransaction(e); + var e = events[i], + t = this.getTransaction(e); this.fireEvent('data', this, e); if(t){ this.doCallback(t, e, true); @@ -34997,11 +36115,10 @@ TestAction.multiply( url: this.url, callback: this.onData, scope: this, - ts: data - }; + ts: data, + timeout: this.timeout + }, callData; - // send only needed data - var callData; if(Ext.isArray(data)){ callData = []; for(var i = 0, len = data.length; i < len; i++){ @@ -35013,7 +36130,7 @@ TestAction.multiply( if(this.enableUrlEncode){ var params = {}; - params[typeof this.enableUrlEncode == 'string' ? this.enableUrlEncode : 'data'] = Ext.encode(callData); + params[Ext.isString(this.enableUrlEncode) ? this.enableUrlEncode : 'data'] = Ext.encode(callData); o.params = params; }else{ o.jsonData = callData; @@ -35039,7 +36156,7 @@ TestAction.multiply( if(!this.callTask){ this.callTask = new Ext.util.DelayedTask(this.combineAndSend, this); } - this.callTask.delay(typeof this.enableBuffer == 'number' ? this.enableBuffer : 10); + this.callTask.delay(Ext.isNumber(this.enableBuffer) ? this.enableBuffer : 10); }else{ this.combineAndSend(); } @@ -35138,8 +36255,8 @@ TestAction.multiply( doCallback: function(t, e){ var fn = e.status ? 'success' : 'failure'; if(t && t.cb){ - var hs = t.cb; - var result = e.result || e.data; + var hs = t.cb, + result = Ext.isDefined(e.result) ? e.result : e.data; if(Ext.isFunction(hs)){ hs(result, e); } else{ @@ -35909,7 +37026,7 @@ Ext.Resizable.Handle.prototype = { *

        A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport, * restored to their prior size, and can be {@link #minimize}d.

        - *

        Windows can also be linked to a {@link Ext.WindowGroup} or managed by the {@link Ext.WindowMgr} to provide + *

        Windows can also be linked to a {@link Ext.WindowGroup} or managed by the {@link Ext.WindowMgr} to provide * grouping, activation, to front, to back and other application-specific behavior.

        *

        By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element * specify {@link Ext.Component#renderTo renderTo}.

        @@ -35961,15 +37078,15 @@ Ext.Window = Ext.extend(Ext.Panel, { */ /** * @cfg {Boolean} collapsed - * True to render the window collapsed, false to render it expanded (defaults to false). Note that if - * {@link #expandOnShow} is true (the default) it will override the collapsed config and the window + * True to render the window collapsed, false to render it expanded (defaults to false). Note that if + * {@link #expandOnShow} is true (the default) it will override the collapsed config and the window * will always be expanded when shown. */ /** * @cfg {Boolean} maximized * True to initially display the window in a maximized state. (Defaults to false). */ - + /** * @cfg {String} baseCls * The base CSS class to apply to this panel's element (defaults to 'x-window'). @@ -36021,15 +37138,15 @@ Ext.Window = Ext.extend(Ext.Panel, { /** * @cfg {Boolean} constrain * True to constrain the window within its containing element, false to allow it to fall outside of its - * containing element. By default the window will be rendered to document.body. To render and constrain the + * containing element. By default the window will be rendered to document.body. To render and constrain the * window within another element specify {@link #renderTo}. * (defaults to false). Optionally the header only can be constrained using {@link #constrainHeader}. */ constrain : false, /** * @cfg {Boolean} constrainHeader - * True to constrain the window header within its containing element (allowing the window body to fall outside - * of its containing element) or false to allow the header to fall outside its containing element (defaults to + * True to constrain the window header within its containing element (allowing the window body to fall outside + * of its containing element) or false to allow the header to fall outside its containing element (defaults to * false). Optionally the entire window can be constrained using {@link #constrain}. */ constrainHeader : false, @@ -36079,8 +37196,17 @@ Ext.Window = Ext.extend(Ext.Panel, { /** * @cfg {Boolean} initHidden * True to hide the window until show() is explicitly called (defaults to true). + * @deprecated + */ + initHidden : undefined, + + /** + * @cfg {Boolean} hidden + * Render this component hidden (default is true). If true, the + * {@link #hide} method will be called internally. */ - initHidden : true, + hidden : true, + /** * @cfg {Boolean} monitorResize @hide * This is automatically managed based on the value of constrain and constrainToHeader @@ -36100,16 +37226,17 @@ Ext.Window = Ext.extend(Ext.Panel, { // private initComponent : function(){ + this.initTools(); Ext.Window.superclass.initComponent.call(this); this.addEvents( /** * @event activate - * Fires after the window has been visually activated via {@link setActive}. + * Fires after the window has been visually activated via {@link #setActive}. * @param {Ext.Window} this */ /** * @event deactivate - * Fires after the window has been visually deactivated via {@link setActive}. + * Fires after the window has been visually deactivated via {@link #setActive}. * @param {Ext.Window} this */ /** @@ -36139,10 +37266,13 @@ Ext.Window = Ext.extend(Ext.Panel, { */ 'restore' ); - if(this.initHidden === false){ - this.show(); - }else{ + // for backwards compat, this should be removed at some point + if(Ext.isDefined(this.initHidden)){ + this.hidden = this.initHidden; + } + if(this.hidden === false){ this.hidden = true; + this.show(); } }, @@ -36174,7 +37304,9 @@ Ext.Window = Ext.extend(Ext.Panel, { this.mask.hide(); this.mon(this.mask, 'click', this.focus, this); } - this.initTools(); + if(this.maximizable){ + this.mon(this.header, 'dblclick', this.toggleMaximize, this); + } }, // private @@ -36273,7 +37405,6 @@ Ext.Window = Ext.extend(Ext.Panel, { handler: this.restore.createDelegate(this, []), hidden:true }); - this.mon(this.header, 'dblclick', this.toggleMaximize, this); } if(this.closable){ this.addTool({ @@ -36318,7 +37449,6 @@ Ext.Window = Ext.extend(Ext.Panel, { this.updateHandles(); this.saveState(); this.doLayout(); - this.fireEvent('resize', this, box.width, box.height); }, /** @@ -36327,10 +37457,10 @@ Ext.Window = Ext.extend(Ext.Panel, { */ focus : function(){ var f = this.focusEl, db = this.defaultButton, t = typeof db; - if(t != 'undefined'){ - if(t == 'number' && this.fbar){ + if(Ext.isDefined(db)){ + if(Ext.isNumber(db) && this.fbar){ f = this.fbar.items.get(db); - }else if(t == 'string'){ + }else if(Ext.isString(db)){ f = Ext.getCmp(db); }else{ f = db; @@ -36395,7 +37525,7 @@ Ext.Window = Ext.extend(Ext.Panel, { this.on('show', cb, scope, {single:true}); } this.hidden = false; - if(animateTarget !== undefined){ + if(Ext.isDefined(animateTarget)){ this.setAnimateTarget(animateTarget); } this.beforeShow(); @@ -36441,15 +37571,15 @@ Ext.Window = Ext.extend(Ext.Panel, { this.proxy.show(); this.proxy.setBox(this.animateTarget.getBox()); this.proxy.setOpacity(0); - var b = this.getBox(false); - b.callback = this.afterShow.createDelegate(this, [true], false); - b.scope = this; - b.duration = 0.25; - b.easing = 'easeNone'; - b.opacity = 0.5; - b.block = true; + var b = this.getBox(); this.el.setStyle('display', 'none'); - this.proxy.shift(b); + this.proxy.shift(Ext.apply(b, { + callback: this.afterShow.createDelegate(this, [true], false), + scope: this, + easing: 'easeNone', + duration: 0.25, + opacity: 0.5 + })); }, /** @@ -36503,14 +37633,13 @@ Ext.Window = Ext.extend(Ext.Panel, { var tb = this.getBox(false); this.proxy.setBox(tb); this.el.hide(); - var b = this.animateTarget.getBox(); - b.callback = this.afterHide; - b.scope = this; - b.duration = 0.25; - b.easing = 'easeNone'; - b.block = true; - b.opacity = 0; - this.proxy.shift(b); + this.proxy.shift(Ext.apply(this.animateTarget.getBox(), { + callback: this.afterHide, + scope: this, + duration: 0.25, + easing: 'easeNone', + opacity: 0 + })); }, // private @@ -36603,12 +37732,19 @@ Ext.Window = Ext.extend(Ext.Panel, { */ close : function(){ if(this.fireEvent('beforeclose', this) !== false){ - this.hide(null, function(){ - this.fireEvent('close', this); - this.destroy(); - }, this); + if(this.hidden){ + this.doClose(); + }else{ + this.hide(null, this.doClose, this); + } } }, + + // private + doClose : function(){ + this.fireEvent('close', this); + this.destroy(); + }, /** * Fits the window within its current container and automatically replaces @@ -36739,7 +37875,7 @@ Ext.Window = Ext.extend(Ext.Panel, { this.alignTo(el, alignment, offsets); }; Ext.EventManager.onWindowResize(this.doAnchor, this); - + var tm = typeof monitorScroll; if(tm != 'undefined'){ Ext.EventManager.on(window, 'scroll', this.doAnchor, this, @@ -36765,7 +37901,8 @@ Ext.Window = Ext.extend(Ext.Panel, { /** * Makes this the active window by showing its shadow, or deactivates it by hiding its shadow. This method also - * fires the {@link #activate} or {@link #deactivate} event depending on which action occurred. + * fires the {@link #activate} or {@link #deactivate} event depending on which action occurred. This method is + * called internally by {@link Ext.WindowMgr}. * @param {Boolean} active True to activate the window, false to deactivate it (defaults to false) */ setActive : function(active){ @@ -37018,698 +38155,704 @@ Ext.WindowGroup = function(){ * with separate z-order stacks, create additional instances of {@link Ext.WindowGroup} as needed. * @singleton */ -Ext.WindowMgr = new Ext.WindowGroup();/** - * @class Ext.MessageBox - *

        Utility class for generating different styles of message boxes. The alias Ext.Msg can also be used.

        - *

        Note that the MessageBox is asynchronous. Unlike a regular JavaScript alert (which will halt - * browser execution), showing a MessageBox will not cause the code to stop. For this reason, if you have code - * that should only run after some user feedback from the MessageBox, you must use a callback function - * (see the function parameter for {@link #show} for more details).

        - *

        Example usage:

        - *
        
        -// Basic alert:
        -Ext.Msg.alert('Status', 'Changes saved successfully.');
        -
        -// Prompt for user data and process the result using a callback:
        -Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
        -    if (btn == 'ok'){
        -        // process text value and close...
        -    }
        -});
        -
        -// Show a dialog using config options:
        -Ext.Msg.show({
        -   title:'Save Changes?',
        -   msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
        -   buttons: Ext.Msg.YESNOCANCEL,
        -   fn: processResult,
        -   animEl: 'elId',
        -   icon: Ext.MessageBox.QUESTION
        -});
        -
        - * @singleton - */ -Ext.MessageBox = function(){ - var dlg, opt, mask, waitTimer; - var bodyEl, msgEl, textboxEl, textareaEl, progressBar, pp, iconEl, spacerEl; - var buttons, activeTextEl, bwidth, iconCls = ''; - - // private - var handleButton = function(button){ - if(dlg.isVisible()){ - dlg.hide(); - handleHide(); - Ext.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value, opt], 1); - } - }; - - // private - var handleHide = function(){ - if(opt && opt.cls){ - dlg.el.removeClass(opt.cls); - } - progressBar.reset(); - }; - - // private - var handleEsc = function(d, k, e){ - if(opt && opt.closable !== false){ - dlg.hide(); - handleHide(); - } - if(e){ - e.stopEvent(); - } - }; - - // private - var updateButtons = function(b){ - var width = 0; - if(!b){ - buttons["ok"].hide(); - buttons["cancel"].hide(); - buttons["yes"].hide(); - buttons["no"].hide(); - return width; - } - dlg.footer.dom.style.display = ''; - for(var k in buttons){ - if(typeof buttons[k] != "function"){ - if(b[k]){ - buttons[k].show(); - buttons[k].setText(typeof b[k] == "string" ? b[k] : Ext.MessageBox.buttonText[k]); - width += buttons[k].el.getWidth()+15; - }else{ - buttons[k].hide(); - } - } - } - return width; - }; - - return { - /** - * Returns a reference to the underlying {@link Ext.Window} element - * @return {Ext.Window} The window - */ - getDialog : function(titleText){ - if(!dlg){ - dlg = new Ext.Window({ - autoCreate : true, - title:titleText, - resizable:false, - constrain:true, - constrainHeader:true, - minimizable : false, - maximizable : false, - stateful: false, - modal: true, - shim:true, - buttonAlign:"center", - width:400, - height:100, - minHeight: 80, - plain:true, - footer:true, - closable:true, - close : function(){ - if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){ - handleButton("no"); - }else{ - handleButton("cancel"); - } - } - }); - buttons = {}; - var bt = this.buttonText; - //TODO: refactor this block into a buttons config to pass into the Window constructor - buttons["ok"] = dlg.addButton(bt["ok"], handleButton.createCallback("ok")); - buttons["yes"] = dlg.addButton(bt["yes"], handleButton.createCallback("yes")); - buttons["no"] = dlg.addButton(bt["no"], handleButton.createCallback("no")); - buttons["cancel"] = dlg.addButton(bt["cancel"], handleButton.createCallback("cancel")); - buttons["ok"].hideMode = buttons["yes"].hideMode = buttons["no"].hideMode = buttons["cancel"].hideMode = 'offsets'; - dlg.render(document.body); - dlg.getEl().addClass('x-window-dlg'); - mask = dlg.mask; - bodyEl = dlg.body.createChild({ - html:'

        ' - }); - iconEl = Ext.get(bodyEl.dom.firstChild); - var contentEl = bodyEl.dom.childNodes[1]; - msgEl = Ext.get(contentEl.firstChild); - textboxEl = Ext.get(contentEl.childNodes[2].firstChild); - textboxEl.enableDisplayMode(); - textboxEl.addKeyListener([10,13], function(){ - if(dlg.isVisible() && opt && opt.buttons){ - if(opt.buttons.ok){ - handleButton("ok"); - }else if(opt.buttons.yes){ - handleButton("yes"); - } - } - }); - textareaEl = Ext.get(contentEl.childNodes[2].childNodes[1]); - textareaEl.enableDisplayMode(); - progressBar = new Ext.ProgressBar({ - renderTo:bodyEl - }); - bodyEl.createChild({cls:'x-clear'}); - } - return dlg; - }, - - /** - * Updates the message box body text - * @param {String} text (optional) Replaces the message box element's innerHTML with the specified string (defaults to - * the XHTML-compliant non-breaking space character '&#160;') - * @return {Ext.MessageBox} this - */ - updateText : function(text){ - if(!dlg.isVisible() && !opt.width){ - dlg.setSize(this.maxWidth, 100); // resize first so content is never clipped from previous shows - } - msgEl.update(text || ' '); - - var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0; - var mw = msgEl.getWidth() + msgEl.getMargins('lr'); - var fw = dlg.getFrameWidth('lr'); - var bw = dlg.body.getFrameWidth('lr'); - if (Ext.isIE && iw > 0){ - //3 pixels get subtracted in the icon CSS for an IE margin issue, - //so we have to add it back here for the overall width to be consistent - iw += 3; - } - var w = Math.max(Math.min(opt.width || iw+mw+fw+bw, this.maxWidth), - Math.max(opt.minWidth || this.minWidth, bwidth || 0)); - - if(opt.prompt === true){ - activeTextEl.setWidth(w-iw-fw-bw); - } - if(opt.progress === true || opt.wait === true){ - progressBar.setSize(w-iw-fw-bw); - } - if(Ext.isIE && w == bwidth){ - w += 4; //Add offset when the content width is smaller than the buttons. - } - dlg.setSize(w, 'auto').center(); - return this; - }, - - /** - * Updates a progress-style message box's text and progress bar. Only relevant on message boxes - * initiated via {@link Ext.MessageBox#progress} or {@link Ext.MessageBox#wait}, - * or by calling {@link Ext.MessageBox#show} with progress: true. - * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0) - * @param {String} progressText The progress text to display inside the progress bar (defaults to '') - * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined - * so that any existing body text will not get overwritten by default unless a new value is passed in) - * @return {Ext.MessageBox} this - */ - updateProgress : function(value, progressText, msg){ - progressBar.updateProgress(value, progressText); - if(msg){ - this.updateText(msg); - } - return this; - }, - - /** - * Returns true if the message box is currently displayed - * @return {Boolean} True if the message box is visible, else false - */ - isVisible : function(){ - return dlg && dlg.isVisible(); - }, - - /** - * Hides the message box if it is displayed - * @return {Ext.MessageBox} this - */ - hide : function(){ - var proxy = dlg ? dlg.activeGhost : null; - if(this.isVisible() || proxy){ - dlg.hide(); - handleHide(); - if (proxy){ - // unghost is a private function, but i saw no better solution - // to fix the locking problem when dragging while it closes - dlg.unghost(false, false); - } - } - return this; - }, - - /** - * Displays a new message box, or reinitializes an existing message box, based on the config options - * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally, - * although those calls are basic shortcuts and do not support all of the config options allowed here. - * @param {Object} config The following config options are supported:
          - *
        • animEl : String/Element
          An id or Element from which the message box should animate as it - * opens and closes (defaults to undefined)
        • - *
        • buttons : Object/Boolean
          A button config object (e.g., Ext.MessageBox.OKCANCEL or {ok:'Foo', - * cancel:'Bar'}), or false to not show any buttons (defaults to false)
        • - *
        • closable : Boolean
          False to hide the top-right close button (defaults to true). Note that - * progress and wait dialogs will ignore this property and always hide the close button as they can only - * be closed programmatically.
        • - *
        • cls : String
          A custom CSS class to apply to the message box's container element
        • - *
        • defaultTextHeight : Number
          The default height in pixels of the message box's multiline textarea - * if displayed (defaults to 75)
        • - *
        • fn : Function
          A callback function which is called when the dialog is dismissed either - * by clicking on the configured buttons, or on the dialog close button, or by pressing - * the return button to enter input. - *

          Progress and wait dialogs will ignore this option since they do not respond to user - * actions and can only be closed programmatically, so any required function should be called - * by the same code after it closes the dialog. Parameters passed:

            - *
          • buttonId : String
            The ID of the button pressed, one of:
              - *
            • ok
            • - *
            • yes
            • - *
            • no
            • - *
            • cancel
            • - *
          • - *
          • text : String
            Value of the input field if either prompt - * or multiline is true
          • - *
          • opt : Object
            The config object passed to show.
          • - *

        • - *
        • scope : Object
          The scope of the callback function
        • - *
        • icon : String
          A CSS class that provides a background image to be used as the body icon for the - * dialog (e.g. Ext.MessageBox.WARNING or 'custom-class') (defaults to '')
        • - *
        • iconCls : String
          The standard {@link Ext.Window#iconCls} to - * add an optional header icon (defaults to '')
        • - *
        • maxWidth : Number
          The maximum width in pixels of the message box (defaults to 600)
        • - *
        • minWidth : Number
          The minimum width in pixels of the message box (defaults to 100)
        • - *
        • modal : Boolean
          False to allow user interaction with the page while the message box is - * displayed (defaults to true)
        • - *
        • msg : String
          A string that will replace the existing message box body text (defaults to the - * XHTML-compliant non-breaking space character '&#160;')
        • - *
        • multiline : Boolean
          - * True to prompt the user to enter multi-line text (defaults to false)
        • - *
        • progress : Boolean
          True to display a progress bar (defaults to false)
        • - *
        • progressText : String
          The text to display inside the progress bar if progress = true (defaults to '')
        • - *
        • prompt : Boolean
          True to prompt the user to enter single-line text (defaults to false)
        • - *
        • proxyDrag : Boolean
          True to display a lightweight proxy while dragging (defaults to false)
        • - *
        • title : String
          The title text
        • - *
        • value : String
          The string value to set into the active textbox element if displayed
        • - *
        • wait : Boolean
          True to display a progress bar (defaults to false)
        • - *
        • waitConfig : Object
          A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)
        • - *
        • width : Number
          The width of the dialog in pixels
        • - *
        - * Example usage: - *
        
        -Ext.Msg.show({
        -   title: 'Address',
        -   msg: 'Please enter your address:',
        -   width: 300,
        -   buttons: Ext.MessageBox.OKCANCEL,
        -   multiline: true,
        -   fn: saveAddress,
        -   animEl: 'addAddressBtn',
        -   icon: Ext.MessageBox.INFO
        -});
        -
        - * @return {Ext.MessageBox} this - */ - show : function(options){ - if(this.isVisible()){ - this.hide(); - } - opt = options; - var d = this.getDialog(opt.title || " "); - - d.setTitle(opt.title || " "); - var allowClose = (opt.closable !== false && opt.progress !== true && opt.wait !== true); - d.tools.close.setDisplayed(allowClose); - activeTextEl = textboxEl; - opt.prompt = opt.prompt || (opt.multiline ? true : false); - if(opt.prompt){ - if(opt.multiline){ - textboxEl.hide(); - textareaEl.show(); - textareaEl.setHeight(typeof opt.multiline == "number" ? - opt.multiline : this.defaultTextHeight); - activeTextEl = textareaEl; - }else{ - textboxEl.show(); - textareaEl.hide(); - } - }else{ - textboxEl.hide(); - textareaEl.hide(); - } - activeTextEl.dom.value = opt.value || ""; - if(opt.prompt){ - d.focusEl = activeTextEl; - }else{ - var bs = opt.buttons; - var db = null; - if(bs && bs.ok){ - db = buttons["ok"]; - }else if(bs && bs.yes){ - db = buttons["yes"]; - } - if (db){ - d.focusEl = db; - } - } - if(opt.iconCls){ - d.setIconClass(opt.iconCls); - } - this.setIcon(opt.icon); - if(opt.cls){ - d.el.addClass(opt.cls); - } - d.proxyDrag = opt.proxyDrag === true; - d.modal = opt.modal !== false; - d.mask = opt.modal !== false ? mask : false; - - d.on('show', function(){ - //workaround for window internally enabling keymap in afterShow - d.keyMap.setDisabled(allowClose !== true); - d.doLayout(); - this.setIcon(opt.icon); - bwidth = updateButtons(opt.buttons); - progressBar.setVisible(opt.progress === true || opt.wait === true); - this.updateProgress(0, opt.progressText); - this.updateText(opt.msg); - if(opt.wait === true){ - progressBar.wait(opt.waitConfig); - } - - }, this, {single:true}); - if(!d.isVisible()){ - // force it to the end of the z-index stack so it gets a cursor in FF - document.body.appendChild(dlg.el.dom); - d.setAnimateTarget(opt.animEl); - d.show(opt.animEl); - } - return this; - }, - - /** - * Adds the specified icon to the dialog. By default, the class 'ext-mb-icon' is applied for default - * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('') - * to clear any existing icon. The following built-in icon classes are supported, but you can also pass - * in a custom class name: - *
        -Ext.MessageBox.INFO
        -Ext.MessageBox.WARNING
        -Ext.MessageBox.QUESTION
        -Ext.MessageBox.ERROR
        -         *
        - * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon - * @return {Ext.MessageBox} this - */ - setIcon : function(icon){ - if(icon && icon != ''){ - iconEl.removeClass('x-hidden'); - iconEl.replaceClass(iconCls, icon); - bodyEl.addClass('x-dlg-icon'); - iconCls = icon; - }else{ - iconEl.replaceClass(iconCls, 'x-hidden'); - bodyEl.removeClass('x-dlg-icon'); - iconCls = ''; - } - return this; - }, - - /** - * Displays a message box with a progress bar. This message box has no buttons and is not closeable by - * the user. You are responsible for updating the progress bar as needed via {@link Ext.MessageBox#updateProgress} - * and closing the message box when the process is complete. - * @param {String} title The title bar text - * @param {String} msg The message box body text - * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '') - * @return {Ext.MessageBox} this - */ - progress : function(title, msg, progressText){ - this.show({ - title : title, - msg : msg, - buttons: false, - progress:true, - closable:false, - minWidth: this.minProgressWidth, - progressText: progressText - }); - return this; - }, - - /** - * Displays a message box with an infinitely auto-updating progress bar. This can be used to block user - * interaction while waiting for a long-running process to complete that does not have defined intervals. - * You are responsible for closing the message box when the process is complete. - * @param {String} msg The message box body text - * @param {String} title (optional) The title bar text - * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object - * @return {Ext.MessageBox} this - */ - wait : function(msg, title, config){ - this.show({ - title : title, - msg : msg, - buttons: false, - closable:false, - wait:true, - modal:true, - minWidth: this.minProgressWidth, - waitConfig: config - }); - return this; - }, - - /** - * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt). - * If a callback function is passed it will be called after the user clicks the button, and the - * id of the button that was clicked will be passed as the only parameter to the callback - * (could also be the top-right close button). - * @param {String} title The title bar text - * @param {String} msg The message box body text - * @param {Function} fn (optional) The callback function invoked after the message box is closed - * @param {Object} scope (optional) The scope of the callback function - * @return {Ext.MessageBox} this - */ - alert : function(title, msg, fn, scope){ - this.show({ - title : title, - msg : msg, - buttons: this.OK, - fn: fn, - scope : scope - }); - return this; - }, - - /** - * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm). - * If a callback function is passed it will be called after the user clicks either button, - * and the id of the button that was clicked will be passed as the only parameter to the callback - * (could also be the top-right close button). - * @param {String} title The title bar text - * @param {String} msg The message box body text - * @param {Function} fn (optional) The callback function invoked after the message box is closed - * @param {Object} scope (optional) The scope of the callback function - * @return {Ext.MessageBox} this - */ - confirm : function(title, msg, fn, scope){ - this.show({ - title : title, - msg : msg, - buttons: this.YESNO, - fn: fn, - scope : scope, - icon: this.QUESTION - }); - return this; - }, - - /** - * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt). - * The prompt can be a single-line or multi-line textbox. If a callback function is passed it will be called after the user - * clicks either button, and the id of the button that was clicked (could also be the top-right - * close button) and the text that was entered will be passed as the two parameters to the callback. - * @param {String} title The title bar text - * @param {String} msg The message box body text - * @param {Function} fn (optional) The callback function invoked after the message box is closed - * @param {Object} scope (optional) The scope of the callback function - * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight - * property, or the height in pixels to create the textbox (defaults to false / single-line) - * @param {String} value (optional) Default value of the text input element (defaults to '') - * @return {Ext.MessageBox} this - */ - prompt : function(title, msg, fn, scope, multiline, value){ - this.show({ - title : title, - msg : msg, - buttons: this.OKCANCEL, - fn: fn, - minWidth:250, - scope : scope, - prompt:true, - multiline: multiline, - value: value - }); - return this; - }, - - /** - * Button config that displays a single OK button - * @type Object - */ - OK : {ok:true}, - /** - * Button config that displays a single Cancel button - * @type Object - */ - CANCEL : {cancel:true}, - /** - * Button config that displays OK and Cancel buttons - * @type Object - */ - OKCANCEL : {ok:true, cancel:true}, - /** - * Button config that displays Yes and No buttons - * @type Object - */ - YESNO : {yes:true, no:true}, - /** - * Button config that displays Yes, No and Cancel buttons - * @type Object - */ - YESNOCANCEL : {yes:true, no:true, cancel:true}, - /** - * The CSS class that provides the INFO icon image - * @type String - */ - INFO : 'ext-mb-info', - /** - * The CSS class that provides the WARNING icon image - * @type String - */ - WARNING : 'ext-mb-warning', - /** - * The CSS class that provides the QUESTION icon image - * @type String - */ - QUESTION : 'ext-mb-question', - /** - * The CSS class that provides the ERROR icon image - * @type String - */ - ERROR : 'ext-mb-error', - - /** - * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75) - * @type Number - */ - defaultTextHeight : 75, - /** - * The maximum width in pixels of the message box (defaults to 600) - * @type Number - */ - maxWidth : 600, - /** - * The minimum width in pixels of the message box (defaults to 110) - * @type Number - */ - minWidth : 110, - /** - * The minimum width in pixels of the message box if it is a progress-style dialog. This is useful - * for setting a different minimum width than text-only dialogs may need (defaults to 250) - * @type Number - */ - minProgressWidth : 250, - /** - * An object containing the default button text strings that can be overriden for localized language support. - * Supported properties are: ok, cancel, yes and no. Generally you should include a locale-specific - * resource file for handling language support across the framework. - * Customize the default text like so: Ext.MessageBox.buttonText.yes = "oui"; //french - * @type Object - */ - buttonText : { - ok : "OK", - cancel : "Cancel", - yes : "Yes", - no : "No" - } - }; -}(); - -/** - * Shorthand for {@link Ext.MessageBox} - */ -Ext.Msg = Ext.MessageBox;/** - * @class Ext.dd.PanelProxy - * A custom drag proxy implementation specific to {@link Ext.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} to proxy for - * @param config Configuration options - */ -Ext.dd.PanelProxy = function(panel, config){ - this.panel = panel; - this.id = this.panel.id +'-ddproxy'; - Ext.apply(this, config); -}; - -Ext.dd.PanelProxy.prototype = { - /** - * @cfg {Boolean} insertProxy True to insert a placeholder proxy element while dragging the panel, - * false to drag with no proxy (defaults to true). - */ - insertProxy : true, +Ext.WindowMgr = new Ext.WindowGroup();/** + * @class Ext.MessageBox + *

        Utility class for generating different styles of message boxes. The alias Ext.Msg can also be used.

        + *

        Note that the MessageBox is asynchronous. Unlike a regular JavaScript alert (which will halt + * browser execution), showing a MessageBox will not cause the code to stop. For this reason, if you have code + * that should only run after some user feedback from the MessageBox, you must use a callback function + * (see the function parameter for {@link #show} for more details).

        + *

        Example usage:

        + *
        
        +// Basic alert:
        +Ext.Msg.alert('Status', 'Changes saved successfully.');
        +
        +// Prompt for user data and process the result using a callback:
        +Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
        +    if (btn == 'ok'){
        +        // process text value and close...
        +    }
        +});
         
        -    // private overrides
        -    setStatus : Ext.emptyFn,
        -    reset : Ext.emptyFn,
        -    update : Ext.emptyFn,
        -    stop : Ext.emptyFn,
        -    sync: Ext.emptyFn,
        +// Show a dialog using config options:
        +Ext.Msg.show({
        +   title:'Save Changes?',
        +   msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
        +   buttons: Ext.Msg.YESNOCANCEL,
        +   fn: processResult,
        +   animEl: 'elId',
        +   icon: Ext.MessageBox.QUESTION
        +});
        +
        + * @singleton + */ +Ext.MessageBox = function(){ + var dlg, opt, mask, waitTimer, + bodyEl, msgEl, textboxEl, textareaEl, progressBar, pp, iconEl, spacerEl, + buttons, activeTextEl, bwidth, bufferIcon = '', iconCls = ''; - /** - * Gets the proxy's element - * @return {Element} The proxy's element - */ - getEl : function(){ - return this.ghost; - }, + // private + var handleButton = function(button){ + if(dlg.isVisible()){ + dlg.hide(); + handleHide(); + Ext.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value, opt], 1); + } + }; - /** - * Gets the proxy's ghost element - * @return {Element} The proxy's ghost element - */ - getGhost : function(){ - return this.ghost; - }, + // private + var handleHide = function(){ + if(opt && opt.cls){ + dlg.el.removeClass(opt.cls); + } + progressBar.reset(); + }; - /** - * Gets the proxy's element - * @return {Element} The proxy's element - */ - getProxy : function(){ - return this.proxy; - }, + // private + var handleEsc = function(d, k, e){ + if(opt && opt.closable !== false){ + dlg.hide(); + handleHide(); + } + if(e){ + e.stopEvent(); + } + }; - /** - * Hides the proxy - */ - hide : function(){ - if(this.ghost){ - if(this.proxy){ - this.proxy.remove(); - delete this.proxy; + // private + var updateButtons = function(b){ + var width = 0; + if(!b){ + buttons["ok"].hide(); + buttons["cancel"].hide(); + buttons["yes"].hide(); + buttons["no"].hide(); + return width; + } + dlg.footer.dom.style.display = ''; + for(var k in buttons){ + if(!Ext.isFunction(buttons[k])){ + if(b[k]){ + buttons[k].show(); + buttons[k].setText(Ext.isString(b[k]) ? b[k] : Ext.MessageBox.buttonText[k]); + width += buttons[k].el.getWidth()+15; + }else{ + buttons[k].hide(); + } } - this.panel.el.dom.style.display = ''; - this.ghost.remove(); - delete this.ghost; } - }, + return width; + }; - /** - * Shows the proxy - */ - show : function(){ - if(!this.ghost){ - this.ghost = this.panel.createGhost(undefined, undefined, Ext.getBody()); + return { + /** + * Returns a reference to the underlying {@link Ext.Window} element + * @return {Ext.Window} The window + */ + getDialog : function(titleText){ + if(!dlg){ + dlg = new Ext.Window({ + autoCreate : true, + title:titleText, + resizable:false, + constrain:true, + constrainHeader:true, + minimizable : false, + maximizable : false, + stateful: false, + modal: true, + shim:true, + buttonAlign:"center", + width:400, + height:100, + minHeight: 80, + plain:true, + footer:true, + closable:true, + close : function(){ + if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){ + handleButton("no"); + }else{ + handleButton("cancel"); + } + } + }); + buttons = {}; + var bt = this.buttonText; + //TODO: refactor this block into a buttons config to pass into the Window constructor + buttons["ok"] = dlg.addButton(bt["ok"], handleButton.createCallback("ok")); + buttons["yes"] = dlg.addButton(bt["yes"], handleButton.createCallback("yes")); + buttons["no"] = dlg.addButton(bt["no"], handleButton.createCallback("no")); + buttons["cancel"] = dlg.addButton(bt["cancel"], handleButton.createCallback("cancel")); + buttons["ok"].hideMode = buttons["yes"].hideMode = buttons["no"].hideMode = buttons["cancel"].hideMode = 'offsets'; + dlg.render(document.body); + dlg.getEl().addClass('x-window-dlg'); + mask = dlg.mask; + bodyEl = dlg.body.createChild({ + html:'

        ' + }); + iconEl = Ext.get(bodyEl.dom.firstChild); + var contentEl = bodyEl.dom.childNodes[1]; + msgEl = Ext.get(contentEl.firstChild); + textboxEl = Ext.get(contentEl.childNodes[2].firstChild); + textboxEl.enableDisplayMode(); + textboxEl.addKeyListener([10,13], function(){ + if(dlg.isVisible() && opt && opt.buttons){ + if(opt.buttons.ok){ + handleButton("ok"); + }else if(opt.buttons.yes){ + handleButton("yes"); + } + } + }); + textareaEl = Ext.get(contentEl.childNodes[2].childNodes[1]); + textareaEl.enableDisplayMode(); + progressBar = new Ext.ProgressBar({ + renderTo:bodyEl + }); + bodyEl.createChild({cls:'x-clear'}); + } + return dlg; + }, + + /** + * Updates the message box body text + * @param {String} text (optional) Replaces the message box element's innerHTML with the specified string (defaults to + * the XHTML-compliant non-breaking space character '&#160;') + * @return {Ext.MessageBox} this + */ + updateText : function(text){ + if(!dlg.isVisible() && !opt.width){ + dlg.setSize(this.maxWidth, 100); // resize first so content is never clipped from previous shows + } + msgEl.update(text || ' '); + + var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0; + var mw = msgEl.getWidth() + msgEl.getMargins('lr'); + var fw = dlg.getFrameWidth('lr'); + var bw = dlg.body.getFrameWidth('lr'); + if (Ext.isIE && iw > 0){ + //3 pixels get subtracted in the icon CSS for an IE margin issue, + //so we have to add it back here for the overall width to be consistent + iw += 3; + } + var w = Math.max(Math.min(opt.width || iw+mw+fw+bw, this.maxWidth), + Math.max(opt.minWidth || this.minWidth, bwidth || 0)); + + if(opt.prompt === true){ + activeTextEl.setWidth(w-iw-fw-bw); + } + if(opt.progress === true || opt.wait === true){ + progressBar.setSize(w-iw-fw-bw); + } + if(Ext.isIE && w == bwidth){ + w += 4; //Add offset when the content width is smaller than the buttons. + } + dlg.setSize(w, 'auto').center(); + return this; + }, + + /** + * Updates a progress-style message box's text and progress bar. Only relevant on message boxes + * initiated via {@link Ext.MessageBox#progress} or {@link Ext.MessageBox#wait}, + * or by calling {@link Ext.MessageBox#show} with progress: true. + * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0) + * @param {String} progressText The progress text to display inside the progress bar (defaults to '') + * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined + * so that any existing body text will not get overwritten by default unless a new value is passed in) + * @return {Ext.MessageBox} this + */ + updateProgress : function(value, progressText, msg){ + progressBar.updateProgress(value, progressText); + if(msg){ + this.updateText(msg); + } + return this; + }, + + /** + * Returns true if the message box is currently displayed + * @return {Boolean} True if the message box is visible, else false + */ + isVisible : function(){ + return dlg && dlg.isVisible(); + }, + + /** + * Hides the message box if it is displayed + * @return {Ext.MessageBox} this + */ + hide : function(){ + var proxy = dlg ? dlg.activeGhost : null; + if(this.isVisible() || proxy){ + dlg.hide(); + handleHide(); + if (proxy){ + // unghost is a private function, but i saw no better solution + // to fix the locking problem when dragging while it closes + dlg.unghost(false, false); + } + } + return this; + }, + + /** + * Displays a new message box, or reinitializes an existing message box, based on the config options + * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally, + * although those calls are basic shortcuts and do not support all of the config options allowed here. + * @param {Object} config The following config options are supported:
          + *
        • animEl : String/Element
          An id or Element from which the message box should animate as it + * opens and closes (defaults to undefined)
        • + *
        • buttons : Object/Boolean
          A button config object (e.g., Ext.MessageBox.OKCANCEL or {ok:'Foo', + * cancel:'Bar'}), or false to not show any buttons (defaults to false)
        • + *
        • closable : Boolean
          False to hide the top-right close button (defaults to true). Note that + * progress and wait dialogs will ignore this property and always hide the close button as they can only + * be closed programmatically.
        • + *
        • cls : String
          A custom CSS class to apply to the message box's container element
        • + *
        • defaultTextHeight : Number
          The default height in pixels of the message box's multiline textarea + * if displayed (defaults to 75)
        • + *
        • fn : Function
          A callback function which is called when the dialog is dismissed either + * by clicking on the configured buttons, or on the dialog close button, or by pressing + * the return button to enter input. + *

          Progress and wait dialogs will ignore this option since they do not respond to user + * actions and can only be closed programmatically, so any required function should be called + * by the same code after it closes the dialog. Parameters passed:

            + *
          • buttonId : String
            The ID of the button pressed, one of:
              + *
            • ok
            • + *
            • yes
            • + *
            • no
            • + *
            • cancel
            • + *
          • + *
          • text : String
            Value of the input field if either prompt + * or multiline is true
          • + *
          • opt : Object
            The config object passed to show.
          • + *

        • + *
        • scope : Object
          The scope of the callback function
        • + *
        • icon : String
          A CSS class that provides a background image to be used as the body icon for the + * dialog (e.g. Ext.MessageBox.WARNING or 'custom-class') (defaults to '')
        • + *
        • iconCls : String
          The standard {@link Ext.Window#iconCls} to + * add an optional header icon (defaults to '')
        • + *
        • maxWidth : Number
          The maximum width in pixels of the message box (defaults to 600)
        • + *
        • minWidth : Number
          The minimum width in pixels of the message box (defaults to 100)
        • + *
        • modal : Boolean
          False to allow user interaction with the page while the message box is + * displayed (defaults to true)
        • + *
        • msg : String
          A string that will replace the existing message box body text (defaults to the + * XHTML-compliant non-breaking space character '&#160;')
        • + *
        • multiline : Boolean
          + * True to prompt the user to enter multi-line text (defaults to false)
        • + *
        • progress : Boolean
          True to display a progress bar (defaults to false)
        • + *
        • progressText : String
          The text to display inside the progress bar if progress = true (defaults to '')
        • + *
        • prompt : Boolean
          True to prompt the user to enter single-line text (defaults to false)
        • + *
        • proxyDrag : Boolean
          True to display a lightweight proxy while dragging (defaults to false)
        • + *
        • title : String
          The title text
        • + *
        • value : String
          The string value to set into the active textbox element if displayed
        • + *
        • wait : Boolean
          True to display a progress bar (defaults to false)
        • + *
        • waitConfig : Object
          A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)
        • + *
        • width : Number
          The width of the dialog in pixels
        • + *
        + * Example usage: + *
        
        +Ext.Msg.show({
        +   title: 'Address',
        +   msg: 'Please enter your address:',
        +   width: 300,
        +   buttons: Ext.MessageBox.OKCANCEL,
        +   multiline: true,
        +   fn: saveAddress,
        +   animEl: 'addAddressBtn',
        +   icon: Ext.MessageBox.INFO
        +});
        +
        + * @return {Ext.MessageBox} this + */ + show : function(options){ + if(this.isVisible()){ + this.hide(); + } + opt = options; + var d = this.getDialog(opt.title || " "); + + d.setTitle(opt.title || " "); + var allowClose = (opt.closable !== false && opt.progress !== true && opt.wait !== true); + d.tools.close.setDisplayed(allowClose); + activeTextEl = textboxEl; + opt.prompt = opt.prompt || (opt.multiline ? true : false); + if(opt.prompt){ + if(opt.multiline){ + textboxEl.hide(); + textareaEl.show(); + textareaEl.setHeight(Ext.isNumber(opt.multiline) ? opt.multiline : this.defaultTextHeight); + activeTextEl = textareaEl; + }else{ + textboxEl.show(); + textareaEl.hide(); + } + }else{ + textboxEl.hide(); + textareaEl.hide(); + } + activeTextEl.dom.value = opt.value || ""; + if(opt.prompt){ + d.focusEl = activeTextEl; + }else{ + var bs = opt.buttons; + var db = null; + if(bs && bs.ok){ + db = buttons["ok"]; + }else if(bs && bs.yes){ + db = buttons["yes"]; + } + if (db){ + d.focusEl = db; + } + } + if(opt.iconCls){ + d.setIconClass(opt.iconCls); + } + this.setIcon(Ext.isDefined(opt.icon) ? opt.icon : bufferIcon); + bwidth = updateButtons(opt.buttons); + progressBar.setVisible(opt.progress === true || opt.wait === true); + this.updateProgress(0, opt.progressText); + this.updateText(opt.msg); + if(opt.cls){ + d.el.addClass(opt.cls); + } + d.proxyDrag = opt.proxyDrag === true; + d.modal = opt.modal !== false; + d.mask = opt.modal !== false ? mask : false; + if(!d.isVisible()){ + // force it to the end of the z-index stack so it gets a cursor in FF + document.body.appendChild(dlg.el.dom); + d.setAnimateTarget(opt.animEl); + d.show(opt.animEl); + } + + //workaround for window internally enabling keymap in afterShow + d.on('show', function(){ + if(allowClose === true){ + d.keyMap.enable(); + }else{ + d.keyMap.disable(); + } + }, this, {single:true}); + + if(opt.wait === true){ + progressBar.wait(opt.waitConfig); + } + return this; + }, + + /** + * Adds the specified icon to the dialog. By default, the class 'ext-mb-icon' is applied for default + * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('') + * to clear any existing icon. This method must be called before the MessageBox is shown. + * The following built-in icon classes are supported, but you can also pass in a custom class name: + *
        +Ext.MessageBox.INFO
        +Ext.MessageBox.WARNING
        +Ext.MessageBox.QUESTION
        +Ext.MessageBox.ERROR
        +         *
        + * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon + * @return {Ext.MessageBox} this + */ + setIcon : function(icon){ + if(!dlg){ + bufferIcon = icon; + return; + } + bufferIcon = undefined; + if(icon && icon != ''){ + iconEl.removeClass('x-hidden'); + iconEl.replaceClass(iconCls, icon); + bodyEl.addClass('x-dlg-icon'); + iconCls = icon; + }else{ + iconEl.replaceClass(iconCls, 'x-hidden'); + bodyEl.removeClass('x-dlg-icon'); + iconCls = ''; + } + return this; + }, + + /** + * Displays a message box with a progress bar. This message box has no buttons and is not closeable by + * the user. You are responsible for updating the progress bar as needed via {@link Ext.MessageBox#updateProgress} + * and closing the message box when the process is complete. + * @param {String} title The title bar text + * @param {String} msg The message box body text + * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '') + * @return {Ext.MessageBox} this + */ + progress : function(title, msg, progressText){ + this.show({ + title : title, + msg : msg, + buttons: false, + progress:true, + closable:false, + minWidth: this.minProgressWidth, + progressText: progressText + }); + return this; + }, + + /** + * Displays a message box with an infinitely auto-updating progress bar. This can be used to block user + * interaction while waiting for a long-running process to complete that does not have defined intervals. + * You are responsible for closing the message box when the process is complete. + * @param {String} msg The message box body text + * @param {String} title (optional) The title bar text + * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object + * @return {Ext.MessageBox} this + */ + wait : function(msg, title, config){ + this.show({ + title : title, + msg : msg, + buttons: false, + closable:false, + wait:true, + modal:true, + minWidth: this.minProgressWidth, + waitConfig: config + }); + return this; + }, + + /** + * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt). + * If a callback function is passed it will be called after the user clicks the button, and the + * id of the button that was clicked will be passed as the only parameter to the callback + * (could also be the top-right close button). + * @param {String} title The title bar text + * @param {String} msg The message box body text + * @param {Function} fn (optional) The callback function invoked after the message box is closed + * @param {Object} scope (optional) The scope of the callback function + * @return {Ext.MessageBox} this + */ + alert : function(title, msg, fn, scope){ + this.show({ + title : title, + msg : msg, + buttons: this.OK, + fn: fn, + scope : scope + }); + return this; + }, + + /** + * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm). + * If a callback function is passed it will be called after the user clicks either button, + * and the id of the button that was clicked will be passed as the only parameter to the callback + * (could also be the top-right close button). + * @param {String} title The title bar text + * @param {String} msg The message box body text + * @param {Function} fn (optional) The callback function invoked after the message box is closed + * @param {Object} scope (optional) The scope of the callback function + * @return {Ext.MessageBox} this + */ + confirm : function(title, msg, fn, scope){ + this.show({ + title : title, + msg : msg, + buttons: this.YESNO, + fn: fn, + scope : scope, + icon: this.QUESTION + }); + return this; + }, + + /** + * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt). + * The prompt can be a single-line or multi-line textbox. If a callback function is passed it will be called after the user + * clicks either button, and the id of the button that was clicked (could also be the top-right + * close button) and the text that was entered will be passed as the two parameters to the callback. + * @param {String} title The title bar text + * @param {String} msg The message box body text + * @param {Function} fn (optional) The callback function invoked after the message box is closed + * @param {Object} scope (optional) The scope of the callback function + * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight + * property, or the height in pixels to create the textbox (defaults to false / single-line) + * @param {String} value (optional) Default value of the text input element (defaults to '') + * @return {Ext.MessageBox} this + */ + prompt : function(title, msg, fn, scope, multiline, value){ + this.show({ + title : title, + msg : msg, + buttons: this.OKCANCEL, + fn: fn, + minWidth:250, + scope : scope, + prompt:true, + multiline: multiline, + value: value + }); + return this; + }, + + /** + * Button config that displays a single OK button + * @type Object + */ + OK : {ok:true}, + /** + * Button config that displays a single Cancel button + * @type Object + */ + CANCEL : {cancel:true}, + /** + * Button config that displays OK and Cancel buttons + * @type Object + */ + OKCANCEL : {ok:true, cancel:true}, + /** + * Button config that displays Yes and No buttons + * @type Object + */ + YESNO : {yes:true, no:true}, + /** + * Button config that displays Yes, No and Cancel buttons + * @type Object + */ + YESNOCANCEL : {yes:true, no:true, cancel:true}, + /** + * The CSS class that provides the INFO icon image + * @type String + */ + INFO : 'ext-mb-info', + /** + * The CSS class that provides the WARNING icon image + * @type String + */ + WARNING : 'ext-mb-warning', + /** + * The CSS class that provides the QUESTION icon image + * @type String + */ + QUESTION : 'ext-mb-question', + /** + * The CSS class that provides the ERROR icon image + * @type String + */ + ERROR : 'ext-mb-error', + + /** + * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75) + * @type Number + */ + defaultTextHeight : 75, + /** + * The maximum width in pixels of the message box (defaults to 600) + * @type Number + */ + maxWidth : 600, + /** + * The minimum width in pixels of the message box (defaults to 100) + * @type Number + */ + minWidth : 100, + /** + * The minimum width in pixels of the message box if it is a progress-style dialog. This is useful + * for setting a different minimum width than text-only dialogs may need (defaults to 250) + * @type Number + */ + minProgressWidth : 250, + /** + * An object containing the default button text strings that can be overriden for localized language support. + * Supported properties are: ok, cancel, yes and no. Generally you should include a locale-specific + * resource file for handling language support across the framework. + * Customize the default text like so: Ext.MessageBox.buttonText.yes = "oui"; //french + * @type Object + */ + buttonText : { + ok : "OK", + cancel : "Cancel", + yes : "Yes", + no : "No" + } + }; +}(); + +/** + * Shorthand for {@link Ext.MessageBox} + */ +Ext.Msg = Ext.MessageBox;/** + * @class Ext.dd.PanelProxy + * A custom drag proxy implementation specific to {@link Ext.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} to proxy for + * @param config Configuration options + */ +Ext.dd.PanelProxy = function(panel, config){ + this.panel = panel; + this.id = this.panel.id +'-ddproxy'; + Ext.apply(this, config); +}; + +Ext.dd.PanelProxy.prototype = { + /** + * @cfg {Boolean} insertProxy True to insert a placeholder proxy element while dragging the panel, + * false to drag with no proxy (defaults to true). + */ + 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; + }, + + /** + * Gets the proxy's ghost element + * @return {Element} The proxy's ghost element + */ + getGhost : function(){ + return this.ghost; + }, + + /** + * Gets the proxy's element + * @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; + } + this.panel.el.dom.style.display = ''; + this.ghost.remove(); + delete this.ghost; + } + }, + + /** + * Shows the proxy + */ + show : function(){ + if(!this.ghost){ + this.ghost = this.panel.createGhost(undefined, undefined, Ext.getBody()); this.ghost.setXY(this.panel.el.getXY()) if(this.insertProxy){ this.proxy = this.panel.el.insertSibling({cls:'x-panel-dd-spacer'}); @@ -37853,17 +38996,19 @@ Ext.extend(Ext.state.Provider, Ext.util.Observable, { return (v == "1"); case "a": var all = []; - var values = v.split("^"); - for(var i = 0, len = values.length; i < len; i++){ - all.push(this.decodeValue(values[i])); + if(v != ''){ + Ext.each(v.split('^'), function(val){ + all.push(this.decodeValue(val)); + }, this); } return all; case "o": var all = {}; - var values = v.split("^"); - for(var i = 0, len = values.length; i < len; i++){ - var kv = values[i].split("="); - all[kv[0]] = this.decodeValue(kv[1]); + if(v != ''){ + Ext.each(v.split('^'), function(val){ + var kv = val.split('='); + all[kv[0]] = this.decodeValue(kv[1]); + }, this); } return all; default: @@ -38364,16 +39509,18 @@ Ext.DataView = Ext.extend(Ext.BoxComponent, { // private onUpdate : function(ds, record){ var index = this.store.indexOf(record); - var sel = this.isSelected(index); - var original = this.all.elements[index]; - var node = this.bufferRender([record], index)[0]; + if(index > -1){ + var sel = this.isSelected(index); + var original = this.all.elements[index]; + var node = this.bufferRender([record], index)[0]; - this.all.replaceElement(index, node, true); - if(sel){ - this.selected.replaceElement(original, node); - this.all.item(index).addClass(this.selectedClass); + this.all.replaceElement(index, node, true); + if(sel){ + this.selected.replaceElement(original, node); + this.all.item(index).addClass(this.selectedClass); + } + this.updateIndexes(index, index); } - this.updateIndexes(index, index); }, // private @@ -38435,14 +39582,18 @@ Ext.DataView = Ext.extend(Ext.BoxComponent, { */ bindStore : function(store, initial){ if(!initial && this.store){ - this.store.un("beforeload", this.onBeforeLoad, this); - this.store.un("datachanged", this.refresh, this); - this.store.un("add", this.onAdd, this); - this.store.un("remove", this.onRemove, this); - this.store.un("update", this.onUpdate, this); - this.store.un("clear", this.refresh, this); if(store !== this.store && this.store.autoDestroy){ this.store.destroy(); + }else{ + this.store.un("beforeload", this.onBeforeLoad, this); + this.store.un("datachanged", this.refresh, this); + this.store.un("add", this.onAdd, this); + this.store.un("remove", this.onRemove, this); + this.store.un("update", this.onUpdate, this); + this.store.un("clear", this.refresh, this); + } + if(!store){ + this.store = null; } } if(store){ @@ -38927,9 +40078,10 @@ Ext.ListView = Ext.extend(Ext.DataView, { */ /** * @cfg {Number} scrollOffset The amount of space to reserve for the scrollbar (defaults to - * 19 pixels) + * undefined). If an explicit value isn't specified, this will be automatically + * calculated. */ - scrollOffset : 19, + scrollOffset : undefined, /** * @cfg {Boolean/Object} columnResize * Specify true or specify a configuration object for {@link Ext.ListView.ColumnResizer} @@ -38977,6 +40129,11 @@ Ext.ListView = Ext.extend(Ext.DataView, { * The template to be used for the header row. See {@link #tpl} for more details. */ + /* + * IE has issues when setting percentage based widths to 100%. Default to 99. + */ + maxWidth: Ext.isIE ? 99 : 100, + initComponent : function(){ if(this.columnResize){ this.colResizer = new Ext.ListView.ColumnResizer(this.colResizer); @@ -39014,9 +40171,13 @@ Ext.ListView = Ext.extend(Ext.DataView, { '
        ' ); }; - var cs = this.columns, allocatedWidth = 0, colsWithWidth = 0, len = cs.length; + var cs = this.columns, + allocatedWidth = 0, + colsWithWidth = 0, + len = cs.length, + columns = []; for(var i = 0; i < len; i++){ - var c = cs[i]; + var c = Ext.apply({}, cs[i]); if(!c.tpl){ c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}'); }else if(Ext.isString(c.tpl)){ @@ -39028,12 +40189,14 @@ Ext.ListView = Ext.extend(Ext.DataView, { allocatedWidth += c.width; colsWithWidth++; } + columns.push(c); } + cs = this.columns = columns; // auto calculate missing column widths if(colsWithWidth < len){ var remaining = len - colsWithWidth; - if(allocatedWidth < 100){ - var perCol = ((100-allocatedWidth) / remaining); + if(allocatedWidth < this.maxWidth){ + var perCol = ((this.maxWidth-allocatedWidth) / remaining); for(var j = 0; j < len; j++){ var c = cs[j]; if(!Ext.isNumber(c.width)){ @@ -39046,6 +40209,9 @@ Ext.ListView = Ext.extend(Ext.DataView, { }, onRender : function(){ + this.autoEl = { + cls: 'x-list-wrap' + }; Ext.ListView.superclass.onRender.apply(this, arguments); this.internalTpl.overwrite(this.el, {columns: this.columns}); @@ -39099,7 +40265,7 @@ Ext.ListView = Ext.extend(Ext.DataView, { } var bdp = bd.parentNode; if(Ext.isNumber(w)){ - var sw = w - this.scrollOffset; + var sw = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth()); if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){ bd.style.width = sw + 'px'; hd.style.width = sw + 'px'; @@ -39114,7 +40280,7 @@ Ext.ListView = Ext.extend(Ext.DataView, { }, 10); } } - if(Ext.isNumber(h == 'number')){ + if(Ext.isNumber(h)){ bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px'; } }, @@ -39180,13 +40346,13 @@ Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, { }, handleHdMove : function(e, t){ - var hw = 5; - var x = e.getPageX(); - var hd = e.getTarget('em', 3, true); + var hw = 5, + x = e.getPageX(), + hd = e.getTarget('em', 3, true); if(hd){ - var r = hd.getRegion(); - var ss = hd.dom.style; - var pn = hd.dom.parentNode; + var r = hd.getRegion(), + ss = hd.dom.style, + pn = hd.dom.parentNode; if(x - r.left <= hw && pn != pn.parentNode.firstChild){ this.activeHd = Ext.get(pn.previousSibling.firstChild); @@ -39211,8 +40377,8 @@ Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, { this.proxy = this.view.el.createChild({cls:'x-list-resizer'}); this.proxy.setHeight(this.view.el.getHeight()); - var x = this.tracker.getXY()[0]; - var w = this.view.innerHd.getWidth(); + var x = this.tracker.getXY()[0], + w = this.view.innerHd.getWidth(); this.hdX = this.dragHd.getX(); this.hdIndex = this.view.findHeaderIndex(this.dragHd); @@ -39233,18 +40399,20 @@ Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, { var nw = this.proxy.getWidth(); this.proxy.remove(); - var index = this.hdIndex; - var vw = this.view, cs = vw.columns, len = cs.length; - var w = this.view.innerHd.getWidth(), minPct = this.minPct * 100; - - var pct = Math.ceil((nw*100) / w); - var diff = cs[index].width - pct; - var each = Math.floor(diff / (len-1-index)); - var mod = diff - (each * (len-1-index)); + var index = this.hdIndex, + vw = this.view, + cs = vw.columns, + len = cs.length, + w = this.view.innerHd.getWidth(), + minPct = this.minPct * 100; + pct = Math.ceil((nw * vw.maxWidth) / w), + diff = cs[index].width - pct, + each = Math.floor(diff / (len-1-index)), + mod = diff - (each * (len-1-index)); for(var i = index+1; i < len; i++){ - var cw = cs[i].width + each; - var ncw = Math.max(minPct, cw); + var cw = cs[i].width + each, + ncw = Math.max(minPct, cw); if(cw != ncw){ mod += cw - ncw; } @@ -39253,8 +40421,8 @@ Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, { cs[index].width = pct; cs[index+1].width += mod; delete this.dragHd; - this.view.setHdWidths(); - this.view.refresh(); + vw.setHdWidths(); + vw.refresh(); setTimeout(function(){ vw.disableHeaders = false; }, 100); @@ -39743,11 +40911,11 @@ new Ext.TabPanel({ // private initEvents : function(){ Ext.TabPanel.superclass.initEvents.call(this); - this.on('add', this.onAdd, this, {target: this}); - this.on('remove', this.onRemove, this, {target: this}); - - this.mon(this.strip, 'mousedown', this.onStripMouseDown, this); - this.mon(this.strip, 'contextmenu', this.onStripContextMenu, this); + this.mon(this.strip, { + scope: this, + mousedown: this.onStripMouseDown, + contextmenu: this.onStripContextMenu + }); if(this.enableTabScroll){ this.mon(this.strip, 'mousewheel', this.onWheel, this); } @@ -39784,6 +40952,7 @@ new Ext.TabPanel({ if(t.close){ if (t.item.fireEvent('beforeclose', t.item) !== false) { t.item.fireEvent('close', t.item); + delete t.item.tabEl; this.remove(t.item); } return; @@ -39881,12 +41050,16 @@ new Ext.TabPanel({ }, // private - onAdd : function(tp, item, index){ - this.initTab(item, index); - if(this.items.getCount() == 1){ - this.syncSize(); + onAdd : function(c){ + Ext.TabPanel.superclass.onAdd.call(this, c); + if(this.rendered){ + var items = this.items; + this.initTab(c, items.indexOf(c)); + if(items.getCount() == 1){ + this.syncSize(); + } + this.delegateUpdates(); } - this.delegateUpdates(); }, // private @@ -39903,15 +41076,16 @@ new Ext.TabPanel({ }, // private - onRemove : function(tp, item){ - Ext.destroy(Ext.get(this.getTabEl(item))); - this.stack.remove(item); - item.un('disable', this.onItemDisabled, this); - item.un('enable', this.onItemEnabled, this); - item.un('titlechange', this.onItemTitleChanged, this); - item.un('iconchange', this.onItemIconChanged, this); - item.un('beforeshow', this.onBeforeShowItem, this); - if(item == this.activeTab){ + onRemove : function(c){ + Ext.TabPanel.superclass.onRemove.call(this, c); + Ext.destroy(Ext.get(this.getTabEl(c))); + this.stack.remove(c); + c.un('disable', this.onItemDisabled, this); + c.un('enable', this.onItemEnabled, this); + c.un('titlechange', this.onItemTitleChanged, this); + c.un('iconchange', this.onItemIconChanged, this); + c.un('beforeshow', this.onBeforeShowItem, this); + if(c == this.activeTab){ var next = this.stack.next(); if(next){ this.setActiveTab(next); @@ -39961,7 +41135,9 @@ new Ext.TabPanel({ onItemIconChanged : function(item, iconCls, oldCls){ var el = this.getTabEl(item); if(el){ - Ext.fly(el).child('span.x-tab-strip-text').replaceClass(oldCls, iconCls); + el = Ext.get(el); + el.child('span.x-tab-strip-text').replaceClass(oldCls, iconCls); + el[Ext.isEmpty(iconCls) ? 'removeClass' : 'addClass']('x-tab-with-icon'); } }, @@ -40348,7 +41524,7 @@ new Ext.TabPanel({ * @hide */ /** - * @property title + * @cfg title * @hide */ /** @@ -40415,7 +41591,8 @@ Ext.TabPanel.AccessStack = function(){ return items.pop(); } }; -};/** +}; +/** * @class Ext.Button * @extends Ext.BoxComponent * Simple Button class @@ -40427,7 +41604,6 @@ Ext.TabPanel.AccessStack = function(){ *
      • b : Button
        This Button.
      • *
      • e : EventObject
        The click event.
      • *
      - * @cfg {Object} scope The scope (this reference) in which the handler is executed. Defaults to this Button. * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width). * See also {@link Ext.Panel}.{@link Ext.Panel#minButtonWidth minButtonWidth}. * @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 @@ -40458,12 +41634,6 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { * @type Boolean */ pressed : false, - /** - * The Button's owner {@link Ext.Panel} (defaults to undefined, and is set automatically when - * the Button is added to a container). Read-only. - * @type Ext.Panel - * @property ownerCt - */ /** * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined) @@ -40478,7 +41648,7 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { * @cfg {Boolean} enableToggle * True to enable pressed/not pressed toggling (defaults to false) */ - enableToggle: false, + enableToggle : false, /** * @cfg {Function} toggleHandler * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:
        @@ -40512,11 +41682,12 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { type : 'button', // private - menuClassTarget: 'tr:nth(2)', + menuClassTarget : 'tr:nth(2)', /** * @cfg {String} clickEvent - * The type of event to map to the button's event handler (defaults to 'click') + * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu). + * Defaults to 'click'. */ clickEvent : 'click', @@ -40538,7 +41709,7 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { * DOM structure created.

        *

        When a custom {@link #template} is used, you must ensure that this selector results in the selection of * a focussable element.

        - *

        Defaults to "button:first-child".

        + *

        Defaults to 'button:first-child'.

        */ buttonSelector : 'button:first-child', @@ -40552,7 +41723,13 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { *
      *

      Defaults to 'small'.

      */ - scale: 'small', + scale : 'small', + + /** + * @cfg {Object} scope The scope (this reference) in which the + * {@link #handler} and {@link #toggleHandler} is + * executed. Defaults to this Button. + */ /** * @cfg {String} iconAlign @@ -40744,7 +41921,12 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { this.el = btn; if(this.id){ - this.el.dom.id = this.el.id = this.id; + var d = this.el.dom, + c = Ext.Element.cache; + + delete c[d.id]; + d.id = this.el.id = this.id; + c[d.id] = this.el; } if(this.icon){ btnEl.setStyle('background-image', 'url(' +this.icon +')'); @@ -40762,7 +41944,7 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { mouseover: this.onMouseOver, mousedown: this.onMouseDown }); - + // new functionality for monitoring on the document level //this.mon(btn, 'mouseout', this.onMouseOut, this); } @@ -40779,7 +41961,6 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {}); this.mon(repeater, 'click', this.onClick, this); } - this.mon(btn, this.clickEvent, this.onClick, this); }, @@ -40829,16 +42010,16 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { } return this; }, - + // private - clearTip: function(){ + clearTip : function(){ if(Ext.isObject(this.tooltip)){ Ext.QuickTips.unregister(this.btnEl); } }, - + // private - beforeDestroy: function(){ + beforeDestroy : function(){ if(this.rendered){ this.clearTip(); } @@ -40877,7 +42058,7 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { /** * Assigns this Button's click handler * @param {Function} handler The function to call when the button is clicked - * @param {Object} scope (optional) Scope for the function passed in + * @param {Object} scope (optional) Scope for the function passed in. Defaults to this Button. * @return {Ext.Button} this */ setHandler : function(handler, scope){ @@ -40917,7 +42098,9 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { toggle : function(state, suppressEvent){ state = state === undefined ? !this.pressed : !!state; if(state != this.pressed){ - this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed'); + if(this.rendered){ + this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed'); + } this.pressed = state; if(!suppressEvent){ this.fireEvent('toggle', this, state); @@ -40945,7 +42128,7 @@ Ext.Button = Ext.extend(Ext.BoxComponent, { onEnable : function(){ this.onDisableChange(false); }, - + onDisableChange : function(disabled){ if(this.el){ if(!Ext.isIE6 || !this.text){ @@ -41364,8 +42547,8 @@ Ext.CycleButton = Ext.extend(Ext.SplitButton, { * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false) */ setActiveItem : function(item, suppressEvent){ - if(typeof item != 'object'){ - item = this.menu.items.get(item); + if(!Ext.isObject(item)){ + item = this.menu.getComponent(item); } if(item){ if(!this.rendered){ @@ -41422,18 +42605,19 @@ Ext.CycleButton = Ext.extend(Ext.SplitButton, { this.menu = {cls:'x-cycle-menu', items:[]}; var checked; - for(var i = 0, len = this.itemCount; i < len; i++){ - var item = this.items[i]; - item.group = item.group || this.id; - item.itemIndex = i; - item.checkHandler = this.checkHandler; - item.scope = this; - item.checked = item.checked || false; + Ext.each(this.items, function(item, i){ + Ext.apply(item, { + group: item.group || this.id, + itemIndex: i, + checkHandler: this.checkHandler, + scope: this, + checked: item.checked || false + }); this.menu.items.push(item); if(item.checked){ checked = item; } - } + }, this); this.setActiveItem(checked, true); Ext.CycleButton.superclass.initComponent.call(this); @@ -41453,13 +42637,18 @@ Ext.CycleButton = Ext.extend(Ext.SplitButton, { * the active item will be set to the first item in the menu. */ toggleSelected : function(){ - this.menu.render(); + var m = this.menu; + m.render(); + // layout if we haven't before so the items are active + if(!m.hasLayout){ + m.doLayout(); + } var nextIdx, checkItem; for (var i = 1; i < this.itemCount; i++) { nextIdx = (this.activeItem.itemIndex + i) % this.itemCount; // check the potential item - checkItem = this.menu.items.itemAt(nextIdx); + checkItem = m.items.itemAt(nextIdx); // if its not disabled then check it. if (!checkItem.disabled) { checkItem.setChecked(true); @@ -41651,7 +42840,7 @@ Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { clearMenu : function(){ var m = this.moreMenu; if(m && m.items){ - this.moreMenu.items.each(function(item){ + m.items.each(function(item){ delete item.menu; }); } @@ -41694,6 +42883,7 @@ Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, { scope: this } }); + this.moreMenu.ownerCt = this.container; this.more = new Ext.Button({ iconCls: 'x-toolbar-more-icon', cls: 'x-toolbar-more', @@ -41831,6 +43021,15 @@ Ext.extend(T, Ext.Container, { defaultType: 'button', + /** + * @cfg {String/Object} layout + * This class assigns a default layout (layout:'toolbar'). + * Developers may 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#layout} for additional information. + */ + trackMenus : true, internalDefaults: {removeMode: 'container', hideParent: true}, toolbarCls: 'x-toolbar', @@ -41856,12 +43055,14 @@ Ext.extend(T, Ext.Container, { }; } this.el = ct.createChild(Ext.apply({ id: this.id },this.autoCreate), position); + Ext.Toolbar.superclass.onRender.apply(this, arguments); } }, /** - * Adds element(s) to the toolbar -- this function takes a variable number of - * arguments of mixed type and adds them to the toolbar. + *

      Adds element(s) to the toolbar -- this function takes a variable number of + * arguments of mixed type and adds them to the toolbar.

      + *

      Note: See the notes within {@link Ext.Container#add}.

      * @param {Mixed} arg1 The following types of arguments are all valid:
      *
        *
      • {@link Ext.Button} config: A valid button config object (equivalent to {@link #addButton})
      • @@ -41928,6 +43129,7 @@ Ext.extend(T, Ext.Container, { /** * Adds a separator + *

        Note: See the notes within {@link Ext.Container#add}.

        * @return {Ext.Toolbar.Item} The separator {@link Ext.Toolbar.Item item} */ addSeparator : function(){ @@ -41936,6 +43138,7 @@ Ext.extend(T, Ext.Container, { /** * Adds a spacer element + *

        Note: See the notes within {@link Ext.Container#add}.

        * @return {Ext.Toolbar.Spacer} The spacer item */ addSpacer : function(){ @@ -41944,6 +43147,7 @@ Ext.extend(T, Ext.Container, { /** * Forces subsequent additions into the float:right toolbar + *

        Note: See the notes within {@link Ext.Container#add}.

        */ addFill : function(){ this.add(new T.Fill()); @@ -41951,6 +43155,7 @@ Ext.extend(T, Ext.Container, { /** * Adds any standard HTML element to the toolbar + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Mixed} el The element or id of the element to add * @return {Ext.Toolbar.Item} The element's item */ @@ -41960,15 +43165,17 @@ Ext.extend(T, Ext.Container, { /** * Adds any Toolbar.Item or subclass + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Ext.Toolbar.Item} item * @return {Ext.Toolbar.Item} The item */ addItem : function(item){ - return Ext.Toolbar.superclass.add.apply(this, arguments); + return this.add.apply(this, arguments); }, /** * Adds a button (or buttons). See {@link Ext.Button} for more info on the config. + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Object/Array} config A button config or array of configs * @return {Ext.Button/Array} */ @@ -41985,6 +43192,7 @@ Ext.extend(T, Ext.Container, { /** * Adds text to the toolbar + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {String} text The text to add * @return {Ext.Toolbar.Item} The element's item */ @@ -41994,6 +43202,7 @@ Ext.extend(T, Ext.Container, { /** * Adds a new element to the toolbar from the passed {@link Ext.DomHelper} config + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Object} config * @return {Ext.Toolbar.Item} The element's item */ @@ -42004,6 +43213,7 @@ Ext.extend(T, Ext.Container, { /** * Adds a dynamically rendered Ext.form field (TextField, ComboBox, etc). Note: the field should not have * been rendered yet. For a field that has already been rendered, use {@link #addElement}. + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Ext.form.Field} field * @return {Ext.Toolbar.Item} */ @@ -42013,6 +43223,7 @@ Ext.extend(T, Ext.Container, { /** * Inserts any {@link Ext.Toolbar.Item}/{@link Ext.Button} at the specified index. + *

        Note: See the notes within {@link Ext.Container#add}.

        * @param {Number} index The index where the item is to be inserted * @param {Object/Ext.Toolbar.Item/Ext.Button/Array} item The button, or button config object to be * inserted, or an array of buttons/configs. @@ -42214,23 +43425,29 @@ new Ext.Panel({ * @xtype tbtext */ T.TextItem = Ext.extend(T.Item, { + /** + * @cfg {String} text The text to be used as innerHTML (html tags are accepted) + */ + constructor: function(config){ - if (Ext.isString(config)) { - config = { autoEl: {cls: 'xtb-text', html: config }}; - } else { - config.autoEl = {cls: 'xtb-text', html: config.text || ''}; - } - T.TextItem.superclass.constructor.call(this, config); + T.TextItem.superclass.constructor.call(this, Ext.isString(config) ? {text: config} : config); + }, + + // private + onRender : function(ct, position) { + this.autoEl = {cls: 'xtb-text', html: this.text || ''}; + T.TextItem.superclass.onRender.call(this, ct, position); }, + /** * Updates this item's text, setting the text to be used as innerHTML. * @param {String} t The text to display (html accepted). */ setText : function(t) { - if (this.rendered) { - this.el.dom.innerHTML = t; - } else { - this.autoEl.html = t; + if(this.rendered){ + this.el.update(t); + }else{ + this.text = t; } } }); @@ -42359,10 +43576,14 @@ Ext.reg('buttongroup', Ext.ButtonGroup); Ext.QuickTips.init(); // to display button quicktips var myStore = new Ext.data.Store({ + reader: new Ext.data.JsonReader({ + {@link Ext.data.JsonReader#totalProperty totalProperty}: 'results', + ... + }), ... }); -var myPageSize = 25; // server script should only send back 25 items +var myPageSize = 25; // server script should only send back 25 items at a time var grid = new Ext.grid.GridPanel({ ... @@ -42383,20 +43604,43 @@ var grid = new Ext.grid.GridPanel({ *
        
         store.load({
             params: {
        -        start: 0,          // specify params for the first page load if using paging
        +        // specify params for the first page load if using paging
        +        start: 0,          
                 limit: myPageSize,
        +        // other params
                 foo:   'bar'
             }
         });
        + * 
        + * + *

        If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:

        + *
        
        +var myStore = new Ext.data.Store({
        +    {@link Ext.data.Store#autoLoad autoLoad}: {params:{start: 0, limit: 25}},
        +    ...
        +});
        + * 
        + * + *

        The packet sent back from the server would have this form:

        + *
        
        +{
        +    "success": true,
        +    "results": 2000, 
        +    "rows": [ // *Note: this must be an Array 
        +        { "id":  1, "name": "Bill", "occupation": "Gardener" },
        +        { "id":  2, "name":  "Ben", "occupation": "Horticulturalist" },
        +        ...
        +        { "id": 25, "name":  "Sue", "occupation": "Botanist" }
        +    ]
        +}
          * 
        *

        Paging with Local Data

        *

        Paging can also be accomplished with local data using extensions:

        *
        - * @constructor - * Create a new PagingToolbar + * @constructor Create a new PagingToolbar * @param {Object} config The config object * @xtype paging */ @@ -42481,10 +43725,13 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { refreshText : 'Refresh', /** - * @deprecated - * The defaults for these should be set in the data store. - * Object mapping of parameter names used for load calls, initially set to: + *

        Deprecated. paramNames should be set in the data store + * (see {@link Ext.data.Store#paramNames}).

        + *

        Object mapping of parameter names used for load calls, initially set to:

        *
        {start: 'start', limit: 'limit'}
        + * @type Object + * @property paramNames + * @deprecated */ /** @@ -42550,7 +43797,7 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { tooltip: this.refreshText, overflowText: this.refreshText, iconCls: 'x-tbar-loading', - handler: this.refresh, + handler: this.doRefresh, scope: this })]; @@ -42600,7 +43847,7 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { ); this.on('afterlayout', this.onFirstLayout, this, {single: true}); this.cursor = 0; - this.bindStore(this.store); + this.bindStore(this.store, true); }, // private @@ -42725,6 +43972,12 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { return this.paramNames || this.store.paramNames; }, + // private + getParams : function(){ + //retain backwards compat, allow params on the toolbar itself, if they exist. + return this.paramNames || this.store.paramNames; + }, + // private beforeLoad : function(){ if(this.rendered && this.refresh){ @@ -42776,7 +44029,7 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { /** * Refresh the current page, has the same effect as clicking the 'refresh' button. */ - refresh : function(){ + doRefresh : function(){ this.doLoad(this.cursor); }, @@ -42788,11 +44041,15 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { bindStore : function(store, initial){ var doLoad; if(!initial && this.store){ - this.store.un('beforeload', this.beforeLoad, this); - this.store.un('load', this.onLoad, this); - this.store.un('exception', this.onLoadError, this); if(store !== this.store && this.store.autoDestroy){ this.store.destroy(); + }else{ + this.store.un('beforeload', this.beforeLoad, this); + this.store.un('load', this.onLoad, this); + this.store.un('exception', this.onLoadError, this); + } + if(!store){ + this.store = null; } } if(store){ @@ -42803,7 +44060,7 @@ Ext.PagingToolbar = Ext.extend(Ext.Toolbar, { load: this.onLoad, exception: this.onLoadError }); - doLoad = store.getCount() > 0; + doLoad = true; } this.store = store; if(doLoad){ @@ -43034,6 +44291,7 @@ tabPanel.on('tabchange', function(tabPanel, tab){ Ext.apply(Ext.History, new Ext.util.Observable());/** * @class Ext.Tip * @extends Ext.Panel + * @xtype tip * This is the base class for {@link Ext.QuickTip} and {@link Ext.Tooltip} that provides the basic layout and * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned * tips that are displayed programmatically, or it can be extended to provide custom tip implementations. @@ -43162,6 +44420,8 @@ tip.showBy('my-el', 'tl-tr'); } }); +Ext.reg('tip', Ext.Tip); + // private - custom Tip DD implementation Ext.Tip.DD = function(tip, config){ Ext.apply(this, config); @@ -43185,69 +44445,85 @@ Ext.extend(Ext.Tip.DD, Ext.dd.DD, { * @class Ext.ToolTip * @extends Ext.Tip * A standard tooltip implementation for providing additional information when hovering over a target element. + * @xtype tooltip * @constructor * Create a new Tooltip * @param {Object} config The configuration options */ Ext.ToolTip = Ext.extend(Ext.Tip, { /** - * When a Tooltip is configured with the {@link #delegate} option to cause selected child elements of the {@link #target} - * Element to each trigger a seperate show event, this property is set to the DOM element which triggered the show. + * When a Tooltip is configured with the {@link #delegate} + * option to cause selected child elements of the {@link #target} + * Element to each trigger a seperate show event, this property is set to + * the DOM element which triggered the show. * @type DOMElement * @property triggerElement */ /** - * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to monitor for mouseover events to trigger - * showing this ToolTip. + * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to monitor + * for mouseover events to trigger showing this ToolTip. */ /** - * @cfg {Boolean} autoHide True to automatically hide the tooltip after the mouse exits the target element - * or after the {@link #dismissDelay} has expired if set (defaults to true). If {@link closable} = true a close - * tool button will be rendered into the tooltip header. + * @cfg {Boolean} autoHide True to automatically hide the tooltip after the + * mouse exits the target element or after the {@link #dismissDelay} + * has expired if set (defaults to true). If {@link closable} = true + * a close tool button will be rendered into the tooltip header. */ /** - * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays after the mouse enters the - * target element (defaults to 500) + * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays + * after the mouse enters the target element (defaults to 500) */ - showDelay: 500, + showDelay : 500, /** - * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the target element but before the - * tooltip actually hides (defaults to 200). Set to 0 for the tooltip to hide immediately. + * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the + * target element but before the tooltip actually hides (defaults to 200). + * Set to 0 for the tooltip to hide immediately. */ - hideDelay: 200, + hideDelay : 200, /** - * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip automatically hides (defaults to 5000). - * To disable automatic hiding, set dismissDelay = 0. + * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip + * automatically hides (defaults to 5000). To disable automatic hiding, set + * dismissDelay = 0. */ - dismissDelay: 5000, + dismissDelay : 5000, /** - * @cfg {Array} mouseOffset An XY offset from the mouse position where the tooltip should be shown (defaults to [15,18]). + * @cfg {Array} mouseOffset An XY offset from the mouse position where the + * tooltip should be shown (defaults to [15,18]). */ /** - * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it moves over the target element (defaults to false). + * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it + * moves over the target element (defaults to false). */ trackMouse : false, /** - * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target element, false to - * anchor it relative to the mouse coordinates (defaults to true). When anchorToTarget is - * true, use {@link #defaultAlign} to control tooltip alignment to the target element. When - * anchorToTarget is false, use {@link #anchorPosition} instead to control alignment. - */ - anchorToTarget: true, - /** - * @cfg {Number} anchorOffset A numeric pixel value used to offset the default position of the - * anchor arrow (defaults to 0). When the anchor position is on the top or bottom of the tooltip, - * anchorOffset will be used as a horizontal offset. Likewise, when the anchor position is on the - * left or right side, anchorOffset will be used as a vertical offset. - */ - anchorOffset: 0, - /** - * @cfg {String} delegate

        Optional. A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements - * within the {@link #target} element to trigger showing and hiding the ToolTip as the mouse moves within the target.

        - *

        When specified, the child element of the target which caused a show event is placed into the {@link #triggerElement} property + * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target + * element, false to anchor it relative to the mouse coordinates (defaults + * to true). When anchorToTarget is true, use + * {@link #defaultAlign} to control tooltip alignment to the + * target element. When anchorToTarget is false, use + * {@link #anchorPosition} instead to control alignment. + */ + anchorToTarget : true, + /** + * @cfg {Number} anchorOffset A numeric pixel value used to offset the + * default position of the anchor arrow (defaults to 0). When the anchor + * position is on the top or bottom of the tooltip, anchorOffset + * will be used as a horizontal offset. Likewise, when the anchor position + * is on the left or right side, anchorOffset will be used as + * a vertical offset. + */ + anchorOffset : 0, + /** + * @cfg {String} delegate

        Optional. A {@link Ext.DomQuery DomQuery} + * selector which allows selection of individual elements within the + * {@link #target} element to trigger showing and hiding the + * ToolTip as the mouse moves within the target.

        + *

        When specified, the child element of the target which caused a show + * event is placed into the {@link #triggerElement} property * before the ToolTip is shown.

        - *

        This may be useful when a Component has regular, repeating elements in it, each of which need a Tooltip which contains - * information specific to that element. For example:

        
        +     * 

        This may be useful when a Component has regular, repeating elements + * in it, each of which need a Tooltip which contains information specific + * to that element. For example:

        
         var myGrid = new Ext.grid.gridPanel(gridConfig);
         myGrid.on('render', function(grid) {
             var store = grid.getStore();  // Capture the Store.
        @@ -43256,24 +44532,27 @@ myGrid.on('render', function(grid) {
                 target: view.mainBody,    // The overall target element.
                 delegate: '.x-grid3-row', // Each grid row causes its own seperate show and hide.
                 trackMouse: true,         // Moving within the row should not hide the tip.
        -        renderTo: document.body,  // Render immediately so that tip.body can be referenced prior to the first show.
        -        listeners: {              // Change content dynamically depending on which element triggered the show.
        +        renderTo: document.body,  // Render immediately so that tip.body can be
        +                                  //  referenced prior to the first show.
        +        listeners: {              // Change content dynamically depending on which element
        +                                  //  triggered the show.
                     beforeshow: function updateTipBody(tip) {
                         var rowIndex = view.findRowIndex(tip.triggerElement);
        -                tip.body.dom.innerHTML = "Over Record ID " + store.getAt(rowIndex).id;
        +                tip.body.dom.innerHTML = 'Over Record ID ' + store.getAt(rowIndex).id;
                     }
                 }
             });
        -});
        +}); + *
        */ // private - targetCounter: 0, + targetCounter : 0, - constrainPosition: false, + constrainPosition : false, // private - initComponent: function(){ + initComponent : function(){ Ext.ToolTip.superclass.initComponent.call(this); this.lastActive = new Date(); this.initTarget(this.target); @@ -43303,10 +44582,10 @@ myGrid.on('render', function(grid) { var t; if((t = Ext.get(target))){ if(this.target){ - this.target = Ext.get(this.target); - this.target.un('mouseover', this.onTargetOver, this); - this.target.un('mouseout', this.onTargetOut, this); - this.target.un('mousemove', this.onMouseMove, this); + var tg = Ext.get(this.target); + this.mun(tg, 'mouseover', this.onTargetOver, this); + this.mun(tg, 'mouseout', this.onTargetOut, this); + this.mun(tg, 'mousemove', this.onMouseMove, this); } this.mon(t, { mouseover: this.onTargetOver, @@ -43342,6 +44621,9 @@ myGrid.on('render', function(grid) { // private getTargetXY : function(){ + if(this.delegate){ + this.anchorTarget = this.triggerElement; + } if(this.anchor){ this.targetCounter++; var offsets = this.getOffsets(); @@ -43419,7 +44701,7 @@ myGrid.on('render', function(grid) { }else{ var m = this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/); if(!m){ - throw "AnchorTip.defaultAlign is invalid"; + throw 'AnchorTip.defaultAlign is invalid'; } this.tipAnchor = m[1].charAt(0); } @@ -43443,7 +44725,7 @@ myGrid.on('render', function(grid) { }, // private - getOffsets: function(){ + getOffsets : function(){ var offsets, ap = this.getAnchorPosition().charAt(0); if(this.anchorToTarget && !this.trackMouse){ switch(ap){ @@ -43572,6 +44854,10 @@ myGrid.on('render', function(grid) { if(this.dismissDelay && this.autoHide !== false){ this.dismissTimer = this.hide.defer(this.dismissDelay, this); } + if(this.anchor && !this.anchorEl.isVisible()){ + this.syncAnchor(); + this.anchorEl.show(); + } }, // private @@ -43666,9 +44952,12 @@ myGrid.on('render', function(grid) { Ext.getDoc().un('mousedown', this.onDocMouseDown, this); Ext.ToolTip.superclass.onDestroy.call(this); } -});/** +}); + +Ext.reg('tooltip', Ext.ToolTip);/** * @class Ext.QuickTip * @extends Ext.ToolTip + * @xtype quicktip * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global * {@link Ext.QuickTips} instance. See the QuickTips class header for additional usage details and examples. * @constructor @@ -43758,6 +45047,23 @@ Ext.QuickTip = Ext.extend(Ext.ToolTip, { } }, + // private + getTipCfg: function(e) { + var t = e.getTarget(), + ttp, + cfg; + if(this.interceptTitles && t.title){ + ttp = t.title; + t.qtip = ttp; + t.removeAttribute("title"); + e.preventDefault(); + }else{ + cfg = this.tagConfig; + ttp = t.qtip || Ext.fly(t).getAttribute(cfg.attribute, cfg.namespace); + } + return ttp; + }, + // private onTargetOver : function(e){ if(this.disabled){ @@ -43768,7 +45074,7 @@ Ext.QuickTip = Ext.extend(Ext.ToolTip, { if(!t || t.nodeType !== 1 || t == document || t == document.body){ return; } - if(this.activeTarget && t == this.activeTarget.el){ + if(this.activeTarget && ((t == this.activeTarget.el) || Ext.fly(this.activeTarget.el).contains(t))){ this.clearTimer('hide'); this.show(); return; @@ -43783,18 +45089,8 @@ Ext.QuickTip = Ext.extend(Ext.ToolTip, { this.delayShow(); return; } - - var ttp, et = Ext.fly(t), cfg = this.tagConfig; - var ns = cfg.namespace; - if(this.interceptTitles && t.title){ - ttp = t.title; - t.qtip = ttp; - t.removeAttribute("title"); - e.preventDefault(); - } else{ - ttp = t.qtip || et.getAttribute(cfg.attribute, ns); - } - if(ttp){ + var ttp, et = Ext.fly(t), cfg = this.tagConfig, ns = cfg.namespace; + if(ttp = this.getTipCfg(e)){ var autoHide = et.getAttribute(cfg.hide, ns); this.activeTarget = { el: t, @@ -43816,6 +45112,12 @@ Ext.QuickTip = Ext.extend(Ext.ToolTip, { // private onTargetOut : function(e){ + + // If moving within the current target, and it does not have a new tip, ignore the mouseout + if (this.activeTarget && e.within(this.activeTarget.el) && !this.getTipCfg(e)) { + return; + } + this.clearTimer('show'); if(this.autoHide !== false){ this.delayHide(); @@ -43866,7 +45168,8 @@ Ext.QuickTip = Ext.extend(Ext.ToolTip, { delete this.activeTarget; Ext.QuickTip.superclass.hide.call(this); } -});/** +}); +Ext.reg('quicktip', Ext.QuickTip);/** * @class Ext.QuickTips *

        Provides attractive and customizable tooltips for any element. The QuickTips * singleton is used to configure and manage tooltips globally for multiple elements @@ -44137,6 +45440,13 @@ Ext.tree.TreePanel = Ext.extend(Ext.Panel, { enableDD : false, hlDrop : Ext.enableFx, pathSeparator: "/", + + /** + * @cfg {Array} bubbleEvents + *

        An array of events that, when fired, should be bubbled to any parent container. + * Defaults to ['add', 'remove']. + */ + bubbleEvents: [], initComponent : function(){ Ext.tree.TreePanel.superclass.initComponent.call(this); @@ -44322,6 +45632,13 @@ Ext.tree.TreePanel = Ext.extend(Ext.Panel, { */ "checkchange", /** + * @event beforedblclick + * Fires before double click processing on a node. Return false to cancel the default action. + * @param {Node} node The node + * @param {Ext.EventObject} e The event object + */ + "beforedblclick", + /** * @event dblclick * Fires when a node is double clicked * @param {Node} node The node @@ -44651,9 +45968,9 @@ new Ext.tree.TreePanel({ */ selectPath : function(path, attr, callback){ attr = attr || "id"; - var keys = path.split(this.pathSeparator); - var v = keys.pop(); - if(keys.length > 0){ + var keys = path.split(this.pathSeparator), + v = keys.pop(); + if(keys.length > 1){ var f = function(success, node){ if(success && node){ var n = node.findChild(attr, v); @@ -44936,14 +46253,21 @@ Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree Ext.tree.TreeEventModel.prototype = { initEvents : function(){ - var el = this.tree.getTreeEl(); - el.on('click', this.delegateClick, this); - if(this.tree.trackMouseOver !== false){ - this.tree.innerCt.on('mouseover', this.delegateOver, this); - this.tree.innerCt.on('mouseout', this.delegateOut, this); + var t = this.tree; + + if(t.trackMouseOver !== false){ + t.mon(t.innerCt, { + scope: this, + mouseover: this.delegateOver, + mouseout: this.delegateOut + }); } - el.on('dblclick', this.delegateDblClick, this); - el.on('contextmenu', this.delegateContextMenu, this); + t.mon(t.getTreeEl(), { + scope: this, + click: this.delegateClick, + dblclick: this.delegateDblClick, + contextmenu: this.delegateContextMenu + }); }, getNode : function(e){ @@ -45125,7 +46449,7 @@ Ext.tree.DefaultSelectionModel = function(config){ Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, { init : function(tree){ this.tree = tree; - tree.getTreeEl().on("keydown", this.onKeyDown, this); + tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this); tree.on("click", this.onNodeClick, this); }, @@ -45138,7 +46462,11 @@ Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, { * @param {TreeNode} node The node to select * @return {TreeNode} The selected node */ - select : function(node){ + select : function(node, /* private*/ selectNextNode){ + // If node is hidden, select the next node in whatever direction was being moved in. + if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) { + return selectNextNode.call(this, node); + } var last = this.selNode; if(node == last){ node.ui.onSelectedChange(true); @@ -45197,24 +46525,24 @@ Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, { * Selects the node above the selected node in the tree, intelligently walking the nodes * @return TreeNode The new selection */ - selectPrevious : function(){ - var s = this.selNode || this.lastSelNode; - if(!s){ + selectPrevious : function(/* private */ s){ + if(!(s = s || this.selNode || this.lastSelNode)){ return null; } + // Here we pass in the current function to select to indicate the direction we're moving var ps = s.previousSibling; if(ps){ if(!ps.isExpanded() || ps.childNodes.length < 1){ - return this.select(ps); + return this.select(ps, this.selectPrevious); } else{ var lc = ps.lastChild; - while(lc && lc.isExpanded() && lc.childNodes.length > 0){ + while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){ lc = lc.lastChild; } - return this.select(lc); + return this.select(lc, this.selectPrevious); } } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){ - return this.select(s.parentNode); + return this.select(s.parentNode, this.selectPrevious); } return null; }, @@ -45223,20 +46551,20 @@ Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, { * Selects the node above the selected node in the tree, intelligently walking the nodes * @return TreeNode The new selection */ - selectNext : function(){ - var s = this.selNode || this.lastSelNode; - if(!s){ + selectNext : function(/* private */ s){ + if(!(s = s || this.selNode || this.lastSelNode)){ return null; } - if(s.firstChild && s.isExpanded()){ - return this.select(s.firstChild); + // Here we pass in the current function to select to indicate the direction we're moving + if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){ + return this.select(s.firstChild, this.selectNext); }else if(s.nextSibling){ - return this.select(s.nextSibling); + return this.select(s.nextSibling, this.selectNext); }else if(s.parentNode){ var newS = null; s.parentNode.bubble(function(){ if(this.nextSibling){ - newS = this.getOwnerTree().selModel.select(this.nextSibling); + newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext); return false; } }); @@ -45308,7 +46636,7 @@ Ext.tree.MultiSelectionModel = function(config){ Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, { init : function(tree){ this.tree = tree; - tree.getTreeEl().on("keydown", this.onKeyDown, this); + tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this); tree.on("click", this.onNodeClick, this); }, @@ -46008,7 +47336,7 @@ Ext.extend(Ext.data.Node, Ext.util.Observable, { if(t){ t.unregisterNode(this); } - this.id = id; + this.id = this.attributes.id = id; if(t){ t.registerNode(this); } @@ -46208,7 +47536,7 @@ Ext.extend(Ext.data.Node, Ext.util.Observable, { */ Ext.tree.TreeNode = function(attributes){ attributes = attributes || {}; - if(typeof attributes == "string"){ + if(typeof attributes == 'string'){ attributes = {text: attributes}; } this.childrenRendered = false; @@ -46220,7 +47548,7 @@ Ext.tree.TreeNode = function(attributes){ this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false; /** - * Read-only. The text for this node. To change it use setText(). + * Read-only. The text for this node. To change it use {@link #setText}. * @type String */ this.text = attributes.text; @@ -46243,7 +47571,7 @@ Ext.tree.TreeNode = function(attributes){ * @param {String} text The new text * @param {String} oldText The old text */ - "textchange", + 'textchange', /** * @event beforeexpand * Fires before this node is expanded, return false to cancel. @@ -46251,7 +47579,7 @@ Ext.tree.TreeNode = function(attributes){ * @param {Boolean} deep * @param {Boolean} anim */ - "beforeexpand", + 'beforeexpand', /** * @event beforecollapse * Fires before this node is collapsed, return false to cancel. @@ -46259,67 +47587,74 @@ Ext.tree.TreeNode = function(attributes){ * @param {Boolean} deep * @param {Boolean} anim */ - "beforecollapse", + 'beforecollapse', /** * @event expand * Fires when this node is expanded * @param {Node} this This node */ - "expand", + 'expand', /** * @event disabledchange * Fires when the disabled status of this node changes * @param {Node} this This node * @param {Boolean} disabled */ - "disabledchange", + 'disabledchange', /** * @event collapse * Fires when this node is collapsed * @param {Node} this This node */ - "collapse", + 'collapse', /** * @event beforeclick * Fires before click processing. Return false to cancel the default action. * @param {Node} this This node * @param {Ext.EventObject} e The event object */ - "beforeclick", + 'beforeclick', /** * @event click * Fires when this node is clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ - "click", + 'click', /** * @event checkchange * Fires when a node with a checkbox's checked property changes * @param {Node} this This node * @param {Boolean} checked */ - "checkchange", + 'checkchange', + /** + * @event beforedblclick + * Fires before double click processing. Return false to cancel the default action. + * @param {Node} this This node + * @param {Ext.EventObject} e The event object + */ + 'beforedblclick', /** * @event dblclick * Fires when this node is double clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ - "dblclick", + 'dblclick', /** * @event contextmenu * Fires when this node is right clicked * @param {Node} this This node * @param {Ext.EventObject} e The event object */ - "contextmenu", + 'contextmenu', /** * @event beforechildrenrendered * Fires right before the child nodes for this node are rendered * @param {Node} this This node */ - "beforechildrenrendered" + 'beforechildrenrendered' ); var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI; @@ -46331,7 +47666,7 @@ Ext.tree.TreeNode = function(attributes){ this.ui = new uiClass(this); }; Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { - preventHScroll: true, + preventHScroll : true, /** * Returns true if this node is expanded * @return {Boolean} @@ -46414,7 +47749,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { // private override insertBefore : function(node, refNode){ - if(!node.render){ + if(!node.render){ node = this.getLoader().createNode(node); } var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode); @@ -46436,7 +47771,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { if(this.rendered){ // event without subscribing this.ui.onTextChange(this, text, oldText); } - this.fireEvent("textchange", this, text, oldText); + this.fireEvent('textchange', this, text, oldText); }, /** @@ -46472,7 +47807,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { */ expand : function(deep, anim, callback, scope){ if(!this.expanded){ - if(this.fireEvent("beforeexpand", this, deep, anim) === false){ + if(this.fireEvent('beforeexpand', this, deep, anim) === false){ return; } if(!this.childrenRendered){ @@ -46481,7 +47816,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { this.expanded = true; if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){ this.ui.animExpand(function(){ - this.fireEvent("expand", this); + this.fireEvent('expand', this); this.runCallback(callback, scope || this, [this]); if(deep === true){ this.expandChildNodes(true); @@ -46490,7 +47825,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { return; }else{ this.ui.expand(); - this.fireEvent("expand", this); + this.fireEvent('expand', this); this.runCallback(callback, scope || this, [this]); } }else{ @@ -46500,8 +47835,8 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { this.expandChildNodes(true); } }, - - runCallback: function(cb, scope, args){ + + runCallback : function(cb, scope, args){ if(Ext.isFunction(cb)){ cb.apply(scope, args); } @@ -46522,13 +47857,13 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { */ collapse : function(deep, anim, callback, scope){ if(this.expanded && !this.isHiddenRoot()){ - if(this.fireEvent("beforecollapse", this, deep, anim) === false){ + if(this.fireEvent('beforecollapse', this, deep, anim) === false){ return; } this.expanded = false; if((this.getOwnerTree().animate && anim !== false) || anim){ this.ui.animCollapse(function(){ - this.fireEvent("collapse", this); + this.fireEvent('collapse', this); this.runCallback(callback, scope || this, [this]); if(deep === true){ this.collapseChildNodes(true); @@ -46537,7 +47872,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { return; }else{ this.ui.collapse(); - this.fireEvent("collapse", this); + this.fireEvent('collapse', this); this.runCallback(callback, scope || this, [this]); } }else if(!this.expanded){ @@ -46623,7 +47958,7 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { if(this.rendered && this.ui.onDisableChange){ // event without subscribing this.ui.onDisableChange(this, true); } - this.fireEvent("disabledchange", this, true); + this.fireEvent('disabledchange', this, true); }, /** @@ -46634,13 +47969,13 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { if(this.rendered && this.ui.onDisableChange){ // event without subscribing this.ui.onDisableChange(this, false); } - this.fireEvent("disabledchange", this, false); + this.fireEvent('disabledchange', this, false); }, // private renderChildren : function(suppressEvent){ if(suppressEvent !== false){ - this.fireEvent("beforechildrenrendered", this); + this.fireEvent('beforechildrenrendered', this); } var cs = this.childNodes; for(var i = 0, len = cs.length; i < len; i++){ @@ -46709,9 +48044,9 @@ Ext.extend(Ext.tree.TreeNode, Ext.data.Node, { this.ui.destroy(); } }, - + // private - onIdChange: function(id){ + onIdChange : function(id){ this.ui.onIdChange(id); } }); @@ -46953,10 +48288,7 @@ Ext.tree.TreeNodeUI.prototype = { this.node.on("move", this.onMove, this); if(this.node.disabled){ - this.addClass("x-tree-node-disabled"); - if (this.checkbox) { - this.checkbox.disabled = true; - } + this.onDisableChange(this.node, true); } if(this.node.hidden){ this.hide(); @@ -47041,13 +48373,15 @@ Ext.tree.TreeNodeUI.prototype = { if(this.disabled){ return; } - if(this.checkbox){ - this.toggleCheck(); - } - if(!this.animating && this.node.isExpandable()){ - this.node.toggle(); + if(this.fireEvent("beforedblclick", this.node, e) !== false){ + if(this.checkbox){ + this.toggleCheck(); + } + if(!this.animating && this.node.isExpandable()){ + this.node.toggle(); + } + this.fireEvent("dblclick", this.node, e); } - this.fireEvent("dblclick", this.node, e); }, onOver : function(e){ @@ -47606,7 +48940,7 @@ paramOrder: 'param1|param2|param' } } if(this.doPreload(node)){ // preloaded json children - this.runCallback(callback, scope || node, []); + this.runCallback(callback, scope || node, [node]); }else if(this.directFn || this.dataUrl || this.url){ this.requestData(node, callback, scope || node); } @@ -47716,7 +49050,7 @@ paramOrder: 'param1|param2|param' * Example:

        
         new Ext.tree.TreePanel({
             ...
        -    new Ext.tree.TreeLoader({
        +    loader: new Ext.tree.TreeLoader({
                 url: 'dataUrl',
                 createNode: function(attr) {
         //          Allow consolidation consignments to have
        @@ -47725,7 +49059,7 @@ new Ext.tree.TreePanel({
                         attr.iconCls = 'x-consol',
                         attr.allowDrop = true;
                     }
        -            return Ext.tree.TreeLoader.prototype.call(this, attr);
        +            return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
                 }
             }),
             ...
        @@ -47738,7 +49072,7 @@ new Ext.tree.TreePanel({
                 if(this.baseAttrs){
                     Ext.applyIf(attr, this.baseAttrs);
                 }
        -        if(this.applyLoader !== false){
        +        if(this.applyLoader !== false && !attr.loader){
                     attr.loader = this;
                 }
                 if(typeof attr.uiProvider == 'string'){
        @@ -49334,6 +50668,19 @@ Ext.FlashComponent = Ext.extend(Ext.BoxComponent, {
              * The wmode of the flash object. This can be used to control layering. Defaults to 'opaque'.
              */
             wmode: 'opaque',
        +    
        +    /**
        +     * @cfg {Object} flashVars
        +     * A set of key value pairs to be passed to the flash object as flash variables. Defaults to undefined.
        +     */
        +    flashVars: undefined,
        +    
        +    /**
        +     * @cfg {Object} flashParams
        +     * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
        +     * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to undefined.
        +     */
        +    flashParams: undefined,
         
             /**
              * @cfg {String} url
        @@ -49360,15 +50707,15 @@ Ext.FlashComponent = Ext.extend(Ext.BoxComponent, {
             onRender : function(){
                 Ext.FlashComponent.superclass.onRender.apply(this, arguments);
         
        -        var params = {
        +        var params = Ext.apply({
                     allowScriptAccess: 'always',
                     bgcolor: this.backgroundColor,
                     wmode: this.wmode
        -        }, vars = {
        +        }, this.flashParams), vars = Ext.apply({
                     allowedDomain: document.location.hostname,
                     elementID: this.getId(),
                     eventHandler: 'Ext.FlashEventProxy.onEvent'
        -        };
        +        }, this.flashVars);
         
                 new swfobject.embedSWF(this.url, this.id, this.swfWidth, this.swfHeight, this.flashVersion,
                     this.expressInstall ? Ext.FlashComponent.EXPRESS_INSTALL_URL : undefined, vars, params);
        @@ -49572,13 +50919,14 @@ Ext.FlashEventProxy = {
              */
             bindStore : function(store, initial){
                 if(!initial && this.store){
        -            this.store.un("datachanged", this.refresh, this);
        -            this.store.un("add", this.delayRefresh, this);
        -            this.store.un("remove", this.delayRefresh, this);
        -            this.store.un("update", this.delayRefresh, this);
        -            this.store.un("clear", this.refresh, this);
                     if(store !== this.store && this.store.autoDestroy){
                         this.store.destroy();
        +            }else{
        +                this.store.un("datachanged", this.refresh, this);
        +                this.store.un("add", this.delayRefresh, this);
        +                this.store.un("remove", this.delayRefresh, this);
        +                this.store.un("update", this.delayRefresh, this);
        +                this.store.un("clear", this.refresh, this);
                     }
                 }
                 if(store){
        @@ -49603,7 +50951,7 @@ Ext.FlashEventProxy = {
                 this.swf.setType(this.type);
         
                 if(this.chartStyle){
        -            this.setStyles(Ext.apply(this.extraStyle || {}, this.chartStyle));
        +            this.setStyles(Ext.apply({}, this.extraStyle, this.chartStyle));
                 }
         
                 if(this.categoryNames){
        @@ -49685,7 +51033,11 @@ Ext.FlashEventProxy = {
             
             onDestroy: function(){
                 Ext.chart.Chart.superclass.onDestroy.call(this);
        -        delete window[this.tipFnName];
        +        this.bindStore(null);
        +        var tip = this.tipFnName;
        +        if(!Ext.isEmpty(tip)){
        +            delete window[tip];
        +        }
             }
         });
         Ext.reg('chart', Ext.chart.Chart);
        @@ -50153,10 +51505,13 @@ Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, {
          * 

        Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.

        */ Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, { - monitorResize: true, + monitorResize : true, setContainer : function(ct){ this.monitorResize = !ct.floating; + // This event is only fired by the menu in IE, used so we don't couple + // the menu with the layout. + ct.on('autosize', this.doAutoSize, this); Ext.layout.MenuLayout.superclass.setContainer.call(this, ct); }, @@ -50183,13 +51538,14 @@ Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, { this.itemTpl.append(target, a, true)); // Link the containing
      • to the item. - c.positionEl.menuItemId = c.itemId || c.id; + c.positionEl.menuItemId = c.getItemId(); // If rendering a regular Component, and it needs an icon, // move the Component rightwards. if (!a.isMenuItem && a.needsIcon) { c.positionEl.addClass('x-menu-list-item-indent'); } + this.configureItem(c, position); }else if(c && !this.isValidParent(c, target)){ if(Ext.isNumber(position)){ position = target.dom.childNodes[position]; @@ -50198,7 +51554,7 @@ Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, { } }, - getItemArgs: function(c) { + getItemArgs : function(c) { var isMenuItem = c instanceof Ext.menu.Item; return { isMenuItem: isMenuItem, @@ -50206,12 +51562,12 @@ Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, { icon: c.icon || Ext.BLANK_IMAGE_URL, iconCls: 'x-menu-item-icon ' + (c.iconCls || ''), itemId: 'x-menu-el-' + c.id, - itemCls: 'x-menu-list-item ' + (this.extraCls || '') + itemCls: 'x-menu-list-item ' }; }, // Valid if the Component is in a
      • which is part of our target
          - isValidParent: function(c, target) { + isValidParent : function(c, target) { return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target); }, @@ -50268,20 +51624,20 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { */ minWidth : 120, /** - * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" - * for bottom-right shadow (defaults to "sides") + * @cfg {Boolean/String} shadow True or 'sides' for the default effect, 'frame' for 4-way shadow, and 'drop' + * for bottom-right shadow (defaults to 'sides') */ - shadow : "sides", + shadow : 'sides', /** * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of - * this menu (defaults to "tl-tr?") + * this menu (defaults to 'tl-tr?') */ - subMenuAlign : "tl-tr?", + subMenuAlign : 'tl-tr?', /** * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu - * relative to its element of origin (defaults to "tl-bl?") + * relative to its element of origin (defaults to 'tl-bl?') */ - defaultAlign : "tl-bl?", + defaultAlign : 'tl-bl?', /** * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false) */ @@ -50294,43 +51650,59 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { /** * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true). */ - enableScrolling: true, + enableScrolling : true, /** * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null). */ - maxHeight: null, + maxHeight : null, /** * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24). */ - scrollIncrement: 24, + scrollIncrement : 24, /** * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true). */ - showSeparator: true, + showSeparator : true, /** * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to * change the default Menu popup position after aligning according to the {@link #defaultAlign} * configuration. Defaults to [0, 0]. */ defaultOffsets : [0, 0], - + + /** + * @cfg {Boolean} plain + * True to remove the incised line down the left side of the menu. Defaults to false. + */ + plain : false, /** * @cfg {Boolean} floating - * May be specified as false to create a Menu which may be used as a child item of another Container - * instead of a free-floating {@link Ext.Layer Layer}. (defaults to true). + *

          By default, a Menu configured as floating:true + * will be rendered as an {@link Ext.Layer} (an absolutely positioned, + * floating Component with zindex=15000). + * If configured as floating:false, the Menu may be + * used as child item of another Container instead of a free-floating + * {@link Ext.Layer Layer}. */ - floating: true, // Render as a Layer by default + floating : true, // private - hidden: true, - layout: 'menu', - hideMode: 'offsets', // Important for laying out Components - scrollerHeight: 8, - autoLayout: true, // Provided for backwards compat - defaultType: 'menuitem', + hidden : true, - initComponent: function(){ + /** + * @cfg {String/Object} layout + * This class assigns a default layout (layout:'menu'). + * Developers may override this configuration option if another layout is required. + * See {@link Ext.Container#layout} for additional information. + */ + layout : 'menu', + hideMode : 'offsets', // Important for laying out Components + scrollerHeight : 8, + autoLayout : true, // Provided for backwards compat + defaultType : 'menuitem', + + initComponent : function(){ if(Ext.isArray(this.initialConfig)){ Ext.apply(this, {items:this.initialConfig}); } @@ -50443,7 +51815,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { // private findTargetItem : function(e){ - var t = e.getTarget(".x-menu-list-item", this.ul, true); + var t = e.getTarget('.x-menu-list-item', this.ul, true); if(t && t.menuItemId){ return this.items.get(t.menuItemId); } @@ -50455,13 +51827,13 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { if(t){ if(t.isFormField){ this.setActiveItem(t); - }else{ + }else if(t instanceof Ext.menu.BaseItem){ if(t.menu && this.ignoreParentClicks){ t.expandMenu(); e.preventDefault(); }else if(t.onClick){ t.onClick(e); - this.fireEvent("click", this, t, e); + this.fireEvent('click', this, t, e); } } } @@ -50481,7 +51853,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { } }, - deactivateActive: function(){ + deactivateActive : function(){ var a = this.activeItem; if(a){ if(a.isFormField){ @@ -50518,7 +51890,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { } } this.over = true; - this.fireEvent("mouseover", this, e, t); + this.fireEvent('mouseover', this, e, t); }, // private @@ -50531,11 +51903,11 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { } } this.over = false; - this.fireEvent("mouseout", this, e, t); + this.fireEvent('mouseout', this, e, t); }, // private - onScroll: function(e, t){ + onScroll : function(e, t){ if(e){ e.stopEvent(); } @@ -50547,7 +51919,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { }, // private - onScrollerIn: function(e, t){ + onScrollerIn : function(e, t){ var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top'); if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){ Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']); @@ -50555,12 +51927,13 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { }, // private - onScrollerOut: function(e, t){ + onScrollerOut : function(e, t){ Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']); }, /** - * Displays this menu relative to another element + * If {@link #floating}=true, shows this menu relative to + * another element using {@link #showat}, otherwise uses {@link Ext.Component#show}. * @param {Mixed} element The element to align to * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to * the element (defaults to this.defaultAlign) @@ -50573,42 +51946,51 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { this.render(); this.doLayout(false, true); } - if(this.fireEvent('beforeshow', this) !== false){ - this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu, false); - } + this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu); }else{ Ext.menu.Menu.superclass.show.call(this); } }, /** - * Displays this menu at a specific xy position + * Displays this menu at a specific xy position and fires the 'show' event if a + * handler for the 'beforeshow' event does not return false cancelling the operation. * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based) * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined) */ - showAt : function(xy, parentMenu, /* private: */_e){ - this.parentMenu = parentMenu; - if(!this.el){ - this.render(); - } - this.el.setXY(xy); - if(this.enableScrolling){ - this.constrainScroll(xy[1]); - } - this.el.show(); - Ext.menu.Menu.superclass.onShow.call(this); - if(Ext.isIE){ - this.layout.doAutoSize(); - if(!Ext.isIE8){ - this.el.repaint(); + showAt : function(xy, parentMenu){ + if(this.fireEvent('beforeshow', this) !== false){ + this.parentMenu = parentMenu; + if(!this.el){ + this.render(); + } + if(this.enableScrolling){ + // set the position so we can figure out the constrain value. + this.el.setXY(xy); + //constrain the value, keep the y coordinate the same + this.constrainScroll(xy[1]); + xy = [this.el.adjustForConstraints(xy)[0], xy[1]]; + }else{ + //constrain to the viewport. + xy = this.el.adjustForConstraints(xy); + } + this.el.setXY(xy); + this.el.show(); + Ext.menu.Menu.superclass.onShow.call(this); + if(Ext.isIE){ + // internal event, used so we don't couple the layout to the menu + this.fireEvent('autosize', this); + if(!Ext.isIE8){ + this.el.repaint(); + } } + this.hidden = false; + this.focus(); + this.fireEvent('show', this); } - this.hidden = false; - this.focus(); - this.fireEvent("show", this); }, - constrainScroll: function(y){ + constrainScroll : function(y){ var max, full = this.ul.setHeight('auto').getHeight(); if(this.floating){ max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize().height - y; @@ -50627,7 +52009,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { this.ul.dom.scrollTop = 0; }, - createScrollers: function(){ + createScrollers : function(){ if(!this.scroller){ this.scroller = { pos: 0, @@ -50657,7 +52039,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { } }, - onLayout: function(){ + onLayout : function(){ if(this.isVisible()){ if(this.enableScrolling){ this.constrainScroll(this.el.getTop()); @@ -50691,19 +52073,24 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { }, // private - onHide: function(){ + onHide : function(){ Ext.menu.Menu.superclass.onHide.call(this); this.deactivateActive(); if(this.el && this.floating){ this.el.hide(); } - if(this.deepHide === true && this.parentMenu){ - this.parentMenu.hide(true); + var pm = this.parentMenu; + if(this.deepHide === true && pm){ + if(pm.floating){ + pm.hide(true); + }else{ + pm.deactivateActive(); + } } }, // private - lookupComponent: function(c){ + lookupComponent : function(c){ if(Ext.isString(c)){ c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c); this.applyDefaults(c); @@ -50736,7 +52123,7 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { }, // private - getMenuItem: function(config){ + getMenuItem : function(config){ if(!config.isXType){ if(!config.xtype && Ext.isBoolean(config.checked)){ return new Ext.menu.CheckItem(config) @@ -50802,6 +52189,11 @@ Ext.menu.Menu = Ext.extend(Ext.Container, { if(s){ Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom); } + Ext.destroy( + this.el, + this.focusEl, + this.ul + ); } }); @@ -50820,7 +52212,7 @@ Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){ } } return { - constructor: function(menu){ + constructor : function(menu){ Ext.menu.MenuNav.superclass.constructor.call(this, menu.el); this.scope = this.menu = menu; }, @@ -50868,12 +52260,13 @@ Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){ if(m.activeItem){ e.stopPropagation(); m.activeItem.onClick(e); - m.fireEvent("click", this, m.activeItem); + m.fireEvent('click', this, m.activeItem); return true; } } }; -}());/** +}()); +/** * @class Ext.menu.MenuMgr * Provides a common registry of all menu items on a page so that they can be easily accessed by id. * @singleton @@ -51220,8 +52613,13 @@ Ext.extend(Ext.menu.BaseItem, Ext.Component, { // private handleClick : function(e){ + var pm = this.parentMenu; if(this.hideOnClick){ - this.parentMenu.hide.defer(this.clickHideDelay, this.parentMenu, [true]); + if(pm.floating){ + pm.hide.defer(this.clickHideDelay, pm, [true]); + }else{ + pm.deactivateActive(); + } } }, @@ -51623,7 +53021,15 @@ Ext.extend(Ext.menu.CheckItem, Ext.menu.Item, { Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @class Ext.menu.DateMenu * @extends Ext.menu.Menu - * A menu containing a {@link Ext.DatePicker} Component. + *

          A menu containing an {@link Ext.DatePicker} Component.

          + *

          Notes:

            + *
          • Although not listed here, the constructor for this class + * accepts all of the configuration options of {@link Ext.DatePicker}.
          • + *
          • If subclassing DateMenu, any configuration options for the DatePicker must be + * applied to the initialConfig property of the DateMenu. + * Applying {@link Ext.DatePicker DatePicker} configuration settings to + * this will not affect the DatePicker's configuration.
          • + *
          * @xtype datemenu */ Ext.menu.DateMenu = Ext.extend(Ext.menu.Menu, { @@ -51631,13 +53037,31 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @cfg {Boolean} enableScrolling * @hide */ - enableScrolling: false, - + enableScrolling : false, + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this menu. + * The handler is passed the following parameters:
            + *
          • picker : DatePicker
            The Ext.DatePicker.
          • + *
          • date : Date
            The selected date.
          • + *
          + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this DateMenu instance. + */ /** * @cfg {Boolean} hideOnClick * False to continue showing the menu after a date is selected, defaults to true. */ - hideOnClick: true, + hideOnClick : true, + + /** + * @cfg {String} pickerId + * An id to assign to the underlying date picker. Defaults to null. + */ + pickerId : null, /** * @cfg {Number} maxHeight @@ -51648,11 +53072,11 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @hide */ /** + * The {@link Ext.DatePicker} instance for this DateMenu * @property picker * @type DatePicker - * The {@link Ext.DatePicker} instance for this DateMenu */ - cls: 'x-date-menu', + cls : 'x-date-menu', /** * @event click @@ -51664,7 +53088,7 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @hide */ - initComponent: function(){ + initComponent : function(){ this.on('beforeshow', this.onBeforeShow, this); if(this.strict = (Ext.isIE7 && Ext.isStrict)){ this.on('show', this.onShow, this, {single: true, delay: 20}); @@ -51672,41 +53096,57 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** Ext.apply(this, { plain: true, showSeparator: false, - items: this.picker = new Ext.DatePicker(Ext.apply({ + items: this.picker = new Ext.DatePicker(Ext.applyIf({ internalRender: this.strict || !Ext.isIE, - ctCls: 'x-menu-date-item' + ctCls: 'x-menu-date-item', + id: this.pickerId }, this.initialConfig)) }); this.picker.purgeListeners(); Ext.menu.DateMenu.superclass.initComponent.call(this); - this.relayEvents(this.picker, ["select"]); + /** + * @event select + * Fires when a date is selected from the {@link #picker Ext.DatePicker} + * @param {DatePicker} picker The {@link #picker Ext.DatePicker} + * @param {Date} date The selected date + */ + this.relayEvents(this.picker, ['select']); this.on('select', this.menuHide, this); if(this.handler){ this.on('select', this.handler, this.scope || this); } }, - menuHide: function() { + menuHide : function() { if(this.hideOnClick){ this.hide(true); } }, - onBeforeShow: function(){ + onBeforeShow : function(){ if(this.picker){ this.picker.hideMonthPicker(true); } }, - onShow: function(){ + onShow : function(){ var el = this.picker.getEl(); el.setWidth(el.getWidth()); //nasty hack for IE7 strict mode } }); - Ext.reg('datemenu', Ext.menu.DateMenu);/** + Ext.reg('datemenu', Ext.menu.DateMenu); + /** * @class Ext.menu.ColorMenu * @extends Ext.menu.Menu - * A menu containing a {@link Ext.ColorPalette} Component. + *

          A menu containing a {@link Ext.ColorPalette} Component.

          + *

          Notes:

            + *
          • Although not listed here, the constructor for this class + * accepts all of the configuration options of {@link Ext.ColorPalette}.
          • + *
          • If subclassing ColorMenu, any configuration options for the ColorPalette must be + * applied to the initialConfig property of the ColorMenu. + * Applying {@link Ext.ColorPalette ColorPalette} configuration settings to + * this will not affect the ColorPalette's configuration.
          • + *
          * * @xtype colormenu */ Ext.menu.ColorMenu = Ext.extend(Ext.menu.Menu, { @@ -51714,13 +53154,34 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @cfg {Boolean} enableScrolling * @hide */ - enableScrolling: false, + enableScrolling : false, + /** + * @cfg {Function} handler + * Optional. A function that will handle the select event of this menu. + * The handler is passed the following parameters:
            + *
          • palette : ColorPalette
            The {@link #palette Ext.ColorPalette}.
          • + *
          • color : String
            The 6-digit color hex code (without the # symbol).
          • + *
          + */ + /** + * @cfg {Object} scope + * The scope (this reference) in which the {@link #handler} + * function will be called. Defaults to this ColorMenu instance. + */ /** * @cfg {Boolean} hideOnClick * False to continue showing the menu after a color is selected, defaults to true. */ - hideOnClick: true, + hideOnClick : true, + + cls : 'x-color-menu', + + /** + * @cfg {String} paletteId + * An id to assign to the underlying color palette. Defaults to null. + */ + paletteId : null, /** * @cfg {Number} maxHeight @@ -51747,28 +53208,37 @@ Ext.reg('menucheckitem', Ext.menu.CheckItem);/** * @hide */ - initComponent: function(){ + initComponent : function(){ Ext.apply(this, { plain: true, showSeparator: false, - items: this.palette = new Ext.ColorPalette(this.initialConfig) + items: this.palette = new Ext.ColorPalette(Ext.applyIf({ + id: this.paletteId + }, this.initialConfig)) }); this.palette.purgeListeners(); Ext.menu.ColorMenu.superclass.initComponent.call(this); + /** + * @event select + * Fires when a color is selected from the {@link #palette Ext.ColorPalette} + * @param {Ext.ColorPalette} palette The {@link #palette Ext.ColorPalette} + * @param {String} color The 6-digit color hex code (without the # symbol) + */ this.relayEvents(this.palette, ['select']); this.on('select', this.menuHide, this); if(this.handler){ - this.on('select', this.handler, this.scope || this) + this.on('select', this.handler, this.scope || this); } }, - menuHide: function(){ + menuHide : function(){ if(this.hideOnClick){ this.hide(true); } } }); -Ext.reg('colormenu', Ext.menu.ColorMenu);/** +Ext.reg('colormenu', Ext.menu.ColorMenu); +/** * @class Ext.form.Field * @extends Ext.BoxComponent * Base class for form fields that provides default event handling, sizing, value handling and other functionality. @@ -51780,7 +53250,7 @@ Ext.reg('colormenu', Ext.menu.ColorMenu);/** Ext.form.Field = Ext.extend(Ext.BoxComponent, { /** * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults - * to "text"). The types "file" and "password" must be used to render those field types currently -- there are + * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are * no separate Ext components for those. Note that if you use inputType:'file', {@link #emptyText} * is not supported and should be avoided. */ @@ -51792,32 +53262,37 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {Mixed} value A value to initialize this field with (defaults to undefined). */ /** - * @cfg {String} name The field's HTML name attribute (defaults to ""). + * @cfg {String} name The field's HTML name attribute (defaults to ''). * Note: this property must be set if this field is to be automatically included with * {@link Ext.form.BasicForm#submit form submit()}. */ /** - * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ""). + * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to ''). */ /** - * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid") + * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to 'x-form-invalid') */ - invalidClass : "x-form-invalid", + invalidClass : 'x-form-invalid', /** * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided - * (defaults to "The value in this field is invalid") + * (defaults to 'The value in this field is invalid') + */ + invalidText : 'The value in this field is invalid', + /** + * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to 'x-form-focus') */ - invalidText : "The value in this field is invalid", + focusClass : 'x-form-focus', /** - * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus") + * @cfg {Boolean} preventMark + * true to disable {@link #markInvalid marking the field invalid}. + * Defaults to false. */ - focusClass : "x-form-focus", /** * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable - automatic validation (defaults to "keyup"). + automatic validation (defaults to 'keyup'). */ - validationEvent : "keyup", + validationEvent : 'keyup', /** * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true). */ @@ -51831,13 +53306,13 @@ Ext.form.Field = Ext.extend(Ext.BoxComponent, { * @cfg {String/Object} autoCreate

          A {@link Ext.DomHelper DomHelper} element spec, or true for a default * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

          - *
          {tag: "input", type: "text", size: "20", autocomplete: "off"}
          + *
          {tag: 'input', type: 'text', size: '20', autocomplete: 'off'}
          */ - defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"}, + defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'}, /** - * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field") + * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field') */ - fieldClass : "x-form-field", + fieldClass : 'x-form-field', /** * @cfg {String} msgTarget The location where error text should display. Should be one of the following values * (defaults to 'qtip'): @@ -51877,6 +53352,9 @@ side Add an error icon to the right of the field with a popup on hover // private isFormField : true, + // private + msgDisplay: '', + // private hasFocus : false, @@ -51957,9 +53435,9 @@ var form = new Ext.form.FormPanel({ /** * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} * attribute of the field if available. - * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} + * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName} */ - getName: function(){ + getName : function(){ return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || ''; }, @@ -51977,7 +53455,7 @@ var form = new Ext.form.FormPanel({ this.autoEl = cfg; } Ext.form.Field.superclass.onRender.call(this, ct, position); - + var type = this.el.dom.type; if(type){ if(type == 'password'){ @@ -51997,7 +53475,7 @@ var form = new Ext.form.FormPanel({ // private getItemCt : function(){ - return this.el.up('.x-form-item', 4); + return this.itemCt; }, // private @@ -52044,7 +53522,7 @@ var form = new Ext.form.FormPanel({ // private fireKey : function(e){ if(e.isSpecialKey()){ - this.fireEvent("specialkey", this, e); + this.fireEvent('specialkey', this, e); } }, @@ -52059,23 +53537,27 @@ var form = new Ext.form.FormPanel({ // private initEvents : function(){ - this.mon(this.el, Ext.EventManager.useKeydown ? "keydown" : "keypress", this.fireKey, this); + this.mon(this.el, Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.fireKey, this); this.mon(this.el, 'focus', this.onFocus, this); - // fix weird FF/Win editor issue when changing OS window focus - var o = this.inEditor && Ext.isWindows && Ext.isGecko ? {buffer:10} : null; - this.mon(this.el, 'blur', this.onBlur, this, o); + // standardise buffer across all browsers + OS-es for consistent event order. + // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus) + this.mon(this.el, 'blur', this.onBlur, this, this.inEditor ? {buffer:10} : null); }, + // private + preFocus: Ext.emptyFn, + // private onFocus : function(){ + this.preFocus(); if(this.focusClass){ this.el.addClass(this.focusClass); } if(!this.hasFocus){ this.hasFocus = true; this.startValue = this.getValue(); - this.fireEvent("focus", this); + this.fireEvent('focus', this); } }, @@ -52089,18 +53571,24 @@ var form = new Ext.form.FormPanel({ this.el.removeClass(this.focusClass); } this.hasFocus = false; - if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){ + if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent != 'blur')){ this.validate(); } var v = this.getValue(); if(String(v) !== String(this.startValue)){ this.fireEvent('change', this, v, this.startValue); } - this.fireEvent("blur", this); + this.fireEvent('blur', this); + this.postBlur(); }, + // private + postBlur : Ext.emptyFn, + /** - * Returns whether or not the field value is currently valid + * Returns whether or not the field value is currently valid by + * {@link #validateValue validating} the {@link #processValue processed value} + * of the field. Note: {@link #disabled} fields are ignored. * @param {Boolean} preventMark True to disable marking the field invalid * @return {Boolean} True if the value is valid, else false */ @@ -52127,20 +53615,31 @@ var form = new Ext.form.FormPanel({ return false; }, - // protected - should be overridden by subclasses if necessary to prepare raw values for validation + /** + * This method should only be overridden if necessary to prepare raw values + * for validation (see {@link #validate} and {@link #isValid}). This method + * is expected to return the processed value for the field which will + * be used for validation (see validateValue method). + * @param {Mixed} value + */ processValue : function(value){ return value; }, - // private - // Subclasses should provide the validation implementation by overriding this + /** + * @private + * Subclasses should provide the validation implementation by overriding this + * @param {Mixed} value + */ validateValue : function(value){ return true; }, /** - * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and - * applying {@link #invalidClass} to the field's element. + * Mark this field as invalid, using {@link #msgTarget} to determine how to + * display the error and applying {@link #invalidClass} to the field's element. + * Note: this method does not actually make the field + * {@link #isValid invalid}. * @param {String} msg (optional) The validation message (defaults to {@link #invalidText}) */ markInvalid : function(msg){ @@ -52234,7 +53733,7 @@ var form = new Ext.form.FormPanel({ * @return {Mixed} value The field value that is set */ setRawValue : function(v){ - return (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)); + return this.rendered ? (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)) : ''; }, /** @@ -52254,26 +53753,6 @@ var form = new Ext.form.FormPanel({ // private, does not work for all fields append : function(v){ this.setValue([this.getValue(), v].join('')); - }, - - // private - adjustSize : function(w, h){ - var s = Ext.form.Field.superclass.adjustSize.call(this, w, h); - s.width = this.adjustWidth(this.el.dom.tagName, s.width); - if(this.offsetCt){ - var ct = this.getItemCt(); - s.width -= ct.getFrameWidth('lr'); - s.height -= ct.getFrameWidth('tb'); - } - return s; - }, - - // private - adjustWidth : function(tag, w){ - if(typeof w == 'number' && (Ext.isIE && (Ext.isIE6 || !Ext.isStrict)) && /input|textarea/i.test(tag) && !this.inEditor){ - return w - 3; - } - return w; } /** @@ -52409,44 +53888,10 @@ Ext.reg('field', Ext.form.Field); * or as the base class for more sophisticated input controls (like {@link Ext.form.TextArea} * and {@link Ext.form.ComboBox}).

          *

          Validation

          - *

          Field validation is processed in a particular order. If validation fails at any particular - * step the validation routine halts.

          + *

          The validation procedure is described in the documentation for {@link #validateValue}.

          + *

          Alter Validation Behavior

          + *

          Validation behavior for each field can be configured:

          *
            - *
          • 1. Field specific validator - *
            - *

            If a field is configured with a {@link Ext.form.TextField#validator validator} function, - * it will be passed the current field value. The {@link Ext.form.TextField#validator validator} - * function is expected to return boolean true if the value is valid or return a string to - * represent the invalid message if invalid.

            - *
          • - *
          • 2. Built in Validation - *
            - *

            Basic validation is affected with the following configuration properties:

            - *
            - * Validation    Invalid Message
            - * {@link Ext.form.TextField#allowBlank allowBlank}    {@link Ext.form.TextField#emptyText emptyText}
            - * {@link Ext.form.TextField#minLength minLength}     {@link Ext.form.TextField#minLengthText minLengthText}
            - * {@link Ext.form.TextField#maxLength maxLength}     {@link Ext.form.TextField#maxLengthText maxLengthText}
            - * 
            - *
          • - *
          • 3. Preconfigured Validation Types (VTypes) - *
            - *

            Using VTypes offers a convenient way to reuse validation. If a field is configured with a - * {@link Ext.form.TextField#vtype vtype}, the corresponding {@link Ext.form.VTypes VTypes} - * validation function will be used for validation. If invalid, either the field's - * {@link Ext.form.TextField#vtypeText vtypeText} or the VTypes vtype Text property will be - * used for the invalid message. Keystrokes on the field will be filtered according to the VTypes - * vtype Mask property.

            - *
          • - *
          • 4. Field specific regex test - *
            - *

            Each field may also specify a {@link Ext.form.TextField#regex regex} test. - * The invalid message for this test is configured with - * {@link Ext.form.TextField#regexText regexText}.

            - *
          • - *
          • Alter Validation Behavior - *
            - *

            Validation behavior for each field can be configured:

              *
            • {@link Ext.form.TextField#invalidText invalidText} : the default validation message to * show if any validation step above does not provide a message when invalid
            • *
            • {@link Ext.form.TextField#maskRe maskRe} : filter out keystrokes before any validation occurs
            • @@ -52456,12 +53901,11 @@ Ext.reg('field', Ext.form.Field); *
            • {@link Ext.form.Field#validateOnBlur validateOnBlur}, * {@link Ext.form.Field#validationDelay validationDelay}, and * {@link Ext.form.Field#validationEvent validationEvent} : modify how/when validation is triggered
            • - *
            - *
          • *
          - * @constructor - * Creates a new TextField + * + * @constructor Creates a new TextField * @param {Object} config Configuration options + * * @xtype textfield */ Ext.form.TextField = Ext.extend(Ext.form.Field, { @@ -52549,11 +53993,22 @@ var myField = new Ext.form.NumberField({ */ blankText : 'This field is required', /** - * @cfg {Function} validator A custom validation function to be called during field validation + * @cfg {Function} validator + *

          A custom validation function to be called during field validation ({@link #validateValue}) * (defaults to null). If specified, this function will be called first, allowing the - * developer to override the default validation process. This function will be passed the current - * field value and expected to return boolean true if the value is valid or a string - * error message if invalid. + * developer to override the default validation process.

          + *

          This function will be passed the following Parameters:

          + *
            + *
          • value: Mixed + *
            The current field value
          • + *
          + *

          This function is to Return:

          + *
            + *
          • true: Boolean + *
            true if the value is valid
          • + *
          • msg: String + *
            An error message if the value is invalid
          • + *
          */ validator : null, /** @@ -52632,22 +54087,13 @@ var myField = new Ext.form.NumberField({ this.validationTask = new Ext.util.DelayedTask(this.validate, this); this.mon(this.el, 'keyup', this.filterValidation, this); } - else if(this.validationEvent !== false){ + else if(this.validationEvent !== false && this.validationEvent != 'blur'){ this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay}); } - if(this.selectOnFocus || this.emptyText){ - this.on('focus', this.preFocus, this); - - this.mon(this.el, 'mousedown', function(){ - if(!this.hasFocus){ - this.el.on('mouseup', function(e){ - e.preventDefault(); - }, this, {single:true}); - } - }, this); + if(this.selectOnFocus || this.emptyText){ + this.mon(this.el, 'mousedown', this.onMouseDown, this); if(this.emptyText){ - this.on('blur', this.postBlur, this); this.applyEmptyText(); } } @@ -52659,9 +54105,18 @@ var myField = new Ext.form.NumberField({ this.mon(this.el, 'click', this.autoSize, this); } if(this.enableKeyEvents){ - this.mon(this.el, 'keyup', this.onKeyUp, this); - this.mon(this.el, 'keydown', this.onKeyDown, this); - this.mon(this.el, 'keypress', this.onKeyPress, this); + this.mon(this.el, { + scope: this, + keyup: this.onKeyUp, + keydown: this.onKeyDown, + keypress: this.onKeyPress + }); + } + }, + + onMouseDown: function(e){ + if(!this.hasFocus){ + this.mon(this.el, 'mouseup', Ext.emptyFn, this, { single: true, preventDefault: true }); } }, @@ -52747,9 +54202,7 @@ var myField = new Ext.form.NumberField({ el.removeClass(this.emptyClass); } if(this.selectOnFocus){ - (function(){ - el.dom.select(); - }).defer(this.inEditor && Ext.isIE ? 50 : 0); + el.dom.select(); } }, @@ -52781,8 +54234,70 @@ var myField = new Ext.form.NumberField({ }, /** - * Validates a value according to the field's validation rules and marks the field as invalid - * if the validation fails + *

          Validates a value according to the field's validation rules and marks the field as invalid + * if the validation fails. Validation rules are processed in the following order:

          + *
            + * + *
          • 1. Field specific validator + *
            + *

            A validator offers a way to customize and reuse a validation specification. + * If a field is configured with a {@link #validator} + * function, it will be passed the current field value. The {@link #validator} + * function is expected to return either: + *

              + *
            • Boolean true if the value is valid (validation continues).
            • + *
            • a String to represent the invalid message if invalid (validation halts).
            • + *
            + *
          • + * + *
          • 2. Basic Validation + *
            + *

            If the {@link #validator} has not halted validation, + * basic validation proceeds as follows:

            + * + *
              + * + *
            • {@link #allowBlank} : (Invalid message = + * {@link #emptyText})
              + * Depending on the configuration of {@link #allowBlank}, a + * blank field will cause validation to halt at this step and return + * Boolean true or false accordingly. + *
            • + * + *
            • {@link #minLength} : (Invalid message = + * {@link #minLengthText})
              + * If the passed value does not satisfy the {@link #minLength} + * specified, validation halts. + *
            • + * + *
            • {@link #maxLength} : (Invalid message = + * {@link #maxLengthText})
              + * If the passed value does not satisfy the {@link #maxLength} + * specified, validation halts. + *
            • + * + *
            + *
          • + * + *
          • 3. Preconfigured Validation Types (VTypes) + *
            + *

            If none of the prior validation steps halts validation, a field + * configured with a {@link #vtype} will utilize the + * corresponding {@link Ext.form.VTypes VTypes} validation function. + * If invalid, either the field's {@link #vtypeText} or + * the VTypes vtype Text property will be used for the invalid message. + * Keystrokes on the field will be filtered according to the VTypes + * vtype Mask property.

            + *
          • + * + *
          • 4. Field specific regex test + *
            + *

            If none of the prior validation steps halts validation, a field's + * configured {@link #regex} test will be processed. + * The invalid message for this test is configured with + * {@link #regexText}.

            + *
          • + * * @param {Mixed} value The value to validate * @return {Boolean} True if the value is valid, else false */ @@ -52887,327 +54402,347 @@ var myField = new Ext.form.NumberField({ } }); Ext.reg('textfield', Ext.form.TextField); -/** - * @class Ext.form.TriggerField - * @extends Ext.form.TextField - * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default). - * The trigger has no default action, so you must assign a function to implement the trigger click handler by - * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox - * for which you can provide a custom implementation. For example: - *
            
            -var trigger = new Ext.form.TriggerField();
            -trigger.onTriggerClick = myTriggerFn;
            -trigger.applyToMarkup('my-field');
            -
            - * - * However, in general you will most likely want to use TriggerField as the base class for a reusable component. - * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this. - * - * @constructor - * Create a new TriggerField. - * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied - * to the base TextField) - * @xtype trigger - */ -Ext.form.TriggerField = Ext.extend(Ext.form.TextField, { - /** - * @cfg {String} triggerClass - * An additional CSS class used to style the trigger button. The trigger will always get the - * class 'x-form-trigger' by default and triggerClass will be appended if specified. - */ - /** - * @cfg {Mixed} triggerConfig - *

            A {@link Ext.DomHelper DomHelper} config object specifying the structure of the - * trigger element for this Field. (Optional).

            - *

            Specify this when you need a customized element to act as the trigger button for a TriggerField.

            - *

            Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning - * and appearance of the trigger. Defaults to:

            - *
            {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}
            - */ - /** - * @cfg {String/Object} autoCreate

            A {@link Ext.DomHelper DomHelper} element spec, or true for a default - * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. - * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

            - *
            {tag: "input", type: "text", size: "16", autocomplete: "off"}
            - */ - defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"}, - /** - * @cfg {Boolean} hideTrigger true to hide the trigger element and display only the base - * text field (defaults to false) - */ - hideTrigger:false, - /** - * @cfg {Boolean} editable false to prevent the user from typing text directly into the field, - * the field will only respond to a click on the trigger to set the value. (defaults to true) - */ - editable: true, - /** - * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to - * x-trigger-wrap-focus. - */ - wrapFocusClass: 'x-trigger-wrap-focus', - /** - * @hide - * @method autoSize - */ - autoSize: Ext.emptyFn, - // private - monitorTab : true, - // private - deferHeight : true, - // private - mimicing : false, - - actionMode: 'wrap', - - // private - onResize : function(w, h){ - Ext.form.TriggerField.superclass.onResize.call(this, w, h); - if(typeof w == 'number'){ - this.el.setWidth(this.adjustWidth('input', w - this.trigger.getWidth())); - } - this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth()); - }, - - // private - adjustSize : Ext.BoxComponent.prototype.adjustSize, - - // private - getResizeEl : function(){ - return this.wrap; - }, - - // private - getPositionEl : function(){ - return this.wrap; - }, - - // private - alignErrorIcon : function(){ - if(this.wrap){ - this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); - } - }, - - // private - onRender : function(ct, position){ - Ext.form.TriggerField.superclass.onRender.call(this, ct, position); - - this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'}); - this.trigger = this.wrap.createChild(this.triggerConfig || - {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}); - if(this.hideTrigger){ - this.trigger.setDisplayed(false); - } - this.initTrigger(); - if(!this.width){ - this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth()); - } - if(!this.editable){ - this.editable = true; - this.setEditable(false); - } - }, - - afterRender : function(){ - Ext.form.TriggerField.superclass.afterRender.call(this); - }, - - // private - initTrigger : function(){ - this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true}); - this.trigger.addClassOnOver('x-form-trigger-over'); - this.trigger.addClassOnClick('x-form-trigger-click'); - }, - - // private - onDestroy : function(){ - Ext.destroy(this.trigger, this.wrap); - if (this.mimicing){ - Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); - } - Ext.form.TriggerField.superclass.onDestroy.call(this); - }, - - // private - onFocus : function(){ - Ext.form.TriggerField.superclass.onFocus.call(this); - if(!this.mimicing){ - this.wrap.addClass(this.wrapFocusClass); - this.mimicing = true; - Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {delay: 10}); - if(this.monitorTab){ - this.el.on('keydown', this.checkTab, this); - } - } - }, - - // private - checkTab : function(e){ - if(e.getKey() == e.TAB){ - this.triggerBlur(); - } - }, - - // private - onBlur : function(){ - // do nothing - }, - - // private - mimicBlur : function(e){ - if(!this.wrap.contains(e.target) && this.validateBlur(e)){ - this.triggerBlur(); - } - }, - - // private - triggerBlur : function(){ - this.mimicing = false; - Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this); - if(this.monitorTab && this.el){ - this.el.un("keydown", this.checkTab, this); - } - Ext.form.TriggerField.superclass.onBlur.call(this); - if(this.wrap){ - this.wrap.removeClass(this.wrapFocusClass); - } - }, - - beforeBlur : Ext.emptyFn, - - /** - * Allow or prevent the user from directly editing the field text. If false is passed, - * the user will only be able to modify the field using the trigger. This method - * is the runtime equivalent of setting the 'editable' config option at config time. - * @param {Boolean} value True to allow the user to directly edit the field text - */ - setEditable : function(value){ - if(value == this.editable){ - return; - } - this.editable = value; - if(!value){ - this.el.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true); - }else{ - this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick, this).dom.removeAttribute('readOnly'); - } - }, - - // private - // This should be overriden by any subclass that needs to check whether or not the field can be blurred. - validateBlur : function(e){ - return true; - }, - - /** - * The function that should handle the trigger's click event. This method does nothing by default - * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for - * sample implementations. - * @method - * @param {EventObject} e - */ - onTriggerClick : Ext.emptyFn - - /** - * @cfg {Boolean} grow @hide - */ - /** - * @cfg {Number} growMin @hide - */ - /** - * @cfg {Number} growMax @hide - */ -}); - -/** - * @class Ext.form.TwinTriggerField - * @extends Ext.form.TriggerField - * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class - * to be extended by an implementing class. For an example of implementing this class, see the custom - * SearchField implementation here: - * http://extjs.com/deploy/ext/examples/form/custom.html - */ -Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, { - /** - * @cfg {Mixed} triggerConfig - *

            A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements - * for this Field. (Optional).

            - *

            Specify this when you need a customized element to contain the two trigger elements for this Field. - * Each trigger element must be marked by the CSS class x-form-trigger (also see - * {@link #trigger1Class} and {@link #trigger2Class}).

            - *

            Note that when using this option, it is the developer's responsibility to ensure correct sizing, - * positioning and appearance of the triggers.

            - */ - /** - * @cfg {String} trigger1Class - * An additional CSS class used to style the trigger button. The trigger will always get the - * class 'x-form-trigger' by default and triggerClass will be appended if specified. - */ - /** - * @cfg {String} trigger2Class - * An additional CSS class used to style the trigger button. The trigger will always get the - * class 'x-form-trigger' by default and triggerClass will be appended if specified. - */ - - initComponent : function(){ - Ext.form.TwinTriggerField.superclass.initComponent.call(this); - - this.triggerConfig = { - tag:'span', cls:'x-form-twin-triggers', cn:[ - {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class}, - {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class} - ]}; - }, - - getTrigger : function(index){ - return this.triggers[index]; - }, - - initTrigger : function(){ - var ts = this.trigger.select('.x-form-trigger', true); - this.wrap.setStyle('overflow', 'hidden'); - var triggerField = this; - ts.each(function(t, all, index){ - t.hide = function(){ - var w = triggerField.wrap.getWidth(); - this.dom.style.display = 'none'; - triggerField.el.setWidth(w-triggerField.trigger.getWidth()); - }; - t.show = function(){ - var w = triggerField.wrap.getWidth(); - this.dom.style.display = ''; - triggerField.el.setWidth(w-triggerField.trigger.getWidth()); - }; - var triggerIndex = 'Trigger'+(index+1); - - if(this['hide'+triggerIndex]){ - t.dom.style.display = 'none'; - } - this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true}); - t.addClassOnOver('x-form-trigger-over'); - t.addClassOnClick('x-form-trigger-click'); - }, this); - this.triggers = ts.elements; - }, - - /** - * The function that should handle the trigger's click event. This method does nothing by default - * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} - * for additional information. - * @method - * @param {EventObject} e - */ - onTrigger1Click : Ext.emptyFn, - /** - * The function that should handle the trigger's click event. This method does nothing by default - * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} - * for additional information. - * @method - * @param {EventObject} e - */ - onTrigger2Click : Ext.emptyFn -}); +/** + * @class Ext.form.TriggerField + * @extends Ext.form.TextField + * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default). + * The trigger has no default action, so you must assign a function to implement the trigger click handler by + * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox + * for which you can provide a custom implementation. For example: + *
            
            +var trigger = new Ext.form.TriggerField();
            +trigger.onTriggerClick = myTriggerFn;
            +trigger.applyToMarkup('my-field');
            +
            + * + * However, in general you will most likely want to use TriggerField as the base class for a reusable component. + * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this. + * + * @constructor + * Create a new TriggerField. + * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied + * to the base TextField) + * @xtype trigger + */ +Ext.form.TriggerField = Ext.extend(Ext.form.TextField, { + /** + * @cfg {String} triggerClass + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + /** + * @cfg {Mixed} triggerConfig + *

            A {@link Ext.DomHelper DomHelper} config object specifying the structure of the + * trigger element for this Field. (Optional).

            + *

            Specify this when you need a customized element to act as the trigger button for a TriggerField.

            + *

            Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning + * and appearance of the trigger. Defaults to:

            + *
            {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}
            + */ + /** + * @cfg {String/Object} autoCreate

            A {@link Ext.DomHelper DomHelper} element spec, or true for a default + * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. + * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

            + *
            {tag: "input", type: "text", size: "16", autocomplete: "off"}
            + */ + defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"}, + /** + * @cfg {Boolean} hideTrigger true to hide the trigger element and display only the base + * text field (defaults to false) + */ + hideTrigger:false, + /** + * @cfg {Boolean} editable false to prevent the user from typing text directly into the field, + * the field will only respond to a click on the trigger to set the value. (defaults to true) + */ + editable: true, + /** + * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to + * x-trigger-wrap-focus. + */ + wrapFocusClass: 'x-trigger-wrap-focus', + /** + * @hide + * @method autoSize + */ + autoSize: Ext.emptyFn, + // private + monitorTab : true, + // private + deferHeight : true, + // private + mimicing : false, + + actionMode: 'wrap', + + defaultTriggerWidth: 17, + + // private + onResize : function(w, h){ + Ext.form.TriggerField.superclass.onResize.call(this, w, h); + var tw = this.getTriggerWidth(); + if(Ext.isNumber(w)){ + this.el.setWidth(w - tw); + } + this.wrap.setWidth(this.el.getWidth() + tw); + }, + + getTriggerWidth: function(){ + var tw = this.trigger.getWidth(); + if(!this.hideTrigger && tw === 0){ + tw = this.defaultTriggerWidth; + } + return tw; + }, + + // private + alignErrorIcon : function(){ + if(this.wrap){ + this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]); + } + }, + + // private + onRender : function(ct, position){ + this.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc(); + Ext.form.TriggerField.superclass.onRender.call(this, ct, position); + + this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'}); + this.trigger = this.wrap.createChild(this.triggerConfig || + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}); + if(this.hideTrigger){ + this.trigger.setDisplayed(false); + } + this.initTrigger(); + if(!this.width){ + this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth()); + } + if(!this.editable){ + this.editable = true; + this.setEditable(false); + } + this.resizeEl = this.positionEl = this.wrap; + }, + + afterRender : function(){ + Ext.form.TriggerField.superclass.afterRender.call(this); + }, + + // private + initTrigger : function(){ + this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true}); + this.trigger.addClassOnOver('x-form-trigger-over'); + this.trigger.addClassOnClick('x-form-trigger-click'); + }, + + // private + onDestroy : function(){ + Ext.destroy(this.trigger, this.wrap); + if (this.mimicing){ + this.doc.un('mousedown', this.mimicBlur, this); + } + Ext.form.TriggerField.superclass.onDestroy.call(this); + }, + + // private + onFocus : function(){ + Ext.form.TriggerField.superclass.onFocus.call(this); + if(!this.mimicing){ + this.wrap.addClass(this.wrapFocusClass); + this.mimicing = true; + this.doc.on('mousedown', this.mimicBlur, this, {delay: 10}); + if(this.monitorTab){ + this.on('specialkey', this.checkTab, this); + } + } + }, + + // private + checkTab : function(me, e){ + if(e.getKey() == e.TAB){ + this.triggerBlur(); + } + }, + + // private + onBlur : Ext.emptyFn, + + // private + mimicBlur : function(e){ + if(!this.isDestroyed && !this.wrap.contains(e.target) && this.validateBlur(e)){ + this.triggerBlur(); + } + }, + + // private + triggerBlur : function(){ + this.mimicing = false; + this.doc.un('mousedown', this.mimicBlur, this); + if(this.monitorTab && this.el){ + this.un('specialkey', this.checkTab, this); + } + Ext.form.TriggerField.superclass.onBlur.call(this); + if(this.wrap){ + this.wrap.removeClass(this.wrapFocusClass); + } + }, + + beforeBlur : Ext.emptyFn, + + /** + * Allow or prevent the user from directly editing the field text. If false is passed, + * the user will only be able to modify the field using the trigger. This method + * is the runtime equivalent of setting the 'editable' config option at config time. + * @param {Boolean} value True to allow the user to directly edit the field text + */ + setEditable : function(value){ + if(value == this.editable){ + return; + } + this.editable = value; + if(!value){ + this.el.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true); + }else{ + this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick, this).dom.removeAttribute('readOnly'); + } + }, + + // private + // This should be overriden by any subclass that needs to check whether or not the field can be blurred. + validateBlur : function(e){ + return true; + }, + + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for + * sample implementations. + * @method + * @param {EventObject} e + */ + onTriggerClick : Ext.emptyFn + + /** + * @cfg {Boolean} grow @hide + */ + /** + * @cfg {Number} growMin @hide + */ + /** + * @cfg {Number} growMax @hide + */ +}); + +/** + * @class Ext.form.TwinTriggerField + * @extends Ext.form.TriggerField + * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class + * to be extended by an implementing class. For an example of implementing this class, see the custom + * SearchField implementation here: + * http://extjs.com/deploy/ext/examples/form/custom.html + */ +Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, { + /** + * @cfg {Mixed} triggerConfig + *

            A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements + * for this Field. (Optional).

            + *

            Specify this when you need a customized element to contain the two trigger elements for this Field. + * Each trigger element must be marked by the CSS class x-form-trigger (also see + * {@link #trigger1Class} and {@link #trigger2Class}).

            + *

            Note that when using this option, it is the developer's responsibility to ensure correct sizing, + * positioning and appearance of the triggers.

            + */ + /** + * @cfg {String} trigger1Class + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + /** + * @cfg {String} trigger2Class + * An additional CSS class used to style the trigger button. The trigger will always get the + * class 'x-form-trigger' by default and triggerClass will be appended if specified. + */ + + initComponent : function(){ + Ext.form.TwinTriggerField.superclass.initComponent.call(this); + + this.triggerConfig = { + tag:'span', cls:'x-form-twin-triggers', cn:[ + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class}, + {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class} + ]}; + }, + + getTrigger : function(index){ + return this.triggers[index]; + }, + + initTrigger : function(){ + var ts = this.trigger.select('.x-form-trigger', true); + var triggerField = this; + ts.each(function(t, all, index){ + var triggerIndex = 'Trigger'+(index+1); + t.hide = function(){ + var w = triggerField.wrap.getWidth(); + this.dom.style.display = 'none'; + triggerField.el.setWidth(w-triggerField.trigger.getWidth()); + this['hidden' + triggerIndex] = true; + }; + t.show = function(){ + var w = triggerField.wrap.getWidth(); + this.dom.style.display = ''; + triggerField.el.setWidth(w-triggerField.trigger.getWidth()); + this['hidden' + triggerIndex] = false; + }; + + if(this['hide'+triggerIndex]){ + t.dom.style.display = 'none'; + this['hidden' + triggerIndex] = true; + } + this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true}); + t.addClassOnOver('x-form-trigger-over'); + t.addClassOnClick('x-form-trigger-click'); + }, this); + this.triggers = ts.elements; + }, + + getTriggerWidth: function(){ + var tw = 0; + Ext.each(this.triggers, function(t, index){ + var triggerIndex = 'Trigger' + (index + 1), + w = t.getWidth(); + if(w === 0 && !this['hidden' + triggerIndex]){ + tw += this.defaultTriggerWidth; + }else{ + tw += w; + } + }, this); + return tw; + }, + + // private + onDestroy : function() { + Ext.destroy(this.triggers); + Ext.form.TwinTriggerField.superclass.onDestroy.call(this); + }, + + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} + * for additional information. + * @method + * @param {EventObject} e + */ + onTrigger1Click : Ext.emptyFn, + /** + * The function that should handle the trigger's click event. This method does nothing by default + * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick} + * for additional information. + * @method + * @param {EventObject} e + */ + onTrigger2Click : Ext.emptyFn +}); Ext.reg('trigger', Ext.form.TriggerField);/** * @class Ext.form.TextArea * @extends Ext.form.TextField @@ -53236,7 +54771,8 @@ Ext.form.TextArea = Ext.extend(Ext.form.TextField, { /** * @cfg {Boolean} preventScrollbars true to prevent scrollbars from appearing regardless of how much text is - * in the field (equivalent to setting overflow: hidden, defaults to false) + * in the field. This option is only relevant when {@link #grow} is true. Equivalent to setting overflow: hidden, defaults to + * false. */ preventScrollbars: false, /** @@ -53924,1231 +55460,1245 @@ Ext.form.DisplayField = Ext.extend(Ext.form.Field, { }); Ext.reg('displayfield', Ext.form.DisplayField); -/** - * @class Ext.form.ComboBox - * @extends Ext.form.TriggerField - *

            A combobox control with support for autocomplete, remote-loading, paging and many other features.

            - *

            A ComboBox works in a similar manner to a traditional HTML <select> field. The difference is - * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input - * field to hold the value of the valueField. The {@link #displayField} is shown in the text field - * which is named according to the {@link #name}.

            - *

            Events

            - *

            To do something when something in ComboBox is selected, configure the select event:

            
            -var cb = new Ext.form.ComboBox({
            -    // all of your config options
            -    listeners:{
            -         scope: yourScope,
            -         'select': yourFunction
            -    }
            -});
            -
            -// Alternatively, you can assign events after the object is created:
            -var cb = new Ext.form.ComboBox(yourOptions);
            -cb.on('select', yourFunction, yourScope);
            - * 

            - * - *

            ComboBox in Grid

            - *

            If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer} - * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement - * a reusable render, for example:

            
            -// create reusable renderer
            -Ext.util.Format.comboRenderer = function(combo){
            -    return function(value){
            -        var record = combo.findRecord(combo.{@link #valueField}, value);
            -        return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText};
            -    }
            -}
            -
            -// create the combo instance
            -var combo = new Ext.form.ComboBox({
            -    {@link #typeAhead}: true,
            -    {@link #triggerAction}: 'all',
            -    {@link #lazyRender}:true,
            -    {@link #mode}: 'local',
            -    {@link #store}: new Ext.data.ArrayStore({
            -        id: 0,
            -        fields: [
            -            'myId',
            -            'displayText'
            -        ],
            -        data: [[1, 'item1'], [2, 'item2']]
            -    }),
            -    {@link #valueField}: 'myId',
            -    {@link #displayField}: 'displayText'
            -});
            -
            -// snippet of column model used within grid
            -var cm = new Ext.grid.ColumnModel([{
            -       ...
            -    },{
            -       header: "Some Header",
            -       dataIndex: 'whatever',
            -       width: 130,
            -       editor: combo, // specify reference to combo instance
            -       renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer
            -    },
            -    ...
            -]);
            - * 

            - * - *

            Filtering

            - *

            A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox - * store manually see {@link #lastQuery}.

            - * @constructor - * Create a new ComboBox. - * @param {Object} config Configuration options - * @xtype combo - */ -Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { - /** - * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox. - * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or - * {@link Ext.form.FormPanel}, you must also set {@link #lazyRender} = true. - */ - /** - * @cfg {Boolean} lazyRender true to prevent the ComboBox from rendering until requested - * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}), - * defaults to false). - */ - /** - * @cfg {String/Object} autoCreate

            A {@link Ext.DomHelper DomHelper} element spec, or true for a default - * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. - * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

            - *
            {tag: "input", type: "text", size: "24", autocomplete: "off"}
            - */ - /** - * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to undefined). - * Acceptable values for this property are: - *
              - *
            • any {@link Ext.data.Store Store} subclass
            • - *
            • an Array : Arrays will be converted to a {@link Ext.data.ArrayStore} internally. - *
                - *
              • 1-dimensional array : (e.g., ['Foo','Bar'])
                - * A 1-dimensional array will automatically be expanded (each array item will be the combo - * {@link #valueField value} and {@link #displayField text})
              • - *
              • 2-dimensional array : (e.g., [['f','Foo'],['b','Bar']])
                - * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo - * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}. - *
            - *

            See also {@link #mode}.

            - */ - /** - * @cfg {String} title If supplied, a header element is created containing this text and added into the top of - * the dropdown list (defaults to undefined, with no header element) - */ - - // private - defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"}, - /** - * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown - * list (defaults to the width of the ComboBox field). See also {@link #minListWidth} - */ - /** - * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'text' if - * {@link #transform transforming a select} a select). - *

            See also {@link #valueField}.

            - *

            Note: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a - * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not - * active.

            - */ - /** - * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this - * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'value' if - * {@link #transform transforming a select}). - *

            Note: use of a valueField requires the user to make a selection in order for a value to be - * mapped. See also {@link #hiddenName}, {@link #hiddenValue}, and {@link #displayField}.

            - */ - /** - * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the - * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically - * post during a form submission. See also {@link #valueField}. - *

            Note: the hidden field's id will also default to this name if {@link #hiddenId} is not specified. - * The ComboBox {@link Ext.Component#id id} and the {@link #hiddenId} should be different, since - * no two DOM nodes should share the same id. So, if the ComboBox {@link Ext.form.Field#name name} and - * hiddenName are the same, you should specify a unique {@link #hiddenId}.

            - */ - /** - * @cfg {String} hiddenId If {@link #hiddenName} is specified, hiddenId can also be provided - * to give the hidden field a unique id (defaults to the {@link #hiddenName}). The hiddenId - * and combo {@link Ext.Component#id id} should be different, since no two DOM - * nodes should share the same id. - */ - /** - * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is - * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured - * {@link Ext.form.Field#value value}. - */ - /** - * @cfg {String} listClass The CSS class to add to the predefined 'x-combo-list' class - * applied the dropdown list element (defaults to ''). - */ - listClass : '', - /** - * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list - * (defaults to 'x-combo-selected') - */ - selectedClass : 'x-combo-selected', - /** - * @cfg {String} listEmptyText The empty text to display in the data view if no items are found. - * (defaults to '') - */ - listEmptyText: '', - /** - * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always - * get the class 'x-form-trigger' and triggerClass will be appended if specified - * (defaults to 'x-form-arrow-trigger' which displays a downward arrow icon). - */ - triggerClass : 'x-form-arrow-trigger', - /** - * @cfg {Boolean/String} shadow true or "sides" for the default effect, "frame" for - * 4-way shadow, and "drop" for bottom-right - */ - shadow : 'sides', - /** - * @cfg {String} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details - * on supported anchor positions (defaults to 'tl-bl?') - */ - listAlign : 'tl-bl?', - /** - * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown - * (defaults to 300) - */ - maxHeight : 300, - /** - * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its - * distance to the viewport edges (defaults to 90) - */ - minHeight : 90, - /** - * @cfg {String} triggerAction The action to execute when the trigger is clicked. - *
              - *
            • 'query' : Default - *

              {@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.

            • - *
            • 'all' : - *

              {@link #doQuery run the query} specified by the {@link #allQuery} config option

            • - *
            - *

            See also {@link #queryParam}.

            - */ - triggerAction : 'query', - /** - * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and - * {@link #typeAhead} activate (defaults to 4 if {@link #mode} = 'remote' or 0 if - * {@link #mode} = 'local', does not apply if - * {@link Ext.form.TriggerField#editable editable} = false). - */ - minChars : 4, - /** - * @cfg {Boolean} typeAhead true to populate and autoselect the remainder of the text being - * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults - * to false) - */ - typeAhead : false, - /** - * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and - * sending the query to filter the dropdown list (defaults to 500 if {@link #mode} = 'remote' - * or 10 if {@link #mode} = 'local') - */ - queryDelay : 500, - /** - * @cfg {Number} pageSize If greater than 0, a {@link Ext.PagingToolbar} is displayed in the - * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and - * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when {@link #mode} = 'remote' - * (defaults to 0). - */ - pageSize : 0, - /** - * @cfg {Boolean} selectOnFocus true to select any existing text in the field immediately on focus. - * Only applies when {@link Ext.form.TriggerField#editable editable} = true (defaults to - * false). - */ - selectOnFocus : false, - /** - * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store) - * as it will be passed on the querystring (defaults to 'query') - */ - queryParam : 'query', - /** - * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies - * when {@link #mode} = 'remote' (defaults to 'Loading...') - */ - loadingText : 'Loading...', - /** - * @cfg {Boolean} resizable true to add a resize handle to the bottom of the dropdown list - * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles). - * Defaults to false. - */ - resizable : false, - /** - * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if - * {@link #resizable} = true (defaults to 8) - */ - handleHeight : 8, - /** - * @cfg {String} allQuery The text query to send to the server to return all records for the list - * with no filtering (defaults to '') - */ - allQuery: '', - /** - * @cfg {String} mode Acceptable values are: - *
              - *
            • 'remote' : Default - *

              Automatically loads the {@link #store} the first time the trigger - * is clicked. If you do not want the store to be automatically loaded the first time the trigger is - * clicked, set to 'local' and manually load the store. To force a requery of the store - * every time the trigger is clicked see {@link #lastQuery}.

            • - *
            • 'local' : - *

              ComboBox loads local data

              - *
              
              -var combo = new Ext.form.ComboBox({
              -    renderTo: document.body,
              -    mode: 'local',
              -    store: new Ext.data.ArrayStore({
              -        id: 0,
              -        fields: [
              -            'myId',  // numeric value is the key
              -            'displayText'
              -        ],
              -        data: [[1, 'item1'], [2, 'item2']]  // data is local
              -    }),
              -    valueField: 'myId',
              -    displayField: 'displayText',
              -    triggerAction: 'all'
              -});
              -     * 
            • - *
            - */ - mode: 'remote', - /** - * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will - * be ignored if {@link #listWidth} has a higher value) - */ - minListWidth : 70, - /** - * @cfg {Boolean} forceSelection true to restrict the selected value to one of the values in the list, - * false to allow the user to set arbitrary text into the field (defaults to false) - */ - forceSelection : false, - /** - * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed - * if {@link #typeAhead} = true (defaults to 250) - */ - typeAheadDelay : 250, - /** - * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in - * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this - * default text is used, it means there is no value set and no validation will occur on this field. - */ - - /** - * @cfg {Boolean} lazyInit true to not initialize the list for this combo until the field is focused - * (defaults to true) - */ - lazyInit : true, - - /** - * The value of the match string used to filter the store. Delete this property to force a requery. - * Example use: - *
            
            -var combo = new Ext.form.ComboBox({
            -    ...
            -    mode: 'remote',
            -    ...
            -    listeners: {
            -        // delete the previous query in the beforequery event or set
            -        // combo.lastQuery = null (this will reload the store the next time it expands)
            -        beforequery: function(qe){
            -            delete qe.combo.lastQuery;
            -        }
            -    }
            -});
            -     * 
            - * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used - * configure the combo with lastQuery=''. Example use: - *
            
            -var combo = new Ext.form.ComboBox({
            -    ...
            -    mode: 'local',
            -    triggerAction: 'all',
            -    lastQuery: ''
            -});
            -     * 
            - * @property lastQuery - * @type String - */ - - // private - initComponent : function(){ - Ext.form.ComboBox.superclass.initComponent.call(this); - this.addEvents( - /** - * @event expand - * Fires when the dropdown list is expanded - * @param {Ext.form.ComboBox} combo This combo box - */ - 'expand', - /** - * @event collapse - * Fires when the dropdown list is collapsed - * @param {Ext.form.ComboBox} combo This combo box - */ - 'collapse', - /** - * @event beforeselect - * Fires before a list item is selected. Return false to cancel the selection. - * @param {Ext.form.ComboBox} combo This combo box - * @param {Ext.data.Record} record The data record returned from the underlying store - * @param {Number} index The index of the selected item in the dropdown list - */ - 'beforeselect', - /** - * @event select - * Fires when a list item is selected - * @param {Ext.form.ComboBox} combo This combo box - * @param {Ext.data.Record} record The data record returned from the underlying store - * @param {Number} index The index of the selected item in the dropdown list - */ - 'select', - /** - * @event beforequery - * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's - * cancel property to true. - * @param {Object} queryEvent An object that has these properties:
              - *
            • combo : Ext.form.ComboBox
              This combo box
            • - *
            • query : String
              The query
            • - *
            • forceAll : Boolean
              True to force "all" query
            • - *
            • cancel : Boolean
              Set to true to cancel the query
            • - *
            - */ - 'beforequery' - ); - if(this.transform){ - var s = Ext.getDom(this.transform); - if(!this.hiddenName){ - this.hiddenName = s.name; - } - if(!this.store){ - this.mode = 'local'; - var d = [], opts = s.options; - for(var i = 0, len = opts.length;i < len; i++){ - var o = opts[i], - value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text; - if(o.selected && Ext.isEmpty(this.value, true)) { - this.value = value; - } - d.push([value, o.text]); - } - this.store = new Ext.data.ArrayStore({ - 'id': 0, - fields: ['value', 'text'], - data : d, - autoDestroy: true - }); - this.valueField = 'value'; - this.displayField = 'text'; - } - s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference - if(!this.lazyRender){ - this.target = true; - this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate); - this.render(this.el.parentNode, s); - Ext.removeNode(s); // remove it - }else{ - Ext.removeNode(s); // remove it - } - } - //auto-configure store from local array data - else if(this.store){ - this.store = Ext.StoreMgr.lookup(this.store); - if(this.store.autoCreated){ - this.displayField = this.valueField = 'field1'; - if(!this.store.expandData){ - this.displayField = 'field2'; - } - this.mode = 'local'; - } - } - - this.selectedIndex = -1; - if(this.mode == 'local'){ - if(!Ext.isDefined(this.initialConfig.queryDelay)){ - this.queryDelay = 10; - } - if(!Ext.isDefined(this.initialConfig.minChars)){ - this.minChars = 0; - } - } - }, - - // private - onRender : function(ct, position){ - Ext.form.ComboBox.superclass.onRender.call(this, ct, position); - if(this.hiddenName){ - this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, - id: (this.hiddenId||this.hiddenName)}, 'before', true); - - // prevent input submission - this.el.dom.removeAttribute('name'); - } - if(Ext.isGecko){ - this.el.dom.setAttribute('autocomplete', 'off'); - } - - if(!this.lazyInit){ - this.initList(); - }else{ - this.on('focus', this.initList, this, {single: true}); - } - }, - - // private - initValue : function(){ - Ext.form.ComboBox.superclass.initValue.call(this); - if(this.hiddenField){ - this.hiddenField.value = - Ext.isDefined(this.hiddenValue) ? this.hiddenValue : - Ext.isDefined(this.value) ? this.value : ''; - } - }, - - // private - initList : function(){ - if(!this.list){ - var cls = 'x-combo-list'; - - this.list = new Ext.Layer({ - parentEl: this.getListParent(), - shadow: this.shadow, - cls: [cls, this.listClass].join(' '), - constrain:false - }); - - var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); - this.list.setSize(lw, 0); - this.list.swallowEvent('mousewheel'); - this.assetHeight = 0; - if(this.syncFont !== false){ - this.list.setStyle('font-size', this.el.getStyle('font-size')); - } - if(this.title){ - this.header = this.list.createChild({cls:cls+'-hd', html: this.title}); - this.assetHeight += this.header.getHeight(); - } - - this.innerList = this.list.createChild({cls:cls+'-inner'}); - this.mon(this.innerList, 'mouseover', this.onViewOver, this); - this.mon(this.innerList, 'mousemove', this.onViewMove, this); - this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); - - if(this.pageSize){ - this.footer = this.list.createChild({cls:cls+'-ft'}); - this.pageTb = new Ext.PagingToolbar({ - store: this.store, - pageSize: this.pageSize, - renderTo:this.footer - }); - this.assetHeight += this.footer.getHeight(); - } - - if(!this.tpl){ - /** - * @cfg {String/Ext.XTemplate} tpl

            The template string, or {@link Ext.XTemplate} instance to - * use to display each item in the dropdown list. The dropdown list is displayed in a - * DataView. See {@link #view}.

            - *

            The default template string is:

            
            -                  '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
            -                * 
            - *

            Override the default value to create custom UI layouts for items in the list. - * For example:

            
            -                  '<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
            -                * 
            - *

            The template must contain one or more substitution parameters using field - * names from the Combo's {@link #store Store}. In the example above an - *

            ext:qtip
            attribute is added to display other fields from the Store.

            - *

            To preserve the default visual look of list items, add the CSS class name - *

            x-combo-list-item
            to the template's container element.

            - *

            Also see {@link #itemSelector} for additional details.

            - */ - this.tpl = '
            {' + this.displayField + '}
            '; - /** - * @cfg {String} itemSelector - *

            A simple CSS selector (e.g. div.some-class or span:first-child) that will be - * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown - * display will be working with.

            - *

            Note: this setting is required if a custom XTemplate has been - * specified in {@link #tpl} which assigns a class other than

            'x-combo-list-item'
            - * to dropdown list items - */ - } - - /** - * The {@link Ext.DataView DataView} used to display the ComboBox's options. - * @type Ext.DataView - */ - this.view = new Ext.DataView({ - applyTo: this.innerList, - tpl: this.tpl, - singleSelect: true, - selectedClass: this.selectedClass, - itemSelector: this.itemSelector || '.' + cls + '-item', - emptyText: this.listEmptyText - }); - - this.mon(this.view, 'click', this.onViewClick, this); - - this.bindStore(this.store, true); - - if(this.resizable){ - this.resizer = new Ext.Resizable(this.list, { - pinned:true, handles:'se' - }); - this.mon(this.resizer, 'resize', function(r, w, h){ - this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight; - this.listWidth = w; - this.innerList.setWidth(w - this.list.getFrameWidth('lr')); - this.restrictHeight(); - }, this); - - this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px'); - } - } - }, - - /** - *

            Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.

            - * A custom implementation may be provided as a configuration option if the floating list needs to be rendered - * to a different Element. An example might be rendering the list inside a Menu so that clicking - * the list does not hide the Menu:
            
            -var store = new Ext.data.ArrayStore({
            -    autoDestroy: true,
            -    fields: ['initials', 'fullname'],
            -    data : [
            -        ['FF', 'Fred Flintstone'],
            -        ['BR', 'Barney Rubble']
            -    ]
            -});
            -
            -var combo = new Ext.form.ComboBox({
            -    store: store,
            -    displayField: 'fullname',
            -    emptyText: 'Select a name...',
            -    forceSelection: true,
            -    getListParent: function() {
            -        return this.el.up('.x-menu');
            -    },
            -    iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
            -    mode: 'local',
            -    selectOnFocus: true,
            -    triggerAction: 'all',
            -    typeAhead: true,
            -    width: 135
            -});
            -
            -var menu = new Ext.menu.Menu({
            -    id: 'mainMenu',
            -    items: [
            -        combo // A Field in a Menu
            -    ]
            -});
            -
            - */ - getListParent : function() { - return document.body; - }, - - /** - * Returns the store associated with this combo. - * @return {Ext.data.Store} The store - */ - getStore : function(){ - return this.store; - }, - - // private - bindStore : function(store, initial){ - if(this.store && !initial){ - this.store.un('beforeload', this.onBeforeLoad, this); - this.store.un('load', this.onLoad, this); - this.store.un('exception', this.collapse, this); - if(this.store !== store && this.store.autoDestroy){ - this.store.destroy(); - } - if(!store){ - this.store = null; - if(this.view){ - this.view.bindStore(null); - } - } - } - if(store){ - if(!initial) { - this.lastQuery = null; - if(this.pageTb) { - this.pageTb.bindStore(store); - } - } - - this.store = Ext.StoreMgr.lookup(store); - this.store.on({ - scope: this, - beforeload: this.onBeforeLoad, - load: this.onLoad, - exception: this.collapse - }); - - if(this.view){ - this.view.bindStore(store); - } - } - }, - - // private - initEvents : function(){ - Ext.form.ComboBox.superclass.initEvents.call(this); - - this.keyNav = new Ext.KeyNav(this.el, { - "up" : function(e){ - this.inKeyMode = true; - this.selectPrev(); - }, - - "down" : function(e){ - if(!this.isExpanded()){ - this.onTriggerClick(); - }else{ - this.inKeyMode = true; - this.selectNext(); - } - }, - - "enter" : function(e){ - this.onViewClick(); - this.delayedCheck = true; - this.unsetDelayCheck.defer(10, this); - }, - - "esc" : function(e){ - this.collapse(); - }, - - "tab" : function(e){ - this.onViewClick(false); - return true; - }, - - scope : this, - - doRelay : function(foo, bar, hname){ - if(hname == 'down' || this.scope.isExpanded()){ - return Ext.KeyNav.prototype.doRelay.apply(this, arguments); - } - return true; - }, - - forceKeyDown : true - }); - this.queryDelay = Math.max(this.queryDelay || 10, - this.mode == 'local' ? 10 : 250); - this.dqTask = new Ext.util.DelayedTask(this.initQuery, this); - if(this.typeAhead){ - this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this); - } - if(this.editable !== false && !this.enableKeyEvents){ - this.mon(this.el, 'keyup', this.onKeyUp, this); - } - }, - - // private - onDestroy : function(){ - if (this.dqTask){ - this.dqTask.cancel(); - this.dqTask = null; - } - this.bindStore(null); - Ext.destroy( - this.resizer, - this.view, - this.pageTb, - this.list - ); - Ext.form.ComboBox.superclass.onDestroy.call(this); - }, - - // private - unsetDelayCheck : function(){ - delete this.delayedCheck; - }, - - // private - fireKey : function(e){ - var fn = function(ev){ - if (ev.isNavKeyPress() && !this.isExpanded() && !this.delayedCheck) { - this.fireEvent("specialkey", this, ev); - } - }; - //For some reason I can't track down, the events fire in a different order in webkit. - //Need a slight delay here - if(this.inEditor && Ext.isWebKit && e.getKey() == e.TAB){ - fn.defer(10, this, [new Ext.EventObjectImpl(e)]); - }else{ - fn.call(this, e); - } - }, - - // private - onResize : function(w, h){ - Ext.form.ComboBox.superclass.onResize.apply(this, arguments); - if(this.list && !Ext.isDefined(this.listWidth)){ - var lw = Math.max(w, this.minListWidth); - this.list.setWidth(lw); - this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); - } - }, - - // private - onEnable : function(){ - Ext.form.ComboBox.superclass.onEnable.apply(this, arguments); - if(this.hiddenField){ - this.hiddenField.disabled = false; - } - }, - - // private - onDisable : function(){ - Ext.form.ComboBox.superclass.onDisable.apply(this, arguments); - if(this.hiddenField){ - this.hiddenField.disabled = true; - } - }, - - // private - onBeforeLoad : function(){ - if(!this.hasFocus){ - return; - } - this.innerList.update(this.loadingText ? - '
            '+this.loadingText+'
            ' : ''); - this.restrictHeight(); - this.selectedIndex = -1; - }, - - // private - onLoad : function(){ - if(!this.hasFocus){ - return; - } - if(this.store.getCount() > 0){ - this.expand(); - this.restrictHeight(); - if(this.lastQuery == this.allQuery){ - if(this.editable){ - this.el.dom.select(); - } - if(!this.selectByValue(this.value, true)){ - this.select(0, true); - } - }else{ - this.selectNext(); - if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ - this.taTask.delay(this.typeAheadDelay); - } - } - }else{ - this.onEmptyResults(); - } - //this.el.focus(); - }, - - // private - onTypeAhead : function(){ - if(this.store.getCount() > 0){ - var r = this.store.getAt(0); - var newValue = r.data[this.displayField]; - var len = newValue.length; - var selStart = this.getRawValue().length; - if(selStart != len){ - this.setRawValue(newValue); - this.selectText(selStart, newValue.length); - } - } - }, - - // private - onSelect : function(record, index){ - if(this.fireEvent('beforeselect', this, record, index) !== false){ - this.setValue(record.data[this.valueField || this.displayField]); - this.collapse(); - this.fireEvent('select', this, record, index); - } - }, - - // inherit docs - getName: function(){ - var hf = this.hiddenField; - return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this); - }, - - /** - * Returns the currently selected field value or empty string if no value is set. - * @return {String} value The selected value - */ - getValue : function(){ - if(this.valueField){ - return Ext.isDefined(this.value) ? this.value : ''; - }else{ - return Ext.form.ComboBox.superclass.getValue.call(this); - } - }, - - /** - * Clears any text/value currently set in the field - */ - clearValue : function(){ - if(this.hiddenField){ - this.hiddenField.value = ''; - } - this.setRawValue(''); - this.lastSelectionText = ''; - this.applyEmptyText(); - this.value = ''; - }, - - /** - * Sets the specified value into the field. If the value finds a match, the corresponding record text - * will be displayed in the field. If the value does not match the data value of an existing item, - * and the valueNotFoundText config option is defined, it will be displayed as the default field text. - * Otherwise the field will be blank (although the value will still be set). - * @param {String} value The value to match - * @return {Ext.form.Field} this - */ - setValue : function(v){ - var text = v; - if(this.valueField){ - var r = this.findRecord(this.valueField, v); - if(r){ - text = r.data[this.displayField]; - }else if(Ext.isDefined(this.valueNotFoundText)){ - text = this.valueNotFoundText; - } - } - this.lastSelectionText = text; - if(this.hiddenField){ - this.hiddenField.value = v; - } - Ext.form.ComboBox.superclass.setValue.call(this, text); - this.value = v; - return this; - }, - - // private - findRecord : function(prop, value){ - var record; - if(this.store.getCount() > 0){ - this.store.each(function(r){ - if(r.data[prop] == value){ - record = r; - return false; - } - }); - } - return record; - }, - - // private - onViewMove : function(e, t){ - this.inKeyMode = false; - }, - - // private - onViewOver : function(e, t){ - if(this.inKeyMode){ // prevent key nav and mouse over conflicts - return; - } - var item = this.view.findItemFromChild(t); - if(item){ - var index = this.view.indexOf(item); - this.select(index, false); - } - }, - - // private - onViewClick : function(doFocus){ - var index = this.view.getSelectedIndexes()[0]; - var r = this.store.getAt(index); - if(r){ - this.onSelect(r, index); - } - if(doFocus !== false){ - this.el.focus(); - } - }, - - // private - restrictHeight : function(){ - this.innerList.dom.style.height = ''; - var inner = this.innerList.dom; - var pad = this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight; - var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight); - var ha = this.getPosition()[1]-Ext.getBody().getScroll().top; - var hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height; - var space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5; - h = Math.min(h, space, this.maxHeight); - - this.innerList.setHeight(h); - this.list.beginUpdate(); - this.list.setHeight(h+pad); - this.list.alignTo(this.wrap, this.listAlign); - this.list.endUpdate(); - }, - - // private - onEmptyResults : function(){ - this.collapse(); - }, - - /** - * Returns true if the dropdown list is expanded, else false. - */ - isExpanded : function(){ - return this.list && this.list.isVisible(); - }, - - /** - * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire. - * The store must be loaded and the list expanded for this function to work, otherwise use setValue. - * @param {String} value The data value of the item to select - * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the - * selected item if it is not currently in view (defaults to true) - * @return {Boolean} True if the value matched an item in the list, else false - */ - selectByValue : function(v, scrollIntoView){ - if(!Ext.isEmpty(v, true)){ - var r = this.findRecord(this.valueField || this.displayField, v); - if(r){ - this.select(this.store.indexOf(r), scrollIntoView); - return true; - } - } - return false; - }, - - /** - * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire. - * The store must be loaded and the list expanded for this function to work, otherwise use setValue. - * @param {Number} index The zero-based index of the list item to select - * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the - * selected item if it is not currently in view (defaults to true) - */ - select : function(index, scrollIntoView){ - this.selectedIndex = index; - this.view.select(index); - if(scrollIntoView !== false){ - var el = this.view.getNode(index); - if(el){ - this.innerList.scrollChildIntoView(el, false); - } - } - }, - - // private - selectNext : function(){ - var ct = this.store.getCount(); - if(ct > 0){ - if(this.selectedIndex == -1){ - this.select(0); - }else if(this.selectedIndex < ct-1){ - this.select(this.selectedIndex+1); - } - } - }, - - // private - selectPrev : function(){ - var ct = this.store.getCount(); - if(ct > 0){ - if(this.selectedIndex == -1){ - this.select(0); - }else if(this.selectedIndex !== 0){ - this.select(this.selectedIndex-1); - } - } - }, - - // private - onKeyUp : function(e){ - var k = e.getKey(); - if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){ - this.lastKey = k; - this.dqTask.delay(this.queryDelay); - } - Ext.form.ComboBox.superclass.onKeyUp.call(this, e); - }, - - // private - validateBlur : function(){ - return !this.list || !this.list.isVisible(); - }, - - // private - initQuery : function(){ - this.doQuery(this.getRawValue()); - }, - - // private - beforeBlur : function(){ - var val = this.getRawValue(); - if(this.forceSelection){ - if(val.length > 0 && val != this.emptyText){ - this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : ''; - this.applyEmptyText(); - }else{ - this.clearValue(); - } - }else{ - var rec = this.findRecord(this.displayField, val); - if(rec){ - val = rec.get(this.valueField || this.displayField); - } - this.setValue(val); - } - }, - - /** - * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the - * query allowing the query action to be canceled if needed. - * @param {String} query The SQL query to execute - * @param {Boolean} forceAll true to force the query to execute even if there are currently fewer - * characters in the field than the minimum specified by the {@link #minChars} config option. It - * also clears any filter previously saved in the current store (defaults to false) - */ - doQuery : function(q, forceAll){ - q = Ext.isEmpty(q) ? '' : q; - var qe = { - query: q, - forceAll: forceAll, - combo: this, - cancel:false - }; - if(this.fireEvent('beforequery', qe)===false || qe.cancel){ - return false; - } - q = qe.query; - forceAll = qe.forceAll; - if(forceAll === true || (q.length >= this.minChars)){ - if(this.lastQuery !== q){ - this.lastQuery = q; - if(this.mode == 'local'){ - this.selectedIndex = -1; - if(forceAll){ - this.store.clearFilter(); - }else{ - this.store.filter(this.displayField, q); - } - this.onLoad(); - }else{ - this.store.baseParams[this.queryParam] = q; - this.store.load({ - params: this.getParams(q) - }); - this.expand(); - } - }else{ - this.selectedIndex = -1; - this.onLoad(); - } - } - }, - - // private - getParams : function(q){ - var p = {}; - //p[this.queryParam] = q; - if(this.pageSize){ - p.start = 0; - p.limit = this.pageSize; - } - return p; - }, - - /** - * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion. - */ - collapse : function(){ - if(!this.isExpanded()){ - return; - } - this.list.hide(); - Ext.getDoc().un('mousewheel', this.collapseIf, this); - Ext.getDoc().un('mousedown', this.collapseIf, this); - this.fireEvent('collapse', this); - }, - - // private - collapseIf : function(e){ - if(!e.within(this.wrap) && !e.within(this.list)){ - this.collapse(); - } - }, - - /** - * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion. - */ - expand : function(){ - if(this.isExpanded() || !this.hasFocus){ - return; - } - this.list.alignTo(this.wrap, this.listAlign); - this.list.show(); - if(Ext.isGecko2){ - this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac - } - Ext.getDoc().on({ - scope: this, - mousewheel: this.collapseIf, - mousedown: this.collapseIf - }); - this.fireEvent('expand', this); - }, - - /** - * @method onTriggerClick - * @hide - */ - // private - // Implements the default empty TriggerField.onTriggerClick function - onTriggerClick : function(){ - if(this.disabled){ - return; - } - if(this.isExpanded()){ - this.collapse(); - this.el.focus(); - }else { - this.onFocus({}); - if(this.triggerAction == 'all') { - this.doQuery(this.allQuery, true); - } else { - this.doQuery(this.getRawValue()); - } - this.el.focus(); - } - } - - /** - * @hide - * @method autoSize - */ - /** - * @cfg {Boolean} grow @hide - */ - /** - * @cfg {Number} growMin @hide - */ - /** - * @cfg {Number} growMax @hide - */ - -}); +/** + * @class Ext.form.ComboBox + * @extends Ext.form.TriggerField + *

            A combobox control with support for autocomplete, remote-loading, paging and many other features.

            + *

            A ComboBox works in a similar manner to a traditional HTML <select> field. The difference is + * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input + * field to hold the value of the valueField. The {@link #displayField} is shown in the text field + * which is named according to the {@link #name}.

            + *

            Events

            + *

            To do something when something in ComboBox is selected, configure the select event:

            
            +var cb = new Ext.form.ComboBox({
            +    // all of your config options
            +    listeners:{
            +         scope: yourScope,
            +         'select': yourFunction
            +    }
            +});
            +
            +// Alternatively, you can assign events after the object is created:
            +var cb = new Ext.form.ComboBox(yourOptions);
            +cb.on('select', yourFunction, yourScope);
            + * 

            + * + *

            ComboBox in Grid

            + *

            If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer} + * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement + * a reusable render, for example:

            
            +// create reusable renderer
            +Ext.util.Format.comboRenderer = function(combo){
            +    return function(value){
            +        var record = combo.findRecord(combo.{@link #valueField}, value);
            +        return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText};
            +    }
            +}
            +
            +// create the combo instance
            +var combo = new Ext.form.ComboBox({
            +    {@link #typeAhead}: true,
            +    {@link #triggerAction}: 'all',
            +    {@link #lazyRender}:true,
            +    {@link #mode}: 'local',
            +    {@link #store}: new Ext.data.ArrayStore({
            +        id: 0,
            +        fields: [
            +            'myId',
            +            'displayText'
            +        ],
            +        data: [[1, 'item1'], [2, 'item2']]
            +    }),
            +    {@link #valueField}: 'myId',
            +    {@link #displayField}: 'displayText'
            +});
            +
            +// snippet of column model used within grid
            +var cm = new Ext.grid.ColumnModel([{
            +       ...
            +    },{
            +       header: "Some Header",
            +       dataIndex: 'whatever',
            +       width: 130,
            +       editor: combo, // specify reference to combo instance
            +       renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer
            +    },
            +    ...
            +]);
            + * 

            + * + *

            Filtering

            + *

            A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox + * store manually see {@link #lastQuery}.

            + * @constructor + * Create a new ComboBox. + * @param {Object} config Configuration options + * @xtype combo + */ +Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, { + /** + * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox. + * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or + * {@link Ext.form.FormPanel}, you must also set {@link #lazyRender} = true. + */ + /** + * @cfg {Boolean} lazyRender true to prevent the ComboBox from rendering until requested + * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}), + * defaults to false). + */ + /** + * @cfg {String/Object} autoCreate

            A {@link Ext.DomHelper DomHelper} element spec, or true for a default + * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component. + * See {@link Ext.Component#autoEl autoEl} for details. Defaults to:

            + *
            {tag: "input", type: "text", size: "24", autocomplete: "off"}
            + */ + /** + * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to undefined). + * Acceptable values for this property are: + *
              + *
            • any {@link Ext.data.Store Store} subclass
            • + *
            • an Array : Arrays will be converted to a {@link Ext.data.ArrayStore} internally, + * automatically generating {@link Ext.data.Field#name field names} to work with all data components. + *
                + *
              • 1-dimensional array : (e.g., ['Foo','Bar'])
                + * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo + * {@link #valueField} and {@link #displayField})
              • + *
              • 2-dimensional array : (e.g., [['f','Foo'],['b','Bar']])
                + * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo + * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}. + *
            + *

            See also {@link #mode}.

            + */ + /** + * @cfg {String} title If supplied, a header element is created containing this text and added into the top of + * the dropdown list (defaults to undefined, with no header element) + */ + + // private + defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"}, + /** + * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown + * list (defaults to the width of the ComboBox field). See also {@link #minListWidth} + */ + /** + * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field1' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). + *

            See also {@link #valueField}.

            + *

            Note: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a + * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not + * active.

            + */ + /** + * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this + * ComboBox (defaults to undefined if {@link #mode} = 'remote' or 'field2' if + * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on + * the store configuration}). + *

            Note: use of a valueField requires the user to make a selection in order for a value to be + * mapped. See also {@link #hiddenName}, {@link #hiddenValue}, and {@link #displayField}.

            + */ + /** + * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the + * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically + * post during a form submission. See also {@link #valueField}. + *

            Note: the hidden field's id will also default to this name if {@link #hiddenId} is not specified. + * The ComboBox {@link Ext.Component#id id} and the {@link #hiddenId} should be different, since + * no two DOM nodes should share the same id. So, if the ComboBox {@link Ext.form.Field#name name} and + * hiddenName are the same, you should specify a unique {@link #hiddenId}.

            + */ + /** + * @cfg {String} hiddenId If {@link #hiddenName} is specified, hiddenId can also be provided + * to give the hidden field a unique id (defaults to the {@link #hiddenName}). The hiddenId + * and combo {@link Ext.Component#id id} should be different, since no two DOM + * nodes should share the same id. + */ + /** + * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is + * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured + * {@link Ext.form.Field#value value}. + */ + /** + * @cfg {String} listClass The CSS class to add to the predefined 'x-combo-list' class + * applied the dropdown list element (defaults to ''). + */ + listClass : '', + /** + * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list + * (defaults to 'x-combo-selected') + */ + selectedClass : 'x-combo-selected', + /** + * @cfg {String} listEmptyText The empty text to display in the data view if no items are found. + * (defaults to '') + */ + listEmptyText: '', + /** + * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always + * get the class 'x-form-trigger' and triggerClass will be appended if specified + * (defaults to 'x-form-arrow-trigger' which displays a downward arrow icon). + */ + triggerClass : 'x-form-arrow-trigger', + /** + * @cfg {Boolean/String} shadow true or "sides" for the default effect, "frame" for + * 4-way shadow, and "drop" for bottom-right + */ + shadow : 'sides', + /** + * @cfg {String} listAlign A valid anchor position value. See {@link Ext.Element#alignTo} for details + * on supported anchor positions (defaults to 'tl-bl?') + */ + listAlign : 'tl-bl?', + /** + * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown + * (defaults to 300) + */ + maxHeight : 300, + /** + * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its + * distance to the viewport edges (defaults to 90) + */ + minHeight : 90, + /** + * @cfg {String} triggerAction The action to execute when the trigger is clicked. + *
              + *
            • 'query' : Default + *

              {@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.

            • + *
            • 'all' : + *

              {@link #doQuery run the query} specified by the {@link #allQuery} config option

            • + *
            + *

            See also {@link #queryParam}.

            + */ + triggerAction : 'query', + /** + * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and + * {@link #typeAhead} activate (defaults to 4 if {@link #mode} = 'remote' or 0 if + * {@link #mode} = 'local', does not apply if + * {@link Ext.form.TriggerField#editable editable} = false). + */ + minChars : 4, + /** + * @cfg {Boolean} typeAhead true to populate and autoselect the remainder of the text being + * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults + * to false) + */ + typeAhead : false, + /** + * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and + * sending the query to filter the dropdown list (defaults to 500 if {@link #mode} = 'remote' + * or 10 if {@link #mode} = 'local') + */ + queryDelay : 500, + /** + * @cfg {Number} pageSize If greater than 0, a {@link Ext.PagingToolbar} is displayed in the + * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and + * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when {@link #mode} = 'remote' + * (defaults to 0). + */ + pageSize : 0, + /** + * @cfg {Boolean} selectOnFocus true to select any existing text in the field immediately on focus. + * Only applies when {@link Ext.form.TriggerField#editable editable} = true (defaults to + * false). + */ + selectOnFocus : false, + /** + * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store) + * as it will be passed on the querystring (defaults to 'query') + */ + queryParam : 'query', + /** + * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies + * when {@link #mode} = 'remote' (defaults to 'Loading...') + */ + loadingText : 'Loading...', + /** + * @cfg {Boolean} resizable true to add a resize handle to the bottom of the dropdown list + * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles). + * Defaults to false. + */ + resizable : false, + /** + * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if + * {@link #resizable} = true (defaults to 8) + */ + handleHeight : 8, + /** + * @cfg {String} allQuery The text query to send to the server to return all records for the list + * with no filtering (defaults to '') + */ + allQuery: '', + /** + * @cfg {String} mode Acceptable values are: + *
              + *
            • 'remote' : Default + *

              Automatically loads the {@link #store} the first time the trigger + * is clicked. If you do not want the store to be automatically loaded the first time the trigger is + * clicked, set to 'local' and manually load the store. To force a requery of the store + * every time the trigger is clicked see {@link #lastQuery}.

            • + *
            • 'local' : + *

              ComboBox loads local data

              + *
              
              +var combo = new Ext.form.ComboBox({
              +    renderTo: document.body,
              +    mode: 'local',
              +    store: new Ext.data.ArrayStore({
              +        id: 0,
              +        fields: [
              +            'myId',  // numeric value is the key
              +            'displayText'
              +        ],
              +        data: [[1, 'item1'], [2, 'item2']]  // data is local
              +    }),
              +    valueField: 'myId',
              +    displayField: 'displayText',
              +    triggerAction: 'all'
              +});
              +     * 
            • + *
            + */ + mode: 'remote', + /** + * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will + * be ignored if {@link #listWidth} has a higher value) + */ + minListWidth : 70, + /** + * @cfg {Boolean} forceSelection true to restrict the selected value to one of the values in the list, + * false to allow the user to set arbitrary text into the field (defaults to false) + */ + forceSelection : false, + /** + * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed + * if {@link #typeAhead} = true (defaults to 250) + */ + typeAheadDelay : 250, + /** + * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in + * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this + * default text is used, it means there is no value set and no validation will occur on this field. + */ + + /** + * @cfg {Boolean} lazyInit true to not initialize the list for this combo until the field is focused + * (defaults to true) + */ + lazyInit : true, + + /** + * The value of the match string used to filter the store. Delete this property to force a requery. + * Example use: + *
            
            +var combo = new Ext.form.ComboBox({
            +    ...
            +    mode: 'remote',
            +    ...
            +    listeners: {
            +        // delete the previous query in the beforequery event or set
            +        // combo.lastQuery = null (this will reload the store the next time it expands)
            +        beforequery: function(qe){
            +            delete qe.combo.lastQuery;
            +        }
            +    }
            +});
            +     * 
            + * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used + * configure the combo with lastQuery=''. Example use: + *
            
            +var combo = new Ext.form.ComboBox({
            +    ...
            +    mode: 'local',
            +    triggerAction: 'all',
            +    lastQuery: ''
            +});
            +     * 
            + * @property lastQuery + * @type String + */ + + // private + initComponent : function(){ + Ext.form.ComboBox.superclass.initComponent.call(this); + this.addEvents( + /** + * @event expand + * Fires when the dropdown list is expanded + * @param {Ext.form.ComboBox} combo This combo box + */ + 'expand', + /** + * @event collapse + * Fires when the dropdown list is collapsed + * @param {Ext.form.ComboBox} combo This combo box + */ + 'collapse', + /** + * @event beforeselect + * Fires before a list item is selected. Return false to cancel the selection. + * @param {Ext.form.ComboBox} combo This combo box + * @param {Ext.data.Record} record The data record returned from the underlying store + * @param {Number} index The index of the selected item in the dropdown list + */ + 'beforeselect', + /** + * @event select + * Fires when a list item is selected + * @param {Ext.form.ComboBox} combo This combo box + * @param {Ext.data.Record} record The data record returned from the underlying store + * @param {Number} index The index of the selected item in the dropdown list + */ + 'select', + /** + * @event beforequery + * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's + * cancel property to true. + * @param {Object} queryEvent An object that has these properties:
              + *
            • combo : Ext.form.ComboBox
              This combo box
            • + *
            • query : String
              The query
            • + *
            • forceAll : Boolean
              True to force "all" query
            • + *
            • cancel : Boolean
              Set to true to cancel the query
            • + *
            + */ + 'beforequery' + ); + if(this.transform){ + var s = Ext.getDom(this.transform); + if(!this.hiddenName){ + this.hiddenName = s.name; + } + if(!this.store){ + this.mode = 'local'; + var d = [], opts = s.options; + for(var i = 0, len = opts.length;i < len; i++){ + var o = opts[i], + value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text; + if(o.selected && Ext.isEmpty(this.value, true)) { + this.value = value; + } + d.push([value, o.text]); + } + this.store = new Ext.data.ArrayStore({ + 'id': 0, + fields: ['value', 'text'], + data : d, + autoDestroy: true + }); + this.valueField = 'value'; + this.displayField = 'text'; + } + s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference + if(!this.lazyRender){ + this.target = true; + this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate); + this.render(this.el.parentNode, s); + Ext.removeNode(s); // remove it + }else{ + Ext.removeNode(s); // remove it + } + } + //auto-configure store from local array data + else if(this.store){ + this.store = Ext.StoreMgr.lookup(this.store); + if(this.store.autoCreated){ + this.displayField = this.valueField = 'field1'; + if(!this.store.expandData){ + this.displayField = 'field2'; + } + this.mode = 'local'; + } + } + + this.selectedIndex = -1; + if(this.mode == 'local'){ + if(!Ext.isDefined(this.initialConfig.queryDelay)){ + this.queryDelay = 10; + } + if(!Ext.isDefined(this.initialConfig.minChars)){ + this.minChars = 0; + } + } + }, + + // private + onRender : function(ct, position){ + Ext.form.ComboBox.superclass.onRender.call(this, ct, position); + if(this.hiddenName){ + this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, + id: (this.hiddenId||this.hiddenName)}, 'before', true); + + // prevent input submission + this.el.dom.removeAttribute('name'); + } + if(Ext.isGecko){ + this.el.dom.setAttribute('autocomplete', 'off'); + } + + if(!this.lazyInit){ + this.initList(); + }else{ + this.on('focus', this.initList, this, {single: true}); + } + }, + + // private + initValue : function(){ + Ext.form.ComboBox.superclass.initValue.call(this); + if(this.hiddenField){ + this.hiddenField.value = + Ext.isDefined(this.hiddenValue) ? this.hiddenValue : + Ext.isDefined(this.value) ? this.value : ''; + } + }, + + // private + initList : function(){ + if(!this.list){ + var cls = 'x-combo-list'; + + this.list = new Ext.Layer({ + parentEl: this.getListParent(), + shadow: this.shadow, + cls: [cls, this.listClass].join(' '), + constrain:false + }); + + var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth); + this.list.setSize(lw, 0); + this.list.swallowEvent('mousewheel'); + this.assetHeight = 0; + if(this.syncFont !== false){ + this.list.setStyle('font-size', this.el.getStyle('font-size')); + } + if(this.title){ + this.header = this.list.createChild({cls:cls+'-hd', html: this.title}); + this.assetHeight += this.header.getHeight(); + } + + this.innerList = this.list.createChild({cls:cls+'-inner'}); + this.mon(this.innerList, 'mouseover', this.onViewOver, this); + this.mon(this.innerList, 'mousemove', this.onViewMove, this); + this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); + + if(this.pageSize){ + this.footer = this.list.createChild({cls:cls+'-ft'}); + this.pageTb = new Ext.PagingToolbar({ + store: this.store, + pageSize: this.pageSize, + renderTo:this.footer + }); + this.assetHeight += this.footer.getHeight(); + } + + if(!this.tpl){ + /** + * @cfg {String/Ext.XTemplate} tpl

            The template string, or {@link Ext.XTemplate} instance to + * use to display each item in the dropdown list. The dropdown list is displayed in a + * DataView. See {@link #view}.

            + *

            The default template string is:

            
            +                  '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
            +                * 
            + *

            Override the default value to create custom UI layouts for items in the list. + * For example:

            
            +                  '<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
            +                * 
            + *

            The template must contain one or more substitution parameters using field + * names from the Combo's {@link #store Store}. In the example above an + *

            ext:qtip
            attribute is added to display other fields from the Store.

            + *

            To preserve the default visual look of list items, add the CSS class name + *

            x-combo-list-item
            to the template's container element.

            + *

            Also see {@link #itemSelector} for additional details.

            + */ + this.tpl = '
            {' + this.displayField + '}
            '; + /** + * @cfg {String} itemSelector + *

            A simple CSS selector (e.g. div.some-class or span:first-child) that will be + * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown + * display will be working with.

            + *

            Note: this setting is required if a custom XTemplate has been + * specified in {@link #tpl} which assigns a class other than

            'x-combo-list-item'
            + * to dropdown list items + */ + } + + /** + * The {@link Ext.DataView DataView} used to display the ComboBox's options. + * @type Ext.DataView + */ + this.view = new Ext.DataView({ + applyTo: this.innerList, + tpl: this.tpl, + singleSelect: true, + selectedClass: this.selectedClass, + itemSelector: this.itemSelector || '.' + cls + '-item', + emptyText: this.listEmptyText + }); + + this.mon(this.view, 'click', this.onViewClick, this); + + this.bindStore(this.store, true); + + if(this.resizable){ + this.resizer = new Ext.Resizable(this.list, { + pinned:true, handles:'se' + }); + this.mon(this.resizer, 'resize', function(r, w, h){ + this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight; + this.listWidth = w; + this.innerList.setWidth(w - this.list.getFrameWidth('lr')); + this.restrictHeight(); + }, this); + + this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px'); + } + } + }, + + /** + *

            Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.

            + * A custom implementation may be provided as a configuration option if the floating list needs to be rendered + * to a different Element. An example might be rendering the list inside a Menu so that clicking + * the list does not hide the Menu:
            
            +var store = new Ext.data.ArrayStore({
            +    autoDestroy: true,
            +    fields: ['initials', 'fullname'],
            +    data : [
            +        ['FF', 'Fred Flintstone'],
            +        ['BR', 'Barney Rubble']
            +    ]
            +});
            +
            +var combo = new Ext.form.ComboBox({
            +    store: store,
            +    displayField: 'fullname',
            +    emptyText: 'Select a name...',
            +    forceSelection: true,
            +    getListParent: function() {
            +        return this.el.up('.x-menu');
            +    },
            +    iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
            +    mode: 'local',
            +    selectOnFocus: true,
            +    triggerAction: 'all',
            +    typeAhead: true,
            +    width: 135
            +});
            +
            +var menu = new Ext.menu.Menu({
            +    id: 'mainMenu',
            +    items: [
            +        combo // A Field in a Menu
            +    ]
            +});
            +
            + */ + getListParent : function() { + return document.body; + }, + + /** + * Returns the store associated with this combo. + * @return {Ext.data.Store} The store + */ + getStore : function(){ + return this.store; + }, + + // private + bindStore : function(store, initial){ + if(this.store && !initial){ + if(this.store !== store && this.store.autoDestroy){ + this.store.destroy(); + }else{ + this.store.un('beforeload', this.onBeforeLoad, this); + this.store.un('load', this.onLoad, this); + this.store.un('exception', this.collapse, this); + } + if(!store){ + this.store = null; + if(this.view){ + this.view.bindStore(null); + } + if(this.pageTb){ + this.pageTb.bindStore(null); + } + } + } + if(store){ + if(!initial) { + this.lastQuery = null; + if(this.pageTb) { + this.pageTb.bindStore(store); + } + } + + this.store = Ext.StoreMgr.lookup(store); + this.store.on({ + scope: this, + beforeload: this.onBeforeLoad, + load: this.onLoad, + exception: this.collapse + }); + + if(this.view){ + this.view.bindStore(store); + } + } + }, + + // private + initEvents : function(){ + Ext.form.ComboBox.superclass.initEvents.call(this); + + this.keyNav = new Ext.KeyNav(this.el, { + "up" : function(e){ + this.inKeyMode = true; + this.selectPrev(); + }, + + "down" : function(e){ + if(!this.isExpanded()){ + this.onTriggerClick(); + }else{ + this.inKeyMode = true; + this.selectNext(); + } + }, + + "enter" : function(e){ + this.onViewClick(); + }, + + "esc" : function(e){ + this.collapse(); + }, + + "tab" : function(e){ + this.onViewClick(false); + return true; + }, + + scope : this, + + doRelay : function(e, h, hname){ + if(hname == 'down' || this.scope.isExpanded()){ + // this MUST be called before ComboBox#fireKey() + var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments); + if(!Ext.isIE && Ext.EventManager.useKeydown){ + // call Combo#fireKey() for browsers which use keydown event (except IE) + this.scope.fireKey(e); + } + return relay; + } + return true; + }, + + forceKeyDown : true, + defaultEventAction: 'stopEvent' + }); + this.queryDelay = Math.max(this.queryDelay || 10, + this.mode == 'local' ? 10 : 250); + this.dqTask = new Ext.util.DelayedTask(this.initQuery, this); + if(this.typeAhead){ + this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this); + } + if(this.editable !== false && !this.enableKeyEvents){ + this.mon(this.el, 'keyup', this.onKeyUp, this); + } + }, + + // private + onDestroy : function(){ + if (this.dqTask){ + this.dqTask.cancel(); + this.dqTask = null; + } + this.bindStore(null); + Ext.destroy( + this.resizer, + this.view, + this.pageTb, + this.list + ); + Ext.form.ComboBox.superclass.onDestroy.call(this); + }, + + // private + fireKey : function(e){ + if (!this.isExpanded()) { + Ext.form.ComboBox.superclass.fireKey.call(this, e); + } + }, + + // private + onResize : function(w, h){ + Ext.form.ComboBox.superclass.onResize.apply(this, arguments); + if(this.isVisible() && this.list){ + this.doResize(w); + }else{ + this.bufferSize = w; + } + }, + + doResize: function(w){ + if(!Ext.isDefined(this.listWidth)){ + var lw = Math.max(w, this.minListWidth); + this.list.setWidth(lw); + this.innerList.setWidth(lw - this.list.getFrameWidth('lr')); + } + }, + + // private + onEnable : function(){ + Ext.form.ComboBox.superclass.onEnable.apply(this, arguments); + if(this.hiddenField){ + this.hiddenField.disabled = false; + } + }, + + // private + onDisable : function(){ + Ext.form.ComboBox.superclass.onDisable.apply(this, arguments); + if(this.hiddenField){ + this.hiddenField.disabled = true; + } + }, + + // private + onBeforeLoad : function(){ + if(!this.hasFocus){ + return; + } + this.innerList.update(this.loadingText ? + '
            '+this.loadingText+'
            ' : ''); + this.restrictHeight(); + this.selectedIndex = -1; + }, + + // private + onLoad : function(){ + if(!this.hasFocus){ + return; + } + if(this.store.getCount() > 0 || this.listEmptyText){ + this.expand(); + this.restrictHeight(); + if(this.lastQuery == this.allQuery){ + if(this.editable){ + this.el.dom.select(); + } + if(!this.selectByValue(this.value, true)){ + this.select(0, true); + } + }else{ + this.selectNext(); + if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){ + this.taTask.delay(this.typeAheadDelay); + } + } + }else{ + this.onEmptyResults(); + } + //this.el.focus(); + }, + + // private + onTypeAhead : function(){ + if(this.store.getCount() > 0){ + var r = this.store.getAt(0); + var newValue = r.data[this.displayField]; + var len = newValue.length; + var selStart = this.getRawValue().length; + if(selStart != len){ + this.setRawValue(newValue); + this.selectText(selStart, newValue.length); + } + } + }, + + // private + onSelect : function(record, index){ + if(this.fireEvent('beforeselect', this, record, index) !== false){ + this.setValue(record.data[this.valueField || this.displayField]); + this.collapse(); + this.fireEvent('select', this, record, index); + } + }, + + // inherit docs + getName: function(){ + var hf = this.hiddenField; + return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this); + }, + + /** + * Returns the currently selected field value or empty string if no value is set. + * @return {String} value The selected value + */ + getValue : function(){ + if(this.valueField){ + return Ext.isDefined(this.value) ? this.value : ''; + }else{ + return Ext.form.ComboBox.superclass.getValue.call(this); + } + }, + + /** + * Clears any text/value currently set in the field + */ + clearValue : function(){ + if(this.hiddenField){ + this.hiddenField.value = ''; + } + this.setRawValue(''); + this.lastSelectionText = ''; + this.applyEmptyText(); + this.value = ''; + }, + + /** + * Sets the specified value into the field. If the value finds a match, the corresponding record text + * will be displayed in the field. If the value does not match the data value of an existing item, + * and the valueNotFoundText config option is defined, it will be displayed as the default field text. + * Otherwise the field will be blank (although the value will still be set). + * @param {String} value The value to match + * @return {Ext.form.Field} this + */ + setValue : function(v){ + var text = v; + if(this.valueField){ + var r = this.findRecord(this.valueField, v); + if(r){ + text = r.data[this.displayField]; + }else if(Ext.isDefined(this.valueNotFoundText)){ + text = this.valueNotFoundText; + } + } + this.lastSelectionText = text; + if(this.hiddenField){ + this.hiddenField.value = v; + } + Ext.form.ComboBox.superclass.setValue.call(this, text); + this.value = v; + return this; + }, + + // private + findRecord : function(prop, value){ + var record; + if(this.store.getCount() > 0){ + this.store.each(function(r){ + if(r.data[prop] == value){ + record = r; + return false; + } + }); + } + return record; + }, + + // private + onViewMove : function(e, t){ + this.inKeyMode = false; + }, + + // private + onViewOver : function(e, t){ + if(this.inKeyMode){ // prevent key nav and mouse over conflicts + return; + } + var item = this.view.findItemFromChild(t); + if(item){ + var index = this.view.indexOf(item); + this.select(index, false); + } + }, + + // private + onViewClick : function(doFocus){ + var index = this.view.getSelectedIndexes()[0], + s = this.store, + r = s.getAt(index); + if(r){ + this.onSelect(r, index); + }else if(s.getCount() === 0){ + this.onEmptyResults(); + } + if(doFocus !== false){ + this.el.focus(); + } + }, + + // private + restrictHeight : function(){ + this.innerList.dom.style.height = ''; + var inner = this.innerList.dom, + pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight, + h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight), + ha = this.getPosition()[1]-Ext.getBody().getScroll().top, + hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height, + space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5; + + h = Math.min(h, space, this.maxHeight); + + this.innerList.setHeight(h); + this.list.beginUpdate(); + this.list.setHeight(h+pad); + this.list.alignTo(this.wrap, this.listAlign); + this.list.endUpdate(); + }, + + // private + onEmptyResults : function(){ + this.collapse(); + }, + + /** + * Returns true if the dropdown list is expanded, else false. + */ + isExpanded : function(){ + return this.list && this.list.isVisible(); + }, + + /** + * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire. + * The store must be loaded and the list expanded for this function to work, otherwise use setValue. + * @param {String} value The data value of the item to select + * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the + * selected item if it is not currently in view (defaults to true) + * @return {Boolean} True if the value matched an item in the list, else false + */ + selectByValue : function(v, scrollIntoView){ + if(!Ext.isEmpty(v, true)){ + var r = this.findRecord(this.valueField || this.displayField, v); + if(r){ + this.select(this.store.indexOf(r), scrollIntoView); + return true; + } + } + return false; + }, + + /** + * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire. + * The store must be loaded and the list expanded for this function to work, otherwise use setValue. + * @param {Number} index The zero-based index of the list item to select + * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the + * selected item if it is not currently in view (defaults to true) + */ + select : function(index, scrollIntoView){ + this.selectedIndex = index; + this.view.select(index); + if(scrollIntoView !== false){ + var el = this.view.getNode(index); + if(el){ + this.innerList.scrollChildIntoView(el, false); + } + } + }, + + // private + selectNext : function(){ + var ct = this.store.getCount(); + if(ct > 0){ + if(this.selectedIndex == -1){ + this.select(0); + }else if(this.selectedIndex < ct-1){ + this.select(this.selectedIndex+1); + } + } + }, + + // private + selectPrev : function(){ + var ct = this.store.getCount(); + if(ct > 0){ + if(this.selectedIndex == -1){ + this.select(0); + }else if(this.selectedIndex !== 0){ + this.select(this.selectedIndex-1); + } + } + }, + + // private + onKeyUp : function(e){ + var k = e.getKey(); + if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){ + this.lastKey = k; + this.dqTask.delay(this.queryDelay); + } + Ext.form.ComboBox.superclass.onKeyUp.call(this, e); + }, + + // private + validateBlur : function(){ + return !this.list || !this.list.isVisible(); + }, + + // private + initQuery : function(){ + this.doQuery(this.getRawValue()); + }, + + // private + beforeBlur : function(){ + var val = this.getRawValue(), + rec = this.findRecord(this.displayField, val); + if(!rec && this.forceSelection){ + if(val.length > 0 && val != this.emptyText){ + this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : ''; + this.applyEmptyText(); + }else{ + this.clearValue(); + } + }else{ + if(rec){ + val = rec.get(this.valueField || this.displayField); + } + this.setValue(val); + } + }, + + /** + * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the + * query allowing the query action to be canceled if needed. + * @param {String} query The SQL query to execute + * @param {Boolean} forceAll true to force the query to execute even if there are currently fewer + * characters in the field than the minimum specified by the {@link #minChars} config option. It + * also clears any filter previously saved in the current store (defaults to false) + */ + doQuery : function(q, forceAll){ + q = Ext.isEmpty(q) ? '' : q; + var qe = { + query: q, + forceAll: forceAll, + combo: this, + cancel:false + }; + if(this.fireEvent('beforequery', qe)===false || qe.cancel){ + return false; + } + q = qe.query; + forceAll = qe.forceAll; + if(forceAll === true || (q.length >= this.minChars)){ + if(this.lastQuery !== q){ + this.lastQuery = q; + if(this.mode == 'local'){ + this.selectedIndex = -1; + if(forceAll){ + this.store.clearFilter(); + }else{ + this.store.filter(this.displayField, q); + } + this.onLoad(); + }else{ + this.store.baseParams[this.queryParam] = q; + this.store.load({ + params: this.getParams(q) + }); + this.expand(); + } + }else{ + this.selectedIndex = -1; + this.onLoad(); + } + } + }, + + // private + getParams : function(q){ + var p = {}; + //p[this.queryParam] = q; + if(this.pageSize){ + p.start = 0; + p.limit = this.pageSize; + } + return p; + }, + + /** + * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion. + */ + collapse : function(){ + if(!this.isExpanded()){ + return; + } + this.list.hide(); + Ext.getDoc().un('mousewheel', this.collapseIf, this); + Ext.getDoc().un('mousedown', this.collapseIf, this); + this.fireEvent('collapse', this); + }, + + // private + collapseIf : function(e){ + if(!e.within(this.wrap) && !e.within(this.list)){ + this.collapse(); + } + }, + + /** + * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion. + */ + expand : function(){ + if(this.isExpanded() || !this.hasFocus){ + return; + } + if(this.bufferSize){ + this.doResize(this.bufferSize); + delete this.bufferSize; + } + this.list.alignTo(this.wrap, this.listAlign); + this.list.show(); + if(Ext.isGecko2){ + this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac + } + Ext.getDoc().on({ + scope: this, + mousewheel: this.collapseIf, + mousedown: this.collapseIf + }); + this.fireEvent('expand', this); + }, + + /** + * @method onTriggerClick + * @hide + */ + // private + // Implements the default empty TriggerField.onTriggerClick function + onTriggerClick : function(){ + if(this.disabled){ + return; + } + if(this.isExpanded()){ + this.collapse(); + this.el.focus(); + }else { + this.onFocus({}); + if(this.triggerAction == 'all') { + this.doQuery(this.allQuery, true); + } else { + this.doQuery(this.getRawValue()); + } + this.el.focus(); + } + } + + /** + * @hide + * @method autoSize + */ + /** + * @cfg {Boolean} grow @hide + */ + /** + * @cfg {Number} growMin @hide + */ + /** + * @cfg {Number} growMax @hide + */ + +}); Ext.reg('combo', Ext.form.ComboBox);/** * @class Ext.form.Checkbox * @extends Ext.form.Field @@ -55193,546 +56743,563 @@ Ext.form.Checkbox = Ext.extend(Ext.form.Field, { /** * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function * (defaults to this Checkbox). - */ - - // private - actionMode : 'wrap', - - // private - initComponent : function(){ - Ext.form.Checkbox.superclass.initComponent.call(this); - this.addEvents( - /** - * @event check - * Fires when the checkbox is checked or unchecked. - * @param {Ext.form.Checkbox} this This checkbox - * @param {Boolean} checked The new checked value - */ - 'check' - ); - }, - - // private - onResize : function(){ - Ext.form.Checkbox.superclass.onResize.apply(this, arguments); - if(!this.boxLabel && !this.fieldLabel){ - this.el.alignTo(this.wrap, 'c-c'); - } - }, - - // private - initEvents : function(){ - Ext.form.Checkbox.superclass.initEvents.call(this); - this.mon(this.el, 'click', this.onClick, this); - this.mon(this.el, 'change', this.onClick, this); - }, - - // private - getResizeEl : function(){ - return this.wrap; - }, - - // private - getPositionEl : function(){ - return this.wrap; - }, - - /** - * @hide - * Overridden and disabled. The editor element does not support standard valid/invalid marking. - * @method - */ - markInvalid : Ext.emptyFn, - /** - * @hide - * Overridden and disabled. The editor element does not support standard valid/invalid marking. - * @method - */ - clearInvalid : Ext.emptyFn, - - // private - onRender : function(ct, position){ - Ext.form.Checkbox.superclass.onRender.call(this, ct, position); - if(this.inputValue !== undefined){ - this.el.dom.value = this.inputValue; - } - this.wrap = this.el.wrap({cls: 'x-form-check-wrap'}); - if(this.boxLabel){ - this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel}); - } - if(this.checked){ - this.setValue(true); - }else{ - this.checked = this.el.dom.checked; - } - }, - - // private - onDestroy : function(){ - Ext.destroy(this.wrap); - Ext.form.Checkbox.superclass.onDestroy.call(this); - }, - - // private - initValue : function() { - this.originalValue = this.getValue(); - }, - - /** - * Returns the checked state of the checkbox. - * @return {Boolean} True if checked, else false - */ - getValue : function(){ - if(this.rendered){ - return this.el.dom.checked; - } - return false; - }, - - // private - onClick : function(){ - if(this.el.dom.checked != this.checked){ - this.setValue(this.el.dom.checked); - } - }, - - /** - * Sets the checked state of the checkbox, fires the 'check' event, and calls a - * {@link #handler} (if configured). - * @param {Boolean/String} checked The following values will check the checkbox: - * true, 'true', '1', or 'on'. Any other value will uncheck the checkbox. - * @return {Ext.form.Field} this - */ - setValue : function(v){ - var checked = this.checked ; - this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on'); - if(this.rendered){ - this.el.dom.checked = this.checked; - this.el.dom.defaultChecked = this.checked; - } - if(checked != this.checked){ - this.fireEvent('check', this, this.checked); - if(this.handler){ - this.handler.call(this.scope || this, this, this.checked); - } - } - return this; - } -}); -Ext.reg('checkbox', Ext.form.Checkbox); -/** - * @class Ext.form.CheckboxGroup - * @extends Ext.form.Field - *

            A grouping container for {@link Ext.form.Checkbox} controls.

            - *

            Sample usage:

            - *
            
            -var myCheckboxGroup = new Ext.form.CheckboxGroup({
            -    id:'myGroup',
            -    xtype: 'checkboxgroup',
            -    fieldLabel: 'Single Column',
            -    itemCls: 'x-check-group-alt',
            -    // Put all controls in a single column with width 100%
            -    columns: 1,
            -    items: [
            -        {boxLabel: 'Item 1', name: 'cb-col-1'},
            -        {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
            -        {boxLabel: 'Item 3', name: 'cb-col-3'}
            -    ]
            -});
            - * 
            - * @constructor - * Creates a new CheckboxGroup - * @param {Object} config Configuration options - * @xtype checkboxgroup - */ -Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, { - /** - * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects - * to arrange in the group. - */ - /** - * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped - * checkbox/radio controls using automatic layout. This config can take several types of values: - *
            • 'auto' :

              The controls will be rendered one per column on one row and the width - * of each column will be evenly distributed based on the width of the overall field container. This is the default.

            • - *
            • Number :

              If you specific a number (e.g., 3) that number of columns will be - * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.

            • - *
            • Array : Object

              You can also specify an array of column widths, mixing integer - * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will - * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float - * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field - * container you should do so.

            - */ - columns : 'auto', - /** - * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column - * top to bottom before starting on the next column. The number of controls in each column will be automatically - * calculated to keep columns as even as possible. The default value is false, so that controls will be added - * to columns one at a time, completely filling each row left to right before starting on the next row. - */ - vertical : false, - /** - * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true). - * If no items are selected at validation time, {@link @blankText} will be used as the error text. - */ - allowBlank : true, - /** - * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must - * select at least one item in this group") - */ - blankText : "You must select at least one item in this group", - - // private - defaultType : 'checkbox', - - // private - groupCls : 'x-form-check-group', - - // private - initComponent: function(){ - this.addEvents( - /** - * @event change - * Fires when the state of a child checkbox changes. - * @param {Ext.form.CheckboxGroup} this - * @param {Array} checked An array containing the checked boxes. - */ - 'change' - ); - Ext.form.CheckboxGroup.superclass.initComponent.call(this); - }, - - // private - onRender : function(ct, position){ - if(!this.el){ - var panelCfg = { - cls: this.groupCls, - layout: 'column', - border: false, - renderTo: ct - }; - var colCfg = { - defaultType: this.defaultType, - layout: 'form', - border: false, - defaults: { - hideLabel: true, - anchor: '100%' - } - }; - - if(this.items[0].items){ - - // The container has standard ColumnLayout configs, so pass them in directly - - Ext.apply(panelCfg, { - layoutConfig: {columns: this.items.length}, - defaults: this.defaults, - items: this.items - }); - for(var i=0, len=this.items.length; i0 && i%rows==0){ - ri++; - } - if(this.items[i].fieldLabel){ - this.items[i].hideLabel = false; - } - cols[ri].items.push(this.items[i]); - }; - }else{ - for(var i=0, len=this.items.length; i
      • - * See {@link Ext.form.Checkbox#setValue} for additional information. - * @param {Mixed} id The checkbox to check, or as described by example shown. - * @param {Boolean} value (optional) The value to set the item. - * @return {Ext.form.CheckboxGroup} this + * @hide + * Overridden and disabled. The editor element does not support standard valid/invalid marking. + * @method */ - setValue : function(id, value){ - if(this.rendered){ - if(arguments.length == 1){ - if(Ext.isArray(id)){ - //an array of boolean values - Ext.each(id, function(val, idx){ - var item = this.items.itemAt(idx); - if(item){ - item.setValue(val); - } - }, this); - }else if(Ext.isObject(id)){ - //set of name/value pairs - for(var i in id){ - var f = this.getBox(i); - if(f){ - f.setValue(id[i]); - } - } - }else{ - this.setValueForItem(id); - } - }else{ - var f = this.getBox(id); - if(f){ - f.setValue(value); - } - } + markInvalid : Ext.emptyFn, + /** + * @hide + * Overridden and disabled. The editor element does not support standard valid/invalid marking. + * @method + */ + clearInvalid : Ext.emptyFn, + + // private + onRender : function(ct, position){ + Ext.form.Checkbox.superclass.onRender.call(this, ct, position); + if(this.inputValue !== undefined){ + this.el.dom.value = this.inputValue; + } + this.wrap = this.el.wrap({cls: 'x-form-check-wrap'}); + if(this.boxLabel){ + this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel}); + } + if(this.checked){ + this.setValue(true); }else{ - this.values = arguments; + this.checked = this.el.dom.checked; } - return this; + // Need to repaint for IE, otherwise positioning is broken + if(Ext.isIE){ + this.wrap.repaint(); + } + this.resizeEl = this.positionEl = this.wrap; }, - - // private - onDestroy: function(){ - Ext.destroy(this.panel); - Ext.form.CheckboxGroup.superclass.onDestroy.call(this); + // private + onDestroy : function(){ + Ext.destroy(this.wrap); + Ext.form.Checkbox.superclass.onDestroy.call(this); }, - - setValueForItem : function(val){ - val = String(val).split(','); - this.eachItem(function(item){ - if(val.indexOf(item.inputValue)> -1){ - item.setValue(true); - } - }); - }, - + // private - getBox : function(id){ - var box = null; - this.eachItem(function(f){ - if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){ - box = f; - return false; - } - }); - return box; + initValue : function() { + this.originalValue = this.getValue(); }, - + /** - * Gets an array of the selected {@link Ext.form.Checkbox} in the group. - * @return {Array} An array of the selected checkboxes. + * Returns the checked state of the checkbox. + * @return {Boolean} True if checked, else false */ getValue : function(){ - var out = []; - this.eachItem(function(item){ - if(item.checked){ - out.push(item); - } - }); - return out; + if(this.rendered){ + return this.el.dom.checked; + } + return this.checked; }, - - // private - eachItem: function(fn){ - if(this.items && this.items.each){ - this.items.each(fn, this); + + // private + onClick : function(){ + if(this.el.dom.checked != this.checked){ + this.setValue(this.el.dom.checked); } }, - - /** - * @cfg {String} name - * @hide - */ - /** - * @method initValue - * @hide - */ - initValue : Ext.emptyFn, - /** - * @method getValue - * @hide - */ - getValue : Ext.emptyFn, - /** - * @method getRawValue - * @hide - */ - getRawValue : Ext.emptyFn, - + /** - * @method setRawValue - * @hide + * Sets the checked state of the checkbox, fires the 'check' event, and calls a + * {@link #handler} (if configured). + * @param {Boolean/String} checked The following values will check the checkbox: + * true, 'true', '1', or 'on'. Any other value will uncheck the checkbox. + * @return {Ext.form.Field} this */ - setRawValue : Ext.emptyFn - + setValue : function(v){ + var checked = this.checked ; + this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on'); + if(this.rendered){ + this.el.dom.checked = this.checked; + this.el.dom.defaultChecked = this.checked; + } + if(checked != this.checked){ + this.fireEvent('check', this, this.checked); + if(this.handler){ + this.handler.call(this.scope || this, this, this.checked); + } + } + return this; + } }); - -Ext.reg('checkboxgroup', Ext.form.CheckboxGroup); +Ext.reg('checkbox', Ext.form.Checkbox); +/** + * @class Ext.form.CheckboxGroup + * @extends Ext.form.Field + *

        A grouping container for {@link Ext.form.Checkbox} controls.

        + *

        Sample usage:

        + *
        
        +var myCheckboxGroup = new Ext.form.CheckboxGroup({
        +    id:'myGroup',
        +    xtype: 'checkboxgroup',
        +    fieldLabel: 'Single Column',
        +    itemCls: 'x-check-group-alt',
        +    // Put all controls in a single column with width 100%
        +    columns: 1,
        +    items: [
        +        {boxLabel: 'Item 1', name: 'cb-col-1'},
        +        {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
        +        {boxLabel: 'Item 3', name: 'cb-col-3'}
        +    ]
        +});
        + * 
        + * @constructor + * Creates a new CheckboxGroup + * @param {Object} config Configuration options + * @xtype checkboxgroup + */ +Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, { + /** + * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects + * to arrange in the group. + */ + /** + * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped + * checkbox/radio controls using automatic layout. This config can take several types of values: + *
        • 'auto' :

          The controls will be rendered one per column on one row and the width + * of each column will be evenly distributed based on the width of the overall field container. This is the default.

        • + *
        • Number :

          If you specific a number (e.g., 3) that number of columns will be + * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.

        • + *
        • Array : Object

          You can also specify an array of column widths, mixing integer + * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will + * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float + * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field + * container you should do so.

        + */ + columns : 'auto', + /** + * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column + * top to bottom before starting on the next column. The number of controls in each column will be automatically + * calculated to keep columns as even as possible. The default value is false, so that controls will be added + * to columns one at a time, completely filling each row left to right before starting on the next row. + */ + vertical : false, + /** + * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true). + * If no items are selected at validation time, {@link @blankText} will be used as the error text. + */ + allowBlank : true, + /** + * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must + * select at least one item in this group") + */ + blankText : "You must select at least one item in this group", + + // private + defaultType : 'checkbox', + + // private + groupCls : 'x-form-check-group', + + // private + initComponent: function(){ + this.addEvents( + /** + * @event change + * Fires when the state of a child checkbox changes. + * @param {Ext.form.CheckboxGroup} this + * @param {Array} checked An array containing the checked boxes. + */ + 'change' + ); + Ext.form.CheckboxGroup.superclass.initComponent.call(this); + }, + + // private + onRender : function(ct, position){ + if(!this.el){ + var panelCfg = { + id: this.id, + cls: this.groupCls, + layout: 'column', + border: false, + renderTo: ct, + bufferResize: false // Default this to false, since it doesn't really have a proper ownerCt. + }; + var colCfg = { + defaultType: this.defaultType, + layout: 'form', + border: false, + defaults: { + hideLabel: true, + anchor: '100%' + } + }; + + if(this.items[0].items){ + + // The container has standard ColumnLayout configs, so pass them in directly + + Ext.apply(panelCfg, { + layoutConfig: {columns: this.items.length}, + defaults: this.defaults, + items: this.items + }); + for(var i=0, len=this.items.length; i0 && i%rows==0){ + ri++; + } + if(this.items[i].fieldLabel){ + this.items[i].hideLabel = false; + } + cols[ri].items.push(this.items[i]); + }; + }else{ + for(var i=0, len=this.items.length; i