3 * Copyright(c) 2006-2009 Ext JS, LLC
5 * http://www.extjs.com/license
9 * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
10 * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11 * from your DOM building code.</p>
13 * <p><b><u>DomHelper element specification object</u></b></p>
14 * <p>A specification object is used when creating elements. Attributes of this object
15 * are assumed to be element attributes, except for 4 special attributes:
16 * <div class="mdetail-params"><ul>
17 * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
18 * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
19 * same kind of element definition objects to be created and appended. These can be nested
20 * as deep as you want.</div></li>
21 * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
22 * This will end up being either the "class" attribute on a HTML fragment or className
23 * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
24 * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
27 * <p><b><u>Insertion methods</u></b></p>
28 * <p>Commonly used insertion methods:
29 * <div class="mdetail-params"><ul>
30 * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
31 * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
32 * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
33 * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
34 * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
35 * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
38 * <p><b><u>Example</u></b></p>
39 * <p>This is an example, where an unordered list with 3 children items is appended to an existing
40 * element with id <tt>'my-div'</tt>:<br>
42 var dh = Ext.DomHelper; // create shorthand alias
43 // specification object
48 // append children after creating
49 children: [ // may also specify 'cn' instead of 'children'
50 {tag: 'li', id: 'item0', html: 'List Item 0'},
51 {tag: 'li', id: 'item1', html: 'List Item 1'},
52 {tag: 'li', id: 'item2', html: 'List Item 2'}
56 'my-div', // the context element 'my-div' can either be the id or the actual node
57 spec // the specification object
60 * <p>Element creation specification parameters in this class may also be passed as an Array of
61 * specification objects. This can be used to insert multiple sibling nodes into an existing
62 * container very efficiently. For example, to add more list items to the example above:<pre><code>
64 {tag: 'li', id: 'item3', html: 'List Item 3'},
65 {tag: 'li', id: 'item4', html: 'List Item 4'}
69 * <p><b><u>Templating</u></b></p>
70 * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
71 * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
72 * insert new elements. Revisiting the example above, we could utilize templating this time:
75 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
77 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
79 for(var i = 0; i < 5, i++){
80 tpl.append(list, [i]); // use template to append to the actual node
83 * <p>An example using a template:<pre><code>
84 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
86 var tpl = new Ext.DomHelper.createTemplate(html);
87 tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
88 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
91 * <p>The same example using named parameters:<pre><code>
92 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
94 var tpl = new Ext.DomHelper.createTemplate(html);
95 tpl.append('blog-roll', {
97 url: 'http://www.jackslocum.com/',
98 text: "Jack's Site"
100 tpl.append('blog-roll', {
102 url: 'http://www.dustindiaz.com/',
103 text: "Dustin's Site"
107 * <p><b><u>Compiling Templates</u></b></p>
108 * <p>Templates are applied using regular expressions. The performance is great, but if
109 * you are adding a bunch of DOM elements using the same template, you can increase
110 * performance even further by {@link Ext.Template#compile "compiling"} the template.
111 * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
112 * broken up at the different variable points and a dynamic function is created and eval'ed.
113 * The generated function performs string concatenation of these parts and the passed
114 * variables instead of using regular expressions.
116 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
118 var tpl = new Ext.DomHelper.createTemplate(html);
121 //... use template like normal
124 * <p><b><u>Performance Boost</u></b></p>
125 * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
126 * of DOM can significantly boost performance.</p>
127 * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
128 * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
129 * results in the creation of a text node. Usage:</p>
131 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
135 Ext.DomHelper = function(){
136 var tempTableEl = null,
137 emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
138 tableRe = /^table|tbody|tr|td$/i,
140 // kill repeat to save bytes
141 afterbegin = "afterbegin",
142 afterend = "afterend",
143 beforebegin = "beforebegin",
144 beforeend = "beforeend",
153 function doInsert(el, o, returnElement, pos, sibling, append){
154 var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
155 return returnElement ? Ext.get(newNode, true) : newNode;
158 // build as innerHTML where available
159 function createHtml(o){
167 if(typeof o == 'string'){
169 } else if (Ext.isArray(o)) {
170 Ext.each(o, function(v) {
174 b += "<" + (o.tag = o.tag || "div");
175 Ext.iterate(o, function(attr, val){
176 if(!/tag|children|cn|html$/i.test(attr)){
177 if (Ext.isObject(val)) {
178 b += " " + attr + "='";
179 Ext.iterate(val, function(key, keyVal){
180 b += key + ":" + keyVal + ";";
184 b += " " + ({cls : "class", htmlFor : "for"}[attr] || attr) + "='" + val + "'";
188 // Now either just close the tag or try to add children and close the tag.
189 if (emptyTags.test(o.tag)) {
193 if ((cn = o.children || o.cn)) {
198 b += "</" + o.tag + ">";
204 function ieTable(depth, s, h, e){
205 tempTableEl.innerHTML = [s, h, e].join('');
216 * Nasty code for IE's broken table implementation
218 function insertIntoTable(tag, where, el, html) {
222 tempTableEl = tempTableEl || document.createElement('div');
224 if(tag == 'td' && (where == afterbegin || where == beforeend) ||
225 !/td|tr|tbody/i.test(tag) && (where == beforebegin || where == afterend)) {
228 before = where == beforebegin ? el :
229 where == afterend ? el.nextSibling :
230 where == afterbegin ? el.firstChild : null;
232 if (where == beforebegin || where == afterend) {
236 if (tag == 'td' || (tag == "tr" && (where == beforeend || where == afterbegin))) {
237 node = ieTable(4, trs, html, tre);
238 } else if ((tag == "tbody" && (where == beforeend || where == afterbegin)) ||
239 (tag == "tr" && (where == beforebegin || where == afterend))) {
240 node = ieTable(3, tbs, html, tbe);
242 node = ieTable(2, ts, html, te);
244 el.insertBefore(node, before);
251 * Returns the markup for the passed Element(s) config.
252 * @param {Object} o The DOM object spec (and children)
255 markup : function(o){
256 return createHtml(o);
260 * Inserts an HTML fragment into the DOM.
261 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
262 * @param {HTMLElement} el The context element
263 * @param {String} html The HTML fragmenet
264 * @return {HTMLElement} The new node
266 insertHtml : function(where, el, html){
275 where = where.toLowerCase();
276 // add these here because they are used in both branches of the condition.
277 hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
278 hash[afterend] = ['AfterEnd', 'nextSibling'];
280 if (el.insertAdjacentHTML) {
281 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
284 // add these two to the hash.
285 hash[afterbegin] = ['AfterBegin', 'firstChild'];
286 hash[beforeend] = ['BeforeEnd', 'lastChild'];
287 if ((hashVal = hash[where])) {
288 el.insertAdjacentHTML(hashVal[0], html);
289 return el[hashVal[1]];
292 range = el.ownerDocument.createRange();
293 setStart = "setStart" + (/end/i.test(where) ? "After" : "Before");
296 frag = range.createContextualFragment(html);
297 el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
298 return el[(where == beforebegin ? "previous" : "next") + "Sibling"];
300 rangeEl = (where == afterbegin ? "first" : "last") + "Child";
302 range[setStart](el[rangeEl]);
303 frag = range.createContextualFragment(html);
304 if(where == afterbegin){
305 el.insertBefore(frag, el.firstChild);
307 el.appendChild(frag);
315 throw 'Illegal insertion point -> "' + where + '"';
319 * Creates new DOM element(s) and inserts them before el.
320 * @param {Mixed} el The context element
321 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
322 * @param {Boolean} returnElement (optional) true to return a Ext.Element
323 * @return {HTMLElement/Ext.Element} The new node
325 insertBefore : function(el, o, returnElement){
326 return doInsert(el, o, returnElement, beforebegin);
330 * Creates new DOM element(s) and inserts them after el.
331 * @param {Mixed} el The context element
332 * @param {Object} o The DOM object spec (and children)
333 * @param {Boolean} returnElement (optional) true to return a Ext.Element
334 * @return {HTMLElement/Ext.Element} The new node
336 insertAfter : function(el, o, returnElement){
337 return doInsert(el, o, returnElement, afterend, "nextSibling");
341 * Creates new DOM element(s) and inserts them as the first child of el.
342 * @param {Mixed} el The context element
343 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
344 * @param {Boolean} returnElement (optional) true to return a Ext.Element
345 * @return {HTMLElement/Ext.Element} The new node
347 insertFirst : function(el, o, returnElement){
348 return doInsert(el, o, returnElement, afterbegin, "firstChild");
352 * Creates new DOM element(s) and appends them to el.
353 * @param {Mixed} el The context element
354 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
355 * @param {Boolean} returnElement (optional) true to return a Ext.Element
356 * @return {HTMLElement/Ext.Element} The new node
358 append : function(el, o, returnElement){
359 return doInsert(el, o, returnElement, beforeend, "", true);
363 * Creates new DOM element(s) and overwrites the contents of el with them.
364 * @param {Mixed} el The context element
365 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
366 * @param {Boolean} returnElement (optional) true to return a Ext.Element
367 * @return {HTMLElement/Ext.Element} The new node
369 overwrite : function(el, o, returnElement){
371 el.innerHTML = createHtml(o);
372 return returnElement ? Ext.get(el.firstChild) : el.firstChild;
375 createHtml : createHtml
379 * @class Ext.DomHelper
\r
381 Ext.apply(Ext.DomHelper,
\r
384 afterbegin = 'afterbegin',
\r
385 afterend = 'afterend',
\r
386 beforebegin = 'beforebegin',
\r
387 beforeend = 'beforeend';
\r
390 function doInsert(el, o, returnElement, pos, sibling, append){
\r
391 el = Ext.getDom(el);
\r
394 newNode = createDom(o, null);
\r
396 el.appendChild(newNode);
\r
398 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
\r
401 newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
\r
403 return returnElement ? Ext.get(newNode, true) : newNode;
\r
408 function createDom(o, parentNode){
\r
416 if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
\r
417 el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
\r
418 Ext.each(o, function(v) {
\r
421 } else if (Ext.isString(o)) { // Allow a string as a child spec.
\r
422 el = doc.createTextNode(o);
\r
424 el = doc.createElement( o.tag || 'div' );
\r
425 useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
\r
426 Ext.iterate(o, function(attr, val){
\r
427 if(!/tag|children|cn|html|style/.test(attr)){
\r
429 el.className = val;
\r
432 el.setAttribute(attr, val);
\r
439 pub.applyStyles(el, o.style);
\r
441 if ((cn = o.children || o.cn)) {
\r
443 } else if (o.html) {
\r
444 el.innerHTML = o.html;
\r
448 parentNode.appendChild(el);
\r
455 * Creates a new Ext.Template from the DOM object spec.
\r
456 * @param {Object} o The DOM object spec (and children)
\r
457 * @return {Ext.Template} The new template
\r
459 createTemplate : function(o){
\r
460 var html = Ext.DomHelper.createHtml(o);
\r
461 return new Ext.Template(html);
\r
464 /** True to force the use of DOM instead of html fragments @type Boolean */
\r
468 * Applies a style specification to an element.
\r
469 * @param {String/HTMLElement} el The element to apply styles to
\r
470 * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
\r
471 * a function which returns such a specification.
\r
473 applyStyles : function(el, styles){
\r
480 if(Ext.isFunction(styles)){
\r
481 styles = styles.call();
\r
483 if(Ext.isString(styles)){
\r
484 styles = styles.trim().split(/\s*(?::|;)\s*/);
\r
485 for(len = styles.length; i < len;){
\r
486 el.setStyle(styles[i++], styles[i++]);
\r
488 }else if (Ext.isObject(styles)){
\r
489 el.setStyle(styles);
\r
495 * Creates new DOM element(s) and inserts them before el.
\r
496 * @param {Mixed} el The context element
\r
497 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
498 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
499 * @return {HTMLElement/Ext.Element} The new node
\r
502 insertBefore : function(el, o, returnElement){
\r
503 return doInsert(el, o, returnElement, beforebegin);
\r
507 * Creates new DOM element(s) and inserts them after el.
\r
508 * @param {Mixed} el The context element
\r
509 * @param {Object} o The DOM object spec (and children)
\r
510 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
511 * @return {HTMLElement/Ext.Element} The new node
\r
514 insertAfter : function(el, o, returnElement){
\r
515 return doInsert(el, o, returnElement, afterend, 'nextSibling');
\r
519 * Creates new DOM element(s) and inserts them as the first child of el.
\r
520 * @param {Mixed} el The context element
\r
521 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
522 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
523 * @return {HTMLElement/Ext.Element} The new node
\r
526 insertFirst : function(el, o, returnElement){
\r
527 return doInsert(el, o, returnElement, afterbegin, 'firstChild');
\r
531 * Creates new DOM element(s) and appends them to el.
\r
532 * @param {Mixed} el The context element
\r
533 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
534 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
535 * @return {HTMLElement/Ext.Element} The new node
\r
538 append: function(el, o, returnElement){
\r
539 return doInsert(el, o, returnElement, beforeend, '', true);
\r
543 * Creates new DOM element(s) without inserting them to the document.
\r
544 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
545 * @return {HTMLElement} The new uninserted node
\r
547 createDom: createDom
\r
551 * @class Ext.Template
552 * Represents an HTML fragment template. Templates can be precompiled for greater performance.
553 * For a list of available format functions, see {@link Ext.util.Format}.<br />
556 var t = new Ext.Template(
557 '<div name="{id}">',
558 '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
561 t.append('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
564 * @param {String/Array} html The HTML fragment or an array of fragments to join("") or multiple arguments to join("")
566 Ext.Template = function(html){
571 if (Ext.isArray(html)) {
572 html = html.join("");
573 } else if (a.length > 1) {
574 Ext.each(a, function(v) {
575 if (Ext.isObject(v)) {
590 Ext.Template.prototype = {
592 * Returns an HTML fragment of this template with the specified values applied.
593 * @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'})
594 * @return {String} The HTML fragment
596 applyTemplate : function(values){
600 me.compiled(values) :
601 me.html.replace(me.re, function(m, name){
602 return values[name] !== undefined ? values[name] : "";
607 * Sets the HTML used as the template and optionally compiles it.
608 * @param {String} html
609 * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
610 * @return {Ext.Template} this
612 set : function(html, compile){
616 return compile ? me.compile() : me;
620 * The regular expression used to match template variables
624 re : /\{([\w-]+)\}/g,
627 * Compiles the template into an internal function, eliminating the RegEx overhead.
628 * @return {Ext.Template} this
630 compile : function(){
632 sep = Ext.isGecko ? "+" : ",";
634 function fn(m, name){
635 name = "values['" + name + "']";
636 return "'"+ sep + '(' + name + " == undefined ? '' : " + name + ')' + sep + "'";
639 eval("this.compiled = function(values){ return " + (Ext.isGecko ? "'" : "['") +
640 me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
641 (Ext.isGecko ? "';};" : "'].join('');};"));
646 * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
647 * @param {Mixed} el The context element
648 * @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'})
649 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
650 * @return {HTMLElement/Ext.Element} The new node or Element
652 insertFirst: function(el, values, returnElement){
653 return this.doInsert('afterBegin', el, values, returnElement);
657 * Applies the supplied values to the template and inserts the new node(s) before el.
658 * @param {Mixed} el The context element
659 * @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'})
660 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
661 * @return {HTMLElement/Ext.Element} The new node or Element
663 insertBefore: function(el, values, returnElement){
664 return this.doInsert('beforeBegin', el, values, returnElement);
668 * Applies the supplied values to the template and inserts the new node(s) after el.
669 * @param {Mixed} el The context element
670 * @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'})
671 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
672 * @return {HTMLElement/Ext.Element} The new node or Element
674 insertAfter : function(el, values, returnElement){
675 return this.doInsert('afterEnd', el, values, returnElement);
679 * Applies the supplied values to the template and appends the new node(s) to el.
680 * @param {Mixed} el The context element
681 * @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'})
682 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
683 * @return {HTMLElement/Ext.Element} The new node or Element
685 append : function(el, values, returnElement){
686 return this.doInsert('beforeEnd', el, values, returnElement);
689 doInsert : function(where, el, values, returnEl){
691 var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
692 return returnEl ? Ext.get(newNode, true) : newNode;
696 * Applies the supplied values to the template and overwrites the content of el with the new node(s).
697 * @param {Mixed} el The context element
698 * @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'})
699 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
700 * @return {HTMLElement/Ext.Element} The new node or Element
702 overwrite : function(el, values, returnElement){
704 el.innerHTML = this.applyTemplate(values);
705 return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
709 * Alias for {@link #applyTemplate}
710 * Returns an HTML fragment of this template with the specified values applied.
711 * @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'})
712 * @return {String} The HTML fragment
713 * @member Ext.Template
716 Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate;
719 * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
720 * @param {String/HTMLElement} el A DOM element or its id
721 * @param {Object} config A configuration object
722 * @return {Ext.Template} The created template
725 Ext.Template.from = function(el, config){
727 return new Ext.Template(el.value || el.innerHTML, config || '');
729 * @class Ext.Template
\r
731 Ext.apply(Ext.Template.prototype, {
\r
733 * Returns an HTML fragment of this template with the specified values applied.
\r
734 * @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'})
\r
735 * @return {String} The HTML fragment
\r
738 applyTemplate : function(values){
\r
740 useF = me.disableFormats !== true,
\r
741 fm = Ext.util.Format,
\r
745 return me.compiled(values);
\r
747 function fn(m, name, format, args){
\r
748 if (format && useF) {
\r
749 if (format.substr(0, 5) == "this.") {
\r
750 return tpl.call(format.substr(5), values[name], values);
\r
753 // quoted values are required for strings in compiled templates,
\r
754 // but for non compiled we need to strip them
\r
755 // quoted reversed for jsmin
\r
756 var re = /^\s*['"](.*)["']\s*$/;
\r
757 args = args.split(',');
\r
758 for(var i = 0, len = args.length; i < len; i++){
\r
759 args[i] = args[i].replace(re, "$1");
\r
761 args = [values[name]].concat(args);
\r
763 args = [values[name]];
\r
765 return fm[format].apply(fm, args);
\r
768 return values[name] !== undefined ? values[name] : "";
\r
771 return me.html.replace(me.re, fn);
\r
775 * <tt>true</tt> to disable format functions (defaults to <tt>false</tt>)
\r
779 disableFormats : false,
\r
782 * The regular expression used to match template variables
\r
787 re : /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
\r
790 * Compiles the template into an internal function, eliminating the RegEx overhead.
\r
791 * @return {Ext.Template} this
\r
794 compile : function(){
\r
796 fm = Ext.util.Format,
\r
797 useF = me.disableFormats !== true,
\r
798 sep = Ext.isGecko ? "+" : ",",
\r
801 function fn(m, name, format, args){
\r
802 if(format && useF){
\r
803 args = args ? ',' + args : "";
\r
804 if(format.substr(0, 5) != "this."){
\r
805 format = "fm." + format + '(';
\r
807 format = 'this.call("'+ format.substr(5) + '", ';
\r
811 args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
\r
813 return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
\r
816 // branched to use + in gecko and [].join() in others
\r
818 body = "this.compiled = function(values){ return '" +
\r
819 me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
\r
822 body = ["this.compiled = function(values){ return ['"];
\r
823 body.push(me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn));
\r
824 body.push("'].join('');};");
\r
825 body = body.join('');
\r
831 // private function used to call members
\r
832 call : function(fnName, value, allValues){
\r
833 return this[fnName](value, allValues);
\r
836 Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate; /*
\r
837 * This is code is also distributed under MIT license for use
\r
838 * with jQuery and prototype JavaScript libraries.
\r
841 * @class Ext.DomQuery
\r
842 Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).
\r
844 DomQuery supports most of the <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors">CSS3 selectors spec</a>, along with some custom selectors and basic XPath.</p>
\r
847 All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example "div.foo:nth-child(odd)[@foo=bar].bar:first" would be a perfectly valid selector. Node filters are processed in the order in which they appear, which allows you to optimize your queries for your document structure.
\r
849 <h4>Element Selectors:</h4>
\r
851 <li> <b>*</b> any element</li>
\r
852 <li> <b>E</b> an element with the tag E</li>
\r
853 <li> <b>E F</b> All descendent elements of E that have the tag F</li>
\r
854 <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
\r
855 <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
\r
856 <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
\r
858 <h4>Attribute Selectors:</h4>
\r
859 <p>The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.</p>
\r
861 <li> <b>E[foo]</b> has an attribute "foo"</li>
\r
862 <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
\r
863 <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
\r
864 <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
\r
865 <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
\r
866 <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
\r
867 <li> <b>E[foo!=bar]</b> has an attribute "foo" that does not equal "bar"</li>
\r
869 <h4>Pseudo Classes:</h4>
\r
871 <li> <b>E:first-child</b> E is the first child of its parent</li>
\r
872 <li> <b>E:last-child</b> E is the last child of its parent</li>
\r
873 <li> <b>E:nth-child(<i>n</i>)</b> E is the <i>n</i>th child of its parent (1 based as per the spec)</li>
\r
874 <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
\r
875 <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
\r
876 <li> <b>E:only-child</b> E is the only child of its parent</li>
\r
877 <li> <b>E:checked</b> E is an element that is has a checked attribute that is true (e.g. a radio or checkbox) </li>
\r
878 <li> <b>E:first</b> the first E in the resultset</li>
\r
879 <li> <b>E:last</b> the last E in the resultset</li>
\r
880 <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
\r
881 <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
\r
882 <li> <b>E:even</b> shortcut for :nth-child(even)</li>
\r
883 <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
\r
884 <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
\r
885 <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
\r
886 <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
\r
887 <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
\r
888 <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
\r
890 <h4>CSS Value Selectors:</h4>
\r
892 <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
\r
893 <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
\r
894 <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
\r
895 <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
\r
896 <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
\r
897 <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
\r
901 Ext.DomQuery = function(){
\r
906 trimRe = /^\s+|\s+$/g,
\r
907 tplRe = /\{(\d+)\}/g,
\r
908 modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
\r
909 tagTokenRe = /^(#)?([\w-\*]+)/,
\r
910 nthRe = /(\d*)n\+?(\d*)/,
\r
912 // This is for IE MSXML which does not support expandos.
\r
913 // IE runs the same speed using setAttribute, however FF slows way down
\r
914 // and Safari completely fails so they need to continue to use expandos.
\r
915 isIE = window.ActiveXObject ? true : false,
\r
916 isOpera = Ext.isOpera,
\r
919 // this eval is stop the compressor from
\r
920 // renaming the variable to something shorter
\r
921 eval("var batch = 30803;");
\r
923 function child(p, index){
\r
927 if(n.nodeType == 1){
\r
938 while((n = n.nextSibling) && n.nodeType != 1);
\r
943 while((n = n.previousSibling) && n.nodeType != 1);
\r
947 function children(d){
\r
948 var n = d.firstChild, ni = -1,
\r
951 nx = n.nextSibling;
\r
952 if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
\r
955 n.nodeIndex = ++ni;
\r
962 function byClassName(c, a, v){
\r
966 var r = [], ri = -1, cn;
\r
967 for(var i = 0, ci; ci = c[i]; i++){
\r
968 if((' '+ci.className+' ').indexOf(v) != -1){
\r
975 function attrValue(n, attr){
\r
976 if(!n.tagName && typeof n.length != "undefined"){
\r
985 if(attr == "class" || attr == "className"){
\r
986 return n.className;
\r
988 return n.getAttribute(attr) || n[attr];
\r
992 function getNodes(ns, mode, tagName){
\r
993 var result = [], ri = -1, cs;
\r
997 tagName = tagName || "*";
\r
998 if(typeof ns.getElementsByTagName != "undefined"){
\r
1002 for(var i = 0, ni; ni = ns[i]; i++){
\r
1003 cs = ni.getElementsByTagName(tagName);
\r
1004 for(var j = 0, ci; ci = cs[j]; j++){
\r
1005 result[++ri] = ci;
\r
1008 }else if(mode == "/" || mode == ">"){
\r
1009 var utag = tagName.toUpperCase();
\r
1010 for(var i = 0, ni, cn; ni = ns[i]; i++){
\r
1011 cn = isOpera ? ni.childNodes : (ni.children || ni.childNodes);
\r
1012 for(var j = 0, cj; cj = cn[j]; j++){
\r
1013 if(cj.nodeName == utag || cj.nodeName == tagName || tagName == '*'){
\r
1014 result[++ri] = cj;
\r
1018 }else if(mode == "+"){
\r
1019 var utag = tagName.toUpperCase();
\r
1020 for(var i = 0, n; n = ns[i]; i++){
\r
1021 while((n = n.nextSibling) && n.nodeType != 1);
\r
1022 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
\r
1026 }else if(mode == "~"){
\r
1027 var utag = tagName.toUpperCase();
\r
1028 for(var i = 0, n; n = ns[i]; i++){
\r
1029 while((n = n.nextSibling)){
\r
1030 if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
\r
1039 function concat(a, b){
\r
1041 return a.concat(b);
\r
1043 for(var i = 0, l = b.length; i < l; i++){
\r
1044 a[a.length] = b[i];
\r
1049 function byTag(cs, tagName){
\r
1050 if(cs.tagName || cs == document){
\r
1056 var r = [], ri = -1;
\r
1057 tagName = tagName.toLowerCase();
\r
1058 for(var i = 0, ci; ci = cs[i]; i++){
\r
1059 if(ci.nodeType == 1 && ci.tagName.toLowerCase()==tagName){
\r
1066 function byId(cs, attr, id){
\r
1067 if(cs.tagName || cs == document){
\r
1073 var r = [], ri = -1;
\r
1074 for(var i = 0,ci; ci = cs[i]; i++){
\r
1075 if(ci && ci.id == id){
\r
1083 function byAttribute(cs, attr, value, op, custom){
\r
1087 f = Ext.DomQuery.operators[op];
\r
1088 for(var i = 0, ci; ci = cs[i]; i++){
\r
1089 if(ci.nodeType != 1){
\r
1094 a = Ext.DomQuery.getStyle(ci, attr);
\r
1096 else if(attr == "class" || attr == "className"){
\r
1098 }else if(attr == "for"){
\r
1100 }else if(attr == "href"){
\r
1101 a = ci.getAttribute("href", 2);
\r
1103 a = ci.getAttribute(attr);
\r
1105 if((f && f(a, value)) || (!f && a)){
\r
1112 function byPseudo(cs, name, value){
\r
1113 return Ext.DomQuery.pseudos[name](cs, value);
\r
1116 function nodupIEXml(cs){
\r
1119 cs[0].setAttribute("_nodup", d);
\r
1121 for(var i = 1, len = cs.length; i < len; i++){
\r
1123 if(!c.getAttribute("_nodup") != d){
\r
1124 c.setAttribute("_nodup", d);
\r
1128 for(var i = 0, len = cs.length; i < len; i++){
\r
1129 cs[i].removeAttribute("_nodup");
\r
1134 function nodup(cs){
\r
1138 var len = cs.length, c, i, r = cs, cj, ri = -1;
\r
1139 if(!len || typeof cs.nodeType != "undefined" || len == 1){
\r
1142 if(isIE && typeof cs[0].selectSingleNode != "undefined"){
\r
1143 return nodupIEXml(cs);
\r
1147 for(i = 1; c = cs[i]; i++){
\r
1148 if(c._nodup != d){
\r
1152 for(var j = 0; j < i; j++){
\r
1155 for(j = i+1; cj = cs[j]; j++){
\r
1156 if(cj._nodup != d){
\r
1167 function quickDiffIEXml(c1, c2){
\r
1170 for(var i = 0, len = c1.length; i < len; i++){
\r
1171 c1[i].setAttribute("_qdiff", d);
\r
1173 for(var i = 0, len = c2.length; i < len; i++){
\r
1174 if(c2[i].getAttribute("_qdiff") != d){
\r
1175 r[r.length] = c2[i];
\r
1178 for(var i = 0, len = c1.length; i < len; i++){
\r
1179 c1[i].removeAttribute("_qdiff");
\r
1184 function quickDiff(c1, c2){
\r
1185 var len1 = c1.length,
\r
1191 if(isIE && c1[0].selectSingleNode){
\r
1192 return quickDiffIEXml(c1, c2);
\r
1194 for(var i = 0; i < len1; i++){
\r
1197 for(var i = 0, len = c2.length; i < len; i++){
\r
1198 if(c2[i]._qdiff != d){
\r
1199 r[r.length] = c2[i];
\r
1205 function quickId(ns, mode, root, id){
\r
1207 var d = root.ownerDocument || root;
\r
1208 return d.getElementById(id);
\r
1210 ns = getNodes(ns, mode, "*");
\r
1211 return byId(ns, null, id);
\r
1215 getStyle : function(el, name){
\r
1216 return Ext.fly(el).getStyle(name);
\r
1219 * Compiles a selector/xpath query into a reusable function. The returned function
\r
1220 * takes one parameter "root" (optional), which is the context node from where the query should start.
\r
1221 * @param {String} selector The selector/xpath query
\r
1222 * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
\r
1223 * @return {Function}
\r
1225 compile : function(path, type){
\r
1226 type = type || "select";
\r
1228 var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
\r
1229 q = path, mode, lq,
\r
1230 tk = Ext.DomQuery.matchers,
\r
1231 tklen = tk.length,
\r
1233 // accept leading mode switch
\r
1234 lmode = q.match(modeRe);
\r
1236 if(lmode && lmode[1]){
\r
1237 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
\r
1238 q = q.replace(lmode[1], "");
\r
1240 // strip leading slashes
\r
1241 while(path.substr(0, 1)=="/"){
\r
1242 path = path.substr(1);
\r
1245 while(q && lq != q){
\r
1247 var tm = q.match(tagTokenRe);
\r
1248 if(type == "select"){
\r
1251 fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';
\r
1253 fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';
\r
1255 q = q.replace(tm[0], "");
\r
1256 }else if(q.substr(0, 1) != '@'){
\r
1257 fn[fn.length] = 'n = getNodes(n, mode, "*");';
\r
1262 fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';
\r
1264 fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';
\r
1266 q = q.replace(tm[0], "");
\r
1269 while(!(mm = q.match(modeRe))){
\r
1270 var matched = false;
\r
1271 for(var j = 0; j < tklen; j++){
\r
1273 var m = q.match(t.re);
\r
1275 fn[fn.length] = t.select.replace(tplRe, function(x, i){
\r
1278 q = q.replace(m[0], "");
\r
1283 // prevent infinite loop on bad selector
\r
1285 throw 'Error parsing selector, parsing failed at "' + q + '"';
\r
1289 fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';
\r
1290 q = q.replace(mm[1], "");
\r
1293 fn[fn.length] = "return nodup(n);\n}";
\r
1294 eval(fn.join(""));
\r
1299 * Selects a group of elements.
\r
1300 * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
\r
1301 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1302 * @return {Array} An Array of DOM elements which match the selector. If there are
\r
1303 * no matches, and empty Array is returned.
\r
1305 select : function(path, root, type){
\r
1306 if(!root || root == document){
\r
1309 if(typeof root == "string"){
\r
1310 root = document.getElementById(root);
\r
1312 var paths = path.split(","),
\r
1314 for(var i = 0, len = paths.length; i < len; i++){
\r
1315 var p = paths[i].replace(trimRe, "");
\r
1317 cache[p] = Ext.DomQuery.compile(p);
\r
1319 throw p + " is not a valid selector";
\r
1322 var result = cache[p](root);
\r
1323 if(result && result != document){
\r
1324 results = results.concat(result);
\r
1327 if(paths.length > 1){
\r
1328 return nodup(results);
\r
1334 * Selects a single element.
\r
1335 * @param {String} selector The selector/xpath query
\r
1336 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1337 * @return {Element} The DOM element which matched the selector.
\r
1339 selectNode : function(path, root){
\r
1340 return Ext.DomQuery.select(path, root)[0];
\r
1344 * Selects the value of a node, optionally replacing null with the defaultValue.
\r
1345 * @param {String} selector The selector/xpath query
\r
1346 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1347 * @param {String} defaultValue
\r
1348 * @return {String}
\r
1350 selectValue : function(path, root, defaultValue){
\r
1351 path = path.replace(trimRe, "");
\r
1352 if(!valueCache[path]){
\r
1353 valueCache[path] = Ext.DomQuery.compile(path, "select");
\r
1355 var n = valueCache[path](root),
\r
1357 n = n[0] ? n[0] : n;
\r
1358 v = (n && n.firstChild ? n.firstChild.nodeValue : null);
\r
1359 return ((v === null||v === undefined||v==='') ? defaultValue : v);
\r
1363 * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
\r
1364 * @param {String} selector The selector/xpath query
\r
1365 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1366 * @param {Number} defaultValue
\r
1367 * @return {Number}
\r
1369 selectNumber : function(path, root, defaultValue){
\r
1370 var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
\r
1371 return parseFloat(v);
\r
1375 * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
\r
1376 * @param {String/HTMLElement/Array} el An element id, element or array of elements
\r
1377 * @param {String} selector The simple selector to test
\r
1378 * @return {Boolean}
\r
1380 is : function(el, ss){
\r
1381 if(typeof el == "string"){
\r
1382 el = document.getElementById(el);
\r
1384 var isArray = Ext.isArray(el),
\r
1385 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
\r
1386 return isArray ? (result.length == el.length) : (result.length > 0);
\r
1390 * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
\r
1391 * @param {Array} el An array of elements to filter
\r
1392 * @param {String} selector The simple selector to test
\r
1393 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
\r
1394 * the selector instead of the ones that match
\r
1395 * @return {Array} An Array of DOM elements which match the selector. If there are
\r
1396 * no matches, and empty Array is returned.
\r
1398 filter : function(els, ss, nonMatches){
\r
1399 ss = ss.replace(trimRe, "");
\r
1400 if(!simpleCache[ss]){
\r
1401 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
\r
1403 var result = simpleCache[ss](els);
\r
1404 return nonMatches ? quickDiff(result, els) : result;
\r
1408 * Collection of matching regular expressions and code snippets.
\r
1411 re: /^\.([\w-]+)/,
\r
1412 select: 'n = byClassName(n, null, " {1} ");'
\r
1414 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
\r
1415 select: 'n = byPseudo(n, "{1}", "{2}");'
\r
1417 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
\r
1418 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
\r
1421 select: 'n = byId(n, null, "{1}");'
\r
1424 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
\r
1429 * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
\r
1430 * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, > <.
\r
1433 "=" : function(a, v){
\r
1436 "!=" : function(a, v){
\r
1439 "^=" : function(a, v){
\r
1440 return a && a.substr(0, v.length) == v;
\r
1442 "$=" : function(a, v){
\r
1443 return a && a.substr(a.length-v.length) == v;
\r
1445 "*=" : function(a, v){
\r
1446 return a && a.indexOf(v) !== -1;
\r
1448 "%=" : function(a, v){
\r
1449 return (a % v) == 0;
\r
1451 "|=" : function(a, v){
\r
1452 return a && (a == v || a.substr(0, v.length+1) == v+'-');
\r
1454 "~=" : function(a, v){
\r
1455 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
\r
1460 * Collection of "pseudo class" processors. Each processor is passed the current nodeset (array)
\r
1461 * and the argument (if any) supplied in the selector.
\r
1464 "first-child" : function(c){
\r
1465 var r = [], ri = -1, n;
\r
1466 for(var i = 0, ci; ci = n = c[i]; i++){
\r
1467 while((n = n.previousSibling) && n.nodeType != 1);
\r
1475 "last-child" : function(c){
\r
1476 var r = [], ri = -1, n;
\r
1477 for(var i = 0, ci; ci = n = c[i]; i++){
\r
1478 while((n = n.nextSibling) && n.nodeType != 1);
\r
1486 "nth-child" : function(c, a) {
\r
1487 var r = [], ri = -1,
\r
1488 m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
\r
1489 f = (m[1] || 1) - 0, l = m[2] - 0;
\r
1490 for(var i = 0, n; n = c[i]; i++){
\r
1491 var pn = n.parentNode;
\r
1492 if (batch != pn._batch) {
\r
1494 for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
\r
1495 if(cn.nodeType == 1){
\r
1496 cn.nodeIndex = ++j;
\r
1499 pn._batch = batch;
\r
1502 if (l == 0 || n.nodeIndex == l){
\r
1505 } else if ((n.nodeIndex + l) % f == 0){
\r
1513 "only-child" : function(c){
\r
1514 var r = [], ri = -1;;
\r
1515 for(var i = 0, ci; ci = c[i]; i++){
\r
1516 if(!prev(ci) && !next(ci)){
\r
1523 "empty" : function(c){
\r
1524 var r = [], ri = -1;
\r
1525 for(var i = 0, ci; ci = c[i]; i++){
\r
1526 var cns = ci.childNodes, j = 0, cn, empty = true;
\r
1527 while(cn = cns[j]){
\r
1529 if(cn.nodeType == 1 || cn.nodeType == 3){
\r
1541 "contains" : function(c, v){
\r
1542 var r = [], ri = -1;
\r
1543 for(var i = 0, ci; ci = c[i]; i++){
\r
1544 if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
\r
1551 "nodeValue" : function(c, v){
\r
1552 var r = [], ri = -1;
\r
1553 for(var i = 0, ci; ci = c[i]; i++){
\r
1554 if(ci.firstChild && ci.firstChild.nodeValue == v){
\r
1561 "checked" : function(c){
\r
1562 var r = [], ri = -1;
\r
1563 for(var i = 0, ci; ci = c[i]; i++){
\r
1564 if(ci.checked == true){
\r
1571 "not" : function(c, ss){
\r
1572 return Ext.DomQuery.filter(c, ss, true);
\r
1575 "any" : function(c, selectors){
\r
1576 var ss = selectors.split('|'),
\r
1577 r = [], ri = -1, s;
\r
1578 for(var i = 0, ci; ci = c[i]; i++){
\r
1579 for(var j = 0; s = ss[j]; j++){
\r
1580 if(Ext.DomQuery.is(ci, s)){
\r
1589 "odd" : function(c){
\r
1590 return this["nth-child"](c, "odd");
\r
1593 "even" : function(c){
\r
1594 return this["nth-child"](c, "even");
\r
1597 "nth" : function(c, a){
\r
1598 return c[a-1] || [];
\r
1601 "first" : function(c){
\r
1602 return c[0] || [];
\r
1605 "last" : function(c){
\r
1606 return c[c.length-1] || [];
\r
1609 "has" : function(c, ss){
\r
1610 var s = Ext.DomQuery.select,
\r
1612 for(var i = 0, ci; ci = c[i]; i++){
\r
1613 if(s(ss, ci).length > 0){
\r
1620 "next" : function(c, ss){
\r
1621 var is = Ext.DomQuery.is,
\r
1623 for(var i = 0, ci; ci = c[i]; i++){
\r
1625 if(n && is(n, ss)){
\r
1632 "prev" : function(c, ss){
\r
1633 var is = Ext.DomQuery.is,
\r
1635 for(var i = 0, ci; ci = c[i]; i++){
\r
1637 if(n && is(n, ss)){
\r
1648 * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
\r
1649 * @param {String} path The selector/xpath query
\r
1650 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1655 Ext.query = Ext.DomQuery.select;
\r
1658 var EXTUTIL = Ext.util,
1659 TOARRAY = Ext.toArray,
1661 ISOBJECT = Ext.isObject,
1665 * @class Ext.util.Observable
1666 * Base class that provides a common interface for publishing events. Subclasses are expected to
1667 * to have a property "events" with all the events defined, and, optionally, a property "listeners"
1668 * with configured listeners defined.<br>
1671 Employee = Ext.extend(Ext.util.Observable, {
1672 constructor: function(config){
1673 this.name = config.name;
1679 // Copy configured listeners into *this* object so that the base class's
1680 // constructor will add them.
1681 this.listeners = config.listeners;
1683 // Call our superclass constructor to complete construction process.
1684 Employee.superclass.constructor.call(config)
1688 * This could then be used like this:<pre><code>
1689 var newEmployee = new Employee({
1693 // By default, "this" will be the object that fired the event.
1694 alert(this.name + " has quit!");
1700 EXTUTIL.Observable = function(){
1702 * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
1703 * object during initialization. This should be a valid listeners config object as specified in the
1704 * {@link #addListener} example for attaching multiple handlers at once.</p>
1705 * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
1706 * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
1707 * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
1708 * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
1709 * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
1710 * has been rendered. A plugin can simplify this step:<pre><code>
1711 // Plugin is configured with a listeners config object.
1712 // The Component is appended to the argument list of all handler functions.
1713 Ext.DomObserver = Ext.extend(Object, {
1714 constructor: function(config) {
1715 this.listeners = config.listeners ? config.listeners : config;
1718 // Component passes itself into plugin's init method
1720 var p, l = this.listeners;
1722 if (Ext.isFunction(l[p])) {
1723 l[p] = this.createHandler(l[p], c);
1725 l[p].fn = this.createHandler(l[p].fn, c);
1729 // Add the listeners to the Element immediately following the render call
1730 c.render = c.render.{@link Function#createSequence createSequence}(function() {
1738 createHandler: function(fn, c) {
1739 return function(e) {
1740 fn.call(this, e, c);
1745 var combo = new Ext.form.ComboBox({
1747 // Collapse combo when its element is clicked on
1748 plugins: [ new Ext.DomObserver({
1749 click: function(evt, comp) {
1756 triggerAction: 'all'
1760 var me = this, e = me.events;
1762 me.on(me.listeners);
1763 delete me.listeners;
1765 me.events = e || {};
1768 EXTUTIL.Observable.prototype = function(){
1769 var filterOptRe = /^(?:scope|delay|buffer|single)$/, toLower = function(s){
1770 return s.toLowerCase();
1775 * <p>Fires the specified event with the passed parameters (minus the event name).</p>
1776 * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
1777 * by calling {@link #enableBubble}.</p>
1778 * @param {String} eventName The name of the event to fire.
1779 * @param {Object...} args Variable number of parameters are passed to handlers.
1780 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
1783 fireEvent : function(){
1784 var a = TOARRAY(arguments),
1785 ename = toLower(a[0]),
1788 ce = me.events[ename],
1791 if (me.eventsSuspended === TRUE) {
1792 if (q = me.suspendedEventsQueue) {
1796 else if(ISOBJECT(ce) && ce.bubble){
1797 if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
1800 c = me.getBubbleTarget && me.getBubbleTarget();
1801 if(c && c.enableBubble) {
1802 c.enableBubble(ename);
1803 return c.fireEvent.apply(c, a);
1809 ret = ce.fire.apply(ce, a);
1816 * Appends an event handler to this object.
1817 * @param {String} eventName The name of the event to listen for.
1818 * @param {Function} handler The method the event invokes.
1819 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
1820 * <b>If omitted, defaults to the object which fired the event.</b>
1821 * @param {Object} options (optional) An object containing handler configuration.
1822 * properties. This may contain any of the following properties:<ul>
1823 * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
1824 * <b>If omitted, defaults to the object which fired the event.</b></div></li>
1825 * <li><b>delay</b> : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
1826 * <li><b>single</b> : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
1827 * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
1828 * by the specified number of milliseconds. If the event fires again within that time, the original
1829 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
1830 * <li><b>target</b> : Observable<div class="sub-desc">Only call the handler if the event was fired on the target Observable, <i>not</i>
1831 * if the event was bubbled up from a child Observable.</div></li>
1834 * <b>Combining Options</b><br>
1835 * Using the options argument, it is possible to combine different types of listeners:<br>
1837 * A delayed, one-time listener.
1839 myDataView.on('click', this.onClick, this, {
1844 * <b>Attaching multiple handlers in 1 call</b><br>
1845 * The method also allows for a single argument to be passed which is a config object containing properties
1846 * which specify multiple handlers.
1856 fn: this.onMouseOver,
1860 fn: this.onMouseOut,
1865 * Or a shorthand syntax:<br>
1868 'click' : this.onClick,
1869 'mouseover' : this.onMouseOver,
1870 'mouseout' : this.onMouseOut,
1874 addListener : function(eventName, fn, scope, o){
1880 if (ISOBJECT(eventName)) {
1884 if (!filterOptRe.test(e)) {
1885 me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
1889 eventName = toLower(eventName);
1890 ce = me.events[eventName] || TRUE;
1891 if (typeof ce == "boolean") {
1892 me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
1894 ce.addListener(fn, scope, ISOBJECT(o) ? o : {});
1899 * Removes an event handler.
1900 * @param {String} eventName The type of event the handler was associated with.
1901 * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
1902 * @param {Object} scope (optional) The scope originally specified for the handler.
1904 removeListener : function(eventName, fn, scope){
1905 var ce = this.events[toLower(eventName)];
1907 ce.removeListener(fn, scope);
1912 * Removes all listeners for this object
1914 purgeListeners : function(){
1915 var events = this.events,
1921 evt.clearListeners();
1927 * Used to define events on this Observable
1928 * @param {Object} object The object with the events defined
1930 addEvents : function(o){
1932 me.events = me.events || {};
1933 if (typeof o == 'string') {
1934 EACH(arguments, function(a) {
1935 me.events[a] = me.events[a] || TRUE;
1938 Ext.applyIf(me.events, o);
1943 * Checks to see if this object has any listeners for a specified event
1944 * @param {String} eventName The name of the event to check for
1945 * @return {Boolean} True if the event is being listened for, else false
1947 hasListener : function(eventName){
1948 var e = this.events[eventName];
1949 return ISOBJECT(e) && e.listeners.length > 0;
1953 * Suspend the firing of all events. (see {@link #resumeEvents})
1954 * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
1955 * after the {@link #resumeEvents} call instead of discarding all suspended events;
1957 suspendEvents : function(queueSuspended){
1958 this.eventsSuspended = TRUE;
1959 if (queueSuspended){
1960 this.suspendedEventsQueue = [];
1965 * Resume firing events. (see {@link #suspendEvents})
1966 * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
1967 * events fired during event suspension will be sent to any listeners now.
1969 resumeEvents : function(){
1971 me.eventsSuspended = !delete me.suspendedEventQueue;
1972 EACH(me.suspendedEventsQueue, function(e) {
1973 me.fireEvent.apply(me, e);
1979 var OBSERVABLE = EXTUTIL.Observable.prototype;
1981 * Appends an event handler to this object (shorthand for {@link #addListener}.)
1982 * @param {String} eventName The type of event to listen for
1983 * @param {Function} handler The method the event invokes
1984 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
1985 * <b>If omitted, defaults to the object which fired the event.</b>
1986 * @param {Object} options (optional) An object containing handler configuration.
1989 OBSERVABLE.on = OBSERVABLE.addListener;
1991 * Removes an event handler (shorthand for {@link #removeListener}.)
1992 * @param {String} eventName The type of event the handler was associated with.
1993 * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
1994 * @param {Object} scope (optional) The scope originally specified for the handler.
1997 OBSERVABLE.un = OBSERVABLE.removeListener;
2000 * Removes <b>all</b> added captures from the Observable.
2001 * @param {Observable} o The Observable to release
2004 EXTUTIL.Observable.releaseCapture = function(o){
2005 o.fireEvent = OBSERVABLE.fireEvent;
2008 function createTargeted(h, o, scope){
2010 if(o.target == arguments[0]){
2011 h.apply(scope, TOARRAY(arguments));
2016 function createBuffered(h, o, scope){
2017 var task = new EXTUTIL.DelayedTask();
2019 task.delay(o.buffer, h, scope, TOARRAY(arguments));
2023 function createSingle(h, e, fn, scope){
2025 e.removeListener(fn, scope);
2026 return h.apply(scope, arguments);
2030 function createDelayed(h, o, scope){
2032 var args = TOARRAY(arguments);
2034 h.apply(scope, args);
2035 }).defer(o.delay || 10);
2039 EXTUTIL.Event = function(obj, name){
2042 this.listeners = [];
2045 EXTUTIL.Event.prototype = {
2046 addListener : function(fn, scope, options){
2049 scope = scope || me.obj;
2050 if(!me.isListening(fn, scope)){
2051 l = me.createListener(fn, scope, options);
2052 if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
2053 me.listeners = me.listeners.slice(0);
2055 me.listeners.push(l);
2059 createListener: function(fn, scope, o){
2060 o = o || {}, scope = scope || this.obj;
2067 h = createTargeted(h, o, scope);
2070 h = createDelayed(h, o, scope);
2073 h = createSingle(h, this, fn, scope);
2076 h = createBuffered(h, o, scope);
2082 findListener : function(fn, scope){
2084 EACH(this.listeners, function(l, i) {
2086 if(l.fn == fn && (s == scope || s == this.obj)){
2095 isListening : function(fn, scope){
2096 return this.findListener(fn, scope) != -1;
2099 removeListener : function(fn, scope){
2103 if((index = me.findListener(fn, scope)) != -1){
2105 me.listeners = me.listeners.slice(0);
2107 me.listeners.splice(index, 1);
2113 clearListeners : function(){
2114 this.listeners = [];
2119 args = TOARRAY(arguments),
2122 EACH(me.listeners, function(l) {
2124 if (l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
2125 return ret = me.firing = FALSE;
2133 * @class Ext.util.Observable
\r
2135 Ext.apply(Ext.util.Observable.prototype, function(){
\r
2136 // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
\r
2137 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
\r
2139 function getMethodEvent(method){
\r
2140 var e = (this.methodEvents = this.methodEvents ||
\r
2141 {})[method], returnValue, v, cancel, obj = this;
\r
2144 this.methodEvents[method] = e = {};
\r
2145 e.originalFn = this[method];
\r
2146 e.methodName = method;
\r
2150 var makeCall = function(fn, scope, args){
\r
2151 if (!Ext.isEmpty(v = fn.apply(scope || obj, args))) {
\r
2152 if (Ext.isObject(v)) {
\r
2153 returnValue = !Ext.isEmpty(v.returnValue) ? v.returnValue : v;
\r
2154 cancel = !!v.cancel;
\r
2157 if (v === false) {
\r
2166 this[method] = function(){
\r
2167 var args = Ext.toArray(arguments);
\r
2168 returnValue = v = undefined;
\r
2171 Ext.each(e.before, function(b){
\r
2172 makeCall(b.fn, b.scope, args);
\r
2174 return returnValue;
\r
2178 if (!Ext.isEmpty(v = e.originalFn.apply(obj, args))) {
\r
2181 Ext.each(e.after, function(a){
\r
2182 makeCall(a.fn, a.scope, args);
\r
2184 return returnValue;
\r
2187 return returnValue;
\r
2194 // these are considered experimental
\r
2195 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
\r
2196 // adds an "interceptor" called before the original method
\r
2197 beforeMethod: function(method, fn, scope){
\r
2198 getMethodEvent.call(this, method).before.push({
\r
2204 // adds a "sequence" called after the original method
\r
2205 afterMethod: function(method, fn, scope){
\r
2206 getMethodEvent.call(this, method).after.push({
\r
2212 removeMethodListener: function(method, fn, scope){
\r
2213 var e = getMethodEvent.call(this, method), found = false;
\r
2214 Ext.each(e.before, function(b, i, arr){
\r
2215 if (b.fn == fn && b.scope == scope) {
\r
2222 Ext.each(e.after, function(a, i, arr){
\r
2223 if (a.fn == fn && a.scope == scope) {
\r
2232 * Relays selected events from the specified Observable as if the events were fired by <tt><b>this</b></tt>.
\r
2233 * @param {Object} o The Observable whose events this object is to relay.
\r
2234 * @param {Array} events Array of event names to relay.
\r
2236 relayEvents: function(o, events){
\r
2238 function createHandler(ename){
\r
2239 return function(){
\r
2240 return me.fireEvent.apply(me, [ename].concat(Ext.toArray(arguments)));
\r
2243 Ext.each(events, function(ename){
\r
2244 me.events[ename] = me.events[ename] || true;
\r
2245 o.on(ename, createHandler(ename), me);
\r
2250 * Used to enable bubbling of events
\r
2251 * @param {Object} events
\r
2253 enableBubble: function(events){
\r
2255 events = Ext.isArray(events) ? events : Ext.toArray(arguments);
\r
2256 Ext.each(events, function(ename){
\r
2257 ename = ename.toLowerCase();
\r
2258 var ce = me.events[ename] || true;
\r
2259 if (typeof ce == "boolean") {
\r
2260 ce = new Ext.util.Event(me, ename);
\r
2261 me.events[ename] = ce;
\r
2271 * Starts capture on the specified Observable. All events will be passed
\r
2272 * to the supplied function with the event name + standard signature of the event
\r
2273 * <b>before</b> the event is fired. If the supplied function returns false,
\r
2274 * the event will not fire.
\r
2275 * @param {Observable} o The Observable to capture
\r
2276 * @param {Function} fn The function to call
\r
2277 * @param {Object} scope (optional) The scope (this object) for the fn
\r
2280 Ext.util.Observable.capture = function(o, fn, scope){
\r
2281 o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
\r
2286 * Sets observability on the passed class constructor.<p>
\r
2287 * <p>This makes any event fired on any instance of the passed class also fire a single event through
\r
2288 * the <i>class</i> allowing for central handling of events on many instances at once.</p>
\r
2289 * <p>Usage:</p><pre><code>
\r
2290 Ext.util.Observable.observeClass(Ext.data.Connection);
\r
2291 Ext.data.Connection.on('beforerequest', function(con, options) {
\r
2292 console.log("Ajax request made to " + options.url);
\r
2294 * @param {Function} c The class constructor to make observable.
\r
2297 Ext.util.Observable.observeClass = function(c){
\r
2298 Ext.apply(c, new Ext.util.Observable());
\r
2299 c.prototype.fireEvent = function(){
\r
2300 return (c.fireEvent.apply(c, arguments) !== false) &&
\r
2301 (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false);
\r
2304 * @class Ext.EventManager
2305 * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
2306 * several useful events directly.
2307 * See {@link Ext.EventObject} for more details on normalized event objects.
2310 Ext.EventManager = function(){
2313 docReadyState = false,
2318 IEDEFERED = "ie-deferred-loader",
2319 DOMCONTENTLOADED = "DOMContentLoaded",
2321 propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
2323 /// There is some jquery work around stuff here that isn't needed in Ext Core.
2324 function addListener(el, ename, fn, wrap, scope){
2325 var id = Ext.id(el),
2326 es = elHash[id] = elHash[id] || {};
2328 (es[ename] = es[ename] || []).push([fn, wrap, scope]);
2329 E.on(el, ename, wrap);
2331 // this is a workaround for jQuery and should somehow be removed from Ext Core in the future
2332 // without breaking ExtJS.
2333 if(ename == "mousewheel" && el.addEventListener){ // workaround for jQuery
2334 var args = ["DOMMouseScroll", wrap, false];
2335 el.addEventListener.apply(el, args);
2336 E.on(window, 'unload', function(){
2337 el.removeEventListener.apply(el, args);
2340 if(ename == "mousedown" && el == document){ // fix stopped mousedowns on the document
2341 Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
2345 function fireDocReady(){
2347 Ext.isReady = docReadyState = true;
2349 clearInterval(docReadyProcId);
2351 if(Ext.isGecko || Ext.isOpera) {
2352 DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
2355 var defer = DOC.getElementById(IEDEFERED);
2357 defer.onreadystatechange = null;
2358 defer.parentNode.removeChild(defer);
2362 docReadyEvent.fire();
2363 docReadyEvent.clearListeners();
2368 function initDocReady(){
2369 var COMPLETE = "complete";
2371 docReadyEvent = new Ext.util.Event();
2372 if (Ext.isGecko || Ext.isOpera) {
2373 DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
2374 } else if (Ext.isIE){
2375 DOC.write("<s"+'cript id=' + IEDEFERED + ' defer="defer" src="/'+'/:"></s'+"cript>");
2376 DOC.getElementById(IEDEFERED).onreadystatechange = function(){
2377 if(this.readyState == COMPLETE){
2381 } else if (Ext.isWebKit){
2382 docReadyProcId = setInterval(function(){
2383 if(DOC.readyState == COMPLETE) {
2388 // no matter what, make sure it fires on load
2389 E.on(WINDOW, "load", fireDocReady);
2392 function createTargeted(h, o){
2394 var args = Ext.toArray(arguments);
2395 if(o.target == Ext.EventObject.setEvent(args[0]).target){
2396 h.apply(this, args);
2401 function createBuffered(h, o){
2402 var task = new Ext.util.DelayedTask(h);
2404 // create new event object impl so new events don't wipe out properties
2405 task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
2409 function createSingle(h, el, ename, fn, scope){
2411 Ext.EventManager.removeListener(el, ename, fn, scope);
2416 function createDelayed(h, o){
2418 // create new event object impl so new events don't wipe out properties
2419 e = new Ext.EventObjectImpl(e);
2420 setTimeout(function(){
2426 function listen(element, ename, opt, fn, scope){
2427 var o = !Ext.isObject(opt) ? {} : opt,
2428 el = Ext.getDom(element);
2431 scope = scope || o.scope;
2434 throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
2437 // prevent errors while unload occurring
2438 if(!Ext){// !window[xname]){ ==> can't we do this?
2441 e = Ext.EventObject.setEvent(e);
2444 if(!(t = e.getTarget(o.delegate, el))){
2453 if (o.preventDefault) {
2456 if (o.stopPropagation) {
2457 e.stopPropagation();
2463 fn.call(scope || el, e, t, o);
2466 h = createTargeted(h, o);
2469 h = createDelayed(h, o);
2472 h = createSingle(h, el, ename, fn, scope);
2475 h = createBuffered(h, o);
2478 addListener(el, ename, fn, h, scope);
2484 * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
2485 * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
2486 * @param {String/HTMLElement} el The html element or id to assign the event handler to
2487 * @param {String} eventName The type of event to listen for
2488 * @param {Function} handler The handler function the event invokes This function is passed
2489 * the following parameters:<ul>
2490 * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
2491 * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
2492 * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
2493 * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
2495 * @param {Object} scope (optional) The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.
2496 * @param {Object} options (optional) An object containing handler configuration properties.
2497 * This may contain any of the following properties:<ul>
2498 * <li>scope : Object<div class="sub-desc">The scope (<b><code>this</code></b> reference) in which the handler function is executed. <b>Defaults to the Element</b>.</div></li>
2499 * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
2500 * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
2501 * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
2502 * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
2503 * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
2504 * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
2505 * <li>single : Boolean<div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
2506 * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
2507 * by the specified number of milliseconds. If the event fires again within that time, the original
2508 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
2509 * <li>target : Element<div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
2511 * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
2513 addListener : function(element, eventName, fn, scope, options){
2514 if(Ext.isObject(eventName)){
2515 var o = eventName, e, val;
2518 if(!propRe.test(e)){
2519 if(Ext.isFunction(val)){
2521 listen(element, e, o, val, o.scope);
2523 // individual options
2524 listen(element, e, val);
2529 listen(element, eventName, options, fn, scope);
2534 * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
2535 * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
2536 * @param {String/HTMLElement} el The id or html element from which to remove the event
2537 * @param {String} eventName The type of event
2538 * @param {Function} fn The handler function to remove
2540 removeListener : function(element, eventName, fn, scope){
2541 var el = Ext.getDom(element),
2545 Ext.each((elHash[id] || {})[eventName], function (v,i,a) {
2546 if (Ext.isArray(v) && v[0] == fn && (!scope || v[2] == scope)) {
2547 E.un(el, eventName, wrap = v[1]);
2553 // jQuery workaround that should be removed from Ext Core
2554 if(eventName == "mousewheel" && el.addEventListener && wrap){
2555 el.removeEventListener("DOMMouseScroll", wrap, false);
2558 if(eventName == "mousedown" && el == DOC && wrap){ // fix stopped mousedowns on the document
2559 Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
2564 * Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
2565 * directly on an Element in favor of calling this version.
2566 * @param {String/HTMLElement} el The id or html element from which to remove the event
2568 removeAll : function(el){
2569 var id = Ext.id(el = Ext.getDom(el)),
2574 if(es.hasOwnProperty(ename)){
2575 Ext.each(es[ename], function(v) {
2576 E.un(el, ename, v.wrap);
2584 * Fires when the document is ready (before onload and before images are loaded). Can be
2585 * accessed shorthanded as Ext.onReady().
2586 * @param {Function} fn The method the event invokes
2587 * @param {Object} scope (optional) An object that becomes the scope of the handler
2588 * @param {boolean} options (optional) An object containing standard {@link #addListener} options
2590 onDocumentReady : function(fn, scope, options){
2591 if(docReadyState){ // if it already fired
2592 docReadyEvent.addListener(fn, scope, options);
2593 docReadyEvent.fire();
2594 docReadyEvent.clearListeners();
2596 if(!docReadyEvent) initDocReady();
2597 options = options || {};
2598 options.delay = options.delay || 1;
2599 docReadyEvent.addListener(fn, scope, options);
2606 * Appends an event handler to an element. Shorthand for {@link #addListener}.
2607 * @param {String/HTMLElement} el The html element or id to assign the event handler to
2608 * @param {String} eventName The type of event to listen for
2609 * @param {Function} handler The handler function the event invokes
2610 * @param {Object} scope (optional) The scope in which to execute the handler
2611 * function (the handler function's "this" context)
2612 * @param {Object} options (optional) An object containing standard {@link #addListener} options
2613 * @member Ext.EventManager
2616 pub.on = pub.addListener;
2618 * Removes an event handler from an element. Shorthand for {@link #removeListener}.
2619 * @param {String/HTMLElement} el The id or html element from which to remove the event
2620 * @param {String} eventName The type of event
2621 * @param {Function} fn The handler function to remove
2622 * @return {Boolean} True if a listener was actually removed, else false
2623 * @member Ext.EventManager
2626 pub.un = pub.removeListener;
2628 pub.stoppedMouseDownEvent = new Ext.util.Event();
2632 * Fires when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
2633 * @param {Function} fn The method the event invokes
2634 * @param {Object} scope An object that becomes the scope of the handler
2635 * @param {boolean} options (optional) An object containing standard {@link #addListener} options
2639 Ext.onReady = Ext.EventManager.onDocumentReady;
2642 //Initialize doc classes
2645 var initExtCss = function(){
2646 // find the body element
2647 var bd = document.body || document.getElementsByTagName('body')[0];
2648 if(!bd){ return false; }
2650 Ext.isIE ? "ext-ie " + (Ext.isIE6 ? 'ext-ie6' : (Ext.isIE7 ? 'ext-ie7' : 'ext-ie8'))
2651 : Ext.isGecko ? "ext-gecko " + (Ext.isGecko2 ? 'ext-gecko2' : 'ext-gecko3')
2652 : Ext.isOpera ? "ext-opera"
2653 : Ext.isWebKit ? "ext-webkit" : ""];
2656 cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
2657 }else if(Ext.isChrome){
2658 cls.push("ext-chrome");
2662 cls.push("ext-mac");
2665 cls.push("ext-linux");
2668 if(Ext.isStrict || Ext.isBorderBox){ // add to the parent to allow for selectors like ".ext-strict .ext-ie"
2669 var p = bd.parentNode;
2671 p.className += Ext.isStrict ? ' ext-strict' : ' ext-border-box';
2674 bd.className += cls.join(' ');
2679 Ext.onReady(initExtCss);
2685 * @class Ext.EventObject
2686 * Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
2687 * wraps the browser's native event-object normalizing cross-browser differences,
2688 * such as which mouse button is clicked, keys pressed, mechanisms to stop
2689 * event-propagation along with a method to prevent default actions from taking place.
2690 * <p>For example:</p>
2692 function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
2694 var target = e.getTarget(); // same as t (the target HTMLElement)
2697 var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
2698 myDiv.on( // 'on' is shorthand for addListener
2699 "click", // perform an action on click of myDiv
2700 handleClick // reference to the action handler
2702 // other methods to do the same:
2703 Ext.EventManager.on("myDiv", 'click', handleClick);
2704 Ext.EventManager.addListener("myDiv", 'click', handleClick);
2708 Ext.EventObject = function(){
2709 var E = Ext.lib.Event,
2710 // safari keypress events for special keys return bad keycodes
2714 63235 : 39, // right
2717 63276 : 33, // page up
2718 63277 : 34, // page down
2719 63272 : 46, // delete
2723 // normalize button clicks
2724 btnMap = Ext.isIE ? {1:0,4:1,2:2} :
2725 (Ext.isWebKit ? {1:0,2:1,3:2} : {0:0,1:1,2:2});
2727 Ext.EventObjectImpl = function(e){
2729 this.setEvent(e.browserEvent || e);
2733 Ext.EventObjectImpl.prototype = {
2735 setEvent : function(e){
2737 if(e == me || (e && e.browserEvent)){ // already wrapped
2740 me.browserEvent = e;
2742 // normalize buttons
2743 me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
2744 if(e.type == 'click' && me.button == -1){
2748 me.shiftKey = e.shiftKey;
2749 // mac metaKey behaves like ctrlKey
2750 me.ctrlKey = e.ctrlKey || e.metaKey || false;
2751 me.altKey = e.altKey;
2752 // in getKey these will be normalized for the mac
2753 me.keyCode = e.keyCode;
2754 me.charCode = e.charCode;
2755 // cache the target for the delayed and or buffered events
2756 me.target = E.getTarget(e);
2761 me.shiftKey = false;
2773 * Stop the event (preventDefault and stopPropagation)
2775 stopEvent : function(){
2777 if(me.browserEvent){
2778 if(me.browserEvent.type == 'mousedown'){
2779 Ext.EventManager.stoppedMouseDownEvent.fire(me);
2781 E.stopEvent(me.browserEvent);
2786 * Prevents the browsers default handling of the event.
2788 preventDefault : function(){
2789 if(this.browserEvent){
2790 E.preventDefault(this.browserEvent);
2795 * Cancels bubbling of the event.
2797 stopPropagation : function(){
2799 if(me.browserEvent){
2800 if(me.browserEvent.type == 'mousedown'){
2801 Ext.EventManager.stoppedMouseDownEvent.fire(me);
2803 E.stopPropagation(me.browserEvent);
2808 * Gets the character code for the event.
2811 getCharCode : function(){
2812 return this.charCode || this.keyCode;
2816 * Returns a normalized keyCode for the event.
2817 * @return {Number} The key code
2819 getKey : function(){
2820 return this.normalizeKey(this.keyCode || this.charCode)
2824 normalizeKey: function(k){
2825 return Ext.isSafari ? (safariKeys[k] || k) : k;
2829 * Gets the x coordinate of the event.
2832 getPageX : function(){
2837 * Gets the y coordinate of the event.
2840 getPageY : function(){
2845 * Gets the page coordinates of the event.
2846 * @return {Array} The xy values like [x, y]
2853 * Gets the target for the event.
2854 * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
2855 * @param {Number/Mixed} maxDepth (optional) The max depth to
2856 search as a number or element (defaults to 10 || document.body)
2857 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
2858 * @return {HTMLelement}
2860 getTarget : function(selector, maxDepth, returnEl){
2861 return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
2865 * Gets the related target.
2866 * @return {HTMLElement}
2868 getRelatedTarget : function(){
2869 return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
2873 * Normalizes mouse wheel delta across browsers
2874 * @return {Number} The delta
2876 getWheelDelta : function(){
2877 var e = this.browserEvent;
2879 if(e.wheelDelta){ /* IE/Opera. */
2880 delta = e.wheelDelta/120;
2881 }else if(e.detail){ /* Mozilla case. */
2882 delta = -e.detail/3;
2888 * 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.
2889 * Example usage:<pre><code>
2890 // Handle click on any child of an element
2891 Ext.getBody().on('click', function(e){
2892 if(e.within('some-el')){
2893 alert('Clicked on a child of some-el!');
2897 // Handle click directly on an element, ignoring clicks on child nodes
2898 Ext.getBody().on('click', function(e,t){
2899 if((t.id == 'some-el') && !e.within(t, true)){
2900 alert('Clicked directly on some-el!');
2904 * @param {Mixed} el The id, DOM element or Ext.Element to check
2905 * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
2906 * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
2909 within : function(el, related, allowEl){
2911 var t = this[related ? "getRelatedTarget" : "getTarget"]();
2912 return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
2918 return new Ext.EventObjectImpl();
2920 * @class Ext.EventManager
\r
2922 Ext.apply(Ext.EventManager, function(){
\r
2928 E = Ext.lib.Event,
\r
2929 propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
\r
2932 // note 1: IE fires ONLY the keydown event on specialkey autorepeat
\r
2933 // note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
\r
2934 // (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
\r
2935 useKeydown = Ext.isSafari ?
\r
2936 Ext.num(navigator.userAgent.toLowerCase().match(/version\/(\d+\.\d)/)[1] || 2) >= 3.1 :
\r
2937 !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera);
\r
2941 doResizeEvent: function(){
\r
2942 var h = D.getViewHeight(),
\r
2943 w = D.getViewWidth();
\r
2945 //whacky problem in IE where the resize event will fire even though the w/h are the same.
\r
2946 if(curHeight != h || curWidth != w){
\r
2947 resizeEvent.fire(curWidth = w, curHeight = h);
\r
2952 * Fires when the window is resized and provides resize event buffering (50 milliseconds), passes new viewport width and height to handlers.
\r
2953 * @param {Function} fn The method the event invokes
\r
2954 * @param {Object} scope An object that becomes the scope of the handler
\r
2955 * @param {boolean} options
\r
2957 onWindowResize : function(fn, scope, options){
\r
2959 resizeEvent = new Ext.util.Event();
\r
2960 resizeTask = new Ext.util.DelayedTask(this.doResizeEvent);
\r
2961 E.on(window, "resize", this.fireWindowResize, this);
\r
2963 resizeEvent.addListener(fn, scope, options);
\r
2966 // exposed only to allow manual firing
\r
2967 fireWindowResize : function(){
\r
2969 if((Ext.isIE||Ext.isAir) && resizeTask){
\r
2970 resizeTask.delay(50);
\r
2972 resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
\r
2978 * Fires when the user changes the active text size. Handler gets called with 2 params, the old size and the new size.
\r
2979 * @param {Function} fn The method the event invokes
\r
2980 * @param {Object} scope An object that becomes the scope of the handler
\r
2981 * @param {boolean} options
\r
2983 onTextResize : function(fn, scope, options){
\r
2985 textEvent = new Ext.util.Event();
\r
2986 var textEl = new Ext.Element(document.createElement('div'));
\r
2987 textEl.dom.className = 'x-text-resize';
\r
2988 textEl.dom.innerHTML = 'X';
\r
2989 textEl.appendTo(document.body);
\r
2990 textSize = textEl.dom.offsetHeight;
\r
2991 setInterval(function(){
\r
2992 if(textEl.dom.offsetHeight != textSize){
\r
2993 textEvent.fire(textSize, textSize = textEl.dom.offsetHeight);
\r
2995 }, this.textResizeInterval);
\r
2997 textEvent.addListener(fn, scope, options);
\r
3001 * Removes the passed window resize listener.
\r
3002 * @param {Function} fn The method the event invokes
\r
3003 * @param {Object} scope The scope of handler
\r
3005 removeResizeListener : function(fn, scope){
\r
3007 resizeEvent.removeListener(fn, scope);
\r
3012 fireResize : function(){
\r
3014 resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
\r
3019 * The frequency, in milliseconds, to check for text resize events (defaults to 50)
\r
3021 textResizeInterval : 50,
\r
3024 * Url used for onDocumentReady with using SSL (defaults to Ext.SSL_SECURE_URL)
\r
3026 ieDeferSrc : false,
\r
3028 // protected for use inside the framework
\r
3029 // detects whether we should use keydown or keypress based on the browser.
\r
3030 useKeydown: useKeydown
\r
3034 Ext.EventManager.on = Ext.EventManager.addListener;
\r
3037 Ext.apply(Ext.EventObjectImpl.prototype, {
\r
3038 /** Key constant @type Number */
\r
3040 /** Key constant @type Number */
\r
3042 /** Key constant @type Number */
\r
3044 /** Key constant @type Number */
\r
3046 /** Key constant @type Number */
\r
3048 /** Key constant @type Number */
\r
3050 /** Key constant @type Number */
\r
3052 CONTROL : 17, // legacy
\r
3053 /** Key constant @type Number */
\r
3055 /** Key constant @type Number */
\r
3057 /** Key constant @type Number */
\r
3059 /** Key constant @type Number */
\r
3061 /** Key constant @type Number */
\r
3063 /** Key constant @type Number */
\r
3065 PAGEUP : 33, // legacy
\r
3066 /** Key constant @type Number */
\r
3068 PAGEDOWN : 34, // legacy
\r
3069 /** Key constant @type Number */
\r
3071 /** Key constant @type Number */
\r
3073 /** Key constant @type Number */
\r
3075 /** Key constant @type Number */
\r
3077 /** Key constant @type Number */
\r
3079 /** Key constant @type Number */
\r
3081 /** Key constant @type Number */
\r
3083 /** Key constant @type Number */
\r
3085 /** Key constant @type Number */
\r
3087 /** Key constant @type Number */
\r
3089 /** Key constant @type Number */
\r
3091 /** Key constant @type Number */
\r
3093 /** Key constant @type Number */
\r
3095 /** Key constant @type Number */
\r
3097 /** Key constant @type Number */
\r
3099 /** Key constant @type Number */
\r
3101 /** Key constant @type Number */
\r
3103 /** Key constant @type Number */
\r
3105 /** Key constant @type Number */
\r
3107 /** Key constant @type Number */
\r
3109 /** Key constant @type Number */
\r
3111 /** Key constant @type Number */
\r
3113 /** Key constant @type Number */
\r
3115 /** Key constant @type Number */
\r
3117 /** Key constant @type Number */
\r
3119 /** Key constant @type Number */
\r
3121 /** Key constant @type Number */
\r
3123 /** Key constant @type Number */
\r
3125 /** Key constant @type Number */
\r
3127 /** Key constant @type Number */
\r
3129 /** Key constant @type Number */
\r
3131 /** Key constant @type Number */
\r
3133 /** Key constant @type Number */
\r
3135 /** Key constant @type Number */
\r
3137 /** Key constant @type Number */
\r
3139 /** Key constant @type Number */
\r
3141 /** Key constant @type Number */
\r
3143 /** Key constant @type Number */
\r
3145 /** Key constant @type Number */
\r
3147 /** Key constant @type Number */
\r
3149 /** Key constant @type Number */
\r
3151 /** Key constant @type Number */
\r
3153 /** Key constant @type Number */
\r
3155 /** Key constant @type Number */
\r
3157 /** Key constant @type Number */
\r
3159 /** Key constant @type Number */
\r
3161 /** Key constant @type Number */
\r
3163 /** Key constant @type Number */
\r
3165 /** Key constant @type Number */
\r
3167 /** Key constant @type Number */
\r
3169 /** Key constant @type Number */
\r
3171 /** Key constant @type Number */
\r
3173 /** Key constant @type Number */
\r
3175 /** Key constant @type Number */
\r
3177 /** Key constant @type Number */
\r
3179 /** Key constant @type Number */
\r
3181 /** Key constant @type Number */
\r
3182 NUM_MULTIPLY: 106,
\r
3183 /** Key constant @type Number */
\r
3185 /** Key constant @type Number */
\r
3187 /** Key constant @type Number */
\r
3189 /** Key constant @type Number */
\r
3190 NUM_DIVISION: 111,
\r
3191 /** Key constant @type Number */
\r
3193 /** Key constant @type Number */
\r
3195 /** Key constant @type Number */
\r
3197 /** Key constant @type Number */
\r
3199 /** Key constant @type Number */
\r
3201 /** Key constant @type Number */
\r
3203 /** Key constant @type Number */
\r
3205 /** Key constant @type Number */
\r
3207 /** Key constant @type Number */
\r
3209 /** Key constant @type Number */
\r
3211 /** Key constant @type Number */
\r
3213 /** Key constant @type Number */
\r
3217 isNavKeyPress : function(){
\r
3219 k = this.normalizeKey(me.keyCode);
\r
3220 return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
\r
3226 isSpecialKey : function(){
\r
3227 var k = this.normalizeKey(this.keyCode);
\r
3228 return (this.type == 'keypress' && this.ctrlKey) ||
\r
3229 this.isNavKeyPress() ||
\r
3230 (k == this.BACKSPACE) || // Backspace
\r
3231 (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
\r
3232 (k >= 44 && k <= 45); // Print Screen, Insert
\r
3235 getPoint : function(){
\r
3236 return new Ext.lib.Point(this.xy[0], this.xy[1]);
\r
3240 * Returns true if the control, meta, shift or alt key was pressed during this event.
\r
3241 * @return {Boolean}
\r
3243 hasModifier : function(){
\r
3244 return ((this.ctrlKey || this.altKey) || this.shiftKey);
\r
3247 * @class Ext.Element
\r
3248 * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
\r
3249 * <p>All instances of this class inherit the methods of {@link Ext.Fx} making visual effects easily available to all DOM elements.</p>
\r
3250 * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
\r
3251 * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
\r
3252 * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
\r
3256 var el = Ext.get("my-div");
\r
3258 // by DOM element reference
\r
3259 var el = Ext.get(myDivElement);
\r
3261 * <b>Animations</b><br />
\r
3262 * <p>When an element is manipulated, by default there is no animation.</p>
\r
3264 var el = Ext.get("my-div");
\r
3269 * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
\r
3270 * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
\r
3272 // default animation
\r
3273 el.setWidth(100, true);
\r
3276 * <p>To configure the effects, an object literal with animation options to use as the Element animation
\r
3277 * configuration object can also be specified. Note that the supported Element animation configuration
\r
3278 * options are a subset of the {@link Ext.Fx} animation options specific to Fx effects. The supported
\r
3279 * Element animation configuration options are:</p>
\r
3281 Option Default Description
\r
3282 --------- -------- ---------------------------------------------
\r
3283 {@link Ext.Fx#duration duration} .35 The duration of the animation in seconds
\r
3284 {@link Ext.Fx#easing easing} easeOut The easing method
\r
3285 {@link Ext.Fx#callback callback} none A function to execute when the anim completes
\r
3286 {@link Ext.Fx#scope scope} this The scope (this) of the callback function
\r
3290 // Element animation options object
\r
3292 {@link Ext.Fx#duration duration}: 1,
\r
3293 {@link Ext.Fx#easing easing}: 'elasticIn',
\r
3294 {@link Ext.Fx#callback callback}: this.foo,
\r
3295 {@link Ext.Fx#scope scope}: this
\r
3297 // animation with some options set
\r
3298 el.setWidth(100, opt);
\r
3300 * <p>The Element animation object being used for the animation will be set on the options
\r
3301 * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
\r
3303 // using the "anim" property to get the Anim object
\r
3304 if(opt.anim.isAnimated()){
\r
3308 * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
\r
3309 * <p><b> Composite (Collections of) Elements</b></p>
\r
3310 * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
\r
3311 * @constructor Create a new Element directly.
\r
3312 * @param {String/HTMLElement} element
\r
3313 * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
\r
3316 var DOC = document;
\r
3318 Ext.Element = function(element, forceNew){
\r
3319 var dom = typeof element == "string" ?
\r
3320 DOC.getElementById(element) : element,
\r
3323 if(!dom) return null;
\r
3327 if(!forceNew && id && Ext.Element.cache[id]){ // element object already exists
\r
3328 return Ext.Element.cache[id];
\r
3333 * @type HTMLElement
\r
3338 * The DOM element ID
\r
3341 this.id = id || Ext.id(dom);
\r
3344 var D = Ext.lib.Dom,
\r
3345 DH = Ext.DomHelper,
\r
3346 E = Ext.lib.Event,
\r
3352 * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
\r
3353 * @param {Object} o The object with the attributes
\r
3354 * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
\r
3355 * @return {Ext.Element} this
\r
3357 set : function(o, useSet){
\r
3358 var el = this.dom,
\r
3364 if (attr != "style" && !Ext.isFunction(val)) {
\r
3365 if (attr == "cls" ) {
\r
3366 el.className = val;
\r
3367 } else if (o.hasOwnProperty(attr)) {
\r
3368 if (useSet || !!el.setAttribute) el.setAttribute(attr, val);
\r
3369 else el[attr] = val;
\r
3374 Ext.DomHelper.applyStyles(el, o.style);
\r
3382 * Fires when a mouse click is detected within the element.
\r
3383 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3384 * @param {HtmlElement} t The target of the event.
\r
3385 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3389 * Fires when a mouse double click is detected within the element.
\r
3390 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3391 * @param {HtmlElement} t The target of the event.
\r
3392 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3395 * @event mousedown
\r
3396 * Fires when a mousedown is detected within the element.
\r
3397 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3398 * @param {HtmlElement} t The target of the event.
\r
3399 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3403 * Fires when a mouseup is detected within the element.
\r
3404 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3405 * @param {HtmlElement} t The target of the event.
\r
3406 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3409 * @event mouseover
\r
3410 * Fires when a mouseover is detected within the element.
\r
3411 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3412 * @param {HtmlElement} t The target of the event.
\r
3413 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3416 * @event mousemove
\r
3417 * Fires when a mousemove is detected with the element.
\r
3418 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3419 * @param {HtmlElement} t The target of the event.
\r
3420 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3424 * Fires when a mouseout is detected with the element.
\r
3425 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3426 * @param {HtmlElement} t The target of the event.
\r
3427 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3430 * @event mouseenter
\r
3431 * Fires when the mouse enters the element.
\r
3432 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3433 * @param {HtmlElement} t The target of the event.
\r
3434 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3437 * @event mouseleave
\r
3438 * Fires when the mouse leaves the element.
\r
3439 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3440 * @param {HtmlElement} t The target of the event.
\r
3441 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3444 // Keyboard events
\r
3447 * Fires when a keypress is detected within the element.
\r
3448 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3449 * @param {HtmlElement} t The target of the event.
\r
3450 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3454 * Fires when a keydown is detected within the element.
\r
3455 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3456 * @param {HtmlElement} t The target of the event.
\r
3457 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3461 * Fires when a keyup is detected within the element.
\r
3462 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3463 * @param {HtmlElement} t The target of the event.
\r
3464 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3468 // HTML frame/object events
\r
3471 * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
\r
3472 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3473 * @param {HtmlElement} t The target of the event.
\r
3474 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3478 * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
\r
3479 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3480 * @param {HtmlElement} t The target of the event.
\r
3481 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3485 * Fires when an object/image is stopped from loading before completely loaded.
\r
3486 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3487 * @param {HtmlElement} t The target of the event.
\r
3488 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3492 * Fires when an object/image/frame cannot be loaded properly.
\r
3493 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3494 * @param {HtmlElement} t The target of the event.
\r
3495 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3499 * Fires when a document view is resized.
\r
3500 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3501 * @param {HtmlElement} t The target of the event.
\r
3502 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3506 * Fires when a document view is scrolled.
\r
3507 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3508 * @param {HtmlElement} t The target of the event.
\r
3509 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3515 * Fires when a user selects some text in a text field, including input and textarea.
\r
3516 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3517 * @param {HtmlElement} t The target of the event.
\r
3518 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3522 * Fires when a control loses the input focus and its value has been modified since gaining focus.
\r
3523 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3524 * @param {HtmlElement} t The target of the event.
\r
3525 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3529 * Fires when a form is submitted.
\r
3530 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3531 * @param {HtmlElement} t The target of the event.
\r
3532 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3536 * Fires when a form is reset.
\r
3537 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3538 * @param {HtmlElement} t The target of the event.
\r
3539 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3543 * Fires when an element receives focus either via the pointing device or by tab navigation.
\r
3544 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3545 * @param {HtmlElement} t The target of the event.
\r
3546 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3550 * Fires when an element loses focus either via the pointing device or by tabbing navigation.
\r
3551 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3552 * @param {HtmlElement} t The target of the event.
\r
3553 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3556 // User Interface events
\r
3558 * @event DOMFocusIn
\r
3559 * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
\r
3560 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3561 * @param {HtmlElement} t The target of the event.
\r
3562 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3565 * @event DOMFocusOut
\r
3566 * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
\r
3567 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3568 * @param {HtmlElement} t The target of the event.
\r
3569 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3572 * @event DOMActivate
\r
3573 * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
\r
3574 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3575 * @param {HtmlElement} t The target of the event.
\r
3576 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3579 // DOM Mutation events
\r
3581 * @event DOMSubtreeModified
\r
3582 * Where supported. Fires when the subtree is modified.
\r
3583 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3584 * @param {HtmlElement} t The target of the event.
\r
3585 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3588 * @event DOMNodeInserted
\r
3589 * Where supported. Fires when a node has been added as a child of another node.
\r
3590 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3591 * @param {HtmlElement} t The target of the event.
\r
3592 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3595 * @event DOMNodeRemoved
\r
3596 * Where supported. Fires when a descendant node of the element is removed.
\r
3597 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3598 * @param {HtmlElement} t The target of the event.
\r
3599 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3602 * @event DOMNodeRemovedFromDocument
\r
3603 * Where supported. Fires when a node is being removed from a document.
\r
3604 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3605 * @param {HtmlElement} t The target of the event.
\r
3606 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3609 * @event DOMNodeInsertedIntoDocument
\r
3610 * Where supported. Fires when a node is being inserted into a document.
\r
3611 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3612 * @param {HtmlElement} t The target of the event.
\r
3613 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3616 * @event DOMAttrModified
\r
3617 * Where supported. Fires when an attribute has been modified.
\r
3618 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3619 * @param {HtmlElement} t The target of the event.
\r
3620 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3623 * @event DOMCharacterDataModified
\r
3624 * Where supported. Fires when the character data has been modified.
\r
3625 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3626 * @param {HtmlElement} t The target of the event.
\r
3627 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3631 * The default unit to append to CSS values where a unit isn't provided (defaults to px).
\r
3634 defaultUnit : "px",
\r
3637 * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
\r
3638 * @param {String} selector The simple selector to test
\r
3639 * @return {Boolean} True if this element matches the selector, else false
\r
3641 is : function(simpleSelector){
\r
3642 return Ext.DomQuery.is(this.dom, simpleSelector);
\r
3646 * Tries to focus the element. Any exceptions are caught and ignored.
\r
3647 * @param {Number} defer (optional) Milliseconds to defer the focus
\r
3648 * @return {Ext.Element} this
\r
3650 focus : function(defer, /* private */ dom) {
\r
3652 dom = dom || me.dom;
\r
3654 if(Number(defer)){
\r
3655 me.focus.defer(defer, null, [null, dom]);
\r
3664 * Tries to blur the element. Any exceptions are caught and ignored.
\r
3665 * @return {Ext.Element} this
\r
3667 blur : function() {
\r
3675 * Returns the value of the "value" attribute
\r
3676 * @param {Boolean} asNumber true to parse the value as a number
\r
3677 * @return {String/Number}
\r
3679 getValue : function(asNumber){
\r
3680 var val = this.dom.value;
\r
3681 return asNumber ? parseInt(val, 10) : val;
\r
3685 * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
\r
3686 * @param {String} eventName The type of event to handle
\r
3687 * @param {Function} fn The handler function the event invokes. This function is passed
\r
3688 * the following parameters:<ul>
\r
3689 * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
\r
3690 * <li><b>el</b> : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
\r
3691 * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
\r
3692 * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
\r
3694 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
\r
3695 * <b>If omitted, defaults to this Element.</b>.
\r
3696 * @param {Object} options (optional) An object containing handler configuration properties.
\r
3697 * This may contain any of the following properties:<ul>
\r
3698 * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
\r
3699 * <b>If omitted, defaults to this Element.</b></div></li>
\r
3700 * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
\r
3701 * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
\r
3702 * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
\r
3703 * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
\r
3704 * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
\r
3705 * <li><b>target</b> Ext.Element: <div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
\r
3706 * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
\r
3707 * <li><b>single</b> Boolean: <div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
\r
3708 * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
\r
3709 * by the specified number of milliseconds. If the event fires again within that time, the original
\r
3710 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
\r
3713 * <b>Combining Options</b><br>
\r
3714 * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
\r
3715 * addListener. The two are equivalent. Using the options argument, it is possible to combine different
\r
3716 * types of listeners:<br>
\r
3718 * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
\r
3719 * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
\r
3720 * Code:<pre><code>
\r
3721 el.on('click', this.onClick, this, {
\r
3726 });</code></pre></p>
\r
3728 * <b>Attaching multiple handlers in 1 call</b><br>
\r
3729 * The method also allows for a single argument to be passed which is a config object containing properties
\r
3730 * which specify multiple handlers.</p>
\r
3732 * Code:<pre><code>
\r
3740 fn: this.onMouseOver,
\r
3744 fn: this.onMouseOut,
\r
3749 * Or a shorthand syntax:<br>
\r
3750 * Code:<pre><code></p>
\r
3752 'click' : this.onClick,
\r
3753 'mouseover' : this.onMouseOver,
\r
3754 'mouseout' : this.onMouseOut,
\r
3757 * </code></pre></p>
\r
3758 * <p><b>delegate</b></p>
\r
3759 * <p>This is a configuration option that you can pass along when registering a handler for
\r
3760 * an event to assist with event delegation. Event delegation is a technique that is used to
\r
3761 * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
\r
3762 * for a container element as opposed to each element within a container. By setting this
\r
3763 * configuration option to a simple selector, the target element will be filtered to look for
\r
3764 * a descendant of the target.
\r
3765 * For example:<pre><code>
\r
3766 // using this markup:
\r
3767 <div id='elId'>
\r
3768 <p id='p1'>paragraph one</p>
\r
3769 <p id='p2' class='clickable'>paragraph two</p>
\r
3770 <p id='p3'>paragraph three</p>
\r
3772 // utilize event delegation to registering just one handler on the container element:
\r
3773 el = Ext.get('elId');
\r
3778 console.info(t.id); // 'p2'
\r
3782 // filter the target element to be a descendant with the class 'clickable'
\r
3783 delegate: '.clickable'
\r
3786 * </code></pre></p>
\r
3787 * @return {Ext.Element} this
\r
3789 addListener : function(eventName, fn, scope, options){
\r
3790 Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
\r
3795 * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
\r
3796 * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
\r
3797 * listener, the same scope must be specified here.
\r
3800 el.removeListener('click', this.handlerFn);
\r
3802 el.un('click', this.handlerFn);
\r
3804 * @param {String} eventName the type of event to remove
\r
3805 * @param {Function} fn the method the event invokes
\r
3806 * @param {Object} scope (optional) The scope (The <tt>this</tt> reference) of the handler function. Defaults
\r
3807 * to this Element.
\r
3808 * @return {Ext.Element} this
\r
3810 removeListener : function(eventName, fn, scope){
\r
3811 Ext.EventManager.removeListener(this.dom, eventName, fn, scope || this);
\r
3816 * Removes all previous added listeners from this element
\r
3817 * @return {Ext.Element} this
\r
3819 removeAllListeners : function(){
\r
3820 Ext.EventManager.removeAll(this.dom);
\r
3825 * @private Test if size has a unit, otherwise appends the default
\r
3827 addUnits : function(size){
\r
3828 if(size === "" || size == "auto" || size === undefined){
\r
3829 size = size || '';
\r
3830 } else if(!isNaN(size) || !unitPattern.test(size)){
\r
3831 size = size + (this.defaultUnit || 'px');
\r
3837 * <p>Updates the <a href="http://developer.mozilla.org/en/DOM/element.innerHTML">innerHTML</a> of this Element
\r
3838 * from a specified URL. Note that this is subject to the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">Same Origin Policy</a></p>
\r
3839 * <p>Updating innerHTML of an element will <b>not</b> execute embedded <tt><script></tt> elements. This is a browser restriction.</p>
\r
3840 * @param {Mixed} options. Either a sring containing the URL from which to load the HTML, or an {@link Ext.Ajax#request} options object specifying
\r
3841 * exactly how to request the HTML.
\r
3842 * @return {Ext.Element} this
\r
3844 load : function(url, params, cb){
\r
3845 Ext.Ajax.request(Ext.apply({
\r
3847 url: url.url || url,
\r
3850 indicatorText: url.indicatorText || ''
\r
3851 }, Ext.isObject(url) ? url : {}));
\r
3856 * Tests various css rules/browsers to determine if this element uses a border box
\r
3857 * @return {Boolean}
\r
3859 isBorderBox : function(){
\r
3860 return noBoxAdjust[(this.dom.tagName || "").toLowerCase()] || Ext.isBorderBox;
\r
3864 * Removes this element from the DOM and deletes it from the cache
\r
3866 remove : function(){
\r
3870 me.removeAllListeners();
\r
3871 delete El.cache[dom.id];
\r
3872 delete El.dataCache[dom.id]
\r
3873 Ext.removeNode(dom);
\r
3877 * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
\r
3878 * @param {Function} overFn The function to call when the mouse enters the Element.
\r
3879 * @param {Function} outFn The function to call when the mouse leaves the Element.
\r
3880 * @param {Object} scope (optional) The scope (<tt>this</tt> reference) in which the functions are executed. Defaults to the Element's DOM element.
\r
3881 * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
\r
3882 * @return {Ext.Element} this
\r
3884 hover : function(overFn, outFn, scope, options){
\r
3886 me.on('mouseenter', overFn, scope || me.dom, options);
\r
3887 me.on('mouseleave', outFn, scope || me.dom, options);
\r
3892 * Returns true if this element is an ancestor of the passed element
\r
3893 * @param {HTMLElement/String} el The element to check
\r
3894 * @return {Boolean} True if this element is an ancestor of el, else false
\r
3896 contains : function(el){
\r
3897 return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
\r
3901 * Returns the value of a namespaced attribute from the element's underlying DOM node.
\r
3902 * @param {String} namespace The namespace in which to look for the attribute
\r
3903 * @param {String} name The attribute name
\r
3904 * @return {String} The attribute value
\r
3907 getAttributeNS : function(ns, name){
\r
3908 return this.getAttribute(name, ns);
\r
3912 * Returns the value of an attribute from the element's underlying DOM node.
\r
3913 * @param {String} name The attribute name
\r
3914 * @param {String} namespace (optional) The namespace in which to look for the attribute
\r
3915 * @return {String} The attribute value
\r
3917 getAttribute : Ext.isIE ? function(name, ns){
\r
3919 type = typeof d[ns + ":" + name];
\r
3921 if(['undefined', 'unknown'].indexOf(type) == -1){
\r
3922 return d[ns + ":" + name];
\r
3925 } : function(name, ns){
\r
3927 return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];
\r
3931 * Update the innerHTML of this element
\r
3932 * @param {String} html The new HTML
\r
3933 * @return {Ext.Element} this
\r
3935 update : function(html) {
\r
3936 this.dom.innerHTML = html;
\r
3941 var ep = El.prototype;
\r
3943 El.addMethods = function(o){
\r
3948 * Appends an event handler (shorthand for {@link #addListener}).
\r
3949 * @param {String} eventName The type of event to handle
\r
3950 * @param {Function} fn The handler function the event invokes
\r
3951 * @param {Object} scope (optional) The scope (this element) of the handler function
\r
3952 * @param {Object} options (optional) An object containing standard {@link #addListener} options
\r
3953 * @member Ext.Element
\r
3956 ep.on = ep.addListener;
\r
3959 * Removes an event handler from this element (see {@link #removeListener} for additional notes).
\r
3960 * @param {String} eventName the type of event to remove
\r
3961 * @param {Function} fn the method the event invokes
\r
3962 * @param {Object} scope (optional) The scope (The <tt>this</tt> reference) of the handler function. Defaults
\r
3963 * to this Element.
\r
3964 * @return {Ext.Element} this
\r
3965 * @member Ext.Element
\r
3968 ep.un = ep.removeListener;
\r
3971 * true to automatically adjust width and height settings for box-model issues (default to true)
\r
3973 ep.autoBoxAdjust = true;
\r
3976 var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
\r
3983 El.dataCache = {};
\r
3986 * Retrieves Ext.Element objects.
\r
3987 * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
\r
3988 * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
\r
3989 * its ID, use {@link Ext.ComponentMgr#get}.</p>
\r
3990 * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
\r
3991 * object was recreated with the same id via AJAX or DOM.</p>
\r
3992 * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
\r
3993 * @return {Element} The Element object (or null if no matching element was found)
\r
3995 * @member Ext.Element
\r
3998 El.get = function(el){
\r
4002 if(!el){ return null; }
\r
4003 if (typeof el == "string") { // element id
\r
4004 if (!(elm = DOC.getElementById(el))) {
\r
4007 if (ex = El.cache[el]) {
\r
4010 ex = El.cache[el] = new El(elm);
\r
4013 } else if (el.tagName) { // dom element
\r
4014 if(!(id = el.id)){
\r
4017 if(ex = El.cache[id]){
\r
4020 ex = El.cache[id] = new El(el);
\r
4023 } else if (el instanceof El) {
\r
4025 el.dom = DOC.getElementById(el.id) || el.dom; // refresh dom element in case no longer valid,
\r
4026 // catch case where it hasn't been appended
\r
4027 El.cache[el.id] = el; // in case it was created directly with Element(), let's cache it
\r
4030 } else if(el.isComposite) {
\r
4032 } else if(Ext.isArray(el)) {
\r
4033 return El.select(el);
\r
4034 } else if(el == DOC) {
\r
4035 // create a bogus element object representing the document object
\r
4037 var f = function(){};
\r
4038 f.prototype = El.prototype;
\r
4047 // private method for getting and setting element data
\r
4048 El.data = function(el, key, value){
\r
4049 var c = El.dataCache[el.id];
\r
4051 c = El.dataCache[el.id] = {};
\r
4053 if(arguments.length == 2){
\r
4061 // Garbage collection - uncache elements/purge listeners on orphaned elements
\r
4062 // so we don't hold a reference and cause the browser to retain them
\r
4063 function garbageCollect(){
\r
4064 if(!Ext.enableGarbageCollector){
\r
4065 clearInterval(El.collectorThread);
\r
4071 for(eid in El.cache){
\r
4072 el = El.cache[eid];
\r
4074 // -------------------------------------------------------
\r
4075 // Determining what is garbage:
\r
4076 // -------------------------------------------------------
\r
4078 // dom node is null, definitely garbage
\r
4079 // -------------------------------------------------------
\r
4081 // no parentNode == direct orphan, definitely garbage
\r
4082 // -------------------------------------------------------
\r
4083 // !d.offsetParent && !document.getElementById(eid)
\r
4084 // display none elements have no offsetParent so we will
\r
4085 // also try to look it up by it's id. However, check
\r
4086 // offsetParent first so we don't do unneeded lookups.
\r
4087 // This enables collection of elements that are not orphans
\r
4088 // directly, but somewhere up the line they have an orphan
\r
4090 // -------------------------------------------------------
\r
4091 if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
\r
4092 delete El.cache[eid];
\r
4093 if(d && Ext.enableListenerCollection){
\r
4094 Ext.EventManager.removeAll(d);
\r
4100 El.collectorThreadId = setInterval(garbageCollect, 30000);
\r
4102 var flyFn = function(){};
\r
4103 flyFn.prototype = El.prototype;
\r
4105 // dom is optional
\r
4106 El.Flyweight = function(dom){
\r
4110 El.Flyweight.prototype = new flyFn();
\r
4111 El.Flyweight.prototype.isFlyweight = true;
\r
4112 El._flyweights = {};
\r
4115 * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
\r
4116 * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
\r
4117 * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
\r
4118 * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
\r
4119 * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
\r
4120 * @param {String/HTMLElement} el The dom node or id
\r
4121 * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
\r
4122 * (e.g. internally Ext uses "_global")
\r
4123 * @return {Element} The shared Element object (or null if no matching element was found)
\r
4124 * @member Ext.Element
\r
4127 El.fly = function(el, named){
\r
4129 named = named || '_global';
\r
4131 if (el = Ext.getDom(el)) {
\r
4132 (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
\r
4133 ret = El._flyweights[named];
\r
4139 * Retrieves Ext.Element objects.
\r
4140 * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
\r
4141 * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
\r
4142 * its ID, use {@link Ext.ComponentMgr#get}.</p>
\r
4143 * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
\r
4144 * object was recreated with the same id via AJAX or DOM.</p>
\r
4145 * Shorthand of {@link Ext.Element#get}
\r
4146 * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
\r
4147 * @return {Element} The Element object (or null if no matching element was found)
\r
4154 * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
\r
4155 * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
\r
4156 * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
\r
4157 * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
\r
4158 * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
\r
4159 * @param {String/HTMLElement} el The dom node or id
\r
4160 * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
\r
4161 * (e.g. internally Ext uses "_global")
\r
4162 * @return {Element} The shared Element object (or null if no matching element was found)
\r
4168 // speedy lookup for elements never to box adjust
\r
4169 var noBoxAdjust = Ext.isStrict ? {
\r
4172 input:1, select:1, textarea:1
\r
4174 if(Ext.isIE || Ext.isGecko){
\r
4175 noBoxAdjust['button'] = 1;
\r
4179 Ext.EventManager.on(window, 'unload', function(){
\r
4181 delete El.dataCache;
\r
4182 delete El._flyweights;
\r
4186 * @class Ext.Element
\r
4188 Ext.Element.addMethods({
\r
4190 * Stops the specified event(s) from bubbling and optionally prevents the default action
\r
4191 * @param {String/Array} eventName an event / array of events to stop from bubbling
\r
4192 * @param {Boolean} preventDefault (optional) true to prevent the default action too
\r
4193 * @return {Ext.Element} this
\r
4195 swallowEvent : function(eventName, preventDefault){
\r
4198 e.stopPropagation();
\r
4199 if(preventDefault){
\r
4200 e.preventDefault();
\r
4203 if(Ext.isArray(eventName)){
\r
4204 Ext.each(eventName, function(e) {
\r
4209 me.on(eventName, fn);
\r
4214 * Create an event handler on this element such that when the event fires and is handled by this element,
\r
4215 * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
\r
4216 * @param {String} eventName The type of event to relay
\r
4217 * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
\r
4218 * for firing the relayed event
\r
4220 relayEvent : function(eventName, observable){
\r
4221 this.on(eventName, function(e){
\r
4222 observable.fireEvent(eventName, e);
\r
4227 * Removes worthless text nodes
\r
4228 * @param {Boolean} forceReclean (optional) By default the element
\r
4229 * keeps track if it has been cleaned already so
\r
4230 * you can call this over and over. However, if you update the element and
\r
4231 * need to force a reclean, you can pass true.
\r
4233 clean : function(forceReclean){
\r
4236 n = dom.firstChild,
\r
4239 if(Ext.Element.data(dom, 'isCleaned') && forceReclean !== true){
\r
4244 var nx = n.nextSibling;
\r
4245 if(n.nodeType == 3 && !/\S/.test(n.nodeValue)){
\r
4246 dom.removeChild(n);
\r
4248 n.nodeIndex = ++ni;
\r
4252 Ext.Element.data(dom, 'isCleaned', true);
\r
4257 * Direct access to the Updater {@link Ext.Updater#update} method. The method takes the same object
\r
4258 * parameter as {@link Ext.Updater#update}
\r
4259 * @return {Ext.Element} this
\r
4261 load : function(){
\r
4262 var um = this.getUpdater();
\r
4263 um.update.apply(um, arguments);
\r
4268 * Gets this element's {@link Ext.Updater Updater}
\r
4269 * @return {Ext.Updater} The Updater
\r
4271 getUpdater : function(){
\r
4272 return this.updateManager || (this.updateManager = new Ext.Updater(this));
\r
4276 * Update the innerHTML of this element, optionally searching for and processing scripts
\r
4277 * @param {String} html The new HTML
\r
4278 * @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
\r
4279 * @param {Function} callback (optional) For async script loading you can be notified when the update completes
\r
4280 * @return {Ext.Element} this
\r
4282 update : function(html, loadScripts, callback){
\r
4283 html = html || "";
\r
4285 if(loadScripts !== true){
\r
4286 this.dom.innerHTML = html;
\r
4287 if(Ext.isFunction(callback)){
\r
4293 var id = Ext.id(),
\r
4296 html += '<span id="' + id + '"></span>';
\r
4298 Ext.lib.Event.onAvailable(id, function(){
\r
4299 var DOC = document,
\r
4300 hd = DOC.getElementsByTagName("head")[0],
\r
4301 re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
\r
4302 srcRe = /\ssrc=([\'\"])(.*?)\1/i,
\r
4303 typeRe = /\stype=([\'\"])(.*?)\1/i,
\r
4311 while((match = re.exec(html))){
\r
4313 srcMatch = attrs ? attrs.match(srcRe) : false;
\r
4314 if(srcMatch && srcMatch[2]){
\r
4315 s = DOC.createElement("script");
\r
4316 s.src = srcMatch[2];
\r
4317 typeMatch = attrs.match(typeRe);
\r
4318 if(typeMatch && typeMatch[2]){
\r
4319 s.type = typeMatch[2];
\r
4321 hd.appendChild(s);
\r
4322 }else if(match[2] && match[2].length > 0){
\r
4323 if(window.execScript) {
\r
4324 window.execScript(match[2]);
\r
4326 window.eval(match[2]);
\r
4330 el = DOC.getElementById(id);
\r
4331 if(el){Ext.removeNode(el);}
\r
4332 if(Ext.isFunction(callback)){
\r
4336 dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
\r
4341 * Creates a proxy element of this element
\r
4342 * @param {String/Object} config The class name of the proxy element or a DomHelper config object
\r
4343 * @param {String/HTMLElement} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
\r
4344 * @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
\r
4345 * @return {Ext.Element} The new proxy element
\r
4347 createProxy : function(config, renderTo, matchBox){
\r
4348 config = Ext.isObject(config) ? config : {tag : "div", cls: config};
\r
4351 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
\r
4352 Ext.DomHelper.insertBefore(me.dom, config, true);
\r
4354 if(matchBox && me.setBox && me.getBox){ // check to make sure Element.position.js is loaded
\r
4355 proxy.setBox(me.getBox());
\r
4361 Ext.Element.prototype.getUpdateManager = Ext.Element.prototype.getUpdater;
\r
4364 Ext.Element.uncache = function(el){
\r
4365 for(var i = 0, a = arguments, len = a.length; i < len; i++) {
\r
4367 delete Ext.Element.cache[a[i].id || a[i]];
\r
4371 * @class Ext.Element
\r
4373 Ext.Element.addMethods({
\r
4375 * Gets the x,y coordinates specified by the anchor position on the element.
\r
4376 * @param {String} anchor (optional) The specified anchor position (defaults to "c"). See {@link #alignTo}
\r
4377 * for details on supported anchor positions.
\r
4378 * @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
\r
4379 * of page coordinates
\r
4380 * @param {Object} size (optional) An object containing the size to use for calculating anchor position
\r
4381 * {width: (target width), height: (target height)} (defaults to the element's current size)
\r
4382 * @return {Array} [x, y] An array containing the element's x and y coordinates
\r
4384 getAnchorXY : function(anchor, local, s){
\r
4385 //Passing a different size is useful for pre-calculating anchors,
\r
4386 //especially for anchored animations that change the el size.
\r
4387 anchor = (anchor || "tl").toLowerCase();
\r
4391 vp = me.dom == document.body || me.dom == document,
\r
4392 w = s.width || vp ? Ext.lib.Dom.getViewWidth() : me.getWidth(),
\r
4393 h = s.height || vp ? Ext.lib.Dom.getViewHeight() : me.getHeight(),
\r
4397 scroll = me.getScroll(),
\r
4398 extraX = vp ? scroll.left : !local ? o[0] : 0,
\r
4399 extraY = vp ? scroll.top : !local ? o[1] : 0,
\r
4401 c : [r(w * 0.5), r(h * 0.5)],
\r
4402 t : [r(w * 0.5), 0],
\r
4403 l : [0, r(h * 0.5)],
\r
4404 r : [w, r(h * 0.5)],
\r
4405 b : [r(w * 0.5), h],
\r
4412 xy = hash[anchor];
\r
4413 return [xy[0] + extraX, xy[1] + extraY];
\r
4417 * Anchors an element to another element and realigns it when the window is resized.
\r
4418 * @param {Mixed} element The element to align to.
\r
4419 * @param {String} position The position to align to.
\r
4420 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4421 * @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
\r
4422 * @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
\r
4423 * is a number, it is used as the buffer delay (defaults to 50ms).
\r
4424 * @param {Function} callback The function to call after the animation finishes
\r
4425 * @return {Ext.Element} this
\r
4427 anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
\r
4431 function action(){
\r
4432 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
\r
4433 Ext.callback(callback, Ext.fly(dom));
\r
4436 Ext.EventManager.onWindowResize(action, me);
\r
4438 if(!Ext.isEmpty(monitorScroll)){
\r
4439 Ext.EventManager.on(window, 'scroll', action, me,
\r
4440 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
\r
4442 action.call(me); // align immediately
\r
4447 * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
\r
4448 * supported position values.
\r
4449 * @param {Mixed} element The element to align to.
\r
4450 * @param {String} position The position to align to.
\r
4451 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4452 * @return {Array} [x, y]
\r
4454 getAlignToXY : function(el, p, o){
\r
4457 if(!el || !el.dom){
\r
4458 throw "Element.alignToXY with an element that doesn't exist";
\r
4462 p = (p == "?" ? "tl-bl?" : (!/-/.test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
\r
4470 //constrain the aligned el to viewport if necessary
\r
4474 dw = Ext.lib.Dom.getViewWidth() -10, // 10px of margin for ie
\r
4475 dh = Ext.lib.Dom.getViewHeight()-10, // 10px of margin for ie
\r
4483 docElement = doc.documentElement,
\r
4484 docBody = doc.body,
\r
4485 scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
\r
4486 scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
\r
4487 c = false, //constrain to viewport
\r
4490 m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
\r
4493 throw "Element.alignTo with an invalid alignment " + p;
\r
4500 //Subtract the aligned el's internal xy from the target's offset xy
\r
4501 //plus custom offset to get the aligned el's new offset xy
\r
4502 a1 = me.getAnchorXY(p1, true);
\r
4503 a2 = el.getAnchorXY(p2, false);
\r
4505 x = a2[0] - a1[0] + o[0];
\r
4506 y = a2[1] - a1[1] + o[1];
\r
4509 w = me.getWidth();
\r
4510 h = me.getHeight();
\r
4511 r = el.getRegion();
\r
4512 //If we are at a viewport boundary and the aligned el is anchored on a target border that is
\r
4513 //perpendicular to the vp border, allow the aligned el to slide on that border,
\r
4514 //otherwise swap the aligned el to the opposite border of the target.
\r
4515 p1y = p1.charAt(0);
\r
4516 p1x = p1.charAt(p1.length-1);
\r
4517 p2y = p2.charAt(0);
\r
4518 p2x = p2.charAt(p2.length-1);
\r
4519 swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
\r
4520 swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
\r
4523 if (x + w > dw + scrollX) {
\r
4524 x = swapX ? r.left-w : dw+scrollX-w;
\r
4526 if (x < scrollX) {
\r
4527 x = swapX ? r.right : scrollX;
\r
4529 if (y + h > dh + scrollY) {
\r
4530 y = swapY ? r.top-h : dh+scrollY-h;
\r
4533 y = swapY ? r.bottom : scrollY;
\r
4540 * Aligns this element with another element relative to the specified anchor points. If the other element is the
\r
4541 * document it aligns it to the viewport.
\r
4542 * The position parameter is optional, and can be specified in any one of the following formats:
\r
4544 * <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
\r
4545 * <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
\r
4546 * The element being aligned will position its top-left corner (tl) to that point. <i>This method has been
\r
4547 * deprecated in favor of the newer two anchor syntax below</i>.</li>
\r
4548 * <li><b>Two anchors</b>: If two values from the table below are passed separated by a dash, the first value is used as the
\r
4549 * element's anchor point, and the second value is used as the target's anchor point.</li>
\r
4551 * In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
\r
4552 * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
\r
4553 * the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
\r
4554 * that specified in order to enforce the viewport constraints.
\r
4555 * Following are all of the supported anchor positions:
\r
4558 ----- -----------------------------
\r
4559 tl The top left corner (default)
\r
4560 t The center of the top edge
\r
4561 tr The top right corner
\r
4562 l The center of the left edge
\r
4563 c In the center of the element
\r
4564 r The center of the right edge
\r
4565 bl The bottom left corner
\r
4566 b The center of the bottom edge
\r
4567 br The bottom right corner
\r
4571 // align el to other-el using the default positioning ("tl-bl", non-constrained)
\r
4572 el.alignTo("other-el");
\r
4574 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
\r
4575 el.alignTo("other-el", "tr?");
\r
4577 // align the bottom right corner of el with the center left edge of other-el
\r
4578 el.alignTo("other-el", "br-l?");
\r
4580 // align the center of el with the bottom left corner of other-el and
\r
4581 // adjust the x position by -6 pixels (and the y position by 0)
\r
4582 el.alignTo("other-el", "c-bl", [-6, 0]);
\r
4584 * @param {Mixed} element The element to align to.
\r
4585 * @param {String} position The position to align to.
\r
4586 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4587 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
4588 * @return {Ext.Element} this
\r
4590 alignTo : function(element, position, offsets, animate){
\r
4592 return me.setXY(me.getAlignToXY(element, position, offsets),
\r
4593 me.preanim && !!animate ? me.preanim(arguments, 3) : false);
\r
4596 // private ==> used outside of core
\r
4597 adjustForConstraints : function(xy, parent, offsets){
\r
4598 return this.getConstrainToXY(parent || document, false, offsets, xy) || xy;
\r
4601 // private ==> used outside of core
\r
4602 getConstrainToXY : function(el, local, offsets, proposedXY){
\r
4603 var os = {top:0, left:0, bottom:0, right: 0};
\r
4605 return function(el, local, offsets, proposedXY){
\r
4607 offsets = offsets ? Ext.applyIf(offsets, os) : os;
\r
4609 var vw, vh, vx = 0, vy = 0;
\r
4610 if(el.dom == document.body || el.dom == document){
\r
4611 vw =Ext.lib.Dom.getViewWidth();
\r
4612 vh = Ext.lib.Dom.getViewHeight();
\r
4614 vw = el.dom.clientWidth;
\r
4615 vh = el.dom.clientHeight;
\r
4617 var vxy = el.getXY();
\r
4623 var s = el.getScroll();
\r
4625 vx += offsets.left + s.left;
\r
4626 vy += offsets.top + s.top;
\r
4628 vw -= offsets.right;
\r
4629 vh -= offsets.bottom;
\r
4634 var xy = proposedXY || (!local ? this.getXY() : [this.getLeft(true), this.getTop(true)]);
\r
4635 var x = xy[0], y = xy[1];
\r
4636 var w = this.dom.offsetWidth, h = this.dom.offsetHeight;
\r
4638 // only move it if it needs it
\r
4639 var moved = false;
\r
4641 // first validate right/bottom
\r
4650 // then make sure top/left isn't negative
\r
4659 return moved ? [x, y] : false;
\r
4665 // el = Ext.get(el);
\r
4666 // offsets = Ext.applyIf(offsets || {}, {top : 0, left : 0, bottom : 0, right : 0});
\r
4669 // doc = document,
\r
4670 // s = el.getScroll(),
\r
4671 // vxy = el.getXY(),
\r
4672 // vx = offsets.left + s.left,
\r
4673 // vy = offsets.top + s.top,
\r
4674 // vw = -offsets.right,
\r
4675 // vh = -offsets.bottom,
\r
4678 // xy = proposedXY || (!local ? me.getXY() : [me.getLeft(true), me.getTop(true)]),
\r
4681 // w = me.dom.offsetWidth, h = me.dom.offsetHeight,
\r
4682 // moved = false; // only move it if it needs it
\r
4685 // if(el.dom == doc.body || el.dom == doc){
\r
4686 // vw += Ext.lib.Dom.getViewWidth();
\r
4687 // vh += Ext.lib.Dom.getViewHeight();
\r
4689 // vw += el.dom.clientWidth;
\r
4690 // vh += el.dom.clientHeight;
\r
4697 // // first validate right/bottom
\r
4698 // if(x + w > vx + vw){
\r
4699 // x = vx + vw - w;
\r
4702 // if(y + h > vy + vh){
\r
4703 // y = vy + vh - h;
\r
4706 // // then make sure top/left isn't negative
\r
4715 // return moved ? [x, y] : false;
\r
4719 * Calculates the x, y to center this element on the screen
\r
4720 * @return {Array} The x, y values [x, y]
\r
4722 getCenterXY : function(){
\r
4723 return this.getAlignToXY(document, 'c-c');
\r
4727 * Centers the Element in either the viewport, or another Element.
\r
4728 * @param {Mixed} centerIn (optional) The element in which to center the element.
\r
4730 center : function(centerIn){
\r
4731 return this.alignTo(centerIn || document, 'c-c');
\r
4735 * @class Ext.Element
\r
4737 Ext.Element.addMethods(function(){
\r
4738 var PARENTNODE = 'parentNode',
\r
4739 NEXTSIBLING = 'nextSibling',
\r
4740 PREVIOUSSIBLING = 'previousSibling',
\r
4741 DQ = Ext.DomQuery,
\r
4746 * Looks at this node and then at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
\r
4747 * @param {String} selector The simple selector to test
\r
4748 * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
\r
4749 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
\r
4750 * @return {HTMLElement} The matching DOM node (or null if no match was found)
\r
4752 findParent : function(simpleSelector, maxDepth, returnEl){
\r
4754 b = document.body,
\r
4757 if(Ext.isGecko && Object.prototype.toString.call(p) == '[object XULElement]') {
\r
4760 maxDepth = maxDepth || 50;
\r
4761 if (isNaN(maxDepth)) {
\r
4762 stopEl = Ext.getDom(maxDepth);
\r
4763 maxDepth = Number.MAX_VALUE;
\r
4765 while(p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl){
\r
4766 if(DQ.is(p, simpleSelector)){
\r
4767 return returnEl ? GET(p) : p;
\r
4776 * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
\r
4777 * @param {String} selector The simple selector to test
\r
4778 * @param {Number/Mixed} maxDepth (optional) The max depth to
\r
4779 search as a number or element (defaults to 10 || document.body)
\r
4780 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
\r
4781 * @return {HTMLElement} The matching DOM node (or null if no match was found)
\r
4783 findParentNode : function(simpleSelector, maxDepth, returnEl){
\r
4784 var p = Ext.fly(this.dom.parentNode, '_internal');
\r
4785 return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
\r
4789 * Walks up the dom looking for a parent node that matches the passed simple selector (e.g. div.some-class or span:first-child).
\r
4790 * This is a shortcut for findParentNode() that always returns an Ext.Element.
\r
4791 * @param {String} selector The simple selector to test
\r
4792 * @param {Number/Mixed} maxDepth (optional) The max depth to
\r
4793 search as a number or element (defaults to 10 || document.body)
\r
4794 * @return {Ext.Element} The matching DOM node (or null if no match was found)
\r
4796 up : function(simpleSelector, maxDepth){
\r
4797 return this.findParentNode(simpleSelector, maxDepth, true);
\r
4801 * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
\r
4802 * @param {String} selector The CSS selector
\r
4803 * @param {Boolean} unique (optional) True to create a unique Ext.Element for each child (defaults to false, which creates a single shared flyweight object)
\r
4804 * @return {CompositeElement/CompositeElementLite} The composite element
\r
4806 select : function(selector, unique){
\r
4807 return Ext.Element.select(selector, unique, this.dom);
\r
4811 * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
\r
4812 * @param {String} selector The CSS selector
\r
4813 * @return {Array} An array of the matched nodes
\r
4815 query : function(selector, unique){
\r
4816 return DQ.select(selector, this.dom);
\r
4820 * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
\r
4821 * @param {String} selector The CSS selector
\r
4822 * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
\r
4823 * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
\r
4825 child : function(selector, returnDom){
\r
4826 var n = DQ.selectNode(selector, this.dom);
\r
4827 return returnDom ? n : GET(n);
\r
4831 * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
\r
4832 * @param {String} selector The CSS selector
\r
4833 * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
\r
4834 * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
\r
4836 down : function(selector, returnDom){
\r
4837 var n = DQ.selectNode(" > " + selector, this.dom);
\r
4838 return returnDom ? n : GET(n);
\r
4842 * Gets the parent node for this element, optionally chaining up trying to match a selector
\r
4843 * @param {String} selector (optional) Find a parent node that matches the passed simple selector
\r
4844 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
4845 * @return {Ext.Element/HTMLElement} The parent node or null
\r
4847 parent : function(selector, returnDom){
\r
4848 return this.matchNode(PARENTNODE, PARENTNODE, selector, returnDom);
\r
4852 * Gets the next sibling, skipping text nodes
\r
4853 * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
\r
4854 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
4855 * @return {Ext.Element/HTMLElement} The next sibling or null
\r
4857 next : function(selector, returnDom){
\r
4858 return this.matchNode(NEXTSIBLING, NEXTSIBLING, selector, returnDom);
\r
4862 * Gets the previous sibling, skipping text nodes
\r
4863 * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
\r
4864 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
4865 * @return {Ext.Element/HTMLElement} The previous sibling or null
\r
4867 prev : function(selector, returnDom){
\r
4868 return this.matchNode(PREVIOUSSIBLING, PREVIOUSSIBLING, selector, returnDom);
\r
4873 * Gets the first child, skipping text nodes
\r
4874 * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
\r
4875 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
4876 * @return {Ext.Element/HTMLElement} The first child or null
\r
4878 first : function(selector, returnDom){
\r
4879 return this.matchNode(NEXTSIBLING, 'firstChild', selector, returnDom);
\r
4883 * Gets the last child, skipping text nodes
\r
4884 * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
\r
4885 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
4886 * @return {Ext.Element/HTMLElement} The last child or null
\r
4888 last : function(selector, returnDom){
\r
4889 return this.matchNode(PREVIOUSSIBLING, 'lastChild', selector, returnDom);
\r
4892 matchNode : function(dir, start, selector, returnDom){
\r
4893 var n = this.dom[start];
\r
4895 if(n.nodeType == 1 && (!selector || DQ.is(n, selector))){
\r
4896 return !returnDom ? GET(n) : n;
\r
4904 * @class Ext.Element
\r
4906 Ext.Element.addMethods(
\r
4908 var GETDOM = Ext.getDom,
\r
4910 DH = Ext.DomHelper,
\r
4911 isEl = function(el){
\r
4912 return (el.nodeType || el.dom || typeof el == 'string');
\r
4917 * Appends the passed element(s) to this element
\r
4918 * @param {String/HTMLElement/Array/Element/CompositeElement} el
\r
4919 * @return {Ext.Element} this
\r
4921 appendChild: function(el){
\r
4922 return GET(el).appendTo(this);
\r
4926 * Appends this element to the passed element
\r
4927 * @param {Mixed} el The new parent element
\r
4928 * @return {Ext.Element} this
\r
4930 appendTo: function(el){
\r
4931 GETDOM(el).appendChild(this.dom);
\r
4936 * Inserts this element before the passed element in the DOM
\r
4937 * @param {Mixed} el The element before which this element will be inserted
\r
4938 * @return {Ext.Element} this
\r
4940 insertBefore: function(el){
\r
4941 (el = GETDOM(el)).parentNode.insertBefore(this.dom, el);
\r
4946 * Inserts this element after the passed element in the DOM
\r
4947 * @param {Mixed} el The element to insert after
\r
4948 * @return {Ext.Element} this
\r
4950 insertAfter: function(el){
\r
4951 (el = GETDOM(el)).parentNode.insertBefore(this.dom, el.nextSibling);
\r
4956 * Inserts (or creates) an element (or DomHelper config) as the first child of this element
\r
4957 * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
\r
4958 * @return {Ext.Element} The new child
\r
4960 insertFirst: function(el, returnDom){
\r
4962 if(isEl(el)){ // element
\r
4964 this.dom.insertBefore(el, this.dom.firstChild);
\r
4965 return !returnDom ? GET(el) : el;
\r
4966 }else{ // dh config
\r
4967 return this.createChild(el, this.dom.firstChild, returnDom);
\r
4972 * Replaces the passed element with this element
\r
4973 * @param {Mixed} el The element to replace
\r
4974 * @return {Ext.Element} this
\r
4976 replace: function(el){
\r
4978 this.insertBefore(el);
\r
4984 * Replaces this element with the passed element
\r
4985 * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
\r
4986 * @return {Ext.Element} this
\r
4988 replaceWith: function(el){
\r
4990 Element = Ext.Element;
\r
4993 me.dom.parentNode.insertBefore(el, me.dom);
\r
4995 el = DH.insertBefore(me.dom, el);
\r
4998 delete Element.cache[me.id];
\r
4999 Ext.removeNode(me.dom);
\r
5000 me.id = Ext.id(me.dom = el);
\r
5001 return Element.cache[me.id] = me;
\r
5005 * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
\r
5006 * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
\r
5007 * automatically generated with the specified attributes.
\r
5008 * @param {HTMLElement} insertBefore (optional) a child element of this element
\r
5009 * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
\r
5010 * @return {Ext.Element} The new child element
\r
5012 createChild: function(config, insertBefore, returnDom){
\r
5013 config = config || {tag:'div'};
\r
5014 return insertBefore ?
\r
5015 DH.insertBefore(insertBefore, config, returnDom !== true) :
\r
5016 DH[!this.dom.firstChild ? 'overwrite' : 'append'](this.dom, config, returnDom !== true);
\r
5020 * Creates and wraps this element with another element
\r
5021 * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
\r
5022 * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
\r
5023 * @return {HTMLElement/Element} The newly created wrapper element
\r
5025 wrap: function(config, returnDom){
\r
5026 var newEl = DH.insertBefore(this.dom, config || {tag: "div"}, !returnDom);
\r
5027 newEl.dom ? newEl.dom.appendChild(this.dom) : newEl.appendChild(this.dom);
\r
5032 * Inserts an html fragment into this element
\r
5033 * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
\r
5034 * @param {String} html The HTML fragment
\r
5035 * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
\r
5036 * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
\r
5038 insertHtml : function(where, html, returnEl){
\r
5039 var el = DH.insertHtml(where, this.dom, html);
\r
5040 return returnEl ? Ext.get(el) : el;
\r
5044 * @class Ext.Element
\r
5046 Ext.apply(Ext.Element.prototype, function() {
\r
5047 var GETDOM = Ext.getDom,
\r
5049 DH = Ext.DomHelper;
\r
5053 * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
\r
5054 * @param {Mixed/Object/Array} el The id, element to insert or a DomHelper config to create and insert *or* an array of any of those.
\r
5055 * @param {String} where (optional) 'before' or 'after' defaults to before
\r
5056 * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
\r
5057 * @return {Ext.Element} the inserted Element
\r
5059 insertSibling: function(el, where, returnDom){
\r
5063 if(Ext.isArray(el)){
\r
5064 Ext.each(el, function(e) {
\r
5065 rt = me.insertSibling(e, where, returnDom);
\r
5070 where = (where || 'before').toLowerCase();
\r
5073 if(el.nodeType || el.dom){
\r
5074 rt = me.dom.parentNode.insertBefore(GETDOM(el), where == 'before' ? me.dom : me.dom.nextSibling);
\r
5079 if (where == 'after' && !me.dom.nextSibling) {
\r
5080 rt = DH.append(me.dom.parentNode, el, !returnDom);
\r
5082 rt = DH[where == 'after' ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
\r
5089 * @class Ext.Element
\r
5091 Ext.Element.addMethods(function(){
\r
5092 // local style camelizing for speed
\r
5093 var propCache = {},
\r
5094 camelRe = /(-[a-z])/gi,
\r
5095 classReCache = {},
\r
5096 view = document.defaultView,
\r
5097 propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat',
\r
5098 opacityRe = /alpha\(opacity=(.*)\)/i,
\r
5099 trimRe = /^\s+|\s+$/g,
\r
5100 EL = Ext.Element,
\r
5101 PADDING = "padding",
\r
5102 MARGIN = "margin",
\r
5103 BORDER = "border",
\r
5107 BOTTOM = "-bottom",
\r
5108 WIDTH = "-width",
\r
5110 HIDDEN = 'hidden',
\r
5111 ISCLIPPED = 'isClipped',
\r
5112 OVERFLOW = 'overflow',
\r
5113 OVERFLOWX = 'overflow-x',
\r
5114 OVERFLOWY = 'overflow-y',
\r
5115 ORIGINALCLIP = 'originalClip',
\r
5116 // special markup used throughout Ext when box wrapping elements
\r
5117 borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
\r
5118 paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
\r
5119 margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
\r
5120 data = Ext.Element.data;
\r
5124 function camelFn(m, a) {
\r
5125 return a.charAt(1).toUpperCase();
\r
5128 // private (needs to be called => addStyles.call(this, sides, styles))
\r
5129 function addStyles(sides, styles){
\r
5132 Ext.each(sides.match(/\w/g), function(s) {
\r
5133 if (s = parseInt(this.getStyle(styles[s]), 10)) {
\r
5134 val += MATH.abs(s);
\r
5141 function chkCache(prop) {
\r
5142 return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn));
\r
5147 // private ==> used by Fx
\r
5148 adjustWidth : function(width) {
\r
5150 var isNum = (typeof width == "number");
\r
5151 if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
\r
5152 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
\r
5154 return (isNum && width < 0) ? 0 : width;
\r
5157 // private ==> used by Fx
\r
5158 adjustHeight : function(height) {
\r
5160 var isNum = (typeof height == "number");
\r
5161 if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
\r
5162 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
\r
5164 return (isNum && height < 0) ? 0 : height;
\r
5169 * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
\r
5170 * @param {String/Array} className The CSS class to add, or an array of classes
\r
5171 * @return {Ext.Element} this
\r
5173 addClass : function(className){
\r
5175 Ext.each(className, function(v) {
\r
5176 me.dom.className += (!me.hasClass(v) && v ? " " + v : "");
\r
5182 * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
\r
5183 * @param {String/Array} className The CSS class to add, or an array of classes
\r
5184 * @return {Ext.Element} this
\r
5186 radioClass : function(className){
\r
5187 Ext.each(this.dom.parentNode.childNodes, function(v) {
\r
5188 if(v.nodeType == 1) {
\r
5189 Ext.fly(v, '_internal').removeClass(className);
\r
5192 return this.addClass(className);
\r
5196 * Removes one or more CSS classes from the element.
\r
5197 * @param {String/Array} className The CSS class to remove, or an array of classes
\r
5198 * @return {Ext.Element} this
\r
5200 removeClass : function(className){
\r
5202 if (me.dom.className) {
\r
5203 Ext.each(className, function(v) {
\r
5204 me.dom.className = me.dom.className.replace(
\r
5205 classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"),
\r
5213 * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
\r
5214 * @param {String} className The CSS class to toggle
\r
5215 * @return {Ext.Element} this
\r
5217 toggleClass : function(className){
\r
5218 return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
\r
5222 * Checks if the specified CSS class exists on this element's DOM node.
\r
5223 * @param {String} className The CSS class to check for
\r
5224 * @return {Boolean} True if the class exists, else false
\r
5226 hasClass : function(className){
\r
5227 return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
\r
5231 * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
\r
5232 * @param {String} oldClassName The CSS class to replace
\r
5233 * @param {String} newClassName The replacement CSS class
\r
5234 * @return {Ext.Element} this
\r
5236 replaceClass : function(oldClassName, newClassName){
\r
5237 return this.removeClass(oldClassName).addClass(newClassName);
\r
5240 isStyle : function(style, val) {
\r
5241 return this.getStyle(style) == val;
\r
5245 * Normalizes currentStyle and computedStyle.
\r
5246 * @param {String} property The style property whose value is returned.
\r
5247 * @return {String} The current value of the style property for this element.
\r
5249 getStyle : function(){
\r
5250 return view && view.getComputedStyle ?
\r
5252 var el = this.dom,
\r
5255 if(el == document) return null;
\r
5256 prop = chkCache(prop);
\r
5257 return (v = el.style[prop]) ? v :
\r
5258 (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
\r
5261 var el = this.dom,
\r
5265 if(el == document) return null;
\r
5266 if (prop == 'opacity') {
\r
5267 if (el.style.filter.match) {
\r
5268 if(m = el.style.filter.match(opacityRe)){
\r
5269 var fv = parseFloat(m[1]);
\r
5271 return fv ? fv / 100 : 0;
\r
5277 prop = chkCache(prop);
\r
5278 return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
\r
5283 * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
\r
5284 * are convert to standard 6 digit hex color.
\r
5285 * @param {String} attr The css attribute
\r
5286 * @param {String} defaultValue The default value to use when a valid color isn't found
\r
5287 * @param {String} prefix (optional) defaults to #. Use an empty string when working with
\r
5290 getColor : function(attr, defaultValue, prefix){
\r
5291 var v = this.getStyle(attr),
\r
5292 color = prefix || '#',
\r
5295 if(!v || /transparent|inherit/.test(v)){
\r
5296 return defaultValue;
\r
5299 Ext.each(v.slice(4, v.length -1).split(','), function(s){
\r
5300 h = parseInt(s, 10);
\r
5301 color += (h < 16 ? '0' : '') + h.toString(16);
\r
5304 v = v.replace('#', '');
\r
5305 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
\r
5307 return(color.length > 5 ? color.toLowerCase() : defaultValue);
\r
5311 * Wrapper for setting style properties, also takes single object parameter of multiple styles.
\r
5312 * @param {String/Object} property The style property to be set, or an object of multiple styles.
\r
5313 * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
\r
5314 * @return {Ext.Element} this
\r
5316 setStyle : function(prop, value){
\r
5320 if (!Ext.isObject(prop)) {
\r
5322 tmp[prop] = value;
\r
5325 for (style in prop) {
\r
5326 value = prop[style];
\r
5327 style == 'opacity' ?
\r
5328 this.setOpacity(value) :
\r
5329 this.dom.style[chkCache(style)] = value;
\r
5335 * Set the opacity of the element
\r
5336 * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
\r
5337 * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
\r
5338 * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
\r
5339 * @return {Ext.Element} this
\r
5341 setOpacity : function(opacity, animate){
\r
5345 if(!animate || !me.anim){
\r
5347 var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
\r
5348 val = s.filter.replace(opacityRe, '').replace(trimRe, '');
\r
5351 s.filter = val + (val.length > 0 ? ' ' : '') + opac;
\r
5353 s.opacity = opacity;
\r
5356 me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
\r
5362 * Clears any opacity settings from this element. Required in some cases for IE.
\r
5363 * @return {Ext.Element} this
\r
5365 clearOpacity : function(){
\r
5366 var style = this.dom.style;
\r
5368 if(!Ext.isEmpty(style.filter)){
\r
5369 style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
\r
5372 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
\r
5378 * Returns the offset height of the element
\r
5379 * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
\r
5380 * @return {Number} The element's height
\r
5382 getHeight : function(contentHeight){
\r
5385 h = MATH.max(dom.offsetHeight, dom.clientHeight) || 0;
\r
5386 h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
\r
5387 return h < 0 ? 0 : h;
\r
5391 * Returns the offset width of the element
\r
5392 * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
\r
5393 * @return {Number} The element's width
\r
5395 getWidth : function(contentWidth){
\r
5398 w = MATH.max(dom.offsetWidth, dom.clientWidth) || 0;
\r
5399 w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
\r
5400 return w < 0 ? 0 : w;
\r
5404 * Set the width of this Element.
\r
5405 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
\r
5406 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
\r
5407 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
\r
5409 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
5410 * @return {Ext.Element} this
\r
5412 setWidth : function(width, animate){
\r
5414 width = me.adjustWidth(width);
\r
5415 !animate || !me.anim ?
\r
5416 me.dom.style.width = me.addUnits(width) :
\r
5417 me.anim({width : {to : width}}, me.preanim(arguments, 1));
\r
5422 * Set the height of this Element.
\r
5424 // change the height to 200px and animate with default configuration
\r
5425 Ext.fly('elementId').setHeight(200, true);
\r
5427 // change the height to 150px and animate with a custom configuration
\r
5428 Ext.fly('elId').setHeight(150, {
\r
5429 duration : .5, // animation will have a duration of .5 seconds
\r
5430 // will change the content to "finished"
\r
5431 callback: function(){ this.{@link #update}("finished"); }
\r
5434 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
\r
5435 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
\r
5436 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
\r
5438 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
5439 * @return {Ext.Element} this
\r
5441 setHeight : function(height, animate){
\r
5443 height = me.adjustHeight(height);
\r
5444 !animate || !me.anim ?
\r
5445 me.dom.style.height = me.addUnits(height) :
\r
5446 me.anim({height : {to : height}}, me.preanim(arguments, 1));
\r
5451 * Gets the width of the border(s) for the specified side(s)
\r
5452 * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
\r
5453 * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
\r
5454 * @return {Number} The width of the sides passed added together
\r
5456 getBorderWidth : function(side){
\r
5457 return addStyles.call(this, side, borders);
\r
5461 * Gets the width of the padding(s) for the specified side(s)
\r
5462 * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
\r
5463 * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
\r
5464 * @return {Number} The padding of the sides passed added together
\r
5466 getPadding : function(side){
\r
5467 return addStyles.call(this, side, paddings);
\r
5471 * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
\r
5472 * @return {Ext.Element} this
\r
5474 clip : function(){
\r
5478 if(!data(dom, ISCLIPPED)){
\r
5479 data(dom, ISCLIPPED, true);
\r
5480 data(dom, ORIGINALCLIP, {
\r
5481 o: me.getStyle(OVERFLOW),
\r
5482 x: me.getStyle(OVERFLOWX),
\r
5483 y: me.getStyle(OVERFLOWY)
\r
5485 me.setStyle(OVERFLOW, HIDDEN);
\r
5486 me.setStyle(OVERFLOWX, HIDDEN);
\r
5487 me.setStyle(OVERFLOWY, HIDDEN);
\r
5493 * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
\r
5494 * @return {Ext.Element} this
\r
5496 unclip : function(){
\r
5500 if(data(dom, ISCLIPPED)){
\r
5501 data(dom, ISCLIPPED, false);
\r
5502 var o = data(dom, ORIGINALCLIP);
\r
5504 me.setStyle(OVERFLOW, o.o);
\r
5507 me.setStyle(OVERFLOWX, o.x);
\r
5510 me.setStyle(OVERFLOWY, o.y);
\r
5516 addStyles : addStyles,
\r
5521 * @class Ext.Element
\r
5524 // special markup used throughout Ext when box wrapping elements
\r
5525 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>';
\r
5527 Ext.Element.addMethods(function(){
\r
5528 var INTERNAL = "_internal";
\r
5531 * More flexible version of {@link #setStyle} for setting style properties.
\r
5532 * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
\r
5533 * a function which returns such a specification.
\r
5534 * @return {Ext.Element} this
\r
5536 applyStyles : function(style){
\r
5537 Ext.DomHelper.applyStyles(this.dom, style);
\r
5542 * Returns an object with properties matching the styles requested.
\r
5543 * For example, el.getStyles('color', 'font-size', 'width') might return
\r
5544 * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
\r
5545 * @param {String} style1 A style name
\r
5546 * @param {String} style2 A style name
\r
5547 * @param {String} etc.
\r
5548 * @return {Object} The style object
\r
5550 getStyles : function(){
\r
5552 Ext.each(arguments, function(v) {
\r
5553 ret[v] = this.getStyle(v);
\r
5559 getStyleSize : function(){
\r
5565 if(s.width && s.width != 'auto'){
\r
5566 w = parseInt(s.width, 10);
\r
5567 if(me.isBorderBox()){
\r
5568 w -= me.getFrameWidth('lr');
\r
5571 if(s.height && s.height != 'auto'){
\r
5572 h = parseInt(s.height, 10);
\r
5573 if(me.isBorderBox()){
\r
5574 h -= me.getFrameWidth('tb');
\r
5577 return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
\r
5580 // private ==> used by ext full
\r
5581 setOverflow : function(v){
\r
5582 var dom = this.dom;
\r
5583 if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug
\r
5584 dom.style.overflow = 'hidden';
\r
5585 (function(){dom.style.overflow = 'auto';}).defer(1);
\r
5587 dom.style.overflow = v;
\r
5592 * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
\r
5593 * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
\r
5594 * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button},
\r
5595 * {@link Ext.Panel} when <tt>{@link Ext.Panel#frame frame=true}</tt>, {@link Ext.Window}). The markup
\r
5596 * is of this form:</p>
\r
5598 Ext.Element.boxMarkup =
\r
5599 '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div>
\r
5600 <div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div>
\r
5601 <div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
\r
5603 * <p>Example usage:</p>
\r
5606 Ext.get("foo").boxWrap();
\r
5608 // You can also add a custom class and use CSS inheritance rules to customize the box look.
\r
5609 // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
\r
5610 // for how to create a custom box wrap style.
\r
5611 Ext.get("foo").boxWrap().addClass("x-box-blue");
\r
5613 * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
\r
5614 * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
\r
5615 * this name to make the overall effect work, so if you supply an alternate base class, make sure you
\r
5616 * also supply all of the necessary rules.
\r
5617 * @return {Ext.Element} this
\r
5619 boxWrap : function(cls){
\r
5620 cls = cls || 'x-box';
\r
5621 var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + String.format(Ext.Element.boxMarkup, cls) + "</div>")); //String.format('<div class="{0}">'+Ext.Element.boxMarkup+'</div>', cls)));
\r
5622 Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
\r
5627 * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
\r
5628 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
\r
5629 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
\r
5630 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
\r
5631 * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
\r
5633 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
\r
5634 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
\r
5635 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
\r
5637 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
5638 * @return {Ext.Element} this
\r
5640 setSize : function(width, height, animate){
\r
5642 if(Ext.isObject(width)){ // in case of object from getSize()
\r
5643 height = width.height;
\r
5644 width = width.width;
\r
5646 width = me.adjustWidth(width);
\r
5647 height = me.adjustHeight(height);
\r
5648 if(!animate || !me.anim){
\r
5649 me.dom.style.width = me.addUnits(width);
\r
5650 me.dom.style.height = me.addUnits(height);
\r
5652 me.anim({width: {to: width}, height: {to: height}}, me.preanim(arguments, 2));
\r
5658 * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
\r
5659 * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
\r
5660 * if a height has not been set using CSS.
\r
5661 * @return {Number}
\r
5663 getComputedHeight : function(){
\r
5665 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
\r
5667 h = parseInt(me.getStyle('height'), 10) || 0;
\r
5668 if(!me.isBorderBox()){
\r
5669 h += me.getFrameWidth('tb');
\r
5676 * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
\r
5677 * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
\r
5678 * if a width has not been set using CSS.
\r
5679 * @return {Number}
\r
5681 getComputedWidth : function(){
\r
5682 var w = Math.max(this.dom.offsetWidth, this.dom.clientWidth);
\r
5684 w = parseInt(this.getStyle('width'), 10) || 0;
\r
5685 if(!this.isBorderBox()){
\r
5686 w += this.getFrameWidth('lr');
\r
5693 * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
\r
5694 for more information about the sides.
\r
5695 * @param {String} sides
\r
5696 * @return {Number}
\r
5698 getFrameWidth : function(sides, onlyContentBox){
\r
5699 return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
\r
5703 * Sets up event handlers to add and remove a css class when the mouse is over this element
\r
5704 * @param {String} className
\r
5705 * @return {Ext.Element} this
\r
5707 addClassOnOver : function(className){
\r
5710 Ext.fly(this, INTERNAL).addClass(className);
\r
5713 Ext.fly(this, INTERNAL).removeClass(className);
\r
5720 * Sets up event handlers to add and remove a css class when this element has the focus
\r
5721 * @param {String} className
\r
5722 * @return {Ext.Element} this
\r
5724 addClassOnFocus : function(className){
\r
5725 this.on("focus", function(){
\r
5726 Ext.fly(this, INTERNAL).addClass(className);
\r
5728 this.on("blur", function(){
\r
5729 Ext.fly(this, INTERNAL).removeClass(className);
\r
5735 * Sets up event handlers to add and remove a css class when the mouse is down and then up on this element (a click effect)
\r
5736 * @param {String} className
\r
5737 * @return {Ext.Element} this
\r
5739 addClassOnClick : function(className){
\r
5740 var dom = this.dom;
\r
5741 this.on("mousedown", function(){
\r
5742 Ext.fly(dom, INTERNAL).addClass(className);
\r
5743 var d = Ext.getDoc(),
\r
5745 Ext.fly(dom, INTERNAL).removeClass(className);
\r
5746 d.removeListener("mouseup", fn);
\r
5748 d.on("mouseup", fn);
\r
5754 * Returns the width and height of the viewport.
\r
5756 var vpSize = Ext.getBody().getViewSize();
\r
5758 // all Windows created afterwards will have a default value of 90% height and 95% width
\r
5759 Ext.Window.override({
\r
5760 width: vpSize.width * 0.9,
\r
5761 height: vpSize.height * 0.95
\r
5763 // To handle window resizing you would have to hook onto onWindowResize.
\r
5765 * @return {Object} An object containing the viewport's size {width: (viewport width), height: (viewport height)}
\r
5767 getViewSize : function(){
\r
5768 var doc = document,
\r
5770 extdom = Ext.lib.Dom,
\r
5771 isDoc = (d == doc || d == doc.body);
\r
5772 return { width : (isDoc ? extdom.getViewWidth() : d.clientWidth),
\r
5773 height : (isDoc ? extdom.getViewHeight() : d.clientHeight) };
\r
5777 * Returns the size of the element.
\r
5778 * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
\r
5779 * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
\r
5781 getSize : function(contentSize){
\r
5782 return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
\r
5786 * Forces the browser to repaint this element
\r
5787 * @return {Ext.Element} this
\r
5789 repaint : function(){
\r
5790 var dom = this.dom;
\r
5791 this.addClass("x-repaint");
\r
5792 setTimeout(function(){
\r
5793 Ext.fly(dom).removeClass("x-repaint");
\r
5799 * Disables text selection for this element (normalized across browsers)
\r
5800 * @return {Ext.Element} this
\r
5802 unselectable : function(){
\r
5803 this.dom.unselectable = "on";
\r
5804 return this.swallowEvent("selectstart", true).
\r
5805 applyStyles("-moz-user-select:none;-khtml-user-select:none;").
\r
5806 addClass("x-unselectable");
\r
5810 * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
\r
5811 * then it returns the calculated width of the sides (see getPadding)
\r
5812 * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
\r
5813 * @return {Object/Number}
\r
5815 getMargins : function(side){
\r
5818 hash = {t:"top", l:"left", r:"right", b: "bottom"},
\r
5822 for (key in me.margins){
\r
5823 o[hash[key]] = parseInt(me.getStyle(me.margins[key]), 10) || 0;
\r
5827 return me.addStyles.call(me, side, me.margins);
\r
5832 * @class Ext.Element
\r
5835 var D = Ext.lib.Dom,
\r
5839 BOTTOM = "bottom",
\r
5840 POSITION = "position",
\r
5841 STATIC = "static",
\r
5842 RELATIVE = "relative",
\r
5844 ZINDEX = "z-index";
\r
5846 function animTest(args, animate, i) {
\r
5847 return this.preanim && !!animate ? this.preanim(args, i) : false
\r
5850 Ext.Element.addMethods({
\r
5852 * 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).
\r
5853 * @return {Number} The X position of the element
\r
5855 getX : function(){
\r
5856 return D.getX(this.dom);
\r
5860 * Gets the current Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5861 * @return {Number} The Y position of the element
\r
5863 getY : function(){
\r
5864 return D.getY(this.dom);
\r
5868 * Gets the current position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5869 * @return {Array} The XY position of the element
\r
5871 getXY : function(){
\r
5872 return D.getXY(this.dom);
\r
5876 * Returns the offsets of this element from the passed element. Both element must be part of the DOM tree and not have display:none to have page coordinates.
\r
5877 * @param {Mixed} element The element to get the offsets from.
\r
5878 * @return {Array} The XY page offsets (e.g. [100, -200])
\r
5880 getOffsetsTo : function(el){
\r
5881 var o = this.getXY(),
\r
5882 e = Ext.fly(el, '_internal').getXY();
\r
5883 return [o[0]-e[0],o[1]-e[1]];
\r
5887 * Sets the X position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5888 * @param {Number} The X position of the element
\r
5889 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
5890 * @return {Ext.Element} this
\r
5892 setX : function(x, animate){
\r
5893 return this.setXY([x, this.getY()], animTest.call(this, arguments, animate, 1));
\r
5897 * Sets the Y position of the element based on page coordinates. Element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5898 * @param {Number} The Y position of the element
\r
5899 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
5900 * @return {Ext.Element} this
\r
5902 setY : function(y, animate){
\r
5903 return this.setXY([this.getX(), y], animTest.call(this, arguments, animate, 1));
\r
5907 * Sets the element's left position directly using CSS style (instead of {@link #setX}).
\r
5908 * @param {String} left The left CSS property value
\r
5909 * @return {Ext.Element} this
\r
5911 setLeft : function(left){
\r
5912 this.setStyle(LEFT, this.addUnits(left));
\r
5917 * Sets the element's top position directly using CSS style (instead of {@link #setY}).
\r
5918 * @param {String} top The top CSS property value
\r
5919 * @return {Ext.Element} this
\r
5921 setTop : function(top){
\r
5922 this.setStyle(TOP, this.addUnits(top));
\r
5927 * Sets the element's CSS right style.
\r
5928 * @param {String} right The right CSS property value
\r
5929 * @return {Ext.Element} this
\r
5931 setRight : function(right){
\r
5932 this.setStyle(RIGHT, this.addUnits(right));
\r
5937 * Sets the element's CSS bottom style.
\r
5938 * @param {String} bottom The bottom CSS property value
\r
5939 * @return {Ext.Element} this
\r
5941 setBottom : function(bottom){
\r
5942 this.setStyle(BOTTOM, this.addUnits(bottom));
\r
5947 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
5948 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5949 * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
\r
5950 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
5951 * @return {Ext.Element} this
\r
5953 setXY : function(pos, animate){
\r
5955 if(!animate || !me.anim){
\r
5956 D.setXY(me.dom, pos);
\r
5958 me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
\r
5964 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
5965 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5966 * @param {Number} x X value for new position (coordinates are page-based)
\r
5967 * @param {Number} y Y value for new position (coordinates are page-based)
\r
5968 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
5969 * @return {Ext.Element} this
\r
5971 setLocation : function(x, y, animate){
\r
5972 return this.setXY([x, y], animTest.call(this, arguments, animate, 2));
\r
5976 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
5977 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
5978 * @param {Number} x X value for new position (coordinates are page-based)
\r
5979 * @param {Number} y Y value for new position (coordinates are page-based)
\r
5980 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
5981 * @return {Ext.Element} this
\r
5983 moveTo : function(x, y, animate){
\r
5984 return this.setXY([x, y], animTest.call(this, arguments, animate, 2));
\r
5988 * Gets the left X coordinate
\r
5989 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
5990 * @return {Number}
\r
5992 getLeft : function(local){
\r
5993 return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
\r
5997 * Gets the right X coordinate of the element (element X position + element width)
\r
5998 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
5999 * @return {Number}
\r
6001 getRight : function(local){
\r
6003 return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
\r
6007 * Gets the top Y coordinate
\r
6008 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6009 * @return {Number}
\r
6011 getTop : function(local) {
\r
6012 return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
\r
6016 * Gets the bottom Y coordinate of the element (element Y position + element height)
\r
6017 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6018 * @return {Number}
\r
6020 getBottom : function(local){
\r
6022 return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
\r
6026 * Initializes positioning on this element. If a desired position is not passed, it will make the
\r
6027 * the element positioned relative IF it is not already positioned.
\r
6028 * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
\r
6029 * @param {Number} zIndex (optional) The zIndex to apply
\r
6030 * @param {Number} x (optional) Set the page X position
\r
6031 * @param {Number} y (optional) Set the page Y position
\r
6033 position : function(pos, zIndex, x, y){
\r
6036 if(!pos && me.isStyle(POSITION, STATIC)){
\r
6037 me.setStyle(POSITION, RELATIVE);
\r
6039 me.setStyle(POSITION, pos);
\r
6042 me.setStyle(ZINDEX, zIndex);
\r
6044 if(x || y) me.setXY([x || false, y || false]);
\r
6048 * Clear positioning back to the default when the document was loaded
\r
6049 * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
\r
6050 * @return {Ext.Element} this
\r
6052 clearPositioning : function(value){
\r
6053 value = value || '';
\r
6066 * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
\r
6067 * snapshot before performing an update and then restoring the element.
\r
6068 * @return {Object}
\r
6070 getPositioning : function(){
\r
6071 var l = this.getStyle(LEFT);
\r
6072 var t = this.getStyle(TOP);
\r
6074 "position" : this.getStyle(POSITION),
\r
6076 "right" : l ? "" : this.getStyle(RIGHT),
\r
6078 "bottom" : t ? "" : this.getStyle(BOTTOM),
\r
6079 "z-index" : this.getStyle(ZINDEX)
\r
6084 * Set positioning with an object returned by getPositioning().
\r
6085 * @param {Object} posCfg
\r
6086 * @return {Ext.Element} this
\r
6088 setPositioning : function(pc){
\r
6090 style = me.dom.style;
\r
6094 if(pc.right == AUTO){
\r
6097 if(pc.bottom == AUTO){
\r
6098 style.bottom = "";
\r
6105 * Translates the passed page coordinates into left/top css values for this element
\r
6106 * @param {Number/Array} x The page x or an array containing [x, y]
\r
6107 * @param {Number} y (optional) The page y, required if x is not an array
\r
6108 * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
\r
6110 translatePoints : function(x, y){
\r
6111 y = isNaN(x[1]) ? y : x[1];
\r
6112 x = isNaN(x[0]) ? x : x[0];
\r
6114 relative = me.isStyle(POSITION, RELATIVE),
\r
6116 l = parseInt(me.getStyle(LEFT), 10),
\r
6117 t = parseInt(me.getStyle(TOP), 10);
\r
6119 l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
\r
6120 t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
\r
6122 return {left: (x - o[0] + l), top: (y - o[1] + t)};
\r
6125 animTest : animTest
\r
6128 * @class Ext.Element
\r
6130 Ext.Element.addMethods({
\r
6132 * Sets the element's box. Use getBox() on another element to get a box obj. If animate is true then width, height, x and y will be animated concurrently.
\r
6133 * @param {Object} box The box to fill {x, y, width, height}
\r
6134 * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
\r
6135 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6136 * @return {Ext.Element} this
\r
6138 setBox : function(box, adjust, animate){
\r
6142 if((adjust && !me.autoBoxAdjust) && !me.isBorderBox()){
\r
6143 w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
\r
6144 h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
\r
6146 me.setBounds(box.x, box.y, w, h, me.animTest.call(me, arguments, animate, 2));
\r
6151 * Return a box {x, y, width, height} that can be used to set another elements
\r
6152 * size/location to match this element.
\r
6153 * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
\r
6154 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
\r
6155 * @return {Object} box An object in the format {x, y, width, height}
\r
6157 getBox : function(contentBox, local) {
\r
6162 getBorderWidth = me.getBorderWidth,
\r
6163 getPadding = me.getPadding,
\r
6171 left = parseInt(me.getStyle("left"), 10) || 0;
\r
6172 top = parseInt(me.getStyle("top"), 10) || 0;
\r
6175 var el = me.dom, w = el.offsetWidth, h = el.offsetHeight, bx;
\r
6177 bx = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: w, height: h};
\r
6179 l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
\r
6180 r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
\r
6181 t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
\r
6182 b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
\r
6183 bx = {x: xy[0]+l, y: xy[1]+t, 0: xy[0]+l, 1: xy[1]+t, width: w-(l+r), height: h-(t+b)};
\r
6185 bx.right = bx.x + bx.width;
\r
6186 bx.bottom = bx.y + bx.height;
\r
6191 * Move this element relative to its current position.
\r
6192 * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
\r
6193 * @param {Number} distance How far to move the element in pixels
\r
6194 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6195 * @return {Ext.Element} this
\r
6197 move : function(direction, distance, animate){
\r
6202 left = [x - distance, y],
\r
6203 right = [x + distance, y],
\r
6204 top = [x, y - distance],
\r
6205 bottom = [x, y + distance],
\r
6219 direction = direction.toLowerCase();
\r
6220 me.moveTo(hash[direction][0], hash[direction][1], me.animTest.call(me, arguments, animate, 2));
\r
6224 * Quick set left and top adding default units
\r
6225 * @param {String} left The left CSS property value
\r
6226 * @param {String} top The top CSS property value
\r
6227 * @return {Ext.Element} this
\r
6229 setLeftTop : function(left, top){
\r
6231 style = me.dom.style;
\r
6232 style.left = me.addUnits(left);
\r
6233 style.top = me.addUnits(top);
\r
6238 * Returns the region of the given element.
\r
6239 * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
\r
6240 * @return {Region} A Ext.lib.Region containing "top, left, bottom, right" member data.
\r
6242 getRegion : function(){
\r
6243 return Ext.lib.Dom.getRegion(this.dom);
\r
6247 * Sets the element's position and size in one shot. If animation is true then width, height, x and y will be animated concurrently.
\r
6248 * @param {Number} x X value for new position (coordinates are page-based)
\r
6249 * @param {Number} y Y value for new position (coordinates are page-based)
\r
6250 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
\r
6251 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
\r
6252 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
\r
6254 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
\r
6255 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
\r
6256 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
\r
6258 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6259 * @return {Ext.Element} this
\r
6261 setBounds : function(x, y, width, height, animate){
\r
6263 if (!animate || !me.anim) {
\r
6264 me.setSize(width, height);
\r
6265 me.setLocation(x, y);
\r
6267 me.anim({points: {to: [x, y]},
\r
6268 width: {to: me.adjustWidth(width)},
\r
6269 height: {to: me.adjustHeight(height)}},
\r
6270 me.preanim(arguments, 4),
\r
6277 * Sets the element's position and size the specified region. If animation is true then width, height, x and y will be animated concurrently.
\r
6278 * @param {Ext.lib.Region} region The region to fill
\r
6279 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6280 * @return {Ext.Element} this
\r
6282 setRegion : function(region, animate) {
\r
6283 return this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, this.animTest.call(this, arguments, animate, 1));
\r
6286 * @class Ext.Element
\r
6288 Ext.Element.addMethods({
\r
6290 * Returns true if this element is scrollable.
\r
6291 * @return {Boolean}
\r
6293 isScrollable : function(){
\r
6294 var dom = this.dom;
\r
6295 return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
\r
6299 * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
\r
6300 * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
\r
6301 * @param {Number} value The new scroll value.
\r
6302 * @return {Element} this
\r
6304 scrollTo : function(side, value){
\r
6305 this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
\r
6310 * Returns the current scroll position of the element.
\r
6311 * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
\r
6313 getScroll : function(){
\r
6314 var d = this.dom,
\r
6317 docElement = doc.documentElement,
\r
6322 if(d == doc || d == body){
\r
6323 if(Ext.isIE && Ext.isStrict){
\r
6324 l = docElement.scrollLeft;
\r
6325 t = docElement.scrollTop;
\r
6327 l = window.pageXOffset;
\r
6328 t = window.pageYOffset;
\r
6330 ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
\r
6332 ret = {left: d.scrollLeft, top: d.scrollTop};
\r
6337 * @class Ext.Element
\r
6339 Ext.Element.addMethods({
\r
6341 * Scrolls this element the specified scroll point. It does NOT do bounds checking so if you scroll to a weird value it will try to do it. For auto bounds checking, use scroll().
\r
6342 * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
\r
6343 * @param {Number} value The new scroll value
\r
6344 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6345 * @return {Element} this
\r
6347 scrollTo : function(side, value, animate){
\r
6348 var tester = /top/i,
\r
6349 prop = "scroll" + (tester.test(side) ? "Top" : "Left"),
\r
6352 if (!animate || !me.anim) {
\r
6353 dom[prop] = value;
\r
6355 me.anim({scroll: {to: tester.test(prop) ? [dom[prop], value] : [value, dom[prop]]}},
\r
6356 me.preanim(arguments, 2), 'scroll');
\r
6362 * Scrolls this element into view within the passed container.
\r
6363 * @param {Mixed} container (optional) The container element to scroll (defaults to document.body). Should be a
\r
6364 * string (id), dom node, or Ext.Element.
\r
6365 * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
\r
6366 * @return {Ext.Element} this
\r
6368 scrollIntoView : function(container, hscroll){
\r
6369 var c = Ext.getDom(container) || Ext.getBody().dom,
\r
6371 o = this.getOffsetsTo(c),
\r
6372 l = o[0] + c.scrollLeft,
\r
6373 t = o[1] + c.scrollTop,
\r
6374 b = t + el.offsetHeight,
\r
6375 r = l + el.offsetWidth,
\r
6376 ch = c.clientHeight,
\r
6377 ct = parseInt(c.scrollTop, 10),
\r
6378 cl = parseInt(c.scrollLeft, 10),
\r
6380 cr = cl + c.clientWidth;
\r
6382 if (el.offsetHeight > ch || t < ct) {
\r
6384 } else if (b > cb){
\r
6385 c.scrollTop = b-ch;
\r
6387 c.scrollTop = c.scrollTop; // corrects IE, other browsers will ignore
\r
6389 if(hscroll !== false){
\r
6390 if(el.offsetWidth > c.clientWidth || l < cl){
\r
6393 c.scrollLeft = r - c.clientWidth;
\r
6395 c.scrollLeft = c.scrollLeft;
\r
6401 scrollChildIntoView : function(child, hscroll){
\r
6402 Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
\r
6406 * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
\r
6407 * within this element's scrollable range.
\r
6408 * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
\r
6409 * @param {Number} distance How far to scroll the element in pixels
\r
6410 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6411 * @return {Boolean} Returns true if a scroll was triggered or false if the element
\r
6412 * was scrolled as far as it could go.
\r
6414 scroll : function(direction, distance, animate){
\r
6415 if(!this.isScrollable()){
\r
6418 var el = this.dom,
\r
6419 l = el.scrollLeft, t = el.scrollTop,
\r
6420 w = el.scrollWidth, h = el.scrollHeight,
\r
6421 cw = el.clientWidth, ch = el.clientHeight,
\r
6422 scrolled = false, v,
\r
6424 l: Math.min(l + distance, w-cw),
\r
6425 r: v = Math.max(l - distance, 0),
\r
6426 t: Math.max(t - distance, 0),
\r
6427 b: Math.min(t + distance, h-ch)
\r
6432 direction = direction.substr(0, 1);
\r
6433 if((v = hash[direction]) > -1){
\r
6435 this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.preanim(arguments, 2));
\r
6440 * @class Ext.Element
\r
6443 * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
\r
6447 Ext.Element.VISIBILITY = 1;
\r
6449 * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
\r
6453 Ext.Element.DISPLAY = 2;
\r
6455 Ext.Element.addMethods(function(){
\r
6456 var VISIBILITY = "visibility",
\r
6457 DISPLAY = "display",
\r
6458 HIDDEN = "hidden",
\r
6460 ORIGINALDISPLAY = 'originalDisplay',
\r
6461 VISMODE = 'visibilityMode',
\r
6462 ELDISPLAY = Ext.Element.DISPLAY,
\r
6463 data = Ext.Element.data,
\r
6464 getDisplay = function(dom){
\r
6465 var d = data(dom, ORIGINALDISPLAY);
\r
6466 if(d === undefined){
\r
6467 data(dom, ORIGINALDISPLAY, d = '');
\r
6471 getVisMode = function(dom){
\r
6472 var m = data(dom, VISMODE);
\r
6473 if(m === undefined){
\r
6474 data(dom, VISMODE, m = 1)
\r
6481 * The element's default display mode (defaults to "")
\r
6484 originalDisplay : "",
\r
6485 visibilityMode : 1,
\r
6488 * Sets the element's visibility mode. When setVisible() is called it
\r
6489 * will use this to determine whether to set the visibility or the display property.
\r
6490 * @param visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
\r
6491 * @return {Ext.Element} this
\r
6493 setVisibilityMode : function(visMode){
\r
6494 data(this.dom, VISMODE, visMode);
\r
6499 * Perform custom animation on this element.
\r
6500 * <div><ul class="mdetail-params">
\r
6501 * <li><u>Animation Properties</u></li>
\r
6503 * <p>The Animation Control Object enables gradual transitions for any member of an
\r
6504 * element's style object that takes a numeric value including but not limited to
\r
6505 * these properties:</p><div><ul class="mdetail-params">
\r
6506 * <li><tt>bottom, top, left, right</tt></li>
\r
6507 * <li><tt>height, width</tt></li>
\r
6508 * <li><tt>margin, padding</tt></li>
\r
6509 * <li><tt>borderWidth</tt></li>
\r
6510 * <li><tt>opacity</tt></li>
\r
6511 * <li><tt>fontSize</tt></li>
\r
6512 * <li><tt>lineHeight</tt></li>
\r
6516 * <li><u>Animation Property Attributes</u></li>
\r
6518 * <p>Each Animation Property is a config object with optional properties:</p>
\r
6519 * <div><ul class="mdetail-params">
\r
6520 * <li><tt>by</tt>* : relative change - start at current value, change by this value</li>
\r
6521 * <li><tt>from</tt> : ignore current value, start from this value</li>
\r
6522 * <li><tt>to</tt>* : start at current value, go to this value</li>
\r
6523 * <li><tt>unit</tt> : any allowable unit specification</li>
\r
6524 * <p>* do not specify both <tt>to</tt> and <tt>by</tt> for an animation property</p>
\r
6527 * <li><u>Animation Types</u></li>
\r
6529 * <p>The supported animation types:</p><div><ul class="mdetail-params">
\r
6530 * <li><tt>'run'</tt> : Default
\r
6532 var el = Ext.get('complexEl');
\r
6534 // animation control object
\r
6536 borderWidth: {to: 3, from: 0},
\r
6537 opacity: {to: .3, from: 1},
\r
6538 height: {to: 50, from: el.getHeight()},
\r
6539 width: {to: 300, from: el.getWidth()},
\r
6540 top : {by: - 100, unit: 'px'},
\r
6542 0.35, // animation duration
\r
6544 'easeOut', // easing method
\r
6545 'run' // animation type ('run','color','motion','scroll')
\r
6549 * <li><tt>'color'</tt>
\r
6550 * <p>Animates transition of background, text, or border colors.</p>
\r
6553 // animation control object
\r
6555 color: { to: '#06e' },
\r
6556 backgroundColor: { to: '#e06' }
\r
6558 0.35, // animation duration
\r
6560 'easeOut', // easing method
\r
6561 'color' // animation type ('run','color','motion','scroll')
\r
6566 * <li><tt>'motion'</tt>
\r
6567 * <p>Animates the motion of an element to/from specific points using optional bezier
\r
6568 * way points during transit.</p>
\r
6571 // animation control object
\r
6573 borderWidth: {to: 3, from: 0},
\r
6574 opacity: {to: .3, from: 1},
\r
6575 height: {to: 50, from: el.getHeight()},
\r
6576 width: {to: 300, from: el.getWidth()},
\r
6577 top : {by: - 100, unit: 'px'},
\r
6579 to: [50, 100], // go to this point
\r
6580 control: [ // optional bezier way points
\r
6586 3000, // animation duration (milliseconds!)
\r
6588 'easeOut', // easing method
\r
6589 'motion' // animation type ('run','color','motion','scroll')
\r
6593 * <li><tt>'scroll'</tt>
\r
6594 * <p>Animate horizontal or vertical scrolling of an overflowing page element.</p>
\r
6597 // animation control object
\r
6599 scroll: {to: [400, 300]}
\r
6601 0.35, // animation duration
\r
6603 'easeOut', // easing method
\r
6604 'scroll' // animation type ('run','color','motion','scroll')
\r
6612 * @param {Object} args The animation control args
\r
6613 * @param {Float} duration (optional) How long the animation lasts in seconds (defaults to <tt>.35</tt>)
\r
6614 * @param {Function} onComplete (optional) Function to call when animation completes
\r
6615 * @param {String} easing (optional) {@link Ext.Fx#easing} method to use (defaults to <tt>'easeOut'</tt>)
\r
6616 * @param {String} animType (optional) <tt>'run'</tt> is the default. Can also be <tt>'color'</tt>,
\r
6617 * <tt>'motion'</tt>, or <tt>'scroll'</tt>
\r
6618 * @return {Ext.Element} this
\r
6620 animate : function(args, duration, onComplete, easing, animType){
\r
6621 this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
\r
6626 * @private Internal animation call
\r
6628 anim : function(args, opt, animType, defaultDur, defaultEase, cb){
\r
6629 animType = animType || 'run';
\r
6632 anim = Ext.lib.Anim[animType](
\r
6635 (opt.duration || defaultDur) || .35,
\r
6636 (opt.easing || defaultEase) || 'easeOut',
\r
6638 if(cb) cb.call(me);
\r
6639 if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
\r
6647 // private legacy anim prep
\r
6648 preanim : function(a, i){
\r
6649 return !a[i] ? false : (Ext.isObject(a[i]) ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
\r
6653 * Checks whether the element is currently visible using both visibility and display properties.
\r
6654 * @return {Boolean} True if the element is currently visible, else false
\r
6656 isVisible : function() {
\r
6657 return !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE);
\r
6661 * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
\r
6662 * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
\r
6663 * @param {Boolean} visible Whether the element is visible
\r
6664 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6665 * @return {Ext.Element} this
\r
6667 setVisible : function(visible, animate){
\r
6670 isDisplay = getVisMode(this.dom) == ELDISPLAY;
\r
6672 if (!animate || !me.anim) {
\r
6674 me.setDisplayed(visible);
\r
6677 dom.style.visibility = visible ? "visible" : HIDDEN;
\r
6680 // closure for composites
\r
6682 me.setOpacity(.01);
\r
6683 me.setVisible(true);
\r
6685 me.anim({opacity: { to: (visible?1:0) }},
\r
6686 me.preanim(arguments, 1),
\r
6692 dom.style[isDisplay ? DISPLAY : VISIBILITY] = (isDisplay) ? NONE : HIDDEN;
\r
6693 Ext.fly(dom).setOpacity(1);
\r
6701 * Toggles the element's visibility or display, depending on visibility mode.
\r
6702 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6703 * @return {Ext.Element} this
\r
6705 toggle : function(animate){
\r
6707 me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
\r
6712 * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
\r
6713 * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
\r
6714 * @return {Ext.Element} this
\r
6716 setDisplayed : function(value) {
\r
6717 if(typeof value == "boolean"){
\r
6718 value = value ? getDisplay(this.dom) : NONE;
\r
6720 this.setStyle(DISPLAY, value);
\r
6725 fixDisplay : function(){
\r
6727 if(me.isStyle(DISPLAY, NONE)){
\r
6728 me.setStyle(VISIBILITY, HIDDEN);
\r
6729 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
\r
6730 if(me.isStyle(DISPLAY, NONE)){ // if that fails, default to block
\r
6731 me.setStyle(DISPLAY, "block");
\r
6737 * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
\r
6738 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6739 * @return {Ext.Element} this
\r
6741 hide : function(animate){
\r
6742 this.setVisible(false, this.preanim(arguments, 0));
\r
6747 * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
\r
6748 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6749 * @return {Ext.Element} this
\r
6751 show : function(animate){
\r
6752 this.setVisible(true, this.preanim(arguments, 0));
\r
6757 * @class Ext.Element
\r
6759 Ext.Element.addMethods(
\r
6761 var VISIBILITY = "visibility",
\r
6762 DISPLAY = "display",
\r
6763 HIDDEN = "hidden",
\r
6765 XMASKED = "x-masked",
\r
6766 XMASKEDRELATIVE = "x-masked-relative",
\r
6767 data = Ext.Element.data;
\r
6771 * Checks whether the element is currently visible using both visibility and display properties.
\r
6772 * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
\r
6773 * @return {Boolean} True if the element is currently visible, else false
\r
6775 isVisible : function(deep) {
\r
6776 var vis = !this.isStyle(VISIBILITY,HIDDEN) && !this.isStyle(DISPLAY,NONE),
\r
6777 p = this.dom.parentNode;
\r
6778 if(deep !== true || !vis){
\r
6781 while(p && !/body/i.test(p.tagName)){
\r
6782 if(!Ext.fly(p, '_isVisible').isVisible()){
\r
6791 * Returns true if display is not "none"
\r
6792 * @return {Boolean}
\r
6794 isDisplayed : function() {
\r
6795 return !this.isStyle(DISPLAY, NONE);
\r
6799 * Convenience method for setVisibilityMode(Element.DISPLAY)
\r
6800 * @param {String} display (optional) What to set display to when visible
\r
6801 * @return {Ext.Element} this
\r
6803 enableDisplayMode : function(display){
\r
6804 this.setVisibilityMode(Ext.Element.DISPLAY);
\r
6805 if(!Ext.isEmpty(display)){
\r
6806 data(this.dom, 'originalDisplay', display);
\r
6812 * Puts a mask over this element to disable user interaction. Requires core.css.
\r
6813 * This method can only be applied to elements which accept child nodes.
\r
6814 * @param {String} msg (optional) A message to display in the mask
\r
6815 * @param {String} msgCls (optional) A css class to apply to the msg element
\r
6816 * @return {Element} The mask element
\r
6818 mask : function(msg, msgCls){
\r
6821 dh = Ext.DomHelper,
\r
6822 EXTELMASKMSG = "ext-el-mask-msg",
\r
6826 if(me.getStyle("position") == "static"){
\r
6827 me.addClass(XMASKEDRELATIVE);
\r
6829 if((el = data(dom, 'maskMsg'))){
\r
6832 if((el = data(dom, 'mask'))){
\r
6836 mask = dh.append(dom, {cls : "ext-el-mask"}, true);
\r
6837 data(dom, 'mask', mask);
\r
6839 me.addClass(XMASKED);
\r
6840 mask.setDisplayed(true);
\r
6841 if(typeof msg == 'string'){
\r
6842 var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
\r
6843 data(dom, 'maskMsg', mm);
\r
6844 mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
\r
6845 mm.dom.firstChild.innerHTML = msg;
\r
6846 mm.setDisplayed(true);
\r
6849 if(Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto'){ // ie will not expand full height automatically
\r
6850 mask.setSize(undefined, me.getHeight());
\r
6856 * Removes a previously applied mask.
\r
6858 unmask : function(){
\r
6861 mask = data(dom, 'mask'),
\r
6862 maskMsg = data(dom, 'maskMsg');
\r
6866 data(dom, 'maskMsg', undefined);
\r
6869 data(dom, 'mask', undefined);
\r
6871 me.removeClass([XMASKED, XMASKEDRELATIVE]);
\r
6875 * Returns true if this element is masked
\r
6876 * @return {Boolean}
\r
6878 isMasked : function(){
\r
6879 var m = data(this.dom, 'mask');
\r
6880 return m && m.isVisible();
\r
6884 * Creates an iframe shim for this element to keep selects and other windowed objects from
\r
6885 * showing through.
\r
6886 * @return {Ext.Element} The new shim element
\r
6888 createShim : function(){
\r
6889 var el = document.createElement('iframe'),
\r
6891 el.frameBorder = '0';
\r
6892 el.className = 'ext-shim';
\r
6893 if(Ext.isIE && Ext.isSecure){
\r
6894 el.src = Ext.SSL_SECURE_URL;
\r
6896 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
\r
6897 shim.autoBoxAdjust = false;
\r
6902 * @class Ext.Element
\r
6904 Ext.Element.addMethods({
\r
6906 * Convenience method for constructing a KeyMap
\r
6907 * @param {Number/Array/Object/String} key Either a string with the keys to listen for, the numeric key code, array of key codes or an object with the following options:
\r
6908 * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
\r
6909 * @param {Function} fn The function to call
\r
6910 * @param {Object} scope (optional) The scope of the function
\r
6911 * @return {Ext.KeyMap} The KeyMap created
\r
6913 addKeyListener : function(key, fn, scope){
\r
6915 if(!Ext.isObject(key) || Ext.isArray(key)){
\r
6924 shift : key.shift,
\r
6931 return new Ext.KeyMap(this, config);
\r
6935 * Creates a KeyMap for this element
\r
6936 * @param {Object} config The KeyMap config. See {@link Ext.KeyMap} for more details
\r
6937 * @return {Ext.KeyMap} The KeyMap created
\r
6939 addKeyMap : function(config){
\r
6940 return new Ext.KeyMap(this, config);
\r
6945 UNDEFINED = undefined,
\r
6952 BOTTOM = "bottom",
\r
6955 HEIGHT = "height",
\r
6957 POINTS = "points",
\r
6958 HIDDEN = "hidden",
\r
6959 ABSOLUTE = "absolute",
\r
6960 VISIBLE = "visible",
\r
6961 MOTION = "motion",
\r
6962 POSITION = "position",
\r
6963 EASEOUT = "easeOut",
\r
6965 * Use a light flyweight here since we are using so many callbacks and are always assured a DOM element
\r
6967 flyEl = new Ext.Element.Flyweight(),
\r
6969 getObject = function(o){
\r
6972 fly = function(dom){
\r
6974 flyEl.id = Ext.id(dom);
\r
6978 * Queueing now stored outside of the element due to closure issues
\r
6980 getQueue = function(id){
\r
6984 return queues[id];
\r
6986 setQueue = function(id, value){
\r
6987 queues[id] = value;
\r
6990 //Notifies Element that fx methods are available
\r
6991 Ext.enableFx = TRUE;
\r
6995 * <p>A class to provide basic animation and visual effects support. <b>Note:</b> This class is automatically applied
\r
6996 * to the {@link Ext.Element} interface when included, so all effects calls should be performed via {@link Ext.Element}.
\r
6997 * Conversely, since the effects are not actually defined in {@link Ext.Element}, Ext.Fx <b>must</b> be
\r
6998 * {@link Ext#enableFx included} in order for the Element effects to work.</p><br/>
\r
7000 * <p><b><u>Method Chaining</u></b></p>
\r
7001 * <p>It is important to note that although the Fx methods and many non-Fx Element methods support "method chaining" in that
\r
7002 * they return the Element object itself as the method return value, it is not always possible to mix the two in a single
\r
7003 * method chain. The Fx methods use an internal effects queue so that each effect can be properly timed and sequenced.
\r
7004 * Non-Fx methods, on the other hand, have no such internal queueing and will always execute immediately. For this reason,
\r
7005 * while it may be possible to mix certain Fx and non-Fx method calls in a single chain, it may not always provide the
\r
7006 * expected results and should be done with care. Also see <tt>{@link #callback}</tt>.</p><br/>
\r
7008 * <p><b><u>Anchor Options for Motion Effects</u></b></p>
\r
7009 * <p>Motion effects support 8-way anchoring, meaning that you can choose one of 8 different anchor points on the Element
\r
7010 * that will serve as either the start or end point of the animation. Following are all of the supported anchor positions:</p>
\r
7013 ----- -----------------------------
\r
7014 tl The top left corner
\r
7015 t The center of the top edge
\r
7016 tr The top right corner
\r
7017 l The center of the left edge
\r
7018 r The center of the right edge
\r
7019 bl The bottom left corner
\r
7020 b The center of the bottom edge
\r
7021 br The bottom right corner
\r
7023 * <b>Note</b>: some Fx methods accept specific custom config parameters. The options shown in the Config Options
\r
7024 * section below are common options that can be passed to any Fx method unless otherwise noted.</b>
\r
7026 * @cfg {Function} callback A function called when the effect is finished. Note that effects are queued internally by the
\r
7027 * Fx class, so a callback is not required to specify another effect -- effects can simply be chained together
\r
7028 * and called in sequence (see note for <b><u>Method Chaining</u></b> above), for example:<pre><code>
\r
7029 * el.slideIn().highlight();
\r
7031 * The callback is intended for any additional code that should run once a particular effect has completed. The Element
\r
7032 * being operated upon is passed as the first parameter.
\r
7034 * @cfg {Object} scope The scope of the <tt>{@link #callback}</tt> function
\r
7036 * @cfg {String} easing A valid Ext.lib.Easing value for the effect:</p><div class="mdetail-params"><ul>
\r
7037 * <li><b><tt>backBoth</tt></b></li>
\r
7038 * <li><b><tt>backIn</tt></b></li>
\r
7039 * <li><b><tt>backOut</tt></b></li>
\r
7040 * <li><b><tt>bounceBoth</tt></b></li>
\r
7041 * <li><b><tt>bounceIn</tt></b></li>
\r
7042 * <li><b><tt>bounceOut</tt></b></li>
\r
7043 * <li><b><tt>easeBoth</tt></b></li>
\r
7044 * <li><b><tt>easeBothStrong</tt></b></li>
\r
7045 * <li><b><tt>easeIn</tt></b></li>
\r
7046 * <li><b><tt>easeInStrong</tt></b></li>
\r
7047 * <li><b><tt>easeNone</tt></b></li>
\r
7048 * <li><b><tt>easeOut</tt></b></li>
\r
7049 * <li><b><tt>easeOutStrong</tt></b></li>
\r
7050 * <li><b><tt>elasticBoth</tt></b></li>
\r
7051 * <li><b><tt>elasticIn</tt></b></li>
\r
7052 * <li><b><tt>elasticOut</tt></b></li>
\r
7055 * @cfg {String} afterCls A css class to apply after the effect
\r
7056 * @cfg {Number} duration The length of time (in seconds) that the effect should last
\r
7058 * @cfg {Number} endOpacity Only applicable for {@link #fadeIn} or {@link #fadeOut}, a number between
\r
7059 * <tt>0</tt> and <tt>1</tt> inclusive to configure the ending opacity value.
\r
7061 * @cfg {Boolean} remove Whether the Element should be removed from the DOM and destroyed after the effect finishes
\r
7062 * @cfg {Boolean} useDisplay Whether to use the <i>display</i> CSS property instead of <i>visibility</i> when hiding Elements (only applies to
\r
7063 * effects that end with the element being visually hidden, ignored otherwise)
\r
7064 * @cfg {String/Object/Function} afterStyle A style specification string, e.g. <tt>"width:100px"</tt>, or an object
\r
7065 * in the form <tt>{width:"100px"}</tt>, or a function which returns such a specification that will be applied to the
\r
7066 * Element after the effect finishes.
\r
7067 * @cfg {Boolean} block Whether the effect should block other effects from queueing while it runs
\r
7068 * @cfg {Boolean} concurrent Whether to allow subsequently-queued effects to run at the same time as the current effect, or to ensure that they run in sequence
\r
7069 * @cfg {Boolean} stopFx Whether preceding effects should be stopped and removed before running current effect (only applies to non blocking effects)
\r
7073 // private - calls the function taking arguments from the argHash based on the key. Returns the return value of the function.
\r
7074 // this is useful for replacing switch statements (for example).
\r
7075 switchStatements : function(key, fn, argHash){
\r
7076 return fn.apply(this, argHash[key]);
\r
7080 * Slides the element into view. An anchor point can be optionally passed to set the point of
\r
7081 * origin for the slide effect. This function automatically handles wrapping the element with
\r
7082 * a fixed-size container if needed. See the Fx class overview for valid anchor point options.
\r
7085 // default: slide the element in from the top
\r
7088 // custom: slide the element in from the right with a 2-second duration
\r
7089 el.slideIn('r', { duration: 2 });
\r
7091 // common config options shown with default values
\r
7093 easing: 'easeOut',
\r
7097 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
\r
7098 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7099 * @return {Ext.Element} The Element
\r
7101 slideIn : function(anchor, o){
\r
7117 anchor = anchor || "t";
\r
7119 me.queueFx(o, function(){
\r
7120 xy = fly(dom).getXY();
\r
7121 // fix display to visibility
\r
7122 fly(dom).fixDisplay();
\r
7124 // restore values after effect
\r
7125 r = fly(dom).getFxRestore();
\r
7126 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
\r
7127 b.right = b.x + b.width;
\r
7128 b.bottom = b.y + b.height;
\r
7130 // fixed size for slide
\r
7131 fly(dom).setWidth(b.width).setHeight(b.height);
\r
7134 wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
\r
7136 st.visibility = VISIBLE;
\r
7137 st.position = ABSOLUTE;
\r
7139 // clear out temp styles after slide and unwrap
\r
7141 fly(dom).fxUnwrap(wrap, r.pos, o);
\r
7142 st.width = r.width;
\r
7143 st.height = r.height;
\r
7144 fly(dom).afterFx(o);
\r
7147 // time to calculate the positions
\r
7148 pt = {to: [b.x, b.y]};
\r
7149 bw = {to: b.width};
\r
7150 bh = {to: b.height};
\r
7152 function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
\r
7154 fly(wrap).setWidth(ww).setHeight(wh);
\r
7155 if(fly(wrap)[sXY]){
\r
7156 fly(wrap)[sXY](sXYval);
\r
7158 style[s1] = style[s2] = "0";
\r
7171 args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
\r
7172 t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
\r
7173 l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
\r
7174 r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
\r
7175 b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
\r
7176 tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
\r
7177 bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
\r
7178 br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
\r
7179 tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
\r
7182 st.visibility = VISIBLE;
\r
7185 arguments.callee.anim = fly(wrap).fxanim(args,
\r
7196 * Slides the element out of view. An anchor point can be optionally passed to set the end point
\r
7197 * for the slide effect. When the effect is completed, the element will be hidden (visibility =
\r
7198 * 'hidden') but block elements will still take up space in the document. The element must be removed
\r
7199 * from the DOM using the 'remove' config option if desired. This function automatically handles
\r
7200 * wrapping the element with a fixed-size container if needed. See the Fx class overview for valid anchor point options.
\r
7203 // default: slide the element out to the top
\r
7206 // custom: slide the element out to the right with a 2-second duration
\r
7207 el.slideOut('r', { duration: 2 });
\r
7209 // common config options shown with default values
\r
7210 el.slideOut('t', {
\r
7211 easing: 'easeOut',
\r
7217 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
\r
7218 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7219 * @return {Ext.Element} The Element
\r
7221 slideOut : function(anchor, o){
\r
7233 anchor = anchor || "t";
\r
7235 me.queueFx(o, function(){
\r
7237 // restore values after effect
\r
7238 r = fly(dom).getFxRestore();
\r
7239 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
\r
7240 b.right = b.x + b.width;
\r
7241 b.bottom = b.y + b.height;
\r
7243 // fixed size for slide
\r
7244 fly(dom).setWidth(b.width).setHeight(b.height);
\r
7247 wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
\r
7249 st.visibility = VISIBLE;
\r
7250 st.position = ABSOLUTE;
\r
7251 fly(wrap).setWidth(b.width).setHeight(b.height);
\r
7254 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7255 fly(dom).fxUnwrap(wrap, r.pos, o);
\r
7256 st.width = r.width;
\r
7257 st.height = r.height;
\r
7258 fly(dom).afterFx(o);
\r
7261 function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
\r
7264 style[s1] = style[s2] = "0";
\r
7276 a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
\r
7277 t : [st, LEFT, BOTTOM, HEIGHT, zero],
\r
7278 l : [st, RIGHT, TOP, WIDTH, zero],
\r
7279 r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
\r
7280 b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
\r
7281 tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
\r
7282 bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
\r
7283 br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
\r
7284 tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
\r
7287 arguments.callee.anim = fly(wrap).fxanim(a,
\r
7298 * Fades the element out while slowly expanding it in all directions. When the effect is completed, the
\r
7299 * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
\r
7300 * The element must be removed from the DOM using the 'remove' config option if desired.
\r
7306 // common config options shown with default values
\r
7308 easing: 'easeOut',
\r
7314 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7315 * @return {Ext.Element} The Element
\r
7317 puff : function(o){
\r
7326 me.queueFx(o, function(){
\r
7327 width = fly(dom).getWidth();
\r
7328 height = fly(dom).getHeight();
\r
7329 fly(dom).clearOpacity();
\r
7332 // restore values after effect
\r
7333 r = fly(dom).getFxRestore();
\r
7336 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7337 fly(dom).clearOpacity();
\r
7338 fly(dom).setPositioning(r.pos);
\r
7339 st.width = r.width;
\r
7340 st.height = r.height;
\r
7342 fly(dom).afterFx(o);
\r
7345 arguments.callee.anim = fly(dom).fxanim({
\r
7346 width : {to : fly(dom).adjustWidth(width * 2)},
\r
7347 height : {to : fly(dom).adjustHeight(height * 2)},
\r
7348 points : {by : [-width * .5, -height * .5]},
\r
7349 opacity : {to : 0},
\r
7350 fontSize: {to : 200, unit: "%"}
\r
7362 * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
\r
7363 * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
\r
7364 * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
\r
7370 // all config options shown with default values
\r
7378 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7379 * @return {Ext.Element} The Element
\r
7381 switchOff : function(o){
\r
7388 me.queueFx(o, function(){
\r
7389 fly(dom).clearOpacity();
\r
7392 // restore values after effect
\r
7393 r = fly(dom).getFxRestore();
\r
7396 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7397 fly(dom).clearOpacity();
\r
7398 fly(dom).setPositioning(r.pos);
\r
7399 st.width = r.width;
\r
7400 st.height = r.height;
\r
7401 fly(dom).afterFx(o);
\r
7404 fly(dom).fxanim({opacity : {to : 0.3}},
\r
7410 fly(dom).clearOpacity();
\r
7413 height : {to : 1},
\r
7414 points : {by : [0, fly(dom).getHeight() * .5]}
\r
7428 * Highlights the Element by setting a color (applies to the background-color by default, but can be
\r
7429 * changed using the "attr" config option) and then fading back to the original color. If no original
\r
7430 * color is available, you should provide the "endColor" config option which will be cleared after the animation.
\r
7433 // default: highlight background to yellow
\r
7436 // custom: highlight foreground text to blue for 2 seconds
\r
7437 el.highlight("0000ff", { attr: 'color', duration: 2 });
\r
7439 // common config options shown with default values
\r
7440 el.highlight("ffff9c", {
\r
7441 attr: "background-color", //can be any valid CSS property (attribute) that supports a color value
\r
7442 endColor: (current color) or "ffffff",
\r
7447 * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
\r
7448 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7449 * @return {Ext.Element} The Element
\r
7451 highlight : function(color, o){
\r
7455 attr = o.attr || "backgroundColor",
\r
7459 me.queueFx(o, function(){
\r
7460 fly(dom).clearOpacity();
\r
7464 dom.style[attr] = restore;
\r
7465 fly(dom).afterFx(o);
\r
7467 restore = dom.style[attr];
\r
7468 a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
\r
7469 arguments.callee.anim = fly(dom).fxanim(a,
\r
7480 * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
\r
7483 // default: a single light blue ripple
\r
7486 // custom: 3 red ripples lasting 3 seconds total
\r
7487 el.frame("ff0000", 3, { duration: 3 });
\r
7489 // common config options shown with default values
\r
7490 el.frame("C3DAF9", 1, {
\r
7491 duration: 1 //duration of each individual ripple.
\r
7492 // Note: Easing is not configurable and will be ignored if included
\r
7495 * @param {String} color (optional) The color of the border. Should be a 6 char hex color without the leading # (defaults to light blue: 'C3DAF9').
\r
7496 * @param {Number} count (optional) The number of ripples to display (defaults to 1)
\r
7497 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7498 * @return {Ext.Element} The Element
\r
7500 frame : function(color, count, o){
\r
7507 me.queueFx(o, function(){
\r
7508 color = color || "#C3DAF9"
\r
7509 if(color.length == 6){
\r
7510 color = "#" + color;
\r
7512 count = count || 1;
\r
7515 var xy = fly(dom).getXY(),
\r
7516 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
\r
7517 queue = function(){
\r
7518 proxy = fly(document.body || document.documentElement).createChild({
\r
7520 visbility: HIDDEN,
\r
7521 position : ABSOLUTE,
\r
7522 "z-index": 35000, // yee haw
\r
7523 border : "0px solid " + color
\r
7526 return proxy.queueFx({}, animFn);
\r
7530 arguments.callee.anim = {
\r
7532 stop: function() {
\r
7538 function animFn(){
\r
7539 var scale = Ext.isBorderBox ? 2 : 1;
\r
7540 active = proxy.anim({
\r
7541 top : {from : b.y, to : b.y - 20},
\r
7542 left : {from : b.x, to : b.x - 20},
\r
7543 borderWidth : {from : 0, to : 10},
\r
7544 opacity : {from : 1, to : 0},
\r
7545 height : {from : b.height, to : b.height + 20 * scale},
\r
7546 width : {from : b.width, to : b.width + 20 * scale}
\r
7548 duration: o.duration || 1,
\r
7549 callback: function() {
\r
7551 --count > 0 ? queue() : fly(dom).afterFx(o);
\r
7554 arguments.callee.anim = {
\r
7567 * Creates a pause before any subsequent queued effects begin. If there are
\r
7568 * no effects queued after the pause it will have no effect.
\r
7573 * @param {Number} seconds The length of time to pause (in seconds)
\r
7574 * @return {Ext.Element} The Element
\r
7576 pause : function(seconds){
\r
7577 var dom = this.dom,
\r
7580 this.queueFx({}, function(){
\r
7581 t = setTimeout(function(){
\r
7582 fly(dom).afterFx({});
\r
7583 }, seconds * 1000);
\r
7584 arguments.callee.anim = {
\r
7588 fly(dom).afterFx({});
\r
7596 * Fade an element in (from transparent to opaque). The ending opacity can be specified
\r
7597 * using the <tt>{@link #endOpacity}</tt> config option.
\r
7600 // default: fade in from opacity 0 to 100%
\r
7603 // custom: fade in from opacity 0 to 75% over 2 seconds
\r
7604 el.fadeIn({ endOpacity: .75, duration: 2});
\r
7606 // common config options shown with default values
\r
7608 endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
\r
7609 easing: 'easeOut',
\r
7613 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7614 * @return {Ext.Element} The Element
\r
7616 fadeIn : function(o){
\r
7620 to = o.endOpacity || 1;
\r
7622 me.queueFx(o, function(){
\r
7623 fly(dom).setOpacity(0);
\r
7624 fly(dom).fixDisplay();
\r
7625 dom.style.visibility = VISIBLE;
\r
7626 arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
\r
7627 o, NULL, .5, EASEOUT, function(){
\r
7629 fly(dom).clearOpacity();
\r
7631 fly(dom).afterFx(o);
\r
7638 * Fade an element out (from opaque to transparent). The ending opacity can be specified
\r
7639 * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
\r
7640 * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
\r
7643 // default: fade out from the element's current opacity to 0
\r
7646 // custom: fade out from the element's current opacity to 25% over 2 seconds
\r
7647 el.fadeOut({ endOpacity: .25, duration: 2});
\r
7649 // common config options shown with default values
\r
7651 endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
\r
7652 easing: 'easeOut',
\r
7658 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7659 * @return {Ext.Element} The Element
\r
7661 fadeOut : function(o){
\r
7665 style = dom.style,
\r
7666 to = o.endOpacity || 0;
\r
7668 me.queueFx(o, function(){
\r
7669 arguments.callee.anim = fly(dom).fxanim({
\r
7670 opacity : {to : to}},
\r
7677 Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
\r
7678 style.display = "none" :
\r
7679 style.visibility = HIDDEN;
\r
7681 fly(dom).clearOpacity();
\r
7683 fly(dom).afterFx(o);
\r
7690 * Animates the transition of an element's dimensions from a starting height/width
\r
7691 * to an ending height/width. This method is a convenience implementation of {@link shift}.
\r
7694 // change height and width to 100x100 pixels
\r
7695 el.scale(100, 100);
\r
7697 // common config options shown with default values. The height and width will default to
\r
7698 // the element's existing values if passed as null.
\r
7700 [element's width],
\r
7701 [element's height], {
\r
7702 easing: 'easeOut',
\r
7707 * @param {Number} width The new width (pass undefined to keep the original width)
\r
7708 * @param {Number} height The new height (pass undefined to keep the original height)
\r
7709 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7710 * @return {Ext.Element} The Element
\r
7712 scale : function(w, h, o){
\r
7713 this.shift(Ext.apply({}, o, {
\r
7721 * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
\r
7722 * Any of these properties not specified in the config object will not be changed. This effect
\r
7723 * requires that at least one new dimension, position or opacity setting must be passed in on
\r
7724 * the config object in order for the function to have any effect.
\r
7727 // slide the element horizontally to x position 200 while changing the height and opacity
\r
7728 el.shift({ x: 200, height: 50, opacity: .8 });
\r
7730 // common config options shown with default values.
\r
7732 width: [element's width],
\r
7733 height: [element's height],
\r
7734 x: [element's x position],
\r
7735 y: [element's y position],
\r
7736 opacity: [element's opacity],
\r
7737 easing: 'easeOut',
\r
7741 * @param {Object} options Object literal with any of the Fx config options
\r
7742 * @return {Ext.Element} The Element
\r
7744 shift : function(o){
\r
7746 var dom = this.dom,
\r
7749 this.queueFx(o, function(){
\r
7750 for (var prop in o) {
\r
7751 if (o[prop] != UNDEFINED) {
\r
7752 a[prop] = {to : o[prop]};
\r
7756 a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
\r
7757 a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
\r
7759 if (a.x || a.y || a.xy) {
\r
7760 a.points = a.xy ||
\r
7761 {to : [ a.x ? a.x.to : fly(dom).getX(),
\r
7762 a.y ? a.y.to : fly(dom).getY()]};
\r
7765 arguments.callee.anim = fly(dom).fxanim(a,
\r
7771 fly(dom).afterFx(o);
\r
7778 * Slides the element while fading it out of view. An anchor point can be optionally passed to set the
\r
7779 * ending point of the effect.
\r
7782 // default: slide the element downward while fading out
\r
7785 // custom: slide the element out to the right with a 2-second duration
\r
7786 el.ghost('r', { duration: 2 });
\r
7788 // common config options shown with default values
\r
7790 easing: 'easeOut',
\r
7796 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
\r
7797 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7798 * @return {Ext.Element} The Element
\r
7800 ghost : function(anchor, o){
\r
7805 a = {opacity: {to: 0}, points: {}},
\r
7811 anchor = anchor || "b";
\r
7813 me.queueFx(o, function(){
\r
7814 // restore values after effect
\r
7815 r = fly(dom).getFxRestore();
\r
7816 w = fly(dom).getWidth();
\r
7817 h = fly(dom).getHeight();
\r
7820 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7821 fly(dom).clearOpacity();
\r
7822 fly(dom).setPositioning(r.pos);
\r
7823 st.width = r.width;
\r
7824 st.height = r.height;
\r
7825 fly(dom).afterFx(o);
\r
7828 pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
\r
7839 arguments.callee.anim = fly(dom).fxanim(a,
\r
7849 * Ensures that all effects queued after syncFx is called on the element are
\r
7850 * run concurrently. This is the opposite of {@link #sequenceFx}.
\r
7851 * @return {Ext.Element} The Element
\r
7853 syncFx : function(){
\r
7855 me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
\r
7857 concurrent : TRUE,
\r
7864 * Ensures that all effects queued after sequenceFx is called on the element are
\r
7865 * run in sequence. This is the opposite of {@link #syncFx}.
\r
7866 * @return {Ext.Element} The Element
\r
7868 sequenceFx : function(){
\r
7870 me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
\r
7872 concurrent : FALSE,
\r
7879 nextFx : function(){
\r
7880 var ef = getQueue(this.dom.id)[0];
\r
7887 * Returns true if the element has any effects actively running or queued, else returns false.
\r
7888 * @return {Boolean} True if element has active effects, else false
\r
7890 hasActiveFx : function(){
\r
7891 return getQueue(this.dom.id)[0];
\r
7895 * Stops any running effects and clears the element's internal effects queue if it contains
\r
7896 * any additional effects that haven't started yet.
\r
7897 * @return {Ext.Element} The Element
\r
7899 stopFx : function(finish){
\r
7902 if(me.hasActiveFx()){
\r
7903 var cur = getQueue(id)[0];
\r
7904 if(cur && cur.anim){
\r
7905 if(cur.anim.isAnimated){
\r
7906 setQueue(id, [cur]); //clear
\r
7907 cur.anim.stop(finish !== undefined ? finish : TRUE);
\r
7917 beforeFx : function(o){
\r
7918 if(this.hasActiveFx() && !o.concurrent){
\r
7929 * Returns true if the element is currently blocking so that no other effect can be queued
\r
7930 * until this effect is finished, else returns false if blocking is not set. This is commonly
\r
7931 * used to ensure that an effect initiated by a user action runs to completion prior to the
\r
7932 * same effect being restarted (e.g., firing only one effect even if the user clicks several times).
\r
7933 * @return {Boolean} True if blocking, else false
\r
7935 hasFxBlock : function(){
\r
7936 var q = getQueue(this.dom.id);
\r
7937 return q && q[0] && q[0].block;
\r
7941 queueFx : function(o, fn){
\r
7943 if(!me.hasFxBlock()){
\r
7944 Ext.applyIf(o, me.fxDefaults);
\r
7945 if(!o.concurrent){
\r
7946 var run = me.beforeFx(o);
\r
7947 fn.block = o.block;
\r
7948 getQueue(me.dom.id).push(fn);
\r
7960 fxWrap : function(pos, o, vis){
\r
7961 var dom = this.dom,
\r
7964 if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
\r
7965 if(o.fixPosition){
\r
7966 wrapXY = fly(dom).getXY();
\r
7968 var div = document.createElement("div");
\r
7969 div.style.visibility = vis;
\r
7970 wrap = dom.parentNode.insertBefore(div, dom);
\r
7971 fly(wrap).setPositioning(pos);
\r
7972 if(fly(wrap).isStyle(POSITION, "static")){
\r
7973 fly(wrap).position("relative");
\r
7975 fly(dom).clearPositioning('auto');
\r
7977 wrap.appendChild(dom);
\r
7979 fly(wrap).setXY(wrapXY);
\r
7986 fxUnwrap : function(wrap, pos, o){
\r
7987 var dom = this.dom;
\r
7988 fly(dom).clearPositioning();
\r
7989 fly(dom).setPositioning(pos);
\r
7991 wrap.parentNode.insertBefore(dom, wrap);
\r
7992 fly(wrap).remove();
\r
7997 getFxRestore : function(){
\r
7998 var st = this.dom.style;
\r
7999 return {pos: this.getPositioning(), width: st.width, height : st.height};
\r
8003 afterFx : function(o){
\r
8004 var dom = this.dom,
\r
8006 notConcurrent = !o.concurrent;
\r
8008 fly(dom).setStyle(o.afterStyle);
\r
8011 fly(dom).addClass(o.afterCls);
\r
8013 if(o.remove == TRUE){
\r
8014 fly(dom).remove();
\r
8016 if(notConcurrent){
\r
8017 getQueue(id).shift();
\r
8020 o.callback.call(o.scope, fly(dom));
\r
8022 if(notConcurrent){
\r
8023 fly(dom).nextFx();
\r
8028 fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
\r
8029 animType = animType || 'run';
\r
8031 var anim = Ext.lib.Anim[animType](
\r
8034 (opt.duration || defaultDur) || .35,
\r
8035 (opt.easing || defaultEase) || EASEOUT,
\r
8044 // backwards compat
\r
8045 Ext.Fx.resize = Ext.Fx.scale;
\r
8047 //When included, Ext.Fx is automatically applied to Element so that all basic
\r
8048 //effects are available directly via the Element API
\r
8049 Ext.Element.addMethods(Ext.Fx);
\r
8051 * @class Ext.CompositeElementLite
\r
8052 * Flyweight composite class. Reuses the same Ext.Element for element operations.
\r
8054 var els = Ext.select("#some-el div.some-class");
\r
8055 // or select directly from an existing element
\r
8056 var el = Ext.get('some-el');
\r
8057 el.select('div.some-class');
\r
8059 els.setWidth(100); // all elements become 100 width
\r
8060 els.hide(true); // all elements fade out and hide
\r
8062 els.setWidth(100).hide(true);
\r
8063 </code></pre><br><br>
\r
8064 * <b>NOTE: Although they are not listed, this class supports all of the set/update methods of Ext.Element. All Ext.Element
\r
8065 * actions will be performed on all the elements in this collection.</b>
\r
8067 Ext.CompositeElementLite = function(els, root){
\r
8068 this.elements = [];
\r
8069 this.add(els, root);
\r
8070 this.el = new Ext.Element.Flyweight();
\r
8073 Ext.CompositeElementLite.prototype = {
\r
8074 isComposite: true,
\r
8076 * Returns the number of elements in this composite
\r
8079 getCount : function(){
\r
8080 return this.elements.length;
\r
8082 add : function(els){
\r
8084 if (Ext.isArray(els)) {
\r
8085 this.elements = this.elements.concat(els);
\r
8087 var yels = this.elements;
\r
8088 Ext.each(els, function(e) {
\r
8095 invoke : function(fn, args){
\r
8096 var els = this.elements,
\r
8098 Ext.each(els, function(e) {
\r
8100 Ext.Element.prototype[fn].apply(el, args);
\r
8105 * Returns a flyweight Element of the dom element object at the specified index
\r
8106 * @param {Number} index
\r
8107 * @return {Ext.Element}
\r
8109 item : function(index){
\r
8111 if(!me.elements[index]){
\r
8114 me.el.dom = me.elements[index];
\r
8118 // fixes scope with flyweight
\r
8119 addListener : function(eventName, handler, scope, opt){
\r
8120 Ext.each(this.elements, function(e) {
\r
8121 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
\r
8126 * Calls the passed function passing (el, this, index) for each element in this composite. <b>The element
\r
8127 * passed is the flyweight (shared) Ext.Element instance, so if you require a
\r
8128 * a reference to the dom node, use el.dom.</b>
\r
8129 * @param {Function} fn The function to call
\r
8130 * @param {Object} scope (optional) The <i>this</i> object (defaults to the element)
\r
8131 * @return {CompositeElement} this
\r
8133 each : function(fn, scope){
\r
8137 Ext.each(me.elements, function(e,i) {
\r
8139 return fn.call(scope || el, el, me, i);
\r
8145 * Find the index of the passed element within the composite collection.
\r
8146 * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
\r
8147 * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
\r
8149 indexOf : function(el){
\r
8150 return this.elements.indexOf(Ext.getDom(el));
\r
8154 * Replaces the specified element with the passed element.
\r
8155 * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
\r
8157 * @param {Mixed} replacement The id of an element or the Element itself.
\r
8158 * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
\r
8159 * @return {CompositeElement} this
\r
8161 replaceElement : function(el, replacement, domReplace){
\r
8162 var index = !isNaN(el) ? el : this.indexOf(el),
\r
8165 replacement = Ext.getDom(replacement);
\r
8167 d = this.elements[index];
\r
8168 d.parentNode.insertBefore(replacement, d);
\r
8169 Ext.removeNode(d);
\r
8171 this.elements.splice(index, 1, replacement);
\r
8177 * Removes all elements.
\r
8179 clear : function(){
\r
8180 this.elements = [];
\r
8184 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
\r
8188 ElProto = Ext.Element.prototype,
\r
8189 CelProto = Ext.CompositeElementLite.prototype;
\r
8191 for(fnName in ElProto){
\r
8192 if(Ext.isFunction(ElProto[fnName])){
\r
8193 (function(fnName){
\r
8194 CelProto[fnName] = CelProto[fnName] || function(){
\r
8195 return this.invoke(fnName, arguments);
\r
8197 }).call(CelProto, fnName);
\r
8204 Ext.Element.selectorFunction = Ext.DomQuery.select;
\r
8208 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8209 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8210 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8211 * @param {String/Array} selector The CSS selector or an array of elements
\r
8212 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object) <b>Not supported in core</b>
\r
8213 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8214 * @return {CompositeElementLite/CompositeElement}
\r
8215 * @member Ext.Element
\r
8218 Ext.Element.select = function(selector, unique, root){
\r
8220 if(typeof selector == "string"){
\r
8221 els = Ext.Element.selectorFunction(selector, root);
\r
8222 }else if(selector.length !== undefined){
\r
8225 throw "Invalid selector";
\r
8227 return new Ext.CompositeElementLite(els);
\r
8230 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8231 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8232 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8233 * @param {String/Array} selector The CSS selector or an array of elements
\r
8234 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8235 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8236 * @return {CompositeElementLite/CompositeElement}
\r
8240 Ext.select = Ext.Element.select;/**
\r
8241 * @class Ext.CompositeElementLite
\r
8243 Ext.apply(Ext.CompositeElementLite.prototype, {
\r
8244 addElements : function(els, root){
\r
8248 if(typeof els == "string"){
\r
8249 els = Ext.Element.selectorFunction(els, root);
\r
8251 var yels = this.elements;
\r
8252 Ext.each(els, function(e) {
\r
8253 yels.push(Ext.get(e));
\r
8259 * Clears this composite and adds the elements returned by the passed selector.
\r
8260 * @param {String/Array} els A string CSS selector, an array of elements or an element
\r
8261 * @return {CompositeElement} this
\r
8263 fill : function(els){
\r
8264 this.elements = [];
\r
8270 * Returns the first Element
\r
8271 * @return {Ext.Element}
\r
8273 first : function(){
\r
8274 return this.item(0);
\r
8278 * Returns the last Element
\r
8279 * @return {Ext.Element}
\r
8281 last : function(){
\r
8282 return this.item(this.getCount()-1);
\r
8286 * Returns true if this composite contains the passed element
\r
8287 * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
\r
8290 contains : function(el){
\r
8291 return this.indexOf(el) != -1;
\r
8295 * Filters this composite to only elements that match the passed selector.
\r
8296 * @param {String} selector A string CSS selector
\r
8297 * @return {CompositeElement} this
\r
8299 filter : function(selector){
\r
8301 this.each(function(el){
\r
8302 if(el.is(selector)){
\r
8303 els[els.length] = el.dom;
\r
8311 * Removes the specified element(s).
\r
8312 * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
\r
8313 * or an array of any of those.
\r
8314 * @param {Boolean} removeDom (optional) True to also remove the element from the document
\r
8315 * @return {CompositeElement} this
\r
8317 removeElement : function(keys, removeDom){
\r
8319 els = this.elements,
\r
8321 Ext.each(keys, function(val){
\r
8322 if ((el = (els[val] || els[val = me.indexOf(val)]))) {
\r
8327 Ext.removeNode(el);
\r
8330 els.splice(val, 1);
\r
8337 * @class Ext.CompositeElement
\r
8338 * @extends Ext.CompositeElementLite
\r
8339 * Standard composite class. Creates a Ext.Element for every element in the collection.
\r
8341 * <b>NOTE: Although they are not listed, this class supports all of the set/update methods of Ext.Element. All Ext.Element
\r
8342 * actions will be performed on all the elements in this collection.</b>
\r
8344 * All methods return <i>this</i> and can be chained.
\r
8346 var els = Ext.select("#some-el div.some-class", true);
\r
8347 // or select directly from an existing element
\r
8348 var el = Ext.get('some-el');
\r
8349 el.select('div.some-class', true);
\r
8351 els.setWidth(100); // all elements become 100 width
\r
8352 els.hide(true); // all elements fade out and hide
\r
8354 els.setWidth(100).hide(true);
\r
8357 Ext.CompositeElement = function(els, root){
\r
8358 this.elements = [];
\r
8359 this.add(els, root);
\r
8362 Ext.extend(Ext.CompositeElement, Ext.CompositeElementLite, {
\r
8363 invoke : function(fn, args){
\r
8364 Ext.each(this.elements, function(e) {
\r
8365 Ext.Element.prototype[fn].apply(e, args);
\r
8371 * Adds elements to this composite.
\r
8372 * @param {String/Array} els A string CSS selector, an array of elements or an element
\r
8373 * @return {CompositeElement} this
\r
8375 add : function(els, root){
\r
8379 if(typeof els == "string"){
\r
8380 els = Ext.Element.selectorFunction(els, root);
\r
8382 var yels = this.elements;
\r
8383 Ext.each(els, function(e) {
\r
8384 yels.push(Ext.get(e));
\r
8390 * Returns the Element object at the specified index
\r
8391 * @param {Number} index
\r
8392 * @return {Ext.Element}
\r
8394 item : function(index){
\r
8395 return this.elements[index] || null;
\r
8399 indexOf : function(el){
\r
8400 return this.elements.indexOf(Ext.get(el));
\r
8403 filter : function(selector){
\r
8407 Ext.each(me.elements, function(el) {
\r
8408 if(el.is(selector)){
\r
8409 out.push(Ext.get(el));
\r
8412 me.elements = out;
\r
8417 * Calls the passed function passing (el, this, index) for each element in this composite.
\r
8418 * @param {Function} fn The function to call
\r
8419 * @param {Object} scope (optional) The <i>this</i> object (defaults to the element)
\r
8420 * @return {CompositeElement} this
\r
8422 each : function(fn, scope){
\r
8423 Ext.each(this.elements, function(e,i) {
\r
8424 return fn.call(scope || e, e, this, i);
\r
8431 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8432 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8433 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8434 * @param {String/Array} selector The CSS selector or an array of elements
\r
8435 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8436 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8437 * @return {CompositeElementLite/CompositeElement}
\r
8438 * @member Ext.Element
\r
8441 Ext.Element.select = function(selector, unique, root){
\r
8443 if(typeof selector == "string"){
\r
8444 els = Ext.Element.selectorFunction(selector, root);
\r
8445 }else if(selector.length !== undefined){
\r
8448 throw "Invalid selector";
\r
8451 return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
\r
8455 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8456 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8457 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8458 * @param {String/Array} selector The CSS selector or an array of elements
\r
8459 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8460 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8461 * @return {CompositeElementLite/CompositeElement}
\r
8462 * @member Ext.Element
\r
8465 Ext.select = Ext.Element.select;(function(){
\r
8466 var BEFOREREQUEST = "beforerequest",
\r
8467 REQUESTCOMPLETE = "requestcomplete",
\r
8468 REQUESTEXCEPTION = "requestexception",
\r
8469 UNDEFINED = undefined,
\r
8476 * @class Ext.data.Connection
\r
8477 * @extends Ext.util.Observable
\r
8478 * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
\r
8479 * either to a configured URL, or to a URL specified at request time.</p>
\r
8480 * <p>Requests made by this class are asynchronous, and will return immediately. No data from
\r
8481 * the server will be available to the statement immediately following the {@link #request} call.
\r
8482 * To process returned data, use a
\r
8483 * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
\r
8484 * in the request options object,
\r
8485 * or an {@link #requestcomplete event listener}.</p>
\r
8486 * <p><h3>File Uploads</h3><a href="#request-option-isUpload" ext:member="request-option-isUpload" ext:cls="Ext.data.Connection">File uploads</a> are not performed using normal "Ajax" techniques, that
\r
8487 * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
\r
8488 * manner with the DOM <tt><form></tt> element temporarily modified to have its
\r
8489 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
\r
8490 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
\r
8491 * but removed after the return data has been gathered.</p>
\r
8492 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
\r
8493 * server is using JSON to send the return object, then the
\r
8494 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
\r
8495 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
\r
8496 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
\r
8497 * "<" as "&lt;", "&" as "&amp;" etc.</p>
\r
8498 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
\r
8499 * is created containing a <tt>responseText</tt> property in order to conform to the
\r
8500 * requirements of event handlers and callbacks.</p>
\r
8501 * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
\r
8502 * and some server technologies (notably JEE) may require some custom processing in order to
\r
8503 * retrieve parameter names and parameter values from the packet content.</p>
\r
8505 * @param {Object} config a configuration object.
\r
8507 Ext.data.Connection = function(config){
\r
8508 Ext.apply(this, config);
\r
8511 * @event beforerequest
\r
8512 * Fires before a network request is made to retrieve a data object.
\r
8513 * @param {Connection} conn This Connection object.
\r
8514 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8518 * @event requestcomplete
\r
8519 * Fires if the request was successfully completed.
\r
8520 * @param {Connection} conn This Connection object.
\r
8521 * @param {Object} response The XHR object containing the response data.
\r
8522 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
\r
8524 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8528 * @event requestexception
\r
8529 * Fires if an error HTTP status was returned from the server.
\r
8530 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
\r
8531 * for details of HTTP status codes.
\r
8532 * @param {Connection} conn This Connection object.
\r
8533 * @param {Object} response The XHR object containing the response data.
\r
8534 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
\r
8536 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8540 Ext.data.Connection.superclass.constructor.call(this);
\r
8544 function handleResponse(response){
\r
8545 this.transId = false;
\r
8546 var options = response.argument.options;
\r
8547 response.argument = options ? options.argument : null;
\r
8548 this.fireEvent(REQUESTCOMPLETE, this, response, options);
\r
8549 if(options.success){
\r
8550 options.success.call(options.scope, response, options);
\r
8552 if(options.callback){
\r
8553 options.callback.call(options.scope, options, true, response);
\r
8558 function handleFailure(response, e){
\r
8559 this.transId = false;
\r
8560 var options = response.argument.options;
\r
8561 response.argument = options ? options.argument : null;
\r
8562 this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
\r
8563 if(options.failure){
\r
8564 options.failure.call(options.scope, response, options);
\r
8566 if(options.callback){
\r
8567 options.callback.call(options.scope, options, false, response);
\r
8572 function doFormUpload(o, ps, url){
\r
8573 var id = Ext.id(),
\r
8575 frame = doc.createElement('iframe'),
\r
8576 form = Ext.getDom(o.form),
\r
8579 encoding = 'multipart/form-data',
\r
8581 target: form.target,
\r
8582 method: form.method,
\r
8583 encoding: form.encoding,
\r
8584 enctype: form.enctype,
\r
8585 action: form.action
\r
8588 Ext.apply(frame, {
\r
8591 className: 'x-hidden',
\r
8592 src: Ext.SSL_SECURE_URL // for IE
\r
8594 doc.body.appendChild(frame);
\r
8596 // This is required so that IE doesn't pop the response up in a new window.
\r
8598 document.frames[id].name = id;
\r
8604 enctype: encoding,
\r
8605 encoding: encoding,
\r
8606 action: url || buf.action
\r
8609 // add dynamic params
\r
8610 ps = Ext.urlDecode(ps, false);
\r
8612 if(ps.hasOwnProperty(k)){
\r
8613 hd = doc.createElement('input');
\r
8614 hd.type = 'hidden';
\r
8615 hd.value = ps[hd.name = k];
\r
8616 form.appendChild(hd);
\r
8623 // bogus response object
\r
8624 r = {responseText : '',
\r
8625 responseXML : null,
\r
8626 argument : o.argument},
\r
8631 doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
\r
8634 if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
\r
8635 r.responseText = firstChild.value;
\r
8637 r.responseText = doc.body.innerHTML;
\r
8640 //in IE the document may still have a body even if returns XML.
\r
8641 r.responseXML = doc.XMLDocument || doc;
\r
8646 Ext.EventManager.removeListener(frame, LOAD, cb, me);
\r
8648 me.fireEvent(REQUESTCOMPLETE, me, r, o);
\r
8650 function runCallback(fn, scope, args){
\r
8651 if(Ext.isFunction(fn)){
\r
8652 fn.apply(scope, args);
\r
8656 runCallback(o.success, o.scope, [r, o]);
\r
8657 runCallback(o.callback, o.scope, [o, true, r]);
\r
8659 if(!me.debugUploads){
\r
8660 setTimeout(function(){Ext.removeNode(frame);}, 100);
\r
8664 Ext.EventManager.on(frame, LOAD, cb, this);
\r
8667 Ext.apply(form, buf);
\r
8668 Ext.each(hiddens, function(h) {
\r
8669 Ext.removeNode(h);
\r
8673 Ext.extend(Ext.data.Connection, Ext.util.Observable, {
\r
8675 * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
\r
8676 * <p>The <code>url</code> config may be a function which <i>returns</i> the URL to use for the Ajax request. The scope
\r
8677 * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
\r
8680 * @cfg {Object} extraParams (Optional) An object containing properties which are used as
\r
8681 * extra parameters to each request made by this object. (defaults to undefined)
\r
8684 * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
\r
8685 * to each request made by this object. (defaults to undefined)
\r
8688 * @cfg {String} method (Optional) The default HTTP method to be used for requests.
\r
8689 * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
\r
8690 * otherwise, GET will be used.)
\r
8693 * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
\r
8697 * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
\r
8703 * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
\r
8706 disableCaching: true,
\r
8709 * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
\r
8710 * through a cache buster. Defaults to '_dc'
\r
8713 disableCachingParam: '_dc',
\r
8716 * <p>Sends an HTTP request to a remote server.</p>
\r
8717 * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
\r
8718 * return before the response has been received. Process any returned data
\r
8719 * in a callback function.</p>
\r
8721 Ext.Ajax.request({
\r
8722 url: 'ajax_demo/sample.json',
\r
8723 success: function(response, opts) {
\r
8724 var obj = Ext.decode(response.responseText);
\r
8727 failure: function(response, opts) {
\r
8728 console.log('server-side failure with status code ' + response.status);
\r
8732 * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
\r
8733 * @param {Object} options An object which may contain the following properties:<ul>
\r
8734 * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
\r
8735 * which to send the request, or a function to call which returns a URL string. The scope of the
\r
8736 * function is specified by the <tt>scope</tt> option. Defaults to the configured
\r
8737 * <tt>{@link #url}</tt>.</div></li>
\r
8738 * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
\r
8739 * An object containing properties which are used as parameters to the
\r
8740 * request, a url encoded string or a function to call to get either. The scope of the function
\r
8741 * is specified by the <tt>scope</tt> option.</div></li>
\r
8742 * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
\r
8743 * for the request. Defaults to the configured method, or if no method was configured,
\r
8744 * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
\r
8745 * the method name is case-sensitive and should be all caps.</div></li>
\r
8746 * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
\r
8747 * function to be called upon receipt of the HTTP response. The callback is
\r
8748 * called regardless of success or failure and is passed the following
\r
8750 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8751 * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
\r
8752 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
\r
8753 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
\r
8754 * accessing elements of the response.</div></li>
\r
8755 * </ul></div></li>
\r
8756 * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
\r
8757 * to be called upon success of the request. The callback is passed the following
\r
8759 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
\r
8760 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8761 * </ul></div></li>
\r
8762 * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
\r
8763 * to be called upon failure of the request. The callback is passed the
\r
8764 * following parameters:<ul>
\r
8765 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
\r
8766 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8767 * </ul></div></li>
\r
8768 * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
\r
8769 * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
\r
8770 * specified as functions from which to draw values, then this also serves as the scope for those function calls.
\r
8771 * Defaults to the browser window.</div></li>
\r
8772 * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
\r
8773 * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
\r
8774 * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
\r
8775 * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
\r
8776 * with the <tt>form</tt> option</b>.
\r
8777 * <p>True if the form object is a file upload (will be set automatically if the form was
\r
8778 * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
\r
8779 * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
\r
8780 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
\r
8781 * DOM <tt><form></tt> element temporarily modified to have its
\r
8782 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
\r
8783 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
\r
8784 * but removed after the return data has been gathered.</p>
\r
8785 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
\r
8786 * server is using JSON to send the return object, then the
\r
8787 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
\r
8788 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
\r
8789 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
\r
8790 * is created containing a <tt>responseText</tt> property in order to conform to the
\r
8791 * requirements of event handlers and callbacks.</p>
\r
8792 * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
\r
8793 * and some server technologies (notably JEE) may require some custom processing in order to
\r
8794 * retrieve parameter names and parameter values from the packet content.</p>
\r
8796 * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
\r
8797 * headers to set for the request.</div></li>
\r
8798 * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
\r
8799 * to use for the post. Note: This will be used instead of params for the post
\r
8800 * data. Any params will be appended to the URL.</div></li>
\r
8801 * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
\r
8802 * data to use as the post. Note: This will be used instead of params for the post
\r
8803 * data. Any params will be appended to the URL.</div></li>
\r
8804 * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
\r
8805 * to add a unique cache-buster param to GET requests.</div></li>
\r
8807 * <p>The options object may also contain any other property which might be needed to perform
\r
8808 * postprocessing in a callback because it is passed to callback functions.</p>
\r
8809 * @return {Number} transactionId The id of the server transaction. This may be used
\r
8810 * to cancel the request.
\r
8812 request : function(o){
\r
8814 if(me.fireEvent(BEFOREREQUEST, me, o)){
\r
8816 if(!Ext.isEmpty(o.indicatorText)){
\r
8817 me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
\r
8819 if(me.indicatorText) {
\r
8820 Ext.getDom(o.el).innerHTML = me.indicatorText;
\r
8822 o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
\r
8823 Ext.getDom(o.el).innerHTML = response.responseText;
\r
8828 url = o.url || me.url,
\r
8830 cb = {success: handleResponse,
\r
8831 failure: handleFailure,
\r
8833 argument: {options: o},
\r
8834 timeout : o.timeout || me.timeout
\r
8840 if (Ext.isFunction(p)) {
\r
8841 p = p.call(o.scope||WINDOW, o);
\r
8844 p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
\r
8846 if (Ext.isFunction(url)) {
\r
8847 url = url.call(o.scope || WINDOW, o);
\r
8850 if((form = Ext.getDom(o.form))){
\r
8851 url = url || form.action;
\r
8852 if(o.isUpload || /multipart\/form-data/i.test(form.getAttribute("enctype"))) {
\r
8853 return doFormUpload.call(me, o, p, url);
\r
8855 serForm = Ext.lib.Ajax.serializeForm(form);
\r
8856 p = p ? (p + '&' + serForm) : serForm;
\r
8859 method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
\r
8861 if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
\r
8862 var dcp = o.disableCachingParam || me.disableCachingParam;
\r
8863 url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
\r
8866 o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
\r
8868 if(o.autoAbort === true || me.autoAbort) {
\r
8872 if((method == GET || o.xmlData || o.jsonData) && p){
\r
8873 url = Ext.urlAppend(url, p);
\r
8876 return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
\r
8878 return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
\r
8883 * Determine whether this object has a request outstanding.
\r
8884 * @param {Number} transactionId (Optional) defaults to the last transaction
\r
8885 * @return {Boolean} True if there is an outstanding request.
\r
8887 isLoading : function(transId){
\r
8888 return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
\r
8892 * Aborts any outstanding request.
\r
8893 * @param {Number} transactionId (Optional) defaults to the last transaction
\r
8895 abort : function(transId){
\r
8896 if(transId || this.isLoading()){
\r
8897 Ext.lib.Ajax.abort(transId || this.transId);
\r
8905 * @extends Ext.data.Connection
\r
8906 * <p>The global Ajax request class that provides a simple way to make Ajax requests
\r
8907 * with maximum flexibility.</p>
\r
8908 * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
\r
8909 * and override them at the request function level only if necessary.</p>
\r
8910 * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
\r
8911 * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
\r
8912 * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
\r
8913 * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
\r
8916 // Default headers to pass in every request
\r
8917 Ext.Ajax.defaultHeaders = {
\r
8918 'Powered-By': 'Ext'
\r
8922 * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
\r
8923 * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
\r
8924 * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
\r
8925 * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
\r
8928 // Example: show a spinner during all Ajax requests
\r
8929 Ext.Ajax.on('beforerequest', this.showSpinner, this);
\r
8930 Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
\r
8931 Ext.Ajax.on('requestexception', this.hideSpinner, this);
\r
8934 * <p>An example request:</p>
\r
8937 Ext.Ajax.{@link Ext.data.Connection#request request}({
\r
8942 'my-header': 'foo'
\r
8944 params: { foo: 'bar' }
\r
8947 // Simple ajax form submission
\r
8948 Ext.Ajax.{@link Ext.data.Connection#request request}({
\r
8949 form: 'some-form',
\r
8956 Ext.Ajax = new Ext.data.Connection({
\r
8958 * @cfg {String} url @hide
\r
8961 * @cfg {Object} extraParams @hide
\r
8964 * @cfg {Object} defaultHeaders @hide
\r
8967 * @cfg {String} method (Optional) @hide
\r
8970 * @cfg {Number} timeout (Optional) @hide
\r
8973 * @cfg {Boolean} autoAbort (Optional) @hide
\r
8977 * @cfg {Boolean} disableCaching (Optional) @hide
\r
8981 * @property disableCaching
\r
8982 * True to add a unique cache-buster param to GET requests. (defaults to true)
\r
8987 * The default URL to be used for requests to the server. (defaults to undefined)
\r
8988 * If the server receives all requests through one URL, setting this once is easier than
\r
8989 * entering it on every request.
\r
8993 * @property extraParams
\r
8994 * An object containing properties which are used as extra parameters to each request made
\r
8995 * by this object (defaults to undefined). Session information and other data that you need
\r
8996 * to pass with each request are commonly put here.
\r
9000 * @property defaultHeaders
\r
9001 * An object containing request headers which are added to each request made by this object
\r
9002 * (defaults to undefined).
\r
9006 * @property method
\r
9007 * The default HTTP method to be used for requests. Note that this is case-sensitive and
\r
9008 * should be all caps (defaults to undefined; if not set but params are present will use
\r
9009 * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
\r
9013 * @property timeout
\r
9014 * The timeout in milliseconds to be used for requests. (defaults to 30000)
\r
9019 * @property autoAbort
\r
9020 * Whether a new request should abort any pending requests. (defaults to false)
\r
9023 autoAbort : false,
\r
9026 * Serialize the passed form into a url encoded string
\r
9027 * @param {String/HTMLElement} form
\r
9028 * @return {String}
\r
9030 serializeForm : function(form){
\r
9031 return Ext.lib.Ajax.serializeForm(form);
\r
9035 * @class Ext.Updater
9036 * @extends Ext.util.Observable
9037 * Provides AJAX-style update capabilities for Element objects. Updater can be used to {@link #update}
9038 * an {@link Ext.Element} once, or you can use {@link #startAutoRefresh} to set up an auto-updating
9039 * {@link Ext.Element Element} on a specific interval.<br><br>
9042 * var el = Ext.get("foo"); // Get Ext.Element object
9043 * var mgr = el.getUpdater();
9045 url: "http://myserver.com/index.php",
9052 * mgr.formUpdate("myFormId", "http://myserver.com/index.php");
9054 * // or directly (returns the same Updater instance)
9055 * var mgr = new Ext.Updater("myElementId");
9056 * mgr.startAutoRefresh(60, "http://myserver.com/index.php");
9057 * mgr.on("update", myFcnNeedsToKnow);
9059 * // short handed call directly from the element object
9060 * Ext.get("foo").load({
9063 params: "param1=foo&param2=bar",
9064 text: "Loading Foo..."
9068 * Create new Updater directly.
9069 * @param {Mixed} el The element to update
9070 * @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already
9071 * has an Updater and if it does it returns the same instance. This will skip that check (useful for extending this class).
9073 Ext.UpdateManager = Ext.Updater = Ext.extend(Ext.util.Observable,
9075 var BEFOREUPDATE = "beforeupdate",
9077 FAILURE = "failure";
9080 function processSuccess(response){
9082 me.transaction = null;
9083 if (response.argument.form && response.argument.reset) {
9084 try { // put in try/catch since some older FF releases had problems with this
9085 response.argument.form.reset();
9088 if (me.loadScripts) {
9089 me.renderer.render(me.el, response, me,
9090 updateComplete.createDelegate(me, [response]));
9092 me.renderer.render(me.el, response, me);
9093 updateComplete.call(me, response);
9098 function updateComplete(response, type, success){
9099 this.fireEvent(type || UPDATE, this.el, response);
9100 if(Ext.isFunction(response.argument.callback)){
9101 response.argument.callback.call(response.argument.scope, this.el, Ext.isEmpty(success) ? true : false, response, response.argument.options);
9106 function processFailure(response){
9107 updateComplete.call(this, response, FAILURE, !!(this.transaction = null));
9111 constructor: function(el, forceNew){
9114 if(!forceNew && el.updateManager){
9115 return el.updateManager;
9118 * The Element object
9123 * Cached url to use for refreshes. Overwritten every time update() is called unless "discardUrl" param is set to true.
9126 me.defaultUrl = null;
9130 * @event beforeupdate
9131 * Fired before an update is made, return false from your handler and the update is cancelled.
9132 * @param {Ext.Element} el
9133 * @param {String/Object/Function} url
9134 * @param {String/Object} params
9139 * Fired after successful update is made.
9140 * @param {Ext.Element} el
9141 * @param {Object} oResponseObject The response Object
9146 * Fired on update failure.
9147 * @param {Ext.Element} el
9148 * @param {Object} oResponseObject The response Object
9153 Ext.apply(me, Ext.Updater.defaults);
9155 * Blank page URL to use with SSL file uploads (defaults to {@link Ext.Updater.defaults#sslBlankUrl}).
9156 * @property sslBlankUrl
9160 * Whether to append unique parameter on get request to disable caching (defaults to {@link Ext.Updater.defaults#disableCaching}).
9161 * @property disableCaching
9165 * Text for loading indicator (defaults to {@link Ext.Updater.defaults#indicatorText}).
9166 * @property indicatorText
9170 * Whether to show indicatorText when loading (defaults to {@link Ext.Updater.defaults#showLoadIndicator}).
9171 * @property showLoadIndicator
9175 * Timeout for requests or form posts in seconds (defaults to {@link Ext.Updater.defaults#timeout}).
9180 * True to process scripts in the output (defaults to {@link Ext.Updater.defaults#loadScripts}).
9181 * @property loadScripts
9186 * Transaction object of the current executing transaction, or null if there is no active transaction.
9188 me.transaction = null;
9190 * Delegate for refresh() prebound to "this", use myUpdater.refreshDelegate.createCallback(arg1, arg2) to bind arguments
9193 me.refreshDelegate = me.refresh.createDelegate(me);
9195 * Delegate for update() prebound to "this", use myUpdater.updateDelegate.createCallback(arg1, arg2) to bind arguments
9198 me.updateDelegate = me.update.createDelegate(me);
9200 * Delegate for formUpdate() prebound to "this", use myUpdater.formUpdateDelegate.createCallback(arg1, arg2) to bind arguments
9203 me.formUpdateDelegate = (me.formUpdate || function(){}).createDelegate(me);
9206 * The renderer for this Updater (defaults to {@link Ext.Updater.BasicRenderer}).
9208 me.renderer = me.renderer || me.getDefaultRenderer();
9210 Ext.Updater.superclass.constructor.call(me);
9214 * Sets the content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
9215 * @param {Object} renderer The object implementing the render() method
9217 setRenderer : function(renderer){
9218 this.renderer = renderer;
9222 * Returns the current content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
9225 getRenderer : function(){
9226 return this.renderer;
9230 * This is an overrideable method which returns a reference to a default
9231 * renderer class if none is specified when creating the Ext.Updater.
9232 * Defaults to {@link Ext.Updater.BasicRenderer}
9234 getDefaultRenderer: function() {
9235 return new Ext.Updater.BasicRenderer();
9239 * Sets the default URL used for updates.
9240 * @param {String/Function} defaultUrl The url or a function to call to get the url
9242 setDefaultUrl : function(defaultUrl){
9243 this.defaultUrl = defaultUrl;
9247 * Get the Element this Updater is bound to
9248 * @return {Ext.Element} The element
9255 * Performs an <b>asynchronous</b> request, updating this element with the response.
9256 * If params are specified it uses POST, otherwise it uses GET.<br><br>
9257 * <b>Note:</b> Due to the asynchronous nature of remote server requests, the Element
9258 * will not have been fully updated when the function returns. To post-process the returned
9259 * data, use the callback option, or an <b><tt>update</tt></b> event handler.
9260 * @param {Object} options A config object containing any of the following options:<ul>
9261 * <li>url : <b>String/Function</b><p class="sub-desc">The URL to request or a function which
9262 * <i>returns</i> the URL (defaults to the value of {@link Ext.Ajax#url} if not specified).</p></li>
9263 * <li>method : <b>String</b><p class="sub-desc">The HTTP method to
9264 * use. Defaults to POST if the <tt>params</tt> argument is present, otherwise GET.</p></li>
9265 * <li>params : <b>String/Object/Function</b><p class="sub-desc">The
9266 * parameters to pass to the server (defaults to none). These may be specified as a url-encoded
9267 * string, or as an object containing properties which represent parameters,
9268 * or as a function, which returns such an object.</p></li>
9269 * <li>scripts : <b>Boolean</b><p class="sub-desc">If <tt>true</tt>
9270 * any <script> tags embedded in the response text will be extracted
9271 * and executed (defaults to {@link Ext.Updater.defaults#loadScripts}). If this option is specified,
9272 * the callback will be called <i>after</i> the execution of the scripts.</p></li>
9273 * <li>callback : <b>Function</b><p class="sub-desc">A function to
9274 * be called when the response from the server arrives. The following
9275 * parameters are passed:<ul>
9276 * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
9277 * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
9278 * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li>
9279 * <li><b>options</b> : Object<p class="sub-desc">The config object passed to the update call.</p></li></ul>
9281 * <li>scope : <b>Object</b><p class="sub-desc">The scope in which
9282 * to execute the callback (The callback's <tt>this</tt> reference.) If the
9283 * <tt>params</tt> argument is a function, this scope is used for that function also.</p></li>
9284 * <li>discardUrl : <b>Boolean</b><p class="sub-desc">By default, the URL of this request becomes
9285 * the default URL for this Updater object, and will be subsequently used in {@link #refresh}
9286 * calls. To bypass this behavior, pass <tt>discardUrl:true</tt> (defaults to false).</p></li>
9287 * <li>timeout : <b>Number</b><p class="sub-desc">The number of seconds to wait for a response before
9288 * timing out (defaults to {@link Ext.Updater.defaults#timeout}).</p></li>
9289 * <li>text : <b>String</b><p class="sub-desc">The text to use as the innerHTML of the
9290 * {@link Ext.Updater.defaults#indicatorText} div (defaults to 'Loading...'). To replace the entire div, not
9291 * just the text, override {@link Ext.Updater.defaults#indicatorText} directly.</p></li>
9292 * <li>nocache : <b>Boolean</b><p class="sub-desc">Only needed for GET
9293 * requests, this option causes an extra, auto-generated parameter to be appended to the request
9294 * to defeat caching (defaults to {@link Ext.Updater.defaults#disableCaching}).</p></li></ul>
9299 url: "your-url.php",
9300 params: {param1: "foo", param2: "bar"}, // or a URL encoded string
9301 callback: yourFunction,
9302 scope: yourObject, //(optional scope)
9307 scripts: false // Save time by avoiding RegExp execution.
9311 update : function(url, params, callback, discardUrl){
9316 if(me.fireEvent(BEFOREUPDATE, me.el, url, params) !== false){
9317 if(Ext.isObject(url)){ // must be config object
9320 params = params || cfg.params;
9321 callback = callback || cfg.callback;
9322 discardUrl = discardUrl || cfg.discardUrl;
9323 callerScope = cfg.scope;
9324 if(!Ext.isEmpty(cfg.nocache)){me.disableCaching = cfg.nocache;};
9325 if(!Ext.isEmpty(cfg.text)){me.indicatorText = '<div class="loading-indicator">'+cfg.text+"</div>";};
9326 if(!Ext.isEmpty(cfg.scripts)){me.loadScripts = cfg.scripts;};
9327 if(!Ext.isEmpty(cfg.timeout)){me.timeout = cfg.timeout;};
9332 me.defaultUrl = url;
9334 if(Ext.isFunction(url)){
9338 var o = Ext.apply({}, {
9340 params: (Ext.isFunction(params) && callerScope) ? params.createDelegate(callerScope) : params,
9341 success: processSuccess,
9342 failure: processFailure,
9344 callback: undefined,
9345 timeout: (me.timeout*1000),
9346 disableCaching: me.disableCaching,
9351 "callback": callback,
9352 "scope": callerScope || window,
9357 me.transaction = Ext.Ajax.request(o);
9362 * <p>Performs an async form post, updating this element with the response. If the form has the attribute
9363 * enctype="<a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>", it assumes it's a file upload.
9364 * Uses this.sslBlankUrl for SSL file uploads to prevent IE security warning.</p>
9365 * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
9366 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
9367 * DOM <tt><form></tt> element temporarily modified to have its
9368 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
9369 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
9370 * but removed after the return data has been gathered.</p>
9371 * <p>Be aware that file upload packets, sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>
9372 * and some server technologies (notably JEE) may require some custom processing in order to
9373 * retrieve parameter names and parameter values from the packet content.</p>
9374 * @param {String/HTMLElement} form The form Id or form element
9375 * @param {String} url (optional) The url to pass the form to. If omitted the action attribute on the form will be used.
9376 * @param {Boolean} reset (optional) Whether to try to reset the form after the update
9377 * @param {Function} callback (optional) Callback when transaction is complete. The following
9378 * parameters are passed:<ul>
9379 * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
9380 * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
9381 * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li></ul>
9383 formUpdate : function(form, url, reset, callback){
9385 if(me.fireEvent(BEFOREUPDATE, me.el, form, url) !== false){
9386 if(Ext.isFunction(url)){
9389 form = Ext.getDom(form)
9390 me.transaction = Ext.Ajax.request({
9393 success: processSuccess,
9394 failure: processFailure,
9396 timeout: (me.timeout*1000),
9400 "callback": callback,
9404 me.showLoading.defer(1, me);
9409 * Set this element to auto refresh. Can be canceled by calling {@link #stopAutoRefresh}.
9410 * @param {Number} interval How often to update (in seconds).
9411 * @param {String/Object/Function} url (optional) The url for this request, a config object in the same format
9412 * supported by {@link #load}, or a function to call to get the url (defaults to the last used url). Note that while
9413 * the url used in a load call can be reused by this method, other load config options will not be reused and must be
9414 * sepcified as part of a config object passed as this paramter if needed.
9415 * @param {String/Object} params (optional) The parameters to pass as either a url encoded string
9416 * "¶m1=1¶m2=2" or as an object {param1: 1, param2: 2}
9417 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
9418 * @param {Boolean} refreshNow (optional) Whether to execute the refresh now, or wait the interval
9420 startAutoRefresh : function(interval, url, params, callback, refreshNow){
9423 me.update(url || me.defaultUrl, params, callback, true);
9425 if(me.autoRefreshProcId){
9426 clearInterval(me.autoRefreshProcId);
9428 me.autoRefreshProcId = setInterval(me.update.createDelegate(me, [url || me.defaultUrl, params, callback, true]), interval * 1000);
9432 * Stop auto refresh on this element.
9434 stopAutoRefresh : function(){
9435 if(this.autoRefreshProcId){
9436 clearInterval(this.autoRefreshProcId);
9437 delete this.autoRefreshProcId;
9442 * Returns true if the Updater is currently set to auto refresh its content (see {@link #startAutoRefresh}), otherwise false.
9444 isAutoRefreshing : function(){
9445 return !!this.autoRefreshProcId;
9449 * Display the element's "loading" state. By default, the element is updated with {@link #indicatorText}. This
9450 * method may be overridden to perform a custom action while this Updater is actively updating its contents.
9452 showLoading : function(){
9453 if(this.showLoadIndicator){
9454 this.el.dom.innerHTML = this.indicatorText;
9459 * Aborts the currently executing transaction, if any.
9462 if(this.transaction){
9463 Ext.Ajax.abort(this.transaction);
9468 * Returns true if an update is in progress, otherwise false.
9471 isUpdating : function(){
9472 return this.transaction ? Ext.Ajax.isLoading(this.transaction) : false;
9476 * Refresh the element with the last used url or defaultUrl. If there is no url, it returns immediately
9477 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
9479 refresh : function(callback){
9480 if(this.defaultUrl){
9481 this.update(this.defaultUrl, null, callback, true);
9488 * @class Ext.Updater.defaults
9489 * The defaults collection enables customizing the default properties of Updater
9491 Ext.Updater.defaults = {
9493 * Timeout for requests or form posts in seconds (defaults to 30 seconds).
9498 * True to append a unique parameter to GET requests to disable caching (defaults to false).
9501 disableCaching : false,
9503 * Whether or not to show {@link #indicatorText} during loading (defaults to true).
9506 showLoadIndicator : true,
9508 * Text for loading indicator (defaults to '<div class="loading-indicator">Loading...</div>').
9511 indicatorText : '<div class="loading-indicator">Loading...</div>',
9513 * True to process scripts by default (defaults to false).
9516 loadScripts : false,
9518 * Blank page URL to use with SSL file uploads (defaults to {@link Ext#SSL_SECURE_URL} if set, or "javascript:false").
9521 sslBlankUrl : (Ext.SSL_SECURE_URL || "javascript:false")
9526 * Static convenience method. <b>This method is deprecated in favor of el.load({url:'foo.php', ...})</b>.
9528 * <pre><code>Ext.Updater.updateElement("my-div", "stuff.php");</code></pre>
9529 * @param {Mixed} el The element to update
9530 * @param {String} url The url
9531 * @param {String/Object} params (optional) Url encoded param string or an object of name/value pairs
9532 * @param {Object} options (optional) A config object with any of the Updater properties you want to set - for
9533 * example: {disableCaching:true, indicatorText: "Loading data..."}
9536 * @member Ext.Updater
9538 Ext.Updater.updateElement = function(el, url, params, options){
9539 var um = Ext.get(el).getUpdater();
9540 Ext.apply(um, options);
9541 um.update(url, params, options ? options.callback : null);
9545 * @class Ext.Updater.BasicRenderer
9546 * Default Content renderer. Updates the elements innerHTML with the responseText.
9548 Ext.Updater.BasicRenderer = function(){};
9550 Ext.Updater.BasicRenderer.prototype = {
9552 * This is called when the transaction is completed and it's time to update the element - The BasicRenderer
9553 * updates the elements innerHTML with the responseText - To perform a custom render (i.e. XML or JSON processing),
9554 * create an object with a "render(el, response)" method and pass it to setRenderer on the Updater.
9555 * @param {Ext.Element} el The element being rendered
9556 * @param {Object} response The XMLHttpRequest object
9557 * @param {Updater} updateManager The calling update manager
9558 * @param {Function} callback A callback that will need to be called if loadScripts is true on the Updater
9560 render : function(el, response, updateManager, callback){
9561 el.update(response.responseText, updateManager.loadScripts, callback);
9566 * The date parsing and formatting syntax contains a subset of
9567 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
9568 * supported will provide results equivalent to their PHP versions.
9570 * The following is a list of all currently supported formats:
9572 Format Description Example returned values
9573 ------ ----------------------------------------------------------------------- -----------------------
9574 d Day of the month, 2 digits with leading zeros 01 to 31
9575 D A short textual representation of the day of the week Mon to Sun
9576 j Day of the month without leading zeros 1 to 31
9577 l A full textual representation of the day of the week Sunday to Saturday
9578 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
9579 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
9580 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
9581 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
9582 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
9583 F A full textual representation of a month, such as January or March January to December
9584 m Numeric representation of a month, with leading zeros 01 to 12
9585 M A short textual representation of a month Jan to Dec
9586 n Numeric representation of a month, without leading zeros 1 to 12
9587 t Number of days in the given month 28 to 31
9588 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
9589 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
9590 belongs to the previous or next year, that year is used instead)
9591 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
9592 y A two digit representation of a year Examples: 99 or 03
9593 a Lowercase Ante meridiem and Post meridiem am or pm
9594 A Uppercase Ante meridiem and Post meridiem AM or PM
9595 g 12-hour format of an hour without leading zeros 1 to 12
9596 G 24-hour format of an hour without leading zeros 0 to 23
9597 h 12-hour format of an hour with leading zeros 01 to 12
9598 H 24-hour format of an hour with leading zeros 00 to 23
9599 i Minutes, with leading zeros 00 to 59
9600 s Seconds, with leading zeros 00 to 59
9601 u Decimal fraction of a second Examples:
9602 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
9603 100 (i.e. 0.100s) or
9604 999 (i.e. 0.999s) or
9605 999876543210 (i.e. 0.999876543210s)
9606 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
9607 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
9608 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
9609 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
9612 1) If unspecified, the month / day defaults to the current month / day, 1991 or
9613 the time defaults to midnight, while the timezone defaults to the 1992-10 or
9614 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
9615 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
9616 are optional. 1995-07-18T17:21:28-02:00 or
9617 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
9618 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
9619 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
9620 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
9621 date-time granularity which are supported, or see 2000-02-13T21:25:33
9622 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
9623 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
9624 M$ Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
9625 \/Date(1238606590509+0800)\/
9628 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
9631 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
9633 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
9634 document.write(dt.format('Y-m-d')); // 2007-01-10
9635 document.write(dt.format('F j, Y, g:i a')); // January 10, 2007, 3:05 pm
9636 document.write(dt.format('l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
9639 * Here are some standard date/time patterns that you might find helpful. They
9640 * are not part of the source of Date.js, but to use them you can simply copy this
9641 * block of code into any script that is included after Date.js and they will also become
9642 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
9645 ISO8601Long:"Y-m-d H:i:s",
9646 ISO8601Short:"Y-m-d",
9648 LongDate: "l, F d, Y",
9649 FullDateTime: "l, F d, Y g:i:s A",
9652 LongTime: "g:i:s A",
9653 SortableDateTime: "Y-m-d\\TH:i:s",
9654 UniversalSortableDateTime: "Y-m-d H:i:sO",
9661 var dt = new Date();
9662 document.write(dt.format(Date.patterns.ShortDate));
9664 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
9665 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
9669 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
9670 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
9671 * They generate precompiled functions from format patterns instead of parsing and
9672 * processing each pattern every time a date is formatted. These functions are available
9673 * on every Date object.
9679 * Global flag which determines if strict date parsing should be used.
9680 * Strict date parsing will not roll-over invalid dates, which is the
9681 * default behaviour of javascript Date objects.
9682 * (see {@link #parseDate} for more information)
9683 * Defaults to <tt>false</tt>.
9687 Date.useStrict = false;
9690 // create private copy of Ext's String.format() method
9691 // - to remove unnecessary dependency
9692 // - to resolve namespace conflict with M$-Ajax's implementation
9693 function xf(format) {
9694 var args = Array.prototype.slice.call(arguments, 1);
9695 return format.replace(/\{(\d+)\}/g, function(m, i) {
9702 Date.formatCodeToRegex = function(character, currentGroup) {
9703 // Note: currentGroup - position in regex result array (see notes for Date.parseCodes below)
9704 var p = Date.parseCodes[character];
9707 p = typeof p == 'function'? p() : p;
9708 Date.parseCodes[character] = p; // reassign function result to prevent repeated execution
9711 return p? Ext.applyIf({
9712 c: p.c? xf(p.c, currentGroup || "{0}") : p.c
9716 s:Ext.escapeRe(character) // treat unrecognised characters as literals
9720 // private shorthand for Date.formatCodeToRegex since we'll be using it fairly often
9721 var $f = Date.formatCodeToRegex;
9725 * <p>An object hash in which each property is a date parsing function. The property name is the
9726 * format string which that function parses.</p>
9727 * <p>This object is automatically populated with date parsing functions as
9728 * date formats are requested for Ext standard formatting strings.</p>
9729 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
9730 * may be used as a format string to {@link #parseDate}.<p>
9731 * <p>Example:</p><pre><code>
9732 Date.parseFunctions['x-date-format'] = myDateParser;
9734 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
9735 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
9736 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
9737 * (i.e. prevent javascript Date "rollover") (The default must be false).
9738 * Invalid date strings should return null when parsed.</div></li>
9740 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
9741 * formatting function must be placed into the {@link #formatFunctions} property.
9742 * @property parseFunctions
9747 "M$": function(input, strict) {
9748 // note: the timezone offset is ignored since the M$ Ajax server sends
9749 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
9750 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
9751 var r = (input || '').match(re);
9752 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
9758 * <p>An object hash in which each property is a date formatting function. The property name is the
9759 * format string which corresponds to the produced formatted date string.</p>
9760 * <p>This object is automatically populated with date formatting functions as
9761 * date formats are requested for Ext standard formatting strings.</p>
9762 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
9763 * may be used as a format string to {@link #format}. Example:</p><pre><code>
9764 Date.formatFunctions['x-date-format'] = myDateFormatter;
9766 * <p>A formatting function should return a string repesentation of the passed Date object:<div class="mdetail-params"><ul>
9767 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
9769 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
9770 * parsing function must be placed into the {@link #parseFunctions} property.
9771 * @property formatFunctions
9777 // UTC milliseconds since Unix epoch (M$-AJAX serialized date format (MRSF))
9778 return '\\/Date(' + this.getTime() + ')\\/';
9785 * Date interval constant
9792 * Date interval constant
9799 * Date interval constant
9805 /** Date interval constant
9812 * Date interval constant
9819 * Date interval constant
9826 * Date interval constant
9833 * <p>An object hash containing default date values used during date parsing.</p>
9834 * <p>The following properties are available:<div class="mdetail-params"><ul>
9835 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
9836 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
9837 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
9838 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
9839 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
9840 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
9841 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
9843 * <p>Override these properties to customize the default date values used by the {@link #parseDate} method.</p>
9844 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
9845 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
9846 * It is the responsiblity of the developer to account for this.</b></p>
9849 // set default day value to the first day of the month
9850 Date.defaults.d = 1;
9852 // parse a February date string containing only year and month values.
9853 // setting the default day value to 1 prevents weird date rollover issues
9854 // when attempting to parse the following date string on, for example, March 31st 2009.
9855 Date.parseDate('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
9857 * @property defaults
9864 * An array of textual day names.
9865 * Override these values for international dates.
9888 * An array of textual month names.
9889 * Override these values for international dates.
9917 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
9918 * Override these values for international dates.
9921 Date.monthNumbers = {
9922 'ShortJanNameInYourLang':0,
9923 'ShortFebNameInYourLang':1,
9946 * Get the short month name for the given month number.
9947 * Override this function for international dates.
9948 * @param {Number} month A zero-based javascript month number.
9949 * @return {String} The short month name.
9952 getShortMonthName : function(month) {
9953 return Date.monthNames[month].substring(0, 3);
9957 * Get the short day name for the given day number.
9958 * Override this function for international dates.
9959 * @param {Number} day A zero-based javascript day number.
9960 * @return {String} The short day name.
9963 getShortDayName : function(day) {
9964 return Date.dayNames[day].substring(0, 3);
9968 * Get the zero-based javascript month number for the given short/full month name.
9969 * Override this function for international dates.
9970 * @param {String} name The short/full month name.
9971 * @return {Number} The zero-based javascript month number.
9974 getMonthNumber : function(name) {
9975 // handle camel casing for english month names (since the keys for the Date.monthNumbers hash are case sensitive)
9976 return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
9980 * The base format-code to formatting-function hashmap used by the {@link #format} method.
9981 * Formatting functions are strings (or functions which return strings) which
9982 * will return the appropriate value when evaluated in the context of the Date object
9983 * from which the {@link #format} method is called.
9984 * Add to / override these mappings for custom date formatting.
9985 * Note: Date.format() treats characters as literals if an appropriate mapping cannot be found.
9988 Date.formatCodes.x = "String.leftPad(this.getDate(), 2, '0')";
9989 (new Date()).format("X"); // returns the current day of the month
9995 d: "String.leftPad(this.getDate(), 2, '0')",
9996 D: "Date.getShortDayName(this.getDay())", // get localised short day name
9997 j: "this.getDate()",
9998 l: "Date.dayNames[this.getDay()]",
9999 N: "(this.getDay() ? this.getDay() : 7)",
10000 S: "this.getSuffix()",
10001 w: "this.getDay()",
10002 z: "this.getDayOfYear()",
10003 W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
10004 F: "Date.monthNames[this.getMonth()]",
10005 m: "String.leftPad(this.getMonth() + 1, 2, '0')",
10006 M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
10007 n: "(this.getMonth() + 1)",
10008 t: "this.getDaysInMonth()",
10009 L: "(this.isLeapYear() ? 1 : 0)",
10010 o: "(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",
10011 Y: "this.getFullYear()",
10012 y: "('' + this.getFullYear()).substring(2, 4)",
10013 a: "(this.getHours() < 12 ? 'am' : 'pm')",
10014 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
10015 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
10016 G: "this.getHours()",
10017 h: "String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
10018 H: "String.leftPad(this.getHours(), 2, '0')",
10019 i: "String.leftPad(this.getMinutes(), 2, '0')",
10020 s: "String.leftPad(this.getSeconds(), 2, '0')",
10021 u: "String.leftPad(this.getMilliseconds(), 3, '0')",
10022 O: "this.getGMTOffset()",
10023 P: "this.getGMTOffset(true)",
10024 T: "this.getTimezone()",
10025 Z: "(this.getTimezoneOffset() * -60)",
10027 c: function() { // ISO-8601 -- GMT format
10028 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
10029 var e = c.charAt(i);
10030 code.push(e == "T" ? "'T'" : Date.getFormatCode(e)); // treat T as a character literal
10032 return code.join(" + ");
10035 c: function() { // ISO-8601 -- UTC format
10037 "this.getUTCFullYear()", "'-'",
10038 "String.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
10039 "String.leftPad(this.getUTCDate(), 2, '0')",
10041 "String.leftPad(this.getUTCHours(), 2, '0')", "':'",
10042 "String.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
10043 "String.leftPad(this.getUTCSeconds(), 2, '0')",
10049 U: "Math.round(this.getTime() / 1000)"
10053 * Checks if the passed Date parameters will cause a javascript Date "rollover".
10054 * @param {Number} year 4-digit year
10055 * @param {Number} month 1-based month-of-year
10056 * @param {Number} day Day of month
10057 * @param {Number} hour (optional) Hour
10058 * @param {Number} minute (optional) Minute
10059 * @param {Number} second (optional) Second
10060 * @param {Number} millisecond (optional) Millisecond
10061 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
10064 isValid : function(y, m, d, h, i, s, ms) {
10071 var dt = new Date(y, m - 1, d, h, i, s, ms);
10073 return y == dt.getFullYear() &&
10074 m == dt.getMonth() + 1 &&
10075 d == dt.getDate() &&
10076 h == dt.getHours() &&
10077 i == dt.getMinutes() &&
10078 s == dt.getSeconds() &&
10079 ms == dt.getMilliseconds();
10083 * Parses the passed string using the specified date format.
10084 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
10085 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
10086 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
10087 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
10088 * Keep in mind that the input date string must precisely match the specified format string
10089 * in order for the parse operation to be successful (failed parse operations return a null value).
10090 * <p>Example:</p><pre><code>
10091 //dt = Fri May 25 2007 (current date)
10092 var dt = new Date();
10094 //dt = Thu May 25 2006 (today's month/day in 2006)
10095 dt = Date.parseDate("2006", "Y");
10097 //dt = Sun Jan 15 2006 (all date parts specified)
10098 dt = Date.parseDate("2006-01-15", "Y-m-d");
10100 //dt = Sun Jan 15 2006 15:20:01
10101 dt = Date.parseDate("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
10103 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
10104 dt = Date.parseDate("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
10106 * @param {String} input The raw date string.
10107 * @param {String} format The expected date string format.
10108 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
10109 (defaults to false). Invalid date strings will return null when parsed.
10110 * @return {Date} The parsed Date.
10113 parseDate : function(input, format, strict) {
10114 var p = Date.parseFunctions;
10115 if (p[format] == null) {
10116 Date.createParser(format);
10118 return p[format](input, Ext.isDefined(strict) ? strict : Date.useStrict);
10122 getFormatCode : function(character) {
10123 var f = Date.formatCodes[character];
10126 f = typeof f == 'function'? f() : f;
10127 Date.formatCodes[character] = f; // reassign function result to prevent repeated execution
10130 // note: unknown characters are treated as literals
10131 return f || ("'" + String.escape(character) + "'");
10135 createFormat : function(format) {
10140 for (var i = 0; i < format.length; ++i) {
10141 ch = format.charAt(i);
10142 if (!special && ch == "\\") {
10144 } else if (special) {
10146 code.push("'" + String.escape(ch) + "'");
10148 code.push(Date.getFormatCode(ch))
10151 Date.formatFunctions[format] = new Function("return " + code.join('+'));
10155 createParser : function() {
10157 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
10158 "def = Date.defaults,",
10159 "results = String(input).match(Date.parseRegexes[{0}]);", // either null, or an array of matched strings
10164 "if(u != null){", // i.e. unix time is defined
10165 "v = new Date(u * 1000);", // give top priority to UNIX time
10167 // create Date object representing midnight of the current day;
10168 // this will provide us with our date defaults
10169 // (note: clearTime() handles Daylight Saving Time automatically)
10170 "dt = (new Date()).clearTime();",
10172 // date calculations (note: these calculations create a dependency on Ext.num())
10173 "y = y >= 0? y : Ext.num(def.y, dt.getFullYear());",
10174 "m = m >= 0? m : Ext.num(def.m - 1, dt.getMonth());",
10175 "d = d >= 0? d : Ext.num(def.d, dt.getDate());",
10177 // time calculations (note: these calculations create a dependency on Ext.num())
10178 "h = h || Ext.num(def.h, dt.getHours());",
10179 "i = i || Ext.num(def.i, dt.getMinutes());",
10180 "s = s || Ext.num(def.s, dt.getSeconds());",
10181 "ms = ms || Ext.num(def.ms, dt.getMilliseconds());",
10183 "if(z >= 0 && y >= 0){",
10184 // both the year and zero-based day of year are defined and >= 0.
10185 // these 2 values alone provide sufficient info to create a full date object
10187 // create Date object representing January 1st for the given year
10188 "v = new Date(y, 0, 1, h, i, s, ms);",
10190 // then add day of year, checking for Date "rollover" if necessary
10191 "v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);",
10192 "}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
10193 "v = null;", // invalid date, so return null
10195 // plain old Date object
10196 "v = new Date(y, m, d, h, i, s, ms);",
10202 // favour UTC offset over GMT offset
10204 // reset to UTC, then add offset
10205 "v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
10207 // reset to GMT, then add offset
10208 "v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
10215 return function(format) {
10216 var regexNum = Date.parseRegexes.length,
10223 for (var i = 0; i < format.length; ++i) {
10224 ch = format.charAt(i);
10225 if (!special && ch == "\\") {
10227 } else if (special) {
10229 regex.push(String.escape(ch));
10231 var obj = $f(ch, currentGroup);
10232 currentGroup += obj.g;
10234 if (obj.g && obj.c) {
10240 Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", "i");
10241 Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
10249 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
10250 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
10251 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
10255 c:"d = parseInt(results[{0}], 10);\n",
10256 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
10260 c:"d = parseInt(results[{0}], 10);\n",
10261 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
10264 for (var a = [], i = 0; i < 7; a.push(Date.getShortDayName(i)), ++i); // get localised short day names
10268 s:"(?:" + a.join("|") +")"
10275 s:"(?:" + Date.dayNames.join("|") + ")"
10281 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
10286 s:"(?:st|nd|rd|th)"
10291 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
10295 c:"z = parseInt(results[{0}], 10);\n",
10296 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
10301 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
10306 c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
10307 s:"(" + Date.monthNames.join("|") + ")"
10311 for (var a = [], i = 0; i < 12; a.push(Date.getShortMonthName(i)), ++i); // get localised short month names
10312 return Ext.applyIf({
10313 s:"(" + a.join("|") + ")"
10318 c:"m = parseInt(results[{0}], 10) - 1;\n",
10319 s:"(\\d{2})" // month number with leading zeros (01 - 12)
10323 c:"m = parseInt(results[{0}], 10) - 1;\n",
10324 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
10329 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
10341 c:"y = parseInt(results[{0}], 10);\n",
10342 s:"(\\d{4})" // 4-digit year
10346 c:"var ty = parseInt(results[{0}], 10);\n"
10347 + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
10352 c:"if (results[{0}] == 'am') {\n"
10353 + "if (h == 12) { h = 0; }\n"
10354 + "} else { if (h < 12) { h += 12; }}",
10359 c:"if (results[{0}] == 'AM') {\n"
10360 + "if (h == 12) { h = 0; }\n"
10361 + "} else { if (h < 12) { h += 12; }}",
10369 c:"h = parseInt(results[{0}], 10);\n",
10370 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
10377 c:"h = parseInt(results[{0}], 10);\n",
10378 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
10382 c:"i = parseInt(results[{0}], 10);\n",
10383 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
10387 c:"s = parseInt(results[{0}], 10);\n",
10388 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
10392 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
10393 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
10398 "o = results[{0}];",
10399 "var sn = o.substring(0,1),", // get + / - sign
10400 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
10401 "mn = o.substring(3,5) % 60;", // get minutes
10402 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
10404 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
10409 "o = results[{0}];",
10410 "var sn = o.substring(0,1),", // get + / - sign
10411 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
10412 "mn = o.substring(4,6) % 60;", // get minutes
10413 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
10415 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
10420 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
10424 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
10425 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
10426 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
10431 $f("Y", 1), // year
10432 $f("m", 2), // month
10434 $f("h", 4), // hour
10435 $f("i", 5), // minute
10436 $f("s", 6), // second
10437 {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
10438 {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
10439 "if(results[8]) {", // timezone specified
10440 "if(results[8] == 'Z'){",
10442 "}else if (results[8].indexOf(':') > -1){",
10443 $f("P", 8).c, // timezone offset with colon separator
10445 $f("O", 8).c, // timezone offset without colon separator
10451 for (var i = 0, l = arr.length; i < l; ++i) {
10452 calc.push(arr[i].c);
10459 arr[0].s, // year (required)
10460 "(?:", "-", arr[1].s, // month (optional)
10461 "(?:", "-", arr[2].s, // day (optional)
10463 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
10464 arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
10465 "(?::", arr[5].s, ")?", // seconds (optional)
10466 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
10467 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
10476 c:"u = parseInt(results[{0}], 10);\n",
10477 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
10484 Ext.apply(Date.prototype, {
10486 dateFormat : function(format) {
10487 if (Date.formatFunctions[format] == null) {
10488 Date.createFormat(format);
10490 return Date.formatFunctions[format].call(this);
10494 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
10496 * Note: The date string returned by the javascript Date object's toString() method varies
10497 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
10498 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
10499 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
10500 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
10501 * from the GMT offset portion of the date string.
10502 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
10504 getTimezone : function() {
10505 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
10507 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
10508 // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
10509 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
10510 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
10511 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
10513 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
10514 // step 1: (?:\((.*)\) -- find timezone in parentheses
10515 // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
10516 // step 3: remove all non uppercase characters found in step 1 and 2
10517 return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
10521 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
10522 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
10523 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
10525 getGMTOffset : function(colon) {
10526 return (this.getTimezoneOffset() > 0 ? "-" : "+")
10527 + String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0")
10528 + (colon ? ":" : "")
10529 + String.leftPad(Math.abs(this.getTimezoneOffset() % 60), 2, "0");
10533 * Get the numeric day number of the year, adjusted for leap year.
10534 * @return {Number} 0 to 364 (365 in leap years).
10536 getDayOfYear: function() {
10540 m = this.getMonth();
10542 for (i = 0, d.setMonth(0); i < m; d.setMonth(++i)) {
10543 num += d.getDaysInMonth();
10545 return num + this.getDate() - 1;
10549 * Get the numeric ISO-8601 week number of the year.
10550 * (equivalent to the format specifier 'W', but without a leading zero).
10551 * @return {Number} 1 to 53
10553 getWeekOfYear : function() {
10554 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
10555 var ms1d = 864e5, // milliseconds in a day
10556 ms7d = 7 * ms1d; // milliseconds in a week
10558 return function() { // return a closure so constants get calculated only once
10559 var DC3 = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 3) / ms1d, // an Absolute Day Number
10560 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
10561 Wyr = new Date(AWN * ms7d).getUTCFullYear();
10563 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
10568 * Checks if the current date falls within a leap year.
10569 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
10571 isLeapYear : function() {
10572 var year = this.getFullYear();
10573 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
10577 * Get the first day of the current month, adjusted for leap year. The returned value
10578 * is the numeric day index within the week (0-6) which can be used in conjunction with
10579 * the {@link #monthNames} array to retrieve the textual day name.
10582 var dt = new Date('1/10/2007');
10583 document.write(Date.dayNames[dt.getFirstDayOfMonth()]); //output: 'Monday'
10585 * @return {Number} The day number (0-6).
10587 getFirstDayOfMonth : function() {
10588 var day = (this.getDay() - (this.getDate() - 1)) % 7;
10589 return (day < 0) ? (day + 7) : day;
10593 * Get the last day of the current month, adjusted for leap year. The returned value
10594 * is the numeric day index within the week (0-6) which can be used in conjunction with
10595 * the {@link #monthNames} array to retrieve the textual day name.
10598 var dt = new Date('1/10/2007');
10599 document.write(Date.dayNames[dt.getLastDayOfMonth()]); //output: 'Wednesday'
10601 * @return {Number} The day number (0-6).
10603 getLastDayOfMonth : function() {
10604 return this.getLastDateOfMonth().getDay();
10609 * Get the date of the first day of the month in which this date resides.
10612 getFirstDateOfMonth : function() {
10613 return new Date(this.getFullYear(), this.getMonth(), 1);
10617 * Get the date of the last day of the month in which this date resides.
10620 getLastDateOfMonth : function() {
10621 return new Date(this.getFullYear(), this.getMonth(), this.getDaysInMonth());
10625 * Get the number of days in the current month, adjusted for leap year.
10626 * @return {Number} The number of days in the month.
10628 getDaysInMonth: function() {
10629 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
10631 return function() { // return a closure for efficiency
10632 var m = this.getMonth();
10634 return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
10639 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
10640 * @return {String} 'st, 'nd', 'rd' or 'th'.
10642 getSuffix : function() {
10643 switch (this.getDate()) {
10660 * Creates and returns a new Date instance with the exact same date value as the called instance.
10661 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
10662 * variable will also be changed. When the intention is to create a new variable that will not
10663 * modify the original instance, you should create a clone.
10665 * Example of correctly cloning a date:
10668 var orig = new Date('10/1/2006');
10671 document.write(orig); //returns 'Thu Oct 05 2006'!
10674 var orig = new Date('10/1/2006');
10675 var copy = orig.clone();
10677 document.write(orig); //returns 'Thu Oct 01 2006'
10679 * @return {Date} The new Date instance.
10681 clone : function() {
10682 return new Date(this.getTime());
10686 * Checks if the current date is affected by Daylight Saving Time (DST).
10687 * @return {Boolean} True if the current date is affected by DST.
10689 isDST : function() {
10690 // adapted from http://extjs.com/forum/showthread.php?p=247172#post247172
10691 // courtesy of @geoffrey.mcgill
10692 return new Date(this.getFullYear(), 0, 1).getTimezoneOffset() != this.getTimezoneOffset();
10696 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
10697 * automatically adjusting for Daylight Saving Time (DST) where applicable.
10698 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
10699 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
10700 * @return {Date} this or the clone.
10702 clearTime : function(clone) {
10704 return this.clone().clearTime();
10707 // get current date before clearing time
10708 var d = this.getDate();
10712 this.setMinutes(0);
10713 this.setSeconds(0);
10714 this.setMilliseconds(0);
10716 if (this.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
10717 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
10718 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
10720 // increment hour until cloned date == current date
10721 for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
10724 this.setHours(c.getHours());
10731 * Provides a convenient method for performing basic date arithmetic. This method
10732 * does not modify the Date instance being called - it creates and returns
10733 * a new Date instance containing the resulting date value.
10738 var dt = new Date('10/29/2006').add(Date.DAY, 5);
10739 document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'
10741 // Negative values will be subtracted:
10742 var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
10743 document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'
10745 // You can even chain several calls together in one line:
10746 var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
10747 document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
10750 * @param {String} interval A valid date interval enum value.
10751 * @param {Number} value The amount to add to the current date.
10752 * @return {Date} The new Date instance.
10754 add : function(interval, value) {
10755 var d = this.clone();
10756 if (!interval || value === 0) return d;
10758 switch(interval.toLowerCase()) {
10760 d.setMilliseconds(this.getMilliseconds() + value);
10763 d.setSeconds(this.getSeconds() + value);
10766 d.setMinutes(this.getMinutes() + value);
10769 d.setHours(this.getHours() + value);
10772 d.setDate(this.getDate() + value);
10775 var day = this.getDate();
10777 day = Math.min(day, this.getFirstDateOfMonth().add('mo', value).getLastDateOfMonth().getDate());
10780 d.setMonth(this.getMonth() + value);
10783 d.setFullYear(this.getFullYear() + value);
10790 * Checks if this date falls on or between the given start and end dates.
10791 * @param {Date} start Start date
10792 * @param {Date} end End date
10793 * @return {Boolean} true if this date falls on or between the given start and end dates.
10795 between : function(start, end) {
10796 var t = this.getTime();
10797 return start.getTime() <= t && t <= end.getTime();
10803 * Formats a date given the supplied format string.
10804 * @param {String} format The format string.
10805 * @return {String} The formatted date.
10808 Date.prototype.format = Date.prototype.dateFormat;
10812 if (Ext.isSafari && (navigator.userAgent.match(/WebKit\/(\d+)/)[1] || NaN) < 420) {
10813 Ext.apply(Date.prototype, {
10814 _xMonth : Date.prototype.setMonth,
10815 _xDate : Date.prototype.setDate,
10817 // Bug in Safari 1.3, 2.0 (WebKit build < 420)
10818 // Date.setMonth does not work consistently if iMonth is not 0-11
10819 setMonth : function(num) {
10821 var n = Math.ceil(-num),
10822 back_year = Math.ceil(n / 12),
10823 month = (n % 12) ? 12 - n % 12 : 0;
10825 this.setFullYear(this.getFullYear() - back_year);
10827 return this._xMonth(month);
10829 return this._xMonth(num);
10833 // Bug in setDate() method (resolved in WebKit build 419.3, so to be safe we target Webkit builds < 420)
10834 // The parameter for Date.setDate() is converted to a signed byte integer in Safari
10835 // http://brianary.blogspot.com/2006/03/safari-date-bug.html
10836 setDate : function(d) {
10837 // use setTime() to workaround setDate() bug
10838 // subtract current day of month in milliseconds, then add desired day of month in milliseconds
10839 return this.setTime(this.getTime() - (this.getDate() - d) * 864e5);
10846 /* Some basic Date tests... (requires Firebug)
10848 Date.parseDate('', 'c'); // call Date.parseDate() once to force computation of regex string so we can console.log() it
10849 console.log('Insane Regex for "c" format: %o', Date.parseCodes.c.s); // view the insane regex for the "c" format specifier
10852 console.group('Standard Date.parseDate() Tests');
10853 console.log('Date.parseDate("2009-01-05T11:38:56", "c") = %o', Date.parseDate("2009-01-05T11:38:56", "c")); // assumes browser's timezone setting
10854 console.log('Date.parseDate("2009-02-04T12:37:55.001000", "c") = %o', Date.parseDate("2009-02-04T12:37:55.001000", "c")); // assumes browser's timezone setting
10855 console.log('Date.parseDate("2009-03-03T13:36:54,101000Z", "c") = %o', Date.parseDate("2009-03-03T13:36:54,101000Z", "c")); // UTC
10856 console.log('Date.parseDate("2009-04-02T14:35:53.901000-0530", "c") = %o', Date.parseDate("2009-04-02T14:35:53.901000-0530", "c")); // GMT-0530
10857 console.log('Date.parseDate("2009-05-01T15:34:52,9876000+08:00", "c") = %o', Date.parseDate("2009-05-01T15:34:52,987600+08:00", "c")); // GMT+08:00
10858 console.groupEnd();
10860 // ISO-8601 format as specified in http://www.w3.org/TR/NOTE-datetime
10861 // -- accepts ALL 6 levels of date-time granularity
10862 console.group('ISO-8601 Granularity Test (see http://www.w3.org/TR/NOTE-datetime)');
10863 console.log('Date.parseDate("1997", "c") = %o', Date.parseDate("1997", "c")); // YYYY (e.g. 1997)
10864 console.log('Date.parseDate("1997-07", "c") = %o', Date.parseDate("1997-07", "c")); // YYYY-MM (e.g. 1997-07)
10865 console.log('Date.parseDate("1997-07-16", "c") = %o', Date.parseDate("1997-07-16", "c")); // YYYY-MM-DD (e.g. 1997-07-16)
10866 console.log('Date.parseDate("1997-07-16T19:20+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20+01:00", "c")); // YYYY-MM-DDThh:mmTZD (e.g. 1997-07-16T19:20+01:00)
10867 console.log('Date.parseDate("1997-07-16T19:20:30+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30+01:00", "c")); // YYYY-MM-DDThh:mm:ssTZD (e.g. 1997-07-16T19:20:30+01:00)
10868 console.log('Date.parseDate("1997-07-16T19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16T19:20:30.45+01:00", "c")); // YYYY-MM-DDThh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
10869 console.log('Date.parseDate("1997-07-16 19:20:30.45+01:00", "c") = %o', Date.parseDate("1997-07-16 19:20:30.45+01:00", "c")); // YYYY-MM-DD hh:mm:ss.sTZD (e.g. 1997-07-16T19:20:30.45+01:00)
10870 console.log('Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)= %o', Date.parseDate("1997-13-16T19:20:30.45+01:00", "c", true)); // strict date parsing with invalid month value
10871 console.groupEnd();
10874 * @class Ext.util.DelayedTask
10875 * <p> The DelayedTask class provides a convenient way to "buffer" the execution of a method,
10876 * performing setTimeout where a new timeout cancels the old timeout. When called, the
10877 * task will wait the specified time period before executing. If durng that time period,
10878 * the task is called again, the original call will be cancelled. This continues so that
10879 * the function is only called a single time for each iteration.</p>
10880 * <p>This method is especially useful for things like detecting whether a user has finished
10881 * typing in a text field. An example would be performing validation on a keypress. You can
10882 * use this class to buffer the keypress events for a certain number of milliseconds, and
10883 * perform only if they stop for that amount of time. Usage:</p><pre><code>
10884 var task = new Ext.util.DelayedTask(function(){
10885 alert(Ext.getDom('myInputField').value.length);
10887 // Wait 500ms before calling our function. If the user presses another key
10888 // during that 500ms, it will be cancelled and we'll wait another 500ms.
10889 Ext.get('myInputField').on('keypress', function(){
10890 task.{@link #delay}(500);
10893 * <p>Note that we are using a DelayedTask here to illustrate a point. The configuration
10894 * option <tt>buffer</tt> for {@link Ext.util.Observable#addListener addListener/on} will
10895 * also setup a delayed task for you to buffer events.</p>
10896 * @constructor The parameters to this constructor serve as defaults and are not required.
10897 * @param {Function} fn (optional) The default function to timeout
10898 * @param {Object} scope (optional) The default scope of that timeout
10899 * @param {Array} args (optional) The default Array of arguments
10901 Ext.util.DelayedTask = function(fn, scope, args){
10907 fn.apply(scope, args || []);
10911 * Cancels any pending timeout and queues a new one
10912 * @param {Number} delay The milliseconds to delay
10913 * @param {Function} newFn (optional) Overrides function passed to constructor
10914 * @param {Object} newScope (optional) Overrides scope passed to constructor
10915 * @param {Array} newArgs (optional) Overrides args passed to constructor
10917 me.delay = function(delay, newFn, newScope, newArgs){
10920 scope = newScope || scope;
10921 args = newArgs || args;
10922 id = setInterval(call, delay);
10926 * Cancel the last queued timeout
10928 me.cancel = function(){
10935 * @class Ext.util.MixedCollection
\r
10936 * @extends Ext.util.Observable
\r
10937 * A Collection class that maintains both numeric indexes and keys and exposes events.
\r
10939 * @param {Boolean} allowFunctions True if the addAll function should add function references to the
\r
10940 * collection (defaults to false)
\r
10941 * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
\r
10942 * and return the key value for that item. This is used when available to look up the key on items that
\r
10943 * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
\r
10944 * equivalent to providing an implementation for the {@link #getKey} method.
\r
10946 Ext.util.MixedCollection = function(allowFunctions, keyFn){
\r
10954 * Fires when the collection is cleared.
\r
10959 * Fires when an item is added to the collection.
\r
10960 * @param {Number} index The index at which the item was added.
\r
10961 * @param {Object} o The item added.
\r
10962 * @param {String} key The key associated with the added item.
\r
10967 * Fires when an item is replaced in the collection.
\r
10968 * @param {String} key he key associated with the new added.
\r
10969 * @param {Object} old The item being replaced.
\r
10970 * @param {Object} new The new item.
\r
10975 * Fires when an item is removed from the collection.
\r
10976 * @param {Object} o The item being removed.
\r
10977 * @param {String} key (optional) The key associated with the removed item.
\r
10982 this.allowFunctions = allowFunctions === true;
\r
10984 this.getKey = keyFn;
\r
10986 Ext.util.MixedCollection.superclass.constructor.call(this);
\r
10989 Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
\r
10990 allowFunctions : false,
\r
10993 * Adds an item to the collection. Fires the {@link #add} event when complete.
\r
10994 * @param {String} key <p>The key to associate with the item, or the new item.</p>
\r
10995 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
\r
10996 * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
\r
10997 * will be able to <i>derive</i> the key for the new item. In this case just pass the new item in
\r
10998 * this parameter.</p>
\r
10999 * @param {Object} o The item to add.
\r
11000 * @return {Object} The item added.
\r
11002 add: function(key, o){
\r
11003 if(arguments.length == 1){
\r
11004 o = arguments[0];
\r
11005 key = this.getKey(o);
\r
11007 if(typeof key != 'undefined' && key !== null){
\r
11008 var old = this.map[key];
\r
11009 if(typeof old != 'undefined'){
\r
11010 return this.replace(key, o);
\r
11012 this.map[key] = o;
\r
11015 this.items.push(o);
\r
11016 this.keys.push(key);
\r
11017 this.fireEvent('add', this.length-1, o, key);
\r
11022 * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
\r
11023 * simply returns <tt style="font-weight:bold;">item.id</tt> but you can provide your own implementation
\r
11024 * to return a different value as in the following examples:
\r
11027 var mc = new Ext.util.MixedCollection();
\r
11028 mc.add(someEl.dom.id, someEl);
\r
11029 mc.add(otherEl.dom.id, otherEl);
\r
11033 var mc = new Ext.util.MixedCollection();
\r
11034 mc.getKey = function(el){
\r
11035 return el.dom.id;
\r
11040 // or via the constructor
\r
11041 var mc = new Ext.util.MixedCollection(false, function(el){
\r
11042 return el.dom.id;
\r
11047 * @param {Object} item The item for which to find the key.
\r
11048 * @return {Object} The key for the passed item.
\r
11050 getKey : function(o){
\r
11055 * Replaces an item in the collection. Fires the {@link #replace} event when complete.
\r
11056 * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
\r
11057 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
\r
11058 * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
\r
11059 * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
\r
11060 * with one having the same key value, then just pass the replacement item in this parameter.</p>
\r
11061 * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
\r
11063 * @return {Object} The new item.
\r
11065 replace : function(key, o){
\r
11066 if(arguments.length == 1){
\r
11067 o = arguments[0];
\r
11068 key = this.getKey(o);
\r
11070 var old = this.map[key];
\r
11071 if(typeof key == "undefined" || key === null || typeof old == "undefined"){
\r
11072 return this.add(key, o);
\r
11074 var index = this.indexOfKey(key);
\r
11075 this.items[index] = o;
\r
11076 this.map[key] = o;
\r
11077 this.fireEvent("replace", key, old, o);
\r
11082 * Adds all elements of an Array or an Object to the collection.
\r
11083 * @param {Object/Array} objs An Object containing properties which will be added to the collection, or
\r
11084 * an Array of values, each of which are added to the collection.
\r
11086 addAll : function(objs){
\r
11087 if(arguments.length > 1 || Ext.isArray(objs)){
\r
11088 var args = arguments.length > 1 ? arguments : objs;
\r
11089 for(var i = 0, len = args.length; i < len; i++){
\r
11090 this.add(args[i]);
\r
11093 for(var key in objs){
\r
11094 if(this.allowFunctions || typeof objs[key] != "function"){
\r
11095 this.add(key, objs[key]);
\r
11102 * Executes the specified function once for every item in the collection, passing the following arguments:
\r
11103 * <div class="mdetail-params"><ul>
\r
11104 * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
\r
11105 * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
\r
11106 * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
\r
11108 * The function should return a boolean value. Returning false from the function will stop the iteration.
\r
11109 * @param {Function} fn The function to execute for each item.
\r
11110 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11112 each : function(fn, scope){
\r
11113 var items = [].concat(this.items); // each safe for removal
\r
11114 for(var i = 0, len = items.length; i < len; i++){
\r
11115 if(fn.call(scope || items[i], items[i], i, len) === false){
\r
11122 * Executes the specified function once for every key in the collection, passing each
\r
11123 * key, and its associated item as the first two parameters.
\r
11124 * @param {Function} fn The function to execute for each item.
\r
11125 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11127 eachKey : function(fn, scope){
\r
11128 for(var i = 0, len = this.keys.length; i < len; i++){
\r
11129 fn.call(scope || window, this.keys[i], this.items[i], i, len);
\r
11134 * Returns the first item in the collection which elicits a true return value from the
\r
11135 * passed selection function.
\r
11136 * @param {Function} fn The selection function to execute for each item.
\r
11137 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11138 * @return {Object} The first item in the collection which returned true from the selection function.
\r
11140 find : function(fn, scope){
\r
11141 for(var i = 0, len = this.items.length; i < len; i++){
\r
11142 if(fn.call(scope || window, this.items[i], this.keys[i])){
\r
11143 return this.items[i];
\r
11150 * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
\r
11151 * @param {Number} index The index to insert the item at.
\r
11152 * @param {String} key The key to associate with the new item, or the item itself.
\r
11153 * @param {Object} o (optional) If the second parameter was a key, the new item.
\r
11154 * @return {Object} The item inserted.
\r
11156 insert : function(index, key, o){
\r
11157 if(arguments.length == 2){
\r
11158 o = arguments[1];
\r
11159 key = this.getKey(o);
\r
11161 if(this.containsKey(key)){
\r
11162 this.suspendEvents();
\r
11163 this.removeKey(key);
\r
11164 this.resumeEvents();
\r
11166 if(index >= this.length){
\r
11167 return this.add(key, o);
\r
11170 this.items.splice(index, 0, o);
\r
11171 if(typeof key != "undefined" && key !== null){
\r
11172 this.map[key] = o;
\r
11174 this.keys.splice(index, 0, key);
\r
11175 this.fireEvent("add", index, o, key);
\r
11180 * Remove an item from the collection.
\r
11181 * @param {Object} o The item to remove.
\r
11182 * @return {Object} The item removed or false if no item was removed.
\r
11184 remove : function(o){
\r
11185 return this.removeAt(this.indexOf(o));
\r
11189 * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
\r
11190 * @param {Number} index The index within the collection of the item to remove.
\r
11191 * @return {Object} The item removed or false if no item was removed.
\r
11193 removeAt : function(index){
\r
11194 if(index < this.length && index >= 0){
\r
11196 var o = this.items[index];
\r
11197 this.items.splice(index, 1);
\r
11198 var key = this.keys[index];
\r
11199 if(typeof key != "undefined"){
\r
11200 delete this.map[key];
\r
11202 this.keys.splice(index, 1);
\r
11203 this.fireEvent("remove", o, key);
\r
11210 * Removed an item associated with the passed key fom the collection.
\r
11211 * @param {String} key The key of the item to remove.
\r
11212 * @return {Object} The item removed or false if no item was removed.
\r
11214 removeKey : function(key){
\r
11215 return this.removeAt(this.indexOfKey(key));
\r
11219 * Returns the number of items in the collection.
\r
11220 * @return {Number} the number of items in the collection.
\r
11222 getCount : function(){
\r
11223 return this.length;
\r
11227 * Returns index within the collection of the passed Object.
\r
11228 * @param {Object} o The item to find the index of.
\r
11229 * @return {Number} index of the item. Returns -1 if not found.
\r
11231 indexOf : function(o){
\r
11232 return this.items.indexOf(o);
\r
11236 * Returns index within the collection of the passed key.
\r
11237 * @param {String} key The key to find the index of.
\r
11238 * @return {Number} index of the key.
\r
11240 indexOfKey : function(key){
\r
11241 return this.keys.indexOf(key);
\r
11245 * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent
\r
11246 * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
\r
11247 * @param {String/Number} key The key or index of the item.
\r
11248 * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
\r
11249 * If an item was found, but is a Class, returns <tt>null</tt>.
\r
11251 item : function(key){
\r
11252 var mk = this.map[key],
\r
11253 item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
\r
11254 return !Ext.isFunction(item) || this.allowFunctions ? item : null; // for prototype!
\r
11258 * Returns the item at the specified index.
\r
11259 * @param {Number} index The index of the item.
\r
11260 * @return {Object} The item at the specified index.
\r
11262 itemAt : function(index){
\r
11263 return this.items[index];
\r
11267 * Returns the item associated with the passed key.
\r
11268 * @param {String/Number} key The key of the item.
\r
11269 * @return {Object} The item associated with the passed key.
\r
11271 key : function(key){
\r
11272 return this.map[key];
\r
11276 * Returns true if the collection contains the passed Object as an item.
\r
11277 * @param {Object} o The Object to look for in the collection.
\r
11278 * @return {Boolean} True if the collection contains the Object as an item.
\r
11280 contains : function(o){
\r
11281 return this.indexOf(o) != -1;
\r
11285 * Returns true if the collection contains the passed Object as a key.
\r
11286 * @param {String} key The key to look for in the collection.
\r
11287 * @return {Boolean} True if the collection contains the Object as a key.
\r
11289 containsKey : function(key){
\r
11290 return typeof this.map[key] != "undefined";
\r
11294 * Removes all items from the collection. Fires the {@link #clear} event when complete.
\r
11296 clear : function(){
\r
11301 this.fireEvent("clear");
\r
11305 * Returns the first item in the collection.
\r
11306 * @return {Object} the first item in the collection..
\r
11308 first : function(){
\r
11309 return this.items[0];
\r
11313 * Returns the last item in the collection.
\r
11314 * @return {Object} the last item in the collection..
\r
11316 last : function(){
\r
11317 return this.items[this.length-1];
\r
11321 _sort : function(property, dir, fn){
\r
11324 dsc = String(dir).toUpperCase() == "DESC" ? -1 : 1,
\r
11325 c = [], k = this.keys, items = this.items;
\r
11327 fn = fn || function(a, b){
\r
11330 for(i = 0, len = items.length; i < len; i++){
\r
11331 c[c.length] = {key: k[i], value: items[i], index: i};
\r
11333 c.sort(function(a, b){
\r
11334 var v = fn(a[property], b[property]) * dsc;
\r
11336 v = (a.index < b.index ? -1 : 1);
\r
11340 for(i = 0, len = c.length; i < len; i++){
\r
11341 items[i] = c[i].value;
\r
11344 this.fireEvent("sort", this);
\r
11348 * Sorts this collection with the passed comparison function
\r
11349 * @param {String} direction (optional) "ASC" or "DESC"
\r
11350 * @param {Function} fn (optional) comparison function
\r
11352 sort : function(dir, fn){
\r
11353 this._sort("value", dir, fn);
\r
11357 * Sorts this collection by keys
\r
11358 * @param {String} direction (optional) "ASC" or "DESC"
\r
11359 * @param {Function} fn (optional) a comparison function (defaults to case insensitive string)
\r
11361 keySort : function(dir, fn){
\r
11362 this._sort("key", dir, fn || function(a, b){
\r
11363 var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
\r
11364 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
\r
11369 * Returns a range of items in this collection
\r
11370 * @param {Number} startIndex (optional) defaults to 0
\r
11371 * @param {Number} endIndex (optional) default to the last item
\r
11372 * @return {Array} An array of items
\r
11374 getRange : function(start, end){
\r
11375 var items = this.items;
\r
11376 if(items.length < 1){
\r
11379 start = start || 0;
\r
11380 end = Math.min(typeof end == "undefined" ? this.length-1 : end, this.length-1);
\r
11382 if(start <= end){
\r
11383 for(i = start; i <= end; i++) {
\r
11384 r[r.length] = items[i];
\r
11387 for(i = start; i >= end; i--) {
\r
11388 r[r.length] = items[i];
\r
11395 * Filter the <i>objects</i> in this collection by a specific property.
\r
11396 * Returns a new collection that has been filtered.
\r
11397 * @param {String} property A property on your objects
\r
11398 * @param {String/RegExp} value Either string that the property values
\r
11399 * should start with or a RegExp to test against the property
\r
11400 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
\r
11401 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
\r
11402 * @return {MixedCollection} The new filtered collection
\r
11404 filter : function(property, value, anyMatch, caseSensitive){
\r
11405 if(Ext.isEmpty(value, false)){
\r
11406 return this.clone();
\r
11408 value = this.createValueMatcher(value, anyMatch, caseSensitive);
\r
11409 return this.filterBy(function(o){
\r
11410 return o && value.test(o[property]);
\r
11415 * Filter by a function. Returns a <i>new</i> collection that has been filtered.
\r
11416 * The passed function will be called with each object in the collection.
\r
11417 * If the function returns true, the value is included otherwise it is filtered.
\r
11418 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
\r
11419 * @param {Object} scope (optional) The scope of the function (defaults to this)
\r
11420 * @return {MixedCollection} The new filtered collection
\r
11422 filterBy : function(fn, scope){
\r
11423 var r = new Ext.util.MixedCollection();
\r
11424 r.getKey = this.getKey;
\r
11425 var k = this.keys, it = this.items;
\r
11426 for(var i = 0, len = it.length; i < len; i++){
\r
11427 if(fn.call(scope||this, it[i], k[i])){
\r
11428 r.add(k[i], it[i]);
\r
11435 * Finds the index of the first matching object in this collection by a specific property/value.
\r
11436 * @param {String} property The name of a property on your objects.
\r
11437 * @param {String/RegExp} value A string that the property values
\r
11438 * should start with or a RegExp to test against the property.
\r
11439 * @param {Number} start (optional) The index to start searching at (defaults to 0).
\r
11440 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
\r
11441 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
\r
11442 * @return {Number} The matched index or -1
\r
11444 findIndex : function(property, value, start, anyMatch, caseSensitive){
\r
11445 if(Ext.isEmpty(value, false)){
\r
11448 value = this.createValueMatcher(value, anyMatch, caseSensitive);
\r
11449 return this.findIndexBy(function(o){
\r
11450 return o && value.test(o[property]);
\r
11455 * Find the index of the first matching object in this collection by a function.
\r
11456 * If the function returns <i>true</i> it is considered a match.
\r
11457 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
\r
11458 * @param {Object} scope (optional) The scope of the function (defaults to this).
\r
11459 * @param {Number} start (optional) The index to start searching at (defaults to 0).
\r
11460 * @return {Number} The matched index or -1
\r
11462 findIndexBy : function(fn, scope, start){
\r
11463 var k = this.keys, it = this.items;
\r
11464 for(var i = (start||0), len = it.length; i < len; i++){
\r
11465 if(fn.call(scope||this, it[i], k[i])){
\r
11473 createValueMatcher : function(value, anyMatch, caseSensitive){
\r
11474 if(!value.exec){ // not a regex
\r
11475 value = String(value);
\r
11476 value = new RegExp((anyMatch === true ? '' : '^') + Ext.escapeRe(value), caseSensitive ? '' : 'i');
\r
11482 * Creates a shallow copy of this collection
\r
11483 * @return {MixedCollection}
\r
11485 clone : function(){
\r
11486 var r = new Ext.util.MixedCollection();
\r
11487 var k = this.keys, it = this.items;
\r
11488 for(var i = 0, len = it.length; i < len; i++){
\r
11489 r.add(k[i], it[i]);
\r
11491 r.getKey = this.getKey;
\r
11496 * This method calls {@link #item item()}.
\r
11497 * Returns the item associated with the passed key OR index. Key has priority over index. This is the equivalent
\r
11498 * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
\r
11499 * @param {String/Number} key The key or index of the item.
\r
11500 * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
\r
11501 * If an item was found, but is a Class, returns <tt>null</tt>.
\r
11503 Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;/**
11504 * @class Ext.util.JSON
11505 * Modified version of Douglas Crockford"s json.js that doesn"t
11506 * mess with the Object prototype
11507 * http://www.json.org/js.html
11510 Ext.util.JSON = new (function(){
11511 var useHasOwn = !!{}.hasOwnProperty,
11512 isNative = function() {
11513 var useNative = null;
11515 return function() {
11516 if (useNative === null) {
11517 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
11523 pad = function(n) {
11524 return n < 10 ? "0" + n : n;
11526 doDecode = function(json){
11527 return eval("(" + json + ')');
11529 doEncode = function(o){
11530 if(typeof o == "undefined" || o === null){
11532 }else if(Ext.isArray(o)){
11533 return encodeArray(o);
11534 }else if(Object.prototype.toString.apply(o) === '[object Date]'){
11535 return Ext.util.JSON.encodeDate(o);
11536 }else if(typeof o == "string"){
11537 return encodeString(o);
11538 }else if(typeof o == "number"){
11539 return isFinite(o) ? String(o) : "null";
11540 }else if(typeof o == "boolean"){
11543 var a = ["{"], b, i, v;
11545 if(!useHasOwn || o.hasOwnProperty(i)) {
11547 switch (typeof v) {
11556 a.push(doEncode(i), ":",
11557 v === null ? "null" : doEncode(v));
11575 encodeString = function(s){
11576 if (/["\\\x00-\x1f]/.test(s)) {
11577 return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
11582 c = b.charCodeAt();
11584 Math.floor(c / 16).toString(16) +
11585 (c % 16).toString(16);
11588 return '"' + s + '"';
11590 encodeArray = function(o){
11591 var a = ["["], b, i, l = o.length, v;
11592 for (i = 0; i < l; i += 1) {
11594 switch (typeof v) {
11603 a.push(v === null ? "null" : Ext.util.JSON.encode(v));
11611 this.encodeDate = function(o){
11612 return '"' + o.getFullYear() + "-" +
11613 pad(o.getMonth() + 1) + "-" +
11614 pad(o.getDate()) + "T" +
11615 pad(o.getHours()) + ":" +
11616 pad(o.getMinutes()) + ":" +
11617 pad(o.getSeconds()) + '"';
11621 * Encodes an Object, Array or other value
11622 * @param {Mixed} o The variable to encode
11623 * @return {String} The JSON string
11625 this.encode = function() {
11627 return function(o) {
11629 // setup encoding function on first access
11630 ec = isNative() ? JSON.stringify : doEncode;
11638 * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
11639 * @param {String} json The JSON string
11640 * @return {Object} The resulting object
11642 this.decode = function() {
11644 return function(json) {
11646 // setup decoding function on first access
11647 dc = isNative() ? JSON.parse : doDecode;
11655 * Shorthand for {@link Ext.util.JSON#encode}
11656 * @param {Mixed} o The variable to encode
11657 * @return {String} The JSON string
11661 Ext.encode = Ext.util.JSON.encode;
11663 * Shorthand for {@link Ext.util.JSON#decode}
11664 * @param {String} json The JSON string
11665 * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
11666 * @return {Object} The resulting object
11670 Ext.decode = Ext.util.JSON.decode;
11672 * @class Ext.util.Format
\r
11673 * Reusable data formatting functions
\r
11676 Ext.util.Format = function(){
\r
11677 var trimRe = /^\s+|\s+$/g;
\r
11680 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
\r
11681 * @param {String} value The string to truncate
\r
11682 * @param {Number} length The maximum length to allow before truncating
\r
11683 * @param {Boolean} word True to try to find a common work break
\r
11684 * @return {String} The converted text
\r
11686 ellipsis : function(value, len, word){
\r
11687 if(value && value.length > len){
\r
11689 var vs = value.substr(0, len - 2);
\r
11690 var index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
\r
11691 if(index == -1 || index < (len - 15)){
\r
11692 return value.substr(0, len - 3) + "...";
\r
11694 return vs.substr(0, index) + "...";
\r
11697 return value.substr(0, len - 3) + "...";
\r
11704 * Checks a reference and converts it to empty string if it is undefined
\r
11705 * @param {Mixed} value Reference to check
\r
11706 * @return {Mixed} Empty string if converted, otherwise the original value
\r
11708 undef : function(value){
\r
11709 return value !== undefined ? value : "";
\r
11713 * Checks a reference and converts it to the default value if it's empty
\r
11714 * @param {Mixed} value Reference to check
\r
11715 * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
\r
11716 * @return {String}
\r
11718 defaultValue : function(value, defaultValue){
\r
11719 return value !== undefined && value !== '' ? value : defaultValue;
\r
11723 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
\r
11724 * @param {String} value The string to encode
\r
11725 * @return {String} The encoded text
\r
11727 htmlEncode : function(value){
\r
11728 return !value ? value : String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
\r
11732 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
\r
11733 * @param {String} value The string to decode
\r
11734 * @return {String} The decoded text
\r
11736 htmlDecode : function(value){
\r
11737 return !value ? value : String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
\r
11741 * Trims any whitespace from either side of a string
\r
11742 * @param {String} value The text to trim
\r
11743 * @return {String} The trimmed text
\r
11745 trim : function(value){
\r
11746 return String(value).replace(trimRe, "");
\r
11750 * Returns a substring from within an original string
\r
11751 * @param {String} value The original text
\r
11752 * @param {Number} start The start index of the substring
\r
11753 * @param {Number} length The length of the substring
\r
11754 * @return {String} The substring
\r
11756 substr : function(value, start, length){
\r
11757 return String(value).substr(start, length);
\r
11761 * Converts a string to all lower case letters
\r
11762 * @param {String} value The text to convert
\r
11763 * @return {String} The converted text
\r
11765 lowercase : function(value){
\r
11766 return String(value).toLowerCase();
\r
11770 * Converts a string to all upper case letters
\r
11771 * @param {String} value The text to convert
\r
11772 * @return {String} The converted text
\r
11774 uppercase : function(value){
\r
11775 return String(value).toUpperCase();
\r
11779 * Converts the first character only of a string to upper case
\r
11780 * @param {String} value The text to convert
\r
11781 * @return {String} The converted text
\r
11783 capitalize : function(value){
\r
11784 return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
\r
11788 call : function(value, fn){
\r
11789 if(arguments.length > 2){
\r
11790 var args = Array.prototype.slice.call(arguments, 2);
\r
11791 args.unshift(value);
\r
11792 return eval(fn).apply(window, args);
\r
11794 return eval(fn).call(window, value);
\r
11799 * Format a number as US currency
\r
11800 * @param {Number/String} value The numeric value to format
\r
11801 * @return {String} The formatted currency string
\r
11803 usMoney : function(v){
\r
11804 v = (Math.round((v-0)*100))/100;
\r
11805 v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
\r
11807 var ps = v.split('.');
\r
11808 var whole = ps[0];
\r
11809 var sub = ps[1] ? '.'+ ps[1] : '.00';
\r
11810 var r = /(\d+)(\d{3})/;
\r
11811 while (r.test(whole)) {
\r
11812 whole = whole.replace(r, '$1' + ',' + '$2');
\r
11815 if(v.charAt(0) == '-'){
\r
11816 return '-$' + v.substr(1);
\r
11822 * Parse a value into a formatted date using the specified format pattern.
\r
11823 * @param {String/Date} value The value to format (Strings must conform to the format expected by the javascript Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method)
\r
11824 * @param {String} format (optional) Any valid date format string (defaults to 'm/d/Y')
\r
11825 * @return {String} The formatted date string
\r
11827 date : function(v, format){
\r
11831 if(!Ext.isDate(v)){
\r
11832 v = new Date(Date.parse(v));
\r
11834 return v.dateFormat(format || "m/d/Y");
\r
11838 * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
\r
11839 * @param {String} format Any valid date format string
\r
11840 * @return {Function} The date formatting function
\r
11842 dateRenderer : function(format){
\r
11843 return function(v){
\r
11844 return Ext.util.Format.date(v, format);
\r
11849 stripTagsRE : /<\/?[^>]+>/gi,
\r
11852 * Strips all HTML tags
\r
11853 * @param {Mixed} value The text from which to strip tags
\r
11854 * @return {String} The stripped text
\r
11856 stripTags : function(v){
\r
11857 return !v ? v : String(v).replace(this.stripTagsRE, "");
\r
11860 stripScriptsRe : /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
\r
11863 * Strips all script tags
\r
11864 * @param {Mixed} value The text from which to strip script tags
\r
11865 * @return {String} The stripped text
\r
11867 stripScripts : function(v){
\r
11868 return !v ? v : String(v).replace(this.stripScriptsRe, "");
\r
11872 * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
\r
11873 * @param {Number/String} size The numeric value to format
\r
11874 * @return {String} The formatted file size
\r
11876 fileSize : function(size){
\r
11877 if(size < 1024) {
\r
11878 return size + " bytes";
\r
11879 } else if(size < 1048576) {
\r
11880 return (Math.round(((size*10) / 1024))/10) + " KB";
\r
11882 return (Math.round(((size*10) / 1048576))/10) + " MB";
\r
11887 * It does simple math for use in a template, for example:<pre><code>
\r
11888 * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
\r
11890 * @return {Function} A function that operates on the passed value.
\r
11892 math : function(){
\r
11894 return function(v, a){
\r
11896 fns[a] = new Function('v', 'return v ' + a + ';');
\r
11898 return fns[a](v);
\r
11903 * Rounds the passed number to the required decimal precision.
\r
11904 * @param {Number/String} value The numeric value to round.
\r
11905 * @param {Number} precision The number of decimal places to which to round the first parameter's value.
\r
11906 * @return {Number} The rounded value.
\r
11908 round : function(value, precision) {
\r
11909 var result = Number(value);
\r
11910 if (typeof precision == 'number') {
\r
11911 precision = Math.pow(10, precision);
\r
11912 result = Math.round(value * precision) / precision;
\r
11918 * Formats the number according to the format string.
\r
11919 * <div style="margin-left:40px">examples (123456.789):
\r
11920 * <div style="margin-left:10px">
\r
11921 * 0 - (123456) show only digits, no precision<br>
\r
11922 * 0.00 - (123456.78) show only digits, 2 precision<br>
\r
11923 * 0.0000 - (123456.7890) show only digits, 4 precision<br>
\r
11924 * 0,000 - (123,456) show comma and digits, no precision<br>
\r
11925 * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
\r
11926 * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
\r
11927 * To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end.
\r
11928 * For example: 0.000,00/i
\r
11930 * @param {Number} v The number to format.
\r
11931 * @param {String} format The way you would like to format this text.
\r
11932 * @return {String} The formatted number.
\r
11934 number: function(v, format) {
\r
11938 v = Ext.num(v, NaN);
\r
11948 if(format.substr(format.length - 2) == '/i'){
\r
11949 format = format.substr(0, format.length - 2);
\r
11955 var hasComma = format.indexOf(comma) != -1,
\r
11956 psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
\r
11958 if(1 < psplit.length){
\r
11959 v = v.toFixed(psplit[1].length);
\r
11960 }else if(2 < psplit.length){
\r
11961 throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
\r
11963 v = v.toFixed(0);
\r
11966 var fnum = v.toString();
\r
11968 psplit = fnum.split('.');
\r
11970 var cnum = psplit[0], parr = [], j = cnum.length, m = Math.floor(j / 3), n = cnum.length % 3 || 3;
\r
11972 for(var i = 0; i < j; i += n){
\r
11976 parr[parr.length] = cnum.substr(i, n);
\r
11979 fnum = parr.join(comma);
\r
11981 fnum += dec + psplit[1];
\r
11985 return (neg ? '-' : '') + format.replace(/[\d,?\.?]+/, fnum);
\r
11989 * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
\r
11990 * @param {String} format Any valid number format string for {@link #number}
\r
11991 * @return {Function} The number formatting function
\r
11993 numberRenderer : function(format){
\r
11994 return function(v){
\r
11995 return Ext.util.Format.number(v, format);
\r
12000 * Selectively do a plural form of a word based on a numeric value. For example, in a template,
\r
12001 * {commentCount:plural("Comment")} would result in "1 Comment" if commentCount was 1 or would be "x Comments"
\r
12002 * if the value is 0 or greater than 1.
\r
12003 * @param {Number} value The value to compare against
\r
12004 * @param {String} singular The singular form of the word
\r
12005 * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
\r
12007 plural : function(v, s, p){
\r
12008 return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
\r
12012 * Converts newline characters to the HTML tag <br/>
\r
12013 * @param {String} The string value to format.
\r
12014 * @return {String} The string with embedded <br/> tags in place of newlines.
\r
12016 nl2br : function(v){
\r
12017 return v === undefined || v === null ? '' : v.replace(/\n/g, '<br/>');
\r
12021 * @class Ext.XTemplate
12022 * @extends Ext.Template
12023 * <p>A template class that supports advanced functionality like autofilling arrays, conditional processing with
12024 * basic comparison operators, sub-templates, basic math function support, special built-in template variables,
12025 * inline code execution and more. XTemplate also provides the templating mechanism built into {@link Ext.DataView}.</p>
12026 * <p>XTemplate supports many special tags and built-in operators that aren't defined as part of the API, but are
12027 * supported in the templates that can be created. The following examples demonstrate all of the supported features.
12028 * This is the data object used for reference in each code example:</p>
12031 name: 'Jack Slocum',
12032 title: 'Lead Developer',
12033 company: 'Ext JS, LLC',
12034 email: 'jack@extjs.com',
12035 address: '4 Red Bulls Drive',
12039 drinks: ['Red Bull', 'Coffee', 'Water'],
12041 name: 'Sara Grace',
12047 name: 'John James',
12052 * <p><b>Auto filling of arrays</b><br/>The <tt>tpl</tt> tag and the <tt>for</tt> operator are used
12053 * to process the provided data object. If <tt>for="."</tt> is specified, the data object provided
12054 * is examined. If the variable in <tt>for</tt> is an array, it will auto-fill, repeating the template
12055 * block inside the <tt>tpl</tt> tag for each item in the array:</p>
12057 var tpl = new Ext.XTemplate(
12059 '<tpl for=".">',
12060 '<p>{name}</p>',
12063 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
12065 * <p><b>Scope switching</b><br/>The <tt>for</tt> property can be leveraged to access specified members
12066 * of the provided data object to populate the template:</p>
12068 var tpl = new Ext.XTemplate(
12069 '<p>Name: {name}</p>',
12070 '<p>Title: {title}</p>',
12071 '<p>Company: {company}</p>',
12073 '<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
12074 '<p>{name}</p>',
12077 tpl.overwrite(panel.body, data);
12079 * <p><b>Access to parent object from within sub-template scope</b><br/>When processing a sub-template, for example while
12080 * looping through a child array, you can access the parent object's members via the <tt>parent</tt> object:</p>
12082 var tpl = new Ext.XTemplate(
12083 '<p>Name: {name}</p>',
12085 '<tpl for="kids">',
12086 '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
12087 '<p>{name}</p>',
12088 '<p>Dad: {parent.name}</p>',
12092 tpl.overwrite(panel.body, data);
12094 * <p><b>Array item index and basic math support</b> <br/>While processing an array, the special variable <tt>{#}</tt>
12095 * will provide the current array index + 1 (starts at 1, not 0). Templates also support the basic math operators
12096 * + - * and / that can be applied directly on numeric data values:</p>
12098 var tpl = new Ext.XTemplate(
12099 '<p>Name: {name}</p>',
12101 '<tpl for="kids">',
12102 '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
12103 '<p>{#}: {name}</p>', // <-- Auto-number each item
12104 '<p>In 5 Years: {age+5}</p>', // <-- Basic math
12105 '<p>Dad: {parent.name}</p>',
12109 tpl.overwrite(panel.body, data);
12111 * <p><b>Auto-rendering of flat arrays</b> <br/>Flat arrays that contain values (and not objects) can be auto-rendered
12112 * using the special <tt>{.}</tt> variable inside a loop. This variable will represent the value of
12113 * the array at the current index:</p>
12115 var tpl = new Ext.XTemplate(
12116 '<p>{name}\'s favorite beverages:</p>',
12117 '<tpl for="drinks">',
12118 '<div> - {.}</div>',
12121 tpl.overwrite(panel.body, data);
12123 * <p><b>Basic conditional logic</b> <br/>Using the <tt>tpl</tt> tag and the <tt>if</tt>
12124 * operator you can provide conditional checks for deciding whether or not to render specific parts of the template.
12125 * Note that there is no <tt>else</tt> operator — if needed, you should use two opposite <tt>if</tt> statements.
12126 * Properly-encoded attributes are required as seen in the following example:</p>
12128 var tpl = new Ext.XTemplate(
12129 '<p>Name: {name}</p>',
12131 '<tpl for="kids">',
12132 '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
12133 '<p>{name}</p>',
12137 tpl.overwrite(panel.body, data);
12139 * <p><b>Ability to execute arbitrary inline code</b> <br/>In an XTemplate, anything between {[ ... ]} is considered
12140 * code to be executed in the scope of the template. There are some special variables available in that code:
12142 * <li><b><tt>values</tt></b>: The values in the current scope. If you are using scope changing sub-templates, you
12143 * can change what <tt>values</tt> is.</li>
12144 * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
12145 * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the loop you are in (1-based).</li>
12146 * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length of the array you are looping.</li>
12147 * <li><b><tt>fm</tt></b>: An alias for <tt>Ext.util.Format</tt>.</li>
12149 * This example demonstrates basic row striping using an inline code block and the <tt>xindex</tt> variable:</p>
12151 var tpl = new Ext.XTemplate(
12152 '<p>Name: {name}</p>',
12153 '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
12155 '<tpl for="kids">',
12156 '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
12161 tpl.overwrite(panel.body, data);
12163 * <p><b>Template member functions</b> <br/>One or more member functions can be defined directly on the config
12164 * object passed into the XTemplate constructor for more complex processing:</p>
12166 var tpl = new Ext.XTemplate(
12167 '<p>Name: {name}</p>',
12169 '<tpl for="kids">',
12170 '<tpl if="this.isGirl(name)">',
12171 '<p>Girl: {name} - {age}</p>',
12173 '<tpl if="this.isGirl(name) == false">',
12174 '<p>Boy: {name} - {age}</p>',
12176 '<tpl if="this.isBaby(age)">',
12177 '<p>{name} is a baby!</p>',
12179 '</tpl></p>', {
12180 isGirl: function(name){
12181 return name == 'Sara Grace';
12183 isBaby: function(age){
12187 tpl.overwrite(panel.body, data);
12190 * @param {String/Array/Object} parts The HTML fragment or an array of fragments to join(""), or multiple arguments
12191 * to join("") that can also include a config object
12193 Ext.XTemplate = function(){
12194 Ext.XTemplate.superclass.constructor.apply(this, arguments);
12198 re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
12199 nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
12200 ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
12201 execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
12209 RETURN = 'return ',
12210 WITHVALUES = 'with(values){ ';
12212 s = ['<tpl>', s, '</tpl>'].join('');
12214 while((m = s.match(re))){
12215 var m2 = m[0].match(nameRe),
12216 m3 = m[0].match(ifRe),
12217 m4 = m[0].match(execRe),
12221 name = m2 && m2[1] ? m2[1] : '';
12224 exp = m3 && m3[1] ? m3[1] : null;
12226 fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
12230 exp = m4 && m4[1] ? m4[1] : null;
12232 exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
12237 case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
12238 case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
12239 default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
12249 s = s.replace(m[0], '{xtpl'+ id + '}');
12252 Ext.each(tpls, function(t) {
12255 me.master = tpls[tpls.length-1];
12258 Ext.extend(Ext.XTemplate, Ext.Template, {
12260 re : /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
12262 codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
12265 applySubTemplate : function(id, values, parent, xindex, xcount){
12271 if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
12272 (t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
12275 vs = t.target ? t.target.call(me, values, parent) : values;
12277 parent = t.target ? values : parent;
12278 if(t.target && Ext.isArray(vs)){
12279 Ext.each(vs, function(v, i) {
12280 buf[buf.length] = t.compiled.call(me, v, parent, i+1, len);
12282 return buf.join('');
12284 return t.compiled.call(me, vs, parent, xindex, xcount);
12288 compileTpl : function(tpl){
12289 var fm = Ext.util.Format,
12290 useF = this.disableFormats !== true,
12291 sep = Ext.isGecko ? "+" : ",",
12294 function fn(m, name, format, args, math){
12295 if(name.substr(0, 4) == 'xtpl'){
12296 return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
12301 }else if(name === '#'){
12303 }else if(name.indexOf('.') != -1){
12306 v = "values['" + name + "']";
12309 v = '(' + v + math + ')';
12311 if (format && useF) {
12312 args = args ? ',' + args : "";
12313 if(format.substr(0, 5) != "this."){
12314 format = "fm." + format + '(';
12316 format = 'this.call("'+ format.substr(5) + '", ';
12320 args= ''; format = "("+v+" === undefined ? '' : ";
12322 return "'"+ sep + format + v + args + ")"+sep+"'";
12325 function codeFn(m, code){
12326 return "'"+ sep +'('+code+')'+sep+"'";
12329 // branched to use + in gecko and [].join() in others
12331 body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
12332 tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
12335 body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
12336 body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
12337 body.push("'].join('');};");
12338 body = body.join('');
12345 * Returns an HTML fragment of this template with the specified values applied.
12346 * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
12347 * @return {String} The HTML fragment
12349 applyTemplate : function(values){
12350 return this.master.compiled.call(this, values, {}, 1, 1);
12354 * Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
12355 * @return {Function} The compiled function
12357 compile : function(){return this;}
12364 * @property disableFormats
12374 * Alias for {@link #applyTemplate}
12375 * Returns an HTML fragment of this template with the specified values applied.
12376 * @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'})
12377 * @return {String} The HTML fragment
12378 * @member Ext.XTemplate
12381 Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
12384 * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
12385 * @param {String/HTMLElement} el A DOM element or its id
12386 * @return {Ext.Template} The created template
12389 Ext.XTemplate.from = function(el){
12390 el = Ext.getDom(el);
12391 return new Ext.XTemplate(el.value || el.innerHTML);
12393 * @class Ext.util.CSS
\r
12394 * Utility class for manipulating CSS rules
\r
12397 Ext.util.CSS = function(){
\r
12398 var rules = null;
\r
12399 var doc = document;
\r
12401 var camelRe = /(-[a-z])/gi;
\r
12402 var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
\r
12406 * Creates a stylesheet from a text blob of rules.
\r
12407 * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
\r
12408 * @param {String} cssText The text containing the css rules
\r
12409 * @param {String} id An id to add to the stylesheet for later removal
\r
12410 * @return {StyleSheet}
\r
12412 createStyleSheet : function(cssText, id){
\r
12414 var head = doc.getElementsByTagName("head")[0];
\r
12415 var rules = doc.createElement("style");
\r
12416 rules.setAttribute("type", "text/css");
\r
12418 rules.setAttribute("id", id);
\r
12421 head.appendChild(rules);
\r
12422 ss = rules.styleSheet;
\r
12423 ss.cssText = cssText;
\r
12426 rules.appendChild(doc.createTextNode(cssText));
\r
12428 rules.cssText = cssText;
\r
12430 head.appendChild(rules);
\r
12431 ss = rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
\r
12433 this.cacheStyleSheet(ss);
\r
12438 * Removes a style or link tag by id
\r
12439 * @param {String} id The id of the tag
\r
12441 removeStyleSheet : function(id){
\r
12442 var existing = doc.getElementById(id);
\r
12444 existing.parentNode.removeChild(existing);
\r
12449 * Dynamically swaps an existing stylesheet reference for a new one
\r
12450 * @param {String} id The id of an existing link tag to remove
\r
12451 * @param {String} url The href of the new stylesheet to include
\r
12453 swapStyleSheet : function(id, url){
\r
12454 this.removeStyleSheet(id);
\r
12455 var ss = doc.createElement("link");
\r
12456 ss.setAttribute("rel", "stylesheet");
\r
12457 ss.setAttribute("type", "text/css");
\r
12458 ss.setAttribute("id", id);
\r
12459 ss.setAttribute("href", url);
\r
12460 doc.getElementsByTagName("head")[0].appendChild(ss);
\r
12464 * Refresh the rule cache if you have dynamically added stylesheets
\r
12465 * @return {Object} An object (hash) of rules indexed by selector
\r
12467 refreshCache : function(){
\r
12468 return this.getRules(true);
\r
12472 cacheStyleSheet : function(ss){
\r
12476 try{// try catch for cross domain access issue
\r
12477 var ssRules = ss.cssRules || ss.rules;
\r
12478 for(var j = ssRules.length-1; j >= 0; --j){
\r
12479 rules[ssRules[j].selectorText] = ssRules[j];
\r
12485 * Gets all css rules for the document
\r
12486 * @param {Boolean} refreshCache true to refresh the internal cache
\r
12487 * @return {Object} An object (hash) of rules indexed by selector
\r
12489 getRules : function(refreshCache){
\r
12490 if(rules === null || refreshCache){
\r
12492 var ds = doc.styleSheets;
\r
12493 for(var i =0, len = ds.length; i < len; i++){
\r
12495 this.cacheStyleSheet(ds[i]);
\r
12503 * Gets an an individual CSS rule by selector(s)
\r
12504 * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
\r
12505 * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
\r
12506 * @return {CSSRule} The CSS rule or null if one is not found
\r
12508 getRule : function(selector, refreshCache){
\r
12509 var rs = this.getRules(refreshCache);
\r
12510 if(!Ext.isArray(selector)){
\r
12511 return rs[selector];
\r
12513 for(var i = 0; i < selector.length; i++){
\r
12514 if(rs[selector[i]]){
\r
12515 return rs[selector[i]];
\r
12523 * Updates a rule property
\r
12524 * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
\r
12525 * @param {String} property The css property
\r
12526 * @param {String} value The new value for the property
\r
12527 * @return {Boolean} true If a rule was found and updated
\r
12529 updateRule : function(selector, property, value){
\r
12530 if(!Ext.isArray(selector)){
\r
12531 var rule = this.getRule(selector);
\r
12533 rule.style[property.replace(camelRe, camelFn)] = value;
\r
12537 for(var i = 0; i < selector.length; i++){
\r
12538 if(this.updateRule(selector[i], property, value)){
\r
12547 @class Ext.util.ClickRepeater
12548 @extends Ext.util.Observable
12550 A wrapper class which can be applied to any element. Fires a "click" event while the
12551 mouse is pressed. The interval between firings may be specified in the config but
12552 defaults to 20 milliseconds.
12554 Optionally, a CSS class may be applied to the element during the time it is pressed.
12556 @cfg {Mixed} el The element to act as a button.
12557 @cfg {Number} delay The initial delay before the repeating event begins firing.
12558 Similar to an autorepeat key delay.
12559 @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
12560 @cfg {String} pressClass A CSS class name to be applied to the element while pressed.
12561 @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
12562 "interval" and "delay" are ignored.
12563 @cfg {Boolean} preventDefault True to prevent the default click event
12564 @cfg {Boolean} stopDefault True to stop the default click event
12567 2007-02-02 jvs Original code contributed by Nige "Animal" White
12568 2007-02-02 jvs Renamed to ClickRepeater
12569 2007-02-03 jvs Modifications for FF Mac and Safari
12572 @param {Mixed} el The element to listen on
12573 @param {Object} config
12575 Ext.util.ClickRepeater = function(el, config)
12577 this.el = Ext.get(el);
12578 this.el.unselectable();
12580 Ext.apply(this, config);
12585 * Fires when the mouse button is depressed.
12586 * @param {Ext.util.ClickRepeater} this
12591 * Fires on a specified interval during the time the element is pressed.
12592 * @param {Ext.util.ClickRepeater} this
12597 * Fires when the mouse key is released.
12598 * @param {Ext.util.ClickRepeater} this
12603 if(!this.disabled){
12604 this.disabled = true;
12608 // allow inline handler
12610 this.on("click", this.handler, this.scope || this);
12613 Ext.util.ClickRepeater.superclass.constructor.call(this);
12616 Ext.extend(Ext.util.ClickRepeater, Ext.util.Observable, {
12619 preventDefault : true,
12620 stopDefault : false,
12624 * Enables the repeater and allows events to fire.
12626 enable: function(){
12628 this.el.on('mousedown', this.handleMouseDown, this);
12629 if(this.preventDefault || this.stopDefault){
12630 this.el.on('click', this.eventOptions, this);
12633 this.disabled = false;
12637 * Disables the repeater and stops events from firing.
12639 disable: function(/* private */ force){
12640 if(force || !this.disabled){
12641 clearTimeout(this.timer);
12642 if(this.pressClass){
12643 this.el.removeClass(this.pressClass);
12645 Ext.getDoc().un('mouseup', this.handleMouseUp, this);
12646 this.el.removeAllListeners();
12648 this.disabled = true;
12652 * Convenience function for setting disabled/enabled by boolean.
12653 * @param {Boolean} disabled
12655 setDisabled: function(disabled){
12656 this[disabled ? 'disable' : 'enable']();
12659 eventOptions: function(e){
12660 if(this.preventDefault){
12661 e.preventDefault();
12663 if(this.stopDefault){
12669 destroy : function() {
12670 this.disable(true);
12671 Ext.destroy(this.el);
12672 this.purgeListeners();
12676 handleMouseDown : function(){
12677 clearTimeout(this.timer);
12679 if(this.pressClass){
12680 this.el.addClass(this.pressClass);
12682 this.mousedownTime = new Date();
12684 Ext.getDoc().on("mouseup", this.handleMouseUp, this);
12685 this.el.on("mouseout", this.handleMouseOut, this);
12687 this.fireEvent("mousedown", this);
12688 this.fireEvent("click", this);
12690 // Do not honor delay or interval if acceleration wanted.
12691 if (this.accelerate) {
12694 this.timer = this.click.defer(this.delay || this.interval, this);
12698 click : function(){
12699 this.fireEvent("click", this);
12700 this.timer = this.click.defer(this.accelerate ?
12701 this.easeOutExpo(this.mousedownTime.getElapsed(),
12705 this.interval, this);
12708 easeOutExpo : function (t, b, c, d) {
12709 return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
12713 handleMouseOut : function(){
12714 clearTimeout(this.timer);
12715 if(this.pressClass){
12716 this.el.removeClass(this.pressClass);
12718 this.el.on("mouseover", this.handleMouseReturn, this);
12722 handleMouseReturn : function(){
12723 this.el.un("mouseover", this.handleMouseReturn, this);
12724 if(this.pressClass){
12725 this.el.addClass(this.pressClass);
12731 handleMouseUp : function(){
12732 clearTimeout(this.timer);
12733 this.el.un("mouseover", this.handleMouseReturn, this);
12734 this.el.un("mouseout", this.handleMouseOut, this);
12735 Ext.getDoc().un("mouseup", this.handleMouseUp, this);
12736 this.el.removeClass(this.pressClass);
12737 this.fireEvent("mouseup", this);
12740 * @class Ext.KeyNav
12741 * <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
12742 * navigation keys to function calls that will get called when the keys are pressed, providing an easy
12743 * way to implement custom navigation schemes for any UI component.</p>
12744 * <p>The following are all of the possible keys that can be implemented: enter, left, right, up, down, tab, esc,
12745 * pageUp, pageDown, del, home, end. Usage:</p>
12747 var nav = new Ext.KeyNav("my-element", {
12748 "left" : function(e){
12749 this.moveLeft(e.ctrlKey);
12751 "right" : function(e){
12752 this.moveRight(e.ctrlKey);
12754 "enter" : function(e){
12761 * @param {Mixed} el The element to bind to
12762 * @param {Object} config The config
12764 Ext.KeyNav = function(el, config){
12765 this.el = Ext.get(el);
12766 Ext.apply(this, config);
12767 if(!this.disabled){
12768 this.disabled = true;
12773 Ext.KeyNav.prototype = {
12775 * @cfg {Boolean} disabled
12776 * True to disable this KeyNav instance (defaults to false)
12780 * @cfg {String} defaultEventAction
12781 * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
12782 * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
12783 * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
12785 defaultEventAction: "stopEvent",
12787 * @cfg {Boolean} forceKeyDown
12788 * Handle the keydown event instead of keypress (defaults to false). KeyNav automatically does this for IE since
12789 * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
12790 * handle keydown instead of keypress.
12792 forceKeyDown : false,
12795 prepareEvent : function(e){
12796 var k = e.getKey();
12797 var h = this.keyToHandler[k];
12798 if(Ext.isSafari2 && h && k >= 37 && k <= 40){
12804 relay : function(e){
12805 var k = e.getKey();
12806 var h = this.keyToHandler[k];
12808 if(this.doRelay(e, this[h], h) !== true){
12809 e[this.defaultEventAction]();
12815 doRelay : function(e, h, hname){
12816 return h.call(this.scope || this, e);
12819 // possible handlers
12833 // quick lookup hash
12850 * Enable this KeyNav
12852 enable: function(){
12854 // ie won't do special keys on keypress, no one else will repeat keys with keydown
12855 // the EventObject will normalize Safari automatically
12856 if(this.isKeydown()){
12857 this.el.on("keydown", this.relay, this);
12859 this.el.on("keydown", this.prepareEvent, this);
12860 this.el.on("keypress", this.relay, this);
12862 this.disabled = false;
12867 * Disable this KeyNav
12869 disable: function(){
12870 if(!this.disabled){
12871 if(this.isKeydown()){
12872 this.el.un("keydown", this.relay, this);
12874 this.el.un("keydown", this.prepareEvent, this);
12875 this.el.un("keypress", this.relay, this);
12877 this.disabled = true;
12882 * Convenience function for setting disabled/enabled by boolean.
12883 * @param {Boolean} disabled
12885 setDisabled : function(disabled){
12886 this[disabled ? "disable" : "enable"]();
12890 isKeydown: function(){
12891 return this.forceKeyDown || Ext.EventManager.useKeydown;
12895 * @class Ext.KeyMap
\r
12896 * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
\r
12897 * The constructor accepts the same config object as defined by {@link #addBinding}.
\r
12898 * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
\r
12899 * combination it will call the function with this signature (if the match is a multi-key
\r
12900 * combination the callback will still be called only once): (String key, Ext.EventObject e)
\r
12901 * A KeyMap can also handle a string representation of keys.<br />
\r
12904 // map one key by key code
\r
12905 var map = new Ext.KeyMap("my-element", {
\r
12906 key: 13, // or Ext.EventObject.ENTER
\r
12911 // map multiple keys to one action by string
\r
12912 var map = new Ext.KeyMap("my-element", {
\r
12918 // map multiple keys to multiple actions by strings and array of codes
\r
12919 var map = new Ext.KeyMap("my-element", [
\r
12922 fn: function(){ alert("Return was pressed"); }
\r
12925 fn: function(){ alert('a, b or c was pressed'); }
\r
12930 fn: function(){ alert('Control + shift + tab was pressed.'); }
\r
12934 * <b>Note: A KeyMap starts enabled</b>
\r
12936 * @param {Mixed} el The element to bind to
\r
12937 * @param {Object} config The config (see {@link #addBinding})
\r
12938 * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
\r
12940 Ext.KeyMap = function(el, config, eventName){
\r
12941 this.el = Ext.get(el);
\r
12942 this.eventName = eventName || "keydown";
\r
12943 this.bindings = [];
\r
12945 this.addBinding(config);
\r
12950 Ext.KeyMap.prototype = {
\r
12952 * True to stop the event from bubbling and prevent the default browser action if the
\r
12953 * key was handled by the KeyMap (defaults to false)
\r
12956 stopEvent : false,
\r
12959 * Add a new binding to this KeyMap. The following config object properties are supported:
\r
12961 Property Type Description
\r
12962 ---------- --------------- ----------------------------------------------------------------------
\r
12963 key String/Array A single keycode or an array of keycodes to handle
\r
12964 shift Boolean True to handle key only when shift is pressed, False to handle the key only when shift is not pressed (defaults to undefined)
\r
12965 ctrl Boolean True to handle key only when ctrl is pressed, False to handle the key only when ctrl is not pressed (defaults to undefined)
\r
12966 alt Boolean True to handle key only when alt is pressed, False to handle the key only when alt is not pressed (defaults to undefined)
\r
12967 handler Function The function to call when KeyMap finds the expected key combination
\r
12968 fn Function Alias of handler (for backwards-compatibility)
\r
12969 scope Object The scope of the callback function
\r
12970 stopEvent Boolean True to stop the event from bubbling and prevent the default browser action if the key was handled by the KeyMap (defaults to false)
\r
12975 // Create a KeyMap
\r
12976 var map = new Ext.KeyMap(document, {
\r
12977 key: Ext.EventObject.ENTER,
\r
12982 //Add a new binding to the existing KeyMap later
\r
12990 * @param {Object/Array} config A single KeyMap config or an array of configs
\r
12992 addBinding : function(config){
\r
12993 if(Ext.isArray(config)){
\r
12994 Ext.each(config, function(c){
\r
12995 this.addBinding(c);
\r
12999 var keyCode = config.key,
\r
13000 fn = config.fn || config.handler,
\r
13001 scope = config.scope;
\r
13003 if (config.stopEvent) {
\r
13004 this.stopEvent = config.stopEvent;
\r
13007 if(typeof keyCode == "string"){
\r
13009 var keyString = keyCode.toUpperCase();
\r
13010 for(var j = 0, len = keyString.length; j < len; j++){
\r
13011 ks.push(keyString.charCodeAt(j));
\r
13015 var keyArray = Ext.isArray(keyCode);
\r
13017 var handler = function(e){
\r
13018 if(this.checkModifiers(config, e)){
\r
13019 var k = e.getKey();
\r
13021 for(var i = 0, len = keyCode.length; i < len; i++){
\r
13022 if(keyCode[i] == k){
\r
13023 if(this.stopEvent){
\r
13026 fn.call(scope || window, k, e);
\r
13031 if(k == keyCode){
\r
13032 if(this.stopEvent){
\r
13035 fn.call(scope || window, k, e);
\r
13040 this.bindings.push(handler);
\r
13044 checkModifiers: function(config, e){
\r
13045 var val, key, keys = ['shift', 'ctrl', 'alt'];
\r
13046 for (var i = 0, len = keys.length; i < len; ++i){
\r
13048 val = config[key];
\r
13049 if(!(val === undefined || (val === e[key + 'Key']))){
\r
13057 * Shorthand for adding a single key listener
\r
13058 * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
\r
13059 * following options:
\r
13060 * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
\r
13061 * @param {Function} fn The function to call
\r
13062 * @param {Object} scope (optional) The scope of the function
\r
13064 on : function(key, fn, scope){
\r
13065 var keyCode, shift, ctrl, alt;
\r
13066 if(typeof key == "object" && !Ext.isArray(key)){
\r
13067 keyCode = key.key;
\r
13068 shift = key.shift;
\r
13074 this.addBinding({
\r
13085 handleKeyDown : function(e){
\r
13086 if(this.enabled){ //just in case
\r
13087 var b = this.bindings;
\r
13088 for(var i = 0, len = b.length; i < len; i++){
\r
13089 b[i].call(this, e);
\r
13095 * Returns true if this KeyMap is enabled
\r
13096 * @return {Boolean}
\r
13098 isEnabled : function(){
\r
13099 return this.enabled;
\r
13103 * Enables this KeyMap
\r
13105 enable: function(){
\r
13106 if(!this.enabled){
\r
13107 this.el.on(this.eventName, this.handleKeyDown, this);
\r
13108 this.enabled = true;
\r
13113 * Disable this KeyMap
\r
13115 disable: function(){
\r
13116 if(this.enabled){
\r
13117 this.el.removeListener(this.eventName, this.handleKeyDown, this);
\r
13118 this.enabled = false;
\r
13123 * Convenience function for setting disabled/enabled by boolean.
\r
13124 * @param {Boolean} disabled
\r
13126 setDisabled : function(disabled){
\r
13127 this[disabled ? "disable" : "enable"]();
\r
13130 * @class Ext.util.TextMetrics
13131 * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
13132 * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
13133 * should not contain any HTML, otherwise it may not be measured correctly.
13136 Ext.util.TextMetrics = function(){
13140 * Measures the size of the specified text
13141 * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
13142 * that can affect the size of the rendered text
13143 * @param {String} text The text to measure
13144 * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
13145 * in order to accurately measure the text height
13146 * @return {Object} An object containing the text's size {width: (width), height: (height)}
13148 measure : function(el, text, fixedWidth){
13150 shared = Ext.util.TextMetrics.Instance(el, fixedWidth);
13153 shared.setFixedWidth(fixedWidth || 'auto');
13154 return shared.getSize(text);
13158 * Return a unique TextMetrics instance that can be bound directly to an element and reused. This reduces
13159 * the overhead of multiple calls to initialize the style properties on each measurement.
13160 * @param {String/HTMLElement} el The element, dom node or id that the instance will be bound to
13161 * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
13162 * in order to accurately measure the text height
13163 * @return {Ext.util.TextMetrics.Instance} instance The new instance
13165 createInstance : function(el, fixedWidth){
13166 return Ext.util.TextMetrics.Instance(el, fixedWidth);
13171 Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
13172 var ml = new Ext.Element(document.createElement('div'));
13173 document.body.appendChild(ml.dom);
13174 ml.position('absolute');
13175 ml.setLeftTop(-1000, -1000);
13179 ml.setWidth(fixedWidth);
13184 * Returns the size of the specified text based on the internal element's style and width properties
13185 * @param {String} text The text to measure
13186 * @return {Object} An object containing the text's size {width: (width), height: (height)}
13188 getSize : function(text){
13190 var s = ml.getSize();
13196 * Binds this TextMetrics instance to an element from which to copy existing CSS styles
13197 * that can affect the size of the rendered text
13198 * @param {String/HTMLElement} el The element, dom node or id
13200 bind : function(el){
13202 Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
13207 * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
13208 * to set a fixed width in order to accurately measure the text height.
13209 * @param {Number} width The width to set on the element
13211 setFixedWidth : function(width){
13212 ml.setWidth(width);
13216 * Returns the measured width of the specified text
13217 * @param {String} text The text to measure
13218 * @return {Number} width The width in pixels
13220 getWidth : function(text){
13221 ml.dom.style.width = 'auto';
13222 return this.getSize(text).width;
13226 * Returns the measured height of the specified text. For multiline text, be sure to call
13227 * {@link #setFixedWidth} if necessary.
13228 * @param {String} text The text to measure
13229 * @return {Number} height The height in pixels
13231 getHeight : function(text){
13232 return this.getSize(text).height;
13236 instance.bind(bindTo);
13241 Ext.Element.addMethods({
13243 * Returns the width in pixels of the passed text, or the width of the text in this Element.
13244 * @param {String} text The text to measure. Defaults to the innerHTML of the element.
13245 * @param {Number} min (Optional) The minumum value to return.
13246 * @param {Number} max (Optional) The maximum value to return.
13247 * @return {Number} The text width in pixels.
13248 * @member Ext.Element getTextWidth
13250 getTextWidth : function(text, min, max){
13251 return (Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width).constrain(min || 0, max || 1000000);
13255 * @class Ext.util.Cookies
\r
13256 * Utility class for managing and interacting with cookies.
\r
13259 Ext.util.Cookies = {
\r
13261 * Create a cookie with the specified name and value. Additional settings
\r
13262 * for the cookie may be optionally specified (for example: expiration,
\r
13263 * access restriction, SSL).
\r
13264 * @param {Object} name
\r
13265 * @param {Object} value
\r
13266 * @param {Object} expires (Optional) Specify an expiration date the
\r
13267 * cookie is to persist until. Note that the specified Date object will
\r
13268 * be converted to Greenwich Mean Time (GMT).
\r
13269 * @param {String} path (Optional) Setting a path on the cookie restricts
\r
13270 * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>).
\r
13271 * @param {String} domain (Optional) Setting a domain restricts access to
\r
13272 * pages on a given domain (typically used to allow cookie access across
\r
13273 * subdomains). For example, "extjs.com" will create a cookie that can be
\r
13274 * accessed from any subdomain of extjs.com, including www.extjs.com,
\r
13275 * support.extjs.com, etc.
\r
13276 * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
\r
13277 * should only be accessible via SSL on a page using the HTTPS protocol.
\r
13278 * Defaults to <tt>false</tt>. Note that this will only work if the page
\r
13279 * calling this code uses the HTTPS protocol, otherwise the cookie will be
\r
13280 * created with default options.
\r
13282 set : function(name, value){
\r
13283 var argv = arguments;
\r
13284 var argc = arguments.length;
\r
13285 var expires = (argc > 2) ? argv[2] : null;
\r
13286 var path = (argc > 3) ? argv[3] : '/';
\r
13287 var domain = (argc > 4) ? argv[4] : null;
\r
13288 var secure = (argc > 5) ? argv[5] : false;
\r
13289 document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
\r
13293 * Retrieves cookies that are accessible by the current page. If a cookie
\r
13294 * does not exist, <code>get()</code> returns <tt>null</tt>. The following
\r
13295 * example retrieves the cookie called "valid" and stores the String value
\r
13296 * in the variable <tt>validStatus</tt>.
\r
13298 * var validStatus = Ext.util.Cookies.get("valid");
\r
13300 * @param {Object} name The name of the cookie to get
\r
13301 * @return {Mixed} Returns the cookie value for the specified name;
\r
13302 * null if the cookie name does not exist.
\r
13304 get : function(name){
\r
13305 var arg = name + "=";
\r
13306 var alen = arg.length;
\r
13307 var clen = document.cookie.length;
\r
13312 if(document.cookie.substring(i, j) == arg){
\r
13313 return Ext.util.Cookies.getCookieVal(j);
\r
13315 i = document.cookie.indexOf(" ", i) + 1;
\r
13324 * Removes a cookie with the provided name from the browser
\r
13326 * @param {Object} name The name of the cookie to remove
\r
13328 clear : function(name){
\r
13329 if(Ext.util.Cookies.get(name)){
\r
13330 document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
\r
13336 getCookieVal : function(offset){
\r
13337 var endstr = document.cookie.indexOf(";", offset);
\r
13338 if(endstr == -1){
\r
13339 endstr = document.cookie.length;
\r
13341 return unescape(document.cookie.substring(offset, endstr));
\r
13344 * Framework-wide error-handler. Developers can override this method to provide
13345 * custom exception-handling. Framework errors will often extend from the base
13347 * @param {Object/Error} e The thrown exception object.
13349 Ext.handleError = function(e) {
13356 * <p>A base error class. Future implementations are intended to provide more
13357 * robust error handling throughout the framework (<b>in the debug build only</b>)
13358 * to check for common errors and problems. The messages issued by this class
13359 * will aid error checking. Error checks will be automatically removed in the
13360 * production build so that performance is not negatively impacted.</p>
13361 * <p>Some sample messages currently implemented:</p><pre>
13362 "DataProxy attempted to execute an API-action but found an undefined
13363 url / function. Please review your Proxy url/api-configuration."
13365 "Could not locate your "root" property in your server response.
13366 Please review your JsonReader config to ensure the config-property
13367 "root" matches the property your server-response. See the JsonReader
13368 docs for additional assistance."
13370 * <p>An example of the code used for generating error messages:</p><pre><code>
13379 function generateError(data) {
13380 throw new Ext.Error('foo-error', data);
13383 * @param {String} message
13385 Ext.Error = function(message) {
13386 // Try to read the message from Ext.Error.lang
13387 this.message = (this.lang[message]) ? this.lang[message] : message;
13389 Ext.Error.prototype = new Error();
13390 Ext.apply(Ext.Error.prototype, {
13391 // protected. Extensions place their error-strings here.
13399 getName : function() {
13406 getMessage : function() {
13407 return this.message;
13413 toJson : function() {
13414 return Ext.encode(this);
13419 * @class Ext.ComponentMgr
13420 * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
13421 * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
13422 * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
13423 * <p>This object also provides a registry of available Component <i>classes</i>
13424 * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
13425 * The <tt>{@link Ext.Component#xtype xtype}</tt> provides a way to avoid instantiating child Components
13426 * when creating a full, nested config object for a complete Ext page.</p>
13427 * <p>A child Component may be specified simply as a <i>config object</i>
13428 * as long as the correct <tt>{@link Ext.Component#xtype xtype}</tt> is specified so that if and when the Component
13429 * needs rendering, the correct type can be looked up for lazy instantiation.</p>
13430 * <p>For a list of all available <tt>{@link Ext.Component#xtype xtypes}</tt>, see {@link Ext.Component}.</p>
13433 Ext.ComponentMgr = function(){
13434 var all = new Ext.util.MixedCollection();
13440 * Registers a component.
13441 * @param {Ext.Component} c The component
13443 register : function(c){
13448 * Unregisters a component.
13449 * @param {Ext.Component} c The component
13451 unregister : function(c){
13456 * Returns a component by {@link Ext.Component#id id}.
13457 * For additional details see {@link Ext.util.MixedCollection#get}.
13458 * @param {String} id The component {@link Ext.Component#id id}
13459 * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
13462 get : function(id){
13463 return all.get(id);
13467 * Registers a function that will be called when a specified component is added to ComponentMgr
13468 * @param {String} id The component {@link Ext.Component#id id}
13469 * @param {Function} fn The callback function
13470 * @param {Object} scope The scope of the callback
13472 onAvailable : function(id, fn, scope){
13473 all.on("add", function(index, o){
13475 fn.call(scope || o, o);
13476 all.un("add", fn, scope);
13482 * The MixedCollection used internally for the component cache. An example usage may be subscribing to
13483 * events on the MixedCollection to monitor addition or removal. Read-only.
13484 * @type {MixedCollection}
13489 * Checks if a Component type is registered.
13490 * @param {Ext.Component} xtype The mnemonic string by which the Component class may be looked up
13491 * @return {Boolean} Whether the type is registered.
13493 isRegistered : function(xtype){
13494 return types[xtype] !== undefined;
13498 * <p>Registers a new Component constructor, keyed by a new
13499 * {@link Ext.Component#xtype}.</p>
13500 * <p>Use this method (or its alias {@link Ext#reg Ext.reg}) to register new
13501 * subclasses of {@link Ext.Component} so that lazy instantiation may be used when specifying
13502 * child Components.
13503 * see {@link Ext.Container#items}</p>
13504 * @param {String} xtype The mnemonic string by which the Component class may be looked up.
13505 * @param {Constructor} cls The new Component class.
13507 registerType : function(xtype, cls){
13508 types[xtype] = cls;
13513 * Creates a new Component from the specified config object using the
13514 * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate.
13515 * @param {Object} config A configuration object for the Component you wish to create.
13516 * @param {Constructor} defaultType The constructor to provide the default Component type if
13517 * the config object does not contain a <tt>xtype</tt>. (Optional if the config contains a <tt>xtype</tt>).
13518 * @return {Ext.Component} The newly instantiated Component.
13520 create : function(config, defaultType){
13521 return config.render ? config : new types[config.xtype || defaultType](config);
13525 * <p>Registers a new Plugin constructor, keyed by a new
13526 * {@link Ext.Component#ptype}.</p>
13527 * <p>Use this method (or its alias {@link Ext#preg Ext.preg}) to register new
13528 * plugins for {@link Ext.Component}s so that lazy instantiation may be used when specifying
13530 * @param {String} ptype The mnemonic string by which the Plugin class may be looked up.
13531 * @param {Constructor} cls The new Plugin class.
13533 registerPlugin : function(ptype, cls){
13534 ptypes[ptype] = cls;
13539 * Creates a new Plugin from the specified config object using the
13540 * config object's {@link Ext.component#ptype ptype} to determine the class to instantiate.
13541 * @param {Object} config A configuration object for the Plugin you wish to create.
13542 * @param {Constructor} defaultType The constructor to provide the default Plugin type if
13543 * the config object does not contain a <tt>ptype</tt>. (Optional if the config contains a <tt>ptype</tt>).
13544 * @return {Ext.Component} The newly instantiated Plugin.
13546 createPlugin : function(config, defaultType){
13547 return new ptypes[config.ptype || defaultType](config);
13553 * Shorthand for {@link Ext.ComponentMgr#registerType}
13554 * @param {String} xtype The {@link Ext.component#xtype mnemonic string} by which the Component class
13555 * may be looked up.
13556 * @param {Constructor} cls The new Component class.
13560 Ext.reg = Ext.ComponentMgr.registerType; // this will be called a lot internally, shorthand to keep the bytes down
13562 * Shorthand for {@link Ext.ComponentMgr#registerPlugin}
13563 * @param {String} ptype The {@link Ext.component#ptype mnemonic string} by which the Plugin class
13564 * may be looked up.
13565 * @param {Constructor} cls The new Plugin class.
13569 Ext.preg = Ext.ComponentMgr.registerPlugin;
13570 Ext.create = Ext.ComponentMgr.create;
13572 * @class Ext.Component
13573 * @extends Ext.util.Observable
13574 * <p>Base class for all Ext components. All subclasses of Component may participate in the automated
13575 * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.Container Container} class.
13576 * Components may be added to a Container through the {@link Ext.Container#items items} config option at the time the Container is created,
13577 * or they may be added dynamically via the {@link Ext.Container#add add} method.</p>
13578 * <p>The Component base class has built-in support for basic hide/show and enable/disable behavior.</p>
13579 * <p>All Components are registered with the {@link Ext.ComponentMgr} on construction so that they can be referenced at any time via
13580 * {@link Ext#getCmp}, passing the {@link #id}.</p>
13581 * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component (or
13582 * {@link Ext.BoxComponent} if managed box model handling is required, ie height and width management).</p>
13583 * <p>See the <a href="http://extjs.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
13584 * and to either extend or augment ExtJs base classes to create custom Components.</p>
13585 * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
13586 * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
13589 ------------- ------------------
13590 box {@link Ext.BoxComponent}
13591 button {@link Ext.Button}
13592 buttongroup {@link Ext.ButtonGroup}
13593 colorpalette {@link Ext.ColorPalette}
13594 component {@link Ext.Component}
13595 container {@link Ext.Container}
13596 cycle {@link Ext.CycleButton}
13597 dataview {@link Ext.DataView}
13598 datepicker {@link Ext.DatePicker}
13599 editor {@link Ext.Editor}
13600 editorgrid {@link Ext.grid.EditorGridPanel}
13601 flash {@link Ext.FlashComponent}
13602 grid {@link Ext.grid.GridPanel}
13603 listview {@link Ext.ListView}
13604 panel {@link Ext.Panel}
13605 progress {@link Ext.ProgressBar}
13606 propertygrid {@link Ext.grid.PropertyGrid}
13607 slider {@link Ext.Slider}
13608 spacer {@link Ext.Spacer}
13609 splitbutton {@link Ext.SplitButton}
13610 tabpanel {@link Ext.TabPanel}
13611 treepanel {@link Ext.tree.TreePanel}
13612 viewport {@link Ext.ViewPort}
13613 window {@link Ext.Window}
13616 ---------------------------------------
13617 paging {@link Ext.PagingToolbar}
13618 toolbar {@link Ext.Toolbar}
13619 tbbutton {@link Ext.Toolbar.Button} (deprecated; use button)
13620 tbfill {@link Ext.Toolbar.Fill}
13621 tbitem {@link Ext.Toolbar.Item}
13622 tbseparator {@link Ext.Toolbar.Separator}
13623 tbspacer {@link Ext.Toolbar.Spacer}
13624 tbsplit {@link Ext.Toolbar.SplitButton} (deprecated; use splitbutton)
13625 tbtext {@link Ext.Toolbar.TextItem}
13628 ---------------------------------------
13629 menu {@link Ext.menu.Menu}
13630 colormenu {@link Ext.menu.ColorMenu}
13631 datemenu {@link Ext.menu.DateMenu}
13632 menubaseitem {@link Ext.menu.BaseItem}
13633 menucheckitem {@link Ext.menu.CheckItem}
13634 menuitem {@link Ext.menu.Item}
13635 menuseparator {@link Ext.menu.Separator}
13636 menutextitem {@link Ext.menu.TextItem}
13639 ---------------------------------------
13640 form {@link Ext.FormPanel}
13641 checkbox {@link Ext.form.Checkbox}
13642 checkboxgroup {@link Ext.form.CheckboxGroup}
13643 combo {@link Ext.form.ComboBox}
13644 datefield {@link Ext.form.DateField}
13645 displayfield {@link Ext.form.DisplayField}
13646 field {@link Ext.form.Field}
13647 fieldset {@link Ext.form.FieldSet}
13648 hidden {@link Ext.form.Hidden}
13649 htmleditor {@link Ext.form.HtmlEditor}
13650 label {@link Ext.form.Label}
13651 numberfield {@link Ext.form.NumberField}
13652 radio {@link Ext.form.Radio}
13653 radiogroup {@link Ext.form.RadioGroup}
13654 textarea {@link Ext.form.TextArea}
13655 textfield {@link Ext.form.TextField}
13656 timefield {@link Ext.form.TimeField}
13657 trigger {@link Ext.form.TriggerField}
13660 ---------------------------------------
13661 chart {@link Ext.chart.Chart}
13662 barchart {@link Ext.chart.BarChart}
13663 cartesianchart {@link Ext.chart.CartesianChart}
13664 columnchart {@link Ext.chart.ColumnChart}
13665 linechart {@link Ext.chart.LineChart}
13666 piechart {@link Ext.chart.PieChart}
13669 ---------------------------------------
13670 arraystore {@link Ext.data.ArrayStore}
13671 directstore {@link Ext.data.DirectStore}
13672 groupingstore {@link Ext.data.GroupingStore}
13673 jsonstore {@link Ext.data.JsonStore}
13674 simplestore {@link Ext.data.SimpleStore} (deprecated; use arraystore)
13675 store {@link Ext.data.Store}
13676 xmlstore {@link Ext.data.XmlStore}
13679 * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
13680 * <div class="mdetail-params"><ul>
13681 * <li><b>an element</b> :
13682 * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
13683 * <li><b>a string</b> :
13684 * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
13685 * <li><b>anything else</b> :
13686 * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
13689 Ext.Component = function(config){
13690 config = config || {};
13691 if(config.initialConfig){
13692 if(config.isAction){ // actions
13693 this.baseAction = config;
13695 config = config.initialConfig; // component cloning / action set up
13696 }else if(config.tagName || config.dom || Ext.isString(config)){ // element object
13697 config = {applyTo: config, id: config.id || config};
13701 * This Component's initial configuration specification. Read-only.
13703 * @property initialConfig
13705 this.initialConfig = config;
13707 Ext.apply(this, config);
13711 * Fires after the component is disabled.
13712 * @param {Ext.Component} this
13717 * Fires after the component is enabled.
13718 * @param {Ext.Component} this
13722 * @event beforeshow
13723 * Fires before the component is shown by calling the {@link #show} method.
13724 * Return false from an event handler to stop the show.
13725 * @param {Ext.Component} this
13730 * Fires after the component is shown when calling the {@link #show} method.
13731 * @param {Ext.Component} this
13735 * @event beforehide
13736 * Fires before the component is hidden by calling the {@link #hide} method.
13737 * Return false from an event handler to stop the hide.
13738 * @param {Ext.Component} this
13743 * Fires after the component is hidden.
13744 * Fires after the component is hidden when calling the {@link #hide} method.
13745 * @param {Ext.Component} this
13749 * @event beforerender
13750 * Fires before the component is {@link #rendered}. Return false from an
13751 * event handler to stop the {@link #render}.
13752 * @param {Ext.Component} this
13757 * Fires after the component markup is {@link #rendered}.
13758 * @param {Ext.Component} this
13762 * @event afterrender
13763 * <p>Fires after the component rendering is finished.</p>
13764 * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
13765 * by any afterRender method defined for the Component, and, if {@link #stateful}, after state
13766 * has been restored.</p>
13767 * @param {Ext.Component} this
13771 * @event beforedestroy
13772 * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
13773 * @param {Ext.Component} this
13778 * Fires after the component is {@link #destroy}ed.
13779 * @param {Ext.Component} this
13783 * @event beforestaterestore
13784 * Fires before the state of the component is restored. Return false from an event handler to stop the restore.
13785 * @param {Ext.Component} this
13786 * @param {Object} state The hash of state values returned from the StateProvider. If this
13787 * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
13788 * that simply copies property values into this Component. The method maybe overriden to
13789 * provide custom state restoration.
13791 'beforestaterestore',
13793 * @event staterestore
13794 * Fires after the state of the component is restored.
13795 * @param {Ext.Component} this
13796 * @param {Object} state The hash of state values returned from the StateProvider. This is passed
13797 * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
13798 * Component. The method maybe overriden to provide custom state restoration.
13802 * @event beforestatesave
13803 * Fires before the state of the component is saved to the configured state provider. Return false to stop the save.
13804 * @param {Ext.Component} this
13805 * @param {Object} state The hash of state values. This is determined by calling
13806 * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
13807 * developer to return whetever representation of state is required, by default, Ext.Component
13808 * has a null implementation.
13813 * Fires after the state of the component is saved to the configured state provider.
13814 * @param {Ext.Component} this
13815 * @param {Object} state The hash of state values. This is determined by calling
13816 * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
13817 * developer to return whetever representation of state is required, by default, Ext.Component
13818 * has a null implementation.
13823 Ext.ComponentMgr.register(this);
13824 Ext.Component.superclass.constructor.call(this);
13826 if(this.baseAction){
13827 this.baseAction.addComponent(this);
13830 this.initComponent();
13833 if(Ext.isArray(this.plugins)){
13834 for(var i = 0, len = this.plugins.length; i < len; i++){
13835 this.plugins[i] = this.initPlugin(this.plugins[i]);
13838 this.plugins = this.initPlugin(this.plugins);
13842 if(this.stateful !== false){
13843 this.initState(config);
13847 this.applyToMarkup(this.applyTo);
13848 delete this.applyTo;
13849 }else if(this.renderTo){
13850 this.render(this.renderTo);
13851 delete this.renderTo;
13856 Ext.Component.AUTO_ID = 1000;
13858 Ext.extend(Ext.Component, Ext.util.Observable, {
13859 // Configs below are used for all Components when rendered by FormLayout.
13861 * @cfg {String} fieldLabel <p>The label text to display next to this Component (defaults to '').</p>
13862 * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container which
13863 * has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout manager (e.g.
13864 * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>).</p><br>
13865 * <p>Also see <tt>{@link #hideLabel}</tt> and
13866 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
13867 * Example use:<pre><code>
13868 new Ext.FormPanel({
13870 renderTo: Ext.getBody(),
13872 xtype: 'textfield',
13879 * @cfg {String} labelStyle <p>A CSS style specification string to apply directly to this field's
13880 * label. Defaults to the container's labelStyle value if set (e.g.,
13881 * <tt>{@link Ext.layout.FormLayout#labelStyle}</tt> , or '').</p>
13882 * <br><p><b>Note</b>: see the note for <code>{@link #clearCls}</code>.</p><br>
13883 * <p>Also see <code>{@link #hideLabel}</code> and
13884 * <code>{@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</code></p>
13885 * Example use:<pre><code>
13886 new Ext.FormPanel({
13888 renderTo: Ext.getBody(),
13890 xtype: 'textfield',
13891 fieldLabel: 'Name',
13892 labelStyle: 'font-weight:bold;'
13898 * @cfg {String} labelSeparator <p>The separator to display after the text of each
13899 * <tt>{@link #fieldLabel}</tt>. This property may be configured at various levels.
13900 * The order of precedence is:
13901 * <div class="mdetail-params"><ul>
13902 * <li>field / component level</li>
13903 * <li>container level</li>
13904 * <li>{@link Ext.layout.FormLayout#labelSeparator layout level} (defaults to colon <tt>':'</tt>)</li>
13906 * To display no separator for this field's label specify empty string ''.</p>
13907 * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
13908 * <p>Also see <tt>{@link #hideLabel}</tt> and
13909 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
13910 * Example use:<pre><code>
13911 new Ext.FormPanel({
13913 renderTo: Ext.getBody(),
13915 labelSeparator: '~' // layout config has lowest priority (defaults to ':')
13917 {@link Ext.layout.FormLayout#labelSeparator labelSeparator}: '>>', // config at container level
13919 xtype: 'textfield',
13920 fieldLabel: 'Field 1',
13921 labelSeparator: '...' // field/component level config supersedes others
13923 xtype: 'textfield',
13924 fieldLabel: 'Field 2' // labelSeparator will be '='
13930 * @cfg {Boolean} hideLabel <p><tt>true</tt> to completely hide the label element
13931 * ({@link #fieldLabel label} and {@link #labelSeparator separator}). Defaults to <tt>false</tt>.
13932 * By default, even if you do not specify a <tt>{@link #fieldLabel}</tt> the space will still be
13933 * reserved so that the field will line up with other fields that do have labels.
13934 * Setting this to <tt>true</tt> will cause the field to not reserve that space.</p>
13935 * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
13936 * Example use:<pre><code>
13937 new Ext.FormPanel({
13939 renderTo: Ext.getBody(),
13948 * @cfg {String} clearCls <p>The CSS class used to to apply to the special clearing div rendered
13949 * directly after each form field wrapper to provide field clearing (defaults to
13950 * <tt>'x-form-clear-left'</tt>).</p>
13951 * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container
13952 * which has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout
13953 * manager (e.g. {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) and either a
13954 * <tt>{@link #fieldLabel}</tt> is specified or <tt>isFormField=true</tt> is specified.</p><br>
13955 * <p>See {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} also.</p>
13958 * @cfg {String} itemCls <p>An additional CSS class to apply to the div wrapping the form item
13959 * element of this field. If supplied, <tt>itemCls</tt> at the <b>field</b> level will override
13960 * the default <tt>itemCls</tt> supplied at the <b>container</b> level. The value specified for
13961 * <tt>itemCls</tt> will be added to the default class (<tt>'x-form-item'</tt>).</p>
13962 * <p>Since it is applied to the item wrapper (see
13963 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}), it allows
13964 * you to write standard CSS rules that can apply to the field, the label (if specified), or
13965 * any other element within the markup for the field.</p>
13966 * <br><p><b>Note</b>: see the note for <tt>{@link #fieldLabel}</tt>.</p><br>
13967 * Example use:<pre><code>
13968 // Apply a style to the field's label:
13970 .required .x-form-item-label {font-weight:bold;color:red;}
13973 new Ext.FormPanel({
13975 renderTo: Ext.getBody(),
13977 xtype: 'textfield',
13978 fieldLabel: 'Name',
13979 itemCls: 'required' //this label will be styled
13981 xtype: 'textfield',
13982 fieldLabel: 'Favorite Color'
13988 // Configs below are used for all Components when rendered by AnchorLayout.
13990 * @cfg {String} anchor <p><b>Note</b>: this config is only used when this Component is rendered
13991 * by a Container which has been configured to use an <b>{@link Ext.layout.AnchorLayout AnchorLayout}</b>
13992 * based layout manager, for example:<div class="mdetail-params"><ul>
13993 * <li>{@link Ext.form.FormPanel}</li>
13994 * <li>specifying <code>layout: 'anchor' // or 'form', or 'absolute'</code></li>
13996 * <p>See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.</p>
14001 * <p>The <b>unique</b> id of this component (defaults to an {@link #getId auto-assigned id}).
14002 * You should assign an id if you need to be able to access the component later and you do
14003 * not have an object reference available (e.g., using {@link Ext}.{@link Ext#getCmp getCmp}).</p>
14004 * <p>Note that this id will also be used as the element id for the containing HTML element
14005 * that is rendered to the page for this component. This allows you to write id-based CSS
14006 * rules to style the specific instance of this component uniquely, and also to select
14007 * sub-elements using this component's id as the parent.</p>
14008 * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see
14009 * <code>{@link #itemId}</code> and <code>{@link #ref}</code>.</p>
14010 * <p><b>Note</b>: to access the container of an item see <code>{@link #ownerCt}</code>.</p>
14013 * @cfg {String} itemId
14014 * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
14015 * when no object reference is available. Instead of using an <code>{@link #id}</code> with
14016 * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
14017 * {@link Ext.Container}.{@link Ext.Container#getComponent getComponent} which will retrieve
14018 * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
14019 * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
14020 * avoiding potential conflicts with {@link Ext.ComponentMgr} which requires a <b>unique</b>
14021 * <code>{@link #id}</code>.</p>
14023 var c = new Ext.Panel({ //
14024 {@link Ext.BoxComponent#height height}: 300,
14025 {@link #renderTo}: document.body,
14026 {@link Ext.Container#layout layout}: 'auto',
14027 {@link Ext.Container#items items}: [
14030 {@link Ext.Panel#title title}: 'Panel 1',
14031 {@link Ext.BoxComponent#height height}: 150
14035 {@link Ext.Panel#title title}: 'Panel 2',
14036 {@link Ext.BoxComponent#height height}: 150
14040 p1 = c.{@link Ext.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
14041 p2 = p1.{@link #ownerCt}.{@link Ext.Container#getComponent getComponent}('p2'); // reference via a sibling
14043 * <p>Also see <tt>{@link #id}</tt> and <code>{@link #ref}</code>.</p>
14044 * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
14047 * @cfg {String} xtype
14048 * The registered <tt>xtype</tt> to create. This config option is not used when passing
14049 * a config object into a constructor. This config option is used only when
14050 * lazy instantiation is being used, and a child item of a Container is being
14051 * specified not as a fully instantiated Component, but as a <i>Component config
14052 * object</i>. The <tt>xtype</tt> will be looked up at render time up to determine what
14053 * type of child Component to create.<br><br>
14054 * The predefined xtypes are listed {@link Ext.Component here}.
14056 * If you subclass Components to create your own Components, you may register
14057 * them using {@link Ext.ComponentMgr#registerType} in order to be able to
14058 * take advantage of lazy instantiation and rendering.
14061 * @cfg {String} ptype
14062 * The registered <tt>ptype</tt> to create. This config option is not used when passing
14063 * a config object into a constructor. This config option is used only when
14064 * lazy instantiation is being used, and a Plugin is being
14065 * specified not as a fully instantiated Component, but as a <i>Component config
14066 * object</i>. The <tt>ptype</tt> will be looked up at render time up to determine what
14067 * type of Plugin to create.<br><br>
14068 * If you create your own Plugins, you may register them using
14069 * {@link Ext.ComponentMgr#registerPlugin} in order to be able to
14070 * take advantage of lazy instantiation and rendering.
14073 * @cfg {String} cls
14074 * An optional extra CSS class that will be added to this component's Element (defaults to ''). This can be
14075 * useful for adding customized styles to the component or any of its children using standard CSS rules.
14078 * @cfg {String} overCls
14079 * An optional extra CSS class that will be added to this component's Element when the mouse moves
14080 * over the Element, and removed when the mouse moves out. (defaults to ''). This can be
14081 * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
14084 * @cfg {String} style
14085 * A custom style specification to be applied to this component's Element. Should be a valid argument to
14086 * {@link Ext.Element#applyStyles}.
14089 title: 'Some Title',
14090 renderTo: Ext.getBody(),
14091 width: 400, height: 300,
14097 marginBottom: '10px'
14104 marginBottom: '10px'
14112 * @cfg {String} ctCls
14113 * <p>An optional extra CSS class that will be added to this component's container. This can be useful for
14114 * adding customized styles to the container or any of its children using standard CSS rules. See
14115 * {@link Ext.layout.ContainerLayout}.{@link Ext.layout.ContainerLayout#extraCls extraCls} also.</p>
14116 * <p><b>Note</b>: <tt>ctCls</tt> defaults to <tt>''</tt> except for the following class
14117 * which assigns a value by default:
14118 * <div class="mdetail-params"><ul>
14119 * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-layout-ct'</tt></li>
14121 * To configure the above Class with an extra CSS class append to the default. For example,
14122 * for BoxLayout (Hbox and Vbox):<pre><code>
14123 * ctCls: 'x-box-layout-ct custom-class'
14128 * @cfg {Boolean} disabled
14129 * Render this component disabled (default is false).
14133 * @cfg {Boolean} hidden
14134 * Render this component hidden (default is false). If <tt>true</tt>, the
14135 * {@link #hide} method will be called internally.
14139 * @cfg {Object/Array} plugins
14140 * An object or array of objects that will provide custom functionality for this component. The only
14141 * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
14142 * When a component is created, if any plugins are available, the component will call the init method on each
14143 * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
14144 * component as needed to provide its functionality.
14147 * @cfg {Mixed} applyTo
14148 * <p>Specify the id of the element, a DOM element or an existing Element corresponding to a DIV
14149 * that is already present in the document that specifies some structural markup for this
14150 * component.</p><div><ul>
14151 * <li><b>Description</b> : <ul>
14152 * <div class="sub-desc">When <tt>applyTo</tt> is used, constituent parts of the component can also be specified
14153 * by id or CSS class name within the main element, and the component being created may attempt
14154 * to create its subcomponents from that markup if applicable.</div>
14156 * <li><b>Notes</b> : <ul>
14157 * <div class="sub-desc">When using this config, a call to render() is not required.</div>
14158 * <div class="sub-desc">If applyTo is specified, any value passed for {@link #renderTo} will be ignored and the target
14159 * element's parent node will automatically be used as the component's container.</div>
14164 * @cfg {Mixed} renderTo
14165 * <p>Specify the id of the element, a DOM element or an existing Element that this component
14166 * will be rendered into.</p><div><ul>
14167 * <li><b>Notes</b> : <ul>
14168 * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
14169 * a {@link Ext.Container Container}. It is the responsibility of the
14170 * {@link Ext.Container Container}'s {@link Ext.Container#layout layout manager}
14171 * to render and manage its child items.</div>
14172 * <div class="sub-desc">When using this config, a call to render() is not required.</div>
14175 * <p>See <tt>{@link #render}</tt> also.</p>
14178 * @cfg {Boolean} stateful
14179 * <p>A flag which causes the Component to attempt to restore the state of
14180 * internal properties from a saved state on startup. The component must have
14181 * either a <code>{@link #stateId}</code> or <code>{@link #id}</code> assigned
14182 * for state to be managed. Auto-generated ids are not guaranteed to be stable
14183 * across page loads and cannot be relied upon to save and restore the same
14184 * state for a component.<p>
14185 * <p>For state saving to work, the state manager's provider must have been
14186 * set to an implementation of {@link Ext.state.Provider} which overrides the
14187 * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
14188 * methods to save and recall name/value pairs. A built-in implementation,
14189 * {@link Ext.state.CookieProvider} is available.</p>
14190 * <p>To set the state provider for the current page:</p>
14192 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
14193 expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
14196 * <p>A stateful Component attempts to save state when one of the events
14197 * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
14198 * <p>To save state, a stateful Component first serializes its state by
14199 * calling <b><code>getState</code></b>. By default, this function does
14200 * nothing. The developer must provide an implementation which returns an
14201 * object hash which represents the Component's restorable state.</p>
14202 * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
14203 * which uses the configured {@link Ext.state.Provider} to save the object
14204 * keyed by the Component's <code>{@link stateId}</code>, or, if that is not
14205 * specified, its <code>{@link #id}</code>.</p>
14206 * <p>During construction, a stateful Component attempts to <i>restore</i>
14207 * its state by calling {@link Ext.state.Manager#get} passing the
14208 * <code>{@link #stateId}</code>, or, if that is not specified, the
14209 * <code>{@link #id}</code>.</p>
14210 * <p>The resulting object is passed to <b><code>applyState</code></b>.
14211 * The default implementation of <code>applyState</code> simply copies
14212 * properties into the object, but a developer may override this to support
14213 * more behaviour.</p>
14214 * <p>You can perform extra processing on state save and restore by attaching
14215 * handlers to the {@link #beforestaterestore}, {@link #staterestore},
14216 * {@link #beforestatesave} and {@link #statesave} events.</p>
14219 * @cfg {String} stateId
14220 * The unique id for this component to use for state management purposes
14221 * (defaults to the component id if one was set, otherwise null if the
14222 * component is using a generated id).
14223 * <p>See <code>{@link #stateful}</code> for an explanation of saving and
14224 * restoring Component state.</p>
14227 * @cfg {Array} stateEvents
14228 * <p>An array of events that, when fired, should trigger this component to
14229 * save its state (defaults to none). <code>stateEvents</code> may be any type
14230 * of event supported by this component, including browser or custom events
14231 * (e.g., <tt>['click', 'customerchange']</tt>).</p>
14232 * <p>See <code>{@link #stateful}</code> for an explanation of saving and
14233 * restoring Component state.</p>
14236 * @cfg {Mixed} autoEl
14237 * <p>A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
14238 * encapsulate this Component.</p>
14239 * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}, {@link Ext.BoxComponent},
14240 * and {@link Ext.Container}, this defaults to <b><tt>'div'</tt></b>. The more complex Ext classes use a more complex
14241 * DOM structure created by their own onRender methods.</p>
14242 * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
14243 * different DOM elements. Example usage:</p><pre><code>
14248 src: 'http://www.example.com/example.jpg'
14254 html: 'autoEl is cool!'
14257 xtype: 'container',
14259 cls: 'ux-unordered-list',
14263 html: 'First list item'
14271 * @cfg {String} disabledClass
14272 * CSS class added to the component when it is disabled (defaults to 'x-item-disabled').
14274 disabledClass : 'x-item-disabled',
14276 * @cfg {Boolean} allowDomMove
14277 * Whether the component can move the Dom node when rendering (defaults to true).
14279 allowDomMove : true,
14281 * @cfg {Boolean} autoShow
14282 * True if the component should check for hidden classes (e.g. 'x-hidden' or 'x-hide-display') and remove
14283 * them on render (defaults to false).
14287 * @cfg {String} hideMode
14288 * <p>How this component should be hidden. Supported values are <tt>'visibility'</tt>
14289 * (css visibility), <tt>'offsets'</tt> (negative offset position) and <tt>'display'</tt>
14290 * (css display).</p>
14291 * <br><p><b>Note</b>: the default of <tt>'display'</tt> is generally preferred
14292 * since items are automatically laid out when they are first shown (no sizing
14293 * is done while hidden).</p>
14295 hideMode : 'display',
14297 * @cfg {Boolean} hideParent
14298 * True to hide and show the component's container when hide/show is called on the component, false to hide
14299 * and show the component itself (defaults to false). For example, this can be used as a shortcut for a hide
14300 * button on a window by setting hide:true on the button when adding it to its parent container.
14302 hideParent : false,
14304 * <p>The {@link Ext.Element} which encapsulates this Component. Read-only.</p>
14305 * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
14306 * that may be overridden using the <code>{@link #autoEl}</code> config.</p>
14307 * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
14308 * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
14309 * for this Component's own Observable events), see the {@link Ext.util.Observable#listeners listeners}
14310 * config for a suggestion, or use a render listener directly:</p><pre><code>
14312 title: 'The Clickable Panel',
14314 render: function(p) {
14315 // Append the Panel to the click handler's argument list.
14316 p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
14318 single: true // Remove the listener after first invocation
14322 * <p>See also <tt>{@link #getEl getEl}</p>
14323 * @type Ext.Element
14327 * The component's owner {@link Ext.Container} (defaults to undefined, and is set automatically when
14328 * the component is added to a container). Read-only.
14329 * <p><b>Note</b>: to access items within the container see <tt>{@link #itemId}</tt>.</p>
14330 * @type Ext.Container
14331 * @property ownerCt
14334 * True if this component is hidden. Read-only.
14339 * True if this component is disabled. Read-only.
14344 * True if this component has been rendered. Read-only.
14351 ctype : 'Ext.Component',
14357 getActionEl : function(){
14358 return this[this.actionMode];
14361 initPlugin : function(p){
14362 if(p.ptype && !Ext.isFunction(p.init)){
14363 p = Ext.ComponentMgr.createPlugin(p);
14364 }else if(Ext.isString(p)){
14365 p = Ext.ComponentMgr.createPlugin({
14374 * Function to be implemented by Component subclasses to be part of standard component initialization flow (it is empty by default).
14376 // Traditional constructor:
14377 Ext.Foo = function(config){
14378 // call superclass constructor:
14379 Ext.Foo.superclass.constructor.call(this, config);
14385 Ext.extend(Ext.Foo, Ext.Bar, {
14389 // initComponent replaces the constructor:
14390 Ext.Foo = Ext.extend(Ext.Bar, {
14391 initComponent : function(){
14392 // call superclass initComponent
14393 Ext.Container.superclass.initComponent.call(this);
14402 initComponent : Ext.emptyFn,
14405 * <p>Render this Component into the passed HTML element.</p>
14406 * <p><b>If you are using a {@link Ext.Container Container} object to house this Component, then
14407 * do not use the render method.</b></p>
14408 * <p>A Container's child Components are rendered by that Container's
14409 * {@link Ext.Container#layout layout} manager when the Container is first rendered.</p>
14410 * <p>Certain layout managers allow dynamic addition of child components. Those that do
14411 * include {@link Ext.layout.CardLayout}, {@link Ext.layout.AnchorLayout},
14412 * {@link Ext.layout.FormLayout}, {@link Ext.layout.TableLayout}.</p>
14413 * <p>If the Container is already rendered when a new child Component is added, you may need to call
14414 * the Container's {@link Ext.Container#doLayout doLayout} to refresh the view which causes any
14415 * unrendered child Components to be rendered. This is required so that you can add multiple
14416 * child components if needed while only refreshing the layout once.</p>
14417 * <p>When creating complex UIs, it is important to remember that sizing and positioning
14418 * of child items is the responsibility of the Container's {@link Ext.Container#layout layout} manager.
14419 * If you expect child items to be sized in response to user interactions, you must
14420 * configure the Container with a layout manager which creates and manages the type of layout you
14421 * have in mind.</p>
14422 * <p><b>Omitting the Container's {@link Ext.Container#layout layout} config means that a basic
14423 * layout manager is used which does nothing but render child components sequentially into the
14424 * Container. No sizing or positioning will be performed in this situation.</b></p>
14425 * @param {Element/HTMLElement/String} container (optional) The element this Component should be
14426 * rendered into. If it is being created from existing markup, this should be omitted.
14427 * @param {String/Number} position (optional) The element ID or DOM node index within the container <b>before</b>
14428 * which this component will be inserted (defaults to appending to the end of the container)
14430 render : function(container, position){
14431 if(!this.rendered && this.fireEvent('beforerender', this) !== false){
14432 if(!container && this.el){
14433 this.el = Ext.get(this.el);
14434 container = this.el.dom.parentNode;
14435 this.allowDomMove = false;
14437 this.container = Ext.get(container);
14439 this.container.addClass(this.ctCls);
14441 this.rendered = true;
14442 if(position !== undefined){
14443 if(Ext.isNumber(position)){
14444 position = this.container.dom.childNodes[position];
14446 position = Ext.getDom(position);
14449 this.onRender(this.container, position || null);
14451 this.el.removeClass(['x-hidden','x-hide-' + this.hideMode]);
14454 this.el.addClass(this.cls);
14458 this.el.applyStyles(this.style);
14462 this.el.addClassOnOver(this.overCls);
14464 this.fireEvent('render', this);
14465 this.afterRender(this.container);
14467 // call this so we don't fire initial hide events.
14471 // pass silent so the event doesn't fire the first time.
14472 this.disable(true);
14475 if(this.stateful !== false){
14476 this.initStateEvents();
14479 this.fireEvent('afterrender', this);
14484 initRef : function(){
14486 * @cfg {String} ref
14487 * <p>A path specification, relative to the Component's {@link #ownerCt} specifying into which
14488 * ancestor Container to place a named reference to this Component.</p>
14489 * <p>The ancestor axis can be traversed by using '/' characters in the path.
14490 * For example, to put a reference to a Toolbar Button into <i>the Panel which owns the Toolbar</i>:</p><pre><code>
14491 var myGrid = new Ext.grid.EditorGridPanel({
14492 title: 'My EditorGridPanel',
14494 colModel: myColModel,
14497 handler: saveChanges,
14499 ref: '../saveButton'
14502 afteredit: function() {
14503 // The button reference is in the GridPanel
14504 myGrid.saveButton.enable();
14509 * <p>In the code above, if the ref had been <code>'saveButton'</code> the reference would
14510 * have been placed into the Toolbar. Each '/' in the ref moves up one level from the
14511 * Component's {@link #ownerCt}.</p>
14514 var levels = this.ref.split('/');
14515 var last = levels.length, i = 0;
14523 t[levels[--i]] = this;
14528 initState : function(config){
14529 if(Ext.state.Manager){
14530 var id = this.getStateId();
14532 var state = Ext.state.Manager.get(id);
14534 if(this.fireEvent('beforestaterestore', this, state) !== false){
14535 this.applyState(state);
14536 this.fireEvent('staterestore', this, state);
14544 getStateId : function(){
14545 return this.stateId || ((this.id.indexOf('ext-comp-') == 0 || this.id.indexOf('ext-gen') == 0) ? null : this.id);
14549 initStateEvents : function(){
14550 if(this.stateEvents){
14551 for(var i = 0, e; e = this.stateEvents[i]; i++){
14552 this.on(e, this.saveState, this, {delay:100});
14558 applyState : function(state, config){
14560 Ext.apply(this, state);
14565 getState : function(){
14570 saveState : function(){
14571 if(Ext.state.Manager && this.stateful !== false){
14572 var id = this.getStateId();
14574 var state = this.getState();
14575 if(this.fireEvent('beforestatesave', this, state) !== false){
14576 Ext.state.Manager.set(id, state);
14577 this.fireEvent('statesave', this, state);
14584 * Apply this component to existing markup that is valid. With this function, no call to render() is required.
14585 * @param {String/HTMLElement} el
14587 applyToMarkup : function(el){
14588 this.allowDomMove = false;
14589 this.el = Ext.get(el);
14590 this.render(this.el.dom.parentNode);
14594 * Adds a CSS class to the component's underlying element.
14595 * @param {string} cls The CSS class name to add
14596 * @return {Ext.Component} this
14598 addClass : function(cls){
14600 this.el.addClass(cls);
14602 this.cls = this.cls ? this.cls + ' ' + cls : cls;
14608 * Removes a CSS class from the component's underlying element.
14609 * @param {string} cls The CSS class name to remove
14610 * @return {Ext.Component} this
14612 removeClass : function(cls){
14614 this.el.removeClass(cls);
14615 }else if(this.cls){
14616 this.cls = this.cls.split(' ').remove(cls).join(' ');
14622 // default function is not really useful
14623 onRender : function(ct, position){
14624 if(!this.el && this.autoEl){
14625 if(Ext.isString(this.autoEl)){
14626 this.el = document.createElement(this.autoEl);
14628 var div = document.createElement('div');
14629 Ext.DomHelper.overwrite(div, this.autoEl);
14630 this.el = div.firstChild;
14633 this.el.id = this.getId();
14637 this.el = Ext.get(this.el);
14638 if(this.allowDomMove !== false){
14639 ct.dom.insertBefore(this.el.dom, position);
14645 getAutoCreate : function(){
14646 var cfg = Ext.isObject(this.autoCreate) ?
14647 this.autoCreate : Ext.apply({}, this.defaultAutoCreate);
14648 if(this.id && !cfg.id){
14655 afterRender : Ext.emptyFn,
14658 * Destroys this component by purging any event listeners, removing the component's element from the DOM,
14659 * removing the component from its {@link Ext.Container} (if applicable) and unregistering it from
14660 * {@link Ext.ComponentMgr}. Destruction is generally handled automatically by the framework and this method
14661 * should usually not need to be called directly.
14664 destroy : function(){
14665 if(this.fireEvent('beforedestroy', this) !== false){
14666 this.beforeDestroy();
14668 this.el.removeAllListeners();
14670 if(this.actionMode == 'container' || this.removeMode == 'container'){
14671 this.container.remove();
14675 Ext.ComponentMgr.unregister(this);
14676 this.fireEvent('destroy', this);
14677 this.purgeListeners();
14682 beforeDestroy : Ext.emptyFn,
14685 onDestroy : Ext.emptyFn,
14688 * <p>Returns the {@link Ext.Element} which encapsulates this Component.</p>
14689 * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
14690 * that may be overridden using the {@link #autoEl} config.</p>
14691 * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
14692 * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
14693 * for this Component's own Observable events), see the {@link #listeners} config for a suggestion,
14694 * or use a render listener directly:</p><pre><code>
14696 title: 'The Clickable Panel',
14698 render: function(p) {
14699 // Append the Panel to the click handler's argument list.
14700 p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
14702 single: true // Remove the listener after first invocation
14706 * @return {Ext.Element} The Element which encapsulates this Component.
14708 getEl : function(){
14713 * Returns the <code>id</code> of this component or automatically generates and
14714 * returns an <code>id</code> if an <code>id</code> is not defined yet:<pre><code>
14715 * 'ext-comp-' + (++Ext.Component.AUTO_ID)
14717 * @return {String} id
14719 getId : function(){
14720 return this.id || (this.id = 'ext-comp-' + (++Ext.Component.AUTO_ID));
14724 * Returns the <code>{@link #itemId}</code> of this component. If an
14725 * <code>{@link #itemId}</code> was not assigned through configuration the
14726 * <code>id</code> is returned using <code>{@link #getId}</code>.
14729 getItemId : function(){
14730 return this.itemId || this.getId();
14734 * Try to focus this component.
14735 * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
14736 * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
14737 * @return {Ext.Component} this
14739 focus : function(selectText, delay){
14741 this.focus.defer(Ext.isNumber(delay) ? delay : 10, this, [selectText, false]);
14746 if(selectText === true){
14747 this.el.dom.select();
14762 * Disable this component and fire the 'disable' event.
14763 * @return {Ext.Component} this
14765 disable : function(/* private */ silent){
14769 this.disabled = true;
14770 if(silent !== true){
14771 this.fireEvent('disable', this);
14777 onDisable : function(){
14778 this.getActionEl().addClass(this.disabledClass);
14779 this.el.dom.disabled = true;
14783 * Enable this component and fire the 'enable' event.
14784 * @return {Ext.Component} this
14786 enable : function(){
14790 this.disabled = false;
14791 this.fireEvent('enable', this);
14796 onEnable : function(){
14797 this.getActionEl().removeClass(this.disabledClass);
14798 this.el.dom.disabled = false;
14802 * Convenience function for setting disabled/enabled by boolean.
14803 * @param {Boolean} disabled
14804 * @return {Ext.Component} this
14806 setDisabled : function(disabled){
14807 return this[disabled ? 'disable' : 'enable']();
14811 * Show this component. Listen to the '{@link #beforeshow}' event and return
14812 * <tt>false</tt> to cancel showing the component. Fires the '{@link #show}'
14813 * event after showing the component.
14814 * @return {Ext.Component} this
14817 if(this.fireEvent('beforeshow', this) !== false){
14818 this.hidden = false;
14819 if(this.autoRender){
14820 this.render(Ext.isBoolean(this.autoRender) ? Ext.getBody() : this.autoRender);
14825 this.fireEvent('show', this);
14831 onShow : function(){
14832 this.getVisibiltyEl().removeClass('x-hide-' + this.hideMode);
14836 * Hide this component. Listen to the '{@link #beforehide}' event and return
14837 * <tt>false</tt> to cancel hiding the component. Fires the '{@link #hide}'
14838 * event after hiding the component. Note this method is called internally if
14839 * the component is configured to be <code>{@link #hidden}</code>.
14840 * @return {Ext.Component} this
14843 if(this.fireEvent('beforehide', this) !== false){
14845 this.fireEvent('hide', this);
14851 doHide: function(){
14852 this.hidden = true;
14859 onHide : function(){
14860 this.getVisibiltyEl().addClass('x-hide-' + this.hideMode);
14864 getVisibiltyEl : function(){
14865 return this.hideParent ? this.container : this.getActionEl();
14869 * Convenience function to hide or show this component by boolean.
14870 * @param {Boolean} visible True to show, false to hide
14871 * @return {Ext.Component} this
14873 setVisible : function(visible){
14874 return this[visible ? 'show' : 'hide']();
14878 * Returns true if this component is visible.
14879 * @return {Boolean} True if this component is visible, false otherwise.
14881 isVisible : function(){
14882 return this.rendered && this.getVisibiltyEl().isVisible();
14886 * Clone the current component using the original config values passed into this instance by default.
14887 * @param {Object} overrides A new config containing any properties to override in the cloned version.
14888 * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
14889 * @return {Ext.Component} clone The cloned copy of this component
14891 cloneConfig : function(overrides){
14892 overrides = overrides || {};
14893 var id = overrides.id || Ext.id();
14894 var cfg = Ext.applyIf(overrides, this.initialConfig);
14895 cfg.id = id; // prevent dup id
14896 return new this.constructor(cfg);
14900 * Gets the xtype for this component as registered with {@link Ext.ComponentMgr}. For a list of all
14901 * available xtypes, see the {@link Ext.Component} header. Example usage:
14903 var t = new Ext.form.TextField();
14904 alert(t.getXType()); // alerts 'textfield'
14906 * @return {String} The xtype
14908 getXType : function(){
14909 return this.constructor.xtype;
14913 * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
14914 * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
14915 * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
14916 * to participate in determination of inherited xtypes.</b></p>
14917 * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
14918 * <p>Example usage:</p>
14920 var t = new Ext.form.TextField();
14921 var isText = t.isXType('textfield'); // true
14922 var isBoxSubclass = t.isXType('box'); // true, descended from BoxComponent
14923 var isBoxInstance = t.isXType('box', true); // false, not a direct BoxComponent instance
14925 * @param {String} xtype The xtype to check for this Component
14926 * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
14927 * the default), or true to check whether this Component is directly of the specified xtype.
14928 * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
14930 isXType : function(xtype, shallow){
14931 //assume a string by default
14932 if (Ext.isFunction(xtype)){
14933 xtype = xtype.xtype; //handle being passed the class, e.g. Ext.Component
14934 }else if (Ext.isObject(xtype)){
14935 xtype = xtype.constructor.xtype; //handle being passed an instance
14938 return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1 : this.constructor.xtype == xtype;
14942 * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
14943 * available xtypes, see the {@link Ext.Component} header.</p>
14944 * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
14945 * to participate in determination of inherited xtypes.</b></p>
14946 * <p>Example usage:</p>
14948 var t = new Ext.form.TextField();
14949 alert(t.getXTypes()); // alerts 'component/box/field/textfield'
14951 * @return {String} The xtype hierarchy string
14953 getXTypes : function(){
14954 var tc = this.constructor;
14956 var c = [], sc = this;
14957 while(sc && sc.constructor.xtype){
14958 c.unshift(sc.constructor.xtype);
14959 sc = sc.constructor.superclass;
14962 tc.xtypes = c.join('/');
14968 * Find a container above this component at any level by a custom function. If the passed function returns
14969 * true, the container will be returned.
14970 * @param {Function} fn The custom function to call with the arguments (container, this component).
14971 * @return {Ext.Container} The first Container for which the custom function returns true
14973 findParentBy : function(fn) {
14974 for (var p = this.ownerCt; (p != null) && !fn(p, this); p = p.ownerCt);
14979 * Find a container above this component at any level by xtype or class
14980 * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
14981 * @return {Ext.Container} The first Container which matches the given xtype or class
14983 findParentByType : function(xtype) {
14984 return Ext.isFunction(xtype) ?
14985 this.findParentBy(function(p){
14986 return p.constructor === xtype;
14988 this.findParentBy(function(p){
14989 return p.constructor.xtype === xtype;
14993 getDomPositionEl : function(){
14994 return this.getPositionEl ? this.getPositionEl() : this.getEl();
14998 purgeListeners : function(){
14999 Ext.Component.superclass.purgeListeners.call(this);
15001 this.on('beforedestroy', this.clearMons, this, {single: true});
15006 clearMons : function(){
15007 Ext.each(this.mons, function(m){
15008 m.item.un(m.ename, m.fn, m.scope);
15013 // internal function for auto removal of assigned event handlers on destruction
15014 mon : function(item, ename, fn, scope, opt){
15017 this.on('beforedestroy', this.clearMons, this, {single: true});
15020 if(Ext.isObject(ename)){
15021 var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
15025 if(propRe.test(e)){
15028 if(Ext.isFunction(o[e])){
15031 item: item, ename: e, fn: o[e], scope: o.scope
15033 item.on(e, o[e], o.scope, o);
15035 // individual options
15037 item: item, ename: e, fn: o[e], scope: o.scope
15047 item: item, ename: ename, fn: fn, scope: scope
15049 item.on(ename, fn, scope, opt);
15052 // protected, opposite of mon
15053 mun : function(item, ename, fn, scope){
15055 for(var i = 0, len = this.mons.length; i < len; ++i){
15056 mon = this.mons[i];
15057 if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){
15058 this.mons.splice(i, 1);
15059 item.un(ename, fn, scope);
15068 * Returns the next component in the owning container
15069 * @return Ext.Component
15071 nextSibling : function(){
15073 var index = this.ownerCt.items.indexOf(this);
15074 if(index != -1 && index+1 < this.ownerCt.items.getCount()){
15075 return this.ownerCt.items.itemAt(index+1);
15082 * Returns the previous component in the owning container
15083 * @return Ext.Component
15085 previousSibling : function(){
15087 var index = this.ownerCt.items.indexOf(this);
15089 return this.ownerCt.items.itemAt(index-1);
15096 * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
15097 * @return {Ext.Container} the Container which owns this Component.
15099 getBubbleTarget : function(){
15100 return this.ownerCt;
15104 Ext.reg('component', Ext.Component);
15106 * @class Ext.Action
\r
15107 * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
\r
15108 * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI
\r
15109 * updates across any components that support the Action interface (primarily {@link Ext.Toolbar}, {@link Ext.Button}
\r
15110 * and {@link Ext.menu.Menu} components).</p>
\r
15111 * <p>Aside from supporting the config object interface, any component that needs to use Actions must also support
\r
15112 * the following method list, as these will be called as needed by the Action class: setText(string), setIconCls(string),
\r
15113 * setDisabled(boolean), setVisible(boolean) and setHandler(function).</p>
\r
15114 * Example usage:<br>
\r
15116 // Define the shared action. Each component below will have the same
\r
15117 // display text and icon, and will display the same message on click.
\r
15118 var action = new Ext.Action({
\r
15119 {@link #text}: 'Do something',
\r
15120 {@link #handler}: function(){
\r
15121 Ext.Msg.alert('Click', 'You did something.');
\r
15123 {@link #iconCls}: 'do-something',
\r
15124 {@link #itemId}: 'myAction'
\r
15127 var panel = new Ext.Panel({
\r
15128 title: 'Actions',
\r
15132 // Add the action directly to a toolbar as a menu button
\r
15135 text: 'Action Menu',
\r
15136 // Add the action to a menu as a text item
\r
15141 // Add the action to the panel body as a standard button
\r
15142 new Ext.Button(action)
\r
15144 renderTo: Ext.getBody()
\r
15147 // Change the text for all components using the action
\r
15148 action.setText('Something else');
\r
15150 // Reference an action through a container using the itemId
\r
15151 var btn = panel.getComponent('myAction');
\r
15152 var aRef = btn.baseAction;
\r
15153 aRef.setText('New text');
\r
15156 * @param {Object} config The configuration options
\r
15158 Ext.Action = function(config){
\r
15159 this.initialConfig = config;
\r
15160 this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
\r
15164 Ext.Action.prototype = {
\r
15166 * @cfg {String} text The text to set for all components using this action (defaults to '').
\r
15169 * @cfg {String} iconCls
\r
15170 * The CSS class selector that specifies a background image to be used as the header icon for
\r
15171 * all components using this action (defaults to '').
\r
15172 * <p>An example of specifying a custom icon class would be something like:
\r
15173 * </p><pre><code>
\r
15174 // specify the property in the config for the class:
\r
15176 iconCls: 'do-something'
\r
15178 // css class that specifies background image to be used as the icon image:
\r
15179 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
\r
15183 * @cfg {Boolean} disabled True to disable all components using this action, false to enable them (defaults to false).
\r
15186 * @cfg {Boolean} hidden True to hide all components using this action, false to show them (defaults to false).
\r
15189 * @cfg {Function} handler The function that will be invoked by each component tied to this action
\r
15190 * when the component's primary event is triggered (defaults to undefined).
\r
15193 * @cfg {String} itemId
\r
15194 * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
\r
15197 * @cfg {Object} scope The scope in which the {@link #handler} function will execute.
\r
15204 * Sets the text to be displayed by all components using this action.
\r
15205 * @param {String} text The text to display
\r
15207 setText : function(text){
\r
15208 this.initialConfig.text = text;
\r
15209 this.callEach('setText', [text]);
\r
15213 * Gets the text currently displayed by all components using this action.
\r
15215 getText : function(){
\r
15216 return this.initialConfig.text;
\r
15220 * Sets the icon CSS class for all components using this action. The class should supply
\r
15221 * a background image that will be used as the icon image.
\r
15222 * @param {String} cls The CSS class supplying the icon image
\r
15224 setIconClass : function(cls){
\r
15225 this.initialConfig.iconCls = cls;
\r
15226 this.callEach('setIconClass', [cls]);
\r
15230 * Gets the icon CSS class currently used by all components using this action.
\r
15232 getIconClass : function(){
\r
15233 return this.initialConfig.iconCls;
\r
15237 * Sets the disabled state of all components using this action. Shortcut method
\r
15238 * for {@link #enable} and {@link #disable}.
\r
15239 * @param {Boolean} disabled True to disable the component, false to enable it
\r
15241 setDisabled : function(v){
\r
15242 this.initialConfig.disabled = v;
\r
15243 this.callEach('setDisabled', [v]);
\r
15247 * Enables all components using this action.
\r
15249 enable : function(){
\r
15250 this.setDisabled(false);
\r
15254 * Disables all components using this action.
\r
15256 disable : function(){
\r
15257 this.setDisabled(true);
\r
15261 * Returns true if the components using this action are currently disabled, else returns false.
\r
15263 isDisabled : function(){
\r
15264 return this.initialConfig.disabled;
\r
15268 * Sets the hidden state of all components using this action. Shortcut method
\r
15269 * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
\r
15270 * @param {Boolean} hidden True to hide the component, false to show it
\r
15272 setHidden : function(v){
\r
15273 this.initialConfig.hidden = v;
\r
15274 this.callEach('setVisible', [!v]);
\r
15278 * Shows all components using this action.
\r
15280 show : function(){
\r
15281 this.setHidden(false);
\r
15285 * Hides all components using this action.
\r
15287 hide : function(){
\r
15288 this.setHidden(true);
\r
15292 * Returns true if the components using this action are currently hidden, else returns false.
\r
15294 isHidden : function(){
\r
15295 return this.initialConfig.hidden;
\r
15299 * Sets the function that will be called by each component using this action when its primary event is triggered.
\r
15300 * @param {Function} fn The function that will be invoked by the action's components. The function
\r
15301 * will be called with no arguments.
\r
15302 * @param {Object} scope The scope in which the function will execute
\r
15304 setHandler : function(fn, scope){
\r
15305 this.initialConfig.handler = fn;
\r
15306 this.initialConfig.scope = scope;
\r
15307 this.callEach('setHandler', [fn, scope]);
\r
15311 * Executes the specified function once for each component currently tied to this action. The function passed
\r
15312 * in should accept a single argument that will be an object that supports the basic Action config/method interface.
\r
15313 * @param {Function} fn The function to execute for each component
\r
15314 * @param {Object} scope The scope in which the function will execute
\r
15316 each : function(fn, scope){
\r
15317 Ext.each(this.items, fn, scope);
\r
15321 callEach : function(fnName, args){
\r
15322 var cs = this.items;
\r
15323 for(var i = 0, len = cs.length; i < len; i++){
\r
15324 cs[i][fnName].apply(cs[i], args);
\r
15329 addComponent : function(comp){
\r
15330 this.items.push(comp);
\r
15331 comp.on('destroy', this.removeComponent, this);
\r
15335 removeComponent : function(comp){
\r
15336 this.items.remove(comp);
\r
15340 * Executes this action manually using the handler function specified in the original config object
\r
15341 * or the handler function set with <code>{@link #setHandler}</code>. Any arguments passed to this
\r
15342 * function will be passed on to the handler function.
\r
15343 * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function
\r
15344 * @param {Mixed} arg2 (optional)
\r
15345 * @param {Mixed} etc... (optional)
\r
15347 execute : function(){
\r
15348 this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments);
\r
15353 * @extends Ext.Element
15354 * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
15355 * automatic maintaining of shadow/shim positions.
15356 * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
15357 * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
15358 * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
15359 * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
15360 * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
15361 * @cfg {String} cls CSS class to add to the element
15362 * @cfg {Number} zindex Starting z-index (defaults to 11000)
15363 * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
15364 * @cfg {Boolean} useDisplay
15365 * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
15366 * to use css style <tt>'display:none;'</tt> to hide the Layer.
15368 * @param {Object} config An object with config options.
15369 * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
15372 Ext.Layer = function(config, existingEl){
15373 config = config || {};
15374 var dh = Ext.DomHelper;
15375 var cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
15377 this.dom = Ext.getDom(existingEl);
15380 var o = config.dh || {tag: 'div', cls: 'x-layer'};
15381 this.dom = dh.append(pel, o);
15384 this.addClass(config.cls);
15386 this.constrain = config.constrain !== false;
15387 this.setVisibilityMode(Ext.Element.VISIBILITY);
15389 this.id = this.dom.id = config.id;
15391 this.id = Ext.id(this.dom);
15393 this.zindex = config.zindex || this.getZIndex();
15394 this.position('absolute', this.zindex);
15396 this.shadowOffset = config.shadowOffset || 4;
15397 this.shadow = new Ext.Shadow({
15398 offset : this.shadowOffset,
15399 mode : config.shadow
15402 this.shadowOffset = 0;
15404 this.useShim = config.shim !== false && Ext.useShims;
15405 this.useDisplay = config.useDisplay;
15409 var supr = Ext.Element.prototype;
15411 // shims are shared among layer to keep from having 100 iframes
15414 Ext.extend(Ext.Layer, Ext.Element, {
15416 getZIndex : function(){
15417 return this.zindex || parseInt((this.getShim() || this).getStyle('z-index'), 10) || 11000;
15420 getShim : function(){
15427 var shim = shims.shift();
15429 shim = this.createShim();
15430 shim.enableDisplayMode('block');
15431 shim.dom.style.display = 'none';
15432 shim.dom.style.visibility = 'visible';
15434 var pn = this.dom.parentNode;
15435 if(shim.dom.parentNode != pn){
15436 pn.insertBefore(shim.dom, this.dom);
15438 shim.setStyle('z-index', this.getZIndex()-2);
15443 hideShim : function(){
15445 this.shim.setDisplayed(false);
15446 shims.push(this.shim);
15451 disableShadow : function(){
15453 this.shadowDisabled = true;
15454 this.shadow.hide();
15455 this.lastShadowOffset = this.shadowOffset;
15456 this.shadowOffset = 0;
15460 enableShadow : function(show){
15462 this.shadowDisabled = false;
15463 this.shadowOffset = this.lastShadowOffset;
15464 delete this.lastShadowOffset;
15472 // this code can execute repeatedly in milliseconds (i.e. during a drag) so
15473 // code size was sacrificed for effeciency (e.g. no getBox/setBox, no XY calls)
15474 sync : function(doShow){
15475 var sw = this.shadow;
15476 if(!this.updating && this.isVisible() && (sw || this.useShim)){
15477 var sh = this.getShim();
15479 var w = this.getWidth(),
15480 h = this.getHeight();
15482 var l = this.getLeft(true),
15483 t = this.getTop(true);
15485 if(sw && !this.shadowDisabled){
15486 if(doShow && !sw.isVisible()){
15489 sw.realign(l, t, w, h);
15495 // fit the shim behind the shadow, so it is shimmed too
15496 var a = sw.adjusts, s = sh.dom.style;
15497 s.left = (Math.min(l, l+a.l))+'px';
15498 s.top = (Math.min(t, t+a.t))+'px';
15499 s.width = (w+a.w)+'px';
15500 s.height = (h+a.h)+'px';
15507 sh.setLeftTop(l, t);
15514 destroy : function(){
15517 this.shadow.hide();
15519 this.removeAllListeners();
15520 Ext.removeNode(this.dom);
15521 Ext.Element.uncache(this.id);
15524 remove : function(){
15529 beginUpdate : function(){
15530 this.updating = true;
15534 endUpdate : function(){
15535 this.updating = false;
15540 hideUnders : function(negOffset){
15542 this.shadow.hide();
15548 constrainXY : function(){
15549 if(this.constrain){
15550 var vw = Ext.lib.Dom.getViewWidth(),
15551 vh = Ext.lib.Dom.getViewHeight();
15552 var s = Ext.getDoc().getScroll();
15554 var xy = this.getXY();
15555 var x = xy[0], y = xy[1];
15556 var so = this.shadowOffset;
15557 var w = this.dom.offsetWidth+so, h = this.dom.offsetHeight+so;
15558 // only move it if it needs it
15560 // first validate right/bottom
15561 if((x + w) > vw+s.left){
15565 if((y + h) > vh+s.top){
15569 // then make sure top/left isn't negative
15580 var ay = this.avoidY;
15581 if(y <= ay && (y+h) >= ay){
15587 supr.setXY.call(this, xy);
15594 isVisible : function(){
15595 return this.visible;
15599 showAction : function(){
15600 this.visible = true; // track visibility to prevent getStyle calls
15601 if(this.useDisplay === true){
15602 this.setDisplayed('');
15603 }else if(this.lastXY){
15604 supr.setXY.call(this, this.lastXY);
15605 }else if(this.lastLT){
15606 supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
15611 hideAction : function(){
15612 this.visible = false;
15613 if(this.useDisplay === true){
15614 this.setDisplayed(false);
15616 this.setLeftTop(-10000,-10000);
15620 // overridden Element method
15621 setVisible : function(v, a, d, c, e){
15626 var cb = function(){
15631 }.createDelegate(this);
15632 supr.setVisible.call(this, true, true, d, cb, e);
15635 this.hideUnders(true);
15644 }.createDelegate(this);
15646 supr.setVisible.call(this, v, a, d, cb, e);
15656 storeXY : function(xy){
15657 delete this.lastLT;
15661 storeLeftTop : function(left, top){
15662 delete this.lastXY;
15663 this.lastLT = [left, top];
15667 beforeFx : function(){
15668 this.beforeAction();
15669 return Ext.Layer.superclass.beforeFx.apply(this, arguments);
15673 afterFx : function(){
15674 Ext.Layer.superclass.afterFx.apply(this, arguments);
15675 this.sync(this.isVisible());
15679 beforeAction : function(){
15680 if(!this.updating && this.shadow){
15681 this.shadow.hide();
15685 // overridden Element method
15686 setLeft : function(left){
15687 this.storeLeftTop(left, this.getTop(true));
15688 supr.setLeft.apply(this, arguments);
15693 setTop : function(top){
15694 this.storeLeftTop(this.getLeft(true), top);
15695 supr.setTop.apply(this, arguments);
15700 setLeftTop : function(left, top){
15701 this.storeLeftTop(left, top);
15702 supr.setLeftTop.apply(this, arguments);
15707 setXY : function(xy, a, d, c, e){
15709 this.beforeAction();
15711 var cb = this.createCB(c);
15712 supr.setXY.call(this, xy, a, d, cb, e);
15720 createCB : function(c){
15731 // overridden Element method
15732 setX : function(x, a, d, c, e){
15733 this.setXY([x, this.getY()], a, d, c, e);
15737 // overridden Element method
15738 setY : function(y, a, d, c, e){
15739 this.setXY([this.getX(), y], a, d, c, e);
15743 // overridden Element method
15744 setSize : function(w, h, a, d, c, e){
15745 this.beforeAction();
15746 var cb = this.createCB(c);
15747 supr.setSize.call(this, w, h, a, d, cb, e);
15754 // overridden Element method
15755 setWidth : function(w, a, d, c, e){
15756 this.beforeAction();
15757 var cb = this.createCB(c);
15758 supr.setWidth.call(this, w, a, d, cb, e);
15765 // overridden Element method
15766 setHeight : function(h, a, d, c, e){
15767 this.beforeAction();
15768 var cb = this.createCB(c);
15769 supr.setHeight.call(this, h, a, d, cb, e);
15776 // overridden Element method
15777 setBounds : function(x, y, w, h, a, d, c, e){
15778 this.beforeAction();
15779 var cb = this.createCB(c);
15781 this.storeXY([x, y]);
15782 supr.setXY.call(this, [x, y]);
15783 supr.setSize.call(this, w, h, a, d, cb, e);
15786 supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
15792 * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
15793 * incremented by two more than the value passed in so that it always shows above any shadow or shim (the shadow
15794 * element, if any, will be assigned z-index + 1, and the shim element, if any, will be assigned the unmodified z-index).
15795 * @param {Number} zindex The new z-index to set
15796 * @return {this} The Layer
15798 setZIndex : function(zindex){
15799 this.zindex = zindex;
15800 this.setStyle('z-index', zindex + 2);
15802 this.shadow.setZIndex(zindex + 1);
15805 this.shim.setStyle('z-index', zindex);
15811 * @class Ext.Shadow
15812 * Simple class that can provide a shadow effect for any element. Note that the element MUST be absolutely positioned,
15813 * and the shadow does not provide any shimming. This should be used only in simple cases -- for more advanced
15814 * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
15816 * Create a new Shadow
15817 * @param {Object} config The config object
15819 Ext.Shadow = function(config){
15820 Ext.apply(this, config);
15821 if(typeof this.mode != "string"){
15822 this.mode = this.defaultMode;
15824 var o = this.offset, a = {h: 0};
15825 var rad = Math.floor(this.offset/2);
15826 switch(this.mode.toLowerCase()){ // all this hideous nonsense calculates the various offsets for shadows
15832 a.l -= this.offset + rad;
15833 a.t -= this.offset + rad;
15844 a.l -= (this.offset - rad);
15845 a.t -= this.offset + rad;
15847 a.w -= (this.offset - rad)*2;
15858 a.l -= (this.offset - rad);
15859 a.t -= (this.offset - rad);
15861 a.w -= (this.offset + rad + 1);
15862 a.h -= (this.offset + rad);
15871 Ext.Shadow.prototype = {
15873 * @cfg {String} mode
15874 * The shadow display mode. Supports the following options:<div class="mdetail-params"><ul>
15875 * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
15876 * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
15877 * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
15881 * @cfg {String} offset
15882 * The number of pixels to offset the shadow from the element (defaults to <tt>4</tt>)
15887 defaultMode: "drop",
15890 * Displays the shadow under the target element
15891 * @param {Mixed} targetEl The id or element under which the shadow should display
15893 show : function(target){
15894 target = Ext.get(target);
15896 this.el = Ext.Shadow.Pool.pull();
15897 if(this.el.dom.nextSibling != target.dom){
15898 this.el.insertBefore(target);
15901 this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10)-1);
15903 this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")";
15906 target.getLeft(true),
15907 target.getTop(true),
15911 this.el.dom.style.display = "block";
15915 * Returns true if the shadow is visible, else false
15917 isVisible : function(){
15918 return this.el ? true : false;
15922 * Direct alignment when values are already available. Show must be called at least once before
15923 * calling this method to ensure it is initialized.
15924 * @param {Number} left The target element left position
15925 * @param {Number} top The target element top position
15926 * @param {Number} width The target element width
15927 * @param {Number} height The target element height
15929 realign : function(l, t, w, h){
15933 var a = this.adjusts, d = this.el.dom, s = d.style;
15935 s.left = (l+a.l)+"px";
15936 s.top = (t+a.t)+"px";
15937 var sw = (w+a.w), sh = (h+a.h), sws = sw +"px", shs = sh + "px";
15938 if(s.width != sws || s.height != shs){
15942 var cn = d.childNodes;
15943 var sww = Math.max(0, (sw-12))+"px";
15944 cn[0].childNodes[1].style.width = sww;
15945 cn[1].childNodes[1].style.width = sww;
15946 cn[2].childNodes[1].style.width = sww;
15947 cn[1].style.height = Math.max(0, (sh-12))+"px";
15953 * Hides this shadow
15957 this.el.dom.style.display = "none";
15958 Ext.Shadow.Pool.push(this.el);
15964 * Adjust the z-index of this shadow
15965 * @param {Number} zindex The new z-index
15967 setZIndex : function(z){
15970 this.el.setStyle("z-index", z);
15975 // Private utility class that manages the internal Shadow cache
15976 Ext.Shadow.Pool = function(){
15978 var markup = Ext.isIE ?
15979 '<div class="x-ie-shadow"></div>' :
15980 '<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';
15983 var sh = p.shift();
15985 sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
15986 sh.autoBoxAdjust = false;
15991 push : function(sh){
15996 * @class Ext.BoxComponent
15997 * @extends Ext.Component
15998 * <p>Base class for any {@link Ext.Component Component} that is to be sized as a box, using width and height.</p>
15999 * <p>BoxComponent provides automatic box model adjustments for sizing and positioning and will work correctly
16000 * within the Component rendering model.</p>
16001 * <p>A BoxComponent may be created as a custom Component which encapsulates any HTML element, either a pre-existing
16002 * element, or one that is created to your specifications at render time. Usually, to participate in layouts,
16003 * a Component will need to be a <b>Box</b>Component in order to have its width and height managed.</p>
16004 * <p>To use a pre-existing element as a BoxComponent, configure it so that you preset the <b>el</b> property to the
16005 * element to reference:<pre><code>
16006 var pageHeader = new Ext.BoxComponent({
16007 el: 'my-header-div'
16009 * This may then be {@link Ext.Container#add added} to a {@link Ext.Container Container} as a child item.</p>
16010 * <p>To create a BoxComponent based around a HTML element to be created at render time, use the
16011 * {@link Ext.Component#autoEl autoEl} config option which takes the form of a
16012 * {@link Ext.DomHelper DomHelper} specification:<pre><code>
16013 var myImage = new Ext.BoxComponent({
16016 src: '/images/my-image.jpg'
16018 });</code></pre></p>
16020 * @param {Ext.Element/String/Object} config The configuration options.
16023 Ext.BoxComponent = Ext.extend(Ext.Component, {
16025 // Configs below are used for all Components when rendered by BorderLayout.
16027 * @cfg {String} region <p><b>Note</b>: this config is only used when this BoxComponent is rendered
16028 * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
16029 * layout manager (e.g. specifying <tt>layout:'border'</tt>).</p><br>
16030 * <p>See {@link Ext.layout.BorderLayout} also.</p>
16032 // margins config is used when a BoxComponent is rendered by BorderLayout or BoxLayout.
16034 * @cfg {Object} margins <p><b>Note</b>: this config is only used when this BoxComponent is rendered
16035 * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
16036 * or one of the two <b>{@link Ext.layout.BoxLayout BoxLayout} subclasses.</b></p>
16037 * <p>An object containing margins to apply to this BoxComponent in the
16038 * format:</p><pre><code>
16041 right: (right margin),
16042 bottom: (bottom margin),
16043 left: (left margin)
16045 * <p>May also be a string containing space-separated, numeric margin values. The order of the
16046 * sides associated with each value matches the way CSS processes margin values:</p>
16047 * <p><div class="mdetail-params"><ul>
16048 * <li>If there is only one value, it applies to all sides.</li>
16049 * <li>If there are two values, the top and bottom borders are set to the first value and the
16050 * right and left are set to the second.</li>
16051 * <li>If there are three values, the top is set to the first value, the left and right are set
16052 * to the second, and the bottom is set to the third.</li>
16053 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
16055 * <p>Defaults to:</p><pre><code>
16056 * {top:0, right:0, bottom:0, left:0}
16061 * The local x (left) coordinate for this component if contained within a positioning container.
16065 * The local y (top) coordinate for this component if contained within a positioning container.
16068 * @cfg {Number} pageX
16069 * The page level x coordinate for this component if contained within a positioning container.
16072 * @cfg {Number} pageY
16073 * The page level y coordinate for this component if contained within a positioning container.
16076 * @cfg {Number} height
16077 * The height of this component in pixels (defaults to auto).
16078 * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
16081 * @cfg {Number} width
16082 * The width of this component in pixels (defaults to auto).
16083 * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
16086 * @cfg {Boolean} autoHeight
16087 * <p>True to use height:'auto', false to use fixed height (or allow it to be managed by its parent
16088 * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
16089 * <p><b>Note</b>: Although many components inherit this config option, not all will
16090 * function as expected with a height of 'auto'. Setting autoHeight:true means that the
16091 * browser will manage height based on the element's contents, and that Ext will not manage it at all.</p>
16092 * <p>If the <i>browser</i> is managing the height, be aware that resizes performed by the browser in response
16093 * to changes within the structure of the Component cannot be detected. Therefore changes to the height might
16094 * result in elements needing to be synchronized with the new height. Example:</p><pre><code>
16095 var w = new Ext.Window({
16100 title: 'Collapse Me',
16105 beforecollapse: function() {
16106 w.el.shadow.hide();
16108 beforeexpand: function() {
16109 w.el.shadow.hide();
16111 collapse: function() {
16114 expand: function() {
16123 * @cfg {Boolean} autoWidth
16124 * <p>True to use width:'auto', false to use fixed width (or allow it to be managed by its parent
16125 * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
16126 * <p><b>Note</b>: Although many components inherit this config option, not all will
16127 * function as expected with a width of 'auto'. Setting autoWidth:true means that the
16128 * browser will manage width based on the element's contents, and that Ext will not manage it at all.</p>
16129 * <p>If the <i>browser</i> is managing the width, be aware that resizes performed by the browser in response
16130 * to changes within the structure of the Component cannot be detected. Therefore changes to the width might
16131 * result in elements needing to be synchronized with the new width. For example, where the target element is:</p><pre><code>
16132 <div id='grid-container' style='margin-left:25%;width:50%'></div>
16134 * A Panel rendered into that target element must listen for browser window resize in order to relay its
16135 * child items when the browser changes its width:<pre><code>
16136 var myPanel = new Ext.Panel({
16137 renderTo: 'grid-container',
16138 monitorResize: true, // relay on browser resize
16160 /* // private internal config
16161 * {Boolean} deferHeight
16162 * True to defer height calculations to an external component, false to allow this component to set its own
16163 * height (defaults to false).
16167 initComponent : function(){
16168 Ext.BoxComponent.superclass.initComponent.call(this);
16172 * Fires after the component is resized.
16173 * @param {Ext.Component} this
16174 * @param {Number} adjWidth The box-adjusted width that was set
16175 * @param {Number} adjHeight The box-adjusted height that was set
16176 * @param {Number} rawWidth The width that was originally specified
16177 * @param {Number} rawHeight The height that was originally specified
16182 * Fires after the component is moved.
16183 * @param {Ext.Component} this
16184 * @param {Number} x The new x position
16185 * @param {Number} y The new y position
16191 // private, set in afterRender to signify that the component has been rendered
16193 // private, used to defer height settings to subclasses
16194 deferHeight: false,
16197 * Sets the width and height of this BoxComponent. This method fires the {@link #resize} event. This method can accept
16198 * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
16199 * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
16200 * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16201 * <li>A String used to set the CSS width style.</li>
16202 * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
16203 * <li><code>undefined</code> to leave the width unchanged.</li>
16205 * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
16206 * This may be one of:<div class="mdetail-params"><ul>
16207 * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16208 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
16209 * <li><code>undefined</code> to leave the height unchanged.</li>
16211 * @return {Ext.BoxComponent} this
16213 setSize : function(w, h){
16214 // support for standard size objects
16215 if(typeof w == 'object'){
16220 if(!this.boxReady){
16226 // prevent recalcs when not needed
16227 if(this.cacheSizes !== false && this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
16230 this.lastSize = {width: w, height: h};
16231 var adj = this.adjustSize(w, h);
16232 var aw = adj.width, ah = adj.height;
16233 if(aw !== undefined || ah !== undefined){ // this code is nasty but performs better with floaters
16234 var rz = this.getResizeEl();
16235 if(!this.deferHeight && aw !== undefined && ah !== undefined){
16236 rz.setSize(aw, ah);
16237 }else if(!this.deferHeight && ah !== undefined){
16239 }else if(aw !== undefined){
16242 this.onResize(aw, ah, w, h);
16243 this.fireEvent('resize', this, aw, ah, w, h);
16249 * Sets the width of the component. This method fires the {@link #resize} event.
16250 * @param {Number} width The new width to setThis may be one of:<div class="mdetail-params"><ul>
16251 * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16252 * <li>A String used to set the CSS width style.</li>
16254 * @return {Ext.BoxComponent} this
16256 setWidth : function(width){
16257 return this.setSize(width);
16261 * Sets the height of the component. This method fires the {@link #resize} event.
16262 * @param {Number} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
16263 * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16264 * <li>A String used to set the CSS height style.</li>
16265 * <li><i>undefined</i> to leave the height unchanged.</li>
16267 * @return {Ext.BoxComponent} this
16269 setHeight : function(height){
16270 return this.setSize(undefined, height);
16274 * Gets the current size of the component's underlying element.
16275 * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
16277 getSize : function(){
16278 return this.getResizeEl().getSize();
16282 * Gets the current width of the component's underlying element.
16285 getWidth : function(){
16286 return this.getResizeEl().getWidth();
16290 * Gets the current height of the component's underlying element.
16293 getHeight : function(){
16294 return this.getResizeEl().getHeight();
16298 * Gets the current size of the component's underlying element, including space taken by its margins.
16299 * @return {Object} An object containing the element's size {width: (element width + left/right margins), height: (element height + top/bottom margins)}
16301 getOuterSize : function(){
16302 var el = this.getResizeEl();
16303 return {width: el.getWidth() + el.getMargins('lr'),
16304 height: el.getHeight() + el.getMargins('tb')};
16308 * Gets the current XY position of the component's underlying element.
16309 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
16310 * @return {Array} The XY position of the element (e.g., [100, 200])
16312 getPosition : function(local){
16313 var el = this.getPositionEl();
16314 if(local === true){
16315 return [el.getLeft(true), el.getTop(true)];
16317 return this.xy || el.getXY();
16321 * Gets the current box measurements of the component's underlying element.
16322 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
16323 * @return {Object} box An object in the format {x, y, width, height}
16325 getBox : function(local){
16326 var pos = this.getPosition(local);
16327 var s = this.getSize();
16334 * Sets the current box measurements of the component's underlying element.
16335 * @param {Object} box An object in the format {x, y, width, height}
16336 * @return {Ext.BoxComponent} this
16338 updateBox : function(box){
16339 this.setSize(box.width, box.height);
16340 this.setPagePosition(box.x, box.y);
16345 * <p>Returns the outermost Element of this Component which defines the Components overall size.</p>
16346 * <p><i>Usually</i> this will return the same Element as <code>{@link #getEl}</code>,
16347 * but in some cases, a Component may have some more wrapping Elements around its main
16348 * active Element.</p>
16349 * <p>An example is a ComboBox. It is encased in a <i>wrapping</i> Element which
16350 * contains both the <code><input></code> Element (which is what would be returned
16351 * by its <code>{@link #getEl}</code> method, <i>and</i> the trigger button Element.
16352 * This Element is returned as the <code>resizeEl</code>.
16354 getResizeEl : function(){
16355 return this.resizeEl || this.el;
16359 getPositionEl : function(){
16360 return this.positionEl || this.el;
16364 * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}.
16365 * This method fires the {@link #move} event.
16366 * @param {Number} left The new left
16367 * @param {Number} top The new top
16368 * @return {Ext.BoxComponent} this
16370 setPosition : function(x, y){
16371 if(x && typeof x[1] == 'number'){
16377 if(!this.boxReady){
16380 var adj = this.adjustPosition(x, y);
16381 var ax = adj.x, ay = adj.y;
16383 var el = this.getPositionEl();
16384 if(ax !== undefined || ay !== undefined){
16385 if(ax !== undefined && ay !== undefined){
16386 el.setLeftTop(ax, ay);
16387 }else if(ax !== undefined){
16389 }else if(ay !== undefined){
16392 this.onPosition(ax, ay);
16393 this.fireEvent('move', this, ax, ay);
16399 * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
16400 * This method fires the {@link #move} event.
16401 * @param {Number} x The new x position
16402 * @param {Number} y The new y position
16403 * @return {Ext.BoxComponent} this
16405 setPagePosition : function(x, y){
16406 if(x && typeof x[1] == 'number'){
16412 if(!this.boxReady){
16415 if(x === undefined || y === undefined){ // cannot translate undefined points
16418 var p = this.getPositionEl().translatePoints(x, y);
16419 this.setPosition(p.left, p.top);
16424 onRender : function(ct, position){
16425 Ext.BoxComponent.superclass.onRender.call(this, ct, position);
16427 this.resizeEl = Ext.get(this.resizeEl);
16429 if(this.positionEl){
16430 this.positionEl = Ext.get(this.positionEl);
16435 afterRender : function(){
16436 Ext.BoxComponent.superclass.afterRender.call(this);
16437 this.boxReady = true;
16438 this.setSize(this.width, this.height);
16439 if(this.x || this.y){
16440 this.setPosition(this.x, this.y);
16441 }else if(this.pageX || this.pageY){
16442 this.setPagePosition(this.pageX, this.pageY);
16447 * Force the component's size to recalculate based on the underlying element's current height and width.
16448 * @return {Ext.BoxComponent} this
16450 syncSize : function(){
16451 delete this.lastSize;
16452 this.setSize(this.autoWidth ? undefined : this.getResizeEl().getWidth(), this.autoHeight ? undefined : this.getResizeEl().getHeight());
16457 * Called after the component is resized, this method is empty by default but can be implemented by any
16458 * subclass that needs to perform custom logic after a resize occurs.
16459 * @param {Number} adjWidth The box-adjusted width that was set
16460 * @param {Number} adjHeight The box-adjusted height that was set
16461 * @param {Number} rawWidth The width that was originally specified
16462 * @param {Number} rawHeight The height that was originally specified
16464 onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
16469 * Called after the component is moved, this method is empty by default but can be implemented by any
16470 * subclass that needs to perform custom logic after a move occurs.
16471 * @param {Number} x The new x position
16472 * @param {Number} y The new y position
16474 onPosition : function(x, y){
16479 adjustSize : function(w, h){
16480 if(this.autoWidth){
16483 if(this.autoHeight){
16486 return {width : w, height: h};
16490 adjustPosition : function(x, y){
16491 return {x : x, y: y};
16494 Ext.reg('box', Ext.BoxComponent);
16498 * @class Ext.Spacer
16499 * @extends Ext.BoxComponent
16500 * <p>Used to provide a sizable space in a layout.</p>
16502 * @param {Object} config
16504 Ext.Spacer = Ext.extend(Ext.BoxComponent, {
16507 Ext.reg('spacer', Ext.Spacer);/**
\r
16508 * @class Ext.SplitBar
\r
16509 * @extends Ext.util.Observable
\r
16510 * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized).
\r
16514 var split = new Ext.SplitBar("elementToDrag", "elementToSize",
\r
16515 Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT);
\r
16516 split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container"));
\r
16517 split.minSize = 100;
\r
16518 split.maxSize = 600;
\r
16519 split.animate = true;
\r
16520 split.on('moved', splitterMoved);
\r
16523 * Create a new SplitBar
\r
16524 * @param {Mixed} dragElement The element to be dragged and act as the SplitBar.
\r
16525 * @param {Mixed} resizingElement The element to be resized based on where the SplitBar element is dragged
\r
16526 * @param {Number} orientation (optional) Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
\r
16527 * @param {Number} placement (optional) Either Ext.SplitBar.LEFT or Ext.SplitBar.RIGHT for horizontal or
\r
16528 Ext.SplitBar.TOP or Ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial
\r
16529 position of the SplitBar).
\r
16531 Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
\r
16534 this.el = Ext.get(dragElement, true);
\r
16535 this.el.dom.unselectable = "on";
\r
16537 this.resizingEl = Ext.get(resizingElement, true);
\r
16541 * The orientation of the split. Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
\r
16542 * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
\r
16545 this.orientation = orientation || Ext.SplitBar.HORIZONTAL;
\r
16548 * The increment, in pixels by which to move this SplitBar. When <i>undefined</i>, the SplitBar moves smoothly.
\r
16550 * @property tickSize
\r
16553 * The minimum size of the resizing element. (Defaults to 0)
\r
16556 this.minSize = 0;
\r
16559 * The maximum size of the resizing element. (Defaults to 2000)
\r
16562 this.maxSize = 2000;
\r
16565 * Whether to animate the transition to the new size
\r
16568 this.animate = false;
\r
16571 * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
\r
16574 this.useShim = false;
\r
16577 this.shim = null;
\r
16579 if(!existingProxy){
\r
16581 this.proxy = Ext.SplitBar.createProxy(this.orientation);
\r
16583 this.proxy = Ext.get(existingProxy).dom;
\r
16586 this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
\r
16589 this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
\r
16592 this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
\r
16595 this.dragSpecs = {};
\r
16598 * @private The adapter to use to positon and resize elements
\r
16600 this.adapter = new Ext.SplitBar.BasicLayoutAdapter();
\r
16601 this.adapter.init(this);
\r
16603 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
16605 this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);
\r
16606 this.el.addClass("x-splitbar-h");
\r
16609 this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);
\r
16610 this.el.addClass("x-splitbar-v");
\r
16616 * Fires when the splitter is moved (alias for {@link #moved})
\r
16617 * @param {Ext.SplitBar} this
\r
16618 * @param {Number} newSize the new width or height
\r
16623 * Fires when the splitter is moved
\r
16624 * @param {Ext.SplitBar} this
\r
16625 * @param {Number} newSize the new width or height
\r
16629 * @event beforeresize
\r
16630 * Fires before the splitter is dragged
\r
16631 * @param {Ext.SplitBar} this
\r
16638 Ext.SplitBar.superclass.constructor.call(this);
\r
16641 Ext.extend(Ext.SplitBar, Ext.util.Observable, {
\r
16642 onStartProxyDrag : function(x, y){
\r
16643 this.fireEvent("beforeresize", this);
\r
16644 this.overlay = Ext.DomHelper.append(document.body, {cls: "x-drag-overlay", html: " "}, true);
\r
16645 this.overlay.unselectable();
\r
16646 this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
\r
16647 this.overlay.show();
\r
16648 Ext.get(this.proxy).setDisplayed("block");
\r
16649 var size = this.adapter.getElementSize(this);
\r
16650 this.activeMinSize = this.getMinimumSize();
\r
16651 this.activeMaxSize = this.getMaximumSize();
\r
16652 var c1 = size - this.activeMinSize;
\r
16653 var c2 = Math.max(this.activeMaxSize - size, 0);
\r
16654 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
16655 this.dd.resetConstraints();
\r
16656 this.dd.setXConstraint(
\r
16657 this.placement == Ext.SplitBar.LEFT ? c1 : c2,
\r
16658 this.placement == Ext.SplitBar.LEFT ? c2 : c1,
\r
16661 this.dd.setYConstraint(0, 0);
\r
16663 this.dd.resetConstraints();
\r
16664 this.dd.setXConstraint(0, 0);
\r
16665 this.dd.setYConstraint(
\r
16666 this.placement == Ext.SplitBar.TOP ? c1 : c2,
\r
16667 this.placement == Ext.SplitBar.TOP ? c2 : c1,
\r
16671 this.dragSpecs.startSize = size;
\r
16672 this.dragSpecs.startPoint = [x, y];
\r
16673 Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
\r
16677 * @private Called after the drag operation by the DDProxy
\r
16679 onEndProxyDrag : function(e){
\r
16680 Ext.get(this.proxy).setDisplayed(false);
\r
16681 var endPoint = Ext.lib.Event.getXY(e);
\r
16682 if(this.overlay){
\r
16683 Ext.destroy(this.overlay);
\r
16684 delete this.overlay;
\r
16687 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
16688 newSize = this.dragSpecs.startSize +
\r
16689 (this.placement == Ext.SplitBar.LEFT ?
\r
16690 endPoint[0] - this.dragSpecs.startPoint[0] :
\r
16691 this.dragSpecs.startPoint[0] - endPoint[0]
\r
16694 newSize = this.dragSpecs.startSize +
\r
16695 (this.placement == Ext.SplitBar.TOP ?
\r
16696 endPoint[1] - this.dragSpecs.startPoint[1] :
\r
16697 this.dragSpecs.startPoint[1] - endPoint[1]
\r
16700 newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
\r
16701 if(newSize != this.dragSpecs.startSize){
\r
16702 if(this.fireEvent('beforeapply', this, newSize) !== false){
\r
16703 this.adapter.setElementSize(this, newSize);
\r
16704 this.fireEvent("moved", this, newSize);
\r
16705 this.fireEvent("resize", this, newSize);
\r
16711 * Get the adapter this SplitBar uses
\r
16712 * @return The adapter object
\r
16714 getAdapter : function(){
\r
16715 return this.adapter;
\r
16719 * Set the adapter this SplitBar uses
\r
16720 * @param {Object} adapter A SplitBar adapter object
\r
16722 setAdapter : function(adapter){
\r
16723 this.adapter = adapter;
\r
16724 this.adapter.init(this);
\r
16728 * Gets the minimum size for the resizing element
\r
16729 * @return {Number} The minimum size
\r
16731 getMinimumSize : function(){
\r
16732 return this.minSize;
\r
16736 * Sets the minimum size for the resizing element
\r
16737 * @param {Number} minSize The minimum size
\r
16739 setMinimumSize : function(minSize){
\r
16740 this.minSize = minSize;
\r
16744 * Gets the maximum size for the resizing element
\r
16745 * @return {Number} The maximum size
\r
16747 getMaximumSize : function(){
\r
16748 return this.maxSize;
\r
16752 * Sets the maximum size for the resizing element
\r
16753 * @param {Number} maxSize The maximum size
\r
16755 setMaximumSize : function(maxSize){
\r
16756 this.maxSize = maxSize;
\r
16760 * Sets the initialize size for the resizing element
\r
16761 * @param {Number} size The initial size
\r
16763 setCurrentSize : function(size){
\r
16764 var oldAnimate = this.animate;
\r
16765 this.animate = false;
\r
16766 this.adapter.setElementSize(this, size);
\r
16767 this.animate = oldAnimate;
\r
16771 * Destroy this splitbar.
\r
16772 * @param {Boolean} removeEl True to remove the element
\r
16774 destroy : function(removeEl){
\r
16775 Ext.destroy(this.shim, Ext.get(this.proxy));
\r
16778 this.el.remove();
\r
16780 this.purgeListeners();
\r
16785 * @private static Create our own proxy element element. So it will be the same same size on all browsers, we won't use borders. Instead we use a background color.
\r
16787 Ext.SplitBar.createProxy = function(dir){
\r
16788 var proxy = new Ext.Element(document.createElement("div"));
\r
16789 proxy.unselectable();
\r
16790 var cls = 'x-splitbar-proxy';
\r
16791 proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
\r
16792 document.body.appendChild(proxy.dom);
\r
16793 return proxy.dom;
\r
16797 * @class Ext.SplitBar.BasicLayoutAdapter
\r
16798 * Default Adapter. It assumes the splitter and resizing element are not positioned
\r
16799 * elements and only gets/sets the width of the element. Generally used for table based layouts.
\r
16801 Ext.SplitBar.BasicLayoutAdapter = function(){
\r
16804 Ext.SplitBar.BasicLayoutAdapter.prototype = {
\r
16805 // do nothing for now
\r
16806 init : function(s){
\r
16810 * Called before drag operations to get the current size of the resizing element.
\r
16811 * @param {Ext.SplitBar} s The SplitBar using this adapter
\r
16813 getElementSize : function(s){
\r
16814 if(s.orientation == Ext.SplitBar.HORIZONTAL){
\r
16815 return s.resizingEl.getWidth();
\r
16817 return s.resizingEl.getHeight();
\r
16822 * Called after drag operations to set the size of the resizing element.
\r
16823 * @param {Ext.SplitBar} s The SplitBar using this adapter
\r
16824 * @param {Number} newSize The new size to set
\r
16825 * @param {Function} onComplete A function to be invoked when resizing is complete
\r
16827 setElementSize : function(s, newSize, onComplete){
\r
16828 if(s.orientation == Ext.SplitBar.HORIZONTAL){
\r
16830 s.resizingEl.setWidth(newSize);
\r
16832 onComplete(s, newSize);
\r
16835 s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');
\r
16840 s.resizingEl.setHeight(newSize);
\r
16842 onComplete(s, newSize);
\r
16845 s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');
\r
16852 *@class Ext.SplitBar.AbsoluteLayoutAdapter
\r
16853 * @extends Ext.SplitBar.BasicLayoutAdapter
\r
16854 * Adapter that moves the splitter element to align with the resized sizing element.
\r
16855 * Used with an absolute positioned SplitBar.
\r
16856 * @param {Mixed} container The container that wraps around the absolute positioned content. If it's
\r
16857 * document.body, make sure you assign an id to the body element.
\r
16859 Ext.SplitBar.AbsoluteLayoutAdapter = function(container){
\r
16860 this.basic = new Ext.SplitBar.BasicLayoutAdapter();
\r
16861 this.container = Ext.get(container);
\r
16864 Ext.SplitBar.AbsoluteLayoutAdapter.prototype = {
\r
16865 init : function(s){
\r
16866 this.basic.init(s);
\r
16869 getElementSize : function(s){
\r
16870 return this.basic.getElementSize(s);
\r
16873 setElementSize : function(s, newSize, onComplete){
\r
16874 this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
\r
16877 moveSplitter : function(s){
\r
16878 var yes = Ext.SplitBar;
\r
16879 switch(s.placement){
\r
16881 s.el.setX(s.resizingEl.getRight());
\r
16884 s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");
\r
16887 s.el.setY(s.resizingEl.getBottom());
\r
16890 s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
\r
16897 * Orientation constant - Create a vertical SplitBar
\r
16901 Ext.SplitBar.VERTICAL = 1;
\r
16904 * Orientation constant - Create a horizontal SplitBar
\r
16908 Ext.SplitBar.HORIZONTAL = 2;
\r
16911 * Placement constant - The resizing element is to the left of the splitter element
\r
16915 Ext.SplitBar.LEFT = 1;
\r
16918 * Placement constant - The resizing element is to the right of the splitter element
\r
16922 Ext.SplitBar.RIGHT = 2;
\r
16925 * Placement constant - The resizing element is positioned above the splitter element
\r
16929 Ext.SplitBar.TOP = 3;
\r
16932 * Placement constant - The resizing element is positioned under splitter element
\r
16936 Ext.SplitBar.BOTTOM = 4;
\r
16938 * @class Ext.Container
16939 * @extends Ext.BoxComponent
16940 * <p>Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the
16941 * basic behavior of containing items, namely adding, inserting and removing items.</p>
16943 * <p>The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}.
16944 * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
16945 * Container to be encapsulated by an HTML element to your specifications by using the
16946 * <tt><b>{@link Ext.Component#autoEl autoEl}</b></tt> config option. This is a useful technique when creating
16947 * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels}
16950 * <p>The code below illustrates both how to explicitly create a Container, and how to implicitly
16951 * create one using the <b><tt>'container'</tt></b> xtype:<pre><code>
16952 // explicitly create a Container
16953 var embeddedColumns = new Ext.Container({
16954 autoEl: 'div', // This is the default
16957 // implicitly create Container by specifying xtype
16958 xtype: 'container',
16959 autoEl: 'div', // This is the default.
16966 // The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
16969 xtype: 'datefield',
16971 fieldLabel: 'Start date'
16975 xtype: 'datefield',
16977 fieldLabel: 'End date'
16980 });</code></pre></p>
16982 * <p><u><b>Layout</b></u></p>
16983 * <p>Container classes delegate the rendering of child Components to a layout
16984 * manager class which must be configured into the Container using the
16985 * <code><b>{@link #layout}</b></code> configuration property.</p>
16986 * <p>When either specifying child <code>{@link #items}</code> of a Container,
16987 * or dynamically {@link #add adding} Components to a Container, remember to
16988 * consider how you wish the Container to arrange those child elements, and
16989 * whether those child elements need to be sized using one of Ext's built-in
16990 * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
16991 * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only
16992 * renders child components, appending them one after the other inside the
16993 * Container, and <b>does not apply any sizing</b> at all.</p>
16994 * <p>A common mistake is when a developer neglects to specify a
16995 * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
16996 * TreePanels are added to Containers for which no <tt><b>{@link #layout}</b></tt>
16997 * has been specified). If a Container is left to use the default
16998 * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its
16999 * child components will be resized, or changed in any way when the Container
17001 * <p>Certain layout managers allow dynamic addition of child components.
17002 * Those that do include {@link Ext.layout.CardLayout},
17003 * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and
17004 * {@link Ext.layout.TableLayout}. For example:<pre><code>
17005 // Create the GridPanel.
17006 var myNewGrid = new Ext.grid.GridPanel({
17008 columns: myColumnModel,
17009 title: 'Results', // the title becomes the title of the tab
17012 myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
17013 myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
17014 * </code></pre></p>
17015 * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
17016 * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which
17017 * means all its child items are sized to {@link Ext.layout.FitLayout fit}
17018 * exactly into its client area.
17019 * <p><b><u>Overnesting is a common problem</u></b>.
17020 * An example of overnesting occurs when a GridPanel is added to a TabPanel
17021 * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
17022 * <tt><b>{@link #layout}</b></tt> specified) and then add that wrapping Panel
17023 * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
17024 * Component which can be added directly to a Container. If the wrapping Panel
17025 * has no <tt><b>{@link #layout}</b></tt> configuration, then the overnested
17026 * GridPanel will not be sized as expected.<p>
17029 * <p><u><b>Adding via remote configuration</b></u></p>
17031 * <p>A server side script can be used to add Components which are generated dynamically on the server.
17032 * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
17033 * based on certain parameters:
17035 // execute an Ajax request to invoke server side script:
17037 url: 'gen-invoice-grid.php',
17038 // send additional parameters to instruct server script
17040 startDate: Ext.getCmp('start-date').getValue(),
17041 endDate: Ext.getCmp('end-date').getValue()
17043 // process the response object to add it to the TabPanel:
17044 success: function(xhr) {
17045 var newComponent = eval(xhr.responseText); // see discussion below
17046 myTabPanel.add(newComponent); // add the component to the TabPanel
17047 myTabPanel.setActiveTab(newComponent);
17049 failure: function() {
17050 Ext.Msg.alert("Grid create failed", "Server communication failure");
17054 * <p>The server script needs to return an executable Javascript statement which, when processed
17055 * using <tt>eval()</tt>, will return either a config object with an {@link Ext.Component#xtype xtype},
17056 * or an instantiated Component. The server might return this for example:</p><pre><code>
17058 function formatDate(value){
17059 return value ? value.dateFormat('M d, Y') : '';
17062 var store = new Ext.data.Store({
17063 url: 'get-invoice-data.php',
17065 startDate: '01/01/2008',
17066 endDate: '01/31/2008'
17068 reader: new Ext.data.JsonReader({
17069 record: 'transaction',
17071 totalRecords: 'total'
17075 {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
17076 {name: 'value', type: 'float'}
17080 var grid = new Ext.grid.GridPanel({
17081 title: 'Invoice Report',
17082 bbar: new Ext.PagingToolbar(store),
17085 {header: "Customer", width: 250, dataIndex: 'customer', sortable: true},
17086 {header: "Invoice Number", width: 120, dataIndex: 'invNo', sortable: true},
17087 {header: "Invoice Date", width: 100, dataIndex: 'date', renderer: formatDate, sortable: true},
17088 {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
17092 return grid; // return instantiated component
17095 * <p>When the above code fragment is passed through the <tt>eval</tt> function in the success handler
17096 * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function
17097 * runs, and returns the instantiated grid component.</p>
17098 * <p>Note: since the code above is <i>generated</i> by a server script, the <tt>baseParams</tt> for
17099 * the Store, the metadata to allow generation of the Record layout, and the ColumnModel
17100 * can all be generated into the code since these are all known on the server.</p>
17104 Ext.Container = Ext.extend(Ext.BoxComponent, {
17106 * @cfg {Boolean} monitorResize
17107 * True to automatically monitor window resize events to handle anything that is sensitive to the current size
17108 * of the viewport. This value is typically managed by the chosen <code>{@link #layout}</code> and should not need
17109 * to be set manually.
17112 * @cfg {String/Object} layout
17113 * When creating complex UIs, it is important to remember that sizing and
17114 * positioning of child items is the responsibility of the Container's
17115 * layout manager. If you expect child items to be sized in response to
17116 * user interactions, <b>you must specify a layout manager</b> which
17117 * creates and manages the type of layout you have in mind. For example:<pre><code>
17119 width:300, height: 300,
17120 layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
17122 title: 'Panel inside a Window'
17126 * <p>Omitting the {@link #layout} config means that the
17127 * {@link Ext.layout.ContainerLayout default layout manager} will be used which does
17128 * nothing but render child components sequentially into the Container (no sizing or
17129 * positioning will be performed in this situation).</p>
17130 * <p>The layout manager class for this container may be specified as either as an
17131 * Object or as a String:</p>
17132 * <div><ul class="mdetail-params">
17134 * <li><u>Specify as an Object</u></li>
17135 * <div><ul class="mdetail-params">
17136 * <li>Example usage:</li>
17145 * <li><tt><b>type</b></tt></li>
17146 * <br/><p>The layout type to be used for this container. If not specified,
17147 * a default {@link Ext.layout.ContainerLayout} will be created and used.</p>
17148 * <br/><p>Valid layout <tt>type</tt> values are:</p>
17149 * <div class="sub-desc"><ul class="mdetail-params">
17150 * <li><tt><b>{@link Ext.layout.AbsoluteLayout absolute}</b></tt></li>
17151 * <li><tt><b>{@link Ext.layout.AccordionLayout accordion}</b></tt></li>
17152 * <li><tt><b>{@link Ext.layout.AnchorLayout anchor}</b></tt></li>
17153 * <li><tt><b>{@link Ext.layout.ContainerLayout auto}</b></tt> <b>Default</b></li>
17154 * <li><tt><b>{@link Ext.layout.BorderLayout border}</b></tt></li>
17155 * <li><tt><b>{@link Ext.layout.CardLayout card}</b></tt></li>
17156 * <li><tt><b>{@link Ext.layout.ColumnLayout column}</b></tt></li>
17157 * <li><tt><b>{@link Ext.layout.FitLayout fit}</b></tt></li>
17158 * <li><tt><b>{@link Ext.layout.FormLayout form}</b></tt></li>
17159 * <li><tt><b>{@link Ext.layout.HBoxLayout hbox}</b></tt></li>
17160 * <li><tt><b>{@link Ext.layout.MenuLayout menu}</b></tt></li>
17161 * <li><tt><b>{@link Ext.layout.TableLayout table}</b></tt></li>
17162 * <li><tt><b>{@link Ext.layout.ToolbarLayout toolbar}</b></tt></li>
17163 * <li><tt><b>{@link Ext.layout.VBoxLayout vbox}</b></tt></li>
17166 * <li>Layout specific configuration properties</li>
17167 * <br/><p>Additional layout specific configuration properties may also be
17168 * specified. For complete details regarding the valid config options for
17169 * each layout type, see the layout class corresponding to the <tt>type</tt>
17174 * <li><u>Specify as a String</u></li>
17175 * <div><ul class="mdetail-params">
17176 * <li>Example usage:</li>
17184 * <li><tt><b>layout</b></tt></li>
17185 * <br/><p>The layout <tt>type</tt> to be used for this container (see list
17186 * of valid layout type values above).</p><br/>
17187 * <li><tt><b>{@link #layoutConfig}</b></tt></li>
17188 * <br/><p>Additional layout specific configuration properties. For complete
17189 * details regarding the valid config options for each layout type, see the
17190 * layout class corresponding to the <tt>layout</tt> specified.</p>
17191 * </ul></div></ul></div>
17194 * @cfg {Object} layoutConfig
17195 * This is a config object containing properties specific to the chosen
17196 * <b><code>{@link #layout}</code></b> if <b><code>{@link #layout}</code></b>
17197 * has been specified as a <i>string</i>.</p>
17200 * @cfg {Boolean/Number} bufferResize
17201 * When set to true (100 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer
17202 * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers
17203 * with a large quantity of sub-components for which frequent layout calls would be expensive.
17208 * @cfg {String/Number} activeItem
17209 * A string component id or the numeric index of the component that should be initially activated within the
17210 * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
17211 * item in the container's collection). activeItem only applies to layout styles that can display
17212 * items one at a time (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout} and
17213 * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}.
17216 * @cfg {Object/Array} items
17217 * <pre><b>** IMPORTANT</b>: be sure to specify a <b><code>{@link #layout}</code> ! **</b></pre>
17218 * <p>A single item, or an array of child Components to be added to this container,
17221 // specifying a single item
17223 layout: 'fit', // specify a layout!
17225 // specifying multiple items
17226 items: [{...}, {...}],
17227 layout: 'anchor', // specify a layout!
17229 * <p>Each item may be:</p>
17230 * <div><ul class="mdetail-params">
17231 * <li>any type of object based on {@link Ext.Component}</li>
17232 * <li>a fully instanciated object or</li>
17233 * <li>an object literal that:</li>
17234 * <div><ul class="mdetail-params">
17235 * <li>has a specified <code>{@link Ext.Component#xtype xtype}</code></li>
17236 * <li>the {@link Ext.Component#xtype} specified is associated with the Component
17237 * desired and should be chosen from one of the available xtypes as listed
17238 * in {@link Ext.Component}.</li>
17239 * <li>If an <code>{@link Ext.Component#xtype xtype}</code> is not explicitly
17240 * specified, the {@link #defaultType} for that Container is used.</li>
17241 * <li>will be "lazily instanciated", avoiding the overhead of constructing a fully
17242 * instanciated Component object</li>
17243 * </ul></div></ul></div>
17244 * <p><b>Notes</b>:</p>
17245 * <div><ul class="mdetail-params">
17246 * <li>Ext uses lazy rendering. Child Components will only be rendered
17247 * should it become necessary. Items are automatically laid out when they are first
17248 * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</li>
17249 * <li>Do not specify <code>{@link Ext.Panel#contentEl contentEl}</code>/
17250 * <code>{@link Ext.Panel#html html}</code> with <code>items</code>.</li>
17254 * @cfg {Object} defaults
17255 * <p>A config object that will be applied to all components added to this container either via the {@link #items}
17256 * config or via the {@link #add} or {@link #insert} methods. The <tt>defaults</tt> config can contain any
17257 * number of name/value property pairs to be added to each item, and should be valid for the types of items
17258 * being added to the container. For example, to automatically apply padding to the body of each of a set of
17259 * contained {@link Ext.Panel} items, you could pass: <tt>defaults: {bodyStyle:'padding:15px'}</tt>.</p><br/>
17260 * <p><b>Note</b>: <tt>defaults</tt> will not be applied to config objects if the option is already specified.
17261 * For example:</p><pre><code>
17262 defaults: { // defaults are applied to items, not the container
17267 xtype: 'panel', // defaults <b>do not</b> have precedence over
17268 id: 'panel1', // options in config objects, so the defaults
17269 autoScroll: false // will not be applied here, panel1 will be autoScroll:false
17271 new Ext.Panel({ // defaults <b>do</b> have precedence over options
17272 id: 'panel2', // options in components, so the defaults
17273 autoScroll: false // will be applied here, panel2 will be autoScroll:true.
17280 /** @cfg {Boolean} autoDestroy
17281 * If true the container will automatically destroy any contained component that is removed from it, else
17282 * destruction must be handled manually (defaults to true).
17284 autoDestroy : true,
17286 /** @cfg {Boolean} forceLayout
17287 * If true the container will force a layout initially even if hidden or collapsed. This option
17288 * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false).
17290 forceLayout: false,
17292 /** @cfg {Boolean} hideBorders
17293 * True to hide the borders of each contained component, false to defer to the component's existing
17294 * border settings (defaults to false).
17296 /** @cfg {String} defaultType
17297 * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
17298 * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
17299 * <p>Defaults to <tt>'panel'</tt>, except {@link Ext.menu.Menu} which defaults to <tt>'menuitem'</tt>,
17300 * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to <tt>'button'</tt>.</p>
17302 defaultType : 'panel',
17305 initComponent : function(){
17306 Ext.Container.superclass.initComponent.call(this);
17310 * @event afterlayout
17311 * Fires when the components in this container are arranged by the associated layout manager.
17312 * @param {Ext.Container} this
17313 * @param {ContainerLayout} layout The ContainerLayout implementation for this container
17318 * Fires before any {@link Ext.Component} is added or inserted into the container.
17319 * A handler can return false to cancel the add.
17320 * @param {Ext.Container} this
17321 * @param {Ext.Component} component The component being added
17322 * @param {Number} index The index at which the component will be added to the container's items collection
17326 * @event beforeremove
17327 * Fires before any {@link Ext.Component} is removed from the container. A handler can return
17328 * false to cancel the remove.
17329 * @param {Ext.Container} this
17330 * @param {Ext.Component} component The component being removed
17336 * Fires after any {@link Ext.Component} is added or inserted into the container.
17337 * @param {Ext.Container} this
17338 * @param {Ext.Component} component The component that was added
17339 * @param {Number} index The index at which the component was added to the container's items collection
17345 * Fires after any {@link Ext.Component} is removed from the container.
17346 * @param {Ext.Container} this
17347 * @param {Ext.Component} component The component that was removed
17352 this.enableBubble('add', 'remove');
17355 * The collection of components in this container as a {@link Ext.util.MixedCollection}
17356 * @type MixedCollection
17359 var items = this.items;
17362 if(Ext.isArray(items) && items.length > 0){
17363 this.add.apply(this, items);
17371 initItems : function(){
17373 this.items = new Ext.util.MixedCollection(false, this.getComponentId);
17374 this.getLayout(); // initialize the layout
17379 setLayout : function(layout){
17380 if(this.layout && this.layout != layout){
17381 this.layout.setContainer(null);
17384 this.layout = layout;
17385 layout.setContainer(this);
17389 render : function(){
17390 Ext.Container.superclass.render.apply(this, arguments);
17392 if(Ext.isObject(this.layout) && !this.layout.layout){
17393 this.layoutConfig = this.layout;
17394 this.layout = this.layoutConfig.type;
17396 if(typeof this.layout == 'string'){
17397 this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);
17399 this.setLayout(this.layout);
17401 if(this.activeItem !== undefined){
17402 var item = this.activeItem;
17403 delete this.activeItem;
17404 this.layout.setActiveItem(item);
17408 // force a layout if no ownerCt is set
17409 this.doLayout(false, true);
17411 if(this.monitorResize === true){
17412 Ext.EventManager.onWindowResize(this.doLayout, this, [false]);
17417 * <p>Returns the Element to be used to contain the child Components of this Container.</p>
17418 * <p>An implementation is provided which returns the Container's {@link #getEl Element}, but
17419 * if there is a more complex structure to a Container, this may be overridden to return
17420 * the element into which the {@link #layout layout} renders child Components.</p>
17421 * @return {Ext.Element} The Element to render child Components into.
17423 getLayoutTarget : function(){
17427 // private - used as the key lookup function for the items collection
17428 getComponentId : function(comp){
17429 return comp.getItemId();
17433 * <p>Adds {@link Ext.Component Component}(s) to this Container.</p>
17434 * <br><p><b>Description</b></u> :
17435 * <div><ul class="mdetail-params">
17436 * <li>Fires the {@link #beforeadd} event before adding</li>
17437 * <li>The Container's {@link #defaults default config values} will be applied
17438 * accordingly (see <code>{@link #defaults}</code> for details).</li>
17439 * <li>Fires the {@link #add} event after the component has been added.</li>
17441 * <br><p><b>Notes</b></u> :
17442 * <div><ul class="mdetail-params">
17443 * <li>If the Container is <i>already rendered</i> when <tt>add</tt>
17444 * is called, you may need to call {@link #doLayout} to refresh the view which causes
17445 * any unrendered child Components to be rendered. This is required so that you can
17446 * <tt>add</tt> multiple child components if needed while only refreshing the layout
17447 * once. For example:<pre><code>
17448 var tb = new {@link Ext.Toolbar}();
17449 tb.render(document.body); // toolbar is rendered
17450 tb.add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.Toolbar Toolbar} is 'button')
17451 tb.add({text:'Button 2'});
17452 tb.{@link #doLayout}(); // refresh the layout
17453 * </code></pre></li>
17454 * <li><i>Warning:</i> Containers directly managed by the BorderLayout layout manager
17455 * may not be removed or added. See the Notes for {@link Ext.layout.BorderLayout BorderLayout}
17456 * for more details.</li>
17458 * @param {Object/Array} component
17459 * <p>Either a single component or an Array of components to add. See
17460 * <code>{@link #items}</code> for additional information.</p>
17461 * @param {Object} (Optional) component_2
17462 * @param {Object} (Optional) component_n
17463 * @return {Ext.Component} component The Component (or config object) that was added.
17465 add : function(comp){
17467 var args = arguments.length > 1;
17468 if(args || Ext.isArray(comp)){
17469 Ext.each(args ? arguments : comp, function(c){
17474 var c = this.lookupComponent(this.applyDefaults(comp));
17475 var pos = this.items.length;
17476 if(this.fireEvent('beforeadd', this, c, pos) !== false && this.onBeforeAdd(c) !== false){
17479 this.fireEvent('add', this, c, pos);
17485 * Inserts a Component into this Container at a specified index. Fires the
17486 * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
17487 * Component has been inserted.
17488 * @param {Number} index The index at which the Component will be inserted
17489 * into the Container's items collection
17490 * @param {Ext.Component} component The child Component to insert.<br><br>
17491 * Ext uses lazy rendering, and will only render the inserted Component should
17492 * it become necessary.<br><br>
17493 * A Component config object may be passed in order to avoid the overhead of
17494 * constructing a real Component object if lazy rendering might mean that the
17495 * inserted Component will not be rendered immediately. To take advantage of
17496 * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
17497 * property to the registered type of the Component wanted.<br><br>
17498 * For a list of all available xtypes, see {@link Ext.Component}.
17499 * @return {Ext.Component} component The Component (or config object) that was
17500 * inserted with the Container's default config values applied.
17502 insert : function(index, comp){
17504 var a = arguments, len = a.length;
17506 for(var i = len-1; i >= 1; --i) {
17507 this.insert(index, a[i]);
17511 var c = this.lookupComponent(this.applyDefaults(comp));
17513 if(c.ownerCt == this && this.items.indexOf(c) < index){
17517 if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
17518 this.items.insert(index, c);
17520 this.fireEvent('add', this, c, index);
17526 applyDefaults : function(c){
17528 if(typeof c == 'string'){
17529 c = Ext.ComponentMgr.get(c);
17530 Ext.apply(c, this.defaults);
17531 }else if(!c.events){
17532 Ext.applyIf(c, this.defaults);
17534 Ext.apply(c, this.defaults);
17541 onBeforeAdd : function(item){
17543 item.ownerCt.remove(item, false);
17545 if(this.hideBorders === true){
17546 item.border = (item.border === true);
17551 * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
17552 * the {@link #remove} event after the component has been removed.
17553 * @param {Component/String} component The component reference or id to remove.
17554 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
17555 * Defaults to the value of this Container's {@link #autoDestroy} config.
17556 * @return {Ext.Component} component The Component that was removed.
17558 remove : function(comp, autoDestroy){
17560 var c = this.getComponent(comp);
17561 if(c && this.fireEvent('beforeremove', this, c) !== false){
17562 this.items.remove(c);
17564 if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
17567 if(this.layout && this.layout.activeItem == c){
17568 delete this.layout.activeItem;
17570 this.fireEvent('remove', this, c);
17576 * Removes all components from this container.
17577 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
17578 * Defaults to the value of this Container's {@link #autoDestroy} config.
17579 * @return {Array} Array of the destroyed components
17581 removeAll: function(autoDestroy){
17583 var item, rem = [], items = [];
17584 this.items.each(function(i){
17587 for (var i = 0, len = rem.length; i < len; ++i){
17589 this.remove(item, autoDestroy);
17590 if(item.ownerCt !== this){
17598 * Examines this container's <code>{@link #items}</code> <b>property</b>
17599 * and gets a direct child component of this container.
17600 * @param {String/Number} comp This parameter may be any of the following:
17601 * <div><ul class="mdetail-params">
17602 * <li>a <b><tt>String</tt></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
17603 * or <code>{@link Ext.Component#id id}</code> of the child component </li>
17604 * <li>a <b><tt>Number</tt></b> : representing the position of the child component
17605 * within the <code>{@link #items}</code> <b>property</b></li>
17607 * <p>For additional information see {@link Ext.util.MixedCollection#get}.
17608 * @return Ext.Component The component (if found).
17610 getComponent : function(comp){
17611 if(Ext.isObject(comp)){
17614 return this.items.get(comp);
17618 lookupComponent : function(comp){
17619 if(typeof comp == 'string'){
17620 return Ext.ComponentMgr.get(comp);
17621 }else if(!comp.events){
17622 return this.createComponent(comp);
17628 createComponent : function(config){
17629 return Ext.create(config, this.defaultType);
17633 * Force this container's layout to be recalculated. A call to this function is required after adding a new component
17634 * to an already rendered container, or possibly after changing sizing/position properties of child components.
17635 * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto
17636 * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer)
17637 * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden.
17638 * @return {Ext.Container} this
17640 doLayout: function(shallow, force){
17641 var rendered = this.rendered,
17642 forceLayout = this.forceLayout;
17644 if(!this.isVisible() || this.collapsed){
17645 this.deferLayout = this.deferLayout || !shallow;
17646 if(!(force || forceLayout)){
17649 shallow = shallow && !this.deferLayout;
17651 delete this.deferLayout;
17653 if(rendered && this.layout){
17654 this.layout.layout();
17656 if(shallow !== true && this.items){
17657 var cs = this.items.items;
17658 for(var i = 0, len = cs.length; i < len; i++){
17661 c.forceLayout = forceLayout;
17667 this.onLayout(shallow, force);
17669 delete this.forceLayout;
17673 onLayout : Ext.emptyFn,
17675 onShow : function(){
17676 Ext.Container.superclass.onShow.call(this);
17677 if(this.deferLayout !== undefined){
17678 this.doLayout(true);
17683 * Returns the layout currently in use by the container. If the container does not currently have a layout
17684 * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout.
17685 * @return {ContainerLayout} layout The container's layout
17687 getLayout : function(){
17689 var layout = new Ext.layout.ContainerLayout(this.layoutConfig);
17690 this.setLayout(layout);
17692 return this.layout;
17696 beforeDestroy : function(){
17698 Ext.destroy.apply(Ext, this.items.items);
17700 if(this.monitorResize){
17701 Ext.EventManager.removeResizeListener(this.doLayout, this);
17703 Ext.destroy(this.layout);
17704 Ext.Container.superclass.beforeDestroy.call(this);
17708 * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
17709 * function call will be the scope provided or the current component. The arguments to the function
17710 * will be the args provided or the current component. If the function returns false at any point,
17711 * the bubble is stopped.
17712 * @param {Function} fn The function to call
17713 * @param {Object} scope (optional) The scope of the function (defaults to current node)
17714 * @param {Array} args (optional) The args to call the function with (default to passing the current component)
17715 * @return {Ext.Container} this
17717 bubble : function(fn, scope, args){
17720 if(fn.apply(scope || p, args || [p]) === false){
17729 * Cascades down the component/container heirarchy from this component (called first), calling the specified function with
17730 * each component. The scope (<i>this</i>) of
17731 * function call will be the scope provided or the current component. The arguments to the function
17732 * will be the args provided or the current component. If the function returns false at any point,
17733 * the cascade is stopped on that branch.
17734 * @param {Function} fn The function to call
17735 * @param {Object} scope (optional) The scope of the function (defaults to current component)
17736 * @param {Array} args (optional) The args to call the function with (defaults to passing the current component)
17737 * @return {Ext.Container} this
17739 cascade : function(fn, scope, args){
17740 if(fn.apply(scope || this, args || [this]) !== false){
17742 var cs = this.items.items;
17743 for(var i = 0, len = cs.length; i < len; i++){
17745 cs[i].cascade(fn, scope, args);
17747 fn.apply(scope || cs[i], args || [cs[i]]);
17756 * Find a component under this container at any level by id
17757 * @param {String} id
17758 * @return Ext.Component
17760 findById : function(id){
17762 this.cascade(function(c){
17763 if(ct != c && c.id === id){
17772 * Find a component under this container at any level by xtype or class
17773 * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
17774 * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
17775 * the default), or true to check whether this Component is directly of the specified xtype.
17776 * @return {Array} Array of Ext.Components
17778 findByType : function(xtype, shallow){
17779 return this.findBy(function(c){
17780 return c.isXType(xtype, shallow);
17785 * Find a component under this container at any level by property
17786 * @param {String} prop
17787 * @param {String} value
17788 * @return {Array} Array of Ext.Components
17790 find : function(prop, value){
17791 return this.findBy(function(c){
17792 return c[prop] === value;
17797 * Find a component under this container at any level by a custom function. If the passed function returns
17798 * true, the component will be included in the results. The passed function is called with the arguments (component, this container).
17799 * @param {Function} fn The function to call
17800 * @param {Object} scope (optional)
17801 * @return {Array} Array of Ext.Components
17803 findBy : function(fn, scope){
17804 var m = [], ct = this;
17805 this.cascade(function(c){
17806 if(ct != c && fn.call(scope || c, c, ct) === true){
17814 * Get a component contained by this container (alias for items.get(key))
17815 * @param {String/Number} key The index or id of the component
17816 * @return {Ext.Component} Ext.Component
17818 get : function(key){
17819 return this.items.get(key);
17823 Ext.Container.LAYOUTS = {};
17824 Ext.reg('container', Ext.Container);
17826 * @class Ext.layout.ContainerLayout
17827 * <p>The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to
17828 * render any child Components when no <tt>{@link Ext.Container#layout layout}</tt> is configured into
17829 * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout
17830 * classes in Ext. It simply renders all child Components into the Container, performing no sizing or
17831 * positioning services. To utilize a layout that provides sizing and positioning of child Components,
17832 * specify an appropriate <tt>{@link Ext.Container#layout layout}</tt>.</p>
17833 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
17834 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
17836 Ext.layout.ContainerLayout = function(config){
17837 Ext.apply(this, config);
17840 Ext.layout.ContainerLayout.prototype = {
17842 * @cfg {String} extraCls
17843 * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
17844 * customized styles to the container or any of its children using standard CSS rules. See
17845 * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.</p>
17846 * <p><b>Note</b>: <tt>extraCls</tt> defaults to <tt>''</tt> except for the following classes
17847 * which assign a value by default:
17848 * <div class="mdetail-params"><ul>
17849 * <li>{@link Ext.layout.AbsoluteLayout Absolute Layout} : <tt>'x-abs-layout-item'</tt></li>
17850 * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-item'</tt></li>
17851 * <li>{@link Ext.layout.ColumnLayout Column Layout} : <tt>'x-column'</tt></li>
17853 * To configure the above Classes with an extra CSS class append to the default. For example,
17854 * for ColumnLayout:<pre><code>
17855 * extraCls: 'x-column custom-class'
17860 * @cfg {Boolean} renderHidden
17861 * True to hide each contained item on render (defaults to false).
17865 * A reference to the {@link Ext.Component} that is active. For example, <pre><code>
17866 * if(myPanel.layout.activeItem.id == 'item-1') { ... }
17868 * <tt>activeItem</tt> only applies to layout styles that can display items one at a time
17869 * (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout}
17870 * and {@link Ext.layout.FitLayout}). Read-only. Related to {@link Ext.Container#activeItem}.
17871 * @type {Ext.Component}
17872 * @property activeItem
17876 monitorResize:false,
17881 layout : function(){
17882 var target = this.container.getLayoutTarget();
17883 this.onLayout(this.container, target);
17884 this.container.fireEvent('afterlayout', this.container, this);
17888 onLayout : function(ct, target){
17889 this.renderAll(ct, target);
17893 isValidParent : function(c, target){
17894 return target && c.getDomPositionEl().dom.parentNode == (target.dom || target);
17898 renderAll : function(ct, target){
17899 var items = ct.items.items;
17900 for(var i = 0, len = items.length; i < len; i++) {
17902 if(c && (!c.rendered || !this.isValidParent(c, target))){
17903 this.renderItem(c, i, target);
17909 renderItem : function(c, position, target){
17910 if(c && !c.rendered){
17911 c.render(target, position);
17912 this.configureItem(c, position);
17913 }else if(c && !this.isValidParent(c, target)){
17914 if(typeof position == 'number'){
17915 position = target.dom.childNodes[position];
17917 target.dom.insertBefore(c.getDomPositionEl().dom, position || null);
17918 c.container = target;
17919 this.configureItem(c, position);
17924 configureItem: function(c, position){
17926 var t = c.getPositionEl ? c.getPositionEl() : c;
17927 t.addClass(this.extraCls);
17929 if (this.renderHidden && c != this.activeItem) {
17933 c.doLayout(false, this.forceLayout);
17938 onResize: function(){
17939 if(this.container.collapsed){
17942 var b = this.container.bufferResize;
17944 if(!this.resizeTask){
17945 this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this);
17946 this.resizeBuffer = typeof b == 'number' ? b : 100;
17948 this.resizeTask.delay(this.resizeBuffer);
17955 runLayout: function(){
17957 this.container.onLayout();
17961 setContainer : function(ct){
17962 if(this.monitorResize && ct != this.container){
17963 if(this.container){
17964 this.container.un('resize', this.onResize, this);
17965 this.container.un('bodyresize', this.onResize, this);
17970 resize: this.onResize,
17971 bodyresize: this.onResize
17975 this.container = ct;
17979 parseMargins : function(v){
17980 if(typeof v == 'number'){
17983 var ms = v.split(' ');
17984 var len = ms.length;
17998 top:parseInt(ms[0], 10) || 0,
17999 right:parseInt(ms[1], 10) || 0,
18000 bottom:parseInt(ms[2], 10) || 0,
18001 left:parseInt(ms[3], 10) || 0
18006 * The {@link Template Ext.Template} used by Field rendering layout classes (such as
18007 * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped,
18008 * labeled and styled form Field. A default Template is supplied, but this may be
18009 * overriden to create custom field structures. The template processes values returned from
18010 * {@link Ext.layout.FormLayout#getTemplateArgs}.
18011 * @property fieldTpl
18012 * @type Ext.Template
18014 fieldTpl: (function() {
18015 var t = new Ext.Template(
18016 '<div class="x-form-item {itemCls}" tabIndex="-1">',
18017 '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
18018 '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
18019 '</div><div class="{clearCls}"></div>',
18022 t.disableFormats = true;
18023 return t.compile();
18027 * Destroys this layout. This is a template method that is empty by default, but should be implemented
18028 * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
18031 destroy : Ext.emptyFn
18033 Ext.Container.LAYOUTS['auto'] = Ext.layout.ContainerLayout;/**
\r
18034 * @class Ext.layout.FitLayout
\r
18035 * @extends Ext.layout.ContainerLayout
\r
18036 * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
\r
18037 * container. This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.Container#layout}
\r
18038 * config, and should generally not need to be created directly via the new keyword.</p>
\r
18039 * <p>FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container
\r
18040 * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has
\r
18041 * multiple panels, only the first one will be displayed. Example usage:</p>
\r
18043 var p = new Ext.Panel({
\r
18044 title: 'Fit Layout',
\r
18047 title: 'Inner Panel',
\r
18048 html: '<p>This is the inner panel content</p>',
\r
18054 Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18056 monitorResize:true,
\r
18059 onLayout : function(ct, target){
\r
18060 Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
\r
18061 if(!this.container.collapsed){
\r
18062 var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize();
\r
18063 this.setItemSize(this.activeItem || ct.items.itemAt(0), sz);
\r
18068 setItemSize : function(item, size){
\r
18069 if(item && size.height > 0){ // display none?
\r
18070 item.setSize(size);
\r
18074 Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/**
\r
18075 * @class Ext.layout.CardLayout
\r
18076 * @extends Ext.layout.FitLayout
\r
18077 * <p>This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
\r
18078 * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc.
\r
18079 * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config,
\r
18080 * and should generally not need to be created directly via the new keyword.</p>
\r
18081 * <p>The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time,
\r
18082 * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
\r
18083 * the next panel to display. The layout itself does not provide a user interface for handling this navigation,
\r
18084 * so that functionality must be provided by the developer.</p>
\r
18085 * <p>In the following example, a simplistic wizard setup is demonstrated. A button bar is added
\r
18086 * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a
\r
18087 * common navigation routine -- for this example, the implementation of that routine has been ommitted since
\r
18088 * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a
\r
18089 * completely different implementation. For serious implementations, a better approach would be to extend
\r
18090 * CardLayout to provide the custom functionality needed. Example usage:</p>
\r
18092 var navHandler = function(direction){
\r
18093 // This routine could contain business logic required to manage the navigation steps.
\r
18094 // It would call setActiveItem as needed, manage navigation button state, handle any
\r
18095 // branching logic that might be required, handle alternate actions like cancellation
\r
18096 // or finalization, etc. A complete wizard implementation could get pretty
\r
18097 // sophisticated depending on the complexity required, and should probably be
\r
18098 // done as a subclass of CardLayout in a real-world implementation.
\r
18101 var card = new Ext.Panel({
\r
18102 title: 'Example Wizard',
\r
18104 activeItem: 0, // make sure the active item is set on the container config!
\r
18105 bodyStyle: 'padding:15px',
\r
18107 // applied to each contained panel
\r
18110 // just an example of one possible navigation scheme, using buttons
\r
18115 handler: navHandler.createDelegate(this, [-1]),
\r
18118 '->', // greedy spacer so that the buttons are aligned to each side
\r
18122 handler: navHandler.createDelegate(this, [1])
\r
18125 // the panels (or "cards") within the layout
\r
18128 html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
\r
18131 html: '<p>Step 2 of 3</p>'
\r
18134 html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
\r
18139 Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
\r
18141 * @cfg {Boolean} deferredRender
\r
18142 * True to render each contained item at the time it becomes active, false to render all contained items
\r
18143 * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
\r
18144 * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
\r
18145 * true might improve performance.
\r
18147 deferredRender : false,
\r
18150 * @cfg {Boolean} layoutOnCardChange
\r
18151 * True to force a layout of the active item when the active card is changed. Defaults to false.
\r
18153 layoutOnCardChange : false,
\r
18156 * @cfg {Boolean} renderHidden @hide
\r
18159 renderHidden : true,
\r
18161 constructor: function(config){
\r
18162 Ext.layout.CardLayout.superclass.constructor.call(this, config);
\r
18163 this.forceLayout = (this.deferredRender === false);
\r
18167 * Sets the active (visible) item in the layout.
\r
18168 * @param {String/Number} item The string component id or numeric index of the item to activate
\r
18170 setActiveItem : function(item){
\r
18171 item = this.container.getComponent(item);
\r
18172 if(this.activeItem != item){
\r
18173 if(this.activeItem){
\r
18174 this.activeItem.hide();
\r
18176 this.activeItem = item;
\r
18178 this.container.doLayout();
\r
18179 if(this.layoutOnCardChange && item.doLayout){
\r
18186 renderAll : function(ct, target){
\r
18187 if(this.deferredRender){
\r
18188 this.renderItem(this.activeItem, undefined, target);
\r
18190 Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
\r
18194 Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;/**
\r
18195 * @class Ext.layout.AnchorLayout
\r
18196 * @extends Ext.layout.ContainerLayout
\r
18197 * <p>This is a layout that enables anchoring of contained elements relative to the container's dimensions.
\r
18198 * If the container is resized, all anchored items are automatically rerendered according to their
\r
18199 * <b><tt>{@link #anchor}</tt></b> rules.</p>
\r
18200 * <p>This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout}
\r
18201 * config, and should generally not need to be created directly via the new keyword.</p>
\r
18202 * <p>AnchorLayout does not have any direct config options (other than inherited ones). By default,
\r
18203 * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
\r
18204 * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
\r
18205 * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
\r
18206 * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
\r
18207 * logic if necessary. For example:</p>
\r
18209 var viewport = new Ext.Viewport({
\r
18211 anchorSize: {width:800, height:600},
\r
18214 html:'Content 1',
\r
18216 anchor:'right 20%'
\r
18219 html:'Content 2',
\r
18224 html:'Content 3',
\r
18226 anchor:'-100 50%'
\r
18231 Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18233 * @cfg {String} anchor
\r
18234 * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
\r
18235 * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
\r
18237 * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
\r
18238 * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
\r
18239 * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
\r
18240 * The following types of anchor values are supported:<div class="mdetail-params"><ul>
\r
18242 * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
\r
18243 * The first anchor is the percentage width that the item should take up within the container, and the
\r
18244 * second is the percentage height. For example:<pre><code>
\r
18245 // two values specified
\r
18246 anchor: '100% 50%' // render item complete width of the container and
\r
18247 // 1/2 height of the container
\r
18248 // one value specified
\r
18249 anchor: '100%' // the width value; the height will default to auto
\r
18250 * </code></pre></div></li>
\r
18252 * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
\r
18253 * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
\r
18254 * and the second is the offset from the bottom edge. For example:<pre><code>
\r
18255 // two values specified
\r
18256 anchor: '-50 -100' // render item the complete width of the container
\r
18257 // minus 50 pixels and
\r
18258 // the complete height minus 100 pixels.
\r
18259 // one value specified
\r
18260 anchor: '-50' // anchor value is assumed to be the right offset value
\r
18261 // bottom offset will default to 0
\r
18262 * </code></pre></div></li>
\r
18264 * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
\r
18265 * (or <tt>'b'</tt>).<div class="sub-desc">
\r
18266 * Either the container must have a fixed size or an anchorSize config value defined at render time in
\r
18267 * order for these to have any effect.</div></li>
\r
18269 * <li><b>Mixed</b> : <div class="sub-desc">
\r
18270 * Anchor values can also be mixed as needed. For example, to render the width offset from the container
\r
18271 * right edge by 50 pixels and 75% of the container's height use:
\r
18273 anchor: '-50 75%'
\r
18274 * </code></pre></div></li>
\r
18281 monitorResize:true,
\r
18284 getAnchorViewSize : function(ct, target){
\r
18285 return target.dom == document.body ?
\r
18286 target.getViewSize() : target.getStyleSize();
\r
18290 onLayout : function(ct, target){
\r
18291 Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target);
\r
18293 var size = this.getAnchorViewSize(ct, target);
\r
18295 var w = size.width, h = size.height;
\r
18297 if(w < 20 && h < 20){
\r
18301 // find the container anchoring size
\r
18303 if(ct.anchorSize){
\r
18304 if(typeof ct.anchorSize == 'number'){
\r
18305 aw = ct.anchorSize;
\r
18307 aw = ct.anchorSize.width;
\r
18308 ah = ct.anchorSize.height;
\r
18311 aw = ct.initialConfig.width;
\r
18312 ah = ct.initialConfig.height;
\r
18315 var cs = ct.items.items, len = cs.length, i, c, a, cw, ch;
\r
18316 for(i = 0; i < len; i++){
\r
18319 a = c.anchorSpec;
\r
18320 if(!a){ // cache all anchor values
\r
18321 var vs = c.anchor.split(' ');
\r
18322 c.anchorSpec = a = {
\r
18323 right: this.parseAnchor(vs[0], c.initialConfig.width, aw),
\r
18324 bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah)
\r
18327 cw = a.right ? this.adjustWidthAnchor(a.right(w), c) : undefined;
\r
18328 ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h), c) : undefined;
\r
18331 c.setSize(cw || undefined, ch || undefined);
\r
18338 parseAnchor : function(a, start, cstart){
\r
18339 if(a && a != 'none'){
\r
18341 if(/^(r|right|b|bottom)$/i.test(a)){ // standard anchor
\r
18342 var diff = cstart - start;
\r
18343 return function(v){
\r
18349 }else if(a.indexOf('%') != -1){
\r
18350 var ratio = parseFloat(a.replace('%', ''))*.01; // percentage
\r
18351 return function(v){
\r
18354 return Math.floor(v*ratio);
\r
18358 a = parseInt(a, 10);
\r
18359 if(!isNaN(a)){ // simple offset adjustment
\r
18360 return function(v){
\r
18373 adjustWidthAnchor : function(value, comp){
\r
18378 adjustHeightAnchor : function(value, comp){
\r
18383 * @property activeItem
\r
18387 Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;/**
\r
18388 * @class Ext.layout.ColumnLayout
\r
18389 * @extends Ext.layout.ContainerLayout
\r
18390 * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
\r
18391 * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
\r
18392 * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config,
\r
18393 * and should generally not need to be created directly via the new keyword.</p>
\r
18394 * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
\r
18395 * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it. The
\r
18396 * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
\r
18397 * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
\r
18398 * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
\r
18399 * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
\r
18400 * less than 1 (e.g., .25).</p>
\r
18401 * <p>The basic rules for specifying column widths are pretty simple. The logic makes two passes through the
\r
18402 * set of contained panels. During the first layout pass, all panels that either have a fixed width or none
\r
18403 * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second
\r
18404 * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
\r
18405 * the total <b>remaining</b> container width. In other words, percentage width panels are designed to fill the space
\r
18406 * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns
\r
18407 * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
\r
18408 * layout may not render as expected. Example usage:</p>
\r
18410 // All columns are percentages -- they must add up to 1
\r
18411 var p = new Ext.Panel({
\r
18412 title: 'Column Layout - Percentage Only',
\r
18415 title: 'Column 1',
\r
18416 columnWidth: .25
\r
18418 title: 'Column 2',
\r
18421 title: 'Column 3',
\r
18426 // Mix of width and columnWidth -- all columnWidth values must add up
\r
18427 // to 1. The first column will take up exactly 120px, and the last two
\r
18428 // columns will fill the remaining container width.
\r
18429 var p = new Ext.Panel({
\r
18430 title: 'Column Layout - Mixed',
\r
18433 title: 'Column 1',
\r
18436 title: 'Column 2',
\r
18439 title: 'Column 3',
\r
18445 Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18447 monitorResize:true,
\r
18449 extraCls: 'x-column',
\r
18451 scrollOffset : 0,
\r
18454 isValidParent : function(c, target){
\r
18455 return (c.getPositionEl ? c.getPositionEl() : c.getEl()).dom.parentNode == this.innerCt.dom;
\r
18459 onLayout : function(ct, target){
\r
18460 var cs = ct.items.items, len = cs.length, c, i;
\r
18462 if(!this.innerCt){
\r
18463 target.addClass('x-column-layout-ct');
\r
18465 // the innerCt prevents wrapping and shuffling while
\r
18466 // the container is resizing
\r
18467 this.innerCt = target.createChild({cls:'x-column-inner'});
\r
18468 this.innerCt.createChild({cls:'x-clear'});
\r
18470 this.renderAll(ct, this.innerCt);
\r
18472 var size = Ext.isIE && target.dom != Ext.getBody().dom ? target.getStyleSize() : target.getViewSize();
\r
18474 if(size.width < 1 && size.height < 1){ // display none?
\r
18478 var w = size.width - target.getPadding('lr') - this.scrollOffset,
\r
18479 h = size.height - target.getPadding('tb'),
\r
18482 this.innerCt.setWidth(w);
\r
18484 // some columns can be percentages while others are fixed
\r
18485 // so we need to make 2 passes
\r
18487 for(i = 0; i < len; i++){
\r
18489 if(!c.columnWidth){
\r
18490 pw -= (c.getSize().width + c.getEl().getMargins('lr'));
\r
18494 pw = pw < 0 ? 0 : pw;
\r
18496 for(i = 0; i < len; i++){
\r
18498 if(c.columnWidth){
\r
18499 c.setSize(Math.floor(c.columnWidth*pw) - c.getEl().getMargins('lr'));
\r
18505 * @property activeItem
\r
18510 Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;/**
18511 * @class Ext.layout.BorderLayout
18512 * @extends Ext.layout.ContainerLayout
18513 * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
18514 * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
18515 * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
18516 * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.</p>
18517 * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
18518 * {@link Ext.Container#layout} config, and should generally not need to be created directly
18519 * via the new keyword.</p>
18520 * <p>BorderLayout does not have any direct config options (other than inherited ones).
18521 * All configuration options available for customizing the BorderLayout are at the
18522 * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
18524 * <p>Example usage:</p>
18526 var myBorderPanel = new Ext.Panel({
18527 {@link Ext.Component#renderTo renderTo}: document.body,
18528 {@link Ext.BoxComponent#width width}: 700,
18529 {@link Ext.BoxComponent#height height}: 500,
18530 {@link Ext.Panel#title title}: 'Border Layout',
18531 {@link Ext.Container#layout layout}: 'border',
18532 {@link Ext.Container#items items}: [{
18533 {@link Ext.Panel#title title}: 'South Region is resizable',
18534 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
18535 {@link Ext.BoxComponent#height height}: 100,
18536 {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
18537 {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
18538 {@link Ext.SplitBar#maxSize maxSize}: 150,
18539 {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
18541 // xtype: 'panel' implied by default
18542 {@link Ext.Panel#title title}: 'West Region is collapsible',
18543 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
18544 {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
18545 {@link Ext.BoxComponent#width width}: 200,
18546 {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
18547 {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
18548 {@link Ext.Component#id id}: 'west-region-container',
18549 {@link Ext.Container#layout layout}: 'fit',
18550 {@link Ext.Panel#unstyled unstyled}: true
18552 {@link Ext.Panel#title title}: 'Center Region',
18553 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
18554 {@link Ext.Component#xtype xtype}: 'container',
18555 {@link Ext.Container#layout layout}: 'fit',
18556 {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
18560 * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
18561 * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
18562 * The child item in the center region will always be resized to fill the remaining space not used by
18563 * the other regions in the layout.</li>
18564 * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
18565 * (an integer representing the number of pixels that the region should take up).</li>
18566 * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
18567 * <li>The regions of a BorderLayout are <b>fixed at render time</b> and thereafter, its child Components may not be removed or added</b>. To add/remove
18568 * Components within a BorderLayout, have them wrapped by an additional Container which is directly
18569 * managed by the BorderLayout. If the region is to be collapsible, the Container used directly
18570 * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel)
18571 * is added to the west region:
18572 * <div style="margin-left:16px"><pre><code>
18573 wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
18574 wrc.{@link Ext.Panel#removeAll removeAll}();
18575 wrc.{@link Ext.Container#add add}({
18576 title: 'Added Panel',
18577 html: 'Some content'
18579 wrc.{@link Ext.Container#doLayout doLayout}();
18580 * </code></pre></div>
18582 * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
18583 * <div style="margin-left:16px"><pre><code>
18584 wr = myBorderPanel.layout.west;
18585 * </code></pre></div>
18589 Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
18591 monitorResize:true,
18596 onLayout : function(ct, target){
18598 if(!this.rendered){
18599 target.addClass('x-border-layout-ct');
18600 var items = ct.items.items;
18602 for(var i = 0, len = items.length; i < len; i++) {
18604 var pos = c.region;
18608 c.collapsed = false;
18610 c.cls = c.cls ? c.cls +' x-border-panel' : 'x-border-panel';
18611 c.render(target, i);
18613 this[pos] = pos != 'center' && c.split ?
18614 new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
18615 new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
18616 this[pos].render(target, c);
18618 this.rendered = true;
18621 var size = target.getViewSize();
18622 if(size.width < 20 || size.height < 20){ // display none?
18624 this.restoreCollapsed = collapsed;
18627 }else if(this.restoreCollapsed){
18628 collapsed = this.restoreCollapsed;
18629 delete this.restoreCollapsed;
18632 var w = size.width, h = size.height;
18633 var centerW = w, centerH = h, centerY = 0, centerX = 0;
18635 var n = this.north, s = this.south, west = this.west, e = this.east, c = this.center;
18636 if(!c && Ext.layout.BorderLayout.WARN !== false){
18637 throw 'No center region defined in BorderLayout ' + ct.id;
18640 if(n && n.isVisible()){
18641 var b = n.getSize();
18642 var m = n.getMargins();
18643 b.width = w - (m.left+m.right);
18646 centerY = b.height + b.y + m.bottom;
18647 centerH -= centerY;
18650 if(s && s.isVisible()){
18651 var b = s.getSize();
18652 var m = s.getMargins();
18653 b.width = w - (m.left+m.right);
18655 var totalHeight = (b.height + m.top + m.bottom);
18656 b.y = h - totalHeight + m.top;
18657 centerH -= totalHeight;
18660 if(west && west.isVisible()){
18661 var b = west.getSize();
18662 var m = west.getMargins();
18663 b.height = centerH - (m.top+m.bottom);
18665 b.y = centerY + m.top;
18666 var totalWidth = (b.width + m.left + m.right);
18667 centerX += totalWidth;
18668 centerW -= totalWidth;
18669 west.applyLayout(b);
18671 if(e && e.isVisible()){
18672 var b = e.getSize();
18673 var m = e.getMargins();
18674 b.height = centerH - (m.top+m.bottom);
18675 var totalWidth = (b.width + m.left + m.right);
18676 b.x = w - totalWidth + m.left;
18677 b.y = centerY + m.top;
18678 centerW -= totalWidth;
18682 var m = c.getMargins();
18684 x: centerX + m.left,
18685 y: centerY + m.top,
18686 width: centerW - (m.left+m.right),
18687 height: centerH - (m.top+m.bottom)
18689 c.applyLayout(centerBox);
18692 for(var i = 0, len = collapsed.length; i < len; i++){
18693 collapsed[i].collapse(false);
18696 if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
18701 destroy: function() {
18702 var r = ['north', 'south', 'east', 'west'];
18703 for (var i = 0; i < r.length; i++) {
18704 var region = this[r[i]];
18706 if(region.destroy){
18708 }else if (region.split){
18709 region.split.destroy(true);
18713 Ext.layout.BorderLayout.superclass.destroy.call(this);
18717 * @property activeItem
18723 * @class Ext.layout.BorderLayout.Region
18724 * <p>This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
18725 * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is
18726 * independent of other regions and the containing BorderLayout, and can be any of the
18727 * {@link Ext.layout.ContainerLayout valid Ext layout types}.</p>
18728 * <p>Region size is managed automatically and cannot be changed by the user -- for
18729 * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
18731 * Create a new Region.
18732 * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
18733 * @param {Object} config The configuration options
18734 * @param {String} position The region position. Valid values are: <tt>north</tt>, <tt>south</tt>,
18735 * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>. Every {@link Ext.layout.BorderLayout BorderLayout}
18736 * <b>must have a center region</b> for the primary content -- all other regions are optional.
18738 Ext.layout.BorderLayout.Region = function(layout, config, pos){
18739 Ext.apply(this, config);
18740 this.layout = layout;
18741 this.position = pos;
18743 if(typeof this.margins == 'string'){
18744 this.margins = this.layout.parseMargins(this.margins);
18746 this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
18747 if(this.collapsible){
18748 if(typeof this.cmargins == 'string'){
18749 this.cmargins = this.layout.parseMargins(this.cmargins);
18751 if(this.collapseMode == 'mini' && !this.cmargins){
18752 this.cmargins = {left:0,top:0,right:0,bottom:0};
18754 this.cmargins = Ext.applyIf(this.cmargins || {},
18755 pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
18760 Ext.layout.BorderLayout.Region.prototype = {
18762 * @cfg {Boolean} animFloat
18763 * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
18764 * panel that will close again once the user mouses out of that panel (or clicks out if
18765 * <tt>{@link #autoHide} = false</tt>). Setting <tt>{@link #animFloat} = false</tt> will
18766 * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
18769 * @cfg {Boolean} autoHide
18770 * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
18771 * panel. If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
18772 * out of the panel. If <tt>autoHide = false</tt>, the panel will continue to display until the
18773 * user clicks outside of the panel (defaults to <tt>true</tt>).
18776 * @cfg {String} collapseMode
18777 * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
18778 * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
18779 * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
18780 * title bar.</div></li>
18781 * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
18782 * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
18783 * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
18786 * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
18787 * 'mini'</tt> and <tt>{@link #split} = true</tt> in order for the region to be {@link #collapsible}
18788 * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.</p>
18789 * <p>See also <tt>{@link #cmargins}</tt>.</p>
18792 * @cfg {Object} margins
18793 * An object containing margins to apply to the region when in the expanded state in the
18794 * format:<pre><code>
18797 right: (right margin),
18798 bottom: (bottom margin),
18799 left: (left margin)
18801 * <p>May also be a string containing space-separated, numeric margin values. The order of the
18802 * sides associated with each value matches the way CSS processes margin values:</p>
18803 * <p><div class="mdetail-params"><ul>
18804 * <li>If there is only one value, it applies to all sides.</li>
18805 * <li>If there are two values, the top and bottom borders are set to the first value and the
18806 * right and left are set to the second.</li>
18807 * <li>If there are three values, the top is set to the first value, the left and right are set
18808 * to the second, and the bottom is set to the third.</li>
18809 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
18811 * <p>Defaults to:</p><pre><code>
18812 * {top:0, right:0, bottom:0, left:0}
18816 * @cfg {Object} cmargins
18817 * An object containing margins to apply to the region when in the collapsed state in the
18818 * format:<pre><code>
18821 right: (right margin),
18822 bottom: (bottom margin),
18823 left: (left margin)
18825 * <p>May also be a string containing space-separated, numeric margin values. The order of the
18826 * sides associated with each value matches the way CSS processes margin values.</p>
18828 * <li>If there is only one value, it applies to all sides.</li>
18829 * <li>If there are two values, the top and bottom borders are set to the first value and the
18830 * right and left are set to the second.</li>
18831 * <li>If there are three values, the top is set to the first value, the left and right are set
18832 * to the second, and the bottom is set to the third.</li>
18833 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
18837 * @cfg {Boolean} collapsible
18838 * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>). If
18839 * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
18840 * bar of the region, otherwise the button will not be shown.</p>
18841 * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
18842 * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
18843 * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
18845 collapsible : false,
18847 * @cfg {Boolean} split
18848 * <p><tt>true</tt> to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
18849 * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
18850 * resize the regions dynamically. Defaults to <tt>false</tt> creating a
18851 * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
18852 * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
18853 * <li>this configuration option is ignored if <tt>region='center'</tt></li>
18854 * <li>when <tt>split == true</tt>, it is common to specify a
18855 * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
18856 * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
18857 * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.</li>
18858 * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
18859 * for the collapse tool</tt></li>
18864 * @cfg {Boolean} floatable
18865 * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
18866 * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
18867 * clicking the expand button to see it again (defaults to <tt>true</tt>).
18871 * @cfg {Number} minWidth
18872 * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
18873 * <tt>maxWidth</tt> may also be specified.</p><br>
18874 * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
18875 * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
18876 * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
18880 * @cfg {Number} minHeight
18881 * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
18882 * <tt>maxHeight</tt> may also be specified.</p><br>
18883 * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
18884 * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
18885 * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
18890 defaultMargins : {left:0,top:0,right:0,bottom:0},
18892 defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
18894 defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
18895 floatingZIndex: 100,
18898 * True if this region is collapsed. Read-only.
18902 isCollapsed : false,
18905 * This region's panel. Read-only.
18910 * This region's layout. Read-only.
18915 * This region's layout position (north, south, east, west or center). Read-only.
18917 * @property position
18921 render : function(ct, p){
18923 p.el.enableDisplayMode();
18924 this.targetEl = ct;
18927 var gs = p.getState, ps = this.position;
18928 p.getState = function(){
18929 return Ext.apply(gs.call(p) || {}, this.state);
18930 }.createDelegate(this);
18932 if(ps != 'center'){
18933 p.allowQueuedExpand = false;
18935 beforecollapse: this.beforeCollapse,
18936 collapse: this.onCollapse,
18937 beforeexpand: this.beforeExpand,
18938 expand: this.onExpand,
18943 if(this.collapsible || this.floatable){
18944 p.collapseEl = 'el';
18945 p.slideAnchor = this.getSlideAnchor();
18947 if(p.tools && p.tools.toggle){
18948 p.tools.toggle.addClass('x-tool-collapse-'+ps);
18949 p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
18955 getCollapsedEl : function(){
18956 if(!this.collapsedEl){
18957 if(!this.toolTemplate){
18958 var tt = new Ext.Template(
18959 '<div class="x-tool x-tool-{id}"> </div>'
18961 tt.disableFormats = true;
18963 Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
18965 this.collapsedEl = this.targetEl.createChild({
18966 cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
18967 id: this.panel.id + '-xcollapsed'
18969 this.collapsedEl.enableDisplayMode('block');
18971 if(this.collapseMode == 'mini'){
18972 this.collapsedEl.addClass('x-layout-cmini-'+this.position);
18973 this.miniCollapsedEl = this.collapsedEl.createChild({
18974 cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
18976 this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
18977 this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
18978 this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
18980 if(this.collapsible !== false && !this.hideCollapseTool) {
18981 var t = this.toolTemplate.append(
18982 this.collapsedEl.dom,
18983 {id:'expand-'+this.position}, true);
18984 t.addClassOnOver('x-tool-expand-'+this.position+'-over');
18985 t.on('click', this.onExpandClick, this, {stopEvent:true});
18987 if(this.floatable !== false || this.titleCollapse){
18988 this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
18989 this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
18993 return this.collapsedEl;
18997 onExpandClick : function(e){
18999 this.afterSlideIn();
19000 this.panel.expand(false);
19002 this.panel.expand();
19007 onCollapseClick : function(e){
19008 this.panel.collapse();
19012 beforeCollapse : function(p, animate){
19013 this.lastAnim = animate;
19015 this.splitEl.hide();
19017 this.getCollapsedEl().show();
19018 this.panel.el.setStyle('z-index', 100);
19019 this.isCollapsed = true;
19020 this.layout.layout();
19024 onCollapse : function(animate){
19025 this.panel.el.setStyle('z-index', 1);
19026 if(this.lastAnim === false || this.panel.animCollapse === false){
19027 this.getCollapsedEl().dom.style.visibility = 'visible';
19029 this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
19031 this.state.collapsed = true;
19032 this.panel.saveState();
19036 beforeExpand : function(animate){
19037 var c = this.getCollapsedEl();
19039 if(this.position == 'east' || this.position == 'west'){
19040 this.panel.setSize(undefined, c.getHeight());
19042 this.panel.setSize(c.getWidth(), undefined);
19045 c.dom.style.visibility = 'hidden';
19046 this.panel.el.setStyle('z-index', this.floatingZIndex);
19050 onExpand : function(){
19051 this.isCollapsed = false;
19053 this.splitEl.show();
19055 this.layout.layout();
19056 this.panel.el.setStyle('z-index', 1);
19057 this.state.collapsed = false;
19058 this.panel.saveState();
19062 collapseClick : function(e){
19064 e.stopPropagation();
19067 e.stopPropagation();
19073 onHide : function(){
19074 if(this.isCollapsed){
19075 this.getCollapsedEl().hide();
19076 }else if(this.splitEl){
19077 this.splitEl.hide();
19082 onShow : function(){
19083 if(this.isCollapsed){
19084 this.getCollapsedEl().show();
19085 }else if(this.splitEl){
19086 this.splitEl.show();
19091 * True if this region is currently visible, else false.
19092 * @return {Boolean}
19094 isVisible : function(){
19095 return !this.panel.hidden;
19099 * Returns the current margins for this region. If the region is collapsed, the
19100 * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
19101 * {@link #margins} value will be returned.
19102 * @return {Object} An object containing the element's margins: <tt>{left: (left
19103 * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
19105 getMargins : function(){
19106 return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
19110 * Returns the current size of this region. If the region is collapsed, the size of the
19111 * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
19112 * @return {Object} An object containing the element's size: <tt>{width: (element width),
19113 * height: (element height)}</tt>
19115 getSize : function(){
19116 return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
19120 * Sets the specified panel as the container element for this region.
19121 * @param {Ext.Panel} panel The new panel
19123 setPanel : function(panel){
19124 this.panel = panel;
19128 * Returns the minimum allowable width for this region.
19129 * @return {Number} The minimum width
19131 getMinWidth: function(){
19132 return this.minWidth;
19136 * Returns the minimum allowable height for this region.
19137 * @return {Number} The minimum height
19139 getMinHeight: function(){
19140 return this.minHeight;
19144 applyLayoutCollapsed : function(box){
19145 var ce = this.getCollapsedEl();
19146 ce.setLeftTop(box.x, box.y);
19147 ce.setSize(box.width, box.height);
19151 applyLayout : function(box){
19152 if(this.isCollapsed){
19153 this.applyLayoutCollapsed(box);
19155 this.panel.setPosition(box.x, box.y);
19156 this.panel.setSize(box.width, box.height);
19161 beforeSlide: function(){
19162 this.panel.beforeEffect();
19166 afterSlide : function(){
19167 this.panel.afterEffect();
19171 initAutoHide : function(){
19172 if(this.autoHide !== false){
19173 if(!this.autoHideHd){
19174 var st = new Ext.util.DelayedTask(this.slideIn, this);
19175 this.autoHideHd = {
19176 "mouseout": function(e){
19177 if(!e.within(this.el, true)){
19181 "mouseover" : function(e){
19187 this.el.on(this.autoHideHd);
19192 clearAutoHide : function(){
19193 if(this.autoHide !== false){
19194 this.el.un("mouseout", this.autoHideHd.mouseout);
19195 this.el.un("mouseover", this.autoHideHd.mouseover);
19200 clearMonitor : function(){
19201 Ext.getDoc().un("click", this.slideInIf, this);
19205 * If this Region is {@link #floatable}, this method slides this Region into full visibility <i>over the top
19206 * of the center Region</i> where it floats until either {@link #slideIn} is called, or other regions of the layout
19207 * are clicked, or the mouse exits the Region.
19209 slideOut : function(){
19210 if(this.isSlid || this.el.hasActiveFx()){
19213 this.isSlid = true;
19214 var ts = this.panel.tools;
19215 if(ts && ts.toggle){
19219 if(this.position == 'east' || this.position == 'west'){
19220 this.panel.setSize(undefined, this.collapsedEl.getHeight());
19222 this.panel.setSize(this.collapsedEl.getWidth(), undefined);
19224 this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
19225 this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
19226 this.el.setStyle("z-index", this.floatingZIndex+2);
19227 this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
19228 if(this.animFloat !== false){
19229 this.beforeSlide();
19230 this.el.slideIn(this.getSlideAnchor(), {
19231 callback: function(){
19233 this.initAutoHide();
19234 Ext.getDoc().on("click", this.slideInIf, this);
19240 this.initAutoHide();
19241 Ext.getDoc().on("click", this.slideInIf, this);
19246 afterSlideIn : function(){
19247 this.clearAutoHide();
19248 this.isSlid = false;
19249 this.clearMonitor();
19250 this.el.setStyle("z-index", "");
19251 this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
19252 this.el.dom.style.left = this.restoreLT[0];
19253 this.el.dom.style.top = this.restoreLT[1];
19255 var ts = this.panel.tools;
19256 if(ts && ts.toggle){
19262 * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
19263 * this region back into its collapsed state.
19265 slideIn : function(cb){
19266 if(!this.isSlid || this.el.hasActiveFx()){
19270 this.isSlid = false;
19271 if(this.animFloat !== false){
19272 this.beforeSlide();
19273 this.el.slideOut(this.getSlideAnchor(), {
19274 callback: function(){
19277 this.afterSlideIn();
19285 this.afterSlideIn();
19290 slideInIf : function(e){
19291 if(!e.within(this.el)){
19321 getAnchor : function(){
19322 return this.anchors[this.position];
19326 getCollapseAnchor : function(){
19327 return this.canchors[this.position];
19331 getSlideAnchor : function(){
19332 return this.sanchors[this.position];
19336 getAlignAdj : function(){
19337 var cm = this.cmargins;
19338 switch(this.position){
19355 getExpandAdj : function(){
19356 var c = this.collapsedEl, cm = this.cmargins;
19357 switch(this.position){
19359 return [-(cm.right+c.getWidth()+cm.left), 0];
19362 return [cm.right+c.getWidth()+cm.left, 0];
19365 return [0, -(cm.top+cm.bottom+c.getHeight())];
19368 return [0, cm.top+cm.bottom+c.getHeight()];
19375 * @class Ext.layout.BorderLayout.SplitRegion
19376 * @extends Ext.layout.BorderLayout.Region
19377 * <p>This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
19378 * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
19379 * is configurable to move either {@link #tickSize smooth or incrementally}.</p>
19381 * Create a new SplitRegion.
19382 * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
19383 * @param {Object} config The configuration options
19384 * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
19385 * BorderLayout must have a center region for the primary content -- all other regions are optional.
19387 Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
19388 Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
19390 this.applyLayout = this.applyFns[pos];
19393 Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
19395 * @cfg {Number} tickSize
19396 * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
19397 * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
19400 * @cfg {String} splitTip
19401 * The tooltip to display when the user hovers over a
19402 * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
19403 * (defaults to <tt>"Drag to resize."</tt>). Only applies if
19404 * <tt>{@link #useSplitTips} = true</tt>.
19406 splitTip : "Drag to resize.",
19408 * @cfg {String} collapsibleSplitTip
19409 * The tooltip to display when the user hovers over a
19410 * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
19411 * (defaults to "Drag to resize. Double click to hide."). Only applies if
19412 * <tt>{@link #useSplitTips} = true</tt>.
19414 collapsibleSplitTip : "Drag to resize. Double click to hide.",
19416 * @cfg {Boolean} useSplitTips
19417 * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
19418 * (defaults to <tt>false</tt>). The tooltip text will be the value of either
19419 * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> as appropriate.
19421 useSplitTips : false,
19426 orientation: Ext.SplitBar.VERTICAL,
19427 placement: Ext.SplitBar.TOP,
19428 maxFn : 'getVMaxSize',
19429 minProp: 'minHeight',
19430 maxProp: 'maxHeight'
19433 orientation: Ext.SplitBar.VERTICAL,
19434 placement: Ext.SplitBar.BOTTOM,
19435 maxFn : 'getVMaxSize',
19436 minProp: 'minHeight',
19437 maxProp: 'maxHeight'
19440 orientation: Ext.SplitBar.HORIZONTAL,
19441 placement: Ext.SplitBar.RIGHT,
19442 maxFn : 'getHMaxSize',
19443 minProp: 'minWidth',
19444 maxProp: 'maxWidth'
19447 orientation: Ext.SplitBar.HORIZONTAL,
19448 placement: Ext.SplitBar.LEFT,
19449 maxFn : 'getHMaxSize',
19450 minProp: 'minWidth',
19451 maxProp: 'maxWidth'
19457 west : function(box){
19458 if(this.isCollapsed){
19459 return this.applyLayoutCollapsed(box);
19461 var sd = this.splitEl.dom, s = sd.style;
19462 this.panel.setPosition(box.x, box.y);
19463 var sw = sd.offsetWidth;
19464 s.left = (box.x+box.width-sw)+'px';
19465 s.top = (box.y)+'px';
19466 s.height = Math.max(0, box.height)+'px';
19467 this.panel.setSize(box.width-sw, box.height);
19469 east : function(box){
19470 if(this.isCollapsed){
19471 return this.applyLayoutCollapsed(box);
19473 var sd = this.splitEl.dom, s = sd.style;
19474 var sw = sd.offsetWidth;
19475 this.panel.setPosition(box.x+sw, box.y);
19476 s.left = (box.x)+'px';
19477 s.top = (box.y)+'px';
19478 s.height = Math.max(0, box.height)+'px';
19479 this.panel.setSize(box.width-sw, box.height);
19481 north : function(box){
19482 if(this.isCollapsed){
19483 return this.applyLayoutCollapsed(box);
19485 var sd = this.splitEl.dom, s = sd.style;
19486 var sh = sd.offsetHeight;
19487 this.panel.setPosition(box.x, box.y);
19488 s.left = (box.x)+'px';
19489 s.top = (box.y+box.height-sh)+'px';
19490 s.width = Math.max(0, box.width)+'px';
19491 this.panel.setSize(box.width, box.height-sh);
19493 south : function(box){
19494 if(this.isCollapsed){
19495 return this.applyLayoutCollapsed(box);
19497 var sd = this.splitEl.dom, s = sd.style;
19498 var sh = sd.offsetHeight;
19499 this.panel.setPosition(box.x, box.y+sh);
19500 s.left = (box.x)+'px';
19501 s.top = (box.y)+'px';
19502 s.width = Math.max(0, box.width)+'px';
19503 this.panel.setSize(box.width, box.height-sh);
19508 render : function(ct, p){
19509 Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
19511 var ps = this.position;
19513 this.splitEl = ct.createChild({
19514 cls: "x-layout-split x-layout-split-"+ps, html: " ",
19515 id: this.panel.id + '-xsplit'
19518 if(this.collapseMode == 'mini'){
19519 this.miniSplitEl = this.splitEl.createChild({
19520 cls: "x-layout-mini x-layout-mini-"+ps, html: " "
19522 this.miniSplitEl.addClassOnOver('x-layout-mini-over');
19523 this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
19526 var s = this.splitSettings[ps];
19528 this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
19529 this.split.tickSize = this.tickSize;
19530 this.split.placement = s.placement;
19531 this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
19532 this.split.minSize = this.minSize || this[s.minProp];
19533 this.split.on("beforeapply", this.onSplitMove, this);
19534 this.split.useShim = this.useShim === true;
19535 this.maxSize = this.maxSize || this[s.maxProp];
19538 this.splitEl.hide();
19541 if(this.useSplitTips){
19542 this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
19544 if(this.collapsible){
19545 this.splitEl.on("dblclick", this.onCollapseClick, this);
19549 //docs inherit from superclass
19550 getSize : function(){
19551 if(this.isCollapsed){
19552 return this.collapsedEl.getSize();
19554 var s = this.panel.getSize();
19555 if(this.position == 'north' || this.position == 'south'){
19556 s.height += this.splitEl.dom.offsetHeight;
19558 s.width += this.splitEl.dom.offsetWidth;
19564 getHMaxSize : function(){
19565 var cmax = this.maxSize || 10000;
19566 var center = this.layout.center;
19567 return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
19571 getVMaxSize : function(){
19572 var cmax = this.maxSize || 10000;
19573 var center = this.layout.center;
19574 return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
19578 onSplitMove : function(split, newSize){
19579 var s = this.panel.getSize();
19580 this.lastSplitSize = newSize;
19581 if(this.position == 'north' || this.position == 'south'){
19582 this.panel.setSize(s.width, newSize);
19583 this.state.height = newSize;
19585 this.panel.setSize(newSize, s.height);
19586 this.state.width = newSize;
19588 this.layout.layout();
19589 this.panel.saveState();
19594 * Returns a reference to the split bar in use by this region.
19595 * @return {Ext.SplitBar} The split bar
19597 getSplitBar : function(){
19602 destroy : function() {
19611 Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;/**
19612 * @class Ext.layout.FormLayout
19613 * @extends Ext.layout.AnchorLayout
19614 * <p>This layout manager is specifically designed for rendering and managing child Components of
19615 * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
19616 * {@link Ext.form.Field Field}s.</p>
19618 * <p>This layout manager is used when a Container is configured with the <tt>layout:'form'</tt>
19619 * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
19620 * via the new keyword. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
19622 * <p>In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
19623 * (which is configured with FormLayout as its layout class by default) since it also provides built-in
19624 * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.</p>
19626 * <p>A {@link Ext.Container Container} <i>using</i> the FormLayout layout manager (e.g.
19627 * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) can also accept the following
19628 * layout-specific config properties:<div class="mdetail-params"><ul>
19629 * <li><b><tt>{@link Ext.form.FormPanel#hideLabels hideLabels}</tt></b></li>
19630 * <li><b><tt>{@link Ext.form.FormPanel#labelAlign labelAlign}</tt></b></li>
19631 * <li><b><tt>{@link Ext.form.FormPanel#labelPad labelPad}</tt></b></li>
19632 * <li><b><tt>{@link Ext.form.FormPanel#labelSeparator labelSeparator}</tt></b></li>
19633 * <li><b><tt>{@link Ext.form.FormPanel#labelWidth labelWidth}</tt></b></li>
19636 * <p>Any Component (including Fields) managed by FormLayout accepts the following as a config option:
19637 * <div class="mdetail-params"><ul>
19638 * <li><b><tt>{@link Ext.Component#anchor anchor}</tt></b></li>
19641 * <p>Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
19642 * configuring it with a non-null <b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b>. Components configured
19643 * in this way may be configured with the following options which affect the way the FormLayout renders them:
19644 * <div class="mdetail-params"><ul>
19645 * <li><b><tt>{@link Ext.Component#clearCls clearCls}</tt></b></li>
19646 * <li><b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b></li>
19647 * <li><b><tt>{@link Ext.Component#hideLabel hideLabel}</tt></b></li>
19648 * <li><b><tt>{@link Ext.Component#itemCls itemCls}</tt></b></li>
19649 * <li><b><tt>{@link Ext.Component#labelSeparator labelSeparator}</tt></b></li>
19650 * <li><b><tt>{@link Ext.Component#labelStyle labelStyle}</tt></b></li>
19653 * <p>Example usage:</p>
19655 // Required if showing validation messages
19656 Ext.QuickTips.init();
19658 // While you can create a basic Panel with layout:'form', practically
19659 // you should usually use a FormPanel to also get its form functionality
19660 // since it already creates a FormLayout internally.
19661 var form = new Ext.form.FormPanel({
19662 title: 'Form Layout',
19663 bodyStyle: 'padding:15px',
19665 defaultType: 'textfield',
19667 // applied to each contained item
19672 fieldLabel: 'First Name',
19675 {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
19677 fieldLabel: 'Last Name',
19680 fieldLabel: 'Email',
19685 hideLabel: true, // override hideLabels layout config
19695 {@link #labelSeparator}: '~' // superseded by assignment below
19697 // config options applicable to container when layout='form':
19699 labelAlign: 'left', // or 'right' or 'top'
19700 {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
19701 labelWidth: 65, // defaults to 100
19702 labelPad: 8 // defaults to 5, must specify labelWidth to be honored
19706 Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
19709 * @cfg {String} labelSeparator
19710 * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
19711 * of this property at the <b>container</b> level takes precedence.
19713 labelSeparator : ':',
19716 * Read only. The CSS style specification string added to field labels in this layout if not
19717 * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
19719 * @property labelStyle
19723 setContainer : function(ct){
19724 Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
19726 ct.addClass('x-form-label-'+ct.labelAlign);
19730 this.labelStyle = "display:none";
19731 this.elementStyle = "padding-left:0;";
19732 this.labelAdjust = 0;
19734 this.labelSeparator = ct.labelSeparator || this.labelSeparator;
19735 ct.labelWidth = ct.labelWidth || 100;
19736 if(typeof ct.labelWidth == 'number'){
19737 var pad = (typeof ct.labelPad == 'number' ? ct.labelPad : 5);
19738 this.labelAdjust = ct.labelWidth+pad;
19739 this.labelStyle = "width:"+ct.labelWidth+"px;";
19740 this.elementStyle = "padding-left:"+(ct.labelWidth+pad)+'px';
19742 if(ct.labelAlign == 'top'){
19743 this.labelStyle = "width:auto;";
19744 this.labelAdjust = 0;
19745 this.elementStyle = "padding-left:0;";
19751 getLabelStyle: function(s){
19752 var ls = '', items = [this.labelStyle, s];
19753 for (var i = 0, len = items.length; i < len; ++i){
19756 if (ls.substr(-1, 1) != ';'){
19765 * @cfg {Ext.Template} fieldTpl
19766 * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
19767 * the fully wrapped, labeled and styled form Field. Defaults to:</p><pre><code>
19769 '<div class="x-form-item {itemCls}" tabIndex="-1">',
19770 '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
19771 '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
19772 '</div><div class="{clearCls}"></div>',
19776 * <p>This may be specified to produce a different DOM structure when rendering form Fields.</p>
19777 * <p>A description of the properties within the template follows:</p><div class="mdetail-params"><ul>
19778 * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
19779 * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
19780 * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
19781 * supplied at the container level.</div></li>
19782 * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
19783 * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
19784 * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
19785 * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
19786 * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
19787 * field (defaults to <tt>''</tt>)</div></li>
19788 * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
19789 * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
19790 * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
19791 * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
19792 * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
19793 * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
19795 * <p>Also see <tt>{@link #getTemplateArgs}</tt></p>
19799 renderItem : function(c, position, target){
19800 if(c && !c.rendered && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
19801 var args = this.getTemplateArgs(c);
19802 if(typeof position == 'number'){
19803 position = target.dom.childNodes[position] || null;
19806 this.fieldTpl.insertBefore(position, args);
19808 this.fieldTpl.append(target, args);
19810 c.render('x-form-el-'+c.id);
19812 Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
19817 * <p>Provides template arguments for rendering the fully wrapped, labeled and styled form Field.</p>
19818 * <p>This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
19819 * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
19820 * create custom layouts. The properties which must be returned are:</p><div class="mdetail-params"><ul>
19821 * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
19822 * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
19823 * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
19824 * supplied at the container level.</div></li>
19825 * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
19826 * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
19827 * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
19828 * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
19829 * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
19830 * field (defaults to <tt>''</tt>)</div></li>
19831 * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
19832 * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
19833 * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
19834 * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
19835 * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
19836 * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
19838 * @param field The {@link Field Ext.form.Field} being rendered.
19839 * @return An object hash containing the properties required to render the Field.
19841 getTemplateArgs: function(field) {
19842 var noLabelSep = !field.fieldLabel || field.hideLabel;
19845 label: field.fieldLabel,
19846 labelStyle: field.labelStyle||this.labelStyle||'',
19847 elementStyle: this.elementStyle||'',
19848 labelSeparator: noLabelSep ? '' : (typeof field.labelSeparator == 'undefined' ? this.labelSeparator : field.labelSeparator),
19849 itemCls: (field.itemCls||this.container.itemCls||'') + (field.hideLabel ? ' x-hide-label' : ''),
19850 clearCls: field.clearCls || 'x-form-clear-left'
19855 adjustWidthAnchor : function(value, comp){
19856 return value - (comp.isFormField || comp.fieldLabel ? (comp.hideLabel ? 0 : this.labelAdjust) : 0);
19860 isValidParent : function(c, target){
19865 * @property activeItem
19870 Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;/**
\r
19871 * @class Ext.layout.AccordionLayout
\r
19872 * @extends Ext.layout.FitLayout
\r
19873 * <p>This is a layout that contains multiple panels in an expandable accordion style such that only
\r
19874 * <b>one panel can be open at any given time</b>. Each panel has built-in support for expanding and collapsing.
\r
19875 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
\r
19876 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
\r
19877 * <p>Example usage:</p>
\r
19879 var accordion = new Ext.Panel({
\r
19880 title: 'Accordion Layout',
\r
19881 layout:'accordion',
\r
19883 // applied to each contained panel
\r
19884 bodyStyle: 'padding:15px'
\r
19887 // layout-specific configs go here
\r
19888 titleCollapse: false,
\r
19890 activeOnTop: true
\r
19893 title: 'Panel 1',
\r
19894 html: '<p>Panel content!</p>'
\r
19896 title: 'Panel 2',
\r
19897 html: '<p>Panel content!</p>'
\r
19899 title: 'Panel 3',
\r
19900 html: '<p>Panel content!</p>'
\r
19905 Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
\r
19907 * @cfg {Boolean} fill
\r
19908 * True to adjust the active item's height to fill the available space in the container, false to use the
\r
19909 * item's current height, or auto height if not explicitly set (defaults to true).
\r
19913 * @cfg {Boolean} autoWidth
\r
19914 * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true).
\r
19915 * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within
\r
19916 * layouts if they have auto width, so in such cases this config should be set to false.
\r
19918 autoWidth : true,
\r
19920 * @cfg {Boolean} titleCollapse
\r
19921 * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
\r
19922 * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false,
\r
19923 * {@link #hideCollapseTool} should be false also.
\r
19925 titleCollapse : true,
\r
19927 * @cfg {Boolean} hideCollapseTool
\r
19928 * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false).
\r
19929 * When set to true, {@link #titleCollapse} should be true also.
\r
19931 hideCollapseTool : false,
\r
19933 * @cfg {Boolean} collapseFirst
\r
19934 * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
\r
19935 * in the contained panels' title bars, false to render it last (defaults to false).
\r
19937 collapseFirst : false,
\r
19939 * @cfg {Boolean} animate
\r
19940 * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
\r
19941 * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each
\r
19942 * contained panel for this property, set this to undefined at the layout level.
\r
19946 * @cfg {Boolean} sequence
\r
19947 * <b>Experimental</b>. If animate is set to true, this will result in each animation running in sequence.
\r
19949 sequence : false,
\r
19951 * @cfg {Boolean} activeOnTop
\r
19952 * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
\r
19953 * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
\r
19955 activeOnTop : false,
\r
19957 renderItem : function(c){
\r
19958 if(this.animate === false){
\r
19959 c.animCollapse = false;
\r
19961 c.collapsible = true;
\r
19962 if(this.autoWidth){
\r
19963 c.autoWidth = true;
\r
19965 if(this.titleCollapse){
\r
19966 c.titleCollapse = true;
\r
19968 if(this.hideCollapseTool){
\r
19969 c.hideCollapseTool = true;
\r
19971 if(this.collapseFirst !== undefined){
\r
19972 c.collapseFirst = this.collapseFirst;
\r
19974 if(!this.activeItem && !c.collapsed){
\r
19975 this.activeItem = c;
\r
19976 }else if(this.activeItem && this.activeItem != c){
\r
19977 c.collapsed = true;
\r
19979 Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
\r
19980 c.header.addClass('x-accordion-hd');
\r
19981 c.on('beforeexpand', this.beforeExpand, this);
\r
19985 beforeExpand : function(p, anim){
\r
19986 var ai = this.activeItem;
\r
19988 if(this.sequence){
\r
19989 delete this.activeItem;
\r
19990 if (!ai.collapsed){
\r
19991 ai.collapse({callback:function(){
\r
19992 p.expand(anim || true);
\r
19993 }, scope: this});
\r
19997 ai.collapse(this.animate);
\r
20000 this.activeItem = p;
\r
20001 if(this.activeOnTop){
\r
20002 p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
\r
20008 setItemSize : function(item, size){
\r
20009 if(this.fill && item){
\r
20011 this.container.items.each(function(p){
\r
20013 hh += p.header.getHeight();
\r
20016 size.height -= hh;
\r
20017 item.setSize(size);
\r
20022 * Sets the active (expanded) item in the layout.
\r
20023 * @param {String/Number} item The string component id or numeric index of the item to activate
\r
20025 setActiveItem : function(item){
\r
20026 item = this.container.getComponent(item);
\r
20027 if(this.activeItem != item){
\r
20028 if(item.rendered && item.collapsed){
\r
20031 this.activeItem = item;
\r
20037 Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
\r
20039 //backwards compat
\r
20040 Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
\r
20041 * @class Ext.layout.TableLayout
\r
20042 * @extends Ext.layout.ContainerLayout
\r
20043 * <p>This layout allows you to easily render content into an HTML table. The total number of columns can be
\r
20044 * specified, and rowspan and colspan can be used to create complex layouts within the table.
\r
20045 * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
\r
20046 * and should generally not need to be created directly via the new keyword.</p>
\r
20047 * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
\r
20048 * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
\r
20049 * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
\r
20050 * TableLayout can supply the following table-specific config properties:</p>
\r
20052 * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
\r
20053 * <li><b>colspan</b> Applied to the table cell containing the item.</li>
\r
20054 * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
\r
20055 * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
\r
20057 * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
\r
20058 * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
\r
20059 * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
\r
20060 * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
\r
20061 * total column count in the layoutConfig and start adding panels in their natural order from left to right,
\r
20062 * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
\r
20063 * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
\r
20064 * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:</p>
\r
20066 // This code will generate a layout table that is 3 columns by 2 rows
\r
20067 // with some spanning included. The basic layout will be:
\r
20068 // +--------+-----------------+
\r
20070 // | |--------+--------|
\r
20072 // +--------+--------+--------+
\r
20073 var table = new Ext.Panel({
\r
20074 title: 'Table Layout',
\r
20077 // applied to each contained panel
\r
20078 bodyStyle:'padding:20px'
\r
20081 // The total column count must be specified here
\r
20085 html: '<p>Cell A content</p>',
\r
20088 html: '<p>Cell B content</p>',
\r
20091 html: '<p>Cell C content</p>',
\r
20092 cellCls: 'highlight'
\r
20094 html: '<p>Cell D content</p>'
\r
20099 Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
20101 * @cfg {Number} columns
\r
20102 * The total number of columns to create in the table for this layout. If not specified, all Components added to
\r
20103 * this layout will be rendered into a single row using one column per Component.
\r
20107 monitorResize:false,
\r
20110 * @cfg {Object} tableAttrs
\r
20111 * <p>An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
\r
20112 * used to create the layout's <tt><table></tt> element. Example:</p><pre><code>
\r
20129 setContainer : function(ct){
\r
20130 Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
\r
20132 this.currentRow = 0;
\r
20133 this.currentColumn = 0;
\r
20138 onLayout : function(ct, target){
\r
20139 var cs = ct.items.items, len = cs.length, c, i;
\r
20142 target.addClass('x-table-layout-ct');
\r
20144 this.table = target.createChild(
\r
20145 Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
\r
20147 this.renderAll(ct, target);
\r
20151 getRow : function(index){
\r
20152 var row = this.table.tBodies[0].childNodes[index];
\r
20154 row = document.createElement('tr');
\r
20155 this.table.tBodies[0].appendChild(row);
\r
20161 getNextCell : function(c){
\r
20162 var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
\r
20163 var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
\r
20164 for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
\r
20165 if(!this.cells[rowIndex]){
\r
20166 this.cells[rowIndex] = [];
\r
20168 for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
\r
20169 this.cells[rowIndex][colIndex] = true;
\r
20172 var td = document.createElement('td');
\r
20174 td.id = c.cellId;
\r
20176 var cls = 'x-table-layout-cell';
\r
20178 cls += ' ' + c.cellCls;
\r
20180 td.className = cls;
\r
20182 td.colSpan = c.colspan;
\r
20185 td.rowSpan = c.rowspan;
\r
20187 this.getRow(curRow).appendChild(td);
\r
20192 getNextNonSpan: function(colIndex, rowIndex){
\r
20193 var cols = this.columns;
\r
20194 while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
\r
20195 if(cols && colIndex >= cols){
\r
20202 return [colIndex, rowIndex];
\r
20206 renderItem : function(c, position, target){
\r
20207 if(c && !c.rendered){
\r
20208 c.render(this.getNextCell(c));
\r
20209 if(this.extraCls){
\r
20210 var t = c.getPositionEl ? c.getPositionEl() : c;
\r
20211 t.addClass(this.extraCls);
\r
20217 isValidParent : function(c, target){
\r
20222 * @property activeItem
\r
20227 Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
\r
20228 * @class Ext.layout.AbsoluteLayout
\r
20229 * @extends Ext.layout.AnchorLayout
\r
20230 * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.AnchorLayout}</b> and adds the
\r
20231 * ability for x/y positioning using the standard x and y component config options.</p>
\r
20232 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
\r
20233 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
\r
20234 * <p>Example usage:</p>
\r
20236 var form = new Ext.form.FormPanel({
\r
20237 title: 'Absolute Layout',
\r
20238 layout:'absolute',
\r
20240 // layout-specific configs go here
\r
20241 extraCls: 'x-abs-layout-item',
\r
20243 baseCls: 'x-plain',
\r
20244 url:'save-form.php',
\r
20245 defaultType: 'textfield',
\r
20255 anchor:'100%' // anchor width by percentage
\r
20265 anchor: '100%' // anchor width by percentage
\r
20269 xtype: 'textarea',
\r
20271 anchor: '100% 100%' // anchor width and height
\r
20276 Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
\r
20278 extraCls: 'x-abs-layout-item',
\r
20280 onLayout : function(ct, target){
\r
20281 target.position();
\r
20282 this.paddingLeft = target.getPadding('l');
\r
20283 this.paddingTop = target.getPadding('t');
\r
20285 Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
\r
20289 adjustWidthAnchor : function(value, comp){
\r
20290 return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
\r
20294 adjustHeightAnchor : function(value, comp){
\r
20295 return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
\r
20298 * @property activeItem
\r
20302 Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
20304 * @class Ext.layout.BoxLayout
\r
20305 * @extends Ext.layout.ContainerLayout
\r
20306 * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
\r
20308 Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
20310 * @cfg {Object} defaultMargins
\r
20311 * <p>If the individual contained items do not have a <tt>margins</tt>
\r
20312 * property specified, the default margins from this property will be
\r
20313 * applied to each item.</p>
\r
20314 * <br><p>This property may be specified as an object containing margins
\r
20315 * to apply in the format:</p><pre><code>
\r
20317 top: (top margin),
\r
20318 right: (right margin),
\r
20319 bottom: (bottom margin),
\r
20320 left: (left margin)
\r
20322 * <p>This property may also be specified as a string containing
\r
20323 * space-separated, numeric margin values. The order of the sides associated
\r
20324 * with each value matches the way CSS processes margin values:</p>
\r
20325 * <div class="mdetail-params"><ul>
\r
20326 * <li>If there is only one value, it applies to all sides.</li>
\r
20327 * <li>If there are two values, the top and bottom borders are set to the
\r
20328 * first value and the right and left are set to the second.</li>
\r
20329 * <li>If there are three values, the top is set to the first value, the left
\r
20330 * and right are set to the second, and the bottom is set to the third.</li>
\r
20331 * <li>If there are four values, they apply to the top, right, bottom, and
\r
20332 * left, respectively.</li>
\r
20334 * <p>Defaults to:</p><pre><code>
\r
20335 * {top:0, right:0, bottom:0, left:0}
\r
20338 defaultMargins : {left:0,top:0,right:0,bottom:0},
\r
20340 * @cfg {String} padding
\r
20341 * Defaults to <tt>'0'</tt>. Sets the padding to be applied to all child items managed by this
\r
20342 * container's layout.
\r
20345 // documented in subclasses
\r
20349 monitorResize : true,
\r
20350 scrollOffset : 0,
\r
20351 extraCls : 'x-box-item',
\r
20352 ctCls : 'x-box-layout-ct',
\r
20353 innerCls : 'x-box-inner',
\r
20356 isValidParent : function(c, target){
\r
20357 return c.getEl().dom.parentNode == this.innerCt.dom;
\r
20361 onLayout : function(ct, target){
\r
20362 var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm;
\r
20364 if(!this.innerCt){
\r
20365 target.addClass(this.ctCls);
\r
20367 // the innerCt prevents wrapping and shuffling while
\r
20368 // the container is resizing
\r
20369 this.innerCt = target.createChild({cls:this.innerCls});
\r
20370 this.padding = this.parseMargins(this.padding);
\r
20372 this.renderAll(ct, this.innerCt);
\r
20376 renderItem : function(c){
\r
20377 if(typeof c.margins == 'string'){
\r
20378 c.margins = this.parseMargins(c.margins);
\r
20379 }else if(!c.margins){
\r
20380 c.margins = this.defaultMargins;
\r
20382 Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
\r
20385 getTargetSize : function(target){
20386 return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize();
\r
20389 getItems: function(ct){
\r
20391 ct.items.each(function(c){
\r
20392 if(c.isVisible()){
\r
20400 * @property activeItem
\r
20406 * @class Ext.layout.VBoxLayout
\r
20407 * @extends Ext.layout.BoxLayout
\r
20408 * A layout that arranges items vertically
\r
20410 Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
\r
20412 * @cfg {String} align
\r
20413 * Controls how the child items of the container are aligned. Acceptable configuration values for this
\r
20415 * <div class="mdetail-params"><ul>
\r
20416 * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
\r
20417 * at the <b>left</b> side of the container</div></li>
\r
20418 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
\r
20419 * <b>mid-width</b> of the container</div></li>
\r
20420 * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
\r
20421 * the width of the container</div></li>
\r
20422 * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
\r
20423 * the size of the largest item.</div></li>
\r
20426 align : 'left', // left, center, stretch, strechmax
\r
20428 * @cfg {String} pack
\r
20429 * Controls how the child items of the container are packed together. Acceptable configuration values
\r
20430 * for this property are:
\r
20431 * <div class="mdetail-params"><ul>
\r
20432 * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
\r
20433 * <b>top</b> side of container</div></li>
\r
20434 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
\r
20435 * <b>mid-height</b> of container</div></li>
\r
20436 * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
\r
20437 * side of container</div></li>
\r
20441 * @cfg {Number} flex
\r
20442 * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
\r
20443 * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
\r
20444 * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
\r
20445 * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
\r
20446 * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
\r
20450 onLayout : function(ct, target){
\r
20451 Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target);
\r
20454 var cs = this.getItems(ct), cm, ch, margin,
\r
20455 size = this.getTargetSize(target),
\r
20456 w = size.width - target.getPadding('lr') - this.scrollOffset,
\r
20457 h = size.height - target.getPadding('tb'),
\r
20458 l = this.padding.left, t = this.padding.top,
\r
20459 isStart = this.pack == 'start',
\r
20460 isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
\r
20461 stretchWidth = w - (this.padding.left + this.padding.right),
\r
20468 Ext.each(cs, function(c){
\r
20470 totalFlex += c.flex || 0;
\r
20471 ch = c.getHeight();
\r
20472 margin = cm.top + cm.bottom;
\r
20473 extraHeight += ch + margin;
\r
20474 flexHeight += margin + (c.flex ? 0 : ch);
\r
20475 maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right);
\r
20477 extraHeight = h - extraHeight - this.padding.top - this.padding.bottom;
\r
20479 var innerCtWidth = maxWidth + this.padding.left + this.padding.right;
\r
20480 switch(this.align){
\r
20482 this.innerCt.setSize(w, h);
\r
20484 case 'stretchmax':
\r
20486 this.innerCt.setSize(innerCtWidth, h);
\r
20489 this.innerCt.setSize(w = Math.max(w, innerCtWidth), h);
\r
20493 var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight),
\r
20494 leftOver = availHeight,
\r
20498 availableWidth = Math.max(0, w - this.padding.left - this.padding.right);
\r
20501 Ext.each(cs, function(c){
\r
20502 if(isStart && c.flex){
\r
20503 ch = Math.floor(availHeight * (c.flex / totalFlex));
\r
20505 heights.push(ch);
\r
20509 if(this.pack == 'center'){
\r
20510 t += extraHeight ? extraHeight / 2 : 0;
\r
20511 }else if(this.pack == 'end'){
\r
20512 t += extraHeight;
\r
20514 Ext.each(cs, function(c){
\r
20517 c.setPosition(l + cm.left, t);
\r
20518 if(isStart && c.flex){
\r
20519 ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0));
\r
20521 restore.push(c.getWidth());
\r
20523 c.setSize(availableWidth, ch);
\r
20525 ch = c.getHeight();
\r
20527 t += ch + cm.bottom;
\r
20531 Ext.each(cs, function(c){
\r
20533 if(this.align == 'stretch'){
\r
20534 c.setWidth((stretchWidth - (cm.left + cm.right)).constrain(
\r
20535 c.minWidth || 0, c.maxWidth || 1000000));
\r
20536 }else if(this.align == 'stretchmax'){
\r
20537 c.setWidth((maxWidth - (cm.left + cm.right)).constrain(
\r
20538 c.minWidth || 0, c.maxWidth || 1000000));
\r
20540 if(this.align == 'center'){
\r
20541 var diff = availableWidth - (c.getWidth() + cm.left + cm.right);
\r
20543 c.setPosition(l + cm.left + (diff/2), c.y);
\r
20546 if(isStart && c.flex){
\r
20547 c.setWidth(restore[idx++]);
\r
20553 * @property activeItem
\r
20558 Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
\r
20561 * @class Ext.layout.HBoxLayout
\r
20562 * @extends Ext.layout.BoxLayout
\r
20563 * A layout that arranges items horizontally
\r
20565 Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
\r
20567 * @cfg {String} align
\r
20568 * Controls how the child items of the container are aligned. Acceptable configuration values for this
\r
20570 * <div class="mdetail-params"><ul>
\r
20571 * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
\r
20572 * at the <b>left</b> side of the container</div></li>
\r
20573 * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically at the
\r
20574 * <b>mid-height</b> of the container</div></li>
\r
20575 * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
\r
20576 * the height of the container</div></li>
\r
20577 * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
\r
20578 * the size of the largest item.</div></li>
\r
20580 align : 'top', // top, middle, stretch, strechmax
\r
20582 * @cfg {String} pack
\r
20583 * Controls how the child items of the container are packed together. Acceptable configuration values
\r
20584 * for this property are:
\r
20585 * <div class="mdetail-params"><ul>
\r
20586 * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
\r
20587 * <b>left</b> side of container</div></li>
\r
20588 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
\r
20589 * <b>mid-width</b> of container</div></li>
\r
20590 * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
\r
20591 * side of container</div></li>
\r
20595 * @cfg {Number} flex
\r
20596 * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
\r
20597 * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
\r
20598 * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
\r
20599 * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
\r
20600 * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
\r
20604 onLayout : function(ct, target){
\r
20605 Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target);
\r
20607 var cs = this.getItems(ct), cm, cw, margin,
\r
20608 size = this.getTargetSize(target),
\r
20609 w = size.width - target.getPadding('lr') - this.scrollOffset,
\r
20610 h = size.height - target.getPadding('tb'),
\r
20611 l = this.padding.left, t = this.padding.top,
\r
20612 isStart = this.pack == 'start',
\r
20613 isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
\r
20614 stretchHeight = h - (this.padding.top + this.padding.bottom),
\r
20621 Ext.each(cs, function(c){
\r
20623 totalFlex += c.flex || 0;
\r
20624 cw = c.getWidth();
\r
20625 margin = cm.left + cm.right;
\r
20626 extraWidth += cw + margin;
\r
20627 flexWidth += margin + (c.flex ? 0 : cw);
\r
20628 maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom);
\r
20630 extraWidth = w - extraWidth - this.padding.left - this.padding.right;
\r
20632 var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom;
\r
20633 switch(this.align){
\r
20635 this.innerCt.setSize(w, h);
\r
20637 case 'stretchmax':
\r
20639 this.innerCt.setSize(w, innerCtHeight);
\r
20642 this.innerCt.setSize(w, h = Math.max(h, innerCtHeight));
\r
20647 var availWidth = Math.max(0, w - this.padding.left - this.padding.right - flexWidth),
\r
20648 leftOver = availWidth,
\r
20652 availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom);
\r
20655 Ext.each(cs, function(c){
\r
20656 if(isStart && c.flex){
\r
20657 cw = Math.floor(availWidth * (c.flex / totalFlex));
\r
20663 if(this.pack == 'center'){
\r
20664 l += extraWidth ? extraWidth / 2 : 0;
\r
20665 }else if(this.pack == 'end'){
\r
20668 Ext.each(cs, function(c){
\r
20671 c.setPosition(l, t + cm.top);
\r
20672 if(isStart && c.flex){
\r
20673 cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0));
\r
20675 restore.push(c.getHeight());
\r
20677 c.setSize(cw, availableHeight);
\r
20679 cw = c.getWidth();
\r
20681 l += cw + cm.right;
\r
20685 Ext.each(cs, function(c){
\r
20686 var cm = c.margins;
\r
20687 if(this.align == 'stretch'){
\r
20688 c.setHeight((stretchHeight - (cm.top + cm.bottom)).constrain(
\r
20689 c.minHeight || 0, c.maxHeight || 1000000));
\r
20690 }else if(this.align == 'stretchmax'){
\r
20691 c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain(
\r
20692 c.minHeight || 0, c.maxHeight || 1000000));
\r
20694 if(this.align == 'middle'){
\r
20695 var diff = availableHeight - (c.getHeight() + cm.top + cm.bottom);
\r
20697 c.setPosition(c.x, t + cm.top + (diff/2));
\r
20700 if(isStart && c.flex){
\r
20701 c.setHeight(restore[idx++]);
\r
20708 * @property activeItem
\r
20713 Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
20715 * @class Ext.Viewport
\r
20716 * @extends Ext.Container
\r
20717 * <p>A specialized container representing the viewable application area (the browser viewport).</p>
\r
20718 * <p>The Viewport renders itself to the document body, and automatically sizes itself to the size of
\r
20719 * the browser viewport and manages window resizing. There may only be one Viewport created
\r
20720 * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s
\r
20721 * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
\r
20722 * method of any of its child Panels may themselves have a layout.</p>
\r
20723 * <p>The Viewport does not provide scrolling, so child Panels within the Viewport should provide
\r
20724 * for scrolling if needed using the {@link #autoScroll} config.</p>
\r
20725 * <p>An example showing a classic application border layout:</p><pre><code>
\r
20726 new Ext.Viewport({
\r
20727 layout: 'border',
\r
20730 html: '<h1 class="x-panel-header">Page Title</h1>',
\r
20731 autoHeight: true,
\r
20733 margins: '0 0 5 0'
\r
20736 collapsible: true,
\r
20737 title: 'Navigation',
\r
20739 // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout}
\r
20742 title: 'Title for Panel',
\r
20743 collapsible: true,
\r
20744 html: 'Information goes here',
\r
20750 title: 'Title for the Grid Panel',
\r
20751 collapsible: true,
\r
20755 // remaining grid configuration not shown ...
\r
20756 // notice that the GridPanel is added directly as the region
\r
20757 // it is not "overnested" inside another Panel
\r
20759 region: 'center',
\r
20760 xtype: 'tabpanel', // TabPanel itself has no title
\r
20762 title: 'Default Tab',
\r
20763 html: 'The first tab\'s content. Others may be added dynamically'
\r
20769 * Create a new Viewport
\r
20770 * @param {Object} config The config object
\r
20771 * @xtype viewport
\r
20773 Ext.Viewport = Ext.extend(Ext.Container, {
\r
20775 * Privatize config options which, if used, would interfere with the
\r
20776 * correct operation of the Viewport as the sole manager of the
\r
20777 * layout of the document body.
\r
20780 * @cfg {Mixed} applyTo @hide
\r
20783 * @cfg {Boolean} allowDomMove @hide
\r
20786 * @cfg {Boolean} hideParent @hide
\r
20789 * @cfg {Mixed} renderTo @hide
\r
20792 * @cfg {Boolean} hideParent @hide
\r
20795 * @cfg {Number} height @hide
\r
20798 * @cfg {Number} width @hide
\r
20801 * @cfg {Boolean} autoHeight @hide
\r
20804 * @cfg {Boolean} autoWidth @hide
\r
20807 * @cfg {Boolean} deferHeight @hide
\r
20810 * @cfg {Boolean} monitorResize @hide
\r
20812 initComponent : function() {
\r
20813 Ext.Viewport.superclass.initComponent.call(this);
\r
20814 document.getElementsByTagName('html')[0].className += ' x-viewport';
\r
20815 this.el = Ext.getBody();
\r
20816 this.el.setHeight = Ext.emptyFn;
\r
20817 this.el.setWidth = Ext.emptyFn;
\r
20818 this.el.setSize = Ext.emptyFn;
\r
20819 this.el.dom.scroll = 'no';
\r
20820 this.allowDomMove = false;
\r
20821 this.autoWidth = true;
\r
20822 this.autoHeight = true;
\r
20823 Ext.EventManager.onWindowResize(this.fireResize, this);
\r
20824 this.renderTo = this.el;
\r
20827 fireResize : function(w, h){
\r
20828 this.fireEvent('resize', this, w, h, w, h);
\r
20831 Ext.reg('viewport', Ext.Viewport);/**
20833 * @extends Ext.Container
20834 * <p>Panel is a container that has specific functionality and structural components that make
20835 * it the perfect building block for application-oriented user interfaces.</p>
20836 * <p>Panels are, by virtue of their inheritance from {@link Ext.Container}, capable
20837 * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.</p>
20838 * <p>When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components
20839 * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
20840 * those child elements need to be sized using one of Ext's built-in <tt><b>{@link Ext.Container#layout layout}</b></tt> schemes. By
20841 * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders
20842 * child components, appending them one after the other inside the Container, and <b>does not apply any sizing</b>
20844 * <p>A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
20845 * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
20846 * information).</p>
20847 * <p>Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with
20848 * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized
20849 * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the
20850 * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.</p>
20852 * @param {Object} config The config object
20855 Ext.Panel = Ext.extend(Ext.Container, {
20857 * The Panel's header {@link Ext.Element Element}. Read-only.
20858 * <p>This Element is used to house the {@link #title} and {@link #tools}</p>
20859 * <br><p><b>Note</b>: see the Note for <tt>{@link Ext.Component#el el} also.</p>
20860 * @type Ext.Element
20864 * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content.
20865 * The content may be specified in the {@link #html} config, or it may be loaded using the
20866 * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only.
20867 * <p>If this is used to load visible HTML elements in either way, then
20868 * the Panel may not be used as a Layout for hosting nested Panels.</p>
20869 * <p>If this Panel is intended to be used as the host of a Layout (See {@link #layout}
20870 * then the body Element must not be loaded or changed - it is under the control
20871 * of the Panel's Layout.
20872 * <br><p><b>Note</b>: see the Note for <tt>{@link Ext.Component#el el} also.</p>
20873 * @type Ext.Element
20877 * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements
20878 * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only.
20879 * @type Ext.Element
20883 * True if this panel is collapsed. Read-only.
20885 * @property collapsed
20888 * @cfg {Object} bodyCfg
20889 * <p>A {@link Ext.DomHelper DomHelper} element specification object may be specified for any
20890 * Panel Element.</p>
20891 * <p>By default, the Default element in the table below will be used for the html markup to
20892 * create a child element with the commensurate Default class name (<tt>baseCls</tt> will be
20893 * replaced by <tt>{@link #baseCls}</tt>):</p>
20895 * Panel Default Default Custom Additional Additional
20896 * Element element class element class style
20897 * ======== ========================== ========= ============== ===========
20898 * {@link #header} div {@link #baseCls}+'-header' {@link #headerCfg} headerCssClass headerStyle
20899 * {@link #bwrap} div {@link #baseCls}+'-bwrap' {@link #bwrapCfg} bwrapCssClass bwrapStyle
20900 * + tbar div {@link #baseCls}+'-tbar' {@link #tbarCfg} tbarCssClass tbarStyle
20901 * + {@link #body} div {@link #baseCls}+'-body' {@link #bodyCfg} {@link #bodyCssClass} {@link #bodyStyle}
20902 * + bbar div {@link #baseCls}+'-bbar' {@link #bbarCfg} bbarCssClass bbarStyle
20903 * + {@link #footer} div {@link #baseCls}+'-footer' {@link #footerCfg} footerCssClass footerStyle
20905 * <p>Configuring a Custom element may be used, for example, to force the {@link #body} Element
20906 * to use a different form of markup than is created by default. An example of this might be
20907 * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as
20908 * a header, or forcing centering of all Panel content by having the body be a <center>
20912 title: 'Message Title',
20913 renderTo: Ext.getBody(),
20914 width: 200, height: 130,
20917 cls: 'x-panel-body', // Default class not applied if Custom element specified
20922 cls: 'x-panel-footer' // same as the Default class
20923 html: 'footer html'
20925 footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
20926 footerStyle: 'background-color:red' // see {@link #bodyStyle}
20929 * <p>The example above also explicitly creates a <tt>{@link #footer}</tt> with custom markup and
20930 * styling applied.</p>
20933 * @cfg {Object} headerCfg
20934 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
20935 * of this Panel's {@link #header} Element. See <tt>{@link #bodyCfg}</tt> also.</p>
20938 * @cfg {Object} bwrapCfg
20939 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
20940 * of this Panel's {@link #bwrap} Element. See <tt>{@link #bodyCfg}</tt> also.</p>
20943 * @cfg {Object} tbarCfg
20944 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
20945 * of this Panel's {@link #tbar} Element. See <tt>{@link #bodyCfg}</tt> also.</p>
20948 * @cfg {Object} bbarCfg
20949 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
20950 * of this Panel's {@link #bbar} Element. See <tt>{@link #bodyCfg}</tt> also.</p>
20953 * @cfg {Object} footerCfg
20954 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
20955 * of this Panel's {@link #footer} Element. See <tt>{@link #bodyCfg}</tt> also.</p>
20958 * @cfg {Boolean} closable
20959 * Panels themselves do not directly support being closed, but some Panel subclasses do (like
20960 * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify <tt>true</tt>
20961 * to enable closing in such situations. Defaults to <tt>false</tt>.
20964 * The Panel's footer {@link Ext.Element Element}. Read-only.
20965 * <p>This Element is used to house the Panel's <tt>{@link #buttons}</tt> or <tt>{@link #fbar}</tt>.</p>
20966 * <br><p><b>Note</b>: see the Note for <tt>{@link Ext.Component#el el} also.</p>
20967 * @type Ext.Element
20971 * @cfg {Mixed} applyTo
20972 * <p>The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in
20973 * the document that specifies some panel-specific structural markup. When <tt>applyTo</tt> is used,
20974 * constituent parts of the panel can be specified by CSS class name within the main element, and the panel
20975 * will automatically create those components from that markup. Any required components not specified in the
20976 * markup will be autogenerated if necessary.</p>
20977 * <p>The following class names are supported (baseCls will be replaced by {@link #baseCls}):</p>
20978 * <ul><li>baseCls + '-header'</li>
20979 * <li>baseCls + '-header-text'</li>
20980 * <li>baseCls + '-bwrap'</li>
20981 * <li>baseCls + '-tbar'</li>
20982 * <li>baseCls + '-body'</li>
20983 * <li>baseCls + '-bbar'</li>
20984 * <li>baseCls + '-footer'</li></ul>
20985 * <p>Using this config, a call to render() is not required. If applyTo is specified, any value passed for
20986 * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the
20987 * panel's container.</p>
20990 * @cfg {Object/Array} tbar
20991 * <p>The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
20992 * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
20993 * To access the top toolbar after render, use {@link #getTopToolbar}.</p>
20994 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> be updated by a load
20995 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
20996 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
20997 * submission parameters are collected from the DOM tree.</p>
21000 * @cfg {Object/Array} bbar
21001 * <p>The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
21002 * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
21003 * To access the bottom toolbar after render, use {@link #getBottomToolbar}.</p>
21004 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not<b> be updated by a load
21005 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
21006 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
21007 * submission parameters are collected from the DOM tree.</p>
21009 /** @cfg {Object/Array} fbar
21010 * <p>A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of
21011 * {@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.</p>
21012 * <p>After render, the <code>fbar</code> property will be an {@link Ext.Toolbar Toolbar} instance.</p>
21013 * <p>If <tt>{@link #buttons}</tt> are specified, they will supersede the <tt>fbar</tt> configuration property.</p>
21014 * The Panel's <tt>{@link #buttonAlign}</tt> configuration affects the layout of these items, for example:
21016 var w = new Ext.Window({
21019 bbar: new Ext.Toolbar({
21026 {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use "-", and "->"
21027 // to control the alignment of fbar items
21035 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not<b> be updated by a load
21036 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
21037 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
21038 * submission parameters are collected from the DOM tree.</p>
21041 * @cfg {Boolean} header
21042 * <tt>true</tt> to create the Panel's header element explicitly, <tt>false</tt> to skip creating
21043 * it. If a <tt>{@link #title}</tt> is set the header will be created automatically, otherwise it will not.
21044 * If a <tt>{@link #title}</tt> is set but <tt>header</tt> is explicitly set to <tt>false</tt>, the header
21045 * will not be rendered.
21048 * @cfg {Boolean} footer
21049 * <tt>true</tt> to create the footer element explicitly, false to skip creating it. The footer
21050 * will be created automatically if <tt>{@link #buttons}</tt> or a <tt>{@link #fbar}</tt> have
21051 * been configured. See <tt>{@link #bodyCfg}</tt> for an example.
21054 * @cfg {String} title
21055 * The title text to be used as innerHTML (html tags are accepted) to display in the panel
21056 * <tt>{@link #header}</tt> (defaults to ''). When a <tt>title</tt> is specified the
21057 * <tt>{@link #header}</tt> element will automatically be created and displayed unless
21058 * {@link #header} is explicitly set to <tt>false</tt>. If you do not want to specify a
21059 * <tt>title</tt> at config time, but you may want one later, you must either specify a non-empty
21060 * <tt>title</tt> (a blank space ' ' will do) or <tt>header:true</tt> so that the container
21061 * element will get created.
21064 * @cfg {Array} buttons
21065 * <tt>buttons</tt> will be used as <tt>{@link Ext.Container#items items}</tt> for the toolbar in
21066 * the footer (<tt>{@link #fbar}</tt>). Typically the value of this configuration property will be
21067 * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects.
21068 * If an item is configured with <tt>minWidth</tt> or the Panel is configured with <tt>minButtonWidth</tt>,
21069 * that width will be applied to the item.
21072 * @cfg {Object/String/Function} autoLoad
21073 * A valid url spec according to the Updater {@link Ext.Updater#update} method.
21074 * If autoLoad is not null, the panel will attempt to load its contents
21075 * immediately upon render.<p>
21076 * The URL will become the default URL for this panel's {@link #body} element,
21077 * so it may be {@link Ext.Element#refresh refresh}ed at any time.</p>
21080 * @cfg {Boolean} frame
21081 * <tt>false</tt> by default to render with plain 1px square borders. <tt>true</tt> to render with
21082 * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}).
21083 * <p>The template generated for each condition is depicted below:</p><pre><code>
21086 <div id="developer-specified-id-goes-here" class="x-panel">
21088 <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
21090 <div class="x-panel-bwrap">
21091 <div class="x-panel-body"><p>html value goes here</p></div>
21095 // frame = true (create 9 elements)
21096 <div id="developer-specified-id-goes-here" class="x-panel">
21097 <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
21098 <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
21099 </div></div></div>
21101 <div class="x-panel-bwrap">
21102 <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
21103 <div class="x-panel-body"><p>html value goes here</p></div>
21104 </div></div></div>
21106 <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
21107 </div></div></div>
21112 * @cfg {Boolean} border
21113 * True to display the borders of the panel's body element, false to hide them (defaults to true). By default,
21114 * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false.
21117 * @cfg {Boolean} bodyBorder
21118 * True to display an interior border on the body element of the panel, false to hide it (defaults to true).
21119 * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display
21120 * as a 1px wide inset border, giving the entire body element an inset appearance.
21123 * @cfg {String/Object/Function} bodyCssClass
21124 * Additional css class selector to be applied to the {@link #body} element in the format expected by
21125 * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}.
21128 * @cfg {String/Object/Function} bodyStyle
21129 * Custom CSS styles to be applied to the {@link #body} element in the format expected by
21130 * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}.
21133 * @cfg {String} iconCls
21134 * The CSS class selector that specifies a background image to be used as the header icon (defaults to '').
21135 * <p>An example of specifying a custom icon class would be something like:
21137 // specify the property in the config for the class:
21141 // css class that specifies background image to be used as the icon image:
21142 .my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
21146 * @cfg {Boolean} collapsible
21147 * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into
21148 * the header tool button area, false to keep the panel statically sized with no button (defaults to false).
21151 * @cfg {Array} tools
21152 * An array of tool button configs to be added to the header tool area. When rendered, each tool is
21153 * stored as an {@link Ext.Element Element} referenced by a public property called <tt><b></b>tools.<i><tool-type></i></tt>
21154 * <p>Each tool config may contain the following properties:
21155 * <div class="mdetail-params"><ul>
21156 * <li><b>id</b> : String<div class="sub-desc"><b>Required.</b> The type
21157 * of tool to create. By default, this assigns a CSS class of the form <tt>x-tool-<i><tool-type></i></tt> to the
21158 * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below.
21159 * The developer may implement custom tools by supplying alternate CSS rules and background images:
21161 * <div class="x-tool x-tool-toggle" style="float:left; margin-right:5;"> </div><div><tt> toggle</tt> (Created by default when {@link #collapsible} is <tt>true</tt>)</div>
21162 * <div class="x-tool x-tool-close" style="float:left; margin-right:5;"> </div><div><tt> close</tt></div>
21163 * <div class="x-tool x-tool-minimize" style="float:left; margin-right:5;"> </div><div><tt> minimize</tt></div>
21164 * <div class="x-tool x-tool-maximize" style="float:left; margin-right:5;"> </div><div><tt> maximize</tt></div>
21165 * <div class="x-tool x-tool-restore" style="float:left; margin-right:5;"> </div><div><tt> restore</tt></div>
21166 * <div class="x-tool x-tool-gear" style="float:left; margin-right:5;"> </div><div><tt> gear</tt></div>
21167 * <div class="x-tool x-tool-pin" style="float:left; margin-right:5;"> </div><div><tt> pin</tt></div>
21168 * <div class="x-tool x-tool-unpin" style="float:left; margin-right:5;"> </div><div><tt> unpin</tt></div>
21169 * <div class="x-tool x-tool-right" style="float:left; margin-right:5;"> </div><div><tt> right</tt></div>
21170 * <div class="x-tool x-tool-left" style="float:left; margin-right:5;"> </div><div><tt> left</tt></div>
21171 * <div class="x-tool x-tool-up" style="float:left; margin-right:5;"> </div><div><tt> up</tt></div>
21172 * <div class="x-tool x-tool-down" style="float:left; margin-right:5;"> </div><div><tt> down</tt></div>
21173 * <div class="x-tool x-tool-refresh" style="float:left; margin-right:5;"> </div><div><tt> refresh</tt></div>
21174 * <div class="x-tool x-tool-minus" style="float:left; margin-right:5;"> </div><div><tt> minus</tt></div>
21175 * <div class="x-tool x-tool-plus" style="float:left; margin-right:5;"> </div><div><tt> plus</tt></div>
21176 * <div class="x-tool x-tool-help" style="float:left; margin-right:5;"> </div><div><tt> help</tt></div>
21177 * <div class="x-tool x-tool-search" style="float:left; margin-right:5;"> </div><div><tt> search</tt></div>
21178 * <div class="x-tool x-tool-save" style="float:left; margin-right:5;"> </div><div><tt> save</tt></div>
21179 * <div class="x-tool x-tool-print" style="float:left; margin-right:5;"> </div><div><tt> print</tt></div>
21181 * <li><b>handler</b> : Function<div class="sub-desc"><b>Required.</b> The function to
21182 * call when clicked. Arguments passed are:<ul>
21183 * <li><b>event</b> : Ext.EventObject<div class="sub-desc">The click event.</div></li>
21184 * <li><b>toolEl</b> : Ext.Element<div class="sub-desc">The tool Element.</div></li>
21185 * <li><b>panel</b> : Ext.Panel<div class="sub-desc">The host Panel</div></li>
21186 * <li><b>tc</b> : Ext.Panel<div class="sub-desc">The tool configuration object</div></li>
21188 * <li><b>stopEvent</b> : Boolean<div class="sub-desc">Defaults to true. Specify as false to allow click event to propagate.</div></li>
21189 * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the handler.</div></li>
21190 * <li><b>qtip</b> : String/Object<div class="sub-desc">A tip string, or
21191 * a config argument to {@link Ext.QuickTip#register}</div></li>
21192 * <li><b>hidden</b> : Boolean<div class="sub-desc">True to initially render hidden.</div></li>
21193 * <li><b>on</b> : Object<div class="sub-desc">A listener config object specifiying
21194 * event listeners in the format of an argument to {@link #addListener}</div></li>
21196 * <p>Note that, apart from the toggle tool which is provided when a panel is collapsible, these
21197 * tools only provide the visual button. Any required functionality must be provided by adding
21198 * handlers that implement the necessary behavior.</p>
21199 * <p>Example usage:</p>
21203 qtip: 'Refresh form Data',
21205 handler: function(event, toolEl, panel){
21212 handler: function(event, toolEl, panel){
21217 * <p>For the custom id of <tt>'help'</tt> define two relevant css classes with a link to
21218 * a 15x15 image:</p>
21220 .x-tool-help {background-image: url(images/help.png);}
21221 .x-tool-help-over {background-image: url(images/help_over.png);}
21222 // if using an image sprite:
21223 .x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
21224 .x-tool-help-over {background-position:-15px 0;}
21228 * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
21229 * <p>A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:</p><pre><code>
21230 new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>')</code></pre>
21231 * <p>This may may be overridden to provide a custom DOM structure for tools based upon a more
21232 * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array)
21233 * as specified in {@link #tools}. In the following example an <a> tag is used to provide a
21234 * visual indication when hovering over the tool:</p><pre><code>
21235 var win = new Ext.Window({
21238 href: '/MyPdfDoc.pdf'
21240 toolTemplate: new Ext.XTemplate(
21241 '<tpl if="id==\'download\'">',
21242 '<a class="x-tool x-tool-pdf" href="{href}"></a>',
21244 '<tpl if="id!=\'download\'">',
21245 '<div class="x-tool x-tool-{id}">&#160;</div>',
21252 * <p>Note that the CSS class "x-tool-pdf" should have an associated style rule which provides an
21253 * appropriate background image, something like:</p>
21255 a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
21259 * @cfg {Boolean} hideCollapseTool
21260 * <tt>true</tt> to hide the expand/collapse toggle button when <code>{@link #collapsible} == true</code>,
21261 * <tt>false</tt> to display it (defaults to <tt>false</tt>).
21264 * @cfg {Boolean} titleCollapse
21265 * <tt>true</tt> to allow expanding and collapsing the panel (when <tt>{@link #collapsible} = true</tt>)
21266 * by clicking anywhere in the header bar, <tt>false</tt>) to allow it only by clicking to tool button
21267 * (defaults to <tt>false</tt>)). If this panel is a child item of a border layout also see the
21268 * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
21269 * <tt>{@link Ext.layout.BorderLayout.Region#floatable floatable}</tt> config option.
21272 * @cfg {Boolean} autoScroll
21273 * <tt>true</tt> to use overflow:'auto' on the panel's body element and show scroll bars automatically when
21274 * necessary, <tt>false</tt> to clip any overflowing content (defaults to <tt>false</tt>).
21277 * @cfg {Mixed} floating
21278 * <p>This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this
21279 * configuration property are:</p><div class="mdetail-params"><ul>
21280 * <li><b><tt>false</tt></b> : <b>Default.</b><div class="sub-desc">Display the panel inline where it is
21281 * rendered.</div></li>
21282 * <li><b><tt>true</tt></b> : <div class="sub-desc">Float the panel (absolute position it with automatic
21283 * shimming and shadow).<ul>
21284 * <div class="sub-desc">Setting floating to true will create an Ext.Layer for this panel and display the
21285 * panel at negative offsets so that it is hidden.</div>
21286 * <div class="sub-desc">Since the panel will be absolute positioned, the position must be set explicitly
21287 * <i>after</i> render (e.g., <tt>myPanel.setPosition(100,100);</tt>).</div>
21288 * <div class="sub-desc"><b>Note</b>: when floating a panel you should always assign a fixed width,
21289 * otherwise it will be auto width and will expand to fill to the right edge of the viewport.</div>
21291 * <li><b><tt>{@link Ext.Layer object}</tt></b> : <div class="sub-desc">The specified object will be used
21292 * as the configuration object for the {@link Ext.Layer} that will be created.</div></li>
21296 * @cfg {Boolean/String} shadow
21297 * <tt>true</tt> (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the
21298 * panel, <tt>false</tt> to display no shadow (defaults to <tt>'sides'</tt>). Note that this option
21299 * only applies when <tt>{@link #floating} = true</tt>.
21302 * @cfg {Number} shadowOffset
21303 * The number of pixels to offset the shadow if displayed (defaults to <tt>4</tt>). Note that this
21304 * option only applies when <tt>{@link #floating} = true</tt>.
21307 * @cfg {Boolean} shim
21308 * <tt>false</tt> to disable the iframe shim in browsers which need one (defaults to <tt>true</tt>).
21309 * Note that this option only applies when <tt>{@link #floating} = true</tt>.
21312 * @cfg {String/Object} html
21313 * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the panel's body
21314 * content (defaults to ''). The HTML content is added by the Panel's {@link #afterRender} method,
21315 * and so the document will not contain this HTML at the time the {@link #render} event is fired.
21316 * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
21319 * @cfg {String} contentEl
21320 * <p>Specify the <tt>id</tt> of an existing HTML node to use as the panel's body content
21321 * (defaults to '').</p><div><ul>
21322 * <li><b>Description</b> : <ul>
21323 * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the body
21324 * of a new panel (it simply moves the specified DOM element into the body element of the Panel
21325 * <i>when the Panel is rendered</i> to use as the content (it is not going to be the
21326 * actual panel itself).</div>
21328 * <li><b>Notes</b> : <ul>
21329 * <div class="sub-desc">The specified HTML Element is appended to the Panel's {@link #body} Element by the
21330 * Panel's {@link #afterRender} method <i>after any configured {@link #html HTML} has
21331 * been inserted</i>, and so the document will not contain this HTML at the time the
21332 * {@link #render} event is fired.</div>
21333 * <div class="sub-desc">The specified HTML element used will not participate in any layout scheme that the
21334 * Panel may use. It's just HTML. Layouts operate on child items.</div>
21335 * <div class="sub-desc">Add either the <tt>x-hidden</tt> or the <tt>x-hide-display</tt> CSS class to
21336 * prevent a brief flicker of the content before it is rendered to the panel.</div>
21341 * @cfg {Object/Array} keys
21342 * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding}
21343 * used to assign custom key handling to this panel (defaults to <tt>null</tt>).
21346 * @cfg {Boolean/Object} draggable
21347 * <p><tt>true</tt> to enable dragging of this Panel (defaults to <tt>false</tt>).</p>
21348 * <p>For custom drag/drop implementations, an <b>Ext.Panel.DD</b> config could also be passed
21349 * in this config instead of <tt>true</tt>. Ext.Panel.DD is an internal, undocumented class which
21350 * moves a proxy Element around in place of the Panel's element, but provides no other behaviour
21351 * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be
21352 * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.:
21358 renderTo: Ext.getBody(),
21364 // Config option of Ext.Panel.DD class.
21365 // It's a floating Panel, so do not show a placeholder proxy in the original position.
21366 insertProxy: false,
21368 // Called for each mousemove event while dragging the DD object.
21369 onDrag : function(e){
21370 // Record the x,y position of the drag proxy so that we can
21371 // position the Panel at end of drag.
21372 var pel = this.proxy.getEl();
21373 this.x = pel.getLeft(true);
21374 this.y = pel.getTop(true);
21376 // Keep the Shadow aligned if there is one.
21377 var s = this.panel.getEl().shadow;
21379 s.realign(this.x, this.y, pel.getWidth(), pel.getHeight());
21383 // Called on the mouseup event.
21384 endDrag : function(e){
21385 this.panel.setPosition(this.x, this.y);
21392 * @cfg {String} tabTip
21393 * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over
21394 * the tab of a Ext.Panel which is an item of a {@link Ext.TabPanel}. {@link Ext.QuickTips}.init()
21395 * must be called in order for the tips to render.
21398 * @cfg {Boolean} disabled
21399 * Render this panel disabled (default is <tt>false</tt>). An important note when using the disabled
21400 * config on panels is that IE will often fail to initialize the disabled mask element correectly if
21401 * the panel's layout has not yet completed by the time the Panel is disabled during the render process.
21402 * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize
21403 * the disabled state:
21412 single: true // important, as many layouts can occur
21419 * @cfg {Boolean} autoHeight
21420 * <tt>true</tt> to use height:'auto', <tt>false</tt> to use fixed height (defaults to <tt>false</tt>).
21421 * <b>Note</b>: Setting <tt>autoHeight:true</tt> means that the browser will manage the panel's height
21422 * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that
21423 * manages dimensions (<tt>fit</tt>, <tt>border</tt>, etc.) then setting <tt>autoHeight:true</tt>
21424 * can cause issues with scrolling and will not generally work as expected since the panel will take
21425 * on the height of its contents rather than the height required by the Ext layout.
21430 * @cfg {String} baseCls
21431 * The base CSS class to apply to this panel's element (defaults to <tt>'x-panel'</tt>).
21432 * <p>Another option available by default is to specify <tt>'x-plain'</tt> which strips all styling
21433 * except for required attributes for Ext layouts to function (e.g. overflow:hidden).
21434 * See <tt>{@link #unstyled}</tt> also.</p>
21436 baseCls : 'x-panel',
21438 * @cfg {String} collapsedCls
21439 * A CSS class to add to the panel's element after it has been collapsed (defaults to
21440 * <tt>'x-panel-collapsed'</tt>).
21442 collapsedCls : 'x-panel-collapsed',
21444 * @cfg {Boolean} maskDisabled
21445 * <tt>true</tt> to mask the panel when it is {@link #disabled}, <tt>false</tt> to not mask it (defaults
21446 * to <tt>true</tt>). Either way, the panel will always tell its contained elements to disable themselves
21447 * when it is disabled, but masking the panel can provide an additional visual cue that the panel is
21450 maskDisabled : true,
21452 * @cfg {Boolean} animCollapse
21453 * <tt>true</tt> to animate the transition when the panel is collapsed, <tt>false</tt> to skip the
21454 * animation (defaults to <tt>true</tt> if the {@link Ext.Fx} class is available, otherwise <tt>false</tt>).
21456 animCollapse : Ext.enableFx,
21458 * @cfg {Boolean} headerAsText
21459 * <tt>true</tt> to display the panel <tt>{@link #title}</tt> in the <tt>{@link #header}</tt>,
21460 * <tt>false</tt> to hide it (defaults to <tt>true</tt>).
21462 headerAsText : true,
21464 * @cfg {String} buttonAlign
21465 * The alignment of any {@link #buttons} added to this panel. Valid values are <tt>'right'</tt>,
21466 * <tt>'left'</tt> and <tt>'center'</tt> (defaults to <tt>'right'</tt>).
21468 buttonAlign : 'right',
21470 * @cfg {Boolean} collapsed
21471 * <tt>true</tt> to render the panel collapsed, <tt>false</tt> to render it expanded (defaults to
21476 * @cfg {Boolean} collapseFirst
21477 * <tt>true</tt> to make sure the collapse/expand toggle button always renders first (to the left of)
21478 * any other tools in the panel's title bar, <tt>false</tt> to render it last (defaults to <tt>true</tt>).
21480 collapseFirst : true,
21482 * @cfg {Number} minButtonWidth
21483 * Minimum width in pixels of all {@link #buttons} in this panel (defaults to <tt>75</tt>)
21485 minButtonWidth : 75,
21487 * @cfg {Boolean} unstyled
21488 * Overrides the <tt>{@link #baseCls}</tt> setting to <tt>{@link #baseCls} = 'x-plain'</tt> which renders
21489 * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden).
21492 * @cfg {String} elements
21493 * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be
21494 * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to
21495 * make sure a structural element is rendered even if not specified at config time (for example, you may want
21496 * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this
21497 * list will allocate the required placeholders in the panel when it is rendered. Valid values are<div class="mdetail-params"><ul>
21498 * <li><tt>header</tt></li>
21499 * <li><tt>tbar</tt> (top bar)</li>
21500 * <li><tt>body</tt></li>
21501 * <li><tt>bbar</tt> (bottom bar)</li>
21502 * <li><tt>footer</tt></li>
21504 * Defaults to '<tt>body</tt>'.
21508 * @cfg {Boolean} preventBodyReset
21509 * Defaults to <tt>false</tt>. When set to <tt>true</tt>, an extra css class <tt>'x-panel-normal'</tt>
21510 * will be added to the panel's element, effectively applying css styles suggested by the W3C
21511 * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's <b>body</b> element (not the header,
21514 preventBodyReset : false,
21516 // protected - these could be used to customize the behavior of the window,
21517 // but changing them would not be useful without further mofifications and
21518 // could lead to unexpected or undesirable results.
21519 toolTarget : 'header',
21520 collapseEl : 'bwrap',
21522 disabledClass : '',
21524 // private, notify box this class will handle heights
21525 deferHeight : true,
21531 collapseDefaults : {
21536 initComponent : function(){
21537 Ext.Panel.superclass.initComponent.call(this);
21541 * @event bodyresize
21542 * Fires after the Panel has been resized.
21543 * @param {Ext.Panel} p the Panel which has been resized.
21544 * @param {Number} width The Panel's new width.
21545 * @param {Number} height The Panel's new height.
21549 * @event titlechange
21550 * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}.
21551 * @param {Ext.Panel} p the Panel which has had its title changed.
21552 * @param {String} The new title.
21556 * @event iconchange
21557 * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}.
21558 * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed.
21559 * @param {String} The new icon class.
21560 * @param {String} The old icon class.
21565 * Fires after the Panel has been collapsed.
21566 * @param {Ext.Panel} p the Panel that has been collapsed.
21571 * Fires after the Panel has been expanded.
21572 * @param {Ext.Panel} p The Panel that has been expanded.
21576 * @event beforecollapse
21577 * Fires before the Panel is collapsed. A handler can return false to cancel the collapse.
21578 * @param {Ext.Panel} p the Panel being collapsed.
21579 * @param {Boolean} animate True if the collapse is animated, else false.
21583 * @event beforeexpand
21584 * Fires before the Panel is expanded. A handler can return false to cancel the expand.
21585 * @param {Ext.Panel} p The Panel being expanded.
21586 * @param {Boolean} animate True if the expand is animated, else false.
21590 * @event beforeclose
21591 * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some
21592 * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only
21593 * applies to such subclasses.
21594 * A handler can return false to cancel the close.
21595 * @param {Ext.Panel} p The Panel being closed.
21600 * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some
21601 * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel.
21602 * @param {Ext.Panel} p The Panel that has been closed.
21607 * Fires after the Panel has been visually activated.
21608 * Note that Panels do not directly support being activated, but some Panel subclasses
21609 * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
21610 * activate and deactivate events under the control of the TabPanel.
21611 * @param {Ext.Panel} p The Panel that has been activated.
21615 * @event deactivate
21616 * Fires after the Panel has been visually deactivated.
21617 * Note that Panels do not directly support being deactivated, but some Panel subclasses
21618 * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
21619 * activate and deactivate events under the control of the TabPanel.
21620 * @param {Ext.Panel} p The Panel that has been deactivated.
21626 this.baseCls = 'x-plain';
21631 this.elements += ',tbar';
21632 if(Ext.isObject(this.tbar)){
21633 this.topToolbar = this.tbar;
21638 this.elements += ',bbar';
21639 if(Ext.isObject(this.bbar)){
21640 this.bottomToolbar = this.bbar;
21645 if(this.header === true){
21646 this.elements += ',header';
21647 delete this.header;
21648 }else if(this.headerCfg || (this.title && this.header !== false)){
21649 this.elements += ',header';
21652 if(this.footerCfg || this.footer === true){
21653 this.elements += ',footer';
21654 delete this.footer;
21658 this.elements += ',footer';
21659 var btns = this.buttons;
21661 * This Panel's Array of buttons as created from the <tt>{@link #buttons}</tt>
21662 * config property. Read only.
21664 * @property buttons
21667 for(var i = 0, len = btns.length; i < len; i++) {
21668 if(btns[i].render){ // button instance
21669 this.buttons.push(btns[i]);
21670 }else if(btns[i].xtype){
21671 this.buttons.push(Ext.create(btns[i], 'button'));
21673 this.addButton(btns[i]);
21678 this.elements += ',footer';
21681 this.on('render', this.doAutoLoad, this, {delay:10});
21686 createElement : function(name, pnode){
21688 pnode.appendChild(this[name].dom);
21692 if(name === 'bwrap' || this.elements.indexOf(name) != -1){
21693 if(this[name+'Cfg']){
21694 this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
21696 var el = document.createElement('div');
21697 el.className = this[name+'Cls'];
21698 this[name] = Ext.get(pnode.appendChild(el));
21700 if(this[name+'CssClass']){
21701 this[name].addClass(this[name+'CssClass']);
21703 if(this[name+'Style']){
21704 this[name].applyStyles(this[name+'Style']);
21710 onRender : function(ct, position){
21711 Ext.Panel.superclass.onRender.call(this, ct, position);
21712 this.createClasses();
21717 el.addClass(this.baseCls);
21718 if(d.firstChild){ // existing markup
21719 this.header = el.down('.'+this.headerCls);
21720 this.bwrap = el.down('.'+this.bwrapCls);
21721 var cp = this.bwrap ? this.bwrap : el;
21722 this.tbar = cp.down('.'+this.tbarCls);
21723 this.body = cp.down('.'+this.bodyCls);
21724 this.bbar = cp.down('.'+this.bbarCls);
21725 this.footer = cp.down('.'+this.footerCls);
21726 this.fromMarkup = true;
21728 if (this.preventBodyReset === true) {
21729 el.addClass('x-panel-reset');
21732 el.addClass(this.cls);
21736 this.elements += ',footer';
21739 // This block allows for maximum flexibility and performance when using existing markup
21741 // framing requires special markup
21743 el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
21745 this.createElement('header', d.firstChild.firstChild.firstChild);
21746 this.createElement('bwrap', d);
21748 // append the mid and bottom frame to the bwrap
21749 bw = this.bwrap.dom;
21750 var ml = d.childNodes[1], bl = d.childNodes[2];
21751 bw.appendChild(ml);
21752 bw.appendChild(bl);
21754 var mc = bw.firstChild.firstChild.firstChild;
21755 this.createElement('tbar', mc);
21756 this.createElement('body', mc);
21757 this.createElement('bbar', mc);
21758 this.createElement('footer', bw.lastChild.firstChild.firstChild);
21761 this.bwrap.dom.lastChild.className += ' x-panel-nofooter';
21764 this.createElement('header', d);
21765 this.createElement('bwrap', d);
21767 // append the mid and bottom frame to the bwrap
21768 bw = this.bwrap.dom;
21769 this.createElement('tbar', bw);
21770 this.createElement('body', bw);
21771 this.createElement('bbar', bw);
21772 this.createElement('footer', bw);
21775 this.body.addClass(this.bodyCls + '-noheader');
21777 this.tbar.addClass(this.tbarCls + '-noheader');
21782 if(this.padding !== undefined) {
21783 this.body.setStyle('padding', this.body.addUnits(this.padding));
21786 if(this.border === false){
21787 this.el.addClass(this.baseCls + '-noborder');
21788 this.body.addClass(this.bodyCls + '-noborder');
21790 this.header.addClass(this.headerCls + '-noborder');
21793 this.footer.addClass(this.footerCls + '-noborder');
21796 this.tbar.addClass(this.tbarCls + '-noborder');
21799 this.bbar.addClass(this.bbarCls + '-noborder');
21803 if(this.bodyBorder === false){
21804 this.body.addClass(this.bodyCls + '-noborder');
21807 this.bwrap.enableDisplayMode('block');
21810 this.header.unselectable();
21812 // for tools, we need to wrap any existing header markup
21813 if(this.headerAsText){
21814 this.header.dom.innerHTML =
21815 '<span class="' + this.headerTextCls + '">'+this.header.dom.innerHTML+'</span>';
21818 this.setIconClass(this.iconCls);
21824 this.makeFloating(this.floating);
21827 if(this.collapsible){
21828 this.tools = this.tools ? this.tools.slice(0) : [];
21829 if(!this.hideCollapseTool){
21830 this.tools[this.collapseFirst?'unshift':'push']({
21832 handler : this.toggleCollapse,
21836 if(this.titleCollapse && this.header){
21837 this.mon(this.header, 'click', this.toggleCollapse, this);
21838 this.header.setStyle('cursor', 'pointer');
21842 var ts = this.tools;
21844 this.addTool.apply(this, ts);
21849 if(this.buttons && this.buttons.length > 0){
21850 this.fbar = new Ext.Toolbar({
21851 items: this.buttons,
21852 toolbarCls: 'x-panel-fbar'
21855 this.toolbars = [];
21857 this.fbar = Ext.create(this.fbar, 'toolbar');
21858 this.fbar.enableOverflow = false;
21859 if(this.fbar.items){
21860 this.fbar.items.each(function(c){
21861 c.minWidth = c.minWidth || this.minButtonWidth;
21864 this.fbar.toolbarCls = 'x-panel-fbar';
21866 var bct = this.footer.createChild({cls: 'x-panel-btns x-panel-btns-'+this.buttonAlign});
21867 this.fbar.ownerCt = this;
21868 this.fbar.render(bct);
21869 bct.createChild({cls:'x-clear'});
21870 this.toolbars.push(this.fbar);
21873 if(this.tbar && this.topToolbar){
21874 if(Ext.isArray(this.topToolbar)){
21875 this.topToolbar = new Ext.Toolbar(this.topToolbar);
21876 }else if(!this.topToolbar.events){
21877 this.topToolbar = Ext.create(this.topToolbar, 'toolbar');
21879 this.topToolbar.ownerCt = this;
21880 this.topToolbar.render(this.tbar);
21881 this.toolbars.push(this.topToolbar);
21883 if(this.bbar && this.bottomToolbar){
21884 if(Ext.isArray(this.bottomToolbar)){
21885 this.bottomToolbar = new Ext.Toolbar(this.bottomToolbar);
21886 }else if(!this.bottomToolbar.events){
21887 this.bottomToolbar = Ext.create(this.bottomToolbar, 'toolbar');
21889 this.bottomToolbar.ownerCt = this;
21890 this.bottomToolbar.render(this.bbar);
21891 this.toolbars.push(this.bottomToolbar);
21893 Ext.each(this.toolbars, function(tb){
21896 afterlayout: this.syncHeight,
21897 remove: this.syncHeight
21903 * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
21904 * icon class if one has already been set and fire the {@link #iconchange} event after completion.
21905 * @param {String} cls The new CSS class name
21907 setIconClass : function(cls){
21908 var old = this.iconCls;
21909 this.iconCls = cls;
21910 if(this.rendered && this.header){
21912 this.header.addClass('x-panel-icon');
21913 this.header.replaceClass(old, this.iconCls);
21915 var hd = this.header.dom;
21916 var img = hd.firstChild && String(hd.firstChild.tagName).toLowerCase() == 'img' ? hd.firstChild : null;
21918 Ext.fly(img).replaceClass(old, this.iconCls);
21920 Ext.DomHelper.insertBefore(hd.firstChild, {
21921 tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
21926 this.fireEvent('iconchange', this, cls, old);
21930 makeFloating : function(cfg){
21931 this.floating = true;
21932 this.el = new Ext.Layer(
21933 Ext.isObject(cfg) ? cfg : {
21934 shadow: this.shadow !== undefined ? this.shadow : 'sides',
21935 shadowOffset: this.shadowOffset,
21937 shim: this.shim === false ? false : undefined
21943 * Returns the {@link Ext.Toolbar toolbar} from the top (<tt>{@link #tbar}</tt>) section of the panel.
21944 * @return {Ext.Toolbar} The toolbar
21946 getTopToolbar : function(){
21947 return this.topToolbar;
21951 * Returns the {@link Ext.Toolbar toolbar} from the bottom (<tt>{@link #bbar}</tt>) section of the panel.
21952 * @return {Ext.Toolbar} The toolbar
21954 getBottomToolbar : function(){
21955 return this.bottomToolbar;
21959 * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred
21960 * approach is to add buttons via the {@link #buttons} config.
21961 * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default
21962 * button config, an object will be treated as a button config object.
21963 * @param {Function} handler The function to be called on button {@link Ext.Button#click}
21964 * @param {Object} scope The scope to use for the button handler function
21965 * @return {Ext.Button} The button that was added
21967 addButton : function(config, handler, scope){
21971 minWidth: this.minButtonWidth,
21974 if(typeof config == "string"){
21977 Ext.apply(bc, config);
21979 var btn = new Ext.Button(bc);
21983 this.buttons.push(btn);
21988 addTool : function(){
21989 if(!this[this.toolTarget]) { // no where to render tools!
21992 if(!this.toolTemplate){
21993 // initialize the global tool template on first use
21994 var tt = new Ext.Template(
21995 '<div class="x-tool x-tool-{id}"> </div>'
21997 tt.disableFormats = true;
21999 Ext.Panel.prototype.toolTemplate = tt;
22001 for(var i = 0, a = arguments, len = a.length; i < len; i++) {
22003 if(!this.tools[tc.id]){
22004 var overCls = 'x-tool-'+tc.id+'-over';
22005 var t = this.toolTemplate.insertFirst((tc.align !== 'left') ? this[this.toolTarget] : this[this.toolTarget].child('span'), tc, true);
22006 this.tools[tc.id] = t;
22007 t.enableDisplayMode('block');
22008 this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this));
22010 this.mon(t, tc.on);
22016 if(Ext.isObject(tc.qtip)){
22017 Ext.QuickTips.register(Ext.apply({
22021 t.dom.qtip = tc.qtip;
22024 t.addClassOnOver(overCls);
22029 onLayout : function(){
22030 if(this.toolbars.length > 0){
22031 this.duringLayout = true;
22032 Ext.each(this.toolbars, function(tb){
22035 delete this.duringLayout;
22040 syncHeight : function(){
22041 if(!(this.autoHeight || this.duringLayout)){
22042 var last = this.lastSize;
22043 if(last && !Ext.isEmpty(last.height)){
22044 var old = last.height, h = this.el.getHeight();
22045 if(old != 'auto' && old != h){
22046 var bd = this.body, bdh = bd.getHeight();
22047 h = Math.max(bdh + old - h, 0);
22048 if(bdh > 0 && bdh != h){
22050 if(Ext.isIE && h <= 0){
22053 var sz = bd.getSize();
22054 this.fireEvent('bodyresize', sz.width, sz.height);
22062 onShow : function(){
22064 return this.el.show();
22066 Ext.Panel.superclass.onShow.call(this);
22070 onHide : function(){
22072 return this.el.hide();
22074 Ext.Panel.superclass.onHide.call(this);
22078 createToolHandler : function(t, tc, overCls, panel){
22079 return function(e){
22080 t.removeClass(overCls);
22081 if(tc.stopEvent !== false){
22085 tc.handler.call(tc.scope || t, e, t, panel, tc);
22091 afterRender : function(){
22092 if(this.floating && !this.hidden){
22096 this.setTitle(this.title);
22098 this.setAutoScroll();
22100 this.body.update(Ext.isObject(this.html) ?
22101 Ext.DomHelper.markup(this.html) :
22105 if(this.contentEl){
22106 var ce = Ext.getDom(this.contentEl);
22107 Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']);
22108 this.body.dom.appendChild(ce);
22110 if(this.collapsed){
22111 this.collapsed = false;
22112 this.collapse(false);
22114 Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last
22119 setAutoScroll : function(){
22120 if(this.rendered && this.autoScroll){
22121 var el = this.body || this.el;
22123 el.setOverflow('auto');
22129 getKeyMap : function(){
22131 this.keyMap = new Ext.KeyMap(this.el, this.keys);
22133 return this.keyMap;
22137 initEvents : function(){
22141 if(this.draggable){
22142 this.initDraggable();
22147 initDraggable : function(){
22149 * <p>If this Panel is configured {@link #draggable}, this property will contain
22150 * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.</p>
22151 * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
22152 * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
22153 * @type Ext.dd.DragSource.
22156 this.dd = new Ext.Panel.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable);
22160 beforeEffect : function(){
22162 this.el.beforeAction();
22164 this.el.addClass('x-panel-animated');
22168 afterEffect : function(){
22170 this.el.removeClass('x-panel-animated');
22173 // private - wraps up an animation param with internal callbacks
22174 createEffect : function(a, cb, scope){
22182 }else if(!a.callback){
22184 }else { // wrap it up
22185 o.callback = function(){
22187 Ext.callback(a.callback, a.scope);
22190 return Ext.applyIf(o, a);
22194 * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will
22195 * cancel the collapse action if it returns false.
22196 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22197 * {@link #animCollapse} panel config)
22198 * @return {Ext.Panel} this
22200 collapse : function(animate){
22201 if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
22204 var doAnim = animate === true || (animate !== false && this.animCollapse);
22205 this.beforeEffect();
22206 this.onCollapse(doAnim, animate);
22211 onCollapse : function(doAnim, animArg){
22213 this[this.collapseEl].slideOut(this.slideAnchor,
22214 Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this),
22215 this.collapseDefaults));
22217 this[this.collapseEl].hide();
22218 this.afterCollapse();
22223 afterCollapse : function(){
22224 this.collapsed = true;
22225 this.el.addClass(this.collapsedCls);
22226 this.afterEffect();
22227 this.fireEvent('collapse', this);
22231 * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
22232 * cancel the expand action if it returns false.
22233 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22234 * {@link #animCollapse} panel config)
22235 * @return {Ext.Panel} this
22237 expand : function(animate){
22238 if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){
22241 var doAnim = animate === true || (animate !== false && this.animCollapse);
22242 this.el.removeClass(this.collapsedCls);
22243 this.beforeEffect();
22244 this.onExpand(doAnim, animate);
22249 onExpand : function(doAnim, animArg){
22251 this[this.collapseEl].slideIn(this.slideAnchor,
22252 Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
22253 this.expandDefaults));
22255 this[this.collapseEl].show();
22256 this.afterExpand();
22261 afterExpand : function(){
22262 this.collapsed = false;
22263 this.afterEffect();
22264 if(this.deferLayout !== undefined){
22265 this.doLayout(true);
22267 this.fireEvent('expand', this);
22271 * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
22272 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22273 * {@link #animCollapse} panel config)
22274 * @return {Ext.Panel} this
22276 toggleCollapse : function(animate){
22277 this[this.collapsed ? 'expand' : 'collapse'](animate);
22282 onDisable : function(){
22283 if(this.rendered && this.maskDisabled){
22286 Ext.Panel.superclass.onDisable.call(this);
22290 onEnable : function(){
22291 if(this.rendered && this.maskDisabled){
22294 Ext.Panel.superclass.onEnable.call(this);
22298 onResize : function(w, h){
22299 if(w !== undefined || h !== undefined){
22300 if(!this.collapsed){
22301 if(typeof w == 'number'){
22302 w = this.adjustBodyWidth(w - this.getFrameWidth());
22304 this.tbar.setWidth(w);
22305 if(this.topToolbar){
22306 this.topToolbar.setSize(w);
22310 this.bbar.setWidth(w);
22311 if(this.bottomToolbar){
22312 this.bottomToolbar.setSize(w);
22318 strict = Ext.isStrict;
22319 if(this.buttonAlign == 'left'){
22320 fWidth = w - f.container.getFrameWidth('lr');
22322 //center/right alignment off in webkit
22323 if(Ext.isIE || Ext.isWebKit){
22324 //center alignment ok on webkit.
22325 //right broken in both, center on IE
22326 if(!(this.buttonAlign == 'center' && Ext.isWebKit) && (!strict || (!Ext.isIE8 && strict))){
22328 f.setWidth(f.getEl().child('.x-toolbar-ct').getWidth());
22337 f.setWidth(fWidth);
22339 this.body.setWidth(w);
22340 }else if(w == 'auto'){
22341 this.body.setWidth(w);
22344 if(typeof h == 'number'){
22345 h = Math.max(0, this.adjustBodyHeight(h - this.getFrameHeight()));
22346 this.body.setHeight(h);
22347 }else if(h == 'auto'){
22348 this.body.setHeight(h);
22351 if(this.disabled && this.el._mask){
22352 this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
22355 this.queuedBodySize = {width: w, height: h};
22356 if(!this.queuedExpand && this.allowQueuedExpand !== false){
22357 this.queuedExpand = true;
22358 this.on('expand', function(){
22359 delete this.queuedExpand;
22360 this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
22362 }, this, {single:true});
22365 this.fireEvent('bodyresize', this, w, h);
22371 adjustBodyHeight : function(h){
22376 adjustBodyWidth : function(w){
22381 onPosition : function(){
22386 * Returns the width in pixels of the framing elements of this panel (not including the body width). To
22387 * retrieve the body width see {@link #getInnerWidth}.
22388 * @return {Number} The frame width
22390 getFrameWidth : function(){
22391 var w = this.el.getFrameWidth('lr')+this.bwrap.getFrameWidth('lr');
22394 var l = this.bwrap.dom.firstChild;
22395 w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
22396 var mc = this.bwrap.dom.firstChild.firstChild.firstChild;
22397 w += Ext.fly(mc).getFrameWidth('lr');
22403 * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and
22404 * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}.
22405 * @return {Number} The frame height
22407 getFrameHeight : function(){
22408 var h = this.el.getFrameWidth('tb')+this.bwrap.getFrameWidth('tb');
22409 h += (this.tbar ? this.tbar.getHeight() : 0) +
22410 (this.bbar ? this.bbar.getHeight() : 0);
22413 var hd = this.el.dom.firstChild;
22414 var ft = this.bwrap.dom.lastChild;
22415 h += (hd.offsetHeight + ft.offsetHeight);
22416 var mc = this.bwrap.dom.firstChild.firstChild.firstChild;
22417 h += Ext.fly(mc).getFrameWidth('tb');
22419 h += (this.header ? this.header.getHeight() : 0) +
22420 (this.footer ? this.footer.getHeight() : 0);
22426 * Returns the width in pixels of the body element (not including the width of any framing elements).
22427 * For the frame width see {@link #getFrameWidth}.
22428 * @return {Number} The body width
22430 getInnerWidth : function(){
22431 return this.getSize().width - this.getFrameWidth();
22435 * Returns the height in pixels of the body element (not including the height of any framing elements).
22436 * For the frame height see {@link #getFrameHeight}.
22437 * @return {Number} The body height
22439 getInnerHeight : function(){
22440 return this.getSize().height - this.getFrameHeight();
22444 syncShadow : function(){
22446 this.el.sync(true);
22451 getLayoutTarget : function(){
22456 * <p>Sets the title text for the panel and optionally the {@link #iconCls icon class}.</p>
22457 * <p>In order to be able to set the title, a header element must have been created
22458 * for the Panel. This is triggered either by configuring the Panel with a non-blank <tt>{@link #title}</tt>,
22459 * or configuring it with <tt><b>{@link #header}: true</b></tt>.</p>
22460 * @param {String} title The title text to set
22461 * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel
22463 setTitle : function(title, iconCls){
22464 this.title = title;
22465 if(this.header && this.headerAsText){
22466 this.header.child('span').update(title);
22469 this.setIconClass(iconCls);
22471 this.fireEvent('titlechange', this, title);
22476 * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body.
22477 * @return {Ext.Updater} The Updater
22479 getUpdater : function(){
22480 return this.body.getUpdater();
22484 * Loads this content panel immediately with content returned from an XHR call.
22485 * @param {Object/String/Function} config A config object containing any of the following options:
22488 url: "your-url.php",
22489 params: {param1: "foo", param2: "bar"}, // or a URL encoded string
22490 callback: yourFunction,
22491 scope: yourObject, // optional scope for the callback
22494 text: "Loading...",
22499 * The only required property is url. The optional properties nocache, text and scripts
22500 * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their
22501 * associated property on this panel Updater instance.
22502 * @return {Ext.Panel} this
22505 var um = this.body.getUpdater();
22506 um.update.apply(um, arguments);
22511 beforeDestroy : function(){
22513 this.header.removeAllListeners();
22514 if(this.headerAsText){
22515 Ext.Element.uncache(this.header.child('span'));
22518 Ext.Element.uncache(
22527 for(var k in this.tools){
22528 Ext.destroy(this.tools[k]);
22532 for(var b in this.buttons){
22533 Ext.destroy(this.buttons[b]);
22536 Ext.destroy(this.toolbars);
22537 Ext.Panel.superclass.beforeDestroy.call(this);
22541 createClasses : function(){
22542 this.headerCls = this.baseCls + '-header';
22543 this.headerTextCls = this.baseCls + '-header-text';
22544 this.bwrapCls = this.baseCls + '-bwrap';
22545 this.tbarCls = this.baseCls + '-tbar';
22546 this.bodyCls = this.baseCls + '-body';
22547 this.bbarCls = this.baseCls + '-bbar';
22548 this.footerCls = this.baseCls + '-footer';
22552 createGhost : function(cls, useShim, appendTo){
22553 var el = document.createElement('div');
22554 el.className = 'x-panel-ghost ' + (cls ? cls : '');
22556 el.appendChild(this.el.dom.firstChild.cloneNode(true));
22558 Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
22559 el.style.width = this.el.dom.offsetWidth + 'px';;
22561 this.container.dom.appendChild(el);
22563 Ext.getDom(appendTo).appendChild(el);
22565 if(useShim !== false && this.el.useShim !== false){
22566 var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
22570 return new Ext.Element(el);
22575 doAutoLoad : function(){
22576 var u = this.body.getUpdater();
22578 u.setRenderer(this.renderer);
22580 u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad});
22584 * Retrieve a tool by id.
22585 * @param {String} id
22586 * @return {Object} tool
22588 getTool : function(id) {
22589 return this.tools[id];
22593 * @cfg {String} autoEl @hide
22596 Ext.reg('panel', Ext.Panel);
22598 * @class Ext.Editor
22599 * @extends Ext.Component
22600 * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.
22602 * Create a new Editor
22603 * @param {Object} config The config object
22606 Ext.Editor = function(field, config){
22608 this.field = Ext.create(field.field, 'textfield');
22609 config = Ext.apply({}, field); // copy so we don't disturb original config
22610 delete config.field;
22612 this.field = field;
22614 Ext.Editor.superclass.constructor.call(this, config);
22617 Ext.extend(Ext.Editor, Ext.Component, {
22619 * @cfg {Ext.form.Field} field
22620 * The Field object (or descendant) or config object for field
22623 * @cfg {Boolean} allowBlur
22624 * True to {@link #completeEdit complete the editing process} if in edit mode when the
22625 * field is blurred. Defaults to <tt>false</tt>.
22628 * @cfg {Boolean/String} autoSize
22629 * True for the editor to automatically adopt the size of the element being edited, "width" to adopt the width only,
22630 * or "height" to adopt the height only (defaults to false)
22633 * @cfg {Boolean} revertInvalid
22634 * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
22635 * validation fails (defaults to true)
22638 * @cfg {Boolean} ignoreNoChange
22639 * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
22640 * the value has not changed (defaults to false). Applies only to string values - edits for other data types
22641 * will never be ignored.
22644 * @cfg {Boolean} hideEl
22645 * False to keep the bound element visible while the editor is displayed (defaults to true)
22648 * @cfg {Mixed} value
22649 * The data value of the underlying field (defaults to "")
22653 * @cfg {String} alignment
22654 * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").
22658 * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
22659 * for bottom-right shadow (defaults to "frame")
22663 * @cfg {Boolean} constrain True to constrain the editor to the viewport
22667 * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
22669 swallowKeys : true,
22671 * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false)
22673 completeOnEnter : false,
22675 * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false)
22677 cancelOnEsc : false,
22679 * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
22683 initComponent : function(){
22684 Ext.Editor.superclass.initComponent.call(this);
22687 * @event beforestartedit
22688 * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
22689 * false from the handler of this event.
22690 * @param {Editor} this
22691 * @param {Ext.Element} boundEl The underlying element bound to this editor
22692 * @param {Mixed} value The field value being set
22697 * Fires when this editor is displayed
22698 * @param {Ext.Element} boundEl The underlying element bound to this editor
22699 * @param {Mixed} value The starting field value
22703 * @event beforecomplete
22704 * Fires after a change has been made to the field, but before the change is reflected in the underlying
22705 * field. Saving the change to the field can be canceled by returning false from the handler of this event.
22706 * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
22707 * event will not fire since no edit actually occurred.
22708 * @param {Editor} this
22709 * @param {Mixed} value The current field value
22710 * @param {Mixed} startValue The original field value
22715 * Fires after editing is complete and any changed value has been written to the underlying field.
22716 * @param {Editor} this
22717 * @param {Mixed} value The current field value
22718 * @param {Mixed} startValue The original field value
22722 * @event canceledit
22723 * Fires after editing has been canceled and the editor's value has been reset.
22724 * @param {Editor} this
22725 * @param {Mixed} value The user-entered field value that was discarded
22726 * @param {Mixed} startValue The original field value that was set back into the editor after cancel
22730 * @event specialkey
22731 * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
22732 * {@link Ext.EventObject#getKey} to determine which key was pressed.
22733 * @param {Ext.form.Field} this
22734 * @param {Ext.EventObject} e The event object
22741 onRender : function(ct, position){
22742 this.el = new Ext.Layer({
22743 shadow: this.shadow,
22747 shadowOffset: this.shadowOffset || 4,
22749 constrain: this.constrain
22752 this.el.setZIndex(this.zIndex);
22754 this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");
22755 if(this.field.msgTarget != 'title'){
22756 this.field.msgTarget = 'qtip';
22758 this.field.inEditor = true;
22759 this.field.render(this.el);
22761 this.field.el.dom.setAttribute('autocomplete', 'off');
22763 this.mon(this.field, "specialkey", this.onSpecialKey, this);
22764 if(this.swallowKeys){
22765 this.field.el.swallowEvent(['keydown','keypress']);
22768 this.mon(this.field, "blur", this.onBlur, this);
22769 if(this.field.grow){
22770 this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1});
22775 onSpecialKey : function(field, e){
22776 var key = e.getKey();
22777 if(this.completeOnEnter && key == e.ENTER){
22779 this.completeEdit();
22780 }else if(this.cancelOnEsc && key == e.ESC){
22783 this.fireEvent('specialkey', field, e);
22785 if(this.field.triggerBlur && (key == e.ENTER || key == e.ESC || key == e.TAB)){
22786 this.field.triggerBlur();
22791 * Starts the editing process and shows the editor.
22792 * @param {Mixed} el The element to edit
22793 * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
22794 * to the innerHTML of el.
22796 startEdit : function(el, value){
22798 this.completeEdit();
22800 this.boundEl = Ext.get(el);
22801 var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
22802 if(!this.rendered){
22803 this.render(this.parentEl || document.body);
22805 if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){
22808 this.startValue = v;
22809 this.field.setValue(v);
22811 this.el.alignTo(this.boundEl, this.alignment);
22812 this.editing = true;
22817 doAutoSize : function(){
22819 var sz = this.boundEl.getSize();
22820 switch(this.autoSize){
22822 this.setSize(sz.width, "");
22825 this.setSize("", sz.height);
22828 this.setSize(sz.width, sz.height);
22834 * Sets the height and width of this editor.
22835 * @param {Number} width The new width
22836 * @param {Number} height The new height
22838 setSize : function(w, h){
22839 delete this.field.lastSize;
22840 this.field.setSize(w, h);
22842 if(Ext.isGecko2 || Ext.isOpera){
22843 // prevent layer scrollbars
22844 this.el.setSize(w, h);
22851 * Realigns the editor to the bound field based on the current alignment config value.
22853 realign : function(){
22854 this.el.alignTo(this.boundEl, this.alignment);
22858 * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
22859 * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)
22861 completeEdit : function(remainVisible){
22865 var v = this.getValue();
22866 if(!this.field.isValid()){
22867 if(this.revertInvalid !== false){
22868 this.cancelEdit(remainVisible);
22872 if(String(v) === String(this.startValue) && this.ignoreNoChange){
22873 this.hideEdit(remainVisible);
22876 if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
22877 v = this.getValue();
22878 if(this.updateEl && this.boundEl){
22879 this.boundEl.update(v);
22881 this.hideEdit(remainVisible);
22882 this.fireEvent("complete", this, v, this.startValue);
22887 onShow : function(){
22889 if(this.hideEl !== false){
22890 this.boundEl.hide();
22893 if(Ext.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time
22894 this.fixIEFocus = true;
22895 this.deferredFocus.defer(50, this);
22897 this.field.focus();
22899 this.fireEvent("startedit", this.boundEl, this.startValue);
22902 deferredFocus : function(){
22904 this.field.focus();
22909 * Cancels the editing process and hides the editor without persisting any changes. The field value will be
22910 * reverted to the original starting value.
22911 * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after
22912 * cancel (defaults to false)
22914 cancelEdit : function(remainVisible){
22916 var v = this.getValue();
22917 this.setValue(this.startValue);
22918 this.hideEdit(remainVisible);
22919 this.fireEvent("canceledit", this, v, this.startValue);
22924 hideEdit: function(remainVisible){
22925 if(remainVisible !== true){
22926 this.editing = false;
22932 onBlur : function(){
22933 if(this.allowBlur !== true && this.editing){
22934 this.completeEdit();
22939 onHide : function(){
22941 this.completeEdit();
22945 if(this.field.collapse){
22946 this.field.collapse();
22949 if(this.hideEl !== false){
22950 this.boundEl.show();
22955 * Sets the data value of the editor
22956 * @param {Mixed} value Any valid value supported by the underlying field
22958 setValue : function(v){
22959 this.field.setValue(v);
22963 * Gets the data value of the editor
22964 * @return {Mixed} The data value
22966 getValue : function(){
22967 return this.field.getValue();
22970 beforeDestroy : function(){
22971 Ext.destroy(this.field);
22975 Ext.reg('editor', Ext.Editor);/**
22976 * @class Ext.ColorPalette
22977 * @extends Ext.Component
22978 * Simple color palette class for choosing colors. The palette can be rendered to any container.<br />
22979 * Here's an example of typical usage:
22981 var cp = new Ext.ColorPalette({value:'993300'}); // initial selected color
22982 cp.render('my-div');
22984 cp.on('select', function(palette, selColor){
22985 // do something with selColor
22989 * Create a new ColorPalette
22990 * @param {Object} config The config object
22991 * @xtype colorpalette
22993 Ext.ColorPalette = function(config){
22994 Ext.ColorPalette.superclass.constructor.call(this, config);
22998 * Fires when a color is selected
22999 * @param {ColorPalette} this
23000 * @param {String} color The 6-digit color hex code (without the # symbol)
23006 this.on("select", this.handler, this.scope, true);
23009 Ext.extend(Ext.ColorPalette, Ext.Component, {
23011 * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component.
23014 * @cfg {String} itemCls
23015 * The CSS class to apply to the containing element (defaults to "x-color-palette")
23017 itemCls : "x-color-palette",
23019 * @cfg {String} value
23020 * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that
23021 * the hex codes are case-sensitive.
23024 clickEvent:'click',
23026 ctype: "Ext.ColorPalette",
23029 * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
23031 allowReselect : false,
23034 * <p>An array of 6-digit color hex code strings (without the # symbol). This array can contain any number
23035 * of colors, and each hex code should be unique. The width of the palette is controlled via CSS by adjusting
23036 * the width property of the 'x-color-palette' class (or assigning a custom class), so you can balance the number
23037 * of colors with the width setting until the box is symmetrical.</p>
23038 * <p>You can override individual colors if needed:</p>
23040 var cp = new Ext.ColorPalette();
23041 cp.colors[0] = "FF0000"; // change the first box to red
23044 Or you can provide a custom array of your own for complete control:
23046 var cp = new Ext.ColorPalette();
23047 cp.colors = ["000000", "993300", "333300"];
23052 "000000", "993300", "333300", "003300", "003366", "000080", "333399", "333333",
23053 "800000", "FF6600", "808000", "008000", "008080", "0000FF", "666699", "808080",
23054 "FF0000", "FF9900", "99CC00", "339966", "33CCCC", "3366FF", "800080", "969696",
23055 "FF00FF", "FFCC00", "FFFF00", "00FF00", "00FFFF", "00CCFF", "993366", "C0C0C0",
23056 "FF99CC", "FFCC99", "FFFF99", "CCFFCC", "CCFFFF", "99CCFF", "CC99FF", "FFFFFF"
23060 onRender : function(container, position){
23061 var t = this.tpl || new Ext.XTemplate(
23062 '<tpl for="."><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" unselectable="on"> </span></em></a></tpl>'
23064 var el = document.createElement("div");
23065 el.id = this.getId();
23066 el.className = this.itemCls;
23067 t.overwrite(el, this.colors);
23068 container.dom.insertBefore(el, position);
23069 this.el = Ext.get(el);
23070 this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'});
23071 if(this.clickEvent != 'click'){
23072 this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true});
23077 afterRender : function(){
23078 Ext.ColorPalette.superclass.afterRender.call(this);
23080 var s = this.value;
23087 handleClick : function(e, t){
23088 e.preventDefault();
23089 if(!this.disabled){
23090 var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
23091 this.select(c.toUpperCase());
23096 * Selects the specified color in the palette (fires the {@link #select} event)
23097 * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
23099 select : function(color){
23100 color = color.replace("#", "");
23101 if(color != this.value || this.allowReselect){
23104 el.child("a.color-"+this.value).removeClass("x-color-palette-sel");
23106 el.child("a.color-"+color).addClass("x-color-palette-sel");
23107 this.value = color;
23108 this.fireEvent("select", this, color);
23113 * @cfg {String} autoEl @hide
23116 Ext.reg('colorpalette', Ext.ColorPalette);/**
\r
23117 * @class Ext.DatePicker
\r
23118 * @extends Ext.Component
\r
23119 * Simple date picker class.
\r
23121 * Create a new DatePicker
\r
23122 * @param {Object} config The config object
\r
23123 * @xtype datepicker
\r
23125 Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
\r
23127 * @cfg {String} todayText
\r
23128 * The text to display on the button that selects the current date (defaults to <tt>'Today'</tt>)
\r
23130 todayText : 'Today',
\r
23132 * @cfg {String} okText
\r
23133 * The text to display on the ok button (defaults to <tt>' OK '</tt> to give the user extra clicking room)
\r
23135 okText : ' OK ',
\r
23137 * @cfg {String} cancelText
\r
23138 * The text to display on the cancel button (defaults to <tt>'Cancel'</tt>)
\r
23140 cancelText : 'Cancel',
\r
23142 * @cfg {String} todayTip
\r
23143 * The tooltip to display for the button that selects the current date (defaults to <tt>'{current date} (Spacebar)'</tt>)
\r
23145 todayTip : '{0} (Spacebar)',
\r
23147 * @cfg {String} minText
\r
23148 * The error text to display if the minDate validation fails (defaults to <tt>'This date is before the minimum date'</tt>)
\r
23150 minText : 'This date is before the minimum date',
\r
23152 * @cfg {String} maxText
\r
23153 * The error text to display if the maxDate validation fails (defaults to <tt>'This date is after the maximum date'</tt>)
\r
23155 maxText : 'This date is after the maximum date',
\r
23157 * @cfg {String} format
\r
23158 * The default date format string which can be overriden for localization support. The format must be
\r
23159 * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/y'</tt>).
\r
23161 format : 'm/d/y',
\r
23163 * @cfg {String} disabledDaysText
\r
23164 * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
\r
23166 disabledDaysText : 'Disabled',
\r
23168 * @cfg {String} disabledDatesText
\r
23169 * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
\r
23171 disabledDatesText : 'Disabled',
\r
23173 * @cfg {Array} monthNames
\r
23174 * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
\r
23176 monthNames : Date.monthNames,
\r
23178 * @cfg {Array} dayNames
\r
23179 * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
\r
23181 dayNames : Date.dayNames,
\r
23183 * @cfg {String} nextText
\r
23184 * The next month navigation button tooltip (defaults to <tt>'Next Month (Control+Right)'</tt>)
\r
23186 nextText : 'Next Month (Control+Right)',
\r
23188 * @cfg {String} prevText
\r
23189 * The previous month navigation button tooltip (defaults to <tt>'Previous Month (Control+Left)'</tt>)
\r
23191 prevText : 'Previous Month (Control+Left)',
\r
23193 * @cfg {String} monthYearText
\r
23194 * The header month selector tooltip (defaults to <tt>'Choose a month (Control+Up/Down to move years)'</tt>)
\r
23196 monthYearText : 'Choose a month (Control+Up/Down to move years)',
\r
23198 * @cfg {Number} startDay
\r
23199 * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
\r
23203 * @cfg {Boolean} showToday
\r
23204 * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
\r
23205 * that selects the current date (defaults to <tt>true</tt>).
\r
23207 showToday : true,
\r
23209 * @cfg {Date} minDate
\r
23210 * Minimum allowable date (JavaScript date object, defaults to null)
\r
23213 * @cfg {Date} maxDate
\r
23214 * Maximum allowable date (JavaScript date object, defaults to null)
\r
23217 * @cfg {Array} disabledDays
\r
23218 * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
\r
23221 * @cfg {RegExp} disabledDatesRE
\r
23222 * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
\r
23223 * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
\r
23224 * disabledDates value.
\r
23227 * @cfg {Array} disabledDates
\r
23228 * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
\r
23229 * expression so they are very powerful. Some examples:
\r
23231 * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
\r
23232 * <li>['03/08', '09/16'] would disable those days for every year</li>
\r
23233 * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
\r
23234 * <li>['03/../2006'] would disable every day in March 2006</li>
\r
23235 * <li>['^03'] would disable every day in every March</li>
\r
23237 * Note that the format of the dates included in the array should exactly match the {@link #format} config.
\r
23238 * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
\r
23239 * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
\r
23243 initComponent : function(){
\r
23244 Ext.DatePicker.superclass.initComponent.call(this);
\r
23246 this.value = this.value ?
\r
23247 this.value.clearTime() : new Date().clearTime();
\r
23252 * Fires when a date is selected
\r
23253 * @param {DatePicker} this
\r
23254 * @param {Date} date The selected date
\r
23259 if(this.handler){
\r
23260 this.on('select', this.handler, this.scope || this);
\r
23263 this.initDisabledDays();
\r
23267 initDisabledDays : function(){
\r
23268 if(!this.disabledDatesRE && this.disabledDates){
\r
23269 var dd = this.disabledDates,
\r
23270 len = dd.length - 1,
\r
23273 Ext.each(dd, function(d, i){
\r
23274 re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
\r
23279 this.disabledDatesRE = new RegExp(re + ')');
\r
23284 * Replaces any existing disabled dates with new values and refreshes the DatePicker.
\r
23285 * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
\r
23286 * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
\r
23288 setDisabledDates : function(dd){
\r
23289 if(Ext.isArray(dd)){
\r
23290 this.disabledDates = dd;
\r
23291 this.disabledDatesRE = null;
\r
23293 this.disabledDatesRE = dd;
\r
23295 this.initDisabledDays();
\r
23296 this.update(this.value, true);
\r
23300 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
\r
23301 * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
\r
23302 * for details on supported values.
\r
23304 setDisabledDays : function(dd){
\r
23305 this.disabledDays = dd;
\r
23306 this.update(this.value, true);
\r
23310 * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
\r
23311 * @param {Date} value The minimum date that can be selected
\r
23313 setMinDate : function(dt){
\r
23314 this.minDate = dt;
\r
23315 this.update(this.value, true);
\r
23319 * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
\r
23320 * @param {Date} value The maximum date that can be selected
\r
23322 setMaxDate : function(dt){
\r
23323 this.maxDate = dt;
\r
23324 this.update(this.value, true);
\r
23328 * Sets the value of the date field
\r
23329 * @param {Date} value The date to set
\r
23331 setValue : function(value){
\r
23332 var old = this.value;
\r
23333 this.value = value.clearTime(true);
\r
23335 this.update(this.value);
\r
23340 * Gets the current selected value of the date field
\r
23341 * @return {Date} The selected date
\r
23343 getValue : function(){
\r
23344 return this.value;
\r
23348 focus : function(){
\r
23350 this.update(this.activeDate);
\r
23355 onEnable: function(initial){
\r
23356 Ext.DatePicker.superclass.onEnable.call(this);
\r
23357 this.doDisabled(false);
\r
23358 this.update(initial ? this.value : this.activeDate);
\r
23360 this.el.repaint();
\r
23366 onDisable: function(){
\r
23367 Ext.DatePicker.superclass.onDisable.call(this);
\r
23368 this.doDisabled(true);
\r
23369 if(Ext.isIE && !Ext.isIE8){
\r
23370 /* Really strange problem in IE6/7, when disabled, have to explicitly
\r
23371 * repaint each of the nodes to get them to display correctly, simply
\r
23372 * calling repaint on the main element doesn't appear to be enough.
\r
23374 Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){
\r
23375 Ext.fly(el).repaint();
\r
23381 doDisabled: function(disabled){
\r
23382 this.keyNav.setDisabled(disabled);
\r
23383 this.prevRepeater.setDisabled(disabled);
\r
23384 this.nextRepeater.setDisabled(disabled);
\r
23385 if(this.showToday){
\r
23386 this.todayKeyListener.setDisabled(disabled);
\r
23387 this.todayBtn.setDisabled(disabled);
\r
23392 onRender : function(container, position){
\r
23394 '<table cellspacing="0">',
\r
23395 '<tr><td class="x-date-left"><a href="#" title="', this.prevText ,'"> </a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="', this.nextText ,'"> </a></td></tr>',
\r
23396 '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],
\r
23397 dn = this.dayNames,
\r
23399 for(i = 0; i < 7; i++){
\r
23400 var d = this.startDay+i;
\r
23404 m.push('<th><span>', dn[d].substr(0,1), '</span></th>');
\r
23406 m[m.length] = '</tr></thead><tbody><tr>';
\r
23407 for(i = 0; i < 42; i++) {
\r
23408 if(i % 7 === 0 && i !== 0){
\r
23409 m[m.length] = '</tr><tr>';
\r
23411 m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';
\r
23413 m.push('</tr></tbody></table></td></tr>',
\r
23414 this.showToday ? '<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>' : '',
\r
23415 '</table><div class="x-date-mp"></div>');
\r
23417 var el = document.createElement('div');
\r
23418 el.className = 'x-date-picker';
\r
23419 el.innerHTML = m.join('');
\r
23421 container.dom.insertBefore(el, position);
\r
23423 this.el = Ext.get(el);
\r
23424 this.eventEl = Ext.get(el.firstChild);
\r
23426 this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), {
\r
23427 handler: this.showPrevMonth,
\r
23429 preventDefault:true,
\r
23433 this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), {
\r
23434 handler: this.showNextMonth,
\r
23436 preventDefault:true,
\r
23440 this.monthPicker = this.el.down('div.x-date-mp');
\r
23441 this.monthPicker.enableDisplayMode('block');
\r
23443 this.keyNav = new Ext.KeyNav(this.eventEl, {
\r
23444 'left' : function(e){
\r
23446 this.showPrevMonth();
\r
23448 this.update(this.activeDate.add('d', -1));
\r
23452 'right' : function(e){
\r
23454 this.showNextMonth();
\r
23456 this.update(this.activeDate.add('d', 1));
\r
23460 'up' : function(e){
\r
23462 this.showNextYear();
\r
23464 this.update(this.activeDate.add('d', -7));
\r
23468 'down' : function(e){
\r
23470 this.showPrevYear();
\r
23472 this.update(this.activeDate.add('d', 7));
\r
23476 'pageUp' : function(e){
\r
23477 this.showNextMonth();
\r
23480 'pageDown' : function(e){
\r
23481 this.showPrevMonth();
\r
23484 'enter' : function(e){
\r
23485 e.stopPropagation();
\r
23492 this.el.unselectable();
\r
23494 this.cells = this.el.select('table.x-date-inner tbody td');
\r
23495 this.textNodes = this.el.query('table.x-date-inner tbody span');
\r
23497 this.mbtn = new Ext.Button({
\r
23499 tooltip: this.monthYearText,
\r
23500 renderTo: this.el.child('td.x-date-middle', true)
\r
23502 this.mbtn.el.child('em').addClass('x-btn-arrow');
\r
23504 if(this.showToday){
\r
23505 this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this);
\r
23506 var today = (new Date()).dateFormat(this.format);
\r
23507 this.todayBtn = new Ext.Button({
\r
23508 renderTo: this.el.child('td.x-date-bottom', true),
\r
23509 text: String.format(this.todayText, today),
\r
23510 tooltip: String.format(this.todayTip, today),
\r
23511 handler: this.selectToday,
\r
23515 this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this);
\r
23516 this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'});
\r
23517 this.mon(this.mbtn, 'click', this.showMonthPicker, this);
\r
23518 this.onEnable(true);
\r
23522 createMonthPicker : function(){
\r
23523 if(!this.monthPicker.dom.firstChild){
\r
23524 var buf = ['<table border="0" cellspacing="0">'];
\r
23525 for(var i = 0; i < 6; i++){
\r
23527 '<tr><td class="x-date-mp-month"><a href="#">', Date.getShortMonthName(i), '</a></td>',
\r
23528 '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', Date.getShortMonthName(i + 6), '</a></td>',
\r
23530 '<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' :
\r
23531 '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
\r
23535 '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
\r
23537 '</button><button type="button" class="x-date-mp-cancel">',
\r
23539 '</button></td></tr>',
\r
23542 this.monthPicker.update(buf.join(''));
\r
23544 this.mon(this.monthPicker, 'click', this.onMonthClick, this);
\r
23545 this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);
\r
23547 this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
\r
23548 this.mpYears = this.monthPicker.select('td.x-date-mp-year');
\r
23550 this.mpMonths.each(function(m, a, i){
\r
23553 m.dom.xmonth = 5 + Math.round(i * 0.5);
\r
23555 m.dom.xmonth = Math.round((i-1) * 0.5);
\r
23562 showMonthPicker : function(){
\r
23563 if(!this.disabled){
\r
23564 this.createMonthPicker();
\r
23565 var size = this.el.getSize();
\r
23566 this.monthPicker.setSize(size);
\r
23567 this.monthPicker.child('table').setSize(size);
\r
23569 this.mpSelMonth = (this.activeDate || this.value).getMonth();
\r
23570 this.updateMPMonth(this.mpSelMonth);
\r
23571 this.mpSelYear = (this.activeDate || this.value).getFullYear();
\r
23572 this.updateMPYear(this.mpSelYear);
\r
23574 this.monthPicker.slideIn('t', {duration:0.2});
\r
23579 updateMPYear : function(y){
\r
23581 var ys = this.mpYears.elements;
\r
23582 for(var i = 1; i <= 10; i++){
\r
23583 var td = ys[i-1], y2;
\r
23585 y2 = y + Math.round(i * 0.5);
\r
23586 td.firstChild.innerHTML = y2;
\r
23589 y2 = y - (5-Math.round(i * 0.5));
\r
23590 td.firstChild.innerHTML = y2;
\r
23593 this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
\r
23598 updateMPMonth : function(sm){
\r
23599 this.mpMonths.each(function(m, a, i){
\r
23600 m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
\r
23605 selectMPMonth : function(m){
\r
23610 onMonthClick : function(e, t){
\r
23612 var el = new Ext.Element(t), pn;
\r
23613 if(el.is('button.x-date-mp-cancel')){
\r
23614 this.hideMonthPicker();
\r
23616 else if(el.is('button.x-date-mp-ok')){
\r
23617 var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate());
\r
23618 if(d.getMonth() != this.mpSelMonth){
\r
23619 // 'fix' the JS rolling date conversion if needed
\r
23620 d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth();
\r
23623 this.hideMonthPicker();
\r
23625 else if((pn = el.up('td.x-date-mp-month', 2))){
\r
23626 this.mpMonths.removeClass('x-date-mp-sel');
\r
23627 pn.addClass('x-date-mp-sel');
\r
23628 this.mpSelMonth = pn.dom.xmonth;
\r
23630 else if((pn = el.up('td.x-date-mp-year', 2))){
\r
23631 this.mpYears.removeClass('x-date-mp-sel');
\r
23632 pn.addClass('x-date-mp-sel');
\r
23633 this.mpSelYear = pn.dom.xyear;
\r
23635 else if(el.is('a.x-date-mp-prev')){
\r
23636 this.updateMPYear(this.mpyear-10);
\r
23638 else if(el.is('a.x-date-mp-next')){
\r
23639 this.updateMPYear(this.mpyear+10);
\r
23644 onMonthDblClick : function(e, t){
\r
23646 var el = new Ext.Element(t), pn;
\r
23647 if((pn = el.up('td.x-date-mp-month', 2))){
\r
23648 this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
\r
23649 this.hideMonthPicker();
\r
23651 else if((pn = el.up('td.x-date-mp-year', 2))){
\r
23652 this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
\r
23653 this.hideMonthPicker();
\r
23658 hideMonthPicker : function(disableAnim){
\r
23659 if(this.monthPicker){
\r
23660 if(disableAnim === true){
\r
23661 this.monthPicker.hide();
\r
23663 this.monthPicker.slideOut('t', {duration:0.2});
\r
23669 showPrevMonth : function(e){
\r
23670 this.update(this.activeDate.add('mo', -1));
\r
23674 showNextMonth : function(e){
\r
23675 this.update(this.activeDate.add('mo', 1));
\r
23679 showPrevYear : function(){
\r
23680 this.update(this.activeDate.add('y', -1));
\r
23684 showNextYear : function(){
\r
23685 this.update(this.activeDate.add('y', 1));
\r
23689 handleMouseWheel : function(e){
\r
23691 if(!this.disabled){
\r
23692 var delta = e.getWheelDelta();
\r
23694 this.showPrevMonth();
\r
23695 } else if(delta < 0){
\r
23696 this.showNextMonth();
\r
23702 handleDateClick : function(e, t){
\r
23704 if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){
\r
23705 this.setValue(new Date(t.dateValue));
\r
23706 this.fireEvent('select', this, this.value);
\r
23711 selectToday : function(){
\r
23712 if(this.todayBtn && !this.todayBtn.disabled){
\r
23713 this.setValue(new Date().clearTime());
\r
23714 this.fireEvent('select', this, this.value);
\r
23719 update : function(date, forceRefresh){
\r
23720 var vd = this.activeDate, vis = this.isVisible();
\r
23721 this.activeDate = date;
\r
23722 if(!forceRefresh && vd && this.el){
\r
23723 var t = date.getTime();
\r
23724 if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
\r
23725 this.cells.removeClass('x-date-selected');
\r
23726 this.cells.each(function(c){
\r
23727 if(c.dom.firstChild.dateValue == t){
\r
23728 c.addClass('x-date-selected');
\r
23730 Ext.fly(c.dom.firstChild).focus(50);
\r
23738 var days = date.getDaysInMonth();
\r
23739 var firstOfMonth = date.getFirstDateOfMonth();
\r
23740 var startingPos = firstOfMonth.getDay()-this.startDay;
\r
23742 if(startingPos <= this.startDay){
\r
23743 startingPos += 7;
\r
23746 var pm = date.add('mo', -1);
\r
23747 var prevStart = pm.getDaysInMonth()-startingPos;
\r
23749 var cells = this.cells.elements;
\r
23750 var textEls = this.textNodes;
\r
23751 days += startingPos;
\r
23753 // convert everything to numbers so it's fast
\r
23754 var day = 86400000;
\r
23755 var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
\r
23756 var today = new Date().clearTime().getTime();
\r
23757 var sel = date.clearTime().getTime();
\r
23758 var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
\r
23759 var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
\r
23760 var ddMatch = this.disabledDatesRE;
\r
23761 var ddText = this.disabledDatesText;
\r
23762 var ddays = this.disabledDays ? this.disabledDays.join('') : false;
\r
23763 var ddaysText = this.disabledDaysText;
\r
23764 var format = this.format;
\r
23766 if(this.showToday){
\r
23767 var td = new Date().clearTime();
\r
23768 var disable = (td < min || td > max ||
\r
23769 (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||
\r
23770 (ddays && ddays.indexOf(td.getDay()) != -1));
\r
23772 if(!this.disabled){
\r
23773 this.todayBtn.setDisabled(disable);
\r
23774 this.todayKeyListener[disable ? 'disable' : 'enable']();
\r
23778 var setCellClass = function(cal, cell){
\r
23780 var t = d.getTime();
\r
23781 cell.firstChild.dateValue = t;
\r
23783 cell.className += ' x-date-today';
\r
23784 cell.title = cal.todayText;
\r
23787 cell.className += ' x-date-selected';
\r
23789 Ext.fly(cell.firstChild).focus(50);
\r
23794 cell.className = ' x-date-disabled';
\r
23795 cell.title = cal.minText;
\r
23799 cell.className = ' x-date-disabled';
\r
23800 cell.title = cal.maxText;
\r
23804 if(ddays.indexOf(d.getDay()) != -1){
\r
23805 cell.title = ddaysText;
\r
23806 cell.className = ' x-date-disabled';
\r
23809 if(ddMatch && format){
\r
23810 var fvalue = d.dateFormat(format);
\r
23811 if(ddMatch.test(fvalue)){
\r
23812 cell.title = ddText.replace('%0', fvalue);
\r
23813 cell.className = ' x-date-disabled';
\r
23819 for(; i < startingPos; i++) {
\r
23820 textEls[i].innerHTML = (++prevStart);
\r
23821 d.setDate(d.getDate()+1);
\r
23822 cells[i].className = 'x-date-prevday';
\r
23823 setCellClass(this, cells[i]);
\r
23825 for(; i < days; i++){
\r
23826 var intDay = i - startingPos + 1;
\r
23827 textEls[i].innerHTML = (intDay);
\r
23828 d.setDate(d.getDate()+1);
\r
23829 cells[i].className = 'x-date-active';
\r
23830 setCellClass(this, cells[i]);
\r
23832 var extraDays = 0;
\r
23833 for(; i < 42; i++) {
\r
23834 textEls[i].innerHTML = (++extraDays);
\r
23835 d.setDate(d.getDate()+1);
\r
23836 cells[i].className = 'x-date-nextday';
\r
23837 setCellClass(this, cells[i]);
\r
23840 this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear());
\r
23842 if(!this.internalRender){
\r
23843 var main = this.el.dom.firstChild;
\r
23844 var w = main.offsetWidth;
\r
23845 this.el.setWidth(w + this.el.getBorderWidth('lr'));
\r
23846 Ext.fly(main).setWidth(w);
\r
23847 this.internalRender = true;
\r
23848 // opera does not respect the auto grow header center column
\r
23849 // then, after it gets a width opera refuses to recalculate
\r
23850 // without a second pass
\r
23851 if(Ext.isOpera && !this.secondPass){
\r
23852 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px';
\r
23853 this.secondPass = true;
\r
23854 this.update.defer(10, this, [date]);
\r
23860 beforeDestroy : function() {
\r
23861 if(this.rendered){
\r
23862 this.keyNav.disable();
\r
23863 this.keyNav = null;
\r
23865 this.leftClickRpt,
\r
23866 this.rightClickRpt,
\r
23867 this.monthPicker,
\r
23876 * @cfg {String} autoEl @hide
\r
23880 Ext.reg('datepicker', Ext.DatePicker);
\r
23882 * @class Ext.LoadMask
23883 * A simple utility class for generically masking elements while loading data. If the {@link #store}
23884 * config option is specified, the masking will be automatically synchronized with the store's loading
23885 * process and the mask element will be cached for reuse. For all other elements, this mask will replace the
23886 * element's Updater load indicator and will be destroyed after the initial load.
23887 * <p>Example usage:</p>
23890 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
23894 * Create a new LoadMask
23895 * @param {Mixed} el The element or DOM node, or its id
23896 * @param {Object} config The config object
23898 Ext.LoadMask = function(el, config){
23899 this.el = Ext.get(el);
23900 Ext.apply(this, config);
23902 this.store.on('beforeload', this.onBeforeLoad, this);
23903 this.store.on('load', this.onLoad, this);
23904 this.store.on('exception', this.onLoad, this);
23905 this.removeMask = Ext.value(this.removeMask, false);
23907 var um = this.el.getUpdater();
23908 um.showLoadIndicator = false; // disable the default indicator
23909 um.on('beforeupdate', this.onBeforeLoad, this);
23910 um.on('update', this.onLoad, this);
23911 um.on('failure', this.onLoad, this);
23912 this.removeMask = Ext.value(this.removeMask, true);
23916 Ext.LoadMask.prototype = {
23918 * @cfg {Ext.data.Store} store
23919 * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
23920 * hidden on either load sucess, or load fail.
23923 * @cfg {Boolean} removeMask
23924 * True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
23925 * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false.
23928 * @cfg {String} msg
23929 * The text to display in a centered loading message box (defaults to 'Loading...')
23931 msg : 'Loading...',
23933 * @cfg {String} msgCls
23934 * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
23936 msgCls : 'x-mask-loading',
23939 * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
23945 * Disables the mask to prevent it from being displayed
23947 disable : function(){
23948 this.disabled = true;
23952 * Enables the mask so that it can be displayed
23954 enable : function(){
23955 this.disabled = false;
23959 onLoad : function(){
23960 this.el.unmask(this.removeMask);
23964 onBeforeLoad : function(){
23965 if(!this.disabled){
23966 this.el.mask(this.msg, this.msgCls);
23971 * Show this LoadMask over the configured Element.
23974 this.onBeforeLoad();
23978 * Hide this LoadMask.
23985 destroy : function(){
23987 this.store.un('beforeload', this.onBeforeLoad, this);
23988 this.store.un('load', this.onLoad, this);
23989 this.store.un('exception', this.onLoad, this);
23991 var um = this.el.getUpdater();
23992 um.un('beforeupdate', this.onBeforeLoad, this);
23993 um.un('update', this.onLoad, this);
23994 um.un('failure', this.onLoad, this);
23998 * @class Ext.Slider
\r
23999 * @extends Ext.BoxComponent
\r
24000 * Slider which supports vertical or horizontal orientation, keyboard adjustments,
\r
24001 * configurable snapping, axis clicking and animation. Can be added as an item to
\r
24002 * any container. Example usage:
\r
24005 renderTo: Ext.getBody(),
\r
24014 Ext.Slider = Ext.extend(Ext.BoxComponent, {
\r
24016 * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
\r
24019 * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
\r
24023 * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
\r
24027 * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
\r
24031 * @cfg {Number/Boolean} decimalPrecision.
\r
24032 * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
\r
24033 * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
\r
24035 decimalPrecision: 0,
\r
24037 * @cfg {Number} keyIncrement How many units to change the Slider when adjusting with keyboard navigation. Defaults to 1. If the increment config is larger, it will be used instead.
\r
24041 * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
\r
24045 clickRange: [5,15],
\r
24047 * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
\r
24049 clickToChange : true,
\r
24051 * @cfg {Boolean} animate Turn on or off animation. Defaults to true
\r
24056 * True while the thumb is in a drag operation
\r
24061 // private override
\r
24062 initComponent : function(){
\r
24063 if(!Ext.isDefined(this.value)){
\r
24064 this.value = this.minValue;
\r
24066 Ext.Slider.superclass.initComponent.call(this);
\r
24067 this.keyIncrement = Math.max(this.increment, this.keyIncrement);
\r
24070 * @event beforechange
\r
24071 * Fires before the slider value is changed. By returning false from an event handler,
\r
24072 * you can cancel the event and prevent the slider from changing.
\r
24073 * @param {Ext.Slider} slider The slider
\r
24074 * @param {Number} newValue The new value which the slider is being changed to.
\r
24075 * @param {Number} oldValue The old value which the slider was previously.
\r
24080 * Fires when the slider value is changed.
\r
24081 * @param {Ext.Slider} slider The slider
\r
24082 * @param {Number} newValue The new value which the slider has been changed to.
\r
24086 * @event changecomplete
\r
24087 * Fires when the slider value is changed by the user and any drag operations have completed.
\r
24088 * @param {Ext.Slider} slider The slider
\r
24089 * @param {Number} newValue The new value which the slider has been changed to.
\r
24091 'changecomplete',
\r
24093 * @event dragstart
\r
24094 * Fires after a drag operation has started.
\r
24095 * @param {Ext.Slider} slider The slider
\r
24096 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24101 * Fires continuously during the drag operation while the mouse is moving.
\r
24102 * @param {Ext.Slider} slider The slider
\r
24103 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24108 * Fires after the drag operation has completed.
\r
24109 * @param {Ext.Slider} slider The slider
\r
24110 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24115 if(this.vertical){
\r
24116 Ext.apply(this, Ext.Slider.Vertical);
\r
24120 // private override
\r
24121 onRender : function(){
\r
24123 cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
\r
24124 cn:{cls:'x-slider-end',cn:{cls:'x-slider-inner',cn:[{cls:'x-slider-thumb'},{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]}}
\r
24126 Ext.Slider.superclass.onRender.apply(this, arguments);
\r
24127 this.endEl = this.el.first();
\r
24128 this.innerEl = this.endEl.first();
\r
24129 this.thumb = this.innerEl.first();
\r
24130 this.halfThumb = (this.vertical ? this.thumb.getHeight() : this.thumb.getWidth())/2;
\r
24131 this.focusEl = this.thumb.next();
\r
24132 this.initEvents();
\r
24135 // private override
\r
24136 initEvents : function(){
\r
24137 this.thumb.addClassOnOver('x-slider-thumb-over');
\r
24138 this.mon(this.el, {
\r
24140 mousedown: this.onMouseDown,
\r
24141 keydown: this.onKeyDown
\r
24144 this.focusEl.swallowEvent("click", true);
\r
24146 this.tracker = new Ext.dd.DragTracker({
\r
24147 onBeforeStart: this.onBeforeDragStart.createDelegate(this),
\r
24148 onStart: this.onDragStart.createDelegate(this),
\r
24149 onDrag: this.onDrag.createDelegate(this),
\r
24150 onEnd: this.onDragEnd.createDelegate(this),
\r
24154 this.tracker.initEl(this.thumb);
\r
24155 this.on('beforedestroy', this.tracker.destroy, this.tracker);
\r
24158 // private override
\r
24159 onMouseDown : function(e){
\r
24160 if(this.disabled) {return;}
\r
24161 if(this.clickToChange && e.target != this.thumb.dom){
\r
24162 var local = this.innerEl.translatePoints(e.getXY());
\r
24163 this.onClickChange(local);
\r
24169 onClickChange : function(local){
\r
24170 if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){
\r
24171 this.setValue(Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
\r
24176 onKeyDown : function(e){
\r
24177 if(this.disabled){e.preventDefault();return;}
\r
24178 var k = e.getKey();
\r
24184 this.setValue(this.maxValue, undefined, true);
\r
24186 this.setValue(this.value+this.keyIncrement, undefined, true);
\r
24193 this.setValue(this.minValue, undefined, true);
\r
24195 this.setValue(this.value-this.keyIncrement, undefined, true);
\r
24199 e.preventDefault();
\r
24204 doSnap : function(value){
\r
24205 if(!this.increment || this.increment == 1 || !value) {
\r
24208 var newValue = value, inc = this.increment;
\r
24209 var m = value % inc;
\r
24214 }else if(m * 2 < -inc){
\r
24218 return newValue.constrain(this.minValue, this.maxValue);
\r
24222 afterRender : function(){
\r
24223 Ext.Slider.superclass.afterRender.apply(this, arguments);
\r
24224 if(this.value !== undefined){
\r
24225 var v = this.normalizeValue(this.value);
\r
24226 if(v !== this.value){
\r
24227 delete this.value;
\r
24228 this.setValue(v, false);
\r
24230 this.moveThumb(this.translateValue(v), false);
\r
24236 getRatio : function(){
\r
24237 var w = this.innerEl.getWidth();
\r
24238 var v = this.maxValue - this.minValue;
\r
24239 return v == 0 ? w : (w/v);
\r
24243 normalizeValue : function(v){
\r
24244 v = this.doSnap(v);
\r
24245 v = Ext.util.Format.round(v, this.decimalPrecision);
\r
24246 v = v.constrain(this.minValue, this.maxValue);
\r
24251 * Programmatically sets the value of the Slider. Ensures that the value is constrained within
\r
24252 * the minValue and maxValue.
\r
24253 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
\r
24254 * @param {Boolean} animate Turn on or off animation, defaults to true
\r
24256 setValue : function(v, animate, changeComplete){
\r
24257 v = this.normalizeValue(v);
\r
24258 if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){
\r
24260 this.moveThumb(this.translateValue(v), animate !== false);
\r
24261 this.fireEvent('change', this, v);
\r
24262 if(changeComplete){
\r
24263 this.fireEvent('changecomplete', this, v);
\r
24269 translateValue : function(v){
\r
24270 var ratio = this.getRatio();
\r
24271 return (v * ratio)-(this.minValue * ratio)-this.halfThumb;
\r
24274 reverseValue : function(pos){
\r
24275 var ratio = this.getRatio();
\r
24276 return (pos+this.halfThumb+(this.minValue * ratio))/ratio;
\r
24280 moveThumb: function(v, animate){
\r
24281 if(!animate || this.animate === false){
\r
24282 this.thumb.setLeft(v);
\r
24284 this.thumb.shift({left: v, stopFx: true, duration:.35});
\r
24289 focus : function(){
\r
24290 this.focusEl.focus(10);
\r
24294 onBeforeDragStart : function(e){
\r
24295 return !this.disabled;
\r
24299 onDragStart: function(e){
\r
24300 this.thumb.addClass('x-slider-thumb-drag');
\r
24301 this.dragging = true;
\r
24302 this.dragStartValue = this.value;
\r
24303 this.fireEvent('dragstart', this, e);
\r
24307 onDrag: function(e){
\r
24308 var pos = this.innerEl.translatePoints(this.tracker.getXY());
\r
24309 this.setValue(Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false);
\r
24310 this.fireEvent('drag', this, e);
\r
24314 onDragEnd: function(e){
\r
24315 this.thumb.removeClass('x-slider-thumb-drag');
\r
24316 this.dragging = false;
\r
24317 this.fireEvent('dragend', this, e);
\r
24318 if(this.dragStartValue != this.value){
\r
24319 this.fireEvent('changecomplete', this, this.value);
\r
24324 onResize : function(w, h){
\r
24325 this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
\r
24326 this.syncThumb();
\r
24330 onDisable: function(){
\r
24331 Ext.Slider.superclass.onDisable.call(this);
\r
24332 this.thumb.addClass(this.disabledClass);
\r
24334 //IE breaks when using overflow visible and opacity other than 1.
\r
24335 //Create a place holder for the thumb and display it.
\r
24336 var xy = this.thumb.getXY();
\r
24337 this.thumb.hide();
\r
24338 this.innerEl.addClass(this.disabledClass).dom.disabled = true;
\r
24339 if (!this.thumbHolder){
\r
24340 this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
\r
24342 this.thumbHolder.show().setXY(xy);
\r
24347 onEnable: function(){
\r
24348 Ext.Slider.superclass.onEnable.call(this);
\r
24349 this.thumb.removeClass(this.disabledClass);
\r
24351 this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
\r
24352 if (this.thumbHolder){
\r
24353 this.thumbHolder.hide();
\r
24355 this.thumb.show();
\r
24356 this.syncThumb();
\r
24361 * Synchronizes the thumb position to the proper proportion of the total component width based
\r
24362 * on the current slider {@link #value}. This will be called automatically when the Slider
\r
24363 * is resized by a layout, but if it is rendered auto width, this method can be called from
\r
24364 * another resize handler to sync the Slider if necessary.
\r
24366 syncThumb : function(){
\r
24367 if(this.rendered){
\r
24368 this.moveThumb(this.translateValue(this.value));
\r
24373 * Returns the current value of the slider
\r
24374 * @return {Number} The current value of the slider
\r
24376 getValue : function(){
\r
24377 return this.value;
\r
24380 Ext.reg('slider', Ext.Slider);
\r
24382 // private class to support vertical sliders
\r
24383 Ext.Slider.Vertical = {
\r
24384 onResize : function(w, h){
\r
24385 this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
\r
24386 this.syncThumb();
\r
24389 getRatio : function(){
\r
24390 var h = this.innerEl.getHeight();
\r
24391 var v = this.maxValue - this.minValue;
\r
24395 moveThumb: function(v, animate){
\r
24396 if(!animate || this.animate === false){
\r
24397 this.thumb.setBottom(v);
\r
24399 this.thumb.shift({bottom: v, stopFx: true, duration:.35});
\r
24403 onDrag: function(e){
\r
24404 var pos = this.innerEl.translatePoints(this.tracker.getXY());
\r
24405 var bottom = this.innerEl.getHeight()-pos.top;
\r
24406 this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), false);
\r
24407 this.fireEvent('drag', this, e);
\r
24410 onClickChange : function(local){
\r
24411 if(local.left > this.clickRange[0] && local.left < this.clickRange[1]){
\r
24412 var bottom = this.innerEl.getHeight()-local.top;
\r
24413 this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), undefined, true);
\r
24417 * @class Ext.ProgressBar
\r
24418 * @extends Ext.BoxComponent
\r
24419 * <p>An updateable progress bar component. The progress bar supports two different modes: manual and automatic.</p>
\r
24420 * <p>In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the
\r
24421 * progress bar as needed from your own code. This method is most appropriate when you want to show progress
\r
24422 * throughout an operation that has predictable points of interest at which you can update the control.</p>
\r
24423 * <p>In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it
\r
24424 * once the operation is complete. You can optionally have the progress bar wait for a specific amount of time
\r
24425 * and then clear itself. Automatic mode is most appropriate for timed operations or asynchronous operations in
\r
24426 * which you have no need for indicating intermediate progress.</p>
\r
24427 * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0)
\r
24428 * @cfg {String} text The progress bar text (defaults to '')
\r
24429 * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress
\r
24430 * bar's internal text element)
\r
24431 * @cfg {String} id The progress bar element's id (defaults to an auto-generated id)
\r
24432 * @xtype progress
\r
24434 Ext.ProgressBar = Ext.extend(Ext.BoxComponent, {
\r
24436 * @cfg {String} baseCls
\r
24437 * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress')
\r
24439 baseCls : 'x-progress',
\r
24442 * @cfg {Boolean} animate
\r
24443 * True to animate the progress bar during transitions (defaults to false)
\r
24448 waitTimer : null,
\r
24451 initComponent : function(){
\r
24452 Ext.ProgressBar.superclass.initComponent.call(this);
\r
24456 * Fires after each update interval
\r
24457 * @param {Ext.ProgressBar} this
\r
24458 * @param {Number} The current progress value
\r
24459 * @param {String} The current progress text
\r
24466 onRender : function(ct, position){
\r
24467 var tpl = new Ext.Template(
\r
24468 '<div class="{cls}-wrap">',
\r
24469 '<div class="{cls}-inner">',
\r
24470 '<div class="{cls}-bar">',
\r
24471 '<div class="{cls}-text">',
\r
24472 '<div> </div>',
\r
24475 '<div class="{cls}-text {cls}-text-back">',
\r
24476 '<div> </div>',
\r
24482 this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true)
\r
24483 : tpl.append(ct, {cls: this.baseCls}, true);
\r
24486 this.el.dom.id = this.id;
\r
24488 var inner = this.el.dom.firstChild;
\r
24489 this.progressBar = Ext.get(inner.firstChild);
\r
24492 //use an external text el
\r
24493 this.textEl = Ext.get(this.textEl);
\r
24494 delete this.textTopEl;
\r
24496 //setup our internal layered text els
\r
24497 this.textTopEl = Ext.get(this.progressBar.dom.firstChild);
\r
24498 var textBackEl = Ext.get(inner.childNodes[1]);
\r
24499 this.textTopEl.setStyle("z-index", 99).addClass('x-hidden');
\r
24500 this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]);
\r
24501 this.textEl.setWidth(inner.offsetWidth);
\r
24503 this.progressBar.setHeight(inner.offsetHeight);
\r
24507 afterRender : function(){
\r
24508 Ext.ProgressBar.superclass.afterRender.call(this);
\r
24510 this.updateProgress(this.value, this.text);
\r
24512 this.updateText(this.text);
\r
24517 * Updates the progress bar value, and optionally its text. If the text argument is not specified,
\r
24518 * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even
\r
24519 * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
\r
24520 * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
\r
24521 * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
\r
24522 * @param {String} text (optional) The string to display in the progress text element (defaults to '')
\r
24523 * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
\r
24524 * not specified, the default for the class is used (default to false)
\r
24525 * @return {Ext.ProgressBar} this
\r
24527 updateProgress : function(value, text, animate){
\r
24528 this.value = value || 0;
\r
24530 this.updateText(text);
\r
24532 if(this.rendered){
\r
24533 var w = Math.floor(value*this.el.dom.firstChild.offsetWidth);
\r
24534 this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate));
\r
24535 if(this.textTopEl){
\r
24536 //textTopEl should be the same width as the bar so overflow will clip as the bar moves
\r
24537 this.textTopEl.removeClass('x-hidden').setWidth(w);
\r
24540 this.fireEvent('update', this, value, text);
\r
24545 * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress
\r
24546 * bar will automatically reset after a fixed amount of time and optionally call a callback function
\r
24547 * if specified. If no duration is passed in, then the progress bar will run indefinitely and must
\r
24548 * be manually cleared by calling {@link #reset}. The wait method accepts a config object with
\r
24549 * the following properties:
\r
24551 Property Type Description
\r
24552 ---------- ------------ ----------------------------------------------------------------------
\r
24553 duration Number The length of time in milliseconds that the progress bar should
\r
24554 run before resetting itself (defaults to undefined, in which case it
\r
24555 will run indefinitely until reset is called)
\r
24556 interval Number The length of time in milliseconds between each progress update
\r
24557 (defaults to 1000 ms)
\r
24558 animate Boolean Whether to animate the transition of the progress bar. If this value is
\r
24559 not specified, the default for the class is used.
\r
24560 increment Number The number of progress update segments to display within the progress
\r
24561 bar (defaults to 10). If the bar reaches the end and is still
\r
24562 updating, it will automatically wrap back to the beginning.
\r
24563 text String Optional text to display in the progress bar element (defaults to '').
\r
24564 fn Function A callback function to execute after the progress bar finishes auto-
\r
24565 updating. The function will be called with no arguments. This function
\r
24566 will be ignored if duration is not specified since in that case the
\r
24567 progress bar can only be stopped programmatically, so any required function
\r
24568 should be called by the same code after it resets the progress bar.
\r
24569 scope Object The scope that is passed to the callback function (only applies when
\r
24570 duration and fn are both passed).
\r
24575 var p = new Ext.ProgressBar({
\r
24576 renderTo: 'my-el'
\r
24579 //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
\r
24581 interval: 100, //bar will move fast!
\r
24584 text: 'Updating...',
\r
24587 Ext.fly('status').update('Done!');
\r
24591 //Or update indefinitely until some async action completes, then reset manually
\r
24593 myAction.on('complete', function(){
\r
24595 Ext.fly('status').update('Done!');
\r
24598 * @param {Object} config (optional) Configuration options
\r
24599 * @return {Ext.ProgressBar} this
\r
24601 wait : function(o){
\r
24602 if(!this.waitTimer){
\r
24603 var scope = this;
\r
24605 this.updateText(o.text);
\r
24606 this.waitTimer = Ext.TaskMgr.start({
\r
24607 run: function(i){
\r
24608 var inc = o.increment || 10;
\r
24609 this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
\r
24611 interval: o.interval || 1000,
\r
24612 duration: o.duration,
\r
24613 onStop: function(){
\r
24615 o.fn.apply(o.scope || this);
\r
24626 * Returns true if the progress bar is currently in a {@link #wait} operation
\r
24627 * @return {Boolean} True if waiting, else false
\r
24629 isWaiting : function(){
\r
24630 return this.waitTimer !== null;
\r
24634 * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress
\r
24635 * bar itself will display the updated text.
\r
24636 * @param {String} text (optional) The string to display in the progress text element (defaults to '')
\r
24637 * @return {Ext.ProgressBar} this
\r
24639 updateText : function(text){
\r
24640 this.text = text || ' ';
\r
24641 if(this.rendered){
\r
24642 this.textEl.update(this.text);
\r
24648 * Synchronizes the inner bar width to the proper proportion of the total componet width based
\r
24649 * on the current progress {@link #value}. This will be called automatically when the ProgressBar
\r
24650 * is resized by a layout, but if it is rendered auto width, this method can be called from
\r
24651 * another resize handler to sync the ProgressBar if necessary.
\r
24653 syncProgressBar : function(){
\r
24655 this.updateProgress(this.value, this.text);
\r
24661 * Sets the size of the progress bar.
\r
24662 * @param {Number} width The new width in pixels
\r
24663 * @param {Number} height The new height in pixels
\r
24664 * @return {Ext.ProgressBar} this
\r
24666 setSize : function(w, h){
\r
24667 Ext.ProgressBar.superclass.setSize.call(this, w, h);
\r
24668 if(this.textTopEl){
\r
24669 var inner = this.el.dom.firstChild;
\r
24670 this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
\r
24672 this.syncProgressBar();
\r
24677 * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress
\r
24678 * bar will also be hidden (using the {@link #hideMode} property internally).
\r
24679 * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
\r
24680 * @return {Ext.ProgressBar} this
\r
24682 reset : function(hide){
\r
24683 this.updateProgress(0);
\r
24684 if(this.textTopEl){
\r
24685 this.textTopEl.addClass('x-hidden');
\r
24687 if(this.waitTimer){
\r
24688 this.waitTimer.onStop = null; //prevent recursion
\r
24689 Ext.TaskMgr.stop(this.waitTimer);
\r
24690 this.waitTimer = null;
\r
24692 if(hide === true){
\r
24698 Ext.reg('progress', Ext.ProgressBar);/*
24699 * These classes are derivatives of the similarly named classes in the YUI Library.
24700 * The original license:
24701 * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
24702 * Code licensed under the BSD License:
24703 * http://developer.yahoo.net/yui/license.txt
24708 var Event=Ext.EventManager;
24709 var Dom=Ext.lib.Dom;
24712 * @class Ext.dd.DragDrop
24713 * Defines the interface and base operation of items that that can be
24714 * dragged or can be drop targets. It was designed to be extended, overriding
24715 * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
24716 * Up to three html elements can be associated with a DragDrop instance:
24718 * <li>linked element: the element that is passed into the constructor.
24719 * This is the element which defines the boundaries for interaction with
24720 * other DragDrop objects.</li>
24721 * <li>handle element(s): The drag operation only occurs if the element that
24722 * was clicked matches a handle element. By default this is the linked
24723 * element, but there are times that you will want only a portion of the
24724 * linked element to initiate the drag operation, and the setHandleElId()
24725 * method provides a way to define this.</li>
24726 * <li>drag element: this represents the element that would be moved along
24727 * with the cursor during a drag operation. By default, this is the linked
24728 * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
24729 * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
24732 * This class should not be instantiated until the onload event to ensure that
24733 * the associated elements are available.
24734 * The following would define a DragDrop obj that would interact with any
24735 * other DragDrop obj in the "group1" group:
24737 * dd = new Ext.dd.DragDrop("div1", "group1");
24739 * Since none of the event handlers have been implemented, nothing would
24740 * actually happen if you were to run the code above. Normally you would
24741 * override this class or one of the default implementations, but you can
24742 * also override the methods you want on an instance of the class...
24744 * dd.onDragDrop = function(e, id) {
24745 * alert("dd was dropped on " + id);
24749 * @param {String} id of the element that is linked to this instance
24750 * @param {String} sGroup the group of related DragDrop objects
24751 * @param {object} config an object containing configurable attributes
24752 * Valid properties for DragDrop:
24753 * padding, isTarget, maintainOffset, primaryButtonOnly
24755 Ext.dd.DragDrop = function(id, sGroup, config) {
24757 this.init(id, sGroup, config);
24761 Ext.dd.DragDrop.prototype = {
24764 * Set to false to enable a DragDrop object to fire drag events while dragging
24765 * over its own Element. Defaults to true - DragDrop objects do not by default
24766 * fire drag events to themselves.
24767 * @property ignoreSelf
24772 * The id of the element associated with this object. This is what we
24773 * refer to as the "linked element" because the size and position of
24774 * this element is used to determine when the drag and drop objects have
24782 * Configuration attributes passed into the constructor
24789 * The id of the element that will be dragged. By default this is same
24790 * as the linked element , but could be changed to another element. Ex:
24792 * @property dragElId
24799 * The ID of the element that initiates the drag operation. By default
24800 * this is the linked element, but could be changed to be a child of this
24801 * element. This lets us do things like only starting the drag when the
24802 * header element within the linked html element is clicked.
24803 * @property handleElId
24810 * An object who's property names identify HTML tags to be considered invalid as drag handles.
24811 * A non-null property value identifies the tag as invalid. Defaults to the
24812 * following value which prevents drag operations from being initiated by <a> elements:<pre><code>
24816 * @property invalidHandleTypes
24819 invalidHandleTypes: null,
24822 * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
24823 * A non-null property value identifies the ID as invalid. For example, to prevent
24824 * dragging from being initiated on element ID "foo", use:<pre><code>
24828 * @property invalidHandleIds
24831 invalidHandleIds: null,
24834 * An Array of CSS class names for elements to be considered in valid as drag handles.
24835 * @property invalidHandleClasses
24838 invalidHandleClasses: null,
24841 * The linked element's absolute X position at the time the drag was
24843 * @property startPageX
24850 * The linked element's absolute X position at the time the drag was
24852 * @property startPageY
24859 * The group defines a logical collection of DragDrop objects that are
24860 * related. Instances only get events when interacting with other
24861 * DragDrop object in the same group. This lets us define multiple
24862 * groups using a single DragDrop subclass if we want.
24864 * @type object An object in the format {'group1':true, 'group2':true}
24869 * Individual drag/drop instances can be locked. This will prevent
24870 * onmousedown start drag.
24878 * Lock this instance
24881 lock: function() { this.locked = true; },
24884 * When set to true, other DD objects in cooperating DDGroups do not receive
24885 * notification events when this DD object is dragged over them. Defaults to false.
24886 * @property moveOnly
24892 * Unlock this instace
24895 unlock: function() { this.locked = false; },
24898 * By default, all instances can be a drop target. This can be disabled by
24899 * setting isTarget to false.
24900 * @property isTarget
24906 * The padding configured for this drag and drop object for calculating
24907 * the drop zone intersection with this object.
24908 * @property padding
24909 * @type int[] An array containing the 4 padding values: [top, right, bottom, left]
24914 * Cached reference to the linked element
24915 * @property _domRef
24921 * Internal typeof flag
24922 * @property __ygDragDrop
24925 __ygDragDrop: true,
24928 * Set to true when horizontal contraints are applied
24929 * @property constrainX
24936 * Set to true when vertical contraints are applied
24937 * @property constrainY
24944 * The left constraint
24952 * The right constraint
24960 * The up constraint
24969 * The down constraint
24977 * Maintain offsets when we resetconstraints. Set to true when you want
24978 * the position of the element relative to its parent to stay the same
24979 * when the page changes
24981 * @property maintainOffset
24984 maintainOffset: false,
24987 * Array of pixel locations the element will snap to if we specified a
24988 * horizontal graduation/interval. This array is generated automatically
24989 * when you define a tick interval.
24996 * Array of pixel locations the element will snap to if we specified a
24997 * vertical graduation/interval. This array is generated automatically
24998 * when you define a tick interval.
25005 * By default the drag and drop instance will only respond to the primary
25006 * button click (left button for a right-handed mouse). Set to true to
25007 * allow drag and drop to start with any mouse click that is propogated
25009 * @property primaryButtonOnly
25012 primaryButtonOnly: true,
25015 * The availabe property is false until the linked dom element is accessible.
25016 * @property available
25022 * By default, drags can only be initiated if the mousedown occurs in the
25023 * region the linked element is. This is done in part to work around a
25024 * bug in some browsers that mis-report the mousedown if the previous
25025 * mouseup happened outside of the window. This property is set to true
25026 * if outer handles are defined.
25028 * @property hasOuterHandles
25032 hasOuterHandles: false,
25035 * Code that executes immediately before the startDrag event
25036 * @method b4StartDrag
25039 b4StartDrag: function(x, y) { },
25042 * Abstract method called after a drag/drop object is clicked
25043 * and the drag or mousedown time thresholds have beeen met.
25044 * @method startDrag
25045 * @param {int} X click location
25046 * @param {int} Y click location
25048 startDrag: function(x, y) { /* override this */ },
25051 * Code that executes immediately before the onDrag event
25055 b4Drag: function(e) { },
25058 * Abstract method called during the onMouseMove event while dragging an
25061 * @param {Event} e the mousemove event
25063 onDrag: function(e) { /* override this */ },
25066 * Abstract method called when this element fist begins hovering over
25067 * another DragDrop obj
25068 * @method onDragEnter
25069 * @param {Event} e the mousemove event
25070 * @param {String|DragDrop[]} id In POINT mode, the element
25071 * id this is hovering over. In INTERSECT mode, an array of one or more
25072 * dragdrop items being hovered over.
25074 onDragEnter: function(e, id) { /* override this */ },
25077 * Code that executes immediately before the onDragOver event
25078 * @method b4DragOver
25081 b4DragOver: function(e) { },
25084 * Abstract method called when this element is hovering over another
25086 * @method onDragOver
25087 * @param {Event} e the mousemove event
25088 * @param {String|DragDrop[]} id In POINT mode, the element
25089 * id this is hovering over. In INTERSECT mode, an array of dd items
25090 * being hovered over.
25092 onDragOver: function(e, id) { /* override this */ },
25095 * Code that executes immediately before the onDragOut event
25096 * @method b4DragOut
25099 b4DragOut: function(e) { },
25102 * Abstract method called when we are no longer hovering over an element
25103 * @method onDragOut
25104 * @param {Event} e the mousemove event
25105 * @param {String|DragDrop[]} id In POINT mode, the element
25106 * id this was hovering over. In INTERSECT mode, an array of dd items
25107 * that the mouse is no longer over.
25109 onDragOut: function(e, id) { /* override this */ },
25112 * Code that executes immediately before the onDragDrop event
25113 * @method b4DragDrop
25116 b4DragDrop: function(e) { },
25119 * Abstract method called when this item is dropped on another DragDrop
25121 * @method onDragDrop
25122 * @param {Event} e the mouseup event
25123 * @param {String|DragDrop[]} id In POINT mode, the element
25124 * id this was dropped on. In INTERSECT mode, an array of dd items this
25127 onDragDrop: function(e, id) { /* override this */ },
25130 * Abstract method called when this item is dropped on an area with no
25132 * @method onInvalidDrop
25133 * @param {Event} e the mouseup event
25135 onInvalidDrop: function(e) { /* override this */ },
25138 * Code that executes immediately before the endDrag event
25139 * @method b4EndDrag
25142 b4EndDrag: function(e) { },
25145 * Fired when we are done dragging the object
25147 * @param {Event} e the mouseup event
25149 endDrag: function(e) { /* override this */ },
25152 * Code executed immediately before the onMouseDown event
25153 * @method b4MouseDown
25154 * @param {Event} e the mousedown event
25157 b4MouseDown: function(e) { },
25160 * Event handler that fires when a drag/drop obj gets a mousedown
25161 * @method onMouseDown
25162 * @param {Event} e the mousedown event
25164 onMouseDown: function(e) { /* override this */ },
25167 * Event handler that fires when a drag/drop obj gets a mouseup
25168 * @method onMouseUp
25169 * @param {Event} e the mouseup event
25171 onMouseUp: function(e) { /* override this */ },
25174 * Override the onAvailable method to do what is needed after the initial
25175 * position was determined.
25176 * @method onAvailable
25178 onAvailable: function () {
25182 * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
25185 defaultPadding : {left:0, right:0, top:0, bottom:0},
25188 * Initializes the drag drop object's constraints to restrict movement to a certain element.
25192 var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
25193 { dragElId: "existingProxyDiv" });
25194 dd.startDrag = function(){
25195 this.constrainTo("parent-id");
25198 * Or you can initalize it using the {@link Ext.Element} object:
25200 Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
25201 startDrag : function(){
25202 this.constrainTo("parent-id");
25206 * @param {Mixed} constrainTo The element to constrain to.
25207 * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
25208 * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
25209 * an object containing the sides to pad. For example: {right:10, bottom:10}
25210 * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
25212 constrainTo : function(constrainTo, pad, inContent){
25213 if(typeof pad == "number"){
25214 pad = {left: pad, right:pad, top:pad, bottom:pad};
25216 pad = pad || this.defaultPadding;
25217 var b = Ext.get(this.getEl()).getBox();
25218 var ce = Ext.get(constrainTo);
25219 var s = ce.getScroll();
25220 var c, cd = ce.dom;
25221 if(cd == document.body){
25222 c = { x: s.left, y: s.top, width: Ext.lib.Dom.getViewWidth(), height: Ext.lib.Dom.getViewHeight()};
25224 var xy = ce.getXY();
25225 c = {x : xy[0]+s.left, y: xy[1]+s.top, width: cd.clientWidth, height: cd.clientHeight};
25229 var topSpace = b.y - c.y;
25230 var leftSpace = b.x - c.x;
25232 this.resetConstraints();
25233 this.setXConstraint(leftSpace - (pad.left||0), // left
25234 c.width - leftSpace - b.width - (pad.right||0), //right
25237 this.setYConstraint(topSpace - (pad.top||0), //top
25238 c.height - topSpace - b.height - (pad.bottom||0), //bottom
25244 * Returns a reference to the linked element
25246 * @return {HTMLElement} the html element
25248 getEl: function() {
25249 if (!this._domRef) {
25250 this._domRef = Ext.getDom(this.id);
25253 return this._domRef;
25257 * Returns a reference to the actual element to drag. By default this is
25258 * the same as the html element, but it can be assigned to another
25259 * element. An example of this can be found in Ext.dd.DDProxy
25260 * @method getDragEl
25261 * @return {HTMLElement} the html element
25263 getDragEl: function() {
25264 return Ext.getDom(this.dragElId);
25268 * Sets up the DragDrop object. Must be called in the constructor of any
25269 * Ext.dd.DragDrop subclass
25271 * @param id the id of the linked element
25272 * @param {String} sGroup the group of related items
25273 * @param {object} config configuration attributes
25275 init: function(id, sGroup, config) {
25276 this.initTarget(id, sGroup, config);
25277 Event.on(this.id, "mousedown", this.handleMouseDown, this);
25278 // Event.on(this.id, "selectstart", Event.preventDefault);
25282 * Initializes Targeting functionality only... the object does not
25283 * get a mousedown handler.
25284 * @method initTarget
25285 * @param id the id of the linked element
25286 * @param {String} sGroup the group of related items
25287 * @param {object} config configuration attributes
25289 initTarget: function(id, sGroup, config) {
25291 // configuration attributes
25292 this.config = config || {};
25294 // create a local reference to the drag and drop manager
25295 this.DDM = Ext.dd.DDM;
25296 // initialize the groups array
25299 // assume that we have an element reference instead of an id if the
25300 // parameter is not a string
25301 if (typeof id !== "string") {
25308 // add to an interaction group
25309 this.addToGroup((sGroup) ? sGroup : "default");
25311 // We don't want to register this as the handle with the manager
25312 // so we just set the id rather than calling the setter.
25313 this.handleElId = id;
25315 // the linked element is the element that gets dragged by default
25316 this.setDragElId(id);
25318 // by default, clicked anchors will not start drag operations.
25319 this.invalidHandleTypes = { A: "A" };
25320 this.invalidHandleIds = {};
25321 this.invalidHandleClasses = [];
25323 this.applyConfig();
25325 this.handleOnAvailable();
25329 * Applies the configuration parameters that were passed into the constructor.
25330 * This is supposed to happen at each level through the inheritance chain. So
25331 * a DDProxy implentation will execute apply config on DDProxy, DD, and
25332 * DragDrop in order to get all of the parameters that are available in
25334 * @method applyConfig
25336 applyConfig: function() {
25338 // configurable properties:
25339 // padding, isTarget, maintainOffset, primaryButtonOnly
25340 this.padding = this.config.padding || [0, 0, 0, 0];
25341 this.isTarget = (this.config.isTarget !== false);
25342 this.maintainOffset = (this.config.maintainOffset);
25343 this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
25348 * Executed when the linked element is available
25349 * @method handleOnAvailable
25352 handleOnAvailable: function() {
25353 this.available = true;
25354 this.resetConstraints();
25355 this.onAvailable();
25359 * Configures the padding for the target zone in px. Effectively expands
25360 * (or reduces) the virtual object size for targeting calculations.
25361 * Supports css-style shorthand; if only one parameter is passed, all sides
25362 * will have that padding, and if only two are passed, the top and bottom
25363 * will have the first param, the left and right the second.
25364 * @method setPadding
25365 * @param {int} iTop Top pad
25366 * @param {int} iRight Right pad
25367 * @param {int} iBot Bot pad
25368 * @param {int} iLeft Left pad
25370 setPadding: function(iTop, iRight, iBot, iLeft) {
25371 // this.padding = [iLeft, iRight, iTop, iBot];
25372 if (!iRight && 0 !== iRight) {
25373 this.padding = [iTop, iTop, iTop, iTop];
25374 } else if (!iBot && 0 !== iBot) {
25375 this.padding = [iTop, iRight, iTop, iRight];
25377 this.padding = [iTop, iRight, iBot, iLeft];
25382 * Stores the initial placement of the linked element.
25383 * @method setInitPosition
25384 * @param {int} diffX the X offset, default 0
25385 * @param {int} diffY the Y offset, default 0
25387 setInitPosition: function(diffX, diffY) {
25388 var el = this.getEl();
25390 if (!this.DDM.verifyEl(el)) {
25394 var dx = diffX || 0;
25395 var dy = diffY || 0;
25397 var p = Dom.getXY( el );
25399 this.initPageX = p[0] - dx;
25400 this.initPageY = p[1] - dy;
25402 this.lastPageX = p[0];
25403 this.lastPageY = p[1];
25406 this.setStartPosition(p);
25410 * Sets the start position of the element. This is set when the obj
25411 * is initialized, the reset when a drag is started.
25412 * @method setStartPosition
25413 * @param pos current position (from previous lookup)
25416 setStartPosition: function(pos) {
25417 var p = pos || Dom.getXY( this.getEl() );
25418 this.deltaSetXY = null;
25420 this.startPageX = p[0];
25421 this.startPageY = p[1];
25425 * Add this instance to a group of related drag/drop objects. All
25426 * instances belong to at least one group, and can belong to as many
25427 * groups as needed.
25428 * @method addToGroup
25429 * @param sGroup {string} the name of the group
25431 addToGroup: function(sGroup) {
25432 this.groups[sGroup] = true;
25433 this.DDM.regDragDrop(this, sGroup);
25437 * Remove's this instance from the supplied interaction group
25438 * @method removeFromGroup
25439 * @param {string} sGroup The group to drop
25441 removeFromGroup: function(sGroup) {
25442 if (this.groups[sGroup]) {
25443 delete this.groups[sGroup];
25446 this.DDM.removeDDFromGroup(this, sGroup);
25450 * Allows you to specify that an element other than the linked element
25451 * will be moved with the cursor during a drag
25452 * @method setDragElId
25453 * @param id {string} the id of the element that will be used to initiate the drag
25455 setDragElId: function(id) {
25456 this.dragElId = id;
25460 * Allows you to specify a child of the linked element that should be
25461 * used to initiate the drag operation. An example of this would be if
25462 * you have a content div with text and links. Clicking anywhere in the
25463 * content area would normally start the drag operation. Use this method
25464 * to specify that an element inside of the content div is the element
25465 * that starts the drag operation.
25466 * @method setHandleElId
25467 * @param id {string} the id of the element that will be used to
25468 * initiate the drag.
25470 setHandleElId: function(id) {
25471 if (typeof id !== "string") {
25474 this.handleElId = id;
25475 this.DDM.regHandle(this.id, id);
25479 * Allows you to set an element outside of the linked element as a drag
25481 * @method setOuterHandleElId
25482 * @param id the id of the element that will be used to initiate the drag
25484 setOuterHandleElId: function(id) {
25485 if (typeof id !== "string") {
25488 Event.on(id, "mousedown",
25489 this.handleMouseDown, this);
25490 this.setHandleElId(id);
25492 this.hasOuterHandles = true;
25496 * Remove all drag and drop hooks for this element
25499 unreg: function() {
25500 Event.un(this.id, "mousedown",
25501 this.handleMouseDown);
25502 this._domRef = null;
25503 this.DDM._remove(this);
25506 destroy : function(){
25511 * Returns true if this instance is locked, or the drag drop mgr is locked
25512 * (meaning that all drag/drop is disabled on the page.)
25514 * @return {boolean} true if this obj or all drag/drop is locked, else
25517 isLocked: function() {
25518 return (this.DDM.isLocked() || this.locked);
25522 * Fired when this object is clicked
25523 * @method handleMouseDown
25525 * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
25528 handleMouseDown: function(e, oDD){
25529 if (this.primaryButtonOnly && e.button != 0) {
25533 if (this.isLocked()) {
25537 this.DDM.refreshCache(this.groups);
25539 var pt = new Ext.lib.Point(Ext.lib.Event.getPageX(e), Ext.lib.Event.getPageY(e));
25540 if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) ) {
25542 if (this.clickValidator(e)) {
25544 // set the initial element position
25545 this.setStartPosition();
25548 this.b4MouseDown(e);
25549 this.onMouseDown(e);
25551 this.DDM.handleMouseDown(e, this);
25553 this.DDM.stopEvent(e);
25561 clickValidator: function(e) {
25562 var target = e.getTarget();
25563 return ( this.isValidHandleChild(target) &&
25564 (this.id == this.handleElId ||
25565 this.DDM.handleWasClicked(target, this.id)) );
25569 * Allows you to specify a tag name that should not start a drag operation
25570 * when clicked. This is designed to facilitate embedding links within a
25571 * drag handle that do something other than start the drag.
25572 * @method addInvalidHandleType
25573 * @param {string} tagName the type of element to exclude
25575 addInvalidHandleType: function(tagName) {
25576 var type = tagName.toUpperCase();
25577 this.invalidHandleTypes[type] = type;
25581 * Lets you to specify an element id for a child of a drag handle
25582 * that should not initiate a drag
25583 * @method addInvalidHandleId
25584 * @param {string} id the element id of the element you wish to ignore
25586 addInvalidHandleId: function(id) {
25587 if (typeof id !== "string") {
25590 this.invalidHandleIds[id] = id;
25594 * Lets you specify a css class of elements that will not initiate a drag
25595 * @method addInvalidHandleClass
25596 * @param {string} cssClass the class of the elements you wish to ignore
25598 addInvalidHandleClass: function(cssClass) {
25599 this.invalidHandleClasses.push(cssClass);
25603 * Unsets an excluded tag name set by addInvalidHandleType
25604 * @method removeInvalidHandleType
25605 * @param {string} tagName the type of element to unexclude
25607 removeInvalidHandleType: function(tagName) {
25608 var type = tagName.toUpperCase();
25609 // this.invalidHandleTypes[type] = null;
25610 delete this.invalidHandleTypes[type];
25614 * Unsets an invalid handle id
25615 * @method removeInvalidHandleId
25616 * @param {string} id the id of the element to re-enable
25618 removeInvalidHandleId: function(id) {
25619 if (typeof id !== "string") {
25622 delete this.invalidHandleIds[id];
25626 * Unsets an invalid css class
25627 * @method removeInvalidHandleClass
25628 * @param {string} cssClass the class of the element(s) you wish to
25631 removeInvalidHandleClass: function(cssClass) {
25632 for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
25633 if (this.invalidHandleClasses[i] == cssClass) {
25634 delete this.invalidHandleClasses[i];
25640 * Checks the tag exclusion list to see if this click should be ignored
25641 * @method isValidHandleChild
25642 * @param {HTMLElement} node the HTMLElement to evaluate
25643 * @return {boolean} true if this is a valid tag type, false if not
25645 isValidHandleChild: function(node) {
25648 // var n = (node.nodeName == "#text") ? node.parentNode : node;
25651 nodeName = node.nodeName.toUpperCase();
25653 nodeName = node.nodeName;
25655 valid = valid && !this.invalidHandleTypes[nodeName];
25656 valid = valid && !this.invalidHandleIds[node.id];
25658 for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
25659 valid = !Ext.fly(node).hasClass(this.invalidHandleClasses[i]);
25668 * Create the array of horizontal tick marks if an interval was specified
25669 * in setXConstraint().
25670 * @method setXTicks
25673 setXTicks: function(iStartX, iTickSize) {
25675 this.xTickSize = iTickSize;
25679 for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
25681 this.xTicks[this.xTicks.length] = i;
25686 for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
25688 this.xTicks[this.xTicks.length] = i;
25693 this.xTicks.sort(this.DDM.numericSort) ;
25697 * Create the array of vertical tick marks if an interval was specified in
25698 * setYConstraint().
25699 * @method setYTicks
25702 setYTicks: function(iStartY, iTickSize) {
25704 this.yTickSize = iTickSize;
25708 for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
25710 this.yTicks[this.yTicks.length] = i;
25715 for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
25717 this.yTicks[this.yTicks.length] = i;
25722 this.yTicks.sort(this.DDM.numericSort) ;
25726 * By default, the element can be dragged any place on the screen. Use
25727 * this method to limit the horizontal travel of the element. Pass in
25728 * 0,0 for the parameters if you want to lock the drag to the y axis.
25729 * @method setXConstraint
25730 * @param {int} iLeft the number of pixels the element can move to the left
25731 * @param {int} iRight the number of pixels the element can move to the
25733 * @param {int} iTickSize optional parameter for specifying that the
25735 * should move iTickSize pixels at a time.
25737 setXConstraint: function(iLeft, iRight, iTickSize) {
25738 this.leftConstraint = iLeft;
25739 this.rightConstraint = iRight;
25741 this.minX = this.initPageX - iLeft;
25742 this.maxX = this.initPageX + iRight;
25743 if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
25745 this.constrainX = true;
25749 * Clears any constraints applied to this instance. Also clears ticks
25750 * since they can't exist independent of a constraint at this time.
25751 * @method clearConstraints
25753 clearConstraints: function() {
25754 this.constrainX = false;
25755 this.constrainY = false;
25760 * Clears any tick interval defined for this instance
25761 * @method clearTicks
25763 clearTicks: function() {
25764 this.xTicks = null;
25765 this.yTicks = null;
25766 this.xTickSize = 0;
25767 this.yTickSize = 0;
25771 * By default, the element can be dragged any place on the screen. Set
25772 * this to limit the vertical travel of the element. Pass in 0,0 for the
25773 * parameters if you want to lock the drag to the x axis.
25774 * @method setYConstraint
25775 * @param {int} iUp the number of pixels the element can move up
25776 * @param {int} iDown the number of pixels the element can move down
25777 * @param {int} iTickSize optional parameter for specifying that the
25778 * element should move iTickSize pixels at a time.
25780 setYConstraint: function(iUp, iDown, iTickSize) {
25781 this.topConstraint = iUp;
25782 this.bottomConstraint = iDown;
25784 this.minY = this.initPageY - iUp;
25785 this.maxY = this.initPageY + iDown;
25786 if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
25788 this.constrainY = true;
25793 * resetConstraints must be called if you manually reposition a dd element.
25794 * @method resetConstraints
25795 * @param {boolean} maintainOffset
25797 resetConstraints: function() {
25800 // Maintain offsets if necessary
25801 if (this.initPageX || this.initPageX === 0) {
25802 // figure out how much this thing has moved
25803 var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
25804 var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
25806 this.setInitPosition(dx, dy);
25808 // This is the first time we have detected the element's position
25810 this.setInitPosition();
25813 if (this.constrainX) {
25814 this.setXConstraint( this.leftConstraint,
25815 this.rightConstraint,
25819 if (this.constrainY) {
25820 this.setYConstraint( this.topConstraint,
25821 this.bottomConstraint,
25827 * Normally the drag element is moved pixel by pixel, but we can specify
25828 * that it move a number of pixels at a time. This method resolves the
25829 * location when we have it set up like this.
25831 * @param {int} val where we want to place the object
25832 * @param {int[]} tickArray sorted array of valid points
25833 * @return {int} the closest tick
25836 getTick: function(val, tickArray) {
25839 // If tick interval is not defined, it is effectively 1 pixel,
25840 // so we return the value passed to us.
25842 } else if (tickArray[0] >= val) {
25843 // The value is lower than the first tick, so we return the first
25845 return tickArray[0];
25847 for (var i=0, len=tickArray.length; i<len; ++i) {
25849 if (tickArray[next] && tickArray[next] >= val) {
25850 var diff1 = val - tickArray[i];
25851 var diff2 = tickArray[next] - val;
25852 return (diff2 > diff1) ? tickArray[i] : tickArray[next];
25856 // The value is larger than the last tick, so we return the last
25858 return tickArray[tickArray.length - 1];
25865 * @return {string} string representation of the dd obj
25867 toString: function() {
25868 return ("DragDrop " + this.id);
25875 * The drag and drop utility provides a framework for building drag and drop
25876 * applications. In addition to enabling drag and drop for specific elements,
25877 * the drag and drop elements are tracked by the manager class, and the
25878 * interactions between the various elements are tracked during the drag and
25879 * the implementing code is notified about these important moments.
25882 // Only load the library once. Rewriting the manager class would orphan
25883 // existing drag and drop instances.
25884 if (!Ext.dd.DragDropMgr) {
25887 * @class Ext.dd.DragDropMgr
25888 * DragDropMgr is a singleton that tracks the element interaction for
25889 * all DragDrop items in the window. Generally, you will not call
25890 * this class directly, but it does have helper methods that could
25891 * be useful in your DragDrop implementations.
25894 Ext.dd.DragDropMgr = function() {
25896 var Event = Ext.EventManager;
25901 * Two dimensional Array of registered DragDrop objects. The first
25902 * dimension is the DragDrop item group, the second the DragDrop
25905 * @type {string: string}
25912 * Array of element ids defined as drag handles. Used to determine
25913 * if the element that generated the mousedown event is actually the
25914 * handle and not the html element itself.
25915 * @property handleIds
25916 * @type {string: string}
25923 * the DragDrop object that is currently being dragged
25924 * @property dragCurrent
25932 * the DragDrop object(s) that are being hovered over
25933 * @property dragOvers
25941 * the X distance between the cursor and the object being dragged
25950 * the Y distance between the cursor and the object being dragged
25959 * Flag to determine if we should prevent the default behavior of the
25960 * events we define. By default this is true, but this can be set to
25961 * false if you need the default behavior (not recommended)
25962 * @property preventDefault
25966 preventDefault: true,
25969 * Flag to determine if we should stop the propagation of the events
25970 * we generate. This is true by default but you may want to set it to
25971 * false if the html element contains other features that require the
25973 * @property stopPropagation
25977 stopPropagation: true,
25980 * Internal flag that is set to true when drag and drop has been
25982 * @property initialized
25986 initialized: false,
25989 * All drag and drop can be disabled.
25997 * Called the first time an element is registered.
26003 this.initialized = true;
26007 * In point mode, drag and drop interaction is defined by the
26008 * location of the cursor during the drag/drop
26016 * In intersect mode, drag and drop interaction is defined by the
26017 * overlap of two or more drag and drop objects.
26018 * @property INTERSECT
26025 * The current drag and drop mode. Default: POINT
26033 * Runs method on all drag and drop objects
26034 * @method _execOnAll
26038 _execOnAll: function(sMethod, args) {
26039 for (var i in this.ids) {
26040 for (var j in this.ids[i]) {
26041 var oDD = this.ids[i][j];
26042 if (! this.isTypeOfDD(oDD)) {
26045 oDD[sMethod].apply(oDD, args);
26051 * Drag and drop initialization. Sets up the global event handlers
26056 _onLoad: function() {
26061 Event.on(document, "mouseup", this.handleMouseUp, this, true);
26062 Event.on(document, "mousemove", this.handleMouseMove, this, true);
26063 Event.on(window, "unload", this._onUnload, this, true);
26064 Event.on(window, "resize", this._onResize, this, true);
26065 // Event.on(window, "mouseout", this._test);
26070 * Reset constraints on all drag and drop objs
26071 * @method _onResize
26075 _onResize: function(e) {
26076 this._execOnAll("resetConstraints", []);
26080 * Lock all drag and drop functionality
26084 lock: function() { this.locked = true; },
26087 * Unlock all drag and drop functionality
26091 unlock: function() { this.locked = false; },
26094 * Is drag and drop locked?
26096 * @return {boolean} True if drag and drop is locked, false otherwise.
26099 isLocked: function() { return this.locked; },
26102 * Location cache that is set for all drag drop objects when a drag is
26103 * initiated, cleared when the drag is finished.
26104 * @property locationCache
26111 * Set useCache to false if you want to force object the lookup of each
26112 * drag and drop linked element constantly during a drag.
26113 * @property useCache
26120 * The number of pixels that the mouse needs to move after the
26121 * mousedown before the drag is initiated. Default=3;
26122 * @property clickPixelThresh
26126 clickPixelThresh: 3,
26129 * The number of milliseconds after the mousedown event to initiate the
26130 * drag if we don't get a mouseup event. Default=1000
26131 * @property clickTimeThresh
26135 clickTimeThresh: 350,
26138 * Flag that indicates that either the drag pixel threshold or the
26139 * mousdown time threshold has been met
26140 * @property dragThreshMet
26145 dragThreshMet: false,
26148 * Timeout used for the click time threshold
26149 * @property clickTimeout
26154 clickTimeout: null,
26157 * The X position of the mousedown event stored for later use when a
26158 * drag threshold is met.
26167 * The Y position of the mousedown event stored for later use when a
26168 * drag threshold is met.
26177 * Each DragDrop instance must be registered with the DragDropMgr.
26178 * This is executed in DragDrop.init()
26179 * @method regDragDrop
26180 * @param {DragDrop} oDD the DragDrop object to register
26181 * @param {String} sGroup the name of the group this element belongs to
26184 regDragDrop: function(oDD, sGroup) {
26185 if (!this.initialized) { this.init(); }
26187 if (!this.ids[sGroup]) {
26188 this.ids[sGroup] = {};
26190 this.ids[sGroup][oDD.id] = oDD;
26194 * Removes the supplied dd instance from the supplied group. Executed
26195 * by DragDrop.removeFromGroup, so don't call this function directly.
26196 * @method removeDDFromGroup
26200 removeDDFromGroup: function(oDD, sGroup) {
26201 if (!this.ids[sGroup]) {
26202 this.ids[sGroup] = {};
26205 var obj = this.ids[sGroup];
26206 if (obj && obj[oDD.id]) {
26207 delete obj[oDD.id];
26212 * Unregisters a drag and drop item. This is executed in
26213 * DragDrop.unreg, use that method instead of calling this directly.
26218 _remove: function(oDD) {
26219 for (var g in oDD.groups) {
26220 if (g && this.ids[g] && this.ids[g][oDD.id]) {
26221 delete this.ids[g][oDD.id];
26224 delete this.handleIds[oDD.id];
26228 * Each DragDrop handle element must be registered. This is done
26229 * automatically when executing DragDrop.setHandleElId()
26230 * @method regHandle
26231 * @param {String} sDDId the DragDrop id this element is a handle for
26232 * @param {String} sHandleId the id of the element that is the drag
26236 regHandle: function(sDDId, sHandleId) {
26237 if (!this.handleIds[sDDId]) {
26238 this.handleIds[sDDId] = {};
26240 this.handleIds[sDDId][sHandleId] = sHandleId;
26244 * Utility function to determine if a given element has been
26245 * registered as a drag drop item.
26246 * @method isDragDrop
26247 * @param {String} id the element id to check
26248 * @return {boolean} true if this element is a DragDrop item,
26252 isDragDrop: function(id) {
26253 return ( this.getDDById(id) ) ? true : false;
26257 * Returns the drag and drop instances that are in all groups the
26258 * passed in instance belongs to.
26259 * @method getRelated
26260 * @param {DragDrop} p_oDD the obj to get related data for
26261 * @param {boolean} bTargetsOnly if true, only return targetable objs
26262 * @return {DragDrop[]} the related instances
26265 getRelated: function(p_oDD, bTargetsOnly) {
26267 for (var i in p_oDD.groups) {
26268 for (var j in this.ids[i]) {
26269 var dd = this.ids[i][j];
26270 if (! this.isTypeOfDD(dd)) {
26273 if (!bTargetsOnly || dd.isTarget) {
26274 oDDs[oDDs.length] = dd;
26283 * Returns true if the specified dd target is a legal target for
26284 * the specifice drag obj
26285 * @method isLegalTarget
26286 * @param {DragDrop} the drag obj
26287 * @param {DragDrop} the target
26288 * @return {boolean} true if the target is a legal target for the
26292 isLegalTarget: function (oDD, oTargetDD) {
26293 var targets = this.getRelated(oDD, true);
26294 for (var i=0, len=targets.length;i<len;++i) {
26295 if (targets[i].id == oTargetDD.id) {
26304 * My goal is to be able to transparently determine if an object is
26305 * typeof DragDrop, and the exact subclass of DragDrop. typeof
26306 * returns "object", oDD.constructor.toString() always returns
26307 * "DragDrop" and not the name of the subclass. So for now it just
26308 * evaluates a well-known variable in DragDrop.
26309 * @method isTypeOfDD
26310 * @param {Object} the object to evaluate
26311 * @return {boolean} true if typeof oDD = DragDrop
26314 isTypeOfDD: function (oDD) {
26315 return (oDD && oDD.__ygDragDrop);
26319 * Utility function to determine if a given element has been
26320 * registered as a drag drop handle for the given Drag Drop object.
26322 * @param {String} id the element id to check
26323 * @return {boolean} true if this element is a DragDrop handle, false
26327 isHandle: function(sDDId, sHandleId) {
26328 return ( this.handleIds[sDDId] &&
26329 this.handleIds[sDDId][sHandleId] );
26333 * Returns the DragDrop instance for a given id
26334 * @method getDDById
26335 * @param {String} id the id of the DragDrop object
26336 * @return {DragDrop} the drag drop object, null if it is not found
26339 getDDById: function(id) {
26340 for (var i in this.ids) {
26341 if (this.ids[i][id]) {
26342 return this.ids[i][id];
26349 * Fired after a registered DragDrop object gets the mousedown event.
26350 * Sets up the events required to track the object being dragged
26351 * @method handleMouseDown
26352 * @param {Event} e the event
26353 * @param oDD the DragDrop object being dragged
26357 handleMouseDown: function(e, oDD) {
26359 Ext.QuickTips.disable();
26361 if(this.dragCurrent){
26362 // the original browser mouseup wasn't handled (e.g. outside FF browser window)
26363 // so clean up first to avoid breaking the next drag
26364 this.handleMouseUp(e);
26367 this.currentTarget = e.getTarget();
26368 this.dragCurrent = oDD;
26370 var el = oDD.getEl();
26372 // track start position
26373 this.startX = e.getPageX();
26374 this.startY = e.getPageY();
26376 this.deltaX = this.startX - el.offsetLeft;
26377 this.deltaY = this.startY - el.offsetTop;
26379 this.dragThreshMet = false;
26381 this.clickTimeout = setTimeout(
26383 var DDM = Ext.dd.DDM;
26384 DDM.startDrag(DDM.startX, DDM.startY);
26386 this.clickTimeThresh );
26390 * Fired when either the drag pixel threshol or the mousedown hold
26391 * time threshold has been met.
26392 * @method startDrag
26393 * @param x {int} the X position of the original mousedown
26394 * @param y {int} the Y position of the original mousedown
26397 startDrag: function(x, y) {
26398 clearTimeout(this.clickTimeout);
26399 if (this.dragCurrent) {
26400 this.dragCurrent.b4StartDrag(x, y);
26401 this.dragCurrent.startDrag(x, y);
26403 this.dragThreshMet = true;
26407 * Internal function to handle the mouseup event. Will be invoked
26408 * from the context of the document.
26409 * @method handleMouseUp
26410 * @param {Event} e the event
26414 handleMouseUp: function(e) {
26417 Ext.QuickTips.enable();
26419 if (! this.dragCurrent) {
26423 clearTimeout(this.clickTimeout);
26425 if (this.dragThreshMet) {
26426 this.fireEvents(e, true);
26436 * Utility to stop event propagation and event default, if these
26437 * features are turned on.
26438 * @method stopEvent
26439 * @param {Event} e the event as returned by this.getEvent()
26442 stopEvent: function(e){
26443 if(this.stopPropagation) {
26444 e.stopPropagation();
26447 if (this.preventDefault) {
26448 e.preventDefault();
26453 * Internal function to clean up event handlers after the drag
26454 * operation is complete
26456 * @param {Event} e the event
26460 stopDrag: function(e) {
26461 // Fire the drag end event for the item that was dragged
26462 if (this.dragCurrent) {
26463 if (this.dragThreshMet) {
26464 this.dragCurrent.b4EndDrag(e);
26465 this.dragCurrent.endDrag(e);
26468 this.dragCurrent.onMouseUp(e);
26471 this.dragCurrent = null;
26472 this.dragOvers = {};
26476 * Internal function to handle the mousemove event. Will be invoked
26477 * from the context of the html element.
26479 * @TODO figure out what we can do about mouse events lost when the
26480 * user drags objects beyond the window boundary. Currently we can
26481 * detect this in internet explorer by verifying that the mouse is
26482 * down during the mousemove event. Firefox doesn't give us the
26483 * button state on the mousemove event.
26484 * @method handleMouseMove
26485 * @param {Event} e the event
26489 handleMouseMove: function(e) {
26490 if (! this.dragCurrent) {
26493 // var button = e.which || e.button;
26495 // check for IE mouseup outside of page boundary
26496 if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
26498 return this.handleMouseUp(e);
26501 if (!this.dragThreshMet) {
26502 var diffX = Math.abs(this.startX - e.getPageX());
26503 var diffY = Math.abs(this.startY - e.getPageY());
26504 if (diffX > this.clickPixelThresh ||
26505 diffY > this.clickPixelThresh) {
26506 this.startDrag(this.startX, this.startY);
26510 if (this.dragThreshMet) {
26511 this.dragCurrent.b4Drag(e);
26512 this.dragCurrent.onDrag(e);
26513 if(!this.dragCurrent.moveOnly){
26514 this.fireEvents(e, false);
26524 * Iterates over all of the DragDrop elements to find ones we are
26525 * hovering over or dropping on
26526 * @method fireEvents
26527 * @param {Event} e the event
26528 * @param {boolean} isDrop is this a drop op or a mouseover op?
26532 fireEvents: function(e, isDrop) {
26533 var dc = this.dragCurrent;
26535 // If the user did the mouse up outside of the window, we could
26536 // get here even though we have ended the drag.
26537 if (!dc || dc.isLocked()) {
26541 var pt = e.getPoint();
26543 // cache the previous dragOver array
26549 var enterEvts = [];
26551 // Check to see if the object(s) we were hovering over is no longer
26552 // being hovered over so we can fire the onDragOut event
26553 for (var i in this.dragOvers) {
26555 var ddo = this.dragOvers[i];
26557 if (! this.isTypeOfDD(ddo)) {
26561 if (! this.isOverTarget(pt, ddo, this.mode)) {
26562 outEvts.push( ddo );
26565 oldOvers[i] = true;
26566 delete this.dragOvers[i];
26569 for (var sGroup in dc.groups) {
26571 if ("string" != typeof sGroup) {
26575 for (i in this.ids[sGroup]) {
26576 var oDD = this.ids[sGroup][i];
26577 if (! this.isTypeOfDD(oDD)) {
26581 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
26582 if (this.isOverTarget(pt, oDD, this.mode)) {
26583 // look for drop interactions
26585 dropEvts.push( oDD );
26586 // look for drag enter and drag over interactions
26589 // initial drag over: dragEnter fires
26590 if (!oldOvers[oDD.id]) {
26591 enterEvts.push( oDD );
26592 // subsequent drag overs: dragOver fires
26594 overEvts.push( oDD );
26597 this.dragOvers[oDD.id] = oDD;
26605 if (outEvts.length) {
26606 dc.b4DragOut(e, outEvts);
26607 dc.onDragOut(e, outEvts);
26610 if (enterEvts.length) {
26611 dc.onDragEnter(e, enterEvts);
26614 if (overEvts.length) {
26615 dc.b4DragOver(e, overEvts);
26616 dc.onDragOver(e, overEvts);
26619 if (dropEvts.length) {
26620 dc.b4DragDrop(e, dropEvts);
26621 dc.onDragDrop(e, dropEvts);
26625 // fire dragout events
26627 for (i=0, len=outEvts.length; i<len; ++i) {
26628 dc.b4DragOut(e, outEvts[i].id);
26629 dc.onDragOut(e, outEvts[i].id);
26632 // fire enter events
26633 for (i=0,len=enterEvts.length; i<len; ++i) {
26634 // dc.b4DragEnter(e, oDD.id);
26635 dc.onDragEnter(e, enterEvts[i].id);
26638 // fire over events
26639 for (i=0,len=overEvts.length; i<len; ++i) {
26640 dc.b4DragOver(e, overEvts[i].id);
26641 dc.onDragOver(e, overEvts[i].id);
26644 // fire drop events
26645 for (i=0, len=dropEvts.length; i<len; ++i) {
26646 dc.b4DragDrop(e, dropEvts[i].id);
26647 dc.onDragDrop(e, dropEvts[i].id);
26652 // notify about a drop that did not find a target
26653 if (isDrop && !dropEvts.length) {
26654 dc.onInvalidDrop(e);
26660 * Helper function for getting the best match from the list of drag
26661 * and drop objects returned by the drag and drop events when we are
26662 * in INTERSECT mode. It returns either the first object that the
26663 * cursor is over, or the object that has the greatest overlap with
26664 * the dragged element.
26665 * @method getBestMatch
26666 * @param {DragDrop[]} dds The array of drag and drop objects
26668 * @return {DragDrop} The best single match
26671 getBestMatch: function(dds) {
26673 // Return null if the input is not what we expect
26674 //if (!dds || !dds.length || dds.length == 0) {
26676 // If there is only one item, it wins
26677 //} else if (dds.length == 1) {
26679 var len = dds.length;
26684 // Loop through the targeted items
26685 for (var i=0; i<len; ++i) {
26687 // If the cursor is over the object, it wins. If the
26688 // cursor is over multiple matches, the first one we come
26690 if (dd.cursorIsOver) {
26693 // Otherwise the object with the most overlap wins
26696 winner.overlap.getArea() < dd.overlap.getArea()) {
26707 * Refreshes the cache of the top-left and bottom-right points of the
26708 * drag and drop objects in the specified group(s). This is in the
26709 * format that is stored in the drag and drop instance, so typical
26712 * Ext.dd.DragDropMgr.refreshCache(ddinstance.groups);
26716 * Ext.dd.DragDropMgr.refreshCache({group1:true, group2:true});
26718 * @TODO this really should be an indexed array. Alternatively this
26719 * method could accept both.
26720 * @method refreshCache
26721 * @param {Object} groups an associative array of groups to refresh
26724 refreshCache: function(groups) {
26725 for (var sGroup in groups) {
26726 if ("string" != typeof sGroup) {
26729 for (var i in this.ids[sGroup]) {
26730 var oDD = this.ids[sGroup][i];
26732 if (this.isTypeOfDD(oDD)) {
26733 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
26734 var loc = this.getLocation(oDD);
26736 this.locationCache[oDD.id] = loc;
26738 delete this.locationCache[oDD.id];
26739 // this will unregister the drag and drop object if
26740 // the element is not in a usable state
26749 * This checks to make sure an element exists and is in the DOM. The
26750 * main purpose is to handle cases where innerHTML is used to remove
26751 * drag and drop objects from the DOM. IE provides an 'unspecified
26752 * error' when trying to access the offsetParent of such an element
26754 * @param {HTMLElement} el the element to check
26755 * @return {boolean} true if the element looks usable
26758 verifyEl: function(el) {
26763 parent = el.offsetParent;
26766 parent = el.offsetParent;
26777 * Returns a Region object containing the drag and drop element's position
26778 * and size, including the padding configured for it
26779 * @method getLocation
26780 * @param {DragDrop} oDD the drag and drop object to get the
26782 * @return {Ext.lib.Region} a Region object representing the total area
26783 * the element occupies, including any padding
26784 * the instance is configured for.
26787 getLocation: function(oDD) {
26788 if (! this.isTypeOfDD(oDD)) {
26792 var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
26795 pos= Ext.lib.Dom.getXY(el);
26803 x2 = x1 + el.offsetWidth;
26805 y2 = y1 + el.offsetHeight;
26807 t = y1 - oDD.padding[0];
26808 r = x2 + oDD.padding[1];
26809 b = y2 + oDD.padding[2];
26810 l = x1 - oDD.padding[3];
26812 return new Ext.lib.Region( t, r, b, l );
26816 * Checks the cursor location to see if it over the target
26817 * @method isOverTarget
26818 * @param {Ext.lib.Point} pt The point to evaluate
26819 * @param {DragDrop} oTarget the DragDrop object we are inspecting
26820 * @return {boolean} true if the mouse is over the target
26824 isOverTarget: function(pt, oTarget, intersect) {
26825 // use cache if available
26826 var loc = this.locationCache[oTarget.id];
26827 if (!loc || !this.useCache) {
26828 loc = this.getLocation(oTarget);
26829 this.locationCache[oTarget.id] = loc;
26837 oTarget.cursorIsOver = loc.contains( pt );
26839 // DragDrop is using this as a sanity check for the initial mousedown
26840 // in this case we are done. In POINT mode, if the drag obj has no
26841 // contraints, we are also done. Otherwise we need to evaluate the
26842 // location of the target as related to the actual location of the
26843 // dragged element.
26844 var dc = this.dragCurrent;
26845 if (!dc || !dc.getTargetCoord ||
26846 (!intersect && !dc.constrainX && !dc.constrainY)) {
26847 return oTarget.cursorIsOver;
26850 oTarget.overlap = null;
26852 // Get the current location of the drag element, this is the
26853 // location of the mouse event less the delta that represents
26854 // where the original mousedown happened on the element. We
26855 // need to consider constraints and ticks as well.
26856 var pos = dc.getTargetCoord(pt.x, pt.y);
26858 var el = dc.getDragEl();
26859 var curRegion = new Ext.lib.Region( pos.y,
26860 pos.x + el.offsetWidth,
26861 pos.y + el.offsetHeight,
26864 var overlap = curRegion.intersect(loc);
26867 oTarget.overlap = overlap;
26868 return (intersect) ? true : oTarget.cursorIsOver;
26875 * unload event handler
26876 * @method _onUnload
26880 _onUnload: function(e, me) {
26881 Ext.dd.DragDropMgr.unregAll();
26885 * Cleans up the drag and drop events and objects.
26890 unregAll: function() {
26892 if (this.dragCurrent) {
26894 this.dragCurrent = null;
26897 this._execOnAll("unreg", []);
26899 for (var i in this.elementCache) {
26900 delete this.elementCache[i];
26903 this.elementCache = {};
26908 * A cache of DOM elements
26909 * @property elementCache
26916 * Get the wrapper for the DOM element specified
26917 * @method getElWrapper
26918 * @param {String} id the id of the element to get
26919 * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
26921 * @deprecated This wrapper isn't that useful
26924 getElWrapper: function(id) {
26925 var oWrapper = this.elementCache[id];
26926 if (!oWrapper || !oWrapper.el) {
26927 oWrapper = this.elementCache[id] =
26928 new this.ElementWrapper(Ext.getDom(id));
26934 * Returns the actual DOM element
26935 * @method getElement
26936 * @param {String} id the id of the elment to get
26937 * @return {Object} The element
26938 * @deprecated use Ext.lib.Ext.getDom instead
26941 getElement: function(id) {
26942 return Ext.getDom(id);
26946 * Returns the style property for the DOM element (i.e.,
26947 * document.getElById(id).style)
26949 * @param {String} id the id of the elment to get
26950 * @return {Object} The style property of the element
26951 * @deprecated use Ext.lib.Dom instead
26954 getCss: function(id) {
26955 var el = Ext.getDom(id);
26956 return (el) ? el.style : null;
26960 * Inner class for cached elements
26961 * @class DragDropMgr.ElementWrapper
26966 ElementWrapper: function(el) {
26971 this.el = el || null;
26976 this.id = this.el && el.id;
26978 * A reference to the style property
26981 this.css = this.el && el.style;
26985 * Returns the X position of an html element
26987 * @param el the element for which to get the position
26988 * @return {int} the X coordinate
26990 * @deprecated use Ext.lib.Dom.getX instead
26993 getPosX: function(el) {
26994 return Ext.lib.Dom.getX(el);
26998 * Returns the Y position of an html element
27000 * @param el the element for which to get the position
27001 * @return {int} the Y coordinate
27002 * @deprecated use Ext.lib.Dom.getY instead
27005 getPosY: function(el) {
27006 return Ext.lib.Dom.getY(el);
27010 * Swap two nodes. In IE, we use the native method, for others we
27011 * emulate the IE behavior
27013 * @param n1 the first node to swap
27014 * @param n2 the other node to swap
27017 swapNode: function(n1, n2) {
27021 var p = n2.parentNode;
27022 var s = n2.nextSibling;
27025 p.insertBefore(n1, n2);
27026 } else if (n2 == n1.nextSibling) {
27027 p.insertBefore(n2, n1);
27029 n1.parentNode.replaceChild(n2, n1);
27030 p.insertBefore(n1, s);
27036 * Returns the current scroll position
27037 * @method getScroll
27041 getScroll: function () {
27042 var t, l, dde=document.documentElement, db=document.body;
27043 if (dde && (dde.scrollTop || dde.scrollLeft)) {
27045 l = dde.scrollLeft;
27052 return { top: t, left: l };
27056 * Returns the specified element style property
27058 * @param {HTMLElement} el the element
27059 * @param {string} styleProp the style property
27060 * @return {string} The value of the style property
27061 * @deprecated use Ext.lib.Dom.getStyle
27064 getStyle: function(el, styleProp) {
27065 return Ext.fly(el).getStyle(styleProp);
27069 * Gets the scrollTop
27070 * @method getScrollTop
27071 * @return {int} the document's scrollTop
27074 getScrollTop: function () { return this.getScroll().top; },
27077 * Gets the scrollLeft
27078 * @method getScrollLeft
27079 * @return {int} the document's scrollTop
27082 getScrollLeft: function () { return this.getScroll().left; },
27085 * Sets the x/y position of an element to the location of the
27088 * @param {HTMLElement} moveEl The element to move
27089 * @param {HTMLElement} targetEl The position reference element
27092 moveToEl: function (moveEl, targetEl) {
27093 var aCoord = Ext.lib.Dom.getXY(targetEl);
27094 Ext.lib.Dom.setXY(moveEl, aCoord);
27098 * Numeric array sort function
27099 * @method numericSort
27102 numericSort: function(a, b) { return (a - b); },
27106 * @property _timeoutCount
27113 * Trying to make the load order less important. Without this we get
27114 * an error if this file is loaded before the Event Utility.
27115 * @method _addListeners
27119 _addListeners: function() {
27120 var DDM = Ext.dd.DDM;
27121 if ( Ext.lib.Event && document ) {
27124 if (DDM._timeoutCount > 2000) {
27126 setTimeout(DDM._addListeners, 10);
27127 if (document && document.body) {
27128 DDM._timeoutCount += 1;
27135 * Recursively searches the immediate parent and all child nodes for
27136 * the handle element in order to determine wheter or not it was
27138 * @method handleWasClicked
27139 * @param node the html element to inspect
27142 handleWasClicked: function(node, id) {
27143 if (this.isHandle(id, node.id)) {
27146 // check to see if this is a text node child of the one we want
27147 var p = node.parentNode;
27150 if (this.isHandle(id, p.id)) {
27165 // shorter alias, save a few bytes
27166 Ext.dd.DDM = Ext.dd.DragDropMgr;
27167 Ext.dd.DDM._addListeners();
27173 * A DragDrop implementation where the linked element follows the
27174 * mouse cursor during a drag.
27175 * @extends Ext.dd.DragDrop
27177 * @param {String} id the id of the linked element
27178 * @param {String} sGroup the group of related DragDrop items
27179 * @param {object} config an object containing configurable attributes
27180 * Valid properties for DD:
27183 Ext.dd.DD = function(id, sGroup, config) {
27185 this.init(id, sGroup, config);
27189 Ext.extend(Ext.dd.DD, Ext.dd.DragDrop, {
27192 * When set to true, the utility automatically tries to scroll the browser
27193 * window when a drag and drop element is dragged near the viewport boundary.
27194 * Defaults to true.
27201 * Sets the pointer offset to the distance between the linked element's top
27202 * left corner and the location the element was clicked
27203 * @method autoOffset
27204 * @param {int} iPageX the X coordinate of the click
27205 * @param {int} iPageY the Y coordinate of the click
27207 autoOffset: function(iPageX, iPageY) {
27208 var x = iPageX - this.startPageX;
27209 var y = iPageY - this.startPageY;
27210 this.setDelta(x, y);
27214 * Sets the pointer offset. You can call this directly to force the
27215 * offset to be in a particular location (e.g., pass in 0,0 to set it
27216 * to the center of the object)
27218 * @param {int} iDeltaX the distance from the left
27219 * @param {int} iDeltaY the distance from the top
27221 setDelta: function(iDeltaX, iDeltaY) {
27222 this.deltaX = iDeltaX;
27223 this.deltaY = iDeltaY;
27227 * Sets the drag element to the location of the mousedown or click event,
27228 * maintaining the cursor location relative to the location on the element
27229 * that was clicked. Override this if you want to place the element in a
27230 * location other than where the cursor is.
27231 * @method setDragElPos
27232 * @param {int} iPageX the X coordinate of the mousedown or drag event
27233 * @param {int} iPageY the Y coordinate of the mousedown or drag event
27235 setDragElPos: function(iPageX, iPageY) {
27236 // the first time we do this, we are going to check to make sure
27237 // the element has css positioning
27239 var el = this.getDragEl();
27240 this.alignElWithMouse(el, iPageX, iPageY);
27244 * Sets the element to the location of the mousedown or click event,
27245 * maintaining the cursor location relative to the location on the element
27246 * that was clicked. Override this if you want to place the element in a
27247 * location other than where the cursor is.
27248 * @method alignElWithMouse
27249 * @param {HTMLElement} el the element to move
27250 * @param {int} iPageX the X coordinate of the mousedown or drag event
27251 * @param {int} iPageY the Y coordinate of the mousedown or drag event
27253 alignElWithMouse: function(el, iPageX, iPageY) {
27254 var oCoord = this.getTargetCoord(iPageX, iPageY);
27255 var fly = el.dom ? el : Ext.fly(el, '_dd');
27256 if (!this.deltaSetXY) {
27257 var aCoord = [oCoord.x, oCoord.y];
27259 var newLeft = fly.getLeft(true);
27260 var newTop = fly.getTop(true);
27261 this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
27263 fly.setLeftTop(oCoord.x + this.deltaSetXY[0], oCoord.y + this.deltaSetXY[1]);
27266 this.cachePosition(oCoord.x, oCoord.y);
27267 this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
27272 * Saves the most recent position so that we can reset the constraints and
27273 * tick marks on-demand. We need to know this so that we can calculate the
27274 * number of pixels the element is offset from its original position.
27275 * @method cachePosition
27276 * @param iPageX the current x position (optional, this just makes it so we
27277 * don't have to look it up again)
27278 * @param iPageY the current y position (optional, this just makes it so we
27279 * don't have to look it up again)
27281 cachePosition: function(iPageX, iPageY) {
27283 this.lastPageX = iPageX;
27284 this.lastPageY = iPageY;
27286 var aCoord = Ext.lib.Dom.getXY(this.getEl());
27287 this.lastPageX = aCoord[0];
27288 this.lastPageY = aCoord[1];
27293 * Auto-scroll the window if the dragged object has been moved beyond the
27294 * visible window boundary.
27295 * @method autoScroll
27296 * @param {int} x the drag element's x position
27297 * @param {int} y the drag element's y position
27298 * @param {int} h the height of the drag element
27299 * @param {int} w the width of the drag element
27302 autoScroll: function(x, y, h, w) {
27305 // The client height
27306 var clientH = Ext.lib.Dom.getViewHeight();
27308 // The client width
27309 var clientW = Ext.lib.Dom.getViewWidth();
27311 // The amt scrolled down
27312 var st = this.DDM.getScrollTop();
27314 // The amt scrolled right
27315 var sl = this.DDM.getScrollLeft();
27317 // Location of the bottom of the element
27320 // Location of the right of the element
27323 // The distance from the cursor to the bottom of the visible area,
27324 // adjusted so that we don't scroll if the cursor is beyond the
27325 // element drag constraints
27326 var toBot = (clientH + st - y - this.deltaY);
27328 // The distance from the cursor to the right of the visible area
27329 var toRight = (clientW + sl - x - this.deltaX);
27332 // How close to the edge the cursor must be before we scroll
27333 // var thresh = (document.all) ? 100 : 40;
27336 // How many pixels to scroll per autoscroll op. This helps to reduce
27337 // clunky scrolling. IE is more sensitive about this ... it needs this
27338 // value to be higher.
27339 var scrAmt = (document.all) ? 80 : 30;
27341 // Scroll down if we are near the bottom of the visible page and the
27342 // obj extends below the crease
27343 if ( bot > clientH && toBot < thresh ) {
27344 window.scrollTo(sl, st + scrAmt);
27347 // Scroll up if the window is scrolled down and the top of the object
27348 // goes above the top border
27349 if ( y < st && st > 0 && y - st < thresh ) {
27350 window.scrollTo(sl, st - scrAmt);
27353 // Scroll right if the obj is beyond the right border and the cursor is
27354 // near the border.
27355 if ( right > clientW && toRight < thresh ) {
27356 window.scrollTo(sl + scrAmt, st);
27359 // Scroll left if the window has been scrolled to the right and the obj
27360 // extends past the left border
27361 if ( x < sl && sl > 0 && x - sl < thresh ) {
27362 window.scrollTo(sl - scrAmt, st);
27368 * Finds the location the element should be placed if we want to move
27369 * it to where the mouse location less the click offset would place us.
27370 * @method getTargetCoord
27371 * @param {int} iPageX the X coordinate of the click
27372 * @param {int} iPageY the Y coordinate of the click
27373 * @return an object that contains the coordinates (Object.x and Object.y)
27376 getTargetCoord: function(iPageX, iPageY) {
27379 var x = iPageX - this.deltaX;
27380 var y = iPageY - this.deltaY;
27382 if (this.constrainX) {
27383 if (x < this.minX) { x = this.minX; }
27384 if (x > this.maxX) { x = this.maxX; }
27387 if (this.constrainY) {
27388 if (y < this.minY) { y = this.minY; }
27389 if (y > this.maxY) { y = this.maxY; }
27392 x = this.getTick(x, this.xTicks);
27393 y = this.getTick(y, this.yTicks);
27400 * Sets up config options specific to this class. Overrides
27401 * Ext.dd.DragDrop, but all versions of this method through the
27402 * inheritance chain are called
27404 applyConfig: function() {
27405 Ext.dd.DD.superclass.applyConfig.call(this);
27406 this.scroll = (this.config.scroll !== false);
27410 * Event that fires prior to the onMouseDown event. Overrides
27413 b4MouseDown: function(e) {
27414 // this.resetConstraints();
27415 this.autoOffset(e.getPageX(),
27420 * Event that fires prior to the onDrag event. Overrides
27423 b4Drag: function(e) {
27424 this.setDragElPos(e.getPageX(),
27428 toString: function() {
27429 return ("DD " + this.id);
27432 //////////////////////////////////////////////////////////////////////////
27433 // Debugging ygDragDrop events that can be overridden
27434 //////////////////////////////////////////////////////////////////////////
27436 startDrag: function(x, y) {
27439 onDrag: function(e) {
27442 onDragEnter: function(e, id) {
27445 onDragOver: function(e, id) {
27448 onDragOut: function(e, id) {
27451 onDragDrop: function(e, id) {
27454 endDrag: function(e) {
27461 * @class Ext.dd.DDProxy
27462 * A DragDrop implementation that inserts an empty, bordered div into
27463 * the document that follows the cursor during drag operations. At the time of
27464 * the click, the frame div is resized to the dimensions of the linked html
27465 * element, and moved to the exact location of the linked element.
27467 * References to the "frame" element refer to the single proxy element that
27468 * was created to be dragged in place of all DDProxy elements on the
27471 * @extends Ext.dd.DD
27473 * @param {String} id the id of the linked html element
27474 * @param {String} sGroup the group of related DragDrop objects
27475 * @param {object} config an object containing configurable attributes
27476 * Valid properties for DDProxy in addition to those in DragDrop:
27477 * resizeFrame, centerFrame, dragElId
27479 Ext.dd.DDProxy = function(id, sGroup, config) {
27481 this.init(id, sGroup, config);
27487 * The default drag frame div id
27488 * @property Ext.dd.DDProxy.dragElId
27492 Ext.dd.DDProxy.dragElId = "ygddfdiv";
27494 Ext.extend(Ext.dd.DDProxy, Ext.dd.DD, {
27497 * By default we resize the drag frame to be the same size as the element
27498 * we want to drag (this is to get the frame effect). We can turn it off
27499 * if we want a different behavior.
27500 * @property resizeFrame
27506 * By default the frame is positioned exactly where the drag element is, so
27507 * we use the cursor offset provided by Ext.dd.DD. Another option that works only if
27508 * you do not have constraints on the obj is to have the drag frame centered
27509 * around the cursor. Set centerFrame to true for this effect.
27510 * @property centerFrame
27513 centerFrame: false,
27516 * Creates the proxy element if it does not yet exist
27517 * @method createFrame
27519 createFrame: function() {
27521 var body = document.body;
27523 if (!body || !body.firstChild) {
27524 setTimeout( function() { self.createFrame(); }, 50 );
27528 var div = this.getDragEl();
27531 div = document.createElement("div");
27532 div.id = this.dragElId;
27535 s.position = "absolute";
27536 s.visibility = "hidden";
27538 s.border = "2px solid #aaa";
27541 // appendChild can blow up IE if invoked prior to the window load event
27542 // while rendering a table. It is possible there are other scenarios
27543 // that would cause this to happen as well.
27544 body.insertBefore(div, body.firstChild);
27549 * Initialization for the drag frame element. Must be called in the
27550 * constructor of all subclasses
27551 * @method initFrame
27553 initFrame: function() {
27554 this.createFrame();
27557 applyConfig: function() {
27558 Ext.dd.DDProxy.superclass.applyConfig.call(this);
27560 this.resizeFrame = (this.config.resizeFrame !== false);
27561 this.centerFrame = (this.config.centerFrame);
27562 this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
27566 * Resizes the drag frame to the dimensions of the clicked object, positions
27567 * it over the object, and finally displays it
27568 * @method showFrame
27569 * @param {int} iPageX X click position
27570 * @param {int} iPageY Y click position
27573 showFrame: function(iPageX, iPageY) {
27574 var el = this.getEl();
27575 var dragEl = this.getDragEl();
27576 var s = dragEl.style;
27578 this._resizeProxy();
27580 if (this.centerFrame) {
27581 this.setDelta( Math.round(parseInt(s.width, 10)/2),
27582 Math.round(parseInt(s.height, 10)/2) );
27585 this.setDragElPos(iPageX, iPageY);
27587 Ext.fly(dragEl).show();
27591 * The proxy is automatically resized to the dimensions of the linked
27592 * element when a drag is initiated, unless resizeFrame is set to false
27593 * @method _resizeProxy
27596 _resizeProxy: function() {
27597 if (this.resizeFrame) {
27598 var el = this.getEl();
27599 Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
27603 // overrides Ext.dd.DragDrop
27604 b4MouseDown: function(e) {
27605 var x = e.getPageX();
27606 var y = e.getPageY();
27607 this.autoOffset(x, y);
27608 this.setDragElPos(x, y);
27611 // overrides Ext.dd.DragDrop
27612 b4StartDrag: function(x, y) {
27613 // show the drag frame
27614 this.showFrame(x, y);
27617 // overrides Ext.dd.DragDrop
27618 b4EndDrag: function(e) {
27619 Ext.fly(this.getDragEl()).hide();
27622 // overrides Ext.dd.DragDrop
27623 // By default we try to move the element to the last location of the frame.
27624 // This is so that the default behavior mirrors that of Ext.dd.DD.
27625 endDrag: function(e) {
27627 var lel = this.getEl();
27628 var del = this.getDragEl();
27630 // Show the drag frame briefly so we can get its position
27631 del.style.visibility = "";
27634 // Hide the linked element before the move to get around a Safari
27636 lel.style.visibility = "hidden";
27637 Ext.dd.DDM.moveToEl(lel, del);
27638 del.style.visibility = "hidden";
27639 lel.style.visibility = "";
27644 beforeMove : function(){
27648 afterDrag : function(){
27652 toString: function() {
27653 return ("DDProxy " + this.id);
27658 * @class Ext.dd.DDTarget
27659 * A DragDrop implementation that does not move, but can be a drop
27660 * target. You would get the same result by simply omitting implementation
27661 * for the event callbacks, but this way we reduce the processing cost of the
27662 * event listener and the callbacks.
27663 * @extends Ext.dd.DragDrop
27665 * @param {String} id the id of the element that is a drop target
27666 * @param {String} sGroup the group of related DragDrop objects
27667 * @param {object} config an object containing configurable attributes
27668 * Valid properties for DDTarget in addition to those in
27672 Ext.dd.DDTarget = function(id, sGroup, config) {
27674 this.initTarget(id, sGroup, config);
27678 // Ext.dd.DDTarget.prototype = new Ext.dd.DragDrop();
27679 Ext.extend(Ext.dd.DDTarget, Ext.dd.DragDrop, {
27680 toString: function() {
27681 return ("DDTarget " + this.id);
27685 * @class Ext.dd.DragTracker
\r
27686 * @extends Ext.util.Observable
\r
27688 Ext.dd.DragTracker = function(config){
\r
27689 Ext.apply(this, config);
\r
27692 * @event mousedown
\r
27693 * @param {Object} this
\r
27694 * @param {Object} e event object
\r
27699 * @param {Object} this
\r
27700 * @param {Object} e event object
\r
27704 * @event mousemove
\r
27705 * @param {Object} this
\r
27706 * @param {Object} e event object
\r
27710 * @event dragstart
\r
27711 * @param {Object} this
\r
27712 * @param {Object} startXY the page coordinates of the event
\r
27717 * @param {Object} this
\r
27718 * @param {Object} e event object
\r
27723 * @param {Object} this
\r
27724 * @param {Object} e event object
\r
27729 this.dragRegion = new Ext.lib.Region(0,0,0,0);
\r
27732 this.initEl(this.el);
\r
27736 Ext.extend(Ext.dd.DragTracker, Ext.util.Observable, {
\r
27738 * @cfg {Boolean} active
\r
27739 * Defaults to <tt>false</tt>.
\r
27743 * @cfg {Number} tolerance
\r
27744 * Defaults to <tt>5</tt>.
\r
27748 * @cfg {Boolean/Number} autoStart
\r
27749 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to defer trigger start by 1000 ms.
\r
27750 * Specify a Number for the number of milliseconds to defer trigger start.
\r
27752 autoStart: false,
\r
27754 initEl: function(el){
\r
27755 this.el = Ext.get(el);
\r
27756 el.on('mousedown', this.onMouseDown, this,
\r
27757 this.delegate ? {delegate: this.delegate} : undefined);
\r
27760 destroy : function(){
\r
27761 this.el.un('mousedown', this.onMouseDown, this);
\r
27764 onMouseDown: function(e, target){
\r
27765 if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
\r
27766 this.startXY = this.lastXY = e.getXY();
\r
27767 this.dragTarget = this.delegate ? target : this.el.dom;
\r
27768 if(this.preventDefault !== false){
\r
27769 e.preventDefault();
\r
27771 var doc = Ext.getDoc();
\r
27772 doc.on('mouseup', this.onMouseUp, this);
\r
27773 doc.on('mousemove', this.onMouseMove, this);
\r
27774 doc.on('selectstart', this.stopSelect, this);
\r
27775 if(this.autoStart){
\r
27776 this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this);
\r
27781 onMouseMove: function(e, target){
\r
27782 // HACK: IE hack to see if button was released outside of window. */
\r
27783 if(this.active && Ext.isIE && !e.browserEvent.button){
\r
27784 e.preventDefault();
\r
27785 this.onMouseUp(e);
\r
27789 e.preventDefault();
\r
27790 var xy = e.getXY(), s = this.startXY;
\r
27791 this.lastXY = xy;
\r
27792 if(!this.active){
\r
27793 if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
\r
27794 this.triggerStart();
\r
27799 this.fireEvent('mousemove', this, e);
\r
27801 this.fireEvent('drag', this, e);
\r
27804 onMouseUp: function(e){
\r
27805 var doc = Ext.getDoc();
\r
27806 doc.un('mousemove', this.onMouseMove, this);
\r
27807 doc.un('mouseup', this.onMouseUp, this);
\r
27808 doc.un('selectstart', this.stopSelect, this);
\r
27809 e.preventDefault();
\r
27810 this.clearStart();
\r
27811 var wasActive = this.active;
\r
27812 this.active = false;
\r
27813 delete this.elRegion;
\r
27814 this.fireEvent('mouseup', this, e);
\r
27817 this.fireEvent('dragend', this, e);
\r
27821 triggerStart: function(isTimer){
\r
27822 this.clearStart();
\r
27823 this.active = true;
\r
27824 this.onStart(this.startXY);
\r
27825 this.fireEvent('dragstart', this, this.startXY);
\r
27828 clearStart : function(){
\r
27830 clearTimeout(this.timer);
\r
27831 delete this.timer;
\r
27835 stopSelect : function(e){
\r
27840 onBeforeStart : function(e){
\r
27844 onStart : function(xy){
\r
27848 onDrag : function(e){
\r
27852 onEnd : function(e){
\r
27856 getDragTarget : function(){
\r
27857 return this.dragTarget;
\r
27860 getDragCt : function(){
\r
27864 getXY : function(constrain){
\r
27865 return constrain ?
\r
27866 this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
\r
27869 getOffset : function(constrain){
\r
27870 var xy = this.getXY(constrain);
\r
27871 var s = this.startXY;
\r
27872 return [s[0]-xy[0], s[1]-xy[1]];
\r
27875 constrainModes: {
\r
27876 'point' : function(xy){
\r
27878 if(!this.elRegion){
\r
27879 this.elRegion = this.getDragCt().getRegion();
\r
27882 var dr = this.dragRegion;
\r
27886 dr.right = xy[0];
\r
27887 dr.bottom = xy[1];
\r
27889 dr.constrainTo(this.elRegion);
\r
27891 return [dr.left, dr.top];
\r
27895 * @class Ext.dd.ScrollManager
\r
27896 * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
\r
27897 * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
\r
27898 * but you can also override most of the configs per scroll container by adding a
\r
27899 * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
\r
27900 * {@link #vthresh}, {@link #increment} and {@link #frequency}. Example usage:
\r
27902 var el = Ext.get('scroll-ct');
\r
27903 el.ddScrollConfig = {
\r
27909 Ext.dd.ScrollManager.register(el);
\r
27911 * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
\r
27914 Ext.dd.ScrollManager = function(){
\r
27915 var ddm = Ext.dd.DragDropMgr;
\r
27917 var dragEl = null;
\r
27920 var onStop = function(e){
\r
27925 var triggerRefresh = function(){
\r
27926 if(ddm.dragCurrent){
\r
27927 ddm.refreshCache(ddm.dragCurrent.groups);
\r
27931 var doScroll = function(){
\r
27932 if(ddm.dragCurrent){
\r
27933 var dds = Ext.dd.ScrollManager;
\r
27934 var inc = proc.el.ddScrollConfig ?
\r
27935 proc.el.ddScrollConfig.increment : dds.increment;
\r
27936 if(!dds.animate){
\r
27937 if(proc.el.scroll(proc.dir, inc)){
\r
27938 triggerRefresh();
\r
27941 proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
\r
27946 var clearProc = function(){
\r
27948 clearInterval(proc.id);
\r
27955 var startProc = function(el, dir){
\r
27959 var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
\r
27960 el.ddScrollConfig.frequency : Ext.dd.ScrollManager.frequency;
\r
27961 proc.id = setInterval(doScroll, freq);
\r
27964 var onFire = function(e, isDrop){
\r
27965 if(isDrop || !ddm.dragCurrent){ return; }
\r
27966 var dds = Ext.dd.ScrollManager;
\r
27967 if(!dragEl || dragEl != ddm.dragCurrent){
\r
27968 dragEl = ddm.dragCurrent;
\r
27969 // refresh regions on drag start
\r
27970 dds.refreshCache();
\r
27973 var xy = Ext.lib.Event.getXY(e);
\r
27974 var pt = new Ext.lib.Point(xy[0], xy[1]);
\r
27975 for(var id in els){
\r
27976 var el = els[id], r = el._region;
\r
27977 var c = el.ddScrollConfig ? el.ddScrollConfig : dds;
\r
27978 if(r && r.contains(pt) && el.isScrollable()){
\r
27979 if(r.bottom - pt.y <= c.vthresh){
\r
27980 if(proc.el != el){
\r
27981 startProc(el, "down");
\r
27984 }else if(r.right - pt.x <= c.hthresh){
\r
27985 if(proc.el != el){
\r
27986 startProc(el, "left");
\r
27989 }else if(pt.y - r.top <= c.vthresh){
\r
27990 if(proc.el != el){
\r
27991 startProc(el, "up");
\r
27994 }else if(pt.x - r.left <= c.hthresh){
\r
27995 if(proc.el != el){
\r
27996 startProc(el, "right");
\r
28005 ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
\r
28006 ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
\r
28010 * Registers new overflow element(s) to auto scroll
\r
28011 * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
\r
28013 register : function(el){
\r
28014 if(Ext.isArray(el)){
\r
28015 for(var i = 0, len = el.length; i < len; i++) {
\r
28016 this.register(el[i]);
\r
28019 el = Ext.get(el);
\r
28025 * Unregisters overflow element(s) so they are no longer scrolled
\r
28026 * @param {Mixed/Array} el The id of or the element to be removed or an array of either
\r
28028 unregister : function(el){
\r
28029 if(Ext.isArray(el)){
\r
28030 for(var i = 0, len = el.length; i < len; i++) {
\r
28031 this.unregister(el[i]);
\r
28034 el = Ext.get(el);
\r
28035 delete els[el.id];
\r
28040 * The number of pixels from the top or bottom edge of a container the pointer needs to be to
\r
28041 * trigger scrolling (defaults to 25)
\r
28046 * The number of pixels from the right or left edge of a container the pointer needs to be to
\r
28047 * trigger scrolling (defaults to 25)
\r
28053 * The number of pixels to scroll in each scroll increment (defaults to 50)
\r
28059 * The frequency of scrolls in milliseconds (defaults to 500)
\r
28065 * True to animate the scroll (defaults to true)
\r
28071 * The animation duration in seconds -
\r
28072 * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
\r
28075 animDuration: .4,
\r
28078 * Manually trigger a cache refresh.
\r
28080 refreshCache : function(){
\r
28081 for(var id in els){
\r
28082 if(typeof els[id] == 'object'){ // for people extending the object prototype
\r
28083 els[id]._region = els[id].getRegion();
\r
28089 * @class Ext.dd.Registry
\r
28090 * Provides easy access to all drag drop components that are registered on a page. Items can be retrieved either
\r
28091 * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
\r
28094 Ext.dd.Registry = function(){
\r
28095 var elements = {};
\r
28096 var handles = {};
\r
28097 var autoIdSeed = 0;
\r
28099 var getId = function(el, autogen){
\r
28100 if(typeof el == "string"){
\r
28104 if(!id && autogen !== false){
\r
28105 id = "extdd-" + (++autoIdSeed);
\r
28113 * Resgister a drag drop element
\r
28114 * @param {String/HTMLElement) element The id or DOM node to register
\r
28115 * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
\r
28116 * in drag drop operations. You can populate this object with any arbitrary properties that your own code
\r
28117 * knows how to interpret, plus there are some specific properties known to the Registry that should be
\r
28118 * populated in the data object (if applicable):
\r
28120 Value Description<br />
\r
28121 --------- ------------------------------------------<br />
\r
28122 handles Array of DOM nodes that trigger dragging<br />
\r
28123 for the element being registered<br />
\r
28124 isHandle True if the element passed in triggers<br />
\r
28125 dragging itself, else false
\r
28128 register : function(el, data){
\r
28129 data = data || {};
\r
28130 if(typeof el == "string"){
\r
28131 el = document.getElementById(el);
\r
28134 elements[getId(el)] = data;
\r
28135 if(data.isHandle !== false){
\r
28136 handles[data.ddel.id] = data;
\r
28138 if(data.handles){
\r
28139 var hs = data.handles;
\r
28140 for(var i = 0, len = hs.length; i < len; i++){
\r
28141 handles[getId(hs[i])] = data;
\r
28147 * Unregister a drag drop element
\r
28148 * @param {String/HTMLElement) element The id or DOM node to unregister
\r
28150 unregister : function(el){
\r
28151 var id = getId(el, false);
\r
28152 var data = elements[id];
\r
28154 delete elements[id];
\r
28155 if(data.handles){
\r
28156 var hs = data.handles;
\r
28157 for(var i = 0, len = hs.length; i < len; i++){
\r
28158 delete handles[getId(hs[i], false)];
\r
28165 * Returns the handle registered for a DOM Node by id
\r
28166 * @param {String/HTMLElement} id The DOM node or id to look up
\r
28167 * @return {Object} handle The custom handle data
\r
28169 getHandle : function(id){
\r
28170 if(typeof id != "string"){ // must be element?
\r
28173 return handles[id];
\r
28177 * Returns the handle that is registered for the DOM node that is the target of the event
\r
28178 * @param {Event} e The event
\r
28179 * @return {Object} handle The custom handle data
\r
28181 getHandleFromEvent : function(e){
\r
28182 var t = Ext.lib.Event.getTarget(e);
\r
28183 return t ? handles[t.id] : null;
\r
28187 * Returns a custom data object that is registered for a DOM node by id
\r
28188 * @param {String/HTMLElement} id The DOM node or id to look up
\r
28189 * @return {Object} data The custom data
\r
28191 getTarget : function(id){
\r
28192 if(typeof id != "string"){ // must be element?
\r
28195 return elements[id];
\r
28199 * Returns a custom data object that is registered for the DOM node that is the target of the event
\r
28200 * @param {Event} e The event
\r
28201 * @return {Object} data The custom data
\r
28203 getTargetFromEvent : function(e){
\r
28204 var t = Ext.lib.Event.getTarget(e);
\r
28205 return t ? elements[t.id] || handles[t.id] : null;
\r
28209 * @class Ext.dd.StatusProxy
\r
28210 * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. This is the
\r
28211 * default drag proxy used by all Ext.dd components.
\r
28213 * @param {Object} config
\r
28215 Ext.dd.StatusProxy = function(config){
\r
28216 Ext.apply(this, config);
\r
28217 this.id = this.id || Ext.id();
\r
28218 this.el = new Ext.Layer({
\r
28220 id: this.id, tag: "div", cls: "x-dd-drag-proxy "+this.dropNotAllowed, children: [
\r
28221 {tag: "div", cls: "x-dd-drop-icon"},
\r
28222 {tag: "div", cls: "x-dd-drag-ghost"}
\r
28225 shadow: !config || config.shadow !== false
\r
28227 this.ghost = Ext.get(this.el.dom.childNodes[1]);
\r
28228 this.dropStatus = this.dropNotAllowed;
\r
28231 Ext.dd.StatusProxy.prototype = {
\r
28233 * @cfg {String} dropAllowed
\r
28234 * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
\r
28236 dropAllowed : "x-dd-drop-ok",
\r
28238 * @cfg {String} dropNotAllowed
\r
28239 * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
28241 dropNotAllowed : "x-dd-drop-nodrop",
\r
28244 * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
\r
28245 * over the current target element.
\r
28246 * @param {String} cssClass The css class for the new drop status indicator image
\r
28248 setStatus : function(cssClass){
\r
28249 cssClass = cssClass || this.dropNotAllowed;
\r
28250 if(this.dropStatus != cssClass){
\r
28251 this.el.replaceClass(this.dropStatus, cssClass);
\r
28252 this.dropStatus = cssClass;
\r
28257 * Resets the status indicator to the default dropNotAllowed value
\r
28258 * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
\r
28260 reset : function(clearGhost){
\r
28261 this.el.dom.className = "x-dd-drag-proxy " + this.dropNotAllowed;
\r
28262 this.dropStatus = this.dropNotAllowed;
\r
28264 this.ghost.update("");
\r
28269 * Updates the contents of the ghost element
\r
28270 * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
\r
28271 * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
\r
28273 update : function(html){
\r
28274 if(typeof html == "string"){
\r
28275 this.ghost.update(html);
\r
28277 this.ghost.update("");
\r
28278 html.style.margin = "0";
\r
28279 this.ghost.dom.appendChild(html);
\r
28281 var el = this.ghost.dom.firstChild;
\r
28283 Ext.fly(el).setStyle('float', 'none');
\r
28288 * Returns the underlying proxy {@link Ext.Layer}
\r
28289 * @return {Ext.Layer} el
\r
28291 getEl : function(){
\r
28296 * Returns the ghost element
\r
28297 * @return {Ext.Element} el
\r
28299 getGhost : function(){
\r
28300 return this.ghost;
\r
28304 * Hides the proxy
\r
28305 * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
\r
28307 hide : function(clear){
\r
28310 this.reset(true);
\r
28315 * Stops the repair animation if it's currently running
\r
28317 stop : function(){
\r
28318 if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
\r
28319 this.anim.stop();
\r
28324 * Displays this proxy
\r
28326 show : function(){
\r
28331 * Force the Layer to sync its shadow and shim positions to the element
\r
28333 sync : function(){
\r
28338 * Causes the proxy to return to its position of origin via an animation. Should be called after an
\r
28339 * invalid drop operation by the item being dragged.
\r
28340 * @param {Array} xy The XY position of the element ([x, y])
\r
28341 * @param {Function} callback The function to call after the repair is complete
\r
28342 * @param {Object} scope The scope in which to execute the callback
\r
28344 repair : function(xy, callback, scope){
\r
28345 this.callback = callback;
\r
28346 this.scope = scope;
\r
28347 if(xy && this.animRepair !== false){
\r
28348 this.el.addClass("x-dd-drag-repair");
\r
28349 this.el.hideUnders(true);
\r
28350 this.anim = this.el.shift({
\r
28351 duration: this.repairDuration || .5,
\r
28352 easing: 'easeOut',
\r
28355 callback: this.afterRepair,
\r
28359 this.afterRepair();
\r
28364 afterRepair : function(){
\r
28366 if(typeof this.callback == "function"){
\r
28367 this.callback.call(this.scope || this);
\r
28369 this.callback = null;
\r
28370 this.scope = null;
\r
28373 * @class Ext.dd.DragSource
\r
28374 * @extends Ext.dd.DDProxy
\r
28375 * A simple class that provides the basic implementation needed to make any element draggable.
\r
28377 * @param {Mixed} el The container element
\r
28378 * @param {Object} config
\r
28380 Ext.dd.DragSource = function(el, config){
\r
28381 this.el = Ext.get(el);
\r
28382 if(!this.dragData){
\r
28383 this.dragData = {};
\r
28386 Ext.apply(this, config);
\r
28389 this.proxy = new Ext.dd.StatusProxy();
\r
28391 Ext.dd.DragSource.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
\r
28392 {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true});
\r
28394 this.dragging = false;
\r
28397 Ext.extend(Ext.dd.DragSource, Ext.dd.DDProxy, {
\r
28399 * @cfg {String} ddGroup
\r
28400 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
28401 * interact with other drag drop objects in the same group (defaults to undefined).
\r
28404 * @cfg {String} dropAllowed
\r
28405 * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
\r
28407 dropAllowed : "x-dd-drop-ok",
\r
28409 * @cfg {String} dropNotAllowed
\r
28410 * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
28412 dropNotAllowed : "x-dd-drop-nodrop",
\r
28415 * Returns the data object associated with this drag source
\r
28416 * @return {Object} data An object containing arbitrary data
\r
28418 getDragData : function(e){
\r
28419 return this.dragData;
\r
28423 onDragEnter : function(e, id){
\r
28424 var target = Ext.dd.DragDropMgr.getDDById(id);
\r
28425 this.cachedTarget = target;
\r
28426 if(this.beforeDragEnter(target, e, id) !== false){
\r
28427 if(target.isNotifyTarget){
\r
28428 var status = target.notifyEnter(this, e, this.dragData);
\r
28429 this.proxy.setStatus(status);
\r
28431 this.proxy.setStatus(this.dropAllowed);
\r
28434 if(this.afterDragEnter){
\r
28436 * An empty function by default, but provided so that you can perform a custom action
\r
28437 * when the dragged item enters the drop target by providing an implementation.
\r
28438 * @param {Ext.dd.DragDrop} target The drop target
\r
28439 * @param {Event} e The event object
\r
28440 * @param {String} id The id of the dragged element
\r
28441 * @method afterDragEnter
\r
28443 this.afterDragEnter(target, e, id);
\r
28449 * An empty function by default, but provided so that you can perform a custom action
\r
28450 * before the dragged item enters the drop target and optionally cancel the onDragEnter.
\r
28451 * @param {Ext.dd.DragDrop} target The drop target
\r
28452 * @param {Event} e The event object
\r
28453 * @param {String} id The id of the dragged element
\r
28454 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
28456 beforeDragEnter : function(target, e, id){
\r
28461 alignElWithMouse: function() {
\r
28462 Ext.dd.DragSource.superclass.alignElWithMouse.apply(this, arguments);
\r
28463 this.proxy.sync();
\r
28467 onDragOver : function(e, id){
\r
28468 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
28469 if(this.beforeDragOver(target, e, id) !== false){
\r
28470 if(target.isNotifyTarget){
\r
28471 var status = target.notifyOver(this, e, this.dragData);
\r
28472 this.proxy.setStatus(status);
\r
28475 if(this.afterDragOver){
\r
28477 * An empty function by default, but provided so that you can perform a custom action
\r
28478 * while the dragged item is over the drop target by providing an implementation.
\r
28479 * @param {Ext.dd.DragDrop} target The drop target
\r
28480 * @param {Event} e The event object
\r
28481 * @param {String} id The id of the dragged element
\r
28482 * @method afterDragOver
\r
28484 this.afterDragOver(target, e, id);
\r
28490 * An empty function by default, but provided so that you can perform a custom action
\r
28491 * while the dragged item is over the drop target and optionally cancel the onDragOver.
\r
28492 * @param {Ext.dd.DragDrop} target The drop target
\r
28493 * @param {Event} e The event object
\r
28494 * @param {String} id The id of the dragged element
\r
28495 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
28497 beforeDragOver : function(target, e, id){
\r
28502 onDragOut : function(e, id){
\r
28503 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
28504 if(this.beforeDragOut(target, e, id) !== false){
\r
28505 if(target.isNotifyTarget){
\r
28506 target.notifyOut(this, e, this.dragData);
\r
28508 this.proxy.reset();
\r
28509 if(this.afterDragOut){
\r
28511 * An empty function by default, but provided so that you can perform a custom action
\r
28512 * after the dragged item is dragged out of the target without dropping.
\r
28513 * @param {Ext.dd.DragDrop} target The drop target
\r
28514 * @param {Event} e The event object
\r
28515 * @param {String} id The id of the dragged element
\r
28516 * @method afterDragOut
\r
28518 this.afterDragOut(target, e, id);
\r
28521 this.cachedTarget = null;
\r
28525 * An empty function by default, but provided so that you can perform a custom action before the dragged
\r
28526 * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
\r
28527 * @param {Ext.dd.DragDrop} target The drop target
\r
28528 * @param {Event} e The event object
\r
28529 * @param {String} id The id of the dragged element
\r
28530 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
28532 beforeDragOut : function(target, e, id){
\r
28537 onDragDrop : function(e, id){
\r
28538 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
28539 if(this.beforeDragDrop(target, e, id) !== false){
\r
28540 if(target.isNotifyTarget){
\r
28541 if(target.notifyDrop(this, e, this.dragData)){ // valid drop?
\r
28542 this.onValidDrop(target, e, id);
\r
28544 this.onInvalidDrop(target, e, id);
\r
28547 this.onValidDrop(target, e, id);
\r
28550 if(this.afterDragDrop){
\r
28552 * An empty function by default, but provided so that you can perform a custom action
\r
28553 * after a valid drag drop has occurred by providing an implementation.
\r
28554 * @param {Ext.dd.DragDrop} target The drop target
\r
28555 * @param {Event} e The event object
\r
28556 * @param {String} id The id of the dropped element
\r
28557 * @method afterDragDrop
\r
28559 this.afterDragDrop(target, e, id);
\r
28562 delete this.cachedTarget;
\r
28566 * An empty function by default, but provided so that you can perform a custom action before the dragged
\r
28567 * item is dropped onto the target and optionally cancel the onDragDrop.
\r
28568 * @param {Ext.dd.DragDrop} target The drop target
\r
28569 * @param {Event} e The event object
\r
28570 * @param {String} id The id of the dragged element
\r
28571 * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
\r
28573 beforeDragDrop : function(target, e, id){
\r
28578 onValidDrop : function(target, e, id){
\r
28579 this.hideProxy();
\r
28580 if(this.afterValidDrop){
\r
28582 * An empty function by default, but provided so that you can perform a custom action
\r
28583 * after a valid drop has occurred by providing an implementation.
\r
28584 * @param {Object} target The target DD
\r
28585 * @param {Event} e The event object
\r
28586 * @param {String} id The id of the dropped element
\r
28587 * @method afterInvalidDrop
\r
28589 this.afterValidDrop(target, e, id);
\r
28594 getRepairXY : function(e, data){
\r
28595 return this.el.getXY();
\r
28599 onInvalidDrop : function(target, e, id){
\r
28600 this.beforeInvalidDrop(target, e, id);
\r
28601 if(this.cachedTarget){
\r
28602 if(this.cachedTarget.isNotifyTarget){
\r
28603 this.cachedTarget.notifyOut(this, e, this.dragData);
\r
28605 this.cacheTarget = null;
\r
28607 this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
\r
28609 if(this.afterInvalidDrop){
\r
28611 * An empty function by default, but provided so that you can perform a custom action
\r
28612 * after an invalid drop has occurred by providing an implementation.
\r
28613 * @param {Event} e The event object
\r
28614 * @param {String} id The id of the dropped element
\r
28615 * @method afterInvalidDrop
\r
28617 this.afterInvalidDrop(e, id);
\r
28622 afterRepair : function(){
\r
28623 if(Ext.enableFx){
\r
28624 this.el.highlight(this.hlColor || "c3daf9");
\r
28626 this.dragging = false;
\r
28630 * An empty function by default, but provided so that you can perform a custom action after an invalid
\r
28631 * drop has occurred.
\r
28632 * @param {Ext.dd.DragDrop} target The drop target
\r
28633 * @param {Event} e The event object
\r
28634 * @param {String} id The id of the dragged element
\r
28635 * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
\r
28637 beforeInvalidDrop : function(target, e, id){
\r
28642 handleMouseDown : function(e){
\r
28643 if(this.dragging) {
\r
28646 var data = this.getDragData(e);
\r
28647 if(data && this.onBeforeDrag(data, e) !== false){
\r
28648 this.dragData = data;
\r
28649 this.proxy.stop();
\r
28650 Ext.dd.DragSource.superclass.handleMouseDown.apply(this, arguments);
\r
28655 * An empty function by default, but provided so that you can perform a custom action before the initial
\r
28656 * drag event begins and optionally cancel it.
\r
28657 * @param {Object} data An object containing arbitrary data to be shared with drop targets
\r
28658 * @param {Event} e The event object
\r
28659 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
28661 onBeforeDrag : function(data, e){
\r
28666 * An empty function by default, but provided so that you can perform a custom action once the initial
\r
28667 * drag event has begun. The drag cannot be canceled from this function.
\r
28668 * @param {Number} x The x position of the click on the dragged object
\r
28669 * @param {Number} y The y position of the click on the dragged object
\r
28671 onStartDrag : Ext.emptyFn,
\r
28673 // private override
\r
28674 startDrag : function(x, y){
\r
28675 this.proxy.reset();
\r
28676 this.dragging = true;
\r
28677 this.proxy.update("");
\r
28678 this.onInitDrag(x, y);
\r
28679 this.proxy.show();
\r
28683 onInitDrag : function(x, y){
\r
28684 var clone = this.el.dom.cloneNode(true);
\r
28685 clone.id = Ext.id(); // prevent duplicate ids
\r
28686 this.proxy.update(clone);
\r
28687 this.onStartDrag(x, y);
\r
28692 * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
\r
28693 * @return {Ext.dd.StatusProxy} proxy The StatusProxy
\r
28695 getProxy : function(){
\r
28696 return this.proxy;
\r
28700 * Hides the drag source's {@link Ext.dd.StatusProxy}
\r
28702 hideProxy : function(){
\r
28703 this.proxy.hide();
\r
28704 this.proxy.reset(true);
\r
28705 this.dragging = false;
\r
28709 triggerCacheRefresh : function(){
\r
28710 Ext.dd.DDM.refreshCache(this.groups);
\r
28713 // private - override to prevent hiding
\r
28714 b4EndDrag: function(e) {
\r
28717 // private - override to prevent moving
\r
28718 endDrag : function(e){
\r
28719 this.onEndDrag(this.dragData, e);
\r
28723 onEndDrag : function(data, e){
\r
28726 // private - pin to cursor
\r
28727 autoOffset : function(x, y) {
\r
28728 this.setDelta(-12, -20);
\r
28731 * @class Ext.dd.DropTarget
\r
28732 * @extends Ext.dd.DDTarget
\r
28733 * A simple class that provides the basic implementation needed to make any element a drop target that can have
\r
28734 * draggable items dropped onto it. The drop has no effect until an implementation of notifyDrop is provided.
\r
28736 * @param {Mixed} el The container element
\r
28737 * @param {Object} config
\r
28739 Ext.dd.DropTarget = function(el, config){
\r
28740 this.el = Ext.get(el);
\r
28742 Ext.apply(this, config);
\r
28744 if(this.containerScroll){
\r
28745 Ext.dd.ScrollManager.register(this.el);
\r
28748 Ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
\r
28749 {isTarget: true});
\r
28753 Ext.extend(Ext.dd.DropTarget, Ext.dd.DDTarget, {
\r
28755 * @cfg {String} ddGroup
\r
28756 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
28757 * interact with other drag drop objects in the same group (defaults to undefined).
\r
28760 * @cfg {String} overClass
\r
28761 * The CSS class applied to the drop target element while the drag source is over it (defaults to "").
\r
28764 * @cfg {String} dropAllowed
\r
28765 * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
\r
28767 dropAllowed : "x-dd-drop-ok",
\r
28769 * @cfg {String} dropNotAllowed
\r
28770 * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
28772 dropNotAllowed : "x-dd-drop-nodrop",
\r
28778 isNotifyTarget : true,
\r
28781 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
\r
28782 * target. This default implementation adds the CSS class specified by overClass (if any) to the drop element
\r
28783 * and returns the dropAllowed config value. This method should be overridden if drop validation is required.
\r
28784 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
28785 * @param {Event} e The event
\r
28786 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
28787 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
28788 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
28790 notifyEnter : function(dd, e, data){
\r
28791 if(this.overClass){
\r
28792 this.el.addClass(this.overClass);
\r
28794 return this.dropAllowed;
\r
28798 * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
\r
28799 * This method will be called on every mouse movement while the drag source is over the drop target.
\r
28800 * This default implementation simply returns the dropAllowed config value.
\r
28801 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
28802 * @param {Event} e The event
\r
28803 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
28804 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
28805 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
28807 notifyOver : function(dd, e, data){
\r
28808 return this.dropAllowed;
\r
28812 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
\r
28813 * out of the target without dropping. This default implementation simply removes the CSS class specified by
\r
28814 * overClass (if any) from the drop element.
\r
28815 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
28816 * @param {Event} e The event
\r
28817 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
28819 notifyOut : function(dd, e, data){
\r
28820 if(this.overClass){
\r
28821 this.el.removeClass(this.overClass);
\r
28826 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
\r
28827 * been dropped on it. This method has no default implementation and returns false, so you must provide an
\r
28828 * implementation that does something to process the drop event and returns true so that the drag source's
\r
28829 * repair action does not run.
\r
28830 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
28831 * @param {Event} e The event
\r
28832 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
28833 * @return {Boolean} True if the drop was valid, else false
\r
28835 notifyDrop : function(dd, e, data){
\r
28839 * @class Ext.dd.DragZone
\r
28840 * @extends Ext.dd.DragSource
\r
28841 * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
\r
28842 * <p>This class does not move the drag target nodes, but a proxy element which may contain
\r
28843 * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
\r
28844 * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
\r
28845 * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
\r
28846 * application object (For example nodes in a {@link Ext.DataView DataView}) then use of this class
\r
28847 * is the most efficient way to "activate" those nodes.</p>
\r
28848 * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
\r
28849 * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
\r
28850 * the DragZone with an implementation of the {@link #getDragData} method which interrogates the passed
\r
28851 * mouse event to see if it has taken place within an element, or class of elements. This is easily done
\r
28852 * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
\r
28853 * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
\r
28854 * technique. Knowledge of the use of the DataView is required:</p><pre><code>
\r
28855 myDataView.on('render', function() {
\r
28856 myDataView.dragZone = new Ext.dd.DragZone(myDataView.getEl(), {
\r
28858 // On receipt of a mousedown event, see if it is within a DataView node.
\r
28859 // Return a drag data object if so.
\r
28860 getDragData: function(e) {
\r
28862 // Use the DataView's own itemSelector (a mandatory property) to
\r
28863 // test if the mousedown is within one of the DataView's nodes.
\r
28864 var sourceEl = e.getTarget(myDataView.itemSelector, 10);
\r
28866 // If the mousedown is within a DataView node, clone the node to produce
\r
28867 // a ddel element for use by the drag proxy. Also add application data
\r
28868 // to the returned data object.
\r
28870 d = sourceEl.cloneNode(true);
\r
28874 sourceEl: sourceEl,
\r
28875 repairXY: Ext.fly(sourceEl).getXY(),
\r
28876 sourceStore: myDataView.store,
\r
28877 draggedRecord: v.getRecord(sourceEl)
\r
28882 // Provide coordinates for the proxy to slide back to on failed drag.
\r
28883 // This is the original XY coordinates of the draggable element captured
\r
28884 // in the getDragData method.
\r
28885 getRepairXY: function() {
\r
28886 return this.dragData.repairXY;
\r
28890 * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
\r
28891 * cooperates with this DragZone.
\r
28893 * @param {Mixed} el The container element
\r
28894 * @param {Object} config
\r
28896 Ext.dd.DragZone = function(el, config){
\r
28897 Ext.dd.DragZone.superclass.constructor.call(this, el, config);
\r
28898 if(this.containerScroll){
\r
28899 Ext.dd.ScrollManager.register(this.el);
\r
28903 Ext.extend(Ext.dd.DragZone, Ext.dd.DragSource, {
\r
28905 * This property contains the data representing the dragged object. This data is set up by the implementation
\r
28906 * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
\r
28907 * any other data according to the application's needs.
\r
28909 * @property dragData
\r
28912 * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
\r
28913 * for auto scrolling during drag operations.
\r
28916 * @cfg {String} hlColor The color to use when visually highlighting the drag source in the afterRepair
\r
28917 * method after a failed drop (defaults to "c3daf9" - light blue)
\r
28921 * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
\r
28922 * for a valid target to drag based on the mouse down. Override this method
\r
28923 * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
\r
28924 * object has a "ddel" attribute (with an HTML Element) for other functions to work.
\r
28925 * @param {EventObject} e The mouse down event
\r
28926 * @return {Object} The dragData
\r
28928 getDragData : function(e){
\r
28929 return Ext.dd.Registry.getHandleFromEvent(e);
\r
28933 * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
\r
28934 * this.dragData.ddel
\r
28935 * @param {Number} x The x position of the click on the dragged object
\r
28936 * @param {Number} y The y position of the click on the dragged object
\r
28937 * @return {Boolean} true to continue the drag, false to cancel
\r
28939 onInitDrag : function(x, y){
\r
28940 this.proxy.update(this.dragData.ddel.cloneNode(true));
\r
28941 this.onStartDrag(x, y);
\r
28946 * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
\r
28948 afterRepair : function(){
\r
28949 if(Ext.enableFx){
\r
28950 Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
\r
28952 this.dragging = false;
\r
28956 * Called before a repair of an invalid drop to get the XY to animate to. By default returns
\r
28957 * the XY of this.dragData.ddel
\r
28958 * @param {EventObject} e The mouse up event
\r
28959 * @return {Array} The xy location (e.g. [100, 200])
\r
28961 getRepairXY : function(e){
\r
28962 return Ext.Element.fly(this.dragData.ddel).getXY();
\r
28965 * @class Ext.dd.DropZone
\r
28966 * @extends Ext.dd.DropTarget
\r
28967 * <p>This class provides a container DD instance that allows dropping on multiple child target nodes.</p>
\r
28968 * <p>By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
\r
28969 * However a simpler way to allow a DropZone to manage any number of target elements is to configure the
\r
28970 * DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
\r
28971 * mouse event to see if it has taken place within an element, or class of elements. This is easily done
\r
28972 * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
\r
28973 * {@link Ext.DomQuery} selector.</p>
\r
28974 * <p>Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
\r
28975 * a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
\r
28976 * {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
\r
28977 * of these methods to provide application-specific behaviour for these events to update both
\r
28978 * application state, and UI state.</p>
\r
28979 * <p>For example to make a GridPanel a cooperating target with the example illustrated in
\r
28980 * {@link Ext.dd.DragZone DragZone}, the following technique might be used:</p><pre><code>
\r
28981 myGridPanel.on('render', function() {
\r
28982 myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
\r
28984 // If the mouse is over a grid row, return that node. This is
\r
28985 // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
\r
28986 getTargetFromEvent: function(e) {
\r
28987 return e.getTarget(myGridPanel.getView().rowSelector);
\r
28990 // On entry into a target node, highlight that node.
\r
28991 onNodeEnter : function(target, dd, e, data){
\r
28992 Ext.fly(target).addClass('my-row-highlight-class');
\r
28995 // On exit from a target node, unhighlight that node.
\r
28996 onNodeOut : function(target, dd, e, data){
\r
28997 Ext.fly(target).removeClass('my-row-highlight-class');
\r
29000 // While over a target node, return the default drop allowed class which
\r
29001 // places a "tick" icon into the drag proxy.
\r
29002 onNodeOver : function(target, dd, e, data){
\r
29003 return Ext.dd.DropZone.prototype.dropAllowed;
\r
29006 // On node drop we can interrogate the target to find the underlying
\r
29007 // application object that is the real target of the dragged data.
\r
29008 // In this case, it is a Record in the GridPanel's Store.
\r
29009 // We can use the data set up by the DragZone's getDragData method to read
\r
29010 // any data we decided to attach in the DragZone's getDragData method.
\r
29011 onNodeDrop : function(target, dd, e, data){
\r
29012 var rowIndex = myGridPanel.getView().findRowIndex(target);
\r
29013 var r = myGridPanel.getStore().getAt(rowIndex);
\r
29014 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
\r
29015 ' on Record id ' + r.id);
\r
29021 * See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
\r
29022 * cooperates with this DropZone.
\r
29024 * @param {Mixed} el The container element
\r
29025 * @param {Object} config
\r
29027 Ext.dd.DropZone = function(el, config){
\r
29028 Ext.dd.DropZone.superclass.constructor.call(this, el, config);
\r
29031 Ext.extend(Ext.dd.DropZone, Ext.dd.DropTarget, {
\r
29033 * Returns a custom data object associated with the DOM node that is the target of the event. By default
\r
29034 * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
\r
29035 * provide your own custom lookup.
\r
29036 * @param {Event} e The event
\r
29037 * @return {Object} data The custom data
\r
29039 getTargetFromEvent : function(e){
\r
29040 return Ext.dd.Registry.getTargetFromEvent(e);
\r
29044 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
\r
29045 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
\r
29046 * This method has no default implementation and should be overridden to provide
\r
29047 * node-specific processing if necessary.
\r
29048 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29049 * {@link #getTargetFromEvent} for this node)
\r
29050 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29051 * @param {Event} e The event
\r
29052 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29054 onNodeEnter : function(n, dd, e, data){
\r
29059 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
\r
29060 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
\r
29061 * The default implementation returns this.dropNotAllowed, so it should be
\r
29062 * overridden to provide the proper feedback.
\r
29063 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29064 * {@link #getTargetFromEvent} for this node)
\r
29065 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29066 * @param {Event} e The event
\r
29067 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29068 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29069 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29071 onNodeOver : function(n, dd, e, data){
\r
29072 return this.dropAllowed;
\r
29076 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
\r
29077 * the drop node without dropping. This method has no default implementation and should be overridden to provide
\r
29078 * node-specific processing if necessary.
\r
29079 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29080 * {@link #getTargetFromEvent} for this node)
\r
29081 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29082 * @param {Event} e The event
\r
29083 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29085 onNodeOut : function(n, dd, e, data){
\r
29090 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
\r
29091 * the drop node. The default implementation returns false, so it should be overridden to provide the
\r
29092 * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
\r
29093 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29094 * {@link #getTargetFromEvent} for this node)
\r
29095 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29096 * @param {Event} e The event
\r
29097 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29098 * @return {Boolean} True if the drop was valid, else false
\r
29100 onNodeDrop : function(n, dd, e, data){
\r
29105 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
\r
29106 * but not over any of its registered drop nodes. The default implementation returns this.dropNotAllowed, so
\r
29107 * it should be overridden to provide the proper feedback if necessary.
\r
29108 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29109 * @param {Event} e The event
\r
29110 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29111 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29112 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29114 onContainerOver : function(dd, e, data){
\r
29115 return this.dropNotAllowed;
\r
29119 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
\r
29120 * but not on any of its registered drop nodes. The default implementation returns false, so it should be
\r
29121 * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
\r
29122 * be able to accept drops. It should return true when valid so that the drag source's repair action does not run.
\r
29123 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29124 * @param {Event} e The event
\r
29125 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29126 * @return {Boolean} True if the drop was valid, else false
\r
29128 onContainerDrop : function(dd, e, data){
\r
29133 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
\r
29134 * the zone. The default implementation returns this.dropNotAllowed and expects that only registered drop
\r
29135 * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
\r
29136 * you should override this method and provide a custom implementation.
\r
29137 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29138 * @param {Event} e The event
\r
29139 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29140 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29141 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29143 notifyEnter : function(dd, e, data){
\r
29144 return this.dropNotAllowed;
\r
29148 * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
\r
29149 * This method will be called on every mouse movement while the drag source is over the drop zone.
\r
29150 * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
\r
29151 * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
\r
29152 * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
\r
29153 * registered node, it will call {@link #onContainerOver}.
\r
29154 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29155 * @param {Event} e The event
\r
29156 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29157 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29158 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29160 notifyOver : function(dd, e, data){
\r
29161 var n = this.getTargetFromEvent(e);
\r
29162 if(!n){ // not over valid drop target
\r
29163 if(this.lastOverNode){
\r
29164 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29165 this.lastOverNode = null;
\r
29167 return this.onContainerOver(dd, e, data);
\r
29169 if(this.lastOverNode != n){
\r
29170 if(this.lastOverNode){
\r
29171 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29173 this.onNodeEnter(n, dd, e, data);
\r
29174 this.lastOverNode = n;
\r
29176 return this.onNodeOver(n, dd, e, data);
\r
29180 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
\r
29181 * out of the zone without dropping. If the drag source is currently over a registered node, the notification
\r
29182 * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
\r
29183 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29184 * @param {Event} e The event
\r
29185 * @param {Object} data An object containing arbitrary data supplied by the drag zone
\r
29187 notifyOut : function(dd, e, data){
\r
29188 if(this.lastOverNode){
\r
29189 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29190 this.lastOverNode = null;
\r
29195 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
\r
29196 * been dropped on it. The drag zone will look up the target node based on the event passed in, and if there
\r
29197 * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
\r
29198 * otherwise it will call {@link #onContainerDrop}.
\r
29199 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29200 * @param {Event} e The event
\r
29201 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29202 * @return {Boolean} True if the drop was valid, else false
\r
29204 notifyDrop : function(dd, e, data){
\r
29205 if(this.lastOverNode){
\r
29206 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29207 this.lastOverNode = null;
\r
29209 var n = this.getTargetFromEvent(e);
\r
29211 this.onNodeDrop(n, dd, e, data) :
\r
29212 this.onContainerDrop(dd, e, data);
\r
29216 triggerCacheRefresh : function(){
\r
29217 Ext.dd.DDM.refreshCache(this.groups);
\r
29220 * @class Ext.Element
\r
29222 Ext.Element.addMethods({
\r
29224 * Initializes a {@link Ext.dd.DD} drag drop object for this element.
\r
29225 * @param {String} group The group the DD object is member of
\r
29226 * @param {Object} config The DD config object
\r
29227 * @param {Object} overrides An object containing methods to override/implement on the DD object
\r
29228 * @return {Ext.dd.DD} The DD object
\r
29230 initDD : function(group, config, overrides){
\r
29231 var dd = new Ext.dd.DD(Ext.id(this.dom), group, config);
\r
29232 return Ext.apply(dd, overrides);
\r
29236 * Initializes a {@link Ext.dd.DDProxy} object for this element.
\r
29237 * @param {String} group The group the DDProxy object is member of
\r
29238 * @param {Object} config The DDProxy config object
\r
29239 * @param {Object} overrides An object containing methods to override/implement on the DDProxy object
\r
29240 * @return {Ext.dd.DDProxy} The DDProxy object
\r
29242 initDDProxy : function(group, config, overrides){
\r
29243 var dd = new Ext.dd.DDProxy(Ext.id(this.dom), group, config);
\r
29244 return Ext.apply(dd, overrides);
\r
29248 * Initializes a {@link Ext.dd.DDTarget} object for this element.
\r
29249 * @param {String} group The group the DDTarget object is member of
\r
29250 * @param {Object} config The DDTarget config object
\r
29251 * @param {Object} overrides An object containing methods to override/implement on the DDTarget object
\r
29252 * @return {Ext.dd.DDTarget} The DDTarget object
\r
29254 initDDTarget : function(group, config, overrides){
\r
29255 var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config);
\r
29256 return Ext.apply(dd, overrides);
\r
29259 * @class Ext.data.Api
29261 * Ext.data.Api is a singleton designed to manage the data API including methods
29262 * for validating a developer's DataProxy API. Defines variables for CRUD actions
29263 * create, read, update and destroy in addition to a mapping of RESTful HTTP methods
29264 * GET, POST, PUT and DELETE to CRUD actions.
29267 Ext.data.Api = (function() {
29269 // private validActions. validActions is essentially an inverted hash of Ext.data.Api.actions, where value becomes the key.
29270 // Some methods in this singleton (e.g.: getActions, getVerb) will loop through actions with the code <code>for (var verb in this.actions)</code>
29271 // For efficiency, some methods will first check this hash for a match. Those methods which do acces validActions will cache their result here.
29272 // We cannot pre-define this hash since the developer may over-ride the actions at runtime.
29273 var validActions = {};
29277 * Defined actions corresponding to remote actions:
29280 create : 'create', // Text representing the remote-action to create records on server.
29281 read : 'read', // Text representing the remote-action to read/load data from server.
29282 update : 'update', // Text representing the remote-action to update records on server.
29283 destroy : 'destroy' // Text representing the remote-action to destroy records on server.
29286 * @property actions
29293 destroy : 'destroy'
29297 * Defined {CRUD action}:{HTTP method} pairs to associate HTTP methods with the
29298 * corresponding actions for {@link Ext.data.DataProxy#restful RESTful proxies}.
29317 * Returns true if supplied action-name is a valid API action defined in <code>{@link #actions}</code> constants
29318 * @param {String} action
29319 * @param {String[]}(Optional) List of available CRUD actions. Pass in list when executing multiple times for efficiency.
29320 * @return {Boolean}
29322 isAction : function(action) {
29323 return (Ext.data.Api.actions[action]) ? true : false;
29327 * Returns the actual CRUD action KEY "create", "read", "update" or "destroy" from the supplied action-name. This method is used internally and shouldn't generally
29328 * need to be used directly. The key/value pair of Ext.data.Api.actions will often be identical but this is not necessarily true. A developer can override this naming
29329 * convention if desired. However, the framework internally calls methods based upon the KEY so a way of retreiving the the words "create", "read", "update" and "destroy" is
29330 * required. This method will cache discovered KEYS into the private validActions hash.
29331 * @param {String} name The runtime name of the action.
29332 * @return {String||null} returns the action-key, or verb of the user-action or null if invalid.
29335 getVerb : function(name) {
29336 if (validActions[name]) {
29337 return validActions[name]; // <-- found in cache. return immediately.
29339 for (var verb in this.actions) {
29340 if (this.actions[verb] === name) {
29341 validActions[name] = verb;
29345 return (validActions[name] !== undefined) ? validActions[name] : null;
29349 * Returns true if the supplied API is valid; that is, check that all keys match defined actions
29350 * otherwise returns an array of mistakes.
29351 * @return {String[]||true}
29353 isValid : function(api){
29355 var crud = this.actions; // <-- cache a copy of the actions.
29356 for (var action in api) {
29357 if (!(action in crud)) {
29358 invalid.push(action);
29361 return (!invalid.length) ? true : invalid;
29365 * Returns true if the supplied verb upon the supplied proxy points to a unique url in that none of the other api-actions
29366 * point to the same url. The question is important for deciding whether to insert the "xaction" HTTP parameter within an
29367 * Ajax request. This method is used internally and shouldn't generally need to be called directly.
29368 * @param {Ext.data.DataProxy} proxy
29369 * @param {String} verb
29370 * @return {Boolean}
29372 hasUniqueUrl : function(proxy, verb) {
29373 var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
29375 for (var action in proxy.api) {
29376 if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
29384 * This method is used internally by <tt>{@link Ext.data.DataProxy DataProxy}</tt> and should not generally need to be used directly.
29385 * Each action of a DataProxy api can be initially defined as either a String or an Object. When specified as an object,
29386 * one can explicitly define the HTTP method (GET|POST) to use for each CRUD action. This method will prepare the supplied API, setting
29387 * each action to the Object form. If your API-actions do not explicitly define the HTTP method, the "method" configuration-parameter will
29388 * be used. If the method configuration parameter is not specified, POST will be used.
29390 new Ext.data.HttpProxy({
29391 method: "POST", // <-- default HTTP method when not specified.
29393 create: 'create.php',
29396 destroy: 'destroy.php'
29400 // Alternatively, one can use the object-form to specify the API
29401 new Ext.data.HttpProxy({
29403 load: {url: 'read.php', method: 'GET'},
29404 create: 'create.php',
29405 destroy: 'destroy.php',
29411 * @param {Ext.data.DataProxy} proxy
29413 prepare : function(proxy) {
29415 proxy.api = {}; // <-- No api? create a blank one.
29417 for (var verb in this.actions) {
29418 var action = this.actions[verb];
29419 proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
29420 if (typeof(proxy.api[action]) == 'string') {
29421 proxy.api[action] = {
29422 url: proxy.api[action]
29429 * Prepares a supplied Proxy to be RESTful. Sets the HTTP method for each api-action to be one of
29430 * GET, POST, PUT, DELETE according to the defined {@link #restActions}.
29431 * @param {Ext.data.DataProxy} proxy
29433 restify : function(proxy) {
29434 proxy.restful = true;
29435 for (var verb in this.restActions) {
29436 proxy.api[this.actions[verb]].method = this.restActions[verb];
29443 * @class Ext.data.Api.Error
29444 * @extends Ext.Error
29445 * Error class for Ext.data.Api errors
29447 Ext.data.Api.Error = Ext.extend(Ext.Error, {
29448 constructor : function(message, arg) {
29450 Ext.Error.call(this, message);
29452 name: 'Ext.data.Api'
29454 Ext.apply(Ext.data.Api.Error.prototype, {
29456 'action-url-undefined': 'No fallback url defined for this action. When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.',
29457 'invalid': 'received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
29458 'invalid-url': 'Invalid url. Please review your proxy configuration.',
29459 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
29464 * @class Ext.data.SortTypes
\r
29466 * Defines the default sorting (casting?) comparison functions used when sorting data.
\r
29468 Ext.data.SortTypes = {
\r
29470 * Default sort that does nothing
\r
29471 * @param {Mixed} s The value being converted
\r
29472 * @return {Mixed} The comparison value
\r
29474 none : function(s){
\r
29479 * The regular expression used to strip tags
\r
29483 stripTagsRE : /<\/?[^>]+>/gi,
\r
29486 * Strips all HTML tags to sort on text only
\r
29487 * @param {Mixed} s The value being converted
\r
29488 * @return {String} The comparison value
\r
29490 asText : function(s){
\r
29491 return String(s).replace(this.stripTagsRE, "");
\r
29495 * Strips all HTML tags to sort on text only - Case insensitive
\r
29496 * @param {Mixed} s The value being converted
\r
29497 * @return {String} The comparison value
\r
29499 asUCText : function(s){
\r
29500 return String(s).toUpperCase().replace(this.stripTagsRE, "");
\r
29504 * Case insensitive string
\r
29505 * @param {Mixed} s The value being converted
\r
29506 * @return {String} The comparison value
\r
29508 asUCString : function(s) {
\r
29509 return String(s).toUpperCase();
\r
29514 * @param {Mixed} s The value being converted
\r
29515 * @return {Number} The comparison value
\r
29517 asDate : function(s) {
\r
29521 if(Ext.isDate(s)){
\r
29522 return s.getTime();
\r
29524 return Date.parse(String(s));
\r
29529 * @param {Mixed} s The value being converted
\r
29530 * @return {Float} The comparison value
\r
29532 asFloat : function(s) {
\r
29533 var val = parseFloat(String(s).replace(/,/g, ""));
\r
29534 return isNaN(val) ? 0 : val;
\r
29538 * Integer sorting
\r
29539 * @param {Mixed} s The value being converted
\r
29540 * @return {Number} The comparison value
\r
29542 asInt : function(s) {
\r
29543 var val = parseInt(String(s).replace(/,/g, ""), 10);
\r
29544 return isNaN(val) ? 0 : val;
\r
29547 * @class Ext.data.Record
29548 * <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record
29549 * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
29550 * to access Records cached in an {@link Ext.data.Store} object.</p>
29551 * <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
29552 * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
29554 * <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
29555 * In order to copy data from one Store to another, use the {@link #copy} method to create an exact
29556 * copy of the Record, and insert the new instance into the other Store.</p>
29557 * <p>When serializing a Record for submission to the server, be aware that it contains many private
29558 * properties, and also a reference to its owning Store which in turn holds references to its Records.
29559 * This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
29560 * <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
29561 * <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
29563 * This constructor should not be used to create Record objects. Instead, use {@link #create} to
29564 * generate a subclass of Ext.data.Record configured with information about its constituent fields.
29565 * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
29566 * fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
29567 * for each field will be assigned.
29568 * @param {Object} id (Optional) The id of the Record. This id should be unique, and is used by the
29569 * {@link Ext.data.Store} object which owns the Record to index its collection of Records. If
29570 * an <code>id</code> is not specified a <b><code>{@link #phantom}</code></b> Record will be created
29571 * with an {@link #Record.id automatically generated id}.
29573 Ext.data.Record = function(data, id){
29574 // if no id, call the auto id method
29575 this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
29576 this.data = data || {};
29580 * Generate a constructor for a specific Record layout.
29581 * @param {Array} o An Array of <b>{@link Ext.data.Field Field}</b> definition objects.
29582 * The constructor generated by this method may be used to create new Record instances. The data
29583 * object must contain properties named after the {@link Ext.data.Field field}
29584 * <b><tt>{@link Ext.data.Field#name}s</tt></b>. Example usage:<pre><code>
29585 // create a Record constructor from a description of the fields
29586 var TopicRecord = Ext.data.Record.create([ // creates a subclass of Ext.data.Record
29587 {{@link Ext.data.Field#name name}: 'title', {@link Ext.data.Field#mapping mapping}: 'topic_title'},
29588 {name: 'author', mapping: 'username', allowBlank: false},
29589 {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
29590 {name: 'lastPost', mapping: 'post_time', type: 'date'},
29591 {name: 'lastPoster', mapping: 'user2'},
29592 {name: 'excerpt', mapping: 'post_text', allowBlank: false},
29593 // In the simplest case, if no properties other than <tt>name</tt> are required,
29594 // a field definition may consist of just a String for the field name.
29598 // create Record instance
29599 var myNewRecord = new TopicRecord(
29601 title: 'Do my job please',
29604 lastPost: new Date(),
29605 lastPoster: 'Animal',
29606 excerpt: 'No way dude!',
29609 id // optionally specify the id of the record otherwise {@link #Record.id one is auto-assigned}
29611 myStore.{@link Ext.data.Store#add add}(myNewRecord);
29614 * @return {function} A constructor which is used to create new Records according
29615 * to the definition. The constructor has the same signature as {@link #Ext.data.Record}.
29618 Ext.data.Record.create = function(o){
29619 var f = Ext.extend(Ext.data.Record, {});
29620 var p = f.prototype;
29621 p.fields = new Ext.util.MixedCollection(false, function(field){
29624 for(var i = 0, len = o.length; i < len; i++){
29625 p.fields.add(new Ext.data.Field(o[i]));
29627 f.getField = function(name){
29628 return p.fields.get(name);
29633 Ext.data.Record.PREFIX = 'ext-record';
29634 Ext.data.Record.AUTO_ID = 1;
29635 Ext.data.Record.EDIT = 'edit';
29636 Ext.data.Record.REJECT = 'reject';
29637 Ext.data.Record.COMMIT = 'commit';
29641 * Generates a sequential id. This method is typically called when a record is {@link #create}d
29642 * and {@link #Record no id has been specified}. The returned id takes the form:
29643 * <tt>{PREFIX}-{AUTO_ID}</tt>.<div class="mdetail-params"><ul>
29644 * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.PREFIX</tt>
29645 * (defaults to <tt>'ext-record'</tt>)</p></li>
29646 * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.AUTO_ID</tt>
29647 * (defaults to <tt>1</tt> initially)</p></li>
29649 * @param {Record} rec The record being created. The record does not exist, it's a {@link #phantom}.
29650 * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
29652 Ext.data.Record.id = function(rec) {
29653 rec.phantom = true;
29654 return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
29657 Ext.data.Record.prototype = {
29659 * <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>
29660 * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record. Read-only.
29662 * @type Ext.util.MixedCollection
29665 * An object hash representing the data for this Record. Every field name in the Record definition
29666 * is represented by a property of that name in this object. Note that unless you specified a field
29667 * with {@link Ext.data.Field#name name} "id" in the Record definition, this will <b>not</b> contain
29668 * an <tt>id</tt> property.
29673 * The unique ID of the Record {@link #Record as specified at construction time}.
29678 * Readonly flag - true if this Record has been modified.
29685 * This object contains a key and value storing the original values of all modified
29686 * fields or is null if no fields have been modified.
29687 * @property modified
29692 * <tt>false</tt> when the record does not yet exist in a server-side database (see
29693 * {@link #markDirty}). Any record which has a real database pk set as its id property
29694 * is NOT a phantom -- it's real.
29695 * @property phantom
29701 join : function(store){
29703 * The {@link Ext.data.Store} to which this Record belongs.
29705 * @type {Ext.data.Store}
29707 this.store = store;
29711 * Set the {@link Ext.data.Field#name named field} to the specified value. For example:
29713 // record has a field named 'firstname'
29714 var Employee = Ext.data.Record.{@link #create}([
29715 {name: 'firstname'},
29719 // update the 2nd record in the store:
29720 var rec = myStore.{@link Ext.data.Store#getAt getAt}(1);
29722 // set the value (shows dirty flag):
29723 rec.set('firstname', 'Betty');
29725 // commit the change (removes dirty flag):
29726 rec.{@link #commit}();
29728 // update the record in the store, bypass setting dirty flag,
29729 // and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records}
29730 rec.{@link #data}['firstname'] = 'Wilma'); // updates record, but not the view
29731 rec.{@link #commit}(); // updates the view
29733 * <b>Notes</b>:<div class="mdetail-params"><ul>
29734 * <li>If the store has a writer and <code>autoSave=true</code>, each set()
29735 * will execute an XHR to the server.</li>
29736 * <li>Use <code>{@link #beginEdit}</code> to prevent the store's <code>update</code>
29737 * event firing while using set().</li>
29738 * <li>Use <code>{@link #endEdit}</code> to have the store's <code>update</code>
29741 * @param {String} name The {@link Ext.data.Field#name name of the field} to set.
29742 * @param {Object} value The value to set the field to.
29744 set : function(name, value){
29745 var isObj = (typeof value === 'object');
29746 if(!isObj && String(this.data[name]) === String(value)){
29748 } else if (isObj && Ext.encode(this.data[name]) === Ext.encode(value)) {
29752 if(!this.modified){
29753 this.modified = {};
29755 if(typeof this.modified[name] == 'undefined'){
29756 this.modified[name] = this.data[name];
29758 this.data[name] = value;
29765 afterEdit: function(){
29767 this.store.afterEdit(this);
29772 afterReject: function(){
29774 this.store.afterReject(this);
29779 afterCommit: function(){
29781 this.store.afterCommit(this);
29786 * Get the value of the {@link Ext.data.Field#name named field}.
29787 * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
29788 * @return {Object} The value of the field.
29790 get : function(name){
29791 return this.data[name];
29795 * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
29796 * are relayed to the containing store.
29797 * See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
29799 beginEdit : function(){
29800 this.editing = true;
29801 this.modified = this.modified || {};
29805 * Cancels all changes made in the current edit operation.
29807 cancelEdit : function(){
29808 this.editing = false;
29809 delete this.modified;
29813 * End an edit. If any data was modified, the containing store is notified
29814 * (ie, the store's <code>update</code> event will fire).
29816 endEdit : function(){
29817 this.editing = false;
29824 * Usually called by the {@link Ext.data.Store} which owns the Record.
29825 * Rejects all changes made to the Record since either creation, or the last commit operation.
29826 * Modified fields are reverted to their original values.
29827 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
29828 * to have their code notified of reject operations.</p>
29829 * @param {Boolean} silent (optional) True to skip notification of the owning
29830 * store of the change (defaults to false)
29832 reject : function(silent){
29833 var m = this.modified;
29835 if(typeof m[n] != "function"){
29836 this.data[n] = m[n];
29839 this.dirty = false;
29840 delete this.modified;
29841 this.editing = false;
29842 if(silent !== true){
29843 this.afterReject();
29848 * Usually called by the {@link Ext.data.Store} which owns the Record.
29849 * Commits all changes made to the Record since either creation, or the last commit operation.
29850 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
29851 * to have their code notified of commit operations.</p>
29852 * @param {Boolean} silent (optional) True to skip notification of the owning
29853 * store of the change (defaults to false)
29855 commit : function(silent){
29856 this.dirty = false;
29857 delete this.modified;
29858 this.editing = false;
29859 if(silent !== true){
29860 this.afterCommit();
29865 * Gets a hash of only the fields that have been modified since this Record was created or commited.
29868 getChanges : function(){
29869 var m = this.modified, cs = {};
29871 if(m.hasOwnProperty(n)){
29872 cs[n] = this.data[n];
29879 hasError : function(){
29880 return this.error !== null;
29884 clearError : function(){
29889 * Creates a copy of this Record.
29890 * @param {String} id (optional) A new Record id, defaults to {@link #Record.id autogenerating an id}.
29891 * Note: if an <code>id</code> is not specified the copy created will be a
29892 * <code>{@link #phantom}</code> Record.
29895 copy : function(newId) {
29896 return new this.constructor(Ext.apply({}, this.data), newId || this.id);
29900 * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
29901 * since the load or last commit.
29902 * @param {String} fieldName {@link Ext.data.Field.{@link Ext.data.Field#name}
29903 * @return {Boolean}
29905 isModified : function(fieldName){
29906 return !!(this.modified && this.modified.hasOwnProperty(fieldName));
29910 * By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
29911 * record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
29912 * <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
29913 * @return {Boolean}
29915 isValid : function() {
29916 return this.fields.find(function(f) {
29917 return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
29918 },this) ? false : true;
29922 * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
29923 * is used interally when adding <code>{@link #phantom}</code> records to a
29924 * {@link Ext.data.Store#writer writer enabled store}.</p>
29925 * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
29926 * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
29927 * have a create action composed for it during {@link Ext.data.Store#save store save}
29930 markDirty : function(){
29932 if(!this.modified){
29933 this.modified = {};
29935 this.fields.each(function(f) {
29936 this.modified[f.name] = this.data[f.name];
29940 * @class Ext.StoreMgr
29941 * @extends Ext.util.MixedCollection
29942 * The default global group of stores.
29945 Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
29947 * @cfg {Object} listeners @hide
29951 * Registers one or more Stores with the StoreMgr. You do not normally need to register stores
29952 * manually. Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
29953 * @param {Ext.data.Store} store1 A Store instance
29954 * @param {Ext.data.Store} store2 (optional)
29955 * @param {Ext.data.Store} etc... (optional)
29957 register : function(){
29958 for(var i = 0, s; (s = arguments[i]); i++){
29964 * Unregisters one or more Stores with the StoreMgr
29965 * @param {String/Object} id1 The id of the Store, or a Store instance
29966 * @param {String/Object} id2 (optional)
29967 * @param {String/Object} etc... (optional)
29969 unregister : function(){
29970 for(var i = 0, s; (s = arguments[i]); i++){
29971 this.remove(this.lookup(s));
29976 * Gets a registered Store by id
29977 * @param {String/Object} id The id of the Store, or a Store instance
29978 * @return {Ext.data.Store}
29980 lookup : function(id){
29981 if(Ext.isArray(id)){
29982 var fields = ['field1'], expand = !Ext.isArray(id[0]);
29984 for(var i = 2, len = id[0].length; i <= len; ++i){
29985 fields.push('field' + i);
29988 return new Ext.data.ArrayStore({
29991 expandData: expand,
29997 return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
30000 // getKey implementation for MixedCollection
30001 getKey : function(o){
30005 * @class Ext.data.Store
30006 * @extends Ext.util.Observable
30007 * <p>The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
30008 * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
30009 * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.</p>
30010 * <p><u>Retrieving Data</u></p>
30011 * <p>A Store object may access a data object using:<div class="mdetail-params"><ul>
30012 * <li>{@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}</li>
30013 * <li>{@link #data} to automatically pass in data</li>
30014 * <li>{@link #loadData} to manually pass in data</li>
30016 * <p><u>Reading Data</u></p>
30017 * <p>A Store object has no inherent knowledge of the format of the data object (it could be
30018 * an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
30019 * of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
30021 * <p><u>Store Types</u></p>
30022 * <p>There are several implementations of Store available which are customized for use with
30023 * a specific DataReader implementation. Here is an example using an ArrayStore which implicitly
30024 * creates a reader commensurate to an Array data object.</p>
30026 var myStore = new Ext.data.ArrayStore({
30027 fields: ['fullname', 'first'],
30028 idIndex: 0 // id for each record will be the first element
30031 * <p>For custom implementations create a basic {@link Ext.data.Store} configured as needed:</p>
30033 // create a {@link Ext.data.Record Record} constructor:
30034 var rt = Ext.data.Record.create([
30035 {name: 'fullname'},
30038 var myStore = new Ext.data.Store({
30039 // explicitly create reader
30040 reader: new Ext.data.ArrayReader(
30042 idIndex: 0 // id for each record will be the first element
30048 * <p>Load some data into store (note the data object is an array which corresponds to the reader):</p>
30051 [1, 'Fred Flintstone', 'Fred'], // note that id for the record is the first element
30052 [2, 'Barney Rubble', 'Barney']
30054 myStore.loadData(myData);
30056 * <p>Records are cached and made available through accessor functions. An example of adding
30057 * a record to the store:</p>
30059 var defaultData = {
30060 fullname: 'Full Name',
30061 first: 'First Name'
30063 var recId = 100; // provide unique id for the record
30064 var r = new myStore.recordType(defaultData, ++recId); // create new record
30065 myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
30068 * Creates a new Store.
30069 * @param {Object} config A config object containing the objects needed for the Store to access data,
30070 * and read the data into Records.
30073 Ext.data.Store = function(config){
30074 this.data = new Ext.util.MixedCollection(false);
30075 this.data.getKey = function(o){
30079 * See the <code>{@link #baseParams corresponding configuration option}</code>
30080 * for a description of this property.
30081 * To modify this property see <code>{@link #setBaseParam}</code>.
30084 this.baseParams = {};
30086 // temporary removed-records cache
30089 if(config && config.data){
30090 this.inlineData = config.data;
30091 delete config.data;
30094 Ext.apply(this, config);
30096 this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
30098 if(this.url && !this.proxy){
30099 this.proxy = new Ext.data.HttpProxy({url: this.url});
30101 // If Store is RESTful, so too is the DataProxy
30102 if (this.restful === true && this.proxy) {
30103 // When operating RESTfully, a unique transaction is generated for each record.
30104 this.batch = false;
30105 Ext.data.Api.restify(this.proxy);
30108 if(this.reader){ // reader passed
30109 if(!this.recordType){
30110 this.recordType = this.reader.recordType;
30112 if(this.reader.onMetaChange){
30113 this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
30115 if (this.writer) { // writer passed
30116 this.writer.meta = this.reader.meta;
30117 this.pruneModifiedRecords = true;
30122 * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
30123 * {@link Ext.data.DataReader Reader}. Read-only.
30124 * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
30125 * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
30126 * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
30127 * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
30128 // create the data store
30129 var store = new Ext.data.ArrayStore({
30133 {name: 'price', type: 'float'},
30134 {name: 'change', type: 'float'},
30135 {name: 'pctChange', type: 'float'},
30136 {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
30139 store.loadData(myData);
30142 var grid = new Ext.grid.EditorGridPanel({
30144 colModel: new Ext.grid.ColumnModel({
30146 {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
30147 {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
30148 {header: 'Change', renderer: change, dataIndex: 'change'},
30149 {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
30150 {header: 'Last Updated', width: 85,
30151 renderer: Ext.util.Format.dateRenderer('m/d/Y'),
30152 dataIndex: 'lastChange'}
30159 autoExpandColumn: 'company', // match the id specified in the column model
30162 title:'Array Grid',
30164 text: 'Add Record',
30165 handler : function(){
30166 var defaultData = {
30168 company: 'New Company',
30169 lastChange: (new Date()).clearTime(),
30173 var recId = 3; // provide unique id
30174 var p = new store.recordType(defaultData, recId); // create new record
30175 grid.stopEditing();
30176 store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
30177 grid.startEditing(0, 0);
30182 * @property recordType
30186 if(this.recordType){
30188 * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
30189 * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
30191 * @type Ext.util.MixedCollection
30193 this.fields = this.recordType.prototype.fields;
30195 this.modified = [];
30199 * @event datachanged
30200 * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
30201 * widget that is using this Store as a Record cache should refresh its view.
30202 * @param {Store} this
30206 * @event metachange
30207 * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
30208 * @param {Store} this
30209 * @param {Object} meta The JSON metadata
30214 * Fires when Records have been {@link #add}ed to the Store
30215 * @param {Store} this
30216 * @param {Ext.data.Record[]} records The array of Records added
30217 * @param {Number} index The index at which the record(s) were added
30222 * Fires when a Record has been {@link #remove}d from the Store
30223 * @param {Store} this
30224 * @param {Ext.data.Record} record The Record that was removed
30225 * @param {Number} index The index at which the record was removed
30230 * Fires when a Record has been updated
30231 * @param {Store} this
30232 * @param {Ext.data.Record} record The Record that was updated
30233 * @param {String} operation The update operation being performed. Value may be one of:
30235 Ext.data.Record.EDIT
30236 Ext.data.Record.REJECT
30237 Ext.data.Record.COMMIT
30243 * Fires when the data cache has been cleared.
30244 * @param {Store} this
30249 * <p>Fires if an exception occurs in the Proxy during a remote request.
30250 * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
30251 * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
30252 * for additional details.
30253 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
30258 * @event beforeload
30259 * Fires before a request is made for a new data object. If the beforeload handler returns
30260 * <tt>false</tt> the {@link #load} action will be canceled.
30261 * @param {Store} this
30262 * @param {Object} options The loading options that were specified (see {@link #load} for details)
30267 * Fires after a new set of Records has been loaded.
30268 * @param {Store} this
30269 * @param {Ext.data.Record[]} records The Records that were loaded
30270 * @param {Object} options The loading options that were specified (see {@link #load} for details)
30274 * @event loadexception
30275 * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
30276 * event instead.</p>
30277 * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
30278 * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
30279 * for additional details.
30280 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
30285 * @event beforewrite
30286 * @param {DataProxy} this
30287 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
30288 * @param {Record/Array[Record]} rs
30289 * @param {Object} options The loading options that were specified. Edit <code>options.params</code> to add Http parameters to the request. (see {@link #save} for details)
30290 * @param {Object} arg The callback's arg object passed to the {@link #request} function
30295 * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
30296 * Success or failure of the action is available in the <code>result['successProperty']</code> property.
30297 * The server-code might set the <code>successProperty</code> to <tt>false</tt> if a database validation
30298 * failed, for example.
30299 * @param {Ext.data.Store} store
30300 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
30301 * @param {Object} result The 'data' picked-out out of the response for convenience.
30302 * @param {Ext.Direct.Transaction} res
30303 * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
30309 this.relayEvents(this.proxy, ['loadexception', 'exception']);
30311 // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
30315 add: this.createRecords,
30316 remove: this.destroyRecord,
30317 update: this.updateRecord
30321 this.sortToggle = {};
30322 if(this.sortField){
30323 this.setDefaultSort(this.sortField, this.sortDir);
30324 }else if(this.sortInfo){
30325 this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
30328 Ext.data.Store.superclass.constructor.call(this);
30331 this.storeId = this.id;
30335 Ext.StoreMgr.register(this);
30337 if(this.inlineData){
30338 this.loadData(this.inlineData);
30339 delete this.inlineData;
30340 }else if(this.autoLoad){
30341 this.load.defer(10, this, [
30342 typeof this.autoLoad == 'object' ?
30343 this.autoLoad : undefined]);
30346 Ext.extend(Ext.data.Store, Ext.util.Observable, {
30348 * @cfg {String} storeId If passed, the id to use to register with the <b>{@link Ext.StoreMgr StoreMgr}</b>.
30349 * <p><b>Note</b>: if a (deprecated) <tt>{@link #id}</tt> is specified it will supersede the <tt>storeId</tt>
30353 * @cfg {String} url If a <tt>{@link #proxy}</tt> is not specified the <tt>url</tt> will be used to
30354 * implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an <tt>url</tt> is specified.
30355 * Typically this option, or the <code>{@link #data}</code> option will be specified.
30358 * @cfg {Boolean/Object} autoLoad If <tt>{@link #data}</tt> is not specified, and if <tt>autoLoad</tt>
30359 * is <tt>true</tt> or an <tt>Object</tt>, this store's {@link #load} method is automatically called
30360 * after creation. If the value of <tt>autoLoad</tt> is an <tt>Object</tt>, this <tt>Object</tt> will
30361 * be passed to the store's {@link #load} method.
30364 * @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
30365 * access to a data object. See <code>{@link #url}</code>.
30368 * @cfg {Array} data An inline data object readable by the <code>{@link #reader}</code>.
30369 * Typically this option, or the <code>{@link #url}</code> option will be specified.
30372 * @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
30373 * data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
30374 * <b><tt>{@link Ext.data.Record#id id}</tt></b> property.
30377 * @cfg {Ext.data.DataWriter} writer
30378 * <p>The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
30379 * to the server-side database.</p>
30380 * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
30381 * events on the store are monitored in order to remotely {@link #createRecords create records},
30382 * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
30383 * <br><p>The proxy for this store will relay any {@link #writexception} events to this store.</p>
30384 * <br><p>Sample implementation:
30386 var writer = new {@link Ext.data.JsonWriter}({
30388 writeAllFields: true // write all fields, not just those that changed
30391 // Typical Store collecting the Proxy, Reader and Writer together.
30392 var store = new Ext.data.Store({
30397 writer: writer, // <-- plug a DataWriter into the store just as you would a Reader
30398 paramsAsHash: true,
30399 autoSave: false // <-- false to delay executing create, update, destroy requests
30400 // until specifically told to do so.
30402 * </code></pre></p>
30404 writer : undefined,
30406 * @cfg {Object} baseParams
30407 * <p>An object containing properties which are to be sent as parameters
30408 * for <i>every</i> HTTP request.</p>
30409 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
30410 * <p><b>Note</b>: <code>baseParams</code> may be superseded by any <code>params</code>
30411 * specified in a <code>{@link #load}</code> request, see <code>{@link #load}</code>
30412 * for more details.</p>
30413 * This property may be modified after creation using the <code>{@link #setBaseParam}</code>
30418 * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
30419 * {@link #load} operation. Note that for local sorting, the <tt>direction</tt> property is
30420 * case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
30421 * For example:<pre><code>
30423 field: 'fieldName',
30424 direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
30429 * @cfg {boolean} remoteSort <tt>true</tt> if sorting is to be handled by requesting the <tt>{@link #proxy Proxy}</tt>
30430 * to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
30431 * in place (defaults to <tt>false</tt>).
30432 * <p>If <tt>remoteSort</tt> is <tt>true</tt>, then clicking on a {@link Ext.grid.Column Grid Column}'s
30433 * {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
30434 * the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
30435 * <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
30436 * {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
30437 * <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
30440 remoteSort : false,
30443 * @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
30444 * to is destroyed (defaults to <tt>false</tt>).
30445 * <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
30447 autoDestroy : false,
30450 * @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
30451 * the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
30452 * for the accessor method to retrieve the modified records.
30454 pruneModifiedRecords : false,
30457 * Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
30458 * for the details of what this may contain. This may be useful for accessing any params which were used
30459 * to load the current Record cache.
30462 lastOptions : null,
30465 * @cfg {Boolean} autoSave
30466 * <p>Defaults to <tt>true</tt> causing the store to automatically {@link #save} records to
30467 * the server when a record is modified (ie: becomes 'dirty'). Specify <tt>false</tt> to manually call {@link #save}
30468 * to send all modifiedRecords to the server.</p>
30469 * <br><p><b>Note</b>: each CRUD action will be sent as a separate request.</p>
30474 * @cfg {Boolean} batch
30475 * <p>Defaults to <tt>true</tt> (unless <code>{@link #restful}:true</code>). Multiple
30476 * requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
30477 * and sent as one transaction. Only applies when <code>{@link #autoSave}</code> is set
30478 * to <tt>false</tt>.</p>
30479 * <br><p>If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
30480 * generated for each record.</p>
30485 * @cfg {Boolean} restful
30486 * Defaults to <tt>false</tt>. Set to <tt>true</tt> to have the Store and the set
30487 * Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
30488 * PUT and DELETE requests to the server. The HTTP method used for any given CRUD
30489 * action is described in {@link Ext.data.Api#restActions}. For additional information
30490 * see {@link Ext.data.DataProxy#restful}.
30491 * <p><b>Note</b>: if <code>{@link #restful}:true</code> <code>batch</code> will
30492 * internally be set to <tt>false</tt>.</p>
30497 * @cfg {Object} paramNames
30498 * <p>An object containing properties which specify the names of the paging and
30499 * sorting parameters passed to remote servers when loading blocks of data. By default, this
30500 * object takes the following form:</p><pre><code>
30502 start : 'start', // The parameter name which specifies the start row
30503 limit : 'limit', // The parameter name which specifies number of rows to return
30504 sort : 'sort', // The parameter name which specifies the column to sort on
30505 dir : 'dir' // The parameter name which specifies the sort direction
30508 * <p>The server must produce the requested data block upon receipt of these parameter names.
30509 * If different parameter names are required, this property can be overriden using a configuration
30511 * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
30512 * the parameter names to use in its {@link #load requests}.
30514 paramNames : undefined,
30517 * @cfg {Object} defaultParamNames
30518 * Provides the default values for the {@link #paramNames} property. To globally modify the parameters
30519 * for all stores, this object should be changed on the store prototype.
30521 defaultParamNames : {
30529 * Destroys the store.
30531 destroy : function(){
30533 Ext.StoreMgr.unregister(this);
30536 Ext.destroy(this.proxy);
30537 this.reader = this.writer = null;
30538 this.purgeListeners();
30542 * Add Records to the Store and fires the {@link #add} event. To add Records
30543 * to the store from a remote source use <code>{@link #load}({add:true})</code>.
30544 * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
30545 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
30546 * to add to the cache. See {@link #recordType}.
30548 add : function(records){
30549 records = [].concat(records);
30550 if(records.length < 1){
30553 for(var i = 0, len = records.length; i < len; i++){
30554 records[i].join(this);
30556 var index = this.data.length;
30557 this.data.addAll(records);
30559 this.snapshot.addAll(records);
30561 this.fireEvent('add', this, records, index);
30565 * (Local sort only) Inserts the passed Record into the Store at the index where it
30566 * should go based on the current sort information.
30567 * @param {Ext.data.Record} record
30569 addSorted : function(record){
30570 var index = this.findInsertIndex(record);
30571 this.insert(index, record);
30575 * Remove a Record from the Store and fires the {@link #remove} event.
30576 * @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache.
30578 remove : function(record){
30579 var index = this.data.indexOf(record);
30581 this.data.removeAt(index);
30582 if(this.pruneModifiedRecords){
30583 this.modified.remove(record);
30586 this.snapshot.remove(record);
30588 this.fireEvent('remove', this, record, index);
30593 * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
30594 * @param {Number} index The index of the record to remove.
30596 removeAt : function(index){
30597 this.remove(this.getAt(index));
30601 * Remove all Records from the Store and fires the {@link #clear} event.
30603 removeAll : function(){
30606 this.snapshot.clear();
30608 if(this.pruneModifiedRecords){
30609 this.modified = [];
30611 this.fireEvent('clear', this);
30615 * Inserts Records into the Store at the given index and fires the {@link #add} event.
30616 * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
30617 * @param {Number} index The start index at which to insert the passed Records.
30618 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
30620 insert : function(index, records){
30621 records = [].concat(records);
30622 for(var i = 0, len = records.length; i < len; i++){
30623 this.data.insert(index, records[i]);
30624 records[i].join(this);
30626 this.fireEvent('add', this, records, index);
30630 * Get the index within the cache of the passed Record.
30631 * @param {Ext.data.Record} record The Ext.data.Record object to find.
30632 * @return {Number} The index of the passed Record. Returns -1 if not found.
30634 indexOf : function(record){
30635 return this.data.indexOf(record);
30639 * Get the index within the cache of the Record with the passed id.
30640 * @param {String} id The id of the Record to find.
30641 * @return {Number} The index of the Record. Returns -1 if not found.
30643 indexOfId : function(id){
30644 return this.data.indexOfKey(id);
30648 * Get the Record with the specified id.
30649 * @param {String} id The id of the Record to find.
30650 * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
30652 getById : function(id){
30653 return this.data.key(id);
30657 * Get the Record at the specified index.
30658 * @param {Number} index The index of the Record to find.
30659 * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
30661 getAt : function(index){
30662 return this.data.itemAt(index);
30666 * Returns a range of Records between specified indices.
30667 * @param {Number} startIndex (optional) The starting index (defaults to 0)
30668 * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
30669 * @return {Ext.data.Record[]} An array of Records
30671 getRange : function(start, end){
30672 return this.data.getRange(start, end);
30676 storeOptions : function(o){
30677 o = Ext.apply({}, o);
30680 this.lastOptions = o;
30684 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
30685 * <br><p>Notes:</p><div class="mdetail-params"><ul>
30686 * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
30687 * loaded. To perform any post-processing where information from the load call is required, specify
30688 * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
30689 * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
30690 * properties in the <code>options.params</code> property to establish the initial position within the
30691 * dataset, and the number of Records to cache on each read from the Proxy.</li>
30692 * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
30693 * will be automatically included with the posted parameters according to the specified
30694 * <code>{@link #paramNames}</code>.</li>
30696 * @param {Object} options An object containing properties which control loading options:<ul>
30697 * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
30698 * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
30699 * <code>{@link #baseParams}</code> of the same name.</p>
30700 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
30701 * <li><b><tt>callback</tt></b> : Function<div class="sub-desc"><p>A function to be called after the Records
30702 * have been loaded. The <tt>callback</tt> is called after the load event and is passed the following arguments:<ul>
30703 * <li><tt>r</tt> : Ext.data.Record[]</li>
30704 * <li><tt>options</tt>: Options object from the load call</li>
30705 * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div></li>
30706 * <li><b><tt>scope</tt></b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
30707 * to the Store object)</p></div></li>
30708 * <li><b><tt>add</tt></b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
30709 * replace the current cache. <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
30711 * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
30712 * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
30714 load : function(options) {
30715 options = options || {};
30716 this.storeOptions(options);
30717 if(this.sortInfo && this.remoteSort){
30718 var pn = this.paramNames;
30719 options.params = options.params || {};
30720 options.params[pn.sort] = this.sortInfo.field;
30721 options.params[pn.dir] = this.sortInfo.direction;
30724 return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
30726 this.handleException(e);
30732 * updateRecord Should not be used directly. This method will be called automatically if a Writer is set.
30733 * Listens to 'update' event.
30734 * @param {Object} store
30735 * @param {Object} record
30736 * @param {Object} action
30739 updateRecord : function(store, record, action) {
30740 if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid))) {
30746 * Should not be used directly. Store#add will call this automatically if a Writer is set
30747 * @param {Object} store
30748 * @param {Object} rs
30749 * @param {Object} index
30752 createRecords : function(store, rs, index) {
30753 for (var i = 0, len = rs.length; i < len; i++) {
30754 if (rs[i].phantom && rs[i].isValid()) {
30755 rs[i].markDirty(); // <-- Mark new records dirty
30756 this.modified.push(rs[i]); // <-- add to modified
30759 if (this.autoSave === true) {
30765 * Destroys a record or records. Should not be used directly. It's called by Store#remove if a Writer is set.
30766 * @param {Store} this
30767 * @param {Ext.data.Record/Ext.data.Record[]}
30768 * @param {Number} index
30771 destroyRecord : function(store, record, index) {
30772 if (this.modified.indexOf(record) != -1) { // <-- handled already if @cfg pruneModifiedRecords == true
30773 this.modified.remove(record);
30775 if (!record.phantom) {
30776 this.removed.push(record);
30778 // since the record has already been removed from the store but the server request has not yet been executed,
30779 // must keep track of the last known index this record existed. If a server error occurs, the record can be
30780 // put back into the store. @see Store#createCallback where the record is returned when response status === false
30781 record.lastIndex = index;
30783 if (this.autoSave === true) {
30790 * This method should generally not be used directly. This method is called internally
30791 * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
30792 * {@link #remove}, or {@link #update} events fire.
30793 * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
30794 * @param {Record/Record[]} rs
30795 * @param {Object} options
30799 execute : function(action, rs, options) {
30800 // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
30801 if (!Ext.data.Api.isAction(action)) {
30802 throw new Ext.data.Api.Error('execute', action);
30804 // make sure options has a params key
30805 options = Ext.applyIf(options||{}, {
30809 // have to separate before-events since load has a different signature than create,destroy and save events since load does not
30810 // include the rs (record resultset) parameter. Capture return values from the beforeaction into doRequest flag.
30811 var doRequest = true;
30813 if (action === 'read') {
30814 doRequest = this.fireEvent('beforeload', this, options);
30817 // if Writer is configured as listful, force single-recoord rs to be [{}} instead of {}
30818 if (this.writer.listful === true && this.restful !== true) {
30819 rs = (Ext.isArray(rs)) ? rs : [rs];
30821 // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
30822 else if (Ext.isArray(rs) && rs.length == 1) {
30825 // Write the action to options.params
30826 if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
30827 this.writer.write(action, options.params, rs);
30830 if (doRequest !== false) {
30831 // Send request to proxy.
30832 var params = Ext.apply({}, options.params, this.baseParams);
30833 if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
30834 params.xaction = action;
30836 // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions. We'll flip it now
30837 // and send the value into DataProxy#request, since it's the value which maps to the DataProxy#api
30838 this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options);
30844 * Saves all pending changes to the store. If the commensurate Ext.data.Api.actions action is not configured, then
30845 * the configured <code>{@link #url}</code> will be used.
30848 * --------------- --------------------
30849 * removed records Ext.data.Api.actions.destroy
30850 * phantom records Ext.data.Api.actions.create
30851 * {@link #getModifiedRecords modified records} Ext.data.Api.actions.update
30853 * @TODO: Create extensions of Error class and send associated Record with thrown exceptions.
30854 * e.g.: Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
30856 save : function() {
30857 if (!this.writer) {
30858 throw new Ext.data.Store.Error('writer-undefined');
30861 // DESTROY: First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
30862 if (this.removed.length) {
30863 this.doTransaction('destroy', this.removed);
30866 // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
30867 var rs = [].concat(this.getModifiedRecords());
30868 if (!rs.length) { // Bail-out if empty...
30872 // CREATE: Next check for phantoms within rs. splice-off and execute create.
30874 for (var i = rs.length-1; i >= 0; i--) {
30875 if (rs[i].phantom === true) {
30876 var rec = rs.splice(i, 1).shift();
30877 if (rec.isValid()) {
30878 phantoms.push(rec);
30880 } else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
30884 // If we have valid phantoms, create them...
30885 if (phantoms.length) {
30886 this.doTransaction('create', phantoms);
30889 // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
30891 this.doTransaction('update', rs);
30896 // private. Simply wraps call to Store#execute in try/catch. Defers to Store#handleException on error. Loops if batch: false
30897 doTransaction : function(action, rs) {
30898 function transaction(records) {
30900 this.execute(action, records);
30902 this.handleException(e);
30905 if (this.batch === false) {
30906 for (var i = 0, len = rs.length; i < len; i++) {
30907 transaction.call(this, rs[i]);
30910 transaction.call(this, rs);
30914 // @private callback-handler for remote CRUD actions
30915 // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
30916 createCallback : function(action, rs) {
30917 var actions = Ext.data.Api.actions;
30918 return (action == 'read') ? this.loadRecords : function(data, response, success) {
30919 // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
30920 this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, data);
30921 // If success === false here, exception will have been called in DataProxy
30922 if (success === true) {
30923 this.fireEvent('write', this, action, data, response, rs);
30928 // Clears records from modified array after an exception event.
30929 // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized?
30930 clearModified : function(rs) {
30931 if (Ext.isArray(rs)) {
30932 for (var n=rs.length-1;n>=0;n--) {
30933 this.modified.splice(this.modified.indexOf(rs[n]), 1);
30936 this.modified.splice(this.modified.indexOf(rs), 1);
30940 // remap record ids in MixedCollection after records have been realized. @see Store#onCreateRecords, @see DataReader#realize
30941 reMap : function(record) {
30942 if (Ext.isArray(record)) {
30943 for (var i = 0, len = record.length; i < len; i++) {
30944 this.reMap(record[i]);
30947 delete this.data.map[record._phid];
30948 this.data.map[record.id] = record;
30949 var index = this.data.keys.indexOf(record._phid);
30950 this.data.keys.splice(index, 1, record.id);
30951 delete record._phid;
30955 // @protected onCreateRecord proxy callback for create action
30956 onCreateRecords : function(success, rs, data) {
30957 if (success === true) {
30959 this.reader.realize(rs, data);
30963 this.handleException(e);
30964 if (Ext.isArray(rs)) {
30965 // Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
30966 this.onCreateRecords(success, rs, data);
30972 // @protected, onUpdateRecords proxy callback for update action
30973 onUpdateRecords : function(success, rs, data) {
30974 if (success === true) {
30976 this.reader.update(rs, data);
30978 this.handleException(e);
30979 if (Ext.isArray(rs)) {
30980 // Recurse to run back into the try {}. DataReader#update splices-off the rs until empty.
30981 this.onUpdateRecords(success, rs, data);
30987 // @protected onDestroyRecords proxy callback for destroy action
30988 onDestroyRecords : function(success, rs, data) {
30989 // splice each rec out of this.removed
30990 rs = (rs instanceof Ext.data.Record) ? [rs] : rs;
30991 for (var i=0,len=rs.length;i<len;i++) {
30992 this.removed.splice(this.removed.indexOf(rs[i]), 1);
30994 if (success === false) {
30995 // put records back into store if remote destroy fails.
30996 // @TODO: Might want to let developer decide.
30997 for (i=rs.length-1;i>=0;i--) {
30998 this.insert(rs[i].lastIndex, rs[i]); // <-- lastIndex set in Store#destroyRecord
31003 // protected handleException. Possibly temporary until Ext framework has an exception-handler.
31004 handleException : function(e) {
31005 // @see core/Error.js
31006 Ext.handleError(e);
31010 * <p>Reloads the Record cache from the configured Proxy using the configured {@link Ext.data.Reader Reader} and
31011 * the options from the last load operation performed.</p>
31012 * <p><b>Note</b>: see the Important note in {@link #load}.</p>
31013 * @param {Object} options (optional) An <tt>Object</tt> containing {@link #load loading options} which may
31014 * override the options used in the last {@link #load} operation. See {@link #load} for details (defaults to
31015 * <tt>null</tt>, in which case the {@link #lastOptions} are used).
31017 reload : function(options){
31018 this.load(Ext.applyIf(options||{}, this.lastOptions));
31022 // Called as a callback by the Reader during a load operation.
31023 loadRecords : function(o, options, success){
31024 if(!o || success === false){
31025 if(success !== false){
31026 this.fireEvent('load', this, [], options);
31028 if(options.callback){
31029 options.callback.call(options.scope || this, [], options, false, o);
31033 var r = o.records, t = o.totalRecords || r.length;
31034 if(!options || options.add !== true){
31035 if(this.pruneModifiedRecords){
31036 this.modified = [];
31038 for(var i = 0, len = r.length; i < len; i++){
31042 this.data = this.snapshot;
31043 delete this.snapshot;
31046 this.data.addAll(r);
31047 this.totalLength = t;
31049 this.fireEvent('datachanged', this);
31051 this.totalLength = Math.max(t, this.data.length+r.length);
31054 this.fireEvent('load', this, r, options);
31055 if(options.callback){
31056 options.callback.call(options.scope || this, r, options, true);
31061 * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
31062 * which understands the format of the data must have been configured in the constructor.
31063 * @param {Object} data The data block from which to read the Records. The format of the data expected
31064 * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
31065 * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
31066 * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
31067 * the existing cache.
31068 * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
31069 * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
31070 * new, unique ids will be added.
31072 loadData : function(o, append){
31073 var r = this.reader.readRecords(o);
31074 this.loadRecords(r, {add: append}, true);
31078 * Gets the number of cached records.
31079 * <p>If using paging, this may not be the total size of the dataset. If the data object
31080 * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
31081 * the dataset size. <b>Note</b>: see the Important note in {@link #load}.</p>
31082 * @return {Number} The number of Records in the Store's cache.
31084 getCount : function(){
31085 return this.data.length || 0;
31089 * Gets the total number of records in the dataset as returned by the server.
31090 * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
31091 * must contain the dataset size. For remote data sources, the value for this property
31092 * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
31093 * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
31094 * <b>Note</b>: see the Important note in {@link #load}.</p>
31095 * @return {Number} The number of Records as specified in the data object passed to the Reader
31097 * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
31099 getTotalCount : function(){
31100 return this.totalLength || 0;
31104 * Returns an object describing the current sort state of this Store.
31105 * @return {Object} The sort state of the Store. An object with two properties:<ul>
31106 * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
31107 * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
31109 * See <tt>{@link #sortInfo}</tt> for additional details.
31111 getSortState : function(){
31112 return this.sortInfo;
31116 applySort : function(){
31117 if(this.sortInfo && !this.remoteSort){
31118 var s = this.sortInfo, f = s.field;
31119 this.sortData(f, s.direction);
31124 sortData : function(f, direction){
31125 direction = direction || 'ASC';
31126 var st = this.fields.get(f).sortType;
31127 var fn = function(r1, r2){
31128 var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
31129 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
31131 this.data.sort(direction, fn);
31132 if(this.snapshot && this.snapshot != this.data){
31133 this.snapshot.sort(direction, fn);
31138 * Sets the default sort column and order to be used by the next {@link #load} operation.
31139 * @param {String} fieldName The name of the field to sort by.
31140 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
31142 setDefaultSort : function(field, dir){
31143 dir = dir ? dir.toUpperCase() : 'ASC';
31144 this.sortInfo = {field: field, direction: dir};
31145 this.sortToggle[field] = dir;
31149 * Sort the Records.
31150 * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
31151 * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
31152 * @param {String} fieldName The name of the field to sort by.
31153 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
31155 sort : function(fieldName, dir){
31156 var f = this.fields.get(fieldName);
31161 if(this.sortInfo && this.sortInfo.field == f.name){ // toggle sort dir
31162 dir = (this.sortToggle[f.name] || 'ASC').toggle('ASC', 'DESC');
31167 var st = (this.sortToggle) ? this.sortToggle[f.name] : null;
31168 var si = (this.sortInfo) ? this.sortInfo : null;
31170 this.sortToggle[f.name] = dir;
31171 this.sortInfo = {field: f.name, direction: dir};
31172 if(!this.remoteSort){
31174 this.fireEvent('datachanged', this);
31176 if (!this.load(this.lastOptions)) {
31178 this.sortToggle[f.name] = st;
31181 this.sortInfo = si;
31188 * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
31189 * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
31190 * Returning <tt>false</tt> aborts and exits the iteration.
31191 * @param {Object} scope (optional) The scope in which to call the function (defaults to the {@link Ext.data.Record Record}).
31193 each : function(fn, scope){
31194 this.data.each(fn, scope);
31198 * Gets all {@link Ext.data.Record records} modified since the last commit. Modified records are
31199 * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
31200 * included. See also <tt>{@link #pruneModifiedRecords}</tt> and
31201 * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
31202 * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
31203 * modifications. To obtain modified fields within a modified record see
31204 *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
31206 getModifiedRecords : function(){
31207 return this.modified;
31211 createFilterFn : function(property, value, anyMatch, caseSensitive){
31212 if(Ext.isEmpty(value, false)){
31215 value = this.data.createValueMatcher(value, anyMatch, caseSensitive);
31216 return function(r){
31217 return value.test(r.data[property]);
31222 * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
31223 * and <tt>end</tt> and returns the result.
31224 * @param {String} property A field in each record
31225 * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
31226 * @param {Number} end (optional) The last record index to include (defaults to length - 1)
31227 * @return {Number} The sum
31229 sum : function(property, start, end){
31230 var rs = this.data.items, v = 0;
31231 start = start || 0;
31232 end = (end || end === 0) ? end : rs.length-1;
31234 for(var i = start; i <= end; i++){
31235 v += (rs[i].data[property] || 0);
31241 * Filter the {@link Ext.data.Record records} by a specified property.
31242 * @param {String} field A field on your records
31243 * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
31244 * against the field.
31245 * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
31246 * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
31248 filter : function(property, value, anyMatch, caseSensitive){
31249 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
31250 return fn ? this.filterBy(fn) : this.clearFilter();
31254 * Filter by a function. The specified function will be called for each
31255 * Record in this Store. If the function returns <tt>true</tt> the Record is included,
31256 * otherwise it is filtered out.
31257 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
31258 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
31259 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
31260 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
31262 * @param {Object} scope (optional) The scope of the function (defaults to this)
31264 filterBy : function(fn, scope){
31265 this.snapshot = this.snapshot || this.data;
31266 this.data = this.queryBy(fn, scope||this);
31267 this.fireEvent('datachanged', this);
31271 * Query the records by a specified property.
31272 * @param {String} field A field on your records
31273 * @param {String/RegExp} value Either a string that the field
31274 * should begin with, or a RegExp to test against the field.
31275 * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
31276 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
31277 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
31279 query : function(property, value, anyMatch, caseSensitive){
31280 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
31281 return fn ? this.queryBy(fn) : this.data.clone();
31285 * Query the cached records in this Store using a filtering function. The specified function
31286 * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
31287 * included in the results.
31288 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
31289 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
31290 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
31291 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
31293 * @param {Object} scope (optional) The scope of the function (defaults to this)
31294 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
31296 queryBy : function(fn, scope){
31297 var data = this.snapshot || this.data;
31298 return data.filterBy(fn, scope||this);
31302 * Finds the index of the first matching record in this store by a specific property/value.
31303 * @param {String} property A property on your objects
31304 * @param {String/RegExp} value Either a string that the property value
31305 * should begin with, or a RegExp to test against the property.
31306 * @param {Number} startIndex (optional) The index to start searching at
31307 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
31308 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
31309 * @return {Number} The matched index or -1
31311 find : function(property, value, start, anyMatch, caseSensitive){
31312 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
31313 return fn ? this.data.findIndexBy(fn, null, start) : -1;
31317 * Finds the index of the first matching record in this store by a specific property/value.
31318 * @param {String} property A property on your objects
31319 * @param {String/RegExp} value The value to match against
31320 * @param {Number} startIndex (optional) The index to start searching at
31321 * @return {Number} The matched index or -1
31323 findExact: function(property, value, start){
31324 return this.data.findIndexBy(function(rec){
31325 return rec.get(property) === value;
31330 * Find the index of the first matching Record in this Store by a function.
31331 * If the function returns <tt>true</tt> it is considered a match.
31332 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
31333 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
31334 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
31335 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
31337 * @param {Object} scope (optional) The scope of the function (defaults to this)
31338 * @param {Number} startIndex (optional) The index to start searching at
31339 * @return {Number} The matched index or -1
31341 findBy : function(fn, scope, start){
31342 return this.data.findIndexBy(fn, scope, start);
31346 * Collects unique values for a particular dataIndex from this store.
31347 * @param {String} dataIndex The property to collect
31348 * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
31349 * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
31350 * @return {Array} An array of the unique values
31352 collect : function(dataIndex, allowNull, bypassFilter){
31353 var d = (bypassFilter === true && this.snapshot) ?
31354 this.snapshot.items : this.data.items;
31355 var v, sv, r = [], l = {};
31356 for(var i = 0, len = d.length; i < len; i++){
31357 v = d[i].data[dataIndex];
31359 if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
31368 * Revert to a view of the Record cache with no filtering applied.
31369 * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
31370 * {@link #datachanged} event.
31372 clearFilter : function(suppressEvent){
31373 if(this.isFiltered()){
31374 this.data = this.snapshot;
31375 delete this.snapshot;
31376 if(suppressEvent !== true){
31377 this.fireEvent('datachanged', this);
31383 * Returns true if this store is currently filtered
31384 * @return {Boolean}
31386 isFiltered : function(){
31387 return this.snapshot && this.snapshot != this.data;
31391 afterEdit : function(record){
31392 if(this.modified.indexOf(record) == -1){
31393 this.modified.push(record);
31395 this.fireEvent('update', this, record, Ext.data.Record.EDIT);
31399 afterReject : function(record){
31400 this.modified.remove(record);
31401 this.fireEvent('update', this, record, Ext.data.Record.REJECT);
31405 afterCommit : function(record){
31406 this.modified.remove(record);
31407 this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
31411 * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
31412 * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
31413 * Ext.data.Record.COMMIT.
31415 commitChanges : function(){
31416 var m = this.modified.slice(0);
31417 this.modified = [];
31418 for(var i = 0, len = m.length; i < len; i++){
31424 * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
31426 rejectChanges : function(){
31427 var m = this.modified.slice(0);
31428 this.modified = [];
31429 for(var i = 0, len = m.length; i < len; i++){
31435 onMetaChange : function(meta, rtype, o){
31436 this.recordType = rtype;
31437 this.fields = rtype.prototype.fields;
31438 delete this.snapshot;
31440 this.sortInfo = meta.sortInfo;
31441 }else if(this.sortInfo && !this.fields.get(this.sortInfo.field)){
31442 delete this.sortInfo;
31444 this.modified = [];
31445 this.fireEvent('metachange', this, this.reader.meta);
31449 findInsertIndex : function(record){
31450 this.suspendEvents();
31451 var data = this.data.clone();
31452 this.data.add(record);
31454 var index = this.data.indexOf(record);
31456 this.resumeEvents();
31461 * Set the value for a property name in this store's {@link #baseParams}. Usage:</p><pre><code>
31462 myStore.setBaseParam('foo', {bar:3});
31464 * @param {String} name Name of the property to assign
31465 * @param {Mixed} value Value to assign the <tt>name</tt>d property
31467 setBaseParam : function (name, value){
31468 this.baseParams = this.baseParams || {};
31469 this.baseParams[name] = value;
31473 Ext.reg('store', Ext.data.Store);
31476 * @class Ext.data.Store.Error
31477 * @extends Ext.Error
31478 * Store Error extension.
31479 * @param {String} name
31481 Ext.data.Store.Error = Ext.extend(Ext.Error, {
31482 name: 'Ext.data.Store'
31484 Ext.apply(Ext.data.Store.Error.prototype, {
31486 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
31491 * @class Ext.data.Field
31492 * <p>This class encapsulates the field definition information specified in the field definition objects
31493 * passed to {@link Ext.data.Record#create}.</p>
31494 * <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
31495 * and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
31497 Ext.data.Field = function(config){
31498 if(typeof config == "string"){
31499 config = {name: config};
31501 Ext.apply(this, config);
31504 this.type = "auto";
31507 var st = Ext.data.SortTypes;
31508 // named sortTypes are supported, here we look them up
31509 if(typeof this.sortType == "string"){
31510 this.sortType = st[this.sortType];
31513 // set default sortType for strings and dates
31514 if(!this.sortType){
31517 this.sortType = st.asUCString;
31520 this.sortType = st.asDate;
31523 this.sortType = st.none;
31528 var stripRe = /[\$,%]/g;
31530 // prebuilt conversion function for this field, instead of
31531 // switching every time we're reading a value
31533 var cv, dateFormat = this.dateFormat;
31538 cv = function(v){ return v; };
31541 cv = function(v){ return (v === undefined || v === null) ? '' : String(v); };
31545 return v !== undefined && v !== null && v !== '' ?
31546 parseInt(String(v).replace(stripRe, ""), 10) : '';
31551 return v !== undefined && v !== null && v !== '' ?
31552 parseFloat(String(v).replace(stripRe, ""), 10) : '';
31557 cv = function(v){ return v === true || v === "true" || v == 1; };
31568 if(dateFormat == "timestamp"){
31569 return new Date(v*1000);
31571 if(dateFormat == "time"){
31572 return new Date(parseInt(v, 10));
31574 return Date.parseDate(v, dateFormat);
31576 var parsed = Date.parse(v);
31577 return parsed ? new Date(parsed) : null;
31586 Ext.data.Field.prototype = {
31588 * @cfg {String} name
31589 * The name by which the field is referenced within the Record. This is referenced by, for example,
31590 * the <tt>dataIndex</tt> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
31591 * <p>Note: In the simplest case, if no properties other than <tt>name</tt> are required, a field
31592 * definition may consist of just a String for the field name.</p>
31595 * @cfg {String} type
31596 * (Optional) The data type for conversion to displayable value if <tt>{@link Ext.data.Field#convert convert}</tt>
31597 * has not been specified. Possible values are
31598 * <div class="mdetail-params"><ul>
31599 * <li>auto (Default, implies no conversion)</li>
31604 * <li>date</li></ul></div>
31607 * @cfg {Function} convert
31608 * (Optional) A function which converts the value provided by the Reader into an object that will be stored
31609 * in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
31610 * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
31611 * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
31612 * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
31613 * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
31614 * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
31617 // example of convert function
31618 function fullName(v, record){
31619 return record.name.last + ', ' + record.name.first;
31622 function location(v, record){
31623 return !record.city ? '' : (record.city + ', ' + record.state);
31626 var Dude = Ext.data.Record.create([
31627 {name: 'fullname', convert: fullName},
31628 {name: 'firstname', mapping: 'name.first'},
31629 {name: 'lastname', mapping: 'name.last'},
31630 {name: 'city', defaultValue: 'homeless'},
31632 {name: 'location', convert: location}
31635 // create the data store
31636 var store = new Ext.data.Store({
31637 reader: new Ext.data.JsonReader(
31641 totalProperty: 'total'
31649 name: { first: 'Fat', last: 'Albert' }
31650 // notice no city, state provided in data object
31653 name: { first: 'Barney', last: 'Rubble' },
31654 city: 'Bedrock', state: 'Stoneridge'
31657 name: { first: 'Cliff', last: 'Claven' },
31658 city: 'Boston', state: 'MA'
31664 * @cfg {String} dateFormat
31665 * (Optional) A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
31666 * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
31667 * javascript millisecond timestamp.
31671 * @cfg {Mixed} defaultValue
31672 * (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
31673 * when the item referenced by the <tt>{@link Ext.data.Field#mapping mapping}</tt> does not exist in the data
31674 * object (i.e. undefined). (defaults to "")
31678 * @cfg {String/Number} mapping
31679 * <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
31680 * that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
31681 * If the path expression is the same as the field name, the mapping may be omitted.</p>
31682 * <p>The form of the mapping expression depends on the Reader being used.</p>
31683 * <div class="mdetail-params"><ul>
31684 * <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
31685 * expression to reference the data from an element of the data item's {@link Ext.data.JsonReader#root root} Array. Defaults to the field name.</div></li>
31686 * <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
31687 * item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
31688 * <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
31689 * of the field's value. Defaults to the field specification's Array position.</div></li>
31691 * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
31692 * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
31693 * return the desired data.</p>
31697 * @cfg {Function} sortType
31698 * (Optional) A function which converts a Field's value to a comparable value in order to ensure
31699 * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
31700 * sort example:<pre><code>
31701 // current sort after sort we want
31702 // +-+------+ +-+------+
31703 // |1|First | |1|First |
31704 // |2|Last | |3|Second|
31705 // |3|Second| |2|Last |
31706 // +-+------+ +-+------+
31708 sortType: function(value) {
31709 switch (value.toLowerCase()) // native toLowerCase():
31711 case 'first': return 1;
31712 case 'second': return 2;
31720 * @cfg {String} sortDir
31721 * (Optional) Initial direction to sort (<tt>"ASC"</tt> or <tt>"DESC"</tt>). Defaults to
31726 * @cfg {Boolean} allowBlank
31727 * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <tt>true</tt>.
31728 * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
31729 * to evaluate to <tt>false</tt>.
31733 * @class Ext.data.DataReader
\r
31734 * Abstract base class for reading structured data from a data source and converting
\r
31735 * it into an object containing {@link Ext.data.Record} objects and metadata for use
\r
31736 * by an {@link Ext.data.Store}. This class is intended to be extended and should not
\r
31737 * be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
\r
31738 * {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
\r
31739 * @constructor Create a new DataReader
\r
31740 * @param {Object} meta Metadata configuration options (implementation-specific).
\r
31741 * @param {Array/Object} recordType
\r
31742 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
\r
31743 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
\r
31744 * constructor created using {@link Ext.data.Record#create}.</p>
\r
31746 Ext.data.DataReader = function(meta, recordType){
\r
31748 * This DataReader's configured metadata as passed to the constructor.
\r
31752 this.meta = meta;
\r
31754 * @cfg {Array/Object} fields
\r
31755 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
\r
31756 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
\r
31757 * constructor created from {@link Ext.data.Record#create}.</p>
\r
31759 this.recordType = Ext.isArray(recordType) ?
\r
31760 Ext.data.Record.create(recordType) : recordType;
\r
31763 Ext.data.DataReader.prototype = {
\r
31766 * Abstract method, overridden in {@link Ext.data.JsonReader}
\r
31768 buildExtractors : Ext.emptyFn,
\r
31771 * Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server.
\r
31772 * You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration. The incoming
\r
31773 * data from server will be merged with the data in the local record.
\r
31774 * In addition, you <b>must</b> return record-data from the server in the same order received.
\r
31775 * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed.
\r
31776 * @param {Record/Record[]} record The phantom record to be realized.
\r
31777 * @param {Object/Object[]} data The new record data to apply. Must include the primary-key from database defined in idProperty field.
\r
31779 realize: function(rs, data){
\r
31780 if (Ext.isArray(rs)) {
\r
31781 for (var i = rs.length - 1; i >= 0; i--) {
\r
31783 if (Ext.isArray(data)) {
\r
31784 this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
\r
31787 // weird...rs is an array but data isn't?? recurse but just send in the whole invalid data object.
\r
31788 // the else clause below will detect !this.isData and throw exception.
\r
31789 this.realize(rs.splice(i,1).shift(), data);
\r
31794 // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
\r
31795 if (Ext.isArray(data) && data.length == 1) {
\r
31796 data = data.shift();
\r
31798 if (!this.isData(data)) {
\r
31799 // TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
\r
31801 throw new Ext.data.DataReader.Error('realize', rs);
\r
31803 this.buildExtractors();
\r
31804 var values = this.extractValues(data, rs.fields.items, rs.fields.items.length);
\r
31805 rs.phantom = false; // <-- That's what it's all about
\r
31806 rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
\r
31807 rs.id = data[this.meta.idProperty];
\r
31808 rs.data = values;
\r
31814 * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
\r
31815 * You <b>must</b> return a complete new record from the server. If you don't, your local record's missing fields
\r
31816 * will be populated with the default values specified in your Ext.data.Record.create specification. Without a defaultValue,
\r
31817 * local fields will be populated with empty string "". So return your entire record's data after both remote create and update.
\r
31818 * In addition, you <b>must</b> return record-data from the server in the same order received.
\r
31819 * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed as the record receives
\r
31820 * a fresh new data-hash.
\r
31821 * @param {Record/Record[]} rs
\r
31822 * @param {Object/Object[]} data
\r
31824 update : function(rs, data) {
\r
31825 if (Ext.isArray(rs)) {
\r
31826 for (var i=rs.length-1; i >= 0; i--) {
\r
31827 if (Ext.isArray(data)) {
\r
31828 this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
\r
31831 // weird...rs is an array but data isn't?? recurse but just send in the whole data object.
\r
31832 // the else clause below will detect !this.isData and throw exception.
\r
31833 this.update(rs.splice(i,1).shift(), data);
\r
31838 // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
\r
31839 if (Ext.isArray(data) && data.length == 1) {
\r
31840 data = data.shift();
\r
31842 if (!this.isData(data)) {
\r
31843 // TODO: create custom Exception class to return record in thrown exception. Allow exception-handler the choice
\r
31844 // to commit or not rather than blindly rs.commit() here.
\r
31846 throw new Ext.data.DataReader.Error('update', rs);
\r
31848 this.buildExtractors();
\r
31849 rs.data = this.extractValues(Ext.apply(rs.data, data), rs.fields.items, rs.fields.items.length);
\r
31855 * Returns true if the supplied data-hash <b>looks</b> and quacks like data. Checks to see if it has a key
\r
31856 * corresponding to idProperty defined in your DataReader config containing non-empty pk.
\r
31857 * @param {Object} data
\r
31858 * @return {Boolean}
\r
31860 isData : function(data) {
\r
31861 return (data && Ext.isObject(data) && !Ext.isEmpty(data[this.meta.idProperty])) ? true : false;
\r
31866 * @class Ext.data.DataReader.Error
\r
31867 * @extends Ext.Error
\r
31868 * General error class for Ext.data.DataReader
\r
31870 Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
\r
31871 constructor : function(message, arg) {
\r
31873 Ext.Error.call(this, message);
\r
31875 name: 'Ext.data.DataReader'
\r
31877 Ext.apply(Ext.data.DataReader.Error.prototype, {
\r
31879 'update': "#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",
\r
31880 'realize': "#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.",
\r
31881 'invalid-response': "#readResponse received an invalid response from the server."
\r
31887 * @class Ext.data.DataWriter
31888 * <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
31889 * an Ext.data.Store and a server-side framework. A Writer enabled Store will
31890 * automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
31891 * <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
31892 * and should not be created directly. For existing implementations, see
31893 * {@link Ext.data.JsonWriter}.</p>
31894 * <p>Creating a writer is simple:</p>
31896 var writer = new Ext.data.JsonWriter();
31898 * <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
31900 // Create a standard HttpProxy instance.
31901 var proxy = new Ext.data.HttpProxy({
31902 url: 'app.php/users'
31905 * <p>For finer grained control, the proxy may also be configured with an <code>api</code>:</p>
31907 // Use the api specification
31908 var proxy = new Ext.data.HttpProxy({
31910 read : 'app.php/users/read',
31911 create : 'app.php/users/create',
31912 update : 'app.php/users/update',
31913 destroy : 'app.php/users/destroy'
31917 * <p>Creating a Writer enabled store:</p>
31919 var store = new Ext.data.Store({
31925 * @constructor Create a new DataWriter
31926 * @param {Object} meta Metadata configuration options (implementation-specific)
31927 * @param {Object} recordType Either an Array of field definition objects as specified
31928 * in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
31929 * using {@link Ext.data.Record#create}.
31931 Ext.data.DataWriter = function(config){
31933 * This DataWriter's configured metadata as passed to the constructor.
31937 Ext.apply(this, config);
31940 Ext.data.DataWriter.prototype = {
31943 * @cfg {Boolean} writeAllFields
31944 * <tt>false</tt> by default. Set <tt>true</tt> to have DataWriter return ALL fields of a modified
31945 * record -- not just those that changed.
31946 * <tt>false</tt> to have DataWriter only request modified fields from a record.
31948 writeAllFields : false,
31950 * @cfg {Boolean} listful
31951 * <tt>false</tt> by default. Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
31952 * even when acting upon a single record.
31954 listful : false, // <-- listful is actually not used internally here in DataWriter. @see Ext.data.Store#execute.
31957 * Writes data in preparation for server-write action. Simply proxies to DataWriter#update, DataWriter#create
31958 * DataWriter#destroy.
31959 * @param {String} action [CREATE|UPDATE|DESTROY]
31960 * @param {Object} params The params-hash to write-to
31961 * @param {Record/Record[]} rs The recordset write.
31963 write : function(action, params, rs) {
31964 this.render(action, rs, params, this[action](rs));
31968 * abstract method meant to be overridden by all DataWriter extensions. It's the extension's job to apply the "data" to the "params".
31969 * The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
31970 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
31971 * @param {Record[]} rs Store recordset
31972 * @param {Object} params Http params to be sent to server.
31973 * @param {Object} data object populated according to DataReader meta-data.
31975 render : Ext.emptyFn,
31979 * @param {Object} p Params-hash to apply result to.
31980 * @param {Record/Record[]} rs Record(s) to write
31983 update : function(rs) {
31985 if (Ext.isArray(rs)) {
31988 Ext.each(rs, function(val){
31990 data.push(this.updateRecord(val));
31992 params[this.meta.idProperty] = ids;
31993 params[this.meta.root] = data;
31995 else if (rs instanceof Ext.data.Record) {
31996 params[this.meta.idProperty] = rs.id;
31997 params[this.meta.root] = this.updateRecord(rs);
32003 * @cfg {Function} saveRecord Abstract method that should be implemented in all subclasses
32004 * (e.g.: {@link Ext.data.JsonWriter#saveRecord JsonWriter.saveRecord}
32006 updateRecord : Ext.emptyFn,
32010 * @param {Object} p Params-hash to apply result to.
32011 * @param {Record/Record[]} rs Record(s) to write
32014 create : function(rs) {
32016 if (Ext.isArray(rs)) {
32018 Ext.each(rs, function(val){
32019 data.push(this.createRecord(val));
32021 params[this.meta.root] = data;
32023 else if (rs instanceof Ext.data.Record) {
32024 params[this.meta.root] = this.createRecord(rs);
32030 * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
32031 * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
32033 createRecord : Ext.emptyFn,
32037 * @param {Object} p Params-hash to apply result to.
32038 * @param {Record/Record[]} rs Record(s) to write
32041 destroy : function(rs) {
32043 if (Ext.isArray(rs)) {
32046 Ext.each(rs, function(val){
32047 data.push(this.destroyRecord(val));
32049 params[this.meta.root] = data;
32050 } else if (rs instanceof Ext.data.Record) {
32051 params[this.meta.root] = this.destroyRecord(rs);
32057 * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
32058 * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
32060 destroyRecord : Ext.emptyFn,
32063 * Converts a Record to a hash
32067 toHash : function(rec) {
32068 var map = rec.fields.map,
32070 raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
32072 Ext.iterate(raw, function(prop, value){
32073 if((m = map[prop])){
32074 data[m.mapping ? m.mapping : m.name] = value;
32077 data[this.meta.idProperty] = rec.id;
32081 * @class Ext.data.DataProxy
\r
32082 * @extends Ext.util.Observable
\r
32083 * <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
\r
32084 * This class is intended to be extended and should not be created directly. For existing implementations,
\r
32085 * see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
\r
32086 * {@link Ext.data.MemoryProxy}.</p>
\r
32087 * <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
\r
32088 * (of the appropriate type which knows how to parse the data object) to provide a block of
\r
32089 * {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
\r
32090 * <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
\r
32091 * config object to an {@link Ext.data.Connection}.</p>
\r
32092 * <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
\r
32093 * <code>load</code> method (deprecated). See
\r
32094 * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
\r
32095 * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
\r
32096 * <p><b><u>Example 1</u></b></p>
\r
32098 proxy: new Ext.data.ScriptTagProxy({
\r
32099 {@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
\r
32102 * <p><b><u>Example 2</u></b></p>
\r
32104 proxy : new Ext.data.HttpProxy({
\r
32105 {@link Ext.data.Connection#method method}: 'GET',
\r
32106 {@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
\r
32107 {@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
\r
32109 // all actions except the following will use above url
\r
32110 create : 'local/new.php',
\r
32111 update : 'local/update.php'
\r
32116 Ext.data.DataProxy = function(conn){
\r
32117 // make sure we have a config object here to support ux proxies.
\r
32118 // All proxies should now send config into superclass constructor.
\r
32119 conn = conn || {};
\r
32121 // This line caused a bug when people use custom Connection object having its own request method.
\r
32122 // http://extjs.com/forum/showthread.php?t=67194. Have to set DataProxy config
\r
32123 //Ext.applyIf(this, conn);
\r
32125 this.api = conn.api;
\r
32126 this.url = conn.url;
\r
32127 this.restful = conn.restful;
\r
32128 this.listeners = conn.listeners;
\r
32131 this.prettyUrls = conn.prettyUrls;
\r
32134 * @cfg {Object} api
\r
32135 * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
\r
32136 * Defaults to:<pre><code>
\r
32138 read : undefined,
\r
32139 create : undefined,
\r
32140 update : undefined,
\r
32141 destroy : undefined
\r
32144 * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
\r
32145 * will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
\r
32146 * <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
\r
32147 * property should be modified before the action is requested using the corresponding before
\r
32148 * action event. For example to modify the URL associated with the load action:
\r
32150 // modify the url for the action
\r
32153 fn: function (store, options) {
\r
32154 // use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
\r
32155 store.proxy.setUrl('changed1.php');
\r
32157 // set optional second parameter to true to make this URL change
\r
32158 // permanent, applying this URL for all subsequent requests.
\r
32159 store.proxy.setUrl('changed1.php', true);
\r
32161 // manually set the <b>private</b> connection URL.
\r
32162 // <b>Warning:</b> Accessing the private URL property should be avoided.
\r
32163 // Use the public method <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> instead, shown above.
\r
32164 // It should be noted that changing the URL like this will affect
\r
32165 // the URL for just this request. Subsequent requests will use the
\r
32166 // API or URL defined in your initial proxy configuration.
\r
32167 store.proxy.conn.url = 'changed1.php';
\r
32169 // proxy URL will be superseded by API (only if proxy created to use ajax):
\r
32170 // It should be noted that proxy API changes are permanent and will
\r
32171 // be used for all subsequent requests.
\r
32172 store.proxy.api.load = 'changed2.php';
\r
32174 // However, altering the proxy API should be done using the public
\r
32175 // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt> instead.
\r
32176 store.proxy.setApi('load', 'changed2.php');
\r
32178 // Or set the entire API with a config-object.
\r
32179 // When using the config-object option, you must redefine the <b>entire</b>
\r
32180 // API -- not just a specific action of it.
\r
32181 store.proxy.setApi({
\r
32182 read : 'changed_read.php',
\r
32183 create : 'changed_create.php',
\r
32184 update : 'changed_update.php',
\r
32185 destroy : 'changed_destroy.php'
\r
32193 // Prepare the proxy api. Ensures all API-actions are defined with the Object-form.
\r
32195 Ext.data.Api.prepare(this);
\r
32197 if (e instanceof Ext.data.Api.Error) {
\r
32204 * @event exception
\r
32205 * <p>Fires if an exception occurs in the Proxy during a remote request.
\r
32206 * This event is relayed through a corresponding
\r
32207 * {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
\r
32208 * so any Store instance may observe this event.
\r
32209 * This event can be fired for one of two reasons:</p>
\r
32210 * <div class="mdetail-params"><ul>
\r
32211 * <li>remote-request <b>failed</b> : <div class="sub-desc">
\r
32212 * The server did not return status === 200.
\r
32214 * <li>remote-request <b>succeeded</b> : <div class="sub-desc">
\r
32215 * The remote-request succeeded but the reader could not read the response.
\r
32216 * This means the server returned data, but the configured Reader threw an
\r
32217 * error while reading the response. In this case, this event will be
\r
32218 * raised and the caught error will be passed along into this event.
\r
32221 * <br><p>This event fires with two different contexts based upon the 2nd
\r
32222 * parameter <tt>type [remote|response]</tt>. The first four parameters
\r
32223 * are identical between the two contexts -- only the final two parameters
\r
32225 * @param {DataProxy} this The proxy that sent the request
\r
32226 * @param {String} type
\r
32227 * <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
\r
32228 * <div class="mdetail-params"><ul>
\r
32229 * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
\r
32230 * <p>An <b>invalid</b> response from the server was returned: either 404,
\r
32231 * 500 or the response meta-data does not match that defined in the DataReader
\r
32232 * (e.g.: root, idProperty, successProperty).</p>
\r
32234 * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
\r
32235 * <p>A <b>valid</b> response was returned from the server having
\r
32236 * successProperty === false. This response might contain an error-message
\r
32237 * sent from the server. For example, the user may have failed
\r
32238 * authentication/authorization or a database validation error occurred.</p>
\r
32241 * @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
\r
32242 * @param {Object} options The options for the action that were specified in the {@link #request}.
\r
32243 * @param {Object} response
\r
32244 * <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
\r
32245 * <div class="mdetail-params"><ul>
\r
32246 * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
\r
32247 * <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
\r
32249 * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
\r
32250 * <p>The decoded response object sent from the server.</p>
\r
32253 * @param {Mixed} arg
\r
32254 * <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
\r
32255 * <div class="mdetail-params"><ul>
\r
32256 * <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
\r
32257 * <p>The JavaScript Error object caught if the configured Reader could not read the data.
\r
32258 * If the remote request returns success===false, this parameter will be null.</p>
\r
32260 * <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
\r
32261 * <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
\r
32262 * (Ext.data.Api.actions.create|update|destroy).</p>
\r
32268 * @event beforeload
\r
32269 * Fires before a request to retrieve a data object.
\r
32270 * @param {DataProxy} this The proxy for the request
\r
32271 * @param {Object} params The params object passed to the {@link #request} function
\r
32276 * Fires before the load method's callback is called.
\r
32277 * @param {DataProxy} this The proxy for the request
\r
32278 * @param {Object} o The request transaction object
\r
32279 * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
\r
32283 * @event loadexception
\r
32284 * <p>This event is <b>deprecated</b>. The signature of the loadexception event
\r
32285 * varies depending on the proxy, use the catch-all {@link #exception} event instead.
\r
32286 * This event will fire in addition to the {@link #exception} event.</p>
\r
32287 * @param {misc} misc See {@link #exception}.
\r
32292 * @event beforewrite
\r
32293 * Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy
\r
32294 * @param {DataProxy} this The proxy for the request
\r
32295 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
\r
32296 * @param {Record/Array[Record]} rs The Record(s) to create|update|destroy.
\r
32297 * @param {Object} params The request <code>params</code> object. Edit <code>params</code> to add parameters to the request.
\r
32302 * Fires before the request-callback is called
\r
32303 * @param {DataProxy} this The proxy that sent the request
\r
32304 * @param {String} action [Ext.data.Api.actions.create|upate|destroy]
\r
32305 * @param {Object} data The data object extracted from the server-response
\r
32306 * @param {Object} response The decoded response from server
\r
32307 * @param {Record/Record{}} rs The records from Store
\r
32308 * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
\r
32312 Ext.data.DataProxy.superclass.constructor.call(this);
\r
32315 Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
\r
32317 * @cfg {Boolean} restful
\r
32318 * <p>Defaults to <tt>false</tt>. Set to <tt>true</tt> to operate in a RESTful manner.</p>
\r
32319 * <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
\r
32320 * {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
\r
32321 * Store is RESTful, there is no need to set this option on the proxy.</p>
\r
32322 * <br><p>RESTful implementations enable the serverside framework to automatically route
\r
32323 * actions sent to one url based upon the HTTP method, for example:
\r
32325 store: new Ext.data.Store({
\r
32327 proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
\r
32331 * There is no <code>{@link #api}</code> specified in the configuration of the proxy,
\r
32332 * all requests will be marshalled to a single RESTful url (/users) so the serverside
\r
32333 * framework can inspect the HTTP Method and act accordingly:
\r
32335 <u>Method</u> <u>url</u> <u>action</u>
\r
32336 POST /users create
\r
32338 PUT /users/23 update
\r
32339 DESTROY /users/23 delete
\r
32345 * <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
\r
32346 * <p>If called with an object as the only parameter, the object should redefine the <b>entire</b> API, e.g.:</p><pre><code>
\r
32348 read : '/users/read',
\r
32349 create : '/users/create',
\r
32350 update : '/users/update',
\r
32351 destroy : '/users/destroy'
\r
32354 * <p>If called with two parameters, the first parameter should be a string specifying the API action to
\r
32355 * redefine and the second parameter should be the URL (or function if using DirectProxy) to call for that action, e.g.:</p><pre><code>
\r
32356 proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
\r
32358 * @param {String/Object} api An API specification object, or the name of an action.
\r
32359 * @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
\r
32361 setApi : function() {
\r
32362 if (arguments.length == 1) {
\r
32363 var valid = Ext.data.Api.isValid(arguments[0]);
\r
32364 if (valid === true) {
\r
32365 this.api = arguments[0];
\r
32368 throw new Ext.data.Api.Error('invalid', valid);
\r
32371 else if (arguments.length == 2) {
\r
32372 if (!Ext.data.Api.isAction(arguments[0])) {
\r
32373 throw new Ext.data.Api.Error('invalid', arguments[0]);
\r
32375 this.api[arguments[0]] = arguments[1];
\r
32377 Ext.data.Api.prepare(this);
\r
32381 * Returns true if the specified action is defined as a unique action in the api-config.
\r
32382 * request. If all API-actions are routed to unique urls, the xaction parameter is unecessary. However, if no api is defined
\r
32383 * and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
\r
32384 * the corresponding code for CRUD action.
\r
32385 * @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
\r
32386 * @return {Boolean}
\r
32388 isApiAction : function(action) {
\r
32389 return (this.api[action]) ? true : false;
\r
32393 * All proxy actions are executed through this method. Automatically fires the "before" + action event
\r
32394 * @param {String} action Name of the action
\r
32395 * @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
\r
32396 * @param {Object} params
\r
32397 * @param {Ext.data.DataReader} reader
\r
32398 * @param {Function} callback
\r
32399 * @param {Object} scope Scope with which to call the callback (defaults to the Proxy object)
\r
32400 * @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
\r
32402 request : function(action, rs, params, reader, callback, scope, options) {
\r
32403 if (!this.api[action] && !this.load) {
\r
32404 throw new Ext.data.DataProxy.Error('action-undefined', action);
\r
32406 params = params || {};
\r
32407 if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
\r
32408 this.doRequest.apply(this, arguments);
\r
32411 callback.call(scope || this, null, options, false);
\r
32417 * <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
\r
32419 * @param {Object} params
\r
32420 * @param {Object} reader
\r
32421 * @param {Object} callback
\r
32422 * @param {Object} scope
\r
32423 * @param {Object} arg
\r
32428 * @cfg {Function} doRequest Abstract method that should be implemented in all subclasses
\r
32429 * (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
\r
32430 * {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
\r
32432 doRequest : function(action, rs, params, reader, callback, scope, options) {
\r
32433 // default implementation of doRequest for backwards compatibility with 2.0 proxies.
\r
32434 // If we're executing here, the action is probably "load".
\r
32435 // Call with the pre-3.0 method signature.
\r
32436 this.load(params, reader, callback, scope, options);
\r
32441 * Sets the appropriate url based upon the action being executed. If restful is true, and only a single record is being acted upon,
\r
32442 * url will be built Rails-style, as in "/controller/action/32". restful will aply iff the supplied record is an
\r
32443 * instance of Ext.data.Record rather than an Array of them.
\r
32444 * @param {String} action The api action being executed [read|create|update|destroy]
\r
32445 * @param {Ext.data.Record/Array[Ext.data.Record]} The record or Array of Records being acted upon.
\r
32446 * @return {String} url
\r
32449 buildUrl : function(action, record) {
\r
32450 record = record || null;
\r
32451 var url = (this.api[action]) ? this.api[action].url : this.url;
\r
32453 throw new Ext.data.Api.Error('invalid-url', action);
\r
32456 var format = null;
\r
32457 var m = url.match(/(.*)(\.\w+)$/); // <-- look for urls with "provides" suffix, e.g.: /users.json, /users.xml, etc
\r
32462 // prettyUrls is deprectated in favor of restful-config
\r
32463 if ((this.prettyUrls === true || this.restful === true) && record instanceof Ext.data.Record && !record.phantom) {
\r
32464 url += '/' + record.id;
\r
32466 if (format) { // <-- append the request format if exists (ie: /users/update/69[.json])
\r
32473 * Destroys the proxy by purging any event listeners and cancelling any active requests.
\r
32475 destroy: function(){
\r
32476 this.purgeListeners();
\r
32481 * @class Ext.data.DataProxy.Error
\r
32482 * @extends Ext.Error
\r
32483 * DataProxy Error extension.
\r
32485 * @param {String} name
\r
32486 * @param {Record/Array[Record]/Array}
\r
32488 Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
\r
32489 constructor : function(message, arg) {
\r
32491 Ext.Error.call(this, message);
\r
32493 name: 'Ext.data.DataProxy'
\r
32495 Ext.apply(Ext.data.DataProxy.Error.prototype, {
\r
32497 'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.",
\r
32498 'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
\r
32502 * @class Ext.data.ScriptTagProxy
\r
32503 * @extends Ext.data.DataProxy
\r
32504 * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
\r
32505 * other than the originating domain of the running page.<br>
\r
32507 * <b>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
\r
32508 * of the running page, you must use this class, rather than HttpProxy.</b><br>
\r
32510 * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
\r
32511 * source code because it is used as the source inside a <script> tag.<br>
\r
32513 * In order for the browser to process the returned data, the server must wrap the data object
\r
32514 * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
\r
32515 * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
\r
32516 * depending on whether the callback name was passed:
\r
32519 boolean scriptTag = false;
\r
32520 String cb = request.getParameter("callback");
\r
32521 if (cb != null) {
\r
32522 scriptTag = true;
\r
32523 response.setContentType("text/javascript");
\r
32525 response.setContentType("application/x-json");
\r
32527 Writer out = response.getWriter();
\r
32529 out.write(cb + "(");
\r
32531 out.print(dataBlock.toJsonString());
\r
32538 * @param {Object} config A configuration object.
\r
32540 Ext.data.ScriptTagProxy = function(config){
\r
32541 Ext.apply(this, config);
\r
32543 Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
\r
32545 this.head = document.getElementsByTagName("head")[0];
\r
32548 * @event loadexception
\r
32549 * <b>Deprecated</b> in favor of 'exception' event.
\r
32550 * Fires if an exception occurs in the Proxy during data loading. This event can be fired for one of two reasons:
\r
32551 * <ul><li><b>The load call timed out.</b> This means the load callback did not execute within the time limit
\r
32552 * specified by {@link #timeout}. In this case, this event will be raised and the
\r
32553 * fourth parameter (read error) will be null.</li>
\r
32554 * <li><b>The load succeeded but the reader could not read the response.</b> This means the server returned
\r
32555 * data, but the configured Reader threw an error while reading the data. In this case, this event will be
\r
32556 * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
\r
32557 * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
\r
32558 * on any Store instance.
\r
32559 * @param {Object} this
\r
32560 * @param {Object} options The loading options that were specified (see {@link #load} for details). If the load
\r
32561 * call timed out, this parameter will be null.
\r
32562 * @param {Object} arg The callback's arg object passed to the {@link #load} function
\r
32563 * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
\r
32564 * If the remote request returns success: false, this parameter will be null.
\r
32568 Ext.data.ScriptTagProxy.TRANS_ID = 1000;
\r
32570 Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
\r
32572 * @cfg {String} url The URL from which to request the data object.
\r
32575 * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
\r
32579 * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
\r
32580 * the server the name of the callback function set up by the load call to process the returned data object.
\r
32581 * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
\r
32582 * javascript output which calls this named function passing the data object as its only parameter.
\r
32584 callbackParam : "callback",
\r
32586 * @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
\r
32587 * name to the request.
\r
32592 * HttpProxy implementation of DataProxy#doRequest
\r
32593 * @param {String} action
\r
32594 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
\r
32595 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
32596 * for the request to the remote server.
\r
32597 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
32598 * object into a block of Ext.data.Records.
\r
32599 * @param {Function} callback The function into which to pass the block of Ext.data.Records.
\r
32600 * The function must be passed <ul>
\r
32601 * <li>The Record block object</li>
\r
32602 * <li>The "arg" argument from the load function</li>
\r
32603 * <li>A boolean success indicator</li>
\r
32605 * @param {Object} scope The scope in which to call the callback
\r
32606 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
32608 doRequest : function(action, rs, params, reader, callback, scope, arg) {
\r
32609 var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
\r
32611 var url = this.buildUrl(action, rs);
\r
32613 throw new Ext.data.Api.Error('invalid-url', url);
\r
32615 url = Ext.urlAppend(url, p);
\r
32617 if(this.nocache){
\r
32618 url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
\r
32620 var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
\r
32624 cb : "stcCallback"+transId,
\r
32625 scriptId : "stcScript"+transId,
\r
32629 callback : callback,
\r
32633 window[trans.cb] = this.createCallback(action, rs, trans);
\r
32634 url += String.format("&{0}={1}", this.callbackParam, trans.cb);
\r
32635 if(this.autoAbort !== false){
\r
32639 trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
\r
32641 var script = document.createElement("script");
\r
32642 script.setAttribute("src", url);
\r
32643 script.setAttribute("type", "text/javascript");
\r
32644 script.setAttribute("id", trans.scriptId);
\r
32645 this.head.appendChild(script);
\r
32647 this.trans = trans;
\r
32650 // @private createCallback
\r
32651 createCallback : function(action, rs, trans) {
\r
32653 return function(res) {
\r
32654 self.trans = false;
\r
32655 self.destroyTrans(trans, true);
\r
32656 if (action === Ext.data.Api.actions.read) {
\r
32657 self.onRead.call(self, action, trans, res);
\r
32659 self.onWrite.call(self, action, trans, res, rs);
\r
32664 * Callback for read actions
\r
32665 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
32666 * @param {Object} trans The request transaction object
\r
32667 * @param {Object} res The server response
\r
32670 onRead : function(action, trans, res) {
\r
32673 result = trans.reader.readRecords(res);
\r
32675 // @deprecated: fire loadexception
\r
32676 this.fireEvent("loadexception", this, trans, res, e);
\r
32678 this.fireEvent('exception', this, 'response', action, trans, res, e);
\r
32679 trans.callback.call(trans.scope||window, null, trans.arg, false);
\r
32682 if (result.success === false) {
\r
32683 // @deprecated: fire old loadexception for backwards-compat.
\r
32684 this.fireEvent('loadexception', this, trans, res);
\r
32686 this.fireEvent('exception', this, 'remote', action, trans, res, null);
\r
32688 this.fireEvent("load", this, res, trans.arg);
\r
32690 trans.callback.call(trans.scope||window, result, trans.arg, result.success);
\r
32693 * Callback for write actions
\r
32694 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
32695 * @param {Object} trans The request transaction object
\r
32696 * @param {Object} res The server response
\r
32699 onWrite : function(action, trans, res, rs) {
\r
32700 var reader = trans.reader;
\r
32702 // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
\r
32703 reader.readResponse(action, res);
\r
32705 this.fireEvent('exception', this, 'response', action, trans, res, e);
\r
32706 trans.callback.call(trans.scope||window, null, res, false);
\r
32709 if(!res[reader.meta.successProperty] === true){
\r
32710 this.fireEvent('exception', this, 'remote', action, trans, res, rs);
\r
32711 trans.callback.call(trans.scope||window, null, res, false);
\r
32714 this.fireEvent("write", this, action, res[reader.meta.root], res, rs, trans.arg );
\r
32715 trans.callback.call(trans.scope||window, res[reader.meta.root], res, true);
\r
32719 isLoading : function(){
\r
32720 return this.trans ? true : false;
\r
32724 * Abort the current server request.
\r
32726 abort : function(){
\r
32727 if(this.isLoading()){
\r
32728 this.destroyTrans(this.trans);
\r
32733 destroyTrans : function(trans, isLoaded){
\r
32734 this.head.removeChild(document.getElementById(trans.scriptId));
\r
32735 clearTimeout(trans.timeoutId);
\r
32737 window[trans.cb] = undefined;
\r
32739 delete window[trans.cb];
\r
32742 // if hasn't been loaded, wait for load to remove it to prevent script error
\r
32743 window[trans.cb] = function(){
\r
32744 window[trans.cb] = undefined;
\r
32746 delete window[trans.cb];
\r
32753 handleFailure : function(trans){
\r
32754 this.trans = false;
\r
32755 this.destroyTrans(trans, false);
\r
32756 if (trans.action === Ext.data.Api.actions.read) {
\r
32757 // @deprecated firing loadexception
\r
32758 this.fireEvent("loadexception", this, null, trans.arg);
\r
32761 this.fireEvent('exception', this, 'response', trans.action, {
\r
32763 options: trans.arg
\r
32765 trans.callback.call(trans.scope||window, null, trans.arg, false);
\r
32769 destroy: function(){
\r
32771 Ext.data.ScriptTagProxy.superclass.destroy.call(this);
\r
32774 * @class Ext.data.HttpProxy
\r
32775 * @extends Ext.data.DataProxy
\r
32776 * <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
\r
32777 * domain of the originating page.</p>
\r
32778 * <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
\r
32779 * than the domain from which the running page was served. For cross-domain requests, use a
\r
32780 * {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
\r
32781 * <p>Be aware that to enable the browser to parse an XML document, the server must set
\r
32782 * the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
\r
32784 * @param {Object} conn
\r
32785 * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
\r
32786 * <p>Note that if this HttpProxy is being used by a (@link Ext.data.Store Store}, then the
\r
32787 * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
\r
32788 * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
\r
32789 * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
\r
32790 * used to pass parameters known at instantiation time.</p>
\r
32791 * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
\r
32792 * the request.</p>
\r
32794 Ext.data.HttpProxy = function(conn){
\r
32795 Ext.data.HttpProxy.superclass.constructor.call(this, conn);
\r
32798 * The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
\r
32799 * uses to make requests to the server. Properties of this object may be changed dynamically to
\r
32800 * change the way data is requested.
\r
32803 this.conn = conn;
\r
32805 // nullify the connection url. The url param has been copied to 'this' above. The connection
\r
32806 // url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the
\r
32807 // connection url during beforeaction events (ie: beforeload, beforesave, etc). The connection url will be nullified
\r
32808 // after each request as well. Url is always re-defined during doRequest.
\r
32809 this.conn.url = null;
\r
32811 this.useAjax = !conn || !conn.events;
\r
32813 //private. A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
\r
32814 var actions = Ext.data.Api.actions;
\r
32815 this.activeRequest = {};
\r
32816 for (var verb in actions) {
\r
32817 this.activeRequest[actions[verb]] = undefined;
\r
32821 Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
\r
32823 * @cfg {Boolean} restful
\r
32824 * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
\r
32825 * {@link Ext.data.Record#id id} will be appended to the url (defaults to <tt>false</tt>).</p><br>
\r
32826 * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
\r
32827 * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
\r
32828 * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
\r
32829 * <p>Some MVC (e.g., Ruby on Rails, Merb and Django) support this style of segment based urls
\r
32830 * where the segments in the URL follow the Model-View-Controller approach.</p><pre><code>
\r
32831 * someSite.com/controller/action/id
\r
32833 * Where the segments in the url are typically:<div class="mdetail-params"><ul>
\r
32834 * <li>The first segment : represents the controller class that should be invoked.</li>
\r
32835 * <li>The second segment : represents the class function, or method, that should be called.</li>
\r
32836 * <li>The third segment : represents the ID (a variable typically passed to the method).</li>
\r
32837 * </ul></div></p>
\r
32838 * <p>For example:</p>
\r
32841 load : '/controller/load',
\r
32842 create : '/controller/new', // Server MUST return idProperty of new record
\r
32843 save : '/controller/update',
\r
32844 destroy : '/controller/destroy_action'
\r
32847 // Alternatively, one can use the object-form to specify each API-action
\r
32849 load: {url: 'read.php', method: 'GET'},
\r
32850 create: 'create.php',
\r
32851 destroy: 'destroy.php',
\r
32852 save: 'update.php'
\r
32857 * Return the {@link Ext.data.Connection} object being used by this Proxy.
\r
32858 * @return {Connection} The Connection object. This object may be used to subscribe to events on
\r
32859 * a finer-grained basis than the DataProxy events.
\r
32861 getConnection : function() {
\r
32862 return this.useAjax ? Ext.Ajax : this.conn;
\r
32866 * Used for overriding the url used for a single request. Designed to be called during a beforeaction event. Calling setUrl
\r
32867 * will override any urls set via the api configuration parameter. Set the optional parameter makePermanent to set the url for
\r
32868 * all subsequent requests. If not set to makePermanent, the next request will use the same url or api configuration defined
\r
32869 * in the initial proxy configuration.
\r
32870 * @param {String} url
\r
32871 * @param {Boolean} makePermanent (Optional) [false]
\r
32873 * (e.g.: beforeload, beforesave, etc).
\r
32875 setUrl : function(url, makePermanent) {
\r
32876 this.conn.url = url;
\r
32877 if (makePermanent === true) {
\r
32879 Ext.data.Api.prepare(this);
\r
32884 * HttpProxy implementation of DataProxy#doRequest
\r
32885 * @param {String} action The crud action type (create, read, update, destroy)
\r
32886 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
\r
32887 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
32888 * for the request to the remote server.
\r
32889 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
32890 * object into a block of Ext.data.Records.
\r
32891 * @param {Function} callback
\r
32892 * <div class="sub-desc"><p>A function to be called after the request.
\r
32893 * The <tt>callback</tt> is passed the following arguments:<ul>
\r
32894 * <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
\r
32895 * <li><tt>options</tt>: Options object from the action request</li>
\r
32896 * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
\r
32897 * @param {Object} scope The scope in which to call the callback
\r
32898 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
32900 doRequest : function(action, rs, params, reader, cb, scope, arg) {
\r
32902 method: (this.api[action]) ? this.api[action]['method'] : undefined,
\r
32909 callback : this.createCallback(action, rs),
\r
32912 // Sample the request data: If it's an object, then it hasn't been json-encoded yet.
\r
32913 // Transmit data using jsonData config of Ext.Ajax.request
\r
32914 if (typeof(params[reader.meta.root]) === 'object') {
\r
32915 o.jsonData = params;
\r
32917 o.params = params || {};
\r
32919 // Set the connection url. If this.conn.url is not null here,
\r
32920 // the user may have overridden the url during a beforeaction event-handler.
\r
32921 // this.conn.url is nullified after each request.
\r
32922 if (this.conn.url === null) {
\r
32923 this.conn.url = this.buildUrl(action, rs);
\r
32925 else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) {
\r
32926 this.conn.url += '/' + rs.id;
\r
32928 if(this.useAjax){
\r
32930 Ext.applyIf(o, this.conn);
\r
32932 // If a currently running request is found for this action, abort it.
\r
32933 if (this.activeRequest[action]) {
\r
32934 // Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array
\r
32935 //Ext.Ajax.abort(this.activeRequest[action]);
\r
32937 this.activeRequest[action] = Ext.Ajax.request(o);
\r
32939 this.conn.request(o);
\r
32941 // request is sent, nullify the connection url in preparation for the next request
\r
32942 this.conn.url = null;
\r
32946 * Returns a callback function for a request. Note a special case is made for the
\r
32947 * read action vs all the others.
\r
32948 * @param {String} action [create|update|delete|load]
\r
32949 * @param {Ext.data.Record[]} rs The Store-recordset being acted upon
\r
32952 createCallback : function(action, rs) {
\r
32953 return function(o, success, response) {
\r
32954 this.activeRequest[action] = undefined;
\r
32956 if (action === Ext.data.Api.actions.read) {
\r
32957 // @deprecated: fire loadexception for backwards compat.
\r
32958 this.fireEvent('loadexception', this, o, response);
\r
32960 this.fireEvent('exception', this, 'response', action, o, response);
\r
32961 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
32964 if (action === Ext.data.Api.actions.read) {
\r
32965 this.onRead(action, o, response);
\r
32967 this.onWrite(action, o, response, rs);
\r
32973 * Callback for read action
\r
32974 * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
\r
32975 * @param {Object} o The request transaction object
\r
32976 * @param {Object} res The server response
\r
32979 onRead : function(action, o, response) {
\r
32982 result = o.reader.read(response);
\r
32984 // @deprecated: fire old loadexception for backwards-compat.
\r
32985 this.fireEvent('loadexception', this, o, response, e);
\r
32986 this.fireEvent('exception', this, 'response', action, o, response, e);
\r
32987 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
32990 if (result.success === false) {
\r
32991 // @deprecated: fire old loadexception for backwards-compat.
\r
32992 this.fireEvent('loadexception', this, o, response);
\r
32994 // Get DataReader read-back a response-object to pass along to exception event
\r
32995 var res = o.reader.readResponse(action, response);
\r
32996 this.fireEvent('exception', this, 'remote', action, o, res, null);
\r
32999 this.fireEvent('load', this, o, o.request.arg);
\r
33001 o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
\r
33004 * Callback for write actions
\r
33005 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
33006 * @param {Object} trans The request transaction object
\r
33007 * @param {Object} res The server response
\r
33010 onWrite : function(action, o, response, rs) {
\r
33011 var reader = o.reader;
\r
33014 res = reader.readResponse(action, response);
\r
33016 this.fireEvent('exception', this, 'response', action, o, response, e);
\r
33017 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
33020 if (res[reader.meta.successProperty] === false) {
\r
33021 this.fireEvent('exception', this, 'remote', action, o, res, rs);
\r
33023 this.fireEvent('write', this, action, res[reader.meta.root], res, rs, o.request.arg);
\r
33025 o.request.callback.call(o.request.scope, res[reader.meta.root], res, res[reader.meta.successProperty]);
\r
33029 destroy: function(){
\r
33030 if(!this.useAjax){
\r
33031 this.conn.abort();
\r
33032 }else if(this.activeRequest){
\r
33033 var actions = Ext.data.Api.actions;
\r
33034 for (var verb in actions) {
\r
33035 if(this.activeRequest[actions[verb]]){
\r
33036 Ext.Ajax.abort(this.activeRequest[actions[verb]]);
\r
33040 Ext.data.HttpProxy.superclass.destroy.call(this);
\r
33043 * @class Ext.data.MemoryProxy
\r
33044 * @extends Ext.data.DataProxy
\r
33045 * An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
\r
33046 * to the Reader when its load method is called.
\r
33048 * @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
\r
33050 Ext.data.MemoryProxy = function(data){
\r
33051 // Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
\r
33053 api[Ext.data.Api.actions.read] = true;
\r
33054 Ext.data.MemoryProxy.superclass.constructor.call(this, {
\r
33057 this.data = data;
\r
33060 Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
\r
33062 * @event loadexception
\r
33063 * Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
\r
33064 * through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
\r
33065 * @param {Object} this
\r
33066 * @param {Object} arg The callback's arg object passed to the {@link #load} function
\r
33067 * @param {Object} null This parameter does not apply and will always be null for MemoryProxy
\r
33068 * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
\r
33072 * MemoryProxy implementation of DataProxy#doRequest
\r
33073 * @param {String} action
\r
33074 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
\r
33075 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
33076 * for the request to the remote server.
\r
33077 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
33078 * object into a block of Ext.data.Records.
\r
33079 * @param {Function} callback The function into which to pass the block of Ext.data.Records.
\r
33080 * The function must be passed <ul>
\r
33081 * <li>The Record block object</li>
\r
33082 * <li>The "arg" argument from the load function</li>
\r
33083 * <li>A boolean success indicator</li>
\r
33085 * @param {Object} scope The scope in which to call the callback
\r
33086 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
33088 doRequest : function(action, rs, params, reader, callback, scope, arg) {
\r
33089 // No implementation for CRUD in MemoryProxy. Assumes all actions are 'load'
\r
33090 params = params || {};
\r
33093 result = reader.readRecords(this.data);
\r
33095 // @deprecated loadexception
\r
33096 this.fireEvent("loadexception", this, null, arg, e);
\r
33098 this.fireEvent('exception', this, 'response', action, arg, null, e);
\r
33099 callback.call(scope, null, arg, false);
\r
33102 callback.call(scope, result, arg, true);
\r
33105 * @class Ext.data.JsonWriter
33106 * @extends Ext.data.DataWriter
33107 * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action.
33109 Ext.data.JsonWriter = function(config) {
33110 Ext.data.JsonWriter.superclass.constructor.call(this, config);
33111 // careful to respect "returnJson", renamed to "encode"
33112 if (this.returnJson != undefined) {
33113 this.encode = this.returnJson;
33116 Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
33118 * @cfg {Boolean} returnJson <b>Deprecated. Use {@link Ext.data.JsonWriter#encode} instead.
33120 returnJson : undefined,
33122 * @cfg {Boolean} encode <tt>true</tt> to {@link Ext.util.JSON#encode encode} the
33123 * {@link Ext.data.DataWriter#toHash hashed data}. Defaults to <tt>true</tt>. When using
33124 * {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
33125 * its own json-encoding. In addition, if you're using {@link Ext.data.HttpProxy}, setting to <tt>false</tt>
33126 * will cause HttpProxy to transmit data using the <b>jsonData</b> configuration-params of {@link Ext.Ajax#request}
33127 * instead of <b>params</b>. When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
33128 * tuned to expect data through the jsonData mechanism. In those cases, one will want to set <b>encode: <tt>false</tt></b>
33133 * Final action of a write event. Apply the written data-object to params.
33134 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
33135 * @param {Record[]} rs
33136 * @param {Object} http params
33137 * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty"
33139 render : function(action, rs, params, data) {
33140 Ext.apply(params, data);
33142 if (this.encode === true) { // <-- @deprecated returnJson
33143 if (Ext.isArray(rs) && data[this.meta.idProperty]) {
33144 params[this.meta.idProperty] = Ext.encode(params[this.meta.idProperty]);
33146 params[this.meta.root] = Ext.encode(params[this.meta.root]);
33152 * @param {Ext.data.Record} rec
33154 createRecord : function(rec) {
33155 return this.toHash(rec);
33160 * @param {Ext.data.Record} rec
33162 updateRecord : function(rec) {
33163 return this.toHash(rec);
33169 * @param {Ext.data.Record} rec
33171 destroyRecord : function(rec) {
33175 * @class Ext.data.JsonReader
33176 * @extends Ext.data.DataReader
33177 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from a JSON response
33178 * based on mappings in a provided {@link Ext.data.Record} constructor.</p>
33179 * <p>Example code:</p>
33181 var Employee = Ext.data.Record.create([
33182 {name: 'firstname'}, // map the Record's "firstname" field to the row object's key of the same name
33183 {name: 'job', mapping: 'occupation'} // map the Record's "job" field to the row object's "occupation" key
33185 var myReader = new Ext.data.JsonReader(
33186 { // The metadata property, with configuration options:
33187 totalProperty: "results", // the property which contains the total dataset size (optional)
33188 root: "rows", // the property which contains an Array of record data objects
33189 idProperty: "id" // the property within each row object that provides an ID for the record (optional)
33191 Employee // {@link Ext.data.Record} constructor that provides mapping for JSON object
33194 * <p>This would consume a JSON data object of the form:</p><pre><code>
33196 results: 2, // Reader's configured totalProperty
33197 rows: [ // Reader's configured root
33198 { id: 1, firstname: 'Bill', occupation: 'Gardener' }, // a row object
33199 { id: 2, firstname: 'Ben' , occupation: 'Horticulturalist' } // another row object
33203 * <p><b><u>Automatic configuration using metaData</u></b></p>
33204 * <p>It is possible to change a JsonReader's metadata at any time by including a <b><tt>metaData</tt></b>
33205 * property in the JSON data object. If the JSON data object has a <b><tt>metaData</tt></b> property, a
33206 * {@link Ext.data.Store Store} object using this Reader will reconfigure itself to use the newly provided
33207 * field definition and fire its {@link Ext.data.Store#metachange metachange} event. The metachange event
33208 * handler may interrogate the <b><tt>metaData</tt></b> property to perform any configuration required.
33209 * Note that reconfiguring a Store potentially invalidates objects which may refer to Fields or Records
33210 * which no longer exist.</p>
33211 * <p>The <b><tt>metaData</tt></b> property in the JSON data object may contain:</p>
33212 * <div class="mdetail-params"><ul>
33213 * <li>any of the configuration options for this class</li>
33214 * <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which the JsonReader will
33215 * use as an argument to the {@link Ext.data.Record#create data Record create method} in order to
33216 * configure the layout of the Records it will produce.</li>
33217 * <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property which the JsonReader will
33218 * use to set the {@link Ext.data.Store}'s {@link Ext.data.Store#sortInfo sortInfo} property</li>
33219 * <li>any user-defined properties needed</li>
33221 * <p>To use this facility to send the same data as the example above (without having to code the creation
33222 * of the Record constructor), you would create the JsonReader like this:</p><pre><code>
33223 var myReader = new Ext.data.JsonReader();
33225 * <p>The first data packet from the server would configure the reader by containing a
33226 * <b><tt>metaData</tt></b> property <b>and</b> the data. For example, the JSON data object might take
33233 totalProperty: 'results',
33236 {name: 'job', mapping: 'occupation'}
33238 sortInfo: {field: 'name', direction:'ASC'}, // used by store to set its sortInfo
33239 foo: 'bar' // custom property
33242 rows: [ // an Array
33243 { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
33244 { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' }
33248 * @cfg {String} totalProperty [total] Name of the property from which to retrieve the total number of records
33249 * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
33250 * paged from the remote server. Defaults to <tt>total</tt>.
33251 * @cfg {String} successProperty [success] Name of the property from which to
33252 * retrieve the success attribute. Defaults to <tt>success</tt>. See
33253 * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
33254 * for additional information.
33255 * @cfg {String} root [undefined] <b>Required</b>. The name of the property
33256 * which contains the Array of row objects. Defaults to <tt>undefined</tt>.
33257 * An exception will be thrown if the root property is undefined. The data packet
33258 * value for this property should be an empty array to clear the data or show
33260 * @cfg {String} idProperty [id] Name of the property within a row object that contains a record identifier value. Defaults to <tt>id</tt>
33262 * Create a new JsonReader
33263 * @param {Object} meta Metadata configuration options.
33264 * @param {Array/Object} recordType
33265 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
33266 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
33267 * constructor created from {@link Ext.data.Record#create}.</p>
33269 Ext.data.JsonReader = function(meta, recordType){
33272 // default idProperty, successProperty & totalProperty -> "id", "success", "total"
33273 Ext.applyIf(meta, {
33275 successProperty: 'success',
33276 totalProperty: 'total'
33279 Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
33281 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
33283 * This JsonReader's metadata as passed to the constructor, or as passed in
33284 * the last data packet's <b><tt>metaData</tt></b> property.
33289 * This method is only used by a DataProxy which has retrieved data from a remote server.
33290 * @param {Object} response The XHR object which contains the JSON data in its responseText.
33291 * @return {Object} data A data block which is used by an Ext.data.Store object as
33292 * a cache of Ext.data.Records.
33294 read : function(response){
33295 var json = response.responseText;
33296 var o = Ext.decode(json);
33298 throw {message: "JsonReader.read: Json object not found"};
33300 return this.readRecords(o);
33303 // private function a store will implement
33304 onMetaChange : function(meta, recordType, o){
33311 simpleAccess: function(obj, subsc) {
33318 getJsonAccessor: function(){
33320 return function(expr) {
33322 return(re.test(expr)) ?
33323 new Function("obj", "return obj." + expr) :
33328 return Ext.emptyFn;
33333 * Create a data block containing Ext.data.Records from a JSON object.
33334 * @param {Object} o An object which contains an Array of row objects in the property specified
33335 * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
33336 * which contains the total size of the dataset.
33337 * @return {Object} data A data block which is used by an Ext.data.Store object as
33338 * a cache of Ext.data.Records.
33340 readRecords : function(o){
33342 * After any data loads, the raw JSON data is available for further custom processing. If no data is
33343 * loaded or there is a load exception this property will be undefined.
33349 this.meta = o.metaData;
33350 this.recordType = Ext.data.Record.create(o.metaData.fields);
33351 this.onMetaChange(this.meta, this.recordType, o);
33353 var s = this.meta, Record = this.recordType,
33354 f = Record.prototype.fields, fi = f.items, fl = f.length, v;
33356 // Generate extraction functions for the totalProperty, the root, the id, and for each field
33357 this.buildExtractors();
33358 var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
33359 if(s.totalProperty){
33360 v = parseInt(this.getTotal(o), 10);
33365 if(s.successProperty){
33366 v = this.getSuccess(o);
33367 if(v === false || v === 'false'){
33373 for(var i = 0; i < c; i++){
33375 var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
33377 records[i] = record;
33382 totalRecords : totalRecords
33387 buildExtractors : function() {
33391 var s = this.meta, Record = this.recordType,
33392 f = Record.prototype.fields, fi = f.items, fl = f.length;
33394 if(s.totalProperty) {
33395 this.getTotal = this.getJsonAccessor(s.totalProperty);
33397 if(s.successProperty) {
33398 this.getSuccess = this.getJsonAccessor(s.successProperty);
33400 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;};
33401 if (s.id || s.idProperty) {
33402 var g = this.getJsonAccessor(s.id || s.idProperty);
33403 this.getId = function(rec) {
33405 return (r === undefined || r === "") ? null : r;
33408 this.getId = function(){return null;};
33411 for(var i = 0; i < fl; i++){
33413 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
33414 ef.push(this.getJsonAccessor(map));
33419 // private extractValues
33420 extractValues: function(data, items, len) {
33421 var f, values = {};
33422 for(var j = 0; j < len; j++){
33424 var v = this.ef[j](data);
33425 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
33431 * Decode a json response from server.
33432 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
33433 * @param {Object} response
33435 readResponse : function(action, response) {
33436 var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
33438 throw new Ext.data.JsonReader.Error('response');
33440 if (Ext.isEmpty(o[this.meta.successProperty])) {
33441 throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
33443 // TODO, separate empty and undefined exceptions.
33444 if ((action === Ext.data.Api.actions.create || action === Ext.data.Api.actions.update)) {
33445 if (Ext.isEmpty(o[this.meta.root])) {
33446 throw new Ext.data.JsonReader.Error('root-emtpy', this.meta.root);
33448 else if (o[this.meta.root] === undefined) {
33449 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
33452 // make sure extraction functions are defined.
33453 this.ef = this.buildExtractors();
33459 * @class Ext.data.JsonReader.Error
33460 * Error class for JsonReader
33462 Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
33463 constructor : function(message, arg) {
33465 Ext.Error.call(this, message);
33467 name : 'Ext.data.JsonReader'
33469 Ext.apply(Ext.data.JsonReader.Error.prototype, {
33471 'response': "An error occurred while json-decoding your server response",
33472 '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.',
33473 '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.',
33474 '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.',
33475 '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.',
33476 '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.'
33480 * @class Ext.data.ArrayReader
33481 * @extends Ext.data.JsonReader
33482 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an Array.
33483 * Each element of that Array represents a row of data fields. The
33484 * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
33485 * of the field definition if it exists, or the field's ordinal position in the definition.</p>
33486 * <p>Example code:</p>
33488 var Employee = Ext.data.Record.create([
33489 {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
33490 {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
33492 var myReader = new Ext.data.ArrayReader({
33493 {@link #idIndex}: 0
33496 * <p>This would consume an Array like this:</p>
33498 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
33501 * Create a new ArrayReader
33502 * @param {Object} meta Metadata configuration options.
33503 * @param {Array/Object} recordType
33504 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
33505 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
33506 * constructor created from {@link Ext.data.Record#create}.</p>
33508 Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
33510 * @cfg {String} successProperty
33514 * @cfg {Number} id (optional) The subscript within row Array that provides an ID for the Record.
33515 * Deprecated. Use {@link #idIndex} instead.
33518 * @cfg {Number} idIndex (optional) The subscript within row Array that provides an ID for the Record.
33521 * Create a data block containing Ext.data.Records from an Array.
33522 * @param {Object} o An Array of row objects which represents the dataset.
33523 * @return {Object} data A data block which is used by an Ext.data.Store object as
33524 * a cache of Ext.data.Records.
33526 readRecords : function(o){
33527 this.arrayData = o;
33529 sid = s ? Ext.num(s.idIndex, s.id) : null,
33530 recordType = this.recordType,
33531 fields = recordType.prototype.fields,
33535 if(!this.getRoot) {
33536 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p) {return p;};
33537 if(s.totalProperty) {
33538 this.getTotal = this.getJsonAccessor(s.totalProperty);
33542 var root = this.getRoot(o);
33544 for(var i = 0; i < root.length; i++) {
33547 var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
33548 for(var j = 0, jlen = fields.length; j < jlen; j++) {
33549 var f = fields.items[j];
33550 var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
33551 v = n[k] !== undefined ? n[k] : f.defaultValue;
33552 v = f.convert(v, n);
33553 values[f.name] = v;
33555 var record = new recordType(values, id);
33557 records[records.length] = record;
33560 var totalRecords = records.length;
33562 if(s.totalProperty) {
33563 v = parseInt(this.getTotal(o), 10);
33571 totalRecords : totalRecords
33575 * @class Ext.data.ArrayStore
33576 * @extends Ext.data.Store
33577 * <p>Formerly known as "SimpleStore".</p>
33578 * <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
33579 * An ArrayStore will be automatically configured with a {@link Ext.data.ArrayReader}.</p>
33580 * <p>A store configuration would be something like:<pre><code>
33581 var store = new Ext.data.ArrayStore({
33584 storeId: 'myStore',
33589 {name: 'price', type: 'float'},
33590 {name: 'change', type: 'float'},
33591 {name: 'pctChange', type: 'float'},
33592 {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
33595 * </code></pre></p>
33596 * <p>This store is configured to consume a returned object of the form:<pre><code>
33598 ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
33599 ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
33600 ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
33601 ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
33602 ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
33605 * An object literal of this form could also be used as the {@link #data} config option.</p>
33606 * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
33607 * <b>{@link Ext.data.ArrayReader ArrayReader}</b>.</p>
33609 * @param {Object} config
33610 * @xtype arraystore
33612 Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
33614 * @cfg {Ext.data.DataReader} reader @hide
33616 constructor: function(config){
33617 Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
33618 reader: new Ext.data.ArrayReader(config)
33622 loadData : function(data, append){
33623 if(this.expandData === true){
33625 for(var i = 0, len = data.length; i < len; i++){
33626 r[r.length] = [data[i]];
33630 Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
33633 Ext.reg('arraystore', Ext.data.ArrayStore);
33635 // backwards compat
33636 Ext.data.SimpleStore = Ext.data.ArrayStore;
33637 Ext.reg('simplestore', Ext.data.SimpleStore);/**
33638 * @class Ext.data.JsonStore
33639 * @extends Ext.data.Store
33640 * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
33641 * A JsonStore will be automatically configured with a {@link Ext.data.JsonReader}.</p>
33642 * <p>A store configuration would be something like:<pre><code>
33643 var store = new Ext.data.JsonStore({
33646 url: 'get-images.php',
33647 storeId: 'myStore',
33650 idProperty: 'name',
33651 fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
33653 * </code></pre></p>
33654 * <p>This store is configured to consume a returned object of the form:<pre><code>
33657 {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
33658 {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
33662 * An object literal of this form could also be used as the {@link #data} config option.</p>
33663 * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
33664 * <b>{@link Ext.data.JsonReader JsonReader}</b>.</p>
33666 * @param {Object} config
33669 Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
33671 * @cfg {Ext.data.DataReader} reader @hide
33673 constructor: function(config){
33674 Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
33675 reader: new Ext.data.JsonReader(config)
33679 Ext.reg('jsonstore', Ext.data.JsonStore);/**
33680 * @class Ext.data.XmlWriter
33681 * @extends Ext.data.DataWriter
33682 * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action via XML.
33684 Ext.data.XmlWriter = Ext.extend(Ext.data.DataWriter, {
33686 * Final action of a write event. Apply the written data-object to params.
33687 * @param {String} action [Ext.data.Api.create|read|update|destroy]
33688 * @param {Record[]} rs
33689 * @param {Object} http params
33690 * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty"
33692 render : function(action, rs, params, data) {
33697 * @param {Ext.data.Record} rec
33699 createRecord : function(rec) {
33704 * @param {Ext.data.Record} rec
33706 updateRecord : function(rec) {
33712 * @param {Ext.data.Record} rec
33714 destroyRecord : function(rec) {
33718 * @class Ext.data.XmlReader
33719 * @extends Ext.data.DataReader
33720 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an XML document
33721 * based on mappings in a provided {@link Ext.data.Record} constructor.</p>
33722 * <p><b>Note</b>: that in order for the browser to parse a returned XML document, the Content-Type
33723 * header in the HTTP response must be set to "text/xml" or "application/xml".</p>
33724 * <p>Example code:</p>
33726 var Employee = Ext.data.Record.create([
33727 {name: 'name', mapping: 'name'}, // "mapping" property not needed if it is the same as "name"
33728 {name: 'occupation'} // This field will use "occupation" as the mapping.
33730 var myReader = new Ext.data.XmlReader({
33731 totalRecords: "results", // The element which contains the total dataset size (optional)
33732 record: "row", // The repeated element which contains row information
33733 id: "id" // The element within the row that provides an ID for the record (optional)
33737 * This would consume an XML file like this:
33739 <?xml version="1.0" encoding="UTF-8"?>
33741 <results>2</results>
33744 <name>Bill</name>
33745 <occupation>Gardener</occupation>
33749 <name>Ben</name>
33750 <occupation>Horticulturalist</occupation>
33754 * @cfg {String} totalRecords The DomQuery path from which to retrieve the total number of records
33755 * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
33756 * paged from the remote server.
33757 * @cfg {String} record The DomQuery path to the repeated element which contains record information.
33758 * @cfg {String} success The DomQuery path to the success attribute used by forms.
33759 * @cfg {String} idPath The DomQuery path relative from the record element to the element that contains
33760 * a record identifier value.
33762 * Create a new XmlReader.
33763 * @param {Object} meta Metadata configuration options
33764 * @param {Object} recordType Either an Array of field definition objects as passed to
33765 * {@link Ext.data.Record#create}, or a Record constructor object created using {@link Ext.data.Record#create}.
33767 Ext.data.XmlReader = function(meta, recordType){
33769 Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields);
33771 Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
33773 * This method is only used by a DataProxy which has retrieved data from a remote server.
33774 * @param {Object} response The XHR object which contains the parsed XML document. The response is expected
33775 * to contain a property called <tt>responseXML</tt> which refers to an XML document object.
33776 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
33777 * a cache of Ext.data.Records.
33779 read : function(response){
33780 var doc = response.responseXML;
33782 throw {message: "XmlReader.read: XML Document not available"};
33784 return this.readRecords(doc);
33788 * Create a data block containing Ext.data.Records from an XML document.
33789 * @param {Object} doc A parsed XML document.
33790 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
33791 * a cache of Ext.data.Records.
33793 readRecords : function(doc){
33795 * After any data loads/reads, the raw XML Document is available for further custom processing.
33796 * @type XMLDocument
33798 this.xmlData = doc;
33799 var root = doc.documentElement || doc;
33800 var q = Ext.DomQuery;
33801 var recordType = this.recordType, fields = recordType.prototype.fields;
33802 var sid = this.meta.idPath || this.meta.id;
33803 var totalRecords = 0, success = true;
33804 if(this.meta.totalRecords){
33805 totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
33808 if(this.meta.success){
33809 var sv = q.selectValue(this.meta.success, root, true);
33810 success = sv !== false && sv !== 'false';
33813 var ns = q.select(this.meta.record, root);
33814 for(var i = 0, len = ns.length; i < len; i++) {
33817 var id = sid ? q.selectValue(sid, n) : undefined;
33818 for(var j = 0, jlen = fields.length; j < jlen; j++){
33819 var f = fields.items[j];
33820 var v = q.selectValue(Ext.value(f.mapping, f.name, true), n, f.defaultValue);
33821 v = f.convert(v, n);
33822 values[f.name] = v;
33824 var record = new recordType(values, id);
33826 records[records.length] = record;
33832 totalRecords : totalRecords || records.length
33836 // TODO: implement readResponse for XmlReader
33837 readResponse : Ext.emptyFn
33839 * @class Ext.data.XmlStore
\r
33840 * @extends Ext.data.Store
\r
33841 * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
\r
33842 * A XmlStore will be automatically configured with a {@link Ext.data.XmlReader}.</p>
\r
33843 * <p>A store configuration would be something like:<pre><code>
\r
33844 var store = new Ext.data.XmlStore({
\r
33846 autoDestroy: true,
\r
33847 storeId: 'myStore',
\r
33848 url: 'sheldon.xml', // automatically configures a HttpProxy
\r
33849 // reader configs
\r
33850 record: 'Item', // records will have an "Item" tag
\r
33852 totalRecords: '@TotalResults'
\r
33854 // set up the fields mapping into the xml doc
\r
33855 // The first needs mapping, the others are very basic
\r
33856 {name: 'Author', mapping: 'ItemAttributes > Author'},
\r
33857 'Title', 'Manufacturer', 'ProductGroup'
\r
33860 * </code></pre></p>
\r
33861 * <p>This store is configured to consume a returned object of the form:<pre><code>
\r
33862 <?xml version="1.0" encoding="UTF-8"?>
\r
33863 <ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
\r
33866 <IsValid>True</IsValid>
\r
33867 <ItemSearchRequest>
\r
33868 <Author>Sidney Sheldon</Author>
\r
33869 <SearchIndex>Books</SearchIndex>
\r
33870 </ItemSearchRequest>
\r
33872 <TotalResults>203</TotalResults>
\r
33873 <TotalPages>21</TotalPages>
\r
33875 <ASIN>0446355453</ASIN>
\r
33876 <DetailPageURL>
\r
33877 http://www.amazon.com/
\r
33878 </DetailPageURL>
\r
33879 <ItemAttributes>
\r
33880 <Author>Sidney Sheldon</Author>
\r
33881 <Manufacturer>Warner Books</Manufacturer>
\r
33882 <ProductGroup>Book</ProductGroup>
\r
33883 <Title>Master of the Game</Title>
\r
33884 </ItemAttributes>
\r
33887 </ItemSearchResponse>
\r
33889 * An object literal of this form could also be used as the {@link #data} config option.</p>
\r
33890 * <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of
\r
33891 * <b>{@link Ext.data.XmlReader XmlReader}</b>.</p>
\r
33893 * @param {Object} config
\r
33894 * @xtype xmlstore
\r
33896 Ext.data.XmlStore = Ext.extend(Ext.data.Store, {
\r
33898 * @cfg {Ext.data.DataReader} reader @hide
\r
33900 constructor: function(config){
\r
33901 Ext.data.XmlStore.superclass.constructor.call(this, Ext.apply(config, {
\r
33902 reader: new Ext.data.XmlReader(config)
\r
33906 Ext.reg('xmlstore', Ext.data.XmlStore);/**
\r
33907 * @class Ext.data.GroupingStore
\r
33908 * @extends Ext.data.Store
\r
33909 * A specialized store implementation that provides for grouping records by one of the available fields. This
\r
33910 * is usually used in conjunction with an {@link Ext.grid.GroupingView} to proved the data model for
\r
33911 * a grouped GridPanel.
\r
33913 * Creates a new GroupingStore.
\r
33914 * @param {Object} config A config object containing the objects needed for the Store to access data,
\r
33915 * and read the data into Records.
\r
33916 * @xtype groupingstore
\r
33918 Ext.data.GroupingStore = Ext.extend(Ext.data.Store, {
\r
33921 constructor: function(config){
\r
33922 Ext.data.GroupingStore.superclass.constructor.call(this, config);
\r
33923 this.applyGroupField();
\r
33927 * @cfg {String} groupField
\r
33928 * The field name by which to sort the store's data (defaults to '').
\r
33931 * @cfg {Boolean} remoteGroup
\r
33932 * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the
\r
33933 * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
\r
33934 * helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call.
\r
33936 remoteGroup : false,
\r
33938 * @cfg {Boolean} groupOnSort
\r
33939 * True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the
\r
33940 * existing sort info (defaults to false).
\r
33942 groupOnSort:false,
\r
33944 groupDir : 'ASC',
\r
33947 * Clears any existing grouping and refreshes the data using the default sort.
\r
33949 clearGrouping : function(){
\r
33950 this.groupField = false;
\r
33951 if(this.remoteGroup){
\r
33952 if(this.baseParams){
\r
33953 delete this.baseParams.groupBy;
\r
33955 var lo = this.lastOptions;
\r
33956 if(lo && lo.params){
\r
33957 delete lo.params.groupBy;
\r
33961 this.applySort();
\r
33962 this.fireEvent('datachanged', this);
\r
33967 * Groups the data by the specified field.
\r
33968 * @param {String} field The field name by which to sort the store's data
\r
33969 * @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed
\r
33970 * in is the same as the current grouping field, false to skip grouping on the same field (defaults to false)
\r
33972 groupBy : function(field, forceRegroup, direction){
\r
33973 direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir;
\r
33974 if(this.groupField == field && this.groupDir == direction && !forceRegroup){
\r
33975 return; // already grouped by this field
\r
33977 this.groupField = field;
\r
33978 this.groupDir = direction;
\r
33979 this.applyGroupField();
\r
33980 if(this.groupOnSort){
\r
33981 this.sort(field, direction);
\r
33984 if(this.remoteGroup){
\r
33987 var si = this.sortInfo || {};
\r
33988 if(si.field != field || si.direction != direction){
\r
33989 this.applySort();
\r
33991 this.sortData(field, direction);
\r
33993 this.fireEvent('datachanged', this);
\r
33998 applyGroupField: function(){
\r
33999 if(this.remoteGroup){
\r
34000 if(!this.baseParams){
\r
34001 this.baseParams = {};
\r
34003 this.baseParams.groupBy = this.groupField;
\r
34004 this.baseParams.groupDir = this.groupDir;
\r
34009 applySort : function(){
\r
34010 Ext.data.GroupingStore.superclass.applySort.call(this);
\r
34011 if(!this.groupOnSort && !this.remoteGroup){
\r
34012 var gs = this.getGroupState();
\r
34013 if(gs && (gs != this.sortInfo.field || this.groupDir != this.sortInfo.direction)){
\r
34014 this.sortData(this.groupField, this.groupDir);
\r
34020 applyGrouping : function(alwaysFireChange){
\r
34021 if(this.groupField !== false){
\r
34022 this.groupBy(this.groupField, true, this.groupDir);
\r
34025 if(alwaysFireChange === true){
\r
34026 this.fireEvent('datachanged', this);
\r
34033 getGroupState : function(){
\r
34034 return this.groupOnSort && this.groupField !== false ?
\r
34035 (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField;
\r
34038 Ext.reg('groupingstore', Ext.data.GroupingStore);/**
\r
34039 * @class Ext.data.DirectProxy
\r
34040 * @extends Ext.data.DataProxy
\r
34042 Ext.data.DirectProxy = function(config){
\r
34043 Ext.apply(this, config);
\r
34044 if(typeof this.paramOrder == 'string'){
\r
34045 this.paramOrder = this.paramOrder.split(/[\s,|]/);
\r
34047 Ext.data.DirectProxy.superclass.constructor.call(this, config);
\r
34050 Ext.extend(Ext.data.DirectProxy, Ext.data.DataProxy, {
\r
34052 * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. A list of params to be executed
\r
34053 * server side. Specify the params in the order in which they must be executed on the server-side
\r
34054 * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
\r
34055 * comma, or pipe. For example,
\r
34056 * any of the following would be acceptable:<pre><code>
\r
34057 paramOrder: ['param1','param2','param3']
\r
34058 paramOrder: 'param1 param2 param3'
\r
34059 paramOrder: 'param1,param2,param3'
\r
34060 paramOrder: 'param1|param2|param'
\r
34063 paramOrder: undefined,
\r
34066 * @cfg {Boolean} paramsAsHash
\r
34067 * Send parameters as a collection of named arguments (defaults to <tt>true</tt>). Providing a
\r
34068 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
\r
34070 paramsAsHash: true,
\r
34073 * @cfg {Function} directFn
\r
34074 * Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
\r
34075 * for Store's which will not implement a full CRUD api.
\r
34077 directFn : undefined,
\r
34080 doRequest : function(action, rs, params, reader, callback, scope, options) {
\r
34082 var directFn = this.api[action] || this.directFn;
\r
34084 switch (action) {
\r
34085 case Ext.data.Api.actions.create:
\r
34086 args.push(params[reader.meta.root]); // <-- create(Hash)
\r
34088 case Ext.data.Api.actions.read:
\r
34089 if(this.paramOrder){
\r
34090 for(var i = 0, len = this.paramOrder.length; i < len; i++){
\r
34091 args.push(params[this.paramOrder[i]]);
\r
34093 }else if(this.paramsAsHash){
\r
34094 args.push(params);
\r
34097 case Ext.data.Api.actions.update:
\r
34098 args.push(params[reader.meta.idProperty]); // <-- save(Integer/Integer[], Hash/Hash[])
\r
34099 args.push(params[reader.meta.root]);
\r
34101 case Ext.data.Api.actions.destroy:
\r
34102 args.push(params[reader.meta.root]); // <-- destroy(Int/Int[])
\r
34107 params : params || {},
\r
34108 callback : callback,
\r
34114 args.push(this.createCallback(action, rs, trans), this);
\r
34115 directFn.apply(window, args);
\r
34119 createCallback : function(action, rs, trans) {
\r
34120 return function(result, res) {
\r
34121 if (!res.status) {
\r
34122 // @deprecated fire loadexception
\r
34123 if (action === Ext.data.Api.actions.read) {
\r
34124 this.fireEvent("loadexception", this, trans, res, null);
\r
34126 this.fireEvent('exception', this, 'remote', action, trans, res, null);
\r
34127 trans.callback.call(trans.scope, null, trans.arg, false);
\r
34130 if (action === Ext.data.Api.actions.read) {
\r
34131 this.onRead(action, trans, result, res);
\r
34133 this.onWrite(action, trans, result, res, rs);
\r
34138 * Callback for read actions
\r
34139 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
34140 * @param {Object} trans The request transaction object
\r
34141 * @param {Object} res The server response
\r
34144 onRead : function(action, trans, result, res) {
\r
34147 records = trans.reader.readRecords(result);
\r
34150 // @deprecated: Fire old loadexception for backwards-compat.
\r
34151 this.fireEvent("loadexception", this, trans, res, ex);
\r
34153 this.fireEvent('exception', this, 'response', action, trans, res, ex);
\r
34154 trans.callback.call(trans.scope, null, trans.arg, false);
\r
34157 this.fireEvent("load", this, res, trans.arg);
\r
34158 trans.callback.call(trans.scope, records, trans.arg, true);
\r
34161 * Callback for write actions
\r
34162 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
34163 * @param {Object} trans The request transaction object
\r
34164 * @param {Object} res The server response
\r
34167 onWrite : function(action, trans, result, res, rs) {
\r
34168 this.fireEvent("write", this, action, result, res, rs, trans.arg);
\r
34169 trans.callback.call(trans.scope, result, res, true);
\r
34174 * @class Ext.data.DirectStore
\r
34175 * @extends Ext.data.Store
\r
34176 * <p>Small helper class to create an {@link Ext.data.Store} configured with an
\r
34177 * {@link Ext.data.DirectProxy} and {@link Ext.data.JsonReader} to make interacting
\r
34178 * with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
\r
34179 * To create a different proxy/reader combination create a basic {@link Ext.data.Store}
\r
34180 * configured as needed.</p>
\r
34182 * <p><b>*Note:</b> Although they are not listed, this class inherits all of the config options of:</p>
\r
34183 * <div><ul class="mdetail-params">
\r
34184 * <li><b>{@link Ext.data.Store Store}</b></li>
\r
34185 * <div class="sub-desc"><ul class="mdetail-params">
\r
34188 * <li><b>{@link Ext.data.JsonReader JsonReader}</b></li>
\r
34189 * <div class="sub-desc"><ul class="mdetail-params">
\r
34190 * <li><tt><b>{@link Ext.data.JsonReader#root root}</b></tt></li>
\r
34191 * <li><tt><b>{@link Ext.data.JsonReader#idProperty idProperty}</b></tt></li>
\r
34192 * <li><tt><b>{@link Ext.data.JsonReader#totalProperty totalProperty}</b></tt></li>
\r
34195 * <li><b>{@link Ext.data.DirectProxy DirectProxy}</b></li>
\r
34196 * <div class="sub-desc"><ul class="mdetail-params">
\r
34197 * <li><tt><b>{@link Ext.data.DirectProxy#directFn directFn}</b></tt></li>
\r
34198 * <li><tt><b>{@link Ext.data.DirectProxy#paramOrder paramOrder}</b></tt></li>
\r
34199 * <li><tt><b>{@link Ext.data.DirectProxy#paramsAsHash paramsAsHash}</b></tt></li>
\r
34203 * @xtype directstore
\r
34206 * @param {Object} config
\r
34208 Ext.data.DirectStore = function(c){
\r
34209 // each transaction upon a singe record will generatie a distinct Direct transaction since Direct queues them into one Ajax request.
\r
34210 c.batchTransactions = false;
\r
34212 Ext.data.DirectStore.superclass.constructor.call(this, Ext.apply(c, {
\r
34213 proxy: (typeof(c.proxy) == 'undefined') ? new Ext.data.DirectProxy(Ext.copyTo({}, c, 'paramOrder,paramsAsHash,directFn,api')) : c.proxy,
\r
34214 reader: (typeof(c.reader) == 'undefined' && typeof(c.fields) == 'object') ? new Ext.data.JsonReader(Ext.copyTo({}, c, 'totalProperty,root,idProperty'), c.fields) : c.reader
\r
34217 Ext.extend(Ext.data.DirectStore, Ext.data.Store, {});
\r
34218 Ext.reg('directstore', Ext.data.DirectStore);
34220 * @class Ext.Direct
\r
34221 * @extends Ext.util.Observable
\r
34222 * <p><b><u>Overview</u></b></p>
\r
34224 * <p>Ext.Direct aims to streamline communication between the client and server
\r
34225 * by providing a single interface that reduces the amount of common code
\r
34226 * typically required to validate data and handle returned data packets
\r
34227 * (reading data, error conditions, etc).</p>
\r
34229 * <p>The Ext.direct namespace includes several classes for a closer integration
\r
34230 * with the server-side. The Ext.data namespace also includes classes for working
\r
34231 * with Ext.data.Stores which are backed by data from an Ext.Direct method.</p>
\r
34233 * <p><b><u>Specification</u></b></p>
\r
34235 * <p>For additional information consult the
\r
34236 * <a href="http://extjs.com/products/extjs/direct.php">Ext.Direct Specification</a>.</p>
\r
34238 * <p><b><u>Providers</u></b></p>
\r
34240 * <p>Ext.Direct uses a provider architecture, where one or more providers are
\r
34241 * used to transport data to and from the server. There are several providers
\r
34242 * that exist in the core at the moment:</p><div class="mdetail-params"><ul>
\r
34244 * <li>{@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations</li>
\r
34245 * <li>{@link Ext.direct.PollingProvider PollingProvider} for repeated requests</li>
\r
34246 * <li>{@link Ext.direct.RemotingProvider RemotingProvider} exposes server side
\r
34247 * on the client.</li>
\r
34250 * <p>A provider does not need to be invoked directly, providers are added via
\r
34251 * {@link Ext.Direct}.{@link Ext.Direct#add add}.</p>
\r
34253 * <p><b><u>Router</u></b></p>
\r
34255 * <p>Ext.Direct utilizes a "router" on the server to direct requests from the client
\r
34256 * to the appropriate server-side method. Because the Ext.Direct API is completely
\r
34257 * platform-agnostic, you could completely swap out a Java based server solution
\r
34258 * and replace it with one that uses C# without changing the client side JavaScript
\r
34261 * <p><b><u>Server side events</u></b></p>
\r
34263 * <p>Custom events from the server may be handled by the client by adding
\r
34264 * listeners, for example:</p>
\r
34266 {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
\r
34268 // add a handler for a 'message' event sent by the server
\r
34269 Ext.Direct.on('message', function(e){
\r
34270 out.append(String.format('<p><i>{0}</i></p>', e.data));
\r
34271 out.el.scrollTo('t', 100000, true);
\r
34276 Ext.Direct = Ext.extend(Ext.util.Observable, {
\r
34278 * Each event type implements a getData() method. The default event types are:
\r
34279 * <div class="mdetail-params"><ul>
\r
34280 * <li><b><tt>event</tt></b> : Ext.Direct.Event</li>
\r
34281 * <li><b><tt>exception</tt></b> : Ext.Direct.ExceptionEvent</li>
\r
34282 * <li><b><tt>rpc</tt></b> : Ext.Direct.RemotingEvent</li>
\r
34284 * @property eventTypes
\r
34289 * Four types of possible exceptions which can occur:
\r
34290 * <div class="mdetail-params"><ul>
\r
34291 * <li><b><tt>Ext.Direct.exceptions.TRANSPORT</tt></b> : 'xhr'</li>
\r
34292 * <li><b><tt>Ext.Direct.exceptions.PARSE</tt></b> : 'parse'</li>
\r
34293 * <li><b><tt>Ext.Direct.exceptions.LOGIN</tt></b> : 'login'</li>
\r
34294 * <li><b><tt>Ext.Direct.exceptions.SERVER</tt></b> : 'exception'</li>
\r
34296 * @property exceptions
\r
34300 TRANSPORT: 'xhr',
\r
34303 SERVER: 'exception'
\r
34307 constructor: function(){
\r
34311 * Fires after an event.
\r
34312 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
34313 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
34317 * @event exception
\r
34318 * Fires after an event exception.
\r
34319 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
34323 this.transactions = {};
\r
34324 this.providers = {};
\r
34328 * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods.
\r
34329 * If the provider is not already connected, it will auto-connect.
\r
34331 var pollProv = new Ext.direct.PollingProvider({
\r
34332 url: 'php/poll2.php'
\r
34335 Ext.Direct.addProvider(
\r
34337 "type":"remoting", // create a {@link Ext.direct.RemotingProvider}
\r
34338 "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
\r
34339 "actions":{ // each property within the actions object represents a Class
\r
34340 "TestAction":[ // array of methods within each server side Class
\r
34342 "name":"doEcho", // name of method
\r
34345 "name":"multiply",
\r
34349 "formHandler":true, // handle form on server with Ext.Direct.Transaction
\r
34353 "namespace":"myApplication",// namespace to create the Remoting Provider in
\r
34355 type: 'polling', // create a {@link Ext.direct.PollingProvider}
\r
34356 url: 'php/poll.php'
\r
34358 pollProv // reference to previously created instance
\r
34361 * @param {Object/Array} provider Accepts either an Array of Provider descriptions (an instance
\r
34362 * or config object for a Provider) or any number of Provider descriptions as arguments. Each
\r
34363 * Provider description instructs Ext.Direct how to create client-side stub methods.
\r
34365 addProvider : function(provider){
\r
34366 var a = arguments;
\r
34367 if(a.length > 1){
\r
34368 for(var i = 0, len = a.length; i < len; i++){
\r
34369 this.addProvider(a[i]);
\r
34374 // if provider has not already been instantiated
\r
34375 if(!provider.events){
\r
34376 provider = new Ext.Direct.PROVIDERS[provider.type](provider);
\r
34378 provider.id = provider.id || Ext.id();
\r
34379 this.providers[provider.id] = provider;
\r
34381 provider.on('data', this.onProviderData, this);
\r
34382 provider.on('exception', this.onProviderException, this);
\r
34385 if(!provider.isConnected()){
\r
34386 provider.connect();
\r
34393 * Retrieve a {@link Ext.direct.Provider provider} by the
\r
34394 * <b><tt>{@link Ext.direct.Provider#id id}</tt></b> specified when the provider is
\r
34395 * {@link #addProvider added}.
\r
34396 * @param {String} id Unique identifier assigned to the provider when calling {@link #addProvider}
\r
34398 getProvider : function(id){
\r
34399 return this.providers[id];
\r
34402 removeProvider : function(id){
\r
34403 var provider = id.id ? id : this.providers[id.id];
\r
34404 provider.un('data', this.onProviderData, this);
\r
34405 provider.un('exception', this.onProviderException, this);
\r
34406 delete this.providers[provider.id];
\r
34410 addTransaction: function(t){
\r
34411 this.transactions[t.tid] = t;
\r
34415 removeTransaction: function(t){
\r
34416 delete this.transactions[t.tid || t];
\r
34420 getTransaction: function(tid){
\r
34421 return this.transactions[tid.tid || tid];
\r
34424 onProviderData : function(provider, e){
\r
34425 if(Ext.isArray(e)){
\r
34426 for(var i = 0, len = e.length; i < len; i++){
\r
34427 this.onProviderData(provider, e[i]);
\r
34431 if(e.name && e.name != 'event' && e.name != 'exception'){
\r
34432 this.fireEvent(e.name, e);
\r
34433 }else if(e.type == 'exception'){
\r
34434 this.fireEvent('exception', e);
\r
34436 this.fireEvent('event', e, provider);
\r
34439 createEvent : function(response, extraProps){
\r
34440 return new Ext.Direct.eventTypes[response.type](Ext.apply(response, extraProps));
\r
34443 // overwrite impl. with static instance
\r
34444 Ext.Direct = new Ext.Direct();
\r
34446 Ext.Direct.TID = 1;
\r
34447 Ext.Direct.PROVIDERS = {};/**
\r
34448 * @class Ext.Direct.Transaction
\r
34449 * @extends Object
\r
34450 * <p>Supporting Class for Ext.Direct (not intended to be used directly).</p>
\r
34452 * @param {Object} config
\r
34454 Ext.Direct.Transaction = function(config){
\r
34455 Ext.apply(this, config);
\r
34456 this.tid = ++Ext.Direct.TID;
\r
34457 this.retryCount = 0;
\r
34459 Ext.Direct.Transaction.prototype = {
\r
34460 send: function(){
\r
34461 this.provider.queueTransaction(this);
\r
34464 retry: function(){
\r
34465 this.retryCount++;
\r
34469 getProvider: function(){
\r
34470 return this.provider;
\r
34472 };Ext.Direct.Event = function(config){
\r
34473 Ext.apply(this, config);
\r
34475 Ext.Direct.Event.prototype = {
\r
34477 getData: function(){
\r
34478 return this.data;
\r
34482 Ext.Direct.RemotingEvent = Ext.extend(Ext.Direct.Event, {
\r
34484 getTransaction: function(){
\r
34485 return this.transaction || Ext.Direct.getTransaction(this.tid);
\r
34489 Ext.Direct.ExceptionEvent = Ext.extend(Ext.Direct.RemotingEvent, {
\r
34491 type: 'exception'
\r
34494 Ext.Direct.eventTypes = {
\r
34495 'rpc': Ext.Direct.RemotingEvent,
\r
34496 'event': Ext.Direct.Event,
\r
34497 'exception': Ext.Direct.ExceptionEvent
\r
34501 * @class Ext.direct.Provider
\r
34502 * @extends Ext.util.Observable
\r
34503 * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
\r
34505 * <p>For example ExtJs implements the following subclasses:</p>
\r
34509 +---{@link Ext.direct.JsonProvider JsonProvider}
\r
34511 +---{@link Ext.direct.PollingProvider PollingProvider}
\r
34513 +---{@link Ext.direct.RemotingProvider RemotingProvider}
\r
34517 Ext.direct.Provider = Ext.extend(Ext.util.Observable, {
\r
34519 * @cfg {String} id
\r
34520 * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
\r
34521 * You should assign an id if you need to be able to access the provider later and you do
\r
34522 * not have an object reference available, for example:
\r
34524 Ext.Direct.addProvider(
\r
34527 url: 'php/poll.php',
\r
34528 id: 'poll-provider'
\r
34532 var p = {@link Ext.Direct Ext.Direct}.{@link Ext.Direct#getProvider getProvider}('poll-provider');
\r
34538 * @cfg {Number} priority
\r
34539 * Priority of the request. Lower is higher priority, <tt>0</tt> means "duplex" (always on).
\r
34540 * All Providers default to <tt>1</tt> except for PollingProvider which defaults to <tt>3</tt>.
\r
34545 * @cfg {String} type
\r
34546 * <b>Required</b>, <tt>undefined</tt> by default. The <tt>type</tt> of provider specified
\r
34547 * to {@link Ext.Direct Ext.Direct}.{@link Ext.Direct#addProvider addProvider} to create a
\r
34548 * new Provider. Acceptable values by default are:<div class="mdetail-params"><ul>
\r
34549 * <li><b><tt>polling</tt></b> : {@link Ext.direct.PollingProvider PollingProvider}</li>
\r
34550 * <li><b><tt>remoting</tt></b> : {@link Ext.direct.RemotingProvider RemotingProvider}</li>
\r
34555 constructor : function(config){
\r
34556 Ext.apply(this, config);
\r
34560 * Fires when the Provider connects to the server-side
\r
34561 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
34565 * @event disconnect
\r
34566 * Fires when the Provider disconnects from the server-side
\r
34567 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
34572 * Fires when the Provider receives data from the server-side
\r
34573 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
34574 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
34578 * @event exception
\r
34579 * Fires when the Provider receives an exception from the server-side
\r
34583 Ext.direct.Provider.superclass.constructor.call(this, config);
\r
34587 * Returns whether or not the server-side is currently connected.
\r
34588 * Abstract method for subclasses to implement.
\r
34590 isConnected: function(){
\r
34595 * Abstract methods for subclasses to implement.
\r
34597 connect: Ext.emptyFn,
\r
34600 * Abstract methods for subclasses to implement.
\r
34602 disconnect: Ext.emptyFn
\r
34605 * @class Ext.direct.JsonProvider
\r
34606 * @extends Ext.direct.Provider
\r
34608 Ext.direct.JsonProvider = Ext.extend(Ext.direct.Provider, {
\r
34609 parseResponse: function(xhr){
\r
34610 if(!Ext.isEmpty(xhr.responseText)){
\r
34611 if(typeof xhr.responseText == 'object'){
\r
34612 return xhr.responseText;
\r
34614 return Ext.decode(xhr.responseText);
\r
34619 getEvents: function(xhr){
\r
34622 data = this.parseResponse(xhr);
\r
34624 var event = new Ext.Direct.ExceptionEvent({
\r
34627 code: Ext.Direct.exceptions.PARSE,
\r
34628 message: 'Error parsing json response: \n\n ' + data
\r
34633 if(Ext.isArray(data)){
\r
34634 for(var i = 0, len = data.length; i < len; i++){
\r
34635 events.push(Ext.Direct.createEvent(data[i]));
\r
34638 events.push(Ext.Direct.createEvent(data));
\r
34643 * @class Ext.direct.PollingProvider
\r
34644 * @extends Ext.direct.JsonProvider
\r
34646 * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
\r
34647 * The initial request for data originates from the client, and then is responded to by the
\r
34650 * <p>All configurations for the PollingProvider should be generated by the server-side
\r
34651 * API portion of the Ext.Direct stack.</p>
\r
34653 * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
\r
34654 * specifying <tt>type = 'polling'</tt>. For example:</p>
\r
34656 var pollA = new Ext.direct.PollingProvider({
\r
34658 url: 'php/pollA.php',
\r
34660 Ext.Direct.addProvider(pollA);
\r
34661 pollA.disconnect();
\r
34663 Ext.Direct.addProvider(
\r
34666 url: 'php/pollB.php',
\r
34667 id: 'pollB-provider'
\r
34670 var pollB = Ext.Direct.getProvider('pollB-provider');
\r
34673 Ext.direct.PollingProvider = Ext.extend(Ext.direct.JsonProvider, {
\r
34675 * @cfg {Number} priority
\r
34676 * Priority of the request (defaults to <tt>3</tt>). See {@link Ext.direct.Provider#priority}.
\r
34678 // override default priority
\r
34682 * @cfg {Number} interval
\r
34683 * How often to poll the server-side in milliseconds (defaults to <tt>3000</tt> - every
\r
34689 * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
\r
34690 * on every polling request
\r
34694 * @cfg {String/Function} url
\r
34695 * The url which the PollingProvider should contact with each request. This can also be
\r
34696 * an imported Ext.Direct method which will accept the baseParams as its only argument.
\r
34700 constructor : function(config){
\r
34701 Ext.direct.PollingProvider.superclass.constructor.call(this, config);
\r
34704 * @event beforepoll
\r
34705 * Fired immediately before a poll takes place, an event handler can return false
\r
34706 * in order to cancel the poll.
\r
34707 * @param {Ext.direct.PollingProvider}
\r
34712 * This event has not yet been implemented.
\r
34713 * @param {Ext.direct.PollingProvider}
\r
34720 isConnected: function(){
\r
34721 return !!this.pollTask;
\r
34725 * Connect to the server-side and begin the polling process. To handle each
\r
34726 * response subscribe to the data event.
\r
34728 connect: function(){
\r
34729 if(this.url && !this.pollTask){
\r
34730 this.pollTask = Ext.TaskMgr.start({
\r
34732 if(this.fireEvent('beforepoll', this) !== false){
\r
34733 if(typeof this.url == 'function'){
\r
34734 this.url(this.baseParams);
\r
34736 Ext.Ajax.request({
\r
34738 callback: this.onData,
\r
34740 params: this.baseParams
\r
34745 interval: this.interval,
\r
34748 this.fireEvent('connect', this);
\r
34749 }else if(!this.url){
\r
34750 throw 'Error initializing PollingProvider, no url configured.';
\r
34755 * Disconnect from the server-side and stop the polling process. The disconnect
\r
34756 * event will be fired on a successful disconnect.
\r
34758 disconnect: function(){
\r
34759 if(this.pollTask){
\r
34760 Ext.TaskMgr.stop(this.pollTask);
\r
34761 delete this.pollTask;
\r
34762 this.fireEvent('disconnect', this);
\r
34767 onData: function(opt, success, xhr){
\r
34769 var events = this.getEvents(xhr);
\r
34770 for(var i = 0, len = events.length; i < len; i++){
\r
34771 var e = events[i];
\r
34772 this.fireEvent('data', this, e);
\r
34775 var e = new Ext.Direct.ExceptionEvent({
\r
34777 code: Ext.Direct.exceptions.TRANSPORT,
\r
34778 message: 'Unable to connect to the server.',
\r
34781 this.fireEvent('data', this, e);
\r
34786 Ext.Direct.PROVIDERS['polling'] = Ext.direct.PollingProvider;/**
\r
34787 * @class Ext.direct.RemotingProvider
\r
34788 * @extends Ext.direct.JsonProvider
\r
34790 * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
\r
34791 * server side methods on the client (a remote procedure call (RPC) type of
\r
34792 * connection where the client can initiate a procedure on the server).</p>
\r
34794 * <p>This allows for code to be organized in a fashion that is maintainable,
\r
34795 * while providing a clear path between client and server, something that is
\r
34796 * not always apparent when using URLs.</p>
\r
34798 * <p>To accomplish this the server-side needs to describe what classes and methods
\r
34799 * are available on the client-side. This configuration will typically be
\r
34800 * outputted by the server-side Ext.Direct stack when the API description is built.</p>
\r
34802 Ext.direct.RemotingProvider = Ext.extend(Ext.direct.JsonProvider, {
\r
34804 * @cfg {Object} actions
\r
34805 * Object literal defining the server side actions and methods. For example, if
\r
34806 * the Provider is configured with:
\r
34808 "actions":{ // each property within the 'actions' object represents a server side Class
\r
34809 "TestAction":[ // array of methods within each server side Class to be
\r
34810 { // stubbed out on client
\r
34811 "name":"doEcho",
\r
34814 "name":"multiply",// name of method
\r
34815 "len":2 // The number of parameters that will be used to create an
\r
34816 // array of data to send to the server side function.
\r
34817 // Ensure the server sends back a Number, not a String.
\r
34820 "formHandler":true, // direct the client to use specialized form handling method
\r
34825 * <p>Note that a Store is not required, a server method can be called at any time.
\r
34826 * In the following example a <b>client side</b> handler is used to call the
\r
34827 * server side method "multiply" in the server-side "TestAction" Class:</p>
\r
34829 TestAction.multiply(
\r
34830 2, 4, // pass two arguments to server, so specify len=2
\r
34831 // callback function after the server is called
\r
34832 // result: the result returned by the server
\r
34833 // e: Ext.Direct.RemotingEvent object
\r
34834 function(result, e){
\r
34835 var t = e.getTransaction();
\r
34836 var action = t.action; // server side Class called
\r
34837 var method = t.method; // server side method called
\r
34839 var answer = Ext.encode(result); // 8
\r
34842 var msg = e.message; // failure message
\r
34847 * In the example above, the server side "multiply" function will be passed two
\r
34848 * arguments (2 and 4). The "multiply" method should return the value 8 which will be
\r
34849 * available as the <tt>result</tt> in the example above.
\r
34853 * @cfg {String/Object} namespace
\r
34854 * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
\r
34855 * Explicitly specify the namespace Object, or specify a String to have a
\r
34856 * {@link Ext#namespace namespace created} implicitly.
\r
34860 * @cfg {String} url
\r
34861 * <b>Required<b>. The url to connect to the {@link Ext.Direct} server-side router.
\r
34865 * @cfg {String} enableUrlEncode
\r
34866 * Specify which param will hold the arguments for the method.
\r
34867 * Defaults to <tt>'data'</tt>.
\r
34871 * @cfg {Number/Boolean} enableBuffer
\r
34872 * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
\r
34873 * calls. If a number is specified this is the amount of time in milliseconds
\r
34874 * to wait before sending a batched request (defaults to <tt>10</tt>).</p>
\r
34875 * <br><p>Calls which are received within the specified timeframe will be
\r
34876 * concatenated together and sent in a single request, optimizing the
\r
34877 * application by reducing the amount of round trips that have to be made
\r
34878 * to the server.</p>
\r
34880 enableBuffer: 10,
\r
34883 * @cfg {Number} maxRetries
\r
34884 * Number of times to re-attempt delivery on failure of a call.
\r
34888 constructor : function(config){
\r
34889 Ext.direct.RemotingProvider.superclass.constructor.call(this, config);
\r
34892 * @event beforecall
\r
34893 * Fires immediately before the client-side sends off the RPC call.
\r
34894 * By returning false from an event handler you can prevent the call from
\r
34896 * @param {Ext.direct.RemotingProvider} provider
\r
34897 * @param {Ext.Direct.Transaction} transaction
\r
34902 * Fires immediately after the request to the server-side is sent. This does
\r
34903 * NOT fire after the response has come back from the call.
\r
34904 * @param {Ext.direct.RemotingProvider} provider
\r
34905 * @param {Ext.Direct.Transaction} transaction
\r
34909 this.namespace = (typeof this.namespace === 'string') ? Ext.ns(this.namespace) : this.namespace || window;
\r
34910 this.transactions = {};
\r
34911 this.callBuffer = [];
\r
34915 initAPI : function(){
\r
34916 var o = this.actions;
\r
34918 var cls = this.namespace[c] || (this.namespace[c] = {});
\r
34920 for(var i = 0, len = ms.length; i < len; i++){
\r
34922 cls[m.name] = this.createMethod(c, m);
\r
34928 isConnected: function(){
\r
34929 return !!this.connected;
\r
34932 connect: function(){
\r
34935 this.connected = true;
\r
34936 this.fireEvent('connect', this);
\r
34937 }else if(!this.url){
\r
34938 throw 'Error initializing RemotingProvider, no url configured.';
\r
34942 disconnect: function(){
\r
34943 if(this.connected){
\r
34944 this.connected = false;
\r
34945 this.fireEvent('disconnect', this);
\r
34949 onData: function(opt, success, xhr){
\r
34951 var events = this.getEvents(xhr);
\r
34952 for(var i = 0, len = events.length; i < len; i++){
\r
34953 var e = events[i];
\r
34954 var t = this.getTransaction(e);
\r
34955 this.fireEvent('data', this, e);
\r
34957 this.doCallback(t, e, true);
\r
34958 Ext.Direct.removeTransaction(t);
\r
34962 var ts = [].concat(opt.ts);
\r
34963 for(var i = 0, len = ts.length; i < len; i++){
\r
34964 var t = this.getTransaction(ts[i]);
\r
34965 if(t && t.retryCount < this.maxRetries){
\r
34968 var e = new Ext.Direct.ExceptionEvent({
\r
34971 code: Ext.Direct.exceptions.TRANSPORT,
\r
34972 message: 'Unable to connect to the server.',
\r
34975 this.fireEvent('data', this, e);
\r
34977 this.doCallback(t, e, false);
\r
34978 Ext.Direct.removeTransaction(t);
\r
34985 getCallData: function(t){
\r
34987 action: t.action,
\r
34988 method: t.method,
\r
34995 doSend : function(data){
\r
34998 callback: this.onData,
\r
35003 // send only needed data
\r
35005 if(Ext.isArray(data)){
\r
35007 for(var i = 0, len = data.length; i < len; i++){
\r
35008 callData.push(this.getCallData(data[i]));
\r
35011 callData = this.getCallData(data);
\r
35014 if(this.enableUrlEncode){
\r
35016 params[typeof this.enableUrlEncode == 'string' ? this.enableUrlEncode : 'data'] = Ext.encode(callData);
\r
35017 o.params = params;
\r
35019 o.jsonData = callData;
\r
35021 Ext.Ajax.request(o);
\r
35024 combineAndSend : function(){
\r
35025 var len = this.callBuffer.length;
\r
35027 this.doSend(len == 1 ? this.callBuffer[0] : this.callBuffer);
\r
35028 this.callBuffer = [];
\r
35032 queueTransaction: function(t){
\r
35034 this.processForm(t);
\r
35037 this.callBuffer.push(t);
\r
35038 if(this.enableBuffer){
\r
35039 if(!this.callTask){
\r
35040 this.callTask = new Ext.util.DelayedTask(this.combineAndSend, this);
\r
35042 this.callTask.delay(typeof this.enableBuffer == 'number' ? this.enableBuffer : 10);
\r
35044 this.combineAndSend();
\r
35048 doCall : function(c, m, args){
\r
35049 var data = null, hs = args[m.len], scope = args[m.len+1];
\r
35052 data = args.slice(0, m.len);
\r
35055 var t = new Ext.Direct.Transaction({
\r
35061 cb: scope && Ext.isFunction(hs) ? hs.createDelegate(scope) : hs
\r
35064 if(this.fireEvent('beforecall', this, t) !== false){
\r
35065 Ext.Direct.addTransaction(t);
\r
35066 this.queueTransaction(t);
\r
35067 this.fireEvent('call', this, t);
\r
35071 doForm : function(c, m, form, callback, scope){
\r
35072 var t = new Ext.Direct.Transaction({
\r
35076 args:[form, callback, scope],
\r
35077 cb: scope && Ext.isFunction(callback) ? callback.createDelegate(scope) : callback,
\r
35081 if(this.fireEvent('beforecall', this, t) !== false){
\r
35082 Ext.Direct.addTransaction(t);
\r
35083 var isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data',
\r
35087 extMethod: m.name,
\r
35089 extUpload: String(isUpload)
\r
35092 // change made from typeof callback check to callback.params
\r
35093 // to support addl param passing in DirectSubmit EAC 6/2
\r
35095 form: Ext.getDom(form),
\r
35096 isUpload: isUpload,
\r
35097 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
\r
35099 this.fireEvent('call', this, t);
\r
35100 this.processForm(t);
\r
35104 processForm: function(t){
\r
35105 Ext.Ajax.request({
\r
35107 params: t.params,
\r
35108 callback: this.onData,
\r
35111 isUpload: t.isUpload,
\r
35116 createMethod : function(c, m){
\r
35118 if(!m.formHandler){
\r
35120 this.doCall(c, m, Array.prototype.slice.call(arguments, 0));
\r
35121 }.createDelegate(this);
\r
35123 f = function(form, callback, scope){
\r
35124 this.doForm(c, m, form, callback, scope);
\r
35125 }.createDelegate(this);
\r
35134 getTransaction: function(opt){
\r
35135 return opt && opt.tid ? Ext.Direct.getTransaction(opt.tid) : null;
\r
35138 doCallback: function(t, e){
\r
35139 var fn = e.status ? 'success' : 'failure';
\r
35142 var result = e.result || e.data;
\r
35143 if(Ext.isFunction(hs)){
\r
35146 Ext.callback(hs[fn], hs.scope, [result, e]);
\r
35147 Ext.callback(hs.callback, hs.scope, [result, e]);
\r
35152 Ext.Direct.PROVIDERS['remoting'] = Ext.direct.RemotingProvider;/**
\r
35153 * @class Ext.Resizable
\r
35154 * @extends Ext.util.Observable
\r
35155 * <p>Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
\r
35156 * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
\r
35157 * the textarea in a div and set 'resizeChild' to true (or to the id of the element), <b>or</b> set wrap:true in your config and
\r
35158 * the element will be wrapped for you automatically.</p>
\r
35159 * <p>Here is the list of valid resize handles:</p>
\r
35161 Value Description
\r
35162 ------ -------------------
\r
35173 * <p>Here's an example showing the creation of a typical Resizable:</p>
\r
35175 var resizer = new Ext.Resizable('element-id', {
\r
35183 resizer.on('resize', myHandler);
\r
35185 * <p>To hide a particular handle, set its display to none in CSS, or through script:<br>
\r
35186 * resizer.east.setDisplayed(false);</p>
\r
35188 * Create a new resizable component
\r
35189 * @param {Mixed} el The id or element to resize
\r
35190 * @param {Object} config configuration options
\r
35192 Ext.Resizable = function(el, config){
\r
35193 this.el = Ext.get(el);
\r
35195 if(config && config.wrap){
\r
35196 config.resizeChild = this.el;
\r
35197 this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : {cls:'xresizable-wrap'});
\r
35198 this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
\r
35199 this.el.setStyle('overflow', 'hidden');
\r
35200 this.el.setPositioning(config.resizeChild.getPositioning());
\r
35201 config.resizeChild.clearPositioning();
\r
35202 if(!config.width || !config.height){
\r
35203 var csize = config.resizeChild.getSize();
\r
35204 this.el.setSize(csize.width, csize.height);
\r
35206 if(config.pinned && !config.adjustments){
\r
35207 config.adjustments = 'auto';
\r
35212 * The proxy Element that is resized in place of the real Element during the resize operation.
\r
35213 * This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
\r
35215 * @type Ext.Element.
\r
35216 * @property proxy
\r
35218 this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
\r
35219 this.proxy.unselectable();
\r
35220 this.proxy.enableDisplayMode('block');
\r
35222 Ext.apply(this, config);
\r
35225 this.disableTrackOver = true;
\r
35226 this.el.addClass('x-resizable-pinned');
\r
35228 // if the element isn't positioned, make it relative
\r
35229 var position = this.el.getStyle('position');
\r
35230 if(position != 'absolute' && position != 'fixed'){
\r
35231 this.el.setStyle('position', 'relative');
\r
35233 if(!this.handles){ // no handles passed, must be legacy style
\r
35234 this.handles = 's,e,se';
\r
35235 if(this.multiDirectional){
\r
35236 this.handles += ',n,w';
\r
35239 if(this.handles == 'all'){
\r
35240 this.handles = 'n s e w ne nw se sw';
\r
35242 var hs = this.handles.split(/\s*?[,;]\s*?| /);
\r
35243 var ps = Ext.Resizable.positions;
\r
35244 for(var i = 0, len = hs.length; i < len; i++){
\r
35245 if(hs[i] && ps[hs[i]]){
\r
35246 var pos = ps[hs[i]];
\r
35247 this[pos] = new Ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent);
\r
35251 this.corner = this.southeast;
\r
35253 if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
\r
35254 this.updateBox = true;
\r
35257 this.activeHandle = null;
\r
35259 if(this.resizeChild){
\r
35260 if(typeof this.resizeChild == 'boolean'){
\r
35261 this.resizeChild = Ext.get(this.el.dom.firstChild, true);
\r
35263 this.resizeChild = Ext.get(this.resizeChild, true);
\r
35267 if(this.adjustments == 'auto'){
\r
35268 var rc = this.resizeChild;
\r
35269 var hw = this.west, he = this.east, hn = this.north, hs = this.south;
\r
35270 if(rc && (hw || hn)){
\r
35271 rc.position('relative');
\r
35272 rc.setLeft(hw ? hw.el.getWidth() : 0);
\r
35273 rc.setTop(hn ? hn.el.getHeight() : 0);
\r
35275 this.adjustments = [
\r
35276 (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
\r
35277 (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
\r
35281 if(this.draggable){
\r
35282 this.dd = this.dynamic ?
\r
35283 this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
\r
35284 this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
\r
35289 * @event beforeresize
\r
35290 * Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
\r
35291 * @param {Ext.Resizable} this
\r
35292 * @param {Ext.EventObject} e The mousedown event
\r
35297 * Fired after a resize.
\r
35298 * @param {Ext.Resizable} this
\r
35299 * @param {Number} width The new width
\r
35300 * @param {Number} height The new height
\r
35301 * @param {Ext.EventObject} e The mouseup event
\r
35306 if(this.width !== null && this.height !== null){
\r
35307 this.resizeTo(this.width, this.height);
\r
35309 this.updateChildSize();
\r
35312 this.el.dom.style.zoom = 1;
\r
35314 Ext.Resizable.superclass.constructor.call(this);
\r
35317 Ext.extend(Ext.Resizable, Ext.util.Observable, {
\r
35320 * @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be <b>added</b> to the
\r
35321 * resize operation's new size (defaults to <tt>[0, 0]</tt>)
\r
35323 adjustments : [0, 0],
\r
35325 * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
\r
35329 * @cfg {Mixed} constrainTo Constrain the resize to a particular element
\r
35332 * @cfg {Boolean} disableTrackOver True to disable mouse tracking. This is only applied at config time. (defaults to false)
\r
35334 disableTrackOver : false,
\r
35336 * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
\r
35338 draggable: false,
\r
35340 * @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
\r
35344 * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
\r
35348 * @cfg {String} easing Animation easing if animate = true (defaults to <tt>'easingOutStrong'</tt>)
\r
35350 easing : 'easeOutStrong',
\r
35352 * @cfg {Boolean} enabled False to disable resizing (defaults to true)
\r
35356 * @property enabled Writable. False if resizing is disabled.
\r
35360 * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
\r
35361 * Specify either <tt>'all'</tt> or any of <tt>'n s e w ne nw se sw'</tt>.
\r
35365 * @cfg {Boolean} multiDirectional <b>Deprecated</b>. Deprecated style of adding multi-direction resize handles.
\r
35367 multiDirectional : false,
\r
35369 * @cfg {Number} height The height of the element in pixels (defaults to null)
\r
35373 * @cfg {Number} width The width of the element in pixels (defaults to null)
\r
35377 * @cfg {Number} heightIncrement The increment to snap the height resize in pixels
\r
35378 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
\r
35380 heightIncrement : 0,
\r
35382 * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
\r
35383 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
\r
35385 widthIncrement : 0,
\r
35387 * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
\r
35391 * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
\r
35395 * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
\r
35397 maxHeight : 10000,
\r
35399 * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
\r
35401 maxWidth : 10000,
\r
35403 * @cfg {Number} minX The minimum x for the element (defaults to 0)
\r
35407 * @cfg {Number} minY The minimum x for the element (defaults to 0)
\r
35411 * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
\r
35412 * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
\r
35416 * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
\r
35417 * and width during resize (defaults to false)
\r
35419 preserveRatio : false,
\r
35421 * @cfg {Boolean/String/Element} resizeChild True to resize the first child, or id/element to resize (defaults to false)
\r
35423 resizeChild : false,
\r
35425 * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
\r
35427 transparent: false,
\r
35429 * @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
\r
35432 * @cfg {Boolean} wrap True to wrap an element with a div if needed (required for textareas and images, defaults to false)
\r
35433 * in favor of the handles config option (defaults to false)
\r
35438 * Perform a manual resize and fires the 'resize' event.
\r
35439 * @param {Number} width
\r
35440 * @param {Number} height
\r
35442 resizeTo : function(width, height){
\r
35443 this.el.setSize(width, height);
\r
35444 this.updateChildSize();
\r
35445 this.fireEvent('resize', this, width, height, null);
\r
35449 startSizing : function(e, handle){
\r
35450 this.fireEvent('beforeresize', this, e);
\r
35451 if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
\r
35453 if(!this.overlay){
\r
35454 this.overlay = this.el.createProxy({tag: 'div', cls: 'x-resizable-overlay', html: ' '}, Ext.getBody());
\r
35455 this.overlay.unselectable();
\r
35456 this.overlay.enableDisplayMode('block');
\r
35457 this.overlay.on({
\r
35459 mousemove: this.onMouseMove,
\r
35460 mouseup: this.onMouseUp
\r
35463 this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));
\r
35465 this.resizing = true;
\r
35466 this.startBox = this.el.getBox();
\r
35467 this.startPoint = e.getXY();
\r
35468 this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
\r
35469 (this.startBox.y + this.startBox.height) - this.startPoint[1]];
\r
35471 this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
\r
35472 this.overlay.show();
\r
35474 if(this.constrainTo) {
\r
35475 var ct = Ext.get(this.constrainTo);
\r
35476 this.resizeRegion = ct.getRegion().adjust(
\r
35477 ct.getFrameWidth('t'),
\r
35478 ct.getFrameWidth('l'),
\r
35479 -ct.getFrameWidth('b'),
\r
35480 -ct.getFrameWidth('r')
\r
35484 this.proxy.setStyle('visibility', 'hidden'); // workaround display none
\r
35485 this.proxy.show();
\r
35486 this.proxy.setBox(this.startBox);
\r
35487 if(!this.dynamic){
\r
35488 this.proxy.setStyle('visibility', 'visible');
\r
35494 onMouseDown : function(handle, e){
\r
35495 if(this.enabled){
\r
35497 this.activeHandle = handle;
\r
35498 this.startSizing(e, handle);
\r
35503 onMouseUp : function(e){
\r
35504 this.activeHandle = null;
\r
35505 var size = this.resizeElement();
\r
35506 this.resizing = false;
\r
35507 this.handleOut();
\r
35508 this.overlay.hide();
\r
35509 this.proxy.hide();
\r
35510 this.fireEvent('resize', this, size.width, size.height, e);
\r
35514 updateChildSize : function(){
\r
35515 if(this.resizeChild){
\r
35516 var el = this.el;
\r
35517 var child = this.resizeChild;
\r
35518 var adj = this.adjustments;
\r
35519 if(el.dom.offsetWidth){
\r
35520 var b = el.getSize(true);
\r
35521 child.setSize(b.width+adj[0], b.height+adj[1]);
\r
35523 // Second call here for IE
\r
35524 // The first call enables instant resizing and
\r
35525 // the second call corrects scroll bars if they
\r
35528 setTimeout(function(){
\r
35529 if(el.dom.offsetWidth){
\r
35530 var b = el.getSize(true);
\r
35531 child.setSize(b.width+adj[0], b.height+adj[1]);
\r
35539 snap : function(value, inc, min){
\r
35540 if(!inc || !value){
\r
35543 var newValue = value;
\r
35544 var m = value % inc;
\r
35547 newValue = value + (inc-m);
\r
35549 newValue = value - m;
\r
35552 return Math.max(min, newValue);
\r
35556 * <p>Performs resizing of the associated Element. This method is called internally by this
\r
35557 * class, and should not be called by user code.</p>
\r
35558 * <p>If a Resizable is being used to resize an Element which encapsulates a more complex UI
\r
35559 * component such as a Panel, this method may be overridden by specifying an implementation
\r
35560 * as a config option to provide appropriate behaviour at the end of the resize operation on
\r
35561 * mouseup, for example resizing the Panel, and relaying the Panel's content.</p>
\r
35562 * <p>The new area to be resized to is available by examining the state of the {@link #proxy}
\r
35563 * Element. Example:
\r
35566 title: 'Resize me',
\r
35569 renderTo: Ext.getBody(),
\r
35575 render: function(p) {
\r
35576 new Ext.Resizable(p.getEl(), {
\r
35579 transparent: true,
\r
35580 resizeElement: function() {
\r
35581 var box = this.proxy.getBox();
\r
35582 p.updateBox(box);
\r
35594 resizeElement : function(){
\r
35595 var box = this.proxy.getBox();
\r
35596 if(this.updateBox){
\r
35597 this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
\r
35599 this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
\r
35601 this.updateChildSize();
\r
35602 if(!this.dynamic){
\r
35603 this.proxy.hide();
\r
35609 constrain : function(v, diff, m, mx){
\r
35610 if(v - diff < m){
\r
35612 }else if(v - diff > mx){
\r
35619 onMouseMove : function(e){
\r
35620 if(this.enabled && this.activeHandle){
\r
35621 try{// try catch so if something goes wrong the user doesn't get hung
\r
35623 if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
\r
35627 //var curXY = this.startPoint;
\r
35628 var curSize = this.curSize || this.startBox,
\r
35629 x = this.startBox.x, y = this.startBox.y,
\r
35632 w = curSize.width,
\r
35633 h = curSize.height,
\r
35636 mw = this.minWidth,
\r
35637 mh = this.minHeight,
\r
35638 mxw = this.maxWidth,
\r
35639 mxh = this.maxHeight,
\r
35640 wi = this.widthIncrement,
\r
35641 hi = this.heightIncrement,
\r
35642 eventXY = e.getXY(),
\r
35643 diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
\r
35644 diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
\r
35645 pos = this.activeHandle.position,
\r
35652 w = Math.min(Math.max(mw, w), mxw);
\r
35656 h = Math.min(Math.max(mh, h), mxh);
\r
35658 case 'southeast':
\r
35661 w = Math.min(Math.max(mw, w), mxw);
\r
35662 h = Math.min(Math.max(mh, h), mxh);
\r
35665 diffY = this.constrain(h, diffY, mh, mxh);
\r
35670 diffX = this.constrain(w, diffX, mw, mxw);
\r
35674 case 'northeast':
\r
35676 w = Math.min(Math.max(mw, w), mxw);
\r
35677 diffY = this.constrain(h, diffY, mh, mxh);
\r
35681 case 'northwest':
\r
35682 diffX = this.constrain(w, diffX, mw, mxw);
\r
35683 diffY = this.constrain(h, diffY, mh, mxh);
\r
35689 case 'southwest':
\r
35690 diffX = this.constrain(w, diffX, mw, mxw);
\r
35692 h = Math.min(Math.max(mh, h), mxh);
\r
35698 var sw = this.snap(w, wi, mw);
\r
35699 var sh = this.snap(h, hi, mh);
\r
35700 if(sw != w || sh != h){
\r
35702 case 'northeast':
\r
35708 case 'southwest':
\r
35714 case 'northwest':
\r
35723 if(this.preserveRatio){
\r
35725 case 'southeast':
\r
35728 h = Math.min(Math.max(mh, h), mxh);
\r
35733 w = Math.min(Math.max(mw, w), mxw);
\r
35736 case 'northeast':
\r
35738 w = Math.min(Math.max(mw, w), mxw);
\r
35744 w = Math.min(Math.max(mw, w), mxw);
\r
35746 x += (tw - w) / 2;
\r
35748 case 'southwest':
\r
35750 h = Math.min(Math.max(mh, h), mxh);
\r
35758 h = Math.min(Math.max(mh, h), mxh);
\r
35759 y += (th - h) / 2;
\r
35764 case 'northwest':
\r
35768 h = Math.min(Math.max(mh, h), mxh);
\r
35776 this.proxy.setBounds(x, y, w, h);
\r
35777 if(this.dynamic){
\r
35778 this.resizeElement();
\r
35785 handleOver : function(){
\r
35786 if(this.enabled){
\r
35787 this.el.addClass('x-resizable-over');
\r
35792 handleOut : function(){
\r
35793 if(!this.resizing){
\r
35794 this.el.removeClass('x-resizable-over');
\r
35799 * Returns the element this component is bound to.
\r
35800 * @return {Ext.Element}
\r
35802 getEl : function(){
\r
35807 * Returns the resizeChild element (or null).
\r
35808 * @return {Ext.Element}
\r
35810 getResizeChild : function(){
\r
35811 return this.resizeChild;
\r
35815 * Destroys this resizable. If the element was wrapped and
\r
35816 * removeEl is not true then the element remains.
\r
35817 * @param {Boolean} removeEl (optional) true to remove the element from the DOM
\r
35819 destroy : function(removeEl){
\r
35820 Ext.destroy(this.dd, this.overlay, this.proxy);
\r
35821 this.overlay = null;
\r
35822 this.proxy = null;
\r
35824 var ps = Ext.Resizable.positions;
\r
35825 for(var k in ps){
\r
35826 if(typeof ps[k] != 'function' && this[ps[k]]){
\r
35827 this[ps[k]].destroy();
\r
35831 this.el.update('');
\r
35832 Ext.destroy(this.el);
\r
35835 this.purgeListeners();
\r
35838 syncHandleHeight : function(){
\r
35839 var h = this.el.getHeight(true);
\r
35841 this.west.el.setHeight(h);
\r
35844 this.east.el.setHeight(h);
\r
35850 // hash to map config positions to true positions
\r
35851 Ext.Resizable.positions = {
\r
35852 n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
\r
35856 Ext.Resizable.Handle = function(rz, pos, disableTrackOver, transparent){
\r
35858 // only initialize the template if resizable is used
\r
35859 var tpl = Ext.DomHelper.createTemplate(
\r
35860 {tag: 'div', cls: 'x-resizable-handle x-resizable-handle-{0}'}
\r
35863 Ext.Resizable.Handle.prototype.tpl = tpl;
\r
35865 this.position = pos;
\r
35867 this.el = this.tpl.append(rz.el.dom, [this.position], true);
\r
35868 this.el.unselectable();
\r
35870 this.el.setOpacity(0);
\r
35872 this.el.on('mousedown', this.onMouseDown, this);
\r
35873 if(!disableTrackOver){
\r
35876 mouseover: this.onMouseOver,
\r
35877 mouseout: this.onMouseOut
\r
35883 Ext.Resizable.Handle.prototype = {
\r
35885 afterResize : function(rz){
\r
35889 onMouseDown : function(e){
\r
35890 this.rz.onMouseDown(this, e);
\r
35893 onMouseOver : function(e){
\r
35894 this.rz.handleOver(this, e);
\r
35897 onMouseOut : function(e){
\r
35898 this.rz.handleOut(this, e);
\r
35901 destroy : function(){
\r
35902 Ext.destroy(this.el);
\r
35907 * @class Ext.Window
35908 * @extends Ext.Panel
35909 * <p>A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
35910 * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport,
35911 * restored to their prior size, and can be {@link #minimize}d.</p>
35912 * <p>Windows can also be linked to a {@link Ext.WindowGroup} or managed by the {@link Ext.WindowMgr} to provide
35913 * grouping, activation, to front, to back and other application-specific behavior.</p>
35914 * <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
35915 * specify {@link Ext.Component#renderTo renderTo}.</p>
35916 * <p><b>Note:</b> By default, the <code>{@link #closable close}</code> header tool <i>destroys</i> the Window resulting in
35917 * destruction of any child Components. This makes the Window object, and all its descendants <b>unusable</b>. To enable
35918 * re-use of a Window, use <b><code>{@link #closeAction closeAction: 'hide'}</code></b>.</p>
35920 * @param {Object} config The config object
35923 Ext.Window = Ext.extend(Ext.Panel, {
35926 * The X position of the left edge of the window on initial showing. Defaults to centering the Window within
35927 * the width of the Window's container {@link Ext.Element Element) (The Element that the Window is rendered to).
35931 * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within
35932 * the height of the Window's container {@link Ext.Element Element) (The Element that the Window is rendered to).
35935 * @cfg {Boolean} modal
35936 * True to make the window modal and mask everything behind it when displayed, false to display it without
35937 * restricting access to other UI elements (defaults to false).
35940 * @cfg {String/Element} animateTarget
35941 * Id or element from which the window should animate while opening (defaults to null with no animation).
35944 * @cfg {String} resizeHandles
35945 * A valid {@link Ext.Resizable} handles config string (defaults to 'all'). Only applies when resizable = true.
35948 * @cfg {Ext.WindowGroup} manager
35949 * A reference to the WindowGroup that should manage this window (defaults to {@link Ext.WindowMgr}).
35952 * @cfg {String/Number/Button} defaultButton
35953 * The id / index of a button or a button instance to focus when this window received the focus.
35956 * @cfg {Function} onEsc
35957 * Allows override of the built-in processing for the escape key. Default action
35958 * is to close the Window (performing whatever action is specified in {@link #closeAction}.
35959 * To prevent the Window closing when the escape key is pressed, specify this as
35960 * Ext.emptyFn (See {@link Ext#emptyFn}).
35963 * @cfg {Boolean} collapsed
35964 * True to render the window collapsed, false to render it expanded (defaults to false). Note that if
35965 * {@link #expandOnShow} is true (the default) it will override the <tt>collapsed</tt> config and the window
35966 * will always be expanded when shown.
35969 * @cfg {Boolean} maximized
35970 * True to initially display the window in a maximized state. (Defaults to false).
35974 * @cfg {String} baseCls
35975 * The base CSS class to apply to this panel's element (defaults to 'x-window').
35977 baseCls : 'x-window',
35979 * @cfg {Boolean} resizable
35980 * True to allow user resizing at each edge and corner of the window, false to disable resizing (defaults to true).
35984 * @cfg {Boolean} draggable
35985 * True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true). Note
35986 * that by default the window will be centered in the viewport, so if dragging is disabled the window may need
35987 * to be positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
35991 * @cfg {Boolean} closable
35992 * <p>True to display the 'close' tool button and allow the user to close the window, false to
35993 * hide the button and disallow closing the window (defaults to true).</p>
35994 * <p>By default, when close is requested by either clicking the close button in the header
35995 * or pressing ESC when the Window has focus, the {@link #close} method will be called. This
35996 * will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
35997 * it may not be reused.</p>
35998 * <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
35999 * {@link #closeAction} to 'hide'.
36003 * @cfg {String} closeAction
36004 * <p>The action to take when the close header tool is clicked:
36005 * <div class="mdetail-params"><ul>
36006 * <li><b><code>'{@link #close}'</code></b> : <b>Default</b><div class="sub-desc">
36007 * {@link #close remove} the window from the DOM and {@link Ext.Component#destroy destroy}
36008 * it and all descendant Components. The window will <b>not</b> be available to be
36009 * redisplayed via the {@link #show} method.
36011 * <li><b><code>'{@link #hide}'</code></b> : <div class="sub-desc">
36012 * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
36013 * The window will be available to be redisplayed via the {@link #show} method.
36016 * <p><b>Note:</b> This setting does not affect the {@link #close} method
36017 * which will always {@link Ext.Component#destroy destroy} the window. To
36018 * programatically <i>hide</i> a window, call {@link #hide}.</p>
36020 closeAction : 'close',
36022 * @cfg {Boolean} constrain
36023 * True to constrain the window within its containing element, false to allow it to fall outside of its
36024 * containing element. By default the window will be rendered to document.body. To render and constrain the
36025 * window within another element specify {@link #renderTo}.
36026 * (defaults to false). Optionally the header only can be constrained using {@link #constrainHeader}.
36030 * @cfg {Boolean} constrainHeader
36031 * True to constrain the window header within its containing element (allowing the window body to fall outside
36032 * of its containing element) or false to allow the header to fall outside its containing element (defaults to
36033 * false). Optionally the entire window can be constrained using {@link #constrain}.
36035 constrainHeader : false,
36037 * @cfg {Boolean} plain
36038 * True to render the window body with a transparent background so that it will blend into the framing
36039 * elements, false to add a lighter background color to visually highlight the body element and separate it
36040 * more distinctly from the surrounding frame (defaults to false).
36044 * @cfg {Boolean} minimizable
36045 * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
36046 * and disallow minimizing the window (defaults to false). Note that this button provides no implementation --
36047 * the behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a
36048 * custom minimize behavior implemented for this option to be useful.
36050 minimizable : false,
36052 * @cfg {Boolean} maximizable
36053 * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
36054 * and disallow maximizing the window (defaults to false). Note that when a window is maximized, the tool button
36055 * will automatically change to a 'restore' button with the appropriate behavior already built-in that will
36056 * restore the window to its previous size.
36058 maximizable : false,
36060 * @cfg {Number} minHeight
36061 * The minimum height in pixels allowed for this window (defaults to 100). Only applies when resizable = true.
36065 * @cfg {Number} minWidth
36066 * The minimum width in pixels allowed for this window (defaults to 200). Only applies when resizable = true.
36070 * @cfg {Boolean} expandOnShow
36071 * True to always expand the window when it is displayed, false to keep it in its current state (which may be
36072 * {@link #collapsed}) when displayed (defaults to true).
36074 expandOnShow : true,
36076 // inherited docs, same default
36077 collapsible : false,
36080 * @cfg {Boolean} initHidden
36081 * True to hide the window until show() is explicitly called (defaults to true).
36085 * @cfg {Boolean} monitorResize @hide
36086 * This is automatically managed based on the value of constrain and constrainToHeader
36088 monitorResize : true,
36090 // The following configs are set to provide the basic functionality of a window.
36091 // Changing them would require additional code to handle correctly and should
36092 // usually only be done in subclasses that can provide custom behavior. Changing them
36093 // may have unexpected or undesirable results.
36094 /** @cfg {String} elements @hide */
36095 elements : 'header,body',
36096 /** @cfg {Boolean} frame @hide */
36098 /** @cfg {Boolean} floating @hide */
36102 initComponent : function(){
36103 Ext.Window.superclass.initComponent.call(this);
36107 * Fires after the window has been visually activated via {@link setActive}.
36108 * @param {Ext.Window} this
36111 * @event deactivate
36112 * Fires after the window has been visually deactivated via {@link setActive}.
36113 * @param {Ext.Window} this
36117 * Fires after the window has been resized.
36118 * @param {Ext.Window} this
36119 * @param {Number} width The window's new width
36120 * @param {Number} height The window's new height
36125 * Fires after the window has been maximized.
36126 * @param {Ext.Window} this
36131 * Fires after the window has been minimized.
36132 * @param {Ext.Window} this
36137 * Fires after the window has been restored to its original size after being maximized.
36138 * @param {Ext.Window} this
36142 if(this.initHidden === false){
36145 this.hidden = true;
36150 getState : function(){
36151 return Ext.apply(Ext.Window.superclass.getState.call(this) || {}, this.getBox(true));
36155 onRender : function(ct, position){
36156 Ext.Window.superclass.onRender.call(this, ct, position);
36159 this.el.addClass('x-window-plain');
36162 // this element allows the Window to be focused for keyboard events
36163 this.focusEl = this.el.createChild({
36164 tag: 'a', href:'#', cls:'x-dlg-focus',
36165 tabIndex:'-1', html: ' '});
36166 this.focusEl.swallowEvent('click', true);
36168 this.proxy = this.el.createProxy('x-window-proxy');
36169 this.proxy.enableDisplayMode('block');
36172 this.mask = this.container.createChild({cls:'ext-el-mask'}, this.el.dom);
36173 this.mask.enableDisplayMode('block');
36175 this.mon(this.mask, 'click', this.focus, this);
36181 initEvents : function(){
36182 Ext.Window.superclass.initEvents.call(this);
36183 if(this.animateTarget){
36184 this.setAnimateTarget(this.animateTarget);
36187 if(this.resizable){
36188 this.resizer = new Ext.Resizable(this.el, {
36189 minWidth: this.minWidth,
36190 minHeight:this.minHeight,
36191 handles: this.resizeHandles || 'all',
36193 resizeElement : this.resizerAction
36195 this.resizer.window = this;
36196 this.mon(this.resizer, 'beforeresize', this.beforeResize, this);
36199 if(this.draggable){
36200 this.header.addClass('x-window-draggable');
36202 this.mon(this.el, 'mousedown', this.toFront, this);
36203 this.manager = this.manager || Ext.WindowMgr;
36204 this.manager.register(this);
36205 if(this.maximized){
36206 this.maximized = false;
36210 var km = this.getKeyMap();
36211 km.on(27, this.onEsc, this);
36216 initDraggable : function(){
36218 * If this Window is configured {@link #draggable}, this property will contain
36219 * an instance of {@link Ext.dd.DD} which handles dragging the Window's DOM Element.
36223 this.dd = new Ext.Window.DD(this);
36227 onEsc : function(){
36228 this[this.closeAction]();
36232 beforeDestroy : function(){
36233 if (this.rendered){
36236 Ext.EventManager.removeResizeListener(this.doAnchor, this);
36237 Ext.EventManager.un(window, 'scroll', this.doAnchor, this);
36247 Ext.Window.superclass.beforeDestroy.call(this);
36251 onDestroy : function(){
36253 this.manager.unregister(this);
36255 Ext.Window.superclass.onDestroy.call(this);
36259 initTools : function(){
36260 if(this.minimizable){
36263 handler: this.minimize.createDelegate(this, [])
36266 if(this.maximizable){
36269 handler: this.maximize.createDelegate(this, [])
36273 handler: this.restore.createDelegate(this, []),
36276 this.mon(this.header, 'dblclick', this.toggleMaximize, this);
36281 handler: this[this.closeAction].createDelegate(this, [])
36287 resizerAction : function(){
36288 var box = this.proxy.getBox();
36290 this.window.handleResize(box);
36295 beforeResize : function(){
36296 this.resizer.minHeight = Math.max(this.minHeight, this.getFrameHeight() + 40); // 40 is a magic minimum content size?
36297 this.resizer.minWidth = Math.max(this.minWidth, this.getFrameWidth() + 40);
36298 this.resizeBox = this.el.getBox();
36302 updateHandles : function(){
36303 if(Ext.isIE && this.resizer){
36304 this.resizer.syncHandleHeight();
36310 handleResize : function(box){
36311 var rz = this.resizeBox;
36312 if(rz.x != box.x || rz.y != box.y){
36313 this.updateBox(box);
36318 this.updateHandles();
36321 this.fireEvent('resize', this, box.width, box.height);
36325 * Focuses the window. If a defaultButton is set, it will receive focus, otherwise the
36326 * window itself will receive focus.
36328 focus : function(){
36329 var f = this.focusEl, db = this.defaultButton, t = typeof db;
36330 if(t != 'undefined'){
36331 if(t == 'number' && this.fbar){
36332 f = this.fbar.items.get(db);
36333 }else if(t == 'string'){
36334 f = Ext.getCmp(db);
36339 f = f || this.focusEl;
36340 f.focus.defer(10, f);
36344 * Sets the target element from which the window should animate while opening.
36345 * @param {String/Element} el The target element or id
36347 setAnimateTarget : function(el){
36349 this.animateTarget = el;
36353 beforeShow : function(){
36354 delete this.el.lastXY;
36355 delete this.el.lastLT;
36356 if(this.x === undefined || this.y === undefined){
36357 var xy = this.el.getAlignToXY(this.container, 'c-c');
36358 var pos = this.el.translatePoints(xy[0], xy[1]);
36359 this.x = this.x === undefined? pos.left : this.x;
36360 this.y = this.y === undefined? pos.top : this.y;
36362 this.el.setLeftTop(this.x, this.y);
36364 if(this.expandOnShow){
36365 this.expand(false);
36369 Ext.getBody().addClass('x-body-masked');
36370 this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
36376 * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
36377 * @param {String/Element} animateTarget (optional) The target element or id from which the window should
36378 * animate while opening (defaults to null with no animation)
36379 * @param {Function} callback (optional) A callback function to call after the window is displayed
36380 * @param {Object} scope (optional) The scope in which to execute the callback
36381 * @return {Ext.Window} this
36383 show : function(animateTarget, cb, scope){
36384 if(!this.rendered){
36385 this.render(Ext.getBody());
36387 if(this.hidden === false){
36391 if(this.fireEvent('beforeshow', this) === false){
36395 this.on('show', cb, scope, {single:true});
36397 this.hidden = false;
36398 if(animateTarget !== undefined){
36399 this.setAnimateTarget(animateTarget);
36402 if(this.animateTarget){
36411 afterShow : function(isAnim){
36413 this.el.setStyle('display', 'block');
36415 if(this.maximized){
36416 this.fitContainer();
36418 if(Ext.isMac && Ext.isGecko){ // work around stupid FF 2.0/Mac scroll bar bug
36419 this.cascade(this.setAutoScroll);
36422 if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
36423 Ext.EventManager.onWindowResize(this.onWindowResize, this);
36425 this.doConstrain();
36428 this.keyMap.enable();
36431 this.updateHandles();
36432 if(isAnim && (Ext.isIE || Ext.isWebKit)){
36433 var sz = this.getSize();
36434 this.onResize(sz.width, sz.height);
36436 this.fireEvent('show', this);
36440 animShow : function(){
36442 this.proxy.setBox(this.animateTarget.getBox());
36443 this.proxy.setOpacity(0);
36444 var b = this.getBox(false);
36445 b.callback = this.afterShow.createDelegate(this, [true], false);
36448 b.easing = 'easeNone';
36451 this.el.setStyle('display', 'none');
36452 this.proxy.shift(b);
36456 * Hides the window, setting it to invisible and applying negative offsets.
36457 * @param {String/Element} animateTarget (optional) The target element or id to which the window should
36458 * animate while hiding (defaults to null with no animation)
36459 * @param {Function} callback (optional) A callback function to call after the window is hidden
36460 * @param {Object} scope (optional) The scope in which to execute the callback
36461 * @return {Ext.Window} this
36463 hide : function(animateTarget, cb, scope){
36464 if(this.hidden || this.fireEvent('beforehide', this) === false){
36468 this.on('hide', cb, scope, {single:true});
36470 this.hidden = true;
36471 if(animateTarget !== undefined){
36472 this.setAnimateTarget(animateTarget);
36476 Ext.getBody().removeClass('x-body-masked');
36478 if(this.animateTarget){
36488 afterHide : function(){
36490 if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
36491 Ext.EventManager.removeResizeListener(this.onWindowResize, this);
36494 this.keyMap.disable();
36496 this.fireEvent('hide', this);
36500 animHide : function(){
36501 this.proxy.setOpacity(0.5);
36503 var tb = this.getBox(false);
36504 this.proxy.setBox(tb);
36506 var b = this.animateTarget.getBox();
36507 b.callback = this.afterHide;
36510 b.easing = 'easeNone';
36513 this.proxy.shift(b);
36517 onWindowResize : function(){
36518 if(this.maximized){
36519 this.fitContainer();
36522 this.mask.setSize('100%', '100%');
36523 var force = this.mask.dom.offsetHeight;
36524 this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
36526 this.doConstrain();
36530 doConstrain : function(){
36531 if(this.constrain || this.constrainHeader){
36533 if(this.constrain){
36535 right:this.el.shadowOffset,
36536 left:this.el.shadowOffset,
36537 bottom:this.el.shadowOffset
36540 var s = this.getSize();
36542 right:-(s.width - 100),
36543 bottom:-(s.height - 25)
36547 var xy = this.el.getConstrainToXY(this.container, true, offsets);
36549 this.setPosition(xy[0], xy[1]);
36554 // private - used for dragging
36555 ghost : function(cls){
36556 var ghost = this.createGhost(cls);
36557 var box = this.getBox(true);
36558 ghost.setLeftTop(box.x, box.y);
36559 ghost.setWidth(box.width);
36561 this.activeGhost = ghost;
36566 unghost : function(show, matchPosition){
36567 if(!this.activeGhost) {
36570 if(show !== false){
36573 if(Ext.isMac && Ext.isGecko){ // work around stupid FF 2.0/Mac scroll bar bug
36574 this.cascade(this.setAutoScroll);
36577 if(matchPosition !== false){
36578 this.setPosition(this.activeGhost.getLeft(true), this.activeGhost.getTop(true));
36580 this.activeGhost.hide();
36581 this.activeGhost.remove();
36582 delete this.activeGhost;
36586 * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
36587 * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior,
36588 * either the minimize event can be handled or this method can be overridden.
36589 * @return {Ext.Window} this
36591 minimize : function(){
36592 this.fireEvent('minimize', this);
36597 * <p>Closes the Window, removes it from the DOM, {@link Ext.Component#destroy destroy}s
36598 * the Window object and all its descendant Components. The {@link Ext.Panel#beforeclose beforeclose}
36599 * event is fired before the close happens and will cancel the close action if it returns false.<p>
36600 * <p><b>Note:</b> This method is not affected by the {@link #closeAction} setting which
36601 * only affects the action triggered when clicking the {@link #closable 'close' tool in the header}.
36602 * To hide the Window without destroying it, call {@link #hide}.</p>
36604 close : function(){
36605 if(this.fireEvent('beforeclose', this) !== false){
36606 this.hide(null, function(){
36607 this.fireEvent('close', this);
36614 * Fits the window within its current container and automatically replaces
36615 * the {@link #maximizable 'maximize' tool button} with the 'restore' tool button.
36616 * Also see {@link #toggleMaximize}.
36617 * @return {Ext.Window} this
36619 maximize : function(){
36620 if(!this.maximized){
36621 this.expand(false);
36622 this.restoreSize = this.getSize();
36623 this.restorePos = this.getPosition(true);
36624 if (this.maximizable){
36625 this.tools.maximize.hide();
36626 this.tools.restore.show();
36628 this.maximized = true;
36629 this.el.disableShadow();
36634 if(this.collapsible){
36635 this.tools.toggle.hide();
36637 this.el.addClass('x-window-maximized');
36638 this.container.addClass('x-window-maximized-ct');
36640 this.setPosition(0, 0);
36641 this.fitContainer();
36642 this.fireEvent('maximize', this);
36648 * Restores a {@link #maximizable maximized} window back to its original
36649 * size and position prior to being maximized and also replaces
36650 * the 'restore' tool button with the 'maximize' tool button.
36651 * Also see {@link #toggleMaximize}.
36652 * @return {Ext.Window} this
36654 restore : function(){
36655 if(this.maximized){
36656 this.el.removeClass('x-window-maximized');
36657 this.tools.restore.hide();
36658 this.tools.maximize.show();
36659 this.setPosition(this.restorePos[0], this.restorePos[1]);
36660 this.setSize(this.restoreSize.width, this.restoreSize.height);
36661 delete this.restorePos;
36662 delete this.restoreSize;
36663 this.maximized = false;
36664 this.el.enableShadow(true);
36669 if(this.collapsible){
36670 this.tools.toggle.show();
36672 this.container.removeClass('x-window-maximized-ct');
36674 this.doConstrain();
36675 this.fireEvent('restore', this);
36681 * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
36682 * state of the window.
36683 * @return {Ext.Window} this
36685 toggleMaximize : function(){
36686 return this[this.maximized ? 'restore' : 'maximize']();
36690 fitContainer : function(){
36691 var vs = this.container.getViewSize();
36692 this.setSize(vs.width, vs.height);
36696 // z-index is managed by the WindowManager and may be overwritten at any time
36697 setZIndex : function(index){
36699 this.mask.setStyle('z-index', index);
36701 this.el.setZIndex(++index);
36705 this.resizer.proxy.setStyle('z-index', ++index);
36708 this.lastZIndex = index;
36712 * Aligns the window to the specified element
36713 * @param {Mixed} element The element to align to.
36714 * @param {String} position The position to align to (see {@link Ext.Element#alignTo} for more details).
36715 * @param {Array} offsets (optional) Offset the positioning by [x, y]
36716 * @return {Ext.Window} this
36718 alignTo : function(element, position, offsets){
36719 var xy = this.el.getAlignToXY(element, position, offsets);
36720 this.setPagePosition(xy[0], xy[1]);
36725 * Anchors this window to another element and realigns it when the window is resized or scrolled.
36726 * @param {Mixed} element The element to align to.
36727 * @param {String} position The position to align to (see {@link Ext.Element#alignTo} for more details)
36728 * @param {Array} offsets (optional) Offset the positioning by [x, y]
36729 * @param {Boolean/Number} monitorScroll (optional) true to monitor body scroll and reposition. If this parameter
36730 * is a number, it is used as the buffer delay (defaults to 50ms).
36731 * @return {Ext.Window} this
36733 anchorTo : function(el, alignment, offsets, monitorScroll){
36735 Ext.EventManager.removeResizeListener(this.doAnchor, this);
36736 Ext.EventManager.un(window, 'scroll', this.doAnchor, this);
36738 this.doAnchor = function(){
36739 this.alignTo(el, alignment, offsets);
36741 Ext.EventManager.onWindowResize(this.doAnchor, this);
36743 var tm = typeof monitorScroll;
36744 if(tm != 'undefined'){
36745 Ext.EventManager.on(window, 'scroll', this.doAnchor, this,
36746 {buffer: tm == 'number' ? monitorScroll : 50});
36753 * Brings this window to the front of any other visible windows
36754 * @param {Boolean} e (optional) Specify <tt>false</tt> to prevent the window from being focused.
36755 * @return {Ext.Window} this
36757 toFront : function(e){
36758 if(this.manager.bringToFront(this)){
36759 if(!e || !e.getTarget().focus){
36767 * Makes this the active window by showing its shadow, or deactivates it by hiding its shadow. This method also
36768 * fires the {@link #activate} or {@link #deactivate} event depending on which action occurred.
36769 * @param {Boolean} active True to activate the window, false to deactivate it (defaults to false)
36771 setActive : function(active){
36773 if(!this.maximized){
36774 this.el.enableShadow(true);
36776 this.fireEvent('activate', this);
36778 this.el.disableShadow();
36779 this.fireEvent('deactivate', this);
36784 * Sends this window to the back of (lower z-index than) any other visible windows
36785 * @return {Ext.Window} this
36787 toBack : function(){
36788 this.manager.sendToBack(this);
36793 * Centers this window in the viewport
36794 * @return {Ext.Window} this
36796 center : function(){
36797 var xy = this.el.getAlignToXY(this.container, 'c-c');
36798 this.setPagePosition(xy[0], xy[1]);
36803 * @cfg {Boolean} autoWidth @hide
36806 Ext.reg('window', Ext.Window);
36808 // private - custom Window DD implementation
36809 Ext.Window.DD = function(win){
36811 Ext.Window.DD.superclass.constructor.call(this, win.el.id, 'WindowDD-'+win.id);
36812 this.setHandleElId(win.header.id);
36813 this.scroll = false;
36816 Ext.extend(Ext.Window.DD, Ext.dd.DD, {
36818 headerOffsets:[100, 25],
36819 startDrag : function(){
36821 this.proxy = w.ghost();
36822 if(w.constrain !== false){
36823 var so = w.el.shadowOffset;
36824 this.constrainTo(w.container, {right: so, left: so, bottom: so});
36825 }else if(w.constrainHeader !== false){
36826 var s = this.proxy.getSize();
36827 this.constrainTo(w.container, {right: -(s.width-this.headerOffsets[0]), bottom: -(s.height-this.headerOffsets[1])});
36830 b4Drag : Ext.emptyFn,
36832 onDrag : function(e){
36833 this.alignElWithMouse(this.proxy, e.getPageX(), e.getPageY());
36836 endDrag : function(e){
36837 this.win.unghost();
36838 this.win.saveState();
36842 * @class Ext.WindowGroup
36843 * An object that represents a group of {@link Ext.Window} instances and provides z-order management
36844 * and window activation behavior.
36847 Ext.WindowGroup = function(){
36849 var accessList = [];
36853 var sortWindows = function(d1, d2){
36854 return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
36858 var orderWindows = function(){
36859 var a = accessList, len = a.length;
36861 a.sort(sortWindows);
36862 var seed = a[0].manager.zseed;
36863 for(var i = 0; i < len; i++){
36865 if(win && !win.hidden){
36866 win.setZIndex(seed + (i*10));
36874 var setActiveWin = function(win){
36877 front.setActive(false);
36881 win.setActive(true);
36887 var activateLast = function(){
36888 for(var i = accessList.length-1; i >=0; --i) {
36889 if(!accessList[i].hidden){
36890 setActiveWin(accessList[i]);
36894 // none to activate
36895 setActiveWin(null);
36900 * The starting z-index for windows (defaults to 9000)
36901 * @type Number The z-index value
36906 register : function(win){
36907 list[win.id] = win;
36908 accessList.push(win);
36909 win.on('hide', activateLast);
36913 unregister : function(win){
36914 delete list[win.id];
36915 win.un('hide', activateLast);
36916 accessList.remove(win);
36920 * Gets a registered window by id.
36921 * @param {String/Object} id The id of the window or a {@link Ext.Window} instance
36922 * @return {Ext.Window}
36924 get : function(id){
36925 return typeof id == "object" ? id : list[id];
36929 * Brings the specified window to the front of any other active windows.
36930 * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
36931 * @return {Boolean} True if the dialog was brought to the front, else false
36932 * if it was already in front
36934 bringToFront : function(win){
36935 win = this.get(win);
36937 win._lastAccess = new Date().getTime();
36945 * Sends the specified window to the back of other active windows.
36946 * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
36947 * @return {Ext.Window} The window
36949 sendToBack : function(win){
36950 win = this.get(win);
36951 win._lastAccess = -(new Date().getTime());
36957 * Hides all windows in the group.
36959 hideAll : function(){
36960 for(var id in list){
36961 if(list[id] && typeof list[id] != "function" && list[id].isVisible()){
36968 * Gets the currently-active window in the group.
36969 * @return {Ext.Window} The active window
36971 getActive : function(){
36976 * Returns zero or more windows in the group using the custom search function passed to this method.
36977 * The function should accept a single {@link Ext.Window} reference as its only argument and should
36978 * return true if the window matches the search criteria, otherwise it should return false.
36979 * @param {Function} fn The search function
36980 * @param {Object} scope (optional) The scope in which to execute the function (defaults to the window
36981 * that gets passed to the function if not specified)
36982 * @return {Array} An array of zero or more matching windows
36984 getBy : function(fn, scope){
36986 for(var i = accessList.length-1; i >=0; --i) {
36987 var win = accessList[i];
36988 if(fn.call(scope||win, win) !== false){
36996 * Executes the specified function once for every window in the group, passing each
36997 * window as the only parameter. Returning false from the function will stop the iteration.
36998 * @param {Function} fn The function to execute for each item
36999 * @param {Object} scope (optional) The scope in which to execute the function
37001 each : function(fn, scope){
37002 for(var id in list){
37003 if(list[id] && typeof list[id] != "function"){
37004 if(fn.call(scope || list[id], list[id]) === false){
37015 * @class Ext.WindowMgr
37016 * @extends Ext.WindowGroup
37017 * The default global window group that is available automatically. To have more than one group of windows
37018 * with separate z-order stacks, create additional instances of {@link Ext.WindowGroup} as needed.
37021 Ext.WindowMgr = new Ext.WindowGroup();/**
37022 * @class Ext.MessageBox
37023 * <p>Utility class for generating different styles of message boxes. The alias Ext.Msg can also be used.<p/>
37024 * <p>Note that the MessageBox is asynchronous. Unlike a regular JavaScript <code>alert</code> (which will halt
37025 * browser execution), showing a MessageBox will not cause the code to stop. For this reason, if you have code
37026 * that should only run <em>after</em> some user feedback from the MessageBox, you must use a callback function
37027 * (see the <code>function</code> parameter for {@link #show} for more details).</p>
37028 * <p>Example usage:</p>
37031 Ext.Msg.alert('Status', 'Changes saved successfully.');
37033 // Prompt for user data and process the result using a callback:
37034 Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
37036 // process text value and close...
37040 // Show a dialog using config options:
37042 title:'Save Changes?',
37043 msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
37044 buttons: Ext.Msg.YESNOCANCEL,
37047 icon: Ext.MessageBox.QUESTION
37052 Ext.MessageBox = function(){
37053 var dlg, opt, mask, waitTimer;
37054 var bodyEl, msgEl, textboxEl, textareaEl, progressBar, pp, iconEl, spacerEl;
37055 var buttons, activeTextEl, bwidth, iconCls = '';
37058 var handleButton = function(button){
37059 if(dlg.isVisible()){
37062 Ext.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value, opt], 1);
37067 var handleHide = function(){
37068 if(opt && opt.cls){
37069 dlg.el.removeClass(opt.cls);
37071 progressBar.reset();
37075 var handleEsc = function(d, k, e){
37076 if(opt && opt.closable !== false){
37086 var updateButtons = function(b){
37089 buttons["ok"].hide();
37090 buttons["cancel"].hide();
37091 buttons["yes"].hide();
37092 buttons["no"].hide();
37095 dlg.footer.dom.style.display = '';
37096 for(var k in buttons){
37097 if(typeof buttons[k] != "function"){
37100 buttons[k].setText(typeof b[k] == "string" ? b[k] : Ext.MessageBox.buttonText[k]);
37101 width += buttons[k].el.getWidth()+15;
37112 * Returns a reference to the underlying {@link Ext.Window} element
37113 * @return {Ext.Window} The window
37115 getDialog : function(titleText){
37117 dlg = new Ext.Window({
37122 constrainHeader:true,
37123 minimizable : false,
37124 maximizable : false,
37128 buttonAlign:"center",
37135 close : function(){
37136 if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){
37137 handleButton("no");
37139 handleButton("cancel");
37144 var bt = this.buttonText;
37145 //TODO: refactor this block into a buttons config to pass into the Window constructor
37146 buttons["ok"] = dlg.addButton(bt["ok"], handleButton.createCallback("ok"));
37147 buttons["yes"] = dlg.addButton(bt["yes"], handleButton.createCallback("yes"));
37148 buttons["no"] = dlg.addButton(bt["no"], handleButton.createCallback("no"));
37149 buttons["cancel"] = dlg.addButton(bt["cancel"], handleButton.createCallback("cancel"));
37150 buttons["ok"].hideMode = buttons["yes"].hideMode = buttons["no"].hideMode = buttons["cancel"].hideMode = 'offsets';
37151 dlg.render(document.body);
37152 dlg.getEl().addClass('x-window-dlg');
37154 bodyEl = dlg.body.createChild({
37155 html:'<div class="ext-mb-icon"></div><div class="ext-mb-content"><span class="ext-mb-text"></span><br /><div class="ext-mb-fix-cursor"><input type="text" class="ext-mb-input" /><textarea class="ext-mb-textarea"></textarea></div></div>'
37157 iconEl = Ext.get(bodyEl.dom.firstChild);
37158 var contentEl = bodyEl.dom.childNodes[1];
37159 msgEl = Ext.get(contentEl.firstChild);
37160 textboxEl = Ext.get(contentEl.childNodes[2].firstChild);
37161 textboxEl.enableDisplayMode();
37162 textboxEl.addKeyListener([10,13], function(){
37163 if(dlg.isVisible() && opt && opt.buttons){
37164 if(opt.buttons.ok){
37165 handleButton("ok");
37166 }else if(opt.buttons.yes){
37167 handleButton("yes");
37171 textareaEl = Ext.get(contentEl.childNodes[2].childNodes[1]);
37172 textareaEl.enableDisplayMode();
37173 progressBar = new Ext.ProgressBar({
37176 bodyEl.createChild({cls:'x-clear'});
37182 * Updates the message box body text
37183 * @param {String} text (optional) Replaces the message box element's innerHTML with the specified string (defaults to
37184 * the XHTML-compliant non-breaking space character '&#160;')
37185 * @return {Ext.MessageBox} this
37187 updateText : function(text){
37188 if(!dlg.isVisible() && !opt.width){
37189 dlg.setSize(this.maxWidth, 100); // resize first so content is never clipped from previous shows
37191 msgEl.update(text || ' ');
37193 var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0;
37194 var mw = msgEl.getWidth() + msgEl.getMargins('lr');
37195 var fw = dlg.getFrameWidth('lr');
37196 var bw = dlg.body.getFrameWidth('lr');
37197 if (Ext.isIE && iw > 0){
37198 //3 pixels get subtracted in the icon CSS for an IE margin issue,
37199 //so we have to add it back here for the overall width to be consistent
37202 var w = Math.max(Math.min(opt.width || iw+mw+fw+bw, this.maxWidth),
37203 Math.max(opt.minWidth || this.minWidth, bwidth || 0));
37205 if(opt.prompt === true){
37206 activeTextEl.setWidth(w-iw-fw-bw);
37208 if(opt.progress === true || opt.wait === true){
37209 progressBar.setSize(w-iw-fw-bw);
37211 if(Ext.isIE && w == bwidth){
37212 w += 4; //Add offset when the content width is smaller than the buttons.
37214 dlg.setSize(w, 'auto').center();
37219 * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
37220 * initiated via {@link Ext.MessageBox#progress} or {@link Ext.MessageBox#wait},
37221 * or by calling {@link Ext.MessageBox#show} with progress: true.
37222 * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0)
37223 * @param {String} progressText The progress text to display inside the progress bar (defaults to '')
37224 * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined
37225 * so that any existing body text will not get overwritten by default unless a new value is passed in)
37226 * @return {Ext.MessageBox} this
37228 updateProgress : function(value, progressText, msg){
37229 progressBar.updateProgress(value, progressText);
37231 this.updateText(msg);
37237 * Returns true if the message box is currently displayed
37238 * @return {Boolean} True if the message box is visible, else false
37240 isVisible : function(){
37241 return dlg && dlg.isVisible();
37245 * Hides the message box if it is displayed
37246 * @return {Ext.MessageBox} this
37249 var proxy = dlg ? dlg.activeGhost : null;
37250 if(this.isVisible() || proxy){
37254 // unghost is a private function, but i saw no better solution
37255 // to fix the locking problem when dragging while it closes
37256 dlg.unghost(false, false);
37263 * Displays a new message box, or reinitializes an existing message box, based on the config options
37264 * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
37265 * although those calls are basic shortcuts and do not support all of the config options allowed here.
37266 * @param {Object} config The following config options are supported: <ul>
37267 * <li><b>animEl</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
37268 * opens and closes (defaults to undefined)</div></li>
37269 * <li><b>buttons</b> : Object/Boolean<div class="sub-desc">A button config object (e.g., Ext.MessageBox.OKCANCEL or {ok:'Foo',
37270 * cancel:'Bar'}), or false to not show any buttons (defaults to false)</div></li>
37271 * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
37272 * progress and wait dialogs will ignore this property and always hide the close button as they can only
37273 * be closed programmatically.</div></li>
37274 * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
37275 * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
37276 * if displayed (defaults to 75)</div></li>
37277 * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
37278 * by clicking on the configured buttons, or on the dialog close button, or by pressing
37279 * the return button to enter input.
37280 * <p>Progress and wait dialogs will ignore this option since they do not respond to user
37281 * actions and can only be closed programmatically, so any required function should be called
37282 * by the same code after it closes the dialog. Parameters passed:<ul>
37283 * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
37284 * <li><tt>ok</tt></li>
37285 * <li><tt>yes</tt></li>
37286 * <li><tt>no</tt></li>
37287 * <li><tt>cancel</tt></li>
37288 * </ul></div></div></li>
37289 * <li><b>text</b> : String<div class="sub-desc">Value of the input field if either <tt><a href="#show-option-prompt" ext:member="show-option-prompt" ext:cls="Ext.MessageBox">prompt</a></tt>
37290 * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.MessageBox">multiline</a></tt> is true</div></li>
37291 * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
37292 * </ul></p></div></li>
37293 * <li><b>scope</b> : Object<div class="sub-desc">The scope of the callback function</div></li>
37294 * <li><b>icon</b> : String<div class="sub-desc">A CSS class that provides a background image to be used as the body icon for the
37295 * dialog (e.g. Ext.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
37296 * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.Window#iconCls} to
37297 * add an optional header icon (defaults to '')</div></li>
37298 * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
37299 * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
37300 * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
37301 * displayed (defaults to true)</div></li>
37302 * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
37303 * XHTML-compliant non-breaking space character '&#160;')</div></li>
37304 * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
37305 * True to prompt the user to enter multi-line text (defaults to false)</div></li>
37306 * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
37307 * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
37308 * <li><a id="show-option-prompt"></a><b>prompt</b> : Boolean<div class="sub-desc">True to prompt the user to enter single-line text (defaults to false)</div></li>
37309 * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
37310 * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
37311 * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
37312 * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
37313 * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)</div></li>
37314 * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
37320 msg: 'Please enter your address:',
37322 buttons: Ext.MessageBox.OKCANCEL,
37325 animEl: 'addAddressBtn',
37326 icon: Ext.MessageBox.INFO
37329 * @return {Ext.MessageBox} this
37331 show : function(options){
37332 if(this.isVisible()){
37336 var d = this.getDialog(opt.title || " ");
37338 d.setTitle(opt.title || " ");
37339 var allowClose = (opt.closable !== false && opt.progress !== true && opt.wait !== true);
37340 d.tools.close.setDisplayed(allowClose);
37341 activeTextEl = textboxEl;
37342 opt.prompt = opt.prompt || (opt.multiline ? true : false);
37347 textareaEl.setHeight(typeof opt.multiline == "number" ?
37348 opt.multiline : this.defaultTextHeight);
37349 activeTextEl = textareaEl;
37358 activeTextEl.dom.value = opt.value || "";
37360 d.focusEl = activeTextEl;
37362 var bs = opt.buttons;
37365 db = buttons["ok"];
37366 }else if(bs && bs.yes){
37367 db = buttons["yes"];
37374 d.setIconClass(opt.iconCls);
37376 this.setIcon(opt.icon);
37378 d.el.addClass(opt.cls);
37380 d.proxyDrag = opt.proxyDrag === true;
37381 d.modal = opt.modal !== false;
37382 d.mask = opt.modal !== false ? mask : false;
37384 d.on('show', function(){
37385 //workaround for window internally enabling keymap in afterShow
37386 d.keyMap.setDisabled(allowClose !== true);
37388 this.setIcon(opt.icon);
37389 bwidth = updateButtons(opt.buttons);
37390 progressBar.setVisible(opt.progress === true || opt.wait === true);
37391 this.updateProgress(0, opt.progressText);
37392 this.updateText(opt.msg);
37393 if(opt.wait === true){
37394 progressBar.wait(opt.waitConfig);
37397 }, this, {single:true});
37398 if(!d.isVisible()){
37399 // force it to the end of the z-index stack so it gets a cursor in FF
37400 document.body.appendChild(dlg.el.dom);
37401 d.setAnimateTarget(opt.animEl);
37402 d.show(opt.animEl);
37408 * Adds the specified icon to the dialog. By default, the class 'ext-mb-icon' is applied for default
37409 * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
37410 * to clear any existing icon. The following built-in icon classes are supported, but you can also pass
37411 * in a custom class name:
37413 Ext.MessageBox.INFO
37414 Ext.MessageBox.WARNING
37415 Ext.MessageBox.QUESTION
37416 Ext.MessageBox.ERROR
37418 * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
37419 * @return {Ext.MessageBox} this
37421 setIcon : function(icon){
37422 if(icon && icon != ''){
37423 iconEl.removeClass('x-hidden');
37424 iconEl.replaceClass(iconCls, icon);
37425 bodyEl.addClass('x-dlg-icon');
37428 iconEl.replaceClass(iconCls, 'x-hidden');
37429 bodyEl.removeClass('x-dlg-icon');
37436 * Displays a message box with a progress bar. This message box has no buttons and is not closeable by
37437 * the user. You are responsible for updating the progress bar as needed via {@link Ext.MessageBox#updateProgress}
37438 * and closing the message box when the process is complete.
37439 * @param {String} title The title bar text
37440 * @param {String} msg The message box body text
37441 * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '')
37442 * @return {Ext.MessageBox} this
37444 progress : function(title, msg, progressText){
37451 minWidth: this.minProgressWidth,
37452 progressText: progressText
37458 * Displays a message box with an infinitely auto-updating progress bar. This can be used to block user
37459 * interaction while waiting for a long-running process to complete that does not have defined intervals.
37460 * You are responsible for closing the message box when the process is complete.
37461 * @param {String} msg The message box body text
37462 * @param {String} title (optional) The title bar text
37463 * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object
37464 * @return {Ext.MessageBox} this
37466 wait : function(msg, title, config){
37474 minWidth: this.minProgressWidth,
37481 * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
37482 * If a callback function is passed it will be called after the user clicks the button, and the
37483 * id of the button that was clicked will be passed as the only parameter to the callback
37484 * (could also be the top-right close button).
37485 * @param {String} title The title bar text
37486 * @param {String} msg The message box body text
37487 * @param {Function} fn (optional) The callback function invoked after the message box is closed
37488 * @param {Object} scope (optional) The scope of the callback function
37489 * @return {Ext.MessageBox} this
37491 alert : function(title, msg, fn, scope){
37503 * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
37504 * If a callback function is passed it will be called after the user clicks either button,
37505 * and the id of the button that was clicked will be passed as the only parameter to the callback
37506 * (could also be the top-right close button).
37507 * @param {String} title The title bar text
37508 * @param {String} msg The message box body text
37509 * @param {Function} fn (optional) The callback function invoked after the message box is closed
37510 * @param {Object} scope (optional) The scope of the callback function
37511 * @return {Ext.MessageBox} this
37513 confirm : function(title, msg, fn, scope){
37517 buttons: this.YESNO,
37520 icon: this.QUESTION
37526 * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
37527 * The prompt can be a single-line or multi-line textbox. If a callback function is passed it will be called after the user
37528 * clicks either button, and the id of the button that was clicked (could also be the top-right
37529 * close button) and the text that was entered will be passed as the two parameters to the callback.
37530 * @param {String} title The title bar text
37531 * @param {String} msg The message box body text
37532 * @param {Function} fn (optional) The callback function invoked after the message box is closed
37533 * @param {Object} scope (optional) The scope of the callback function
37534 * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight
37535 * property, or the height in pixels to create the textbox (defaults to false / single-line)
37536 * @param {String} value (optional) Default value of the text input element (defaults to '')
37537 * @return {Ext.MessageBox} this
37539 prompt : function(title, msg, fn, scope, multiline, value){
37543 buttons: this.OKCANCEL,
37548 multiline: multiline,
37555 * Button config that displays a single OK button
37560 * Button config that displays a single Cancel button
37563 CANCEL : {cancel:true},
37565 * Button config that displays OK and Cancel buttons
37568 OKCANCEL : {ok:true, cancel:true},
37570 * Button config that displays Yes and No buttons
37573 YESNO : {yes:true, no:true},
37575 * Button config that displays Yes, No and Cancel buttons
37578 YESNOCANCEL : {yes:true, no:true, cancel:true},
37580 * The CSS class that provides the INFO icon image
37583 INFO : 'ext-mb-info',
37585 * The CSS class that provides the WARNING icon image
37588 WARNING : 'ext-mb-warning',
37590 * The CSS class that provides the QUESTION icon image
37593 QUESTION : 'ext-mb-question',
37595 * The CSS class that provides the ERROR icon image
37598 ERROR : 'ext-mb-error',
37601 * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75)
37604 defaultTextHeight : 75,
37606 * The maximum width in pixels of the message box (defaults to 600)
37611 * The minimum width in pixels of the message box (defaults to 110)
37616 * The minimum width in pixels of the message box if it is a progress-style dialog. This is useful
37617 * for setting a different minimum width than text-only dialogs may need (defaults to 250)
37620 minProgressWidth : 250,
37622 * An object containing the default button text strings that can be overriden for localized language support.
37623 * Supported properties are: ok, cancel, yes and no. Generally you should include a locale-specific
37624 * resource file for handling language support across the framework.
37625 * Customize the default text like so: Ext.MessageBox.buttonText.yes = "oui"; //french
37638 * Shorthand for {@link Ext.MessageBox}
37640 Ext.Msg = Ext.MessageBox;/**
\r
37641 * @class Ext.dd.PanelProxy
\r
37642 * A custom drag proxy implementation specific to {@link Ext.Panel}s. This class is primarily used internally
\r
37643 * for the Panel's drag drop implementation, and should never need to be created directly.
\r
37645 * @param panel The {@link Ext.Panel} to proxy for
\r
37646 * @param config Configuration options
\r
37648 Ext.dd.PanelProxy = function(panel, config){
\r
37649 this.panel = panel;
\r
37650 this.id = this.panel.id +'-ddproxy';
\r
37651 Ext.apply(this, config);
\r
37654 Ext.dd.PanelProxy.prototype = {
\r
37656 * @cfg {Boolean} insertProxy True to insert a placeholder proxy element while dragging the panel,
\r
37657 * false to drag with no proxy (defaults to true).
\r
37659 insertProxy : true,
\r
37661 // private overrides
\r
37662 setStatus : Ext.emptyFn,
\r
37663 reset : Ext.emptyFn,
\r
37664 update : Ext.emptyFn,
\r
37665 stop : Ext.emptyFn,
\r
37666 sync: Ext.emptyFn,
\r
37669 * Gets the proxy's element
\r
37670 * @return {Element} The proxy's element
\r
37672 getEl : function(){
\r
37673 return this.ghost;
\r
37677 * Gets the proxy's ghost element
\r
37678 * @return {Element} The proxy's ghost element
\r
37680 getGhost : function(){
\r
37681 return this.ghost;
\r
37685 * Gets the proxy's element
\r
37686 * @return {Element} The proxy's element
\r
37688 getProxy : function(){
\r
37689 return this.proxy;
\r
37693 * Hides the proxy
\r
37695 hide : function(){
\r
37698 this.proxy.remove();
\r
37699 delete this.proxy;
\r
37701 this.panel.el.dom.style.display = '';
\r
37702 this.ghost.remove();
\r
37703 delete this.ghost;
\r
37708 * Shows the proxy
\r
37710 show : function(){
\r
37712 this.ghost = this.panel.createGhost(undefined, undefined, Ext.getBody());
\r
37713 this.ghost.setXY(this.panel.el.getXY())
\r
37714 if(this.insertProxy){
\r
37715 this.proxy = this.panel.el.insertSibling({cls:'x-panel-dd-spacer'});
\r
37716 this.proxy.setSize(this.panel.getSize());
\r
37718 this.panel.el.dom.style.display = 'none';
\r
37723 repair : function(xy, callback, scope){
\r
37725 if(typeof callback == "function"){
\r
37726 callback.call(scope || this);
\r
37731 * Moves the proxy to a different position in the DOM. This is typically called while dragging the Panel
\r
37732 * to keep the proxy sync'd to the Panel's location.
\r
37733 * @param {HTMLElement} parentNode The proxy's parent DOM node
\r
37734 * @param {HTMLElement} before (optional) The sibling node before which the proxy should be inserted (defaults
\r
37735 * to the parent's last child if not specified)
\r
37737 moveProxy : function(parentNode, before){
\r
37739 parentNode.insertBefore(this.proxy.dom, before);
\r
37744 // private - DD implementation for Panels
\r
37745 Ext.Panel.DD = function(panel, cfg){
\r
37746 this.panel = panel;
\r
37747 this.dragData = {panel: panel};
\r
37748 this.proxy = new Ext.dd.PanelProxy(panel, cfg);
\r
37749 Ext.Panel.DD.superclass.constructor.call(this, panel.el, cfg);
\r
37750 var h = panel.header;
\r
37752 this.setHandleElId(h.id);
\r
37754 (h ? h : this.panel.body).setStyle('cursor', 'move');
\r
37755 this.scroll = false;
\r
37758 Ext.extend(Ext.Panel.DD, Ext.dd.DragSource, {
\r
37759 showFrame: Ext.emptyFn,
\r
37760 startDrag: Ext.emptyFn,
\r
37761 b4StartDrag: function(x, y) {
\r
37762 this.proxy.show();
\r
37764 b4MouseDown: function(e) {
\r
37765 var x = e.getPageX();
\r
37766 var y = e.getPageY();
\r
37767 this.autoOffset(x, y);
\r
37769 onInitDrag : function(x, y){
\r
37770 this.onStartDrag(x, y);
\r
37773 createFrame : Ext.emptyFn,
\r
37774 getDragEl : function(e){
\r
37775 return this.proxy.ghost.dom;
\r
37777 endDrag : function(e){
\r
37778 this.proxy.hide();
\r
37779 this.panel.saveState();
\r
37782 autoOffset : function(x, y) {
\r
37783 x -= this.startPageX;
\r
37784 y -= this.startPageY;
\r
37785 this.setDelta(x, y);
\r
37788 * @class Ext.state.Provider
37789 * Abstract base class for state provider implementations. This class provides methods
37790 * for encoding and decoding <b>typed</b> variables including dates and defines the
37791 * Provider interface.
37793 Ext.state.Provider = function(){
37795 * @event statechange
37796 * Fires when a state change occurs.
37797 * @param {Provider} this This state provider
37798 * @param {String} key The state key which was changed
37799 * @param {String} value The encoded value for the state
37801 this.addEvents("statechange");
37803 Ext.state.Provider.superclass.constructor.call(this);
37805 Ext.extend(Ext.state.Provider, Ext.util.Observable, {
37807 * Returns the current value for a key
37808 * @param {String} name The key name
37809 * @param {Mixed} defaultValue A default value to return if the key's value is not found
37810 * @return {Mixed} The state data
37812 get : function(name, defaultValue){
37813 return typeof this.state[name] == "undefined" ?
37814 defaultValue : this.state[name];
37818 * Clears a value from the state
37819 * @param {String} name The key name
37821 clear : function(name){
37822 delete this.state[name];
37823 this.fireEvent("statechange", this, name, null);
37827 * Sets the value for a key
37828 * @param {String} name The key name
37829 * @param {Mixed} value The value to set
37831 set : function(name, value){
37832 this.state[name] = value;
37833 this.fireEvent("statechange", this, name, value);
37837 * Decodes a string previously encoded with {@link #encodeValue}.
37838 * @param {String} value The value to decode
37839 * @return {Mixed} The decoded value
37841 decodeValue : function(cookie){
37842 var re = /^(a|n|d|b|s|o)\:(.*)$/;
37843 var matches = re.exec(unescape(cookie));
37844 if(!matches || !matches[1]) return; // non state cookie
37845 var type = matches[1];
37846 var v = matches[2];
37849 return parseFloat(v);
37851 return new Date(Date.parse(v));
37856 var values = v.split("^");
37857 for(var i = 0, len = values.length; i < len; i++){
37858 all.push(this.decodeValue(values[i]));
37863 var values = v.split("^");
37864 for(var i = 0, len = values.length; i < len; i++){
37865 var kv = values[i].split("=");
37866 all[kv[0]] = this.decodeValue(kv[1]);
37875 * Encodes a value including type information. Decode with {@link #decodeValue}.
37876 * @param {Mixed} value The value to encode
37877 * @return {String} The encoded value
37879 encodeValue : function(v){
37881 if(typeof v == "number"){
37883 }else if(typeof v == "boolean"){
37884 enc = "b:" + (v ? "1" : "0");
37885 }else if(Ext.isDate(v)){
37886 enc = "d:" + v.toGMTString();
37887 }else if(Ext.isArray(v)){
37889 for(var i = 0, len = v.length; i < len; i++){
37890 flat += this.encodeValue(v[i]);
37891 if(i != len-1) flat += "^";
37894 }else if(typeof v == "object"){
37897 if(typeof v[key] != "function" && v[key] !== undefined){
37898 flat += key + "=" + this.encodeValue(v[key]) + "^";
37901 enc = "o:" + flat.substring(0, flat.length-1);
37905 return escape(enc);
37909 * @class Ext.state.Manager
\r
37910 * This is the global state manager. By default all components that are "state aware" check this class
\r
37911 * for state information if you don't pass them a custom state provider. In order for this class
\r
37912 * to be useful, it must be initialized with a provider when your application initializes. Example usage:
\r
37914 // in your initialization function
\r
37915 init : function(){
\r
37916 Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
\r
37917 var win = new Window(...);
\r
37918 win.restoreState();
\r
37923 Ext.state.Manager = function(){
\r
37924 var provider = new Ext.state.Provider();
\r
37928 * Configures the default state provider for your application
\r
37929 * @param {Provider} stateProvider The state provider to set
\r
37931 setProvider : function(stateProvider){
\r
37932 provider = stateProvider;
\r
37936 * Returns the current value for a key
\r
37937 * @param {String} name The key name
\r
37938 * @param {Mixed} defaultValue The default value to return if the key lookup does not match
\r
37939 * @return {Mixed} The state data
\r
37941 get : function(key, defaultValue){
\r
37942 return provider.get(key, defaultValue);
\r
37946 * Sets the value for a key
\r
37947 * @param {String} name The key name
\r
37948 * @param {Mixed} value The state data
\r
37950 set : function(key, value){
\r
37951 provider.set(key, value);
\r
37955 * Clears a value from the state
\r
37956 * @param {String} name The key name
\r
37958 clear : function(key){
\r
37959 provider.clear(key);
\r
37963 * Gets the currently configured state provider
\r
37964 * @return {Provider} The state provider
\r
37966 getProvider : function(){
\r
37972 * @class Ext.state.CookieProvider
\r
37973 * @extends Ext.state.Provider
\r
37974 * The default Provider implementation which saves state via cookies.
\r
37977 var cp = new Ext.state.CookieProvider({
\r
37978 path: "/cgi-bin/",
\r
37979 expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
\r
37980 domain: "extjs.com"
\r
37982 Ext.state.Manager.setProvider(cp);
\r
37984 * @cfg {String} path The path for which the cookie is active (defaults to root '/' which makes it active for all pages in the site)
\r
37985 * @cfg {Date} expires The cookie expiration date (defaults to 7 days from now)
\r
37986 * @cfg {String} domain The domain to save the cookie for. Note that you cannot specify a different domain than
\r
37987 * your page is on, but you can specify a sub-domain, or simply the domain itself like 'extjs.com' to include
\r
37988 * all sub-domains if you need to access cookies across different sub-domains (defaults to null which uses the same
\r
37989 * domain the page is running on including the 'www' like 'www.extjs.com')
\r
37990 * @cfg {Boolean} secure True if the site is using SSL (defaults to false)
\r
37992 * Create a new CookieProvider
\r
37993 * @param {Object} config The configuration object
\r
37995 Ext.state.CookieProvider = function(config){
\r
37996 Ext.state.CookieProvider.superclass.constructor.call(this);
\r
37998 this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
\r
37999 this.domain = null;
\r
38000 this.secure = false;
\r
38001 Ext.apply(this, config);
\r
38002 this.state = this.readCookies();
\r
38005 Ext.extend(Ext.state.CookieProvider, Ext.state.Provider, {
\r
38007 set : function(name, value){
\r
38008 if(typeof value == "undefined" || value === null){
\r
38009 this.clear(name);
\r
38012 this.setCookie(name, value);
\r
38013 Ext.state.CookieProvider.superclass.set.call(this, name, value);
\r
38017 clear : function(name){
\r
38018 this.clearCookie(name);
\r
38019 Ext.state.CookieProvider.superclass.clear.call(this, name);
\r
38023 readCookies : function(){
\r
38024 var cookies = {};
\r
38025 var c = document.cookie + ";";
\r
38026 var re = /\s?(.*?)=(.*?);/g;
\r
38028 while((matches = re.exec(c)) != null){
\r
38029 var name = matches[1];
\r
38030 var value = matches[2];
\r
38031 if(name && name.substring(0,3) == "ys-"){
\r
38032 cookies[name.substr(3)] = this.decodeValue(value);
\r
38039 setCookie : function(name, value){
\r
38040 document.cookie = "ys-"+ name + "=" + this.encodeValue(value) +
\r
38041 ((this.expires == null) ? "" : ("; expires=" + this.expires.toGMTString())) +
\r
38042 ((this.path == null) ? "" : ("; path=" + this.path)) +
\r
38043 ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
\r
38044 ((this.secure == true) ? "; secure" : "");
\r
38048 clearCookie : function(name){
\r
38049 document.cookie = "ys-" + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
\r
38050 ((this.path == null) ? "" : ("; path=" + this.path)) +
\r
38051 ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
\r
38052 ((this.secure == true) ? "; secure" : "");
\r
38055 * @class Ext.DataView
38056 * @extends Ext.BoxComponent
38057 * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
38058 * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
38059 * so that as the data in the store changes the view is automatically updated to reflect the changes. The view also
38060 * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
38061 * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
38062 * config must be provided for the DataView to determine what nodes it will be working with.</b>
38064 * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>
38066 var store = new Ext.data.JsonStore({
38067 url: 'get-images.php',
38071 {name:'size', type: 'float'},
38072 {name:'lastmod', type:'date', dateFormat:'timestamp'}
38077 var tpl = new Ext.XTemplate(
38078 '<tpl for=".">',
38079 '<div class="thumb-wrap" id="{name}">',
38080 '<div class="thumb"><img src="{url}" title="{name}"></div>',
38081 '<span class="x-editable">{shortName}</span></div>',
38083 '<div class="x-clear"></div>'
38086 var panel = new Ext.Panel({
38093 title:'Simple DataView',
38095 items: new Ext.DataView({
38100 overClass:'x-view-over',
38101 itemSelector:'div.thumb-wrap',
38102 emptyText: 'No images to display'
38105 panel.render(document.body);
38108 * Create a new DataView
38109 * @param {Object} config The config object
38112 Ext.DataView = Ext.extend(Ext.BoxComponent, {
38114 * @cfg {String/Array} tpl
38115 * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
38116 * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
38119 * @cfg {Ext.data.Store} store
38120 * The {@link Ext.data.Store} to bind this DataView to.
38123 * @cfg {String} itemSelector
38124 * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
38125 * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
38129 * @cfg {Boolean} multiSelect
38130 * True to allow selection of more than one item at a time, false to allow selection of only a single item
38131 * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
38134 * @cfg {Boolean} singleSelect
38135 * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
38136 * Note that if {@link #multiSelect} = true, this value will be ignored.
38139 * @cfg {Boolean} simpleSelect
38140 * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
38141 * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
38144 * @cfg {String} overClass
38145 * A CSS class to apply to each item in the view on mouseover (defaults to undefined).
38148 * @cfg {String} loadingText
38149 * A string to display during data load operations (defaults to undefined). If specified, this text will be
38150 * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
38151 * contents will continue to display normally until the new data is loaded and the contents are replaced.
38154 * @cfg {String} selectedClass
38155 * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
38157 selectedClass : "x-view-selected",
38159 * @cfg {String} emptyText
38160 * The text to display in the view when there is no data to display (defaults to '').
38165 * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
38167 deferEmptyText: true,
38169 * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
38177 initComponent : function(){
38178 Ext.DataView.superclass.initComponent.call(this);
38179 if(Ext.isString(this.tpl) || Ext.isArray(this.tpl)){
38180 this.tpl = new Ext.XTemplate(this.tpl);
38185 * @event beforeclick
38186 * Fires before a click is processed. Returns false to cancel the default action.
38187 * @param {Ext.DataView} this
38188 * @param {Number} index The index of the target node
38189 * @param {HTMLElement} node The target node
38190 * @param {Ext.EventObject} e The raw event object
38195 * Fires when a template node is clicked.
38196 * @param {Ext.DataView} this
38197 * @param {Number} index The index of the target node
38198 * @param {HTMLElement} node The target node
38199 * @param {Ext.EventObject} e The raw event object
38203 * @event mouseenter
38204 * Fires when the mouse enters a template node. trackOver:true or an overCls must be set to enable this event.
38205 * @param {Ext.DataView} this
38206 * @param {Number} index The index of the target node
38207 * @param {HTMLElement} node The target node
38208 * @param {Ext.EventObject} e The raw event object
38212 * @event mouseleave
38213 * Fires when the mouse leaves a template node. trackOver:true or an overCls must be set to enable this event.
38214 * @param {Ext.DataView} this
38215 * @param {Number} index The index of the target node
38216 * @param {HTMLElement} node The target node
38217 * @param {Ext.EventObject} e The raw event object
38221 * @event containerclick
38222 * Fires when a click occurs and it is not on a template node.
38223 * @param {Ext.DataView} this
38224 * @param {Ext.EventObject} e The raw event object
38229 * Fires when a template node is double clicked.
38230 * @param {Ext.DataView} this
38231 * @param {Number} index The index of the target node
38232 * @param {HTMLElement} node The target node
38233 * @param {Ext.EventObject} e The raw event object
38237 * @event contextmenu
38238 * Fires when a template node is right clicked.
38239 * @param {Ext.DataView} this
38240 * @param {Number} index The index of the target node
38241 * @param {HTMLElement} node The target node
38242 * @param {Ext.EventObject} e The raw event object
38246 * @event containercontextmenu
38247 * Fires when a right click occurs that is not on a template node.
38248 * @param {Ext.DataView} this
38249 * @param {Ext.EventObject} e The raw event object
38251 "containercontextmenu",
38253 * @event selectionchange
38254 * Fires when the selected nodes change.
38255 * @param {Ext.DataView} this
38256 * @param {Array} selections Array of the selected nodes
38261 * @event beforeselect
38262 * Fires before a selection is made. If any handlers return false, the selection is cancelled.
38263 * @param {Ext.DataView} this
38264 * @param {HTMLElement} node The node to be selected
38265 * @param {Array} selections Array of currently selected nodes
38270 this.store = Ext.StoreMgr.lookup(this.store);
38271 this.all = new Ext.CompositeElementLite();
38272 this.selected = new Ext.CompositeElementLite();
38276 afterRender : function(){
38277 Ext.DataView.superclass.afterRender.call(this);
38279 this.mon(this.getTemplateTarget(), {
38280 "click": this.onClick,
38281 "dblclick": this.onDblClick,
38282 "contextmenu": this.onContextMenu,
38286 if(this.overClass || this.trackOver){
38287 this.mon(this.getTemplateTarget(), {
38288 "mouseover": this.onMouseOver,
38289 "mouseout": this.onMouseOut,
38295 this.bindStore(this.store, true);
38300 * Refreshes the view by reloading the data from the store and re-rendering the template.
38302 refresh : function(){
38303 this.clearSelections(false, true);
38304 var el = this.getTemplateTarget();
38306 var records = this.store.getRange();
38307 if(records.length < 1){
38308 if(!this.deferEmptyText || this.hasSkippedEmptyText){
38309 el.update(this.emptyText);
38313 this.tpl.overwrite(el, this.collectData(records, 0));
38314 this.all.fill(Ext.query(this.itemSelector, el.dom));
38315 this.updateIndexes(0);
38317 this.hasSkippedEmptyText = true;
38320 getTemplateTarget: function(){
38325 * Function which can be overridden to provide custom formatting for each Record that is used by this
38326 * DataView's {@link #tpl template} to render each node.
38327 * @param {Array/Object} data The raw data object that was used to create the Record.
38328 * @param {Number} recordIndex the index number of the Record being prepared for rendering.
38329 * @param {Record} record The Record being prepared for rendering.
38330 * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
38331 * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
38333 prepareData : function(data){
38338 * <p>Function which can be overridden which returns the data object passed to this
38339 * DataView's {@link #tpl template} to render the whole DataView.</p>
38340 * <p>This is usually an Array of data objects, each element of which is processed by an
38341 * {@link Ext.XTemplate XTemplate} which uses <tt>'<tpl for=".">'</tt> to iterate over its supplied
38342 * data object as an Array. However, <i>named</i> properties may be placed into the data object to
38343 * provide non-repeating data such as headings, totals etc.</p>
38344 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
38345 * @param {Number} startIndex the index number of the Record being prepared for rendering.
38346 * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
38347 * contain <i>named</i> properties.
38349 collectData : function(records, startIndex){
38351 for(var i = 0, len = records.length; i < len; i++){
38352 r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);
38358 bufferRender : function(records){
38359 var div = document.createElement('div');
38360 this.tpl.overwrite(div, this.collectData(records));
38361 return Ext.query(this.itemSelector, div);
38365 onUpdate : function(ds, record){
38366 var index = this.store.indexOf(record);
38367 var sel = this.isSelected(index);
38368 var original = this.all.elements[index];
38369 var node = this.bufferRender([record], index)[0];
38371 this.all.replaceElement(index, node, true);
38373 this.selected.replaceElement(original, node);
38374 this.all.item(index).addClass(this.selectedClass);
38376 this.updateIndexes(index, index);
38380 onAdd : function(ds, records, index){
38381 if(this.all.getCount() === 0){
38385 var nodes = this.bufferRender(records, index), n, a = this.all.elements;
38386 if(index < this.all.getCount()){
38387 n = this.all.item(index).insertSibling(nodes, 'before', true);
38388 a.splice.apply(a, [index, 0].concat(nodes));
38390 n = this.all.last().insertSibling(nodes, 'after', true);
38391 a.push.apply(a, nodes);
38393 this.updateIndexes(index);
38397 onRemove : function(ds, record, index){
38398 this.deselect(index);
38399 this.all.removeElement(index, true);
38400 this.updateIndexes(index);
38401 if (this.store.getCount() === 0){
38407 * Refreshes an individual node's data from the store.
38408 * @param {Number} index The item's data index in the store
38410 refreshNode : function(index){
38411 this.onUpdate(this.store, this.store.getAt(index));
38415 updateIndexes : function(startIndex, endIndex){
38416 var ns = this.all.elements;
38417 startIndex = startIndex || 0;
38418 endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
38419 for(var i = startIndex; i <= endIndex; i++){
38420 ns[i].viewIndex = i;
38425 * Returns the store associated with this DataView.
38426 * @return {Ext.data.Store} The store
38428 getStore : function(){
38433 * Changes the data store bound to this view and refreshes it.
38434 * @param {Store} store The store to bind to this view
38436 bindStore : function(store, initial){
38437 if(!initial && this.store){
38438 this.store.un("beforeload", this.onBeforeLoad, this);
38439 this.store.un("datachanged", this.refresh, this);
38440 this.store.un("add", this.onAdd, this);
38441 this.store.un("remove", this.onRemove, this);
38442 this.store.un("update", this.onUpdate, this);
38443 this.store.un("clear", this.refresh, this);
38444 if(store !== this.store && this.store.autoDestroy){
38445 this.store.destroy();
38449 store = Ext.StoreMgr.lookup(store);
38452 beforeload: this.onBeforeLoad,
38453 datachanged: this.refresh,
38455 remove: this.onRemove,
38456 update: this.onUpdate,
38457 clear: this.refresh
38460 this.store = store;
38467 * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
38468 * @param {HTMLElement} node
38469 * @return {HTMLElement} The template node
38471 findItemFromChild : function(node){
38472 return Ext.fly(node).findParent(this.itemSelector, this.getTemplateTarget());
38476 onClick : function(e){
38477 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
38479 var index = this.indexOf(item);
38480 if(this.onItemClick(item, index, e) !== false){
38481 this.fireEvent("click", this, index, item, e);
38484 if(this.fireEvent("containerclick", this, e) !== false){
38485 this.onContainerClick(e);
38490 onContainerClick : function(e){
38491 this.clearSelections();
38495 onContextMenu : function(e){
38496 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
38498 this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
38500 this.fireEvent("containercontextmenu", this, e);
38505 onDblClick : function(e){
38506 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
38508 this.fireEvent("dblclick", this, this.indexOf(item), item, e);
38513 onMouseOver : function(e){
38514 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
38515 if(item && item !== this.lastItem){
38516 this.lastItem = item;
38517 Ext.fly(item).addClass(this.overClass);
38518 this.fireEvent("mouseenter", this, this.indexOf(item), item, e);
38523 onMouseOut : function(e){
38525 if(!e.within(this.lastItem, true, true)){
38526 Ext.fly(this.lastItem).removeClass(this.overClass);
38527 this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);
38528 delete this.lastItem;
38534 onItemClick : function(item, index, e){
38535 if(this.fireEvent("beforeclick", this, index, item, e) === false){
38538 if(this.multiSelect){
38539 this.doMultiSelection(item, index, e);
38540 e.preventDefault();
38541 }else if(this.singleSelect){
38542 this.doSingleSelection(item, index, e);
38543 e.preventDefault();
38549 doSingleSelection : function(item, index, e){
38550 if(e.ctrlKey && this.isSelected(index)){
38551 this.deselect(index);
38553 this.select(index, false);
38558 doMultiSelection : function(item, index, e){
38559 if(e.shiftKey && this.last !== false){
38560 var last = this.last;
38561 this.selectRange(last, index, e.ctrlKey);
38562 this.last = last; // reset the last
38564 if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){
38565 this.deselect(index);
38567 this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);
38573 * Gets the number of selected nodes.
38574 * @return {Number} The node count
38576 getSelectionCount : function(){
38577 return this.selected.getCount();
38581 * Gets the currently selected nodes.
38582 * @return {Array} An array of HTMLElements
38584 getSelectedNodes : function(){
38585 return this.selected.elements;
38589 * Gets the indexes of the selected nodes.
38590 * @return {Array} An array of numeric indexes
38592 getSelectedIndexes : function(){
38593 var indexes = [], s = this.selected.elements;
38594 for(var i = 0, len = s.length; i < len; i++){
38595 indexes.push(s[i].viewIndex);
38601 * Gets an array of the selected records
38602 * @return {Array} An array of {@link Ext.data.Record} objects
38604 getSelectedRecords : function(){
38605 var r = [], s = this.selected.elements;
38606 for(var i = 0, len = s.length; i < len; i++){
38607 r[r.length] = this.store.getAt(s[i].viewIndex);
38613 * Gets an array of the records from an array of nodes
38614 * @param {Array} nodes The nodes to evaluate
38615 * @return {Array} records The {@link Ext.data.Record} objects
38617 getRecords : function(nodes){
38618 var r = [], s = nodes;
38619 for(var i = 0, len = s.length; i < len; i++){
38620 r[r.length] = this.store.getAt(s[i].viewIndex);
38626 * Gets a record from a node
38627 * @param {HTMLElement} node The node to evaluate
38628 * @return {Record} record The {@link Ext.data.Record} object
38630 getRecord : function(node){
38631 return this.store.getAt(node.viewIndex);
38635 * Clears all selections.
38636 * @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event
38638 clearSelections : function(suppressEvent, skipUpdate){
38639 if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){
38641 this.selected.removeClass(this.selectedClass);
38643 this.selected.clear();
38645 if(!suppressEvent){
38646 this.fireEvent("selectionchange", this, this.selected.elements);
38652 * Returns true if the passed node is selected, else false.
38653 * @param {HTMLElement/Number} node The node or node index to check
38654 * @return {Boolean} True if selected, else false
38656 isSelected : function(node){
38657 return this.selected.contains(this.getNode(node));
38661 * Deselects a node.
38662 * @param {HTMLElement/Number} node The node to deselect
38664 deselect : function(node){
38665 if(this.isSelected(node)){
38666 node = this.getNode(node);
38667 this.selected.removeElement(node);
38668 if(this.last == node.viewIndex){
38671 Ext.fly(node).removeClass(this.selectedClass);
38672 this.fireEvent("selectionchange", this, this.selected.elements);
38677 * Selects a set of nodes.
38678 * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node,
38679 * id of a template node or an array of any of those to select
38680 * @param {Boolean} keepExisting (optional) true to keep existing selections
38681 * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
38683 select : function(nodeInfo, keepExisting, suppressEvent){
38684 if(Ext.isArray(nodeInfo)){
38686 this.clearSelections(true);
38688 for(var i = 0, len = nodeInfo.length; i < len; i++){
38689 this.select(nodeInfo[i], true, true);
38691 if(!suppressEvent){
38692 this.fireEvent("selectionchange", this, this.selected.elements);
38695 var node = this.getNode(nodeInfo);
38697 this.clearSelections(true);
38699 if(node && !this.isSelected(node)){
38700 if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){
38701 Ext.fly(node).addClass(this.selectedClass);
38702 this.selected.add(node);
38703 this.last = node.viewIndex;
38704 if(!suppressEvent){
38705 this.fireEvent("selectionchange", this, this.selected.elements);
38713 * Selects a range of nodes. All nodes between start and end are selected.
38714 * @param {Number} start The index of the first node in the range
38715 * @param {Number} end The index of the last node in the range
38716 * @param {Boolean} keepExisting (optional) True to retain existing selections
38718 selectRange : function(start, end, keepExisting){
38720 this.clearSelections(true);
38722 this.select(this.getNodes(start, end), true);
38726 * Gets a template node.
38727 * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
38728 * @return {HTMLElement} The node or null if it wasn't found
38730 getNode : function(nodeInfo){
38731 if(Ext.isString(nodeInfo)){
38732 return document.getElementById(nodeInfo);
38733 }else if(Ext.isNumber(nodeInfo)){
38734 return this.all.elements[nodeInfo];
38740 * Gets a range nodes.
38741 * @param {Number} start (optional) The index of the first node in the range
38742 * @param {Number} end (optional) The index of the last node in the range
38743 * @return {Array} An array of nodes
38745 getNodes : function(start, end){
38746 var ns = this.all.elements;
38747 start = start || 0;
38748 end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
38751 for(i = start; i <= end && ns[i]; i++){
38755 for(i = start; i >= end && ns[i]; i--){
38763 * Finds the index of the passed node.
38764 * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
38765 * @return {Number} The index of the node or -1
38767 indexOf : function(node){
38768 node = this.getNode(node);
38769 if(Ext.isNumber(node.viewIndex)){
38770 return node.viewIndex;
38772 return this.all.indexOf(node);
38776 onBeforeLoad : function(){
38777 if(this.loadingText){
38778 this.clearSelections(false, true);
38779 this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+'</div>');
38784 onDestroy : function(){
38785 Ext.DataView.superclass.onDestroy.call(this);
38786 this.bindStore(null);
38791 * Changes the data store bound to this view and refreshes it. (deprecated in favor of bindStore)
38792 * @param {Store} store The store to bind to this view
38794 Ext.DataView.prototype.setStore = Ext.DataView.prototype.bindStore;
38796 Ext.reg('dataview', Ext.DataView);/**
\r
38797 * @class Ext.ListView
\r
38798 * @extends Ext.DataView
\r
38799 * <p>Ext.ListView is a fast and light-weight implentation of a
\r
38800 * {@link Ext.grid.GridPanel Grid} like view with the following characteristics:</p>
\r
38801 * <div class="mdetail-params"><ul>
\r
38802 * <li>resizable columns</li>
\r
38803 * <li>selectable</li>
\r
38804 * <li>column widths are initially proportioned by percentage based on the container
\r
38805 * width and number of columns</li>
\r
38806 * <li>uses templates to render the data in any required format</li>
\r
38807 * <li>no horizontal scrolling</li>
\r
38808 * <li>no editing</li>
\r
38810 * <p>Example usage:</p>
\r
38812 // consume JSON of this form:
\r
38816 "name":"dance_fever.jpg",
\r
38818 "lastmod":1236974993000,
\r
38819 "url":"images\/thumbs\/dance_fever.jpg"
\r
38822 "name":"zack_sink.jpg",
\r
38824 "lastmod":1236974993000,
\r
38825 "url":"images\/thumbs\/zack_sink.jpg"
\r
38829 var store = new Ext.data.JsonStore({
\r
38830 url: 'get-images.php',
\r
38834 {name:'size', type: 'float'},
\r
38835 {name:'lastmod', type:'date', dateFormat:'timestamp'}
\r
38840 var listView = new Ext.ListView({
\r
38842 multiSelect: true,
\r
38843 emptyText: 'No images to display',
\r
38844 reserveScrollOffset: true,
\r
38848 dataIndex: 'name'
\r
38850 header: 'Last Modified',
\r
38852 dataIndex: 'lastmod',
\r
38853 tpl: '{lastmod:date("m-d h:i a")}'
\r
38856 dataIndex: 'size',
\r
38857 tpl: '{size:fileSize}', // format using Ext.util.Format.fileSize()
\r
38862 // put it in a Panel so it looks pretty
\r
38863 var panel = new Ext.Panel({
\r
38864 id:'images-view',
\r
38867 collapsible:true,
\r
38869 title:'Simple ListView <i>(0 items selected)</i>',
\r
38872 panel.render(document.body);
\r
38874 // little bit of feedback
\r
38875 listView.on('selectionchange', function(view, nodes){
\r
38876 var l = nodes.length;
\r
38877 var s = l != 1 ? 's' : '';
\r
38878 panel.setTitle('Simple ListView <i>('+l+' item'+s+' selected)</i>');
\r
38882 * @param {Object} config
\r
38883 * @xtype listview
\r
38885 Ext.ListView = Ext.extend(Ext.DataView, {
\r
38887 * Set this property to <tt>true</tt> to disable the header click handler disabling sort
\r
38888 * (defaults to <tt>false</tt>).
\r
38890 * @property disableHeaders
\r
38893 * @cfg {Boolean} hideHeaders
\r
38894 * <tt>true</tt> to hide the {@link #internalTpl header row} (defaults to <tt>false</tt> so
\r
38895 * the {@link #internalTpl header row} will be shown).
\r
38898 * @cfg {String} itemSelector
\r
38899 * Defaults to <tt>'dl'</tt> to work with the preconfigured <b><tt>{@link Ext.DataView#tpl tpl}</tt></b>.
\r
38900 * This setting specifies the CSS selector (e.g. <tt>div.some-class</tt> or <tt>span:first-child</tt>)
\r
38901 * that will be used to determine what nodes the ListView will be working with.
\r
38903 itemSelector: 'dl',
\r
38905 * @cfg {String} selectedClass The CSS class applied to a selected row (defaults to
\r
38906 * <tt>'x-list-selected'</tt>). An example overriding the default styling:
\r
38908 .x-list-selected {background-color: yellow;}
\r
38912 selectedClass:'x-list-selected',
\r
38914 * @cfg {String} overClass The CSS class applied when over a row (defaults to
\r
38915 * <tt>'x-list-over'</tt>). An example overriding the default styling:
\r
38917 .x-list-over {background-color: orange;}
\r
38921 overClass:'x-list-over',
\r
38923 * @cfg {Boolean} reserveScrollOffset
\r
38924 * By default will defer accounting for the configured <b><tt>{@link #scrollOffset}</tt></b>
\r
38925 * for 10 milliseconds. Specify <tt>true</tt> to account for the configured
\r
38926 * <b><tt>{@link #scrollOffset}</tt></b> immediately.
\r
38929 * @cfg {Number} scrollOffset The amount of space to reserve for the scrollbar (defaults to
\r
38930 * <tt>19</tt> pixels)
\r
38932 scrollOffset : 19,
\r
38934 * @cfg {Boolean/Object} columnResize
\r
38935 * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.ColumnResizer}
\r
38936 * to enable the columns to be resizable (defaults to <tt>true</tt>).
\r
38938 columnResize: true,
\r
38940 * @cfg {Array} columns An array of column configuration objects, for example:
\r
38944 dataIndex: 'size',
\r
38946 tpl: '{size:fileSize}',
\r
38950 * Acceptable properties for each column configuration object are:
\r
38951 * <div class="mdetail-params"><ul>
\r
38952 * <li><b><tt>align</tt></b> : String<div class="sub-desc">Set the CSS text-align property
\r
38953 * of the column. Defaults to <tt>'left'</tt>.</div></li>
\r
38954 * <li><b><tt>dataIndex</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
\r
38955 * {@link Ext.grid.Column#dataIndex dataIndex} for details.</div></li>
\r
38956 * <li><b><tt>header</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
\r
38957 * {@link Ext.grid.Column#header header} for details.</div></li>
\r
38958 * <li><b><tt>tpl</tt></b> : String<div class="sub-desc">Specify a string to pass as the
\r
38959 * configuration string for {@link Ext.XTemplate}. By default an {@link Ext.XTemplate}
\r
38960 * will be implicitly created using the <tt>dataIndex</tt>.</div></li>
\r
38961 * <li><b><tt>width</tt></b> : Number<div class="sub-desc">Percentage of the container width
\r
38962 * this column should be allocated. Columns that have no width specified will be
\r
38963 * allocated with an equal percentage to fill 100% of the container width. To easily take
\r
38964 * advantage of the full container width, leave the width of at least one column undefined.
\r
38965 * Note that if you do not want to take up the full width of the container, the width of
\r
38966 * every column needs to be explicitly defined.</div></li>
\r
38970 * @cfg {Boolean/Object} columnSort
\r
38971 * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.Sorter}
\r
38972 * to enable the columns to be sortable (defaults to <tt>true</tt>).
\r
38974 columnSort: true,
\r
38976 * @cfg {String/Array} internalTpl
\r
38977 * The template to be used for the header row. See {@link #tpl} for more details.
\r
38980 initComponent : function(){
\r
38981 if(this.columnResize){
\r
38982 this.colResizer = new Ext.ListView.ColumnResizer(this.colResizer);
\r
38983 this.colResizer.init(this);
\r
38985 if(this.columnSort){
\r
38986 this.colSorter = new Ext.ListView.Sorter(this.columnSort);
\r
38987 this.colSorter.init(this);
\r
38989 if(!this.internalTpl){
\r
38990 this.internalTpl = new Ext.XTemplate(
\r
38991 '<div class="x-list-header"><div class="x-list-header-inner">',
\r
38992 '<tpl for="columns">',
\r
38993 '<div style="width:{width}%;text-align:{align};"><em unselectable="on" id="',this.id, '-xlhd-{#}">',
\r
38997 '<div class="x-clear"></div>',
\r
38999 '<div class="x-list-body"><div class="x-list-body-inner">',
\r
39004 this.tpl = new Ext.XTemplate(
\r
39005 '<tpl for="rows">',
\r
39007 '<tpl for="parent.columns">',
\r
39008 '<dt style="width:{width}%;text-align:{align};"><em unselectable="on">',
\r
39009 '{[values.tpl.apply(parent)]}',
\r
39012 '<div class="x-clear"></div>',
\r
39017 var cs = this.columns, allocatedWidth = 0, colsWithWidth = 0, len = cs.length;
\r
39018 for(var i = 0; i < len; i++){
\r
39021 c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}');
\r
39022 }else if(Ext.isString(c.tpl)){
\r
39023 c.tpl = new Ext.XTemplate(c.tpl);
\r
39025 c.align = c.align || 'left';
\r
39026 if(Ext.isNumber(c.width)){
\r
39028 allocatedWidth += c.width;
\r
39032 // auto calculate missing column widths
\r
39033 if(colsWithWidth < len){
\r
39034 var remaining = len - colsWithWidth;
\r
39035 if(allocatedWidth < 100){
\r
39036 var perCol = ((100-allocatedWidth) / remaining);
\r
39037 for(var j = 0; j < len; j++){
\r
39039 if(!Ext.isNumber(c.width)){
\r
39040 c.width = perCol;
\r
39045 Ext.ListView.superclass.initComponent.call(this);
\r
39048 onRender : function(){
\r
39049 Ext.ListView.superclass.onRender.apply(this, arguments);
\r
39051 this.internalTpl.overwrite(this.el, {columns: this.columns});
\r
39053 this.innerBody = Ext.get(this.el.dom.childNodes[1].firstChild);
\r
39054 this.innerHd = Ext.get(this.el.dom.firstChild.firstChild);
\r
39056 if(this.hideHeaders){
\r
39057 this.el.dom.firstChild.style.display = 'none';
\r
39061 getTemplateTarget : function(){
\r
39062 return this.innerBody;
\r
39066 * <p>Function which can be overridden which returns the data object passed to this
\r
39067 * view's {@link #tpl template} to render the whole ListView. The returned object
\r
39068 * shall contain the following properties:</p>
\r
39069 * <div class="mdetail-params"><ul>
\r
39070 * <li><b>columns</b> : String<div class="sub-desc">See <tt>{@link #columns}</tt></div></li>
\r
39071 * <li><b>rows</b> : String<div class="sub-desc">See
\r
39072 * <tt>{@link Ext.DataView}.{@link Ext.DataView#collectData collectData}</div></li>
\r
39074 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
\r
39075 * @param {Number} startIndex the index number of the Record being prepared for rendering.
\r
39076 * @return {Object} A data object containing properties to be processed by a repeating
\r
39077 * XTemplate as described above.
\r
39079 collectData : function(){
\r
39080 var rs = Ext.ListView.superclass.collectData.apply(this, arguments);
\r
39082 columns: this.columns,
\r
39087 verifyInternalSize : function(){
\r
39088 if(this.lastSize){
\r
39089 this.onResize(this.lastSize.width, this.lastSize.height);
\r
39094 onResize : function(w, h){
\r
39095 var bd = this.innerBody.dom;
\r
39096 var hd = this.innerHd.dom
\r
39100 var bdp = bd.parentNode;
\r
39101 if(Ext.isNumber(w)){
\r
39102 var sw = w - this.scrollOffset;
\r
39103 if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){
\r
39104 bd.style.width = sw + 'px';
\r
39105 hd.style.width = sw + 'px';
\r
39107 bd.style.width = w + 'px';
\r
39108 hd.style.width = w + 'px';
\r
39109 setTimeout(function(){
\r
39110 if((bdp.offsetWidth - bdp.clientWidth) > 10){
\r
39111 bd.style.width = sw + 'px';
\r
39112 hd.style.width = sw + 'px';
\r
39117 if(Ext.isNumber(h == 'number')){
\r
39118 bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px';
\r
39122 updateIndexes : function(){
\r
39123 Ext.ListView.superclass.updateIndexes.apply(this, arguments);
\r
39124 this.verifyInternalSize();
\r
39127 findHeaderIndex : function(hd){
\r
39128 hd = hd.dom || hd;
\r
39129 var pn = hd.parentNode, cs = pn.parentNode.childNodes;
\r
39130 for(var i = 0, c; c = cs[i]; i++){
\r
39138 setHdWidths : function(){
\r
39139 var els = this.innerHd.dom.getElementsByTagName('div');
\r
39140 for(var i = 0, cs = this.columns, len = cs.length; i < len; i++){
\r
39141 els[i].style.width = cs[i].width + '%';
\r
39146 Ext.reg('listview', Ext.ListView);/**
\r
39147 * @class Ext.ListView.ColumnResizer
\r
39148 * @extends Ext.util.Observable
\r
39149 * <p>Supporting Class for Ext.ListView.</p>
\r
39151 * @param {Object} config
\r
39153 Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, {
\r
39155 * @cfg {Number} minPct The minimum percentage to allot for any column (defaults to <tt>.05</tt>)
\r
39159 constructor: function(config){
\r
39160 Ext.apply(this, config);
\r
39161 Ext.ListView.ColumnResizer.superclass.constructor.call(this);
\r
39163 init : function(listView){
\r
39164 this.view = listView;
\r
39165 listView.on('render', this.initEvents, this);
\r
39168 initEvents : function(view){
\r
39169 view.mon(view.innerHd, 'mousemove', this.handleHdMove, this);
\r
39170 this.tracker = new Ext.dd.DragTracker({
\r
39171 onBeforeStart: this.onBeforeStart.createDelegate(this),
\r
39172 onStart: this.onStart.createDelegate(this),
\r
39173 onDrag: this.onDrag.createDelegate(this),
\r
39174 onEnd: this.onEnd.createDelegate(this),
\r
39178 this.tracker.initEl(view.innerHd);
\r
39179 view.on('beforedestroy', this.tracker.destroy, this.tracker);
\r
39182 handleHdMove : function(e, t){
\r
39184 var x = e.getPageX();
\r
39185 var hd = e.getTarget('em', 3, true);
\r
39187 var r = hd.getRegion();
\r
39188 var ss = hd.dom.style;
\r
39189 var pn = hd.dom.parentNode;
\r
39191 if(x - r.left <= hw && pn != pn.parentNode.firstChild){
\r
39192 this.activeHd = Ext.get(pn.previousSibling.firstChild);
\r
39193 ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
\r
39194 } else if(r.right - x <= hw && pn != pn.parentNode.lastChild.previousSibling){
\r
39195 this.activeHd = hd;
\r
39196 ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
\r
39198 delete this.activeHd;
\r
39204 onBeforeStart : function(e){
\r
39205 this.dragHd = this.activeHd;
\r
39206 return !!this.dragHd;
\r
39209 onStart: function(e){
\r
39210 this.view.disableHeaders = true;
\r
39211 this.proxy = this.view.el.createChild({cls:'x-list-resizer'});
\r
39212 this.proxy.setHeight(this.view.el.getHeight());
\r
39214 var x = this.tracker.getXY()[0];
\r
39215 var w = this.view.innerHd.getWidth();
\r
39217 this.hdX = this.dragHd.getX();
\r
39218 this.hdIndex = this.view.findHeaderIndex(this.dragHd);
\r
39220 this.proxy.setX(this.hdX);
\r
39221 this.proxy.setWidth(x-this.hdX);
\r
39223 this.minWidth = w*this.minPct;
\r
39224 this.maxWidth = w - (this.minWidth*(this.view.columns.length-1-this.hdIndex));
\r
39227 onDrag: function(e){
\r
39228 var cursorX = this.tracker.getXY()[0];
\r
39229 this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
\r
39232 onEnd: function(e){
\r
39233 var nw = this.proxy.getWidth();
\r
39234 this.proxy.remove();
\r
39236 var index = this.hdIndex;
\r
39237 var vw = this.view, cs = vw.columns, len = cs.length;
\r
39238 var w = this.view.innerHd.getWidth(), minPct = this.minPct * 100;
\r
39240 var pct = Math.ceil((nw*100) / w);
\r
39241 var diff = cs[index].width - pct;
\r
39242 var each = Math.floor(diff / (len-1-index));
\r
39243 var mod = diff - (each * (len-1-index));
\r
39245 for(var i = index+1; i < len; i++){
\r
39246 var cw = cs[i].width + each;
\r
39247 var ncw = Math.max(minPct, cw);
\r
39251 cs[i].width = ncw;
\r
39253 cs[index].width = pct;
\r
39254 cs[index+1].width += mod;
\r
39255 delete this.dragHd;
\r
39256 this.view.setHdWidths();
\r
39257 this.view.refresh();
\r
39258 setTimeout(function(){
\r
39259 vw.disableHeaders = false;
\r
39263 * @class Ext.ListView.Sorter
\r
39264 * @extends Ext.util.Observable
\r
39265 * <p>Supporting Class for Ext.ListView.</p>
\r
39267 * @param {Object} config
\r
39269 Ext.ListView.Sorter = Ext.extend(Ext.util.Observable, {
\r
39271 * @cfg {Array} sortClasses
\r
39272 * The CSS classes applied to a header when it is sorted. (defaults to <tt>["sort-asc", "sort-desc"]</tt>)
\r
39274 sortClasses : ["sort-asc", "sort-desc"],
\r
39276 constructor: function(config){
\r
39277 Ext.apply(this, config);
\r
39278 Ext.ListView.Sorter.superclass.constructor.call(this);
\r
39281 init : function(listView){
\r
39282 this.view = listView;
\r
39283 listView.on('render', this.initEvents, this);
\r
39286 initEvents : function(view){
\r
39287 view.mon(view.innerHd, 'click', this.onHdClick, this);
\r
39288 view.innerHd.setStyle('cursor', 'pointer');
\r
39289 view.mon(view.store, 'datachanged', this.updateSortState, this);
\r
39290 this.updateSortState.defer(10, this, [view.store]);
\r
39293 updateSortState : function(store){
\r
39294 var state = store.getSortState();
\r
39298 this.sortState = state;
\r
39299 var cs = this.view.columns, sortColumn = -1;
\r
39300 for(var i = 0, len = cs.length; i < len; i++){
\r
39301 if(cs[i].dataIndex == state.field){
\r
39306 if(sortColumn != -1){
\r
39307 var sortDir = state.direction;
\r
39308 this.updateSortIcon(sortColumn, sortDir);
\r
39312 updateSortIcon : function(col, dir){
\r
39313 var sc = this.sortClasses;
\r
39314 var hds = this.view.innerHd.select('em').removeClass(sc);
\r
39315 hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
\r
39318 onHdClick : function(e){
\r
39319 var hd = e.getTarget('em', 3);
\r
39320 if(hd && !this.view.disableHeaders){
\r
39321 var index = this.view.findHeaderIndex(hd);
\r
39322 this.view.store.sort(this.view.columns[index].dataIndex);
\r
39326 * @class Ext.TabPanel
39327 * <p>A basic tab container. TabPanels can be used exactly like a standard {@link Ext.Panel}
39328 * for layout purposes, but also have special support for containing child Components
39329 * (<tt>{@link Ext.Container#items items}</tt>) that are managed using a
39330 * {@link Ext.layout.CardLayout CardLayout layout manager}, and displayed as separate tabs.</p>
39332 * <b>Note:</b> By default, a tab's close tool <i>destroys</i> the child tab Component
39333 * and all its descendants. This makes the child tab Component, and all its descendants <b>unusable</b>. To enable
39334 * re-use of a tab, configure the TabPanel with <b><code>{@link #autoDestroy autoDestroy: false}</code></b>.
39336 * <p><b><u>TabPanel header/footer elements</u></b></p>
39337 * <p>TabPanels use their {@link Ext.Panel#header header} or {@link Ext.Panel#footer footer} element
39338 * (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
39339 * This means that a TabPanel will not display any configured title, and will not display any
39340 * configured header {@link Ext.Panel#tools tools}.</p>
39341 * <p>To display a header, embed the TabPanel in a {@link Ext.Panel Panel} which uses
39342 * <b><tt>{@link Ext.Container#layout layout:'fit'}</tt></b>.</p>
39344 * <p><b><u>Tab Events</u></b></p>
39345 * <p>There is no actual tab class — each tab is simply a {@link Ext.BoxComponent Component}
39346 * such as a {@link Ext.Panel Panel}. However, when rendered in a TabPanel, each child Component
39347 * can fire additional events that only exist for tabs and are not available from other Components.
39348 * These events are:</p>
39349 * <div><ul class="mdetail-params">
39350 * <li><tt><b>{@link Ext.Panel#activate activate}</b></tt> : Fires when this Component becomes
39351 * the active tab.</li>
39352 * <li><tt><b>{@link Ext.Panel#deactivate deactivate}</b></tt> : Fires when the Component that
39353 * was the active tab becomes deactivated.</li>
39355 * <p><b><u>Creating TabPanels from Code</u></b></p>
39356 * <p>TabPanels can be created and rendered completely in code, as in this example:</p>
39358 var tabs = new Ext.TabPanel({
39359 renderTo: Ext.getBody(),
39363 html: 'A simple tab'
39366 html: 'Another one'
39370 * <p><b><u>Creating TabPanels from Existing Markup</u></b></p>
39371 * <p>TabPanels can also be rendered from pre-existing markup in a couple of ways.</p>
39372 * <div><ul class="mdetail-params">
39374 * <li>Pre-Structured Markup</li>
39375 * <div class="sub-desc">
39376 * <p>A container div with one or more nested tab divs with class <tt>'x-tab'</tt> can be rendered entirely
39377 * from existing markup (See the {@link #autoTabs} example).</p>
39380 * <li>Un-Structured Markup</li>
39381 * <div class="sub-desc">
39382 * <p>A TabPanel can also be rendered from markup that is not strictly structured by simply specifying by id
39383 * which elements should be the container and the tabs. Using this method tab content can be pulled from different
39384 * elements within the page by id regardless of page structure. For example:</p>
39386 var tabs = new Ext.TabPanel({
39387 renderTo: 'my-tabs',
39390 {contentEl:'tab1', title:'Tab 1'},
39391 {contentEl:'tab2', title:'Tab 2'}
39395 // Note that the tabs do not have to be nested within the container (although they can be)
39396 <div id="my-tabs"></div>
39397 <div id="tab1" class="x-hide-display">A simple tab</div>
39398 <div id="tab2" class="x-hide-display">Another one</div>
39400 * Note that the tab divs in this example contain the class <tt>'x-hide-display'</tt> so that they can be rendered
39401 * deferred without displaying outside the tabs. You could alternately set <tt>{@link #deferredRender} = false </tt>
39402 * to render all content tabs on page load.
39407 * @extends Ext.Panel
39409 * @param {Object} config The configuration options
39412 Ext.TabPanel = Ext.extend(Ext.Panel, {
39414 * @cfg {Boolean} layoutOnTabChange
39415 * Set to true to force a layout of the active tab when the tab is changed. Defaults to false.
39416 * See {@link Ext.layout.CardLayout}.<code>{@link Ext.layout.CardLayout#layoutOnCardChange layoutOnCardChange}</code>.
39419 * @cfg {String} tabCls <b>This config option is used on <u>child Components</u> of ths TabPanel.</b> A CSS
39420 * class name applied to the tab strip item representing the child Component, allowing special
39421 * styling to be applied.
39424 * @cfg {Boolean} monitorResize True to automatically monitor window resize events and rerender the layout on
39425 * browser resize (defaults to true).
39427 monitorResize : true,
39429 * @cfg {Boolean} deferredRender
39430 * <p><tt>true</tt> by default to defer the rendering of child <tt>{@link Ext.Container#items items}</tt>
39431 * to the browsers DOM until a tab is activated. <tt>false</tt> will render all contained
39432 * <tt>{@link Ext.Container#items items}</tt> as soon as the {@link Ext.layout.CardLayout layout}
39433 * is rendered. If there is a significant amount of content or a lot of heavy controls being
39434 * rendered into panels that are not displayed by default, setting this to <tt>true</tt> might
39435 * improve performance.</p>
39436 * <br><p>The <tt>deferredRender</tt> property is internally passed to the layout manager for
39437 * TabPanels ({@link Ext.layout.CardLayout}) as its {@link Ext.layout.CardLayout#deferredRender}
39438 * configuration value.</p>
39439 * <br><p><b>Note</b>: leaving <tt>deferredRender</tt> as <tt>true</tt> means that the content
39440 * within an unactivated tab will not be available. For example, this means that if the TabPanel
39441 * is within a {@link Ext.form.FormPanel form}, then until a tab is activated, any Fields within
39442 * unactivated tabs will not be rendered, and will therefore not be submitted and will not be
39443 * available to either {@link Ext.form.BasicForm#getValues getValues} or
39444 * {@link Ext.form.BasicForm#setValues setValues}.</p>
39446 deferredRender : true,
39448 * @cfg {Number} tabWidth The initial width in pixels of each new tab (defaults to 120).
39452 * @cfg {Number} minTabWidth The minimum width in pixels for each tab when {@link #resizeTabs} = true (defaults to 30).
39456 * @cfg {Boolean} resizeTabs True to automatically resize each tab so that the tabs will completely fill the
39457 * tab strip (defaults to false). Setting this to true may cause specific widths that might be set per tab to
39458 * be overridden in order to fit them all into view (although {@link #minTabWidth} will always be honored).
39460 resizeTabs : false,
39462 * @cfg {Boolean} enableTabScroll True to enable scrolling to tabs that may be invisible due to overflowing the
39463 * overall TabPanel width. Only available with tabPosition:'top' (defaults to false).
39465 enableTabScroll : false,
39467 * @cfg {Number} scrollIncrement The number of pixels to scroll each time a tab scroll button is pressed
39468 * (defaults to <tt>100</tt>, or if <tt>{@link #resizeTabs} = true</tt>, the calculated tab width). Only
39469 * applies when <tt>{@link #enableTabScroll} = true</tt>.
39471 scrollIncrement : 0,
39473 * @cfg {Number} scrollRepeatInterval Number of milliseconds between each scroll while a tab scroll button is
39474 * continuously pressed (defaults to <tt>400</tt>).
39476 scrollRepeatInterval : 400,
39478 * @cfg {Float} scrollDuration The number of milliseconds that each scroll animation should last (defaults
39479 * to <tt>.35</tt>). Only applies when <tt>{@link #animScroll} = true</tt>.
39481 scrollDuration : 0.35,
39483 * @cfg {Boolean} animScroll True to animate tab scrolling so that hidden tabs slide smoothly into view (defaults
39484 * to <tt>true</tt>). Only applies when <tt>{@link #enableTabScroll} = true</tt>.
39488 * @cfg {String} tabPosition The position where the tab strip should be rendered (defaults to <tt>'top'</tt>).
39489 * The only other supported value is <tt>'bottom'</tt>. <b>Note</b>: tab scrolling is only supported for
39490 * <tt>tabPosition: 'top'</tt>.
39492 tabPosition : 'top',
39494 * @cfg {String} baseCls The base CSS class applied to the panel (defaults to <tt>'x-tab-panel'</tt>).
39496 baseCls : 'x-tab-panel',
39498 * @cfg {Boolean} autoTabs
39499 * <p><tt>true</tt> to query the DOM for any divs with a class of 'x-tab' to be automatically converted
39500 * to tabs and added to this panel (defaults to <tt>false</tt>). Note that the query will be executed within
39501 * the scope of the container element only (so that multiple tab panels from markup can be supported via this
39503 * <p>This method is only possible when the markup is structured correctly as a container with nested divs
39504 * containing the class <tt>'x-tab'</tt>. To create TabPanels without these limitations, or to pull tab content
39505 * from other elements on the page, see the example at the top of the class for generating tabs from markup.</p>
39506 * <p>There are a couple of things to note when using this method:<ul>
39507 * <li>When using the <tt>autoTabs</tt> config (as opposed to passing individual tab configs in the TabPanel's
39508 * {@link #items} collection), you must use <tt>{@link #applyTo}</tt> to correctly use the specified <tt>id</tt>
39509 * as the tab container. The <tt>autoTabs</tt> method <em>replaces</em> existing content with the TabPanel
39511 * <li>Make sure that you set <tt>{@link #deferredRender}: false</tt> so that the content elements for each
39512 * tab will be rendered into the TabPanel immediately upon page load, otherwise they will not be transformed
39513 * until each tab is activated and will be visible outside the TabPanel.</li>
39514 * </ul>Example usage:</p>
39516 var tabs = new Ext.TabPanel({
39517 applyTo: 'my-tabs',
39519 deferredRender: false,
39523 // This markup will be converted to a TabPanel from the code above
39524 <div id="my-tabs">
39525 <div class="x-tab" title="Tab 1">A simple tab</div>
39526 <div class="x-tab" title="Tab 2">Another one</div>
39532 * @cfg {String} autoTabSelector The CSS selector used to search for tabs in existing markup when
39533 * <tt>{@link #autoTabs} = true</tt> (defaults to <tt>'div.x-tab'</tt>). This can be any valid selector
39534 * supported by {@link Ext.DomQuery#select}. Note that the query will be executed within the scope of this
39535 * tab panel only (so that multiple tab panels from markup can be supported on a page).
39537 autoTabSelector : 'div.x-tab',
39539 * @cfg {String/Number} activeTab A string id or the numeric index of the tab that should be initially
39540 * activated on render (defaults to none).
39544 * @cfg {Number} tabMargin The number of pixels of space to calculate into the sizing and scrolling of
39545 * tabs. If you change the margin in CSS, you will need to update this value so calculations are correct
39546 * with either <tt>{@link #resizeTabs}</tt> or scrolling tabs. (defaults to <tt>2</tt>)
39550 * @cfg {Boolean} plain </tt>true</tt> to render the tab strip without a background container image
39551 * (defaults to <tt>false</tt>).
39555 * @cfg {Number} wheelIncrement For scrolling tabs, the number of pixels to increment on mouse wheel
39556 * scrolling (defaults to <tt>20</tt>).
39558 wheelIncrement : 20,
39561 * This is a protected property used when concatenating tab ids to the TabPanel id for internal uniqueness.
39562 * It does not generally need to be changed, but can be if external code also uses an id scheme that can
39563 * potentially clash with this one.
39565 idDelimiter : '__',
39568 itemCls : 'x-tab-item',
39570 // private config overrides
39572 headerAsText : false,
39577 initComponent : function(){
39578 this.frame = false;
39579 Ext.TabPanel.superclass.initComponent.call(this);
39582 * @event beforetabchange
39583 * Fires before the active tab changes. Handlers can <tt>return false</tt> to cancel the tab change.
39584 * @param {TabPanel} this
39585 * @param {Panel} newTab The tab being activated
39586 * @param {Panel} currentTab The current active tab
39591 * Fires after the active tab has changed.
39592 * @param {TabPanel} this
39593 * @param {Panel} tab The new active tab
39597 * @event contextmenu
39598 * Relays the contextmenu event from a tab selector element in the tab strip.
39599 * @param {TabPanel} this
39600 * @param {Panel} tab The target tab
39601 * @param {EventObject} e
39606 * @cfg {Object} layoutConfig
39607 * TabPanel implicitly uses {@link Ext.layout.CardLayout} as its layout manager.
39608 * <code>layoutConfig</code> may be used to configure this layout manager.
39609 * <code>{@link #deferredRender}</code> and <code>{@link #layoutOnTabChange}</code>
39610 * configured on the TabPanel will be applied as configs to the layout manager.
39612 this.setLayout(new Ext.layout.CardLayout(Ext.apply({
39613 layoutOnCardChange: this.layoutOnTabChange,
39614 deferredRender: this.deferredRender
39615 }, this.layoutConfig)));
39617 if(this.tabPosition == 'top'){
39618 this.elements += ',header';
39619 this.stripTarget = 'header';
39621 this.elements += ',footer';
39622 this.stripTarget = 'footer';
39625 this.stack = Ext.TabPanel.AccessStack();
39631 onRender : function(ct, position){
39632 Ext.TabPanel.superclass.onRender.call(this, ct, position);
39635 var pos = this.tabPosition == 'top' ? 'header' : 'footer';
39636 this[pos].addClass('x-tab-panel-'+pos+'-plain');
39639 var st = this[this.stripTarget];
39641 this.stripWrap = st.createChild({cls:'x-tab-strip-wrap', cn:{
39642 tag:'ul', cls:'x-tab-strip x-tab-strip-'+this.tabPosition}});
39644 var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
39645 this.stripSpacer = st.createChild({cls:'x-tab-strip-spacer'}, beforeEl);
39646 this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
39648 this.edge = this.strip.createChild({tag:'li', cls:'x-tab-edge'});
39649 this.strip.createChild({cls:'x-clear'});
39651 this.body.addClass('x-tab-panel-body-'+this.tabPosition);
39654 * @cfg {Template/XTemplate} itemTpl <p>(Optional) A {@link Ext.Template Template} or
39655 * {@link Ext.XTemplate XTemplate} which may be provided to process the data object returned from
39656 * <tt>{@link #getTemplateArgs}</tt> to produce a clickable selector element in the tab strip.</p>
39657 * <p>The main element created should be a <tt><li></tt> element. In order for a click event on
39658 * a selector element to be connected to its item, it must take its <i>id</i> from the TabPanel's
39659 * native <tt>{@link #getTemplateArgs}</tt>.</p>
39660 * <p>The child element which contains the title text must be marked by the CSS class
39661 * <tt>x-tab-strip-inner</tt>.</p>
39662 * <p>To enable closability, the created element should contain an element marked by the CSS class
39663 * <tt>x-tab-strip-close</tt>.</p>
39664 * <p>If a custom <tt>itemTpl</tt> is supplied, it is the developer's responsibility to create CSS
39665 * style rules to create the desired appearance.</p>
39666 * Below is an example of how to create customized tab selector items:<pre><code>
39668 renderTo: document.body,
39671 enableTabScroll: true,
39674 defaults: {autoScroll:true},
39675 itemTpl: new Ext.XTemplate(
39676 '<li class="{cls}" id="{id}" style="overflow:hidden">',
39677 '<tpl if="closable">',
39678 '<a class="x-tab-strip-close" onclick="return false;"></a>',
39680 '<a class="x-tab-right" href="#" onclick="return false;" style="padding-left:6px">',
39681 '<em class="x-tab-left">',
39682 '<span class="x-tab-strip-inner">',
39683 '<img src="{src}" style="float:left;margin:3px 3px 0 0">',
39684 '<span style="margin-left:20px" class="x-tab-strip-text {iconCls}">{text} {extra}</span>',
39690 getTemplateArgs: function(item) {
39691 // Call the native method to collect the base data. Like the ID!
39692 var result = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);
39694 // Add stuff used in our template
39695 return Ext.apply(result, {
39696 closable: item.closable,
39698 extra: item.extraText || ''
39702 title: 'New Tab 1',
39703 iconSrc: '../shared/icons/fam/grid.png',
39704 html: 'Tab Body 1',
39707 title: 'New Tab 2',
39708 iconSrc: '../shared/icons/fam/grid.png',
39709 html: 'Tab Body 2',
39710 extraText: 'Extra stuff in the tab button'
39716 var tt = new Ext.Template(
39717 '<li class="{cls}" id="{id}"><a class="x-tab-strip-close" onclick="return false;"></a>',
39718 '<a class="x-tab-right" href="#" onclick="return false;"><em class="x-tab-left">',
39719 '<span class="x-tab-strip-inner"><span class="x-tab-strip-text {iconCls}">{text}</span></span>',
39722 tt.disableFormats = true;
39724 Ext.TabPanel.prototype.itemTpl = tt;
39727 this.items.each(this.initTab, this);
39731 afterRender : function(){
39732 Ext.TabPanel.superclass.afterRender.call(this);
39734 this.readTabs(false);
39736 if(this.activeTab !== undefined){
39737 var item = Ext.isObject(this.activeTab) ? this.activeTab : this.items.get(this.activeTab);
39738 delete this.activeTab;
39739 this.setActiveTab(item);
39744 initEvents : function(){
39745 Ext.TabPanel.superclass.initEvents.call(this);
39746 this.on('add', this.onAdd, this, {target: this});
39747 this.on('remove', this.onRemove, this, {target: this});
39749 this.mon(this.strip, 'mousedown', this.onStripMouseDown, this);
39750 this.mon(this.strip, 'contextmenu', this.onStripContextMenu, this);
39751 if(this.enableTabScroll){
39752 this.mon(this.strip, 'mousewheel', this.onWheel, this);
39757 findTargets : function(e){
39759 var itemEl = e.getTarget('li', this.strip);
39761 item = this.getComponent(itemEl.id.split(this.idDelimiter)[1]);
39771 close : e.getTarget('.x-tab-strip-close', this.strip),
39778 onStripMouseDown : function(e){
39779 if(e.button !== 0){
39782 e.preventDefault();
39783 var t = this.findTargets(e);
39785 if (t.item.fireEvent('beforeclose', t.item) !== false) {
39786 t.item.fireEvent('close', t.item);
39787 this.remove(t.item);
39791 if(t.item && t.item != this.activeTab){
39792 this.setActiveTab(t.item);
39797 onStripContextMenu : function(e){
39798 e.preventDefault();
39799 var t = this.findTargets(e);
39801 this.fireEvent('contextmenu', this, t.item, e);
39806 * True to scan the markup in this tab panel for <tt>{@link #autoTabs}</tt> using the
39807 * <tt>{@link #autoTabSelector}</tt>
39808 * @param {Boolean} removeExisting True to remove existing tabs
39810 readTabs : function(removeExisting){
39811 if(removeExisting === true){
39812 this.items.each(function(item){
39816 var tabs = this.el.query(this.autoTabSelector);
39817 for(var i = 0, len = tabs.length; i < len; i++){
39819 var title = tab.getAttribute('title');
39820 tab.removeAttribute('title');
39829 initTab : function(item, index){
39830 var before = this.strip.dom.childNodes[index];
39831 var p = this.getTemplateArgs(item);
39833 this.itemTpl.insertBefore(before, p) :
39834 this.itemTpl.append(this.strip, p);
39836 Ext.fly(el).addClassOnOver('x-tab-strip-over');
39839 Ext.fly(el).child('span.x-tab-strip-text', true).qtip = item.tabTip;
39843 item.on('disable', this.onItemDisabled, this);
39844 item.on('enable', this.onItemEnabled, this);
39845 item.on('titlechange', this.onItemTitleChanged, this);
39846 item.on('iconchange', this.onItemIconChanged, this);
39847 item.on('beforeshow', this.onBeforeShowItem, this);
39851 * <p>Provides template arguments for rendering a tab selector item in the tab strip.</p>
39852 * <p>This method returns an object hash containing properties used by the TabPanel's <tt>{@link #itemTpl}</tt>
39853 * to create a formatted, clickable tab selector element. The properties which must be returned
39854 * are:</p><div class="mdetail-params"><ul>
39855 * <li><b>id</b> : String<div class="sub-desc">A unique identifier which links to the item</div></li>
39856 * <li><b>text</b> : String<div class="sub-desc">The text to display</div></li>
39857 * <li><b>cls</b> : String<div class="sub-desc">The CSS class name</div></li>
39858 * <li><b>iconCls</b> : String<div class="sub-desc">A CSS class to provide appearance for an icon.</div></li>
39860 * @param {BoxComponent} item The {@link Ext.BoxComponent BoxComponent} for which to create a selector element in the tab strip.
39861 * @return {Object} An object hash containing the properties required to render the selector element.
39863 getTemplateArgs : function(item) {
39864 var cls = item.closable ? 'x-tab-strip-closable' : '';
39866 cls += ' x-item-disabled';
39869 cls += ' x-tab-with-icon';
39872 cls += ' ' + item.tabCls;
39876 id: this.id + this.idDelimiter + item.getItemId(),
39879 iconCls: item.iconCls || ''
39884 onAdd : function(tp, item, index){
39885 this.initTab(item, index);
39886 if(this.items.getCount() == 1){
39889 this.delegateUpdates();
39893 onBeforeAdd : function(item){
39894 var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);
39896 this.setActiveTab(item);
39899 Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);
39900 var es = item.elements;
39901 item.elements = es ? es.replace(',header', '') : es;
39902 item.border = (item.border === true);
39906 onRemove : function(tp, item){
39907 Ext.destroy(Ext.get(this.getTabEl(item)));
39908 this.stack.remove(item);
39909 item.un('disable', this.onItemDisabled, this);
39910 item.un('enable', this.onItemEnabled, this);
39911 item.un('titlechange', this.onItemTitleChanged, this);
39912 item.un('iconchange', this.onItemIconChanged, this);
39913 item.un('beforeshow', this.onBeforeShowItem, this);
39914 if(item == this.activeTab){
39915 var next = this.stack.next();
39917 this.setActiveTab(next);
39918 }else if(this.items.getCount() > 0){
39919 this.setActiveTab(0);
39921 this.activeTab = null;
39924 this.delegateUpdates();
39928 onBeforeShowItem : function(item){
39929 if(item != this.activeTab){
39930 this.setActiveTab(item);
39936 onItemDisabled : function(item){
39937 var el = this.getTabEl(item);
39939 Ext.fly(el).addClass('x-item-disabled');
39941 this.stack.remove(item);
39945 onItemEnabled : function(item){
39946 var el = this.getTabEl(item);
39948 Ext.fly(el).removeClass('x-item-disabled');
39953 onItemTitleChanged : function(item){
39954 var el = this.getTabEl(item);
39956 Ext.fly(el).child('span.x-tab-strip-text', true).innerHTML = item.title;
39961 onItemIconChanged : function(item, iconCls, oldCls){
39962 var el = this.getTabEl(item);
39964 Ext.fly(el).child('span.x-tab-strip-text').replaceClass(oldCls, iconCls);
39969 * Gets the DOM element for the tab strip item which activates the child panel with the specified
39970 * ID. Access this to change the visual treatment of the item, for example by changing the CSS class name.
39971 * @param {Panel/Number/String} tab The tab component, or the tab's index, or the tabs id or itemId.
39972 * @return {HTMLElement} The DOM node
39974 getTabEl : function(item){
39975 return document.getElementById(this.id + this.idDelimiter + this.getComponent(item).getItemId());
39979 onResize : function(){
39980 Ext.TabPanel.superclass.onResize.apply(this, arguments);
39981 this.delegateUpdates();
39985 * Suspends any internal calculations or scrolling while doing a bulk operation. See {@link #endUpdate}
39987 beginUpdate : function(){
39988 this.suspendUpdates = true;
39992 * Resumes calculations and scrolling at the end of a bulk operation. See {@link #beginUpdate}
39994 endUpdate : function(){
39995 this.suspendUpdates = false;
39996 this.delegateUpdates();
40000 * Hides the tab strip item for the passed tab
40001 * @param {Number/String/Panel} item The tab index, id or item
40003 hideTabStripItem : function(item){
40004 item = this.getComponent(item);
40005 var el = this.getTabEl(item);
40007 el.style.display = 'none';
40008 this.delegateUpdates();
40010 this.stack.remove(item);
40014 * Unhides the tab strip item for the passed tab
40015 * @param {Number/String/Panel} item The tab index, id or item
40017 unhideTabStripItem : function(item){
40018 item = this.getComponent(item);
40019 var el = this.getTabEl(item);
40021 el.style.display = '';
40022 this.delegateUpdates();
40027 delegateUpdates : function(){
40028 if(this.suspendUpdates){
40031 if(this.resizeTabs && this.rendered){
40032 this.autoSizeTabs();
40034 if(this.enableTabScroll && this.rendered){
40035 this.autoScrollTabs();
40040 autoSizeTabs : function(){
40041 var count = this.items.length;
40042 var ce = this.tabPosition != 'bottom' ? 'header' : 'footer';
40043 var ow = this[ce].dom.offsetWidth;
40044 var aw = this[ce].dom.clientWidth;
40046 if(!this.resizeTabs || count < 1 || !aw){ // !aw for display:none
40050 var each = Math.max(Math.min(Math.floor((aw-4) / count) - this.tabMargin, this.tabWidth), this.minTabWidth); // -4 for float errors in IE
40051 this.lastTabWidth = each;
40052 var lis = this.strip.query("li:not([className^=x-tab-edge])");
40053 for(var i = 0, len = lis.length; i < len; i++) {
40055 var inner = Ext.fly(li).child('.x-tab-strip-inner', true);
40056 var tw = li.offsetWidth;
40057 var iw = inner.offsetWidth;
40058 inner.style.width = (each - (tw-iw)) + 'px';
40063 adjustBodyWidth : function(w){
40065 this.header.setWidth(w);
40068 this.footer.setWidth(w);
40074 * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which
40075 * can <tt>return false</tt> to cancel the tab change.
40076 * @param {String/Number} item
40077 * The id or tab Panel to activate. This parameter may be any of the following:
40078 * <div><ul class="mdetail-params">
40079 * <li>a <b><tt>String</tt></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
40080 * or <code>{@link Ext.Component#id id}</code> of the child component </li>
40081 * <li>a <b><tt>Number</tt></b> : representing the position of the child component
40082 * within the <code>{@link Ext.Container#items items}</code> <b>property</b></li>
40084 * <p>For additional information see {@link Ext.util.MixedCollection#get}.
40086 setActiveTab : function(item){
40087 item = this.getComponent(item);
40088 if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){
40091 if(!this.rendered){
40092 this.activeTab = item;
40095 if(this.activeTab != item){
40096 if(this.activeTab){
40097 var oldEl = this.getTabEl(this.activeTab);
40099 Ext.fly(oldEl).removeClass('x-tab-strip-active');
40101 this.activeTab.fireEvent('deactivate', this.activeTab);
40103 var el = this.getTabEl(item);
40104 Ext.fly(el).addClass('x-tab-strip-active');
40105 this.activeTab = item;
40106 this.stack.add(item);
40108 this.layout.setActiveItem(item);
40109 if(this.scrolling){
40110 this.scrollToTab(item, this.animScroll);
40113 item.fireEvent('activate', item);
40114 this.fireEvent('tabchange', this, item);
40119 * Gets the currently active tab.
40120 * @return {Panel} The active tab
40122 getActiveTab : function(){
40123 return this.activeTab || null;
40127 * Gets the specified tab by id.
40128 * @param {String} id The tab id
40129 * @return {Panel} The tab
40131 getItem : function(item){
40132 return this.getComponent(item);
40136 autoScrollTabs : function(){
40137 this.pos = this.tabPosition=='bottom' ? this.footer : this.header;
40138 var count = this.items.length;
40139 var ow = this.pos.dom.offsetWidth;
40140 var tw = this.pos.dom.clientWidth;
40142 var wrap = this.stripWrap;
40144 var cw = wd.offsetWidth;
40145 var pos = this.getScrollPos();
40146 var l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos;
40148 if(!this.enableTabScroll || count < 1 || cw < 20){ // 20 to prevent display:none issues
40154 if(this.scrolling){
40155 this.scrolling = false;
40156 this.pos.removeClass('x-tab-scrolling');
40157 this.scrollLeft.hide();
40158 this.scrollRight.hide();
40159 // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari
40160 if(Ext.isAir || Ext.isWebKit){
40161 wd.style.marginLeft = '';
40162 wd.style.marginRight = '';
40166 if(!this.scrolling){
40167 this.pos.addClass('x-tab-scrolling');
40168 // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari
40169 if(Ext.isAir || Ext.isWebKit){
40170 wd.style.marginLeft = '18px';
40171 wd.style.marginRight = '18px';
40174 tw -= wrap.getMargins('lr');
40175 wrap.setWidth(tw > 20 ? tw : 20);
40176 if(!this.scrolling){
40177 if(!this.scrollLeft){
40178 this.createScrollers();
40180 this.scrollLeft.show();
40181 this.scrollRight.show();
40184 this.scrolling = true;
40185 if(pos > (l-tw)){ // ensure it stays within bounds
40186 wd.scrollLeft = l-tw;
40187 }else{ // otherwise, make sure the active tab is still visible
40188 this.scrollToTab(this.activeTab, false);
40190 this.updateScrollButtons();
40195 createScrollers : function(){
40196 this.pos.addClass('x-tab-scrolling-' + this.tabPosition);
40197 var h = this.stripWrap.dom.offsetHeight;
40200 var sl = this.pos.insertFirst({
40201 cls:'x-tab-scroller-left'
40204 sl.addClassOnOver('x-tab-scroller-left-over');
40205 this.leftRepeater = new Ext.util.ClickRepeater(sl, {
40206 interval : this.scrollRepeatInterval,
40207 handler: this.onScrollLeft,
40210 this.scrollLeft = sl;
40213 var sr = this.pos.insertFirst({
40214 cls:'x-tab-scroller-right'
40217 sr.addClassOnOver('x-tab-scroller-right-over');
40218 this.rightRepeater = new Ext.util.ClickRepeater(sr, {
40219 interval : this.scrollRepeatInterval,
40220 handler: this.onScrollRight,
40223 this.scrollRight = sr;
40227 getScrollWidth : function(){
40228 return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos();
40232 getScrollPos : function(){
40233 return parseInt(this.stripWrap.dom.scrollLeft, 10) || 0;
40237 getScrollArea : function(){
40238 return parseInt(this.stripWrap.dom.clientWidth, 10) || 0;
40242 getScrollAnim : function(){
40243 return {duration:this.scrollDuration, callback: this.updateScrollButtons, scope: this};
40247 getScrollIncrement : function(){
40248 return this.scrollIncrement || (this.resizeTabs ? this.lastTabWidth+2 : 100);
40252 * Scrolls to a particular tab if tab scrolling is enabled
40253 * @param {Panel} item The item to scroll to
40254 * @param {Boolean} animate True to enable animations
40257 scrollToTab : function(item, animate){
40258 if(!item){ return; }
40259 var el = this.getTabEl(item);
40260 var pos = this.getScrollPos(), area = this.getScrollArea();
40261 var left = Ext.fly(el).getOffsetsTo(this.stripWrap)[0] + pos;
40262 var right = left + el.offsetWidth;
40264 this.scrollTo(left, animate);
40265 }else if(right > (pos + area)){
40266 this.scrollTo(right - area, animate);
40271 scrollTo : function(pos, animate){
40272 this.stripWrap.scrollTo('left', pos, animate ? this.getScrollAnim() : false);
40274 this.updateScrollButtons();
40278 onWheel : function(e){
40279 var d = e.getWheelDelta()*this.wheelIncrement*-1;
40282 var pos = this.getScrollPos();
40283 var newpos = pos + d;
40284 var sw = this.getScrollWidth()-this.getScrollArea();
40286 var s = Math.max(0, Math.min(sw, newpos));
40288 this.scrollTo(s, false);
40293 onScrollRight : function(){
40294 var sw = this.getScrollWidth()-this.getScrollArea();
40295 var pos = this.getScrollPos();
40296 var s = Math.min(sw, pos + this.getScrollIncrement());
40298 this.scrollTo(s, this.animScroll);
40303 onScrollLeft : function(){
40304 var pos = this.getScrollPos();
40305 var s = Math.max(0, pos - this.getScrollIncrement());
40307 this.scrollTo(s, this.animScroll);
40312 updateScrollButtons : function(){
40313 var pos = this.getScrollPos();
40314 this.scrollLeft[pos === 0 ? 'addClass' : 'removeClass']('x-tab-scroller-left-disabled');
40315 this.scrollRight[pos >= (this.getScrollWidth()-this.getScrollArea()) ? 'addClass' : 'removeClass']('x-tab-scroller-right-disabled');
40319 beforeDestroy : function() {
40321 this.items.each(function(item){
40322 if(item && item.tabEl){
40323 Ext.get(item.tabEl).removeAllListeners();
40329 this.strip.removeAllListeners();
40331 Ext.TabPanel.superclass.beforeDestroy.apply(this);
40335 * @cfg {Boolean} collapsible
40339 * @cfg {String} header
40343 * @cfg {Boolean} headerAsText
40355 * @cfg {Array} tools
40359 * @cfg {Array} toolTemplate
40363 * @cfg {Boolean} hideCollapseTool
40367 * @cfg {Boolean} titleCollapse
40371 * @cfg {Boolean} collapsed
40375 * @cfg {String} layout
40379 * @cfg {Boolean} preventBodyReset
40383 Ext.reg('tabpanel', Ext.TabPanel);
40386 * See {@link #setActiveTab}. Sets the specified tab as the active tab. This method fires
40387 * the {@link #beforetabchange} event which can <tt>return false</tt> to cancel the tab change.
40388 * @param {String/Panel} tab The id or tab Panel to activate
40391 Ext.TabPanel.prototype.activate = Ext.TabPanel.prototype.setActiveTab;
40393 // private utility class used by TabPanel
40394 Ext.TabPanel.AccessStack = function(){
40397 add : function(item){
40399 if(items.length > 10){
40404 remove : function(item){
40406 for(var i = 0, len = items.length; i < len; i++) {
40407 if(items[i] != item){
40415 return items.pop();
40419 * @class Ext.Button
40420 * @extends Ext.BoxComponent
40421 * Simple Button class
40422 * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
40423 * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
40424 * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
40425 * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
40426 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
40427 * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
40428 * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
40430 * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the handler is executed. Defaults to this Button.
40431 * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
40432 * See also {@link Ext.Panel}.<tt>{@link Ext.Panel#minButtonWidth minButtonWidth}</tt>.
40433 * @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
40434 * @cfg {Boolean} hidden True to start hidden (defaults to false)
40435 * @cfg {Boolean} disabled True to start disabled (defaults to false)
40436 * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
40437 * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
40438 * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
40439 * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
40441 * Create a new button
40442 * @param {Object} config The config object
40445 Ext.Button = Ext.extend(Ext.BoxComponent, {
40447 * Read-only. True if this button is hidden
40452 * Read-only. True if this button is disabled
40457 * Read-only. True if this button is pressed (only if enableToggle = true)
40462 * The Button's owner {@link Ext.Panel} (defaults to undefined, and is set automatically when
40463 * the Button is added to a container). Read-only.
40465 * @property ownerCt
40469 * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
40473 * @cfg {Boolean} allowDepress
40474 * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
40478 * @cfg {Boolean} enableToggle
40479 * True to enable pressed/not pressed toggling (defaults to false)
40481 enableToggle: false,
40483 * @cfg {Function} toggleHandler
40484 * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
40485 * <li><b>button</b> : Ext.Button<div class="sub-desc">this Button object</div></li>
40486 * <li><b>state</b> : Boolean<div class="sub-desc">The next state if the Button, true means pressed.</div></li>
40490 * @cfg {Mixed} menu
40491 * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
40494 * @cfg {String} menuAlign
40495 * The position to align the menu to (see {@link Ext.Element#alignTo} for more details, defaults to 'tl-bl?').
40497 menuAlign : 'tl-bl?',
40500 * @cfg {String} overflowText If used in a {@link Ext.Toolbar Toolbar}, the
40501 * text to be used if this item is shown in the overflow menu. See also
40502 * {@link Ext.Toolbar.Item}.<code>{@link Ext.Toolbar.Item#overflowText overflowText}</code>.
40505 * @cfg {String} iconCls
40506 * A css class which sets a background image to be used as the icon for this button
40509 * @cfg {String} type
40510 * submit, reset or button - defaults to 'button'
40515 menuClassTarget: 'tr:nth(2)',
40518 * @cfg {String} clickEvent
40519 * The type of event to map to the button's event handler (defaults to 'click')
40521 clickEvent : 'click',
40524 * @cfg {Boolean} handleMouseEvents
40525 * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
40527 handleMouseEvents : true,
40530 * @cfg {String} tooltipType
40531 * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
40533 tooltipType : 'qtip',
40536 * @cfg {String} buttonSelector
40537 * <p>(Optional) A {@link Ext.DomQuery DomQuery} selector which is used to extract the active, clickable element from the
40538 * DOM structure created.</p>
40539 * <p>When a custom {@link #template} is used, you must ensure that this selector results in the selection of
40540 * a focussable element.</p>
40541 * <p>Defaults to <b><tt>"button:first-child"</tt></b>.</p>
40543 buttonSelector : 'button:first-child',
40546 * @cfg {String} scale
40547 * <p>(Optional) The size of the Button. Three values are allowed:</p>
40548 * <ul class="mdetail-params">
40549 * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
40550 * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
40551 * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
40553 * <p>Defaults to <b><tt>'small'</tt></b>.</p>
40558 * @cfg {String} iconAlign
40559 * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
40560 * <ul class="mdetail-params">
40561 * <li>'top'<div class="sub-desc"></div></li>
40562 * <li>'right'<div class="sub-desc"></div></li>
40563 * <li>'bottom'<div class="sub-desc"></div></li>
40564 * <li>'left'<div class="sub-desc"></div></li>
40566 * <p>Defaults to <b><tt>'left'</tt></b>.</p>
40568 iconAlign : 'left',
40571 * @cfg {String} arrowAlign
40572 * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
40573 * Two values are allowed:</p>
40574 * <ul class="mdetail-params">
40575 * <li>'right'<div class="sub-desc"></div></li>
40576 * <li>'bottom'<div class="sub-desc"></div></li>
40578 * <p>Defaults to <b><tt>'right'</tt></b>.</p>
40580 arrowAlign : 'right',
40583 * @cfg {Ext.Template} template (Optional)
40584 * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
40585 * Instances, or subclasses which need a different DOM structure may provide a different
40586 * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
40587 * @type Ext.Template
40588 * @property template
40591 * @cfg {String} cls
40592 * A CSS class string to apply to the button's main element.
40597 * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
40600 initComponent : function(){
40601 Ext.Button.superclass.initComponent.call(this);
40606 * Fires when this button is clicked
40607 * @param {Button} this
40608 * @param {EventObject} e The click event
40613 * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
40614 * @param {Button} this
40615 * @param {Boolean} pressed
40620 * Fires when the mouse hovers over the button
40621 * @param {Button} this
40622 * @param {Event} e The event object
40627 * Fires when the mouse exits the button
40628 * @param {Button} this
40629 * @param {Event} e The event object
40634 * If this button has a menu, this event fires when it is shown
40635 * @param {Button} this
40636 * @param {Menu} menu
40641 * If this button has a menu, this event fires when it is hidden
40642 * @param {Button} this
40643 * @param {Menu} menu
40647 * @event menutriggerover
40648 * If this button has a menu, this event fires when the mouse enters the menu triggering element
40649 * @param {Button} this
40650 * @param {Menu} menu
40651 * @param {EventObject} e
40655 * @event menutriggerout
40656 * If this button has a menu, this event fires when the mouse leaves the menu triggering element
40657 * @param {Button} this
40658 * @param {Menu} menu
40659 * @param {EventObject} e
40664 this.menu = Ext.menu.MenuMgr.get(this.menu);
40666 if(Ext.isString(this.toggleGroup)){
40667 this.enableToggle = true;
40672 * <p>This method returns an object which provides substitution parameters for the {@link #template Template} used
40673 * to create this Button's DOM structure.</p>
40674 * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
40675 * own implementation of this method.</p>
40676 * <p>The default implementation which provides data for the default {@link #template} returns an Array containing the
40677 * following items:</p><div class="mdetail-params"><ul>
40678 * <li>The Button's {@link #text}</li>
40679 * <li>The <button>'s {@link #type}</li>
40680 * <li>The {@link iconCls} applied to the <button> {@link #btnEl element}</li>
40681 * <li>The {@link #cls} applied to the Button's main {@link #getEl Element}</li>
40682 * <li>A CSS class name controlling the Button's {@link #scale} and {@link #iconAlign icon alignment}</li>
40683 * <li>A CSS class name which applies an arrow to the Button if configured with a {@link #menu}</li>
40685 * @return {Object} Substitution data for a Template.
40687 getTemplateArgs : function(){
40688 var cls = (this.cls || '');
40689 cls += (this.iconCls || this.icon) ? (this.text ? ' x-btn-text-icon' : ' x-btn-icon') : ' x-btn-noicon';
40691 cls += ' x-btn-pressed';
40693 return [this.text || ' ', this.type, this.iconCls || '', cls, 'x-btn-' + this.scale + ' x-btn-icon-' + this.scale + '-' + this.iconAlign, this.getMenuClass()];
40697 getMenuClass : function(){
40698 return this.menu ? (this.arrowAlign != 'bottom' ? 'x-btn-arrow' : 'x-btn-arrow-bottom') : '';
40702 onRender : function(ct, position){
40703 if(!this.template){
40704 if(!Ext.Button.buttonTemplate){
40705 // hideous table template
40706 Ext.Button.buttonTemplate = new Ext.Template(
40707 '<table cellspacing="0" class="x-btn {3}"><tbody class="{4}">',
40708 '<tr><td class="x-btn-tl"><i> </i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i> </i></td></tr>',
40709 '<tr><td class="x-btn-ml"><i> </i></td><td class="x-btn-mc"><em class="{5}" unselectable="on"><button class="x-btn-text {2}" type="{1}">{0}</button></em></td><td class="x-btn-mr"><i> </i></td></tr>',
40710 '<tr><td class="x-btn-bl"><i> </i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i> </i></td></tr>',
40711 "</tbody></table>");
40712 Ext.Button.buttonTemplate.compile();
40714 this.template = Ext.Button.buttonTemplate;
40717 var btn, targs = this.getTemplateArgs();
40720 btn = this.template.insertBefore(position, targs, true);
40722 btn = this.template.append(ct, targs, true);
40725 * An {@link Ext.Element Element} encapsulating the Button's clickable element. By default,
40726 * this references a <tt><button></tt> element. Read only.
40727 * @type Ext.Element
40730 this.btnEl = btn.child(this.buttonSelector);
40731 this.mon(this.btnEl, {
40733 focus: this.onFocus,
40737 this.initButtonEl(btn, this.btnEl);
40739 Ext.ButtonToggleMgr.register(this);
40743 initButtonEl : function(btn, btnEl){
40747 this.el.dom.id = this.el.id = this.id;
40750 btnEl.setStyle('background-image', 'url(' +this.icon +')');
40752 if(this.tabIndex !== undefined){
40753 btnEl.dom.tabIndex = this.tabIndex;
40756 this.setTooltip(this.tooltip, true);
40759 if(this.handleMouseEvents){
40762 mouseover: this.onMouseOver,
40763 mousedown: this.onMouseDown
40766 // new functionality for monitoring on the document level
40767 //this.mon(btn, 'mouseout', this.onMouseOut, this);
40771 this.mon(this.menu, {
40773 show: this.onMenuShow,
40774 hide: this.onMenuHide
40779 var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
40780 this.mon(repeater, 'click', this.onClick, this);
40783 this.mon(btn, this.clickEvent, this.onClick, this);
40787 afterRender : function(){
40788 Ext.Button.superclass.afterRender.call(this);
40789 this.doAutoWidth();
40793 * Sets the CSS class that provides a background image to use as the button's icon. This method also changes
40794 * the value of the {@link iconCls} config internally.
40795 * @param {String} cls The CSS class providing the icon image
40796 * @return {Ext.Button} this
40798 setIconClass : function(cls){
40800 this.btnEl.replaceClass(this.iconCls, cls);
40802 this.iconCls = cls;
40807 * Sets the tooltip for this Button.
40808 * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
40809 * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
40810 * <li><b>Object</b> : A configuration object for {@link Ext.QuickTips#register}.</li>
40812 * @return {Ext.Button} this
40814 setTooltip : function(tooltip, /* private */ initial){
40819 if(Ext.isObject(tooltip)){
40820 Ext.QuickTips.register(Ext.apply({
40821 target: this.btnEl.id
40823 this.tooltip = tooltip;
40825 this.btnEl.dom[this.tooltipType] = tooltip;
40828 this.tooltip = tooltip;
40834 clearTip: function(){
40835 if(Ext.isObject(this.tooltip)){
40836 Ext.QuickTips.unregister(this.btnEl);
40841 beforeDestroy: function(){
40845 Ext.destroy(this.menu, this.repeater);
40849 onDestroy : function(){
40850 var doc = Ext.getDoc();
40851 doc.un('mouseover', this.monitorMouseOver, this);
40852 doc.un('mouseup', this.onMouseUp, this);
40854 Ext.ButtonToggleMgr.unregister(this);
40859 doAutoWidth : function(){
40860 if(this.el && this.text && this.width === undefined){
40861 this.el.setWidth('auto');
40862 if(Ext.isIE7 && Ext.isStrict){
40863 var ib = this.btnEl;
40864 if(ib && ib.getWidth() > 20){
40866 ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));
40870 if(this.el.getWidth() < this.minWidth){
40871 this.el.setWidth(this.minWidth);
40878 * Assigns this Button's click handler
40879 * @param {Function} handler The function to call when the button is clicked
40880 * @param {Object} scope (optional) Scope for the function passed in
40881 * @return {Ext.Button} this
40883 setHandler : function(handler, scope){
40884 this.handler = handler;
40885 this.scope = scope;
40890 * Sets this Button's text
40891 * @param {String} text The button text
40892 * @return {Ext.Button} this
40894 setText : function(text){
40897 this.el.child('td.x-btn-mc ' + this.buttonSelector).update(text);
40899 this.doAutoWidth();
40904 * Gets the text for this Button
40905 * @return {String} The button text
40907 getText : function(){
40912 * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
40913 * @param {Boolean} state (optional) Force a particular state
40914 * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
40915 * @return {Ext.Button} this
40917 toggle : function(state, suppressEvent){
40918 state = state === undefined ? !this.pressed : !!state;
40919 if(state != this.pressed){
40920 this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed');
40921 this.pressed = state;
40922 if(!suppressEvent){
40923 this.fireEvent('toggle', this, state);
40924 if(this.toggleHandler){
40925 this.toggleHandler.call(this.scope || this, this, state);
40935 focus : function(){
40936 this.btnEl.focus();
40940 onDisable : function(){
40941 this.onDisableChange(true);
40945 onEnable : function(){
40946 this.onDisableChange(false);
40949 onDisableChange : function(disabled){
40951 if(!Ext.isIE6 || !this.text){
40952 this.el[disabled ? 'addClass' : 'removeClass'](this.disabledClass);
40954 this.el.dom.disabled = disabled;
40956 this.disabled = disabled;
40960 * Show this button's menu (if it has one)
40962 showMenu : function(){
40963 if(this.rendered && this.menu){
40965 Ext.QuickTips.getQuickTip().cancelShow(this.btnEl);
40967 this.menu.show(this.el, this.menuAlign);
40973 * Hide this button's menu (if it has one)
40975 hideMenu : function(){
40983 * Returns true if the button has a menu and it is visible
40984 * @return {Boolean}
40986 hasVisibleMenu : function(){
40987 return this.menu && this.menu.isVisible();
40991 onClick : function(e){
40993 e.preventDefault();
40995 if(e.button !== 0){
40998 if(!this.disabled){
40999 if(this.enableToggle && (this.allowDepress !== false || !this.pressed)){
41002 if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){
41005 this.fireEvent('click', this, e);
41007 //this.el.removeClass('x-btn-over');
41008 this.handler.call(this.scope || this, this, e);
41014 isMenuTriggerOver : function(e, internal){
41015 return this.menu && !internal;
41019 isMenuTriggerOut : function(e, internal){
41020 return this.menu && !internal;
41024 onMouseOver : function(e){
41025 if(!this.disabled){
41026 var internal = e.within(this.el, true);
41028 this.el.addClass('x-btn-over');
41029 if(!this.monitoringMouseOver){
41030 Ext.getDoc().on('mouseover', this.monitorMouseOver, this);
41031 this.monitoringMouseOver = true;
41033 this.fireEvent('mouseover', this, e);
41035 if(this.isMenuTriggerOver(e, internal)){
41036 this.fireEvent('menutriggerover', this, this.menu, e);
41042 monitorMouseOver : function(e){
41043 if(e.target != this.el.dom && !e.within(this.el)){
41044 if(this.monitoringMouseOver){
41045 Ext.getDoc().un('mouseover', this.monitorMouseOver, this);
41046 this.monitoringMouseOver = false;
41048 this.onMouseOut(e);
41053 onMouseOut : function(e){
41054 var internal = e.within(this.el) && e.target != this.el.dom;
41055 this.el.removeClass('x-btn-over');
41056 this.fireEvent('mouseout', this, e);
41057 if(this.isMenuTriggerOut(e, internal)){
41058 this.fireEvent('menutriggerout', this, this.menu, e);
41062 onFocus : function(e){
41063 if(!this.disabled){
41064 this.el.addClass('x-btn-focus');
41068 onBlur : function(e){
41069 this.el.removeClass('x-btn-focus');
41073 getClickEl : function(e, isUp){
41078 onMouseDown : function(e){
41079 if(!this.disabled && e.button === 0){
41080 this.getClickEl(e).addClass('x-btn-click');
41081 Ext.getDoc().on('mouseup', this.onMouseUp, this);
41085 onMouseUp : function(e){
41086 if(e.button === 0){
41087 this.getClickEl(e, true).removeClass('x-btn-click');
41088 Ext.getDoc().un('mouseup', this.onMouseUp, this);
41092 onMenuShow : function(e){
41093 this.ignoreNextClick = 0;
41094 this.el.addClass('x-btn-menu-active');
41095 this.fireEvent('menushow', this, this.menu);
41098 onMenuHide : function(e){
41099 this.el.removeClass('x-btn-menu-active');
41100 this.ignoreNextClick = this.restoreClick.defer(250, this);
41101 this.fireEvent('menuhide', this, this.menu);
41105 restoreClick : function(){
41106 this.ignoreNextClick = 0;
41112 * @cfg {String} autoEl @hide
41115 Ext.reg('button', Ext.Button);
41117 // Private utility class used by Button
41118 Ext.ButtonToggleMgr = function(){
41121 function toggleGroup(btn, state){
41123 var g = groups[btn.toggleGroup];
41124 for(var i = 0, l = g.length; i < l; i++){
41126 g[i].toggle(false);
41133 register : function(btn){
41134 if(!btn.toggleGroup){
41137 var g = groups[btn.toggleGroup];
41139 g = groups[btn.toggleGroup] = [];
41142 btn.on('toggle', toggleGroup);
41145 unregister : function(btn){
41146 if(!btn.toggleGroup){
41149 var g = groups[btn.toggleGroup];
41152 btn.un('toggle', toggleGroup);
41157 * Gets the pressed button in the passed group or null
41158 * @param {String} group
41161 getPressed : function(group){
41162 var g = groups[group];
41164 for(var i = 0, len = g.length; i < len; i++){
41165 if(g[i].pressed === true){
41174 * @class Ext.SplitButton
\r
41175 * @extends Ext.Button
\r
41176 * A split button that provides a built-in dropdown arrow that can fire an event separately from the default
\r
41177 * click event of the button. Typically this would be used to display a dropdown menu that provides additional
\r
41178 * options to the primary button action, but any custom handler can provide the arrowclick implementation. Example usage:
\r
41180 // display a dropdown menu:
\r
41181 new Ext.SplitButton({
\r
41182 renderTo: 'button-ct', // the container id
\r
41184 handler: optionsHandler, // handle a click on the button itself
\r
41185 menu: new Ext.menu.Menu({
\r
41187 // these items will render as dropdown menu items when the arrow is clicked:
\r
41188 {text: 'Item 1', handler: item1Handler},
\r
41189 {text: 'Item 2', handler: item2Handler}
\r
41194 // Instead of showing a menu, you provide any type of custom
\r
41195 // functionality you want when the dropdown arrow is clicked:
\r
41196 new Ext.SplitButton({
\r
41197 renderTo: 'button-ct',
\r
41199 handler: optionsHandler,
\r
41200 arrowHandler: myCustomHandler
\r
41203 * @cfg {Function} arrowHandler A function called when the arrow button is clicked (can be used instead of click event)
\r
41204 * @cfg {String} arrowTooltip The title attribute of the arrow
\r
41206 * Create a new menu button
\r
41207 * @param {Object} config The config object
\r
41208 * @xtype splitbutton
\r
41210 Ext.SplitButton = Ext.extend(Ext.Button, {
\r
41212 arrowSelector : 'em',
\r
41216 initComponent : function(){
\r
41217 Ext.SplitButton.superclass.initComponent.call(this);
\r
41219 * @event arrowclick
\r
41220 * Fires when this button's arrow is clicked
\r
41221 * @param {MenuButton} this
\r
41222 * @param {EventObject} e The click event
\r
41224 this.addEvents("arrowclick");
\r
41228 onRender : function(){
\r
41229 Ext.SplitButton.superclass.onRender.apply(this, arguments);
\r
41230 if(this.arrowTooltip){
\r
41231 this.el.child(this.arrowSelector).dom[this.tooltipType] = this.arrowTooltip;
\r
41236 * Sets this button's arrow click handler.
\r
41237 * @param {Function} handler The function to call when the arrow is clicked
\r
41238 * @param {Object} scope (optional) Scope for the function passed above
\r
41240 setArrowHandler : function(handler, scope){
\r
41241 this.arrowHandler = handler;
\r
41242 this.scope = scope;
\r
41245 getMenuClass : function(){
\r
41246 return 'x-btn-split' + (this.arrowAlign == 'bottom' ? '-bottom' : '');
\r
41249 isClickOnArrow : function(e){
\r
41250 return this.arrowAlign != 'bottom' ?
\r
41251 e.getPageX() > this.el.child(this.buttonSelector).getRegion().right :
\r
41252 e.getPageY() > this.el.child(this.buttonSelector).getRegion().bottom;
\r
41256 onClick : function(e, t){
\r
41257 e.preventDefault();
\r
41258 if(!this.disabled){
\r
41259 if(this.isClickOnArrow(e)){
\r
41260 if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){
\r
41263 this.fireEvent("arrowclick", this, e);
\r
41264 if(this.arrowHandler){
\r
41265 this.arrowHandler.call(this.scope || this, this, e);
\r
41268 if(this.enableToggle){
\r
41271 this.fireEvent("click", this, e);
\r
41272 if(this.handler){
\r
41273 this.handler.call(this.scope || this, this, e);
\r
41280 isMenuTriggerOver : function(e){
\r
41281 return this.menu && e.target.tagName == 'em';
\r
41285 isMenuTriggerOut : function(e, internal){
\r
41286 return this.menu && e.target.tagName != 'em';
\r
41290 Ext.reg('splitbutton', Ext.SplitButton);/**
\r
41291 * @class Ext.CycleButton
\r
41292 * @extends Ext.SplitButton
\r
41293 * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
\r
41294 * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
\r
41295 * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
\r
41296 * button displays the dropdown menu just like a normal SplitButton. Example usage:
\r
41298 var btn = new Ext.CycleButton({
\r
41300 prependText: 'View as ',
\r
41302 text:'text only',
\r
41303 iconCls:'view-text',
\r
41307 iconCls:'view-html'
\r
41309 changeHandler:function(btn, item){
\r
41310 Ext.Msg.alert('Change View', item.text);
\r
41315 * Create a new split button
\r
41316 * @param {Object} config The config object
\r
41319 Ext.CycleButton = Ext.extend(Ext.SplitButton, {
\r
41321 * @cfg {Array} items An array of {@link Ext.menu.CheckItem} <b>config</b> objects to be used when creating the
\r
41322 * button's menu items (e.g., {text:'Foo', iconCls:'foo-icon'})
\r
41325 * @cfg {Boolean} showText True to display the active item's text as the button text (defaults to false)
\r
41328 * @cfg {String} prependText A static string to prepend before the active item's text when displayed as the
\r
41329 * button's text (only applies when showText = true, defaults to '')
\r
41332 * @cfg {Function} changeHandler A callback function that will be invoked each time the active menu
\r
41333 * item in the button's menu has changed. If this callback is not supplied, the SplitButton will instead
\r
41334 * fire the {@link #change} event on active item change. The changeHandler function will be called with the
\r
41335 * following argument list: (SplitButton this, Ext.menu.CheckItem item)
\r
41338 * @cfg {String} forceIcon A css class which sets an image to be used as the static icon for this button. This
\r
41339 * icon will always be displayed regardless of which item is selected in the dropdown list. This overrides the
\r
41340 * default behavior of changing the button's icon to match the selected item's icon on change.
\r
41345 * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the available choices.
\r
41349 getItemText : function(item){
\r
41350 if(item && this.showText === true){
\r
41352 if(this.prependText){
\r
41353 text += this.prependText;
\r
41355 text += item.text;
\r
41358 return undefined;
\r
41362 * Sets the button's active menu item.
\r
41363 * @param {Ext.menu.CheckItem} item The item to activate
\r
41364 * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false)
\r
41366 setActiveItem : function(item, suppressEvent){
\r
41367 if(typeof item != 'object'){
\r
41368 item = this.menu.items.get(item);
\r
41371 if(!this.rendered){
\r
41372 this.text = this.getItemText(item);
\r
41373 this.iconCls = item.iconCls;
\r
41375 var t = this.getItemText(item);
\r
41379 this.setIconClass(item.iconCls);
\r
41381 this.activeItem = item;
\r
41382 if(!item.checked){
\r
41383 item.setChecked(true, true);
\r
41385 if(this.forceIcon){
\r
41386 this.setIconClass(this.forceIcon);
\r
41388 if(!suppressEvent){
\r
41389 this.fireEvent('change', this, item);
\r
41395 * Gets the currently active menu item.
\r
41396 * @return {Ext.menu.CheckItem} The active item
\r
41398 getActiveItem : function(){
\r
41399 return this.activeItem;
\r
41403 initComponent : function(){
\r
41407 * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function
\r
41408 * is set on this CycleButton, it will be called instead on active item change and this change event will
\r
41410 * @param {Ext.CycleButton} this
\r
41411 * @param {Ext.menu.CheckItem} item The menu item that was selected
\r
41416 if(this.changeHandler){
\r
41417 this.on('change', this.changeHandler, this.scope||this);
\r
41418 delete this.changeHandler;
\r
41421 this.itemCount = this.items.length;
\r
41423 this.menu = {cls:'x-cycle-menu', items:[]};
\r
41425 for(var i = 0, len = this.itemCount; i < len; i++){
\r
41426 var item = this.items[i];
\r
41427 item.group = item.group || this.id;
\r
41428 item.itemIndex = i;
\r
41429 item.checkHandler = this.checkHandler;
\r
41430 item.scope = this;
\r
41431 item.checked = item.checked || false;
\r
41432 this.menu.items.push(item);
\r
41433 if(item.checked){
\r
41437 this.setActiveItem(checked, true);
\r
41438 Ext.CycleButton.superclass.initComponent.call(this);
\r
41440 this.on('click', this.toggleSelected, this);
\r
41444 checkHandler : function(item, pressed){
\r
41446 this.setActiveItem(item);
\r
41451 * This is normally called internally on button click, but can be called externally to advance the button's
\r
41452 * active item programmatically to the next one in the menu. If the current item is the last one in the menu
\r
41453 * the active item will be set to the first item in the menu.
\r
41455 toggleSelected : function(){
\r
41456 this.menu.render();
\r
41458 var nextIdx, checkItem;
\r
41459 for (var i = 1; i < this.itemCount; i++) {
\r
41460 nextIdx = (this.activeItem.itemIndex + i) % this.itemCount;
\r
41461 // check the potential item
\r
41462 checkItem = this.menu.items.itemAt(nextIdx);
\r
41463 // if its not disabled then check it.
\r
41464 if (!checkItem.disabled) {
\r
41465 checkItem.setChecked(true);
\r
41471 Ext.reg('cycle', Ext.CycleButton);/**
\r
41472 * @class Ext.layout.ToolbarLayout
\r
41473 * @extends Ext.layout.ContainerLayout
\r
41474 * Layout manager implicitly used by Ext.Toolbar.
\r
41476 Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
41477 monitorResize : true,
\r
41478 triggerWidth : 18,
\r
41479 lastOverflow : false,
\r
41481 noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
\r
41483 onLayout : function(ct, target){
\r
41484 if(!this.leftTr){
\r
41485 target.addClass('x-toolbar-layout-ct');
\r
41486 target.insertHtml('beforeEnd',
\r
41487 '<table cellspacing="0" class="x-toolbar-ct"><tbody><tr><td class="x-toolbar-left" align="left"><table cellspacing="0"><tbody><tr class="x-toolbar-left-row"></tr></tbody></table></td><td class="x-toolbar-right" align="right"><table cellspacing="0" class="x-toolbar-right-ct"><tbody><tr><td><table cellspacing="0"><tbody><tr class="x-toolbar-right-row"></tr></tbody></table></td><td><table cellspacing="0"><tbody><tr class="x-toolbar-extras-row"></tr></tbody></table></td></tr></tbody></table></td></tr></tbody></table>');
\r
41488 this.leftTr = target.child('tr.x-toolbar-left-row', true);
\r
41489 this.rightTr = target.child('tr.x-toolbar-right-row', true);
\r
41490 this.extrasTr = target.child('tr.x-toolbar-extras-row', true);
\r
41492 var side = this.leftTr;
\r
41495 var items = ct.items.items;
\r
41496 for(var i = 0, len = items.length, c; i < len; i++, pos++) {
\r
41499 side = this.rightTr;
\r
41501 }else if(!c.rendered){
\r
41502 c.render(this.insertCell(c, side, pos));
\r
41504 if(!c.xtbHidden && !this.isValidParent(c, side.childNodes[pos])){
\r
41505 var td = this.insertCell(c, side, pos);
\r
41506 td.appendChild(c.getDomPositionEl().dom);
\r
41507 c.container = Ext.get(td);
\r
41511 //strip extra empty cells
\r
41512 this.cleanup(this.leftTr);
\r
41513 this.cleanup(this.rightTr);
\r
41514 this.cleanup(this.extrasTr);
\r
41515 this.fitToSize(target);
\r
41518 cleanup : function(row){
\r
41519 var cn = row.childNodes;
\r
41520 for(var i = cn.length-1, c; i >= 0 && (c = cn[i]); i--){
\r
41521 if(!c.firstChild){
\r
41522 row.removeChild(c);
\r
41527 insertCell : function(c, side, pos){
\r
41528 var td = document.createElement('td');
\r
41529 td.className='x-toolbar-cell';
\r
41530 side.insertBefore(td, side.childNodes[pos]||null);
\r
41534 hideItem : function(item){
\r
41535 var h = (this.hiddens = this.hiddens || []);
\r
41537 item.xtbHidden = true;
\r
41538 item.xtbWidth = item.getDomPositionEl().dom.parentNode.offsetWidth;
\r
41542 unhideItem : function(item){
\r
41544 item.xtbHidden = false;
\r
41545 this.hiddens.remove(item);
\r
41546 if(this.hiddens.length < 1){
\r
41547 delete this.hiddens;
\r
41551 getItemWidth : function(c){
\r
41552 return c.hidden ? (c.xtbWidth || 0) : c.getDomPositionEl().dom.parentNode.offsetWidth;
\r
41555 fitToSize : function(t){
\r
41556 if(this.container.enableOverflow === false){
\r
41559 var w = t.dom.clientWidth;
\r
41560 var lw = this.lastWidth || 0;
\r
41561 this.lastWidth = w;
\r
41562 var iw = t.dom.firstChild.offsetWidth;
\r
41564 var clipWidth = w - this.triggerWidth;
\r
41565 var hideIndex = -1;
\r
41567 if(iw > w || (this.hiddens && w >= lw)){
\r
41568 var i, items = this.container.items.items, len = items.length, c;
\r
41569 var loopWidth = 0;
\r
41570 for(i = 0; i < len; i++) {
\r
41573 loopWidth += this.getItemWidth(c);
\r
41574 if(loopWidth > clipWidth){
\r
41575 if(!c.xtbHidden){
\r
41576 this.hideItem(c);
\r
41580 this.unhideItem(c);
\r
41586 if(this.hiddens){
\r
41588 if(!this.lastOverflow){
\r
41589 this.container.fireEvent('overflowchange', this.container, true);
\r
41590 this.lastOverflow = true;
\r
41592 }else if(this.more){
\r
41593 this.clearMenu();
\r
41594 this.more.destroy();
\r
41595 delete this.more;
\r
41596 if(this.lastOverflow){
\r
41597 this.container.fireEvent('overflowchange', this.container, false);
\r
41598 this.lastOverflow = false;
\r
41603 createMenuConfig : function(c, hideOnClick){
\r
41604 var cfg = Ext.apply({}, c.initialConfig),
\r
41605 group = c.toggleGroup;
\r
41608 text: c.overflowText || c.text,
\r
41609 iconCls: c.iconCls,
\r
41611 itemId: c.itemId,
\r
41612 disabled: c.disabled,
\r
41613 handler: c.handler,
\r
41616 hideOnClick: hideOnClick
\r
41618 if(group || c.enableToggle){
\r
41621 checked: c.pressed,
\r
41623 checkchange: function(item, checked){
\r
41624 c.toggle(checked);
\r
41629 delete cfg.xtype;
\r
41635 addComponentToMenu : function(m, c){
\r
41636 if(c instanceof Ext.Toolbar.Separator){
\r
41638 }else if(Ext.isFunction(c.isXType)){
\r
41639 if(c.isXType('splitbutton')){
\r
41640 m.add(this.createMenuConfig(c, true));
\r
41641 }else if(c.isXType('button')){
\r
41642 m.add(this.createMenuConfig(c, !c.menu));
\r
41643 }else if(c.isXType('buttongroup')){
\r
41644 c.items.each(function(item){
\r
41645 this.addComponentToMenu(m, item);
\r
41651 clearMenu : function(){
\r
41652 var m = this.moreMenu;
\r
41653 if(m && m.items){
\r
41654 this.moreMenu.items.each(function(item){
\r
41655 delete item.menu;
\r
41661 beforeMoreShow : function(m){
\r
41662 var h = this.container.items.items,
\r
41666 needsSep = function(group, item){
\r
41667 return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
\r
41670 this.clearMenu();
\r
41672 for(var i = 0; i < len; i++){
\r
41675 if(prev && (needsSep(c, prev) || needsSep(prev, c))){
\r
41678 this.addComponentToMenu(m, c);
\r
41682 // put something so the menu isn't empty
\r
41683 // if no compatible items found
\r
41684 if(m.items.length < 1){
\r
41685 m.add(this.noItemsMenuText);
\r
41689 initMore : function(){
\r
41691 this.moreMenu = new Ext.menu.Menu({
\r
41693 beforeshow: this.beforeMoreShow,
\r
41697 this.more = new Ext.Button({
\r
41698 iconCls: 'x-toolbar-more-icon',
\r
41699 cls: 'x-toolbar-more',
\r
41700 menu: this.moreMenu
\r
41702 var td = this.insertCell(this.more, this.extrasTr, 100);
\r
41703 this.more.render(td);
\r
41707 destroy : function(){
\r
41708 Ext.destroy(this.more, this.moreMenu);
\r
41709 Ext.layout.ToolbarLayout.superclass.destroy.call(this);
\r
41712 * @property activeItem
\r
41717 Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout;
\r
41720 * @class Ext.Toolbar
\r
41721 * @extends Ext.Container
\r
41722 * <p>Basic Toolbar class. Although the <tt>{@link Ext.Container#defaultType defaultType}</tt> for Toolbar
\r
41723 * is <tt>{@link Ext.Button button}</tt>, Toolbar elements (child items for the Toolbar container) may
\r
41724 * be virtually any type of Component. Toolbar elements can be created explicitly via their constructors,
\r
41725 * or implicitly via their xtypes, and can be <tt>{@link #add}</tt>ed dynamically.</p>
\r
41726 * <p>Some items have shortcut strings for creation:</p>
\r
41728 <u>Shortcut</u> <u>xtype</u> <u>Class</u> <u>Description</u>
\r
41729 '->' 'tbfill' {@link Ext.Toolbar.Fill} begin using the right-justified button container
\r
41730 '-' 'tbseparator' {@link Ext.Toolbar.Separator} add a vertical separator bar between toolbar items
\r
41731 ' ' 'tbspacer' {@link Ext.Toolbar.Spacer} add horiztonal space between elements
\r
41734 * Example usage of various elements:
\r
41736 var tb = new Ext.Toolbar({
\r
41737 renderTo: document.body,
\r
41742 // xtype: 'button', // default for Toolbars, same as 'tbbutton'
\r
41746 xtype: 'splitbutton', // same as 'tbsplitbutton'
\r
41747 text: 'Split Button'
\r
41749 // begin using the right-justified button container
\r
41750 '->', // same as {xtype: 'tbfill'}, // Ext.Toolbar.Fill
\r
41752 xtype: 'textfield',
\r
41754 emptyText: 'enter search term'
\r
41756 // add a vertical separator bar between toolbar items
\r
41757 '-', // same as {xtype: 'tbseparator'} to create Ext.Toolbar.Separator
\r
41758 'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.Toolbar.TextItem
\r
41759 {xtype: 'tbspacer'},// same as ' ' to create Ext.Toolbar.Spacer
\r
41761 {xtype: 'tbspacer', width: 50}, // add a 50px space
\r
41766 * Example adding a ComboBox within a menu of a button:
\r
41768 // ComboBox creation
\r
41769 var combo = new Ext.form.ComboBox({
\r
41770 store: new Ext.data.ArrayStore({
\r
41771 autoDestroy: true,
\r
41772 fields: ['initials', 'fullname'],
\r
41774 ['FF', 'Fred Flintstone'],
\r
41775 ['BR', 'Barney Rubble']
\r
41778 displayField: 'fullname',
\r
41781 forceSelection: true,
\r
41782 triggerAction: 'all',
\r
41783 emptyText: 'Select a name...',
\r
41784 selectOnFocus: true,
\r
41786 getListParent: function() {
\r
41787 return this.el.up('.x-menu');
\r
41789 iconCls: 'no-icon' //use iconCls if placing within menu to shift to right side of menu
\r
41792 // put ComboBox in a Menu
\r
41793 var menu = new Ext.menu.Menu({
\r
41796 combo // A Field in a Menu
\r
41800 // add a Button with the menu
\r
41802 text:'Button w/ Menu',
\r
41803 menu: menu // assign menu by instance
\r
41808 * Creates a new Toolbar
\r
41809 * @param {Object/Array} config A config object or an array of buttons to <tt>{@link #add}</tt>
\r
41812 Ext.Toolbar = function(config){
\r
41813 if(Ext.isArray(config)){
\r
41814 config = {items: config, layout: 'toolbar'};
\r
41816 config = Ext.apply({
\r
41817 layout: 'toolbar'
\r
41819 if(config.buttons) {
\r
41820 config.items = config.buttons;
\r
41823 Ext.Toolbar.superclass.constructor.call(this, config);
\r
41828 var T = Ext.Toolbar;
\r
41830 Ext.extend(T, Ext.Container, {
\r
41832 defaultType: 'button',
\r
41834 trackMenus : true,
\r
41835 internalDefaults: {removeMode: 'container', hideParent: true},
\r
41836 toolbarCls: 'x-toolbar',
\r
41838 initComponent : function(){
\r
41839 T.superclass.initComponent.call(this);
\r
41842 * @event overflowchange
\r
41843 * Fires after the overflow state has changed.
\r
41844 * @param {Object} c The Container
\r
41845 * @param {Boolean} lastOverflow overflow state
\r
41847 this.addEvents('overflowchange');
\r
41851 onRender : function(ct, position){
\r
41853 if(!this.autoCreate){
\r
41854 this.autoCreate = {
\r
41855 cls: this.toolbarCls + ' x-small-editor'
\r
41858 this.el = ct.createChild(Ext.apply({ id: this.id },this.autoCreate), position);
\r
41863 * Adds element(s) to the toolbar -- this function takes a variable number of
\r
41864 * arguments of mixed type and adds them to the toolbar.
\r
41865 * @param {Mixed} arg1 The following types of arguments are all valid:<br />
\r
41867 * <li>{@link Ext.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
\r
41868 * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
\r
41869 * <li>Field: Any form field (equivalent to {@link #addField})</li>
\r
41870 * <li>Item: Any subclass of {@link Ext.Toolbar.Item} (equivalent to {@link #addItem})</li>
\r
41871 * <li>String: Any generic string (gets wrapped in a {@link Ext.Toolbar.TextItem}, equivalent to {@link #addText}).
\r
41872 * Note that there are a few special strings that are treated differently as explained next.</li>
\r
41873 * <li>'-': Creates a separator element (equivalent to {@link #addSeparator})</li>
\r
41874 * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
\r
41875 * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
\r
41877 * @param {Mixed} arg2
\r
41878 * @param {Mixed} etc.
\r
41883 lookupComponent : function(c){
\r
41884 if(Ext.isString(c)){
\r
41886 c = new T.Separator();
\r
41887 }else if(c == ' '){
\r
41888 c = new T.Spacer();
\r
41889 }else if(c == '->'){
\r
41890 c = new T.Fill();
\r
41892 c = new T.TextItem(c);
\r
41894 this.applyDefaults(c);
\r
41896 if(c.isFormField || c.render){ // some kind of form field, some kind of Toolbar.Item
\r
41897 c = this.constructItem(c);
\r
41898 }else if(c.tag){ // DomHelper spec
\r
41899 c = new T.Item({autoEl: c});
\r
41900 }else if(c.tagName){ // element
\r
41901 c = new T.Item({el:c});
\r
41902 }else if(Ext.isObject(c)){ // must be button config?
\r
41903 c = c.xtype ? this.constructItem(c) : this.constructButton(c);
\r
41910 applyDefaults : function(c){
\r
41911 if(!Ext.isString(c)){
\r
41912 c = Ext.Toolbar.superclass.applyDefaults.call(this, c);
\r
41913 var d = this.internalDefaults;
\r
41915 Ext.applyIf(c.initialConfig, d);
\r
41918 Ext.applyIf(c, d);
\r
41925 constructItem : function(item, type){
\r
41926 return Ext.create(item, type || this.defaultType);
\r
41930 * Adds a separator
\r
41931 * @return {Ext.Toolbar.Item} The separator {@link Ext.Toolbar.Item item}
\r
41933 addSeparator : function(){
\r
41934 return this.add(new T.Separator());
\r
41938 * Adds a spacer element
\r
41939 * @return {Ext.Toolbar.Spacer} The spacer item
\r
41941 addSpacer : function(){
\r
41942 return this.add(new T.Spacer());
\r
41946 * Forces subsequent additions into the float:right toolbar
\r
41948 addFill : function(){
\r
41949 this.add(new T.Fill());
\r
41953 * Adds any standard HTML element to the toolbar
\r
41954 * @param {Mixed} el The element or id of the element to add
\r
41955 * @return {Ext.Toolbar.Item} The element's item
\r
41957 addElement : function(el){
\r
41958 return this.addItem(new T.Item({el:el}));
\r
41962 * Adds any Toolbar.Item or subclass
\r
41963 * @param {Ext.Toolbar.Item} item
\r
41964 * @return {Ext.Toolbar.Item} The item
\r
41966 addItem : function(item){
\r
41967 return Ext.Toolbar.superclass.add.apply(this, arguments);
\r
41971 * Adds a button (or buttons). See {@link Ext.Button} for more info on the config.
\r
41972 * @param {Object/Array} config A button config or array of configs
\r
41973 * @return {Ext.Button/Array}
\r
41975 addButton : function(config){
\r
41976 if(Ext.isArray(config)){
\r
41977 var buttons = [];
\r
41978 for(var i = 0, len = config.length; i < len; i++) {
\r
41979 buttons.push(this.addButton(config[i]));
\r
41983 return this.add(this.constructButton(config));
\r
41987 * Adds text to the toolbar
\r
41988 * @param {String} text The text to add
\r
41989 * @return {Ext.Toolbar.Item} The element's item
\r
41991 addText : function(text){
\r
41992 return this.addItem(new T.TextItem(text));
\r
41996 * Adds a new element to the toolbar from the passed {@link Ext.DomHelper} config
\r
41997 * @param {Object} config
\r
41998 * @return {Ext.Toolbar.Item} The element's item
\r
42000 addDom : function(config){
\r
42001 return this.add(new T.Item({autoEl: config}));
\r
42005 * Adds a dynamically rendered Ext.form field (TextField, ComboBox, etc). Note: the field should not have
\r
42006 * been rendered yet. For a field that has already been rendered, use {@link #addElement}.
\r
42007 * @param {Ext.form.Field} field
\r
42008 * @return {Ext.Toolbar.Item}
\r
42010 addField : function(field){
\r
42011 return this.add(field);
\r
42015 * Inserts any {@link Ext.Toolbar.Item}/{@link Ext.Button} at the specified index.
\r
42016 * @param {Number} index The index where the item is to be inserted
\r
42017 * @param {Object/Ext.Toolbar.Item/Ext.Button/Array} item The button, or button config object to be
\r
42018 * inserted, or an array of buttons/configs.
\r
42019 * @return {Ext.Button/Item}
\r
42021 insertButton : function(index, item){
\r
42022 if(Ext.isArray(item)){
\r
42023 var buttons = [];
\r
42024 for(var i = 0, len = item.length; i < len; i++) {
\r
42025 buttons.push(this.insertButton(index + i, item[i]));
\r
42029 return Ext.Toolbar.superclass.insert.call(this, index, item);
\r
42033 initMenuTracking : function(item){
\r
42034 if(this.trackMenus && item.menu){
\r
42036 'menutriggerover' : this.onButtonTriggerOver,
\r
42037 'menushow' : this.onButtonMenuShow,
\r
42038 'menuhide' : this.onButtonMenuHide,
\r
42045 constructButton : function(item){
\r
42046 var b = item.events ? item : this.constructItem(item, item.split ? 'splitbutton' : this.defaultType);
\r
42047 this.initMenuTracking(b);
\r
42052 onDisable : function(){
\r
42053 this.items.each(function(item){
\r
42054 if(item.disable){
\r
42061 onEnable : function(){
\r
42062 this.items.each(function(item){
\r
42070 onButtonTriggerOver : function(btn){
\r
42071 if(this.activeMenuBtn && this.activeMenuBtn != btn){
\r
42072 this.activeMenuBtn.hideMenu();
\r
42074 this.activeMenuBtn = btn;
\r
42079 onButtonMenuShow : function(btn){
\r
42080 this.activeMenuBtn = btn;
\r
42084 onButtonMenuHide : function(btn){
\r
42085 delete this.activeMenuBtn;
\r
42088 Ext.reg('toolbar', Ext.Toolbar);
\r
42091 * @class Ext.Toolbar.Item
\r
42092 * @extends Ext.BoxComponent
\r
42093 * The base class that other non-interacting Toolbar Item classes should extend in order to
\r
42094 * get some basic common toolbar item functionality.
\r
42096 * Creates a new Item
\r
42097 * @param {HTMLElement} el
\r
42100 T.Item = Ext.extend(Ext.BoxComponent, {
\r
42101 hideParent: true, // Hiding a Toolbar.Item hides its containing TD
\r
42102 enable:Ext.emptyFn,
\r
42103 disable:Ext.emptyFn,
\r
42104 focus:Ext.emptyFn
\r
42106 * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
\r
42109 Ext.reg('tbitem', T.Item);
\r
42112 * @class Ext.Toolbar.Separator
\r
42113 * @extends Ext.Toolbar.Item
\r
42114 * A simple class that adds a vertical separator bar between toolbar items
\r
42115 * (css class:<tt>'xtb-sep'</tt>). Example usage:
\r
42120 {xtype: 'tbseparator'}, // or '-'
\r
42126 * Creates a new Separator
\r
42127 * @xtype tbseparator
\r
42129 T.Separator = Ext.extend(T.Item, {
\r
42130 onRender : function(ct, position){
\r
42131 this.el = ct.createChild({tag:'span', cls:'xtb-sep'}, position);
\r
42134 Ext.reg('tbseparator', T.Separator);
\r
42137 * @class Ext.Toolbar.Spacer
\r
42138 * @extends Ext.Toolbar.Item
\r
42139 * A simple element that adds extra horizontal space between items in a toolbar.
\r
42140 * By default a 2px wide space is added via css specification:<pre><code>
\r
42141 .x-toolbar .xtb-spacer {
\r
42145 * <p>Example usage:</p>
\r
42150 {xtype: 'tbspacer'}, // or ' '
\r
42152 // space width is also configurable via javascript
\r
42153 {xtype: 'tbspacer', width: 50}, // add a 50px space
\r
42159 * Creates a new Spacer
\r
42160 * @xtype tbspacer
\r
42162 T.Spacer = Ext.extend(T.Item, {
\r
42164 * @cfg {Number} width
\r
42165 * The width of the spacer in pixels (defaults to 2px via css style <tt>.x-toolbar .xtb-spacer</tt>).
\r
42168 onRender : function(ct, position){
\r
42169 this.el = ct.createChild({tag:'div', cls:'xtb-spacer', style: this.width?'width:'+this.width+'px':''}, position);
\r
42172 Ext.reg('tbspacer', T.Spacer);
\r
42175 * @class Ext.Toolbar.Fill
\r
42176 * @extends Ext.Toolbar.Spacer
\r
42177 * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
\r
42178 * the right-justified button container.
\r
42183 {xtype: 'tbfill'}, // or '->'
\r
42189 * Creates a new Fill
\r
42192 T.Fill = Ext.extend(T.Item, {
\r
42194 render : Ext.emptyFn,
\r
42197 Ext.reg('tbfill', T.Fill);
\r
42200 * @class Ext.Toolbar.TextItem
\r
42201 * @extends Ext.Toolbar.Item
\r
42202 * A simple class that renders text directly into a toolbar
\r
42203 * (with css class:<tt>'xtb-text'</tt>). Example usage:
\r
42207 {xtype: 'tbtext', text: 'Item 1'} // or simply 'Item 1'
\r
42212 * Creates a new TextItem
\r
42213 * @param {String/Object} text A text string, or a config object containing a <tt>text</tt> property
\r
42216 T.TextItem = Ext.extend(T.Item, {
\r
42217 constructor: function(config){
\r
42218 if (Ext.isString(config)) {
\r
42219 config = { autoEl: {cls: 'xtb-text', html: config }};
\r
42221 config.autoEl = {cls: 'xtb-text', html: config.text || ''};
\r
42223 T.TextItem.superclass.constructor.call(this, config);
\r
42226 * Updates this item's text, setting the text to be used as innerHTML.
\r
42227 * @param {String} t The text to display (html accepted).
\r
42229 setText : function(t) {
\r
42230 if (this.rendered) {
\r
42231 this.el.dom.innerHTML = t;
\r
42233 this.autoEl.html = t;
\r
42237 Ext.reg('tbtext', T.TextItem);
\r
42239 // backwards compat
\r
42240 T.Button = Ext.extend(Ext.Button, {});
\r
42241 T.SplitButton = Ext.extend(Ext.SplitButton, {});
\r
42242 Ext.reg('tbbutton', T.Button);
\r
42243 Ext.reg('tbsplit', T.SplitButton);
\r
42247 * @class Ext.ButtonGroup
\r
42248 * @extends Ext.Panel
\r
42249 * Container for a group of buttons. Example usage:
\r
42251 var p = new Ext.Panel({
\r
42252 title: 'Panel with Button Group',
\r
42255 renderTo: document.body,
\r
42256 html: 'whatever',
\r
42258 xtype: 'buttongroup',
\r
42259 {@link #columns}: 3,
\r
42260 title: 'Clipboard',
\r
42264 rowspan: 3, iconCls: 'add',
\r
42265 iconAlign: 'top',
\r
42266 cls: 'x-btn-as-arrow'
\r
42268 xtype:'splitbutton',
\r
42269 text: 'Menu Button',
\r
42273 iconAlign: 'top',
\r
42274 arrowAlign:'bottom',
\r
42275 menu: [{text: 'Menu Item 1'}]
\r
42277 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
\r
42279 text: 'Copy', iconCls: 'add16'
\r
42281 text: 'Format', iconCls: 'add16'
\r
42286 * @xtype buttongroup
\r
42288 Ext.ButtonGroup = Ext.extend(Ext.Panel, {
\r
42290 * @cfg {Number} columns The <tt>columns</tt> configuration property passed to the
\r
42291 * {@link #layout configured layout manager}. See {@link Ext.layout.TableLayout#columns}.
\r
42294 * @cfg {String} baseCls Defaults to <tt>'x-btn-group'</tt>. See {@link Ext.Panel#baseCls}.
\r
42296 baseCls: 'x-btn-group',
\r
42298 * @cfg {String} layout Defaults to <tt>'table'</tt>. See {@link Ext.Container#layout}.
\r
42301 defaultType: 'button',
\r
42303 * @cfg {Boolean} frame Defaults to <tt>true</tt>. See {@link Ext.Panel#frame}.
\r
42306 internalDefaults: {removeMode: 'container', hideParent: true},
\r
42308 initComponent : function(){
\r
42309 this.layoutConfig = this.layoutConfig || {};
\r
42310 Ext.applyIf(this.layoutConfig, {
\r
42311 columns : this.columns
\r
42314 this.addClass('x-btn-group-notitle');
\r
42316 this.on('afterlayout', this.onAfterLayout, this);
\r
42317 Ext.ButtonGroup.superclass.initComponent.call(this);
\r
42320 applyDefaults : function(c){
\r
42321 c = Ext.ButtonGroup.superclass.applyDefaults.call(this, c);
\r
42322 var d = this.internalDefaults;
\r
42324 Ext.applyIf(c.initialConfig, d);
\r
42327 Ext.applyIf(c, d);
\r
42332 onAfterLayout : function(){
\r
42333 var bodyWidth = this.body.getFrameWidth('lr') + this.body.dom.firstChild.offsetWidth;
\r
42334 this.body.setWidth(bodyWidth);
\r
42335 this.el.setWidth(bodyWidth + this.getFrameWidth());
\r
42338 * @cfg {Array} tools @hide
\r
42342 Ext.reg('buttongroup', Ext.ButtonGroup);
\r
42344 * @class Ext.PagingToolbar
42345 * @extends Ext.Toolbar
42346 * <p>As the amount of records increases, the time required for the browser to render
42347 * them increases. Paging is used to reduce the amount of data exchanged with the client.
42348 * Note: if there are more records/rows than can be viewed in the available screen area, vertical
42349 * scrollbars will be added.</p>
42350 * <p>Paging is typically handled on the server side (see exception below). The client sends
42351 * parameters to the server side, which the server needs to interpret and then respond with the
42352 * approprate data.</p>
42353 * <p><b>Ext.PagingToolbar</b> is a specialized toolbar that is bound to a {@link Ext.data.Store}
42354 * and provides automatic paging control. This Component {@link Ext.data.Store#load load}s blocks
42355 * of data into the <tt>{@link #store}</tt> by passing {@link Ext.data.Store#paramNames paramNames} used for
42356 * paging criteria.</p>
42357 * <p>PagingToolbar is typically used as one of the Grid's toolbars:</p>
42359 Ext.QuickTips.init(); // to display button quicktips
42361 var myStore = new Ext.data.Store({
42365 var myPageSize = 25; // server script should only send back 25 items
42367 var grid = new Ext.grid.GridPanel({
42370 bbar: new Ext.PagingToolbar({
42371 {@link #store}: myStore, // grid and PagingToolbar using same store
42372 {@link #displayInfo}: true,
42373 {@link #pageSize}: myPageSize,
42374 {@link #prependButtons}: true,
42382 * <p>To use paging, pass the paging requirements to the server when the store is first loaded.</p>
42386 start: 0, // specify params for the first page load if using paging
42392 * <p><u>Paging with Local Data</u></p>
42393 * <p>Paging can also be accomplished with local data using extensions:</p>
42394 * <div class="mdetail-params"><ul>
42395 * <li><a href="http://extjs.com/forum/showthread.php?t=57386">Ext.ux.data.PagingStore</a></li>
42396 * <li>Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)</li>
42399 * Create a new PagingToolbar
42400 * @param {Object} config The config object
42405 var T = Ext.Toolbar;
42407 Ext.PagingToolbar = Ext.extend(Ext.Toolbar, {
42409 * @cfg {Ext.data.Store} store
42410 * The {@link Ext.data.Store} the paging toolbar should use as its data source (required).
42413 * @cfg {Boolean} displayInfo
42414 * <tt>true</tt> to display the displayMsg (defaults to <tt>false</tt>)
42417 * @cfg {Number} pageSize
42418 * The number of records to display per page (defaults to <tt>20</tt>)
42422 * @cfg {Boolean} prependButtons
42423 * <tt>true</tt> to insert any configured <tt>items</tt> <i>before</i> the paging buttons.
42424 * Defaults to <tt>false</tt>.
42427 * @cfg {String} displayMsg
42428 * The paging status message to display (defaults to <tt>'Displaying {0} - {1} of {2}'</tt>).
42429 * Note that this string is formatted using the braced numbers <tt>{0}-{2}</tt> as tokens
42430 * that are replaced by the values for start, end and total respectively. These tokens should
42431 * be preserved when overriding this string if showing those values is desired.
42433 displayMsg : 'Displaying {0} - {1} of {2}',
42435 * @cfg {String} emptyMsg
42436 * The message to display when no records are found (defaults to 'No data to display')
42438 emptyMsg : 'No data to display',
42440 * @cfg {String} beforePageText
42441 * The text displayed before the input item (defaults to <tt>'Page'</tt>).
42443 beforePageText : 'Page',
42445 * @cfg {String} afterPageText
42446 * Customizable piece of the default paging text (defaults to <tt>'of {0}'</tt>). Note that
42447 * this string is formatted using <tt>{0}</tt> as a token that is replaced by the number of
42448 * total pages. This token should be preserved when overriding this string if showing the
42449 * total page count is desired.
42451 afterPageText : 'of {0}',
42453 * @cfg {String} firstText
42454 * The quicktip text displayed for the first page button (defaults to <tt>'First Page'</tt>).
42455 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
42457 firstText : 'First Page',
42459 * @cfg {String} prevText
42460 * The quicktip text displayed for the previous page button (defaults to <tt>'Previous Page'</tt>).
42461 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
42463 prevText : 'Previous Page',
42465 * @cfg {String} nextText
42466 * The quicktip text displayed for the next page button (defaults to <tt>'Next Page'</tt>).
42467 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
42469 nextText : 'Next Page',
42471 * @cfg {String} lastText
42472 * The quicktip text displayed for the last page button (defaults to <tt>'Last Page'</tt>).
42473 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
42475 lastText : 'Last Page',
42477 * @cfg {String} refreshText
42478 * The quicktip text displayed for the Refresh button (defaults to <tt>'Refresh'</tt>).
42479 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
42481 refreshText : 'Refresh',
42485 * <b>The defaults for these should be set in the data store.</b>
42486 * Object mapping of parameter names used for load calls, initially set to:
42487 * <pre>{start: 'start', limit: 'limit'}</pre>
42491 * The number of records to display per page. See also <tt>{@link #cursor}</tt>.
42493 * @property pageSize
42497 * Indicator for the record position. This property might be used to get the active page
42498 * number for example:<pre><code>
42499 * // t is reference to the paging toolbar instance
42500 * var activePage = Math.ceil((t.cursor + t.pageSize) / t.pageSize);
42506 initComponent : function(){
42507 var pagingItems = [this.first = new T.Button({
42508 tooltip: this.firstText,
42509 overflowText: this.firstText,
42510 iconCls: 'x-tbar-page-first',
42512 handler: this.moveFirst,
42514 }), this.prev = new T.Button({
42515 tooltip: this.prevText,
42516 overflowText: this.prevText,
42517 iconCls: 'x-tbar-page-prev',
42519 handler: this.movePrevious,
42521 }), '-', this.beforePageText,
42522 this.inputItem = new Ext.form.NumberField({
42523 cls: 'x-tbar-page-number',
42524 allowDecimals: false,
42525 allowNegative: false,
42526 enableKeyEvents: true,
42527 selectOnFocus: true,
42530 keydown: this.onPagingKeyDown,
42531 blur: this.onPagingBlur
42533 }), this.afterTextItem = new T.TextItem({
42534 text: String.format(this.afterPageText, 1)
42535 }), '-', this.next = new T.Button({
42536 tooltip: this.nextText,
42537 overflowText: this.nextText,
42538 iconCls: 'x-tbar-page-next',
42540 handler: this.moveNext,
42542 }), this.last = new T.Button({
42543 tooltip: this.lastText,
42544 overflowText: this.lastText,
42545 iconCls: 'x-tbar-page-last',
42547 handler: this.moveLast,
42549 }), '-', this.refresh = new T.Button({
42550 tooltip: this.refreshText,
42551 overflowText: this.refreshText,
42552 iconCls: 'x-tbar-loading',
42553 handler: this.refresh,
42558 var userItems = this.items || this.buttons || [];
42559 if (this.prependButtons) {
42560 this.items = userItems.concat(pagingItems);
42562 this.items = pagingItems.concat(userItems);
42564 delete this.buttons;
42565 if(this.displayInfo){
42566 this.items.push('->');
42567 this.items.push(this.displayItem = new T.TextItem({}));
42569 Ext.PagingToolbar.superclass.initComponent.call(this);
42573 * Fires after the active page has been changed.
42574 * @param {Ext.PagingToolbar} this
42575 * @param {Object} pageData An object that has these properties:<ul>
42576 * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as
42577 * returned by the server</div></li>
42578 * <li><code>activePage</code> : Number <div class="sub-desc">The current page number</div></li>
42579 * <li><code>pages</code> : Number <div class="sub-desc">The total number of pages (calculated from
42580 * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
42585 * @event beforechange
42586 * Fires just before the active page is changed.
42587 * Return false to prevent the active page from being changed.
42588 * @param {Ext.PagingToolbar} this
42589 * @param {Object} params An object hash of the parameters which the PagingToolbar will send when
42590 * loading the required page. This will contain:<ul>
42591 * <li><code>start</code> : Number <div class="sub-desc">The starting row number for the next page of records to
42592 * be retrieved from the server</div></li>
42593 * <li><code>limit</code> : Number <div class="sub-desc">The number of records to be retrieved from the server</div></li>
42595 * <p>(note: the names of the <b>start</b> and <b>limit</b> properties are determined
42596 * by the store's {@link Ext.data.Store#paramNames paramNames} property.)</p>
42597 * <p>Parameters may be added as required in the event handler.</p>
42601 this.on('afterlayout', this.onFirstLayout, this, {single: true});
42603 this.bindStore(this.store);
42607 onFirstLayout : function(){
42609 this.onLoad.apply(this, this.dsLoaded);
42614 updateInfo : function(){
42615 if(this.displayItem){
42616 var count = this.store.getCount();
42617 var msg = count == 0 ?
42621 this.cursor+1, this.cursor+count, this.store.getTotalCount()
42623 this.displayItem.setText(msg);
42628 onLoad : function(store, r, o){
42629 if(!this.rendered){
42630 this.dsLoaded = [store, r, o];
42633 var p = this.getParams();
42634 this.cursor = (o.params && o.params[p.start]) ? o.params[p.start] : 0;
42635 var d = this.getPageData(), ap = d.activePage, ps = d.pages;
42637 this.afterTextItem.setText(String.format(this.afterPageText, d.pages));
42638 this.inputItem.setValue(ap);
42639 this.first.setDisabled(ap == 1);
42640 this.prev.setDisabled(ap == 1);
42641 this.next.setDisabled(ap == ps);
42642 this.last.setDisabled(ap == ps);
42643 this.refresh.enable();
42645 this.fireEvent('change', this, d);
42649 getPageData : function(){
42650 var total = this.store.getTotalCount();
42653 activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize),
42654 pages : total < this.pageSize ? 1 : Math.ceil(total/this.pageSize)
42659 * Change the active page
42660 * @param {Integer} page The page to display
42662 changePage : function(page){
42663 this.doLoad(((page-1) * this.pageSize).constrain(0, this.store.getTotalCount()));
42667 onLoadError : function(){
42668 if(!this.rendered){
42671 this.refresh.enable();
42675 readPage : function(d){
42676 var v = this.inputItem.getValue(), pageNum;
42677 if (!v || isNaN(pageNum = parseInt(v, 10))) {
42678 this.inputItem.setValue(d.activePage);
42684 onPagingFocus : function(){
42685 this.inputItem.select();
42689 onPagingBlur : function(e){
42690 this.inputItem.setValue(this.getPageData().activePage);
42694 onPagingKeyDown : function(field, e){
42695 var k = e.getKey(), d = this.getPageData(), pageNum;
42696 if (k == e.RETURN) {
42698 pageNum = this.readPage(d);
42699 if(pageNum !== false){
42700 pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1;
42701 this.doLoad(pageNum * this.pageSize);
42703 }else if (k == e.HOME || k == e.END){
42705 pageNum = k == e.HOME ? 1 : d.pages;
42706 field.setValue(pageNum);
42707 }else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN){
42709 if((pageNum = this.readPage(d))){
42710 var increment = e.shiftKey ? 10 : 1;
42711 if(k == e.DOWN || k == e.PAGEDOWN){
42714 pageNum += increment;
42715 if(pageNum >= 1 & pageNum <= d.pages){
42716 field.setValue(pageNum);
42723 getParams : function(){
42724 //retain backwards compat, allow params on the toolbar itself, if they exist.
42725 return this.paramNames || this.store.paramNames;
42729 beforeLoad : function(){
42730 if(this.rendered && this.refresh){
42731 this.refresh.disable();
42736 doLoad : function(start){
42737 var o = {}, pn = this.getParams();
42738 o[pn.start] = start;
42739 o[pn.limit] = this.pageSize;
42740 if(this.fireEvent('beforechange', this, o) !== false){
42741 this.store.load({params:o});
42746 * Move to the first page, has the same effect as clicking the 'first' button.
42748 moveFirst : function(){
42753 * Move to the previous page, has the same effect as clicking the 'previous' button.
42755 movePrevious : function(){
42756 this.doLoad(Math.max(0, this.cursor-this.pageSize));
42760 * Move to the next page, has the same effect as clicking the 'next' button.
42762 moveNext : function(){
42763 this.doLoad(this.cursor+this.pageSize);
42767 * Move to the last page, has the same effect as clicking the 'last' button.
42769 moveLast : function(){
42770 var total = this.store.getTotalCount(),
42771 extra = total % this.pageSize;
42773 this.doLoad(extra ? (total - extra) : total - this.pageSize);
42777 * Refresh the current page, has the same effect as clicking the 'refresh' button.
42779 refresh : function(){
42780 this.doLoad(this.cursor);
42784 * Binds the paging toolbar to the specified {@link Ext.data.Store}
42785 * @param {Store} store The store to bind to this toolbar
42786 * @param {Boolean} initial (Optional) true to not remove listeners
42788 bindStore : function(store, initial){
42790 if(!initial && this.store){
42791 this.store.un('beforeload', this.beforeLoad, this);
42792 this.store.un('load', this.onLoad, this);
42793 this.store.un('exception', this.onLoadError, this);
42794 if(store !== this.store && this.store.autoDestroy){
42795 this.store.destroy();
42799 store = Ext.StoreMgr.lookup(store);
42802 beforeload: this.beforeLoad,
42804 exception: this.onLoadError
42806 doLoad = store.getCount() > 0;
42808 this.store = store;
42810 this.onLoad(store, null, {});
42815 * Unbinds the paging toolbar from the specified {@link Ext.data.Store} <b>(deprecated)</b>
42816 * @param {Ext.data.Store} store The data store to unbind
42818 unbind : function(store){
42819 this.bindStore(null);
42823 * Binds the paging toolbar to the specified {@link Ext.data.Store} <b>(deprecated)</b>
42824 * @param {Ext.data.Store} store The data store to bind
42826 bind : function(store){
42827 this.bindStore(store);
42831 onDestroy : function(){
42832 this.bindStore(null);
42833 Ext.PagingToolbar.superclass.onDestroy.call(this);
42838 Ext.reg('paging', Ext.PagingToolbar);/**
\r
42839 * @class Ext.History
\r
42840 * @extends Ext.util.Observable
\r
42841 * History management component that allows you to register arbitrary tokens that signify application
\r
42842 * history state on navigation actions. You can then handle the history {@link #change} event in order
\r
42843 * to reset your application UI to the appropriate state when the user navigates forward or backward through
\r
42844 * the browser history stack.
\r
42847 Ext.History = (function () {
\r
42848 var iframe, hiddenField;
\r
42849 var ready = false;
\r
42850 var currentToken;
\r
42852 function getHash() {
\r
42853 var href = top.location.href, i = href.indexOf("#");
\r
42854 return i >= 0 ? href.substr(i + 1) : null;
\r
42857 function doSave() {
\r
42858 hiddenField.value = currentToken;
\r
42861 function handleStateChange(token) {
\r
42862 currentToken = token;
\r
42863 Ext.History.fireEvent('change', token);
\r
42866 function updateIFrame (token) {
\r
42867 var html = ['<html><body><div id="state">',token,'</div></body></html>'].join('');
\r
42869 var doc = iframe.contentWindow.document;
\r
42879 function checkIFrame() {
\r
42880 if (!iframe.contentWindow || !iframe.contentWindow.document) {
\r
42881 setTimeout(checkIFrame, 10);
\r
42885 var doc = iframe.contentWindow.document;
\r
42886 var elem = doc.getElementById("state");
\r
42887 var token = elem ? elem.innerText : null;
\r
42889 var hash = getHash();
\r
42891 setInterval(function () {
\r
42893 doc = iframe.contentWindow.document;
\r
42894 elem = doc.getElementById("state");
\r
42896 var newtoken = elem ? elem.innerText : null;
\r
42898 var newHash = getHash();
\r
42900 if (newtoken !== token) {
\r
42901 token = newtoken;
\r
42902 handleStateChange(token);
\r
42903 top.location.hash = token;
\r
42906 } else if (newHash !== hash) {
\r
42908 updateIFrame(newHash);
\r
42915 Ext.History.fireEvent('ready', Ext.History);
\r
42918 function startUp() {
\r
42919 currentToken = hiddenField.value ? hiddenField.value : getHash();
\r
42924 var hash = getHash();
\r
42925 setInterval(function () {
\r
42926 var newHash = getHash();
\r
42927 if (newHash !== hash) {
\r
42929 handleStateChange(hash);
\r
42934 Ext.History.fireEvent('ready', Ext.History);
\r
42940 * The id of the hidden field required for storing the current history token.
\r
42944 fieldId: 'x-history-field',
\r
42946 * The id of the iframe required by IE to manage the history stack.
\r
42950 iframeId: 'x-history-frame',
\r
42955 * Initialize the global History instance.
\r
42956 * @param {Boolean} onReady (optional) A callback function that will be called once the history
\r
42957 * component is fully initialized.
\r
42958 * @param {Object} scope (optional) The callback scope
\r
42960 init: function (onReady, scope) {
\r
42962 Ext.callback(onReady, scope, [this]);
\r
42965 if(!Ext.isReady){
\r
42966 Ext.onReady(function(){
\r
42967 Ext.History.init(onReady, scope);
\r
42971 hiddenField = Ext.getDom(Ext.History.fieldId);
\r
42973 iframe = Ext.getDom(Ext.History.iframeId);
\r
42975 this.addEvents('ready', 'change');
\r
42977 this.on('ready', onReady, scope, {single:true});
\r
42983 * Add a new token to the history stack. This can be any arbitrary value, although it would
\r
42984 * commonly be the concatenation of a component id and another id marking the specifc history
\r
42985 * state of that component. Example usage:
\r
42987 // Handle tab changes on a TabPanel
\r
42988 tabPanel.on('tabchange', function(tabPanel, tab){
\r
42989 Ext.History.add(tabPanel.id + ':' + tab.id);
\r
42992 * @param {String} token The value that defines a particular application-specific history state
\r
42993 * @param {Boolean} preventDuplicates When true, if the passed token matches the current token
\r
42994 * it will not save a new history step. Set to false if the same state can be saved more than once
\r
42995 * at the same history stack location (defaults to true).
\r
42997 add: function (token, preventDup) {
\r
42998 if(preventDup !== false){
\r
42999 if(this.getToken() == token){
\r
43004 return updateIFrame(token);
\r
43006 top.location.hash = token;
\r
43012 * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
\r
43014 back: function(){
\r
43019 * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
\r
43021 forward: function(){
\r
43026 * Retrieves the currently-active history token.
\r
43027 * @return {String} The token
\r
43029 getToken: function() {
\r
43030 return ready ? currentToken : getHash();
\r
43034 Ext.apply(Ext.History, new Ext.util.Observable());/**
\r
43036 * @extends Ext.Panel
\r
43037 * This is the base class for {@link Ext.QuickTip} and {@link Ext.Tooltip} that provides the basic layout and
\r
43038 * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
\r
43039 * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
\r
43041 * Create a new Tip
\r
43042 * @param {Object} config The configuration options
\r
43044 Ext.Tip = Ext.extend(Ext.Panel, {
\r
43046 * @cfg {Boolean} closable True to render a close tool button into the tooltip header (defaults to false).
\r
43049 * @cfg {Number} width
\r
43050 * Width in pixels of the tip (defaults to auto). Width will be ignored if it exceeds the bounds of
\r
43051 * {@link #minWidth} or {@link #maxWidth}. The maximum supported value is 500.
\r
43054 * @cfg {Number} minWidth The minimum width of the tip in pixels (defaults to 40).
\r
43058 * @cfg {Number} maxWidth The maximum width of the tip in pixels (defaults to 300). The maximum supported value is 500.
\r
43062 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
\r
43063 * for bottom-right shadow (defaults to "sides").
\r
43065 shadow : "sides",
\r
43067 * @cfg {String} defaultAlign <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value
\r
43068 * for this tip relative to its element of origin (defaults to "tl-bl?").
\r
43070 defaultAlign : "tl-bl?",
\r
43071 autoRender: true,
\r
43072 quickShowInterval : 250,
\r
43074 // private panel overrides
\r
43077 baseCls: 'x-tip',
\r
43078 floating:{shadow:true,shim:true,useDisplay:true,constrain:false},
\r
43081 closeAction: 'hide',
\r
43084 initComponent : function(){
\r
43085 Ext.Tip.superclass.initComponent.call(this);
\r
43086 if(this.closable && !this.title){
\r
43087 this.elements += ',header';
\r
43092 afterRender : function(){
\r
43093 Ext.Tip.superclass.afterRender.call(this);
\r
43094 if(this.closable){
\r
43097 handler: this[this.closeAction],
\r
43104 * Shows this tip at the specified XY position. Example usage:
\r
43106 // Show the tip at x:50 and y:100
\r
43107 tip.showAt([50,100]);
\r
43109 * @param {Array} xy An array containing the x and y coordinates
\r
43111 showAt : function(xy){
\r
43112 Ext.Tip.superclass.show.call(this);
\r
43113 if(this.measureWidth !== false && (!this.initialConfig || typeof this.initialConfig.width != 'number')){
\r
43114 this.doAutoWidth();
\r
43116 if(this.constrainPosition){
\r
43117 xy = this.el.adjustForConstraints(xy);
\r
43119 this.setPagePosition(xy[0], xy[1]);
\r
43123 doAutoWidth : function(){
\r
43124 var bw = this.body.getTextWidth();
\r
43126 bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
\r
43128 bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr");
\r
43129 this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
\r
43131 // IE7 repaint bug on initial show
\r
43132 if(Ext.isIE7 && !this.repainted){
\r
43133 this.el.repaint();
\r
43134 this.repainted = true;
\r
43139 * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
\r
43140 * anchor position value. Example usage:
\r
43142 // Show the tip at the default position ('tl-br?')
\r
43143 tip.showBy('my-el');
\r
43145 // Show the tip's top-left corner anchored to the element's top-right corner
\r
43146 tip.showBy('my-el', 'tl-tr');
\r
43148 * @param {Mixed} el An HTMLElement, Ext.Element or string id of the target element to align to
\r
43149 * @param {String} position (optional) A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
\r
43150 * {@link #defaultAlign} if specified).
\r
43152 showBy : function(el, pos){
\r
43153 if(!this.rendered){
\r
43154 this.render(Ext.getBody());
\r
43156 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
\r
43159 initDraggable : function(){
\r
43160 this.dd = new Ext.Tip.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable);
\r
43161 this.header.addClass('x-tip-draggable');
\r
43165 // private - custom Tip DD implementation
\r
43166 Ext.Tip.DD = function(tip, config){
\r
43167 Ext.apply(this, config);
\r
43169 Ext.Tip.DD.superclass.constructor.call(this, tip.el.id, 'WindowDD-'+tip.id);
\r
43170 this.setHandleElId(tip.header.id);
\r
43171 this.scroll = false;
\r
43174 Ext.extend(Ext.Tip.DD, Ext.dd.DD, {
\r
43177 headerOffsets:[100, 25],
\r
43178 startDrag : function(){
\r
43179 this.tip.el.disableShadow();
\r
43181 endDrag : function(e){
\r
43182 this.tip.el.enableShadow(true);
\r
43185 * @class Ext.ToolTip
\r
43186 * @extends Ext.Tip
\r
43187 * A standard tooltip implementation for providing additional information when hovering over a target element.
\r
43189 * Create a new Tooltip
\r
43190 * @param {Object} config The configuration options
\r
43192 Ext.ToolTip = Ext.extend(Ext.Tip, {
\r
43194 * When a Tooltip is configured with the {@link #delegate} option to cause selected child elements of the {@link #target}
\r
43195 * Element to each trigger a seperate show event, this property is set to the DOM element which triggered the show.
\r
43196 * @type DOMElement
\r
43197 * @property triggerElement
\r
43200 * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to monitor for mouseover events to trigger
\r
43201 * showing this ToolTip.
\r
43204 * @cfg {Boolean} autoHide True to automatically hide the tooltip after the mouse exits the target element
\r
43205 * or after the {@link #dismissDelay} has expired if set (defaults to true). If {@link closable} = true a close
\r
43206 * tool button will be rendered into the tooltip header.
\r
43209 * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays after the mouse enters the
\r
43210 * target element (defaults to 500)
\r
43214 * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the target element but before the
\r
43215 * tooltip actually hides (defaults to 200). Set to 0 for the tooltip to hide immediately.
\r
43219 * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip automatically hides (defaults to 5000).
\r
43220 * To disable automatic hiding, set dismissDelay = 0.
\r
43222 dismissDelay: 5000,
\r
43224 * @cfg {Array} mouseOffset An XY offset from the mouse position where the tooltip should be shown (defaults to [15,18]).
\r
43227 * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it moves over the target element (defaults to false).
\r
43229 trackMouse : false,
\r
43231 * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target element, false to
\r
43232 * anchor it relative to the mouse coordinates (defaults to true). When anchorToTarget is
\r
43233 * true, use {@link #defaultAlign} to control tooltip alignment to the target element. When
\r
43234 * anchorToTarget is false, use {@link #anchorPosition} instead to control alignment.
\r
43236 anchorToTarget: true,
\r
43238 * @cfg {Number} anchorOffset A numeric pixel value used to offset the default position of the
\r
43239 * anchor arrow (defaults to 0). When the anchor position is on the top or bottom of the tooltip,
\r
43240 * anchorOffset will be used as a horizontal offset. Likewise, when the anchor position is on the
\r
43241 * left or right side, anchorOffset will be used as a vertical offset.
\r
43245 * @cfg {String} delegate <p>Optional. A {@link Ext.DomQuery DomQuery} selector which allows selection of individual elements
\r
43246 * within the {@link #target} element to trigger showing and hiding the ToolTip as the mouse moves within the target.</p>
\r
43247 * <p>When specified, the child element of the target which caused a show event is placed into the {@link #triggerElement} property
\r
43248 * before the ToolTip is shown.</p>
\r
43249 * <p>This may be useful when a Component has regular, repeating elements in it, each of which need a Tooltip which contains
\r
43250 * information specific to that element. For example:</p><pre><code>
\r
43251 var myGrid = new Ext.grid.gridPanel(gridConfig);
\r
43252 myGrid.on('render', function(grid) {
\r
43253 var store = grid.getStore(); // Capture the Store.
\r
43254 var view = grid.getView(); // Capture the GridView.
\r
43255 myGrid.tip = new Ext.ToolTip({
\r
43256 target: view.mainBody, // The overall target element.
\r
43257 delegate: '.x-grid3-row', // Each grid row causes its own seperate show and hide.
\r
43258 trackMouse: true, // Moving within the row should not hide the tip.
\r
43259 renderTo: document.body, // Render immediately so that tip.body can be referenced prior to the first show.
\r
43260 listeners: { // Change content dynamically depending on which element triggered the show.
\r
43261 beforeshow: function updateTipBody(tip) {
\r
43262 var rowIndex = view.findRowIndex(tip.triggerElement);
\r
43263 tip.body.dom.innerHTML = "Over Record ID " + store.getAt(rowIndex).id;
\r
43271 targetCounter: 0,
\r
43273 constrainPosition: false,
\r
43276 initComponent: function(){
\r
43277 Ext.ToolTip.superclass.initComponent.call(this);
\r
43278 this.lastActive = new Date();
\r
43279 this.initTarget(this.target);
\r
43280 this.origAnchor = this.anchor;
\r
43284 onRender : function(ct, position){
\r
43285 Ext.ToolTip.superclass.onRender.call(this, ct, position);
\r
43286 this.anchorCls = 'x-tip-anchor-' + this.getAnchorPosition();
\r
43287 this.anchorEl = this.el.createChild({
\r
43288 cls: 'x-tip-anchor ' + this.anchorCls
\r
43293 afterRender : function(){
\r
43294 Ext.ToolTip.superclass.afterRender.call(this);
\r
43295 this.anchorEl.setStyle('z-index', this.el.getZIndex() + 1);
\r
43299 * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
\r
43300 * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
\r
43302 initTarget : function(target){
\r
43304 if((t = Ext.get(target))){
\r
43306 this.target = Ext.get(this.target);
\r
43307 this.target.un('mouseover', this.onTargetOver, this);
\r
43308 this.target.un('mouseout', this.onTargetOut, this);
\r
43309 this.target.un('mousemove', this.onMouseMove, this);
\r
43312 mouseover: this.onTargetOver,
\r
43313 mouseout: this.onTargetOut,
\r
43314 mousemove: this.onMouseMove,
\r
43320 this.anchorTarget = this.target;
\r
43325 onMouseMove : function(e){
\r
43326 var t = this.delegate ? e.getTarget(this.delegate) : this.triggerElement = true;
\r
43328 this.targetXY = e.getXY();
\r
43329 if (t === this.triggerElement) {
\r
43330 if(!this.hidden && this.trackMouse){
\r
43331 this.setPagePosition(this.getTargetXY());
\r
43335 this.lastActive = new Date(0);
\r
43336 this.onTargetOver(e);
\r
43338 } else if (!this.closable && this.isVisible()) {
\r
43344 getTargetXY : function(){
\r
43346 this.targetCounter++;
\r
43347 var offsets = this.getOffsets();
\r
43348 var xy = (this.anchorToTarget && !this.trackMouse) ?
\r
43349 this.el.getAlignToXY(this.anchorTarget, this.getAnchorAlign()) :
\r
43352 var dw = Ext.lib.Dom.getViewWidth()-5;
\r
43353 var dh = Ext.lib.Dom.getViewHeight()-5;
\r
43354 var scrollX = (document.documentElement.scrollLeft || document.body.scrollLeft || 0)+5;
\r
43355 var scrollY = (document.documentElement.scrollTop || document.body.scrollTop || 0)+5;
\r
43357 var axy = [xy[0] + offsets[0], xy[1] + offsets[1]];
\r
43358 var sz = this.getSize();
\r
43359 this.anchorEl.removeClass(this.anchorCls);
\r
43361 if(this.targetCounter < 2){
\r
43362 if(axy[0] < scrollX){
\r
43363 if(this.anchorToTarget){
\r
43364 this.defaultAlign = 'l-r';
\r
43365 if(this.mouseOffset){this.mouseOffset[0] *= -1;}
\r
43367 this.anchor = 'left';
\r
43368 return this.getTargetXY();
\r
43370 if(axy[0]+sz.width > dw){
\r
43371 if(this.anchorToTarget){
\r
43372 this.defaultAlign = 'r-l';
\r
43373 if(this.mouseOffset){this.mouseOffset[0] *= -1;}
\r
43375 this.anchor = 'right';
\r
43376 return this.getTargetXY();
\r
43378 if(axy[1] < scrollY){
\r
43379 if(this.anchorToTarget){
\r
43380 this.defaultAlign = 't-b';
\r
43381 if(this.mouseOffset){this.mouseOffset[1] *= -1;}
\r
43383 this.anchor = 'top';
\r
43384 return this.getTargetXY();
\r
43386 if(axy[1]+sz.height > dh){
\r
43387 if(this.anchorToTarget){
\r
43388 this.defaultAlign = 'b-t';
\r
43389 if(this.mouseOffset){this.mouseOffset[1] *= -1;}
\r
43391 this.anchor = 'bottom';
\r
43392 return this.getTargetXY();
\r
43396 this.anchorCls = 'x-tip-anchor-'+this.getAnchorPosition();
\r
43397 this.anchorEl.addClass(this.anchorCls);
\r
43398 this.targetCounter = 0;
\r
43401 var mouseOffset = this.getMouseOffset();
\r
43402 return [this.targetXY[0]+mouseOffset[0], this.targetXY[1]+mouseOffset[1]];
\r
43406 getMouseOffset : function(){
\r
43407 var offset = this.anchor ? [0,0] : [15,18];
\r
43408 if(this.mouseOffset){
\r
43409 offset[0] += this.mouseOffset[0];
\r
43410 offset[1] += this.mouseOffset[1];
\r
43416 getAnchorPosition : function(){
\r
43418 this.tipAnchor = this.anchor.charAt(0);
\r
43420 var m = this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
\r
43422 throw "AnchorTip.defaultAlign is invalid";
\r
43424 this.tipAnchor = m[1].charAt(0);
\r
43427 switch(this.tipAnchor){
\r
43428 case 't': return 'top';
\r
43429 case 'b': return 'bottom';
\r
43430 case 'r': return 'right';
\r
43436 getAnchorAlign : function(){
\r
43437 switch(this.anchor){
\r
43438 case 'top' : return 'tl-bl';
\r
43439 case 'left' : return 'tl-tr';
\r
43440 case 'right': return 'tr-tl';
\r
43441 default : return 'bl-tl';
\r
43446 getOffsets: function(){
\r
43447 var offsets, ap = this.getAnchorPosition().charAt(0);
\r
43448 if(this.anchorToTarget && !this.trackMouse){
\r
43451 offsets = [0, 9];
\r
43454 offsets = [0, -13];
\r
43457 offsets = [-13, 0];
\r
43460 offsets = [9, 0];
\r
43466 offsets = [-15-this.anchorOffset, 30];
\r
43469 offsets = [-19-this.anchorOffset, -13-this.el.dom.offsetHeight];
\r
43472 offsets = [-15-this.el.dom.offsetWidth, -13-this.anchorOffset];
\r
43475 offsets = [25, -13-this.anchorOffset];
\r
43479 var mouseOffset = this.getMouseOffset();
\r
43480 offsets[0] += mouseOffset[0];
\r
43481 offsets[1] += mouseOffset[1];
\r
43487 onTargetOver : function(e){
\r
43488 if(this.disabled || e.within(this.target.dom, true)){
\r
43491 var t = e.getTarget(this.delegate);
\r
43493 this.triggerElement = t;
\r
43494 this.clearTimer('hide');
\r
43495 this.targetXY = e.getXY();
\r
43496 this.delayShow();
\r
43501 delayShow : function(){
\r
43502 if(this.hidden && !this.showTimer){
\r
43503 if(this.lastActive.getElapsed() < this.quickShowInterval){
\r
43506 this.showTimer = this.show.defer(this.showDelay, this);
\r
43508 }else if(!this.hidden && this.autoHide !== false){
\r
43514 onTargetOut : function(e){
\r
43515 if(this.disabled || e.within(this.target.dom, true)){
\r
43518 this.clearTimer('show');
\r
43519 if(this.autoHide !== false){
\r
43520 this.delayHide();
\r
43525 delayHide : function(){
\r
43526 if(!this.hidden && !this.hideTimer){
\r
43527 this.hideTimer = this.hide.defer(this.hideDelay, this);
\r
43532 * Hides this tooltip if visible.
\r
43534 hide: function(){
\r
43535 this.clearTimer('dismiss');
\r
43536 this.lastActive = new Date();
\r
43537 if(this.anchorEl){
\r
43538 this.anchorEl.hide();
\r
43540 Ext.ToolTip.superclass.hide.call(this);
\r
43541 delete this.triggerElement;
\r
43545 * Shows this tooltip at the current event target XY position.
\r
43547 show : function(){
\r
43549 // pre-show it off screen so that the el will have dimensions
\r
43550 // for positioning calcs when getting xy next
\r
43551 this.showAt([-1000,-1000]);
\r
43552 this.origConstrainPosition = this.constrainPosition;
\r
43553 this.constrainPosition = false;
\r
43554 this.anchor = this.origAnchor;
\r
43556 this.showAt(this.getTargetXY());
\r
43559 this.syncAnchor();
\r
43560 this.anchorEl.show();
\r
43561 this.constrainPosition = this.origConstrainPosition;
\r
43563 this.anchorEl.hide();
\r
43568 showAt : function(xy){
\r
43569 this.lastActive = new Date();
\r
43570 this.clearTimers();
\r
43571 Ext.ToolTip.superclass.showAt.call(this, xy);
\r
43572 if(this.dismissDelay && this.autoHide !== false){
\r
43573 this.dismissTimer = this.hide.defer(this.dismissDelay, this);
\r
43578 syncAnchor : function(){
\r
43579 var anchorPos, targetPos, offset;
\r
43580 switch(this.tipAnchor.charAt(0)){
\r
43583 targetPos = 'tl';
\r
43584 offset = [20+this.anchorOffset, 2];
\r
43588 targetPos = 'tr';
\r
43589 offset = [-2, 11+this.anchorOffset];
\r
43593 targetPos = 'bl';
\r
43594 offset = [20+this.anchorOffset, -2];
\r
43598 targetPos = 'tl';
\r
43599 offset = [2, 11+this.anchorOffset];
\r
43602 this.anchorEl.alignTo(this.el, anchorPos+'-'+targetPos, offset);
\r
43606 setPagePosition : function(x, y){
\r
43607 Ext.ToolTip.superclass.setPagePosition.call(this, x, y);
\r
43609 this.syncAnchor();
\r
43614 clearTimer : function(name){
\r
43615 name = name + 'Timer';
\r
43616 clearTimeout(this[name]);
\r
43617 delete this[name];
\r
43621 clearTimers : function(){
\r
43622 this.clearTimer('show');
\r
43623 this.clearTimer('dismiss');
\r
43624 this.clearTimer('hide');
\r
43628 onShow : function(){
\r
43629 Ext.ToolTip.superclass.onShow.call(this);
\r
43630 Ext.getDoc().on('mousedown', this.onDocMouseDown, this);
\r
43634 onHide : function(){
\r
43635 Ext.ToolTip.superclass.onHide.call(this);
\r
43636 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
\r
43640 onDocMouseDown : function(e){
\r
43641 if(this.autoHide !== true && !this.closable && !e.within(this.el.dom)){
\r
43643 this.enable.defer(100, this);
\r
43648 onDisable : function(){
\r
43649 this.clearTimers();
\r
43654 adjustPosition : function(x, y){
\r
43655 if(this.contstrainPosition){
\r
43656 var ay = this.targetXY[1], h = this.getSize().height;
\r
43657 if(y <= ay && (y+h) >= ay){
\r
43661 return {x : x, y: y};
\r
43665 onDestroy : function(){
\r
43666 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
\r
43667 Ext.ToolTip.superclass.onDestroy.call(this);
\r
43670 * @class Ext.QuickTip
\r
43671 * @extends Ext.ToolTip
\r
43672 * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
\r
43673 * {@link Ext.QuickTips} instance. See the QuickTips class header for additional usage details and examples.
\r
43675 * Create a new Tip
\r
43676 * @param {Object} config The configuration options
\r
43678 Ext.QuickTip = Ext.extend(Ext.ToolTip, {
\r
43680 * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to associate with this quicktip (defaults to the document).
\r
43683 * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
\r
43685 interceptTitles : false,
\r
43689 namespace : "ext",
\r
43690 attribute : "qtip",
\r
43691 width : "qwidth",
\r
43692 target : "target",
\r
43693 title : "qtitle",
\r
43696 align : "qalign",
\r
43697 anchor : "anchor"
\r
43701 initComponent : function(){
\r
43702 this.target = this.target || Ext.getDoc();
\r
43703 this.targets = this.targets || {};
\r
43704 Ext.QuickTip.superclass.initComponent.call(this);
\r
43708 * Configures a new quick tip instance and assigns it to a target element. The following config values are
\r
43709 * supported (for example usage, see the {@link Ext.QuickTips} class header):
\r
43710 * <div class="mdetail-params"><ul>
\r
43711 * <li>autoHide</li>
\r
43713 * <li>dismissDelay (overrides the singleton value)</li>
\r
43714 * <li>target (required)</li>
\r
43715 * <li>text (required)</li>
\r
43717 * <li>width</li></ul></div>
\r
43718 * @param {Object} config The config object
\r
43720 register : function(config){
\r
43721 var cs = Ext.isArray(config) ? config : arguments;
\r
43722 for(var i = 0, len = cs.length; i < len; i++){
\r
43724 var target = c.target;
\r
43726 if(Ext.isArray(target)){
\r
43727 for(var j = 0, jlen = target.length; j < jlen; j++){
\r
43728 this.targets[Ext.id(target[j])] = c;
\r
43731 this.targets[Ext.id(target)] = c;
\r
43738 * Removes this quick tip from its element and destroys it.
\r
43739 * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
\r
43741 unregister : function(el){
\r
43742 delete this.targets[Ext.id(el)];
\r
43746 * Hides a visible tip or cancels an impending show for a particular element.
\r
43747 * @param {String/HTMLElement/Element} el The element that is the target of the tip.
\r
43749 cancelShow: function(el){
\r
43750 var at = this.activeTarget;
\r
43751 el = Ext.get(el).dom;
\r
43752 if(this.isVisible()){
\r
43753 if(at && at.el == el){
\r
43756 }else if(at && at.el == el){
\r
43757 this.clearTimer('show');
\r
43762 onTargetOver : function(e){
\r
43763 if(this.disabled){
\r
43766 this.targetXY = e.getXY();
\r
43767 var t = e.getTarget();
\r
43768 if(!t || t.nodeType !== 1 || t == document || t == document.body){
\r
43771 if(this.activeTarget && t == this.activeTarget.el){
\r
43772 this.clearTimer('hide');
\r
43776 if(t && this.targets[t.id]){
\r
43777 this.activeTarget = this.targets[t.id];
\r
43778 this.activeTarget.el = t;
\r
43779 this.anchor = this.activeTarget.anchor;
\r
43781 this.anchorTarget = t;
\r
43783 this.delayShow();
\r
43787 var ttp, et = Ext.fly(t), cfg = this.tagConfig;
\r
43788 var ns = cfg.namespace;
\r
43789 if(this.interceptTitles && t.title){
\r
43792 t.removeAttribute("title");
\r
43793 e.preventDefault();
\r
43795 ttp = t.qtip || et.getAttribute(cfg.attribute, ns);
\r
43798 var autoHide = et.getAttribute(cfg.hide, ns);
\r
43799 this.activeTarget = {
\r
43802 width: et.getAttribute(cfg.width, ns),
\r
43803 autoHide: autoHide != "user" && autoHide !== 'false',
\r
43804 title: et.getAttribute(cfg.title, ns),
\r
43805 cls: et.getAttribute(cfg.cls, ns),
\r
43806 align: et.getAttribute(cfg.align, ns)
\r
43809 this.anchor = et.getAttribute(cfg.anchor, ns);
\r
43811 this.anchorTarget = t;
\r
43813 this.delayShow();
\r
43818 onTargetOut : function(e){
\r
43819 this.clearTimer('show');
\r
43820 if(this.autoHide !== false){
\r
43821 this.delayHide();
\r
43826 showAt : function(xy){
\r
43827 var t = this.activeTarget;
\r
43829 if(!this.rendered){
\r
43830 this.render(Ext.getBody());
\r
43831 this.activeTarget = t;
\r
43834 this.setWidth(t.width);
\r
43835 this.body.setWidth(this.adjustBodyWidth(t.width - this.getFrameWidth()));
\r
43836 this.measureWidth = false;
\r
43838 this.measureWidth = true;
\r
43840 this.setTitle(t.title || '');
\r
43841 this.body.update(t.text);
\r
43842 this.autoHide = t.autoHide;
\r
43843 this.dismissDelay = t.dismissDelay || this.dismissDelay;
\r
43844 if(this.lastCls){
\r
43845 this.el.removeClass(this.lastCls);
\r
43846 delete this.lastCls;
\r
43849 this.el.addClass(t.cls);
\r
43850 this.lastCls = t.cls;
\r
43853 this.constrainPosition = false;
\r
43854 }else if(t.align){ // TODO: this doesn't seem to work consistently
\r
43855 xy = this.el.getAlignToXY(t.el, t.align);
\r
43856 this.constrainPosition = false;
\r
43858 this.constrainPosition = true;
\r
43861 Ext.QuickTip.superclass.showAt.call(this, xy);
\r
43865 hide: function(){
\r
43866 delete this.activeTarget;
\r
43867 Ext.QuickTip.superclass.hide.call(this);
\r
43870 * @class Ext.QuickTips
\r
43871 * <p>Provides attractive and customizable tooltips for any element. The QuickTips
\r
43872 * singleton is used to configure and manage tooltips globally for multiple elements
\r
43873 * in a generic manner. To create individual tooltips with maximum customizability,
\r
43874 * you should consider either {@link Ext.Tip} or {@link Ext.ToolTip}.</p>
\r
43875 * <p>Quicktips can be configured via tag attributes directly in markup, or by
\r
43876 * registering quick tips programmatically via the {@link #register} method.</p>
\r
43877 * <p>The singleton's instance of {@link Ext.QuickTip} is available via
\r
43878 * {@link #getQuickTip}, and supports all the methods, and all the all the
\r
43879 * configuration properties of Ext.QuickTip. These settings will apply to all
\r
43880 * tooltips shown by the singleton.</p>
\r
43881 * <p>Below is the summary of the configuration properties which can be used.
\r
43882 * For detailed descriptions see {@link #getQuickTip}</p>
\r
43883 * <p><b>QuickTips singleton configs (all are optional)</b></p>
\r
43884 * <div class="mdetail-params"><ul><li>dismissDelay</li>
\r
43885 * <li>hideDelay</li>
\r
43886 * <li>maxWidth</li>
\r
43887 * <li>minWidth</li>
\r
43888 * <li>showDelay</li>
\r
43889 * <li>trackMouse</li></ul></div>
\r
43890 * <p><b>Target element configs (optional unless otherwise noted)</b></p>
\r
43891 * <div class="mdetail-params"><ul><li>autoHide</li>
\r
43893 * <li>dismissDelay (overrides singleton value)</li>
\r
43894 * <li>target (required)</li>
\r
43895 * <li>text (required)</li>
\r
43897 * <li>width</li></ul></div>
\r
43898 * <p>Here is an example showing how some of these config options could be used:</p>
\r
43900 // Init the singleton. Any tag-based quick tips will start working.
\r
43901 Ext.QuickTips.init();
\r
43903 // Apply a set of config properties to the singleton
\r
43904 Ext.apply(Ext.QuickTips.getQuickTip(), {
\r
43911 // Manually register a quick tip for a specific element
\r
43912 Ext.QuickTips.register({
\r
43913 target: 'my-div',
\r
43914 title: 'My Tooltip',
\r
43915 text: 'This tooltip was added in code',
\r
43920 * <p>To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
\r
43921 * the <b>ext:</b> namespace. The HTML element itself is automatically set as the quick tip target. Here is the summary
\r
43922 * of supported attributes (optional unless otherwise noted):</p>
\r
43923 * <ul><li><b>hide</b>: Specifying "user" is equivalent to setting autoHide = false. Any other value will be the
\r
43924 * same as autoHide = true.</li>
\r
43925 * <li><b>qclass</b>: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).</li>
\r
43926 * <li><b>qtip (required)</b>: The quick tip text (equivalent to the 'text' target element config).</li>
\r
43927 * <li><b>qtitle</b>: The quick tip title (equivalent to the 'title' target element config).</li>
\r
43928 * <li><b>qwidth</b>: The quick tip width (equivalent to the 'width' target element config).</li></ul>
\r
43929 * <p>Here is an example of configuring an HTML element to display a tooltip from markup:</p>
\r
43931 // Add a quick tip to an HTML button
\r
43932 <input type="button" value="OK" ext:qtitle="OK Button" ext:qwidth="100"
\r
43933 ext:qtip="This is a quick tip from markup!"></input>
\r
43937 Ext.QuickTips = function(){
\r
43938 var tip, locks = [];
\r
43941 * Initialize the global QuickTips instance and prepare any quick tips.
\r
43942 * @param {Boolean} autoRender True to render the QuickTips container immediately to preload images. (Defaults to true)
\r
43944 init : function(autoRender){
\r
43946 if(!Ext.isReady){
\r
43947 Ext.onReady(function(){
\r
43948 Ext.QuickTips.init(autoRender);
\r
43952 tip = new Ext.QuickTip({elements:'header,body'});
\r
43953 if(autoRender !== false){
\r
43954 tip.render(Ext.getBody());
\r
43960 * Enable quick tips globally.
\r
43962 enable : function(){
\r
43965 if(locks.length < 1){
\r
43972 * Disable quick tips globally.
\r
43974 disable : function(){
\r
43982 * Returns true if quick tips are enabled, else false.
\r
43983 * @return {Boolean}
\r
43985 isEnabled : function(){
\r
43986 return tip !== undefined && !tip.disabled;
\r
43990 * Gets the global QuickTips instance.
\r
43992 getQuickTip : function(){
\r
43997 * Configures a new quick tip instance and assigns it to a target element. See
\r
43998 * {@link Ext.QuickTip#register} for details.
\r
43999 * @param {Object} config The config object
\r
44001 register : function(){
\r
44002 tip.register.apply(tip, arguments);
\r
44006 * Removes any registered quick tip from the target element and destroys it.
\r
44007 * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
\r
44009 unregister : function(){
\r
44010 tip.unregister.apply(tip, arguments);
\r
44014 * Alias of {@link #register}.
\r
44015 * @param {Object} config The config object
\r
44017 tips :function(){
\r
44018 tip.register.apply(tip, arguments);
\r
44022 * @class Ext.tree.TreePanel
\r
44023 * @extends Ext.Panel
\r
44024 * <p>The TreePanel provides tree-structured UI representation of tree-structured data.</p>
\r
44025 * <p>{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata
\r
44026 * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.</p>
\r
44027 * <p><b>A TreePanel must have a {@link #root} node before it is rendered.</b> This may either be
\r
44028 * specified using the {@link #root} config option, or using the {@link #setRootNode} method.
\r
44029 * <p>An example of tree rendered to an existing div:</p><pre><code>
\r
44030 var tree = new Ext.tree.TreePanel({
\r
44031 renderTo: 'tree-div',
\r
44033 autoScroll: true,
\r
44036 containerScroll: true,
\r
44038 // auto create TreeLoader
\r
44039 dataUrl: 'get-nodes.php',
\r
44042 nodeType: 'async',
\r
44044 draggable: false,
\r
44049 tree.getRootNode().expand();
\r
44051 * <p>The example above would work with a data packet similar to this:</p><pre><code>
\r
44053 "text": "adapter",
\r
44054 "id": "source\/adapter",
\r
44058 "id": "source\/dd",
\r
44061 "text": "debug.js",
\r
44062 "id": "source\/debug.js",
\r
44067 * <p>An example of tree within a Viewport:</p><pre><code>
\r
44068 new Ext.Viewport({
\r
44069 layout: 'border',
\r
44072 collapsible: true,
\r
44073 title: 'Navigation',
\r
44074 xtype: 'treepanel',
\r
44076 autoScroll: true,
\r
44078 loader: new Ext.tree.TreeLoader(),
\r
44079 root: new Ext.tree.AsyncTreeNode({
\r
44082 text: 'Menu Option 1',
\r
44085 text: 'Menu Option 2',
\r
44088 text: 'Menu Option 3',
\r
44092 rootVisible: false,
\r
44094 click: function(n) {
\r
44095 Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
\r
44099 region: 'center',
\r
44100 xtype: 'tabpanel',
\r
44101 // remaining code not shown ...
\r
44106 * @cfg {Ext.tree.TreeNode} root The root node for the tree.
\r
44107 * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
\r
44108 * @cfg {Boolean} lines <tt>false</tt> to disable tree lines (defaults to <tt>true</tt>)
\r
44109 * @cfg {Boolean} enableDD <tt>true</tt> to enable drag and drop
\r
44110 * @cfg {Boolean} enableDrag <tt>true</tt> to enable just drag
\r
44111 * @cfg {Boolean} enableDrop <tt>true</tt> to enable just drop
\r
44112 * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance
\r
44113 * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
\r
44114 * @cfg {String} ddGroup The DD group this TreePanel belongs to
\r
44115 * @cfg {Boolean} ddAppendOnly <tt>true</tt> if the tree should only allow append drops (use for trees which are sorted)
\r
44116 * @cfg {Boolean} ddScroll <tt>true</tt> to enable body scrolling
\r
44117 * @cfg {Boolean} containerScroll <tt>true</tt> to register this container with ScrollManager
\r
44118 * @cfg {Boolean} hlDrop <tt>false</tt> to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})
\r
44119 * @cfg {String} hlColor The color of the node highlight (defaults to <tt>'C3DAF9'</tt>)
\r
44120 * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})
\r
44121 * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
\r
44122 * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})
\r
44123 * @cfg {Boolean} trackMouseOver <tt>false</tt> to disable mouse over highlighting
\r
44124 * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel
\r
44125 * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to <tt>'/'</tt>)
\r
44126 * @cfg {Boolean} useArrows <tt>true</tt> to use Vista-style arrows in the tree (defaults to <tt>false</tt>)
\r
44127 * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
\r
44130 * @param {Object} config
\r
44131 * @xtype treepanel
\r
44133 Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
\r
44134 rootVisible : true,
\r
44135 animate: Ext.enableFx,
\r
44137 enableDD : false,
\r
44138 hlDrop : Ext.enableFx,
\r
44139 pathSeparator: "/",
\r
44141 initComponent : function(){
\r
44142 Ext.tree.TreePanel.superclass.initComponent.call(this);
\r
44144 if(!this.eventModel){
\r
44145 this.eventModel = new Ext.tree.TreeEventModel(this);
\r
44148 // initialize the loader
\r
44149 var l = this.loader;
\r
44151 l = new Ext.tree.TreeLoader({
\r
44152 dataUrl: this.dataUrl,
\r
44153 requestMethod: this.requestMethod
\r
44155 }else if(typeof l == 'object' && !l.load){
\r
44156 l = new Ext.tree.TreeLoader(l);
\r
44160 this.nodeHash = {};
\r
44163 * The root node of this tree.
\r
44164 * @type Ext.tree.TreeNode
\r
44168 var r = this.root;
\r
44169 delete this.root;
\r
44170 this.setRootNode(r);
\r
44178 * Fires when a new child node is appended to a node in this tree.
\r
44179 * @param {Tree} tree The owner tree
\r
44180 * @param {Node} parent The parent node
\r
44181 * @param {Node} node The newly appended node
\r
44182 * @param {Number} index The index of the newly appended node
\r
44187 * Fires when a child node is removed from a node in this tree.
\r
44188 * @param {Tree} tree The owner tree
\r
44189 * @param {Node} parent The parent node
\r
44190 * @param {Node} node The child node removed
\r
44194 * @event movenode
\r
44195 * Fires when a node is moved to a new location in the tree
\r
44196 * @param {Tree} tree The owner tree
\r
44197 * @param {Node} node The node moved
\r
44198 * @param {Node} oldParent The old parent of this node
\r
44199 * @param {Node} newParent The new parent of this node
\r
44200 * @param {Number} index The index it was moved to
\r
44205 * Fires when a new child node is inserted in a node in this tree.
\r
44206 * @param {Tree} tree The owner tree
\r
44207 * @param {Node} parent The parent node
\r
44208 * @param {Node} node The child node inserted
\r
44209 * @param {Node} refNode The child node the node was inserted before
\r
44213 * @event beforeappend
\r
44214 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
\r
44215 * @param {Tree} tree The owner tree
\r
44216 * @param {Node} parent The parent node
\r
44217 * @param {Node} node The child node to be appended
\r
44221 * @event beforeremove
\r
44222 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
\r
44223 * @param {Tree} tree The owner tree
\r
44224 * @param {Node} parent The parent node
\r
44225 * @param {Node} node The child node to be removed
\r
44229 * @event beforemovenode
\r
44230 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
\r
44231 * @param {Tree} tree The owner tree
\r
44232 * @param {Node} node The node being moved
\r
44233 * @param {Node} oldParent The parent of the node
\r
44234 * @param {Node} newParent The new parent the node is moving to
\r
44235 * @param {Number} index The index it is being moved to
\r
44237 "beforemovenode",
\r
44239 * @event beforeinsert
\r
44240 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
\r
44241 * @param {Tree} tree The owner tree
\r
44242 * @param {Node} parent The parent node
\r
44243 * @param {Node} node The child node to be inserted
\r
44244 * @param {Node} refNode The child node the node is being inserted before
\r
44249 * @event beforeload
\r
44250 * Fires before a node is loaded, return false to cancel
\r
44251 * @param {Node} node The node being loaded
\r
44256 * Fires when a node is loaded
\r
44257 * @param {Node} node The node that was loaded
\r
44261 * @event textchange
\r
44262 * Fires when the text for a node is changed
\r
44263 * @param {Node} node The node
\r
44264 * @param {String} text The new text
\r
44265 * @param {String} oldText The old text
\r
44269 * @event beforeexpandnode
\r
44270 * Fires before a node is expanded, return false to cancel.
\r
44271 * @param {Node} node The node
\r
44272 * @param {Boolean} deep
\r
44273 * @param {Boolean} anim
\r
44275 "beforeexpandnode",
\r
44277 * @event beforecollapsenode
\r
44278 * Fires before a node is collapsed, return false to cancel.
\r
44279 * @param {Node} node The node
\r
44280 * @param {Boolean} deep
\r
44281 * @param {Boolean} anim
\r
44283 "beforecollapsenode",
\r
44285 * @event expandnode
\r
44286 * Fires when a node is expanded
\r
44287 * @param {Node} node The node
\r
44291 * @event disabledchange
\r
44292 * Fires when the disabled status of a node changes
\r
44293 * @param {Node} node The node
\r
44294 * @param {Boolean} disabled
\r
44296 "disabledchange",
\r
44298 * @event collapsenode
\r
44299 * Fires when a node is collapsed
\r
44300 * @param {Node} node The node
\r
44304 * @event beforeclick
\r
44305 * Fires before click processing on a node. Return false to cancel the default action.
\r
44306 * @param {Node} node The node
\r
44307 * @param {Ext.EventObject} e The event object
\r
44312 * Fires when a node is clicked
\r
44313 * @param {Node} node The node
\r
44314 * @param {Ext.EventObject} e The event object
\r
44318 * @event checkchange
\r
44319 * Fires when a node with a checkbox's checked property changes
\r
44320 * @param {Node} this This node
\r
44321 * @param {Boolean} checked
\r
44325 * @event dblclick
\r
44326 * Fires when a node is double clicked
\r
44327 * @param {Node} node The node
\r
44328 * @param {Ext.EventObject} e The event object
\r
44332 * @event contextmenu
\r
44333 * Fires when a node is right clicked. To display a context menu in response to this
\r
44334 * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add
\r
44335 * a handler for this event:<pre><code>
\r
44336 new Ext.tree.TreePanel({
\r
44337 title: 'My TreePanel',
\r
44338 root: new Ext.tree.AsyncTreeNode({
\r
44339 text: 'The Root',
\r
44341 { text: 'Child node 1', leaf: true },
\r
44342 { text: 'Child node 2', leaf: true }
\r
44345 contextMenu: new Ext.menu.Menu({
\r
44347 id: 'delete-node',
\r
44348 text: 'Delete Node'
\r
44351 itemclick: function(item) {
\r
44352 switch (item.id) {
\r
44353 case 'delete-node':
\r
44354 var n = item.parentMenu.contextNode;
\r
44355 if (n.parentNode) {
\r
44364 contextmenu: function(node, e) {
\r
44365 // Register the context node with the menu so that a Menu Item's handler function can access
\r
44366 // it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
\r
44368 var c = node.getOwnerTree().contextMenu;
\r
44369 c.contextNode = node;
\r
44370 c.showAt(e.getXY());
\r
44375 * @param {Node} node The node
\r
44376 * @param {Ext.EventObject} e The event object
\r
44380 * @event beforechildrenrendered
\r
44381 * Fires right before the child nodes for a node are rendered
\r
44382 * @param {Node} node The node
\r
44384 "beforechildrenrendered",
\r
44386 * @event startdrag
\r
44387 * Fires when a node starts being dragged
\r
44388 * @param {Ext.tree.TreePanel} this
\r
44389 * @param {Ext.tree.TreeNode} node
\r
44390 * @param {event} e The raw browser event
\r
44395 * Fires when a drag operation is complete
\r
44396 * @param {Ext.tree.TreePanel} this
\r
44397 * @param {Ext.tree.TreeNode} node
\r
44398 * @param {event} e The raw browser event
\r
44402 * @event dragdrop
\r
44403 * Fires when a dragged node is dropped on a valid DD target
\r
44404 * @param {Ext.tree.TreePanel} this
\r
44405 * @param {Ext.tree.TreeNode} node
\r
44406 * @param {DD} dd The dd it was dropped on
\r
44407 * @param {event} e The raw browser event
\r
44411 * @event beforenodedrop
\r
44412 * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
\r
44413 * passed to handlers has the following properties:<br />
\r
44414 * <ul style="padding:5px;padding-left:16px;">
\r
44415 * <li>tree - The TreePanel</li>
\r
44416 * <li>target - The node being targeted for the drop</li>
\r
44417 * <li>data - The drag data from the drag source</li>
\r
44418 * <li>point - The point of the drop - append, above or below</li>
\r
44419 * <li>source - The drag source</li>
\r
44420 * <li>rawEvent - Raw mouse event</li>
\r
44421 * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
\r
44422 * to be inserted by setting them on this object.</li>
\r
44423 * <li>cancel - Set this to true to cancel the drop.</li>
\r
44424 * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
\r
44425 * will prevent the animated "repair" from appearing.</li>
\r
44427 * @param {Object} dropEvent
\r
44429 "beforenodedrop",
\r
44431 * @event nodedrop
\r
44432 * Fires after a DD object is dropped on a node in this tree. The dropEvent
\r
44433 * passed to handlers has the following properties:<br />
\r
44434 * <ul style="padding:5px;padding-left:16px;">
\r
44435 * <li>tree - The TreePanel</li>
\r
44436 * <li>target - The node being targeted for the drop</li>
\r
44437 * <li>data - The drag data from the drag source</li>
\r
44438 * <li>point - The point of the drop - append, above or below</li>
\r
44439 * <li>source - The drag source</li>
\r
44440 * <li>rawEvent - Raw mouse event</li>
\r
44441 * <li>dropNode - Dropped node(s).</li>
\r
44443 * @param {Object} dropEvent
\r
44447 * @event nodedragover
\r
44448 * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent
\r
44449 * passed to handlers has the following properties:<br />
\r
44450 * <ul style="padding:5px;padding-left:16px;">
\r
44451 * <li>tree - The TreePanel</li>
\r
44452 * <li>target - The node being targeted for the drop</li>
\r
44453 * <li>data - The drag data from the drag source</li>
\r
44454 * <li>point - The point of the drop - append, above or below</li>
\r
44455 * <li>source - The drag source</li>
\r
44456 * <li>rawEvent - Raw mouse event</li>
\r
44457 * <li>dropNode - Drop node(s) provided by the source.</li>
\r
44458 * <li>cancel - Set this to true to signal drop not allowed.</li>
\r
44460 * @param {Object} dragOverEvent
\r
44464 if(this.singleExpand){
\r
44465 this.on("beforeexpandnode", this.restrictExpand, this);
\r
44470 proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){
\r
44471 if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){
\r
44472 ename = ename+'node';
\r
44474 // args inline for performance while bubbling events
\r
44475 return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);
\r
44480 * Returns this root node for this tree
\r
44483 getRootNode : function(){
\r
44484 return this.root;
\r
44488 * Sets the root node for this tree. If the TreePanel has already rendered a root node, the
\r
44489 * previous root node (and all of its descendants) are destroyed before the new root node is rendered.
\r
44490 * @param {Node} node
\r
44493 setRootNode : function(node){
\r
44494 Ext.destroy(this.root);
\r
44495 if(!node.render){ // attributes passed
\r
44496 node = this.loader.createNode(node);
\r
44498 this.root = node;
\r
44499 node.ownerTree = this;
\r
44500 node.isRoot = true;
\r
44501 this.registerNode(node);
\r
44502 if(!this.rootVisible){
\r
44503 var uiP = node.attributes.uiProvider;
\r
44504 node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);
\r
44506 if (this.innerCt) {
\r
44507 this.innerCt.update('');
\r
44508 this.afterRender();
\r
44514 * Gets a node in this tree by its id
\r
44515 * @param {String} id
\r
44518 getNodeById : function(id){
\r
44519 return this.nodeHash[id];
\r
44523 registerNode : function(node){
\r
44524 this.nodeHash[node.id] = node;
\r
44528 unregisterNode : function(node){
\r
44529 delete this.nodeHash[node.id];
\r
44533 toString : function(){
\r
44534 return "[Tree"+(this.id?" "+this.id:"")+"]";
\r
44538 restrictExpand : function(node){
\r
44539 var p = node.parentNode;
\r
44541 if(p.expandedChild && p.expandedChild.parentNode == p){
\r
44542 p.expandedChild.collapse();
\r
44544 p.expandedChild = node;
\r
44549 * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. "id")
\r
44550 * @param {String} attribute (optional) Defaults to null (return the actual nodes)
\r
44551 * @param {TreeNode} startNode (optional) The node to start from, defaults to the root
\r
44552 * @return {Array}
\r
44554 getChecked : function(a, startNode){
\r
44555 startNode = startNode || this.root;
\r
44557 var f = function(){
\r
44558 if(this.attributes.checked){
\r
44559 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
\r
44562 startNode.cascade(f);
\r
44567 * Returns the container element for this TreePanel.
\r
44568 * @return {Element} The container element for this TreePanel.
\r
44570 getEl : function(){
\r
44575 * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.
\r
44576 * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.
\r
44578 getLoader : function(){
\r
44579 return this.loader;
\r
44583 * Expand all nodes
\r
44585 expandAll : function(){
\r
44586 this.root.expand(true);
\r
44590 * Collapse all nodes
\r
44592 collapseAll : function(){
\r
44593 this.root.collapse(true);
\r
44597 * Returns the selection model used by this TreePanel.
\r
44598 * @return {TreeSelectionModel} The selection model used by this TreePanel
\r
44600 getSelectionModel : function(){
\r
44601 if(!this.selModel){
\r
44602 this.selModel = new Ext.tree.DefaultSelectionModel();
\r
44604 return this.selModel;
\r
44608 * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
\r
44609 * @param {String} path
\r
44610 * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
\r
44611 * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
\r
44612 * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
\r
44614 expandPath : function(path, attr, callback){
\r
44615 attr = attr || "id";
\r
44616 var keys = path.split(this.pathSeparator);
\r
44617 var curNode = this.root;
\r
44618 if(curNode.attributes[attr] != keys[1]){ // invalid root
\r
44620 callback(false, null);
\r
44625 var f = function(){
\r
44626 if(++index == keys.length){
\r
44628 callback(true, curNode);
\r
44632 var c = curNode.findChild(attr, keys[index]);
\r
44635 callback(false, curNode);
\r
44640 c.expand(false, false, f);
\r
44642 curNode.expand(false, false, f);
\r
44646 * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
\r
44647 * @param {String} path
\r
44648 * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
\r
44649 * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
\r
44650 * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
\r
44652 selectPath : function(path, attr, callback){
\r
44653 attr = attr || "id";
\r
44654 var keys = path.split(this.pathSeparator);
\r
44655 var v = keys.pop();
\r
44656 if(keys.length > 0){
\r
44657 var f = function(success, node){
\r
44658 if(success && node){
\r
44659 var n = node.findChild(attr, v);
\r
44663 callback(true, n);
\r
44665 }else if(callback){
\r
44666 callback(false, n);
\r
44670 callback(false, n);
\r
44674 this.expandPath(keys.join(this.pathSeparator), attr, f);
\r
44676 this.root.select();
\r
44678 callback(true, this.root);
\r
44684 * Returns the underlying Element for this tree
\r
44685 * @return {Ext.Element} The Element
\r
44687 getTreeEl : function(){
\r
44688 return this.body;
\r
44692 onRender : function(ct, position){
\r
44693 Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);
\r
44694 this.el.addClass('x-tree');
\r
44695 this.innerCt = this.body.createChild({tag:"ul",
\r
44696 cls:"x-tree-root-ct " +
\r
44697 (this.useArrows ? 'x-tree-arrows' : this.lines ? "x-tree-lines" : "x-tree-no-lines")});
\r
44701 initEvents : function(){
\r
44702 Ext.tree.TreePanel.superclass.initEvents.call(this);
\r
44704 if(this.containerScroll){
\r
44705 Ext.dd.ScrollManager.register(this.body);
\r
44707 if((this.enableDD || this.enableDrop) && !this.dropZone){
\r
44709 * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})
\r
44710 * @property dropZone
\r
44711 * @type Ext.tree.TreeDropZone
\r
44713 this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {
\r
44714 ddGroup: this.ddGroup || "TreeDD", appendOnly: this.ddAppendOnly === true
\r
44717 if((this.enableDD || this.enableDrag) && !this.dragZone){
\r
44719 * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})
\r
44720 * @property dragZone
\r
44721 * @type Ext.tree.TreeDragZone
\r
44723 this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {
\r
44724 ddGroup: this.ddGroup || "TreeDD",
\r
44725 scroll: this.ddScroll
\r
44728 this.getSelectionModel().init(this);
\r
44732 afterRender : function(){
\r
44733 Ext.tree.TreePanel.superclass.afterRender.call(this);
\r
44734 this.root.render();
\r
44735 if(!this.rootVisible){
\r
44736 this.root.renderChildren();
\r
44740 onDestroy : function(){
\r
44741 if(this.rendered){
\r
44742 this.body.removeAllListeners();
\r
44743 Ext.dd.ScrollManager.unregister(this.body);
\r
44744 if(this.dropZone){
\r
44745 this.dropZone.unreg();
\r
44747 if(this.dragZone){
\r
44748 this.dragZone.unreg();
\r
44751 this.root.destroy();
\r
44752 this.nodeHash = null;
\r
44753 Ext.tree.TreePanel.superclass.onDestroy.call(this);
\r
44757 * @cfg {String/Number} activeItem
\r
44761 * @cfg {Boolean} autoDestroy
\r
44765 * @cfg {Object/String/Function} autoLoad
\r
44769 * @cfg {Boolean} autoWidth
\r
44773 * @cfg {Boolean/Number} bufferResize
\r
44777 * @cfg {String} defaultType
\r
44781 * @cfg {Object} defaults
\r
44785 * @cfg {Boolean} hideBorders
\r
44789 * @cfg {Mixed} items
\r
44793 * @cfg {String} layout
\r
44797 * @cfg {Object} layoutConfig
\r
44801 * @cfg {Boolean} monitorResize
\r
44805 * @property items
\r
44809 * @method cascade
\r
44813 * @method doLayout
\r
44825 * @method findById
\r
44829 * @method findByType
\r
44833 * @method getComponent
\r
44837 * @method getLayout
\r
44841 * @method getUpdater
\r
44861 * @method removeAll
\r
44865 * @event afterLayout
\r
44869 * @event beforeadd
\r
44873 * @event beforeremove
\r
44884 * @cfg {String} allowDomMove @hide
\r
44887 * @cfg {String} autoEl @hide
\r
44890 * @cfg {String} applyTo @hide
\r
44893 * @cfg {String} contentEl @hide
\r
44896 * @cfg {String} disabledClass @hide
\r
44899 * @cfg {String} elements @hide
\r
44902 * @cfg {String} html @hide
\r
44905 * @cfg {Boolean} preventBodyReset
\r
44909 * @property disabled
\r
44913 * @method applyToMarkup
\r
44921 * @method disable
\r
44925 * @method setDisabled
\r
44930 Ext.tree.TreePanel.nodeTypes = {};
\r
44932 Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
\r
44933 this.tree = tree;
\r
44934 this.tree.on('render', this.initEvents, this);
\r
44937 Ext.tree.TreeEventModel.prototype = {
\r
44938 initEvents : function(){
\r
44939 var el = this.tree.getTreeEl();
\r
44940 el.on('click', this.delegateClick, this);
\r
44941 if(this.tree.trackMouseOver !== false){
\r
44942 this.tree.innerCt.on('mouseover', this.delegateOver, this);
\r
44943 this.tree.innerCt.on('mouseout', this.delegateOut, this);
\r
44945 el.on('dblclick', this.delegateDblClick, this);
\r
44946 el.on('contextmenu', this.delegateContextMenu, this);
\r
44949 getNode : function(e){
\r
44951 if(t = e.getTarget('.x-tree-node-el', 10)){
\r
44952 var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');
\r
44954 return this.tree.getNodeById(id);
\r
44960 getNodeTarget : function(e){
\r
44961 var t = e.getTarget('.x-tree-node-icon', 1);
\r
44963 t = e.getTarget('.x-tree-node-el', 6);
\r
44968 delegateOut : function(e, t){
\r
44969 if(!this.beforeEvent(e)){
\r
44972 if(e.getTarget('.x-tree-ec-icon', 1)){
\r
44973 var n = this.getNode(e);
\r
44974 this.onIconOut(e, n);
\r
44975 if(n == this.lastEcOver){
\r
44976 delete this.lastEcOver;
\r
44979 if((t = this.getNodeTarget(e)) && !e.within(t, true)){
\r
44980 this.onNodeOut(e, this.getNode(e));
\r
44984 delegateOver : function(e, t){
\r
44985 if(!this.beforeEvent(e)){
\r
44988 if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF
\r
44989 Ext.getBody().on('mouseover', this.trackExit, this);
\r
44990 this.trackingDoc = true;
\r
44992 if(this.lastEcOver){ // prevent hung highlight
\r
44993 this.onIconOut(e, this.lastEcOver);
\r
44994 delete this.lastEcOver;
\r
44996 if(e.getTarget('.x-tree-ec-icon', 1)){
\r
44997 this.lastEcOver = this.getNode(e);
\r
44998 this.onIconOver(e, this.lastEcOver);
\r
45000 if(t = this.getNodeTarget(e)){
\r
45001 this.onNodeOver(e, this.getNode(e));
\r
45005 trackExit : function(e){
\r
45006 if(this.lastOverNode && !e.within(this.lastOverNode.ui.getEl())){
\r
45007 this.onNodeOut(e, this.lastOverNode);
\r
45008 delete this.lastOverNode;
\r
45009 Ext.getBody().un('mouseover', this.trackExit, this);
\r
45010 this.trackingDoc = false;
\r
45014 delegateClick : function(e, t){
\r
45015 if(!this.beforeEvent(e)){
\r
45019 if(e.getTarget('input[type=checkbox]', 1)){
\r
45020 this.onCheckboxClick(e, this.getNode(e));
\r
45022 else if(e.getTarget('.x-tree-ec-icon', 1)){
\r
45023 this.onIconClick(e, this.getNode(e));
\r
45025 else if(this.getNodeTarget(e)){
\r
45026 this.onNodeClick(e, this.getNode(e));
\r
45030 delegateDblClick : function(e, t){
\r
45031 if(this.beforeEvent(e) && this.getNodeTarget(e)){
\r
45032 this.onNodeDblClick(e, this.getNode(e));
\r
45036 delegateContextMenu : function(e, t){
\r
45037 if(this.beforeEvent(e) && this.getNodeTarget(e)){
\r
45038 this.onNodeContextMenu(e, this.getNode(e));
\r
45042 onNodeClick : function(e, node){
\r
45043 node.ui.onClick(e);
\r
45046 onNodeOver : function(e, node){
\r
45047 this.lastOverNode = node;
\r
45048 node.ui.onOver(e);
\r
45051 onNodeOut : function(e, node){
\r
45052 node.ui.onOut(e);
\r
45055 onIconOver : function(e, node){
\r
45056 node.ui.addClass('x-tree-ec-over');
\r
45059 onIconOut : function(e, node){
\r
45060 node.ui.removeClass('x-tree-ec-over');
\r
45063 onIconClick : function(e, node){
\r
45064 node.ui.ecClick(e);
\r
45067 onCheckboxClick : function(e, node){
\r
45068 node.ui.onCheckChange(e);
\r
45071 onNodeDblClick : function(e, node){
\r
45072 node.ui.onDblClick(e);
\r
45075 onNodeContextMenu : function(e, node){
\r
45076 node.ui.onContextMenu(e);
\r
45079 beforeEvent : function(e){
\r
45080 if(this.disabled){
\r
45087 disable: function(){
\r
45088 this.disabled = true;
\r
45091 enable: function(){
\r
45092 this.disabled = false;
\r
45095 * @class Ext.tree.DefaultSelectionModel
\r
45096 * @extends Ext.util.Observable
\r
45097 * The default single selection for a TreePanel.
\r
45099 Ext.tree.DefaultSelectionModel = function(config){
\r
45100 this.selNode = null;
\r
45104 * @event selectionchange
\r
45105 * Fires when the selected node changes
\r
45106 * @param {DefaultSelectionModel} this
\r
45107 * @param {TreeNode} node the new selection
\r
45109 "selectionchange",
\r
45112 * @event beforeselect
\r
45113 * Fires before the selected node changes, return false to cancel the change
\r
45114 * @param {DefaultSelectionModel} this
\r
45115 * @param {TreeNode} node the new selection
\r
45116 * @param {TreeNode} node the old selection
\r
45121 Ext.apply(this, config);
\r
45122 Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
\r
45125 Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
\r
45126 init : function(tree){
\r
45127 this.tree = tree;
\r
45128 tree.getTreeEl().on("keydown", this.onKeyDown, this);
\r
45129 tree.on("click", this.onNodeClick, this);
\r
45132 onNodeClick : function(node, e){
\r
45133 this.select(node);
\r
45138 * @param {TreeNode} node The node to select
\r
45139 * @return {TreeNode} The selected node
\r
45141 select : function(node){
\r
45142 var last = this.selNode;
\r
45143 if(node == last){
\r
45144 node.ui.onSelectedChange(true);
\r
45145 }else if(this.fireEvent('beforeselect', this, node, last) !== false){
\r
45147 last.ui.onSelectedChange(false);
\r
45149 this.selNode = node;
\r
45150 node.ui.onSelectedChange(true);
\r
45151 this.fireEvent("selectionchange", this, node, last);
\r
45157 * Deselect a node.
\r
45158 * @param {TreeNode} node The node to unselect
\r
45160 unselect : function(node){
\r
45161 if(this.selNode == node){
\r
45162 this.clearSelections();
\r
45167 * Clear all selections
\r
45169 clearSelections : function(){
\r
45170 var n = this.selNode;
\r
45172 n.ui.onSelectedChange(false);
\r
45173 this.selNode = null;
\r
45174 this.fireEvent("selectionchange", this, null);
\r
45180 * Get the selected node
\r
45181 * @return {TreeNode} The selected node
\r
45183 getSelectedNode : function(){
\r
45184 return this.selNode;
\r
45188 * Returns true if the node is selected
\r
45189 * @param {TreeNode} node The node to check
\r
45190 * @return {Boolean}
\r
45192 isSelected : function(node){
\r
45193 return this.selNode == node;
\r
45197 * Selects the node above the selected node in the tree, intelligently walking the nodes
\r
45198 * @return TreeNode The new selection
\r
45200 selectPrevious : function(){
\r
45201 var s = this.selNode || this.lastSelNode;
\r
45205 var ps = s.previousSibling;
\r
45207 if(!ps.isExpanded() || ps.childNodes.length < 1){
\r
45208 return this.select(ps);
\r
45210 var lc = ps.lastChild;
\r
45211 while(lc && lc.isExpanded() && lc.childNodes.length > 0){
\r
45212 lc = lc.lastChild;
\r
45214 return this.select(lc);
\r
45216 } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
\r
45217 return this.select(s.parentNode);
\r
45223 * Selects the node above the selected node in the tree, intelligently walking the nodes
\r
45224 * @return TreeNode The new selection
\r
45226 selectNext : function(){
\r
45227 var s = this.selNode || this.lastSelNode;
\r
45231 if(s.firstChild && s.isExpanded()){
\r
45232 return this.select(s.firstChild);
\r
45233 }else if(s.nextSibling){
\r
45234 return this.select(s.nextSibling);
\r
45235 }else if(s.parentNode){
\r
45237 s.parentNode.bubble(function(){
\r
45238 if(this.nextSibling){
\r
45239 newS = this.getOwnerTree().selModel.select(this.nextSibling);
\r
45248 onKeyDown : function(e){
\r
45249 var s = this.selNode || this.lastSelNode;
\r
45250 // undesirable, but required
\r
45255 var k = e.getKey();
\r
45259 this.selectNext();
\r
45263 this.selectPrevious();
\r
45266 e.preventDefault();
\r
45267 if(s.hasChildNodes()){
\r
45268 if(!s.isExpanded()){
\r
45270 }else if(s.firstChild){
\r
45271 this.select(s.firstChild, e);
\r
45276 e.preventDefault();
\r
45277 if(s.hasChildNodes() && s.isExpanded()){
\r
45279 }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
\r
45280 this.select(s.parentNode, e);
\r
45288 * @class Ext.tree.MultiSelectionModel
\r
45289 * @extends Ext.util.Observable
\r
45290 * Multi selection for a TreePanel.
\r
45292 Ext.tree.MultiSelectionModel = function(config){
\r
45293 this.selNodes = [];
\r
45294 this.selMap = {};
\r
45297 * @event selectionchange
\r
45298 * Fires when the selected nodes change
\r
45299 * @param {MultiSelectionModel} this
\r
45300 * @param {Array} nodes Array of the selected nodes
\r
45302 "selectionchange"
\r
45304 Ext.apply(this, config);
\r
45305 Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
\r
45308 Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
\r
45309 init : function(tree){
\r
45310 this.tree = tree;
\r
45311 tree.getTreeEl().on("keydown", this.onKeyDown, this);
\r
45312 tree.on("click", this.onNodeClick, this);
\r
45315 onNodeClick : function(node, e){
\r
45316 if(e.ctrlKey && this.isSelected(node)){
\r
45317 this.unselect(node);
\r
45319 this.select(node, e, e.ctrlKey);
\r
45325 * @param {TreeNode} node The node to select
\r
45326 * @param {EventObject} e (optional) An event associated with the selection
\r
45327 * @param {Boolean} keepExisting True to retain existing selections
\r
45328 * @return {TreeNode} The selected node
\r
45330 select : function(node, e, keepExisting){
\r
45331 if(keepExisting !== true){
\r
45332 this.clearSelections(true);
\r
45334 if(this.isSelected(node)){
\r
45335 this.lastSelNode = node;
\r
45338 this.selNodes.push(node);
\r
45339 this.selMap[node.id] = node;
\r
45340 this.lastSelNode = node;
\r
45341 node.ui.onSelectedChange(true);
\r
45342 this.fireEvent("selectionchange", this, this.selNodes);
\r
45347 * Deselect a node.
\r
45348 * @param {TreeNode} node The node to unselect
\r
45350 unselect : function(node){
\r
45351 if(this.selMap[node.id]){
\r
45352 node.ui.onSelectedChange(false);
\r
45353 var sn = this.selNodes;
\r
45354 var index = sn.indexOf(node);
\r
45356 this.selNodes.splice(index, 1);
\r
45358 delete this.selMap[node.id];
\r
45359 this.fireEvent("selectionchange", this, this.selNodes);
\r
45364 * Clear all selections
\r
45366 clearSelections : function(suppressEvent){
\r
45367 var sn = this.selNodes;
\r
45368 if(sn.length > 0){
\r
45369 for(var i = 0, len = sn.length; i < len; i++){
\r
45370 sn[i].ui.onSelectedChange(false);
\r
45372 this.selNodes = [];
\r
45373 this.selMap = {};
\r
45374 if(suppressEvent !== true){
\r
45375 this.fireEvent("selectionchange", this, this.selNodes);
\r
45381 * Returns true if the node is selected
\r
45382 * @param {TreeNode} node The node to check
\r
45383 * @return {Boolean}
\r
45385 isSelected : function(node){
\r
45386 return this.selMap[node.id] ? true : false;
\r
45390 * Returns an array of the selected nodes
\r
45391 * @return {Array}
\r
45393 getSelectedNodes : function(){
\r
45394 return this.selNodes;
\r
45397 onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
\r
45399 selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
\r
45401 selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
\r
45403 * @class Ext.data.Tree
\r
45404 * @extends Ext.util.Observable
\r
45405 * Represents a tree data structure and bubbles all the events for its nodes. The nodes
\r
45406 * in the tree have most standard DOM functionality.
\r
45408 * @param {Node} root (optional) The root node
\r
45410 Ext.data.Tree = function(root){
\r
45411 this.nodeHash = {};
\r
45413 * The root node for this tree
\r
45416 this.root = null;
\r
45418 this.setRootNode(root);
\r
45423 * Fires when a new child node is appended to a node in this tree.
\r
45424 * @param {Tree} tree The owner tree
\r
45425 * @param {Node} parent The parent node
\r
45426 * @param {Node} node The newly appended node
\r
45427 * @param {Number} index The index of the newly appended node
\r
45432 * Fires when a child node is removed from a node in this tree.
\r
45433 * @param {Tree} tree The owner tree
\r
45434 * @param {Node} parent The parent node
\r
45435 * @param {Node} node The child node removed
\r
45440 * Fires when a node is moved to a new location in the tree
\r
45441 * @param {Tree} tree The owner tree
\r
45442 * @param {Node} node The node moved
\r
45443 * @param {Node} oldParent The old parent of this node
\r
45444 * @param {Node} newParent The new parent of this node
\r
45445 * @param {Number} index The index it was moved to
\r
45450 * Fires when a new child node is inserted in a node in this tree.
\r
45451 * @param {Tree} tree The owner tree
\r
45452 * @param {Node} parent The parent node
\r
45453 * @param {Node} node The child node inserted
\r
45454 * @param {Node} refNode The child node the node was inserted before
\r
45458 * @event beforeappend
\r
45459 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
\r
45460 * @param {Tree} tree The owner tree
\r
45461 * @param {Node} parent The parent node
\r
45462 * @param {Node} node The child node to be appended
\r
45466 * @event beforeremove
\r
45467 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
\r
45468 * @param {Tree} tree The owner tree
\r
45469 * @param {Node} parent The parent node
\r
45470 * @param {Node} node The child node to be removed
\r
45474 * @event beforemove
\r
45475 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
\r
45476 * @param {Tree} tree The owner tree
\r
45477 * @param {Node} node The node being moved
\r
45478 * @param {Node} oldParent The parent of the node
\r
45479 * @param {Node} newParent The new parent the node is moving to
\r
45480 * @param {Number} index The index it is being moved to
\r
45484 * @event beforeinsert
\r
45485 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
\r
45486 * @param {Tree} tree The owner tree
\r
45487 * @param {Node} parent The parent node
\r
45488 * @param {Node} node The child node to be inserted
\r
45489 * @param {Node} refNode The child node the node is being inserted before
\r
45494 Ext.data.Tree.superclass.constructor.call(this);
\r
45497 Ext.extend(Ext.data.Tree, Ext.util.Observable, {
\r
45499 * @cfg {String} pathSeparator
\r
45500 * The token used to separate paths in node ids (defaults to '/').
\r
45502 pathSeparator: "/",
\r
45505 proxyNodeEvent : function(){
\r
45506 return this.fireEvent.apply(this, arguments);
\r
45510 * Returns the root node for this tree.
\r
45513 getRootNode : function(){
\r
45514 return this.root;
\r
45518 * Sets the root node for this tree.
\r
45519 * @param {Node} node
\r
45522 setRootNode : function(node){
\r
45523 this.root = node;
\r
45524 node.ownerTree = this;
\r
45525 node.isRoot = true;
\r
45526 this.registerNode(node);
\r
45531 * Gets a node in this tree by its id.
\r
45532 * @param {String} id
\r
45535 getNodeById : function(id){
\r
45536 return this.nodeHash[id];
\r
45540 registerNode : function(node){
\r
45541 this.nodeHash[node.id] = node;
\r
45545 unregisterNode : function(node){
\r
45546 delete this.nodeHash[node.id];
\r
45549 toString : function(){
\r
45550 return "[Tree"+(this.id?" "+this.id:"")+"]";
\r
45555 * @class Ext.data.Node
\r
45556 * @extends Ext.util.Observable
\r
45557 * @cfg {Boolean} leaf true if this node is a leaf and does not have children
\r
45558 * @cfg {String} id The id for this node. If one is not specified, one is generated.
\r
45560 * @param {Object} attributes The attributes/config for the node
\r
45562 Ext.data.Node = function(attributes){
\r
45564 * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
\r
45567 this.attributes = attributes || {};
\r
45568 this.leaf = this.attributes.leaf;
\r
45570 * The node id. @type String
\r
45572 this.id = this.attributes.id;
\r
45574 this.id = Ext.id(null, "xnode-");
\r
45575 this.attributes.id = this.id;
\r
45578 * All child nodes of this node. @type Array
\r
45580 this.childNodes = [];
\r
45581 if(!this.childNodes.indexOf){ // indexOf is a must
\r
45582 this.childNodes.indexOf = function(o){
\r
45583 for(var i = 0, len = this.length; i < len; i++){
\r
45584 if(this[i] == o){
\r
45592 * The parent node for this node. @type Node
\r
45594 this.parentNode = null;
\r
45596 * The first direct child node of this node, or null if this node has no child nodes. @type Node
\r
45598 this.firstChild = null;
\r
45600 * The last direct child node of this node, or null if this node has no child nodes. @type Node
\r
45602 this.lastChild = null;
\r
45604 * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
\r
45606 this.previousSibling = null;
\r
45608 * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
\r
45610 this.nextSibling = null;
\r
45615 * Fires when a new child node is appended
\r
45616 * @param {Tree} tree The owner tree
\r
45617 * @param {Node} this This node
\r
45618 * @param {Node} node The newly appended node
\r
45619 * @param {Number} index The index of the newly appended node
\r
45624 * Fires when a child node is removed
\r
45625 * @param {Tree} tree The owner tree
\r
45626 * @param {Node} this This node
\r
45627 * @param {Node} node The removed node
\r
45632 * Fires when this node is moved to a new location in the tree
\r
45633 * @param {Tree} tree The owner tree
\r
45634 * @param {Node} this This node
\r
45635 * @param {Node} oldParent The old parent of this node
\r
45636 * @param {Node} newParent The new parent of this node
\r
45637 * @param {Number} index The index it was moved to
\r
45642 * Fires when a new child node is inserted.
\r
45643 * @param {Tree} tree The owner tree
\r
45644 * @param {Node} this This node
\r
45645 * @param {Node} node The child node inserted
\r
45646 * @param {Node} refNode The child node the node was inserted before
\r
45650 * @event beforeappend
\r
45651 * Fires before a new child is appended, return false to cancel the append.
\r
45652 * @param {Tree} tree The owner tree
\r
45653 * @param {Node} this This node
\r
45654 * @param {Node} node The child node to be appended
\r
45656 "beforeappend" : true,
\r
45658 * @event beforeremove
\r
45659 * Fires before a child is removed, return false to cancel the remove.
\r
45660 * @param {Tree} tree The owner tree
\r
45661 * @param {Node} this This node
\r
45662 * @param {Node} node The child node to be removed
\r
45664 "beforeremove" : true,
\r
45666 * @event beforemove
\r
45667 * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
\r
45668 * @param {Tree} tree The owner tree
\r
45669 * @param {Node} this This node
\r
45670 * @param {Node} oldParent The parent of this node
\r
45671 * @param {Node} newParent The new parent this node is moving to
\r
45672 * @param {Number} index The index it is being moved to
\r
45674 "beforemove" : true,
\r
45676 * @event beforeinsert
\r
45677 * Fires before a new child is inserted, return false to cancel the insert.
\r
45678 * @param {Tree} tree The owner tree
\r
45679 * @param {Node} this This node
\r
45680 * @param {Node} node The child node to be inserted
\r
45681 * @param {Node} refNode The child node the node is being inserted before
\r
45683 "beforeinsert" : true
\r
45685 this.listeners = this.attributes.listeners;
\r
45686 Ext.data.Node.superclass.constructor.call(this);
\r
45689 Ext.extend(Ext.data.Node, Ext.util.Observable, {
\r
45691 fireEvent : function(evtName){
\r
45692 // first do standard event for this node
\r
45693 if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
\r
45696 // then bubble it up to the tree if the event wasn't cancelled
\r
45697 var ot = this.getOwnerTree();
\r
45699 if(ot.proxyNodeEvent.apply(ot, arguments) === false){
\r
45707 * Returns true if this node is a leaf
\r
45708 * @return {Boolean}
\r
45710 isLeaf : function(){
\r
45711 return this.leaf === true;
\r
45715 setFirstChild : function(node){
\r
45716 this.firstChild = node;
\r
45720 setLastChild : function(node){
\r
45721 this.lastChild = node;
\r
45726 * Returns true if this node is the last child of its parent
\r
45727 * @return {Boolean}
\r
45729 isLast : function(){
\r
45730 return (!this.parentNode ? true : this.parentNode.lastChild == this);
\r
45734 * Returns true if this node is the first child of its parent
\r
45735 * @return {Boolean}
\r
45737 isFirst : function(){
\r
45738 return (!this.parentNode ? true : this.parentNode.firstChild == this);
\r
45742 * Returns true if this node has one or more child nodes, else false.
\r
45743 * @return {Boolean}
\r
45745 hasChildNodes : function(){
\r
45746 return !this.isLeaf() && this.childNodes.length > 0;
\r
45750 * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
\r
45751 * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
\r
45752 * @return {Boolean}
\r
45754 isExpandable : function(){
\r
45755 return this.attributes.expandable || this.hasChildNodes();
\r
45759 * Insert node(s) as the last child node of this node.
\r
45760 * @param {Node/Array} node The node or Array of nodes to append
\r
45761 * @return {Node} The appended node if single append, or null if an array was passed
\r
45763 appendChild : function(node){
\r
45764 var multi = false;
\r
45765 if(Ext.isArray(node)){
\r
45767 }else if(arguments.length > 1){
\r
45768 multi = arguments;
\r
45770 // if passed an array or multiple args do them one by one
\r
45772 for(var i = 0, len = multi.length; i < len; i++) {
\r
45773 this.appendChild(multi[i]);
\r
45776 if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
\r
45779 var index = this.childNodes.length;
\r
45780 var oldParent = node.parentNode;
\r
45781 // it's a move, make sure we move it cleanly
\r
45783 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
\r
45786 oldParent.removeChild(node);
\r
45788 index = this.childNodes.length;
\r
45790 this.setFirstChild(node);
\r
45792 this.childNodes.push(node);
\r
45793 node.parentNode = this;
\r
45794 var ps = this.childNodes[index-1];
\r
45796 node.previousSibling = ps;
\r
45797 ps.nextSibling = node;
\r
45799 node.previousSibling = null;
\r
45801 node.nextSibling = null;
\r
45802 this.setLastChild(node);
\r
45803 node.setOwnerTree(this.getOwnerTree());
\r
45804 this.fireEvent("append", this.ownerTree, this, node, index);
\r
45806 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
\r
45813 * Removes a child node from this node.
\r
45814 * @param {Node} node The node to remove
\r
45815 * @return {Node} The removed node
\r
45817 removeChild : function(node){
\r
45818 var index = this.childNodes.indexOf(node);
\r
45822 if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
\r
45826 // remove it from childNodes collection
\r
45827 this.childNodes.splice(index, 1);
\r
45829 // update siblings
\r
45830 if(node.previousSibling){
\r
45831 node.previousSibling.nextSibling = node.nextSibling;
\r
45833 if(node.nextSibling){
\r
45834 node.nextSibling.previousSibling = node.previousSibling;
\r
45837 // update child refs
\r
45838 if(this.firstChild == node){
\r
45839 this.setFirstChild(node.nextSibling);
\r
45841 if(this.lastChild == node){
\r
45842 this.setLastChild(node.previousSibling);
\r
45845 node.setOwnerTree(null);
\r
45846 // clear any references from the node
\r
45847 node.parentNode = null;
\r
45848 node.previousSibling = null;
\r
45849 node.nextSibling = null;
\r
45850 this.fireEvent("remove", this.ownerTree, this, node);
\r
45855 * Inserts the first node before the second node in this nodes childNodes collection.
\r
45856 * @param {Node} node The node to insert
\r
45857 * @param {Node} refNode The node to insert before (if null the node is appended)
\r
45858 * @return {Node} The inserted node
\r
45860 insertBefore : function(node, refNode){
\r
45861 if(!refNode){ // like standard Dom, refNode can be null for append
\r
45862 return this.appendChild(node);
\r
45865 if(node == refNode){
\r
45869 if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
\r
45872 var index = this.childNodes.indexOf(refNode);
\r
45873 var oldParent = node.parentNode;
\r
45874 var refIndex = index;
\r
45876 // when moving internally, indexes will change after remove
\r
45877 if(oldParent == this && this.childNodes.indexOf(node) < index){
\r
45881 // it's a move, make sure we move it cleanly
\r
45883 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
\r
45886 oldParent.removeChild(node);
\r
45888 if(refIndex === 0){
\r
45889 this.setFirstChild(node);
\r
45891 this.childNodes.splice(refIndex, 0, node);
\r
45892 node.parentNode = this;
\r
45893 var ps = this.childNodes[refIndex-1];
\r
45895 node.previousSibling = ps;
\r
45896 ps.nextSibling = node;
\r
45898 node.previousSibling = null;
\r
45900 node.nextSibling = refNode;
\r
45901 refNode.previousSibling = node;
\r
45902 node.setOwnerTree(this.getOwnerTree());
\r
45903 this.fireEvent("insert", this.ownerTree, this, node, refNode);
\r
45905 node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
\r
45911 * Removes this node from its parent
\r
45912 * @return {Node} this
\r
45914 remove : function(){
\r
45915 this.parentNode.removeChild(this);
\r
45920 * Returns the child node at the specified index.
\r
45921 * @param {Number} index
\r
45924 item : function(index){
\r
45925 return this.childNodes[index];
\r
45929 * Replaces one child node in this node with another.
\r
45930 * @param {Node} newChild The replacement node
\r
45931 * @param {Node} oldChild The node to replace
\r
45932 * @return {Node} The replaced node
\r
45934 replaceChild : function(newChild, oldChild){
\r
45935 var s = oldChild ? oldChild.nextSibling : null;
\r
45936 this.removeChild(oldChild);
\r
45937 this.insertBefore(newChild, s);
\r
45942 * Returns the index of a child node
\r
45943 * @param {Node} node
\r
45944 * @return {Number} The index of the node or -1 if it was not found
\r
45946 indexOf : function(child){
\r
45947 return this.childNodes.indexOf(child);
\r
45951 * Returns the tree this node is in.
\r
45954 getOwnerTree : function(){
\r
45955 // if it doesn't have one, look for one
\r
45956 if(!this.ownerTree){
\r
45960 this.ownerTree = p.ownerTree;
\r
45963 p = p.parentNode;
\r
45966 return this.ownerTree;
\r
45970 * Returns depth of this node (the root node has a depth of 0)
\r
45971 * @return {Number}
\r
45973 getDepth : function(){
\r
45976 while(p.parentNode){
\r
45978 p = p.parentNode;
\r
45984 setOwnerTree : function(tree){
\r
45985 // if it is a move, we need to update everyone
\r
45986 if(tree != this.ownerTree){
\r
45987 if(this.ownerTree){
\r
45988 this.ownerTree.unregisterNode(this);
\r
45990 this.ownerTree = tree;
\r
45991 var cs = this.childNodes;
\r
45992 for(var i = 0, len = cs.length; i < len; i++) {
\r
45993 cs[i].setOwnerTree(tree);
\r
45996 tree.registerNode(this);
\r
46002 * Changes the id of this node.
\r
46003 * @param {String} id The new id for the node.
\r
46005 setId: function(id){
\r
46006 if(id !== this.id){
\r
46007 var t = this.ownerTree;
\r
46009 t.unregisterNode(this);
\r
46013 t.registerNode(this);
\r
46015 this.onIdChange(id);
\r
46020 onIdChange: Ext.emptyFn,
\r
46023 * Returns the path for this node. The path can be used to expand or select this node programmatically.
\r
46024 * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
\r
46025 * @return {String} The path
\r
46027 getPath : function(attr){
\r
46028 attr = attr || "id";
\r
46029 var p = this.parentNode;
\r
46030 var b = [this.attributes[attr]];
\r
46032 b.unshift(p.attributes[attr]);
\r
46033 p = p.parentNode;
\r
46035 var sep = this.getOwnerTree().pathSeparator;
\r
46036 return sep + b.join(sep);
\r
46040 * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
46041 * function call will be the scope provided or the current node. The arguments to the function
\r
46042 * will be the args provided or the current node. If the function returns false at any point,
\r
46043 * the bubble is stopped.
\r
46044 * @param {Function} fn The function to call
\r
46045 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
46046 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
46048 bubble : function(fn, scope, args){
\r
46051 if(fn.apply(scope || p, args || [p]) === false){
\r
46054 p = p.parentNode;
\r
46059 * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
46060 * function call will be the scope provided or the current node. The arguments to the function
\r
46061 * will be the args provided or the current node. If the function returns false at any point,
\r
46062 * the cascade is stopped on that branch.
\r
46063 * @param {Function} fn The function to call
\r
46064 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
46065 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
46067 cascade : function(fn, scope, args){
\r
46068 if(fn.apply(scope || this, args || [this]) !== false){
\r
46069 var cs = this.childNodes;
\r
46070 for(var i = 0, len = cs.length; i < len; i++) {
\r
46071 cs[i].cascade(fn, scope, args);
\r
46077 * Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
46078 * function call will be the scope provided or the current node. The arguments to the function
\r
46079 * will be the args provided or the current node. If the function returns false at any point,
\r
46080 * the iteration stops.
\r
46081 * @param {Function} fn The function to call
\r
46082 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
46083 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
46085 eachChild : function(fn, scope, args){
\r
46086 var cs = this.childNodes;
\r
46087 for(var i = 0, len = cs.length; i < len; i++) {
\r
46088 if(fn.apply(scope || this, args || [cs[i]]) === false){
\r
46095 * Finds the first child that has the attribute with the specified value.
\r
46096 * @param {String} attribute The attribute name
\r
46097 * @param {Mixed} value The value to search for
\r
46098 * @return {Node} The found child or null if none was found
\r
46100 findChild : function(attribute, value){
\r
46101 var cs = this.childNodes;
\r
46102 for(var i = 0, len = cs.length; i < len; i++) {
\r
46103 if(cs[i].attributes[attribute] == value){
\r
46111 * Finds the first child by a custom function. The child matches if the function passed
\r
46113 * @param {Function} fn
\r
46114 * @param {Object} scope (optional)
\r
46115 * @return {Node} The found child or null if none was found
\r
46117 findChildBy : function(fn, scope){
\r
46118 var cs = this.childNodes;
\r
46119 for(var i = 0, len = cs.length; i < len; i++) {
\r
46120 if(fn.call(scope||cs[i], cs[i]) === true){
\r
46128 * Sorts this nodes children using the supplied sort function
\r
46129 * @param {Function} fn
\r
46130 * @param {Object} scope (optional)
\r
46132 sort : function(fn, scope){
\r
46133 var cs = this.childNodes;
\r
46134 var len = cs.length;
\r
46136 var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
\r
46138 for(var i = 0; i < len; i++){
\r
46140 n.previousSibling = cs[i-1];
\r
46141 n.nextSibling = cs[i+1];
\r
46143 this.setFirstChild(n);
\r
46146 this.setLastChild(n);
\r
46153 * Returns true if this node is an ancestor (at any point) of the passed node.
\r
46154 * @param {Node} node
\r
46155 * @return {Boolean}
\r
46157 contains : function(node){
\r
46158 return node.isAncestor(this);
\r
46162 * Returns true if the passed node is an ancestor (at any point) of this node.
\r
46163 * @param {Node} node
\r
46164 * @return {Boolean}
\r
46166 isAncestor : function(node){
\r
46167 var p = this.parentNode;
\r
46172 p = p.parentNode;
\r
46177 toString : function(){
\r
46178 return "[Node"+(this.id?" "+this.id:"")+"]";
\r
46181 * @class Ext.tree.TreeNode
\r
46182 * @extends Ext.data.Node
\r
46183 * @cfg {String} text The text for this node
\r
46184 * @cfg {Boolean} expanded true to start the node expanded
\r
46185 * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)
\r
46186 * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)
\r
46187 * @cfg {Boolean} disabled true to start the node disabled
\r
46188 * @cfg {String} icon The path to an icon for the node. The preferred way to do this
\r
46189 * is to use the cls or iconCls attributes and add the icon via a CSS background image.
\r
46190 * @cfg {String} cls A css class to be added to the node
\r
46191 * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
\r
46192 * @cfg {String} href URL of the link used for the node (defaults to #)
\r
46193 * @cfg {String} hrefTarget target frame for the link
\r
46194 * @cfg {Boolean} hidden True to render hidden. (Defaults to false).
\r
46195 * @cfg {String} qtip An Ext QuickTip for the node
\r
46196 * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty
\r
46197 * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
\r
46198 * @cfg {Boolean} singleClickExpand True for single click expand on this node
\r
46199 * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Ext.tree.TreeNodeUI)
\r
46200 * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
\r
46201 * (defaults to undefined with no checkbox rendered)
\r
46202 * @cfg {Boolean} draggable True to make this node draggable (defaults to false)
\r
46203 * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)
\r
46204 * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)
\r
46205 * @cfg {Boolean} editable False to not allow this node to be edited by an (@link Ext.tree.TreeEditor} (defaults to true)
\r
46207 * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
\r
46209 Ext.tree.TreeNode = function(attributes){
\r
46210 attributes = attributes || {};
\r
46211 if(typeof attributes == "string"){
\r
46212 attributes = {text: attributes};
\r
46214 this.childrenRendered = false;
\r
46215 this.rendered = false;
\r
46216 Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
\r
46217 this.expanded = attributes.expanded === true;
\r
46218 this.isTarget = attributes.isTarget !== false;
\r
46219 this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
\r
46220 this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
\r
46223 * Read-only. The text for this node. To change it use setText().
\r
46226 this.text = attributes.text;
\r
46228 * True if this node is disabled.
\r
46231 this.disabled = attributes.disabled === true;
\r
46233 * True if this node is hidden.
\r
46236 this.hidden = attributes.hidden === true;
\r
46240 * @event textchange
\r
46241 * Fires when the text for this node is changed
\r
46242 * @param {Node} this This node
\r
46243 * @param {String} text The new text
\r
46244 * @param {String} oldText The old text
\r
46248 * @event beforeexpand
\r
46249 * Fires before this node is expanded, return false to cancel.
\r
46250 * @param {Node} this This node
\r
46251 * @param {Boolean} deep
\r
46252 * @param {Boolean} anim
\r
46256 * @event beforecollapse
\r
46257 * Fires before this node is collapsed, return false to cancel.
\r
46258 * @param {Node} this This node
\r
46259 * @param {Boolean} deep
\r
46260 * @param {Boolean} anim
\r
46262 "beforecollapse",
\r
46265 * Fires when this node is expanded
\r
46266 * @param {Node} this This node
\r
46270 * @event disabledchange
\r
46271 * Fires when the disabled status of this node changes
\r
46272 * @param {Node} this This node
\r
46273 * @param {Boolean} disabled
\r
46275 "disabledchange",
\r
46277 * @event collapse
\r
46278 * Fires when this node is collapsed
\r
46279 * @param {Node} this This node
\r
46283 * @event beforeclick
\r
46284 * Fires before click processing. Return false to cancel the default action.
\r
46285 * @param {Node} this This node
\r
46286 * @param {Ext.EventObject} e The event object
\r
46291 * Fires when this node is clicked
\r
46292 * @param {Node} this This node
\r
46293 * @param {Ext.EventObject} e The event object
\r
46297 * @event checkchange
\r
46298 * Fires when a node with a checkbox's checked property changes
\r
46299 * @param {Node} this This node
\r
46300 * @param {Boolean} checked
\r
46304 * @event dblclick
\r
46305 * Fires when this node is double clicked
\r
46306 * @param {Node} this This node
\r
46307 * @param {Ext.EventObject} e The event object
\r
46311 * @event contextmenu
\r
46312 * Fires when this node is right clicked
\r
46313 * @param {Node} this This node
\r
46314 * @param {Ext.EventObject} e The event object
\r
46318 * @event beforechildrenrendered
\r
46319 * Fires right before the child nodes for this node are rendered
\r
46320 * @param {Node} this This node
\r
46322 "beforechildrenrendered"
\r
46325 var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
\r
46328 * Read-only. The UI for this node
\r
46329 * @type TreeNodeUI
\r
46331 this.ui = new uiClass(this);
\r
46333 Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
\r
46334 preventHScroll: true,
\r
46336 * Returns true if this node is expanded
\r
46337 * @return {Boolean}
\r
46339 isExpanded : function(){
\r
46340 return this.expanded;
\r
46344 * Returns the UI object for this node.
\r
46345 * @return {TreeNodeUI} The object which is providing the user interface for this tree
\r
46346 * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance
\r
46347 * of {@link Ext.tree.TreeNodeUI}
\r
46349 getUI : function(){
\r
46353 getLoader : function(){
\r
46355 return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : new Ext.tree.TreeLoader());
\r
46358 // private override
\r
46359 setFirstChild : function(node){
\r
46360 var of = this.firstChild;
\r
46361 Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
\r
46362 if(this.childrenRendered && of && node != of){
\r
46363 of.renderIndent(true, true);
\r
46365 if(this.rendered){
\r
46366 this.renderIndent(true, true);
\r
46370 // private override
\r
46371 setLastChild : function(node){
\r
46372 var ol = this.lastChild;
\r
46373 Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
\r
46374 if(this.childrenRendered && ol && node != ol){
\r
46375 ol.renderIndent(true, true);
\r
46377 if(this.rendered){
\r
46378 this.renderIndent(true, true);
\r
46382 // these methods are overridden to provide lazy rendering support
\r
46383 // private override
\r
46384 appendChild : function(n){
\r
46385 if(!n.render && !Ext.isArray(n)){
\r
46386 n = this.getLoader().createNode(n);
\r
46388 var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
\r
46389 if(node && this.childrenRendered){
\r
46392 this.ui.updateExpandIcon();
\r
46396 // private override
\r
46397 removeChild : function(node){
\r
46398 this.ownerTree.getSelectionModel().unselect(node);
\r
46399 Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
\r
46400 // if it's been rendered remove dom node
\r
46401 if(this.childrenRendered){
\r
46402 node.ui.remove();
\r
46404 if(this.childNodes.length < 1){
\r
46405 this.collapse(false, false);
\r
46407 this.ui.updateExpandIcon();
\r
46409 if(!this.firstChild && !this.isHiddenRoot()) {
\r
46410 this.childrenRendered = false;
\r
46415 // private override
\r
46416 insertBefore : function(node, refNode){
\r
46417 if(!node.render){
\r
46418 node = this.getLoader().createNode(node);
\r
46420 var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
\r
46421 if(newNode && refNode && this.childrenRendered){
\r
46424 this.ui.updateExpandIcon();
\r
46429 * Sets the text for this node
\r
46430 * @param {String} text
\r
46432 setText : function(text){
\r
46433 var oldText = this.text;
\r
46434 this.text = text;
\r
46435 this.attributes.text = text;
\r
46436 if(this.rendered){ // event without subscribing
\r
46437 this.ui.onTextChange(this, text, oldText);
\r
46439 this.fireEvent("textchange", this, text, oldText);
\r
46443 * Triggers selection of this node
\r
46445 select : function(){
\r
46446 this.getOwnerTree().getSelectionModel().select(this);
\r
46450 * Triggers deselection of this node
\r
46452 unselect : function(){
\r
46453 this.getOwnerTree().getSelectionModel().unselect(this);
\r
46457 * Returns true if this node is selected
\r
46458 * @return {Boolean}
\r
46460 isSelected : function(){
\r
46461 return this.getOwnerTree().getSelectionModel().isSelected(this);
\r
46465 * Expand this node.
\r
46466 * @param {Boolean} deep (optional) True to expand all children as well
\r
46467 * @param {Boolean} anim (optional) false to cancel the default animation
\r
46468 * @param {Function} callback (optional) A callback to be called when
\r
46469 * expanding this node completes (does not wait for deep expand to complete).
\r
46470 * Called with 1 parameter, this node.
\r
46471 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
46473 expand : function(deep, anim, callback, scope){
\r
46474 if(!this.expanded){
\r
46475 if(this.fireEvent("beforeexpand", this, deep, anim) === false){
\r
46478 if(!this.childrenRendered){
\r
46479 this.renderChildren();
\r
46481 this.expanded = true;
\r
46482 if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
\r
46483 this.ui.animExpand(function(){
\r
46484 this.fireEvent("expand", this);
\r
46485 this.runCallback(callback, scope || this, [this]);
\r
46486 if(deep === true){
\r
46487 this.expandChildNodes(true);
\r
46489 }.createDelegate(this));
\r
46492 this.ui.expand();
\r
46493 this.fireEvent("expand", this);
\r
46494 this.runCallback(callback, scope || this, [this]);
\r
46497 this.runCallback(callback, scope || this, [this]);
\r
46499 if(deep === true){
\r
46500 this.expandChildNodes(true);
\r
46504 runCallback: function(cb, scope, args){
\r
46505 if(Ext.isFunction(cb)){
\r
46506 cb.apply(scope, args);
\r
46510 isHiddenRoot : function(){
\r
46511 return this.isRoot && !this.getOwnerTree().rootVisible;
\r
46515 * Collapse this node.
\r
46516 * @param {Boolean} deep (optional) True to collapse all children as well
\r
46517 * @param {Boolean} anim (optional) false to cancel the default animation
\r
46518 * @param {Function} callback (optional) A callback to be called when
\r
46519 * expanding this node completes (does not wait for deep expand to complete).
\r
46520 * Called with 1 parameter, this node.
\r
46521 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
46523 collapse : function(deep, anim, callback, scope){
\r
46524 if(this.expanded && !this.isHiddenRoot()){
\r
46525 if(this.fireEvent("beforecollapse", this, deep, anim) === false){
\r
46528 this.expanded = false;
\r
46529 if((this.getOwnerTree().animate && anim !== false) || anim){
\r
46530 this.ui.animCollapse(function(){
\r
46531 this.fireEvent("collapse", this);
\r
46532 this.runCallback(callback, scope || this, [this]);
\r
46533 if(deep === true){
\r
46534 this.collapseChildNodes(true);
\r
46536 }.createDelegate(this));
\r
46539 this.ui.collapse();
\r
46540 this.fireEvent("collapse", this);
\r
46541 this.runCallback(callback, scope || this, [this]);
\r
46543 }else if(!this.expanded){
\r
46544 this.runCallback(callback, scope || this, [this]);
\r
46546 if(deep === true){
\r
46547 var cs = this.childNodes;
\r
46548 for(var i = 0, len = cs.length; i < len; i++) {
\r
46549 cs[i].collapse(true, false);
\r
46555 delayedExpand : function(delay){
\r
46556 if(!this.expandProcId){
\r
46557 this.expandProcId = this.expand.defer(delay, this);
\r
46562 cancelExpand : function(){
\r
46563 if(this.expandProcId){
\r
46564 clearTimeout(this.expandProcId);
\r
46566 this.expandProcId = false;
\r
46570 * Toggles expanded/collapsed state of the node
\r
46572 toggle : function(){
\r
46573 if(this.expanded){
\r
46581 * Ensures all parent nodes are expanded, and if necessary, scrolls
\r
46582 * the node into view.
\r
46583 * @param {Function} callback (optional) A function to call when the node has been made visible.
\r
46584 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
46586 ensureVisible : function(callback, scope){
\r
46587 var tree = this.getOwnerTree();
\r
46588 tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
\r
46589 var node = tree.getNodeById(this.id); // Somehow if we don't do this, we lose changes that happened to node in the meantime
\r
46590 tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
\r
46591 this.runCallback(callback, scope || this, [this]);
\r
46592 }.createDelegate(this));
\r
46596 * Expand all child nodes
\r
46597 * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
\r
46599 expandChildNodes : function(deep){
\r
46600 var cs = this.childNodes;
\r
46601 for(var i = 0, len = cs.length; i < len; i++) {
\r
46602 cs[i].expand(deep);
\r
46607 * Collapse all child nodes
\r
46608 * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
\r
46610 collapseChildNodes : function(deep){
\r
46611 var cs = this.childNodes;
\r
46612 for(var i = 0, len = cs.length; i < len; i++) {
\r
46613 cs[i].collapse(deep);
\r
46618 * Disables this node
\r
46620 disable : function(){
\r
46621 this.disabled = true;
\r
46623 if(this.rendered && this.ui.onDisableChange){ // event without subscribing
\r
46624 this.ui.onDisableChange(this, true);
\r
46626 this.fireEvent("disabledchange", this, true);
\r
46630 * Enables this node
\r
46632 enable : function(){
\r
46633 this.disabled = false;
\r
46634 if(this.rendered && this.ui.onDisableChange){ // event without subscribing
\r
46635 this.ui.onDisableChange(this, false);
\r
46637 this.fireEvent("disabledchange", this, false);
\r
46641 renderChildren : function(suppressEvent){
\r
46642 if(suppressEvent !== false){
\r
46643 this.fireEvent("beforechildrenrendered", this);
\r
46645 var cs = this.childNodes;
\r
46646 for(var i = 0, len = cs.length; i < len; i++){
\r
46647 cs[i].render(true);
\r
46649 this.childrenRendered = true;
\r
46653 sort : function(fn, scope){
\r
46654 Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
\r
46655 if(this.childrenRendered){
\r
46656 var cs = this.childNodes;
\r
46657 for(var i = 0, len = cs.length; i < len; i++){
\r
46658 cs[i].render(true);
\r
46664 render : function(bulkRender){
\r
46665 this.ui.render(bulkRender);
\r
46666 if(!this.rendered){
\r
46667 // make sure it is registered
\r
46668 this.getOwnerTree().registerNode(this);
\r
46669 this.rendered = true;
\r
46670 if(this.expanded){
\r
46671 this.expanded = false;
\r
46672 this.expand(false, false);
\r
46678 renderIndent : function(deep, refresh){
\r
46680 this.ui.childIndent = null;
\r
46682 this.ui.renderIndent();
\r
46683 if(deep === true && this.childrenRendered){
\r
46684 var cs = this.childNodes;
\r
46685 for(var i = 0, len = cs.length; i < len; i++){
\r
46686 cs[i].renderIndent(true, refresh);
\r
46691 beginUpdate : function(){
\r
46692 this.childrenRendered = false;
\r
46695 endUpdate : function(){
\r
46696 if(this.expanded && this.rendered){
\r
46697 this.renderChildren();
\r
46701 destroy : function(){
\r
46702 if(this.childNodes){
\r
46703 for(var i = 0,l = this.childNodes.length; i < l; i++){
\r
46704 this.childNodes[i].destroy();
\r
46706 this.childNodes = null;
\r
46708 if(this.ui.destroy){
\r
46709 this.ui.destroy();
\r
46714 onIdChange: function(id){
\r
46715 this.ui.onIdChange(id);
\r
46719 Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**
\r
46720 * @class Ext.tree.AsyncTreeNode
\r
46721 * @extends Ext.tree.TreeNode
\r
46722 * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
\r
46724 * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
\r
46726 Ext.tree.AsyncTreeNode = function(config){
\r
46727 this.loaded = config && config.loaded === true;
\r
46728 this.loading = false;
\r
46729 Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
\r
46731 * @event beforeload
\r
46732 * Fires before this node is loaded, return false to cancel
\r
46733 * @param {Node} this This node
\r
46735 this.addEvents('beforeload', 'load');
\r
46738 * Fires when this node is loaded
\r
46739 * @param {Node} this This node
\r
46742 * The loader used by this node (defaults to using the tree's defined loader)
\r
46743 * @type TreeLoader
\r
46744 * @property loader
\r
46747 Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
\r
46748 expand : function(deep, anim, callback, scope){
\r
46749 if(this.loading){ // if an async load is already running, waiting til it's done
\r
46751 var f = function(){
\r
46752 if(!this.loading){ // done loading
\r
46753 clearInterval(timer);
\r
46754 this.expand(deep, anim, callback, scope);
\r
46756 }.createDelegate(this);
\r
46757 timer = setInterval(f, 200);
\r
46760 if(!this.loaded){
\r
46761 if(this.fireEvent("beforeload", this) === false){
\r
46764 this.loading = true;
\r
46765 this.ui.beforeLoad(this);
\r
46766 var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
\r
46768 loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
\r
46772 Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
\r
46776 * Returns true if this node is currently loading
\r
46777 * @return {Boolean}
\r
46779 isLoading : function(){
\r
46780 return this.loading;
\r
46783 loadComplete : function(deep, anim, callback, scope){
\r
46784 this.loading = false;
\r
46785 this.loaded = true;
\r
46786 this.ui.afterLoad(this);
\r
46787 this.fireEvent("load", this);
\r
46788 this.expand(deep, anim, callback, scope);
\r
46792 * Returns true if this node has been loaded
\r
46793 * @return {Boolean}
\r
46795 isLoaded : function(){
\r
46796 return this.loaded;
\r
46799 hasChildNodes : function(){
\r
46800 if(!this.isLeaf() && !this.loaded){
\r
46803 return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
\r
46808 * Trigger a reload for this node
\r
46809 * @param {Function} callback
\r
46810 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
46812 reload : function(callback, scope){
\r
46813 this.collapse(false, false);
\r
46814 while(this.firstChild){
\r
46815 this.removeChild(this.firstChild).destroy();
\r
46817 this.childrenRendered = false;
\r
46818 this.loaded = false;
\r
46819 if(this.isHiddenRoot()){
\r
46820 this.expanded = false;
\r
46822 this.expand(false, false, callback, scope);
\r
46826 Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**
\r
46827 * @class Ext.tree.TreeNodeUI
\r
46828 * This class provides the default UI implementation for Ext TreeNodes.
\r
46829 * The TreeNode UI implementation is separate from the
\r
46830 * tree implementation, and allows customizing of the appearance of
\r
46831 * tree nodes.<br>
\r
46833 * If you are customizing the Tree's user interface, you
\r
46834 * may need to extend this class, but you should never need to instantiate this class.<br>
\r
46836 * This class provides access to the user interface components of an Ext TreeNode, through
\r
46837 * {@link Ext.tree.TreeNode#getUI}
\r
46839 Ext.tree.TreeNodeUI = function(node){
\r
46840 this.node = node;
\r
46841 this.rendered = false;
\r
46842 this.animating = false;
\r
46843 this.wasLeaf = true;
\r
46844 this.ecc = 'x-tree-ec-icon x-tree-elbow';
\r
46845 this.emptyIcon = Ext.BLANK_IMAGE_URL;
\r
46848 Ext.tree.TreeNodeUI.prototype = {
\r
46850 removeChild : function(node){
\r
46851 if(this.rendered){
\r
46852 this.ctNode.removeChild(node.ui.getEl());
\r
46857 beforeLoad : function(){
\r
46858 this.addClass("x-tree-node-loading");
\r
46862 afterLoad : function(){
\r
46863 this.removeClass("x-tree-node-loading");
\r
46867 onTextChange : function(node, text, oldText){
\r
46868 if(this.rendered){
\r
46869 this.textNode.innerHTML = text;
\r
46874 onDisableChange : function(node, state){
\r
46875 this.disabled = state;
\r
46876 if (this.checkbox) {
\r
46877 this.checkbox.disabled = state;
\r
46880 this.addClass("x-tree-node-disabled");
\r
46882 this.removeClass("x-tree-node-disabled");
\r
46887 onSelectedChange : function(state){
\r
46890 this.addClass("x-tree-selected");
\r
46893 this.removeClass("x-tree-selected");
\r
46898 onMove : function(tree, node, oldParent, newParent, index, refNode){
\r
46899 this.childIndent = null;
\r
46900 if(this.rendered){
\r
46901 var targetNode = newParent.ui.getContainer();
\r
46902 if(!targetNode){//target not rendered
\r
46903 this.holder = document.createElement("div");
\r
46904 this.holder.appendChild(this.wrap);
\r
46907 var insertBefore = refNode ? refNode.ui.getEl() : null;
\r
46908 if(insertBefore){
\r
46909 targetNode.insertBefore(this.wrap, insertBefore);
\r
46911 targetNode.appendChild(this.wrap);
\r
46913 this.node.renderIndent(true, oldParent != newParent);
\r
46918 * Adds one or more CSS classes to the node's UI element.
\r
46919 * Duplicate classes are automatically filtered out.
\r
46920 * @param {String/Array} className The CSS class to add, or an array of classes
\r
46922 addClass : function(cls){
\r
46924 Ext.fly(this.elNode).addClass(cls);
\r
46929 * Removes one or more CSS classes from the node's UI element.
\r
46930 * @param {String/Array} className The CSS class to remove, or an array of classes
\r
46932 removeClass : function(cls){
\r
46934 Ext.fly(this.elNode).removeClass(cls);
\r
46939 remove : function(){
\r
46940 if(this.rendered){
\r
46941 this.holder = document.createElement("div");
\r
46942 this.holder.appendChild(this.wrap);
\r
46947 fireEvent : function(){
\r
46948 return this.node.fireEvent.apply(this.node, arguments);
\r
46952 initEvents : function(){
\r
46953 this.node.on("move", this.onMove, this);
\r
46955 if(this.node.disabled){
\r
46956 this.addClass("x-tree-node-disabled");
\r
46957 if (this.checkbox) {
\r
46958 this.checkbox.disabled = true;
\r
46961 if(this.node.hidden){
\r
46964 var ot = this.node.getOwnerTree();
\r
46965 var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
\r
46966 if(dd && (!this.node.isRoot || ot.rootVisible)){
\r
46967 Ext.dd.Registry.register(this.elNode, {
\r
46969 handles: this.getDDHandles(),
\r
46976 getDDHandles : function(){
\r
46977 return [this.iconNode, this.textNode, this.elNode];
\r
46981 * Hides this node.
\r
46983 hide : function(){
\r
46984 this.node.hidden = true;
\r
46986 this.wrap.style.display = "none";
\r
46991 * Shows this node.
\r
46993 show : function(){
\r
46994 this.node.hidden = false;
\r
46996 this.wrap.style.display = "";
\r
47001 onContextMenu : function(e){
\r
47002 if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
\r
47003 e.preventDefault();
\r
47005 this.fireEvent("contextmenu", this.node, e);
\r
47010 onClick : function(e){
\r
47011 if(this.dropping){
\r
47015 if(this.fireEvent("beforeclick", this.node, e) !== false){
\r
47016 var a = e.getTarget('a');
\r
47017 if(!this.disabled && this.node.attributes.href && a){
\r
47018 this.fireEvent("click", this.node, e);
\r
47020 }else if(a && e.ctrlKey){
\r
47023 e.preventDefault();
\r
47024 if(this.disabled){
\r
47028 if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
\r
47029 this.node.toggle();
\r
47032 this.fireEvent("click", this.node, e);
\r
47039 onDblClick : function(e){
\r
47040 e.preventDefault();
\r
47041 if(this.disabled){
\r
47044 if(this.checkbox){
\r
47045 this.toggleCheck();
\r
47047 if(!this.animating && this.node.isExpandable()){
\r
47048 this.node.toggle();
\r
47050 this.fireEvent("dblclick", this.node, e);
\r
47053 onOver : function(e){
\r
47054 this.addClass('x-tree-node-over');
\r
47057 onOut : function(e){
\r
47058 this.removeClass('x-tree-node-over');
\r
47062 onCheckChange : function(){
\r
47063 var checked = this.checkbox.checked;
\r
47065 this.checkbox.defaultChecked = checked;
\r
47066 this.node.attributes.checked = checked;
\r
47067 this.fireEvent('checkchange', this.node, checked);
\r
47071 ecClick : function(e){
\r
47072 if(!this.animating && this.node.isExpandable()){
\r
47073 this.node.toggle();
\r
47078 startDrop : function(){
\r
47079 this.dropping = true;
\r
47082 // delayed drop so the click event doesn't get fired on a drop
\r
47083 endDrop : function(){
\r
47084 setTimeout(function(){
\r
47085 this.dropping = false;
\r
47086 }.createDelegate(this), 50);
\r
47090 expand : function(){
\r
47091 this.updateExpandIcon();
\r
47092 this.ctNode.style.display = "";
\r
47096 focus : function(){
\r
47097 if(!this.node.preventHScroll){
\r
47098 try{this.anchor.focus();
\r
47102 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
\r
47103 var l = noscroll.scrollLeft;
\r
47104 this.anchor.focus();
\r
47105 noscroll.scrollLeft = l;
\r
47111 * Sets the checked status of the tree node to the passed value, or, if no value was passed,
\r
47112 * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
\r
47113 * @param {Boolean} (optional) The new checked status.
\r
47115 toggleCheck : function(value){
\r
47116 var cb = this.checkbox;
\r
47118 cb.checked = (value === undefined ? !cb.checked : value);
\r
47119 this.onCheckChange();
\r
47124 blur : function(){
\r
47126 this.anchor.blur();
\r
47131 animExpand : function(callback){
\r
47132 var ct = Ext.get(this.ctNode);
\r
47134 if(!this.node.isExpandable()){
\r
47135 this.updateExpandIcon();
\r
47136 this.ctNode.style.display = "";
\r
47137 Ext.callback(callback);
\r
47140 this.animating = true;
\r
47141 this.updateExpandIcon();
\r
47143 ct.slideIn('t', {
\r
47144 callback : function(){
\r
47145 this.animating = false;
\r
47146 Ext.callback(callback);
\r
47149 duration: this.node.ownerTree.duration || .25
\r
47154 highlight : function(){
\r
47155 var tree = this.node.getOwnerTree();
\r
47156 Ext.fly(this.wrap).highlight(
\r
47157 tree.hlColor || "C3DAF9",
\r
47158 {endColor: tree.hlBaseColor}
\r
47163 collapse : function(){
\r
47164 this.updateExpandIcon();
\r
47165 this.ctNode.style.display = "none";
\r
47169 animCollapse : function(callback){
\r
47170 var ct = Ext.get(this.ctNode);
\r
47171 ct.enableDisplayMode('block');
\r
47174 this.animating = true;
\r
47175 this.updateExpandIcon();
\r
47177 ct.slideOut('t', {
\r
47178 callback : function(){
\r
47179 this.animating = false;
\r
47180 Ext.callback(callback);
\r
47183 duration: this.node.ownerTree.duration || .25
\r
47188 getContainer : function(){
\r
47189 return this.ctNode;
\r
47193 getEl : function(){
\r
47194 return this.wrap;
\r
47198 appendDDGhost : function(ghostNode){
\r
47199 ghostNode.appendChild(this.elNode.cloneNode(true));
\r
47203 getDDRepairXY : function(){
\r
47204 return Ext.lib.Dom.getXY(this.iconNode);
\r
47208 onRender : function(){
\r
47213 render : function(bulkRender){
\r
47214 var n = this.node, a = n.attributes;
\r
47215 var targetNode = n.parentNode ?
\r
47216 n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
\r
47218 if(!this.rendered){
\r
47219 this.rendered = true;
\r
47221 this.renderElements(n, a, targetNode, bulkRender);
\r
47224 if(this.textNode.setAttributeNS){
\r
47225 this.textNode.setAttributeNS("ext", "qtip", a.qtip);
\r
47227 this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
\r
47230 this.textNode.setAttribute("ext:qtip", a.qtip);
\r
47232 this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
\r
47235 }else if(a.qtipCfg){
\r
47236 a.qtipCfg.target = Ext.id(this.textNode);
\r
47237 Ext.QuickTips.register(a.qtipCfg);
\r
47239 this.initEvents();
\r
47240 if(!this.node.expanded){
\r
47241 this.updateExpandIcon(true);
\r
47244 if(bulkRender === true) {
\r
47245 targetNode.appendChild(this.wrap);
\r
47251 renderElements : function(n, a, targetNode, bulkRender){
\r
47252 // add some indent caching, this helps performance when rendering a large tree
\r
47253 this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
\r
47255 var cb = typeof a.checked == 'boolean';
\r
47257 var href = a.href ? a.href : Ext.isGecko ? "" : "#";
\r
47258 var buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
\r
47259 '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
\r
47260 '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
\r
47261 '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
\r
47262 cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
\r
47263 '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
\r
47264 a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
\r
47265 '<ul class="x-tree-node-ct" style="display:none;"></ul>',
\r
47266 "</li>"].join('');
\r
47269 if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
\r
47270 this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
\r
47272 this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
\r
47275 this.elNode = this.wrap.childNodes[0];
\r
47276 this.ctNode = this.wrap.childNodes[1];
\r
47277 var cs = this.elNode.childNodes;
\r
47278 this.indentNode = cs[0];
\r
47279 this.ecNode = cs[1];
\r
47280 this.iconNode = cs[2];
\r
47283 this.checkbox = cs[3];
\r
47285 this.checkbox.defaultChecked = this.checkbox.checked;
\r
47288 this.anchor = cs[index];
\r
47289 this.textNode = cs[index].firstChild;
\r
47293 * Returns the <a> element that provides focus for the node's UI.
\r
47294 * @return {HtmlElement} The DOM anchor element.
\r
47296 getAnchor : function(){
\r
47297 return this.anchor;
\r
47301 * Returns the text node.
\r
47302 * @return {HtmlNode} The DOM text node.
\r
47304 getTextEl : function(){
\r
47305 return this.textNode;
\r
47309 * Returns the icon <img> element.
\r
47310 * @return {HtmlElement} The DOM image element.
\r
47312 getIconEl : function(){
\r
47313 return this.iconNode;
\r
47317 * Returns the checked status of the node. If the node was rendered with no
\r
47318 * checkbox, it returns false.
\r
47319 * @return {Boolean} The checked flag.
\r
47321 isChecked : function(){
\r
47322 return this.checkbox ? this.checkbox.checked : false;
\r
47326 updateExpandIcon : function(){
\r
47327 if(this.rendered){
\r
47328 var n = this.node, c1, c2;
\r
47329 var cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow";
\r
47330 var hasChild = n.hasChildNodes();
\r
47331 if(hasChild || n.attributes.expandable){
\r
47334 c1 = "x-tree-node-collapsed";
\r
47335 c2 = "x-tree-node-expanded";
\r
47338 c1 = "x-tree-node-expanded";
\r
47339 c2 = "x-tree-node-collapsed";
\r
47341 if(this.wasLeaf){
\r
47342 this.removeClass("x-tree-node-leaf");
\r
47343 this.wasLeaf = false;
\r
47345 if(this.c1 != c1 || this.c2 != c2){
\r
47346 Ext.fly(this.elNode).replaceClass(c1, c2);
\r
47347 this.c1 = c1; this.c2 = c2;
\r
47350 if(!this.wasLeaf){
\r
47351 Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-leaf");
\r
47354 this.wasLeaf = true;
\r
47357 var ecc = "x-tree-ec-icon "+cls;
\r
47358 if(this.ecc != ecc){
\r
47359 this.ecNode.className = ecc;
\r
47366 onIdChange: function(id){
\r
47367 if(this.rendered){
\r
47368 this.elNode.setAttribute('ext:tree-node-id', id);
\r
47373 getChildIndent : function(){
\r
47374 if(!this.childIndent){
\r
47376 var p = this.node;
\r
47378 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
\r
47379 if(!p.isLast()) {
\r
47380 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
\r
47382 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
\r
47385 p = p.parentNode;
\r
47387 this.childIndent = buf.join("");
\r
47389 return this.childIndent;
\r
47393 renderIndent : function(){
\r
47394 if(this.rendered){
\r
47396 var p = this.node.parentNode;
\r
47398 indent = p.ui.getChildIndent();
\r
47400 if(this.indentMarkup != indent){ // don't rerender if not required
\r
47401 this.indentNode.innerHTML = indent;
\r
47402 this.indentMarkup = indent;
\r
47404 this.updateExpandIcon();
\r
47408 destroy : function(){
\r
47410 Ext.dd.Registry.unregister(this.elNode.id);
\r
47412 delete this.elNode;
\r
47413 delete this.ctNode;
\r
47414 delete this.indentNode;
\r
47415 delete this.ecNode;
\r
47416 delete this.iconNode;
\r
47417 delete this.checkbox;
\r
47418 delete this.anchor;
\r
47419 delete this.textNode;
\r
47421 if (this.holder){
\r
47422 delete this.wrap;
\r
47423 Ext.removeNode(this.holder);
\r
47424 delete this.holder;
\r
47426 Ext.removeNode(this.wrap);
\r
47427 delete this.wrap;
\r
47433 * @class Ext.tree.RootTreeNodeUI
\r
47434 * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
\r
47435 * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
\r
47437 * If you are customizing the Tree's user interface, you
\r
47438 * may need to extend this class, but you should never need to instantiate this class.<br>
\r
47440 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
\r
47442 render : function(){
\r
47443 if(!this.rendered){
\r
47444 var targetNode = this.node.ownerTree.innerCt.dom;
\r
47445 this.node.expanded = true;
\r
47446 targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
\r
47447 this.wrap = this.ctNode = targetNode.firstChild;
\r
47450 collapse : Ext.emptyFn,
\r
47451 expand : Ext.emptyFn
\r
47453 * @class Ext.tree.TreeLoader
\r
47454 * @extends Ext.util.Observable
\r
47455 * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
\r
47456 * nodes from a specified URL. The response must be a JavaScript Array definition
\r
47457 * whose elements are node definition objects. e.g.:
\r
47461 text: 'A leaf Node',
\r
47465 text: 'A folder Node',
\r
47468 text: 'A child Node',
\r
47474 * A server request is sent, and child nodes are loaded only when a node is expanded.
\r
47475 * The loading node's id is passed to the server under the parameter name "node" to
\r
47476 * enable the server to produce the correct child nodes.
\r
47478 * To pass extra parameters, an event handler may be attached to the "beforeload"
\r
47479 * event, and the parameters specified in the TreeLoader's baseParams property:
\r
47481 myTreeLoader.on("beforeload", function(treeLoader, node) {
\r
47482 this.baseParams.category = node.attributes.category;
\r
47485 * This would pass an HTTP parameter called "category" to the server containing
\r
47486 * the value of the Node's "category" attribute.
\r
47488 * Creates a new Treeloader.
\r
47489 * @param {Object} config A config object containing config properties.
\r
47491 Ext.tree.TreeLoader = function(config){
\r
47492 this.baseParams = {};
\r
47493 Ext.apply(this, config);
\r
47497 * @event beforeload
\r
47498 * Fires before a network request is made to retrieve the Json text which specifies a node's children.
\r
47499 * @param {Object} This TreeLoader object.
\r
47500 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
47501 * @param {Object} callback The callback function specified in the {@link #load} call.
\r
47506 * Fires when the node has been successfuly loaded.
\r
47507 * @param {Object} This TreeLoader object.
\r
47508 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
47509 * @param {Object} response The response object containing the data from the server.
\r
47513 * @event loadexception
\r
47514 * Fires if the network request failed.
\r
47515 * @param {Object} This TreeLoader object.
\r
47516 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
47517 * @param {Object} response The response object containing the data from the server.
\r
47521 Ext.tree.TreeLoader.superclass.constructor.call(this);
\r
47522 if(typeof this.paramOrder == 'string'){
\r
47523 this.paramOrder = this.paramOrder.split(/[\s,|]/);
\r
47527 Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
\r
47529 * @cfg {String} dataUrl The URL from which to request a Json string which
\r
47530 * specifies an array of node definition objects representing the child nodes
\r
47534 * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
\r
47537 * @cfg {String} url Equivalent to {@link #dataUrl}.
\r
47540 * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
\r
47543 * @cfg {Object} baseParams (optional) An object containing properties which
\r
47544 * specify HTTP parameters to be passed to each request for child nodes.
\r
47547 * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
\r
47548 * created by this loader. If the attributes sent by the server have an attribute in this object,
\r
47549 * they take priority.
\r
47552 * @cfg {Object} uiProviders (optional) An object containing properties which
\r
47553 * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
\r
47554 * <i>uiProvider</i> attribute of a returned child node is a string rather
\r
47555 * than a reference to a TreeNodeUI implementation, then that string value
\r
47556 * is used as a property name in the uiProviders object.
\r
47558 uiProviders : {},
\r
47561 * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
\r
47562 * child nodes before loading.
\r
47564 clearOnLoad : true,
\r
47567 * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.
\r
47568 * A list of params to be executed
\r
47569 * server side. Specify the params in the order in which they must be executed on the server-side
\r
47570 * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
\r
47571 * comma, or pipe. For example,
\r
47572 * any of the following would be acceptable:<pre><code>
\r
47573 paramOrder: ['param1','param2','param3']
\r
47574 paramOrder: 'param1 param2 param3'
\r
47575 paramOrder: 'param1,param2,param3'
\r
47576 paramOrder: 'param1|param2|param'
\r
47579 paramOrder: undefined,
\r
47582 * @cfg {Boolean} paramsAsHash Only used when using directFn.
\r
47583 * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a
\r
47584 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
\r
47586 paramsAsHash: false,
\r
47589 * @cfg {Function} directFn
\r
47590 * Function to call when executing a request.
\r
47592 directFn : undefined,
\r
47595 * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
\r
47596 * This is called automatically when a node is expanded, but may be used to reload
\r
47597 * a node (or append new children if the {@link #clearOnLoad} option is false.)
\r
47598 * @param {Ext.tree.TreeNode} node
\r
47599 * @param {Function} callback
\r
47600 * @param (Object) scope
\r
47602 load : function(node, callback, scope){
\r
47603 if(this.clearOnLoad){
\r
47604 while(node.firstChild){
\r
47605 node.removeChild(node.firstChild);
\r
47608 if(this.doPreload(node)){ // preloaded json children
\r
47609 this.runCallback(callback, scope || node, []);
\r
47610 }else if(this.directFn || this.dataUrl || this.url){
\r
47611 this.requestData(node, callback, scope || node);
\r
47615 doPreload : function(node){
\r
47616 if(node.attributes.children){
\r
47617 if(node.childNodes.length < 1){ // preloaded?
\r
47618 var cs = node.attributes.children;
\r
47619 node.beginUpdate();
\r
47620 for(var i = 0, len = cs.length; i < len; i++){
\r
47621 var cn = node.appendChild(this.createNode(cs[i]));
\r
47622 if(this.preloadChildren){
\r
47623 this.doPreload(cn);
\r
47626 node.endUpdate();
\r
47633 getParams: function(node){
\r
47634 var buf = [], bp = this.baseParams;
\r
47635 if(this.directFn){
\r
47636 buf.push(node.id);
\r
47638 if(this.paramOrder){
\r
47639 for(var i = 0, len = this.paramOrder.length; i < len; i++){
\r
47640 buf.push(bp[this.paramOrder[i]]);
\r
47642 }else if(this.paramsAsHash){
\r
47648 for(var key in bp){
\r
47649 if(!Ext.isFunction(bp[key])){
\r
47650 buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
\r
47653 buf.push("node=", encodeURIComponent(node.id));
\r
47654 return buf.join("");
\r
47658 requestData : function(node, callback, scope){
\r
47659 if(this.fireEvent("beforeload", this, node, callback) !== false){
\r
47660 if(this.directFn){
\r
47661 var args = this.getParams(node);
\r
47662 args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
\r
47663 this.directFn.apply(window, args);
\r
47665 this.transId = Ext.Ajax.request({
\r
47666 method:this.requestMethod,
\r
47667 url: this.dataUrl||this.url,
\r
47668 success: this.handleResponse,
\r
47669 failure: this.handleFailure,
\r
47671 argument: {callback: callback, node: node, scope: scope},
\r
47672 params: this.getParams(node)
\r
47676 // if the load is cancelled, make sure we notify
\r
47677 // the node that we are done
\r
47678 this.runCallback(callback, scope || node, []);
\r
47682 processDirectResponse: function(result, response, args){
\r
47683 if(response.status){
\r
47684 this.handleResponse({
\r
47685 responseData: Ext.isArray(result) ? result : null,
\r
47686 responseText: result,
\r
47690 this.handleFailure({
\r
47697 runCallback: function(cb, scope, args){
\r
47698 if(Ext.isFunction(cb)){
\r
47699 cb.apply(scope, args);
\r
47703 isLoading : function(){
\r
47704 return !!this.transId;
\r
47707 abort : function(){
\r
47708 if(this.isLoading()){
\r
47709 Ext.Ajax.abort(this.transId);
\r
47714 * <p>Override this function for custom TreeNode node implementation, or to
\r
47715 * modify the attributes at creation time.</p>
\r
47716 * Example:<pre><code>
\r
47717 new Ext.tree.TreePanel({
\r
47719 new Ext.tree.TreeLoader({
\r
47721 createNode: function(attr) {
\r
47722 // Allow consolidation consignments to have
\r
47723 // consignments dropped into them.
\r
47724 if (attr.isConsolidation) {
\r
47725 attr.iconCls = 'x-consol',
\r
47726 attr.allowDrop = true;
\r
47728 return Ext.tree.TreeLoader.prototype.call(this, attr);
\r
47734 * @param attr {Object} The attributes from which to create the new node.
\r
47736 createNode : function(attr){
\r
47737 // apply baseAttrs, nice idea Corey!
\r
47738 if(this.baseAttrs){
\r
47739 Ext.applyIf(attr, this.baseAttrs);
\r
47741 if(this.applyLoader !== false){
\r
47742 attr.loader = this;
\r
47744 if(typeof attr.uiProvider == 'string'){
\r
47745 attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
\r
47747 if(attr.nodeType){
\r
47748 return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
\r
47750 return attr.leaf ?
\r
47751 new Ext.tree.TreeNode(attr) :
\r
47752 new Ext.tree.AsyncTreeNode(attr);
\r
47756 processResponse : function(response, node, callback, scope){
\r
47757 var json = response.responseText;
\r
47759 var o = response.responseData || Ext.decode(json);
\r
47760 node.beginUpdate();
\r
47761 for(var i = 0, len = o.length; i < len; i++){
\r
47762 var n = this.createNode(o[i]);
\r
47764 node.appendChild(n);
\r
47767 node.endUpdate();
\r
47768 this.runCallback(callback, scope || node, [node]);
\r
47770 this.handleFailure(response);
\r
47774 handleResponse : function(response){
\r
47775 this.transId = false;
\r
47776 var a = response.argument;
\r
47777 this.processResponse(response, a.node, a.callback, a.scope);
\r
47778 this.fireEvent("load", this, a.node, response);
\r
47781 handleFailure : function(response){
\r
47782 this.transId = false;
\r
47783 var a = response.argument;
\r
47784 this.fireEvent("loadexception", this, a.node, response);
\r
47785 this.runCallback(a.callback, a.scope || a.node, [a.node]);
\r
47788 * @class Ext.tree.TreeFilter
47789 * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
47790 * @param {TreePanel} tree
47791 * @param {Object} config (optional)
47793 Ext.tree.TreeFilter = function(tree, config){
47795 this.filtered = {};
47796 Ext.apply(this, config);
47799 Ext.tree.TreeFilter.prototype = {
47806 * Filter the data by a specific attribute.
47807 * @param {String/RegExp} value Either string that the attribute value
47808 * should start with or a RegExp to test against the attribute
47809 * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
47810 * @param {TreeNode} startNode (optional) The node to start the filter at.
47812 filter : function(value, attr, startNode){
47813 attr = attr || "text";
47815 if(typeof value == "string"){
47816 var vlen = value.length;
47817 // auto clear empty filter
47818 if(vlen == 0 && this.clearBlank){
47822 value = value.toLowerCase();
47824 return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
47826 }else if(value.exec){ // regex?
47828 return value.test(n.attributes[attr]);
47831 throw 'Illegal filter type, must be string or regex';
47833 this.filterBy(f, null, startNode);
47837 * Filter by a function. The passed function will be called with each
47838 * node in the tree (or from the startNode). If the function returns true, the node is kept
47839 * otherwise it is filtered. If a node is filtered, its children are also filtered.
47840 * @param {Function} fn The filter function
47841 * @param {Object} scope (optional) The scope of the function (defaults to the current node)
47843 filterBy : function(fn, scope, startNode){
47844 startNode = startNode || this.tree.root;
47845 if(this.autoClear){
47848 var af = this.filtered, rv = this.reverse;
47849 var f = function(n){
47850 if(n == startNode){
47856 var m = fn.call(scope || n, n);
47864 startNode.cascade(f);
47867 if(typeof id != "function"){
47869 if(n && n.parentNode){
47870 n.parentNode.removeChild(n);
47878 * Clears the current filter. Note: with the "remove" option
47879 * set a filter cannot be cleared.
47881 clear : function(){
47883 var af = this.filtered;
47885 if(typeof id != "function"){
47892 this.filtered = {};
47896 * @class Ext.tree.TreeSorter
\r
47897 * Provides sorting of nodes in a {@link Ext.tree.TreePanel}. The TreeSorter automatically monitors events on the
\r
47898 * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
\r
47899 * Example usage:<br />
\r
47901 new Ext.tree.TreeSorter(myTree, {
\r
47902 folderSort: true,
\r
47904 sortType: function(node) {
\r
47905 // sort by a custom, typed attribute:
\r
47906 return parseInt(node.id, 10);
\r
47911 * @param {TreePanel} tree
\r
47912 * @param {Object} config
\r
47914 Ext.tree.TreeSorter = function(tree, config){
\r
47916 * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
\r
47919 * @cfg {String} property The named attribute on the node to sort by (defaults to "text"). Note that this
\r
47920 * property is only used if no {@link #sortType} function is specified, otherwise it is ignored.
\r
47923 * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")
\r
47926 * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")
\r
47929 * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)
\r
47932 * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting. The function
\r
47933 * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return
\r
47934 * the node's sort value cast to the specific data type required for sorting. This could be used, for example, when
\r
47935 * a node's text (or other attribute) should be sorted as a date or numeric value. See the class description for
\r
47936 * example usage. Note that if a sortType is specified, any {@link #property} config will be ignored.
\r
47939 Ext.apply(this, config);
\r
47940 tree.on("beforechildrenrendered", this.doSort, this);
\r
47941 tree.on("append", this.updateSort, this);
\r
47942 tree.on("insert", this.updateSort, this);
\r
47943 tree.on("textchange", this.updateSortParent, this);
\r
47945 var dsc = this.dir && this.dir.toLowerCase() == "desc";
\r
47946 var p = this.property || "text";
\r
47947 var sortType = this.sortType;
\r
47948 var fs = this.folderSort;
\r
47949 var cs = this.caseSensitive === true;
\r
47950 var leafAttr = this.leafAttr || 'leaf';
\r
47952 this.sortFn = function(n1, n2){
\r
47954 if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
\r
47957 if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
\r
47961 var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
\r
47962 var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
\r
47964 return dsc ? +1 : -1;
\r
47965 }else if(v1 > v2){
\r
47966 return dsc ? -1 : +1;
\r
47973 Ext.tree.TreeSorter.prototype = {
\r
47974 doSort : function(node){
\r
47975 node.sort(this.sortFn);
\r
47978 compareNodes : function(n1, n2){
\r
47979 return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
\r
47982 updateSort : function(tree, node){
\r
47983 if(node.childrenRendered){
\r
47984 this.doSort.defer(1, this, [node]);
\r
47988 updateSortParent : function(node){
\r
47989 var p = node.parentNode;
\r
47990 if(p && p.childrenRendered){
\r
47991 this.doSort.defer(1, this, [p]);
\r
47995 * @class Ext.tree.TreeDropZone
\r
47996 * @extends Ext.dd.DropZone
\r
47998 * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
\r
47999 * @param {Object} config
\r
48001 if(Ext.dd.DropZone){
\r
48003 Ext.tree.TreeDropZone = function(tree, config){
\r
48005 * @cfg {Boolean} allowParentInsert
\r
48006 * Allow inserting a dragged node between an expanded parent node and its first child that will become a
\r
48007 * sibling of the parent when dropped (defaults to false)
\r
48009 this.allowParentInsert = config.allowParentInsert || false;
\r
48011 * @cfg {String} allowContainerDrop
\r
48012 * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
\r
48014 this.allowContainerDrop = config.allowContainerDrop || false;
\r
48016 * @cfg {String} appendOnly
\r
48017 * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
\r
48019 this.appendOnly = config.appendOnly || false;
\r
48021 Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
\r
48023 * The TreePanel for this drop zone
\r
48024 * @type Ext.tree.TreePanel
\r
48027 this.tree = tree;
\r
48029 * Arbitrary data that can be associated with this tree and will be included in the event object that gets
\r
48030 * passed to any nodedragover event handler (defaults to {})
\r
48031 * @type Ext.tree.TreePanel
\r
48034 this.dragOverData = {};
\r
48036 this.lastInsertClass = "x-tree-no-status";
\r
48039 Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
\r
48041 * @cfg {String} ddGroup
\r
48042 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
48043 * interact with other drag drop objects in the same group (defaults to 'TreeDD').
\r
48045 ddGroup : "TreeDD",
\r
48048 * @cfg {String} expandDelay
\r
48049 * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
\r
48050 * over the target (defaults to 1000)
\r
48052 expandDelay : 1000,
\r
48055 expandNode : function(node){
\r
48056 if(node.hasChildNodes() && !node.isExpanded()){
\r
48057 node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
\r
48062 queueExpand : function(node){
\r
48063 this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
\r
48067 cancelExpand : function(){
\r
48068 if(this.expandProcId){
\r
48069 clearTimeout(this.expandProcId);
\r
48070 this.expandProcId = false;
\r
48075 isValidDropPoint : function(n, pt, dd, e, data){
\r
48076 if(!n || !data){ return false; }
\r
48077 var targetNode = n.node;
\r
48078 var dropNode = data.node;
\r
48079 // default drop rules
\r
48080 if(!(targetNode && targetNode.isTarget && pt)){
\r
48083 if(pt == "append" && targetNode.allowChildren === false){
\r
48086 if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
\r
48089 if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
\r
48092 // reuse the object
\r
48093 var overEvent = this.dragOverData;
\r
48094 overEvent.tree = this.tree;
\r
48095 overEvent.target = targetNode;
\r
48096 overEvent.data = data;
\r
48097 overEvent.point = pt;
\r
48098 overEvent.source = dd;
\r
48099 overEvent.rawEvent = e;
\r
48100 overEvent.dropNode = dropNode;
\r
48101 overEvent.cancel = false;
\r
48102 var result = this.tree.fireEvent("nodedragover", overEvent);
\r
48103 return overEvent.cancel === false && result !== false;
\r
48107 getDropPoint : function(e, n, dd){
\r
48110 return tn.allowChildren !== false ? "append" : false; // always append for root
\r
48112 var dragEl = n.ddel;
\r
48113 var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
\r
48114 var y = Ext.lib.Event.getPageY(e);
\r
48115 var noAppend = tn.allowChildren === false || tn.isLeaf();
\r
48116 if(this.appendOnly || tn.parentNode.allowChildren === false){
\r
48117 return noAppend ? false : "append";
\r
48119 var noBelow = false;
\r
48120 if(!this.allowParentInsert){
\r
48121 noBelow = tn.hasChildNodes() && tn.isExpanded();
\r
48123 var q = (b - t) / (noAppend ? 2 : 3);
\r
48124 if(y >= t && y < (t + q)){
\r
48126 }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
\r
48134 onNodeEnter : function(n, dd, e, data){
\r
48135 this.cancelExpand();
\r
48138 onContainerOver : function(dd, e, data) {
\r
48139 if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
\r
48140 return this.dropAllowed;
\r
48142 return this.dropNotAllowed;
\r
48146 onNodeOver : function(n, dd, e, data){
\r
48147 var pt = this.getDropPoint(e, n, dd);
\r
48148 var node = n.node;
\r
48150 // auto node expand check
\r
48151 if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
\r
48152 this.queueExpand(node);
\r
48153 }else if(pt != "append"){
\r
48154 this.cancelExpand();
\r
48157 // set the insert point style on the target node
\r
48158 var returnCls = this.dropNotAllowed;
\r
48159 if(this.isValidDropPoint(n, pt, dd, e, data)){
\r
48163 if(pt == "above"){
\r
48164 returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
\r
48165 cls = "x-tree-drag-insert-above";
\r
48166 }else if(pt == "below"){
\r
48167 returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
\r
48168 cls = "x-tree-drag-insert-below";
\r
48170 returnCls = "x-tree-drop-ok-append";
\r
48171 cls = "x-tree-drag-append";
\r
48173 if(this.lastInsertClass != cls){
\r
48174 Ext.fly(el).replaceClass(this.lastInsertClass, cls);
\r
48175 this.lastInsertClass = cls;
\r
48179 return returnCls;
\r
48183 onNodeOut : function(n, dd, e, data){
\r
48184 this.cancelExpand();
\r
48185 this.removeDropIndicators(n);
\r
48189 onNodeDrop : function(n, dd, e, data){
\r
48190 var point = this.getDropPoint(e, n, dd);
\r
48191 var targetNode = n.node;
\r
48192 targetNode.ui.startDrop();
\r
48193 if(!this.isValidDropPoint(n, point, dd, e, data)){
\r
48194 targetNode.ui.endDrop();
\r
48197 // first try to find the drop node
\r
48198 var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
\r
48199 return this.processDrop(targetNode, data, point, dd, e, dropNode);
\r
48202 onContainerDrop : function(dd, e, data){
\r
48203 if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
\r
48204 var targetNode = this.tree.getRootNode();
\r
48205 targetNode.ui.startDrop();
\r
48206 var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
\r
48207 return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
\r
48213 processDrop: function(target, data, point, dd, e, dropNode){
\r
48214 var dropEvent = {
\r
48215 tree : this.tree,
\r
48221 dropNode: dropNode,
\r
48222 cancel: !dropNode,
\r
48223 dropStatus: false
\r
48225 var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
\r
48226 if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
\r
48227 target.ui.endDrop();
\r
48228 return dropEvent.dropStatus;
\r
48231 target = dropEvent.target;
\r
48232 if(point == 'append' && !target.isExpanded()){
\r
48233 target.expand(false, null, function(){
\r
48234 this.completeDrop(dropEvent);
\r
48235 }.createDelegate(this));
\r
48237 this.completeDrop(dropEvent);
\r
48243 completeDrop : function(de){
\r
48244 var ns = de.dropNode, p = de.point, t = de.target;
\r
48245 if(!Ext.isArray(ns)){
\r
48249 for(var i = 0, len = ns.length; i < len; i++){
\r
48251 if(p == "above"){
\r
48252 t.parentNode.insertBefore(n, t);
\r
48253 }else if(p == "below"){
\r
48254 t.parentNode.insertBefore(n, t.nextSibling);
\r
48256 t.appendChild(n);
\r
48260 if(Ext.enableFx && this.tree.hlDrop){
\r
48261 n.ui.highlight();
\r
48264 this.tree.fireEvent("nodedrop", de);
\r
48268 afterNodeMoved : function(dd, data, e, targetNode, dropNode){
\r
48269 if(Ext.enableFx && this.tree.hlDrop){
\r
48270 dropNode.ui.focus();
\r
48271 dropNode.ui.highlight();
\r
48273 this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
\r
48277 getTree : function(){
\r
48278 return this.tree;
\r
48282 removeDropIndicators : function(n){
\r
48285 Ext.fly(el).removeClass([
\r
48286 "x-tree-drag-insert-above",
\r
48287 "x-tree-drag-insert-below",
\r
48288 "x-tree-drag-append"]);
\r
48289 this.lastInsertClass = "_noclass";
\r
48294 beforeDragDrop : function(target, e, id){
\r
48295 this.cancelExpand();
\r
48300 afterRepair : function(data){
\r
48301 if(data && Ext.enableFx){
\r
48302 data.node.ui.highlight();
\r
48304 this.hideProxy();
\r
48309 * @class Ext.tree.TreeDragZone
\r
48310 * @extends Ext.dd.DragZone
\r
48312 * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging
\r
48313 * @param {Object} config
\r
48315 if(Ext.dd.DragZone){
\r
48316 Ext.tree.TreeDragZone = function(tree, config){
\r
48317 Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
\r
48319 * The TreePanel for this drag zone
\r
48320 * @type Ext.tree.TreePanel
\r
48323 this.tree = tree;
\r
48326 Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
\r
48328 * @cfg {String} ddGroup
\r
48329 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
48330 * interact with other drag drop objects in the same group (defaults to 'TreeDD').
\r
48332 ddGroup : "TreeDD",
\r
48335 onBeforeDrag : function(data, e){
\r
48336 var n = data.node;
\r
48337 return n && n.draggable && !n.disabled;
\r
48341 onInitDrag : function(e){
\r
48342 var data = this.dragData;
\r
48343 this.tree.getSelectionModel().select(data.node);
\r
48344 this.tree.eventModel.disable();
\r
48345 this.proxy.update("");
\r
48346 data.node.ui.appendDDGhost(this.proxy.ghost.dom);
\r
48347 this.tree.fireEvent("startdrag", this.tree, data.node, e);
\r
48351 getRepairXY : function(e, data){
\r
48352 return data.node.ui.getDDRepairXY();
\r
48356 onEndDrag : function(data, e){
\r
48357 this.tree.eventModel.enable.defer(100, this.tree.eventModel);
\r
48358 this.tree.fireEvent("enddrag", this.tree, data.node, e);
\r
48362 onValidDrop : function(dd, e, id){
\r
48363 this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
\r
48364 this.hideProxy();
\r
48368 beforeInvalidDrop : function(e, id){
\r
48369 // this scrolls the original position back into view
\r
48370 var sm = this.tree.getSelectionModel();
\r
48371 sm.clearSelections();
\r
48372 sm.select(this.dragData.node);
\r
48376 afterRepair : function(){
\r
48377 if (Ext.enableFx && this.tree.hlDrop) {
\r
48378 Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
\r
48380 this.dragging = false;
\r
48384 * @class Ext.tree.TreeEditor
48385 * @extends Ext.Editor
48386 * Provides editor functionality for inline tree node editing. Any valid {@link Ext.form.Field} subclass can be used
48387 * as the editor field.
48389 * @param {TreePanel} tree
48390 * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
48391 * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
48392 * @param {Object} config (optional) A TreeEditor config object
48394 Ext.tree.TreeEditor = function(tree, fc, config){
48396 var field = fc.events ? fc : new Ext.form.TextField(fc);
48397 Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
48401 if(!tree.rendered){
48402 tree.on('render', this.initEditor, this);
48404 this.initEditor(tree);
48408 Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
48410 * @cfg {String} alignment
48411 * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
48417 * @cfg {Boolean} hideEl
48418 * True to hide the bound element while the editor is displayed (defaults to false)
48422 * @cfg {String} cls
48423 * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
48425 cls: "x-small-editor x-tree-editor",
48427 * @cfg {Boolean} shim
48428 * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
48434 * @cfg {Number} maxWidth
48435 * The maximum width in pixels of the editor field (defaults to 250). Note that if the maxWidth would exceed
48436 * the containing tree element's size, it will be automatically limited for you to the container width, taking
48437 * scroll and client offsets into account prior to each edit.
48441 * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
48442 * editing on the current node (defaults to 350). If two clicks occur on the same node within this time span,
48443 * the editor for the node will display, otherwise it will be processed as a regular click.
48447 initEditor : function(tree){
48448 tree.on('beforeclick', this.beforeNodeClick, this);
48449 tree.on('dblclick', this.onNodeDblClick, this);
48450 this.on('complete', this.updateNode, this);
48451 this.on('beforestartedit', this.fitToTree, this);
48452 this.on('startedit', this.bindScroll, this, {delay:10});
48453 this.on('specialkey', this.onSpecialKey, this);
48457 fitToTree : function(ed, el){
48458 var td = this.tree.getTreeEl().dom, nd = el.dom;
48459 if(td.scrollLeft > nd.offsetLeft){ // ensure the node left point is visible
48460 td.scrollLeft = nd.offsetLeft;
48464 (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
48465 this.setSize(w, '');
48469 * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
48470 * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
48472 triggerEdit : function(node, defer){
48473 this.completeEdit();
48474 if(node.attributes.editable !== false){
48476 * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
48477 * @type Ext.tree.TreeNode
48478 * @property editNode
48480 this.editNode = node;
48481 if(this.tree.autoScroll){
48482 Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
48484 var value = node.text || '';
48485 if (!Ext.isGecko && Ext.isEmpty(node.text)){
48486 node.setText(' ');
48488 this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
48494 bindScroll : function(){
48495 this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
48499 beforeNodeClick : function(node, e){
48500 clearTimeout(this.autoEditTimer);
48501 if(this.tree.getSelectionModel().isSelected(node)){
48503 return this.triggerEdit(node);
48507 onNodeDblClick : function(node, e){
48508 clearTimeout(this.autoEditTimer);
48512 updateNode : function(ed, value){
48513 this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
48514 this.editNode.setText(value);
48518 onHide : function(){
48519 Ext.tree.TreeEditor.superclass.onHide.call(this);
48521 this.editNode.ui.focus.defer(50, this.editNode.ui);
48526 onSpecialKey : function(field, e){
48527 var k = e.getKey();
48531 }else if(k == e.ENTER && !e.hasModifier()){
48533 this.completeEdit();
48536 });/*! SWFObject v2.2 <http://code.google.com/p/swfobject/>
\r
48537 is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
\r
48540 var swfobject = function() {
\r
48542 var UNDEF = "undefined",
\r
48543 OBJECT = "object",
\r
48544 SHOCKWAVE_FLASH = "Shockwave Flash",
\r
48545 SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
\r
48546 FLASH_MIME_TYPE = "application/x-shockwave-flash",
\r
48547 EXPRESS_INSTALL_ID = "SWFObjectExprInst",
\r
48548 ON_READY_STATE_CHANGE = "onreadystatechange",
\r
48555 domLoadFnArr = [main],
\r
48558 listenersArr = [],
\r
48559 storedAltContent,
\r
48560 storedAltContentId,
\r
48561 storedCallbackFn,
\r
48562 storedCallbackObj,
\r
48563 isDomLoaded = false,
\r
48564 isExpressInstallActive = false,
\r
48565 dynamicStylesheet,
\r
48566 dynamicStylesheetMedia,
\r
48567 autoHideShow = true,
\r
48569 /* Centralized function for browser feature detection
\r
48570 - User agent string detection is only used when no good alternative is possible
\r
48571 - Is executed directly for optimal performance
\r
48573 ua = function() {
\r
48574 var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
\r
48575 u = nav.userAgent.toLowerCase(),
\r
48576 p = nav.platform.toLowerCase(),
\r
48577 windows = p ? /win/.test(p) : /win/.test(u),
\r
48578 mac = p ? /mac/.test(p) : /mac/.test(u),
\r
48579 webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
\r
48580 ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
\r
48581 playerVersion = [0,0,0],
\r
48583 if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
\r
48584 d = nav.plugins[SHOCKWAVE_FLASH].description;
\r
48585 if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
\r
48587 ie = false; // cascaded feature detection for Internet Explorer
\r
48588 d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
\r
48589 playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
\r
48590 playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
\r
48591 playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
\r
48594 else if (typeof win.ActiveXObject != UNDEF) {
\r
48596 var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
\r
48597 if (a) { // a will return null when ActiveX is disabled
\r
48598 d = a.GetVariable("$version");
\r
48600 ie = true; // cascaded feature detection for Internet Explorer
\r
48601 d = d.split(" ")[1].split(",");
\r
48602 playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
\r
48608 return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
\r
48611 /* Cross-browser onDomLoad
\r
48612 - Will fire an event as soon as the DOM of a web page is loaded
\r
48613 - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/
\r
48614 - Regular onload serves as fallback
\r
48616 onDomLoad = function() {
\r
48617 if (!ua.w3) { return; }
\r
48618 if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically
\r
48619 callDomLoadFunctions();
\r
48621 if (!isDomLoaded) {
\r
48622 if (typeof doc.addEventListener != UNDEF) {
\r
48623 doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
\r
48625 if (ua.ie && ua.win) {
\r
48626 doc.attachEvent(ON_READY_STATE_CHANGE, function() {
\r
48627 if (doc.readyState == "complete") {
\r
48628 doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
\r
48629 callDomLoadFunctions();
\r
48632 if (win == top) { // if not inside an iframe
\r
48634 if (isDomLoaded) { return; }
\r
48636 doc.documentElement.doScroll("left");
\r
48639 setTimeout(arguments.callee, 0);
\r
48642 callDomLoadFunctions();
\r
48648 if (isDomLoaded) { return; }
\r
48649 if (!/loaded|complete/.test(doc.readyState)) {
\r
48650 setTimeout(arguments.callee, 0);
\r
48653 callDomLoadFunctions();
\r
48656 addLoadEvent(callDomLoadFunctions);
\r
48660 function callDomLoadFunctions() {
\r
48661 if (isDomLoaded) { return; }
\r
48662 try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early
\r
48663 var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
\r
48664 t.parentNode.removeChild(t);
\r
48666 catch (e) { return; }
\r
48667 isDomLoaded = true;
\r
48668 var dl = domLoadFnArr.length;
\r
48669 for (var i = 0; i < dl; i++) {
\r
48670 domLoadFnArr[i]();
\r
48674 function addDomLoadEvent(fn) {
\r
48675 if (isDomLoaded) {
\r
48679 domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
\r
48683 /* Cross-browser onload
\r
48684 - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
\r
48685 - Will fire an event as soon as a web page including all of its assets are loaded
\r
48687 function addLoadEvent(fn) {
\r
48688 if (typeof win.addEventListener != UNDEF) {
\r
48689 win.addEventListener("load", fn, false);
\r
48691 else if (typeof doc.addEventListener != UNDEF) {
\r
48692 doc.addEventListener("load", fn, false);
\r
48694 else if (typeof win.attachEvent != UNDEF) {
\r
48695 addListener(win, "onload", fn);
\r
48697 else if (typeof win.onload == "function") {
\r
48698 var fnOld = win.onload;
\r
48699 win.onload = function() {
\r
48710 - Will preferably execute onDomLoad, otherwise onload (as a fallback)
\r
48712 function main() {
\r
48714 testPlayerVersion();
\r
48721 /* Detect the Flash Player version for non-Internet Explorer browsers
\r
48722 - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description:
\r
48723 a. Both release and build numbers can be detected
\r
48724 b. Avoid wrong descriptions by corrupt installers provided by Adobe
\r
48725 c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports
\r
48726 - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available
\r
48728 function testPlayerVersion() {
\r
48729 var b = doc.getElementsByTagName("body")[0];
\r
48730 var o = createElement(OBJECT);
\r
48731 o.setAttribute("type", FLASH_MIME_TYPE);
\r
48732 var t = b.appendChild(o);
\r
48736 if (typeof t.GetVariable != UNDEF) {
\r
48737 var d = t.GetVariable("$version");
\r
48739 d = d.split(" ")[1].split(",");
\r
48740 ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
\r
48743 else if (counter < 10) {
\r
48745 setTimeout(arguments.callee, 10);
\r
48748 b.removeChild(o);
\r
48758 /* Perform Flash Player and SWF version matching; static publishing only
\r
48760 function matchVersions() {
\r
48761 var rl = regObjArr.length;
\r
48763 for (var i = 0; i < rl; i++) { // for each registered object element
\r
48764 var id = regObjArr[i].id;
\r
48765 var cb = regObjArr[i].callbackFn;
\r
48766 var cbObj = {success:false, id:id};
\r
48767 if (ua.pv[0] > 0) {
\r
48768 var obj = getElementById(id);
\r
48770 if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match!
\r
48771 setVisibility(id, true);
\r
48773 cbObj.success = true;
\r
48774 cbObj.ref = getObjectById(id);
\r
48778 else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported
\r
48780 att.data = regObjArr[i].expressInstall;
\r
48781 att.width = obj.getAttribute("width") || "0";
\r
48782 att.height = obj.getAttribute("height") || "0";
\r
48783 if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
\r
48784 if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
\r
48785 // parse HTML object param element's name-value pairs
\r
48787 var p = obj.getElementsByTagName("param");
\r
48788 var pl = p.length;
\r
48789 for (var j = 0; j < pl; j++) {
\r
48790 if (p[j].getAttribute("name").toLowerCase() != "movie") {
\r
48791 par[p[j].getAttribute("name")] = p[j].getAttribute("value");
\r
48794 showExpressInstall(att, par, id, cb);
\r
48796 else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF
\r
48797 displayAltContent(obj);
\r
48798 if (cb) { cb(cbObj); }
\r
48802 else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content)
\r
48803 setVisibility(id, true);
\r
48805 var o = getObjectById(id); // test whether there is an HTML object element or not
\r
48806 if (o && typeof o.SetVariable != UNDEF) {
\r
48807 cbObj.success = true;
\r
48817 function getObjectById(objectIdStr) {
\r
48819 var o = getElementById(objectIdStr);
\r
48820 if (o && o.nodeName == "OBJECT") {
\r
48821 if (typeof o.SetVariable != UNDEF) {
\r
48825 var n = o.getElementsByTagName(OBJECT)[0];
\r
48834 /* Requirements for Adobe Express Install
\r
48835 - only one instance can be active at a time
\r
48836 - fp 6.0.65 or higher
\r
48837 - Win/Mac OS only
\r
48838 - no Webkit engines older than version 312
\r
48840 function canExpressInstall() {
\r
48841 return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
\r
48844 /* Show the Adobe Express Install dialog
\r
48845 - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
\r
48847 function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
\r
48848 isExpressInstallActive = true;
\r
48849 storedCallbackFn = callbackFn || null;
\r
48850 storedCallbackObj = {success:false, id:replaceElemIdStr};
\r
48851 var obj = getElementById(replaceElemIdStr);
\r
48853 if (obj.nodeName == "OBJECT") { // static publishing
\r
48854 storedAltContent = abstractAltContent(obj);
\r
48855 storedAltContentId = null;
\r
48857 else { // dynamic publishing
\r
48858 storedAltContent = obj;
\r
48859 storedAltContentId = replaceElemIdStr;
\r
48861 att.id = EXPRESS_INSTALL_ID;
\r
48862 if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
\r
48863 if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
\r
48864 doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
\r
48865 var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
\r
48866 fv = "MMredirectURL=" + win.location.toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
\r
48867 if (typeof par.flashvars != UNDEF) {
\r
48868 par.flashvars += "&" + fv;
\r
48871 par.flashvars = fv;
\r
48873 // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
\r
48874 // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
\r
48875 if (ua.ie && ua.win && obj.readyState != 4) {
\r
48876 var newObj = createElement("div");
\r
48877 replaceElemIdStr += "SWFObjectNew";
\r
48878 newObj.setAttribute("id", replaceElemIdStr);
\r
48879 obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf
\r
48880 obj.style.display = "none";
\r
48882 if (obj.readyState == 4) {
\r
48883 obj.parentNode.removeChild(obj);
\r
48886 setTimeout(arguments.callee, 10);
\r
48890 createSWF(att, par, replaceElemIdStr);
\r
48894 /* Functions to abstract and display alternative content
\r
48896 function displayAltContent(obj) {
\r
48897 if (ua.ie && ua.win && obj.readyState != 4) {
\r
48898 // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
\r
48899 // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
\r
48900 var el = createElement("div");
\r
48901 obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content
\r
48902 el.parentNode.replaceChild(abstractAltContent(obj), el);
\r
48903 obj.style.display = "none";
\r
48905 if (obj.readyState == 4) {
\r
48906 obj.parentNode.removeChild(obj);
\r
48909 setTimeout(arguments.callee, 10);
\r
48914 obj.parentNode.replaceChild(abstractAltContent(obj), obj);
\r
48918 function abstractAltContent(obj) {
\r
48919 var ac = createElement("div");
\r
48920 if (ua.win && ua.ie) {
\r
48921 ac.innerHTML = obj.innerHTML;
\r
48924 var nestedObj = obj.getElementsByTagName(OBJECT)[0];
\r
48926 var c = nestedObj.childNodes;
\r
48928 var cl = c.length;
\r
48929 for (var i = 0; i < cl; i++) {
\r
48930 if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
\r
48931 ac.appendChild(c[i].cloneNode(true));
\r
48940 /* Cross-browser dynamic SWF creation
\r
48942 function createSWF(attObj, parObj, id) {
\r
48943 var r, el = getElementById(id);
\r
48944 if (ua.wk && ua.wk < 312) { return r; }
\r
48946 if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
\r
48949 if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML
\r
48951 for (var i in attObj) {
\r
48952 if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries
\r
48953 if (i.toLowerCase() == "data") {
\r
48954 parObj.movie = attObj[i];
\r
48956 else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
\r
48957 att += ' class="' + attObj[i] + '"';
\r
48959 else if (i.toLowerCase() != "classid") {
\r
48960 att += ' ' + i + '="' + attObj[i] + '"';
\r
48965 for (var j in parObj) {
\r
48966 if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries
\r
48967 par += '<param name="' + j + '" value="' + parObj[j] + '" />';
\r
48970 el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
\r
48971 objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only)
\r
48972 r = getElementById(attObj.id);
\r
48974 else { // well-behaving browsers
\r
48975 var o = createElement(OBJECT);
\r
48976 o.setAttribute("type", FLASH_MIME_TYPE);
\r
48977 for (var m in attObj) {
\r
48978 if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries
\r
48979 if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
\r
48980 o.setAttribute("class", attObj[m]);
\r
48982 else if (m.toLowerCase() != "classid") { // filter out IE specific attribute
\r
48983 o.setAttribute(m, attObj[m]);
\r
48987 for (var n in parObj) {
\r
48988 if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element
\r
48989 createObjParam(o, n, parObj[n]);
\r
48992 el.parentNode.replaceChild(o, el);
\r
48999 function createObjParam(el, pName, pValue) {
\r
49000 var p = createElement("param");
\r
49001 p.setAttribute("name", pName);
\r
49002 p.setAttribute("value", pValue);
\r
49003 el.appendChild(p);
\r
49006 /* Cross-browser SWF removal
\r
49007 - Especially needed to safely and completely remove a SWF in Internet Explorer
\r
49009 function removeSWF(id) {
\r
49010 var obj = getElementById(id);
\r
49011 if (obj && obj.nodeName == "OBJECT") {
\r
49012 if (ua.ie && ua.win) {
\r
49013 obj.style.display = "none";
\r
49015 if (obj.readyState == 4) {
\r
49016 removeObjectInIE(id);
\r
49019 setTimeout(arguments.callee, 10);
\r
49024 obj.parentNode.removeChild(obj);
\r
49029 function removeObjectInIE(id) {
\r
49030 var obj = getElementById(id);
\r
49032 for (var i in obj) {
\r
49033 if (typeof obj[i] == "function") {
\r
49037 obj.parentNode.removeChild(obj);
\r
49041 /* Functions to optimize JavaScript compression
\r
49043 function getElementById(id) {
\r
49046 el = doc.getElementById(id);
\r
49052 function createElement(el) {
\r
49053 return doc.createElement(el);
\r
49056 /* Updated attachEvent function for Internet Explorer
\r
49057 - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
\r
49059 function addListener(target, eventType, fn) {
\r
49060 target.attachEvent(eventType, fn);
\r
49061 listenersArr[listenersArr.length] = [target, eventType, fn];
\r
49064 /* Flash Player and SWF content version matching
\r
49066 function hasPlayerVersion(rv) {
\r
49067 var pv = ua.pv, v = rv.split(".");
\r
49068 v[0] = parseInt(v[0], 10);
\r
49069 v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
\r
49070 v[2] = parseInt(v[2], 10) || 0;
\r
49071 return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
\r
49074 /* Cross-browser dynamic CSS creation
\r
49075 - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
\r
49077 function createCSS(sel, decl, media, newStyle) {
\r
49078 if (ua.ie && ua.mac) { return; }
\r
49079 var h = doc.getElementsByTagName("head")[0];
\r
49080 if (!h) { return; } // to also support badly authored HTML pages that lack a head element
\r
49081 var m = (media && typeof media == "string") ? media : "screen";
\r
49083 dynamicStylesheet = null;
\r
49084 dynamicStylesheetMedia = null;
\r
49086 if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
\r
49087 // create dynamic stylesheet + get a global reference to it
\r
49088 var s = createElement("style");
\r
49089 s.setAttribute("type", "text/css");
\r
49090 s.setAttribute("media", m);
\r
49091 dynamicStylesheet = h.appendChild(s);
\r
49092 if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
\r
49093 dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
\r
49095 dynamicStylesheetMedia = m;
\r
49097 // add style rule
\r
49098 if (ua.ie && ua.win) {
\r
49099 if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
\r
49100 dynamicStylesheet.addRule(sel, decl);
\r
49104 if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
\r
49105 dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
\r
49110 function setVisibility(id, isVisible) {
\r
49111 if (!autoHideShow) { return; }
\r
49112 var v = isVisible ? "visible" : "hidden";
\r
49113 if (isDomLoaded && getElementById(id)) {
\r
49114 getElementById(id).style.visibility = v;
\r
49117 createCSS("#" + id, "visibility:" + v);
\r
49121 /* Filter to avoid XSS attacks
\r
49123 function urlEncodeIfNecessary(s) {
\r
49124 var regex = /[\\\"<>\.;]/;
\r
49125 var hasBadChars = regex.exec(s) != null;
\r
49126 return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
\r
49129 /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
\r
49131 var cleanup = function() {
\r
49132 if (ua.ie && ua.win) {
\r
49133 window.attachEvent("onunload", function() {
\r
49134 // remove listeners to avoid memory leaks
\r
49135 var ll = listenersArr.length;
\r
49136 for (var i = 0; i < ll; i++) {
\r
49137 listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
\r
49139 // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
\r
49140 var il = objIdArr.length;
\r
49141 for (var j = 0; j < il; j++) {
\r
49142 removeSWF(objIdArr[j]);
\r
49144 // cleanup library's main closures to avoid memory leaks
\r
49145 for (var k in ua) {
\r
49149 for (var l in swfobject) {
\r
49150 swfobject[l] = null;
\r
49152 swfobject = null;
\r
49159 - Reference: http://code.google.com/p/swfobject/wiki/documentation
\r
49161 registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
\r
49162 if (ua.w3 && objectIdStr && swfVersionStr) {
\r
49164 regObj.id = objectIdStr;
\r
49165 regObj.swfVersion = swfVersionStr;
\r
49166 regObj.expressInstall = xiSwfUrlStr;
\r
49167 regObj.callbackFn = callbackFn;
\r
49168 regObjArr[regObjArr.length] = regObj;
\r
49169 setVisibility(objectIdStr, false);
\r
49171 else if (callbackFn) {
\r
49172 callbackFn({success:false, id:objectIdStr});
\r
49176 getObjectById: function(objectIdStr) {
\r
49178 return getObjectById(objectIdStr);
\r
49182 embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
\r
49183 var callbackObj = {success:false, id:replaceElemIdStr};
\r
49184 if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
\r
49185 setVisibility(replaceElemIdStr, false);
\r
49186 addDomLoadEvent(function() {
\r
49187 widthStr += ""; // auto-convert to string
\r
49190 if (attObj && typeof attObj === OBJECT) {
\r
49191 for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs
\r
49192 att[i] = attObj[i];
\r
49195 att.data = swfUrlStr;
\r
49196 att.width = widthStr;
\r
49197 att.height = heightStr;
\r
49199 if (parObj && typeof parObj === OBJECT) {
\r
49200 for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs
\r
49201 par[j] = parObj[j];
\r
49204 if (flashvarsObj && typeof flashvarsObj === OBJECT) {
\r
49205 for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs
\r
49206 if (typeof par.flashvars != UNDEF) {
\r
49207 par.flashvars += "&" + k + "=" + flashvarsObj[k];
\r
49210 par.flashvars = k + "=" + flashvarsObj[k];
\r
49214 if (hasPlayerVersion(swfVersionStr)) { // create SWF
\r
49215 var obj = createSWF(att, par, replaceElemIdStr);
\r
49216 if (att.id == replaceElemIdStr) {
\r
49217 setVisibility(replaceElemIdStr, true);
\r
49219 callbackObj.success = true;
\r
49220 callbackObj.ref = obj;
\r
49222 else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install
\r
49223 att.data = xiSwfUrlStr;
\r
49224 showExpressInstall(att, par, replaceElemIdStr, callbackFn);
\r
49227 else { // show alternative content
\r
49228 setVisibility(replaceElemIdStr, true);
\r
49230 if (callbackFn) { callbackFn(callbackObj); }
\r
49233 else if (callbackFn) { callbackFn(callbackObj); }
\r
49236 switchOffAutoHideShow: function() {
\r
49237 autoHideShow = false;
\r
49242 getFlashPlayerVersion: function() {
\r
49243 return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
\r
49246 hasFlashPlayerVersion: hasPlayerVersion,
\r
49248 createSWF: function(attObj, parObj, replaceElemIdStr) {
\r
49250 return createSWF(attObj, parObj, replaceElemIdStr);
\r
49253 return undefined;
\r
49257 showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
\r
49258 if (ua.w3 && canExpressInstall()) {
\r
49259 showExpressInstall(att, par, replaceElemIdStr, callbackFn);
\r
49263 removeSWF: function(objElemIdStr) {
\r
49265 removeSWF(objElemIdStr);
\r
49269 createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
\r
49271 createCSS(selStr, declStr, mediaStr, newStyleBoolean);
\r
49275 addDomLoadEvent: addDomLoadEvent,
\r
49277 addLoadEvent: addLoadEvent,
\r
49279 getQueryParamValue: function(param) {
\r
49280 var q = doc.location.search || doc.location.hash;
\r
49282 if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark
\r
49283 if (param == null) {
\r
49284 return urlEncodeIfNecessary(q);
\r
49286 var pairs = q.split("&");
\r
49287 for (var i = 0; i < pairs.length; i++) {
\r
49288 if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
\r
49289 return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
\r
49296 // For internal usage only
\r
49297 expressInstallCallback: function() {
\r
49298 if (isExpressInstallActive) {
\r
49299 var obj = getElementById(EXPRESS_INSTALL_ID);
\r
49300 if (obj && storedAltContent) {
\r
49301 obj.parentNode.replaceChild(storedAltContent, obj);
\r
49302 if (storedAltContentId) {
\r
49303 setVisibility(storedAltContentId, true);
\r
49304 if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
\r
49306 if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
\r
49308 isExpressInstallActive = false;
\r
49314 * @class Ext.FlashComponent
49315 * @extends Ext.BoxComponent
49319 Ext.FlashComponent = Ext.extend(Ext.BoxComponent, {
49321 * @cfg {String} flashVersion
49322 * Indicates the version the flash content was published for. Defaults to <tt>'9.0.45'</tt>.
49324 flashVersion : '9.0.45',
49327 * @cfg {String} backgroundColor
49328 * The background color of the chart. Defaults to <tt>'#ffffff'</tt>.
49330 backgroundColor: '#ffffff',
49333 * @cfg {String} wmode
49334 * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
49339 * @cfg {String} url
49340 * The URL of the chart to include. Defaults to <tt>undefined</tt>.
49348 * @cfg {Boolean} expressInstall
49349 * True to prompt the user to install flash if not installed. Note that this uses
49350 * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
49352 expressInstall: false,
49354 initComponent : function(){
49355 Ext.FlashComponent.superclass.initComponent.call(this);
49357 this.addEvents('initialize');
49360 onRender : function(){
49361 Ext.FlashComponent.superclass.onRender.apply(this, arguments);
49364 allowScriptAccess: 'always',
49365 bgcolor: this.backgroundColor,
49368 allowedDomain: document.location.hostname,
49369 elementID: this.getId(),
49370 eventHandler: 'Ext.FlashEventProxy.onEvent'
49373 new swfobject.embedSWF(this.url, this.id, this.swfWidth, this.swfHeight, this.flashVersion,
49374 this.expressInstall ? Ext.FlashComponent.EXPRESS_INSTALL_URL : undefined, vars, params);
49376 this.swf = Ext.getDom(this.id);
49377 this.el = Ext.get(this.swf);
49380 getSwfId : function(){
49381 return this.swfId || (this.swfId = "extswf" + (++Ext.Component.AUTO_ID));
49384 getId : function(){
49385 return this.id || (this.id = "extflashcmp" + (++Ext.Component.AUTO_ID));
49388 onFlashEvent : function(e){
49396 e.component = this;
49397 this.fireEvent(e.type.toLowerCase().replace(/event$/, ''), e);
49400 initSwf : function(){
49401 this.onSwfReady(!!this.isInitialized);
49402 this.isInitialized = true;
49403 this.fireEvent('initialize', this);
49406 beforeDestroy: function(){
49408 swfobject.removeSWF(this.swf.id);
49410 Ext.FlashComponent.superclass.beforeDestroy.call(this);
49413 onSwfReady : Ext.emptyFn
49417 * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
49421 Ext.FlashComponent.EXPRESS_INSTALL_URL = 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf';
49423 Ext.reg('flash', Ext.FlashComponent);/**
\r
49424 * @class Ext.FlashProxy
\r
49427 Ext.FlashEventProxy = {
\r
49428 onEvent : function(id, e){
\r
49429 var fp = Ext.getCmp(id);
\r
49431 fp.onFlashEvent(e);
\r
49433 arguments.callee.defer(10, this, [id, e]);
\r
49437 * @class Ext.chart.Chart
\r
49438 * @extends Ext.FlashComponent
\r
49439 * The Ext.chart package provides the capability to visualize data with flash based charting.
\r
49440 * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
\r
49445 Ext.chart.Chart = Ext.extend(Ext.FlashComponent, {
\r
49446 refreshBuffer: 100,
\r
49449 * @cfg {Object} chartStyle
\r
49450 * Sets styles for this chart. Contains a number of default values. Modifying this property will override
\r
49451 * the base styles on the chart.
\r
49455 animationEnabled: true,
\r
49481 * @cfg {String} url
\r
49482 * The url to load the chart from. This defaults to Ext.chart.Chart.CHART_URL, which should
\r
49483 * be modified to point to the local charts resource.
\r
49487 * @cfg {Object} extraStyle
\r
49488 * Contains extra styles that will be added or overwritten to the default chartStyle. Defaults to <tt>null</tt>.
\r
49490 extraStyle: null,
\r
49493 * @cfg {Boolean} disableCaching
\r
49494 * True to add a "cache buster" to the end of the chart url. Defaults to true for Opera and IE.
\r
49496 disableCaching: Ext.isIE || Ext.isOpera,
\r
49497 disableCacheParam: '_dc',
\r
49499 initComponent : function(){
\r
49500 Ext.chart.Chart.superclass.initComponent.call(this);
\r
49502 this.url = Ext.chart.Chart.CHART_URL;
\r
49504 if(this.disableCaching){
\r
49505 this.url = Ext.urlAppend(this.url, String.format('{0}={1}', this.disableCacheParam, new Date().getTime()));
\r
49511 'itemdoubleclick',
\r
49516 this.store = Ext.StoreMgr.lookup(this.store);
\r
49520 * Sets a single style value on the Chart instance.
\r
49522 * @param name {String} Name of the Chart style value to change.
\r
49523 * @param value {Object} New value to pass to the Chart style.
\r
49525 setStyle: function(name, value){
\r
49526 this.swf.setStyle(name, Ext.encode(value));
\r
49530 * Resets all styles on the Chart instance.
\r
49532 * @param styles {Object} Initializer for all Chart styles.
\r
49534 setStyles: function(styles){
\r
49535 this.swf.setStyles(Ext.encode(styles));
\r
49539 * Sets the styles on all series in the Chart.
\r
49541 * @param styles {Array} Initializer for all Chart series styles.
\r
49543 setSeriesStyles: function(styles){
\r
49545 Ext.each(styles, function(style){
\r
49546 s.push(Ext.encode(style));
\r
49548 this.swf.setSeriesStyles(s);
\r
49551 setCategoryNames : function(names){
\r
49552 this.swf.setCategoryNames(names);
\r
49555 setTipRenderer : function(fn){
\r
49556 var chart = this;
\r
49557 this.tipFnName = this.createFnProxy(function(item, index, series){
\r
49558 var record = chart.store.getAt(index);
\r
49559 return fn(chart, record, index, series);
\r
49560 }, this.tipFnName);
\r
49561 this.swf.setDataTipFunction(this.tipFnName);
\r
49564 setSeries : function(series){
\r
49565 this.series = series;
\r
49570 * Changes the data store bound to this chart and refreshes it.
\r
49571 * @param {Store} store The store to bind to this chart
\r
49573 bindStore : function(store, initial){
\r
49574 if(!initial && this.store){
\r
49575 this.store.un("datachanged", this.refresh, this);
\r
49576 this.store.un("add", this.delayRefresh, this);
\r
49577 this.store.un("remove", this.delayRefresh, this);
\r
49578 this.store.un("update", this.delayRefresh, this);
\r
49579 this.store.un("clear", this.refresh, this);
\r
49580 if(store !== this.store && this.store.autoDestroy){
\r
49581 this.store.destroy();
\r
49585 store = Ext.StoreMgr.lookup(store);
\r
49588 datachanged: this.refresh,
\r
49589 add: this.delayRefresh,
\r
49590 remove: this.delayRefresh,
\r
49591 update: this.delayRefresh,
\r
49592 clear: this.refresh
\r
49595 this.store = store;
\r
49596 if(store && !initial){
\r
49601 onSwfReady : function(isReset){
\r
49602 Ext.chart.Chart.superclass.onSwfReady.call(this, isReset);
\r
49603 this.swf.setType(this.type);
\r
49605 if(this.chartStyle){
\r
49606 this.setStyles(Ext.apply(this.extraStyle || {}, this.chartStyle));
\r
49609 if(this.categoryNames){
\r
49610 this.setCategoryNames(this.categoryNames);
\r
49613 if(this.tipRenderer){
\r
49614 this.setTipRenderer(this.tipRenderer);
\r
49617 this.bindStore(this.store, true);
\r
49619 this.refresh.defer(10, this);
\r
49622 delayRefresh : function(){
\r
49623 if(!this.refreshTask){
\r
49624 this.refreshTask = new Ext.util.DelayedTask(this.refresh, this);
\r
49626 this.refreshTask.delay(this.refreshBuffer);
\r
49629 refresh : function(){
\r
49630 var styleChanged = false;
\r
49631 // convert the store data into something YUI charts can understand
\r
49632 var data = [], rs = this.store.data.items;
\r
49633 for(var j = 0, len = rs.length; j < len; j++){
\r
49634 data[j] = rs[j].data;
\r
49636 //make a copy of the series definitions so that we aren't
\r
49637 //editing them directly.
\r
49638 var dataProvider = [];
\r
49639 var seriesCount = 0;
\r
49640 var currentSeries = null;
\r
49643 seriesCount = this.series.length;
\r
49644 for(i = 0; i < seriesCount; i++){
\r
49645 currentSeries = this.series[i];
\r
49646 var clonedSeries = {};
\r
49647 for(var prop in currentSeries){
\r
49648 if(prop == "style" && currentSeries.style !== null){
\r
49649 clonedSeries.style = Ext.encode(currentSeries.style);
\r
49650 styleChanged = true;
\r
49651 //we don't want to modify the styles again next time
\r
49652 //so null out the style property.
\r
49653 // this causes issues
\r
49654 // currentSeries.style = null;
\r
49656 clonedSeries[prop] = currentSeries[prop];
\r
49659 dataProvider.push(clonedSeries);
\r
49663 if(seriesCount > 0){
\r
49664 for(i = 0; i < seriesCount; i++){
\r
49665 currentSeries = dataProvider[i];
\r
49666 if(!currentSeries.type){
\r
49667 currentSeries.type = this.type;
\r
49669 currentSeries.dataProvider = data;
\r
49672 dataProvider.push({type: this.type, dataProvider: data});
\r
49674 this.swf.setDataProvider(dataProvider);
\r
49677 createFnProxy : function(fn, old){
\r
49679 delete window[old];
\r
49681 var fnName = "extFnProxy" + (++Ext.chart.Chart.PROXY_FN_ID);
\r
49682 window[fnName] = fn;
\r
49686 onDestroy: function(){
\r
49687 Ext.chart.Chart.superclass.onDestroy.call(this);
\r
49688 delete window[this.tipFnName];
\r
49691 Ext.reg('chart', Ext.chart.Chart);
\r
49692 Ext.chart.Chart.PROXY_FN_ID = 0;
\r
49695 * Sets the url to load the chart from. This should be set to a local resource.
\r
49699 Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.7.0/build/charts/assets/charts.swf';
\r
49702 * @class Ext.chart.PieChart
\r
49703 * @extends Ext.chart.Chart
\r
49705 * @xtype piechart
\r
49707 Ext.chart.PieChart = Ext.extend(Ext.chart.Chart, {
\r
49710 onSwfReady : function(isReset){
\r
49711 Ext.chart.PieChart.superclass.onSwfReady.call(this, isReset);
\r
49713 this.setDataField(this.dataField);
\r
49714 this.setCategoryField(this.categoryField);
\r
49717 setDataField : function(field){
\r
49718 this.dataField = field;
\r
49719 this.swf.setDataField(field);
\r
49722 setCategoryField : function(field){
\r
49723 this.categoryField = field;
\r
49724 this.swf.setCategoryField(field);
\r
49727 Ext.reg('piechart', Ext.chart.PieChart);
\r
49730 * @class Ext.chart.CartesianChart
\r
49731 * @extends Ext.chart.Chart
\r
49733 * @xtype cartesianchart
\r
49735 Ext.chart.CartesianChart = Ext.extend(Ext.chart.Chart, {
\r
49736 onSwfReady : function(isReset){
\r
49737 Ext.chart.CartesianChart.superclass.onSwfReady.call(this, isReset);
\r
49740 this.setXField(this.xField);
\r
49743 this.setYField(this.yField);
\r
49746 this.setXAxis(this.xAxis);
\r
49749 this.setYAxis(this.yAxis);
\r
49753 setXField : function(value){
\r
49754 this.xField = value;
\r
49755 this.swf.setHorizontalField(value);
\r
49758 setYField : function(value){
\r
49759 this.yField = value;
\r
49760 this.swf.setVerticalField(value);
\r
49763 setXAxis : function(value){
\r
49764 this.xAxis = this.createAxis('xAxis', value);
\r
49765 this.swf.setHorizontalAxis(this.xAxis);
\r
49768 setYAxis : function(value){
\r
49769 this.yAxis = this.createAxis('yAxis', value);
\r
49770 this.swf.setVerticalAxis(this.yAxis);
\r
49773 createAxis : function(axis, value){
\r
49774 var o = Ext.apply({}, value), oldFn = null;
\r
49776 oldFn = this[axis].labelFunction;
\r
49778 if(o.labelRenderer){
\r
49779 var fn = o.labelRenderer;
\r
49780 o.labelFunction = this.createFnProxy(function(v){
\r
49783 delete o.labelRenderer;
\r
49788 Ext.reg('cartesianchart', Ext.chart.CartesianChart);
\r
49791 * @class Ext.chart.LineChart
\r
49792 * @extends Ext.chart.CartesianChart
\r
49794 * @xtype linechart
\r
49796 Ext.chart.LineChart = Ext.extend(Ext.chart.CartesianChart, {
\r
49799 Ext.reg('linechart', Ext.chart.LineChart);
\r
49802 * @class Ext.chart.ColumnChart
\r
49803 * @extends Ext.chart.CartesianChart
\r
49805 * @xtype columnchart
\r
49807 Ext.chart.ColumnChart = Ext.extend(Ext.chart.CartesianChart, {
\r
49810 Ext.reg('columnchart', Ext.chart.ColumnChart);
\r
49813 * @class Ext.chart.StackedColumnChart
\r
49814 * @extends Ext.chart.CartesianChart
\r
49816 * @xtype stackedcolumnchart
\r
49818 Ext.chart.StackedColumnChart = Ext.extend(Ext.chart.CartesianChart, {
\r
49819 type: 'stackcolumn'
\r
49821 Ext.reg('stackedcolumnchart', Ext.chart.StackedColumnChart);
\r
49824 * @class Ext.chart.BarChart
\r
49825 * @extends Ext.chart.CartesianChart
\r
49827 * @xtype barchart
\r
49829 Ext.chart.BarChart = Ext.extend(Ext.chart.CartesianChart, {
\r
49832 Ext.reg('barchart', Ext.chart.BarChart);
\r
49835 * @class Ext.chart.StackedBarChart
\r
49836 * @extends Ext.chart.CartesianChart
\r
49838 * @xtype stackedbarchart
\r
49840 Ext.chart.StackedBarChart = Ext.extend(Ext.chart.CartesianChart, {
\r
49843 Ext.reg('stackedbarchart', Ext.chart.StackedBarChart);
\r
49848 * @class Ext.chart.Axis
\r
49849 * Defines a CartesianChart's vertical or horizontal axis.
\r
49852 Ext.chart.Axis = function(config){
\r
49853 Ext.apply(this, config);
\r
49856 Ext.chart.Axis.prototype =
\r
49859 * The type of axis.
\r
49867 * The direction in which the axis is drawn. May be "horizontal" or "vertical".
\r
49869 * @property orientation
\r
49872 orientation: "horizontal",
\r
49875 * If true, the items on the axis will be drawn in opposite direction.
\r
49877 * @property reverse
\r
49883 * A string reference to the globally-accessible function that may be called to
\r
49884 * determine each of the label values for this axis.
\r
49886 * @property labelFunction
\r
49889 labelFunction: null,
\r
49892 * If true, labels that overlap previously drawn labels on the axis will be hidden.
\r
49894 * @property hideOverlappingLabels
\r
49897 hideOverlappingLabels: true
\r
49901 * @class Ext.chart.NumericAxis
\r
49902 * @extends Ext.chart.Axis
\r
49903 * A type of axis whose units are measured in numeric values.
\r
49906 Ext.chart.NumericAxis = Ext.extend(Ext.chart.Axis, {
\r
49910 * The minimum value drawn by the axis. If not set explicitly, the axis minimum
\r
49911 * will be calculated automatically.
\r
49913 * @property minimum
\r
49919 * The maximum value drawn by the axis. If not set explicitly, the axis maximum
\r
49920 * will be calculated automatically.
\r
49922 * @property maximum
\r
49928 * The spacing between major intervals on this axis.
\r
49930 * @property majorUnit
\r
49936 * The spacing between minor intervals on this axis.
\r
49938 * @property minorUnit
\r
49944 * If true, the labels, ticks, gridlines, and other objects will snap to
\r
49945 * the nearest major or minor unit. If false, their position will be based
\r
49946 * on the minimum value.
\r
49948 * @property snapToUnits
\r
49951 snapToUnits: true,
\r
49954 * If true, and the bounds are calculated automatically, either the minimum or
\r
49955 * maximum will be set to zero.
\r
49957 * @property alwaysShowZero
\r
49960 alwaysShowZero: true,
\r
49963 * The scaling algorithm to use on this axis. May be "linear" or "logarithmic".
\r
49965 * @property scale
\r
49972 * @class Ext.chart.TimeAxis
\r
49973 * @extends Ext.chart.Axis
\r
49974 * A type of axis whose units are measured in time-based values.
\r
49977 Ext.chart.TimeAxis = Ext.extend(Ext.chart.Axis, {
\r
49981 * The minimum value drawn by the axis. If not set explicitly, the axis minimum
\r
49982 * will be calculated automatically.
\r
49984 * @property minimum
\r
49990 * The maximum value drawn by the axis. If not set explicitly, the axis maximum
\r
49991 * will be calculated automatically.
\r
49993 * @property maximum
\r
49999 * The spacing between major intervals on this axis.
\r
50001 * @property majorUnit
\r
50007 * The time unit used by the majorUnit.
\r
50009 * @property majorTimeUnit
\r
50012 majorTimeUnit: null,
\r
50015 * The spacing between minor intervals on this axis.
\r
50017 * @property majorUnit
\r
50023 * The time unit used by the minorUnit.
\r
50025 * @property majorTimeUnit
\r
50028 minorTimeUnit: null,
\r
50031 * If true, the labels, ticks, gridlines, and other objects will snap to
\r
50032 * the nearest major or minor unit. If false, their position will be based
\r
50033 * on the minimum value.
\r
50035 * @property snapToUnits
\r
50038 snapToUnits: true
\r
50042 * @class Ext.chart.CategoryAxis
\r
50043 * @extends Ext.chart.Axis
\r
50044 * A type of axis that displays items in categories.
\r
50047 Ext.chart.CategoryAxis = Ext.extend(Ext.chart.Axis, {
\r
50048 type: "category",
\r
50051 * A list of category names to display along this axis.
\r
50053 * @property categoryNames
\r
50056 categoryNames: null
\r
50060 * @class Ext.chart.Series
\r
50061 * Series class for the charts widget.
\r
50064 Ext.chart.Series = function(config) { Ext.apply(this, config); };
\r
50066 Ext.chart.Series.prototype =
\r
50069 * The type of series.
\r
50077 * The human-readable name of the series.
\r
50079 * @property displayName
\r
50082 displayName: null
\r
50086 * @class Ext.chart.CartesianSeries
\r
50087 * @extends Ext.chart.Series
\r
50088 * CartesianSeries class for the charts widget.
\r
50091 Ext.chart.CartesianSeries = Ext.extend(Ext.chart.Series, {
\r
50093 * The field used to access the x-axis value from the items from the data source.
\r
50095 * @property xField
\r
50101 * The field used to access the y-axis value from the items from the data source.
\r
50103 * @property yField
\r
50110 * @class Ext.chart.ColumnSeries
\r
50111 * @extends Ext.chart.CartesianSeries
\r
50112 * ColumnSeries class for the charts widget.
\r
50115 Ext.chart.ColumnSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
50120 * @class Ext.chart.LineSeries
\r
50121 * @extends Ext.chart.CartesianSeries
\r
50122 * LineSeries class for the charts widget.
\r
50125 Ext.chart.LineSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
50130 * @class Ext.chart.BarSeries
\r
50131 * @extends Ext.chart.CartesianSeries
\r
50132 * BarSeries class for the charts widget.
\r
50135 Ext.chart.BarSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
50141 * @class Ext.chart.PieSeries
\r
50142 * @extends Ext.chart.Series
\r
50143 * PieSeries class for the charts widget.
\r
50146 Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, {
\r
50149 categoryField: null
\r
50151 * @class Ext.layout.MenuLayout
\r
50152 * @extends Ext.layout.ContainerLayout
\r
50153 * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>
\r
50155 Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
50156 monitorResize: true,
\r
50158 setContainer : function(ct){
\r
50159 this.monitorResize = !ct.floating;
\r
50160 Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);
\r
50163 renderItem : function(c, position, target){
\r
50164 if (!this.itemTpl) {
\r
50165 this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
\r
50166 '<li id="{itemId}" class="{itemCls}">',
\r
50167 '<tpl if="needsIcon">',
\r
50168 '<img src="{icon}" class="{iconCls}"/>',
\r
50174 if(c && !c.rendered){
\r
50175 if(Ext.isNumber(position)){
\r
50176 position = target.dom.childNodes[position];
\r
50178 var a = this.getItemArgs(c);
\r
50180 // The Component's positionEl is the <li> it is rendered into
\r
50181 c.render(c.positionEl = position ?
\r
50182 this.itemTpl.insertBefore(position, a, true) :
\r
50183 this.itemTpl.append(target, a, true));
\r
50185 // Link the containing <li> to the item.
\r
50186 c.positionEl.menuItemId = c.itemId || c.id;
\r
50188 // If rendering a regular Component, and it needs an icon,
\r
50189 // move the Component rightwards.
\r
50190 if (!a.isMenuItem && a.needsIcon) {
\r
50191 c.positionEl.addClass('x-menu-list-item-indent');
\r
50193 }else if(c && !this.isValidParent(c, target)){
\r
50194 if(Ext.isNumber(position)){
\r
50195 position = target.dom.childNodes[position];
\r
50197 target.dom.insertBefore(c.getActionEl().dom, position || null);
\r
50201 getItemArgs: function(c) {
\r
50202 var isMenuItem = c instanceof Ext.menu.Item;
\r
50204 isMenuItem: isMenuItem,
\r
50205 needsIcon: !isMenuItem && (c.icon || c.iconCls),
\r
50206 icon: c.icon || Ext.BLANK_IMAGE_URL,
\r
50207 iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
\r
50208 itemId: 'x-menu-el-' + c.id,
\r
50209 itemCls: 'x-menu-list-item ' + (this.extraCls || '')
\r
50213 // Valid if the Component is in a <li> which is part of our target <ul>
\r
50214 isValidParent: function(c, target) {
\r
50215 return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);
\r
50218 onLayout : function(ct, target){
\r
50219 this.renderAll(ct, target);
\r
50220 this.doAutoSize();
\r
50223 doAutoSize : function(){
\r
50224 var ct = this.container, w = ct.width;
\r
50228 }else if(Ext.isIE){
\r
50229 ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth);
\r
50230 var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc
\r
50231 ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));
\r
50236 Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;
\r
50239 * @class Ext.menu.Menu
\r
50240 * @extends Ext.Container
\r
50241 * <p>A menu object. This is the container to which you may add menu items. Menu can also serve as a base class
\r
50242 * when you want a specialized menu based off of another component (like {@link Ext.menu.DateMenu} for example).</p>
\r
50243 * <p>Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Component}s.</p>
\r
50244 * <p>To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items}
\r
50245 * specify <tt>iconCls: 'no-icon'</tt>. This reserves a space for an icon, and indents the Component in line
\r
50246 * with the other menu items. See {@link Ext.form.ComboBox}.{@link Ext.form.ComboBox#getListParent getListParent}
\r
50247 * for an example.</p>
\r
50248 * <p>By default, Menus are absolutely positioned, floating Components. By configuring a Menu with
\r
50249 * <b><tt>{@link #floating}:false</tt></b>, a Menu may be used as child of a Container.</p>
\r
50253 Ext.menu.Menu = Ext.extend(Ext.Container, {
\r
50255 * @cfg {Object} defaults
\r
50256 * A config object that will be applied to all items added to this container either via the {@link #items}
\r
50257 * config or via the {@link #add} method. The defaults config can contain any number of
\r
50258 * name/value property pairs to be added to each item, and should be valid for the types of items
\r
50259 * being added to the menu.
\r
50262 * @cfg {Mixed} items
\r
50263 * An array of items to be added to this menu. Menus may contain either {@link Ext.menu.Item menu items},
\r
50264 * or general {@link Ext.Component Component}s.
\r
50267 * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
\r
50271 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
\r
50272 * for bottom-right shadow (defaults to "sides")
\r
50274 shadow : "sides",
\r
50276 * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of
\r
50277 * this menu (defaults to "tl-tr?")
\r
50279 subMenuAlign : "tl-tr?",
\r
50281 * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu
\r
50282 * relative to its element of origin (defaults to "tl-bl?")
\r
50284 defaultAlign : "tl-bl?",
\r
50286 * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
\r
50288 allowOtherMenus : false,
\r
50290 * @cfg {Boolean} ignoreParentClicks True to ignore clicks on any item in this menu that is a parent item (displays
\r
50291 * a submenu) so that the submenu is not dismissed when clicking the parent item (defaults to false).
\r
50293 ignoreParentClicks : false,
\r
50295 * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true).
\r
50297 enableScrolling: true,
\r
50299 * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null).
\r
50303 * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24).
\r
50305 scrollIncrement: 24,
\r
50307 * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
\r
50309 showSeparator: true,
\r
50311 * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to
\r
50312 * change the default Menu popup position after aligning according to the {@link #defaultAlign}
\r
50313 * configuration. Defaults to <tt>[0, 0]</tt>.
\r
50315 defaultOffsets : [0, 0],
\r
50319 * @cfg {Boolean} floating
\r
50320 * May be specified as false to create a Menu which may be used as a child item of another Container
\r
50321 * instead of a free-floating {@link Ext.Layer Layer}. (defaults to true).
\r
50323 floating: true, // Render as a Layer by default
\r
50328 hideMode: 'offsets', // Important for laying out Components
\r
50329 scrollerHeight: 8,
\r
50330 autoLayout: true, // Provided for backwards compat
\r
50331 defaultType: 'menuitem',
\r
50333 initComponent: function(){
\r
50334 if(Ext.isArray(this.initialConfig)){
\r
50335 Ext.apply(this, {items:this.initialConfig});
\r
50340 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
\r
50341 * @param {Ext.menu.Menu} this
\r
50342 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
50343 * @param {Ext.EventObject} e
\r
50347 * @event mouseover
\r
50348 * Fires when the mouse is hovering over this menu
\r
50349 * @param {Ext.menu.Menu} this
\r
50350 * @param {Ext.EventObject} e
\r
50351 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
50355 * @event mouseout
\r
50356 * Fires when the mouse exits this menu
\r
50357 * @param {Ext.menu.Menu} this
\r
50358 * @param {Ext.EventObject} e
\r
50359 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
50363 * @event itemclick
\r
50364 * Fires when a menu item contained in this menu is clicked
\r
50365 * @param {Ext.menu.BaseItem} baseItem The BaseItem that was clicked
\r
50366 * @param {Ext.EventObject} e
\r
50370 Ext.menu.MenuMgr.register(this);
\r
50371 if(this.floating){
\r
50372 Ext.EventManager.onWindowResize(this.hide, this);
\r
50374 if(this.initialConfig.hidden !== false){
\r
50375 this.hidden = false;
\r
50377 this.internalDefaults = {hideOnClick: false};
\r
50379 Ext.menu.Menu.superclass.initComponent.call(this);
\r
50380 if(this.autoLayout){
\r
50382 add: this.doLayout,
\r
50383 remove: this.doLayout,
\r
50390 getLayoutTarget : function() {
\r
50395 onRender : function(ct, position){
\r
50397 ct = Ext.getBody();
\r
50401 id: this.getId(),
\r
50402 cls: 'x-menu ' + ((this.floating) ? 'x-menu-floating x-layer ' : '') + (this.cls || '') + (this.plain ? ' x-menu-plain' : '') + (this.showSeparator ? '' : ' x-menu-nosep'),
\r
50403 style: this.style,
\r
50405 {tag: 'a', cls: 'x-menu-focus', href: '#', onclick: 'return false;', tabIndex: '-1'},
\r
50406 {tag: 'ul', cls: 'x-menu-list'}
\r
50409 if(this.floating){
\r
50410 this.el = new Ext.Layer({
\r
50411 shadow: this.shadow,
\r
50413 constrain: false,
\r
50418 this.el = ct.createChild(dh);
\r
50420 Ext.menu.Menu.superclass.onRender.call(this, ct, position);
\r
50422 if(!this.keyNav){
\r
50423 this.keyNav = new Ext.menu.MenuNav(this);
\r
50425 // generic focus element
\r
50426 this.focusEl = this.el.child('a.x-menu-focus');
\r
50427 this.ul = this.el.child('ul.x-menu-list');
\r
50428 this.mon(this.ul, {
\r
50430 click: this.onClick,
\r
50431 mouseover: this.onMouseOver,
\r
50432 mouseout: this.onMouseOut
\r
50434 if(this.enableScrolling){
\r
50435 this.mon(this.el, {
\r
50437 delegate: '.x-menu-scroller',
\r
50438 click: this.onScroll,
\r
50439 mouseover: this.deactivateActive
\r
50445 findTargetItem : function(e){
\r
50446 var t = e.getTarget(".x-menu-list-item", this.ul, true);
\r
50447 if(t && t.menuItemId){
\r
50448 return this.items.get(t.menuItemId);
\r
50453 onClick : function(e){
\r
50454 var t = this.findTargetItem(e);
\r
50456 if(t.isFormField){
\r
50457 this.setActiveItem(t);
\r
50459 if(t.menu && this.ignoreParentClicks){
\r
50461 e.preventDefault();
\r
50462 }else if(t.onClick){
\r
50464 this.fireEvent("click", this, t, e);
\r
50471 setActiveItem : function(item, autoExpand){
\r
50472 if(item != this.activeItem){
\r
50473 this.deactivateActive();
\r
50474 if((this.activeItem = item).isFormField){
\r
50477 item.activate(autoExpand);
\r
50479 }else if(autoExpand){
\r
50480 item.expandMenu();
\r
50484 deactivateActive: function(){
\r
50485 var a = this.activeItem;
\r
50487 if(a.isFormField){
\r
50488 //Fields cannot deactivate, but Combos must collapse
\r
50495 delete this.activeItem;
\r
50500 tryActivate : function(start, step){
\r
50501 var items = this.items;
\r
50502 for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
\r
50503 var item = items.get(i);
\r
50504 if(!item.disabled && (item.canActivate || item.isFormField)){
\r
50505 this.setActiveItem(item, false);
\r
50513 onMouseOver : function(e){
\r
50514 var t = this.findTargetItem(e);
\r
50516 if(t.canActivate && !t.disabled){
\r
50517 this.setActiveItem(t, true);
\r
50520 this.over = true;
\r
50521 this.fireEvent("mouseover", this, e, t);
\r
50525 onMouseOut : function(e){
\r
50526 var t = this.findTargetItem(e);
\r
50528 if(t == this.activeItem && t.shouldDeactivate && t.shouldDeactivate(e)){
\r
50529 this.activeItem.deactivate();
\r
50530 delete this.activeItem;
\r
50533 this.over = false;
\r
50534 this.fireEvent("mouseout", this, e, t);
\r
50538 onScroll: function(e, t){
\r
50542 var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
\r
50543 ul.scrollTop += this.scrollIncrement * (top ? -1 : 1);
\r
50544 if(top ? ul.scrollTop <= 0 : ul.scrollTop + this.activeMax >= ul.scrollHeight){
\r
50545 this.onScrollerOut(null, t);
\r
50550 onScrollerIn: function(e, t){
\r
50551 var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
\r
50552 if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){
\r
50553 Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);
\r
50558 onScrollerOut: function(e, t){
\r
50559 Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);
\r
50563 * Displays this menu relative to another element
\r
50564 * @param {Mixed} element The element to align to
\r
50565 * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to
\r
50566 * the element (defaults to this.defaultAlign)
\r
50567 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
50569 show : function(el, pos, parentMenu){
\r
50570 if(this.floating){
\r
50571 this.parentMenu = parentMenu;
\r
50574 this.doLayout(false, true);
\r
50576 if(this.fireEvent('beforeshow', this) !== false){
\r
50577 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu, false);
\r
50580 Ext.menu.Menu.superclass.show.call(this);
\r
50585 * Displays this menu at a specific xy position
\r
50586 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
\r
50587 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
50589 showAt : function(xy, parentMenu, /* private: */_e){
\r
50590 this.parentMenu = parentMenu;
\r
50594 this.el.setXY(xy);
\r
50595 if(this.enableScrolling){
\r
50596 this.constrainScroll(xy[1]);
\r
50599 Ext.menu.Menu.superclass.onShow.call(this);
\r
50601 this.layout.doAutoSize();
\r
50603 this.el.repaint();
\r
50606 this.hidden = false;
\r
50608 this.fireEvent("show", this);
\r
50611 constrainScroll: function(y){
\r
50612 var max, full = this.ul.setHeight('auto').getHeight();
\r
50613 if(this.floating){
\r
50614 max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize().height - y;
\r
50616 max = this.getHeight();
\r
50618 if(full > max && max > 0){
\r
50619 this.activeMax = max - this.scrollerHeight * 2 - this.el.getFrameWidth('tb') - Ext.num(this.el.shadowOffset, 0);
\r
50620 this.ul.setHeight(this.activeMax);
\r
50621 this.createScrollers();
\r
50622 this.el.select('.x-menu-scroller').setDisplayed('');
\r
50624 this.ul.setHeight(full);
\r
50625 this.el.select('.x-menu-scroller').setDisplayed('none');
\r
50627 this.ul.dom.scrollTop = 0;
\r
50630 createScrollers: function(){
\r
50631 if(!this.scroller){
\r
50632 this.scroller = {
\r
50634 top: this.el.insertFirst({
\r
50636 cls: 'x-menu-scroller x-menu-scroller-top',
\r
50639 bottom: this.el.createChild({
\r
50641 cls: 'x-menu-scroller x-menu-scroller-bottom',
\r
50645 this.scroller.top.hover(this.onScrollerIn, this.onScrollerOut, this);
\r
50646 this.scroller.topRepeater = new Ext.util.ClickRepeater(this.scroller.top, {
\r
50648 click: this.onScroll.createDelegate(this, [null, this.scroller.top], false)
\r
50651 this.scroller.bottom.hover(this.onScrollerIn, this.onScrollerOut, this);
\r
50652 this.scroller.bottomRepeater = new Ext.util.ClickRepeater(this.scroller.bottom, {
\r
50654 click: this.onScroll.createDelegate(this, [null, this.scroller.bottom], false)
\r
50660 onLayout: function(){
\r
50661 if(this.isVisible()){
\r
50662 if(this.enableScrolling){
\r
50663 this.constrainScroll(this.el.getTop());
\r
50665 if(this.floating){
\r
50671 focus : function(){
\r
50672 if(!this.hidden){
\r
50673 this.doFocus.defer(50, this);
\r
50677 doFocus : function(){
\r
50678 if(!this.hidden){
\r
50679 this.focusEl.focus();
\r
50684 * Hides this menu and optionally all parent menus
\r
50685 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
\r
50687 hide : function(deep){
\r
50688 this.deepHide = deep;
\r
50689 Ext.menu.Menu.superclass.hide.call(this);
\r
50690 delete this.deepHide;
\r
50694 onHide: function(){
\r
50695 Ext.menu.Menu.superclass.onHide.call(this);
\r
50696 this.deactivateActive();
\r
50697 if(this.el && this.floating){
\r
50700 if(this.deepHide === true && this.parentMenu){
\r
50701 this.parentMenu.hide(true);
\r
50706 lookupComponent: function(c){
\r
50707 if(Ext.isString(c)){
\r
50708 c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);
\r
50709 this.applyDefaults(c);
\r
50711 if(Ext.isObject(c)){
\r
50712 c = this.getMenuItem(c);
\r
50713 }else if(c.tagName || c.el){ // element. Wrap it.
\r
50714 c = new Ext.BoxComponent({
\r
50722 applyDefaults : function(c){
\r
50723 if(!Ext.isString(c)){
\r
50724 c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);
\r
50725 var d = this.internalDefaults;
\r
50728 Ext.applyIf(c.initialConfig, d);
\r
50731 Ext.applyIf(c, d);
\r
50739 getMenuItem: function(config){
\r
50740 if(!config.isXType){
\r
50741 if(!config.xtype && Ext.isBoolean(config.checked)){
\r
50742 return new Ext.menu.CheckItem(config)
\r
50744 return Ext.create(config, this.defaultType);
\r
50750 * Adds a separator bar to the menu
\r
50751 * @return {Ext.menu.Item} The menu item that was added
\r
50753 addSeparator : function(){
\r
50754 return this.add(new Ext.menu.Separator());
\r
50758 * Adds an {@link Ext.Element} object to the menu
\r
50759 * @param {Mixed} el The element or DOM node to add, or its id
\r
50760 * @return {Ext.menu.Item} The menu item that was added
\r
50762 addElement : function(el){
\r
50763 return this.add(new Ext.menu.BaseItem(el));
\r
50767 * Adds an existing object based on {@link Ext.menu.BaseItem} to the menu
\r
50768 * @param {Ext.menu.Item} item The menu item to add
\r
50769 * @return {Ext.menu.Item} The menu item that was added
\r
50771 addItem : function(item){
\r
50772 return this.add(item);
\r
50776 * Creates a new {@link Ext.menu.Item} based an the supplied config object and adds it to the menu
\r
50777 * @param {Object} config A MenuItem config object
\r
50778 * @return {Ext.menu.Item} The menu item that was added
\r
50780 addMenuItem : function(config){
\r
50781 return this.add(this.getMenuItem(config));
\r
50785 * Creates a new {@link Ext.menu.TextItem} with the supplied text and adds it to the menu
\r
50786 * @param {String} text The text to display in the menu item
\r
50787 * @return {Ext.menu.Item} The menu item that was added
\r
50789 addText : function(text){
\r
50790 return this.add(new Ext.menu.TextItem(text));
\r
50794 onDestroy : function(){
\r
50795 Ext.menu.Menu.superclass.onDestroy.call(this);
\r
50796 Ext.menu.MenuMgr.unregister(this);
\r
50797 Ext.EventManager.removeResizeListener(this.hide, this);
\r
50798 if(this.keyNav) {
\r
50799 this.keyNav.disable();
\r
50801 var s = this.scroller;
\r
50803 Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
\r
50808 Ext.reg('menu', Ext.menu.Menu);
\r
50810 // MenuNav is a private utility class used internally by the Menu
\r
50811 Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
\r
50812 function up(e, m){
\r
50813 if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
\r
50814 m.tryActivate(m.items.length-1, -1);
\r
50817 function down(e, m){
\r
50818 if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
\r
50819 m.tryActivate(0, 1);
\r
50823 constructor: function(menu){
\r
50824 Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);
\r
50825 this.scope = this.menu = menu;
\r
50828 doRelay : function(e, h){
\r
50829 var k = e.getKey();
\r
50830 // Keystrokes within a form Field (e.g.: down in a Combo) do not navigate. Allow only TAB
\r
50831 if (this.menu.activeItem && this.menu.activeItem.isFormField && k != e.TAB) {
\r
50834 if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
\r
50835 this.menu.tryActivate(0, 1);
\r
50838 return h.call(this.scope || this, e, this.menu);
\r
50841 tab: function(e, m) {
\r
50843 if (e.shiftKey) {
\r
50854 right : function(e, m){
\r
50855 if(m.activeItem){
\r
50856 m.activeItem.expandMenu(true);
\r
50860 left : function(e, m){
\r
50862 if(m.parentMenu && m.parentMenu.activeItem){
\r
50863 m.parentMenu.activeItem.activate();
\r
50867 enter : function(e, m){
\r
50868 if(m.activeItem){
\r
50869 e.stopPropagation();
\r
50870 m.activeItem.onClick(e);
\r
50871 m.fireEvent("click", this, m.activeItem);
\r
50877 * @class Ext.menu.MenuMgr
50878 * Provides a common registry of all menu items on a page so that they can be easily accessed by id.
50881 Ext.menu.MenuMgr = function(){
50882 var menus, active, groups = {}, attached = false, lastShow = new Date();
50884 // private - called when first menu is created
50887 active = new Ext.util.MixedCollection();
50888 Ext.getDoc().addKeyListener(27, function(){
50889 if(active.length > 0){
50896 function hideAll(){
50897 if(active && active.length > 0){
50898 var c = active.clone();
50899 c.each(function(m){
50906 function onHide(m){
50908 if(active.length < 1){
50909 Ext.getDoc().un("mousedown", onMouseDown);
50915 function onShow(m){
50916 var last = active.last();
50917 lastShow = new Date();
50920 Ext.getDoc().on("mousedown", onMouseDown);
50924 m.getEl().setZIndex(parseInt(m.parentMenu.getEl().getStyle("z-index"), 10) + 3);
50925 m.parentMenu.activeChild = m;
50926 }else if(last && last.isVisible()){
50927 m.getEl().setZIndex(parseInt(last.getEl().getStyle("z-index"), 10) + 3);
50932 function onBeforeHide(m){
50934 m.activeChild.hide();
50936 if(m.autoHideTimer){
50937 clearTimeout(m.autoHideTimer);
50938 delete m.autoHideTimer;
50943 function onBeforeShow(m){
50944 var pm = m.parentMenu;
50945 if(!pm && !m.allowOtherMenus){
50947 }else if(pm && pm.activeChild){
50948 pm.activeChild.hide();
50953 function onMouseDown(e){
50954 if(lastShow.getElapsed() > 50 && active.length > 0 && !e.getTarget(".x-menu")){
50960 function onBeforeCheck(mi, state){
50962 var g = groups[mi.group];
50963 for(var i = 0, l = g.length; i < l; i++){
50965 g[i].setChecked(false);
50974 * Hides all menus that are currently visible
50976 hideAll : function(){
50981 register : function(menu){
50985 menus[menu.id] = menu;
50986 menu.on("beforehide", onBeforeHide);
50987 menu.on("hide", onHide);
50988 menu.on("beforeshow", onBeforeShow);
50989 menu.on("show", onShow);
50990 var g = menu.group;
50991 if(g && menu.events["checkchange"]){
50995 groups[g].push(menu);
50996 menu.on("checkchange", onCheck);
51001 * Returns a {@link Ext.menu.Menu} object
51002 * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
51003 * be used to generate and return a new Menu instance.
51004 * @return {Ext.menu.Menu} The specified menu, or null if none are found
51006 get : function(menu){
51007 if(typeof menu == "string"){ // menu id
51008 if(!menus){ // not initialized, no menus to return
51011 return menus[menu];
51012 }else if(menu.events){ // menu instance
51014 }else if(typeof menu.length == 'number'){ // array of menu items?
51015 return new Ext.menu.Menu({items:menu});
51016 }else{ // otherwise, must be a config
51017 return Ext.create(menu, 'menu');
51022 unregister : function(menu){
51023 delete menus[menu.id];
51024 menu.un("beforehide", onBeforeHide);
51025 menu.un("hide", onHide);
51026 menu.un("beforeshow", onBeforeShow);
51027 menu.un("show", onShow);
51028 var g = menu.group;
51029 if(g && menu.events["checkchange"]){
51030 groups[g].remove(menu);
51031 menu.un("checkchange", onCheck);
51036 registerCheckable : function(menuItem){
51037 var g = menuItem.group;
51042 groups[g].push(menuItem);
51043 menuItem.on("beforecheckchange", onBeforeCheck);
51048 unregisterCheckable : function(menuItem){
51049 var g = menuItem.group;
51051 groups[g].remove(menuItem);
51052 menuItem.un("beforecheckchange", onBeforeCheck);
51056 getCheckedItem : function(groupId){
51057 var g = groups[groupId];
51059 for(var i = 0, l = g.length; i < l; i++){
51068 setCheckedItem : function(groupId, itemId){
51069 var g = groups[groupId];
51071 for(var i = 0, l = g.length; i < l; i++){
51072 if(g[i].id == itemId){
51073 g[i].setChecked(true);
51082 * @class Ext.menu.BaseItem
51083 * @extends Ext.Component
51084 * The base class for all items that render into menus. BaseItem provides default rendering, activated state
51085 * management and base configuration options shared by all menu components.
51087 * Creates a new BaseItem
51088 * @param {Object} config Configuration options
51089 * @xtype menubaseitem
51091 Ext.menu.BaseItem = function(config){
51092 Ext.menu.BaseItem.superclass.constructor.call(this, config);
51097 * Fires when this item is clicked
51098 * @param {Ext.menu.BaseItem} this
51099 * @param {Ext.EventObject} e
51104 * Fires when this item is activated
51105 * @param {Ext.menu.BaseItem} this
51109 * @event deactivate
51110 * Fires when this item is deactivated
51111 * @param {Ext.menu.BaseItem} this
51117 this.on("click", this.handler, this.scope);
51121 Ext.extend(Ext.menu.BaseItem, Ext.Component, {
51123 * @property parentMenu
51124 * @type Ext.menu.Menu
51125 * The parent Menu of this Item.
51128 * @cfg {Function} handler
51129 * A function that will handle the click event of this menu item (optional).
51130 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
51131 * <li><code>b</code> : Item<div class="sub-desc">This menu Item.</div></li>
51132 * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
51136 * @cfg {Object} scope
51137 * The scope (<tt><b>this</b></tt> reference) in which the handler function will be called.
51140 * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to false)
51142 canActivate : false,
51144 * @cfg {String} activeClass The CSS class to use when the item becomes activated (defaults to "x-menu-item-active")
51146 activeClass : "x-menu-item-active",
51148 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to true)
51150 hideOnClick : true,
51152 * @cfg {Number} clickHideDelay Length of time in milliseconds to wait before hiding after a click (defaults to 100)
51154 clickHideDelay : 1,
51157 ctype : "Ext.menu.BaseItem",
51160 actionMode : "container",
51163 onRender : function(container, position){
51164 Ext.menu.BaseItem.superclass.onRender.apply(this, arguments);
51165 if(this.ownerCt && this.ownerCt instanceof Ext.menu.Menu){
51166 this.parentMenu = this.ownerCt;
51168 this.container.addClass('x-menu-list-item');
51169 this.mon(this.el, 'click', this.onClick, this);
51170 this.mon(this.el, 'mouseenter', this.activate, this);
51171 this.mon(this.el, 'mouseleave', this.deactivate, this);
51176 * Sets the function that will handle click events for this item (equivalent to passing in the {@link #handler}
51177 * config property). If an existing handler is already registered, it will be unregistered for you.
51178 * @param {Function} handler The function that should be called on click
51179 * @param {Object} scope The scope that should be passed to the handler
51181 setHandler : function(handler, scope){
51183 this.un("click", this.handler, this.scope);
51185 this.on("click", this.handler = handler, this.scope = scope);
51189 onClick : function(e){
51190 if(!this.disabled && this.fireEvent("click", this, e) !== false
51191 && (this.parentMenu && this.parentMenu.fireEvent("itemclick", this, e) !== false)){
51192 this.handleClick(e);
51199 activate : function(){
51203 var li = this.container;
51204 li.addClass(this.activeClass);
51205 this.region = li.getRegion().adjust(2, 2, -2, -2);
51206 this.fireEvent("activate", this);
51211 deactivate : function(){
51212 this.container.removeClass(this.activeClass);
51213 this.fireEvent("deactivate", this);
51217 shouldDeactivate : function(e){
51218 return !this.region || !this.region.contains(e.getPoint());
51222 handleClick : function(e){
51223 if(this.hideOnClick){
51224 this.parentMenu.hide.defer(this.clickHideDelay, this.parentMenu, [true]);
51228 // private. Do nothing
51229 expandMenu : Ext.emptyFn,
51231 // private. Do nothing
51232 hideMenu : Ext.emptyFn
51234 Ext.reg('menubaseitem', Ext.menu.BaseItem);/**
51235 * @class Ext.menu.TextItem
51236 * @extends Ext.menu.BaseItem
51237 * Adds a static text string to a menu, usually used as either a heading or group separator.
51239 * Creates a new TextItem
51240 * @param {Object/String} config If config is a string, it is used as the text to display, otherwise it
51241 * is applied as a config object (and should contain a <tt>text</tt> property).
51242 * @xtype menutextitem
51244 Ext.menu.TextItem = function(cfg){
51245 if(typeof cfg == 'string'){
51248 Ext.menu.TextItem.superclass.constructor.call(this, cfg);
51251 Ext.extend(Ext.menu.TextItem, Ext.menu.BaseItem, {
51253 * @cfg {String} text The text to display for this item (defaults to '')
51256 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
51258 hideOnClick : false,
51260 * @cfg {String} itemCls The default CSS class to use for text items (defaults to "x-menu-text")
51262 itemCls : "x-menu-text",
51265 onRender : function(){
51266 var s = document.createElement("span");
51267 s.className = this.itemCls;
51268 s.innerHTML = this.text;
51270 Ext.menu.TextItem.superclass.onRender.apply(this, arguments);
51273 Ext.reg('menutextitem', Ext.menu.TextItem);/**
51274 * @class Ext.menu.Separator
51275 * @extends Ext.menu.BaseItem
51276 * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
51277 * add one of these by using "-" in you call to add() or in your items config rather than creating one directly.
51279 * @param {Object} config Configuration options
51280 * @xtype menuseparator
51282 Ext.menu.Separator = function(config){
51283 Ext.menu.Separator.superclass.constructor.call(this, config);
51286 Ext.extend(Ext.menu.Separator, Ext.menu.BaseItem, {
51288 * @cfg {String} itemCls The default CSS class to use for separators (defaults to "x-menu-sep")
51290 itemCls : "x-menu-sep",
51292 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
51294 hideOnClick : false,
51297 * @cfg {String} activeClass
51303 onRender : function(li){
51304 var s = document.createElement("span");
51305 s.className = this.itemCls;
51306 s.innerHTML = " ";
51308 li.addClass("x-menu-sep-li");
51309 Ext.menu.Separator.superclass.onRender.apply(this, arguments);
51312 Ext.reg('menuseparator', Ext.menu.Separator);/**
\r
51313 * @class Ext.menu.Item
\r
51314 * @extends Ext.menu.BaseItem
\r
51315 * A base class for all menu items that require menu-related functionality (like sub-menus) and are not static
\r
51316 * display items. Item extends the base functionality of {@link Ext.menu.BaseItem} by adding menu-specific
\r
51317 * activation and click handling.
\r
51319 * Creates a new Item
\r
51320 * @param {Object} config Configuration options
\r
51321 * @xtype menuitem
\r
51323 Ext.menu.Item = function(config){
\r
51324 Ext.menu.Item.superclass.constructor.call(this, config);
\r
51326 this.menu = Ext.menu.MenuMgr.get(this.menu);
\r
51329 Ext.extend(Ext.menu.Item, Ext.menu.BaseItem, {
\r
51332 * @type Ext.menu.Menu
\r
51333 * The submenu associated with this Item if one was configured.
\r
51336 * @cfg {Mixed} menu (optional) Either an instance of {@link Ext.menu.Menu} or the config object for an
\r
51337 * {@link Ext.menu.Menu} which acts as the submenu when this item is activated.
\r
51340 * @cfg {String} icon The path to an icon to display in this item (defaults to Ext.BLANK_IMAGE_URL). If
\r
51341 * icon is specified {@link #iconCls} should not be.
\r
51344 * @cfg {String} iconCls A CSS class that specifies a background image that will be used as the icon for
\r
51345 * this item (defaults to ''). If iconCls is specified {@link #icon} should not be.
\r
51348 * @cfg {String} text The text to display in this item (defaults to '').
\r
51351 * @cfg {String} href The href attribute to use for the underlying anchor link (defaults to '#').
\r
51354 * @cfg {String} hrefTarget The target attribute to use for the underlying anchor link (defaults to '').
\r
51357 * @cfg {String} itemCls The default CSS class to use for menu items (defaults to 'x-menu-item')
\r
51359 itemCls : 'x-menu-item',
\r
51361 * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to true)
\r
51363 canActivate : true,
\r
51365 * @cfg {Number} showDelay Length of time in milliseconds to wait before showing this item (defaults to 200)
\r
51368 // doc'd in BaseItem
\r
51372 ctype: 'Ext.menu.Item',
\r
51375 onRender : function(container, position){
\r
51376 if (!this.itemTpl) {
\r
51377 this.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(
\r
51378 '<a id="{id}" class="{cls}" hidefocus="true" unselectable="on" href="{href}"',
\r
51379 '<tpl if="hrefTarget">',
\r
51380 ' target="{hrefTarget}"',
\r
51383 '<img src="{icon}" class="x-menu-item-icon {iconCls}"/>',
\r
51384 '<span class="x-menu-item-text">{text}</span>',
\r
51388 var a = this.getTemplateArgs();
\r
51389 this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true);
\r
51390 this.iconEl = this.el.child('img.x-menu-item-icon');
\r
51391 this.textEl = this.el.child('.x-menu-item-text');
\r
51392 Ext.menu.Item.superclass.onRender.call(this, container, position);
\r
51395 getTemplateArgs: function() {
\r
51398 cls: this.itemCls + (this.menu ? ' x-menu-item-arrow' : '') + (this.cls ? ' ' + this.cls : ''),
\r
51399 href: this.href || '#',
\r
51400 hrefTarget: this.hrefTarget,
\r
51401 icon: this.icon || Ext.BLANK_IMAGE_URL,
\r
51402 iconCls: this.iconCls || '',
\r
51403 text: this.itemText||this.text||' '
\r
51408 * Sets the text to display in this menu item
\r
51409 * @param {String} text The text to display
\r
51411 setText : function(text){
\r
51412 this.text = text||' ';
\r
51413 if(this.rendered){
\r
51414 this.textEl.update(this.text);
\r
51415 this.parentMenu.layout.doAutoSize();
\r
51420 * Sets the CSS class to apply to the item's icon element
\r
51421 * @param {String} cls The CSS class to apply
\r
51423 setIconClass : function(cls){
\r
51424 var oldCls = this.iconCls;
\r
51425 this.iconCls = cls;
\r
51426 if(this.rendered){
\r
51427 this.iconEl.replaceClass(oldCls, this.iconCls);
\r
51432 beforeDestroy: function(){
\r
51434 this.menu.destroy();
\r
51436 Ext.menu.Item.superclass.beforeDestroy.call(this);
\r
51440 handleClick : function(e){
\r
51441 if(!this.href){ // if no link defined, stop the event automatically
\r
51444 Ext.menu.Item.superclass.handleClick.apply(this, arguments);
\r
51448 activate : function(autoExpand){
\r
51449 if(Ext.menu.Item.superclass.activate.apply(this, arguments)){
\r
51452 this.expandMenu();
\r
51459 shouldDeactivate : function(e){
\r
51460 if(Ext.menu.Item.superclass.shouldDeactivate.call(this, e)){
\r
51461 if(this.menu && this.menu.isVisible()){
\r
51462 return !this.menu.getEl().getRegion().contains(e.getPoint());
\r
51470 deactivate : function(){
\r
51471 Ext.menu.Item.superclass.deactivate.apply(this, arguments);
\r
51476 expandMenu : function(autoActivate){
\r
51477 if(!this.disabled && this.menu){
\r
51478 clearTimeout(this.hideTimer);
\r
51479 delete this.hideTimer;
\r
51480 if(!this.menu.isVisible() && !this.showTimer){
\r
51481 this.showTimer = this.deferExpand.defer(this.showDelay, this, [autoActivate]);
\r
51482 }else if (this.menu.isVisible() && autoActivate){
\r
51483 this.menu.tryActivate(0, 1);
\r
51489 deferExpand : function(autoActivate){
\r
51490 delete this.showTimer;
\r
51491 this.menu.show(this.container, this.parentMenu.subMenuAlign || 'tl-tr?', this.parentMenu);
\r
51492 if(autoActivate){
\r
51493 this.menu.tryActivate(0, 1);
\r
51498 hideMenu : function(){
\r
51499 clearTimeout(this.showTimer);
\r
51500 delete this.showTimer;
\r
51501 if(!this.hideTimer && this.menu && this.menu.isVisible()){
\r
51502 this.hideTimer = this.deferHide.defer(this.hideDelay, this);
\r
51507 deferHide : function(){
\r
51508 delete this.hideTimer;
\r
51509 if(this.menu.over){
\r
51510 this.parentMenu.setActiveItem(this, false);
\r
51512 this.menu.hide();
\r
51516 Ext.reg('menuitem', Ext.menu.Item);/**
51517 * @class Ext.menu.CheckItem
51518 * @extends Ext.menu.Item
51519 * Adds a menu item that contains a checkbox by default, but can also be part of a radio group.
51521 * Creates a new CheckItem
51522 * @param {Object} config Configuration options
51523 * @xtype menucheckitem
51525 Ext.menu.CheckItem = function(config){
51526 Ext.menu.CheckItem.superclass.constructor.call(this, config);
51529 * @event beforecheckchange
51530 * Fires before the checked value is set, providing an opportunity to cancel if needed
51531 * @param {Ext.menu.CheckItem} this
51532 * @param {Boolean} checked The new checked value that will be set
51534 "beforecheckchange" ,
51536 * @event checkchange
51537 * Fires after the checked value has been set
51538 * @param {Ext.menu.CheckItem} this
51539 * @param {Boolean} checked The checked value that was set
51544 * A function that handles the checkchange event. The function is undefined by default, but if an implementation
51545 * is provided, it will be called automatically when the checkchange event fires.
51546 * @param {Ext.menu.CheckItem} this
51547 * @param {Boolean} checked The checked value that was set
51548 * @method checkHandler
51550 if(this.checkHandler){
51551 this.on('checkchange', this.checkHandler, this.scope);
51553 Ext.menu.MenuMgr.registerCheckable(this);
51555 Ext.extend(Ext.menu.CheckItem, Ext.menu.Item, {
51557 * @cfg {String} group
51558 * All check items with the same group name will automatically be grouped into a single-select
51559 * radio button group (defaults to '')
51562 * @cfg {String} itemCls The default CSS class to use for check items (defaults to "x-menu-item x-menu-check-item")
51564 itemCls : "x-menu-item x-menu-check-item",
51566 * @cfg {String} groupClass The default CSS class to use for radio group check items (defaults to "x-menu-group-item")
51568 groupClass : "x-menu-group-item",
51571 * @cfg {Boolean} checked True to initialize this checkbox as checked (defaults to false). Note that
51572 * if this checkbox is part of a radio group (group = true) only the last item in the group that is
51573 * initialized with checked = true will be rendered as checked.
51578 ctype: "Ext.menu.CheckItem",
51581 onRender : function(c){
51582 Ext.menu.CheckItem.superclass.onRender.apply(this, arguments);
51584 this.el.addClass(this.groupClass);
51587 this.checked = false;
51588 this.setChecked(true, true);
51593 destroy : function(){
51594 Ext.menu.MenuMgr.unregisterCheckable(this);
51595 Ext.menu.CheckItem.superclass.destroy.apply(this, arguments);
51599 * Set the checked state of this item
51600 * @param {Boolean} checked The new checked value
51601 * @param {Boolean} suppressEvent (optional) True to prevent the checkchange event from firing (defaults to false)
51603 setChecked : function(state, suppressEvent){
51604 if(this.checked != state && this.fireEvent("beforecheckchange", this, state) !== false){
51605 if(this.container){
51606 this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
51608 this.checked = state;
51609 if(suppressEvent !== true){
51610 this.fireEvent("checkchange", this, state);
51616 handleClick : function(e){
51617 if(!this.disabled && !(this.checked && this.group)){// disable unselect on radio item
51618 this.setChecked(!this.checked);
51620 Ext.menu.CheckItem.superclass.handleClick.apply(this, arguments);
51623 Ext.reg('menucheckitem', Ext.menu.CheckItem);/**
\r
51624 * @class Ext.menu.DateMenu
\r
51625 * @extends Ext.menu.Menu
\r
51626 * A menu containing a {@link Ext.DatePicker} Component.
\r
51627 * @xtype datemenu
\r
51629 Ext.menu.DateMenu = Ext.extend(Ext.menu.Menu, {
\r
51631 * @cfg {Boolean} enableScrolling
\r
51634 enableScrolling: false,
\r
51637 * @cfg {Boolean} hideOnClick
\r
51638 * False to continue showing the menu after a date is selected, defaults to true.
\r
51640 hideOnClick: true,
\r
51643 * @cfg {Number} maxHeight
\r
51647 * @cfg {Number} scrollIncrement
\r
51651 * @property picker
\r
51652 * @type DatePicker
\r
51653 * The {@link Ext.DatePicker} instance for this DateMenu
\r
51655 cls: 'x-date-menu',
\r
51663 * @event itemclick
\r
51667 initComponent: function(){
\r
51668 this.on('beforeshow', this.onBeforeShow, this);
\r
51669 if(this.strict = (Ext.isIE7 && Ext.isStrict)){
\r
51670 this.on('show', this.onShow, this, {single: true, delay: 20});
\r
51672 Ext.apply(this, {
\r
51674 showSeparator: false,
\r
51675 items: this.picker = new Ext.DatePicker(Ext.apply({
\r
51676 internalRender: this.strict || !Ext.isIE,
\r
51677 ctCls: 'x-menu-date-item'
\r
51678 }, this.initialConfig))
\r
51680 this.picker.purgeListeners();
\r
51681 Ext.menu.DateMenu.superclass.initComponent.call(this);
\r
51682 this.relayEvents(this.picker, ["select"]);
\r
51683 this.on('select', this.menuHide, this);
\r
51684 if(this.handler){
\r
51685 this.on('select', this.handler, this.scope || this);
\r
51689 menuHide: function() {
\r
51690 if(this.hideOnClick){
\r
51695 onBeforeShow: function(){
\r
51697 this.picker.hideMonthPicker(true);
\r
51701 onShow: function(){
\r
51702 var el = this.picker.getEl();
\r
51703 el.setWidth(el.getWidth()); //nasty hack for IE7 strict mode
\r
51706 Ext.reg('datemenu', Ext.menu.DateMenu);/**
\r
51707 * @class Ext.menu.ColorMenu
\r
51708 * @extends Ext.menu.Menu
\r
51709 * A menu containing a {@link Ext.ColorPalette} Component.
\r
51710 * @xtype colormenu
\r
51712 Ext.menu.ColorMenu = Ext.extend(Ext.menu.Menu, {
\r
51714 * @cfg {Boolean} enableScrolling
\r
51717 enableScrolling: false,
\r
51720 * @cfg {Boolean} hideOnClick
\r
51721 * False to continue showing the menu after a color is selected, defaults to true.
\r
51723 hideOnClick: true,
\r
51726 * @cfg {Number} maxHeight
\r
51730 * @cfg {Number} scrollIncrement
\r
51734 * @property palette
\r
51735 * @type ColorPalette
\r
51736 * The {@link Ext.ColorPalette} instance for this ColorMenu
\r
51746 * @event itemclick
\r
51750 initComponent: function(){
\r
51751 Ext.apply(this, {
\r
51753 showSeparator: false,
\r
51754 items: this.palette = new Ext.ColorPalette(this.initialConfig)
\r
51756 this.palette.purgeListeners();
\r
51757 Ext.menu.ColorMenu.superclass.initComponent.call(this);
\r
51758 this.relayEvents(this.palette, ['select']);
\r
51759 this.on('select', this.menuHide, this);
\r
51760 if(this.handler){
\r
51761 this.on('select', this.handler, this.scope || this)
\r
51765 menuHide: function(){
\r
51766 if(this.hideOnClick){
\r
51771 Ext.reg('colormenu', Ext.menu.ColorMenu);/**
51772 * @class Ext.form.Field
51773 * @extends Ext.BoxComponent
51774 * Base class for form fields that provides default event handling, sizing, value handling and other functionality.
51776 * Creates a new Field
51777 * @param {Object} config Configuration options
51780 Ext.form.Field = Ext.extend(Ext.BoxComponent, {
51782 * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults
51783 * to "text"). The types "file" and "password" must be used to render those field types currently -- there are
51784 * no separate Ext components for those. Note that if you use <tt>inputType:'file'</tt>, {@link #emptyText}
51785 * is not supported and should be avoided.
51788 * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
51789 * not those which are built via applyTo (defaults to undefined).
51792 * @cfg {Mixed} value A value to initialize this field with (defaults to undefined).
51795 * @cfg {String} name The field's HTML name attribute (defaults to "").
51796 * <b>Note</b>: this property must be set if this field is to be automatically included with
51797 * {@link Ext.form.BasicForm#submit form submit()}.
51800 * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to "").
51804 * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid")
51806 invalidClass : "x-form-invalid",
51808 * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
51809 * (defaults to "The value in this field is invalid")
51811 invalidText : "The value in this field is invalid",
51813 * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus")
51815 focusClass : "x-form-focus",
51817 * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
51818 automatic validation (defaults to "keyup").
51820 validationEvent : "keyup",
51822 * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
51824 validateOnBlur : true,
51826 * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation
51827 * is initiated (defaults to 250)
51829 validationDelay : 250,
51831 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
51832 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
51833 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
51834 * <pre><code>{tag: "input", type: "text", size: "20", autocomplete: "off"}</code></pre>
51836 defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "off"},
51838 * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field")
51840 fieldClass : "x-form-field",
51842 * @cfg {String} msgTarget The location where error text should display. Should be one of the following values
51843 * (defaults to 'qtip'):
51846 ----------- ----------------------------------------------------------------------
51847 qtip Display a quick tip when the user hovers over the field
51848 title Display a default browser title attribute popup
51849 under Add a block div beneath the field containing the error text
51850 side Add an error icon to the right of the field with a popup on hover
51851 [element id] Add the error text directly to the innerHTML of the specified element
51854 msgTarget : 'qtip',
51856 * @cfg {String} msgFx <b>Experimental</b> The effect used when displaying a validation message under the field
51857 * (defaults to 'normal').
51861 * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
51862 * (defaults to <tt>false</tt>).
51863 * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
51864 * Setting <code>readOnly=true</code>, for example, will not disable triggering a
51865 * ComboBox or DateField; it gives you the option of forcing the user to choose
51866 * via the trigger without typing in the text box. To hide the trigger use
51867 * <code>{@link Ext.form.TriggerField#hideTrigger hideTrigger}</code>.</p>
51871 * @cfg {Boolean} disabled True to disable the field (defaults to false).
51872 * <p>Be aware that conformant with the <a href="http://www.w3.org/TR/html401/interact/forms.html#h-17.12.1">HTML specification</a>,
51873 * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.</p>
51878 isFormField : true,
51884 initComponent : function(){
51885 Ext.form.Field.superclass.initComponent.call(this);
51889 * Fires when this field receives input focus.
51890 * @param {Ext.form.Field} this
51895 * Fires when this field loses input focus.
51896 * @param {Ext.form.Field} this
51900 * @event specialkey
51901 * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
51902 * To handle other keys see {@link Ext.Panel#keys} or {@link Ext.KeyMap}.
51903 * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
51904 * For example: <pre><code>
51905 var form = new Ext.form.FormPanel({
51908 fieldLabel: 'Field 1',
51912 fieldLabel: 'Field 2',
51915 specialkey: function(field, e){
51916 // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
51917 // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
51918 if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
51919 var form = field.ownerCt.getForm();
51929 * @param {Ext.form.Field} this
51930 * @param {Ext.EventObject} e The event object
51935 * Fires just before the field blurs if the field value has changed.
51936 * @param {Ext.form.Field} this
51937 * @param {Mixed} newValue The new value
51938 * @param {Mixed} oldValue The original value
51943 * Fires after the field has been marked as invalid.
51944 * @param {Ext.form.Field} this
51945 * @param {String} msg The validation message
51950 * Fires after the field has been validated with no errors.
51951 * @param {Ext.form.Field} this
51958 * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
51959 * attribute of the field if available.
51960 * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
51962 getName: function(){
51963 return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || '';
51967 onRender : function(ct, position){
51969 var cfg = this.getAutoCreate();
51972 cfg.name = this.name || this.id;
51974 if(this.inputType){
51975 cfg.type = this.inputType;
51979 Ext.form.Field.superclass.onRender.call(this, ct, position);
51981 var type = this.el.dom.type;
51983 if(type == 'password'){
51986 this.el.addClass('x-form-'+type);
51989 this.el.dom.readOnly = true;
51991 if(this.tabIndex !== undefined){
51992 this.el.dom.setAttribute('tabIndex', this.tabIndex);
51995 this.el.addClass([this.fieldClass, this.cls]);
51999 getItemCt : function(){
52000 return this.el.up('.x-form-item', 4);
52004 initValue : function(){
52005 if(this.value !== undefined){
52006 this.setValue(this.value);
52007 }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){
52008 this.setValue(this.el.dom.value);
52011 * The original value of the field as configured in the {@link #value} configuration, or
52012 * as loaded by the last form load operation if the form's {@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
52013 * setting is <code>true</code>.
52015 * @property originalValue
52017 this.originalValue = this.getValue();
52021 * <p>Returns true if the value of this Field has been changed from its original value.
52022 * Will return false if the field is disabled or has not been rendered yet.</p>
52023 * <p>Note that if the owning {@link Ext.form.BasicForm form} was configured with
52024 * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
52025 * then the <i>original value</i> is updated when the values are loaded by
52026 * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.</p>
52027 * @return {Boolean} True if this field has been changed from its original value (and
52028 * is not disabled), false otherwise.
52030 isDirty : function() {
52031 if(this.disabled || !this.rendered) {
52034 return String(this.getValue()) !== String(this.originalValue);
52038 afterRender : function(){
52039 Ext.form.Field.superclass.afterRender.call(this);
52045 fireKey : function(e){
52046 if(e.isSpecialKey()){
52047 this.fireEvent("specialkey", this, e);
52052 * Resets the current field value to the originally loaded value and clears any validation messages.
52053 * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
52055 reset : function(){
52056 this.setValue(this.originalValue);
52057 this.clearInvalid();
52061 initEvents : function(){
52062 this.mon(this.el, Ext.EventManager.useKeydown ? "keydown" : "keypress", this.fireKey, this);
52063 this.mon(this.el, 'focus', this.onFocus, this);
52065 // fix weird FF/Win editor issue when changing OS window focus
52066 var o = this.inEditor && Ext.isWindows && Ext.isGecko ? {buffer:10} : null;
52067 this.mon(this.el, 'blur', this.onBlur, this, o);
52071 onFocus : function(){
52072 if(this.focusClass){
52073 this.el.addClass(this.focusClass);
52075 if(!this.hasFocus){
52076 this.hasFocus = true;
52077 this.startValue = this.getValue();
52078 this.fireEvent("focus", this);
52083 beforeBlur : Ext.emptyFn,
52086 onBlur : function(){
52088 if(this.focusClass){
52089 this.el.removeClass(this.focusClass);
52091 this.hasFocus = false;
52092 if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
52095 var v = this.getValue();
52096 if(String(v) !== String(this.startValue)){
52097 this.fireEvent('change', this, v, this.startValue);
52099 this.fireEvent("blur", this);
52103 * Returns whether or not the field value is currently valid
52104 * @param {Boolean} preventMark True to disable marking the field invalid
52105 * @return {Boolean} True if the value is valid, else false
52107 isValid : function(preventMark){
52111 var restore = this.preventMark;
52112 this.preventMark = preventMark === true;
52113 var v = this.validateValue(this.processValue(this.getRawValue()));
52114 this.preventMark = restore;
52119 * Validates the field value
52120 * @return {Boolean} True if the value is valid, else false
52122 validate : function(){
52123 if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
52124 this.clearInvalid();
52130 // protected - should be overridden by subclasses if necessary to prepare raw values for validation
52131 processValue : function(value){
52136 // Subclasses should provide the validation implementation by overriding this
52137 validateValue : function(value){
52142 * Mark this field as invalid, using {@link #msgTarget} to determine how to display the error and
52143 * applying {@link #invalidClass} to the field's element.
52144 * @param {String} msg (optional) The validation message (defaults to {@link #invalidText})
52146 markInvalid : function(msg){
52147 if(!this.rendered || this.preventMark){ // not rendered
52150 msg = msg || this.invalidText;
52152 var mt = this.getMessageHandler();
52154 mt.mark(this, msg);
52155 }else if(this.msgTarget){
52156 this.el.addClass(this.invalidClass);
52157 var t = Ext.getDom(this.msgTarget);
52160 t.style.display = this.msgDisplay;
52163 this.fireEvent('invalid', this, msg);
52167 * Clear any invalid styles/messages for this field
52169 clearInvalid : function(){
52170 if(!this.rendered || this.preventMark){ // not rendered
52173 this.el.removeClass(this.invalidClass);
52174 var mt = this.getMessageHandler();
52177 }else if(this.msgTarget){
52178 this.el.removeClass(this.invalidClass);
52179 var t = Ext.getDom(this.msgTarget);
52182 t.style.display = 'none';
52185 this.fireEvent('valid', this);
52189 getMessageHandler : function(){
52190 return Ext.form.MessageTargets[this.msgTarget];
52194 getErrorCt : function(){
52195 return this.el.findParent('.x-form-element', 5, true) || // use form element wrap if available
52196 this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap
52200 alignErrorIcon : function(){
52201 this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]);
52205 * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
52206 * @return {Mixed} value The field value
52208 getRawValue : function(){
52209 var v = this.rendered ? this.el.getValue() : Ext.value(this.value, '');
52210 if(v === this.emptyText){
52217 * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
52218 * @return {Mixed} value The field value
52220 getValue : function(){
52221 if(!this.rendered) {
52224 var v = this.el.getValue();
52225 if(v === this.emptyText || v === undefined){
52232 * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}.
52233 * @param {Mixed} value The value to set
52234 * @return {Mixed} value The field value that is set
52236 setRawValue : function(v){
52237 return (this.el.dom.value = (Ext.isEmpty(v) ? '' : v));
52241 * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
52242 * @param {Mixed} value The value to set
52243 * @return {Ext.form.Field} this
52245 setValue : function(v){
52248 this.el.dom.value = (Ext.isEmpty(v) ? '' : v);
52254 // private, does not work for all fields
52255 append : function(v){
52256 this.setValue([this.getValue(), v].join(''));
52260 adjustSize : function(w, h){
52261 var s = Ext.form.Field.superclass.adjustSize.call(this, w, h);
52262 s.width = this.adjustWidth(this.el.dom.tagName, s.width);
52264 var ct = this.getItemCt();
52265 s.width -= ct.getFrameWidth('lr');
52266 s.height -= ct.getFrameWidth('tb');
52272 adjustWidth : function(tag, w){
52273 if(typeof w == 'number' && (Ext.isIE && (Ext.isIE6 || !Ext.isStrict)) && /input|textarea/i.test(tag) && !this.inEditor){
52280 * @cfg {Boolean} autoWidth @hide
52283 * @cfg {Boolean} autoHeight @hide
52287 * @cfg {String} autoEl @hide
52292 Ext.form.MessageTargets = {
52294 mark: function(field, msg){
52295 field.el.addClass(field.invalidClass);
52296 field.el.dom.qtip = msg;
52297 field.el.dom.qclass = 'x-form-invalid-tip';
52298 if(Ext.QuickTips){ // fix for floating editors interacting with DND
52299 Ext.QuickTips.enable();
52302 clear: function(field){
52303 field.el.removeClass(field.invalidClass);
52304 field.el.dom.qtip = '';
52308 mark: function(field, msg){
52309 field.el.addClass(field.invalidClass);
52310 field.el.dom.title = msg;
52312 clear: function(field){
52313 field.el.dom.title = '';
52317 mark: function(field, msg){
52318 field.el.addClass(field.invalidClass);
52319 if(!field.errorEl){
52320 var elp = field.getErrorCt();
52321 if(!elp){ // field has no container el
52322 field.el.dom.title = msg;
52325 field.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
52326 field.errorEl.setWidth(elp.getWidth(true)-20);
52328 field.errorEl.update(msg);
52329 Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field);
52331 clear: function(field){
52332 field.el.removeClass(field.invalidClass);
52334 Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field);
52336 field.el.dom.title = '';
52341 mark: function(field, msg){
52342 field.el.addClass(field.invalidClass);
52343 if(!field.errorIcon){
52344 var elp = field.getErrorCt();
52345 if(!elp){ // field has no container el
52346 field.el.dom.title = msg;
52349 field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
52351 field.alignErrorIcon();
52352 field.errorIcon.dom.qtip = msg;
52353 field.errorIcon.dom.qclass = 'x-form-invalid-tip';
52354 field.errorIcon.show();
52355 field.on('resize', field.alignErrorIcon, field);
52357 clear: function(field){
52358 field.el.removeClass(field.invalidClass);
52359 if(field.errorIcon){
52360 field.errorIcon.dom.qtip = '';
52361 field.errorIcon.hide();
52362 field.un('resize', field.alignErrorIcon, field);
52364 field.el.dom.title = '';
52370 // anything other than normal should be considered experimental
52371 Ext.form.Field.msgFx = {
52373 show: function(msgEl, f){
52374 msgEl.setDisplayed('block');
52377 hide : function(msgEl, f){
52378 msgEl.setDisplayed(false).update('');
52383 show: function(msgEl, f){
52384 msgEl.slideIn('t', {stopFx:true});
52387 hide : function(msgEl, f){
52388 msgEl.slideOut('t', {stopFx:true,useDisplay:true});
52393 show: function(msgEl, f){
52394 msgEl.fixDisplay();
52395 msgEl.alignTo(f.el, 'tl-tr');
52396 msgEl.slideIn('l', {stopFx:true});
52399 hide : function(msgEl, f){
52400 msgEl.slideOut('l', {stopFx:true,useDisplay:true});
52404 Ext.reg('field', Ext.form.Field);
52406 * @class Ext.form.TextField
52407 * @extends Ext.form.Field
52408 * <p>Basic text field. Can be used as a direct replacement for traditional text inputs,
52409 * or as the base class for more sophisticated input controls (like {@link Ext.form.TextArea}
52410 * and {@link Ext.form.ComboBox}).</p>
52411 * <p><b><u>Validation</u></b></p>
52412 * <p>Field validation is processed in a particular order. If validation fails at any particular
52413 * step the validation routine halts.</p>
52414 * <div class="mdetail-params"><ul>
52415 * <li><b>1. Field specific validator</b>
52416 * <div class="sub-desc">
52417 * <p>If a field is configured with a <code>{@link Ext.form.TextField#validator validator}</code> function,
52418 * it will be passed the current field value. The <code>{@link Ext.form.TextField#validator validator}</code>
52419 * function is expected to return boolean <tt>true</tt> if the value is valid or return a string to
52420 * represent the invalid message if invalid.</p>
52422 * <li><b>2. Built in Validation</b>
52423 * <div class="sub-desc">
52424 * <p>Basic validation is affected with the following configuration properties:</p>
52426 * <u>Validation</u> <u>Invalid Message</u>
52427 * <code>{@link Ext.form.TextField#allowBlank allowBlank} {@link Ext.form.TextField#emptyText emptyText}</code>
52428 * <code>{@link Ext.form.TextField#minLength minLength} {@link Ext.form.TextField#minLengthText minLengthText}</code>
52429 * <code>{@link Ext.form.TextField#maxLength maxLength} {@link Ext.form.TextField#maxLengthText maxLengthText}</code>
52432 * <li><b>3. Preconfigured Validation Types (VTypes)</b>
52433 * <div class="sub-desc">
52434 * <p>Using VTypes offers a convenient way to reuse validation. If a field is configured with a
52435 * <code>{@link Ext.form.TextField#vtype vtype}</code>, the corresponding {@link Ext.form.VTypes VTypes}
52436 * validation function will be used for validation. If invalid, either the field's
52437 * <code>{@link Ext.form.TextField#vtypeText vtypeText}</code> or the VTypes vtype Text property will be
52438 * used for the invalid message. Keystrokes on the field will be filtered according to the VTypes
52439 * vtype Mask property.</p>
52441 * <li><b>4. Field specific regex test</b>
52442 * <div class="sub-desc">
52443 * <p>Each field may also specify a <code>{@link Ext.form.TextField#regex regex}</code> test.
52444 * The invalid message for this test is configured with
52445 * <code>{@link Ext.form.TextField#regexText regexText}</code>.</p>
52447 * <li><b>Alter Validation Behavior</b>
52448 * <div class="sub-desc">
52449 * <p>Validation behavior for each field can be configured:</p><ul>
52450 * <li><code>{@link Ext.form.TextField#invalidText invalidText}</code> : the default validation message to
52451 * show if any validation step above does not provide a message when invalid</li>
52452 * <li><code>{@link Ext.form.TextField#maskRe maskRe}</code> : filter out keystrokes before any validation occurs</li>
52453 * <li><code>{@link Ext.form.TextField#stripCharsRe stripCharsRe}</code> : filter characters after being typed in,
52454 * but before being validated</li>
52455 * <li><code>{@link Ext.form.Field#invalidClass invalidClass}</code> : alternate style when invalid</li>
52456 * <li><code>{@link Ext.form.Field#validateOnBlur validateOnBlur}</code>,
52457 * <code>{@link Ext.form.Field#validationDelay validationDelay}</code>, and
52458 * <code>{@link Ext.form.Field#validationEvent validationEvent}</code> : modify how/when validation is triggered</li>
52463 * Creates a new TextField
52464 * @param {Object} config Configuration options
52467 Ext.form.TextField = Ext.extend(Ext.form.Field, {
52469 * @cfg {String} vtypeText A custom error message to display in place of the default message provided
52470 * for the <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>''</tt>). <b>Note</b>:
52471 * only applies if <b><code>{@link #vtype}</code></b> is set, else ignored.
52474 * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value
52475 * before validation (defaults to <tt>null</tt>).
52478 * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content
52479 * (defaults to <tt>false</tt>)
52483 * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
52488 * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
52493 * @cfg {String} vtype A validation type name as defined in {@link Ext.form.VTypes} (defaults to <tt>null</tt>)
52497 * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do
52498 * not match (defaults to <tt>null</tt>)
52502 * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults
52503 * to <tt>false</tt>)
52505 disableKeyFilter : false,
52507 * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to
52512 * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>)
52516 * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE).
52517 * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting
52518 * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be
52519 * entered into the field use <tt><b>{@link Ext.form.Field#autoCreate autoCreate}</b></tt> to add
52520 * any attributes you want to a field, for example:<pre><code>
52521 var myField = new Ext.form.NumberField({
52524 fieldLabel: 'Mobile',
52525 maxLength: 16, // for validation
52526 autoCreate: {tag: 'input', type: 'text', size: '20', autocomplete: 'off', maxlength: '10'}
52530 maxLength : Number.MAX_VALUE,
52532 * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b>
52533 * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>)
52535 minLengthText : 'The minimum length for this field is {0}',
52537 * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b>
52538 * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>)
52540 maxLengthText : 'The maximum length for this field is {0}',
52542 * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field
52543 * receives input focus (defaults to <tt>false</tt>)
52545 selectOnFocus : false,
52547 * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation
52548 * fails (defaults to <tt>'This field is required'</tt>)
52550 blankText : 'This field is required',
52552 * @cfg {Function} validator A custom validation function to be called during field validation
52553 * (defaults to <tt>null</tt>). If specified, this function will be called first, allowing the
52554 * developer to override the default validation process. This function will be passed the current
52555 * field value and expected to return boolean <tt>true</tt> if the value is valid or a string
52556 * error message if invalid.
52560 * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation
52561 * (defaults to <tt>null</tt>). If the test fails, the field will be marked invalid using
52562 * <b><tt>{@link #regexText}</tt></b>.
52566 * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the
52567 * test fails during validation (defaults to <tt>''</tt>)
52571 * @cfg {String} emptyText The default text to place into an empty field (defaults to <tt>null</tt>).
52572 * <b>Note</b>: that this value will be submitted to the server if this field is enabled and configured
52573 * with a {@link #name}.
52577 * @cfg {String} emptyClass The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b>
52578 * (defaults to <tt>'x-form-empty-field'</tt>). This class is automatically added and removed as needed
52579 * depending on the current field value.
52581 emptyClass : 'x-form-empty-field',
52584 * @cfg {Boolean} enableKeyEvents <tt>true</tt> to enable the proxying of key events for the HTML input
52585 * field (defaults to <tt>false</tt>)
52588 initComponent : function(){
52589 Ext.form.TextField.superclass.initComponent.call(this);
52593 * Fires when the <tt><b>{@link #autoSize}</b></tt> function is triggered. The field may or
52594 * may not have actually changed size according to the default logic, but this event provides
52595 * a hook for the developer to apply additional logic at runtime to resize the field if needed.
52596 * @param {Ext.form.Field} this This text field
52597 * @param {Number} width The new field width
52603 * Keydown input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
52605 * @param {Ext.form.TextField} this This text field
52606 * @param {Ext.EventObject} e
52611 * Keyup input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
52613 * @param {Ext.form.TextField} this This text field
52614 * @param {Ext.EventObject} e
52619 * Keypress input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
52621 * @param {Ext.form.TextField} this This text field
52622 * @param {Ext.EventObject} e
52629 initEvents : function(){
52630 Ext.form.TextField.superclass.initEvents.call(this);
52631 if(this.validationEvent == 'keyup'){
52632 this.validationTask = new Ext.util.DelayedTask(this.validate, this);
52633 this.mon(this.el, 'keyup', this.filterValidation, this);
52635 else if(this.validationEvent !== false){
52636 this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay});
52638 if(this.selectOnFocus || this.emptyText){
52639 this.on('focus', this.preFocus, this);
52641 this.mon(this.el, 'mousedown', function(){
52642 if(!this.hasFocus){
52643 this.el.on('mouseup', function(e){
52644 e.preventDefault();
52645 }, this, {single:true});
52649 if(this.emptyText){
52650 this.on('blur', this.postBlur, this);
52651 this.applyEmptyText();
52654 if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){
52655 this.mon(this.el, 'keypress', this.filterKeys, this);
52658 this.mon(this.el, 'keyup', this.onKeyUpBuffered, this, {buffer: 50});
52659 this.mon(this.el, 'click', this.autoSize, this);
52661 if(this.enableKeyEvents){
52662 this.mon(this.el, 'keyup', this.onKeyUp, this);
52663 this.mon(this.el, 'keydown', this.onKeyDown, this);
52664 this.mon(this.el, 'keypress', this.onKeyPress, this);
52668 processValue : function(value){
52669 if(this.stripCharsRe){
52670 var newValue = value.replace(this.stripCharsRe, '');
52671 if(newValue !== value){
52672 this.setRawValue(newValue);
52679 filterValidation : function(e){
52680 if(!e.isNavKeyPress()){
52681 this.validationTask.delay(this.validationDelay);
52686 onDisable: function(){
52687 Ext.form.TextField.superclass.onDisable.call(this);
52689 this.el.dom.unselectable = 'on';
52694 onEnable: function(){
52695 Ext.form.TextField.superclass.onEnable.call(this);
52697 this.el.dom.unselectable = '';
52702 onKeyUpBuffered : function(e){
52703 if(!e.isNavKeyPress()){
52709 onKeyUp : function(e){
52710 this.fireEvent('keyup', this, e);
52714 onKeyDown : function(e){
52715 this.fireEvent('keydown', this, e);
52719 onKeyPress : function(e){
52720 this.fireEvent('keypress', this, e);
52724 * Resets the current field value to the originally-loaded value and clears any validation messages.
52725 * Also adds <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyClass}</b></tt> if the
52726 * original value was blank.
52728 reset : function(){
52729 Ext.form.TextField.superclass.reset.call(this);
52730 this.applyEmptyText();
52733 applyEmptyText : function(){
52734 if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){
52735 this.setRawValue(this.emptyText);
52736 this.el.addClass(this.emptyClass);
52741 preFocus : function(){
52743 if(this.emptyText){
52744 if(el.dom.value == this.emptyText){
52745 this.setRawValue('');
52747 el.removeClass(this.emptyClass);
52749 if(this.selectOnFocus){
52752 }).defer(this.inEditor && Ext.isIE ? 50 : 0);
52757 postBlur : function(){
52758 this.applyEmptyText();
52762 filterKeys : function(e){
52763 // special keys don't generate charCodes, so leave them alone
52764 if(e.ctrlKey || e.isSpecialKey()){
52768 if(!this.maskRe.test(String.fromCharCode(e.getCharCode()))){
52773 setValue : function(v){
52774 if(this.emptyText && this.el && !Ext.isEmpty(v)){
52775 this.el.removeClass(this.emptyClass);
52777 Ext.form.TextField.superclass.setValue.apply(this, arguments);
52778 this.applyEmptyText();
52784 * Validates a value according to the field's validation rules and marks the field as invalid
52785 * if the validation fails
52786 * @param {Mixed} value The value to validate
52787 * @return {Boolean} True if the value is valid, else false
52789 validateValue : function(value){
52790 if(Ext.isFunction(this.validator)){
52791 var msg = this.validator(value);
52793 this.markInvalid(msg);
52797 if(value.length < 1 || value === this.emptyText){ // if it's blank
52798 if(this.allowBlank){
52799 this.clearInvalid();
52802 this.markInvalid(this.blankText);
52806 if(value.length < this.minLength){
52807 this.markInvalid(String.format(this.minLengthText, this.minLength));
52810 if(value.length > this.maxLength){
52811 this.markInvalid(String.format(this.maxLengthText, this.maxLength));
52815 var vt = Ext.form.VTypes;
52816 if(!vt[this.vtype](value, this)){
52817 this.markInvalid(this.vtypeText || vt[this.vtype +'Text']);
52821 if(this.regex && !this.regex.test(value)){
52822 this.markInvalid(this.regexText);
52829 * Selects text in this field
52830 * @param {Number} start (optional) The index where the selection should start (defaults to 0)
52831 * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
52833 selectText : function(start, end){
52834 var v = this.getRawValue();
52835 var doFocus = false;
52837 start = start === undefined ? 0 : start;
52838 end = end === undefined ? v.length : end;
52839 var d = this.el.dom;
52840 if(d.setSelectionRange){
52841 d.setSelectionRange(start, end);
52842 }else if(d.createTextRange){
52843 var range = d.createTextRange();
52844 range.moveStart('character', start);
52845 range.moveEnd('character', end-v.length);
52848 doFocus = Ext.isGecko || Ext.isOpera;
52858 * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
52859 * This only takes effect if <tt><b>{@link #grow}</b> = true</tt>, and fires the {@link #autosize} event.
52861 autoSize : function(){
52862 if(!this.grow || !this.rendered){
52866 this.metrics = Ext.util.TextMetrics.createInstance(this.el);
52869 var v = el.dom.value;
52870 var d = document.createElement('div');
52871 d.appendChild(document.createTextNode(v));
52876 var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
52877 this.el.setWidth(w);
52878 this.fireEvent('autosize', this, w);
52881 onDestroy: function(){
52882 if(this.validationTask){
52883 this.validationTask.cancel();
52884 this.validationTask = null;
52886 Ext.form.TextField.superclass.onDestroy.call(this);
52889 Ext.reg('textfield', Ext.form.TextField);
52891 * @class Ext.form.TriggerField
52892 * @extends Ext.form.TextField
52893 * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
52894 * The trigger has no default action, so you must assign a function to implement the trigger click handler by
52895 * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox
52896 * for which you can provide a custom implementation. For example:
52898 var trigger = new Ext.form.TriggerField();
52899 trigger.onTriggerClick = myTriggerFn;
52900 trigger.applyToMarkup('my-field');
52903 * However, in general you will most likely want to use TriggerField as the base class for a reusable component.
52904 * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this.
52907 * Create a new TriggerField.
52908 * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied
52909 * to the base TextField)
52912 Ext.form.TriggerField = Ext.extend(Ext.form.TextField, {
52914 * @cfg {String} triggerClass
52915 * An additional CSS class used to style the trigger button. The trigger will always get the
52916 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
52919 * @cfg {Mixed} triggerConfig
52920 * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the
52921 * trigger element for this Field. (Optional).</p>
52922 * <p>Specify this when you need a customized element to act as the trigger button for a TriggerField.</p>
52923 * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning
52924 * and appearance of the trigger. Defaults to:</p>
52925 * <pre><code>{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}</code></pre>
52928 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
52929 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
52930 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
52931 * <pre><code>{tag: "input", type: "text", size: "16", autocomplete: "off"}</code></pre>
52933 defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"},
52935 * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
52936 * text field (defaults to <tt>false</tt>)
52940 * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field,
52941 * the field will only respond to a click on the trigger to set the value. (defaults to <tt>true</tt>)
52945 * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to
52946 * <tt>x-trigger-wrap-focus</tt>.
52948 wrapFocusClass: 'x-trigger-wrap-focus',
52953 autoSize: Ext.emptyFn,
52957 deferHeight : true,
52961 actionMode: 'wrap',
52964 onResize : function(w, h){
52965 Ext.form.TriggerField.superclass.onResize.call(this, w, h);
52966 if(typeof w == 'number'){
52967 this.el.setWidth(this.adjustWidth('input', w - this.trigger.getWidth()));
52969 this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
52973 adjustSize : Ext.BoxComponent.prototype.adjustSize,
52976 getResizeEl : function(){
52981 getPositionEl : function(){
52986 alignErrorIcon : function(){
52988 this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
52993 onRender : function(ct, position){
52994 Ext.form.TriggerField.superclass.onRender.call(this, ct, position);
52996 this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
52997 this.trigger = this.wrap.createChild(this.triggerConfig ||
52998 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
52999 if(this.hideTrigger){
53000 this.trigger.setDisplayed(false);
53002 this.initTrigger();
53004 this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
53006 if(!this.editable){
53007 this.editable = true;
53008 this.setEditable(false);
53012 afterRender : function(){
53013 Ext.form.TriggerField.superclass.afterRender.call(this);
53017 initTrigger : function(){
53018 this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true});
53019 this.trigger.addClassOnOver('x-form-trigger-over');
53020 this.trigger.addClassOnClick('x-form-trigger-click');
53024 onDestroy : function(){
53025 Ext.destroy(this.trigger, this.wrap);
53026 if (this.mimicing){
53027 Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
53029 Ext.form.TriggerField.superclass.onDestroy.call(this);
53033 onFocus : function(){
53034 Ext.form.TriggerField.superclass.onFocus.call(this);
53035 if(!this.mimicing){
53036 this.wrap.addClass(this.wrapFocusClass);
53037 this.mimicing = true;
53038 Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {delay: 10});
53039 if(this.monitorTab){
53040 this.el.on('keydown', this.checkTab, this);
53046 checkTab : function(e){
53047 if(e.getKey() == e.TAB){
53048 this.triggerBlur();
53053 onBlur : function(){
53058 mimicBlur : function(e){
53059 if(!this.wrap.contains(e.target) && this.validateBlur(e)){
53060 this.triggerBlur();
53065 triggerBlur : function(){
53066 this.mimicing = false;
53067 Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
53068 if(this.monitorTab && this.el){
53069 this.el.un("keydown", this.checkTab, this);
53071 Ext.form.TriggerField.superclass.onBlur.call(this);
53073 this.wrap.removeClass(this.wrapFocusClass);
53077 beforeBlur : Ext.emptyFn,
53080 * Allow or prevent the user from directly editing the field text. If false is passed,
53081 * the user will only be able to modify the field using the trigger. This method
53082 * is the runtime equivalent of setting the 'editable' config option at config time.
53083 * @param {Boolean} value True to allow the user to directly edit the field text
53085 setEditable : function(value){
53086 if(value == this.editable){
53089 this.editable = value;
53091 this.el.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true);
53093 this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick, this).dom.removeAttribute('readOnly');
53098 // This should be overriden by any subclass that needs to check whether or not the field can be blurred.
53099 validateBlur : function(e){
53104 * The function that should handle the trigger's click event. This method does nothing by default
53105 * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for
53106 * sample implementations.
53108 * @param {EventObject} e
53110 onTriggerClick : Ext.emptyFn
53113 * @cfg {Boolean} grow @hide
53116 * @cfg {Number} growMin @hide
53119 * @cfg {Number} growMax @hide
53124 * @class Ext.form.TwinTriggerField
53125 * @extends Ext.form.TriggerField
53126 * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class
53127 * to be extended by an implementing class. For an example of implementing this class, see the custom
53128 * SearchField implementation here:
53129 * <a href="http://extjs.com/deploy/ext/examples/form/custom.html">http://extjs.com/deploy/ext/examples/form/custom.html</a>
53131 Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, {
53133 * @cfg {Mixed} triggerConfig
53134 * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements
53135 * for this Field. (Optional).</p>
53136 * <p>Specify this when you need a customized element to contain the two trigger elements for this Field.
53137 * Each trigger element must be marked by the CSS class <tt>x-form-trigger</tt> (also see
53138 * <tt>{@link #trigger1Class}</tt> and <tt>{@link #trigger2Class}</tt>).</p>
53139 * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing,
53140 * positioning and appearance of the triggers.</p>
53143 * @cfg {String} trigger1Class
53144 * An additional CSS class used to style the trigger button. The trigger will always get the
53145 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
53148 * @cfg {String} trigger2Class
53149 * An additional CSS class used to style the trigger button. The trigger will always get the
53150 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
53153 initComponent : function(){
53154 Ext.form.TwinTriggerField.superclass.initComponent.call(this);
53156 this.triggerConfig = {
53157 tag:'span', cls:'x-form-twin-triggers', cn:[
53158 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
53159 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
53163 getTrigger : function(index){
53164 return this.triggers[index];
53167 initTrigger : function(){
53168 var ts = this.trigger.select('.x-form-trigger', true);
53169 this.wrap.setStyle('overflow', 'hidden');
53170 var triggerField = this;
53171 ts.each(function(t, all, index){
53172 t.hide = function(){
53173 var w = triggerField.wrap.getWidth();
53174 this.dom.style.display = 'none';
53175 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
53177 t.show = function(){
53178 var w = triggerField.wrap.getWidth();
53179 this.dom.style.display = '';
53180 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
53182 var triggerIndex = 'Trigger'+(index+1);
53184 if(this['hide'+triggerIndex]){
53185 t.dom.style.display = 'none';
53187 this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
53188 t.addClassOnOver('x-form-trigger-over');
53189 t.addClassOnClick('x-form-trigger-click');
53191 this.triggers = ts.elements;
53195 * The function that should handle the trigger's click event. This method does nothing by default
53196 * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
53197 * for additional information.
53199 * @param {EventObject} e
53201 onTrigger1Click : Ext.emptyFn,
53203 * The function that should handle the trigger's click event. This method does nothing by default
53204 * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
53205 * for additional information.
53207 * @param {EventObject} e
53209 onTrigger2Click : Ext.emptyFn
53211 Ext.reg('trigger', Ext.form.TriggerField);/**
53212 * @class Ext.form.TextArea
53213 * @extends Ext.form.TextField
53214 * Multiline text field. Can be used as a direct replacement for traditional textarea fields, plus adds
53215 * support for auto-sizing.
53217 * Creates a new TextArea
53218 * @param {Object} config Configuration options
53221 Ext.form.TextArea = Ext.extend(Ext.form.TextField, {
53223 * @cfg {Number} growMin The minimum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
53224 * (defaults to <tt>60</tt>)
53228 * @cfg {Number} growMax The maximum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
53229 * (defaults to <tt>1000</tt>)
53232 growAppend : ' \n ',
53233 growPad : Ext.isWebKit ? -6 : 0,
53235 enterIsSpecial : false,
53238 * @cfg {Boolean} preventScrollbars <tt>true</tt> to prevent scrollbars from appearing regardless of how much text is
53239 * in the field (equivalent to setting overflow: hidden, defaults to <tt>false</tt>)
53241 preventScrollbars: false,
53243 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
53244 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
53245 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
53246 * <pre><code>{tag: "textarea", style: "width:100px;height:60px;", autocomplete: "off"}</code></pre>
53250 onRender : function(ct, position){
53252 this.defaultAutoCreate = {
53254 style:"width:100px;height:60px;",
53255 autocomplete: "off"
53258 Ext.form.TextArea.superclass.onRender.call(this, ct, position);
53260 this.textSizeEl = Ext.DomHelper.append(document.body, {
53261 tag: "pre", cls: "x-form-grow-sizer"
53263 if(this.preventScrollbars){
53264 this.el.setStyle("overflow", "hidden");
53266 this.el.setHeight(this.growMin);
53270 onDestroy : function(){
53271 Ext.destroy(this.textSizeEl);
53272 Ext.form.TextArea.superclass.onDestroy.call(this);
53275 fireKey : function(e){
53276 if(e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() != e.ENTER || e.hasModifier()))){
53277 this.fireEvent("specialkey", this, e);
53282 onKeyUp : function(e){
53283 if(!e.isNavKeyPress() || e.getKey() == e.ENTER){
53286 Ext.form.TextArea.superclass.onKeyUp.call(this, e);
53290 * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
53291 * This only takes effect if grow = true, and fires the {@link #autosize} event if the height changes.
53293 autoSize: function(){
53294 if(!this.grow || !this.textSizeEl){
53298 var v = el.dom.value;
53299 var ts = this.textSizeEl;
53301 ts.appendChild(document.createTextNode(v));
53303 Ext.fly(ts).setWidth(this.el.getWidth());
53305 v = "  ";
53307 v += this.growAppend;
53309 v = v.replace(/\n/g, '<br />');
53313 var h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin) + this.growPad);
53314 if(h != this.lastHeight){
53315 this.lastHeight = h;
53316 this.el.setHeight(h);
53317 this.fireEvent("autosize", this, h);
53321 Ext.reg('textarea', Ext.form.TextArea);/**
53322 * @class Ext.form.NumberField
53323 * @extends Ext.form.TextField
53324 * Numeric text field that provides automatic keystroke filtering and numeric validation.
53326 * Creates a new NumberField
53327 * @param {Object} config Configuration options
53328 * @xtype numberfield
53330 Ext.form.NumberField = Ext.extend(Ext.form.TextField, {
53332 * @cfg {RegExp} stripCharsRe @hide
53335 * @cfg {RegExp} maskRe @hide
53338 * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
53340 fieldClass: "x-form-field x-form-num-field",
53342 * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
53344 allowDecimals : true,
53346 * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
53348 decimalSeparator : ".",
53350 * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
53352 decimalPrecision : 2,
53354 * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
53356 allowNegative : true,
53358 * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
53360 minValue : Number.NEGATIVE_INFINITY,
53362 * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
53364 maxValue : Number.MAX_VALUE,
53366 * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
53368 minText : "The minimum value for this field is {0}",
53370 * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
53372 maxText : "The maximum value for this field is {0}",
53374 * @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen
53375 * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
53377 nanText : "{0} is not a valid number",
53379 * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
53381 baseChars : "0123456789",
53384 initEvents : function(){
53385 var allowed = this.baseChars + '';
53386 if (this.allowDecimals) {
53387 allowed += this.decimalSeparator;
53389 if (this.allowNegative) {
53392 this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');
53393 Ext.form.NumberField.superclass.initEvents.call(this);
53397 validateValue : function(value){
53398 if(!Ext.form.NumberField.superclass.validateValue.call(this, value)){
53401 if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
53404 value = String(value).replace(this.decimalSeparator, ".");
53406 this.markInvalid(String.format(this.nanText, value));
53409 var num = this.parseValue(value);
53410 if(num < this.minValue){
53411 this.markInvalid(String.format(this.minText, this.minValue));
53414 if(num > this.maxValue){
53415 this.markInvalid(String.format(this.maxText, this.maxValue));
53421 getValue : function(){
53422 return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)));
53425 setValue : function(v){
53426 v = typeof v == 'number' ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
53427 v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
53428 return Ext.form.NumberField.superclass.setValue.call(this, v);
53432 parseValue : function(value){
53433 value = parseFloat(String(value).replace(this.decimalSeparator, "."));
53434 return isNaN(value) ? '' : value;
53438 fixPrecision : function(value){
53439 var nan = isNaN(value);
53440 if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
53441 return nan ? '' : value;
53443 return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
53446 beforeBlur : function(){
53447 var v = this.parseValue(this.getRawValue());
53448 if(!Ext.isEmpty(v)){
53449 this.setValue(this.fixPrecision(v));
53453 Ext.reg('numberfield', Ext.form.NumberField);/**
53454 * @class Ext.form.DateField
53455 * @extends Ext.form.TriggerField
53456 * Provides a date input field with a {@link Ext.DatePicker} dropdown and automatic date validation.
53458 * Create a new DateField
53459 * @param {Object} config
53462 Ext.form.DateField = Ext.extend(Ext.form.TriggerField, {
53464 * @cfg {String} format
53465 * The default date format string which can be overriden for localization support. The format must be
53466 * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/Y'</tt>).
53470 * @cfg {String} altFormats
53471 * Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
53472 * does not match the defined format (defaults to
53473 * <tt>'m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d'</tt>).
53475 altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d",
53477 * @cfg {String} disabledDaysText
53478 * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
53480 disabledDaysText : "Disabled",
53482 * @cfg {String} disabledDatesText
53483 * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
53485 disabledDatesText : "Disabled",
53487 * @cfg {String} minText
53488 * The error text to display when the date in the cell is before <tt>{@link #minValue}</tt> (defaults to
53489 * <tt>'The date in this field must be after {minValue}'</tt>).
53491 minText : "The date in this field must be equal to or after {0}",
53493 * @cfg {String} maxText
53494 * The error text to display when the date in the cell is after <tt>{@link #maxValue}</tt> (defaults to
53495 * <tt>'The date in this field must be before {maxValue}'</tt>).
53497 maxText : "The date in this field must be equal to or before {0}",
53499 * @cfg {String} invalidText
53500 * The error text to display when the date in the field is invalid (defaults to
53501 * <tt>'{value} is not a valid date - it must be in the format {format}'</tt>).
53503 invalidText : "{0} is not a valid date - it must be in the format {1}",
53505 * @cfg {String} triggerClass
53506 * An additional CSS class used to style the trigger button. The trigger will always get the
53507 * class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
53508 * (defaults to <tt>'x-form-date-trigger'</tt> which displays a calendar icon).
53510 triggerClass : 'x-form-date-trigger',
53512 * @cfg {Boolean} showToday
53513 * <tt>false</tt> to hide the footer area of the DatePicker containing the Today button and disable
53514 * the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
53518 * @cfg {Date/String} minValue
53519 * The minimum allowed date. Can be either a Javascript date object or a string date in a
53520 * valid format (defaults to null).
53523 * @cfg {Date/String} maxValue
53524 * The maximum allowed date. Can be either a Javascript date object or a string date in a
53525 * valid format (defaults to null).
53528 * @cfg {Array} disabledDays
53529 * An array of days to disable, 0 based (defaults to null). Some examples:<pre><code>
53530 // disable Sunday and Saturday:
53531 disabledDays: [0, 6]
53532 // disable weekdays:
53533 disabledDays: [1,2,3,4,5]
53537 * @cfg {Array} disabledDates
53538 * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
53539 * expression so they are very powerful. Some examples:<pre><code>
53540 // disable these exact dates:
53541 disabledDates: ["03/08/2003", "09/16/2003"]
53542 // disable these days for every year:
53543 disabledDates: ["03/08", "09/16"]
53544 // only match the beginning (useful if you are using short years):
53545 disabledDates: ["^03/08"]
53546 // disable every day in March 2006:
53547 disabledDates: ["03/../2006"]
53548 // disable every day in every March:
53549 disabledDates: ["^03"]
53551 * Note that the format of the dates included in the array should exactly match the {@link #format} config.
53552 * In order to support regular expressions, if you are using a {@link #format date format} that has "." in
53553 * it, you will have to escape the dot when restricting dates. For example: <tt>["03\\.08\\.03"]</tt>.
53556 * @cfg {String/Object} autoCreate
53557 * A {@link Ext.DomHelper DomHelper element specification object}, or <tt>true</tt> for the default element
53558 * specification object:<pre><code>
53559 * autoCreate: {tag: "input", type: "text", size: "10", autocomplete: "off"}
53564 defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"},
53566 initComponent : function(){
53567 Ext.form.DateField.superclass.initComponent.call(this);
53572 * Fires when a date is selected via the date picker.
53573 * @param {Ext.form.DateField} this
53574 * @param {Date} date The date that was selected
53579 if(Ext.isString(this.minValue)){
53580 this.minValue = this.parseDate(this.minValue);
53582 if(Ext.isString(this.maxValue)){
53583 this.maxValue = this.parseDate(this.maxValue);
53585 this.disabledDatesRE = null;
53586 this.initDisabledDays();
53590 initDisabledDays : function(){
53591 if(this.disabledDates){
53592 var dd = this.disabledDates,
53593 len = dd.length - 1,
53596 Ext.each(dd, function(d, i){
53597 re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
53602 this.disabledDatesRE = new RegExp(re + ')');
53607 * Replaces any existing disabled dates with new values and refreshes the DatePicker.
53608 * @param {Array} disabledDates An array of date strings (see the <tt>{@link #disabledDates}</tt> config
53609 * for details on supported values) used to disable a pattern of dates.
53611 setDisabledDates : function(dd){
53612 this.disabledDates = dd;
53613 this.initDisabledDays();
53615 this.menu.picker.setDisabledDates(this.disabledDatesRE);
53620 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
53621 * @param {Array} disabledDays An array of disabled day indexes. See the <tt>{@link #disabledDays}</tt>
53622 * config for details on supported values.
53624 setDisabledDays : function(dd){
53625 this.disabledDays = dd;
53627 this.menu.picker.setDisabledDays(dd);
53632 * Replaces any existing <tt>{@link #minValue}</tt> with the new value and refreshes the DatePicker.
53633 * @param {Date} value The minimum date that can be selected
53635 setMinValue : function(dt){
53636 this.minValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
53638 this.menu.picker.setMinDate(this.minValue);
53643 * Replaces any existing <tt>{@link #maxValue}</tt> with the new value and refreshes the DatePicker.
53644 * @param {Date} value The maximum date that can be selected
53646 setMaxValue : function(dt){
53647 this.maxValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
53649 this.menu.picker.setMaxDate(this.maxValue);
53654 validateValue : function(value){
53655 value = this.formatDate(value);
53656 if(!Ext.form.DateField.superclass.validateValue.call(this, value)){
53659 if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
53662 var svalue = value;
53663 value = this.parseDate(value);
53665 this.markInvalid(String.format(this.invalidText, svalue, this.format));
53668 var time = value.getTime();
53669 if(this.minValue && time < this.minValue.getTime()){
53670 this.markInvalid(String.format(this.minText, this.formatDate(this.minValue)));
53673 if(this.maxValue && time > this.maxValue.getTime()){
53674 this.markInvalid(String.format(this.maxText, this.formatDate(this.maxValue)));
53677 if(this.disabledDays){
53678 var day = value.getDay();
53679 for(var i = 0; i < this.disabledDays.length; i++) {
53680 if(day === this.disabledDays[i]){
53681 this.markInvalid(this.disabledDaysText);
53686 var fvalue = this.formatDate(value);
53687 if(this.disabledDatesRE && this.disabledDatesRE.test(fvalue)){
53688 this.markInvalid(String.format(this.disabledDatesText, fvalue));
53695 // Provides logic to override the default TriggerField.validateBlur which just returns true
53696 validateBlur : function(){
53697 return !this.menu || !this.menu.isVisible();
53701 * Returns the current date value of the date field.
53702 * @return {Date} The date value
53704 getValue : function(){
53705 return this.parseDate(Ext.form.DateField.superclass.getValue.call(this)) || "";
53709 * Sets the value of the date field. You can pass a date object or any string that can be
53710 * parsed into a valid date, using <tt>{@link #format}</tt> as the date format, according
53711 * to the same rules as {@link Date#parseDate} (the default format used is <tt>"m/d/Y"</tt>).
53714 //All of these calls set the same date value (May 4, 2006)
53716 //Pass a date object:
53717 var dt = new Date('5/4/2006');
53718 dateField.setValue(dt);
53720 //Pass a date string (default format):
53721 dateField.setValue('05/04/2006');
53723 //Pass a date string (custom format):
53724 dateField.format = 'Y-m-d';
53725 dateField.setValue('2006-05-04');
53727 * @param {String/Date} date The date or valid date string
53728 * @return {Ext.form.Field} this
53730 setValue : function(date){
53731 return Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
53735 parseDate : function(value){
53736 if(!value || Ext.isDate(value)){
53739 var v = Date.parseDate(value, this.format);
53740 if(!v && this.altFormats){
53741 if(!this.altFormatsArray){
53742 this.altFormatsArray = this.altFormats.split("|");
53744 for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){
53745 v = Date.parseDate(value, this.altFormatsArray[i]);
53752 onDestroy : function(){
53753 Ext.destroy(this.menu);
53754 Ext.form.DateField.superclass.onDestroy.call(this);
53758 formatDate : function(date){
53759 return Ext.isDate(date) ? date.dateFormat(this.format) : date;
53763 * @method onTriggerClick
53767 // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker
53768 onTriggerClick : function(){
53772 if(this.menu == null){
53773 this.menu = new Ext.menu.DateMenu({
53778 Ext.apply(this.menu.picker, {
53779 minDate : this.minValue,
53780 maxDate : this.maxValue,
53781 disabledDatesRE : this.disabledDatesRE,
53782 disabledDatesText : this.disabledDatesText,
53783 disabledDays : this.disabledDays,
53784 disabledDaysText : this.disabledDaysText,
53785 format : this.format,
53786 showToday : this.showToday,
53787 minText : String.format(this.minText, this.formatDate(this.minValue)),
53788 maxText : String.format(this.maxText, this.formatDate(this.maxValue))
53790 this.menu.picker.setValue(this.getValue() || new Date());
53791 this.menu.show(this.el, "tl-bl?");
53792 this.menuEvents('on');
53796 menuEvents: function(method){
53797 this.menu[method]('select', this.onSelect, this);
53798 this.menu[method]('hide', this.onMenuHide, this);
53799 this.menu[method]('show', this.onFocus, this);
53802 onSelect: function(m, d){
53804 this.fireEvent('select', this, d);
53808 onMenuHide: function(){
53809 this.focus(false, 60);
53810 this.menuEvents('un');
53814 beforeBlur : function(){
53815 var v = this.parseDate(this.getRawValue());
53822 * @cfg {Boolean} grow @hide
53825 * @cfg {Number} growMin @hide
53828 * @cfg {Number} growMax @hide
53835 Ext.reg('datefield', Ext.form.DateField);/**
\r
53836 * @class Ext.form.DisplayField
\r
53837 * @extends Ext.form.Field
\r
53838 * A display-only text field which is not validated and not submitted.
\r
53840 * Creates a new DisplayField.
\r
53841 * @param {Object} config Configuration options
\r
53842 * @xtype displayfield
\r
53844 Ext.form.DisplayField = Ext.extend(Ext.form.Field, {
\r
53845 validationEvent : false,
\r
53846 validateOnBlur : false,
\r
53847 defaultAutoCreate : {tag: "div"},
\r
53849 * @cfg {String} fieldClass The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
\r
53851 fieldClass : "x-form-display-field",
\r
53853 * @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
\r
53854 * <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
\r
53855 * rendering them as string literals per the default logic.
\r
53857 htmlEncode: false,
\r
53860 initEvents : Ext.emptyFn,
\r
53862 isValid : function(){
\r
53866 validate : function(){
\r
53870 getRawValue : function(){
\r
53871 var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, '');
\r
53872 if(v === this.emptyText){
\r
53875 if(this.htmlEncode){
\r
53876 v = Ext.util.Format.htmlDecode(v);
\r
53881 getValue : function(){
\r
53882 return this.getRawValue();
\r
53885 getName: function() {
\r
53886 return this.name;
\r
53889 setRawValue : function(v){
\r
53890 if(this.htmlEncode){
\r
53891 v = Ext.util.Format.htmlEncode(v);
\r
53893 return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
\r
53896 setValue : function(v){
\r
53897 this.setRawValue(v);
\r
53901 * @cfg {String} inputType
\r
53905 * @cfg {Boolean} disabled
\r
53909 * @cfg {Boolean} readOnly
\r
53913 * @cfg {Boolean} validateOnBlur
\r
53917 * @cfg {Number} validationDelay
\r
53921 * @cfg {String/Boolean} validationEvent
\r
53926 Ext.reg('displayfield', Ext.form.DisplayField);
\r
53928 * @class Ext.form.ComboBox
53929 * @extends Ext.form.TriggerField
53930 * <p>A combobox control with support for autocomplete, remote-loading, paging and many other features.</p>
53931 * <p>A ComboBox works in a similar manner to a traditional HTML <select> field. The difference is
53932 * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input
53933 * field to hold the value of the valueField. The <i>{@link #displayField}</i> is shown in the text field
53934 * which is named according to the {@link #name}.</p>
53935 * <p><b><u>Events</u></b></p>
53936 * <p>To do something when something in ComboBox is selected, configure the select event:<pre><code>
53937 var cb = new Ext.form.ComboBox({
53938 // all of your config options
53941 'select': yourFunction
53945 // Alternatively, you can assign events after the object is created:
53946 var cb = new Ext.form.ComboBox(yourOptions);
53947 cb.on('select', yourFunction, yourScope);
53948 * </code></pre></p>
53950 * <p><b><u>ComboBox in Grid</u></b></p>
53951 * <p>If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer}
53952 * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement
53953 * a reusable render, for example:<pre><code>
53954 // create reusable renderer
53955 Ext.util.Format.comboRenderer = function(combo){
53956 return function(value){
53957 var record = combo.findRecord(combo.{@link #valueField}, value);
53958 return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText};
53962 // create the combo instance
53963 var combo = new Ext.form.ComboBox({
53964 {@link #typeAhead}: true,
53965 {@link #triggerAction}: 'all',
53966 {@link #lazyRender}:true,
53967 {@link #mode}: 'local',
53968 {@link #store}: new Ext.data.ArrayStore({
53974 data: [[1, 'item1'], [2, 'item2']]
53976 {@link #valueField}: 'myId',
53977 {@link #displayField}: 'displayText'
53980 // snippet of column model used within grid
53981 var cm = new Ext.grid.ColumnModel([{
53984 header: "Some Header",
53985 dataIndex: 'whatever',
53987 editor: combo, // specify reference to combo instance
53988 renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer
53992 * </code></pre></p>
53994 * <p><b><u>Filtering</u></b></p>
53995 * <p>A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox
53996 * store manually see <tt>{@link #lastQuery}</tt>.</p>
53998 * Create a new ComboBox.
53999 * @param {Object} config Configuration options
54002 Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
54004 * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox.
54005 * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or
54006 * {@link Ext.form.FormPanel}, you must also set <tt>{@link #lazyRender} = true</tt>.
54009 * @cfg {Boolean} lazyRender <tt>true</tt> to prevent the ComboBox from rendering until requested
54010 * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}),
54011 * defaults to <tt>false</tt>).
54014 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or <tt>true</tt> for a default
54015 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
54016 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
54017 * <pre><code>{tag: "input", type: "text", size: "24", autocomplete: "off"}</code></pre>
54020 * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <tt>undefined</tt>).
54021 * Acceptable values for this property are:
54022 * <div class="mdetail-params"><ul>
54023 * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
54024 * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.
54025 * <div class="mdetail-params"><ul>
54026 * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
54027 * A 1-dimensional array will automatically be expanded (each array item will be the combo
54028 * {@link #valueField value} and {@link #displayField text})</div></li>
54029 * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
54030 * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
54031 * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.
54032 * </div></li></ul></div></li></ul></div>
54033 * <p>See also <tt>{@link #mode}</tt>.</p>
54036 * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
54037 * the dropdown list (defaults to undefined, with no header element)
54041 defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
54043 * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown
54044 * list (defaults to the width of the ComboBox field). See also <tt>{@link #minListWidth}
54047 * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
54048 * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'text'</tt> if
54049 * {@link #transform transforming a select} a select).
54050 * <p>See also <tt>{@link #valueField}</tt>.</p>
54051 * <p><b>Note</b>: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a
54052 * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not
54056 * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this
54057 * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'value'</tt> if
54058 * {@link #transform transforming a select}).
54059 * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
54060 * mapped. See also <tt>{@link #hiddenName}</tt>, <tt>{@link #hiddenValue}</tt>, and <tt>{@link #displayField}</tt>.</p>
54063 * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
54064 * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically
54065 * post during a form submission. See also {@link #valueField}.
54066 * <p><b>Note</b>: the hidden field's id will also default to this name if {@link #hiddenId} is not specified.
54067 * The ComboBox {@link Ext.Component#id id} and the <tt>{@link #hiddenId}</tt> <b>should be different</b>, since
54068 * no two DOM nodes should share the same id. So, if the ComboBox <tt>{@link Ext.form.Field#name name}</tt> and
54069 * <tt>hiddenName</tt> are the same, you should specify a unique <tt>{@link #hiddenId}</tt>.</p>
54072 * @cfg {String} hiddenId If <tt>{@link #hiddenName}</tt> is specified, <tt>hiddenId</tt> can also be provided
54073 * to give the hidden field a unique id (defaults to the <tt>{@link #hiddenName}</tt>). The <tt>hiddenId</tt>
54074 * and combo {@link Ext.Component#id id} should be different, since no two DOM
54075 * nodes should share the same id.
54078 * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is
54079 * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured
54080 * <tt>{@link Ext.form.Field#value value}</tt>.
54083 * @cfg {String} listClass The CSS class to add to the predefined <tt>'x-combo-list'</tt> class
54084 * applied the dropdown list element (defaults to '').
54088 * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list
54089 * (defaults to <tt>'x-combo-selected'</tt>)
54091 selectedClass : 'x-combo-selected',
54093 * @cfg {String} listEmptyText The empty text to display in the data view if no items are found.
54098 * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always
54099 * get the class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
54100 * (defaults to <tt>'x-form-arrow-trigger'</tt> which displays a downward arrow icon).
54102 triggerClass : 'x-form-arrow-trigger',
54104 * @cfg {Boolean/String} shadow <tt>true</tt> or <tt>"sides"</tt> for the default effect, <tt>"frame"</tt> for
54105 * 4-way shadow, and <tt>"drop"</tt> for bottom-right
54109 * @cfg {String} listAlign A valid anchor position value. See <tt>{@link Ext.Element#alignTo}</tt> for details
54110 * on supported anchor positions (defaults to <tt>'tl-bl?'</tt>)
54112 listAlign : 'tl-bl?',
54114 * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown
54115 * (defaults to <tt>300</tt>)
54119 * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its
54120 * distance to the viewport edges (defaults to <tt>90</tt>)
54124 * @cfg {String} triggerAction The action to execute when the trigger is clicked.
54125 * <div class="mdetail-params"><ul>
54126 * <li><b><tt>'query'</tt></b> : <b>Default</b>
54127 * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.</p></li>
54128 * <li><b><tt>'all'</tt></b> :
54129 * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
54131 * <p>See also <code>{@link #queryParam}</code>.</p>
54133 triggerAction : 'query',
54135 * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
54136 * {@link #typeAhead} activate (defaults to <tt>4</tt> if <tt>{@link #mode} = 'remote'</tt> or <tt>0</tt> if
54137 * <tt>{@link #mode} = 'local'</tt>, does not apply if
54138 * <tt>{@link Ext.form.TriggerField#editable editable} = false</tt>).
54142 * @cfg {Boolean} typeAhead <tt>true</tt> to populate and autoselect the remainder of the text being
54143 * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
54144 * to <tt>false</tt>)
54148 * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
54149 * sending the query to filter the dropdown list (defaults to <tt>500</tt> if <tt>{@link #mode} = 'remote'</tt>
54150 * or <tt>10</tt> if <tt>{@link #mode} = 'local'</tt>)
54154 * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.PagingToolbar} is displayed in the
54155 * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
54156 * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when <tt>{@link #mode} = 'remote'</tt>
54157 * (defaults to <tt>0</tt>).
54161 * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus.
54162 * Only applies when <tt>{@link Ext.form.TriggerField#editable editable} = true</tt> (defaults to
54165 selectOnFocus : false,
54167 * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store)
54168 * as it will be passed on the querystring (defaults to <tt>'query'</tt>)
54170 queryParam : 'query',
54172 * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
54173 * when <tt>{@link #mode} = 'remote'</tt> (defaults to <tt>'Loading...'</tt>)
54175 loadingText : 'Loading...',
54177 * @cfg {Boolean} resizable <tt>true</tt> to add a resize handle to the bottom of the dropdown list
54178 * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles).
54179 * Defaults to <tt>false</tt>.
54183 * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if
54184 * <tt>{@link #resizable} = true</tt> (defaults to <tt>8</tt>)
54188 * @cfg {String} allQuery The text query to send to the server to return all records for the list
54189 * with no filtering (defaults to '')
54193 * @cfg {String} mode Acceptable values are:
54194 * <div class="mdetail-params"><ul>
54195 * <li><b><tt>'remote'</tt></b> : <b>Default</b>
54196 * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
54197 * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
54198 * clicked, set to <tt>'local'</tt> and manually load the store. To force a requery of the store
54199 * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
54200 * <li><b><tt>'local'</tt></b> :
54201 * <p class="sub-desc">ComboBox loads local data</p>
54203 var combo = new Ext.form.ComboBox({
54204 renderTo: document.body,
54206 store: new Ext.data.ArrayStore({
54209 'myId', // numeric value is the key
54212 data: [[1, 'item1'], [2, 'item2']] // data is local
54214 valueField: 'myId',
54215 displayField: 'displayText',
54216 triggerAction: 'all'
54218 * </code></pre></li>
54223 * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to <tt>70</tt>, will
54224 * be ignored if <tt>{@link #listWidth}</tt> has a higher value)
54228 * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
54229 * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
54231 forceSelection : false,
54233 * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
54234 * if <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
54236 typeAheadDelay : 250,
54238 * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
54239 * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
54240 * default text is used, it means there is no value set and no validation will occur on this field.
54244 * @cfg {Boolean} lazyInit <tt>true</tt> to not initialize the list for this combo until the field is focused
54245 * (defaults to <tt>true</tt>)
54250 * The value of the match string used to filter the store. Delete this property to force a requery.
54253 var combo = new Ext.form.ComboBox({
54258 // delete the previous query in the beforequery event or set
54259 // combo.lastQuery = null (this will reload the store the next time it expands)
54260 beforequery: function(qe){
54261 delete qe.combo.lastQuery;
54266 * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
54267 * configure the combo with <tt>lastQuery=''</tt>. Example use:
54269 var combo = new Ext.form.ComboBox({
54272 triggerAction: 'all',
54276 * @property lastQuery
54281 initComponent : function(){
54282 Ext.form.ComboBox.superclass.initComponent.call(this);
54286 * Fires when the dropdown list is expanded
54287 * @param {Ext.form.ComboBox} combo This combo box
54292 * Fires when the dropdown list is collapsed
54293 * @param {Ext.form.ComboBox} combo This combo box
54297 * @event beforeselect
54298 * Fires before a list item is selected. Return false to cancel the selection.
54299 * @param {Ext.form.ComboBox} combo This combo box
54300 * @param {Ext.data.Record} record The data record returned from the underlying store
54301 * @param {Number} index The index of the selected item in the dropdown list
54306 * Fires when a list item is selected
54307 * @param {Ext.form.ComboBox} combo This combo box
54308 * @param {Ext.data.Record} record The data record returned from the underlying store
54309 * @param {Number} index The index of the selected item in the dropdown list
54313 * @event beforequery
54314 * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
54315 * cancel property to true.
54316 * @param {Object} queryEvent An object that has these properties:<ul>
54317 * <li><code>combo</code> : Ext.form.ComboBox <div class="sub-desc">This combo box</div></li>
54318 * <li><code>query</code> : String <div class="sub-desc">The query</div></li>
54319 * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
54320 * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
54325 if(this.transform){
54326 var s = Ext.getDom(this.transform);
54327 if(!this.hiddenName){
54328 this.hiddenName = s.name;
54331 this.mode = 'local';
54332 var d = [], opts = s.options;
54333 for(var i = 0, len = opts.length;i < len; i++){
54335 value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text;
54336 if(o.selected && Ext.isEmpty(this.value, true)) {
54337 this.value = value;
54339 d.push([value, o.text]);
54341 this.store = new Ext.data.ArrayStore({
54343 fields: ['value', 'text'],
54347 this.valueField = 'value';
54348 this.displayField = 'text';
54350 s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference
54351 if(!this.lazyRender){
54352 this.target = true;
54353 this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
54354 this.render(this.el.parentNode, s);
54355 Ext.removeNode(s); // remove it
54357 Ext.removeNode(s); // remove it
54360 //auto-configure store from local array data
54361 else if(this.store){
54362 this.store = Ext.StoreMgr.lookup(this.store);
54363 if(this.store.autoCreated){
54364 this.displayField = this.valueField = 'field1';
54365 if(!this.store.expandData){
54366 this.displayField = 'field2';
54368 this.mode = 'local';
54372 this.selectedIndex = -1;
54373 if(this.mode == 'local'){
54374 if(!Ext.isDefined(this.initialConfig.queryDelay)){
54375 this.queryDelay = 10;
54377 if(!Ext.isDefined(this.initialConfig.minChars)){
54384 onRender : function(ct, position){
54385 Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
54386 if(this.hiddenName){
54387 this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
54388 id: (this.hiddenId||this.hiddenName)}, 'before', true);
54390 // prevent input submission
54391 this.el.dom.removeAttribute('name');
54394 this.el.dom.setAttribute('autocomplete', 'off');
54397 if(!this.lazyInit){
54400 this.on('focus', this.initList, this, {single: true});
54405 initValue : function(){
54406 Ext.form.ComboBox.superclass.initValue.call(this);
54407 if(this.hiddenField){
54408 this.hiddenField.value =
54409 Ext.isDefined(this.hiddenValue) ? this.hiddenValue :
54410 Ext.isDefined(this.value) ? this.value : '';
54415 initList : function(){
54417 var cls = 'x-combo-list';
54419 this.list = new Ext.Layer({
54420 parentEl: this.getListParent(),
54421 shadow: this.shadow,
54422 cls: [cls, this.listClass].join(' '),
54426 var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
54427 this.list.setSize(lw, 0);
54428 this.list.swallowEvent('mousewheel');
54429 this.assetHeight = 0;
54430 if(this.syncFont !== false){
54431 this.list.setStyle('font-size', this.el.getStyle('font-size'));
54434 this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
54435 this.assetHeight += this.header.getHeight();
54438 this.innerList = this.list.createChild({cls:cls+'-inner'});
54439 this.mon(this.innerList, 'mouseover', this.onViewOver, this);
54440 this.mon(this.innerList, 'mousemove', this.onViewMove, this);
54441 this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
54444 this.footer = this.list.createChild({cls:cls+'-ft'});
54445 this.pageTb = new Ext.PagingToolbar({
54447 pageSize: this.pageSize,
54448 renderTo:this.footer
54450 this.assetHeight += this.footer.getHeight();
54455 * @cfg {String/Ext.XTemplate} tpl <p>The template string, or {@link Ext.XTemplate} instance to
54456 * use to display each item in the dropdown list. The dropdown list is displayed in a
54457 * DataView. See {@link #view}.</p>
54458 * <p>The default template string is:</p><pre><code>
54459 '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
54461 * <p>Override the default value to create custom UI layouts for items in the list.
54462 * For example:</p><pre><code>
54463 '<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
54465 * <p>The template <b>must</b> contain one or more substitution parameters using field
54466 * names from the Combo's</b> {@link #store Store}. In the example above an
54467 * <pre>ext:qtip</pre> attribute is added to display other fields from the Store.</p>
54468 * <p>To preserve the default visual look of list items, add the CSS class name
54469 * <pre>x-combo-list-item</pre> to the template's container element.</p>
54470 * <p>Also see {@link #itemSelector} for additional details.</p>
54472 this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
54474 * @cfg {String} itemSelector
54475 * <p>A simple CSS selector (e.g. div.some-class or span:first-child) that will be
54476 * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown
54477 * display will be working with.</p>
54478 * <p><b>Note</b>: this setting is <b>required</b> if a custom XTemplate has been
54479 * specified in {@link #tpl} which assigns a class other than <pre>'x-combo-list-item'</pre>
54480 * to dropdown list items</b>
54485 * The {@link Ext.DataView DataView} used to display the ComboBox's options.
54486 * @type Ext.DataView
54488 this.view = new Ext.DataView({
54489 applyTo: this.innerList,
54491 singleSelect: true,
54492 selectedClass: this.selectedClass,
54493 itemSelector: this.itemSelector || '.' + cls + '-item',
54494 emptyText: this.listEmptyText
54497 this.mon(this.view, 'click', this.onViewClick, this);
54499 this.bindStore(this.store, true);
54501 if(this.resizable){
54502 this.resizer = new Ext.Resizable(this.list, {
54503 pinned:true, handles:'se'
54505 this.mon(this.resizer, 'resize', function(r, w, h){
54506 this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
54507 this.listWidth = w;
54508 this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
54509 this.restrictHeight();
54512 this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
54518 * <p>Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.</p>
54519 * A custom implementation may be provided as a configuration option if the floating list needs to be rendered
54520 * to a different Element. An example might be rendering the list inside a Menu so that clicking
54521 * the list does not hide the Menu:<pre><code>
54522 var store = new Ext.data.ArrayStore({
54524 fields: ['initials', 'fullname'],
54526 ['FF', 'Fred Flintstone'],
54527 ['BR', 'Barney Rubble']
54531 var combo = new Ext.form.ComboBox({
54533 displayField: 'fullname',
54534 emptyText: 'Select a name...',
54535 forceSelection: true,
54536 getListParent: function() {
54537 return this.el.up('.x-menu');
54539 iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
54541 selectOnFocus: true,
54542 triggerAction: 'all',
54547 var menu = new Ext.menu.Menu({
54550 combo // A Field in a Menu
54555 getListParent : function() {
54556 return document.body;
54560 * Returns the store associated with this combo.
54561 * @return {Ext.data.Store} The store
54563 getStore : function(){
54568 bindStore : function(store, initial){
54569 if(this.store && !initial){
54570 this.store.un('beforeload', this.onBeforeLoad, this);
54571 this.store.un('load', this.onLoad, this);
54572 this.store.un('exception', this.collapse, this);
54573 if(this.store !== store && this.store.autoDestroy){
54574 this.store.destroy();
54579 this.view.bindStore(null);
54585 this.lastQuery = null;
54587 this.pageTb.bindStore(store);
54591 this.store = Ext.StoreMgr.lookup(store);
54594 beforeload: this.onBeforeLoad,
54596 exception: this.collapse
54600 this.view.bindStore(store);
54606 initEvents : function(){
54607 Ext.form.ComboBox.superclass.initEvents.call(this);
54609 this.keyNav = new Ext.KeyNav(this.el, {
54610 "up" : function(e){
54611 this.inKeyMode = true;
54615 "down" : function(e){
54616 if(!this.isExpanded()){
54617 this.onTriggerClick();
54619 this.inKeyMode = true;
54624 "enter" : function(e){
54625 this.onViewClick();
54626 this.delayedCheck = true;
54627 this.unsetDelayCheck.defer(10, this);
54630 "esc" : function(e){
54634 "tab" : function(e){
54635 this.onViewClick(false);
54641 doRelay : function(foo, bar, hname){
54642 if(hname == 'down' || this.scope.isExpanded()){
54643 return Ext.KeyNav.prototype.doRelay.apply(this, arguments);
54648 forceKeyDown : true
54650 this.queryDelay = Math.max(this.queryDelay || 10,
54651 this.mode == 'local' ? 10 : 250);
54652 this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
54653 if(this.typeAhead){
54654 this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
54656 if(this.editable !== false && !this.enableKeyEvents){
54657 this.mon(this.el, 'keyup', this.onKeyUp, this);
54662 onDestroy : function(){
54664 this.dqTask.cancel();
54665 this.dqTask = null;
54667 this.bindStore(null);
54674 Ext.form.ComboBox.superclass.onDestroy.call(this);
54678 unsetDelayCheck : function(){
54679 delete this.delayedCheck;
54683 fireKey : function(e){
54684 var fn = function(ev){
54685 if (ev.isNavKeyPress() && !this.isExpanded() && !this.delayedCheck) {
54686 this.fireEvent("specialkey", this, ev);
54689 //For some reason I can't track down, the events fire in a different order in webkit.
54690 //Need a slight delay here
54691 if(this.inEditor && Ext.isWebKit && e.getKey() == e.TAB){
54692 fn.defer(10, this, [new Ext.EventObjectImpl(e)]);
54699 onResize : function(w, h){
54700 Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
54701 if(this.list && !Ext.isDefined(this.listWidth)){
54702 var lw = Math.max(w, this.minListWidth);
54703 this.list.setWidth(lw);
54704 this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
54709 onEnable : function(){
54710 Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
54711 if(this.hiddenField){
54712 this.hiddenField.disabled = false;
54717 onDisable : function(){
54718 Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
54719 if(this.hiddenField){
54720 this.hiddenField.disabled = true;
54725 onBeforeLoad : function(){
54726 if(!this.hasFocus){
54729 this.innerList.update(this.loadingText ?
54730 '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
54731 this.restrictHeight();
54732 this.selectedIndex = -1;
54736 onLoad : function(){
54737 if(!this.hasFocus){
54740 if(this.store.getCount() > 0){
54742 this.restrictHeight();
54743 if(this.lastQuery == this.allQuery){
54745 this.el.dom.select();
54747 if(!this.selectByValue(this.value, true)){
54748 this.select(0, true);
54752 if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
54753 this.taTask.delay(this.typeAheadDelay);
54757 this.onEmptyResults();
54763 onTypeAhead : function(){
54764 if(this.store.getCount() > 0){
54765 var r = this.store.getAt(0);
54766 var newValue = r.data[this.displayField];
54767 var len = newValue.length;
54768 var selStart = this.getRawValue().length;
54769 if(selStart != len){
54770 this.setRawValue(newValue);
54771 this.selectText(selStart, newValue.length);
54777 onSelect : function(record, index){
54778 if(this.fireEvent('beforeselect', this, record, index) !== false){
54779 this.setValue(record.data[this.valueField || this.displayField]);
54781 this.fireEvent('select', this, record, index);
54786 getName: function(){
54787 var hf = this.hiddenField;
54788 return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this);
54792 * Returns the currently selected field value or empty string if no value is set.
54793 * @return {String} value The selected value
54795 getValue : function(){
54796 if(this.valueField){
54797 return Ext.isDefined(this.value) ? this.value : '';
54799 return Ext.form.ComboBox.superclass.getValue.call(this);
54804 * Clears any text/value currently set in the field
54806 clearValue : function(){
54807 if(this.hiddenField){
54808 this.hiddenField.value = '';
54810 this.setRawValue('');
54811 this.lastSelectionText = '';
54812 this.applyEmptyText();
54817 * Sets the specified value into the field. If the value finds a match, the corresponding record text
54818 * will be displayed in the field. If the value does not match the data value of an existing item,
54819 * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
54820 * Otherwise the field will be blank (although the value will still be set).
54821 * @param {String} value The value to match
54822 * @return {Ext.form.Field} this
54824 setValue : function(v){
54826 if(this.valueField){
54827 var r = this.findRecord(this.valueField, v);
54829 text = r.data[this.displayField];
54830 }else if(Ext.isDefined(this.valueNotFoundText)){
54831 text = this.valueNotFoundText;
54834 this.lastSelectionText = text;
54835 if(this.hiddenField){
54836 this.hiddenField.value = v;
54838 Ext.form.ComboBox.superclass.setValue.call(this, text);
54844 findRecord : function(prop, value){
54846 if(this.store.getCount() > 0){
54847 this.store.each(function(r){
54848 if(r.data[prop] == value){
54858 onViewMove : function(e, t){
54859 this.inKeyMode = false;
54863 onViewOver : function(e, t){
54864 if(this.inKeyMode){ // prevent key nav and mouse over conflicts
54867 var item = this.view.findItemFromChild(t);
54869 var index = this.view.indexOf(item);
54870 this.select(index, false);
54875 onViewClick : function(doFocus){
54876 var index = this.view.getSelectedIndexes()[0];
54877 var r = this.store.getAt(index);
54879 this.onSelect(r, index);
54881 if(doFocus !== false){
54887 restrictHeight : function(){
54888 this.innerList.dom.style.height = '';
54889 var inner = this.innerList.dom;
54890 var pad = this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight;
54891 var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
54892 var ha = this.getPosition()[1]-Ext.getBody().getScroll().top;
54893 var hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height;
54894 var space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
54895 h = Math.min(h, space, this.maxHeight);
54897 this.innerList.setHeight(h);
54898 this.list.beginUpdate();
54899 this.list.setHeight(h+pad);
54900 this.list.alignTo(this.wrap, this.listAlign);
54901 this.list.endUpdate();
54905 onEmptyResults : function(){
54910 * Returns true if the dropdown list is expanded, else false.
54912 isExpanded : function(){
54913 return this.list && this.list.isVisible();
54917 * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
54918 * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
54919 * @param {String} value The data value of the item to select
54920 * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
54921 * selected item if it is not currently in view (defaults to true)
54922 * @return {Boolean} True if the value matched an item in the list, else false
54924 selectByValue : function(v, scrollIntoView){
54925 if(!Ext.isEmpty(v, true)){
54926 var r = this.findRecord(this.valueField || this.displayField, v);
54928 this.select(this.store.indexOf(r), scrollIntoView);
54936 * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
54937 * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
54938 * @param {Number} index The zero-based index of the list item to select
54939 * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
54940 * selected item if it is not currently in view (defaults to true)
54942 select : function(index, scrollIntoView){
54943 this.selectedIndex = index;
54944 this.view.select(index);
54945 if(scrollIntoView !== false){
54946 var el = this.view.getNode(index);
54948 this.innerList.scrollChildIntoView(el, false);
54954 selectNext : function(){
54955 var ct = this.store.getCount();
54957 if(this.selectedIndex == -1){
54959 }else if(this.selectedIndex < ct-1){
54960 this.select(this.selectedIndex+1);
54966 selectPrev : function(){
54967 var ct = this.store.getCount();
54969 if(this.selectedIndex == -1){
54971 }else if(this.selectedIndex !== 0){
54972 this.select(this.selectedIndex-1);
54978 onKeyUp : function(e){
54979 var k = e.getKey();
54980 if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){
54982 this.dqTask.delay(this.queryDelay);
54984 Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
54988 validateBlur : function(){
54989 return !this.list || !this.list.isVisible();
54993 initQuery : function(){
54994 this.doQuery(this.getRawValue());
54998 beforeBlur : function(){
54999 var val = this.getRawValue();
55000 if(this.forceSelection){
55001 if(val.length > 0 && val != this.emptyText){
55002 this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : '';
55003 this.applyEmptyText();
55008 var rec = this.findRecord(this.displayField, val);
55010 val = rec.get(this.valueField || this.displayField);
55012 this.setValue(val);
55017 * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
55018 * query allowing the query action to be canceled if needed.
55019 * @param {String} query The SQL query to execute
55020 * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
55021 * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
55022 * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
55024 doQuery : function(q, forceAll){
55025 q = Ext.isEmpty(q) ? '' : q;
55028 forceAll: forceAll,
55032 if(this.fireEvent('beforequery', qe)===false || qe.cancel){
55036 forceAll = qe.forceAll;
55037 if(forceAll === true || (q.length >= this.minChars)){
55038 if(this.lastQuery !== q){
55039 this.lastQuery = q;
55040 if(this.mode == 'local'){
55041 this.selectedIndex = -1;
55043 this.store.clearFilter();
55045 this.store.filter(this.displayField, q);
55049 this.store.baseParams[this.queryParam] = q;
55051 params: this.getParams(q)
55056 this.selectedIndex = -1;
55063 getParams : function(q){
55065 //p[this.queryParam] = q;
55068 p.limit = this.pageSize;
55074 * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
55076 collapse : function(){
55077 if(!this.isExpanded()){
55081 Ext.getDoc().un('mousewheel', this.collapseIf, this);
55082 Ext.getDoc().un('mousedown', this.collapseIf, this);
55083 this.fireEvent('collapse', this);
55087 collapseIf : function(e){
55088 if(!e.within(this.wrap) && !e.within(this.list)){
55094 * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
55096 expand : function(){
55097 if(this.isExpanded() || !this.hasFocus){
55100 this.list.alignTo(this.wrap, this.listAlign);
55103 this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
55107 mousewheel: this.collapseIf,
55108 mousedown: this.collapseIf
55110 this.fireEvent('expand', this);
55114 * @method onTriggerClick
55118 // Implements the default empty TriggerField.onTriggerClick function
55119 onTriggerClick : function(){
55123 if(this.isExpanded()){
55128 if(this.triggerAction == 'all') {
55129 this.doQuery(this.allQuery, true);
55131 this.doQuery(this.getRawValue());
55142 * @cfg {Boolean} grow @hide
55145 * @cfg {Number} growMin @hide
55148 * @cfg {Number} growMax @hide
55152 Ext.reg('combo', Ext.form.ComboBox);/**
55153 * @class Ext.form.Checkbox
55154 * @extends Ext.form.Field
55155 * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields.
55157 * Creates a new Checkbox
55158 * @param {Object} config Configuration options
55161 Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
55163 * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
55165 focusClass : undefined,
55167 * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to 'x-form-field')
55169 fieldClass : 'x-form-field',
55171 * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
55175 * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
55176 * {tag: 'input', type: 'checkbox', autocomplete: 'off'})
55178 defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
55180 * @cfg {String} boxLabel The text that appears beside the checkbox
55183 * @cfg {String} inputValue The value that should go into the generated input element's value attribute
55186 * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
55187 * handling the check event). The handler is passed the following parameters:
55188 * <div class="mdetail-params"><ul>
55189 * <li><b>checkbox</b> : Ext.form.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
55190 * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
55194 * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
55195 * (defaults to this Checkbox).
55199 actionMode : 'wrap',
55202 initComponent : function(){
55203 Ext.form.Checkbox.superclass.initComponent.call(this);
55207 * Fires when the checkbox is checked or unchecked.
55208 * @param {Ext.form.Checkbox} this This checkbox
55209 * @param {Boolean} checked The new checked value
55216 onResize : function(){
55217 Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
55218 if(!this.boxLabel && !this.fieldLabel){
55219 this.el.alignTo(this.wrap, 'c-c');
55224 initEvents : function(){
55225 Ext.form.Checkbox.superclass.initEvents.call(this);
55226 this.mon(this.el, 'click', this.onClick, this);
55227 this.mon(this.el, 'change', this.onClick, this);
55231 getResizeEl : function(){
55236 getPositionEl : function(){
55242 * Overridden and disabled. The editor element does not support standard valid/invalid marking.
55245 markInvalid : Ext.emptyFn,
55248 * Overridden and disabled. The editor element does not support standard valid/invalid marking.
55251 clearInvalid : Ext.emptyFn,
55254 onRender : function(ct, position){
55255 Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
55256 if(this.inputValue !== undefined){
55257 this.el.dom.value = this.inputValue;
55259 this.wrap = this.el.wrap({cls: 'x-form-check-wrap'});
55261 this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
55264 this.setValue(true);
55266 this.checked = this.el.dom.checked;
55271 onDestroy : function(){
55272 Ext.destroy(this.wrap);
55273 Ext.form.Checkbox.superclass.onDestroy.call(this);
55277 initValue : function() {
55278 this.originalValue = this.getValue();
55282 * Returns the checked state of the checkbox.
55283 * @return {Boolean} True if checked, else false
55285 getValue : function(){
55287 return this.el.dom.checked;
55293 onClick : function(){
55294 if(this.el.dom.checked != this.checked){
55295 this.setValue(this.el.dom.checked);
55300 * Sets the checked state of the checkbox, fires the 'check' event, and calls a
55301 * <code>{@link #handler}</code> (if configured).
55302 * @param {Boolean/String} checked The following values will check the checkbox:
55303 * <code>true, 'true', '1', or 'on'</code>. Any other value will uncheck the checkbox.
55304 * @return {Ext.form.Field} this
55306 setValue : function(v){
55307 var checked = this.checked ;
55308 this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
55310 this.el.dom.checked = this.checked;
55311 this.el.dom.defaultChecked = this.checked;
55313 if(checked != this.checked){
55314 this.fireEvent('check', this, this.checked);
55316 this.handler.call(this.scope || this, this, this.checked);
55322 Ext.reg('checkbox', Ext.form.Checkbox);
55324 * @class Ext.form.CheckboxGroup
55325 * @extends Ext.form.Field
55326 * <p>A grouping container for {@link Ext.form.Checkbox} controls.</p>
55327 * <p>Sample usage:</p>
55329 var myCheckboxGroup = new Ext.form.CheckboxGroup({
55331 xtype: 'checkboxgroup',
55332 fieldLabel: 'Single Column',
55333 itemCls: 'x-check-group-alt',
55334 // Put all controls in a single column with width 100%
55337 {boxLabel: 'Item 1', name: 'cb-col-1'},
55338 {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
55339 {boxLabel: 'Item 3', name: 'cb-col-3'}
55344 * Creates a new CheckboxGroup
55345 * @param {Object} config Configuration options
55346 * @xtype checkboxgroup
55348 Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
55350 * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects
55351 * to arrange in the group.
55354 * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
55355 * checkbox/radio controls using automatic layout. This config can take several types of values:
55356 * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
55357 * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
55358 * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
55359 * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
55360 * <li><b>Array</b> : Object<p class="sub-desc">You can also specify an array of column widths, mixing integer
55361 * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
55362 * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
55363 * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
55364 * container you should do so.</p></li></ul>
55368 * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
55369 * top to bottom before starting on the next column. The number of controls in each column will be automatically
55370 * calculated to keep columns as even as possible. The default value is false, so that controls will be added
55371 * to columns one at a time, completely filling each row left to right before starting on the next row.
55375 * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
55376 * If no items are selected at validation time, {@link @blankText} will be used as the error text.
55380 * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
55381 * select at least one item in this group")
55383 blankText : "You must select at least one item in this group",
55386 defaultType : 'checkbox',
55389 groupCls : 'x-form-check-group',
55392 initComponent: function(){
55396 * Fires when the state of a child checkbox changes.
55397 * @param {Ext.form.CheckboxGroup} this
55398 * @param {Array} checked An array containing the checked boxes.
55402 Ext.form.CheckboxGroup.superclass.initComponent.call(this);
55406 onRender : function(ct, position){
55409 cls: this.groupCls,
55415 defaultType: this.defaultType,
55424 if(this.items[0].items){
55426 // The container has standard ColumnLayout configs, so pass them in directly
55428 Ext.apply(panelCfg, {
55429 layoutConfig: {columns: this.items.length},
55430 defaults: this.defaults,
55433 for(var i=0, len=this.items.length; i<len; i++){
55434 Ext.applyIf(this.items[i], colCfg);
55439 // The container has field item configs, so we have to generate the column
55440 // panels first then move the items into the columns as needed.
55442 var numCols, cols = [];
55444 if(typeof this.columns == 'string'){ // 'auto' so create a col per item
55445 this.columns = this.items.length;
55447 if(!Ext.isArray(this.columns)){
55449 for(var i=0; i<this.columns; i++){
55450 cs.push((100/this.columns)*.01); // distribute by even %
55455 numCols = this.columns.length;
55457 // Generate the column configs with the correct width setting
55458 for(var i=0; i<numCols; i++){
55459 var cc = Ext.apply({items:[]}, colCfg);
55460 cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
55462 cc.defaults = Ext.apply(cc.defaults || {}, this.defaults)
55467 // Distribute the original items into the columns
55469 var rows = Math.ceil(this.items.length / numCols), ri = 0;
55470 for(var i=0, len=this.items.length; i<len; i++){
55471 if(i>0 && i%rows==0){
55474 if(this.items[i].fieldLabel){
55475 this.items[i].hideLabel = false;
55477 cols[ri].items.push(this.items[i]);
55480 for(var i=0, len=this.items.length; i<len; i++){
55481 var ci = i % numCols;
55482 if(this.items[i].fieldLabel){
55483 this.items[i].hideLabel = false;
55485 cols[ci].items.push(this.items[i]);
55489 Ext.apply(panelCfg, {
55490 layoutConfig: {columns: numCols},
55495 this.panel = new Ext.Panel(panelCfg);
55496 this.panel.ownerCt = this;
55497 this.el = this.panel.getEl();
55499 if(this.forId && this.itemCls){
55500 var l = this.el.up(this.itemCls).child('label', true);
55502 l.setAttribute('htmlFor', this.forId);
55506 var fields = this.panel.findBy(function(c){
55507 return c.isFormField;
55510 this.items = new Ext.util.MixedCollection();
55511 this.items.addAll(fields);
55513 Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
55516 afterRender : function(){
55517 Ext.form.CheckboxGroup.superclass.afterRender.call(this);
55519 this.setValue.apply(this, this.values);
55520 delete this.values;
55522 this.eachItem(function(item){
55523 item.on('check', this.fireChecked, this);
55524 item.inGroup = true;
55529 doLayout: function(){
55530 //ugly method required to layout hidden items
55532 this.panel.forceLayout = this.ownerCt.forceLayout;
55533 this.panel.doLayout();
55538 fireChecked: function(){
55540 this.eachItem(function(item){
55545 this.fireEvent('change', this, arr);
55549 validateValue : function(value){
55550 if(!this.allowBlank){
55552 this.eachItem(function(f){
55554 return (blank = false);
55558 this.markInvalid(this.blankText);
55566 onDisable : function(){
55567 this.eachItem(function(item){
55573 onEnable : function(){
55574 this.eachItem(function(item){
55580 doLayout: function(){
55582 this.panel.forceLayout = this.ownerCt.forceLayout;
55583 this.panel.doLayout();
55588 onResize : function(w, h){
55589 this.panel.setSize(w, h);
55590 this.panel.doLayout();
55593 // inherit docs from Field
55594 reset : function(){
55595 Ext.form.CheckboxGroup.superclass.reset.call(this);
55596 this.eachItem(function(c){
55604 * {@link Ext.form.Checkbox#setValue Set the value(s)} of an item or items
55605 * in the group. Examples illustrating how this method may be called:
55607 // call with name and value
55608 myCheckboxGroup.setValue('cb-col-1', true);
55609 // call with an array of boolean values
55610 myCheckboxGroup.setValue([true, false, false]);
55611 // call with an object literal specifying item:value pairs
55612 myCheckboxGroup.setValue({
55616 // use comma separated string to set items with name to true (checked)
55617 myCheckboxGroup.setValue('cb-col-1,cb-col-3');
55619 * See {@link Ext.form.Checkbox#setValue} for additional information.
55620 * @param {Mixed} id The checkbox to check, or as described by example shown.
55621 * @param {Boolean} value (optional) The value to set the item.
55622 * @return {Ext.form.CheckboxGroup} this
55624 setValue : function(id, value){
55626 if(arguments.length == 1){
55627 if(Ext.isArray(id)){
55628 //an array of boolean values
55629 Ext.each(id, function(val, idx){
55630 var item = this.items.itemAt(idx);
55632 item.setValue(val);
55635 }else if(Ext.isObject(id)){
55636 //set of name/value pairs
55638 var f = this.getBox(i);
55644 this.setValueForItem(id);
55647 var f = this.getBox(id);
55653 this.values = arguments;
55659 onDestroy: function(){
55660 Ext.destroy(this.panel);
55661 Ext.form.CheckboxGroup.superclass.onDestroy.call(this);
55665 setValueForItem : function(val){
55666 val = String(val).split(',');
55667 this.eachItem(function(item){
55668 if(val.indexOf(item.inputValue)> -1){
55669 item.setValue(true);
55675 getBox : function(id){
55677 this.eachItem(function(f){
55678 if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){
55687 * Gets an array of the selected {@link Ext.form.Checkbox} in the group.
55688 * @return {Array} An array of the selected checkboxes.
55690 getValue : function(){
55692 this.eachItem(function(item){
55701 eachItem: function(fn){
55702 if(this.items && this.items.each){
55703 this.items.each(fn, this);
55708 * @cfg {String} name
55712 * @method initValue
55715 initValue : Ext.emptyFn,
55720 getValue : Ext.emptyFn,
55722 * @method getRawValue
55725 getRawValue : Ext.emptyFn,
55728 * @method setRawValue
55731 setRawValue : Ext.emptyFn
55735 Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
55737 * @class Ext.form.Radio
55738 * @extends Ext.form.Checkbox
55739 * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
55740 * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
55742 * Creates a new Radio
55743 * @param {Object} config Configuration options
55746 Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
55747 inputType: 'radio',
55750 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
55753 markInvalid : Ext.emptyFn,
55755 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
55758 clearInvalid : Ext.emptyFn,
55761 * If this radio is part of a group, it will return the selected value
55764 getGroupValue : function(){
55765 var p = this.el.up('form') || Ext.getBody();
55766 var c = p.child('input[name='+this.el.dom.name+']:checked', true);
55767 return c ? c.value : null;
55771 onClick : function(){
55772 if(this.el.dom.checked != this.checked){
55773 var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
55774 els.each(function(el){
55775 if(el.dom.id == this.id){
55776 this.setValue(true);
55778 Ext.getCmp(el.dom.id).setValue(false);
55785 * Sets either the checked/unchecked status of this Radio, or, if a string value
55786 * is passed, checks a sibling Radio of the same name whose value is the value specified.
55787 * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
55788 * @return {Ext.form.Field} this
55790 setValue : function(v){
55791 if (typeof v == 'boolean') {
55792 Ext.form.Radio.superclass.setValue.call(this, v);
55794 var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
55796 Ext.getCmp(r.id).setValue(true);
55803 getCheckEl: function(){
55805 return this.el.up('.x-form-radio-group')
55807 return this.el.up('form') || Ext.getBody();
55810 Ext.reg('radio', Ext.form.Radio);
55812 * @class Ext.form.RadioGroup
55813 * @extends Ext.form.CheckboxGroup
55814 * A grouping container for {@link Ext.form.Radio} controls.
55816 * Creates a new RadioGroup
55817 * @param {Object} config Configuration options
55818 * @xtype radiogroup
55820 Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
55822 * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
55823 * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
55824 * be used as the error text.
55828 * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
55829 * (defaults to 'You must select one item in this group')
55831 blankText : 'You must select one item in this group',
55834 defaultType : 'radio',
55837 groupCls : 'x-form-radio-group',
55841 * Fires when the state of a child radio changes.
55842 * @param {Ext.form.RadioGroup} this
55843 * @param {Ext.form.Radio} checked The checked radio
55847 * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
55848 * @return {Ext.form.Radio} The selected radio.
55850 getValue : function(){
55852 this.eachItem(function(item){
55862 * Sets the checked radio in the group.
55863 * @param {String/Ext.form.Radio} id The radio to check.
55864 * @param {Boolean} value The value to set the radio.
55865 * @return {Ext.form.RadioGroup} this
55867 setValue : function(id, value){
55869 if(arguments.length > 1){
55870 var f = this.getBox(id);
55874 this.eachItem(function(item){
55876 item.setValue(false);
55882 this.setValueForItem(id);
55885 this.values = arguments;
55891 fireChecked : function(){
55892 if(!this.checkTask){
55893 this.checkTask = new Ext.util.DelayedTask(this.bufferChecked, this);
55895 this.checkTask.delay(10);
55899 bufferChecked : function(){
55901 this.eachItem(function(item){
55907 this.fireEvent('change', this, out);
55910 onDestroy : function(){
55911 if(this.checkTask){
55912 this.checkTask.cancel();
55913 this.checkTask = null;
55915 Ext.form.RadioGroup.superclass.onDestroy.call(this);
55920 Ext.reg('radiogroup', Ext.form.RadioGroup);
55922 * @class Ext.form.Hidden
\r
55923 * @extends Ext.form.Field
\r
55924 * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
\r
55926 * Create a new Hidden field.
\r
55927 * @param {Object} config Configuration options
\r
55930 Ext.form.Hidden = Ext.extend(Ext.form.Field, {
\r
55932 inputType : 'hidden',
\r
55935 onRender : function(){
\r
55936 Ext.form.Hidden.superclass.onRender.apply(this, arguments);
\r
55940 initEvents : function(){
\r
55941 this.originalValue = this.getValue();
\r
55944 // These are all private overrides
\r
55945 setSize : Ext.emptyFn,
\r
55946 setWidth : Ext.emptyFn,
\r
55947 setHeight : Ext.emptyFn,
\r
55948 setPosition : Ext.emptyFn,
\r
55949 setPagePosition : Ext.emptyFn,
\r
55950 markInvalid : Ext.emptyFn,
\r
55951 clearInvalid : Ext.emptyFn
\r
55953 Ext.reg('hidden', Ext.form.Hidden);/**
55954 * @class Ext.form.BasicForm
55955 * @extends Ext.util.Observable
55956 * <p>Encapsulates the DOM <form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
55957 * input field management, validation, submission, and form loading services.</p>
55958 * <p>By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
55959 * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option.</p>
55960 * <p><b><u>File Uploads</u></b></p>
55961 * <p>{@link #fileUpload File uploads} are not performed using Ajax submission, that
55962 * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
55963 * manner with the DOM <tt><form></tt> element temporarily modified to have its
55964 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
55965 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
55966 * but removed after the return data has been gathered.</p>
55967 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
55968 * server is using JSON to send the return object, then the
55969 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
55970 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
55971 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
55972 * "<" as "&lt;", "&" as "&amp;" etc.</p>
55973 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
55974 * is created containing a <tt>responseText</tt> property in order to conform to the
55975 * requirements of event handlers and callbacks.</p>
55976 * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
55977 * and some server technologies (notably JEE) may require some custom processing in order to
55978 * retrieve parameter names and parameter values from the packet content.</p>
55980 * @param {Mixed} el The form element or its id
55981 * @param {Object} config Configuration options
55983 Ext.form.BasicForm = function(el, config){
55984 Ext.apply(this, config);
55985 if(Ext.isString(this.paramOrder)){
55986 this.paramOrder = this.paramOrder.split(/[\s,|]/);
55990 * A {@link Ext.util.MixedCollection MixedCollection) containing all the Ext.form.Fields in this form.
55991 * @type MixedCollection
55993 this.items = new Ext.util.MixedCollection(false, function(o){
55994 return o.itemId || o.id || (o.id = Ext.id());
55998 * @event beforeaction
55999 * Fires before any action is performed. Return false to cancel the action.
56000 * @param {Form} this
56001 * @param {Action} action The {@link Ext.form.Action} to be performed
56005 * @event actionfailed
56006 * Fires when an action fails.
56007 * @param {Form} this
56008 * @param {Action} action The {@link Ext.form.Action} that failed
56012 * @event actioncomplete
56013 * Fires when an action is completed.
56014 * @param {Form} this
56015 * @param {Action} action The {@link Ext.form.Action} that completed
56023 Ext.form.BasicForm.superclass.constructor.call(this);
56026 Ext.extend(Ext.form.BasicForm, Ext.util.Observable, {
56028 * @cfg {String} method
56029 * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
56032 * @cfg {DataReader} reader
56033 * An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to read
56034 * data when executing 'load' actions. This is optional as there is built-in
56035 * support for processing JSON. For additional information on using an XMLReader
56036 * see the example provided in examples/form/xml-form.html.
56039 * @cfg {DataReader} errorReader
56040 * <p>An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to
56041 * read field error messages returned from 'submit' actions. This is optional
56042 * as there is built-in support for processing JSON.</p>
56043 * <p>The Records which provide messages for the invalid Fields must use the
56044 * Field name (or id) as the Record ID, and must contain a field called 'msg'
56045 * which contains the error message.</p>
56046 * <p>The errorReader does not have to be a full-blown implementation of a
56047 * DataReader. It simply needs to implement a <tt>read(xhr)</tt> function
56048 * which returns an Array of Records in an object with the following
56049 * structure:</p><pre><code>
56051 records: recordArray
56056 * @cfg {String} url
56057 * The URL to use for form actions if one isn't supplied in the
56058 * <code>{@link #doAction doAction} options</code>.
56061 * @cfg {Boolean} fileUpload
56062 * Set to true if this form is a file upload.
56063 * <p>File uploads are not performed using normal 'Ajax' techniques, that is they are <b>not</b>
56064 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
56065 * DOM <tt><form></tt> element temporarily modified to have its
56066 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
56067 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
56068 * but removed after the return data has been gathered.</p>
56069 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
56070 * server is using JSON to send the return object, then the
56071 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
56072 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
56073 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
56074 * "<" as "&lt;", "&" as "&amp;" etc.</p>
56075 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
56076 * is created containing a <tt>responseText</tt> property in order to conform to the
56077 * requirements of event handlers and callbacks.</p>
56078 * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
56079 * and some server technologies (notably JEE) may require some custom processing in order to
56080 * retrieve parameter names and parameter values from the packet content.</p>
56083 * @cfg {Object} baseParams
56084 * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
56085 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
56088 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
56093 * @cfg {Object} api (Optional) If specified load and submit actions will be handled
56094 * with {@link Ext.form.Action.DirectLoad} and {@link Ext.form.Action.DirectSubmit}.
56095 * Methods which have been imported by Ext.Direct can be specified here to load and submit
56097 * Such as the following:<pre><code>
56099 load: App.ss.MyProfile.load,
56100 submit: App.ss.MyProfile.submit
56103 * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
56104 * to customize how the load method is invoked.
56105 * Submit actions will always use a standard form submit. The formHandler configuration must
56106 * be set on the associated server-side method which has been imported by Ext.Direct</p>
56110 * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
56111 * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
56112 * <code>load</code> configuration.</p>
56113 * <br><p>Specify the params in the order in which they must be executed on the
56114 * server-side as either (1) an Array of String values, or (2) a String of params
56115 * delimited by either whitespace, comma, or pipe. For example,
56116 * any of the following would be acceptable:</p><pre><code>
56117 paramOrder: ['param1','param2','param3']
56118 paramOrder: 'param1 param2 param3'
56119 paramOrder: 'param1,param2,param3'
56120 paramOrder: 'param1|param2|param'
56123 paramOrder: undefined,
56126 * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
56127 * <code>load</code> configuration. Send parameters as a collection of named
56128 * arguments (defaults to <tt>false</tt>). Providing a
56129 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
56131 paramsAsHash: false,
56135 activeAction : null,
56138 * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
56139 * or {@link #setValues}() data instead of when the form was first created. Defaults to <tt>false</tt>.
56141 trackResetOnLoad : false,
56144 * @cfg {Boolean} standardSubmit If set to true, standard HTML form submits are used instead of XHR (Ajax) style
56145 * form submissions. (defaults to false)<br>
56146 * <p><b>Note:</b> When using standardSubmit, the options to {@link #submit} are ignored because Ext's
56147 * Ajax infrastracture is bypassed. To pass extra parameters (baseParams and params), you will need to
56148 * create hidden fields within the form.</p>
56149 * <p>The url config option is also bypassed, so set the action as well:</p>
56151 PANEL.getForm().getEl().dom.action = 'URL'
56153 * An example encapsulating the above:
56155 new Ext.FormPanel({
56156 standardSubmit: true,
56160 url: 'myProcess.php',
56162 xtype: 'textfield',
56167 handler: function(){
56168 var O = this.ownerCt;
56169 if (O.getForm().isValid()) {
56171 O.getForm().getEl().dom.action = O.url;
56172 if (O.baseParams) {
56173 for (i in O.baseParams) {
56177 value: O.baseParams[i]
56182 O.getForm().submit();
56190 * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
56191 * element by passing it or its id or mask the form itself by passing in true.
56193 * @property waitMsgTarget
56197 initEl : function(el){
56198 this.el = Ext.get(el);
56199 this.id = this.el.id || Ext.id();
56200 if(!this.standardSubmit){
56201 this.el.on('submit', this.onSubmit, this);
56203 this.el.addClass('x-form');
56207 * Get the HTML form Element
56208 * @return Ext.Element
56215 onSubmit : function(e){
56220 destroy: function() {
56221 this.items.each(function(f){
56225 this.el.removeAllListeners();
56228 this.purgeListeners();
56232 * Returns true if client-side validation on the form is successful.
56235 isValid : function(){
56237 this.items.each(function(f){
56246 * <p>Returns true if any fields in this form have changed from their original values.</p>
56247 * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
56248 * Fields' <i>original values</i> are updated when the values are loaded by {@link #setValues}
56249 * or {@link #loadRecord}.</p>
56252 isDirty : function(){
56254 this.items.each(function(f){
56264 * Performs a predefined action ({@link Ext.form.Action.Submit} or
56265 * {@link Ext.form.Action.Load}) or a custom extension of {@link Ext.form.Action}
56266 * to perform application-specific processing.
56267 * @param {String/Object} actionName The name of the predefined action type,
56268 * or instance of {@link Ext.form.Action} to perform.
56269 * @param {Object} options (optional) The options to pass to the {@link Ext.form.Action}.
56270 * All of the config options listed below are supported by both the
56271 * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
56272 * actions unless otherwise noted (custom actions could also accept
56273 * other config options):<ul>
56275 * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
56276 * to the form's {@link #url}.)</div></li>
56278 * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
56279 * to the form's method, or POST if not defined)</div></li>
56281 * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
56282 * (defaults to the form's baseParams, or none if not defined)</p>
56283 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
56285 * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action
56286 * (defaults to the form's default headers)</div></li>
56288 * <li><b>success</b> : Function<div class="sub-desc">The callback that will
56289 * be invoked after a successful response (see top of
56290 * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
56291 * for a description of what constitutes a successful response).
56292 * The function is passed the following parameters:<ul>
56293 * <li><tt>form</tt> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
56294 * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
56295 * <div class="sub-desc">The action object contains these properties of interest:<ul>
56296 * <li><tt>{@link Ext.form.Action#response response}</tt></li>
56297 * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
56298 * <li><tt>{@link Ext.form.Action#type type}</tt></li>
56299 * </ul></div></li></ul></div></li>
56301 * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
56302 * failed transaction attempt. The function is passed the following parameters:<ul>
56303 * <li><tt>form</tt> : The {@link Ext.form.BasicForm} that requested the action.</li>
56304 * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
56305 * <div class="sub-desc">The action object contains these properties of interest:<ul>
56306 * <li><tt>{@link Ext.form.Action#failureType failureType}</tt></li>
56307 * <li><tt>{@link Ext.form.Action#response response}</tt></li>
56308 * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
56309 * <li><tt>{@link Ext.form.Action#type type}</tt></li>
56310 * </ul></div></li></ul></div></li>
56312 * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
56313 * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
56315 * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
56316 * Determines whether a Form's fields are validated in a final call to
56317 * {@link Ext.form.BasicForm#isValid isValid} prior to submission. Set to <tt>false</tt>
56318 * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
56320 * @return {BasicForm} this
56322 doAction : function(action, options){
56323 if(Ext.isString(action)){
56324 action = new Ext.form.Action.ACTION_TYPES[action](this, options);
56326 if(this.fireEvent('beforeaction', this, action) !== false){
56327 this.beforeAction(action);
56328 action.run.defer(100, action);
56334 * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Submit submit action}.
56335 * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
56336 * <p><b>Note:</b> this is ignored when using the {@link #standardSubmit} option.</p>
56337 * <p>The following code:</p><pre><code>
56338 myFormPanel.getForm().submit({
56339 clientValidation: true,
56340 url: 'updateConsignment.php',
56342 newStatus: 'delivered'
56344 success: function(form, action) {
56345 Ext.Msg.alert('Success', action.result.msg);
56347 failure: function(form, action) {
56348 switch (action.failureType) {
56349 case Ext.form.Action.CLIENT_INVALID:
56350 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
56352 case Ext.form.Action.CONNECT_FAILURE:
56353 Ext.Msg.alert('Failure', 'Ajax communication failed');
56355 case Ext.form.Action.SERVER_INVALID:
56356 Ext.Msg.alert('Failure', action.result.msg);
56361 * would process the following server response for a successful submission:<pre><code>
56363 "success":true, // note this is Boolean, not string
56364 "msg":"Consignment updated"
56367 * and the following server response for a failed submission:<pre><code>
56369 "success":false, // note this is Boolean, not string
56370 "msg":"You do not have permission to perform this operation"
56373 * @return {BasicForm} this
56375 submit : function(options){
56376 if(this.standardSubmit){
56377 var v = this.isValid();
56379 this.el.dom.submit();
56383 var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
56384 this.doAction(submitAction, options);
56389 * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Load load action}.
56390 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
56391 * @return {BasicForm} this
56393 load : function(options){
56394 var loadAction = String.format('{0}load', this.api ? 'direct' : '');
56395 this.doAction(loadAction, options);
56400 * Persists the values in this form into the passed {@link Ext.data.Record} object in a beginEdit/endEdit block.
56401 * @param {Record} record The record to edit
56402 * @return {BasicForm} this
56404 updateRecord : function(record){
56405 record.beginEdit();
56406 var fs = record.fields;
56407 fs.each(function(f){
56408 var field = this.findField(f.name);
56410 record.set(f.name, field.getValue());
56418 * Loads an {@link Ext.data.Record} into this form by calling {@link #setValues} with the
56419 * {@link Ext.data.Record#data record data}.
56420 * See also {@link #trackResetOnLoad}.
56421 * @param {Record} record The record to load
56422 * @return {BasicForm} this
56424 loadRecord : function(record){
56425 this.setValues(record.data);
56430 beforeAction : function(action){
56431 var o = action.options;
56433 if(this.waitMsgTarget === true){
56434 this.el.mask(o.waitMsg, 'x-mask-loading');
56435 }else if(this.waitMsgTarget){
56436 this.waitMsgTarget = Ext.get(this.waitMsgTarget);
56437 this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
56439 Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle || 'Please Wait...');
56445 afterAction : function(action, success){
56446 this.activeAction = null;
56447 var o = action.options;
56449 if(this.waitMsgTarget === true){
56451 }else if(this.waitMsgTarget){
56452 this.waitMsgTarget.unmask();
56454 Ext.MessageBox.updateProgress(1);
56455 Ext.MessageBox.hide();
56462 Ext.callback(o.success, o.scope, [this, action]);
56463 this.fireEvent('actioncomplete', this, action);
56465 Ext.callback(o.failure, o.scope, [this, action]);
56466 this.fireEvent('actionfailed', this, action);
56471 * Find a {@link Ext.form.Field} in this form.
56472 * @param {String} id The value to search for (specify either a {@link Ext.Component#id id},
56473 * {@link Ext.grid.Column#dataIndex dataIndex}, {@link Ext.form.Field#getName name or hiddenName}).
56476 findField : function(id){
56477 var field = this.items.get(id);
56478 if(!Ext.isObject(field)){
56479 this.items.each(function(f){
56480 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
56486 return field || null;
56491 * Mark fields in this form invalid in bulk.
56492 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
56493 * @return {BasicForm} this
56495 markInvalid : function(errors){
56496 if(Ext.isArray(errors)){
56497 for(var i = 0, len = errors.length; i < len; i++){
56498 var fieldError = errors[i];
56499 var f = this.findField(fieldError.id);
56501 f.markInvalid(fieldError.msg);
56507 if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
56508 field.markInvalid(errors[id]);
56516 * Set values for fields in this form in bulk.
56517 * @param {Array/Object} values Either an array in the form:<pre><code>
56518 [{id:'clientName', value:'Fred. Olsen Lines'},
56519 {id:'portOfLoading', value:'FXT'},
56520 {id:'portOfDischarge', value:'OSL'} ]</code></pre>
56521 * or an object hash of the form:<pre><code>
56523 clientName: 'Fred. Olsen Lines',
56524 portOfLoading: 'FXT',
56525 portOfDischarge: 'OSL'
56527 * @return {BasicForm} this
56529 setValues : function(values){
56530 if(Ext.isArray(values)){ // array of objects
56531 for(var i = 0, len = values.length; i < len; i++){
56533 var f = this.findField(v.id);
56535 f.setValue(v.value);
56536 if(this.trackResetOnLoad){
56537 f.originalValue = f.getValue();
56541 }else{ // object hash
56544 if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
56545 field.setValue(values[id]);
56546 if(this.trackResetOnLoad){
56547 field.originalValue = field.getValue();
56556 * <p>Returns the fields in this form as an object with key/value pairs as they would be submitted using a standard form submit.
56557 * If multiple fields exist with the same name they are returned as an array.</p>
56558 * <p><b>Note:</b> The values are collected from all enabled HTML input elements within the form, <u>not</u> from
56559 * the Ext Field objects. This means that all returned values are Strings (or Arrays of Strings) and that the
56560 * value can potentially be the emptyText of a field.</p>
56561 * @param {Boolean} asString (optional) Pass true to return the values as a string. (defaults to false, returning an Object)
56562 * @return {String/Object}
56564 getValues : function(asString){
56565 var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
56566 if(asString === true){
56569 return Ext.urlDecode(fs);
56572 getFieldValues : function(){
56574 this.items.each(function(f){
56575 o[f.getName()] = f.getValue();
56581 * Clears all invalid messages in this form.
56582 * @return {BasicForm} this
56584 clearInvalid : function(){
56585 this.items.each(function(f){
56592 * Resets this form.
56593 * @return {BasicForm} this
56595 reset : function(){
56596 this.items.each(function(f){
56603 * Add Ext.form Components to this form's Collection. This does not result in rendering of
56604 * the passed Component, it just enables the form to validate Fields, and distribute values to
56606 * <p><b>You will not usually call this function. In order to be rendered, a Field must be added
56607 * to a {@link Ext.Container Container}, usually an {@link Ext.form.FormPanel FormPanel}.
56608 * The FormPanel to which the field is added takes care of adding the Field to the BasicForm's
56609 * collection.</b></p>
56610 * @param {Field} field1
56611 * @param {Field} field2 (optional)
56612 * @param {Field} etc (optional)
56613 * @return {BasicForm} this
56616 this.items.addAll(Array.prototype.slice.call(arguments, 0));
56622 * Removes a field from the items collection (does NOT remove its markup).
56623 * @param {Field} field
56624 * @return {BasicForm} this
56626 remove : function(field){
56627 this.items.remove(field);
56632 * Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm,
56633 * checks them for an id attribute, and calls {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
56634 * @return {BasicForm} this
56636 render : function(){
56637 this.items.each(function(f){
56638 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
56639 f.applyToMarkup(f.id);
56646 * Calls {@link Ext#apply} for all fields in this form with the passed object.
56647 * @param {Object} values
56648 * @return {BasicForm} this
56650 applyToFields : function(o){
56651 this.items.each(function(f){
56658 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
56659 * @param {Object} values
56660 * @return {BasicForm} this
56662 applyIfToFields : function(o){
56663 this.items.each(function(f){
56669 callFieldMethod : function(fnName, args){
56671 this.items.each(function(f){
56672 if(Ext.isFunction(f[fnName])){
56673 f[fnName].apply(f, args);
56681 Ext.BasicForm = Ext.form.BasicForm;/**
56682 * @class Ext.form.FormPanel
56683 * @extends Ext.Panel
56684 * <p>Standard form container.</p>
56686 * <p><b><u>Layout</u></b></p>
56687 * <p>By default, FormPanel is configured with <tt>layout:'form'</tt> to use an {@link Ext.layout.FormLayout}
56688 * layout manager, which styles and renders fields and labels correctly. When nesting additional Containers
56689 * within a FormPanel, you should ensure that any descendant Containers which host input Fields use the
56690 * {@link Ext.layout.FormLayout} layout manager.</p>
56692 * <p><b><u>BasicForm</u></b></p>
56693 * <p>Although <b>not listed</b> as configuration options of FormPanel, the FormPanel class accepts all
56694 * of the config options required to configure its internal {@link Ext.form.BasicForm} for:
56695 * <div class="mdetail-params"><ul>
56696 * <li>{@link Ext.form.BasicForm#fileUpload file uploads}</li>
56697 * <li>functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form</li>
56700 * <p><b>Note</b>: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
56701 * the <tt><b>initialConfig</b></tt> property of the FormPanel. Applying {@link Ext.form.BasicForm BasicForm}
56702 * configuration settings to <b><tt>this</tt></b> will <b>not</b> affect the BasicForm's configuration.</p>
56704 * <p><b><u>Form Validation</u></b></p>
56705 * <p>For information on form validation see the following:</p>
56706 * <div class="mdetail-params"><ul>
56707 * <li>{@link Ext.form.TextField}</li>
56708 * <li>{@link Ext.form.VTypes}</li>
56709 * <li>{@link Ext.form.BasicForm#doAction BasicForm.doAction <b>clientValidation</b> notes}</li>
56710 * <li><tt>{@link Ext.form.FormPanel#monitorValid monitorValid}</tt></li>
56713 * <p><b><u>Form Submission</u></b></p>
56714 * <p>By default, Ext Forms are submitted through Ajax, using {@link Ext.form.Action}. To enable normal browser
56715 * submission of the {@link Ext.form.BasicForm BasicForm} contained in this FormPanel, see the
56716 * <tt><b>{@link Ext.form.BasicForm#standardSubmit standardSubmit}</b></tt> option.</p>
56719 * @param {Object} config Configuration options
56722 Ext.FormPanel = Ext.extend(Ext.Panel, {
56724 * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id).
56727 * @cfg {Boolean} hideLabels
56728 * <p><tt>true</tt> to hide field labels by default (sets <tt>display:none</tt>). Defaults to
56729 * <tt>false</tt>.</p>
56730 * <p>Also see {@link Ext.Component}.<tt>{@link Ext.Component#hideLabel hideLabel}</tt>.
56733 * @cfg {Number} labelPad
56734 * The default padding in pixels for field labels (defaults to <tt>5</tt>). <tt>labelPad</tt> only
56735 * applies if <tt>{@link #labelWidth}</tt> is also specified, otherwise it will be ignored.
56738 * @cfg {String} labelSeparator
56739 * See {@link Ext.Component}.<tt>{@link Ext.Component#labelSeparator labelSeparator}</tt>
56742 * @cfg {Number} labelWidth The width of labels in pixels. This property cascades to child containers
56743 * and can be overridden on any child container (e.g., a fieldset can specify a different <tt>labelWidth</tt>
56744 * for its fields) (defaults to <tt>100</tt>).
56747 * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
56750 * @cfg {Array} buttons
56751 * An array of {@link Ext.Button}s or {@link Ext.Button} configs used to add buttons to the footer of this FormPanel.<br>
56752 * <p>Buttons in the footer of a FormPanel may be configured with the option <tt>formBind: true</tt>. This causes
56753 * the form's {@link #monitorValid valid state monitor task} to enable/disable those Buttons depending on
56754 * the form's valid/invalid state.</p>
56759 * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to <tt>75</tt>).
56761 minButtonWidth : 75,
56764 * @cfg {String} labelAlign The label alignment value used for the <tt>text-align</tt> specification
56765 * for the <b>container</b>. Valid values are <tt>"left</tt>", <tt>"top"</tt> or <tt>"right"</tt>
56766 * (defaults to <tt>"left"</tt>). This property cascades to child <b>containers</b> and can be
56767 * overridden on any child <b>container</b> (e.g., a fieldset can specify a different <tt>labelAlign</tt>
56770 labelAlign : 'left',
56773 * @cfg {Boolean} monitorValid If <tt>true</tt>, the form monitors its valid state <b>client-side</b> and
56774 * regularly fires the {@link #clientvalidation} event passing that state.<br>
56775 * <p>When monitoring valid state, the FormPanel enables/disables any of its configured
56776 * {@link #buttons} which have been configured with <code>formBind: true</code> depending
56777 * on whether the {@link Ext.form.BasicForm#isValid form is valid} or not. Defaults to <tt>false</tt></p>
56779 monitorValid : false,
56782 * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
56787 * @cfg {String} layout Defaults to <tt>'form'</tt>. Normally this configuration property should not be altered.
56788 * For additional details see {@link Ext.layout.FormLayout} and {@link Ext.Container#layout Ext.Container.layout}.
56793 initComponent : function(){
56794 this.form = this.createForm();
56795 Ext.FormPanel.superclass.initComponent.call(this);
56799 cls: this.baseCls + '-body',
56800 method : this.method || 'POST',
56801 id : this.formId || Ext.id()
56803 if(this.fileUpload) {
56804 this.bodyCfg.enctype = 'multipart/form-data';
56810 * @event clientvalidation
56811 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
56812 * @param {Ext.form.FormPanel} this
56813 * @param {Boolean} valid true if the form has passed client-side validation
56818 this.relayEvents(this.form, ['beforeaction', 'actionfailed', 'actioncomplete']);
56822 createForm : function(){
56823 var config = Ext.applyIf({listeners: {}}, this.initialConfig);
56824 return new Ext.form.BasicForm(null, config);
56828 initFields : function(){
56830 var formPanel = this;
56831 var fn = function(c){
56832 if(formPanel.isField(c)){
56834 }if(c.isFieldWrap){
56836 labelAlign: c.ownerCt.labelAlign,
56837 labelWidth: c.ownerCt.labelWidth,
56838 itemCls: c.ownerCt.itemCls
56841 }else if(c.doLayout && c != formPanel){
56843 labelAlign: c.ownerCt.labelAlign,
56844 labelWidth: c.ownerCt.labelWidth,
56845 itemCls: c.ownerCt.itemCls
56847 //each check required for check/radio groups.
56848 if(c.items && c.items.each){
56849 c.items.each(fn, this);
56853 this.items.each(fn, this);
56857 getLayoutTarget : function(){
56858 return this.form.el;
56862 * Provides access to the {@link Ext.form.BasicForm Form} which this Panel contains.
56863 * @return {Ext.form.BasicForm} The {@link Ext.form.BasicForm Form} which this Panel contains.
56865 getForm : function(){
56870 onRender : function(ct, position){
56872 Ext.FormPanel.superclass.onRender.call(this, ct, position);
56873 this.form.initEl(this.body);
56877 beforeDestroy : function(){
56878 this.stopMonitoring();
56879 Ext.FormPanel.superclass.beforeDestroy.call(this);
56881 * Clear the items here to prevent them being destroyed again.
56882 * Don't move this behaviour to BasicForm because it can be used
56885 this.form.items.clear();
56886 Ext.destroy(this.form);
56889 // Determine if a Component is usable as a form Field.
56890 isField : function(c) {
56891 return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid;
56895 initEvents : function(){
56896 Ext.FormPanel.superclass.initEvents.call(this);
56897 this.on('remove', this.onRemove, this);
56898 this.on('add', this.onAdd, this);
56899 if(this.monitorValid){ // initialize after render
56900 this.startMonitoring();
56905 onAdd : function(ct, c) {
56906 // If a single form Field, add it
56907 if (this.isField(c)) {
56909 // If a Container, add any Fields it might contain
56910 } else if (c.findBy) {
56912 labelAlign: c.ownerCt.labelAlign,
56913 labelWidth: c.ownerCt.labelWidth,
56914 itemCls: c.ownerCt.itemCls
56916 this.form.add.apply(this.form, c.findBy(this.isField));
56921 onRemove : function(ct, c) {
56922 // If a single form Field, remove it
56923 if (this.isField(c)) {
56924 Ext.destroy(c.container.up('.x-form-item'));
56925 this.form.remove(c);
56926 // If a Container, remove any Fields it might contain
56927 } else if (c.findByType) {
56928 Ext.each(c.findBy(this.isField), this.form.remove, this.form);
56933 * Starts monitoring of the valid state of this form. Usually this is done by passing the config
56934 * option "monitorValid"
56936 startMonitoring : function(){
56937 if(!this.validTask){
56938 this.validTask = new Ext.util.TaskRunner();
56939 this.validTask.start({
56940 run : this.bindHandler,
56941 interval : this.monitorPoll || 200,
56948 * Stops monitoring of the valid state of this form
56950 stopMonitoring : function(){
56951 if(this.validTask){
56952 this.validTask.stopAll();
56953 this.validTask = null;
56958 * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call.
56959 * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details)
56962 this.form.load.apply(this.form, arguments);
56966 onDisable : function(){
56967 Ext.FormPanel.superclass.onDisable.call(this);
56969 this.form.items.each(function(){
56976 onEnable : function(){
56977 Ext.FormPanel.superclass.onEnable.call(this);
56979 this.form.items.each(function(){
56986 bindHandler : function(){
56988 this.form.items.each(function(f){
56989 if(!f.isValid(true)){
56995 var fitems = this.fbar.items.items;
56996 for(var i = 0, len = fitems.length; i < len; i++){
56997 var btn = fitems[i];
56998 if(btn.formBind === true && btn.disabled === valid){
56999 btn.setDisabled(!valid);
57003 this.fireEvent('clientvalidation', this, valid);
57006 Ext.reg('form', Ext.FormPanel);
57008 Ext.form.FormPanel = Ext.FormPanel;
57011 * @class Ext.form.FieldSet
\r
57012 * @extends Ext.Panel
\r
57013 * Standard container used for grouping items within a {@link Ext.form.FormPanel form}.
\r
57015 var form = new Ext.FormPanel({
\r
57016 title: 'Simple Form with FieldSets',
\r
57017 labelWidth: 75, // label settings here cascade unless overridden
\r
57018 url: 'save-form.php',
\r
57020 bodyStyle:'padding:5px 5px 0',
\r
57022 renderTo: document.body,
\r
57023 layout:'column', // arrange items in columns
\r
57024 defaults: { // defaults applied to items
\r
57027 bodyStyle: 'padding:4px'
\r
57030 // Fieldset in Column 1
\r
57031 xtype:'fieldset',
\r
57032 columnWidth: 0.5,
\r
57033 title: 'Fieldset 1',
\r
57034 collapsible: true,
\r
57037 anchor: '-20' // leave room for error icon
\r
57039 defaultType: 'textfield',
\r
57041 fieldLabel: 'Field 1'
\r
57043 fieldLabel: 'Field 2'
\r
57045 fieldLabel: 'Field 3'
\r
57049 // Fieldset in Column 2 - Panel inside
\r
57050 xtype:'fieldset',
\r
57051 title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
\r
57053 columnWidth: 0.5,
\r
57054 checkboxToggle: true,
\r
57055 collapsed: true, // fieldset initially collapsed
\r
57060 title: 'Panel inside a fieldset',
\r
57068 * @param {Object} config Configuration options
\r
57069 * @xtype fieldset
\r
57071 Ext.form.FieldSet = Ext.extend(Ext.Panel, {
\r
57073 * @cfg {Mixed} checkboxToggle <tt>true</tt> to render a checkbox into the fieldset frame just
\r
57074 * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
\r
57075 * to <tt>false</tt>).
\r
57076 * <p>A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox.
\r
57077 * If <tt>true</tt> is specified, the default DomHelper config object used to create the element
\r
57078 * is:</p><pre><code>
\r
57079 * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
\r
57083 * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
\r
57084 * (defaults to <tt>'[checkbox id]-checkbox'</tt>).
\r
57087 * @cfg {Boolean} collapsible
\r
57088 * <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
\r
57089 * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
\r
57090 * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
\r
57093 * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
\r
57096 * @cfg {String} itemCls A css class to apply to the <tt>x-form-item</tt> of fields (see
\r
57097 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details).
\r
57098 * This property cascades to child containers.
\r
57101 * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
\r
57103 baseCls : 'x-fieldset',
\r
57105 * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to <tt>'form'</tt>).
\r
57109 * @cfg {Boolean} animCollapse
\r
57110 * <tt>true</tt> to animate the transition when the panel is collapsed, <tt>false</tt> to skip the
\r
57111 * animation (defaults to <tt>false</tt>).
\r
57113 animCollapse : false,
\r
57116 onRender : function(ct, position){
\r
57118 this.el = document.createElement('fieldset');
\r
57119 this.el.id = this.id;
\r
57120 if (this.title || this.header || this.checkboxToggle) {
\r
57121 this.el.appendChild(document.createElement('legend')).className = 'x-fieldset-header';
\r
57125 Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
\r
57127 if(this.checkboxToggle){
\r
57128 var o = typeof this.checkboxToggle == 'object' ?
\r
57129 this.checkboxToggle :
\r
57130 {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
\r
57131 this.checkbox = this.header.insertFirst(o);
\r
57132 this.checkbox.dom.checked = !this.collapsed;
\r
57133 this.mon(this.checkbox, 'click', this.onCheckClick, this);
\r
57138 onCollapse : function(doAnim, animArg){
\r
57139 if(this.checkbox){
\r
57140 this.checkbox.dom.checked = false;
\r
57142 Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
\r
57147 onExpand : function(doAnim, animArg){
\r
57148 if(this.checkbox){
\r
57149 this.checkbox.dom.checked = true;
\r
57151 Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
\r
57155 * This function is called by the fieldset's checkbox when it is toggled (only applies when
\r
57156 * checkboxToggle = true). This method should never be called externally, but can be
\r
57157 * overridden to provide custom behavior when the checkbox is toggled if needed.
\r
57159 onCheckClick : function(){
\r
57160 this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
\r
57164 * @cfg {String/Number} activeItem
\r
57168 * @cfg {Mixed} applyTo
\r
57172 * @cfg {Boolean} bodyBorder
\r
57176 * @cfg {Boolean} border
\r
57180 * @cfg {Boolean/Number} bufferResize
\r
57184 * @cfg {Boolean} collapseFirst
\r
57188 * @cfg {String} defaultType
\r
57192 * @cfg {String} disabledClass
\r
57196 * @cfg {String} elements
\r
57200 * @cfg {Boolean} floating
\r
57204 * @cfg {Boolean} footer
\r
57208 * @cfg {Boolean} frame
\r
57212 * @cfg {Boolean} header
\r
57216 * @cfg {Boolean} headerAsText
\r
57220 * @cfg {Boolean} hideCollapseTool
\r
57224 * @cfg {String} iconCls
\r
57228 * @cfg {Boolean/String} shadow
\r
57232 * @cfg {Number} shadowOffset
\r
57236 * @cfg {Boolean} shim
\r
57240 * @cfg {Object/Array} tbar
\r
57244 * @cfg {String} tabTip
\r
57248 * @cfg {Boolean} titleCollapse
\r
57252 * @cfg {Array} tools
\r
57256 * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
\r
57260 * @cfg {String} xtype
\r
57264 * @property header
\r
57268 * @property footer
\r
57276 * @method getBottomToolbar
\r
57280 * @method getTopToolbar
\r
57284 * @method setIconClass
\r
57288 * @event activate
\r
57292 * @event beforeclose
\r
57296 * @event bodyresize
\r
57304 * @event deactivate
\r
57308 Ext.reg('fieldset', Ext.form.FieldSet);
\r
57310 * @class Ext.form.HtmlEditor
\r
57311 * @extends Ext.form.Field
\r
57312 * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
\r
57313 * automatically hidden when needed. These are noted in the config options where appropriate.
\r
57314 * <br><br>The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
\r
57315 * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.
\r
57316 * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
\r
57317 * supported by this editor.</b>
\r
57318 * <br><br>An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
\r
57319 * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
\r
57320 * <br><br>Example usage:
\r
57322 // Simple example rendered with default options:
\r
57323 Ext.QuickTips.init(); // enable tooltips
\r
57324 new Ext.form.HtmlEditor({
\r
57325 renderTo: Ext.getBody(),
\r
57330 // Passed via xtype into a container and with custom options:
\r
57331 Ext.QuickTips.init(); // enable tooltips
\r
57333 title: 'HTML Editor',
\r
57334 renderTo: Ext.getBody(),
\r
57340 xtype: 'htmleditor',
\r
57341 enableColors: false,
\r
57342 enableAlignments: false
\r
57347 * Create a new HtmlEditor
\r
57348 * @param {Object} config
\r
57349 * @xtype htmleditor
\r
57352 Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
\r
57354 * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
\r
57356 enableFormat : true,
\r
57358 * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
\r
57360 enableFontSize : true,
\r
57362 * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
\r
57364 enableColors : true,
\r
57366 * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
\r
57368 enableAlignments : true,
\r
57370 * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
\r
57372 enableLists : true,
\r
57374 * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
\r
57376 enableSourceEdit : true,
\r
57378 * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
\r
57380 enableLinks : true,
\r
57382 * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
\r
57384 enableFont : true,
\r
57386 * @cfg {String} createLinkText The default text for the create link prompt
\r
57388 createLinkText : 'Please enter the URL for the link:',
\r
57390 * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
\r
57392 defaultLinkValue : 'http:/'+'/',
\r
57394 * @cfg {Array} fontFamilies An array of available font families
\r
57400 'Times New Roman',
\r
57403 defaultFont: 'tahoma',
\r
57405 * @cfg {String} defaultValue A default value to be put into the editor to resolve focus issues (defaults to ​ (Zero-width space), (Non-breaking space) in Opera and IE6).
\r
57407 defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '​',
\r
57409 // private properties
\r
57410 actionMode: 'wrap',
\r
57411 validationEvent : false,
\r
57412 deferHeight: true,
\r
57413 initialized : false,
\r
57414 activated : false,
\r
57415 sourceEditMode : false,
\r
57416 onFocus : Ext.emptyFn,
\r
57418 hideMode:'offsets',
\r
57419 defaultAutoCreate : {
\r
57421 style:"width:500px;height:300px;",
\r
57422 autocomplete: "off"
\r
57426 initComponent : function(){
\r
57429 * @event initialize
\r
57430 * Fires when the editor is fully initialized (including the iframe)
\r
57431 * @param {HtmlEditor} this
\r
57435 * @event activate
\r
57436 * Fires when the editor is first receives the focus. Any insertion must wait
\r
57437 * until after this event.
\r
57438 * @param {HtmlEditor} this
\r
57442 * @event beforesync
\r
57443 * Fires before the textarea is updated with content from the editor iframe. Return false
\r
57444 * to cancel the sync.
\r
57445 * @param {HtmlEditor} this
\r
57446 * @param {String} html
\r
57450 * @event beforepush
\r
57451 * Fires before the iframe editor is updated with content from the textarea. Return false
\r
57452 * to cancel the push.
\r
57453 * @param {HtmlEditor} this
\r
57454 * @param {String} html
\r
57459 * Fires when the textarea is updated with content from the editor iframe.
\r
57460 * @param {HtmlEditor} this
\r
57461 * @param {String} html
\r
57466 * Fires when the iframe editor is updated with content from the textarea.
\r
57467 * @param {HtmlEditor} this
\r
57468 * @param {String} html
\r
57472 * @event editmodechange
\r
57473 * Fires when the editor switches edit modes
\r
57474 * @param {HtmlEditor} this
\r
57475 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
\r
57482 createFontOptions : function(){
\r
57483 var buf = [], fs = this.fontFamilies, ff, lc;
\r
57484 for(var i = 0, len = fs.length; i< len; i++){
\r
57486 lc = ff.toLowerCase();
\r
57488 '<option value="',lc,'" style="font-family:',ff,';"',
\r
57489 (this.defaultFont == lc ? ' selected="true">' : '>'),
\r
57494 return buf.join('');
\r
57498 * Protected method that will not generally be called directly. It
\r
57499 * is called when the editor creates its toolbar. Override this method if you need to
\r
57500 * add custom toolbar buttons.
\r
57501 * @param {HtmlEditor} editor
\r
57503 createToolbar : function(editor){
\r
57505 var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
\r
57507 function btn(id, toggle, handler){
\r
57510 cls : 'x-btn-icon',
\r
57511 iconCls: 'x-edit-'+id,
\r
57512 enableToggle:toggle !== false,
\r
57514 handler:handler||editor.relayBtnCmd,
\r
57515 clickEvent:'mousedown',
\r
57516 tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
\r
57517 overflowText: editor.buttonTips[id].title || undefined,
\r
57522 // build the toolbar
\r
57523 var tb = new Ext.Toolbar({
\r
57524 renderTo:this.wrap.dom.firstChild
\r
57527 // stop form submits
\r
57528 this.mon(tb.el, 'click', function(e){
\r
57529 e.preventDefault();
\r
57532 if(this.enableFont && !Ext.isSafari2){
\r
57533 this.fontSelect = tb.el.createChild({
\r
57535 cls:'x-font-select',
\r
57536 html: this.createFontOptions()
\r
57538 this.mon(this.fontSelect, 'change', function(){
\r
57539 var font = this.fontSelect.dom.value;
\r
57540 this.relayCmd('fontname', font);
\r
57541 this.deferFocus();
\r
57545 this.fontSelect.dom,
\r
57550 if(this.enableFormat){
\r
57558 if(this.enableFontSize){
\r
57561 btn('increasefontsize', false, this.adjustFont),
\r
57562 btn('decreasefontsize', false, this.adjustFont)
\r
57566 if(this.enableColors){
\r
57569 itemId:'forecolor',
\r
57570 cls:'x-btn-icon',
\r
57571 iconCls: 'x-edit-forecolor',
\r
57572 clickEvent:'mousedown',
\r
57573 tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
\r
57575 menu : new Ext.menu.ColorMenu({
\r
57576 allowReselect: true,
\r
57577 focus: Ext.emptyFn,
\r
57582 select: function(cp, color){
\r
57583 this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
\r
57584 this.deferFocus();
\r
57587 clickEvent:'mousedown'
\r
57590 itemId:'backcolor',
\r
57591 cls:'x-btn-icon',
\r
57592 iconCls: 'x-edit-backcolor',
\r
57593 clickEvent:'mousedown',
\r
57594 tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
\r
57596 menu : new Ext.menu.ColorMenu({
\r
57597 focus: Ext.emptyFn,
\r
57600 allowReselect: true,
\r
57603 select: function(cp, color){
\r
57605 this.execCmd('useCSS', false);
\r
57606 this.execCmd('hilitecolor', color);
\r
57607 this.execCmd('useCSS', true);
\r
57608 this.deferFocus();
\r
57610 this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
\r
57611 this.deferFocus();
\r
57615 clickEvent:'mousedown'
\r
57621 if(this.enableAlignments){
\r
57624 btn('justifyleft'),
\r
57625 btn('justifycenter'),
\r
57626 btn('justifyright')
\r
57630 if(!Ext.isSafari2){
\r
57631 if(this.enableLinks){
\r
57634 btn('createlink', false, this.createLink)
\r
57638 if(this.enableLists){
\r
57641 btn('insertorderedlist'),
\r
57642 btn('insertunorderedlist')
\r
57645 if(this.enableSourceEdit){
\r
57648 btn('sourceedit', true, function(btn){
\r
57649 this.toggleSourceEdit(!this.sourceEditMode);
\r
57659 * Protected method that will not generally be called directly. It
\r
57660 * is called when the editor initializes the iframe with HTML contents. Override this method if you
\r
57661 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
\r
57663 getDocMarkup : function(){
\r
57664 return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';
\r
57668 getEditorBody : function(){
\r
57669 return this.doc.body || this.doc.documentElement;
\r
57673 getDoc : function(){
\r
57674 return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
\r
57678 getWin : function(){
\r
57679 return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
\r
57683 onRender : function(ct, position){
\r
57684 Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
\r
57685 this.el.dom.style.border = '0 none';
\r
57686 this.el.dom.setAttribute('tabIndex', -1);
\r
57687 this.el.addClass('x-hidden');
\r
57688 if(Ext.isIE){ // fix IE 1px bogus margin
\r
57689 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
\r
57691 this.wrap = this.el.wrap({
\r
57692 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
\r
57695 this.createToolbar(this);
\r
57697 this.disableItems(true);
\r
57698 // is this needed?
\r
57699 // this.tb.doLayout();
\r
57701 this.createIFrame();
\r
57704 var sz = this.el.getSize();
\r
57705 this.setSize(sz.width, this.height || sz.height);
\r
57709 createIFrame: function(){
\r
57710 var iframe = document.createElement('iframe');
\r
57711 iframe.name = Ext.id();
\r
57712 iframe.frameBorder = '0';
\r
57713 iframe.src = Ext.isIE ? Ext.SSL_SECURE_URL : "javascript:;";
\r
57714 this.wrap.dom.appendChild(iframe);
\r
57716 this.iframe = iframe;
\r
57718 this.monitorTask = Ext.TaskMgr.start({
\r
57719 run: this.checkDesignMode,
\r
57725 initFrame : function(){
\r
57726 Ext.TaskMgr.stop(this.monitorTask);
\r
57727 this.doc = this.getDoc();
\r
57728 this.win = this.getWin();
\r
57731 this.doc.write(this.getDocMarkup());
\r
57732 this.doc.close();
\r
57734 var task = { // must defer to wait for browser to be ready
\r
57735 run : function(){
\r
57736 if(this.doc.body || this.doc.readyState == 'complete'){
\r
57737 Ext.TaskMgr.stop(task);
\r
57738 this.doc.designMode="on";
\r
57739 this.initEditor.defer(10, this);
\r
57746 Ext.TaskMgr.start(task);
\r
57750 checkDesignMode : function(){
\r
57751 if(this.wrap && this.wrap.dom.offsetWidth){
\r
57752 var doc = this.getDoc();
\r
57756 if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){
\r
57757 this.initFrame();
\r
57762 disableItems: function(disabled){
\r
57763 if(this.fontSelect){
\r
57764 this.fontSelect.dom.disabled = disabled;
\r
57766 this.tb.items.each(function(item){
\r
57767 if(item.itemId != 'sourceedit'){
\r
57768 item.setDisabled(disabled);
\r
57774 onResize : function(w, h){
\r
57775 Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
\r
57776 if(this.el && this.iframe){
\r
57777 if(typeof w == 'number'){
\r
57778 var aw = w - this.wrap.getFrameWidth('lr');
\r
57779 this.el.setWidth(this.adjustWidth('textarea', aw));
\r
57780 this.tb.setWidth(aw);
\r
57781 this.iframe.style.width = Math.max(aw, 0) + 'px';
\r
57783 if(typeof h == 'number'){
\r
57784 var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
\r
57785 this.el.setHeight(this.adjustWidth('textarea', ah));
\r
57786 this.iframe.style.height = Math.max(ah, 0) + 'px';
\r
57788 this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
\r
57795 * Toggles the editor between standard and source edit mode.
\r
57796 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
\r
57798 toggleSourceEdit : function(sourceEditMode){
\r
57799 if(sourceEditMode === undefined){
\r
57800 sourceEditMode = !this.sourceEditMode;
\r
57802 this.sourceEditMode = sourceEditMode === true;
\r
57803 var btn = this.tb.items.get('sourceedit');
\r
57804 if(btn.pressed !== this.sourceEditMode){
\r
57805 btn.toggle(this.sourceEditMode);
\r
57806 if(!btn.xtbHidden){
\r
57810 if(this.sourceEditMode){
\r
57811 this.disableItems(true);
\r
57812 this.syncValue();
\r
57813 this.iframe.className = 'x-hidden';
\r
57814 this.el.removeClass('x-hidden');
\r
57815 this.el.dom.removeAttribute('tabIndex');
\r
57818 if(this.initialized){
\r
57819 this.disableItems(false);
\r
57821 this.pushValue();
\r
57822 this.iframe.className = '';
\r
57823 this.el.addClass('x-hidden');
\r
57824 this.el.dom.setAttribute('tabIndex', -1);
\r
57825 this.deferFocus();
\r
57827 var lastSize = this.lastSize;
\r
57829 delete this.lastSize;
\r
57830 this.setSize(lastSize);
\r
57832 this.fireEvent('editmodechange', this, this.sourceEditMode);
\r
57835 // private used internally
\r
57836 createLink : function(){
\r
57837 var url = prompt(this.createLinkText, this.defaultLinkValue);
\r
57838 if(url && url != 'http:/'+'/'){
\r
57839 this.relayCmd('createlink', url);
\r
57843 // private (for BoxComponent)
\r
57844 adjustSize : Ext.BoxComponent.prototype.adjustSize,
\r
57846 // private (for BoxComponent)
\r
57847 getResizeEl : function(){
\r
57848 return this.wrap;
\r
57851 // private (for BoxComponent)
\r
57852 getPositionEl : function(){
\r
57853 return this.wrap;
\r
57857 initEvents : function(){
\r
57858 this.originalValue = this.getValue();
\r
57862 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
\r
57865 markInvalid : Ext.emptyFn,
\r
57868 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
\r
57871 clearInvalid : Ext.emptyFn,
\r
57873 // docs inherit from Field
\r
57874 setValue : function(v){
\r
57875 Ext.form.HtmlEditor.superclass.setValue.call(this, v);
\r
57876 this.pushValue();
\r
57881 * Protected method that will not generally be called directly. If you need/want
\r
57882 * custom HTML cleanup, this is the method you should override.
\r
57883 * @param {String} html The HTML to be cleaned
\r
57884 * @return {String} The cleaned HTML
\r
57886 cleanHtml : function(html){
\r
57887 html = String(html);
\r
57888 if(html.length > 5){
\r
57889 if(Ext.isWebKit){ // strip safari nonsense
\r
57890 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
\r
57893 if(html == this.defaultValue){
\r
57900 * Protected method that will not generally be called directly. Syncs the contents
\r
57901 * of the editor iframe with the textarea.
\r
57903 syncValue : function(){
\r
57904 if(this.initialized){
\r
57905 var bd = this.getEditorBody();
\r
57906 var html = bd.innerHTML;
\r
57907 if(Ext.isWebKit){
\r
57908 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
\r
57909 var m = bs.match(/text-align:(.*?);/i);
\r
57911 html = '<div style="'+m[0]+'">' + html + '</div>';
\r
57914 html = this.cleanHtml(html);
\r
57915 if(this.fireEvent('beforesync', this, html) !== false){
\r
57916 this.el.dom.value = html;
\r
57917 this.fireEvent('sync', this, html);
\r
57922 //docs inherit from Field
\r
57923 getValue : function() {
\r
57924 this[this.sourceEditMode ? 'pushValue' : 'syncValue']();
\r
57925 return Ext.form.HtmlEditor.superclass.getValue.call(this);
\r
57929 * Protected method that will not generally be called directly. Pushes the value of the textarea
\r
57930 * into the iframe editor.
\r
57932 pushValue : function(){
\r
57933 if(this.initialized){
\r
57934 var v = this.el.dom.value;
\r
57935 if(!this.activated && v.length < 1){
\r
57936 v = this.defaultValue;
\r
57938 if(this.fireEvent('beforepush', this, v) !== false){
\r
57939 this.getEditorBody().innerHTML = v;
\r
57941 // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
\r
57942 var d = this.doc,
\r
57943 mode = d.designMode.toLowerCase();
\r
57945 d.designMode = mode.toggle('on', 'off');
\r
57946 d.designMode = mode;
\r
57948 this.fireEvent('push', this, v);
\r
57954 deferFocus : function(){
\r
57955 this.focus.defer(10, this);
\r
57958 // docs inherit from Field
\r
57959 focus : function(){
\r
57960 if(this.win && !this.sourceEditMode){
\r
57961 this.win.focus();
\r
57968 initEditor : function(){
\r
57969 //Destroying the component during/before initEditor can cause issues.
\r
57971 var dbody = this.getEditorBody();
\r
57972 var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
\r
57973 ss['background-attachment'] = 'fixed'; // w3c
\r
57974 dbody.bgProperties = 'fixed'; // ie
\r
57976 Ext.DomHelper.applyStyles(dbody, ss);
\r
57980 Ext.EventManager.removeAll(this.doc);
\r
57984 this.doc = this.getDoc();
\r
57986 Ext.EventManager.on(this.doc, {
\r
57987 'mousedown': this.onEditorEvent,
\r
57988 'dblclick': this.onEditorEvent,
\r
57989 'click': this.onEditorEvent,
\r
57990 'keyup': this.onEditorEvent,
\r
57996 Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this);
\r
57998 if(Ext.isIE || Ext.isWebKit || Ext.isOpera){
\r
57999 Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
\r
58001 this.initialized = true;
\r
58002 this.fireEvent('initialize', this);
\r
58003 this.doc.editorInitialized = true;
\r
58004 this.pushValue();
\r
58009 onDestroy : function(){
\r
58010 if(this.monitorTask){
\r
58011 Ext.TaskMgr.stop(this.monitorTask);
\r
58013 if(this.rendered){
\r
58014 Ext.destroy(this.tb);
\r
58016 this.wrap.dom.innerHTML = '';
\r
58017 this.wrap.remove();
\r
58021 this.el.removeAllListeners();
\r
58022 this.el.remove();
\r
58027 Ext.EventManager.removeAll(this.doc);
\r
58028 for (var prop in this.doc){
\r
58029 delete this.doc[prop];
\r
58033 this.purgeListeners();
\r
58037 onFirstFocus : function(){
\r
58038 this.activated = true;
\r
58039 this.disableItems(false);
\r
58040 if(Ext.isGecko){ // prevent silly gecko errors
\r
58041 this.win.focus();
\r
58042 var s = this.win.getSelection();
\r
58043 if(!s.focusNode || s.focusNode.nodeType != 3){
\r
58044 var r = s.getRangeAt(0);
\r
58045 r.selectNodeContents(this.getEditorBody());
\r
58046 r.collapse(true);
\r
58047 this.deferFocus();
\r
58050 this.execCmd('useCSS', true);
\r
58051 this.execCmd('styleWithCSS', false);
\r
58054 this.fireEvent('activate', this);
\r
58058 adjustFont: function(btn){
\r
58059 var adjust = btn.itemId == 'increasefontsize' ? 1 : -1;
\r
58061 var v = parseInt(this.doc.queryCommandValue('FontSize') || 2, 10);
\r
58062 if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){
\r
58063 // Safari 3 values
\r
58064 // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
\r
58067 }else if(v <= 13){
\r
58069 }else if(v <= 16){
\r
58071 }else if(v <= 18){
\r
58073 }else if(v <= 24){
\r
58078 v = v.constrain(1, 6);
\r
58080 if(Ext.isSafari){ // safari
\r
58083 v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);
\r
58085 this.execCmd('FontSize', v);
\r
58089 onEditorEvent : function(e){
\r
58090 this.updateToolbar();
\r
58095 * Protected method that will not generally be called directly. It triggers
\r
58096 * a toolbar update by reading the markup state of the current selection in the editor.
\r
58098 updateToolbar: function(){
\r
58100 if(!this.activated){
\r
58101 this.onFirstFocus();
\r
58105 var btns = this.tb.items.map, doc = this.doc;
\r
58107 if(this.enableFont && !Ext.isSafari2){
\r
58108 var name = (this.doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();
\r
58109 if(name != this.fontSelect.dom.value){
\r
58110 this.fontSelect.dom.value = name;
\r
58113 if(this.enableFormat){
\r
58114 btns.bold.toggle(doc.queryCommandState('bold'));
\r
58115 btns.italic.toggle(doc.queryCommandState('italic'));
\r
58116 btns.underline.toggle(doc.queryCommandState('underline'));
\r
58118 if(this.enableAlignments){
\r
58119 btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
\r
58120 btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
\r
58121 btns.justifyright.toggle(doc.queryCommandState('justifyright'));
\r
58123 if(!Ext.isSafari2 && this.enableLists){
\r
58124 btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));
\r
58125 btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));
\r
58128 Ext.menu.MenuMgr.hideAll();
\r
58130 this.syncValue();
\r
58134 relayBtnCmd : function(btn){
\r
58135 this.relayCmd(btn.itemId);
\r
58139 * Executes a Midas editor command on the editor document and performs necessary focus and
\r
58140 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
\r
58141 * @param {String} cmd The Midas command
\r
58142 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
\r
58144 relayCmd : function(cmd, value){
\r
58147 this.execCmd(cmd, value);
\r
58148 this.updateToolbar();
\r
58149 }).defer(10, this);
\r
58153 * Executes a Midas editor command directly on the editor document.
\r
58154 * For visual commands, you should use {@link #relayCmd} instead.
\r
58155 * <b>This should only be called after the editor is initialized.</b>
\r
58156 * @param {String} cmd The Midas command
\r
58157 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
\r
58159 execCmd : function(cmd, value){
\r
58160 this.doc.execCommand(cmd, false, value === undefined ? null : value);
\r
58161 this.syncValue();
\r
58165 applyCommand : function(e){
\r
58167 var c = e.getCharCode(), cmd;
\r
58169 c = String.fromCharCode(c);
\r
58178 cmd = 'underline';
\r
58182 this.win.focus();
\r
58183 this.execCmd(cmd);
\r
58184 this.deferFocus();
\r
58185 e.preventDefault();
\r
58192 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
\r
58193 * to insert text.
\r
58194 * @param {String} text
\r
58196 insertAtCursor : function(text){
\r
58197 if(!this.activated){
\r
58201 this.win.focus();
\r
58202 var r = this.doc.selection.createRange();
\r
58204 r.collapse(true);
\r
58205 r.pasteHTML(text);
\r
58206 this.syncValue();
\r
58207 this.deferFocus();
\r
58209 }else if(Ext.isGecko || Ext.isOpera){
\r
58210 this.win.focus();
\r
58211 this.execCmd('InsertHTML', text);
\r
58212 this.deferFocus();
\r
58213 }else if(Ext.isWebKit){
\r
58214 this.execCmd('InsertText', text);
\r
58215 this.deferFocus();
\r
58220 fixKeys : function(){ // load time branching for fastest keydown performance
\r
58222 return function(e){
\r
58223 var k = e.getKey(), r;
\r
58226 r = this.doc.selection.createRange();
\r
58228 r.collapse(true);
\r
58229 r.pasteHTML(' ');
\r
58230 this.deferFocus();
\r
58232 }else if(k == e.ENTER){
\r
58233 r = this.doc.selection.createRange();
\r
58235 var target = r.parentElement();
\r
58236 if(!target || target.tagName.toLowerCase() != 'li'){
\r
58238 r.pasteHTML('<br />');
\r
58239 r.collapse(false);
\r
58245 }else if(Ext.isOpera){
\r
58246 return function(e){
\r
58247 var k = e.getKey();
\r
58250 this.win.focus();
\r
58251 this.execCmd('InsertHTML',' ');
\r
58252 this.deferFocus();
\r
58255 }else if(Ext.isWebKit){
\r
58256 return function(e){
\r
58257 var k = e.getKey();
\r
58260 this.execCmd('InsertText','\t');
\r
58261 this.deferFocus();
\r
58268 * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
\r
58269 * @return {Ext.Toolbar}
\r
58271 getToolbar : function(){
\r
58276 * Object collection of toolbar tooltips for the buttons in the editor. The key
\r
58277 * is the command id associated with that button and the value is a valid QuickTips object.
\r
58282 title: 'Bold (Ctrl+B)',
\r
58283 text: 'Make the selected text bold.',
\r
58284 cls: 'x-html-editor-tip'
\r
58287 title: 'Italic (Ctrl+I)',
\r
58288 text: 'Make the selected text italic.',
\r
58289 cls: 'x-html-editor-tip'
\r
58297 title: 'Bold (Ctrl+B)',
\r
58298 text: 'Make the selected text bold.',
\r
58299 cls: 'x-html-editor-tip'
\r
58302 title: 'Italic (Ctrl+I)',
\r
58303 text: 'Make the selected text italic.',
\r
58304 cls: 'x-html-editor-tip'
\r
58307 title: 'Underline (Ctrl+U)',
\r
58308 text: 'Underline the selected text.',
\r
58309 cls: 'x-html-editor-tip'
\r
58311 increasefontsize : {
\r
58312 title: 'Grow Text',
\r
58313 text: 'Increase the font size.',
\r
58314 cls: 'x-html-editor-tip'
\r
58316 decreasefontsize : {
\r
58317 title: 'Shrink Text',
\r
58318 text: 'Decrease the font size.',
\r
58319 cls: 'x-html-editor-tip'
\r
58322 title: 'Text Highlight Color',
\r
58323 text: 'Change the background color of the selected text.',
\r
58324 cls: 'x-html-editor-tip'
\r
58327 title: 'Font Color',
\r
58328 text: 'Change the color of the selected text.',
\r
58329 cls: 'x-html-editor-tip'
\r
58332 title: 'Align Text Left',
\r
58333 text: 'Align text to the left.',
\r
58334 cls: 'x-html-editor-tip'
\r
58336 justifycenter : {
\r
58337 title: 'Center Text',
\r
58338 text: 'Center text in the editor.',
\r
58339 cls: 'x-html-editor-tip'
\r
58342 title: 'Align Text Right',
\r
58343 text: 'Align text to the right.',
\r
58344 cls: 'x-html-editor-tip'
\r
58346 insertunorderedlist : {
\r
58347 title: 'Bullet List',
\r
58348 text: 'Start a bulleted list.',
\r
58349 cls: 'x-html-editor-tip'
\r
58351 insertorderedlist : {
\r
58352 title: 'Numbered List',
\r
58353 text: 'Start a numbered list.',
\r
58354 cls: 'x-html-editor-tip'
\r
58357 title: 'Hyperlink',
\r
58358 text: 'Make the selected text a hyperlink.',
\r
58359 cls: 'x-html-editor-tip'
\r
58362 title: 'Source Edit',
\r
58363 text: 'Switch to source editing mode.',
\r
58364 cls: 'x-html-editor-tip'
\r
58368 // hide stuff that is not compatible
\r
58382 * @event specialkey
\r
58386 * @cfg {String} fieldClass @hide
\r
58389 * @cfg {String} focusClass @hide
\r
58392 * @cfg {String} autoCreate @hide
\r
58395 * @cfg {String} inputType @hide
\r
58398 * @cfg {String} invalidClass @hide
\r
58401 * @cfg {String} invalidText @hide
\r
58404 * @cfg {String} msgFx @hide
\r
58407 * @cfg {String} validateOnBlur @hide
\r
58410 * @cfg {Boolean} allowDomMove @hide
\r
58413 * @cfg {String} applyTo @hide
\r
58416 * @cfg {String} autoHeight @hide
\r
58419 * @cfg {String} autoWidth @hide
\r
58422 * @cfg {String} cls @hide
\r
58425 * @cfg {String} disabled @hide
\r
58428 * @cfg {String} disabledClass @hide
\r
58431 * @cfg {String} msgTarget @hide
\r
58434 * @cfg {String} readOnly @hide
\r
58437 * @cfg {String} style @hide
\r
58440 * @cfg {String} validationDelay @hide
\r
58443 * @cfg {String} validationEvent @hide
\r
58446 * @cfg {String} tabIndex @hide
\r
58449 * @property disabled
\r
58453 * @method applyToMarkup
\r
58457 * @method disable
\r
58465 * @method validate
\r
58473 * @method setDisabled
\r
58481 Ext.reg('htmleditor', Ext.form.HtmlEditor);/**
\r
58482 * @class Ext.form.TimeField
\r
58483 * @extends Ext.form.ComboBox
\r
58484 * Provides a time input field with a time dropdown and automatic time validation. Example usage:
\r
58486 new Ext.form.TimeField({
\r
58487 minValue: '9:00 AM',
\r
58488 maxValue: '6:00 PM',
\r
58493 * Create a new TimeField
\r
58494 * @param {Object} config
\r
58495 * @xtype timefield
\r
58497 Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
\r
58499 * @cfg {Date/String} minValue
\r
58500 * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
\r
58501 * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
\r
58505 * @cfg {Date/String} maxValue
\r
58506 * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
\r
58507 * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
\r
58511 * @cfg {String} minText
\r
58512 * The error text to display when the date in the cell is before minValue (defaults to
\r
58513 * 'The time in this field must be equal to or after {0}').
\r
58515 minText : "The time in this field must be equal to or after {0}",
\r
58517 * @cfg {String} maxText
\r
58518 * The error text to display when the time is after maxValue (defaults to
\r
58519 * 'The time in this field must be equal to or before {0}').
\r
58521 maxText : "The time in this field must be equal to or before {0}",
\r
58523 * @cfg {String} invalidText
\r
58524 * The error text to display when the time in the field is invalid (defaults to
\r
58525 * '{value} is not a valid time').
\r
58527 invalidText : "{0} is not a valid time",
\r
58529 * @cfg {String} format
\r
58530 * The default time format string which can be overriden for localization support. The format must be
\r
58531 * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
\r
58532 * format try 'H:i' instead.
\r
58534 format : "g:i A",
\r
58536 * @cfg {String} altFormats
\r
58537 * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
\r
58538 * format (defaults to 'g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H').
\r
58540 altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H",
\r
58542 * @cfg {Number} increment
\r
58543 * The number of minutes between each time value in the list (defaults to 15).
\r
58547 // private override
\r
58549 // private override
\r
58550 triggerAction: 'all',
\r
58551 // private override
\r
58552 typeAhead: false,
\r
58554 // private - This is the date to use when generating time values in the absence of either minValue
\r
58555 // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
\r
58556 // arbitrary "safe" date that can be any date aside from DST boundary dates.
\r
58557 initDate: '1/1/2008',
\r
58560 initComponent : function(){
\r
58561 if(typeof this.minValue == "string"){
\r
58562 this.minValue = this.parseDate(this.minValue);
\r
58564 if(typeof this.maxValue == "string"){
\r
58565 this.maxValue = this.parseDate(this.maxValue);
\r
58569 var min = this.parseDate(this.minValue) || new Date(this.initDate).clearTime();
\r
58570 var max = this.parseDate(this.maxValue) || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1);
\r
58572 while(min <= max){
\r
58573 times.push(min.dateFormat(this.format));
\r
58574 min = min.add('mi', this.increment);
\r
58576 this.store = times;
\r
58578 Ext.form.TimeField.superclass.initComponent.call(this);
\r
58581 // inherited docs
\r
58582 getValue : function(){
\r
58583 var v = Ext.form.TimeField.superclass.getValue.call(this);
\r
58584 return this.formatDate(this.parseDate(v)) || '';
\r
58587 // inherited docs
\r
58588 setValue : function(value){
\r
58589 return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
\r
58592 // private overrides
\r
58593 validateValue : Ext.form.DateField.prototype.validateValue,
\r
58594 parseDate : Ext.form.DateField.prototype.parseDate,
\r
58595 formatDate : Ext.form.DateField.prototype.formatDate,
\r
58598 beforeBlur : function(){
\r
58599 var v = this.parseDate(this.getRawValue());
\r
58601 this.setValue(v.dateFormat(this.format));
\r
58603 Ext.form.TimeField.superclass.beforeBlur.call(this);
\r
58607 * @cfg {Boolean} grow @hide
\r
58610 * @cfg {Number} growMin @hide
\r
58613 * @cfg {Number} growMax @hide
\r
58617 * @method autoSize
\r
58620 Ext.reg('timefield', Ext.form.TimeField);/**
58621 * @class Ext.form.Label
58622 * @extends Ext.BoxComponent
58623 * Basic Label field.
58625 * Creates a new Label
58626 * @param {Ext.Element/String/Object} config The configuration options. If an element is passed, it is set as the internal
58627 * element and its id used as the component id. If a string is passed, it is assumed to be the id of an existing element
58628 * and is used as the component id. Otherwise, it is assumed to be a standard config object and is applied to the component.
58631 Ext.form.Label = Ext.extend(Ext.BoxComponent, {
58633 * @cfg {String} text The plain text to display within the label (defaults to ''). If you need to include HTML
58634 * tags within the label's innerHTML, use the {@link #html} config instead.
58637 * @cfg {String} forId The id of the input element to which this label will be bound via the standard HTML 'for'
58638 * attribute. If not specified, the attribute will not be added to the label.
58641 * @cfg {String} html An HTML fragment that will be used as the label's innerHTML (defaults to '').
58642 * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
58646 onRender : function(ct, position){
58648 this.el = document.createElement('label');
58649 this.el.id = this.getId();
58650 this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
58652 this.el.setAttribute('for', this.forId);
58655 Ext.form.Label.superclass.onRender.call(this, ct, position);
58659 * Updates the label's innerHTML with the specified string.
58660 * @param {String} text The new label text
58661 * @param {Boolean} encode (optional) False to skip HTML-encoding the text when rendering it
58662 * to the label (defaults to true which encodes the value). This might be useful if you want to include
58663 * tags in the label's innerHTML rather than rendering them as string literals per the default logic.
58664 * @return {Label} this
58666 setText : function(t, encode){
58667 var e = encode === false;
58668 this[!e ? 'text' : 'html'] = t;
58669 delete this[e ? 'text' : 'html'];
58671 this.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(t) : t;
58677 Ext.reg('label', Ext.form.Label);/**
58678 * @class Ext.form.Action
58679 * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.BasicForm Form}s.</p>
58680 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
58681 * the Form needs to perform an action such as submit or load. The Configuration options
58682 * listed for this class are set through the Form's action methods: {@link Ext.form.BasicForm#submit submit},
58683 * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}</p>
58684 * <p>The instance of Action which performed the action is passed to the success
58685 * and failure callbacks of the Form's action methods ({@link Ext.form.BasicForm#submit submit},
58686 * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}),
58687 * and to the {@link Ext.form.BasicForm#actioncomplete actioncomplete} and
58688 * {@link Ext.form.BasicForm#actionfailed actionfailed} event handlers.</p>
58690 Ext.form.Action = function(form, options){
58692 this.options = options || {};
58696 * Failure type returned when client side validation of the Form fails
58697 * thus aborting a submit action. Client side validation is performed unless
58698 * {@link #clientValidation} is explicitly set to <tt>false</tt>.
58702 Ext.form.Action.CLIENT_INVALID = 'client';
58704 * <p>Failure type returned when server side processing fails and the {@link #result}'s
58705 * <tt style="font-weight:bold">success</tt> property is set to <tt>false</tt>.</p>
58706 * <p>In the case of a form submission, field-specific error messages may be returned in the
58707 * {@link #result}'s <tt style="font-weight:bold">errors</tt> property.</p>
58711 Ext.form.Action.SERVER_INVALID = 'server';
58713 * Failure type returned when a communication error happens when attempting
58714 * to send a request to the remote server. The {@link #response} may be examined to
58715 * provide further information.
58719 Ext.form.Action.CONNECT_FAILURE = 'connect';
58721 * Failure type returned when the response's <tt style="font-weight:bold">success</tt>
58722 * property is set to <tt>false</tt>, or no field values are returned in the response's
58723 * <tt style="font-weight:bold">data</tt> property.
58727 Ext.form.Action.LOAD_FAILURE = 'load';
58729 Ext.form.Action.prototype = {
58731 * @cfg {String} url The URL that the Action is to invoke.
58734 * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
58735 * {@link Ext.form.BasicForm.reset reset} on Action success. If specified, this happens
58736 * <b>before</b> the {@link #success} callback is called and before the Form's
58737 * {@link Ext.form.BasicForm.actioncomplete actioncomplete} event fires.
58740 * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
58741 * {@link Ext.form.BasicForm}'s method, or if that is not specified, the underlying DOM form's method.
58744 * @cfg {Mixed} params <p>Extra parameter values to pass. These are added to the Form's
58745 * {@link Ext.form.BasicForm#baseParams} and passed to the specified URL along with the Form's
58746 * input fields.</p>
58747 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
58750 * @cfg {Number} timeout The number of seconds to wait for a server response before
58751 * failing with the {@link #failureType} as {@link #Action.CONNECT_FAILURE}. If not specified,
58752 * defaults to the configured <tt>{@link Ext.form.BasicForm#timeout timeout}</tt> of the
58753 * {@link Ext.form.BasicForm form}.
58756 * @cfg {Function} success The function to call when a valid success return packet is recieved.
58757 * The function is passed the following parameters:<ul class="mdetail-params">
58758 * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
58759 * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. The {@link #result}
58760 * property of this object may be examined to perform custom postprocessing.</div></li>
58764 * @cfg {Function} failure The function to call when a failure packet was recieved, or when an
58765 * error ocurred in the Ajax communication.
58766 * The function is passed the following parameters:<ul class="mdetail-params">
58767 * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
58768 * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. If an Ajax
58769 * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
58770 * property of this object may be examined to perform custom postprocessing.</div></li>
58774 * @cfg {Object} scope The scope in which to call the callback functions (The <tt>this</tt> reference
58775 * for the callback functions).
58778 * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.MessageBox#wait}
58779 * during the time the action is being processed.
58782 * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.MessageBox#wait}
58783 * during the time the action is being processed.
58787 * The type of action this Action instance performs.
58788 * Currently only "submit" and "load" are supported.
58793 * The type of failure detected will be one of these: {@link #CLIENT_INVALID},
58794 * {@link #SERVER_INVALID}, {@link #CONNECT_FAILURE}, or {@link #LOAD_FAILURE}. Usage:
58796 var fp = new Ext.form.FormPanel({
58801 handler: function(){
58802 if(fp.getForm().isValid()){
58803 fp.getForm().submit({
58804 url: 'form-submit.php',
58805 waitMsg: 'Submitting your data...',
58806 success: function(form, action){
58807 // server responded with success = true
58808 var result = action.{@link #result};
58810 failure: function(form, action){
58811 if (action.{@link #failureType} === Ext.form.Action.{@link #CONNECT_FAILURE}) {
58812 Ext.Msg.alert('Error',
58813 'Status:'+action.{@link #response}.status+': '+
58814 action.{@link #response}.statusText);
58816 if (action.failureType === Ext.form.Action.{@link #SERVER_INVALID}){
58817 // server responded with success = false
58818 Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
58826 handler: function(){
58827 fp.getForm().reset();
58831 * @property failureType
58835 * The XMLHttpRequest object used to perform the action.
58836 * @property response
58840 * The decoded response object containing a boolean <tt style="font-weight:bold">success</tt> property and
58841 * other, action-specific properties.
58846 // interface method
58847 run : function(options){
58851 // interface method
58852 success : function(response){
58856 // interface method
58857 handleResponse : function(response){
58861 // default connection failure
58862 failure : function(response){
58863 this.response = response;
58864 this.failureType = Ext.form.Action.CONNECT_FAILURE;
58865 this.form.afterAction(this, false);
58869 // shared code among all Actions to validate that there was a response
58870 // with either responseText or responseXml
58871 processResponse : function(response){
58872 this.response = response;
58873 if(!response.responseText && !response.responseXML){
58876 this.result = this.handleResponse(response);
58877 return this.result;
58880 // utility functions used internally
58881 getUrl : function(appendParams){
58882 var url = this.options.url || this.form.url || this.form.el.dom.action;
58884 var p = this.getParams();
58886 url = Ext.urlAppend(url, p);
58893 getMethod : function(){
58894 return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
58898 getParams : function(){
58899 var bp = this.form.baseParams;
58900 var p = this.options.params;
58902 if(typeof p == "object"){
58903 p = Ext.urlEncode(Ext.applyIf(p, bp));
58904 }else if(typeof p == 'string' && bp){
58905 p += '&' + Ext.urlEncode(bp);
58908 p = Ext.urlEncode(bp);
58914 createCallback : function(opts){
58915 var opts = opts || {};
58917 success: this.success,
58918 failure: this.failure,
58920 timeout: (opts.timeout*1000) || (this.form.timeout*1000),
58921 upload: this.form.fileUpload ? this.success : undefined
58927 * @class Ext.form.Action.Submit
58928 * @extends Ext.form.Action
58929 * <p>A class which handles submission of data from {@link Ext.form.BasicForm Form}s
58930 * and processes the returned response.</p>
58931 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
58932 * {@link Ext.form.BasicForm#submit submit}ting.</p>
58933 * <p><u><b>Response Packet Criteria</b></u></p>
58934 * <p>A response packet may contain:
58935 * <div class="mdetail-params"><ul>
58936 * <li><b><code>success</code></b> property : Boolean
58937 * <div class="sub-desc">The <code>success</code> property is required.</div></li>
58938 * <li><b><code>errors</code></b> property : Object
58939 * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
58940 * which is optional, contains error messages for invalid fields.</div></li>
58942 * <p><u><b>JSON Packets</b></u></p>
58943 * <p>By default, response packets are assumed to be JSON, so a typical response
58944 * packet may look like this:</p><pre><code>
58948 clientCode: "Client not found",
58949 portOfLoading: "This field must not be null"
58952 * <p>Other data may be placed into the response for processing by the {@link Ext.form.BasicForm}'s callback
58953 * or event handler methods. The object decoded from this JSON is available in the
58954 * {@link Ext.form.Action#result result} property.</p>
58955 * <p>Alternatively, if an {@link #errorReader} is specified as an {@link Ext.data.XmlReader XmlReader}:</p><pre><code>
58956 errorReader: new Ext.data.XmlReader({
58958 success: '@success'
58964 * <p>then the results may be sent back in XML format:</p><pre><code>
58965 <?xml version="1.0" encoding="UTF-8"?>
58966 <message success="false">
58969 <id>clientCode</id>
58970 <msg><![CDATA[Code not found. <br /><i>This is a test validation message from the server </i>]]></msg>
58973 <id>portOfLoading</id>
58974 <msg><![CDATA[Port not found. <br /><i>This is a test validation message from the server </i>]]></msg>
58979 * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.BasicForm}'s callback
58980 * or event handler methods. The XML document is available in the {@link #errorReader}'s {@link Ext.data.XmlReader#xmlData xmlData} property.</p>
58982 Ext.form.Action.Submit = function(form, options){
58983 Ext.form.Action.Submit.superclass.constructor.call(this, form, options);
58986 Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
58988 * @cfg {Ext.data.DataReader} errorReader <p><b>Optional. JSON is interpreted with
58989 * no need for an errorReader.</b></p>
58990 * <p>A Reader which reads a single record from the returned data. The DataReader's
58991 * <b>success</b> property specifies how submission success is determined. The Record's
58992 * data provides the error messages to apply to any invalid form Fields.</p>
58995 * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
58996 * in a final call to {@link Ext.form.BasicForm#isValid isValid} prior to submission.
58997 * Pass <tt>false</tt> in the Form's submit options to prevent this. If not defined, pre-submission field validation
59004 var o = this.options;
59005 var method = this.getMethod();
59006 var isGet = method == 'GET';
59007 if(o.clientValidation === false || this.form.isValid()){
59008 Ext.Ajax.request(Ext.apply(this.createCallback(o), {
59009 form:this.form.el.dom,
59010 url:this.getUrl(isGet),
59012 headers: o.headers,
59013 params:!isGet ? this.getParams() : null,
59014 isUpload: this.form.fileUpload
59016 }else if (o.clientValidation !== false){ // client validation failed
59017 this.failureType = Ext.form.Action.CLIENT_INVALID;
59018 this.form.afterAction(this, false);
59023 success : function(response){
59024 var result = this.processResponse(response);
59025 if(result === true || result.success){
59026 this.form.afterAction(this, true);
59030 this.form.markInvalid(result.errors);
59031 this.failureType = Ext.form.Action.SERVER_INVALID;
59033 this.form.afterAction(this, false);
59037 handleResponse : function(response){
59038 if(this.form.errorReader){
59039 var rs = this.form.errorReader.read(response);
59042 for(var i = 0, len = rs.records.length; i < len; i++) {
59043 var r = rs.records[i];
59044 errors[i] = r.data;
59047 if(errors.length < 1){
59051 success : rs.success,
59055 return Ext.decode(response.responseText);
59061 * @class Ext.form.Action.Load
59062 * @extends Ext.form.Action
59063 * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.BasicForm}.</p>
59064 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
59065 * {@link Ext.form.BasicForm#load load}ing.</p>
59066 * <p><u><b>Response Packet Criteria</b></u></p>
59067 * <p>A response packet <b>must</b> contain:
59068 * <div class="mdetail-params"><ul>
59069 * <li><b><code>success</code></b> property : Boolean</li>
59070 * <li><b><code>data</code></b> property : Object</li>
59071 * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
59072 * The individual value object for each Field is passed to the Field's
59073 * {@link Ext.form.Field#setValue setValue} method.</div></li>
59075 * <p><u><b>JSON Packets</b></u></p>
59076 * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
59077 var myFormPanel = new Ext.form.FormPanel({
59078 title: 'Client and routing info',
59080 fieldLabel: 'Client',
59083 fieldLabel: 'Port of loading',
59084 name: 'portOfLoading'
59086 fieldLabel: 'Port of discharge',
59087 name: 'portOfDischarge'
59090 myFormPanel.{@link Ext.form.FormPanel#getForm getForm}().{@link Ext.form.BasicForm#load load}({
59091 url: '/getRoutingInfo.php',
59093 consignmentRef: myConsignmentRef
59095 failure: function(form, action() {
59096 Ext.Msg.alert("Load failed", action.result.errorMessage);
59100 * a <b>success response</b> packet may look like this:</p><pre><code>
59104 clientName: "Fred. Olsen Lines",
59105 portOfLoading: "FXT",
59106 portOfDischarge: "OSL"
59109 * while a <b>failure response</b> packet may look like this:</p><pre><code>
59112 errorMessage: "Consignment reference not found"
59114 * <p>Other data may be placed into the response for processing the {@link Ext.form.BasicForm Form}'s
59115 * callback or event handler methods. The object decoded from this JSON is available in the
59116 * {@link Ext.form.Action#result result} property.</p>
59118 Ext.form.Action.Load = function(form, options){
59119 Ext.form.Action.Load.superclass.constructor.call(this, form, options);
59120 this.reader = this.form.reader;
59123 Ext.extend(Ext.form.Action.Load, Ext.form.Action, {
59129 Ext.Ajax.request(Ext.apply(
59130 this.createCallback(this.options), {
59131 method:this.getMethod(),
59132 url:this.getUrl(false),
59133 headers: this.options.headers,
59134 params:this.getParams()
59139 success : function(response){
59140 var result = this.processResponse(response);
59141 if(result === true || !result.success || !result.data){
59142 this.failureType = Ext.form.Action.LOAD_FAILURE;
59143 this.form.afterAction(this, false);
59146 this.form.clearInvalid();
59147 this.form.setValues(result.data);
59148 this.form.afterAction(this, true);
59152 handleResponse : function(response){
59153 if(this.form.reader){
59154 var rs = this.form.reader.read(response);
59155 var data = rs.records && rs.records[0] ? rs.records[0].data : null;
59157 success : rs.success,
59161 return Ext.decode(response.responseText);
59168 * @class Ext.form.Action.DirectLoad
59169 * @extends Ext.form.Action.Load
59170 * Provides Ext.direct support for loading form data. This example illustrates usage
59171 * of Ext.Direct to load a submit a form through Ext.Direct.
59173 var myFormPanel = new Ext.form.FormPanel({
59174 // configs for FormPanel
59175 title: 'Basic Information',
59180 handler: function(){
59181 basicInfo.getForm().submit({
59189 // configs apply to child items
59190 defaults: {anchor: '100%'},
59191 defaultType: 'textfield',
59193 // form fields go here
59196 // configs for BasicForm
59198 load: Profile.getBasicInfo,
59199 // The server-side must mark the submit handler as a 'formHandler'
59200 submit: Profile.updateBasicInfo
59202 paramOrder: ['uid']
59206 myFormPanel.getForm().load({
59213 Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
59214 constructor: function(form, opts) {
59215 Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts);
59217 type: 'directload',
59220 var args = this.getParams();
59221 args.push(this.success, this);
59222 this.form.api.load.apply(window, args);
59225 getParams: function() {
59226 var buf = [], o = {};
59227 var bp = this.form.baseParams;
59228 var p = this.options.params;
59229 Ext.apply(o, p, bp);
59230 var paramOrder = this.form.paramOrder;
59232 for(var i = 0, len = paramOrder.length; i < len; i++){
59233 buf.push(o[paramOrder[i]]);
59235 }else if(this.form.paramsAsHash){
59240 // Direct actions have already been processed and therefore
59241 // we can directly set the result; Direct Actions do not have
59242 // a this.response property.
59243 processResponse: function(result) {
59244 this.result = result;
59250 * @class Ext.form.Action.DirectSubmit
59251 * @extends Ext.form.Action.Submit
59252 * Provides Ext.direct support for submitting form data.
59253 * See {@link Ext.form.Action.DirectLoad}.
59255 Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
59256 constructor: function(form, opts) {
59257 Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts);
59259 type: 'directsubmit',
59260 // override of Submit
59262 var o = this.options;
59263 if(o.clientValidation === false || this.form.isValid()){
59264 // tag on any additional params to be posted in the
59266 this.success.params = this.getParams();
59267 this.form.api.submit(this.form.el.dom, this.success, this);
59268 }else if (o.clientValidation !== false){ // client validation failed
59269 this.failureType = Ext.form.Action.CLIENT_INVALID;
59270 this.form.afterAction(this, false);
59274 getParams: function() {
59276 var bp = this.form.baseParams;
59277 var p = this.options.params;
59278 Ext.apply(o, p, bp);
59281 // Direct actions have already been processed and therefore
59282 // we can directly set the result; Direct Actions do not have
59283 // a this.response property.
59284 processResponse: function(result) {
59285 this.result = result;
59291 Ext.form.Action.ACTION_TYPES = {
59292 'load' : Ext.form.Action.Load,
59293 'submit' : Ext.form.Action.Submit,
59294 'directload': Ext.form.Action.DirectLoad,
59295 'directsubmit': Ext.form.Action.DirectSubmit
59298 * @class Ext.form.VTypes
59299 * <p>This is a singleton object which contains a set of commonly used field validation functions.
59300 * The validations provided are basic and intended to be easily customizable and extended.</p>
59301 * <p>To add custom VTypes specify the <code>{@link Ext.form.TextField#vtype vtype}</code> validation
59302 * test function, and optionally specify any corresponding error text to display and any keystroke
59303 * filtering mask to apply. For example:</p>
59305 // custom Vtype for vtype:'time'
59306 var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
59307 Ext.apply(Ext.form.VTypes, {
59308 // vtype validation function
59309 time: function(val, field) {
59310 return timeTest.test(val);
59312 // vtype Text property: The error text to display when the validation function returns false
59313 timeText: 'Not a valid time. Must be in the format "12:34 PM".',
59314 // vtype Mask property: The keystroke filter mask
59315 timeMask: /[\d\s:amp]/i
59320 // custom Vtype for vtype:'IPAddress'
59321 Ext.apply(Ext.form.VTypes, {
59322 IPAddress: function(v) {
59323 return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
59325 IPAddressText: 'Must be a numeric IP address',
59326 IPAddressMask: /[\d\.]/i
59331 Ext.form.VTypes = function(){
59332 // closure these in so they are only created once.
59333 var alpha = /^[a-zA-Z_]+$/;
59334 var alphanum = /^[a-zA-Z0-9_]+$/;
59335 var email = /^(\w+)([-+.][\w]+)*@(\w[-\w]*\.){1,5}([A-Za-z]){2,4}$/;
59336 var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
59338 // All these messages and functions are configurable
59341 * The function used to validate email addresses. Note that this is a very basic validation -- complete
59342 * validation per the email RFC specifications is very complex and beyond the scope of this class, although
59343 * this function can be overridden if a more comprehensive validation scheme is desired. See the validation
59344 * section of the <a href="http://en.wikipedia.org/wiki/E-mail_address">Wikipedia article on email addresses</a>
59345 * for additional information. This implementation is intended to validate the following emails:<tt>
59346 * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
59348 * @param {String} value The email address
59349 * @return {Boolean} true if the RegExp test passed, and false if not.
59351 'email' : function(v){
59352 return email.test(v);
59355 * The error text to display when the email validation function returns false. Defaults to:
59356 * <tt>'This field should be an e-mail address in the format "user@example.com"'</tt>
59359 'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
59361 * The keystroke filter mask to be applied on email input. See the {@link #email} method for
59362 * information about more complex email validation. Defaults to:
59363 * <tt>/[a-z0-9_\.\-@]/i</tt>
59366 'emailMask' : /[a-z0-9_\.\-@]/i,
59369 * The function used to validate URLs
59370 * @param {String} value The URL
59371 * @return {Boolean} true if the RegExp test passed, and false if not.
59373 'url' : function(v){
59374 return url.test(v);
59377 * The error text to display when the url validation function returns false. Defaults to:
59378 * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
59381 'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
59384 * The function used to validate alpha values
59385 * @param {String} value The value
59386 * @return {Boolean} true if the RegExp test passed, and false if not.
59388 'alpha' : function(v){
59389 return alpha.test(v);
59392 * The error text to display when the alpha validation function returns false. Defaults to:
59393 * <tt>'This field should only contain letters and _'</tt>
59396 'alphaText' : 'This field should only contain letters and _',
59398 * The keystroke filter mask to be applied on alpha input. Defaults to:
59399 * <tt>/[a-z_]/i</tt>
59402 'alphaMask' : /[a-z_]/i,
59405 * The function used to validate alphanumeric values
59406 * @param {String} value The value
59407 * @return {Boolean} true if the RegExp test passed, and false if not.
59409 'alphanum' : function(v){
59410 return alphanum.test(v);
59413 * The error text to display when the alphanumeric validation function returns false. Defaults to:
59414 * <tt>'This field should only contain letters, numbers and _'</tt>
59417 'alphanumText' : 'This field should only contain letters, numbers and _',
59419 * The keystroke filter mask to be applied on alphanumeric input. Defaults to:
59420 * <tt>/[a-z0-9_]/i</tt>
59423 'alphanumMask' : /[a-z0-9_]/i
59426 * @class Ext.grid.GridPanel
\r
59427 * @extends Ext.Panel
\r
59428 * <p>This class represents the primary interface of a component based grid control to represent data
\r
59429 * in a tabular format of rows and columns. The GridPanel is composed of the following:</p>
\r
59430 * <div class="mdetail-params"><ul>
\r
59431 * <li><b>{@link Ext.data.Store Store}</b> : The Model holding the data records (rows)
\r
59432 * <div class="sub-desc"></div></li>
\r
59433 * <li><b>{@link Ext.grid.ColumnModel Column model}</b> : Column makeup
\r
59434 * <div class="sub-desc"></div></li>
\r
59435 * <li><b>{@link Ext.grid.GridView View}</b> : Encapsulates the user interface
\r
59436 * <div class="sub-desc"></div></li>
\r
59437 * <li><b>{@link Ext.grid.AbstractSelectionModel selection model}</b> : Selection behavior
\r
59438 * <div class="sub-desc"></div></li>
\r
59440 * <p>Example usage:</p>
\r
59442 var grid = new Ext.grid.GridPanel({
\r
59443 {@link #store}: new (@link Ext.data.Store}({
\r
59444 {@link Ext.data.Store#autoDestroy autoDestroy}: true,
\r
59445 {@link Ext.data.Store#reader reader}: reader,
\r
59446 {@link Ext.data.Store#data data}: xg.dummyData
\r
59448 {@link #columns}: [
\r
59449 {id: 'company', header: 'Company', width: 200, sortable: true, dataIndex: 'company'},
\r
59450 {header: 'Price', width: 120, sortable: true, renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
\r
59451 {header: 'Change', width: 120, sortable: true, dataIndex: 'change'},
\r
59452 {header: '% Change', width: 120, sortable: true, dataIndex: 'pctChange'},
\r
59453 // instead of specifying renderer: Ext.util.Format.dateRenderer('m/d/Y') use xtype
\r
59454 {header: 'Last Updated', width: 135, sortable: true, dataIndex: 'lastChange', xtype: 'datecolumn', format: 'M d, Y'}
\r
59456 {@link #viewConfig}: {
\r
59457 {@link Ext.grid.GridView#forceFit forceFit}: true,
\r
59459 // Return CSS class to apply to rows depending upon data values
\r
59460 {@link Ext.grid.GridView#getRowClass getRowClass}: function(record, index) {
\r
59461 var c = record.{@link Ext.data.Record#get get}('change');
\r
59463 return 'price-fall';
\r
59464 } else if (c > 0) {
\r
59465 return 'price-rise';
\r
59469 {@link #sm}: new Ext.grid.RowSelectionModel({singleSelect:true}),
\r
59473 title: 'Framed with Row Selection and Horizontal Scrolling',
\r
59474 iconCls: 'icon-grid'
\r
59477 * <p><b><u>Notes:</u></b></p>
\r
59478 * <div class="mdetail-params"><ul>
\r
59479 * <li>Although this class inherits many configuration options from base classes, some of them
\r
59480 * (such as autoScroll, autoWidth, layout, items, etc) are not used by this class, and will
\r
59481 * have no effect.</li>
\r
59482 * <li>A grid <b>requires</b> a width in which to scroll its columns, and a height in which to
\r
59483 * scroll its rows. These dimensions can either be set explicitly through the
\r
59484 * <tt>{@link Ext.BoxComponent#height height}</tt> and <tt>{@link Ext.BoxComponent#width width}</tt>
\r
59485 * configuration options or implicitly set by using the grid as a child item of a
\r
59486 * {@link Ext.Container Container} which will have a {@link Ext.Container#layout layout manager}
\r
59487 * provide the sizing of its child items (for example the Container of the Grid may specify
\r
59488 * <tt>{@link Ext.Container#layout layout}:'fit'</tt>).</li>
\r
59489 * <li>To access the data in a Grid, it is necessary to use the data model encapsulated
\r
59490 * by the {@link #store Store}. See the {@link #cellclick} event for more details.</li>
\r
59493 * @param {Object} config The config object
\r
59496 Ext.grid.GridPanel = Ext.extend(Ext.Panel, {
\r
59498 * @cfg {String} autoExpandColumn
\r
59499 * <p>The <tt>{@link Ext.grid.Column#id id}</tt> of a {@link Ext.grid.Column column} in
\r
59500 * this grid that should expand to fill unused space. This value specified here can not
\r
59501 * be <tt>0</tt>.</p>
\r
59502 * <br><p><b>Note</b>: If the Grid's {@link Ext.grid.GridView view} is configured with
\r
59503 * <tt>{@link Ext.grid.GridView#forceFit forceFit}=true</tt> the <tt>autoExpandColumn</tt>
\r
59504 * is ignored. See {@link Ext.grid.Column}.<tt>{@link Ext.grid.Column#width width}</tt>
\r
59505 * for additional details.</p>
\r
59506 * <p>See <tt>{@link #autoExpandMax}</tt> and <tt>{@link #autoExpandMin}</tt> also.</p>
\r
59508 autoExpandColumn : false,
\r
59510 * @cfg {Number} autoExpandMax The maximum width the <tt>{@link #autoExpandColumn}</tt>
\r
59511 * can have (if enabled). Defaults to <tt>1000</tt>.
\r
59513 autoExpandMax : 1000,
\r
59515 * @cfg {Number} autoExpandMin The minimum width the <tt>{@link #autoExpandColumn}</tt>
\r
59516 * can have (if enabled). Defaults to <tt>50</tt>.
\r
59518 autoExpandMin : 50,
\r
59520 * @cfg {Boolean} columnLines <tt>true</tt> to add css for column separation lines.
\r
59521 * Default is <tt>false</tt>.
\r
59523 columnLines : false,
\r
59525 * @cfg {Object} cm Shorthand for <tt>{@link #colModel}</tt>.
\r
59528 * @cfg {Object} colModel The {@link Ext.grid.ColumnModel} to use when rendering the grid (required).
\r
59531 * @cfg {Array} columns An array of {@link Ext.grid.Column columns} to auto create a
\r
59532 * {@link Ext.grid.ColumnModel}. The ColumnModel may be explicitly created via the
\r
59533 * <tt>{@link #colModel}</tt> configuration property.
\r
59536 * @cfg {String} ddGroup The DD group this GridPanel belongs to. Defaults to <tt>'GridDD'</tt> if not specified.
\r
59539 * @cfg {String} ddText
\r
59540 * Configures the text in the drag proxy. Defaults to:
\r
59542 * ddText : '{0} selected row{1}'
\r
59544 * <tt>{0}</tt> is replaced with the number of selected rows.
\r
59546 ddText : '{0} selected row{1}',
\r
59548 * @cfg {Boolean} deferRowRender <P>Defaults to <tt>true</tt> to enable deferred row rendering.</p>
\r
59549 * <p>This allows the GridPanel to be initially rendered empty, with the expensive update of the row
\r
59550 * structure deferred so that layouts with GridPanels appear more quickly.</p>
\r
59552 deferRowRender : true,
\r
59554 * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selections in the grid. Defaults to <tt>false</tt>.</p>
\r
59555 * <p>Ignored if a {@link #selModel SelectionModel} is specified.</p>
\r
59558 * @cfg {Boolean} enableColumnResize <tt>false</tt> to turn off column resizing for the whole grid. Defaults to <tt>true</tt>.
\r
59561 * @cfg {Boolean} enableColumnHide Defaults to <tt>true</tt> to enable hiding of columns with the header context menu.
\r
59563 enableColumnHide : true,
\r
59565 * @cfg {Boolean} enableColumnMove Defaults to <tt>true</tt> to enable drag and drop reorder of columns. <tt>false</tt>
\r
59566 * to turn off column reordering via drag drop.
\r
59568 enableColumnMove : true,
\r
59570 * @cfg {Boolean} enableDragDrop <p>Enables dragging of the selected rows of the GridPanel. Defaults to <tt>false</tt>.</p>
\r
59571 * <p>Setting this to <b><tt>true</tt></b> causes this GridPanel's {@link #getView GridView} to
\r
59572 * create an instance of {@link Ext.grid.GridDragZone}. <b>Note</b>: this is available only <b>after</b>
\r
59573 * the Grid has been rendered as the GridView's <tt>{@link Ext.grid.GridView#dragZone dragZone}</tt>
\r
59575 * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's implementations of
\r
59576 * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
\r
59577 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} are able
\r
59578 * to process the {@link Ext.grid.GridDragZone#getDragData data} which is provided.</p>
\r
59580 enableDragDrop : false,
\r
59582 * @cfg {Boolean} enableHdMenu Defaults to <tt>true</tt> to enable the drop down button for menu in the headers.
\r
59584 enableHdMenu : true,
\r
59586 * @cfg {Boolean} hideHeaders True to hide the grid's header. Defaults to <code>false</code>.
\r
59589 * @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the grid while
\r
59590 * loading. Defaults to <code>false</code>.
\r
59592 loadMask : false,
\r
59594 * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if <tt>autoHeight</tt> is not on.
\r
59597 * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Defaults to <tt>25</tt>.
\r
59599 minColumnWidth : 25,
\r
59601 * @cfg {Object} sm Shorthand for <tt>{@link #selModel}</tt>.
\r
59604 * @cfg {Object} selModel Any subclass of {@link Ext.grid.AbstractSelectionModel} that will provide
\r
59605 * the selection model for the grid (defaults to {@link Ext.grid.RowSelectionModel} if not specified).
\r
59608 * @cfg {Ext.data.Store} store The {@link Ext.data.Store} the grid should use as its data source (required).
\r
59611 * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
\r
59612 * <p>This causes the CSS class <tt><b>x-grid3-row-alt</b></tt> to be added to alternate rows of
\r
59613 * the grid. A default CSS rule is provided which sets a background colour, but you can override this
\r
59614 * with a rule which either overrides the <b>background-color</b> style using the '!important'
\r
59615 * modifier, or which uses a CSS selector of higher specificity.</p>
\r
59617 stripeRows : false,
\r
59619 * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is <tt>true</tt>
\r
59620 * for GridPanel, but <tt>false</tt> for EditorGridPanel.
\r
59622 trackMouseOver : true,
\r
59624 * @cfg {Array} stateEvents
\r
59625 * An array of events that, when fired, should trigger this component to save its state.
\r
59626 * Defaults to:<pre><code>
\r
59627 * stateEvents: ['columnmove', 'columnresize', 'sortchange']
\r
59629 * <p>These can be any types of events supported by this component, including browser or
\r
59630 * custom events (e.g., <tt>['click', 'customerchange']</tt>).</p>
\r
59631 * <p>See {@link Ext.Component#stateful} for an explanation of saving and restoring
\r
59632 * Component state.</p>
\r
59634 stateEvents : ['columnmove', 'columnresize', 'sortchange'],
\r
59636 * @cfg {Object} view The {@link Ext.grid.GridView} used by the grid. This can be set
\r
59637 * before a call to {@link Ext.Component#render render()}.
\r
59641 * @cfg {Object} viewConfig A config object that will be applied to the grid's UI view. Any of
\r
59642 * the config options available for {@link Ext.grid.GridView} can be specified here. This option
\r
59643 * is ignored if <tt>{@link #view}</tt> is specified.
\r
59647 rendered : false,
\r
59649 viewReady : false,
\r
59652 initComponent : function(){
\r
59653 Ext.grid.GridPanel.superclass.initComponent.call(this);
\r
59655 if(this.columnLines){
\r
59656 this.cls = (this.cls || '') + ' x-grid-with-col-lines';
\r
59658 // override any provided value since it isn't valid
\r
59659 // and is causing too many bug reports ;)
\r
59660 this.autoScroll = false;
\r
59661 this.autoWidth = false;
\r
59663 if(Ext.isArray(this.columns)){
\r
59664 this.colModel = new Ext.grid.ColumnModel(this.columns);
\r
59665 delete this.columns;
\r
59668 // check and correct shorthanded configs
\r
59670 this.store = this.ds;
\r
59674 this.colModel = this.cm;
\r
59678 this.selModel = this.sm;
\r
59681 this.store = Ext.StoreMgr.lookup(this.store);
\r
59687 * The raw click event for the entire grid.
\r
59688 * @param {Ext.EventObject} e
\r
59692 * @event dblclick
\r
59693 * The raw dblclick event for the entire grid.
\r
59694 * @param {Ext.EventObject} e
\r
59698 * @event contextmenu
\r
59699 * The raw contextmenu event for the entire grid.
\r
59700 * @param {Ext.EventObject} e
\r
59704 * @event mousedown
\r
59705 * The raw mousedown event for the entire grid.
\r
59706 * @param {Ext.EventObject} e
\r
59711 * The raw mouseup event for the entire grid.
\r
59712 * @param {Ext.EventObject} e
\r
59716 * @event mouseover
\r
59717 * The raw mouseover event for the entire grid.
\r
59718 * @param {Ext.EventObject} e
\r
59722 * @event mouseout
\r
59723 * The raw mouseout event for the entire grid.
\r
59724 * @param {Ext.EventObject} e
\r
59728 * @event keypress
\r
59729 * The raw keypress event for the entire grid.
\r
59730 * @param {Ext.EventObject} e
\r
59735 * The raw keydown event for the entire grid.
\r
59736 * @param {Ext.EventObject} e
\r
59742 * @event cellmousedown
\r
59743 * Fires before a cell is clicked
\r
59744 * @param {Grid} this
\r
59745 * @param {Number} rowIndex
\r
59746 * @param {Number} columnIndex
\r
59747 * @param {Ext.EventObject} e
\r
59751 * @event rowmousedown
\r
59752 * Fires before a row is clicked
\r
59753 * @param {Grid} this
\r
59754 * @param {Number} rowIndex
\r
59755 * @param {Ext.EventObject} e
\r
59759 * @event headermousedown
\r
59760 * Fires before a header is clicked
\r
59761 * @param {Grid} this
\r
59762 * @param {Number} columnIndex
\r
59763 * @param {Ext.EventObject} e
\r
59765 'headermousedown',
\r
59768 * @event cellclick
\r
59769 * Fires when a cell is clicked.
\r
59770 * The data for the cell is drawn from the {@link Ext.data.Record Record}
\r
59771 * for this row. To access the data in the listener function use the
\r
59772 * following technique:
\r
59774 function(grid, rowIndex, columnIndex, e) {
\r
59775 var record = grid.getStore().getAt(rowIndex); // Get the Record
\r
59776 var fieldName = grid.getColumnModel().getDataIndex(columnIndex); // Get field name
\r
59777 var data = record.get(fieldName);
\r
59780 * @param {Grid} this
\r
59781 * @param {Number} rowIndex
\r
59782 * @param {Number} columnIndex
\r
59783 * @param {Ext.EventObject} e
\r
59787 * @event celldblclick
\r
59788 * Fires when a cell is double clicked
\r
59789 * @param {Grid} this
\r
59790 * @param {Number} rowIndex
\r
59791 * @param {Number} columnIndex
\r
59792 * @param {Ext.EventObject} e
\r
59796 * @event rowclick
\r
59797 * Fires when a row is clicked
\r
59798 * @param {Grid} this
\r
59799 * @param {Number} rowIndex
\r
59800 * @param {Ext.EventObject} e
\r
59804 * @event rowdblclick
\r
59805 * Fires when a row is double clicked
\r
59806 * @param {Grid} this
\r
59807 * @param {Number} rowIndex
\r
59808 * @param {Ext.EventObject} e
\r
59812 * @event headerclick
\r
59813 * Fires when a header is clicked
\r
59814 * @param {Grid} this
\r
59815 * @param {Number} columnIndex
\r
59816 * @param {Ext.EventObject} e
\r
59820 * @event headerdblclick
\r
59821 * Fires when a header cell is double clicked
\r
59822 * @param {Grid} this
\r
59823 * @param {Number} columnIndex
\r
59824 * @param {Ext.EventObject} e
\r
59826 'headerdblclick',
\r
59828 * @event rowcontextmenu
\r
59829 * Fires when a row is right clicked
\r
59830 * @param {Grid} this
\r
59831 * @param {Number} rowIndex
\r
59832 * @param {Ext.EventObject} e
\r
59834 'rowcontextmenu',
\r
59836 * @event cellcontextmenu
\r
59837 * Fires when a cell is right clicked
\r
59838 * @param {Grid} this
\r
59839 * @param {Number} rowIndex
\r
59840 * @param {Number} cellIndex
\r
59841 * @param {Ext.EventObject} e
\r
59843 'cellcontextmenu',
\r
59845 * @event headercontextmenu
\r
59846 * Fires when a header is right clicked
\r
59847 * @param {Grid} this
\r
59848 * @param {Number} columnIndex
\r
59849 * @param {Ext.EventObject} e
\r
59851 'headercontextmenu',
\r
59853 * @event bodyscroll
\r
59854 * Fires when the body element is scrolled
\r
59855 * @param {Number} scrollLeft
\r
59856 * @param {Number} scrollTop
\r
59860 * @event columnresize
\r
59861 * Fires when the user resizes a column
\r
59862 * @param {Number} columnIndex
\r
59863 * @param {Number} newSize
\r
59867 * @event columnmove
\r
59868 * Fires when the user moves a column
\r
59869 * @param {Number} oldIndex
\r
59870 * @param {Number} newIndex
\r
59874 * @event sortchange
\r
59875 * Fires when the grid's store sort changes
\r
59876 * @param {Grid} this
\r
59877 * @param {Object} sortInfo An object with the keys field and direction
\r
59881 * @event reconfigure
\r
59882 * Fires when the grid is reconfigured with a new store and/or column model.
\r
59883 * @param {Grid} this
\r
59884 * @param {Ext.data.Store} store The new store
\r
59885 * @param {Ext.grid.ColumnModel} colModel The new column model
\r
59892 onRender : function(ct, position){
\r
59893 Ext.grid.GridPanel.superclass.onRender.apply(this, arguments);
\r
59895 var c = this.body;
\r
59897 this.el.addClass('x-grid-panel');
\r
59899 var view = this.getView();
\r
59903 mousedown: this.onMouseDown,
\r
59904 click: this.onClick,
\r
59905 dblclick: this.onDblClick,
\r
59906 contextmenu: this.onContextMenu,
\r
59907 keydown: this.onKeyDown,
\r
59911 this.relayEvents(c, ['mousedown','mouseup','mouseover','mouseout','keypress']);
\r
59913 this.getSelectionModel().init(this);
\r
59914 this.view.render();
\r
59918 initEvents : function(){
\r
59919 Ext.grid.GridPanel.superclass.initEvents.call(this);
\r
59921 if(this.loadMask){
\r
59922 this.loadMask = new Ext.LoadMask(this.bwrap,
\r
59923 Ext.apply({store:this.store}, this.loadMask));
\r
59927 initStateEvents : function(){
\r
59928 Ext.grid.GridPanel.superclass.initStateEvents.call(this);
\r
59929 this.mon(this.colModel, 'hiddenchange', this.saveState, this, {delay: 100});
\r
59932 applyState : function(state){
\r
59933 var cm = this.colModel;
\r
59934 var cs = state.columns;
\r
59936 for(var i = 0, len = cs.length; i < len; i++){
\r
59938 var c = cm.getColumnById(s.id);
\r
59940 c.hidden = s.hidden;
\r
59941 c.width = s.width;
\r
59942 var oldIndex = cm.getIndexById(s.id);
\r
59943 if(oldIndex != i){
\r
59944 cm.moveColumn(oldIndex, i);
\r
59949 if(state.sort && this.store){
\r
59950 this.store[this.store.remoteSort ? 'setDefaultSort' : 'sort'](state.sort.field, state.sort.direction);
\r
59952 delete state.columns;
\r
59953 delete state.sort;
\r
59954 Ext.grid.GridPanel.superclass.applyState.call(this, state);
\r
59957 getState : function(){
\r
59958 var o = {columns: []};
\r
59959 for(var i = 0, c; (c = this.colModel.config[i]); i++){
\r
59965 o.columns[i].hidden = true;
\r
59969 var ss = this.store.getSortState();
\r
59978 afterRender : function(){
\r
59979 Ext.grid.GridPanel.superclass.afterRender.call(this);
\r
59980 this.view.layout();
\r
59981 if(this.deferRowRender){
\r
59982 this.view.afterRender.defer(10, this.view);
\r
59984 this.view.afterRender();
\r
59986 this.viewReady = true;
\r
59990 * <p>Reconfigures the grid to use a different Store and Column Model
\r
59991 * and fires the 'reconfigure' event. The View will be bound to the new
\r
59992 * objects and refreshed.</p>
\r
59993 * <p>Be aware that upon reconfiguring a GridPanel, certain existing settings <i>may</i> become
\r
59994 * invalidated. For example the configured {@link #autoExpandColumn} may no longer exist in the
\r
59995 * new ColumnModel. Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound
\r
59996 * to the old Store, and will need rebinding. Any {@link #plugins} might also need reconfiguring
\r
59997 * with the new data.</p>
\r
59998 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
\r
59999 * @param {Ext.grid.ColumnModel} colModel The new {@link Ext.grid.ColumnModel} object
\r
60001 reconfigure : function(store, colModel){
\r
60002 if(this.loadMask){
\r
60003 this.loadMask.destroy();
\r
60004 this.loadMask = new Ext.LoadMask(this.bwrap,
\r
60005 Ext.apply({}, {store:store}, this.initialConfig.loadMask));
\r
60007 this.view.initData(store, colModel);
\r
60008 this.store = store;
\r
60009 this.colModel = colModel;
\r
60010 if(this.rendered){
\r
60011 this.view.refresh(true);
\r
60013 this.fireEvent('reconfigure', this, store, colModel);
\r
60017 onKeyDown : function(e){
\r
60018 this.fireEvent('keydown', e);
\r
60022 onDestroy : function(){
\r
60023 if(this.rendered){
\r
60024 var c = this.body;
\r
60025 c.removeAllListeners();
\r
60027 Ext.destroy(this.view, this.loadMask);
\r
60028 }else if(this.store && this.store.autoDestroy){
\r
60029 this.store.destroy();
\r
60031 Ext.destroy(this.colModel, this.selModel);
\r
60032 this.store = this.selModel = this.colModel = this.view = this.loadMask = null;
\r
60033 Ext.grid.GridPanel.superclass.onDestroy.call(this);
\r
60037 processEvent : function(name, e){
\r
60038 this.fireEvent(name, e);
\r
60039 var t = e.getTarget();
\r
60040 var v = this.view;
\r
60041 var header = v.findHeaderIndex(t);
\r
60042 if(header !== false){
\r
60043 this.fireEvent('header' + name, this, header, e);
\r
60045 var row = v.findRowIndex(t);
\r
60046 var cell = v.findCellIndex(t);
\r
60047 if(row !== false){
\r
60048 this.fireEvent('row' + name, this, row, e);
\r
60049 if(cell !== false){
\r
60050 this.fireEvent('cell' + name, this, row, cell, e);
\r
60057 onClick : function(e){
\r
60058 this.processEvent('click', e);
\r
60062 onMouseDown : function(e){
\r
60063 this.processEvent('mousedown', e);
\r
60067 onContextMenu : function(e, t){
\r
60068 this.processEvent('contextmenu', e);
\r
60072 onDblClick : function(e){
\r
60073 this.processEvent('dblclick', e);
\r
60077 walkCells : function(row, col, step, fn, scope){
\r
60078 var cm = this.colModel, clen = cm.getColumnCount();
\r
60079 var ds = this.store, rlen = ds.getCount(), first = true;
\r
60091 if(fn.call(scope || this, row, col, cm) === true){
\r
60092 return [row, col];
\r
60103 while(row < rlen){
\r
60108 while(col < clen){
\r
60109 if(fn.call(scope || this, row, col, cm) === true){
\r
60110 return [row, col];
\r
60121 onResize : function(){
\r
60122 Ext.grid.GridPanel.superclass.onResize.apply(this, arguments);
\r
60123 if(this.viewReady){
\r
60124 this.view.layout();
\r
60129 * Returns the grid's underlying element.
\r
60130 * @return {Element} The element
\r
60132 getGridEl : function(){
\r
60133 return this.body;
\r
60136 // private for compatibility, overridden by editor grid
\r
60137 stopEditing : Ext.emptyFn,
\r
60140 * Returns the grid's selection model configured by the <code>{@link #selModel}</code>
\r
60141 * configuration option. If no selection model was configured, this will create
\r
60142 * and return a {@link Ext.grid.RowSelectionModel RowSelectionModel}.
\r
60143 * @return {SelectionModel}
\r
60145 getSelectionModel : function(){
\r
60146 if(!this.selModel){
\r
60147 this.selModel = new Ext.grid.RowSelectionModel(
\r
60148 this.disableSelection ? {selectRow: Ext.emptyFn} : null);
\r
60150 return this.selModel;
\r
60154 * Returns the grid's data store.
\r
60155 * @return {Ext.data.Store} The store
\r
60157 getStore : function(){
\r
60158 return this.store;
\r
60162 * Returns the grid's ColumnModel.
\r
60163 * @return {Ext.grid.ColumnModel} The column model
\r
60165 getColumnModel : function(){
\r
60166 return this.colModel;
\r
60170 * Returns the grid's GridView object.
\r
60171 * @return {Ext.grid.GridView} The grid view
\r
60173 getView : function(){
\r
60175 this.view = new Ext.grid.GridView(this.viewConfig);
\r
60177 return this.view;
\r
60180 * Called to get grid's drag proxy text, by default returns this.ddText.
\r
60181 * @return {String} The text
\r
60183 getDragDropText : function(){
\r
60184 var count = this.selModel.getCount();
\r
60185 return String.format(this.ddText, count, count == 1 ? '' : 's');
\r
60189 * @cfg {String/Number} activeItem
\r
60193 * @cfg {Boolean} autoDestroy
\r
60197 * @cfg {Object/String/Function} autoLoad
\r
60201 * @cfg {Boolean} autoWidth
\r
60205 * @cfg {Boolean/Number} bufferResize
\r
60209 * @cfg {String} defaultType
\r
60213 * @cfg {Object} defaults
\r
60217 * @cfg {Boolean} hideBorders
\r
60221 * @cfg {Mixed} items
\r
60225 * @cfg {String} layout
\r
60229 * @cfg {Object} layoutConfig
\r
60233 * @cfg {Boolean} monitorResize
\r
60237 * @property items
\r
60245 * @method cascade
\r
60249 * @method doLayout
\r
60257 * @method findBy
\r
60261 * @method findById
\r
60265 * @method findByType
\r
60269 * @method getComponent
\r
60273 * @method getLayout
\r
60277 * @method getUpdater
\r
60281 * @method insert
\r
60289 * @method remove
\r
60297 * @event afterLayout
\r
60301 * @event beforeadd
\r
60305 * @event beforeremove
\r
60316 * @cfg {String} allowDomMove @hide
\r
60319 * @cfg {String} autoEl @hide
\r
60322 * @cfg {String} applyTo @hide
\r
60325 * @cfg {String} autoScroll @hide
\r
60328 * @cfg {String} bodyBorder @hide
\r
60331 * @cfg {String} bodyStyle @hide
\r
60334 * @cfg {String} contentEl @hide
\r
60337 * @cfg {String} disabledClass @hide
\r
60340 * @cfg {String} elements @hide
\r
60343 * @cfg {String} html @hide
\r
60346 * @cfg {Boolean} preventBodyReset
\r
60350 * @property disabled
\r
60354 * @method applyToMarkup
\r
60362 * @method disable
\r
60366 * @method setDisabled
\r
60370 Ext.reg('grid', Ext.grid.GridPanel);/**
60371 * @class Ext.grid.GridView
60372 * @extends Ext.util.Observable
60373 * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
60374 * Methods of this class may be used to access user interface elements to enable
60375 * special display effects. Do not change the DOM structure of the user interface.</p>
60376 * <p>This class does not provide ways to manipulate the underlying data. The data
60377 * model of a Grid is held in an {@link Ext.data.Store}.</p>
60379 * @param {Object} config
60381 Ext.grid.GridView = function(config){
60382 Ext.apply(this, config);
60383 // These events are only used internally by the grid components
60386 * @event beforerowremoved
60387 * Internal UI Event. Fired before a row is removed.
60388 * @param {Ext.grid.GridView} view
60389 * @param {Number} rowIndex The index of the row to be removed.
60390 * @param {Ext.data.Record} record The Record to be removed
60392 "beforerowremoved",
60394 * @event beforerowsinserted
60395 * Internal UI Event. Fired before rows are inserted.
60396 * @param {Ext.grid.GridView} view
60397 * @param {Number} firstRow The index of the first row to be inserted.
60398 * @param {Number} lastRow The index of the last row to be inserted.
60400 "beforerowsinserted",
60402 * @event beforerefresh
60403 * Internal UI Event. Fired before the view is refreshed.
60404 * @param {Ext.grid.GridView} view
60408 * @event rowremoved
60409 * Internal UI Event. Fired after a row is removed.
60410 * @param {Ext.grid.GridView} view
60411 * @param {Number} rowIndex The index of the row that was removed.
60412 * @param {Ext.data.Record} record The Record that was removed
60416 * @event rowsinserted
60417 * Internal UI Event. Fired after rows are inserted.
60418 * @param {Ext.grid.GridView} view
60419 * @param {Number} firstRow The index of the first inserted.
60420 * @param {Number} lastRow The index of the last row inserted.
60424 * @event rowupdated
60425 * Internal UI Event. Fired after a row has been updated.
60426 * @param {Ext.grid.GridView} view
60427 * @param {Number} firstRow The index of the row updated.
60428 * @param {Ext.data.record} record The Record backing the row updated.
60433 * Internal UI Event. Fired after the GridView's body has been refreshed.
60434 * @param {Ext.grid.GridView} view
60438 Ext.grid.GridView.superclass.constructor.call(this);
60441 Ext.extend(Ext.grid.GridView, Ext.util.Observable, {
60443 * Override this function to apply custom CSS classes to rows during rendering. You can also supply custom
60444 * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
60445 * parameter. This function should return the CSS class name (or empty string '' for none) that will be added
60446 * to the row's wrapping div. To apply multiple class names, simply return them space-delimited within the string
60447 * (e.g., 'my-class another-class'). Example usage:
60451 showPreview: true, // custom property
60452 enableRowBody: true, // required to create a second, full-width row to show expanded Record data
60453 getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
60454 if(this.showPreview){
60455 rp.body = '<p>'+record.data.excerpt+'</p>';
60456 return 'x-grid3-row-expanded';
60458 return 'x-grid3-row-collapsed';
60462 * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
60463 * @param {Number} index The row index.
60464 * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
60465 * customization of various aspects of a grid row.
60466 * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
60467 * by this function, and will be used to render a full-width expansion row below each grid row:</p>
60469 * <li><code>body</code> : String <div class="sub-desc">An HTML fragment to be used as the expansion row's body content (defaults to '').</div></li>
60470 * <li><code>bodyStyle</code> : String <div class="sub-desc">A CSS style specification that will be applied to the expansion row's <tr> element. (defaults to '').</div></li>
60472 * The following property will be passed in, and may be appended to:
60474 * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the <table> element which encapsulates
60475 * both the standard grid row, and any expansion row.</div></li>
60477 * @param {Store} store The {@link Ext.data.Store} this grid is bound to
60478 * @method getRowClass
60479 * @return {String} a CSS class name to add to the row.
60482 * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
60483 * that spans beneath the data row. Use the {@link #getRowClass} method's rowParams config to customize the row body.
60486 * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
60487 * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
60489 this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
60493 * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
60494 * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
60495 * config to disable the <i>menu</i> for individual columns. While this config is true the
60496 * following will be disabled:<div class="mdetail-params"><ul>
60497 * <li>clicking on header to sort</li>
60498 * <li>the trigger to reveal the menu.</li>
60502 * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
60503 * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
60504 * See {@link Ext.grid.GridDragZone} for details.</p>
60505 * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
60506 * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
60507 * <li><i>after</i> the owning GridPanel has been rendered.</li>
60509 * @property dragZone
60510 * @type {Ext.grid.GridDragZone}
60513 * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
60514 * first load (defaults to <tt>true</tt>).
60516 deferEmptyText : true,
60518 * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
60519 * (defaults to <tt>19</tt> pixels).
60523 * @cfg {Boolean} autoFill
60524 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
60525 * when the grid is <b>initially rendered</b>. The
60526 * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
60527 * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
60528 * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
60529 * See <tt>{@link #forceFit}</tt> also.
60533 * @cfg {Boolean} forceFit
60534 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
60535 * at <b>all times</b>. The {@link Ext.grid.Column#width initially configured width}</tt> of each
60536 * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
60537 * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
60538 * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
60542 * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>["sort-asc", "sort-desc"]</tt>)
60544 sortClasses : ["sort-asc", "sort-desc"],
60546 * @cfg {String} sortAscText The text displayed in the "Sort Ascending" menu item (defaults to <tt>"Sort Ascending"</tt>)
60548 sortAscText : "Sort Ascending",
60550 * @cfg {String} sortDescText The text displayed in the "Sort Descending" menu item (defaults to <tt>"Sort Descending"</tt>)
60552 sortDescText : "Sort Descending",
60554 * @cfg {String} columnsText The text displayed in the "Columns" menu item (defaults to <tt>"Columns"</tt>)
60556 columnsText : "Columns",
60559 * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>"x-grid3-row-selected"</tt>). An
60560 * example overriding the default styling:
60562 .x-grid3-row-selected {background-color: yellow;}
60564 * Note that this only controls the row, and will not do anything for the text inside it. To style inner
60565 * facets (like text) use something like:
60567 .x-grid3-row-selected .x-grid3-cell-inner {
60573 selectedRowClass : "x-grid3-row-selected",
60577 tdClass : 'x-grid3-cell',
60578 hdCls : 'x-grid3-hd',
60582 * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
60584 cellSelectorDepth : 4,
60586 * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
60588 rowSelectorDepth : 10,
60591 * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
60593 cellSelector : 'td.x-grid3-cell',
60595 * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
60597 rowSelector : 'div.x-grid3-row',
60600 firstRowCls: 'x-grid3-row-first',
60601 lastRowCls: 'x-grid3-row-last',
60602 rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
60604 /* -------------------------------- UI Specific ----------------------------- */
60607 initTemplates : function(){
60608 var ts = this.templates || {};
60610 ts.master = new Ext.Template(
60611 '<div class="x-grid3" hidefocus="true">',
60612 '<div class="x-grid3-viewport">',
60613 '<div class="x-grid3-header"><div class="x-grid3-header-inner"><div class="x-grid3-header-offset" style="{ostyle}">{header}</div></div><div class="x-clear"></div></div>',
60614 '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
60616 '<div class="x-grid3-resize-marker"> </div>',
60617 '<div class="x-grid3-resize-proxy"> </div>',
60623 ts.header = new Ext.Template(
60624 '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
60625 '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
60631 ts.hcell = new Ext.Template(
60632 '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}"><div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">', this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
60633 '{value}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
60639 ts.body = new Ext.Template('{rows}');
60643 ts.row = new Ext.Template(
60644 '<div class="x-grid3-row {alt}" style="{tstyle}"><table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
60645 '<tbody><tr>{cells}</tr>',
60646 (this.enableRowBody ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>' : ''),
60647 '</tbody></table></div>'
60652 ts.cell = new Ext.Template(
60653 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
60654 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
60661 if(t && typeof t.compile == 'function' && !t.compiled){
60662 t.disableFormats = true;
60667 this.templates = ts;
60668 this.colRe = new RegExp("x-grid3-td-([^\\s]+)", "");
60672 fly : function(el){
60673 if(!this._flyweight){
60674 this._flyweight = new Ext.Element.Flyweight(document.body);
60676 this._flyweight.dom = el;
60677 return this._flyweight;
60681 getEditorParent : function(){
60682 return this.scroller.dom;
60686 initElements : function(){
60687 var E = Ext.Element;
60689 var el = this.grid.getGridEl().dom.firstChild;
60690 var cs = el.childNodes;
60692 this.el = new E(el);
60694 this.mainWrap = new E(cs[0]);
60695 this.mainHd = new E(this.mainWrap.dom.firstChild);
60697 if(this.grid.hideHeaders){
60698 this.mainHd.setDisplayed(false);
60701 this.innerHd = this.mainHd.dom.firstChild;
60702 this.scroller = new E(this.mainWrap.dom.childNodes[1]);
60704 this.scroller.setStyle('overflow-x', 'hidden');
60707 * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
60708 * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
60709 * @type Ext.Element
60710 * @property mainBody
60712 this.mainBody = new E(this.scroller.dom.firstChild);
60714 this.focusEl = new E(this.scroller.dom.childNodes[1]);
60715 this.focusEl.swallowEvent("click", true);
60717 this.resizeMarker = new E(cs[1]);
60718 this.resizeProxy = new E(cs[2]);
60722 getRows : function(){
60723 return this.hasRows() ? this.mainBody.dom.childNodes : [];
60726 // finder methods, used with delegation
60729 findCell : function(el){
60733 return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
60737 * <p>Return the index of the grid column which contains the passed element.</p>
60738 * See also {@link #findRowIndex}
60739 * @param {Element} el The target element
60740 * @return The column index, or <b>false</b> if the target element is not within a row of this GridView.
60742 findCellIndex : function(el, requiredCls){
60743 var cell = this.findCell(el);
60744 if(cell && (!requiredCls || this.fly(cell).hasClass(requiredCls))){
60745 return this.getCellIndex(cell);
60751 getCellIndex : function(el){
60753 var m = el.className.match(this.colRe);
60755 return this.cm.getIndexById(m[1]);
60762 findHeaderCell : function(el){
60763 var cell = this.findCell(el);
60764 return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
60768 findHeaderIndex : function(el){
60769 return this.findCellIndex(el, this.hdCls);
60773 * Return the HtmlElement representing the grid row which contains the passed element.
60774 * @param {Element} el The target element
60775 * @return The row element, or null if the target element is not within a row of this GridView.
60777 findRow : function(el){
60781 return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
60785 * <p>Return the index of the grid row which contains the passed element.</p>
60786 * See also {@link #findCellIndex}
60787 * @param {Element} el The target element
60788 * @return The row index, or <b>false</b> if the target element is not within a row of this GridView.
60790 findRowIndex : function(el){
60791 var r = this.findRow(el);
60792 return r ? r.rowIndex : false;
60795 // getter methods for fetching elements dynamically in the grid
60798 * Return the <tt><div></tt> HtmlElement which represents a Grid row for the specified index.
60799 * @param {Number} index The row index
60800 * @return {HtmlElement} The div element.
60802 getRow : function(row){
60803 return this.getRows()[row];
60807 * Returns the grid's <tt><td></tt> HtmlElement at the specified coordinates.
60808 * @param {Number} row The row index in which to find the cell.
60809 * @param {Number} col The column index of the cell.
60810 * @return {HtmlElement} The td at the specified coordinates.
60812 getCell : function(row, col){
60813 return this.getRow(row).getElementsByTagName('td')[col];
60817 * Return the <tt><td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
60818 * @param {Number} index The column index
60819 * @return {HtmlElement} The td element.
60821 getHeaderCell : function(index){
60822 return this.mainHd.dom.getElementsByTagName('td')[index];
60825 // manipulating elements
60827 // private - use getRowClass to apply custom row classes
60828 addRowClass : function(row, cls){
60829 var r = this.getRow(row);
60831 this.fly(r).addClass(cls);
60836 removeRowClass : function(row, cls){
60837 var r = this.getRow(row);
60839 this.fly(r).removeClass(cls);
60844 removeRow : function(row){
60845 Ext.removeNode(this.getRow(row));
60846 this.syncFocusEl(row);
60850 removeRows : function(firstRow, lastRow){
60851 var bd = this.mainBody.dom;
60852 for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
60853 Ext.removeNode(bd.childNodes[firstRow]);
60855 this.syncFocusEl(firstRow);
60861 getScrollState : function(){
60862 var sb = this.scroller.dom;
60863 return {left: sb.scrollLeft, top: sb.scrollTop};
60867 restoreScroll : function(state){
60868 var sb = this.scroller.dom;
60869 sb.scrollLeft = state.left;
60870 sb.scrollTop = state.top;
60874 * Scrolls the grid to the top
60876 scrollToTop : function(){
60877 this.scroller.dom.scrollTop = 0;
60878 this.scroller.dom.scrollLeft = 0;
60882 syncScroll : function(){
60883 this.syncHeaderScroll();
60884 var mb = this.scroller.dom;
60885 this.grid.fireEvent("bodyscroll", mb.scrollLeft, mb.scrollTop);
60889 syncHeaderScroll : function(){
60890 var mb = this.scroller.dom;
60891 this.innerHd.scrollLeft = mb.scrollLeft;
60892 this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
60896 updateSortIcon : function(col, dir){
60897 var sc = this.sortClasses;
60898 var hds = this.mainHd.select('td').removeClass(sc);
60899 hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
60903 updateAllColumnWidths : function(){
60904 var tw = this.getTotalWidth(),
60905 clen = this.cm.getColumnCount(),
60909 for(i = 0; i < clen; i++){
60910 ws[i] = this.getColumnWidth(i);
60912 this.innerHd.firstChild.style.width = this.getOffsetWidth();
60913 this.innerHd.firstChild.firstChild.style.width = tw;
60914 this.mainBody.dom.style.width = tw;
60915 for(i = 0; i < clen; i++){
60916 var hd = this.getHeaderCell(i);
60917 hd.style.width = ws[i];
60920 var ns = this.getRows(), row, trow;
60921 for(i = 0, len = ns.length; i < len; i++){
60923 row.style.width = tw;
60924 if(row.firstChild){
60925 row.firstChild.style.width = tw;
60926 trow = row.firstChild.rows[0];
60927 for (var j = 0; j < clen; j++) {
60928 trow.childNodes[j].style.width = ws[j];
60933 this.onAllColumnWidthsUpdated(ws, tw);
60937 updateColumnWidth : function(col, width){
60938 var w = this.getColumnWidth(col);
60939 var tw = this.getTotalWidth();
60940 this.innerHd.firstChild.style.width = this.getOffsetWidth();
60941 this.innerHd.firstChild.firstChild.style.width = tw;
60942 this.mainBody.dom.style.width = tw;
60943 var hd = this.getHeaderCell(col);
60944 hd.style.width = w;
60946 var ns = this.getRows(), row;
60947 for(var i = 0, len = ns.length; i < len; i++){
60949 row.style.width = tw;
60950 if(row.firstChild){
60951 row.firstChild.style.width = tw;
60952 row.firstChild.rows[0].childNodes[col].style.width = w;
60956 this.onColumnWidthUpdated(col, w, tw);
60960 updateColumnHidden : function(col, hidden){
60961 var tw = this.getTotalWidth();
60962 this.innerHd.firstChild.style.width = this.getOffsetWidth();
60963 this.innerHd.firstChild.firstChild.style.width = tw;
60964 this.mainBody.dom.style.width = tw;
60965 var display = hidden ? 'none' : '';
60967 var hd = this.getHeaderCell(col);
60968 hd.style.display = display;
60970 var ns = this.getRows(), row;
60971 for(var i = 0, len = ns.length; i < len; i++){
60973 row.style.width = tw;
60974 if(row.firstChild){
60975 row.firstChild.style.width = tw;
60976 row.firstChild.rows[0].childNodes[col].style.display = display;
60980 this.onColumnHiddenUpdated(col, hidden, tw);
60981 delete this.lastViewWidth; // force recalc
60986 doRender : function(cs, rs, ds, startRow, colCount, stripe){
60987 var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1;
60988 var tstyle = 'width:'+this.getTotalWidth()+';';
60990 var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
60991 for(var j = 0, len = rs.length; j < len; j++){
60992 r = rs[j]; cb = [];
60993 var rowIndex = (j+startRow);
60994 for(var i = 0; i < colCount; i++){
60997 p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
60998 p.attr = p.cellAttr = "";
60999 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
61001 if(Ext.isEmpty(p.value)){
61002 p.value = " ";
61004 if(this.markDirty && r.dirty && typeof r.modified[c.name] !== 'undefined'){
61005 p.css += ' x-grid3-dirty-cell';
61007 cb[cb.length] = ct.apply(p);
61010 if(stripe && ((rowIndex+1) % 2 === 0)){
61011 alt[0] = "x-grid3-row-alt";
61014 alt[1] = " x-grid3-dirty-row";
61016 rp.cols = colCount;
61017 if(this.getRowClass){
61018 alt[2] = this.getRowClass(r, rowIndex, rp, ds);
61020 rp.alt = alt.join(" ");
61021 rp.cells = cb.join("");
61022 buf[buf.length] = rt.apply(rp);
61024 return buf.join("");
61028 processRows : function(startRow, skipStripe){
61029 if(!this.ds || this.ds.getCount() < 1){
61032 var rows = this.getRows();
61033 skipStripe = skipStripe || !this.grid.stripeRows;
61034 startRow = startRow || 0;
61035 Ext.each(rows, function(row, idx){
61036 row.rowIndex = idx;
61037 row.className = row.className.replace(this.rowClsRe, ' ');
61038 if (!skipStripe && (idx + 1) % 2 === 0) {
61039 row.className += ' x-grid3-row-alt';
61042 // add first/last-row classes
61043 if(startRow === 0){
61044 Ext.fly(rows[0]).addClass(this.firstRowCls);
61046 Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
61049 afterRender : function(){
61050 if(!this.ds || !this.cm){
61053 this.mainBody.dom.innerHTML = this.renderRows() || ' ';
61054 this.processRows(0, true);
61056 if(this.deferEmptyText !== true){
61057 this.applyEmptyText();
61062 renderUI : function(){
61064 var header = this.renderHeaders();
61065 var body = this.templates.body.apply({rows:' '});
61068 var html = this.templates.master.apply({
61071 ostyle: 'width:'+this.getOffsetWidth()+';',
61072 bstyle: 'width:'+this.getTotalWidth()+';'
61077 g.getGridEl().dom.innerHTML = html;
61079 this.initElements();
61081 // get mousedowns early
61082 Ext.fly(this.innerHd).on("click", this.handleHdDown, this);
61085 mouseover: this.handleHdOver,
61086 mouseout: this.handleHdOut,
61087 mousemove: this.handleHdMove
61090 this.scroller.on('scroll', this.syncScroll, this);
61091 if(g.enableColumnResize !== false){
61092 this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
61095 if(g.enableColumnMove){
61096 this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
61097 this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
61100 if(g.enableHdMenu !== false){
61101 this.hmenu = new Ext.menu.Menu({id: g.id + "-hctx"});
61103 {itemId:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
61104 {itemId:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
61106 if(g.enableColumnHide !== false){
61107 this.colMenu = new Ext.menu.Menu({id:g.id + "-hcols-menu"});
61110 beforeshow: this.beforeColMenuShow,
61111 itemclick: this.handleHdMenuClick
61113 this.hmenu.add('-', {
61115 hideOnClick: false,
61116 text: this.columnsText,
61117 menu: this.colMenu,
61118 iconCls: 'x-cols-icon'
61121 this.hmenu.on("itemclick", this.handleHdMenuClick, this);
61124 if(g.trackMouseOver){
61127 mouseover: this.onRowOver,
61128 mouseout: this.onRowOut
61132 if(g.enableDragDrop || g.enableDrag){
61133 this.dragZone = new Ext.grid.GridDragZone(g, {
61134 ddGroup : g.ddGroup || 'GridDD'
61138 this.updateHeaderSortState();
61143 layout : function(){
61144 if(!this.mainBody){
61145 return; // not rendered
61148 var c = g.getGridEl();
61149 var csize = c.getSize(true);
61150 var vw = csize.width;
61152 if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ // display: none?
61157 this.scroller.dom.style.overflow = 'visible';
61159 this.scroller.dom.style.position = 'static';
61162 this.el.setSize(csize.width, csize.height);
61164 var hdHeight = this.mainHd.getHeight();
61165 var vh = csize.height - (hdHeight);
61167 this.scroller.setSize(vw, vh);
61169 this.innerHd.style.width = (vw)+'px';
61173 if(this.lastViewWidth != vw){
61174 this.fitColumns(false, false);
61175 this.lastViewWidth = vw;
61179 this.syncHeaderScroll();
61181 this.onLayout(vw, vh);
61184 // template functions for subclasses and plugins
61185 // these functions include precalculated values
61186 onLayout : function(vw, vh){
61190 onColumnWidthUpdated : function(col, w, tw){
61194 onAllColumnWidthsUpdated : function(ws, tw){
61198 onColumnHiddenUpdated : function(col, hidden, tw){
61202 updateColumnText : function(col, text){
61206 afterMove : function(colIndex){
61210 /* ----------------------------------- Core Specific -------------------------------------------*/
61212 init : function(grid){
61215 this.initTemplates();
61216 this.initData(grid.store, grid.colModel);
61221 getColumnId : function(index){
61222 return this.cm.getColumnId(index);
61226 getOffsetWidth : function() {
61227 return (this.cm.getTotalWidth() + this.scrollOffset) + 'px';
61231 renderHeaders : function(){
61233 ts = this.templates,
61237 len = cm.getColumnCount(),
61240 for(var i = 0; i < len; i++){
61241 p.id = cm.getColumnId(i);
61242 p.value = cm.getColumnHeader(i) || "";
61243 p.style = this.getColumnStyle(i, true);
61244 p.tooltip = this.getColumnTooltip(i);
61245 p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
61246 if(cm.config[i].align == 'right'){
61247 p.istyle = 'padding-right:16px';
61251 cb[cb.length] = ct.apply(p);
61253 return ts.header.apply({cells: cb.join(""), tstyle:'width:'+this.getTotalWidth()+';'});
61257 getColumnTooltip : function(i){
61258 var tt = this.cm.getColumnTooltip(i);
61260 if(Ext.QuickTips.isEnabled()){
61261 return 'ext:qtip="'+tt+'"';
61263 return 'title="'+tt+'"';
61270 beforeUpdate : function(){
61271 this.grid.stopEditing(true);
61275 updateHeaders : function(){
61276 this.innerHd.firstChild.innerHTML = this.renderHeaders();
61277 this.innerHd.firstChild.style.width = this.getOffsetWidth();
61278 this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
61282 * Focuses the specified row.
61283 * @param {Number} row The row index
61285 focusRow : function(row){
61286 this.focusCell(row, 0, false);
61290 * Focuses the specified cell.
61291 * @param {Number} row The row index
61292 * @param {Number} col The column index
61294 focusCell : function(row, col, hscroll){
61295 this.syncFocusEl(this.ensureVisible(row, col, hscroll));
61297 this.focusEl.focus();
61299 this.focusEl.focus.defer(1, this.focusEl);
61303 resolveCell : function(row, col, hscroll){
61304 if(typeof row != "number"){
61305 row = row.rowIndex;
61310 if(row < 0 || row >= this.ds.getCount()){
61313 col = (col !== undefined ? col : 0);
61315 var rowEl = this.getRow(row),
61317 colCount = cm.getColumnCount(),
61319 if(!(hscroll === false && col === 0)){
61320 while(col < colCount && cm.isHidden(col)){
61323 cellEl = this.getCell(row, col);
61326 return {row: rowEl, cell: cellEl};
61329 getResolvedXY : function(resolved){
61333 var s = this.scroller.dom, c = resolved.cell, r = resolved.row;
61334 return c ? Ext.fly(c).getXY() : [this.el.getX(), Ext.fly(r).getY()];
61337 syncFocusEl : function(row, col, hscroll){
61339 if(!Ext.isArray(xy)){
61340 row = Math.min(row, Math.max(0, this.getRows().length-1));
61341 xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
61343 this.focusEl.setXY(xy||this.scroller.getXY());
61346 ensureVisible : function(row, col, hscroll){
61347 var resolved = this.resolveCell(row, col, hscroll);
61348 if(!resolved || !resolved.row){
61352 var rowEl = resolved.row,
61353 cellEl = resolved.cell,
61354 c = this.scroller.dom,
61357 stop = this.el.dom;
61359 while(p && p != stop){
61360 ctop += p.offsetTop;
61361 p = p.offsetParent;
61363 ctop -= this.mainHd.dom.offsetHeight;
61365 var cbot = ctop + rowEl.offsetHeight,
61366 ch = c.clientHeight,
61369 stop = parseInt(c.scrollTop, 10);
61373 c.scrollTop = ctop;
61374 }else if(cbot > sbot){
61375 c.scrollTop = cbot-ch;
61378 if(hscroll !== false){
61379 var cleft = parseInt(cellEl.offsetLeft, 10);
61380 var cright = cleft + cellEl.offsetWidth;
61382 var sleft = parseInt(c.scrollLeft, 10);
61383 var sright = sleft + c.clientWidth;
61385 c.scrollLeft = cleft;
61386 }else if(cright > sright){
61387 c.scrollLeft = cright-c.clientWidth;
61390 return this.getResolvedXY(resolved);
61394 insertRows : function(dm, firstRow, lastRow, isUpdate){
61395 var last = dm.getCount() - 1;
61396 if(!isUpdate && firstRow === 0 && lastRow >= last){
61400 this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
61402 var html = this.renderRows(firstRow, lastRow),
61403 before = this.getRow(firstRow);
61405 if(firstRow === 0){
61406 Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
61408 Ext.DomHelper.insertHtml('beforeBegin', before, html);
61410 var r = this.getRow(last - 1);
61412 Ext.fly(r).removeClass(this.lastRowCls);
61414 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
61417 this.fireEvent("rowsinserted", this, firstRow, lastRow);
61418 this.processRows(firstRow);
61419 }else if(firstRow === 0 || firstRow >= last){
61420 //ensure first/last row is kept after an update.
61421 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
61424 this.syncFocusEl(firstRow);
61428 deleteRows : function(dm, firstRow, lastRow){
61429 if(dm.getRowCount()<1){
61432 this.fireEvent("beforerowsdeleted", this, firstRow, lastRow);
61434 this.removeRows(firstRow, lastRow);
61436 this.processRows(firstRow);
61437 this.fireEvent("rowsdeleted", this, firstRow, lastRow);
61442 getColumnStyle : function(col, isHeader){
61443 var style = !isHeader ? (this.cm.config[col].css || '') : '';
61444 style += 'width:'+this.getColumnWidth(col)+';';
61445 if(this.cm.isHidden(col)){
61446 style += 'display:none;';
61448 var align = this.cm.config[col].align;
61450 style += 'text-align:'+align+';';
61456 getColumnWidth : function(col){
61457 var w = this.cm.getColumnWidth(col);
61458 if(typeof w == 'number'){
61459 return (Ext.isBorderBox ? w : (w-this.borderWidth > 0 ? w-this.borderWidth:0)) + 'px';
61465 getTotalWidth : function(){
61466 return this.cm.getTotalWidth()+'px';
61470 fitColumns : function(preventRefresh, onlyExpand, omitColumn){
61471 var cm = this.cm, i;
61472 var tw = cm.getTotalWidth(false);
61473 var aw = this.grid.getGridEl().getWidth(true)-this.scrollOffset;
61475 if(aw < 20){ // not initialized, so don't screw up the default widths
61478 var extra = aw - tw;
61484 var vc = cm.getColumnCount(true);
61485 var ac = vc-(typeof omitColumn == 'number' ? 1 : 0);
61488 omitColumn = undefined;
61490 var colCount = cm.getColumnCount();
61495 for (i = 0; i < colCount; i++){
61496 if(!cm.isHidden(i) && !cm.isFixed(i) && i !== omitColumn){
61497 w = cm.getColumnWidth(i);
61504 var frac = (aw - cm.getTotalWidth())/width;
61505 while (cols.length){
61508 cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w + w*frac)), true);
61511 if((tw = cm.getTotalWidth(false)) > aw){
61512 var adjustCol = ac != vc ? omitColumn : extraCol;
61513 cm.setColumnWidth(adjustCol, Math.max(1,
61514 cm.getColumnWidth(adjustCol)- (tw-aw)), true);
61517 if(preventRefresh !== true){
61518 this.updateAllColumnWidths();
61526 autoExpand : function(preventUpdate){
61527 var g = this.grid, cm = this.cm;
61528 if(!this.userResized && g.autoExpandColumn){
61529 var tw = cm.getTotalWidth(false);
61530 var aw = this.grid.getGridEl().getWidth(true)-this.scrollOffset;
61532 var ci = cm.getIndexById(g.autoExpandColumn);
61533 var currentWidth = cm.getColumnWidth(ci);
61534 var cw = Math.min(Math.max(((aw-tw)+currentWidth), g.autoExpandMin), g.autoExpandMax);
61535 if(cw != currentWidth){
61536 cm.setColumnWidth(ci, cw, true);
61537 if(preventUpdate !== true){
61538 this.updateColumnWidth(ci, cw);
61546 getColumnData : function(){
61547 // build a map for all the columns
61548 var cs = [], cm = this.cm, colCount = cm.getColumnCount();
61549 for(var i = 0; i < colCount; i++){
61550 var name = cm.getDataIndex(i);
61552 name : (typeof name == 'undefined' ? this.ds.fields.get(i).name : name),
61553 renderer : cm.getRenderer(i),
61554 id : cm.getColumnId(i),
61555 style : this.getColumnStyle(i)
61562 renderRows : function(startRow, endRow){
61563 // pull in all the crap needed to render rows
61564 var g = this.grid, cm = g.colModel, ds = g.store, stripe = g.stripeRows;
61565 var colCount = cm.getColumnCount();
61567 if(ds.getCount() < 1){
61571 var cs = this.getColumnData();
61573 startRow = startRow || 0;
61574 endRow = typeof endRow == "undefined"? ds.getCount()-1 : endRow;
61576 // records to render
61577 var rs = ds.getRange(startRow, endRow);
61579 return this.doRender(cs, rs, ds, startRow, colCount, stripe);
61583 renderBody : function(){
61584 var markup = this.renderRows() || ' ';
61585 return this.templates.body.apply({rows: markup});
61589 refreshRow : function(record){
61590 var ds = this.ds, index;
61591 if(typeof record == 'number'){
61593 record = ds.getAt(index);
61598 index = ds.indexOf(record);
61603 this.insertRows(ds, index, index, true);
61604 this.getRow(index).rowIndex = index;
61605 this.onRemove(ds, record, index+1, true);
61606 this.fireEvent("rowupdated", this, index, record);
61610 * Refreshs the grid UI
61611 * @param {Boolean} headersToo (optional) True to also refresh the headers
61613 refresh : function(headersToo){
61614 this.fireEvent("beforerefresh", this);
61615 this.grid.stopEditing(true);
61617 var result = this.renderBody();
61618 this.mainBody.update(result).setWidth(this.getTotalWidth());
61619 if(headersToo === true){
61620 this.updateHeaders();
61621 this.updateHeaderSortState();
61623 this.processRows(0, true);
61625 this.applyEmptyText();
61626 this.fireEvent("refresh", this);
61630 applyEmptyText : function(){
61631 if(this.emptyText && !this.hasRows()){
61632 this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
61637 updateHeaderSortState : function(){
61638 var state = this.ds.getSortState();
61642 if(!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)){
61643 this.grid.fireEvent('sortchange', this.grid, state);
61645 this.sortState = state;
61646 var sortColumn = this.cm.findColumnIndex(state.field);
61647 if(sortColumn != -1){
61648 var sortDir = state.direction;
61649 this.updateSortIcon(sortColumn, sortDir);
61654 destroy : function(){
61656 Ext.menu.MenuMgr.unregister(this.colMenu);
61657 this.colMenu.destroy();
61658 delete this.colMenu;
61661 Ext.menu.MenuMgr.unregister(this.hmenu);
61662 this.hmenu.destroy();
61665 if(this.grid.enableColumnMove){
61666 var dds = Ext.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
61668 for(var dd in dds){
61669 if(!dds[dd].config.isTarget && dds[dd].dragElId){
61670 var elid = dds[dd].dragElId;
61672 Ext.get(elid).remove();
61673 } else if(dds[dd].config.isTarget){
61674 dds[dd].proxyTop.remove();
61675 dds[dd].proxyBottom.remove();
61678 if(Ext.dd.DDM.locationCache[dd]){
61679 delete Ext.dd.DDM.locationCache[dd];
61682 delete Ext.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
61687 this.dragZone.unreg();
61690 Ext.fly(this.innerHd).removeAllListeners();
61691 Ext.removeNode(this.innerHd);
61693 Ext.destroy(this.resizeMarker, this.resizeProxy, this.focusEl, this.mainBody,
61694 this.scroller, this.mainHd, this.mainWrap, this.dragZone,
61695 this.splitZone, this.columnDrag, this.columnDrop);
61697 this.initData(null, null);
61698 Ext.EventManager.removeResizeListener(this.onWindowResize, this);
61699 this.purgeListeners();
61703 onDenyColumnHide : function(){
61708 render : function(){
61710 var ct = this.grid.ownerCt;
61711 if (ct && ct.getLayout()){
61712 ct.on('afterlayout', function(){
61713 this.fitColumns(true, true);
61714 this.updateHeaders();
61715 }, this, {single: true});
61717 this.fitColumns(true, true);
61719 }else if(this.forceFit){
61720 this.fitColumns(true, false);
61721 }else if(this.grid.autoExpandColumn){
61722 this.autoExpand(true);
61728 /* --------------------------------- Model Events and Handlers --------------------------------*/
61730 initData : function(ds, cm){
61732 this.ds.un("load", this.onLoad, this);
61733 this.ds.un("datachanged", this.onDataChange, this);
61734 this.ds.un("add", this.onAdd, this);
61735 this.ds.un("remove", this.onRemove, this);
61736 this.ds.un("update", this.onUpdate, this);
61737 this.ds.un("clear", this.onClear, this);
61738 if(this.ds !== ds && this.ds.autoDestroy){
61746 datachanged: this.onDataChange,
61748 remove: this.onRemove,
61749 update: this.onUpdate,
61750 clear: this.onClear
61756 this.cm.un("configchange", this.onColConfigChange, this);
61757 this.cm.un("widthchange", this.onColWidthChange, this);
61758 this.cm.un("headerchange", this.onHeaderChange, this);
61759 this.cm.un("hiddenchange", this.onHiddenChange, this);
61760 this.cm.un("columnmoved", this.onColumnMove, this);
61763 delete this.lastViewWidth;
61766 configchange: this.onColConfigChange,
61767 widthchange: this.onColWidthChange,
61768 headerchange: this.onHeaderChange,
61769 hiddenchange: this.onHiddenChange,
61770 columnmoved: this.onColumnMove
61777 onDataChange : function(){
61779 this.updateHeaderSortState();
61780 this.syncFocusEl(0);
61784 onClear : function(){
61786 this.syncFocusEl(0);
61790 onUpdate : function(ds, record){
61791 this.refreshRow(record);
61795 onAdd : function(ds, records, index){
61796 this.insertRows(ds, index, index + (records.length-1));
61800 onRemove : function(ds, record, index, isUpdate){
61801 if(isUpdate !== true){
61802 this.fireEvent("beforerowremoved", this, index, record);
61804 this.removeRow(index);
61805 if(isUpdate !== true){
61806 this.processRows(index);
61807 this.applyEmptyText();
61808 this.fireEvent("rowremoved", this, index, record);
61813 onLoad : function(){
61814 this.scrollToTop();
61818 onColWidthChange : function(cm, col, width){
61819 this.updateColumnWidth(col, width);
61823 onHeaderChange : function(cm, col, text){
61824 this.updateHeaders();
61828 onHiddenChange : function(cm, col, hidden){
61829 this.updateColumnHidden(col, hidden);
61833 onColumnMove : function(cm, oldIndex, newIndex){
61834 this.indexMap = null;
61835 var s = this.getScrollState();
61836 this.refresh(true);
61837 this.restoreScroll(s);
61838 this.afterMove(newIndex);
61839 this.grid.fireEvent('columnmove', oldIndex, newIndex);
61843 onColConfigChange : function(){
61844 delete this.lastViewWidth;
61845 this.indexMap = null;
61846 this.refresh(true);
61849 /* -------------------- UI Events and Handlers ------------------------------ */
61851 initUI : function(grid){
61852 grid.on("headerclick", this.onHeaderClick, this);
61856 initEvents : function(){
61860 onHeaderClick : function(g, index){
61861 if(this.headersDisabled || !this.cm.isSortable(index)){
61864 g.stopEditing(true);
61865 g.store.sort(this.cm.getDataIndex(index));
61869 onRowOver : function(e, t){
61871 if((row = this.findRowIndex(t)) !== false){
61872 this.addRowClass(row, "x-grid3-row-over");
61877 onRowOut : function(e, t){
61879 if((row = this.findRowIndex(t)) !== false && !e.within(this.getRow(row), true)){
61880 this.removeRowClass(row, "x-grid3-row-over");
61885 handleWheel : function(e){
61886 e.stopPropagation();
61890 onRowSelect : function(row){
61891 this.addRowClass(row, this.selectedRowClass);
61895 onRowDeselect : function(row){
61896 this.removeRowClass(row, this.selectedRowClass);
61900 onCellSelect : function(row, col){
61901 var cell = this.getCell(row, col);
61903 this.fly(cell).addClass("x-grid3-cell-selected");
61908 onCellDeselect : function(row, col){
61909 var cell = this.getCell(row, col);
61911 this.fly(cell).removeClass("x-grid3-cell-selected");
61916 onColumnSplitterMoved : function(i, w){
61917 this.userResized = true;
61918 var cm = this.grid.colModel;
61919 cm.setColumnWidth(i, w, true);
61922 this.fitColumns(true, false, i);
61923 this.updateAllColumnWidths();
61925 this.updateColumnWidth(i, w);
61926 this.syncHeaderScroll();
61929 this.grid.fireEvent("columnresize", i, w);
61933 handleHdMenuClick : function(item){
61934 var index = this.hdCtxIndex;
61935 var cm = this.cm, ds = this.ds;
61936 switch(item.itemId){
61938 ds.sort(cm.getDataIndex(index), "ASC");
61941 ds.sort(cm.getDataIndex(index), "DESC");
61944 index = cm.getIndexById(item.itemId.substr(4));
61946 if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
61947 this.onDenyColumnHide();
61950 cm.setHidden(index, item.checked);
61957 isHideableColumn : function(c){
61958 return !c.hidden && !c.fixed;
61962 beforeColMenuShow : function(){
61963 var cm = this.cm, colCount = cm.getColumnCount();
61964 this.colMenu.removeAll();
61965 for(var i = 0; i < colCount; i++){
61966 if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
61967 this.colMenu.add(new Ext.menu.CheckItem({
61968 itemId: "col-"+cm.getColumnId(i),
61969 text: cm.getColumnHeader(i),
61970 checked: !cm.isHidden(i),
61972 disabled: cm.config[i].hideable === false
61979 handleHdDown : function(e, t){
61980 if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
61982 var hd = this.findHeaderCell(t);
61983 Ext.fly(hd).addClass('x-grid3-hd-menu-open');
61984 var index = this.getCellIndex(hd);
61985 this.hdCtxIndex = index;
61986 var ms = this.hmenu.items, cm = this.cm;
61987 ms.get("asc").setDisabled(!cm.isSortable(index));
61988 ms.get("desc").setDisabled(!cm.isSortable(index));
61989 this.hmenu.on("hide", function(){
61990 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
61991 }, this, {single:true});
61992 this.hmenu.show(t, "tl-bl?");
61997 handleHdOver : function(e, t){
61998 var hd = this.findHeaderCell(t);
61999 if(hd && !this.headersDisabled){
62000 this.activeHd = hd;
62001 this.activeHdIndex = this.getCellIndex(hd);
62002 var fly = this.fly(hd);
62003 this.activeHdRegion = fly.getRegion();
62004 if(!this.cm.isMenuDisabled(this.activeHdIndex)){
62005 fly.addClass("x-grid3-hd-over");
62006 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
62007 if(this.activeHdBtn){
62008 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
62015 handleHdMove : function(e, t){
62016 if(this.activeHd && !this.headersDisabled){
62017 var hw = this.splitHandleWidth || 5;
62018 var r = this.activeHdRegion;
62019 var x = e.getPageX();
62020 var ss = this.activeHd.style;
62021 if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex-1)){
62022 ss.cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
62023 }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
62024 ss.cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
62032 handleHdOut : function(e, t){
62033 var hd = this.findHeaderCell(t);
62034 if(hd && (!Ext.isIE || !e.within(hd, true))){
62035 this.activeHd = null;
62036 this.fly(hd).removeClass("x-grid3-hd-over");
62037 hd.style.cursor = '';
62042 hasRows : function(){
62043 var fc = this.mainBody.dom.firstChild;
62044 return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
62048 bind : function(d, c){
62049 this.initData(d, c);
62055 // This is a support class used internally by the Grid components
62056 Ext.grid.GridView.SplitDragZone = function(grid, hd){
62058 this.view = grid.getView();
62059 this.marker = this.view.resizeMarker;
62060 this.proxy = this.view.resizeProxy;
62061 Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
62062 "gridSplitters" + this.grid.getGridEl().id, {
62063 dragElId : Ext.id(this.proxy.dom), resizeFrame:false
62065 this.scroll = false;
62066 this.hw = this.view.splitHandleWidth || 5;
62068 Ext.extend(Ext.grid.GridView.SplitDragZone, Ext.dd.DDProxy, {
62070 b4StartDrag : function(x, y){
62071 this.view.headersDisabled = true;
62072 var h = this.view.mainWrap.getHeight();
62073 this.marker.setHeight(h);
62074 this.marker.show();
62075 this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
62076 this.proxy.setHeight(h);
62077 var w = this.cm.getColumnWidth(this.cellIndex);
62078 var minw = Math.max(w-this.grid.minColumnWidth, 0);
62079 this.resetConstraints();
62080 this.setXConstraint(minw, 1000);
62081 this.setYConstraint(0, 0);
62082 this.minX = x - minw;
62083 this.maxX = x + 1000;
62085 Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
62089 handleMouseDown : function(e){
62090 var t = this.view.findHeaderCell(e.getTarget());
62092 var xy = this.view.fly(t).getXY(), x = xy[0], y = xy[1];
62093 var exy = e.getXY(), ex = exy[0];
62094 var w = t.offsetWidth, adjust = false;
62095 if((ex - x) <= this.hw){
62097 }else if((x+w) - ex <= this.hw){
62100 if(adjust !== false){
62101 this.cm = this.grid.colModel;
62102 var ci = this.view.getCellIndex(t);
62104 if (ci + adjust < 0) {
62107 while(this.cm.isHidden(ci+adjust)){
62114 this.cellIndex = ci+adjust;
62115 this.split = t.dom;
62116 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
62117 Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
62119 }else if(this.view.columnDrag){
62120 this.view.columnDrag.callHandleMouseDown(e);
62125 endDrag : function(e){
62126 this.marker.hide();
62128 var endX = Math.max(this.minX, e.getPageX());
62129 var diff = endX - this.startPos;
62130 v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
62131 setTimeout(function(){
62132 v.headersDisabled = false;
62136 autoOffset : function(){
62137 this.setDelta(0,0);
62141 // This is a support class used internally by the Grid components
\r
62142 Ext.grid.HeaderDragZone = function(grid, hd, hd2){
\r
62143 this.grid = grid;
\r
62144 this.view = grid.getView();
\r
62145 this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
\r
62146 Ext.grid.HeaderDragZone.superclass.constructor.call(this, hd);
\r
62148 this.setHandleElId(Ext.id(hd));
\r
62149 this.setOuterHandleElId(Ext.id(hd2));
\r
62151 this.scroll = false;
\r
62153 Ext.extend(Ext.grid.HeaderDragZone, Ext.dd.DragZone, {
\r
62154 maxDragWidth: 120,
\r
62155 getDragData : function(e){
\r
62156 var t = Ext.lib.Event.getTarget(e);
\r
62157 var h = this.view.findHeaderCell(t);
\r
62159 return {ddel: h.firstChild, header:h};
\r
62164 onInitDrag : function(e){
\r
62165 this.view.headersDisabled = true;
\r
62166 var clone = this.dragData.ddel.cloneNode(true);
\r
62167 clone.id = Ext.id();
\r
62168 clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
\r
62169 this.proxy.update(clone);
\r
62173 afterValidDrop : function(){
\r
62174 var v = this.view;
\r
62175 setTimeout(function(){
\r
62176 v.headersDisabled = false;
\r
62180 afterInvalidDrop : function(){
\r
62181 var v = this.view;
\r
62182 setTimeout(function(){
\r
62183 v.headersDisabled = false;
\r
62189 // This is a support class used internally by the Grid components
\r
62190 Ext.grid.HeaderDropZone = function(grid, hd, hd2){
\r
62191 this.grid = grid;
\r
62192 this.view = grid.getView();
\r
62193 // split the proxies so they don't interfere with mouse events
\r
62194 this.proxyTop = Ext.DomHelper.append(document.body, {
\r
62195 cls:"col-move-top", html:" "
\r
62197 this.proxyBottom = Ext.DomHelper.append(document.body, {
\r
62198 cls:"col-move-bottom", html:" "
\r
62200 this.proxyTop.hide = this.proxyBottom.hide = function(){
\r
62201 this.setLeftTop(-100,-100);
\r
62202 this.setStyle("visibility", "hidden");
\r
62204 this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
\r
62205 // temporarily disabled
\r
62206 //Ext.dd.ScrollManager.register(this.view.scroller.dom);
\r
62207 Ext.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
\r
62209 Ext.extend(Ext.grid.HeaderDropZone, Ext.dd.DropZone, {
\r
62210 proxyOffsets : [-4, -9],
\r
62211 fly: Ext.Element.fly,
\r
62213 getTargetFromEvent : function(e){
\r
62214 var t = Ext.lib.Event.getTarget(e);
\r
62215 var cindex = this.view.findCellIndex(t);
\r
62216 if(cindex !== false){
\r
62217 return this.view.getHeaderCell(cindex);
\r
62221 nextVisible : function(h){
\r
62222 var v = this.view, cm = this.grid.colModel;
\r
62223 h = h.nextSibling;
\r
62225 if(!cm.isHidden(v.getCellIndex(h))){
\r
62228 h = h.nextSibling;
\r
62233 prevVisible : function(h){
\r
62234 var v = this.view, cm = this.grid.colModel;
\r
62235 h = h.prevSibling;
\r
62237 if(!cm.isHidden(v.getCellIndex(h))){
\r
62240 h = h.prevSibling;
\r
62245 positionIndicator : function(h, n, e){
\r
62246 var x = Ext.lib.Event.getPageX(e);
\r
62247 var r = Ext.lib.Dom.getRegion(n.firstChild);
\r
62248 var px, pt, py = r.top + this.proxyOffsets[1];
\r
62249 if((r.right - x) <= (r.right-r.left)/2){
\r
62250 px = r.right+this.view.borderWidth;
\r
62257 if(this.grid.colModel.isFixed(this.view.getCellIndex(n))){
\r
62261 px += this.proxyOffsets[0];
\r
62262 this.proxyTop.setLeftTop(px, py);
\r
62263 this.proxyTop.show();
\r
62264 if(!this.bottomOffset){
\r
62265 this.bottomOffset = this.view.mainHd.getHeight();
\r
62267 this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
\r
62268 this.proxyBottom.show();
\r
62272 onNodeEnter : function(n, dd, e, data){
\r
62273 if(data.header != n){
\r
62274 this.positionIndicator(data.header, n, e);
\r
62278 onNodeOver : function(n, dd, e, data){
\r
62279 var result = false;
\r
62280 if(data.header != n){
\r
62281 result = this.positionIndicator(data.header, n, e);
\r
62284 this.proxyTop.hide();
\r
62285 this.proxyBottom.hide();
\r
62287 return result ? this.dropAllowed : this.dropNotAllowed;
\r
62290 onNodeOut : function(n, dd, e, data){
\r
62291 this.proxyTop.hide();
\r
62292 this.proxyBottom.hide();
\r
62295 onNodeDrop : function(n, dd, e, data){
\r
62296 var h = data.header;
\r
62298 var cm = this.grid.colModel;
\r
62299 var x = Ext.lib.Event.getPageX(e);
\r
62300 var r = Ext.lib.Dom.getRegion(n.firstChild);
\r
62301 var pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
\r
62302 var oldIndex = this.view.getCellIndex(h);
\r
62303 var newIndex = this.view.getCellIndex(n);
\r
62304 if(pt == "after"){
\r
62307 if(oldIndex < newIndex){
\r
62310 cm.moveColumn(oldIndex, newIndex);
\r
62311 this.grid.fireEvent("columnmove", oldIndex, newIndex);
\r
62319 Ext.grid.GridView.ColumnDragZone = function(grid, hd){
\r
62320 Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
\r
62321 this.proxy.el.addClass('x-grid3-col-dd');
\r
62324 Ext.extend(Ext.grid.GridView.ColumnDragZone, Ext.grid.HeaderDragZone, {
\r
62325 handleMouseDown : function(e){
\r
62329 callHandleMouseDown : function(e){
\r
62330 Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
\r
62333 // This is a support class used internally by the Grid components
62334 Ext.grid.SplitDragZone = function(grid, hd, hd2){
62336 this.view = grid.getView();
62337 this.proxy = this.view.resizeProxy;
62338 Ext.grid.SplitDragZone.superclass.constructor.call(this, hd,
62339 "gridSplitters" + this.grid.getGridEl().id, {
62340 dragElId : Ext.id(this.proxy.dom), resizeFrame:false
62342 this.setHandleElId(Ext.id(hd));
62343 this.setOuterHandleElId(Ext.id(hd2));
62344 this.scroll = false;
62346 Ext.extend(Ext.grid.SplitDragZone, Ext.dd.DDProxy, {
62347 fly: Ext.Element.fly,
62349 b4StartDrag : function(x, y){
62350 this.view.headersDisabled = true;
62351 this.proxy.setHeight(this.view.mainWrap.getHeight());
62352 var w = this.cm.getColumnWidth(this.cellIndex);
62353 var minw = Math.max(w-this.grid.minColumnWidth, 0);
62354 this.resetConstraints();
62355 this.setXConstraint(minw, 1000);
62356 this.setYConstraint(0, 0);
62357 this.minX = x - minw;
62358 this.maxX = x + 1000;
62360 Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
62364 handleMouseDown : function(e){
62365 var ev = Ext.EventObject.setEvent(e);
62366 var t = this.fly(ev.getTarget());
62367 if(t.hasClass("x-grid-split")){
62368 this.cellIndex = this.view.getCellIndex(t.dom);
62369 this.split = t.dom;
62370 this.cm = this.grid.colModel;
62371 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
62372 Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
62377 endDrag : function(e){
62378 this.view.headersDisabled = false;
62379 var endX = Math.max(this.minX, Ext.lib.Event.getPageX(e));
62380 var diff = endX - this.startPos;
62381 this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
62384 autoOffset : function(){
62385 this.setDelta(0,0);
62388 * @class Ext.grid.GridDragZone
62389 * @extends Ext.dd.DragZone
62390 * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations of two of the
62391 * template methods of DragZone to enable dragging of the selected rows of a GridPanel.</p>
62392 * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's template method implementations of
62393 * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
62394 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop}</p> are able
62395 * to process the {@link #getDragData data} which is provided.
62397 Ext.grid.GridDragZone = function(grid, config){
62398 this.view = grid.getView();
62399 Ext.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
62400 this.scroll = false;
62402 this.ddel = document.createElement('div');
62403 this.ddel.className = 'x-grid-dd-wrap';
62406 Ext.extend(Ext.grid.GridDragZone, Ext.dd.DragZone, {
62407 ddGroup : "GridDD",
62410 * <p>The provided implementation of the getDragData method which collects the data to be dragged from the GridPanel on mousedown.</p>
62411 * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
62412 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
62413 * <p>The data object contains the following properties:<ul>
62414 * <li><b>grid</b> : Ext.Grid.GridPanel<div class="sub-desc">The GridPanel from which the data is being dragged.</div></li>
62415 * <li><b>ddel</b> : htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
62416 * <li><b>rowIndex</b> : Number<div class="sub-desc">The index of the row which receieved the mousedown gesture which triggered the drag.</div></li>
62417 * <li><b>selections</b> : Array<div class="sub-desc">An Array of the selected Records which are being dragged from the GridPanel.</div></li>
62420 getDragData : function(e){
62421 var t = Ext.lib.Event.getTarget(e);
62422 var rowIndex = this.view.findRowIndex(t);
62423 if(rowIndex !== false){
62424 var sm = this.grid.selModel;
62425 if(!sm.isSelected(rowIndex) || e.hasModifier()){
62426 sm.handleMouseDown(this.grid, rowIndex, e);
62428 return {grid: this.grid, ddel: this.ddel, rowIndex: rowIndex, selections:sm.getSelections()};
62434 * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
62435 * of the data being dragged.</p>
62436 * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
62438 onInitDrag : function(e){
62439 var data = this.dragData;
62440 this.ddel.innerHTML = this.grid.getDragDropText();
62441 this.proxy.update(this.ddel);
62442 // fire start drag?
62446 * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
62447 * the selected rows to show that they have not been dragged.
62449 afterRepair : function(){
62450 this.dragging = false;
62454 * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
62455 * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
62456 * @param {EventObject} e The mouse up event
62457 * @return {Array} The xy location (e.g. [100, 200])
62459 getRepairXY : function(e, data){
62463 onEndDrag : function(data, e){
62467 onValidDrop : function(dd, e, id){
62472 beforeInvalidDrop : function(e, id){
62477 * @class Ext.grid.ColumnModel
62478 * @extends Ext.util.Observable
62479 * <p>After the data has been read into the client side cache (<b>{@link Ext.data.Store Store}</b>),
62480 * the ColumnModel is used to configure how and what parts of that data will be displayed in the
62481 * vertical slices (columns) of the grid. The Ext.grid.ColumnModel Class is the default implementation
62482 * of a ColumnModel used by implentations of {@link Ext.grid.GridPanel GridPanel}.</p>
62483 * <p>Data is mapped into the store's records and then indexed into the ColumnModel using the
62484 * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt>:</p>
62486 {data source} == mapping ==> {data store} == <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> ==> {ColumnModel}
62488 * <p>Each {@link Ext.grid.Column Column} in the grid's ColumnModel is configured with a
62489 * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt> to specify how the data within
62490 * each record in the store is indexed into the ColumnModel.</p>
62491 * <p>There are two ways to initialize the ColumnModel class:</p>
62492 * <p><u>Initialization Method 1: an Array</u></p>
62494 var colModel = new Ext.grid.ColumnModel([
62495 { header: "Ticker", width: 60, sortable: true},
62496 { header: "Company Name", width: 150, sortable: true, id: 'company'},
62497 { header: "Market Cap.", width: 100, sortable: true},
62498 { header: "$ Sales", width: 100, sortable: true, renderer: money},
62499 { header: "Employees", width: 100, sortable: true, resizable: false}
62502 * <p>The ColumnModel may be initialized with an Array of {@link Ext.grid.Column} column configuration
62503 * objects to define the initial layout / display of the columns in the Grid. The order of each
62504 * {@link Ext.grid.Column} column configuration object within the specified Array defines the initial
62505 * order of the column display. A Column's display may be initially hidden using the
62506 * <tt>{@link Ext.grid.Column#hidden hidden}</tt></b> config property (and then shown using the column
62507 * header menu). Field's that are not included in the ColumnModel will not be displayable at all.</p>
62508 * <p>How each column in the grid correlates (maps) to the {@link Ext.data.Record} field in the
62509 * {@link Ext.data.Store Store} the column draws its data from is configured through the
62510 * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b>. If the
62511 * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> is not explicitly defined (as shown in the
62512 * example above) it will use the column configuration's index in the Array as the index.</p>
62513 * <p>See <b><tt>{@link Ext.grid.Column}</tt></b> for additional configuration options for each column.</p>
62514 * <p><u>Initialization Method 2: an Object</u></p>
62515 * <p>In order to use configuration options from <tt>Ext.grid.ColumnModel</tt>, an Object may be used to
62516 * initialize the ColumnModel. The column configuration Array will be specified in the <tt><b>{@link #columns}</b></tt>
62517 * config property. The <tt><b>{@link #defaults}</b></tt> config property can be used to apply defaults
62518 * for all columns, e.g.:</p><pre><code>
62519 var colModel = new Ext.grid.ColumnModel({
62521 { header: "Ticker", width: 60, menuDisabled: false},
62522 { header: "Company Name", width: 150, id: 'company'},
62523 { header: "Market Cap."},
62524 { header: "$ Sales", renderer: money},
62525 { header: "Employees", resizable: false}
62529 menuDisabled: true,
62533 {@link #hiddenchange}: function(cm, colIndex, hidden) {
62534 saveConfig(colIndex, hidden);
62539 * <p>In both examples above, the ability to apply a CSS class to all cells in a column (including the
62540 * header) is demonstrated through the use of the <b><tt>{@link Ext.grid.Column#id id}</tt></b> config
62541 * option. This column could be styled by including the following css:</p><pre><code>
62542 //add this css *after* the core css is loaded
62543 .x-grid3-td-company {
62544 color: red; // entire column will have red font
62546 // modify the header row only, adding an icon to the column header
62547 .x-grid3-hd-company {
62548 background: transparent
62549 url(../../resources/images/icons/silk/building.png)
62550 no-repeat 3px 3px ! important;
62554 * Note that the "Company Name" column could be specified as the
62555 * <b><tt>{@link Ext.grid.GridPanel}.{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></b>.
62557 * @param {Mixed} config Specify either an Array of {@link Ext.grid.Column} configuration objects or specify
62558 * a configuration Object (see introductory section discussion utilizing Initialization Method 2 above).
62560 Ext.grid.ColumnModel = function(config){
62562 * An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
62563 * of this ColumnModel. See {@link Ext.grid.Column} for the configuration properties that may
62568 if(config.columns){
62569 Ext.apply(this, config);
62570 this.setConfig(config.columns, true);
62572 this.setConfig(config, true);
62576 * @event widthchange
62577 * Fires when the width of a column is programmaticially changed using
62578 * <code>{@link #setColumnWidth}</code>.
62579 * Note internal resizing suppresses the event from firing. See also
62580 * {@link Ext.grid.GridPanel}.<code>{@link #columnresize}</code>.
62581 * @param {ColumnModel} this
62582 * @param {Number} columnIndex The column index
62583 * @param {Number} newWidth The new width
62587 * @event headerchange
62588 * Fires when the text of a header changes.
62589 * @param {ColumnModel} this
62590 * @param {Number} columnIndex The column index
62591 * @param {String} newText The new header text
62595 * @event hiddenchange
62596 * Fires when a column is hidden or "unhidden".
62597 * @param {ColumnModel} this
62598 * @param {Number} columnIndex The column index
62599 * @param {Boolean} hidden true if hidden, false otherwise
62603 * @event columnmoved
62604 * Fires when a column is moved.
62605 * @param {ColumnModel} this
62606 * @param {Number} oldIndex
62607 * @param {Number} newIndex
62611 * @event configchange
62612 * Fires when the configuration is changed
62613 * @param {ColumnModel} this
62617 Ext.grid.ColumnModel.superclass.constructor.call(this);
62619 Ext.extend(Ext.grid.ColumnModel, Ext.util.Observable, {
62621 * @cfg {Number} defaultWidth (optional) The width of columns which have no <tt>{@link #width}</tt>
62622 * specified (defaults to <tt>100</tt>). This property shall preferably be configured through the
62623 * <tt><b>{@link #defaults}</b></tt> config property.
62627 * @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
62628 * sortable specified (defaults to <tt>false</tt>). This property shall preferably be configured
62629 * through the <tt><b>{@link #defaults}</b></tt> config property.
62631 defaultSortable: false,
62633 * @cfg {Array} columns An Array of object literals. The config options defined by
62634 * <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
62635 * individual column definition.
62638 * @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
62639 * configuration options to all <tt><b>{@link #columns}</b></tt>. Configuration options specified with
62640 * individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
62644 * Returns the id of the column at the specified index.
62645 * @param {Number} index The column index
62646 * @return {String} the id
62648 getColumnId : function(index){
62649 return this.config[index].id;
62652 getColumnAt : function(index){
62653 return this.config[index];
62657 * <p>Reconfigures this column model according to the passed Array of column definition objects.
62658 * For a description of the individual properties of a column definition object, see the
62659 * <a href="#Ext.grid.ColumnModel-configs">Config Options</a>.</p>
62660 * <p>Causes the {@link #configchange} event to be fired. A {@link Ext.grid.GridPanel GridPanel}
62661 * using this ColumnModel will listen for this event and refresh its UI automatically.</p>
62662 * @param {Array} config Array of Column definition objects.
62663 * @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
62664 * and destroys existing editors.
62666 setConfig : function(config, initial){
62668 if(!initial){ // cleanup
62669 delete this.totalWidth;
62670 for(i = 0, len = this.config.length; i < len; i++){
62671 c = this.config[i];
62673 c.editor.destroy();
62678 // backward compatibility
62679 this.defaults = Ext.apply({
62680 width: this.defaultWidth,
62681 sortable: this.defaultSortable
62684 this.config = config;
62686 // if no id, create one
62687 for(i = 0, len = config.length; i < len; i++){
62688 c = Ext.applyIf(config[i], this.defaults);
62690 var cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
62694 this.lookup[c.id] = c;
62697 this.fireEvent('configchange', this);
62702 * Returns the column for a specified id.
62703 * @param {String} id The column id
62704 * @return {Object} the column
62706 getColumnById : function(id){
62707 return this.lookup[id];
62711 * Returns the index for a specified column id.
62712 * @param {String} id The column id
62713 * @return {Number} the index, or -1 if not found
62715 getIndexById : function(id){
62716 for(var i = 0, len = this.config.length; i < len; i++){
62717 if(this.config[i].id == id){
62725 * Moves a column from one position to another.
62726 * @param {Number} oldIndex The index of the column to move.
62727 * @param {Number} newIndex The position at which to reinsert the coolumn.
62729 moveColumn : function(oldIndex, newIndex){
62730 var c = this.config[oldIndex];
62731 this.config.splice(oldIndex, 1);
62732 this.config.splice(newIndex, 0, c);
62733 this.dataMap = null;
62734 this.fireEvent("columnmoved", this, oldIndex, newIndex);
62738 * Returns the number of columns.
62739 * @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
62742 getColumnCount : function(visibleOnly){
62743 if(visibleOnly === true){
62745 for(var i = 0, len = this.config.length; i < len; i++){
62746 if(!this.isHidden(i)){
62752 return this.config.length;
62756 * Returns the column configs that return true by the passed function that is called
62757 * with (columnConfig, index)
62759 // returns an array of column config objects for all hidden columns
62760 var columns = grid.getColumnModel().getColumnsBy(function(c){
62764 * @param {Function} fn
62765 * @param {Object} scope (optional)
62766 * @return {Array} result
62768 getColumnsBy : function(fn, scope){
62770 for(var i = 0, len = this.config.length; i < len; i++){
62771 var c = this.config[i];
62772 if(fn.call(scope||this, c, i) === true){
62780 * Returns true if the specified column is sortable.
62781 * @param {Number} col The column index
62782 * @return {Boolean}
62784 isSortable : function(col){
62785 return this.config[col].sortable;
62789 * Returns true if the specified column menu is disabled.
62790 * @param {Number} col The column index
62791 * @return {Boolean}
62793 isMenuDisabled : function(col){
62794 return !!this.config[col].menuDisabled;
62798 * Returns the rendering (formatting) function defined for the column.
62799 * @param {Number} col The column index.
62800 * @return {Function} The function used to render the cell. See {@link #setRenderer}.
62802 getRenderer : function(col){
62803 if(!this.config[col].renderer){
62804 return Ext.grid.ColumnModel.defaultRenderer;
62806 return this.config[col].renderer;
62810 * Sets the rendering (formatting) function for a column. See {@link Ext.util.Format} for some
62811 * default formatting functions.
62812 * @param {Number} col The column index
62813 * @param {Function} fn The function to use to process the cell's raw data
62814 * to return HTML markup for the grid view. The render function is called with
62815 * the following parameters:<ul>
62816 * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
62817 * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
62818 * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
62819 * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container element <i>within</i> the table cell
62820 * (e.g. 'style="color:red;"').</p></li></ul></p></li>
62821 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was extracted.</p></li>
62822 * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
62823 * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
62824 * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record was extracted.</p></li></ul>
62826 setRenderer : function(col, fn){
62827 this.config[col].renderer = fn;
62831 * Returns the width for the specified column.
62832 * @param {Number} col The column index
62835 getColumnWidth : function(col){
62836 return this.config[col].width;
62840 * Sets the width for a column.
62841 * @param {Number} col The column index
62842 * @param {Number} width The new width
62843 * @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
62844 * event. Defaults to false.
62846 setColumnWidth : function(col, width, suppressEvent){
62847 this.config[col].width = width;
62848 this.totalWidth = null;
62849 if(!suppressEvent){
62850 this.fireEvent("widthchange", this, col, width);
62855 * Returns the total width of all columns.
62856 * @param {Boolean} includeHidden True to include hidden column widths
62859 getTotalWidth : function(includeHidden){
62860 if(!this.totalWidth){
62861 this.totalWidth = 0;
62862 for(var i = 0, len = this.config.length; i < len; i++){
62863 if(includeHidden || !this.isHidden(i)){
62864 this.totalWidth += this.getColumnWidth(i);
62868 return this.totalWidth;
62872 * Returns the header for the specified column.
62873 * @param {Number} col The column index
62876 getColumnHeader : function(col){
62877 return this.config[col].header;
62881 * Sets the header for a column.
62882 * @param {Number} col The column index
62883 * @param {String} header The new header
62885 setColumnHeader : function(col, header){
62886 this.config[col].header = header;
62887 this.fireEvent("headerchange", this, col, header);
62891 * Returns the tooltip for the specified column.
62892 * @param {Number} col The column index
62895 getColumnTooltip : function(col){
62896 return this.config[col].tooltip;
62899 * Sets the tooltip for a column.
62900 * @param {Number} col The column index
62901 * @param {String} tooltip The new tooltip
62903 setColumnTooltip : function(col, tooltip){
62904 this.config[col].tooltip = tooltip;
62908 * Returns the dataIndex for the specified column.
62910 // Get field name for the column
62911 var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
62913 * @param {Number} col The column index
62914 * @return {String} The column's dataIndex
62916 getDataIndex : function(col){
62917 return this.config[col].dataIndex;
62921 * Sets the dataIndex for a column.
62922 * @param {Number} col The column index
62923 * @param {String} dataIndex The new dataIndex
62925 setDataIndex : function(col, dataIndex){
62926 this.config[col].dataIndex = dataIndex;
62930 * Finds the index of the first matching column for the given dataIndex.
62931 * @param {String} col The dataIndex to find
62932 * @return {Number} The column index, or -1 if no match was found
62934 findColumnIndex : function(dataIndex){
62935 var c = this.config;
62936 for(var i = 0, len = c.length; i < len; i++){
62937 if(c[i].dataIndex == dataIndex){
62945 * Returns true if the cell is editable.
62947 var store = new Ext.data.Store({...});
62948 var colModel = new Ext.grid.ColumnModel({
62950 isCellEditable: function(col, row) {
62951 var record = store.getAt(row);
62952 if (record.get('readonly')) { // replace with your condition
62955 return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, col, row);
62958 var grid = new Ext.grid.GridPanel({
62960 colModel: colModel,
62964 * @param {Number} colIndex The column index
62965 * @param {Number} rowIndex The row index
62966 * @return {Boolean}
62968 isCellEditable : function(colIndex, rowIndex){
62969 return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
62973 * Returns the editor defined for the cell/column.
62974 * @param {Number} colIndex The column index
62975 * @param {Number} rowIndex The row index
62976 * @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
62977 * the {@link Ext.form.Field Field} used to edit the cell.
62979 getCellEditor : function(colIndex, rowIndex){
62980 return this.config[colIndex].getCellEditor(rowIndex);
62984 * Sets if a column is editable.
62985 * @param {Number} col The column index
62986 * @param {Boolean} editable True if the column is editable
62988 setEditable : function(col, editable){
62989 this.config[col].editable = editable;
62994 * Returns true if the column is hidden.
62995 * @param {Number} colIndex The column index
62996 * @return {Boolean}
62998 isHidden : function(colIndex){
62999 return this.config[colIndex].hidden;
63004 * Returns true if the column width cannot be changed
63006 isFixed : function(colIndex){
63007 return this.config[colIndex].fixed;
63011 * Returns true if the column can be resized
63012 * @return {Boolean}
63014 isResizable : function(colIndex){
63015 return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
63018 * Sets if a column is hidden.
63020 myGrid.getColumnModel().setHidden(0, true); // hide column 0 (0 = the first column).
63022 * @param {Number} colIndex The column index
63023 * @param {Boolean} hidden True if the column is hidden
63025 setHidden : function(colIndex, hidden){
63026 var c = this.config[colIndex];
63027 if(c.hidden !== hidden){
63029 this.totalWidth = null;
63030 this.fireEvent("hiddenchange", this, colIndex, hidden);
63035 * Sets the editor for a column and destroys the prior editor.
63036 * @param {Number} col The column index
63037 * @param {Object} editor The editor object
63039 setEditor : function(col, editor){
63040 Ext.destroy(this.config[col].editor);
63041 this.config[col].editor = editor;
63045 * Destroys this column model by purging any event listeners, and removing any editors.
63047 destroy : function(){
63048 for(var i = 0, c = this.config, len = c.length; i < len; i++){
63049 Ext.destroy(c[i].editor);
63051 this.purgeListeners();
63056 Ext.grid.ColumnModel.defaultRenderer = function(value){
63057 if(typeof value == "string" && value.length < 1){
63062 * @class Ext.grid.AbstractSelectionModel
\r
63063 * @extends Ext.util.Observable
\r
63064 * Abstract base class for grid SelectionModels. It provides the interface that should be
\r
63065 * implemented by descendant classes. This class should not be directly instantiated.
\r
63068 Ext.grid.AbstractSelectionModel = function(){
\r
63069 this.locked = false;
\r
63070 Ext.grid.AbstractSelectionModel.superclass.constructor.call(this);
\r
63073 Ext.extend(Ext.grid.AbstractSelectionModel, Ext.util.Observable, {
\r
63075 * The GridPanel for which this SelectionModel is handling selection. Read-only.
\r
63080 /** @ignore Called by the grid automatically. Do not call directly. */
\r
63081 init : function(grid){
\r
63082 this.grid = grid;
\r
63083 this.initEvents();
\r
63087 * Locks the selections.
\r
63089 lock : function(){
\r
63090 this.locked = true;
\r
63094 * Unlocks the selections.
\r
63096 unlock : function(){
\r
63097 this.locked = false;
\r
63101 * Returns true if the selections are locked.
\r
63102 * @return {Boolean}
\r
63104 isLocked : function(){
\r
63105 return this.locked;
\r
63108 destroy: function(){
\r
63109 this.purgeListeners();
\r
63112 * @class Ext.grid.RowSelectionModel
63113 * @extends Ext.grid.AbstractSelectionModel
63114 * The default SelectionModel used by {@link Ext.grid.GridPanel}.
63115 * It supports multiple selections and keyboard selection/navigation. The objects stored
63116 * as selections and returned by {@link #getSelected}, and {@link #getSelections} are
63117 * the {@link Ext.data.Record Record}s which provide the data for the selected rows.
63119 * @param {Object} config
63121 Ext.grid.RowSelectionModel = function(config){
63122 Ext.apply(this, config);
63123 this.selections = new Ext.util.MixedCollection(false, function(o){
63128 this.lastActive = false;
63132 * @event selectionchange
63133 * Fires when the selection changes
63134 * @param {SelectionModel} this
63138 * @event beforerowselect
63139 * Fires before a row is selected, return false to cancel the selection.
63140 * @param {SelectionModel} this
63141 * @param {Number} rowIndex The index to be selected
63142 * @param {Boolean} keepExisting False if other selections will be cleared
63143 * @param {Record} record The record to be selected
63148 * Fires when a row is selected.
63149 * @param {SelectionModel} this
63150 * @param {Number} rowIndex The selected index
63151 * @param {Ext.data.Record} r The selected record
63155 * @event rowdeselect
63156 * Fires when a row is deselected. To prevent deselection
63157 * {@link Ext.grid.AbstractSelectionModel#lock lock the selections}.
63158 * @param {SelectionModel} this
63159 * @param {Number} rowIndex
63160 * @param {Record} record
63165 Ext.grid.RowSelectionModel.superclass.constructor.call(this);
63168 Ext.extend(Ext.grid.RowSelectionModel, Ext.grid.AbstractSelectionModel, {
63170 * @cfg {Boolean} singleSelect
63171 * <tt>true</tt> to allow selection of only one row at a time (defaults to <tt>false</tt>
63172 * allowing multiple selections)
63174 singleSelect : false,
63177 * @cfg {Boolean} moveEditorOnEnter
63178 * <tt>false</tt> to turn off moving the editor to the next row down when the enter key is pressed
63179 * or the next row up when shift + enter keys are pressed.
63182 initEvents : function(){
63184 if(!this.grid.enableDragDrop && !this.grid.enableDrag){
63185 this.grid.on("rowmousedown", this.handleMouseDown, this);
63186 }else{ // allow click to work like normal
63187 this.grid.on("rowclick", function(grid, rowIndex, e) {
63188 if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
63189 this.selectRow(rowIndex, false);
63190 grid.view.focusRow(rowIndex);
63195 this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
63196 "up" : function(e){
63197 if(!e.shiftKey || this.singleSelect){
63198 this.selectPrevious(false);
63199 }else if(this.last !== false && this.lastActive !== false){
63200 var last = this.last;
63201 this.selectRange(this.last, this.lastActive-1);
63202 this.grid.getView().focusRow(this.lastActive);
63203 if(last !== false){
63207 this.selectFirstRow();
63210 "down" : function(e){
63211 if(!e.shiftKey || this.singleSelect){
63212 this.selectNext(false);
63213 }else if(this.last !== false && this.lastActive !== false){
63214 var last = this.last;
63215 this.selectRange(this.last, this.lastActive+1);
63216 this.grid.getView().focusRow(this.lastActive);
63217 if(last !== false){
63221 this.selectFirstRow();
63227 var view = this.grid.view;
63228 view.on("refresh", this.onRefresh, this);
63229 view.on("rowupdated", this.onRowUpdated, this);
63230 view.on("rowremoved", this.onRemove, this);
63234 onRefresh : function(){
63235 var ds = this.grid.store, index;
63236 var s = this.getSelections();
63237 this.clearSelections(true);
63238 for(var i = 0, len = s.length; i < len; i++){
63240 if((index = ds.indexOfId(r.id)) != -1){
63241 this.selectRow(index, true);
63244 if(s.length != this.selections.getCount()){
63245 this.fireEvent("selectionchange", this);
63250 onRemove : function(v, index, r){
63251 if(this.selections.remove(r) !== false){
63252 this.fireEvent('selectionchange', this);
63257 onRowUpdated : function(v, index, r){
63258 if(this.isSelected(r)){
63259 v.onRowSelect(index);
63265 * @param {Array} records The records to select
63266 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
63268 selectRecords : function(records, keepExisting){
63270 this.clearSelections();
63272 var ds = this.grid.store;
63273 for(var i = 0, len = records.length; i < len; i++){
63274 this.selectRow(ds.indexOf(records[i]), true);
63279 * Gets the number of selected rows.
63282 getCount : function(){
63283 return this.selections.length;
63287 * Selects the first row in the grid.
63289 selectFirstRow : function(){
63294 * Select the last row.
63295 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
63297 selectLastRow : function(keepExisting){
63298 this.selectRow(this.grid.store.getCount() - 1, keepExisting);
63302 * Selects the row immediately following the last selected row.
63303 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
63304 * @return {Boolean} <tt>true</tt> if there is a next row, else <tt>false</tt>
63306 selectNext : function(keepExisting){
63307 if(this.hasNext()){
63308 this.selectRow(this.last+1, keepExisting);
63309 this.grid.getView().focusRow(this.last);
63316 * Selects the row that precedes the last selected row.
63317 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
63318 * @return {Boolean} <tt>true</tt> if there is a previous row, else <tt>false</tt>
63320 selectPrevious : function(keepExisting){
63321 if(this.hasPrevious()){
63322 this.selectRow(this.last-1, keepExisting);
63323 this.grid.getView().focusRow(this.last);
63330 * Returns true if there is a next record to select
63331 * @return {Boolean}
63333 hasNext : function(){
63334 return this.last !== false && (this.last+1) < this.grid.store.getCount();
63338 * Returns true if there is a previous record to select
63339 * @return {Boolean}
63341 hasPrevious : function(){
63342 return !!this.last;
63347 * Returns the selected records
63348 * @return {Array} Array of selected records
63350 getSelections : function(){
63351 return [].concat(this.selections.items);
63355 * Returns the first selected record.
63358 getSelected : function(){
63359 return this.selections.itemAt(0);
63363 * Calls the passed function with each selection. If the function returns
63364 * <tt>false</tt>, iteration is stopped and this function returns
63365 * <tt>false</tt>. Otherwise it returns <tt>true</tt>.
63366 * @param {Function} fn
63367 * @param {Object} scope (optional)
63368 * @return {Boolean} true if all selections were iterated
63370 each : function(fn, scope){
63371 var s = this.getSelections();
63372 for(var i = 0, len = s.length; i < len; i++){
63373 if(fn.call(scope || this, s[i], i) === false){
63381 * Clears all selections if the selection model
63382 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
63383 * @param {Boolean} fast (optional) <tt>true</tt> to bypass the
63384 * conditional checks and events described in {@link #deselectRow}.
63386 clearSelections : function(fast){
63387 if(this.isLocked()){
63391 var ds = this.grid.store;
63392 var s = this.selections;
63393 s.each(function(r){
63394 this.deselectRow(ds.indexOfId(r.id));
63398 this.selections.clear();
63405 * Selects all rows if the selection model
63406 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
63408 selectAll : function(){
63409 if(this.isLocked()){
63412 this.selections.clear();
63413 for(var i = 0, len = this.grid.store.getCount(); i < len; i++){
63414 this.selectRow(i, true);
63419 * Returns <tt>true</tt> if there is a selection.
63420 * @return {Boolean}
63422 hasSelection : function(){
63423 return this.selections.length > 0;
63427 * Returns <tt>true</tt> if the specified row is selected.
63428 * @param {Number/Record} index The record or index of the record to check
63429 * @return {Boolean}
63431 isSelected : function(index){
63432 var r = typeof index == "number" ? this.grid.store.getAt(index) : index;
63433 return (r && this.selections.key(r.id) ? true : false);
63437 * Returns <tt>true</tt> if the specified record id is selected.
63438 * @param {String} id The id of record to check
63439 * @return {Boolean}
63441 isIdSelected : function(id){
63442 return (this.selections.key(id) ? true : false);
63446 handleMouseDown : function(g, rowIndex, e){
63447 if(e.button !== 0 || this.isLocked()){
63450 var view = this.grid.getView();
63451 if(e.shiftKey && !this.singleSelect && this.last !== false){
63452 var last = this.last;
63453 this.selectRange(last, rowIndex, e.ctrlKey);
63454 this.last = last; // reset the last
63455 view.focusRow(rowIndex);
63457 var isSelected = this.isSelected(rowIndex);
63458 if(e.ctrlKey && isSelected){
63459 this.deselectRow(rowIndex);
63460 }else if(!isSelected || this.getCount() > 1){
63461 this.selectRow(rowIndex, e.ctrlKey || e.shiftKey);
63462 view.focusRow(rowIndex);
63468 * Selects multiple rows.
63469 * @param {Array} rows Array of the indexes of the row to select
63470 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep
63471 * existing selections (defaults to <tt>false</tt>)
63473 selectRows : function(rows, keepExisting){
63475 this.clearSelections();
63477 for(var i = 0, len = rows.length; i < len; i++){
63478 this.selectRow(rows[i], true);
63483 * Selects a range of rows if the selection model
63484 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
63485 * All rows in between startRow and endRow are also selected.
63486 * @param {Number} startRow The index of the first row in the range
63487 * @param {Number} endRow The index of the last row in the range
63488 * @param {Boolean} keepExisting (optional) True to retain existing selections
63490 selectRange : function(startRow, endRow, keepExisting){
63492 if(this.isLocked()){
63496 this.clearSelections();
63498 if(startRow <= endRow){
63499 for(i = startRow; i <= endRow; i++){
63500 this.selectRow(i, true);
63503 for(i = startRow; i >= endRow; i--){
63504 this.selectRow(i, true);
63510 * Deselects a range of rows if the selection model
63511 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
63512 * All rows in between startRow and endRow are also deselected.
63513 * @param {Number} startRow The index of the first row in the range
63514 * @param {Number} endRow The index of the last row in the range
63516 deselectRange : function(startRow, endRow, preventViewNotify){
63517 if(this.isLocked()){
63520 for(var i = startRow; i <= endRow; i++){
63521 this.deselectRow(i, preventViewNotify);
63526 * Selects a row. Before selecting a row, checks if the selection model
63527 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked} and fires the
63528 * {@link #beforerowselect} event. If these checks are satisfied the row
63529 * will be selected and followed up by firing the {@link #rowselect} and
63530 * {@link #selectionchange} events.
63531 * @param {Number} row The index of the row to select
63532 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
63533 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
63534 * prevent notifying the view (disables updating the selected appearance)
63536 selectRow : function(index, keepExisting, preventViewNotify){
63537 if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){
63540 var r = this.grid.store.getAt(index);
63541 if(r && this.fireEvent("beforerowselect", this, index, keepExisting, r) !== false){
63542 if(!keepExisting || this.singleSelect){
63543 this.clearSelections();
63545 this.selections.add(r);
63546 this.last = this.lastActive = index;
63547 if(!preventViewNotify){
63548 this.grid.getView().onRowSelect(index);
63550 this.fireEvent("rowselect", this, index, r);
63551 this.fireEvent("selectionchange", this);
63556 * Deselects a row. Before deselecting a row, checks if the selection model
63557 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
63558 * If this check is satisfied the row will be deselected and followed up by
63559 * firing the {@link #rowdeselect} and {@link #selectionchange} events.
63560 * @param {Number} row The index of the row to deselect
63561 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
63562 * prevent notifying the view (disables updating the selected appearance)
63564 deselectRow : function(index, preventViewNotify){
63565 if(this.isLocked()){
63568 if(this.last == index){
63571 if(this.lastActive == index){
63572 this.lastActive = false;
63574 var r = this.grid.store.getAt(index);
63576 this.selections.remove(r);
63577 if(!preventViewNotify){
63578 this.grid.getView().onRowDeselect(index);
63580 this.fireEvent("rowdeselect", this, index, r);
63581 this.fireEvent("selectionchange", this);
63586 restoreLast : function(){
63588 this.last = this._last;
63593 acceptsNav : function(row, col, cm){
63594 return !cm.isHidden(col) && cm.isCellEditable(col, row);
63598 onEditorKey : function(field, e){
63599 var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
63600 var shift = e.shiftKey;
63605 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
63607 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
63609 }else if(k == e.ENTER){
63612 if(this.moveEditorOnEnter !== false){
63614 newCell = g.walkCells(ed.row - 1, ed.col, -1, this.acceptsNav, this);
63616 newCell = g.walkCells(ed.row + 1, ed.col, 1, this.acceptsNav, this);
63619 }else if(k == e.ESC){
63623 g.startEditing(newCell[0], newCell[1]);
63627 destroy: function(){
63629 this.rowNav.disable();
63630 this.rowNav = null;
63632 Ext.grid.RowSelectionModel.superclass.destroy.call(this);
63635 * @class Ext.grid.Column
\r
63636 * <p>This class encapsulates column configuration data to be used in the initialization of a
\r
63637 * {@link Ext.grid.ColumnModel ColumnModel}.</p>
\r
63638 * <p>While subclasses are provided to render data in different ways, this class renders a passed
\r
63639 * data field unchanged and is usually used for textual columns.</p>
\r
63641 Ext.grid.Column = function(config){
\r
63642 Ext.apply(this, config);
\r
63644 if(typeof this.renderer == 'string'){
\r
63645 this.renderer = Ext.util.Format[this.renderer];
\r
63646 } else if(Ext.isObject(this.renderer)){
\r
63647 this.scope = this.renderer.scope;
\r
63648 this.renderer = this.renderer.fn;
\r
63650 this.renderer = this.renderer.createDelegate(this.scope || config);
\r
63652 if(this.id === undefined){
\r
63653 this.id = ++Ext.grid.Column.AUTO_ID;
\r
63656 this.editor = Ext.create(this.editor, 'textfield');
\r
63660 Ext.grid.Column.AUTO_ID = 0;
\r
63662 Ext.grid.Column.prototype = {
\r
63664 * @cfg {Boolean} editable Optional. Defaults to <tt>true</tt>, enabling the configured
\r
63665 * <tt>{@link #editor}</tt>. Set to <tt>false</tt> to initially disable editing on this column.
\r
63666 * The initial configuration may be dynamically altered using
\r
63667 * {@link Ext.grid.ColumnModel}.{@link Ext.grid.ColumnModel#setEditable setEditable()}.
\r
63670 * @cfg {String} id Optional. A name which identifies this column (defaults to the column's initial
\r
63671 * ordinal position.) The <tt>id</tt> is used to create a CSS <b>class</b> name which is applied to all
\r
63672 * table cells (including headers) in that column (in this context the <tt>id</tt> does not need to be
\r
63673 * unique). The class name takes the form of <pre>x-grid3-td-<b>id</b></pre>
\r
63674 * Header cells will also receive this class name, but will also have the class <pre>x-grid3-hd</pre>
\r
63675 * So, to target header cells, use CSS selectors such as:<pre>.x-grid3-hd-row .x-grid3-td-<b>id</b></pre>
\r
63676 * The {@link Ext.grid.GridPanel#autoExpandColumn} grid config option references the column via this
\r
63677 * unique identifier.
\r
63680 * @cfg {String} header Optional. The header text to be used as innerHTML
\r
63681 * (html tags are accepted) to display in the Grid view. <b>Note</b>: to
\r
63682 * have a clickable header with no text displayed use <tt>' '</tt>.
\r
63685 * @cfg {Boolean} groupable Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
63686 * may be used to disable the header menu item to group by the column selected. Defaults to <tt>true</tt>,
\r
63687 * which enables the header menu group option. Set to <tt>false</tt> to disable (but still show) the
\r
63688 * group option in the header menu for the column. See also <code>{@link #groupName}</code>.
\r
63691 * @cfg {String} groupName Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
63692 * may be used to specify the text with which to prefix the group field value in the group header line.
\r
63693 * See also {@link #groupRenderer} and
\r
63694 * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#showGroupName showGroupName}.
\r
63697 * @cfg {Function} groupRenderer <p>Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
63698 * may be used to specify the function used to format the grouping field value for display in the group
\r
63699 * {@link #groupName header}. If a <tt><b>groupRenderer</b></tt> is not specified, the configured
\r
63700 * <tt><b>{@link #renderer}</b></tt> will be called; if a <tt><b>{@link #renderer}</b></tt> is also not specified
\r
63701 * the new value of the group field will be used.</p>
\r
63702 * <p>The called function (either the <tt><b>groupRenderer</b></tt> or <tt><b>{@link #renderer}</b></tt>) will be
\r
63703 * passed the following parameters:
\r
63704 * <div class="mdetail-params"><ul>
\r
63705 * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li>
\r
63706 * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li>
\r
63707 * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data
\r
63708 * for the row which caused group change.</p></li>
\r
63709 * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
63710 * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li>
\r
63711 * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
\r
63712 * </ul></div></p>
\r
63713 * <p>The function should return a string value.</p>
\r
63716 * @cfg {String} emptyGroupText Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
63717 * may be used to specify the text to display when there is an empty group value. Defaults to the
\r
63718 * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#emptyGroupText emptyGroupText}.
\r
63721 * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
\r
63722 * grid's {@link Ext.data.Store}'s {@link Ext.data.Record} definition from
\r
63723 * which to draw the column's value.</p>
\r
63726 * @cfg {Number} width
\r
63727 * Optional. The initial width in pixels of the column.
\r
63728 * The width of each column can also be affected if any of the following are configured:
\r
63729 * <div class="mdetail-params"><ul>
\r
63730 * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></li>
\r
63731 * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#forceFit forceFit}</tt>
\r
63732 * <div class="sub-desc">
\r
63733 * <p>By specifying <tt>forceFit:true</tt>, {@link #fixed non-fixed width} columns will be
\r
63734 * re-proportioned (based on the relative initial widths) to fill the width of the grid so
\r
63735 * that no horizontal scrollbar is shown.</p>
\r
63737 * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#autoFill autoFill}</tt></li>
\r
63738 * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#minColumnWidth minColumnWidth}</tt></li>
\r
63739 * <br><p><b>Note</b>: when the width of each column is determined, a space on the right side
\r
63740 * is reserved for the vertical scrollbar. The
\r
63741 * {@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#scrollOffset scrollOffset}</tt>
\r
63742 * can be modified to reduce or eliminate the reserved offset.</p>
\r
63745 * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
\r
63746 * Defaults to the value of the {@link #defaultSortable} property.
\r
63747 * Whether local/remote sorting is used is specified in {@link Ext.data.Store#remoteSort}.
\r
63750 * @cfg {Boolean} fixed Optional. <tt>true</tt> if the column width cannot be changed. Defaults to <tt>false</tt>.
\r
63753 * @cfg {Boolean} resizable Optional. <tt>false</tt> to disable column resizing. Defaults to <tt>true</tt>.
\r
63756 * @cfg {Boolean} menuDisabled Optional. <tt>true</tt> to disable the column menu. Defaults to <tt>false</tt>.
\r
63759 * @cfg {Boolean} hidden Optional. <tt>true</tt> to hide the column. Defaults to <tt>false</tt>.
\r
63762 * @cfg {String} tooltip Optional. A text string to use as the column header's tooltip. If Quicktips
\r
63763 * are enabled, this value will be used as the text of the quick tip, otherwise it will be set as the
\r
63764 * header's HTML title attribute. Defaults to ''.
\r
63767 * @cfg {Mixed} renderer
\r
63768 * <p>For an alternative to specifying a renderer see <code>{@link #xtype}</code></p>
\r
63769 * <p>Optional. A renderer is an 'interceptor' method which can be used transform data (value,
\r
63770 * appearance, etc.) before it is rendered). This may be specified in either of three ways:
\r
63771 * <div class="mdetail-params"><ul>
\r
63772 * <li>A renderer function used to return HTML markup for a cell given the cell's data value.</li>
\r
63773 * <li>A string which references a property name of the {@link Ext.util.Format} class which
\r
63774 * provides a renderer function.</li>
\r
63775 * <li>An object specifying both the renderer function, and its execution scope (<tt><b>this</b></tt>
\r
63776 * reference) e.g.:<pre style="margin-left:1.2em"><code>
\r
63778 fn: this.gridRenderer,
\r
63781 </code></pre></li></ul></div>
\r
63782 * If not specified, the default renderer uses the raw data value.</p>
\r
63783 * <p>For information about the renderer function (passed parameters, etc.), see
\r
63784 * {@link Ext.grid.ColumnModel#setRenderer}. An example of specifying renderer function inline:</p><pre><code>
\r
63785 var companyColumn = {
\r
63786 header: 'Company Name',
\r
63787 dataIndex: 'company',
\r
63788 renderer: function(value, metaData, record, rowIndex, colIndex, store) {
\r
63789 // provide the logic depending on business rules
\r
63790 // name of your own choosing to manipulate the cell depending upon
\r
63791 // the data in the underlying Record object.
\r
63792 if (value == 'whatever') {
\r
63793 //metaData.css : String : A CSS class name to add to the TD element of the cell.
\r
63794 //metaData.attr : String : An html attribute definition string to apply to
\r
63795 // the data container element within the table
\r
63796 // cell (e.g. 'style="color:red;"').
\r
63797 metaData.css = 'name-of-css-class-you-will-define';
\r
63803 * See also {@link #scope}.
\r
63806 * @cfg {String} xtype Optional. A String which references a predefined {@link Ext.grid.Column} subclass
\r
63807 * type which is preconfigured with an appropriate <code>{@link #renderer}</code> to be easily
\r
63808 * configured into a ColumnModel. The predefined {@link Ext.grid.Column} subclass types are:
\r
63809 * <div class="mdetail-params"><ul>
\r
63810 * <li><b><tt>gridcolumn</tt></b> : {@link Ext.grid.Column} (<b>Default</b>)<p class="sub-desc"></p></li>
\r
63811 * <li><b><tt>booleancolumn</tt></b> : {@link Ext.grid.BooleanColumn}<p class="sub-desc"></p></li>
\r
63812 * <li><b><tt>numbercolumn</tt></b> : {@link Ext.grid.NumberColumn}<p class="sub-desc"></p></li>
\r
63813 * <li><b><tt>datecolumn</tt></b> : {@link Ext.grid.DateColumn}<p class="sub-desc"></p></li>
\r
63814 * <li><b><tt>templatecolumn</tt></b> : {@link Ext.grid.TemplateColumn}<p class="sub-desc"></p></li>
\r
63816 * <p>Configuration properties for the specified <code>xtype</code> may be specified with
\r
63817 * the Column configuration properties, for example:</p>
\r
63819 var grid = new Ext.grid.GridPanel({
\r
63822 header: 'Last Updated',
\r
63823 dataIndex: 'lastChange',
\r
63826 //renderer: Ext.util.Format.dateRenderer('m/d/Y'),
\r
63827 xtype: 'datecolumn', // use xtype instead of renderer
\r
63828 format: 'M/d/Y' // configuration property for {@link Ext.grid.DateColumn}
\r
63836 * @cfg {Object} scope Optional. The scope (<tt><b>this</b></tt> reference) in which to execute the
\r
63837 * renderer. Defaults to the Column configuration object.
\r
63840 * @cfg {String} align Optional. Set the CSS text-align property of the column. Defaults to undefined.
\r
63843 * @cfg {String} css Optional. An inline style definition string which is applied to all table cells in the column
\r
63844 * (excluding headers). Defaults to undefined.
\r
63847 * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
\r
63848 * (defaults to true). To disallow column hiding globally for all columns in the grid, use
\r
63849 * {@link Ext.grid.GridPanel#enableColumnHide} instead.
\r
63852 * @cfg {Ext.form.Field} editor Optional. The {@link Ext.form.Field} to use when editing values in this column
\r
63853 * if editing is supported by the grid. See <tt>{@link #editable}</tt> also.
\r
63856 // private. Used by ColumnModel to avoid reprocessing
\r
63859 * Optional. A function which returns displayable data when passed the following parameters:
\r
63860 * <div class="mdetail-params"><ul>
\r
63861 * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
\r
63862 * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
\r
63863 * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
\r
63864 * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container
\r
63865 * element <i>within</i> the table cell (e.g. 'style="color:red;"').</p></li></ul></p></li>
\r
63866 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was
\r
63867 * extracted.</p></li>
\r
63868 * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
\r
63869 * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
\r
63870 * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record
\r
63871 * was extracted.</p></li>
\r
63873 * @property renderer
\r
63876 renderer : function(value){
\r
63877 if(typeof value == 'string' && value.length < 1){
\r
63884 getEditor: function(rowIndex){
\r
63885 return this.editable !== false ? this.editor : null;
\r
63889 * Returns the {@link Ext.Editor editor} defined for this column that was created to wrap the {@link Ext.form.Field Field}
\r
63890 * used to edit the cell.
\r
63891 * @param {Number} rowIndex The row index
\r
63892 * @return {Ext.Editor}
\r
63894 getCellEditor: function(rowIndex){
\r
63895 var editor = this.getEditor(rowIndex);
\r
63897 if(!editor.startEdit){
\r
63898 if(!editor.gridEditor){
\r
63899 editor.gridEditor = new Ext.grid.GridEditor(editor);
\r
63901 return editor.gridEditor;
\r
63902 }else if(editor.startEdit){
\r
63911 * @class Ext.grid.BooleanColumn
\r
63912 * @extends Ext.grid.Column
\r
63913 * <p>A Column definition class which renders boolean data fields. See the {@link Ext.grid.ColumnModel#xtype xtype}
\r
63914 * config option of {@link Ext.grid.ColumnModel} for more details.</p>
\r
63916 Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
\r
63918 * @cfg {String} trueText
\r
63919 * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
\r
63921 trueText: 'true',
\r
63923 * @cfg {String} falseText
\r
63924 * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
\r
63925 * <tt>'false'</tt>).
\r
63927 falseText: 'false',
\r
63929 * @cfg {String} undefinedText
\r
63930 * The string returned by the renderer when the column value is undefined (defaults to <tt>' '</tt>).
\r
63932 undefinedText: ' ',
\r
63934 constructor: function(cfg){
\r
63935 Ext.grid.BooleanColumn.superclass.constructor.call(this, cfg);
\r
63936 var t = this.trueText, f = this.falseText, u = this.undefinedText;
\r
63937 this.renderer = function(v){
\r
63938 if(v === undefined){
\r
63941 if(!v || v === 'false'){
\r
63950 * @class Ext.grid.NumberColumn
\r
63951 * @extends Ext.grid.Column
\r
63952 * <p>A Column definition class which renders a numeric data field according to a {@link #format} string. See the
\r
63953 * {@link Ext.grid.ColumnModel#xtype xtype} config option of {@link Ext.grid.ColumnModel} for more details.</p>
\r
63955 Ext.grid.NumberColumn = Ext.extend(Ext.grid.Column, {
\r
63957 * @cfg {String} format
\r
63958 * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
\r
63959 * (defaults to <tt>'0,000.00'</tt>).
\r
63961 format : '0,000.00',
\r
63962 constructor: function(cfg){
\r
63963 Ext.grid.NumberColumn.superclass.constructor.call(this, cfg);
\r
63964 this.renderer = Ext.util.Format.numberRenderer(this.format);
\r
63969 * @class Ext.grid.DateColumn
\r
63970 * @extends Ext.grid.Column
\r
63971 * <p>A Column definition class which renders a passed date according to the default locale, or a configured
\r
63972 * {@link #format}. See the {@link Ext.grid.ColumnModel#xtype xtype} config option of {@link Ext.grid.ColumnModel}
\r
63973 * for more details.</p>
\r
63975 Ext.grid.DateColumn = Ext.extend(Ext.grid.Column, {
\r
63977 * @cfg {String} format
\r
63978 * A formatting string as used by {@link Date#format} to format a Date for this Column
\r
63979 * (defaults to <tt>'m/d/Y'</tt>).
\r
63981 format : 'm/d/Y',
\r
63982 constructor: function(cfg){
\r
63983 Ext.grid.DateColumn.superclass.constructor.call(this, cfg);
\r
63984 this.renderer = Ext.util.Format.dateRenderer(this.format);
\r
63989 * @class Ext.grid.TemplateColumn
\r
63990 * @extends Ext.grid.Column
\r
63991 * <p>A Column definition class which renders a value by processing a {@link Ext.data.Record Record}'s
\r
63992 * {@link Ext.data.Record#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
\r
63993 * See the {@link Ext.grid.ColumnModel#xtype xtype} config option of {@link Ext.grid.ColumnModel} for more
\r
63996 Ext.grid.TemplateColumn = Ext.extend(Ext.grid.Column, {
\r
63998 * @cfg {String/XTemplate} tpl
\r
63999 * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
\r
64000 * {@link Ext.data.Record Record}'s {@link Ext.data.Record#data data} to produce a column's rendered value.
\r
64002 constructor: function(cfg){
\r
64003 Ext.grid.TemplateColumn.superclass.constructor.call(this, cfg);
\r
64004 var tpl = typeof Ext.isObject(this.tpl) ? this.tpl : new Ext.XTemplate(this.tpl);
\r
64005 this.renderer = function(value, p, r){
\r
64006 return tpl.apply(r.data);
\r
64013 * @property types
\r
64015 * @member Ext.grid.Column
\r
64017 * <p>An object containing predefined Column classes keyed by a mnemonic code which may be referenced
\r
64018 * by the {@link Ext.grid.ColumnModel#xtype xtype} config option of ColumnModel.</p>
\r
64019 * <p>This contains the following properties</p><div class="mdesc-details"><ul>
\r
64020 * <li>gridcolumn : <b>{@link Ext.grid.Column Column constructor}</b></li>
\r
64021 * <li>booleancolumn : <b>{@link Ext.grid.BooleanColumn BooleanColumn constructor}</b></li>
\r
64022 * <li>numbercolumn : <b>{@link Ext.grid.NumberColumn NumberColumn constructor}</b></li>
\r
64023 * <li>datecolumn : <b>{@link Ext.grid.DateColumn DateColumn constructor}</b></li>
\r
64024 * <li>templatecolumn : <b>{@link Ext.grid.TemplateColumn TemplateColumn constructor}</b></li>
\r
64027 Ext.grid.Column.types = {
\r
64028 gridcolumn : Ext.grid.Column,
\r
64029 booleancolumn: Ext.grid.BooleanColumn,
\r
64030 numbercolumn: Ext.grid.NumberColumn,
\r
64031 datecolumn: Ext.grid.DateColumn,
\r
64032 templatecolumn: Ext.grid.TemplateColumn
\r
64034 * @class Ext.grid.RowNumberer
64035 * This is a utility class that can be passed into a {@link Ext.grid.ColumnModel} as a column config that provides
64036 * an automatic row numbering column.
64039 // This is a typical column config with the first column providing row numbers
64040 var colModel = new Ext.grid.ColumnModel([
64041 new Ext.grid.RowNumberer(),
64042 {header: "Name", width: 80, sortable: true},
64043 {header: "Code", width: 50, sortable: true},
64044 {header: "Description", width: 200, sortable: true}
64048 * @param {Object} config The configuration options
64050 Ext.grid.RowNumberer = function(config){
64051 Ext.apply(this, config);
64053 this.renderer = this.renderer.createDelegate(this);
64057 Ext.grid.RowNumberer.prototype = {
64059 * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the row
64060 * number column (defaults to '').
64064 * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
64068 * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
64078 rowspan: undefined,
64081 renderer : function(v, p, record, rowIndex){
64083 p.cellAttr = 'rowspan="'+this.rowspan+'"';
64088 * @class Ext.grid.CheckboxSelectionModel
\r
64089 * @extends Ext.grid.RowSelectionModel
\r
64090 * A custom selection model that renders a column of checkboxes that can be toggled to select or deselect rows.
\r
64092 * @param {Object} config The configuration options
\r
64094 Ext.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
\r
64097 * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
\r
64098 * checkbox column (defaults to <tt>false</tt>).
\r
64101 * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the
\r
64102 * checkbox column. Defaults to:<pre><code>
\r
64103 * '<div class="x-grid3-hd-checker">&#160;</div>'</tt>
\r
64105 * The default CSS class of <tt>'x-grid3-hd-checker'</tt> displays a checkbox in the header
\r
64106 * and provides support for automatic check all/none behavior on header click. This string
\r
64107 * can be replaced by any valid HTML fragment, including a simple text string (e.g.,
\r
64108 * <tt>'Select Rows'</tt>), but the automatic check all/none behavior will only work if the
\r
64109 * <tt>'x-grid3-hd-checker'</tt> class is supplied.
\r
64111 header: '<div class="x-grid3-hd-checker"> </div>',
\r
64113 * @cfg {Number} width The default width in pixels of the checkbox column (defaults to <tt>20</tt>).
\r
64117 * @cfg {Boolean} sortable <tt>true</tt> if the checkbox column is sortable (defaults to
\r
64118 * <tt>false</tt>).
\r
64123 menuDisabled:true,
\r
64128 constructor: function(){
\r
64129 Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
\r
64131 if(this.checkOnly){
\r
64132 this.handleMouseDown = Ext.emptyFn;
\r
64137 initEvents : function(){
\r
64138 Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
\r
64139 this.grid.on('render', function(){
\r
64140 var view = this.grid.getView();
\r
64141 view.mainBody.on('mousedown', this.onMouseDown, this);
\r
64142 Ext.fly(view.innerHd).on('mousedown', this.onHdMouseDown, this);
\r
64148 onMouseDown : function(e, t){
\r
64149 if(e.button === 0 && t.className == 'x-grid3-row-checker'){ // Only fire if left-click
\r
64151 var row = e.getTarget('.x-grid3-row');
\r
64153 var index = row.rowIndex;
\r
64154 if(this.isSelected(index)){
\r
64155 this.deselectRow(index);
\r
64157 this.selectRow(index, true);
\r
64164 onHdMouseDown : function(e, t){
\r
64165 if(t.className == 'x-grid3-hd-checker'){
\r
64167 var hd = Ext.fly(t.parentNode);
\r
64168 var isChecked = hd.hasClass('x-grid3-hd-checker-on');
\r
64170 hd.removeClass('x-grid3-hd-checker-on');
\r
64171 this.clearSelections();
\r
64173 hd.addClass('x-grid3-hd-checker-on');
\r
64174 this.selectAll();
\r
64180 renderer : function(v, p, record){
\r
64181 return '<div class="x-grid3-row-checker"> </div>';
\r
64184 * @class Ext.grid.CellSelectionModel
64185 * @extends Ext.grid.AbstractSelectionModel
64186 * This class provides the basic implementation for <i>single</i> <b>cell</b> selection in a grid.
64187 * The object stored as the selection contains the following properties:
64188 * <div class="mdetail-params"><ul>
64189 * <li><b>cell</b> : see {@link #getSelectedCell}
64190 * <li><b>record</b> : Ext.data.record The {@link Ext.data.Record Record}
64191 * which provides the data for the row containing the selection</li>
64194 * @param {Object} config The object containing the configuration of this model.
64196 Ext.grid.CellSelectionModel = function(config){
64197 Ext.apply(this, config);
64199 this.selection = null;
64203 * @event beforecellselect
64204 * Fires before a cell is selected, return false to cancel the selection.
64205 * @param {SelectionModel} this
64206 * @param {Number} rowIndex The selected row index
64207 * @param {Number} colIndex The selected cell index
64209 "beforecellselect",
64211 * @event cellselect
64212 * Fires when a cell is selected.
64213 * @param {SelectionModel} this
64214 * @param {Number} rowIndex The selected row index
64215 * @param {Number} colIndex The selected cell index
64219 * @event selectionchange
64220 * Fires when the active selection changes.
64221 * @param {SelectionModel} this
64222 * @param {Object} selection null for no selection or an object with two properties
64223 * <div class="mdetail-params"><ul>
64224 * <li><b>cell</b> : see {@link #getSelectedCell}
64225 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record Record}
64226 * which provides the data for the row containing the selection</p></li>
64232 Ext.grid.CellSelectionModel.superclass.constructor.call(this);
64235 Ext.extend(Ext.grid.CellSelectionModel, Ext.grid.AbstractSelectionModel, {
64238 initEvents : function(){
64239 this.grid.on("cellmousedown", this.handleMouseDown, this);
64240 this.grid.getGridEl().on(Ext.EventManager.useKeydown ? "keydown" : "keypress", this.handleKeyDown, this);
64241 var view = this.grid.view;
64242 view.on("refresh", this.onViewChange, this);
64243 view.on("rowupdated", this.onRowUpdated, this);
64244 view.on("beforerowremoved", this.clearSelections, this);
64245 view.on("beforerowsinserted", this.clearSelections, this);
64246 if(this.grid.isEditor){
64247 this.grid.on("beforeedit", this.beforeEdit, this);
64252 beforeEdit : function(e){
64253 this.select(e.row, e.column, false, true, e.record);
64257 onRowUpdated : function(v, index, r){
64258 if(this.selection && this.selection.record == r){
64259 v.onCellSelect(index, this.selection.cell[1]);
64264 onViewChange : function(){
64265 this.clearSelections(true);
64269 * Returns an array containing the row and column indexes of the currently selected cell
64270 * (e.g., [0, 0]), or null if none selected. The array has elements:
64271 * <div class="mdetail-params"><ul>
64272 * <li><b>rowIndex</b> : Number<p class="sub-desc">The index of the selected row</p></li>
64273 * <li><b>cellIndex</b> : Number<p class="sub-desc">The index of the selected cell.
64274 * Due to possible column reordering, the cellIndex should <b>not</b> be used as an
64275 * index into the Record's data. Instead, use the cellIndex to determine the <i>name</i>
64276 * of the selected cell and use the field name to retrieve the data value from the record:<pre><code>
64278 var fieldName = grid.getColumnModel().getDataIndex(cellIndex);
64279 // get data value based on name
64280 var data = record.get(fieldName);
64281 * </code></pre></p></li>
64283 * @return {Array} An array containing the row and column indexes of the selected cell, or null if none selected.
64285 getSelectedCell : function(){
64286 return this.selection ? this.selection.cell : null;
64290 * If anything is selected, clears all selections and fires the selectionchange event.
64291 * @param {Boolean} preventNotify <tt>true</tt> to prevent the gridview from
64292 * being notified about the change.
64294 clearSelections : function(preventNotify){
64295 var s = this.selection;
64297 if(preventNotify !== true){
64298 this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
64300 this.selection = null;
64301 this.fireEvent("selectionchange", this, null);
64306 * Returns <tt>true</tt> if there is a selection.
64307 * @return {Boolean}
64309 hasSelection : function(){
64310 return this.selection ? true : false;
64314 handleMouseDown : function(g, row, cell, e){
64315 if(e.button !== 0 || this.isLocked()){
64318 this.select(row, cell);
64322 * Selects a cell. Before selecting a cell, fires the
64323 * {@link #beforecellselect} event. If this check is satisfied the cell
64324 * will be selected and followed up by firing the {@link #cellselect} and
64325 * {@link #selectionchange} events.
64326 * @param {Number} rowIndex The index of the row to select
64327 * @param {Number} colIndex The index of the column to select
64328 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
64329 * prevent notifying the view (disables updating the selected appearance)
64330 * @param {Boolean} preventFocus (optional) Whether to prevent the cell at
64331 * the specified rowIndex / colIndex from being focused.
64332 * @param {Ext.data.Record} r (optional) The record to select
64334 select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){
64335 if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
64336 this.clearSelections();
64337 r = r || this.grid.store.getAt(rowIndex);
64340 cell : [rowIndex, colIndex]
64342 if(!preventViewNotify){
64343 var v = this.grid.getView();
64344 v.onCellSelect(rowIndex, colIndex);
64345 if(preventFocus !== true){
64346 v.focusCell(rowIndex, colIndex);
64349 this.fireEvent("cellselect", this, rowIndex, colIndex);
64350 this.fireEvent("selectionchange", this, this.selection);
64355 isSelectable : function(rowIndex, colIndex, cm){
64356 return !cm.isHidden(colIndex);
64360 handleKeyDown : function(e){
64361 if(!e.isNavKeyPress()){
64364 var g = this.grid, s = this.selection;
64367 var cell = g.walkCells(0, 0, 1, this.isSelectable, this);
64369 this.select(cell[0], cell[1]);
64374 var walk = function(row, col, step){
64375 return g.walkCells(row, col, step, sm.isSelectable, sm);
64377 var k = e.getKey(), r = s.cell[0], c = s.cell[1];
64383 newCell = walk(r, c-1, -1);
64385 newCell = walk(r, c+1, 1);
64389 newCell = walk(r+1, c, 1);
64392 newCell = walk(r-1, c, -1);
64395 newCell = walk(r, c+1, 1);
64398 newCell = walk(r, c-1, -1);
64401 if(g.isEditor && !g.editing){
64402 g.startEditing(r, c);
64409 this.select(newCell[0], newCell[1]);
64414 acceptsNav : function(row, col, cm){
64415 return !cm.isHidden(col) && cm.isCellEditable(col, row);
64418 onEditorKey : function(field, e){
64419 var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
64422 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
64424 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
64427 }else if(k == e.ENTER){
64430 }else if(k == e.ESC){
64435 g.startEditing(newCell[0], newCell[1]);
64439 * @class Ext.grid.EditorGridPanel
64440 * @extends Ext.grid.GridPanel
64441 * <p>This class extends the {@link Ext.grid.GridPanel GridPanel Class} to provide cell editing
64442 * on selected {@link Ext.grid.Column columns}. The editable columns are specified by providing
64443 * an {@link Ext.grid.ColumnModel#editor editor} in the {@link Ext.grid.Column column configuration}.</p>
64444 * <p>Editability of columns may be controlled programatically by inserting an implementation
64445 * of {@link Ext.grid.ColumnModel#isCellEditable isCellEditable} into the
64446 * {@link Ext.grid.ColumnModel ColumnModel}.</p>
64447 * <p>Editing is performed on the value of the <i>field</i> specified by the column's
64448 * <tt>{@link Ext.grid.ColumnModel#dataIndex dataIndex}</tt> in the backing {@link Ext.data.Store Store}
64449 * (so if you are using a {@link Ext.grid.ColumnModel#setRenderer renderer} in order to display
64450 * transformed data, this must be accounted for).</p>
64451 * <p>If a value-to-description mapping is used to render a column, then a {@link Ext.form.Field#ComboBox ComboBox}
64452 * which uses the same {@link Ext.form.Field#valueField value}-to-{@link Ext.form.Field#displayFieldField description}
64453 * mapping would be an appropriate editor.</p>
64454 * If there is a more complex mismatch between the visible data in the grid, and the editable data in
64455 * the {@link Edt.data.Store Store}, then code to transform the data both before and after editing can be
64456 * injected using the {@link #beforeedit} and {@link #afteredit} events.
64458 * @param {Object} config The config object
64459 * @xtype editorgrid
64461 Ext.grid.EditorGridPanel = Ext.extend(Ext.grid.GridPanel, {
64463 * @cfg {Number} clicksToEdit
64464 * <p>The number of clicks on a cell required to display the cell's editor (defaults to 2).</p>
64465 * <p>Setting this option to 'auto' means that mousedown <i>on the selected cell</i> starts
64466 * editing that cell.</p>
64471 * @cfg {Boolean} forceValidation
64472 * True to force validation even if the value is unmodified (defaults to false)
64474 forceValidation: false,
64482 * @cfg {Boolean} autoEncode
64483 * True to automatically HTML encode and decode values pre and post edit (defaults to false)
64485 autoEncode : false,
64488 * @cfg {Boolean} trackMouseOver @hide
64491 trackMouseOver: false, // causes very odd FF errors
64494 initComponent : function(){
64495 Ext.grid.EditorGridPanel.superclass.initComponent.call(this);
64497 if(!this.selModel){
64499 * @cfg {Object} selModel Any subclass of AbstractSelectionModel that will provide the selection model for
64500 * the grid (defaults to {@link Ext.grid.CellSelectionModel} if not specified).
64502 this.selModel = new Ext.grid.CellSelectionModel();
64505 this.activeEditor = null;
64509 * @event beforeedit
64510 * Fires before cell editing is triggered. The edit event object has the following properties <br />
64511 * <ul style="padding:5px;padding-left:16px;">
64512 * <li>grid - This grid</li>
64513 * <li>record - The record being edited</li>
64514 * <li>field - The field name being edited</li>
64515 * <li>value - The value for the field being edited.</li>
64516 * <li>row - The grid row index</li>
64517 * <li>column - The grid column index</li>
64518 * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
64520 * @param {Object} e An edit event (see above for description)
64525 * Fires after a cell is edited. The edit event object has the following properties <br />
64526 * <ul style="padding:5px;padding-left:16px;">
64527 * <li>grid - This grid</li>
64528 * <li>record - The record being edited</li>
64529 * <li>field - The field name being edited</li>
64530 * <li>value - The value being set</li>
64531 * <li>originalValue - The original value for the field, before the edit.</li>
64532 * <li>row - The grid row index</li>
64533 * <li>column - The grid column index</li>
64537 grid.on('afteredit', afterEdit, this );
64539 function afterEdit(e) {
64540 // execute an XHR to send/commit data to the server, in callback do (if successful):
64544 * @param {Object} e An edit event (see above for description)
64548 * @event validateedit
64549 * Fires after a cell is edited, but before the value is set in the record. Return false
64550 * to cancel the change. The edit event object has the following properties <br />
64551 * <ul style="padding:5px;padding-left:16px;">
64552 * <li>grid - This grid</li>
64553 * <li>record - The record being edited</li>
64554 * <li>field - The field name being edited</li>
64555 * <li>value - The value being set</li>
64556 * <li>originalValue - The original value for the field, before the edit.</li>
64557 * <li>row - The grid row index</li>
64558 * <li>column - The grid column index</li>
64559 * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
64561 * Usage example showing how to remove the red triangle (dirty record indicator) from some
64562 * records (not all). By observing the grid's validateedit event, it can be cancelled if
64563 * the edit occurs on a targeted row (for example) and then setting the field's new value
64564 * in the Record directly:
64566 grid.on('validateedit', function(e) {
64567 var myTargetRow = 6;
64569 if (e.row == myTargetRow) {
64571 e.record.data[e.field] = e.value;
64575 * @param {Object} e An edit event (see above for description)
64582 initEvents : function(){
64583 Ext.grid.EditorGridPanel.superclass.initEvents.call(this);
64585 this.on("bodyscroll", this.stopEditing, this, [true]);
64586 this.on("columnresize", this.stopEditing, this, [true]);
64588 if(this.clicksToEdit == 1){
64589 this.on("cellclick", this.onCellDblClick, this);
64591 if(this.clicksToEdit == 'auto' && this.view.mainBody){
64592 this.view.mainBody.on("mousedown", this.onAutoEditClick, this);
64594 this.on("celldblclick", this.onCellDblClick, this);
64599 onCellDblClick : function(g, row, col){
64600 this.startEditing(row, col);
64604 onAutoEditClick : function(e, t){
64605 if(e.button !== 0){
64608 var row = this.view.findRowIndex(t);
64609 var col = this.view.findCellIndex(t);
64610 if(row !== false && col !== false){
64611 this.stopEditing();
64612 if(this.selModel.getSelectedCell){ // cell sm
64613 var sc = this.selModel.getSelectedCell();
64614 if(sc && sc[0] === row && sc[1] === col){
64615 this.startEditing(row, col);
64618 if(this.selModel.isSelected(row)){
64619 this.startEditing(row, col);
64626 onEditComplete : function(ed, value, startValue){
64627 this.editing = false;
64628 this.activeEditor = null;
64629 ed.un("specialkey", this.selModel.onEditorKey, this.selModel);
64631 var field = this.colModel.getDataIndex(ed.col);
64632 value = this.postEditValue(value, startValue, r, field);
64633 if(this.forceValidation === true || String(value) !== String(startValue)){
64638 originalValue: startValue,
64644 if(this.fireEvent("validateedit", e) !== false && !e.cancel && String(value) !== String(startValue)){
64645 r.set(field, e.value);
64647 this.fireEvent("afteredit", e);
64650 this.view.focusCell(ed.row, ed.col);
64654 * Starts editing the specified for the specified row/column
64655 * @param {Number} rowIndex
64656 * @param {Number} colIndex
64658 startEditing : function(row, col){
64659 this.stopEditing();
64660 if(this.colModel.isCellEditable(col, row)){
64661 this.view.ensureVisible(row, col, true);
64662 var r = this.store.getAt(row);
64663 var field = this.colModel.getDataIndex(col);
64668 value: r.data[field],
64673 if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
64674 this.editing = true;
64675 var ed = this.colModel.getCellEditor(col, row);
64680 ed.render(this.view.getEditorParent(ed));
64682 (function(){ // complex but required for focus issues in safari, ie and opera
64686 ed.on("complete", this.onEditComplete, this, {single: true});
64687 ed.on("specialkey", this.selModel.onEditorKey, this.selModel);
64689 * The currently active editor or null
64692 this.activeEditor = ed;
64693 var v = this.preEditValue(r, field);
64694 ed.startEdit(this.view.getCell(row, col).firstChild, v === undefined ? '' : v);
64695 }).defer(50, this);
64701 preEditValue : function(r, field){
64702 var value = r.data[field];
64703 return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlDecode(value) : value;
64707 postEditValue : function(value, originalValue, r, field){
64708 return this.autoEncode && typeof value == 'string' ? Ext.util.Format.htmlEncode(value) : value;
64712 * Stops any active editing
64713 * @param {Boolean} cancel (optional) True to cancel any changes
64715 stopEditing : function(cancel){
64716 if(this.activeEditor){
64717 this.activeEditor[cancel === true ? 'cancelEdit' : 'completeEdit']();
64719 this.activeEditor = null;
64722 Ext.reg('editorgrid', Ext.grid.EditorGridPanel);// private
64723 // This is a support class used internally by the Grid components
64724 Ext.grid.GridEditor = function(field, config){
64725 Ext.grid.GridEditor.superclass.constructor.call(this, field, config);
64726 field.monitorTab = false;
64729 Ext.extend(Ext.grid.GridEditor, Ext.Editor, {
64730 alignment: "tl-tl",
64733 cls: "x-small-editor x-grid-editor",
64737 * @class Ext.grid.PropertyRecord
64738 * A specific {@link Ext.data.Record} type that represents a name/value pair and is made to work with the
64739 * {@link Ext.grid.PropertyGrid}. Typically, PropertyRecords do not need to be created directly as they can be
64740 * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.PropertyGrid#source}
64741 * config property or by calling {@link Ext.grid.PropertyGrid#setSource}. However, if the need arises, these records
64742 * can also be created explicitly as shwon below. Example usage:
64744 var rec = new Ext.grid.PropertyRecord({
64746 value: new Date(Date.parse('05/26/1972'))
64748 // Add record to an already populated grid
64749 grid.store.addSorted(rec);
64752 * @param {Object} config A data object in the format: {name: [name], value: [value]}. The specified value's type
64753 * will be read automatically by the grid to determine the type of editor to use when displaying it.
64755 Ext.grid.PropertyRecord = Ext.data.Record.create([
64756 {name:'name',type:'string'}, 'value'
64760 * @class Ext.grid.PropertyStore
64761 * @extends Ext.util.Observable
64762 * A custom wrapper for the {@link Ext.grid.PropertyGrid}'s {@link Ext.data.Store}. This class handles the mapping
64763 * between the custom data source objects supported by the grid and the {@link Ext.grid.PropertyRecord} format
64764 * required for compatibility with the underlying store. Generally this class should not need to be used directly --
64765 * the grid's data should be accessed from the underlying store via the {@link #store} property.
64767 * @param {Ext.grid.Grid} grid The grid this store will be bound to
64768 * @param {Object} source The source data config object
64770 Ext.grid.PropertyStore = function(grid, source){
64772 this.store = new Ext.data.Store({
64773 recordType : Ext.grid.PropertyRecord
64775 this.store.on('update', this.onUpdate, this);
64777 this.setSource(source);
64779 Ext.grid.PropertyStore.superclass.constructor.call(this);
64781 Ext.extend(Ext.grid.PropertyStore, Ext.util.Observable, {
64782 // protected - should only be called by the grid. Use grid.setSource instead.
64783 setSource : function(o){
64785 this.store.removeAll();
64788 if(this.isEditableValue(o[k])){
64789 data.push(new Ext.grid.PropertyRecord({name: k, value: o[k]}, k));
64792 this.store.loadRecords({records: data}, {}, true);
64796 onUpdate : function(ds, record, type){
64797 if(type == Ext.data.Record.EDIT){
64798 var v = record.data.value;
64799 var oldValue = record.modified.value;
64800 if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
64801 this.source[record.id] = v;
64803 this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
64811 getProperty : function(row){
64812 return this.store.getAt(row);
64816 isEditableValue: function(val){
64817 if(Ext.isDate(val)){
64820 return !(Ext.isObject(val) || Ext.isFunction(val));
64824 setValue : function(prop, value){
64825 this.source[prop] = value;
64826 this.store.getById(prop).set('value', value);
64829 // protected - should only be called by the grid. Use grid.getSource instead.
64830 getSource : function(){
64831 return this.source;
64836 * @class Ext.grid.PropertyColumnModel
64837 * @extends Ext.grid.ColumnModel
64838 * A custom column model for the {@link Ext.grid.PropertyGrid}. Generally it should not need to be used directly.
64840 * @param {Ext.grid.Grid} grid The grid this store will be bound to
64841 * @param {Object} source The source data config object
64843 Ext.grid.PropertyColumnModel = function(grid, store){
64848 g.PropertyColumnModel.superclass.constructor.call(this, [
64849 {header: this.nameText, width:50, sortable: true, dataIndex:'name', id: 'name', menuDisabled:true},
64850 {header: this.valueText, width:50, resizable:false, dataIndex: 'value', id: 'value', menuDisabled:true}
64852 this.store = store;
64854 var bfield = new f.Field({
64855 autoCreate: {tag: 'select', children: [
64856 {tag: 'option', value: 'true', html: 'true'},
64857 {tag: 'option', value: 'false', html: 'false'}
64859 getValue : function(){
64860 return this.el.value == 'true';
64864 'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
64865 'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
64866 'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
64867 'boolean' : new g.GridEditor(bfield)
64869 this.renderCellDelegate = this.renderCell.createDelegate(this);
64870 this.renderPropDelegate = this.renderProp.createDelegate(this);
64873 Ext.extend(Ext.grid.PropertyColumnModel, Ext.grid.ColumnModel, {
64874 // private - strings used for locale support
64876 valueText : 'Value',
64877 dateFormat : 'm/j/Y',
64880 renderDate : function(dateVal){
64881 return dateVal.dateFormat(this.dateFormat);
64885 renderBool : function(bVal){
64886 return bVal ? 'true' : 'false';
64890 isCellEditable : function(colIndex, rowIndex){
64891 return colIndex == 1;
64895 getRenderer : function(col){
64897 this.renderCellDelegate : this.renderPropDelegate;
64901 renderProp : function(v){
64902 return this.getPropertyName(v);
64906 renderCell : function(val){
64908 if(Ext.isDate(val)){
64909 rv = this.renderDate(val);
64910 }else if(typeof val == 'boolean'){
64911 rv = this.renderBool(val);
64913 return Ext.util.Format.htmlEncode(rv);
64917 getPropertyName : function(name){
64918 var pn = this.grid.propertyNames;
64919 return pn && pn[name] ? pn[name] : name;
64923 getCellEditor : function(colIndex, rowIndex){
64924 var p = this.store.getProperty(rowIndex),
64926 val = p.data.value;
64927 if(this.grid.customEditors[n]){
64928 return this.grid.customEditors[n];
64930 if(Ext.isDate(val)){
64931 return this.editors.date;
64932 }else if(typeof val == 'number'){
64933 return this.editors.number;
64934 }else if(typeof val == 'boolean'){
64935 return this.editors['boolean'];
64937 return this.editors.string;
64942 destroy : function(){
64943 Ext.grid.PropertyColumnModel.superclass.destroy.call(this);
64944 for(var ed in this.editors){
64951 * @class Ext.grid.PropertyGrid
64952 * @extends Ext.grid.EditorGridPanel
64953 * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
64954 * development IDEs. Each row in the grid represents a property of some object, and the data is stored
64955 * as a set of name/value pairs in {@link Ext.grid.PropertyRecord}s. Example usage:
64957 var grid = new Ext.grid.PropertyGrid({
64958 title: 'Properties Grid',
64961 renderTo: 'grid-ct',
64963 "(name)": "My Object",
64964 "Created": new Date(Date.parse('10/15/2006')),
64965 "Available": false,
64967 "Description": "A test object"
64972 * @param {Object} config The grid config object
64974 Ext.grid.PropertyGrid = Ext.extend(Ext.grid.EditorGridPanel, {
64976 * @cfg {Object} propertyNames An object containing property name/display name pairs.
64977 * If specified, the display name will be shown in the name column instead of the property name.
64980 * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
64983 * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
64984 * the grid to support additional types of editable fields. By default, the grid supports strongly-typed editing
64985 * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
64986 * associated with a custom input control by specifying a custom editor. The name of the editor
64987 * type should correspond with the name of the property that will use the editor. Example usage:
64989 var grid = new Ext.grid.PropertyGrid({
64992 'Start Time': new Ext.grid.GridEditor(new Ext.form.TimeField({selectOnFocus:true}))
64995 'Start Time': '10:00 AM'
65001 // private config overrides
65002 enableColumnMove:false,
65004 trackMouseOver: false,
65006 enableHdMenu : false,
65012 initComponent : function(){
65013 this.customEditors = this.customEditors || {};
65014 this.lastEditRow = null;
65015 var store = new Ext.grid.PropertyStore(this);
65016 this.propStore = store;
65017 var cm = new Ext.grid.PropertyColumnModel(this, store);
65018 store.store.sort('name', 'ASC');
65021 * @event beforepropertychange
65022 * Fires before a property value changes. Handlers can return false to cancel the property change
65023 * (this will internally call {@link Ext.data.Record#reject} on the property's record).
65024 * @param {Object} source The source data object for the grid (corresponds to the same object passed in
65025 * as the {@link #source} config property).
65026 * @param {String} recordId The record's id in the data store
65027 * @param {Mixed} value The current edited property value
65028 * @param {Mixed} oldValue The original property value prior to editing
65030 'beforepropertychange',
65032 * @event propertychange
65033 * Fires after a property value has changed.
65034 * @param {Object} source The source data object for the grid (corresponds to the same object passed in
65035 * as the {@link #source} config property).
65036 * @param {String} recordId The record's id in the data store
65037 * @param {Mixed} value The current edited property value
65038 * @param {Mixed} oldValue The original property value prior to editing
65043 this.ds = store.store;
65044 Ext.grid.PropertyGrid.superclass.initComponent.call(this);
65046 this.mon(this.selModel, 'beforecellselect', function(sm, rowIndex, colIndex){
65047 if(colIndex === 0){
65048 this.startEditing.defer(200, this, [rowIndex, 1]);
65055 onRender : function(){
65056 Ext.grid.PropertyGrid.superclass.onRender.apply(this, arguments);
65058 this.getGridEl().addClass('x-props-grid');
65062 afterRender: function(){
65063 Ext.grid.PropertyGrid.superclass.afterRender.apply(this, arguments);
65065 this.setSource(this.source);
65070 * Sets the source data object containing the property data. The data object can contain one or more name/value
65071 * pairs representing all of the properties of an object to display in the grid, and this data will automatically
65072 * be loaded into the grid's {@link #store}. The values should be supplied in the proper data type if needed,
65073 * otherwise string type will be assumed. If the grid already contains data, this method will replace any
65074 * existing data. See also the {@link #source} config value. Example usage:
65077 "(name)": "My Object",
65078 "Created": new Date(Date.parse('10/15/2006')), // date type
65079 "Available": false, // boolean type
65080 "Version": .01, // decimal type
65081 "Description": "A test object"
65084 * @param {Object} source The data object
65086 setSource : function(source){
65087 this.propStore.setSource(source);
65091 * Gets the source data object containing the property data. See {@link #setSource} for details regarding the
65092 * format of the data object.
65093 * @return {Object} The data object
65095 getSource : function(){
65096 return this.propStore.getSource();
65099 Ext.reg("propertygrid", Ext.grid.PropertyGrid);
65101 * @class Ext.grid.GroupingView
\r
65102 * @extends Ext.grid.GridView
\r
65103 * Adds the ability for single level grouping to the grid. A {@link Ext.data.GroupingStore GroupingStore}
\r
65104 * must be used to enable grouping. Some grouping characteristics may also be configured at the
\r
65105 * {@link Ext.grid.Column Column level}<div class="mdetail-params"><ul>
\r
65106 * <li><code>{@link Ext.grid.Column#emptyGroupText emptyGroupText}</li>
\r
65107 * <li><code>{@link Ext.grid.Column#groupable groupable}</li>
\r
65108 * <li><code>{@link Ext.grid.Column#groupName groupName}</li>
\r
65109 * <li><code>{@link Ext.grid.Column#groupRender groupRender}</li>
\r
65111 * <p>Sample usage:</p>
\r
65113 var grid = new Ext.grid.GridPanel({
\r
65114 // A groupingStore is required for a GroupingView
\r
65115 store: new {@link Ext.data.GroupingStore}({
\r
65116 autoDestroy: true,
\r
65118 data: xg.dummyData,
\r
65119 sortInfo: {field: 'company', direction: 'ASC'},
\r
65120 {@link Ext.data.GroupingStore#groupOnSort groupOnSort}: true,
\r
65121 {@link Ext.data.GroupingStore#remoteGroup remoteGroup}: true,
\r
65122 {@link Ext.data.GroupingStore#groupField groupField}: 'industry'
\r
65124 colModel: new {@link Ext.grid.ColumnModel}({
\r
65126 {id:'company',header: 'Company', width: 60, dataIndex: 'company'},
\r
65127 // {@link Ext.grid.Column#groupable groupable}, {@link Ext.grid.Column#groupName groupName}, {@link Ext.grid.Column#groupRender groupRender} are also configurable at column level
\r
65128 {header: 'Price', renderer: Ext.util.Format.usMoney, dataIndex: 'price', {@link Ext.grid.Column#groupable groupable}: false},
\r
65129 {header: 'Change', dataIndex: 'change', renderer: Ext.util.Format.usMoney},
\r
65130 {header: 'Industry', dataIndex: 'industry'},
\r
65131 {header: 'Last Updated', renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
\r
65135 menuDisabled: false,
\r
65140 view: new Ext.grid.GroupingView({
\r
65141 {@link Ext.grid.GridView#forceFit forceFit}: true,
\r
65142 // custom grouping text template to display the number of items per group
\r
65143 {@link #groupTextTpl}: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
\r
65149 collapsible: true,
\r
65150 animCollapse: false,
\r
65151 title: 'Grouping Example',
\r
65152 iconCls: 'icon-grid',
\r
65153 renderTo: document.body
\r
65157 * @param {Object} config
\r
65159 Ext.grid.GroupingView = Ext.extend(Ext.grid.GridView, {
\r
65162 * @cfg {String} groupByText Text displayed in the grid header menu for grouping by a column
\r
65163 * (defaults to 'Group By This Field').
\r
65165 groupByText : 'Group By This Field',
\r
65167 * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
\r
65168 * (defaults to 'Show in Groups').
\r
65170 showGroupsText : 'Show in Groups',
\r
65172 * @cfg {Boolean} hideGroupedColumn <tt>true</tt> to hide the column that is currently grouped (defaults to <tt>false</tt>)
\r
65174 hideGroupedColumn : false,
\r
65176 * @cfg {Boolean} showGroupName If <tt>true</tt> will display a prefix plus a ': ' before the group field value
\r
65177 * in the group header line. The prefix will consist of the <tt><b>{@link Ext.grid.Column#groupName groupName}</b></tt>
\r
65178 * (or the configured <tt><b>{@link Ext.grid.Column#header header}</b></tt> if not provided) configured in the
\r
65179 * {@link Ext.grid.Column} for each set of grouped rows (defaults to <tt>true</tt>).
\r
65181 showGroupName : true,
\r
65183 * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
\r
65185 startCollapsed : false,
\r
65187 * @cfg {Boolean} enableGrouping <tt>false</tt> to disable grouping functionality (defaults to <tt>true</tt>)
\r
65189 enableGrouping : true,
\r
65191 * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the column menu (defaults to <tt>true</tt>)
\r
65193 enableGroupingMenu : true,
\r
65195 * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
\r
65197 enableNoGroups : true,
\r
65199 * @cfg {String} emptyGroupText The text to display when there is an empty group value (defaults to <tt>'(None)'</tt>).
\r
65200 * May also be specified per column, see {@link Ext.grid.Column}.{@link Ext.grid.Column#emptyGroupText emptyGroupText}.
\r
65202 emptyGroupText : '(None)',
\r
65204 * @cfg {Boolean} ignoreAdd <tt>true</tt> to skip refreshing the view when new rows are added (defaults to <tt>false</tt>)
\r
65206 ignoreAdd : false,
\r
65208 * @cfg {String} groupTextTpl The template used to render the group header (defaults to <tt>'{text}'</tt>).
\r
65209 * This is used to format an object which contains the following properties:
\r
65210 * <div class="mdetail-params"><ul>
\r
65211 * <li><b>group</b> : String<p class="sub-desc">The <i>rendered</i> value of the group field.
\r
65212 * By default this is the unchanged value of the group field. If a <tt><b>{@link Ext.grid.Column#groupRenderer groupRenderer}</b></tt>
\r
65213 * is specified, it is the result of a call to that function.</p></li>
\r
65214 * <li><b>gvalue</b> : Object<p class="sub-desc">The <i>raw</i> value of the group field.</p></li>
\r
65215 * <li><b>text</b> : String<p class="sub-desc">The configured header (as described in <tt>{@link #showGroupName})</tt>
\r
65216 * if <tt>{@link #showGroupName}</tt> is <tt>true</tt>) plus the <i>rendered</i> group field value.</p></li>
\r
65217 * <li><b>groupId</b> : String<p class="sub-desc">A unique, generated ID which is applied to the
\r
65218 * View Element which contains the group.</p></li>
\r
65219 * <li><b>startRow</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
65220 * <li><b>rs</b> : Array<p class="sub-desc">Contains a single element: The Record providing the data
\r
65221 * for the row which caused group change.</p></li>
\r
65222 * <li><b>cls</b> : String<p class="sub-desc">The generated class name string to apply to the group header Element.</p></li>
\r
65223 * <li><b>style</b> : String<p class="sub-desc">The inline style rules to apply to the group header Element.</p></li>
\r
65224 * </ul></div></p>
\r
65225 * See {@link Ext.XTemplate} for information on how to format data using a template. Possible usage:<pre><code>
\r
65226 var grid = new Ext.grid.GridPanel({
\r
65228 view: new Ext.grid.GroupingView({
\r
65229 groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
\r
65234 groupTextTpl : '{text}',
\r
65236 * @cfg {Function} groupRenderer This property must be configured in the {@link Ext.grid.Column} for
\r
65244 initTemplates : function(){
\r
65245 Ext.grid.GroupingView.superclass.initTemplates.call(this);
\r
65248 var sm = this.grid.getSelectionModel();
\r
65249 sm.on(sm.selectRow ? 'beforerowselect' : 'beforecellselect',
\r
65250 this.onBeforeRowSelect, this);
\r
65252 if(!this.startGroup){
\r
65253 this.startGroup = new Ext.XTemplate(
\r
65254 '<div id="{groupId}" class="x-grid-group {cls}">',
\r
65255 '<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div class="x-grid-group-title">', this.groupTextTpl ,'</div></div>',
\r
65256 '<div id="{groupId}-bd" class="x-grid-group-body">'
\r
65259 this.startGroup.compile();
\r
65260 this.endGroup = '</div></div>';
\r
65264 findGroup : function(el){
\r
65265 return Ext.fly(el).up('.x-grid-group', this.mainBody.dom);
\r
65269 getGroups : function(){
\r
65270 return this.hasRows() ? this.mainBody.dom.childNodes : [];
\r
65274 onAdd : function(){
\r
65275 if(this.enableGrouping && !this.ignoreAdd){
\r
65276 var ss = this.getScrollState();
\r
65278 this.restoreScroll(ss);
\r
65279 }else if(!this.enableGrouping){
\r
65280 Ext.grid.GroupingView.superclass.onAdd.apply(this, arguments);
\r
65285 onRemove : function(ds, record, index, isUpdate){
\r
65286 Ext.grid.GroupingView.superclass.onRemove.apply(this, arguments);
\r
65287 var g = document.getElementById(record._groupId);
\r
65288 if(g && g.childNodes[1].childNodes.length < 1){
\r
65289 Ext.removeNode(g);
\r
65291 this.applyEmptyText();
\r
65295 refreshRow : function(record){
\r
65296 if(this.ds.getCount()==1){
\r
65299 this.isUpdating = true;
\r
65300 Ext.grid.GroupingView.superclass.refreshRow.apply(this, arguments);
\r
65301 this.isUpdating = false;
\r
65306 beforeMenuShow : function(){
\r
65307 var item, items = this.hmenu.items, disabled = this.cm.config[this.hdCtxIndex].groupable === false;
\r
65308 if((item = items.get('groupBy'))){
\r
65309 item.setDisabled(disabled);
\r
65311 if((item = items.get('showGroups'))){
\r
65312 item.setDisabled(disabled);
\r
65313 item.setChecked(!!this.getGroupField(), true);
\r
65318 renderUI : function(){
\r
65319 Ext.grid.GroupingView.superclass.renderUI.call(this);
\r
65320 this.mainBody.on('mousedown', this.interceptMouse, this);
\r
65322 if(this.enableGroupingMenu && this.hmenu){
\r
65323 this.hmenu.add('-',{
\r
65324 itemId:'groupBy',
\r
65325 text: this.groupByText,
\r
65326 handler: this.onGroupByClick,
\r
65328 iconCls:'x-group-by-icon'
\r
65330 if(this.enableNoGroups){
\r
65332 itemId:'showGroups',
\r
65333 text: this.showGroupsText,
\r
65335 checkHandler: this.onShowGroupsClick,
\r
65339 this.hmenu.on('beforeshow', this.beforeMenuShow, this);
\r
65344 onGroupByClick : function(){
\r
65345 this.grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
\r
65346 this.beforeMenuShow(); // Make sure the checkboxes get properly set when changing groups
\r
65350 onShowGroupsClick : function(mi, checked){
\r
65352 this.onGroupByClick();
\r
65354 this.grid.store.clearGrouping();
\r
65359 * Toggles the specified group if no value is passed, otherwise sets the expanded state of the group to the value passed.
\r
65360 * @param {String} groupId The groupId assigned to the group (see getGroupId)
\r
65361 * @param {Boolean} expanded (optional)
\r
65363 toggleGroup : function(group, expanded){
\r
65364 this.grid.stopEditing(true);
\r
65365 group = Ext.getDom(group);
\r
65366 var gel = Ext.fly(group);
\r
65367 expanded = expanded !== undefined ?
\r
65368 expanded : gel.hasClass('x-grid-group-collapsed');
\r
65370 this.state[gel.dom.id] = expanded;
\r
65371 gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed');
\r
65375 * Toggles all groups if no value is passed, otherwise sets the expanded state of all groups to the value passed.
\r
65376 * @param {Boolean} expanded (optional)
\r
65378 toggleAllGroups : function(expanded){
\r
65379 var groups = this.getGroups();
\r
65380 for(var i = 0, len = groups.length; i < len; i++){
\r
65381 this.toggleGroup(groups[i], expanded);
\r
65386 * Expands all grouped rows.
\r
65388 expandAllGroups : function(){
\r
65389 this.toggleAllGroups(true);
\r
65393 * Collapses all grouped rows.
\r
65395 collapseAllGroups : function(){
\r
65396 this.toggleAllGroups(false);
\r
65400 interceptMouse : function(e){
\r
65401 var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
\r
65404 this.toggleGroup(hd.parentNode);
\r
65409 getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){
\r
65410 var g = groupRenderer ? groupRenderer(v, {}, r, rowIndex, colIndex, ds) : String(v);
\r
65412 g = this.cm.config[colIndex].emptyGroupText || this.emptyGroupText;
\r
65418 getGroupField : function(){
\r
65419 return this.grid.store.getGroupState();
\r
65423 afterRender : function(){
\r
65424 Ext.grid.GroupingView.superclass.afterRender.call(this);
\r
65425 if(this.grid.deferRowRender){
\r
65426 this.updateGroupWidths();
\r
65431 renderRows : function(){
\r
65432 var groupField = this.getGroupField();
\r
65433 var eg = !!groupField;
\r
65434 // if they turned off grouping and the last grouped field is hidden
\r
65435 if(this.hideGroupedColumn) {
\r
65436 var colIndex = this.cm.findColumnIndex(groupField);
\r
65437 if(!eg && this.lastGroupField !== undefined) {
\r
65438 this.mainBody.update('');
\r
65439 this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField), false);
\r
65440 delete this.lastGroupField;
\r
65441 }else if (eg && this.lastGroupField === undefined) {
\r
65442 this.lastGroupField = groupField;
\r
65443 this.cm.setHidden(colIndex, true);
\r
65444 }else if (eg && this.lastGroupField !== undefined && groupField !== this.lastGroupField) {
\r
65445 this.mainBody.update('');
\r
65446 var oldIndex = this.cm.findColumnIndex(this.lastGroupField);
\r
65447 this.cm.setHidden(oldIndex, false);
\r
65448 this.lastGroupField = groupField;
\r
65449 this.cm.setHidden(colIndex, true);
\r
65452 return Ext.grid.GroupingView.superclass.renderRows.apply(
\r
65453 this, arguments);
\r
65457 doRender : function(cs, rs, ds, startRow, colCount, stripe){
\r
65458 if(rs.length < 1){
\r
65461 var groupField = this.getGroupField(),
\r
65462 colIndex = this.cm.findColumnIndex(groupField),
\r
65465 this.enableGrouping = !!groupField;
\r
65467 if(!this.enableGrouping || this.isUpdating){
\r
65468 return Ext.grid.GroupingView.superclass.doRender.apply(
\r
65469 this, arguments);
\r
65471 var gstyle = 'width:'+this.getTotalWidth()+';';
\r
65473 var gidPrefix = this.grid.getGridEl().id;
\r
65474 var cfg = this.cm.config[colIndex];
\r
65475 var groupRenderer = cfg.groupRenderer || cfg.renderer;
\r
65476 var prefix = this.showGroupName ?
\r
65477 (cfg.groupName || cfg.header)+': ' : '';
\r
65479 var groups = [], curGroup, i, len, gid;
\r
65480 for(i = 0, len = rs.length; i < len; i++){
\r
65481 var rowIndex = startRow + i,
\r
65483 gvalue = r.data[groupField];
\r
65485 g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
\r
65486 if(!curGroup || curGroup.group != g){
\r
65487 gid = gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(g);
\r
65488 // if state is defined use it, however state is in terms of expanded
\r
65489 // so negate it, otherwise use the default.
\r
65490 var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
\r
65491 var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
\r
65495 text: prefix + g,
\r
65497 startRow: rowIndex,
\r
65502 groups.push(curGroup);
\r
65504 curGroup.rs.push(r);
\r
65506 r._groupId = gid;
\r
65510 for(i = 0, len = groups.length; i < len; i++){
\r
65512 this.doGroupStart(buf, g, cs, ds, colCount);
\r
65513 buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call(
\r
65514 this, cs, g.rs, ds, g.startRow, colCount, stripe);
\r
65516 this.doGroupEnd(buf, g, cs, ds, colCount);
\r
65518 return buf.join('');
\r
65522 * Dynamically tries to determine the groupId of a specific value
\r
65523 * @param {String} value
\r
65524 * @return {String} The group id
\r
65526 getGroupId : function(value){
\r
65527 var gidPrefix = this.grid.getGridEl().id;
\r
65528 var groupField = this.getGroupField();
\r
65529 var colIndex = this.cm.findColumnIndex(groupField);
\r
65530 var cfg = this.cm.config[colIndex];
\r
65531 var groupRenderer = cfg.groupRenderer || cfg.renderer;
\r
65532 var gtext = this.getGroup(value, {data:{}}, groupRenderer, 0, colIndex, this.ds);
\r
65533 return gidPrefix + '-gp-' + groupField + '-' + Ext.util.Format.htmlEncode(value);
\r
65537 doGroupStart : function(buf, g, cs, ds, colCount){
\r
65538 buf[buf.length] = this.startGroup.apply(g);
\r
65542 doGroupEnd : function(buf, g, cs, ds, colCount){
\r
65543 buf[buf.length] = this.endGroup;
\r
65547 getRows : function(){
\r
65548 if(!this.enableGrouping){
\r
65549 return Ext.grid.GroupingView.superclass.getRows.call(this);
\r
65552 var g, gs = this.getGroups();
\r
65553 for(var i = 0, len = gs.length; i < len; i++){
\r
65554 g = gs[i].childNodes[1].childNodes;
\r
65555 for(var j = 0, jlen = g.length; j < jlen; j++){
\r
65556 r[r.length] = g[j];
\r
65563 updateGroupWidths : function(){
\r
65564 if(!this.enableGrouping || !this.hasRows()){
\r
65567 var tw = Math.max(this.cm.getTotalWidth(), this.el.dom.offsetWidth-this.scrollOffset) +'px';
\r
65568 var gs = this.getGroups();
\r
65569 for(var i = 0, len = gs.length; i < len; i++){
\r
65570 gs[i].firstChild.style.width = tw;
\r
65575 onColumnWidthUpdated : function(col, w, tw){
\r
65576 Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this, col, w, tw);
\r
65577 this.updateGroupWidths();
\r
65581 onAllColumnWidthsUpdated : function(ws, tw){
\r
65582 Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this, ws, tw);
\r
65583 this.updateGroupWidths();
\r
65587 onColumnHiddenUpdated : function(col, hidden, tw){
\r
65588 Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this, col, hidden, tw);
\r
65589 this.updateGroupWidths();
\r
65593 onLayout : function(){
\r
65594 this.updateGroupWidths();
\r
65598 onBeforeRowSelect : function(sm, rowIndex){
\r
65599 if(!this.enableGrouping){
\r
65602 var row = this.getRow(rowIndex);
\r
65603 if(row && !row.offsetParent){
\r
65604 var g = this.findGroup(row);
\r
65605 this.toggleGroup(g, true);
\r
65610 Ext.grid.GroupingView.GROUP_ID = 1000;