3 * Copyright(c) 2006-2009 Ext JS, LLC
5 * http://www.extjs.com/license
8 * @class Ext.DomHelper
\r
9 * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
\r
10 * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
\r
11 * from your DOM building code.</p>
\r
13 * <p><b><u>DomHelper element specification object</u></b></p>
\r
14 * <p>A specification object is used when creating elements. Attributes of this object
\r
15 * are assumed to be element attributes, except for 4 special attributes:
\r
16 * <div class="mdetail-params"><ul>
\r
17 * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
\r
18 * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
\r
19 * same kind of element definition objects to be created and appended. These can be nested
\r
20 * as deep as you want.</div></li>
\r
21 * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
\r
22 * This will end up being either the "class" attribute on a HTML fragment or className
\r
23 * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
\r
24 * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
\r
27 * <p><b><u>Insertion methods</u></b></p>
\r
28 * <p>Commonly used insertion methods:
\r
29 * <div class="mdetail-params"><ul>
\r
30 * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
\r
31 * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
\r
32 * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
\r
33 * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
\r
34 * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
\r
35 * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
\r
38 * <p><b><u>Example</u></b></p>
\r
39 * <p>This is an example, where an unordered list with 3 children items is appended to an existing
\r
40 * element with id <tt>'my-div'</tt>:<br>
\r
42 var dh = Ext.DomHelper; // create shorthand alias
\r
43 // specification object
\r
48 // append children after creating
\r
49 children: [ // may also specify 'cn' instead of 'children'
\r
50 {tag: 'li', id: 'item0', html: 'List Item 0'},
\r
51 {tag: 'li', id: 'item1', html: 'List Item 1'},
\r
52 {tag: 'li', id: 'item2', html: 'List Item 2'}
\r
55 var list = dh.append(
\r
56 'my-div', // the context element 'my-div' can either be the id or the actual node
\r
57 spec // the specification object
\r
60 * <p>Element creation specification parameters in this class may also be passed as an Array of
\r
61 * specification objects. This can be used to insert multiple sibling nodes into an existing
\r
62 * container very efficiently. For example, to add more list items to the example above:<pre><code>
\r
63 dh.append('my-ul', [
\r
64 {tag: 'li', id: 'item3', html: 'List Item 3'},
\r
65 {tag: 'li', id: 'item4', html: 'List Item 4'}
\r
69 * <p><b><u>Templating</u></b></p>
\r
70 * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
\r
71 * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
\r
72 * insert new elements. Revisiting the example above, we could utilize templating this time:
\r
75 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
\r
77 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
\r
79 for(var i = 0; i < 5, i++){
\r
80 tpl.append(list, [i]); // use template to append to the actual node
\r
83 * <p>An example using a template:<pre><code>
\r
84 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
\r
86 var tpl = new Ext.DomHelper.createTemplate(html);
\r
87 tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack's Site"]);
\r
88 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
\r
91 * <p>The same example using named parameters:<pre><code>
\r
92 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
\r
94 var tpl = new Ext.DomHelper.createTemplate(html);
\r
95 tpl.append('blog-roll', {
\r
97 url: 'http://www.jackslocum.com/',
\r
98 text: "Jack's Site"
\r
100 tpl.append('blog-roll', {
\r
102 url: 'http://www.dustindiaz.com/',
\r
103 text: "Dustin's Site"
\r
105 * </code></pre></p>
\r
107 * <p><b><u>Compiling Templates</u></b></p>
\r
108 * <p>Templates are applied using regular expressions. The performance is great, but if
\r
109 * you are adding a bunch of DOM elements using the same template, you can increase
\r
110 * performance even further by {@link Ext.Template#compile "compiling"} the template.
\r
111 * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
\r
112 * broken up at the different variable points and a dynamic function is created and eval'ed.
\r
113 * The generated function performs string concatenation of these parts and the passed
\r
114 * variables instead of using regular expressions.
\r
116 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
\r
118 var tpl = new Ext.DomHelper.createTemplate(html);
\r
121 //... use template like normal
\r
122 * </code></pre></p>
\r
124 * <p><b><u>Performance Boost</u></b></p>
\r
125 * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
\r
126 * of DOM can significantly boost performance.</p>
\r
127 * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
\r
128 * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
\r
129 * results in the creation of a text node. Usage:</p>
\r
131 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
\r
135 Ext.DomHelper = function(){
\r
136 var tempTableEl = null,
\r
137 emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
\r
138 tableRe = /^table|tbody|tr|td$/i,
\r
140 // kill repeat to save bytes
\r
141 afterbegin = 'afterbegin',
\r
142 afterend = 'afterend',
\r
143 beforebegin = 'beforebegin',
\r
144 beforeend = 'beforeend',
\r
147 tbs = ts+'<tbody>',
\r
148 tbe = '</tbody>'+te,
\r
149 trs = tbs + '<tr>',
\r
153 function doInsert(el, o, returnElement, pos, sibling, append){
\r
154 var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
\r
155 return returnElement ? Ext.get(newNode, true) : newNode;
\r
158 // build as innerHTML where available
\r
159 function createHtml(o){
\r
167 if(Ext.isString(o)){
\r
169 } else if (Ext.isArray(o)) {
\r
170 Ext.each(o, function(v) {
\r
171 b += createHtml(v);
\r
174 b += '<' + (o.tag = o.tag || 'div');
\r
175 Ext.iterate(o, function(attr, val){
\r
176 if(!/tag|children|cn|html$/i.test(attr)){
\r
177 if (Ext.isObject(val)) {
\r
178 b += ' ' + attr + '="';
\r
179 Ext.iterate(val, function(key, keyVal){
\r
180 b += key + ':' + keyVal + ';';
\r
184 b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
\r
188 // Now either just close the tag or try to add children and close the tag.
\r
189 if (emptyTags.test(o.tag)) {
\r
193 if ((cn = o.children || o.cn)) {
\r
194 b += createHtml(cn);
\r
198 b += '</' + o.tag + '>';
\r
204 function ieTable(depth, s, h, e){
\r
205 tempTableEl.innerHTML = [s, h, e].join('');
\r
209 while(++i < depth){
\r
210 el = el.firstChild;
\r
212 // If the result is multiple siblings, then encapsulate them into one fragment.
\r
213 if(ns = el.nextSibling){
\r
214 var df = document.createDocumentFragment();
\r
216 ns = el.nextSibling;
\r
217 df.appendChild(el);
\r
227 * Nasty code for IE's broken table implementation
\r
229 function insertIntoTable(tag, where, el, html) {
\r
233 tempTableEl = tempTableEl || document.createElement('div');
\r
235 if(tag == 'td' && (where == afterbegin || where == beforeend) ||
\r
236 !/td|tr|tbody/i.test(tag) && (where == beforebegin || where == afterend)) {
\r
239 before = where == beforebegin ? el :
\r
240 where == afterend ? el.nextSibling :
\r
241 where == afterbegin ? el.firstChild : null;
\r
243 if (where == beforebegin || where == afterend) {
\r
244 el = el.parentNode;
\r
247 if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
\r
248 node = ieTable(4, trs, html, tre);
\r
249 } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
\r
250 (tag == 'tr' && (where == beforebegin || where == afterend))) {
\r
251 node = ieTable(3, tbs, html, tbe);
\r
253 node = ieTable(2, ts, html, te);
\r
255 el.insertBefore(node, before);
\r
262 * Returns the markup for the passed Element(s) config.
\r
263 * @param {Object} o The DOM object spec (and children)
\r
266 markup : function(o){
\r
267 return createHtml(o);
\r
271 * Applies a style specification to an element.
\r
272 * @param {String/HTMLElement} el The element to apply styles to
\r
273 * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
\r
274 * a function which returns such a specification.
\r
276 applyStyles : function(el, styles){
\r
283 if(Ext.isFunction(styles)){
\r
284 styles = styles.call();
\r
286 if(Ext.isString(styles)){
\r
287 styles = styles.trim().split(/\s*(?::|;)\s*/);
\r
288 for(len = styles.length; i < len;){
\r
289 el.setStyle(styles[i++], styles[i++]);
\r
291 }else if (Ext.isObject(styles)){
\r
292 el.setStyle(styles);
\r
298 * Inserts an HTML fragment into the DOM.
\r
299 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
\r
300 * @param {HTMLElement} el The context element
\r
301 * @param {String} html The HTML fragment
\r
302 * @return {HTMLElement} The new node
\r
304 insertHtml : function(where, el, html){
\r
313 where = where.toLowerCase();
\r
314 // add these here because they are used in both branches of the condition.
\r
315 hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
\r
316 hash[afterend] = ['AfterEnd', 'nextSibling'];
\r
318 if (el.insertAdjacentHTML) {
\r
319 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
\r
322 // add these two to the hash.
\r
323 hash[afterbegin] = ['AfterBegin', 'firstChild'];
\r
324 hash[beforeend] = ['BeforeEnd', 'lastChild'];
\r
325 if ((hashVal = hash[where])) {
\r
326 el.insertAdjacentHTML(hashVal[0], html);
\r
327 return el[hashVal[1]];
\r
330 range = el.ownerDocument.createRange();
\r
331 setStart = 'setStart' + (/end/i.test(where) ? 'After' : 'Before');
\r
333 range[setStart](el);
\r
334 frag = range.createContextualFragment(html);
\r
335 el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
\r
336 return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
\r
338 rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
\r
339 if (el.firstChild) {
\r
340 range[setStart](el[rangeEl]);
\r
341 frag = range.createContextualFragment(html);
\r
342 if(where == afterbegin){
\r
343 el.insertBefore(frag, el.firstChild);
\r
345 el.appendChild(frag);
\r
348 el.innerHTML = html;
\r
350 return el[rangeEl];
\r
353 throw 'Illegal insertion point -> "' + where + '"';
\r
357 * Creates new DOM element(s) and inserts them before el.
\r
358 * @param {Mixed} el The context element
\r
359 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
360 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
361 * @return {HTMLElement/Ext.Element} The new node
\r
363 insertBefore : function(el, o, returnElement){
\r
364 return doInsert(el, o, returnElement, beforebegin);
\r
368 * Creates new DOM element(s) and inserts them after el.
\r
369 * @param {Mixed} el The context element
\r
370 * @param {Object} o The DOM object spec (and children)
\r
371 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
372 * @return {HTMLElement/Ext.Element} The new node
\r
374 insertAfter : function(el, o, returnElement){
\r
375 return doInsert(el, o, returnElement, afterend, 'nextSibling');
\r
379 * Creates new DOM element(s) and inserts them as the first child of el.
\r
380 * @param {Mixed} el The context element
\r
381 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
382 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
383 * @return {HTMLElement/Ext.Element} The new node
\r
385 insertFirst : function(el, o, returnElement){
\r
386 return doInsert(el, o, returnElement, afterbegin, 'firstChild');
\r
390 * Creates new DOM element(s) and appends them to el.
\r
391 * @param {Mixed} el The context element
\r
392 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
393 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
394 * @return {HTMLElement/Ext.Element} The new node
\r
396 append : function(el, o, returnElement){
\r
397 return doInsert(el, o, returnElement, beforeend, '', true);
\r
401 * Creates new DOM element(s) and overwrites the contents of el with them.
\r
402 * @param {Mixed} el The context element
\r
403 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
404 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
405 * @return {HTMLElement/Ext.Element} The new node
\r
407 overwrite : function(el, o, returnElement){
\r
408 el = Ext.getDom(el);
\r
409 el.innerHTML = createHtml(o);
\r
410 return returnElement ? Ext.get(el.firstChild) : el.firstChild;
\r
413 createHtml : createHtml
\r
417 * @class Ext.DomHelper
\r
419 Ext.apply(Ext.DomHelper,
\r
422 afterbegin = 'afterbegin',
\r
423 afterend = 'afterend',
\r
424 beforebegin = 'beforebegin',
\r
425 beforeend = 'beforeend';
\r
428 function doInsert(el, o, returnElement, pos, sibling, append){
\r
429 el = Ext.getDom(el);
\r
432 newNode = createDom(o, null);
\r
434 el.appendChild(newNode);
\r
436 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
\r
439 newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
\r
441 return returnElement ? Ext.get(newNode, true) : newNode;
\r
446 function createDom(o, parentNode){
\r
454 if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
\r
455 el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
\r
456 Ext.each(o, function(v) {
\r
459 } else if (Ext.isString(o)) { // Allow a string as a child spec.
\r
460 el = doc.createTextNode(o);
\r
462 el = doc.createElement( o.tag || 'div' );
\r
463 useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
\r
464 Ext.iterate(o, function(attr, val){
\r
465 if(!/tag|children|cn|html|style/.test(attr)){
\r
467 el.className = val;
\r
470 el.setAttribute(attr, val);
\r
477 Ext.DomHelper.applyStyles(el, o.style);
\r
479 if ((cn = o.children || o.cn)) {
\r
481 } else if (o.html) {
\r
482 el.innerHTML = o.html;
\r
486 parentNode.appendChild(el);
\r
493 * Creates a new Ext.Template from the DOM object spec.
\r
494 * @param {Object} o The DOM object spec (and children)
\r
495 * @return {Ext.Template} The new template
\r
497 createTemplate : function(o){
\r
498 var html = Ext.DomHelper.createHtml(o);
\r
499 return new Ext.Template(html);
\r
502 /** True to force the use of DOM instead of html fragments @type Boolean */
\r
506 * Creates new DOM element(s) and inserts them before el.
\r
507 * @param {Mixed} el The context element
\r
508 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
509 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
510 * @return {HTMLElement/Ext.Element} The new node
\r
513 insertBefore : function(el, o, returnElement){
\r
514 return doInsert(el, o, returnElement, beforebegin);
\r
518 * Creates new DOM element(s) and inserts them after el.
\r
519 * @param {Mixed} el The context element
\r
520 * @param {Object} o The DOM object spec (and children)
\r
521 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
522 * @return {HTMLElement/Ext.Element} The new node
\r
525 insertAfter : function(el, o, returnElement){
\r
526 return doInsert(el, o, returnElement, afterend, 'nextSibling');
\r
530 * Creates new DOM element(s) and inserts them as the first child of el.
\r
531 * @param {Mixed} el The context element
\r
532 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
533 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
534 * @return {HTMLElement/Ext.Element} The new node
\r
537 insertFirst : function(el, o, returnElement){
\r
538 return doInsert(el, o, returnElement, afterbegin, 'firstChild');
\r
542 * Creates new DOM element(s) and appends them to el.
\r
543 * @param {Mixed} el The context element
\r
544 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
545 * @param {Boolean} returnElement (optional) true to return a Ext.Element
\r
546 * @return {HTMLElement/Ext.Element} The new node
\r
549 append: function(el, o, returnElement){
\r
550 return doInsert(el, o, returnElement, beforeend, '', true);
\r
554 * Creates new DOM element(s) without inserting them to the document.
\r
555 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
\r
556 * @return {HTMLElement} The new uninserted node
\r
558 createDom: createDom
\r
562 * @class Ext.Template
563 * <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
564 * for greater performance.</p>
565 * <p>For example usage {@link #Template see the constructor}.</p>
568 * An instance of this class may be created by passing to the constructor either
569 * a single argument, or multiple arguments:
570 * <div class="mdetail-params"><ul>
571 * <li><b>single argument</b> : String/Array
572 * <div class="sub-desc">
573 * The single argument may be either a String or an Array:<ul>
574 * <li><tt>String</tt> : </li><pre><code>
575 var t = new Ext.Template("<div>Hello {0}.</div>");
576 t.{@link #append}('some-element', ['foo']);
578 * <li><tt>Array</tt> : </li>
579 * An Array will be combined with <code>join('')</code>.
581 var t = new Ext.Template([
582 '<div name="{id}">',
583 '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
586 t.{@link #compile}();
587 t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
590 * <li><b>multiple arguments</b> : String, Object, Array, ...
591 * <div class="sub-desc">
592 * Multiple arguments will be combined with <code>join('')</code>.
594 var t = new Ext.Template(
595 '<div name="{id}">',
596 '<span class="{cls}">{name} {value}</span>',
598 // a configuration object:
600 compiled: true, // {@link #compile} immediately
601 disableFormats: true // See Notes below.
605 * <p><b>Notes</b>:</p>
606 * <div class="mdetail-params"><ul>
607 * <li>Formatting and <code>disableFormats</code> are not applicable for Ext Core.</li>
608 * <li>For a list of available format functions, see {@link Ext.util.Format}.</li>
609 * <li><code>disableFormats</code> reduces <code>{@link #apply}</code> time
610 * when no formatting is required.</li>
614 * @param {Mixed} config
616 Ext.Template = function(html){
621 if (Ext.isArray(html)) {
622 html = html.join("");
623 } else if (a.length > 1) {
624 Ext.each(a, function(v) {
625 if (Ext.isObject(v)) {
637 * @cfg {Boolean} compiled Specify <tt>true</tt> to compile the template
638 * immediately (see <code>{@link #compile}</code>).
639 * Defaults to <tt>false</tt>.
645 Ext.Template.prototype = {
647 * @cfg {RegExp} re The regular expression used to match template variables.
648 * Defaults to:<pre><code>
649 * re : /\{([\w-]+)\}/g // for Ext Core
650 * re : /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g // for Ext JS
653 re : /\{([\w-]+)\}/g,
655 * See <code>{@link #re}</code>.
661 * Returns an HTML fragment of this template with the specified <code>values</code> applied.
662 * @param {Object/Array} values
663 * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
664 * or an object (i.e. <code>{foo: 'bar'}</code>).
665 * @return {String} The HTML fragment
667 applyTemplate : function(values){
671 me.compiled(values) :
672 me.html.replace(me.re, function(m, name){
673 return values[name] !== undefined ? values[name] : "";
678 * Sets the HTML used as the template and optionally compiles it.
679 * @param {String} html
680 * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
681 * @return {Ext.Template} this
683 set : function(html, compile){
687 return compile ? me.compile() : me;
691 * Compiles the template into an internal function, eliminating the RegEx overhead.
692 * @return {Ext.Template} this
694 compile : function(){
696 sep = Ext.isGecko ? "+" : ",";
698 function fn(m, name){
699 name = "values['" + name + "']";
700 return "'"+ sep + '(' + name + " == undefined ? '' : " + name + ')' + sep + "'";
703 eval("this.compiled = function(values){ return " + (Ext.isGecko ? "'" : "['") +
704 me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
705 (Ext.isGecko ? "';};" : "'].join('');};"));
710 * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
711 * @param {Mixed} el The context element
712 * @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'})
713 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
714 * @return {HTMLElement/Ext.Element} The new node or Element
716 insertFirst: function(el, values, returnElement){
717 return this.doInsert('afterBegin', el, values, returnElement);
721 * Applies the supplied values to the template and inserts the new node(s) before el.
722 * @param {Mixed} el The context element
723 * @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'})
724 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
725 * @return {HTMLElement/Ext.Element} The new node or Element
727 insertBefore: function(el, values, returnElement){
728 return this.doInsert('beforeBegin', el, values, returnElement);
732 * Applies the supplied values to the template and inserts the new node(s) after el.
733 * @param {Mixed} el The context element
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'})
735 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
736 * @return {HTMLElement/Ext.Element} The new node or Element
738 insertAfter : function(el, values, returnElement){
739 return this.doInsert('afterEnd', el, values, returnElement);
743 * Applies the supplied <code>values</code> to the template and appends
744 * the new node(s) to the specified <code>el</code>.
745 * <p>For example usage {@link #Template see the constructor}.</p>
746 * @param {Mixed} el The context element
747 * @param {Object/Array} values
748 * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
749 * or an object (i.e. <code>{foo: 'bar'}</code>).
750 * @param {Boolean} returnElement (optional) true to return an Ext.Element (defaults to undefined)
751 * @return {HTMLElement/Ext.Element} The new node or Element
753 append : function(el, values, returnElement){
754 return this.doInsert('beforeEnd', el, values, returnElement);
757 doInsert : function(where, el, values, returnEl){
759 var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
760 return returnEl ? Ext.get(newNode, true) : newNode;
764 * Applies the supplied values to the template and overwrites the content of el with the new node(s).
765 * @param {Mixed} el The context element
766 * @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'})
767 * @param {Boolean} returnElement (optional) true to return a Ext.Element (defaults to undefined)
768 * @return {HTMLElement/Ext.Element} The new node or Element
770 overwrite : function(el, values, returnElement){
772 el.innerHTML = this.applyTemplate(values);
773 return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
777 * Alias for {@link #applyTemplate}
778 * Returns an HTML fragment of this template with the specified <code>values</code> applied.
779 * @param {Object/Array} values
780 * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
781 * or an object (i.e. <code>{foo: 'bar'}</code>).
782 * @return {String} The HTML fragment
783 * @member Ext.Template
786 Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate;
789 * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
790 * @param {String/HTMLElement} el A DOM element or its id
791 * @param {Object} config A configuration object
792 * @return {Ext.Template} The created template
795 Ext.Template.from = function(el, config){
797 return new Ext.Template(el.value || el.innerHTML, config || '');
799 * @class Ext.Template
\r
801 Ext.apply(Ext.Template.prototype, {
\r
803 * @cfg {Boolean} disableFormats Specify <tt>true</tt> to disable format
\r
804 * functions in the template. If the template does not contain
\r
805 * {@link Ext.util.Format format functions}, setting <code>disableFormats</code>
\r
806 * to true will reduce <code>{@link #apply}</code> time. Defaults to <tt>false</tt>.
\r
808 var t = new Ext.Template(
\r
809 '<div name="{id}">',
\r
810 '<span class="{cls}">{name} {value}</span>',
\r
813 compiled: true, // {@link #compile} immediately
\r
814 disableFormats: true // reduce <code>{@link #apply}</code> time since no formatting
\r
818 * For a list of available format functions, see {@link Ext.util.Format}.
\r
820 disableFormats : false,
\r
822 * See <code>{@link #disableFormats}</code>.
\r
824 * @property disableFormats
\r
828 * The regular expression used to match template variables
\r
833 re : /\{([\w-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
\r
836 * Returns an HTML fragment of this template with the specified values applied.
\r
837 * @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
838 * @return {String} The HTML fragment
\r
841 applyTemplate : function(values){
\r
843 useF = me.disableFormats !== true,
\r
844 fm = Ext.util.Format,
\r
848 return me.compiled(values);
\r
850 function fn(m, name, format, args){
\r
851 if (format && useF) {
\r
852 if (format.substr(0, 5) == "this.") {
\r
853 return tpl.call(format.substr(5), values[name], values);
\r
856 // quoted values are required for strings in compiled templates,
\r
857 // but for non compiled we need to strip them
\r
858 // quoted reversed for jsmin
\r
859 var re = /^\s*['"](.*)["']\s*$/;
\r
860 args = args.split(',');
\r
861 for(var i = 0, len = args.length; i < len; i++){
\r
862 args[i] = args[i].replace(re, "$1");
\r
864 args = [values[name]].concat(args);
\r
866 args = [values[name]];
\r
868 return fm[format].apply(fm, args);
\r
871 return values[name] !== undefined ? values[name] : "";
\r
874 return me.html.replace(me.re, fn);
\r
878 * Compiles the template into an internal function, eliminating the RegEx overhead.
\r
879 * @return {Ext.Template} this
\r
882 compile : function(){
\r
884 fm = Ext.util.Format,
\r
885 useF = me.disableFormats !== true,
\r
886 sep = Ext.isGecko ? "+" : ",",
\r
889 function fn(m, name, format, args){
\r
890 if(format && useF){
\r
891 args = args ? ',' + args : "";
\r
892 if(format.substr(0, 5) != "this."){
\r
893 format = "fm." + format + '(';
\r
895 format = 'this.call("'+ format.substr(5) + '", ';
\r
899 args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
\r
901 return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
\r
904 // branched to use + in gecko and [].join() in others
\r
906 body = "this.compiled = function(values){ return '" +
\r
907 me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
\r
910 body = ["this.compiled = function(values){ return ['"];
\r
911 body.push(me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn));
\r
912 body.push("'].join('');};");
\r
913 body = body.join('');
\r
919 // private function used to call members
\r
920 call : function(fnName, value, allValues){
\r
921 return this[fnName](value, allValues);
\r
924 Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate; /*
\r
925 * This is code is also distributed under MIT license for use
\r
926 * with jQuery and prototype JavaScript libraries.
\r
929 * @class Ext.DomQuery
\r
930 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
932 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
935 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
937 <h4>Element Selectors:</h4>
\r
939 <li> <b>*</b> any element</li>
\r
940 <li> <b>E</b> an element with the tag E</li>
\r
941 <li> <b>E F</b> All descendent elements of E that have the tag F</li>
\r
942 <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
\r
943 <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
\r
944 <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
\r
946 <h4>Attribute Selectors:</h4>
\r
947 <p>The use of @ and quotes are optional. For example, div[@foo='bar'] is also a valid attribute selector.</p>
\r
949 <li> <b>E[foo]</b> has an attribute "foo"</li>
\r
950 <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
\r
951 <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
\r
952 <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
\r
953 <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
\r
954 <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
\r
955 <li> <b>E[foo!=bar]</b> has an attribute "foo" that does not equal "bar"</li>
\r
957 <h4>Pseudo Classes:</h4>
\r
959 <li> <b>E:first-child</b> E is the first child of its parent</li>
\r
960 <li> <b>E:last-child</b> E is the last child of its parent</li>
\r
961 <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
962 <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
\r
963 <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
\r
964 <li> <b>E:only-child</b> E is the only child of its parent</li>
\r
965 <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
966 <li> <b>E:first</b> the first E in the resultset</li>
\r
967 <li> <b>E:last</b> the last E in the resultset</li>
\r
968 <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
\r
969 <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
\r
970 <li> <b>E:even</b> shortcut for :nth-child(even)</li>
\r
971 <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
\r
972 <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
\r
973 <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
\r
974 <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
\r
975 <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
\r
976 <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
\r
978 <h4>CSS Value Selectors:</h4>
\r
980 <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
\r
981 <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
\r
982 <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
\r
983 <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
\r
984 <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
\r
985 <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
\r
989 Ext.DomQuery = function(){
\r
994 trimRe = /^\s+|\s+$/g,
\r
995 tplRe = /\{(\d+)\}/g,
\r
996 modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
\r
997 tagTokenRe = /^(#)?([\w-\*]+)/,
\r
998 nthRe = /(\d*)n\+?(\d*)/,
\r
1000 // This is for IE MSXML which does not support expandos.
\r
1001 // IE runs the same speed using setAttribute, however FF slows way down
\r
1002 // and Safari completely fails so they need to continue to use expandos.
\r
1003 isIE = window.ActiveXObject ? true : false,
\r
1004 isOpera = Ext.isOpera,
\r
1007 // this eval is stop the compressor from
\r
1008 // renaming the variable to something shorter
\r
1009 eval("var batch = 30803;");
\r
1011 function child(p, index){
\r
1015 if(n.nodeType == 1){
\r
1020 n = n.nextSibling;
\r
1026 while((n = n.nextSibling) && n.nodeType != 1);
\r
1031 while((n = n.previousSibling) && n.nodeType != 1);
\r
1035 function children(d){
\r
1036 var n = d.firstChild, ni = -1,
\r
1039 nx = n.nextSibling;
\r
1040 if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
\r
1043 n.nodeIndex = ++ni;
\r
1050 function byClassName(c, a, v){
\r
1054 var r = [], ri = -1, cn;
\r
1055 for(var i = 0, ci; ci = c[i]; i++){
\r
1056 if((' '+ci.className+' ').indexOf(v) != -1){
\r
1063 function attrValue(n, attr){
\r
1064 if(!n.tagName && typeof n.length != "undefined"){
\r
1070 if(attr == "for"){
\r
1073 if(attr == "class" || attr == "className"){
\r
1074 return n.className;
\r
1076 return n.getAttribute(attr) || n[attr];
\r
1080 function getNodes(ns, mode, tagName){
\r
1081 var result = [], ri = -1, cs;
\r
1085 tagName = tagName || "*";
\r
1086 if(typeof ns.getElementsByTagName != "undefined"){
\r
1090 for(var i = 0, ni; ni = ns[i]; i++){
\r
1091 cs = ni.getElementsByTagName(tagName);
\r
1092 for(var j = 0, ci; ci = cs[j]; j++){
\r
1093 result[++ri] = ci;
\r
1096 }else if(mode == "/" || mode == ">"){
\r
1097 var utag = tagName.toUpperCase();
\r
1098 for(var i = 0, ni, cn; ni = ns[i]; i++){
\r
1099 cn = isOpera ? ni.childNodes : (ni.children || ni.childNodes);
\r
1100 for(var j = 0, cj; cj = cn[j]; j++){
\r
1101 if(cj.nodeName == utag || cj.nodeName == tagName || tagName == '*'){
\r
1102 result[++ri] = cj;
\r
1106 }else if(mode == "+"){
\r
1107 var utag = tagName.toUpperCase();
\r
1108 for(var i = 0, n; n = ns[i]; i++){
\r
1109 while((n = n.nextSibling) && n.nodeType != 1);
\r
1110 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
\r
1114 }else if(mode == "~"){
\r
1115 var utag = tagName.toUpperCase();
\r
1116 for(var i = 0, n; n = ns[i]; i++){
\r
1117 while((n = n.nextSibling)){
\r
1118 if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
\r
1127 function concat(a, b){
\r
1129 return a.concat(b);
\r
1131 for(var i = 0, l = b.length; i < l; i++){
\r
1132 a[a.length] = b[i];
\r
1137 function byTag(cs, tagName){
\r
1138 if(cs.tagName || cs == document){
\r
1144 var r = [], ri = -1;
\r
1145 tagName = tagName.toLowerCase();
\r
1146 for(var i = 0, ci; ci = cs[i]; i++){
\r
1147 if(ci.nodeType == 1 && ci.tagName.toLowerCase()==tagName){
\r
1154 function byId(cs, attr, id){
\r
1155 if(cs.tagName || cs == document){
\r
1161 var r = [], ri = -1;
\r
1162 for(var i = 0,ci; ci = cs[i]; i++){
\r
1163 if(ci && ci.id == id){
\r
1171 function byAttribute(cs, attr, value, op, custom){
\r
1175 f = Ext.DomQuery.operators[op];
\r
1176 for(var i = 0, ci; ci = cs[i]; i++){
\r
1177 if(ci.nodeType != 1){
\r
1182 a = Ext.DomQuery.getStyle(ci, attr);
\r
1184 else if(attr == "class" || attr == "className"){
\r
1186 }else if(attr == "for"){
\r
1188 }else if(attr == "href"){
\r
1189 a = ci.getAttribute("href", 2);
\r
1191 a = ci.getAttribute(attr);
\r
1193 if((f && f(a, value)) || (!f && a)){
\r
1200 function byPseudo(cs, name, value){
\r
1201 return Ext.DomQuery.pseudos[name](cs, value);
\r
1204 function nodupIEXml(cs){
\r
1207 cs[0].setAttribute("_nodup", d);
\r
1209 for(var i = 1, len = cs.length; i < len; i++){
\r
1211 if(!c.getAttribute("_nodup") != d){
\r
1212 c.setAttribute("_nodup", d);
\r
1216 for(var i = 0, len = cs.length; i < len; i++){
\r
1217 cs[i].removeAttribute("_nodup");
\r
1222 function nodup(cs){
\r
1226 var len = cs.length, c, i, r = cs, cj, ri = -1;
\r
1227 if(!len || typeof cs.nodeType != "undefined" || len == 1){
\r
1230 if(isIE && typeof cs[0].selectSingleNode != "undefined"){
\r
1231 return nodupIEXml(cs);
\r
1235 for(i = 1; c = cs[i]; i++){
\r
1236 if(c._nodup != d){
\r
1240 for(var j = 0; j < i; j++){
\r
1243 for(j = i+1; cj = cs[j]; j++){
\r
1244 if(cj._nodup != d){
\r
1255 function quickDiffIEXml(c1, c2){
\r
1258 for(var i = 0, len = c1.length; i < len; i++){
\r
1259 c1[i].setAttribute("_qdiff", d);
\r
1261 for(var i = 0, len = c2.length; i < len; i++){
\r
1262 if(c2[i].getAttribute("_qdiff") != d){
\r
1263 r[r.length] = c2[i];
\r
1266 for(var i = 0, len = c1.length; i < len; i++){
\r
1267 c1[i].removeAttribute("_qdiff");
\r
1272 function quickDiff(c1, c2){
\r
1273 var len1 = c1.length,
\r
1279 if(isIE && c1[0].selectSingleNode){
\r
1280 return quickDiffIEXml(c1, c2);
\r
1282 for(var i = 0; i < len1; i++){
\r
1285 for(var i = 0, len = c2.length; i < len; i++){
\r
1286 if(c2[i]._qdiff != d){
\r
1287 r[r.length] = c2[i];
\r
1293 function quickId(ns, mode, root, id){
\r
1295 var d = root.ownerDocument || root;
\r
1296 return d.getElementById(id);
\r
1298 ns = getNodes(ns, mode, "*");
\r
1299 return byId(ns, null, id);
\r
1303 getStyle : function(el, name){
\r
1304 return Ext.fly(el).getStyle(name);
\r
1307 * Compiles a selector/xpath query into a reusable function. The returned function
\r
1308 * takes one parameter "root" (optional), which is the context node from where the query should start.
\r
1309 * @param {String} selector The selector/xpath query
\r
1310 * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
\r
1311 * @return {Function}
\r
1313 compile : function(path, type){
\r
1314 type = type || "select";
\r
1316 var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
\r
1317 q = path, mode, lq,
\r
1318 tk = Ext.DomQuery.matchers,
\r
1319 tklen = tk.length,
\r
1321 // accept leading mode switch
\r
1322 lmode = q.match(modeRe);
\r
1324 if(lmode && lmode[1]){
\r
1325 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
\r
1326 q = q.replace(lmode[1], "");
\r
1328 // strip leading slashes
\r
1329 while(path.substr(0, 1)=="/"){
\r
1330 path = path.substr(1);
\r
1333 while(q && lq != q){
\r
1335 var tm = q.match(tagTokenRe);
\r
1336 if(type == "select"){
\r
1339 fn[fn.length] = 'n = quickId(n, mode, root, "'+tm[2]+'");';
\r
1341 fn[fn.length] = 'n = getNodes(n, mode, "'+tm[2]+'");';
\r
1343 q = q.replace(tm[0], "");
\r
1344 }else if(q.substr(0, 1) != '@'){
\r
1345 fn[fn.length] = 'n = getNodes(n, mode, "*");';
\r
1350 fn[fn.length] = 'n = byId(n, null, "'+tm[2]+'");';
\r
1352 fn[fn.length] = 'n = byTag(n, "'+tm[2]+'");';
\r
1354 q = q.replace(tm[0], "");
\r
1357 while(!(mm = q.match(modeRe))){
\r
1358 var matched = false;
\r
1359 for(var j = 0; j < tklen; j++){
\r
1361 var m = q.match(t.re);
\r
1363 fn[fn.length] = t.select.replace(tplRe, function(x, i){
\r
1366 q = q.replace(m[0], "");
\r
1371 // prevent infinite loop on bad selector
\r
1373 throw 'Error parsing selector, parsing failed at "' + q + '"';
\r
1377 fn[fn.length] = 'mode="'+mm[1].replace(trimRe, "")+'";';
\r
1378 q = q.replace(mm[1], "");
\r
1381 fn[fn.length] = "return nodup(n);\n}";
\r
1382 eval(fn.join(""));
\r
1387 * Selects a group of elements.
\r
1388 * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
\r
1389 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1390 * @return {Array} An Array of DOM elements which match the selector. If there are
\r
1391 * no matches, and empty Array is returned.
\r
1393 select : function(path, root, type){
\r
1394 if(!root || root == document){
\r
1397 if(typeof root == "string"){
\r
1398 root = document.getElementById(root);
\r
1400 var paths = path.split(","),
\r
1402 for(var i = 0, len = paths.length; i < len; i++){
\r
1403 var p = paths[i].replace(trimRe, "");
\r
1405 cache[p] = Ext.DomQuery.compile(p);
\r
1407 throw p + " is not a valid selector";
\r
1410 var result = cache[p](root);
\r
1411 if(result && result != document){
\r
1412 results = results.concat(result);
\r
1415 if(paths.length > 1){
\r
1416 return nodup(results);
\r
1422 * Selects a single element.
\r
1423 * @param {String} selector The selector/xpath query
\r
1424 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1425 * @return {Element} The DOM element which matched the selector.
\r
1427 selectNode : function(path, root){
\r
1428 return Ext.DomQuery.select(path, root)[0];
\r
1432 * Selects the value of a node, optionally replacing null with the defaultValue.
\r
1433 * @param {String} selector The selector/xpath query
\r
1434 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1435 * @param {String} defaultValue
\r
1436 * @return {String}
\r
1438 selectValue : function(path, root, defaultValue){
\r
1439 path = path.replace(trimRe, "");
\r
1440 if(!valueCache[path]){
\r
1441 valueCache[path] = Ext.DomQuery.compile(path, "select");
\r
1443 var n = valueCache[path](root),
\r
1445 n = n[0] ? n[0] : n;
\r
1446 v = (n && n.firstChild ? n.firstChild.nodeValue : null);
\r
1447 return ((v === null||v === undefined||v==='') ? defaultValue : v);
\r
1451 * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
\r
1452 * @param {String} selector The selector/xpath query
\r
1453 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1454 * @param {Number} defaultValue
\r
1455 * @return {Number}
\r
1457 selectNumber : function(path, root, defaultValue){
\r
1458 var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
\r
1459 return parseFloat(v);
\r
1463 * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
\r
1464 * @param {String/HTMLElement/Array} el An element id, element or array of elements
\r
1465 * @param {String} selector The simple selector to test
\r
1466 * @return {Boolean}
\r
1468 is : function(el, ss){
\r
1469 if(typeof el == "string"){
\r
1470 el = document.getElementById(el);
\r
1472 var isArray = Ext.isArray(el),
\r
1473 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
\r
1474 return isArray ? (result.length == el.length) : (result.length > 0);
\r
1478 * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
\r
1479 * @param {Array} el An array of elements to filter
\r
1480 * @param {String} selector The simple selector to test
\r
1481 * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
\r
1482 * the selector instead of the ones that match
\r
1483 * @return {Array} An Array of DOM elements which match the selector. If there are
\r
1484 * no matches, and empty Array is returned.
\r
1486 filter : function(els, ss, nonMatches){
\r
1487 ss = ss.replace(trimRe, "");
\r
1488 if(!simpleCache[ss]){
\r
1489 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
\r
1491 var result = simpleCache[ss](els);
\r
1492 return nonMatches ? quickDiff(result, els) : result;
\r
1496 * Collection of matching regular expressions and code snippets.
\r
1499 re: /^\.([\w-]+)/,
\r
1500 select: 'n = byClassName(n, null, " {1} ");'
\r
1502 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
\r
1503 select: 'n = byPseudo(n, "{1}", "{2}");'
\r
1505 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
\r
1506 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
\r
1509 select: 'n = byId(n, null, "{1}");'
\r
1512 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
\r
1517 * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
\r
1518 * 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
1521 "=" : function(a, v){
\r
1524 "!=" : function(a, v){
\r
1527 "^=" : function(a, v){
\r
1528 return a && a.substr(0, v.length) == v;
\r
1530 "$=" : function(a, v){
\r
1531 return a && a.substr(a.length-v.length) == v;
\r
1533 "*=" : function(a, v){
\r
1534 return a && a.indexOf(v) !== -1;
\r
1536 "%=" : function(a, v){
\r
1537 return (a % v) == 0;
\r
1539 "|=" : function(a, v){
\r
1540 return a && (a == v || a.substr(0, v.length+1) == v+'-');
\r
1542 "~=" : function(a, v){
\r
1543 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
\r
1548 * <p>Object hash of "pseudo class" filter functions which are used when filtering selections. Each function is passed
\r
1549 * two parameters:</p><div class="mdetail-params"><ul>
\r
1550 * <li><b>c</b> : Array<div class="sub-desc">An Array of DOM elements to filter.</div></li>
\r
1551 * <li><b>v</b> : String<div class="sub-desc">The argument (if any) supplied in the selector.</div></li>
\r
1553 * <p>A filter function returns an Array of DOM elements which conform to the pseudo class.</p>
\r
1554 * <p>In addition to the provided pseudo classes listed above such as <code>first-child</code> and <code>nth-child</code>,
\r
1555 * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.</p>
\r
1556 * <p>For example, to filter <code><a></code> elements to only return links to <i>external</i> resources:</p>
\r
1558 Ext.DomQuery.pseudos.external = function(c, v){
\r
1559 var r = [], ri = -1;
\r
1560 for(var i = 0, ci; ci = c[i]; i++){
\r
1561 // Include in result set only if it's a link to an external resource
\r
1562 if(ci.hostname != location.hostname){
\r
1568 * Then external links could be gathered with the following statement:<code><pre>
\r
1569 var externalLinks = Ext.select("a:external");
\r
1573 "first-child" : function(c){
\r
1574 var r = [], ri = -1, n;
\r
1575 for(var i = 0, ci; ci = n = c[i]; i++){
\r
1576 while((n = n.previousSibling) && n.nodeType != 1);
\r
1584 "last-child" : function(c){
\r
1585 var r = [], ri = -1, n;
\r
1586 for(var i = 0, ci; ci = n = c[i]; i++){
\r
1587 while((n = n.nextSibling) && n.nodeType != 1);
\r
1595 "nth-child" : function(c, a) {
\r
1596 var r = [], ri = -1,
\r
1597 m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
\r
1598 f = (m[1] || 1) - 0, l = m[2] - 0;
\r
1599 for(var i = 0, n; n = c[i]; i++){
\r
1600 var pn = n.parentNode;
\r
1601 if (batch != pn._batch) {
\r
1603 for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
\r
1604 if(cn.nodeType == 1){
\r
1605 cn.nodeIndex = ++j;
\r
1608 pn._batch = batch;
\r
1611 if (l == 0 || n.nodeIndex == l){
\r
1614 } else if ((n.nodeIndex + l) % f == 0){
\r
1622 "only-child" : function(c){
\r
1623 var r = [], ri = -1;;
\r
1624 for(var i = 0, ci; ci = c[i]; i++){
\r
1625 if(!prev(ci) && !next(ci)){
\r
1632 "empty" : function(c){
\r
1633 var r = [], ri = -1;
\r
1634 for(var i = 0, ci; ci = c[i]; i++){
\r
1635 var cns = ci.childNodes, j = 0, cn, empty = true;
\r
1636 while(cn = cns[j]){
\r
1638 if(cn.nodeType == 1 || cn.nodeType == 3){
\r
1650 "contains" : function(c, v){
\r
1651 var r = [], ri = -1;
\r
1652 for(var i = 0, ci; ci = c[i]; i++){
\r
1653 if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
\r
1660 "nodeValue" : function(c, v){
\r
1661 var r = [], ri = -1;
\r
1662 for(var i = 0, ci; ci = c[i]; i++){
\r
1663 if(ci.firstChild && ci.firstChild.nodeValue == v){
\r
1670 "checked" : function(c){
\r
1671 var r = [], ri = -1;
\r
1672 for(var i = 0, ci; ci = c[i]; i++){
\r
1673 if(ci.checked == true){
\r
1680 "not" : function(c, ss){
\r
1681 return Ext.DomQuery.filter(c, ss, true);
\r
1684 "any" : function(c, selectors){
\r
1685 var ss = selectors.split('|'),
\r
1686 r = [], ri = -1, s;
\r
1687 for(var i = 0, ci; ci = c[i]; i++){
\r
1688 for(var j = 0; s = ss[j]; j++){
\r
1689 if(Ext.DomQuery.is(ci, s)){
\r
1698 "odd" : function(c){
\r
1699 return this["nth-child"](c, "odd");
\r
1702 "even" : function(c){
\r
1703 return this["nth-child"](c, "even");
\r
1706 "nth" : function(c, a){
\r
1707 return c[a-1] || [];
\r
1710 "first" : function(c){
\r
1711 return c[0] || [];
\r
1714 "last" : function(c){
\r
1715 return c[c.length-1] || [];
\r
1718 "has" : function(c, ss){
\r
1719 var s = Ext.DomQuery.select,
\r
1721 for(var i = 0, ci; ci = c[i]; i++){
\r
1722 if(s(ss, ci).length > 0){
\r
1729 "next" : function(c, ss){
\r
1730 var is = Ext.DomQuery.is,
\r
1732 for(var i = 0, ci; ci = c[i]; i++){
\r
1734 if(n && is(n, ss)){
\r
1741 "prev" : function(c, ss){
\r
1742 var is = Ext.DomQuery.is,
\r
1744 for(var i = 0, ci; ci = c[i]; i++){
\r
1746 if(n && is(n, ss)){
\r
1757 * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
\r
1758 * @param {String} path The selector/xpath query
\r
1759 * @param {Node} root (optional) The start of the query (defaults to document).
\r
1764 Ext.query = Ext.DomQuery.select;
\r
1767 var EXTUTIL = Ext.util,
1768 TOARRAY = Ext.toArray,
1770 ISOBJECT = Ext.isObject,
1774 * @class Ext.util.Observable
1775 * Base class that provides a common interface for publishing events. Subclasses are expected to
1776 * to have a property "events" with all the events defined, and, optionally, a property "listeners"
1777 * with configured listeners defined.<br>
1780 Employee = Ext.extend(Ext.util.Observable, {
1781 constructor: function(config){
1782 this.name = config.name;
1788 // Copy configured listeners into *this* object so that the base class's
1789 // constructor will add them.
1790 this.listeners = config.listeners;
1792 // Call our superclass constructor to complete construction process.
1793 Employee.superclass.constructor.call(config)
1797 * This could then be used like this:<pre><code>
1798 var newEmployee = new Employee({
1802 // By default, "this" will be the object that fired the event.
1803 alert(this.name + " has quit!");
1809 EXTUTIL.Observable = function(){
1811 * @cfg {Object} listeners (optional) <p>A config object containing one or more event handlers to be added to this
1812 * object during initialization. This should be a valid listeners config object as specified in the
1813 * {@link #addListener} example for attaching multiple handlers at once.</p>
1814 * <br><p><b><u>DOM events from ExtJs {@link Ext.Component Components}</u></b></p>
1815 * <br><p>While <i>some</i> ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this
1816 * is usually only done when extra value can be added. For example the {@link Ext.DataView DataView}'s
1817 * <b><code>{@link Ext.DataView#click click}</code></b> event passing the node clicked on. To access DOM
1818 * events directly from a Component's HTMLElement, listeners must be added to the <i>{@link Ext.Component#getEl Element}</i> after the Component
1819 * has been rendered. A plugin can simplify this step:<pre><code>
1820 // Plugin is configured with a listeners config object.
1821 // The Component is appended to the argument list of all handler functions.
1822 Ext.DomObserver = Ext.extend(Object, {
1823 constructor: function(config) {
1824 this.listeners = config.listeners ? config.listeners : config;
1827 // Component passes itself into plugin's init method
1829 var p, l = this.listeners;
1831 if (Ext.isFunction(l[p])) {
1832 l[p] = this.createHandler(l[p], c);
1834 l[p].fn = this.createHandler(l[p].fn, c);
1838 // Add the listeners to the Element immediately following the render call
1839 c.render = c.render.{@link Function#createSequence createSequence}(function() {
1847 createHandler: function(fn, c) {
1848 return function(e) {
1849 fn.call(this, e, c);
1854 var combo = new Ext.form.ComboBox({
1856 // Collapse combo when its element is clicked on
1857 plugins: [ new Ext.DomObserver({
1858 click: function(evt, comp) {
1865 triggerAction: 'all'
1869 var me = this, e = me.events;
1871 me.on(me.listeners);
1872 delete me.listeners;
1874 me.events = e || {};
1877 EXTUTIL.Observable.prototype = {
1879 filterOptRe : /^(?:scope|delay|buffer|single)$/,
1882 * <p>Fires the specified event with the passed parameters (minus the event name).</p>
1883 * <p>An event may be set to bubble up an Observable parent hierarchy (See {@link Ext.Component#getBubbleTarget})
1884 * by calling {@link #enableBubble}.</p>
1885 * @param {String} eventName The name of the event to fire.
1886 * @param {Object...} args Variable number of parameters are passed to handlers.
1887 * @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
1889 fireEvent : function(){
1890 var a = TOARRAY(arguments),
1891 ename = a[0].toLowerCase(),
1894 ce = me.events[ename],
1897 if (me.eventsSuspended === TRUE) {
1898 if (q = me.eventQueue) {
1902 else if(ISOBJECT(ce) && ce.bubble){
1903 if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
1906 c = me.getBubbleTarget && me.getBubbleTarget();
1907 if(c && c.enableBubble) {
1908 if(!c.events[ename] || !Ext.isObject(c.events[ename]) || !c.events[ename].bubble) {
1909 c.enableBubble(ename);
1911 return c.fireEvent.apply(c, a);
1917 ret = ce.fire.apply(ce, a);
1924 * Appends an event handler to this object.
1925 * @param {String} eventName The name of the event to listen for.
1926 * @param {Function} handler The method the event invokes.
1927 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
1928 * <b>If omitted, defaults to the object which fired the event.</b>
1929 * @param {Object} options (optional) An object containing handler configuration.
1930 * properties. This may contain any of the following properties:<ul>
1931 * <li><b>scope</b> : Object<div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
1932 * <b>If omitted, defaults to the object which fired the event.</b></div></li>
1933 * <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>
1934 * <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>
1935 * <li><b>buffer</b> : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
1936 * by the specified number of milliseconds. If the event fires again within that time, the original
1937 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
1938 * <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>
1939 * if the event was bubbled up from a child Observable.</div></li>
1942 * <b>Combining Options</b><br>
1943 * Using the options argument, it is possible to combine different types of listeners:<br>
1945 * A delayed, one-time listener.
1947 myDataView.on('click', this.onClick, this, {
1952 * <b>Attaching multiple handlers in 1 call</b><br>
1953 * The method also allows for a single argument to be passed which is a config object containing properties
1954 * which specify multiple handlers.
1964 fn: this.onMouseOver,
1968 fn: this.onMouseOut,
1973 * Or a shorthand syntax:<br>
1976 'click' : this.onClick,
1977 'mouseover' : this.onMouseOver,
1978 'mouseout' : this.onMouseOut,
1982 addListener : function(eventName, fn, scope, o){
1988 if (ISOBJECT(eventName)) {
1992 if (!me.filterOptRe.test(e)) {
1993 me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
1997 eventName = eventName.toLowerCase();
1998 ce = me.events[eventName] || TRUE;
1999 if (Ext.isBoolean(ce)) {
2000 me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
2002 ce.addListener(fn, scope, ISOBJECT(o) ? o : {});
2007 * Removes an event handler.
2008 * @param {String} eventName The type of event the handler was associated with.
2009 * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
2010 * @param {Object} scope (optional) The scope originally specified for the handler.
2012 removeListener : function(eventName, fn, scope){
2013 var ce = this.events[eventName.toLowerCase()];
2015 ce.removeListener(fn, scope);
2020 * Removes all listeners for this object
2022 purgeListeners : function(){
2023 var events = this.events,
2029 evt.clearListeners();
2035 * Adds the specified events to the list of events which this Observable may fire.
2036 * @param {Object|String} o Either an object with event names as properties with a value of <code>true</code>
2037 * or the first event name string if multiple event names are being passed as separate parameters.
2038 * @param {string} Optional. Event name if multiple event names are being passed as separate parameters.
2040 this.addEvents('storeloaded', 'storecleared');
2043 addEvents : function(o){
2045 me.events = me.events || {};
2046 if (Ext.isString(o)) {
2047 EACH(arguments, function(a) {
2048 me.events[a] = me.events[a] || TRUE;
2051 Ext.applyIf(me.events, o);
2056 * Checks to see if this object has any listeners for a specified event
2057 * @param {String} eventName The name of the event to check for
2058 * @return {Boolean} True if the event is being listened for, else false
2060 hasListener : function(eventName){
2061 var e = this.events[eventName];
2062 return ISOBJECT(e) && e.listeners.length > 0;
2066 * Suspend the firing of all events. (see {@link #resumeEvents})
2067 * @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
2068 * after the {@link #resumeEvents} call instead of discarding all suspended events;
2070 suspendEvents : function(queueSuspended){
2071 this.eventsSuspended = TRUE;
2072 if(queueSuspended && !this.eventQueue){
2073 this.eventQueue = [];
2078 * Resume firing events. (see {@link #suspendEvents})
2079 * If events were suspended using the <tt><b>queueSuspended</b></tt> parameter, then all
2080 * events fired during event suspension will be sent to any listeners now.
2082 resumeEvents : function(){
2084 queued = me.eventQueue || [];
2085 me.eventsSuspended = FALSE;
2086 delete me.eventQueue;
2087 EACH(queued, function(e) {
2088 me.fireEvent.apply(me, e);
2093 var OBSERVABLE = EXTUTIL.Observable.prototype;
2095 * Appends an event handler to this object (shorthand for {@link #addListener}.)
2096 * @param {String} eventName The type of event to listen for
2097 * @param {Function} handler The method the event invokes
2098 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
2099 * <b>If omitted, defaults to the object which fired the event.</b>
2100 * @param {Object} options (optional) An object containing handler configuration.
2103 OBSERVABLE.on = OBSERVABLE.addListener;
2105 * Removes an event handler (shorthand for {@link #removeListener}.)
2106 * @param {String} eventName The type of event the handler was associated with.
2107 * @param {Function} handler The handler to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
2108 * @param {Object} scope (optional) The scope originally specified for the handler.
2111 OBSERVABLE.un = OBSERVABLE.removeListener;
2114 * Removes <b>all</b> added captures from the Observable.
2115 * @param {Observable} o The Observable to release
2118 EXTUTIL.Observable.releaseCapture = function(o){
2119 o.fireEvent = OBSERVABLE.fireEvent;
2122 function createTargeted(h, o, scope){
2124 if(o.target == arguments[0]){
2125 h.apply(scope, TOARRAY(arguments));
2130 function createBuffered(h, o, scope){
2131 var task = new EXTUTIL.DelayedTask();
2133 task.delay(o.buffer, h, scope, TOARRAY(arguments));
2137 function createSingle(h, e, fn, scope){
2139 e.removeListener(fn, scope);
2140 return h.apply(scope, arguments);
2144 function createDelayed(h, o, scope){
2146 var args = TOARRAY(arguments);
2148 h.apply(scope, args);
2149 }).defer(o.delay || 10);
2153 EXTUTIL.Event = function(obj, name){
2156 this.listeners = [];
2159 EXTUTIL.Event.prototype = {
2160 addListener : function(fn, scope, options){
2163 scope = scope || me.obj;
2164 if(!me.isListening(fn, scope)){
2165 l = me.createListener(fn, scope, options);
2166 if(me.firing){ // if we are currently firing this event, don't disturb the listener loop
2167 me.listeners = me.listeners.slice(0);
2169 me.listeners.push(l);
2173 createListener: function(fn, scope, o){
2174 o = o || {}, scope = scope || this.obj;
2181 h = createTargeted(h, o, scope);
2184 h = createDelayed(h, o, scope);
2187 h = createSingle(h, this, fn, scope);
2190 h = createBuffered(h, o, scope);
2196 findListener : function(fn, scope){
2198 EACH(this.listeners, function(l, i) {
2200 if(l.fn == fn && (s == scope || s == this.obj)){
2209 isListening : function(fn, scope){
2210 return this.findListener(fn, scope) != -1;
2213 removeListener : function(fn, scope){
2217 if((index = me.findListener(fn, scope)) != -1){
2219 me.listeners = me.listeners.slice(0);
2221 me.listeners.splice(index, 1);
2227 clearListeners : function(){
2228 this.listeners = [];
2233 args = TOARRAY(arguments),
2236 EACH(me.listeners, function(l) {
2238 if (l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
2239 return ret = me.firing = FALSE;
2247 * @class Ext.util.Observable
\r
2249 Ext.apply(Ext.util.Observable.prototype, function(){
\r
2250 // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
\r
2251 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
\r
2253 function getMethodEvent(method){
\r
2254 var e = (this.methodEvents = this.methodEvents ||
\r
2255 {})[method], returnValue, v, cancel, obj = this;
\r
2258 this.methodEvents[method] = e = {};
\r
2259 e.originalFn = this[method];
\r
2260 e.methodName = method;
\r
2264 var makeCall = function(fn, scope, args){
\r
2265 if (!Ext.isEmpty(v = fn.apply(scope || obj, args))) {
\r
2266 if (Ext.isObject(v)) {
\r
2267 returnValue = !Ext.isEmpty(v.returnValue) ? v.returnValue : v;
\r
2268 cancel = !!v.cancel;
\r
2271 if (v === false) {
\r
2280 this[method] = function(){
\r
2281 var args = Ext.toArray(arguments);
\r
2282 returnValue = v = undefined;
\r
2285 Ext.each(e.before, function(b){
\r
2286 makeCall(b.fn, b.scope, args);
\r
2288 return returnValue;
\r
2292 if (!Ext.isEmpty(v = e.originalFn.apply(obj, args))) {
\r
2295 Ext.each(e.after, function(a){
\r
2296 makeCall(a.fn, a.scope, args);
\r
2298 return returnValue;
\r
2301 return returnValue;
\r
2308 // these are considered experimental
\r
2309 // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
\r
2310 // adds an "interceptor" called before the original method
\r
2311 beforeMethod: function(method, fn, scope){
\r
2312 getMethodEvent.call(this, method).before.push({
\r
2318 // adds a "sequence" called after the original method
\r
2319 afterMethod: function(method, fn, scope){
\r
2320 getMethodEvent.call(this, method).after.push({
\r
2326 removeMethodListener: function(method, fn, scope){
\r
2327 var e = getMethodEvent.call(this, method), found = false;
\r
2328 Ext.each(e.before, function(b, i, arr){
\r
2329 if (b.fn == fn && b.scope == scope) {
\r
2336 Ext.each(e.after, function(a, i, arr){
\r
2337 if (a.fn == fn && a.scope == scope) {
\r
2346 * Relays selected events from the specified Observable as if the events were fired by <tt><b>this</b></tt>.
\r
2347 * @param {Object} o The Observable whose events this object is to relay.
\r
2348 * @param {Array} events Array of event names to relay.
\r
2350 relayEvents: function(o, events){
\r
2352 function createHandler(ename){
\r
2353 return function(){
\r
2354 return me.fireEvent.apply(me, [ename].concat(Ext.toArray(arguments)));
\r
2357 Ext.each(events, function(ename){
\r
2358 me.events[ename] = me.events[ename] || true;
\r
2359 o.on(ename, createHandler(ename), me);
\r
2364 * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
\r
2365 * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
\r
2366 * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component.getBubbleTarget}. The default
\r
2367 * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
\r
2368 * access the required target more quickly.</p>
\r
2369 * <p>Example:</p><pre><code>
\r
2370 Ext.override(Ext.form.Field, {
\r
2371 // Add functionality to Field's initComponent to enable the change event to bubble
\r
2372 initComponent: Ext.form.Field.prototype.initComponent.createSequence(function() {
\r
2373 this.enableBubble('change');
\r
2376 // We know that we want Field's events to bubble directly to the FormPanel.
\r
2377 getBubbleTarget: function() {
\r
2378 if (!this.formPanel) {
\r
2379 this.formPanel = this.findParentByType('form');
\r
2381 return this.formPanel;
\r
2385 var myForm = new Ext.formPanel({
\r
2386 title: 'User Details',
\r
2391 change: function() {
\r
2392 // Title goes red if form has been modified.
\r
2393 myForm.header.setStyle("color", "red");
\r
2398 * @param {Object} events The event name to bubble, or an Array of event names.
\r
2400 enableBubble: function(events){
\r
2402 if(!Ext.isEmpty(events)){
\r
2403 events = Ext.isArray(events) ? events : Ext.toArray(arguments);
\r
2404 Ext.each(events, function(ename){
\r
2405 ename = ename.toLowerCase();
\r
2406 var ce = me.events[ename] || true;
\r
2407 if (Ext.isBoolean(ce)) {
\r
2408 ce = new Ext.util.Event(me, ename);
\r
2409 me.events[ename] = ce;
\r
2420 * Starts capture on the specified Observable. All events will be passed
\r
2421 * to the supplied function with the event name + standard signature of the event
\r
2422 * <b>before</b> the event is fired. If the supplied function returns false,
\r
2423 * the event will not fire.
\r
2424 * @param {Observable} o The Observable to capture
\r
2425 * @param {Function} fn The function to call
\r
2426 * @param {Object} scope (optional) The scope (this object) for the fn
\r
2429 Ext.util.Observable.capture = function(o, fn, scope){
\r
2430 o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
\r
2435 * Sets observability on the passed class constructor.<p>
\r
2436 * <p>This makes any event fired on any instance of the passed class also fire a single event through
\r
2437 * the <i>class</i> allowing for central handling of events on many instances at once.</p>
\r
2438 * <p>Usage:</p><pre><code>
\r
2439 Ext.util.Observable.observeClass(Ext.data.Connection);
\r
2440 Ext.data.Connection.on('beforerequest', function(con, options) {
\r
2441 console.log("Ajax request made to " + options.url);
\r
2443 * @param {Function} c The class constructor to make observable.
\r
2446 Ext.util.Observable.observeClass = function(c){
\r
2447 Ext.apply(c, new Ext.util.Observable());
\r
2448 c.prototype.fireEvent = function(){
\r
2449 return (c.fireEvent.apply(c, arguments) !== false) &&
\r
2450 (Ext.util.Observable.prototype.fireEvent.apply(this, arguments) !== false);
\r
2453 * @class Ext.EventManager
2454 * Registers event handlers that want to receive a normalized EventObject instead of the standard browser event and provides
2455 * several useful events directly.
2456 * See {@link Ext.EventObject} for more details on normalized event objects.
2459 Ext.EventManager = function(){
2462 docReadyState = false,
2467 IEDEFERED = "ie-deferred-loader",
2468 DOMCONTENTLOADED = "DOMContentLoaded",
2470 propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
2472 /// There is some jquery work around stuff here that isn't needed in Ext Core.
2473 function addListener(el, ename, fn, wrap, scope){
2474 var id = Ext.id(el),
2475 es = elHash[id] = elHash[id] || {};
2477 (es[ename] = es[ename] || []).push([fn, wrap, scope]);
2478 E.on(el, ename, wrap);
2480 // this is a workaround for jQuery and should somehow be removed from Ext Core in the future
2481 // without breaking ExtJS.
2482 if(ename == "mousewheel" && el.addEventListener){ // workaround for jQuery
2483 var args = ["DOMMouseScroll", wrap, false];
2484 el.addEventListener.apply(el, args);
2485 E.on(window, 'unload', function(){
2486 el.removeEventListener.apply(el, args);
2489 if(ename == "mousedown" && el == document){ // fix stopped mousedowns on the document
2490 Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
2494 function fireDocReady(){
2496 Ext.isReady = docReadyState = true;
2498 clearInterval(docReadyProcId);
2500 if(Ext.isGecko || Ext.isOpera) {
2501 DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
2504 var defer = DOC.getElementById(IEDEFERED);
2506 defer.onreadystatechange = null;
2507 defer.parentNode.removeChild(defer);
2511 docReadyEvent.fire();
2512 docReadyEvent.clearListeners();
2517 function initDocReady(){
2518 var COMPLETE = "complete";
2520 docReadyEvent = new Ext.util.Event();
2521 if (Ext.isGecko || Ext.isOpera) {
2522 DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
2523 } else if (Ext.isIE){
2524 DOC.write("<s"+'cript id=' + IEDEFERED + ' defer="defer" src="/'+'/:"></s'+"cript>");
2525 DOC.getElementById(IEDEFERED).onreadystatechange = function(){
2526 if(this.readyState == COMPLETE){
2530 } else if (Ext.isWebKit){
2531 docReadyProcId = setInterval(function(){
2532 if(DOC.readyState == COMPLETE) {
2537 // no matter what, make sure it fires on load
2538 E.on(WINDOW, "load", fireDocReady);
2541 function createTargeted(h, o){
2543 var args = Ext.toArray(arguments);
2544 if(o.target == Ext.EventObject.setEvent(args[0]).target){
2545 h.apply(this, args);
2550 function createBuffered(h, o){
2551 var task = new Ext.util.DelayedTask(h);
2553 // create new event object impl so new events don't wipe out properties
2554 task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
2558 function createSingle(h, el, ename, fn, scope){
2560 Ext.EventManager.removeListener(el, ename, fn, scope);
2565 function createDelayed(h, o){
2567 // create new event object impl so new events don't wipe out properties
2568 e = new Ext.EventObjectImpl(e);
2569 setTimeout(function(){
2575 function listen(element, ename, opt, fn, scope){
2576 var o = !Ext.isObject(opt) ? {} : opt,
2577 el = Ext.getDom(element);
2580 scope = scope || o.scope;
2583 throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
2586 // prevent errors while unload occurring
2587 if(!Ext){// !window[xname]){ ==> can't we do this?
2590 e = Ext.EventObject.setEvent(e);
2593 if(!(t = e.getTarget(o.delegate, el))){
2602 if (o.preventDefault) {
2605 if (o.stopPropagation) {
2606 e.stopPropagation();
2612 fn.call(scope || el, e, t, o);
2615 h = createTargeted(h, o);
2618 h = createDelayed(h, o);
2621 h = createSingle(h, el, ename, fn, scope);
2624 h = createBuffered(h, o);
2627 addListener(el, ename, fn, h, scope);
2633 * Appends an event handler to an element. The shorthand version {@link #on} is equivalent. Typically you will
2634 * use {@link Ext.Element#addListener} directly on an Element in favor of calling this version.
2635 * @param {String/HTMLElement} el The html element or id to assign the event handler to.
2636 * @param {String} eventName The name of the event to listen for.
2637 * @param {Function} handler The handler function the event invokes. This function is passed
2638 * the following parameters:<ul>
2639 * <li>evt : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
2640 * <li>t : Element<div class="sub-desc">The {@link Ext.Element Element} which was the target of the event.
2641 * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
2642 * <li>o : Object<div class="sub-desc">The options object from the addListener call.</div></li>
2644 * @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>.
2645 * @param {Object} options (optional) An object containing handler configuration properties.
2646 * This may contain any of the following properties:<ul>
2647 * <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>
2648 * <li>delegate : String<div class="sub-desc">A simple selector to filter the target or look for a descendant of the target</div></li>
2649 * <li>stopEvent : Boolean<div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
2650 * <li>preventDefault : Boolean<div class="sub-desc">True to prevent the default action</div></li>
2651 * <li>stopPropagation : Boolean<div class="sub-desc">True to prevent event propagation</div></li>
2652 * <li>normalized : Boolean<div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
2653 * <li>delay : Number<div class="sub-desc">The number of milliseconds to delay the invocation of the handler after te event fires.</div></li>
2654 * <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>
2655 * <li>buffer : Number<div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
2656 * by the specified number of milliseconds. If the event fires again within that time, the original
2657 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
2658 * <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>
2660 * <p>See {@link Ext.Element#addListener} for examples of how to use these options.</p>
2662 addListener : function(element, eventName, fn, scope, options){
2663 if(Ext.isObject(eventName)){
2664 var o = eventName, e, val;
2667 if(!propRe.test(e)){
2668 if(Ext.isFunction(val)){
2670 listen(element, e, o, val, o.scope);
2672 // individual options
2673 listen(element, e, val);
2678 listen(element, eventName, options, fn, scope);
2683 * Removes an event handler from an element. The shorthand version {@link #un} is equivalent. Typically
2684 * you will use {@link Ext.Element#removeListener} directly on an Element in favor of calling this version.
2685 * @param {String/HTMLElement} el The id or html element from which to remove the listener.
2686 * @param {String} eventName The name of the event.
2687 * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
2688 * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
2689 * then this must refer to the same object.
2691 removeListener : function(element, eventName, fn, scope){
2692 var el = Ext.getDom(element),
2696 Ext.each((elHash[id] || {})[eventName], function (v,i,a) {
2697 if (Ext.isArray(v) && v[0] == fn && (!scope || v[2] == scope)) {
2698 E.un(el, eventName, wrap = v[1]);
2704 // jQuery workaround that should be removed from Ext Core
2705 if(eventName == "mousewheel" && el.addEventListener && wrap){
2706 el.removeEventListener("DOMMouseScroll", wrap, false);
2709 if(eventName == "mousedown" && el == DOC && wrap){ // fix stopped mousedowns on the document
2710 Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
2715 * Removes all event handers from an element. Typically you will use {@link Ext.Element#removeAllListeners}
2716 * directly on an Element in favor of calling this version.
2717 * @param {String/HTMLElement} el The id or html element from which to remove all event handlers.
2719 removeAll : function(el){
2720 var id = Ext.id(el = Ext.getDom(el)),
2725 if(es.hasOwnProperty(ename)){
2726 Ext.each(es[ename], function(v) {
2727 E.un(el, ename, v.wrap);
2735 * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Can be
2736 * accessed shorthanded as Ext.onReady().
2737 * @param {Function} fn The method the event invokes.
2738 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
2739 * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
2740 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
2742 onDocumentReady : function(fn, scope, options){
2743 if(docReadyState){ // if it already fired
2744 docReadyEvent.addListener(fn, scope, options);
2745 docReadyEvent.fire();
2746 docReadyEvent.clearListeners();
2748 if(!docReadyEvent) initDocReady();
2749 options = options || {};
2750 options.delay = options.delay || 1;
2751 docReadyEvent.addListener(fn, scope, options);
2758 * Appends an event handler to an element. Shorthand for {@link #addListener}.
2759 * @param {String/HTMLElement} el The html element or id to assign the event handler to
2760 * @param {String} eventName The name of the event to listen for.
2761 * @param {Function} handler The handler function the event invokes.
2762 * @param {Object} scope (optional) (<code>this</code> reference) in which the handler function executes. <b>Defaults to the Element</b>.
2763 * @param {Object} options (optional) An object containing standard {@link #addListener} options
2764 * @member Ext.EventManager
2767 pub.on = pub.addListener;
2769 * Removes an event handler from an element. Shorthand for {@link #removeListener}.
2770 * @param {String/HTMLElement} el The id or html element from which to remove the listener.
2771 * @param {String} eventName The name of the event.
2772 * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #on} call.</b>
2773 * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
2774 * then this must refer to the same object.
2775 * @member Ext.EventManager
2778 pub.un = pub.removeListener;
2780 pub.stoppedMouseDownEvent = new Ext.util.Event();
2784 * Adds a listener to be notified when the document is ready (before onload and before images are loaded). Shorthand of {@link Ext.EventManager#onDocumentReady}.
2785 * @param {Function} fn The method the event invokes.
2786 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function executes. Defaults to the browser window.
2787 * @param {boolean} options (optional) Options object as passed to {@link Ext.Element#addListener}. It is recommended that the options
2788 * <code>{single: true}</code> be used so that the handler is removed on first invocation.
2792 Ext.onReady = Ext.EventManager.onDocumentReady;
2795 //Initialize doc classes
2798 var initExtCss = function(){
2799 // find the body element
2800 var bd = document.body || document.getElementsByTagName('body')[0];
2801 if(!bd){ return false; }
2803 Ext.isIE ? "ext-ie " + (Ext.isIE6 ? 'ext-ie6' : (Ext.isIE7 ? 'ext-ie7' : 'ext-ie8'))
2804 : Ext.isGecko ? "ext-gecko " + (Ext.isGecko2 ? 'ext-gecko2' : 'ext-gecko3')
2805 : Ext.isOpera ? "ext-opera"
2806 : Ext.isWebKit ? "ext-webkit" : ""];
2809 cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
2810 }else if(Ext.isChrome){
2811 cls.push("ext-chrome");
2815 cls.push("ext-mac");
2818 cls.push("ext-linux");
2821 if(Ext.isStrict || Ext.isBorderBox){ // add to the parent to allow for selectors like ".ext-strict .ext-ie"
2822 var p = bd.parentNode;
2824 p.className += Ext.isStrict ? ' ext-strict' : ' ext-border-box';
2827 bd.className += cls.join(' ');
2832 Ext.onReady(initExtCss);
2838 * @class Ext.EventObject
2839 * Just as {@link Ext.Element} wraps around a native DOM node, Ext.EventObject
2840 * wraps the browser's native event-object normalizing cross-browser differences,
2841 * such as which mouse button is clicked, keys pressed, mechanisms to stop
2842 * event-propagation along with a method to prevent default actions from taking place.
2843 * <p>For example:</p>
2845 function handleClick(e, t){ // e is not a standard event object, it is a Ext.EventObject
2847 var target = e.getTarget(); // same as t (the target HTMLElement)
2850 var myDiv = {@link Ext#get Ext.get}("myDiv"); // get reference to an {@link Ext.Element}
2851 myDiv.on( // 'on' is shorthand for addListener
2852 "click", // perform an action on click of myDiv
2853 handleClick // reference to the action handler
2855 // other methods to do the same:
2856 Ext.EventManager.on("myDiv", 'click', handleClick);
2857 Ext.EventManager.addListener("myDiv", 'click', handleClick);
2861 Ext.EventObject = function(){
2862 var E = Ext.lib.Event,
2863 // safari keypress events for special keys return bad keycodes
2867 63235 : 39, // right
2870 63276 : 33, // page up
2871 63277 : 34, // page down
2872 63272 : 46, // delete
2876 // normalize button clicks
2877 btnMap = Ext.isIE ? {1:0,4:1,2:2} :
2878 (Ext.isWebKit ? {1:0,2:1,3:2} : {0:0,1:1,2:2});
2880 Ext.EventObjectImpl = function(e){
2882 this.setEvent(e.browserEvent || e);
2886 Ext.EventObjectImpl.prototype = {
2888 setEvent : function(e){
2890 if(e == me || (e && e.browserEvent)){ // already wrapped
2893 me.browserEvent = e;
2895 // normalize buttons
2896 me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
2897 if(e.type == 'click' && me.button == -1){
2901 me.shiftKey = e.shiftKey;
2902 // mac metaKey behaves like ctrlKey
2903 me.ctrlKey = e.ctrlKey || e.metaKey || false;
2904 me.altKey = e.altKey;
2905 // in getKey these will be normalized for the mac
2906 me.keyCode = e.keyCode;
2907 me.charCode = e.charCode;
2908 // cache the target for the delayed and or buffered events
2909 me.target = E.getTarget(e);
2914 me.shiftKey = false;
2926 * Stop the event (preventDefault and stopPropagation)
2928 stopEvent : function(){
2930 if(me.browserEvent){
2931 if(me.browserEvent.type == 'mousedown'){
2932 Ext.EventManager.stoppedMouseDownEvent.fire(me);
2934 E.stopEvent(me.browserEvent);
2939 * Prevents the browsers default handling of the event.
2941 preventDefault : function(){
2942 if(this.browserEvent){
2943 E.preventDefault(this.browserEvent);
2948 * Cancels bubbling of the event.
2950 stopPropagation : function(){
2952 if(me.browserEvent){
2953 if(me.browserEvent.type == 'mousedown'){
2954 Ext.EventManager.stoppedMouseDownEvent.fire(me);
2956 E.stopPropagation(me.browserEvent);
2961 * Gets the character code for the event.
2964 getCharCode : function(){
2965 return this.charCode || this.keyCode;
2969 * Returns a normalized keyCode for the event.
2970 * @return {Number} The key code
2972 getKey : function(){
2973 return this.normalizeKey(this.keyCode || this.charCode)
2977 normalizeKey: function(k){
2978 return Ext.isSafari ? (safariKeys[k] || k) : k;
2982 * Gets the x coordinate of the event.
2985 getPageX : function(){
2990 * Gets the y coordinate of the event.
2993 getPageY : function(){
2998 * Gets the page coordinates of the event.
2999 * @return {Array} The xy values like [x, y]
3006 * Gets the target for the event.
3007 * @param {String} selector (optional) A simple selector to filter the target or look for an ancestor of the target
3008 * @param {Number/Mixed} maxDepth (optional) The max depth to
3009 search as a number or element (defaults to 10 || document.body)
3010 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
3011 * @return {HTMLelement}
3013 getTarget : function(selector, maxDepth, returnEl){
3014 return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
3018 * Gets the related target.
3019 * @return {HTMLElement}
3021 getRelatedTarget : function(){
3022 return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
3026 * Normalizes mouse wheel delta across browsers
3027 * @return {Number} The delta
3029 getWheelDelta : function(){
3030 var e = this.browserEvent;
3032 if(e.wheelDelta){ /* IE/Opera. */
3033 delta = e.wheelDelta/120;
3034 }else if(e.detail){ /* Mozilla case. */
3035 delta = -e.detail/3;
3041 * 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.
3042 * Example usage:<pre><code>
3043 // Handle click on any child of an element
3044 Ext.getBody().on('click', function(e){
3045 if(e.within('some-el')){
3046 alert('Clicked on a child of some-el!');
3050 // Handle click directly on an element, ignoring clicks on child nodes
3051 Ext.getBody().on('click', function(e,t){
3052 if((t.id == 'some-el') && !e.within(t, true)){
3053 alert('Clicked directly on some-el!');
3057 * @param {Mixed} el The id, DOM element or Ext.Element to check
3058 * @param {Boolean} related (optional) true to test if the related target is within el instead of the target
3059 * @param {Boolean} allowEl {optional} true to also check if the passed element is the target or related target
3062 within : function(el, related, allowEl){
3064 var t = this[related ? "getRelatedTarget" : "getTarget"]();
3065 return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
3071 return new Ext.EventObjectImpl();
3073 * @class Ext.EventManager
\r
3075 Ext.apply(Ext.EventManager, function(){
\r
3081 E = Ext.lib.Event,
\r
3082 propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
\r
3085 // note 1: IE fires ONLY the keydown event on specialkey autorepeat
\r
3086 // note 2: Safari < 3.1, Gecko (Mac/Linux) & Opera fire only the keypress event on specialkey autorepeat
\r
3087 // (research done by @Jan Wolter at http://unixpapa.com/js/key.html)
\r
3088 useKeydown = Ext.isWebKit ?
\r
3089 Ext.num(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1]) >= 525 :
\r
3090 !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera);
\r
3094 doResizeEvent: function(){
\r
3095 var h = D.getViewHeight(),
\r
3096 w = D.getViewWidth();
\r
3098 //whacky problem in IE where the resize event will fire even though the w/h are the same.
\r
3099 if(curHeight != h || curWidth != w){
\r
3100 resizeEvent.fire(curWidth = w, curHeight = h);
\r
3105 * Fires when the window is resized and provides resize event buffering (50 milliseconds), passes new viewport width and height to handlers.
\r
3106 * @param {Function} fn The method the event invokes
\r
3107 * @param {Object} scope An object that becomes the scope of the handler
\r
3108 * @param {boolean} options
\r
3110 onWindowResize : function(fn, scope, options){
\r
3112 resizeEvent = new Ext.util.Event();
\r
3113 resizeTask = new Ext.util.DelayedTask(this.doResizeEvent);
\r
3114 E.on(window, "resize", this.fireWindowResize, this);
\r
3116 resizeEvent.addListener(fn, scope, options);
\r
3119 // exposed only to allow manual firing
\r
3120 fireWindowResize : function(){
\r
3122 if((Ext.isIE||Ext.isAir) && resizeTask){
\r
3123 resizeTask.delay(50);
\r
3125 resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
\r
3131 * Fires when the user changes the active text size. Handler gets called with 2 params, the old size and the new size.
\r
3132 * @param {Function} fn The method the event invokes
\r
3133 * @param {Object} scope An object that becomes the scope of the handler
\r
3134 * @param {boolean} options
\r
3136 onTextResize : function(fn, scope, options){
\r
3138 textEvent = new Ext.util.Event();
\r
3139 var textEl = new Ext.Element(document.createElement('div'));
\r
3140 textEl.dom.className = 'x-text-resize';
\r
3141 textEl.dom.innerHTML = 'X';
\r
3142 textEl.appendTo(document.body);
\r
3143 textSize = textEl.dom.offsetHeight;
\r
3144 setInterval(function(){
\r
3145 if(textEl.dom.offsetHeight != textSize){
\r
3146 textEvent.fire(textSize, textSize = textEl.dom.offsetHeight);
\r
3148 }, this.textResizeInterval);
\r
3150 textEvent.addListener(fn, scope, options);
\r
3154 * Removes the passed window resize listener.
\r
3155 * @param {Function} fn The method the event invokes
\r
3156 * @param {Object} scope The scope of handler
\r
3158 removeResizeListener : function(fn, scope){
\r
3160 resizeEvent.removeListener(fn, scope);
\r
3165 fireResize : function(){
\r
3167 resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
\r
3172 * The frequency, in milliseconds, to check for text resize events (defaults to 50)
\r
3174 textResizeInterval : 50,
\r
3177 * Url used for onDocumentReady with using SSL (defaults to Ext.SSL_SECURE_URL)
\r
3179 ieDeferSrc : false,
\r
3181 // protected for use inside the framework
\r
3182 // detects whether we should use keydown or keypress based on the browser.
\r
3183 useKeydown: useKeydown
\r
3187 Ext.EventManager.on = Ext.EventManager.addListener;
\r
3190 Ext.apply(Ext.EventObjectImpl.prototype, {
\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 CONTROL : 17, // legacy
\r
3206 /** Key constant @type Number */
\r
3208 /** Key constant @type Number */
\r
3210 /** Key constant @type Number */
\r
3212 /** Key constant @type Number */
\r
3214 /** Key constant @type Number */
\r
3216 /** Key constant @type Number */
\r
3218 PAGEUP : 33, // legacy
\r
3219 /** Key constant @type Number */
\r
3221 PAGEDOWN : 34, // legacy
\r
3222 /** Key constant @type Number */
\r
3224 /** Key constant @type Number */
\r
3226 /** Key constant @type Number */
\r
3228 /** Key constant @type Number */
\r
3230 /** Key constant @type Number */
\r
3232 /** Key constant @type Number */
\r
3234 /** Key constant @type Number */
\r
3236 /** Key constant @type Number */
\r
3238 /** Key constant @type Number */
\r
3240 /** Key constant @type Number */
\r
3242 /** Key constant @type Number */
\r
3244 /** Key constant @type Number */
\r
3246 /** Key constant @type Number */
\r
3248 /** Key constant @type Number */
\r
3250 /** Key constant @type Number */
\r
3252 /** Key constant @type Number */
\r
3254 /** Key constant @type Number */
\r
3256 /** Key constant @type Number */
\r
3258 /** Key constant @type Number */
\r
3260 /** Key constant @type Number */
\r
3262 /** Key constant @type Number */
\r
3264 /** Key constant @type Number */
\r
3266 /** Key constant @type Number */
\r
3268 /** Key constant @type Number */
\r
3270 /** Key constant @type Number */
\r
3272 /** Key constant @type Number */
\r
3274 /** Key constant @type Number */
\r
3276 /** Key constant @type Number */
\r
3278 /** Key constant @type Number */
\r
3280 /** Key constant @type Number */
\r
3282 /** Key constant @type Number */
\r
3284 /** Key constant @type Number */
\r
3286 /** Key constant @type Number */
\r
3288 /** Key constant @type Number */
\r
3290 /** Key constant @type Number */
\r
3292 /** Key constant @type Number */
\r
3294 /** Key constant @type Number */
\r
3296 /** Key constant @type Number */
\r
3298 /** Key constant @type Number */
\r
3300 /** Key constant @type Number */
\r
3302 /** Key constant @type Number */
\r
3304 /** Key constant @type Number */
\r
3306 /** Key constant @type Number */
\r
3308 /** Key constant @type Number */
\r
3310 /** Key constant @type Number */
\r
3312 /** Key constant @type Number */
\r
3314 /** Key constant @type Number */
\r
3316 /** Key constant @type Number */
\r
3318 /** Key constant @type Number */
\r
3320 /** Key constant @type Number */
\r
3322 /** Key constant @type Number */
\r
3324 /** Key constant @type Number */
\r
3326 /** Key constant @type Number */
\r
3328 /** Key constant @type Number */
\r
3330 /** Key constant @type Number */
\r
3332 /** Key constant @type Number */
\r
3334 /** Key constant @type Number */
\r
3335 NUM_MULTIPLY: 106,
\r
3336 /** Key constant @type Number */
\r
3338 /** Key constant @type Number */
\r
3340 /** Key constant @type Number */
\r
3342 /** Key constant @type Number */
\r
3343 NUM_DIVISION: 111,
\r
3344 /** Key constant @type Number */
\r
3346 /** Key constant @type Number */
\r
3348 /** Key constant @type Number */
\r
3350 /** Key constant @type Number */
\r
3352 /** Key constant @type Number */
\r
3354 /** Key constant @type Number */
\r
3356 /** Key constant @type Number */
\r
3358 /** Key constant @type Number */
\r
3360 /** Key constant @type Number */
\r
3362 /** Key constant @type Number */
\r
3364 /** Key constant @type Number */
\r
3366 /** Key constant @type Number */
\r
3370 isNavKeyPress : function(){
\r
3372 k = this.normalizeKey(me.keyCode);
\r
3373 return (k >= 33 && k <= 40) || // Page Up/Down, End, Home, Left, Up, Right, Down
\r
3379 isSpecialKey : function(){
\r
3380 var k = this.normalizeKey(this.keyCode);
\r
3381 return (this.type == 'keypress' && this.ctrlKey) ||
\r
3382 this.isNavKeyPress() ||
\r
3383 (k == this.BACKSPACE) || // Backspace
\r
3384 (k >= 16 && k <= 20) || // Shift, Ctrl, Alt, Pause, Caps Lock
\r
3385 (k >= 44 && k <= 45); // Print Screen, Insert
\r
3388 getPoint : function(){
\r
3389 return new Ext.lib.Point(this.xy[0], this.xy[1]);
\r
3393 * Returns true if the control, meta, shift or alt key was pressed during this event.
\r
3394 * @return {Boolean}
\r
3396 hasModifier : function(){
\r
3397 return ((this.ctrlKey || this.altKey) || this.shiftKey);
\r
3400 * @class Ext.Element
\r
3401 * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
\r
3402 * <p>All instances of this class inherit the methods of {@link Ext.Fx} making visual effects easily available to all DOM elements.</p>
\r
3403 * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
\r
3404 * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
\r
3405 * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
\r
3409 var el = Ext.get("my-div");
\r
3411 // by DOM element reference
\r
3412 var el = Ext.get(myDivElement);
\r
3414 * <b>Animations</b><br />
\r
3415 * <p>When an element is manipulated, by default there is no animation.</p>
\r
3417 var el = Ext.get("my-div");
\r
3422 * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
\r
3423 * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
\r
3425 // default animation
\r
3426 el.setWidth(100, true);
\r
3429 * <p>To configure the effects, an object literal with animation options to use as the Element animation
\r
3430 * configuration object can also be specified. Note that the supported Element animation configuration
\r
3431 * options are a subset of the {@link Ext.Fx} animation options specific to Fx effects. The supported
\r
3432 * Element animation configuration options are:</p>
\r
3434 Option Default Description
\r
3435 --------- -------- ---------------------------------------------
\r
3436 {@link Ext.Fx#duration duration} .35 The duration of the animation in seconds
\r
3437 {@link Ext.Fx#easing easing} easeOut The easing method
\r
3438 {@link Ext.Fx#callback callback} none A function to execute when the anim completes
\r
3439 {@link Ext.Fx#scope scope} this The scope (this) of the callback function
\r
3443 // Element animation options object
\r
3445 {@link Ext.Fx#duration duration}: 1,
\r
3446 {@link Ext.Fx#easing easing}: 'elasticIn',
\r
3447 {@link Ext.Fx#callback callback}: this.foo,
\r
3448 {@link Ext.Fx#scope scope}: this
\r
3450 // animation with some options set
\r
3451 el.setWidth(100, opt);
\r
3453 * <p>The Element animation object being used for the animation will be set on the options
\r
3454 * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
\r
3456 // using the "anim" property to get the Anim object
\r
3457 if(opt.anim.isAnimated()){
\r
3461 * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
\r
3462 * <p><b> Composite (Collections of) Elements</b></p>
\r
3463 * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
\r
3464 * @constructor Create a new Element directly.
\r
3465 * @param {String/HTMLElement} element
\r
3466 * @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
3469 var DOC = document;
\r
3471 Ext.Element = function(element, forceNew){
\r
3472 var dom = typeof element == "string" ?
\r
3473 DOC.getElementById(element) : element,
\r
3476 if(!dom) return null;
\r
3480 if(!forceNew && id && Ext.Element.cache[id]){ // element object already exists
\r
3481 return Ext.Element.cache[id];
\r
3486 * @type HTMLElement
\r
3491 * The DOM element ID
\r
3494 this.id = id || Ext.id(dom);
\r
3497 var D = Ext.lib.Dom,
\r
3498 DH = Ext.DomHelper,
\r
3499 E = Ext.lib.Event,
\r
3505 * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
\r
3506 * @param {Object} o The object with the attributes
\r
3507 * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
\r
3508 * @return {Ext.Element} this
\r
3510 set : function(o, useSet){
\r
3511 var el = this.dom,
\r
3517 if (attr != "style" && !Ext.isFunction(val)) {
\r
3518 if (attr == "cls" ) {
\r
3519 el.className = val;
\r
3520 } else if (o.hasOwnProperty(attr)) {
\r
3521 if (useSet || !!el.setAttribute) el.setAttribute(attr, val);
\r
3522 else el[attr] = val;
\r
3527 DH.applyStyles(el, o.style);
\r
3535 * Fires when a mouse click is detected within the element.
\r
3536 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3537 * @param {HtmlElement} t The target of the event.
\r
3538 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3541 * @event contextmenu
\r
3542 * Fires when a right click is detected within the element.
\r
3543 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3544 * @param {HtmlElement} t The target of the event.
\r
3545 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3549 * Fires when a mouse double click is detected within the element.
\r
3550 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3551 * @param {HtmlElement} t The target of the event.
\r
3552 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3555 * @event mousedown
\r
3556 * Fires when a mousedown is detected within the element.
\r
3557 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3558 * @param {HtmlElement} t The target of the event.
\r
3559 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3563 * Fires when a mouseup is detected within the element.
\r
3564 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3565 * @param {HtmlElement} t The target of the event.
\r
3566 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3569 * @event mouseover
\r
3570 * Fires when a mouseover is detected within the element.
\r
3571 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3572 * @param {HtmlElement} t The target of the event.
\r
3573 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3576 * @event mousemove
\r
3577 * Fires when a mousemove is detected with the element.
\r
3578 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3579 * @param {HtmlElement} t The target of the event.
\r
3580 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3584 * Fires when a mouseout is detected with the element.
\r
3585 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3586 * @param {HtmlElement} t The target of the event.
\r
3587 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3590 * @event mouseenter
\r
3591 * Fires when the mouse enters the element.
\r
3592 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3593 * @param {HtmlElement} t The target of the event.
\r
3594 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3597 * @event mouseleave
\r
3598 * Fires when the mouse leaves the element.
\r
3599 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3600 * @param {HtmlElement} t The target of the event.
\r
3601 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3604 // Keyboard events
\r
3607 * Fires when a keypress is detected within the element.
\r
3608 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3609 * @param {HtmlElement} t The target of the event.
\r
3610 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3614 * Fires when a keydown is detected within the element.
\r
3615 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3616 * @param {HtmlElement} t The target of the event.
\r
3617 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3621 * Fires when a keyup is detected within the element.
\r
3622 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3623 * @param {HtmlElement} t The target of the event.
\r
3624 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3628 // HTML frame/object events
\r
3631 * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
\r
3632 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3633 * @param {HtmlElement} t The target of the event.
\r
3634 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3638 * 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
3639 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3640 * @param {HtmlElement} t The target of the event.
\r
3641 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3645 * Fires when an object/image is stopped from loading before completely loaded.
\r
3646 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3647 * @param {HtmlElement} t The target of the event.
\r
3648 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3652 * Fires when an object/image/frame cannot be loaded properly.
\r
3653 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3654 * @param {HtmlElement} t The target of the event.
\r
3655 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3659 * Fires when a document view is resized.
\r
3660 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3661 * @param {HtmlElement} t The target of the event.
\r
3662 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3666 * Fires when a document view is scrolled.
\r
3667 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3668 * @param {HtmlElement} t The target of the event.
\r
3669 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3675 * Fires when a user selects some text in a text field, including input and textarea.
\r
3676 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3677 * @param {HtmlElement} t The target of the event.
\r
3678 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3682 * Fires when a control loses the input focus and its value has been modified since gaining focus.
\r
3683 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3684 * @param {HtmlElement} t The target of the event.
\r
3685 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3689 * Fires when a form is submitted.
\r
3690 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3691 * @param {HtmlElement} t The target of the event.
\r
3692 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3696 * Fires when a form is reset.
\r
3697 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3698 * @param {HtmlElement} t The target of the event.
\r
3699 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3703 * Fires when an element receives focus either via the pointing device or by tab navigation.
\r
3704 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3705 * @param {HtmlElement} t The target of the event.
\r
3706 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3710 * Fires when an element loses focus either via the pointing device or by tabbing navigation.
\r
3711 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3712 * @param {HtmlElement} t The target of the event.
\r
3713 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3716 // User Interface events
\r
3718 * @event DOMFocusIn
\r
3719 * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
\r
3720 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3721 * @param {HtmlElement} t The target of the event.
\r
3722 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3725 * @event DOMFocusOut
\r
3726 * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
\r
3727 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3728 * @param {HtmlElement} t The target of the event.
\r
3729 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3732 * @event DOMActivate
\r
3733 * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
\r
3734 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3735 * @param {HtmlElement} t The target of the event.
\r
3736 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3739 // DOM Mutation events
\r
3741 * @event DOMSubtreeModified
\r
3742 * Where supported. Fires when the subtree is modified.
\r
3743 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3744 * @param {HtmlElement} t The target of the event.
\r
3745 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3748 * @event DOMNodeInserted
\r
3749 * Where supported. Fires when a node has been added as a child of another node.
\r
3750 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3751 * @param {HtmlElement} t The target of the event.
\r
3752 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3755 * @event DOMNodeRemoved
\r
3756 * Where supported. Fires when a descendant node of the element is removed.
\r
3757 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3758 * @param {HtmlElement} t The target of the event.
\r
3759 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3762 * @event DOMNodeRemovedFromDocument
\r
3763 * Where supported. Fires when a node is being removed from a document.
\r
3764 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3765 * @param {HtmlElement} t The target of the event.
\r
3766 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3769 * @event DOMNodeInsertedIntoDocument
\r
3770 * Where supported. Fires when a node is being inserted into a document.
\r
3771 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3772 * @param {HtmlElement} t The target of the event.
\r
3773 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3776 * @event DOMAttrModified
\r
3777 * Where supported. Fires when an attribute has been modified.
\r
3778 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3779 * @param {HtmlElement} t The target of the event.
\r
3780 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3783 * @event DOMCharacterDataModified
\r
3784 * Where supported. Fires when the character data has been modified.
\r
3785 * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
\r
3786 * @param {HtmlElement} t The target of the event.
\r
3787 * @param {Object} o The options configuration passed to the {@link #addListener} call.
\r
3791 * The default unit to append to CSS values where a unit isn't provided (defaults to px).
\r
3794 defaultUnit : "px",
\r
3797 * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
\r
3798 * @param {String} selector The simple selector to test
\r
3799 * @return {Boolean} True if this element matches the selector, else false
\r
3801 is : function(simpleSelector){
\r
3802 return Ext.DomQuery.is(this.dom, simpleSelector);
\r
3806 * Tries to focus the element. Any exceptions are caught and ignored.
\r
3807 * @param {Number} defer (optional) Milliseconds to defer the focus
\r
3808 * @return {Ext.Element} this
\r
3810 focus : function(defer, /* private */ dom) {
\r
3812 dom = dom || me.dom;
\r
3814 if(Number(defer)){
\r
3815 me.focus.defer(defer, null, [null, dom]);
\r
3824 * Tries to blur the element. Any exceptions are caught and ignored.
\r
3825 * @return {Ext.Element} this
\r
3827 blur : function() {
\r
3835 * Returns the value of the "value" attribute
\r
3836 * @param {Boolean} asNumber true to parse the value as a number
\r
3837 * @return {String/Number}
\r
3839 getValue : function(asNumber){
\r
3840 var val = this.dom.value;
\r
3841 return asNumber ? parseInt(val, 10) : val;
\r
3845 * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
\r
3846 * @param {String} eventName The name of event to handle.
\r
3847 * @param {Function} fn The handler function the event invokes. This function is passed
\r
3848 * the following parameters:<ul>
\r
3849 * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
\r
3850 * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
\r
3851 * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
\r
3852 * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
\r
3854 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
\r
3855 * <b>If omitted, defaults to this Element.</b>.
\r
3856 * @param {Object} options (optional) An object containing handler configuration properties.
\r
3857 * This may contain any of the following properties:<ul>
\r
3858 * <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
3859 * <b>If omitted, defaults to this Element.</b></div></li>
\r
3860 * <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
3861 * <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
3862 * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
\r
3863 * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
\r
3864 * <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
3865 * <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
3866 * <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
3867 * <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
3868 * <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
3869 * by the specified number of milliseconds. If the event fires again within that time, the original
\r
3870 * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
\r
3873 * <b>Combining Options</b><br>
\r
3874 * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
\r
3875 * addListener. The two are equivalent. Using the options argument, it is possible to combine different
\r
3876 * types of listeners:<br>
\r
3878 * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
\r
3879 * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
\r
3880 * Code:<pre><code>
\r
3881 el.on('click', this.onClick, this, {
\r
3886 });</code></pre></p>
\r
3888 * <b>Attaching multiple handlers in 1 call</b><br>
\r
3889 * The method also allows for a single argument to be passed which is a config object containing properties
\r
3890 * which specify multiple handlers.</p>
\r
3892 * Code:<pre><code>
\r
3900 fn: this.onMouseOver,
\r
3904 fn: this.onMouseOut,
\r
3909 * Or a shorthand syntax:<br>
\r
3910 * Code:<pre><code></p>
\r
3912 'click' : this.onClick,
\r
3913 'mouseover' : this.onMouseOver,
\r
3914 'mouseout' : this.onMouseOut,
\r
3917 * </code></pre></p>
\r
3918 * <p><b>delegate</b></p>
\r
3919 * <p>This is a configuration option that you can pass along when registering a handler for
\r
3920 * an event to assist with event delegation. Event delegation is a technique that is used to
\r
3921 * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
\r
3922 * for a container element as opposed to each element within a container. By setting this
\r
3923 * configuration option to a simple selector, the target element will be filtered to look for
\r
3924 * a descendant of the target.
\r
3925 * For example:<pre><code>
\r
3926 // using this markup:
\r
3927 <div id='elId'>
\r
3928 <p id='p1'>paragraph one</p>
\r
3929 <p id='p2' class='clickable'>paragraph two</p>
\r
3930 <p id='p3'>paragraph three</p>
\r
3932 // utilize event delegation to registering just one handler on the container element:
\r
3933 el = Ext.get('elId');
\r
3938 console.info(t.id); // 'p2'
\r
3942 // filter the target element to be a descendant with the class 'clickable'
\r
3943 delegate: '.clickable'
\r
3946 * </code></pre></p>
\r
3947 * @return {Ext.Element} this
\r
3949 addListener : function(eventName, fn, scope, options){
\r
3950 Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
\r
3955 * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
\r
3956 * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
\r
3957 * listener, the same scope must be specified here.
\r
3960 el.removeListener('click', this.handlerFn);
\r
3962 el.un('click', this.handlerFn);
\r
3964 * @param {String} eventName The name of the event from which to remove the handler.
\r
3965 * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
\r
3966 * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
\r
3967 * then this must refer to the same object.
\r
3968 * @return {Ext.Element} this
\r
3970 removeListener : function(eventName, fn, scope){
\r
3971 Ext.EventManager.removeListener(this.dom, eventName, fn, scope || this);
\r
3976 * Removes all previous added listeners from this element
\r
3977 * @return {Ext.Element} this
\r
3979 removeAllListeners : function(){
\r
3980 Ext.EventManager.removeAll(this.dom);
\r
3985 * @private Test if size has a unit, otherwise appends the default
\r
3987 addUnits : function(size){
\r
3988 if(size === "" || size == "auto" || size === undefined){
\r
3989 size = size || '';
\r
3990 } else if(!isNaN(size) || !unitPattern.test(size)){
\r
3991 size = size + (this.defaultUnit || 'px');
\r
3997 * <p>Updates the <a href="http://developer.mozilla.org/en/DOM/element.innerHTML">innerHTML</a> of this Element
\r
3998 * 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
3999 * <p>Updating innerHTML of an element will <b>not</b> execute embedded <tt><script></tt> elements. This is a browser restriction.</p>
\r
4000 * @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
4001 * exactly how to request the HTML.
\r
4002 * @return {Ext.Element} this
\r
4004 load : function(url, params, cb){
\r
4005 Ext.Ajax.request(Ext.apply({
\r
4007 url: url.url || url,
\r
4010 indicatorText: url.indicatorText || ''
\r
4011 }, Ext.isObject(url) ? url : {}));
\r
4016 * Tests various css rules/browsers to determine if this element uses a border box
\r
4017 * @return {Boolean}
\r
4019 isBorderBox : function(){
\r
4020 return noBoxAdjust[(this.dom.tagName || "").toLowerCase()] || Ext.isBorderBox;
\r
4024 * Removes this element from the DOM and deletes it from the cache
\r
4026 remove : function(){
\r
4030 me.removeAllListeners();
\r
4033 delete El.cache[dom.id];
\r
4034 delete El.dataCache[dom.id];
\r
4035 Ext.removeNode(dom);
\r
4040 * Sets up event handlers to call the passed functions when the mouse is moved into and out of the Element.
\r
4041 * @param {Function} overFn The function to call when the mouse enters the Element.
\r
4042 * @param {Function} outFn The function to call when the mouse leaves the Element.
\r
4043 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the functions are executed. Defaults to the Element's DOM element.
\r
4044 * @param {Object} options (optional) Options for the listener. See {@link Ext.util.Observable#addListener the <tt>options</tt> parameter}.
\r
4045 * @return {Ext.Element} this
\r
4047 hover : function(overFn, outFn, scope, options){
\r
4049 me.on('mouseenter', overFn, scope || me.dom, options);
\r
4050 me.on('mouseleave', outFn, scope || me.dom, options);
\r
4055 * Returns true if this element is an ancestor of the passed element
\r
4056 * @param {HTMLElement/String} el The element to check
\r
4057 * @return {Boolean} True if this element is an ancestor of el, else false
\r
4059 contains : function(el){
\r
4060 return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
\r
4064 * Returns the value of a namespaced attribute from the element's underlying DOM node.
\r
4065 * @param {String} namespace The namespace in which to look for the attribute
\r
4066 * @param {String} name The attribute name
\r
4067 * @return {String} The attribute value
\r
4070 getAttributeNS : function(ns, name){
\r
4071 return this.getAttribute(name, ns);
\r
4075 * Returns the value of an attribute from the element's underlying DOM node.
\r
4076 * @param {String} name The attribute name
\r
4077 * @param {String} namespace (optional) The namespace in which to look for the attribute
\r
4078 * @return {String} The attribute value
\r
4080 getAttribute : Ext.isIE ? function(name, ns){
\r
4082 type = typeof d[ns + ":" + name];
\r
4084 if(['undefined', 'unknown'].indexOf(type) == -1){
\r
4085 return d[ns + ":" + name];
\r
4088 } : function(name, ns){
\r
4090 return d.getAttributeNS(ns, name) || d.getAttribute(ns + ":" + name) || d.getAttribute(name) || d[name];
\r
4094 * Update the innerHTML of this element
\r
4095 * @param {String} html The new HTML
\r
4096 * @return {Ext.Element} this
\r
4098 update : function(html) {
\r
4100 this.dom.innerHTML = html;
\r
4106 var ep = El.prototype;
\r
4108 El.addMethods = function(o){
\r
4113 * Appends an event handler (shorthand for {@link #addListener}).
\r
4114 * @param {String} eventName The name of event to handle.
\r
4115 * @param {Function} fn The handler function the event invokes.
\r
4116 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed.
\r
4117 * @param {Object} options (optional) An object containing standard {@link #addListener} options
\r
4118 * @member Ext.Element
\r
4121 ep.on = ep.addListener;
\r
4124 * Removes an event handler from this element (see {@link #removeListener} for additional notes).
\r
4125 * @param {String} eventName The name of the event from which to remove the handler.
\r
4126 * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
\r
4127 * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
\r
4128 * then this must refer to the same object.
\r
4129 * @return {Ext.Element} this
\r
4130 * @member Ext.Element
\r
4133 ep.un = ep.removeListener;
\r
4136 * true to automatically adjust width and height settings for box-model issues (default to true)
\r
4138 ep.autoBoxAdjust = true;
\r
4141 var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
\r
4148 El.dataCache = {};
\r
4151 * Retrieves Ext.Element objects.
\r
4152 * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
\r
4153 * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
\r
4154 * its ID, use {@link Ext.ComponentMgr#get}.</p>
\r
4155 * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
\r
4156 * object was recreated with the same id via AJAX or DOM.</p>
\r
4157 * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
\r
4158 * @return {Element} The Element object (or null if no matching element was found)
\r
4160 * @member Ext.Element
\r
4163 El.get = function(el){
\r
4167 if(!el){ return null; }
\r
4168 if (typeof el == "string") { // element id
\r
4169 if (!(elm = DOC.getElementById(el))) {
\r
4172 if (ex = El.cache[el]) {
\r
4175 ex = El.cache[el] = new El(elm);
\r
4178 } else if (el.tagName) { // dom element
\r
4179 if(!(id = el.id)){
\r
4182 if(ex = El.cache[id]){
\r
4185 ex = El.cache[id] = new El(el);
\r
4188 } else if (el instanceof El) {
\r
4190 el.dom = DOC.getElementById(el.id) || el.dom; // refresh dom element in case no longer valid,
\r
4191 // catch case where it hasn't been appended
\r
4192 El.cache[el.id] = el; // in case it was created directly with Element(), let's cache it
\r
4195 } else if(el.isComposite) {
\r
4197 } else if(Ext.isArray(el)) {
\r
4198 return El.select(el);
\r
4199 } else if(el == DOC) {
\r
4200 // create a bogus element object representing the document object
\r
4202 var f = function(){};
\r
4203 f.prototype = El.prototype;
\r
4212 // private method for getting and setting element data
\r
4213 El.data = function(el, key, value){
\r
4214 var c = El.dataCache[el.id];
\r
4216 c = El.dataCache[el.id] = {};
\r
4218 if(arguments.length == 2){
\r
4221 return (c[key] = value);
\r
4226 // Garbage collection - uncache elements/purge listeners on orphaned elements
\r
4227 // so we don't hold a reference and cause the browser to retain them
\r
4228 function garbageCollect(){
\r
4229 if(!Ext.enableGarbageCollector){
\r
4230 clearInterval(El.collectorThread);
\r
4236 for(eid in El.cache){
\r
4237 el = El.cache[eid];
\r
4239 // -------------------------------------------------------
\r
4240 // Determining what is garbage:
\r
4241 // -------------------------------------------------------
\r
4243 // dom node is null, definitely garbage
\r
4244 // -------------------------------------------------------
\r
4246 // no parentNode == direct orphan, definitely garbage
\r
4247 // -------------------------------------------------------
\r
4248 // !d.offsetParent && !document.getElementById(eid)
\r
4249 // display none elements have no offsetParent so we will
\r
4250 // also try to look it up by it's id. However, check
\r
4251 // offsetParent first so we don't do unneeded lookups.
\r
4252 // This enables collection of elements that are not orphans
\r
4253 // directly, but somewhere up the line they have an orphan
\r
4255 // -------------------------------------------------------
\r
4256 if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
\r
4257 delete El.cache[eid];
\r
4258 if(d && Ext.enableListenerCollection){
\r
4259 Ext.EventManager.removeAll(d);
\r
4265 El.collectorThreadId = setInterval(garbageCollect, 30000);
\r
4267 var flyFn = function(){};
\r
4268 flyFn.prototype = El.prototype;
\r
4270 // dom is optional
\r
4271 El.Flyweight = function(dom){
\r
4275 El.Flyweight.prototype = new flyFn();
\r
4276 El.Flyweight.prototype.isFlyweight = true;
\r
4277 El._flyweights = {};
\r
4280 * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
\r
4281 * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
\r
4282 * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
\r
4283 * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
\r
4284 * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
\r
4285 * @param {String/HTMLElement} el The dom node or id
\r
4286 * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
\r
4287 * (e.g. internally Ext uses "_global")
\r
4288 * @return {Element} The shared Element object (or null if no matching element was found)
\r
4289 * @member Ext.Element
\r
4292 El.fly = function(el, named){
\r
4294 named = named || '_global';
\r
4296 if (el = Ext.getDom(el)) {
\r
4297 (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
\r
4298 ret = El._flyweights[named];
\r
4304 * Retrieves Ext.Element objects.
\r
4305 * <p><b>This method does not retrieve {@link Ext.Component Component}s.</b> This method
\r
4306 * retrieves Ext.Element objects which encapsulate DOM elements. To retrieve a Component by
\r
4307 * its ID, use {@link Ext.ComponentMgr#get}.</p>
\r
4308 * <p>Uses simple caching to consistently return the same object. Automatically fixes if an
\r
4309 * object was recreated with the same id via AJAX or DOM.</p>
\r
4310 * Shorthand of {@link Ext.Element#get}
\r
4311 * @param {Mixed} el The id of the node, a DOM Node or an existing Element.
\r
4312 * @return {Element} The Element object (or null if no matching element was found)
\r
4319 * <p>Gets the globally shared flyweight Element, with the passed node as the active element. Do not store a reference to this element -
\r
4320 * the dom node can be overwritten by other code. Shorthand of {@link Ext.Element#fly}</p>
\r
4321 * <p>Use this to make one-time references to DOM elements which are not going to be accessed again either by
\r
4322 * application code, or by Ext's classes. If accessing an element which will be processed regularly, then {@link Ext#get}
\r
4323 * will be more appropriate to take advantage of the caching provided by the Ext.Element class.</p>
\r
4324 * @param {String/HTMLElement} el The dom node or id
\r
4325 * @param {String} named (optional) Allows for creation of named reusable flyweights to prevent conflicts
\r
4326 * (e.g. internally Ext uses "_global")
\r
4327 * @return {Element} The shared Element object (or null if no matching element was found)
\r
4333 // speedy lookup for elements never to box adjust
\r
4334 var noBoxAdjust = Ext.isStrict ? {
\r
4337 input:1, select:1, textarea:1
\r
4339 if(Ext.isIE || Ext.isGecko){
\r
4340 noBoxAdjust['button'] = 1;
\r
4344 Ext.EventManager.on(window, 'unload', function(){
\r
4346 delete El.dataCache;
\r
4347 delete El._flyweights;
\r
4351 * @class Ext.Element
\r
4353 Ext.Element.addMethods({
\r
4355 * Stops the specified event(s) from bubbling and optionally prevents the default action
\r
4356 * @param {String/Array} eventName an event / array of events to stop from bubbling
\r
4357 * @param {Boolean} preventDefault (optional) true to prevent the default action too
\r
4358 * @return {Ext.Element} this
\r
4360 swallowEvent : function(eventName, preventDefault){
\r
4363 e.stopPropagation();
\r
4364 if(preventDefault){
\r
4365 e.preventDefault();
\r
4368 if(Ext.isArray(eventName)){
\r
4369 Ext.each(eventName, function(e) {
\r
4374 me.on(eventName, fn);
\r
4379 * Create an event handler on this element such that when the event fires and is handled by this element,
\r
4380 * it will be relayed to another object (i.e., fired again as if it originated from that object instead).
\r
4381 * @param {String} eventName The type of event to relay
\r
4382 * @param {Object} object Any object that extends {@link Ext.util.Observable} that will provide the context
\r
4383 * for firing the relayed event
\r
4385 relayEvent : function(eventName, observable){
\r
4386 this.on(eventName, function(e){
\r
4387 observable.fireEvent(eventName, e);
\r
4392 * Removes worthless text nodes
\r
4393 * @param {Boolean} forceReclean (optional) By default the element
\r
4394 * keeps track if it has been cleaned already so
\r
4395 * you can call this over and over. However, if you update the element and
\r
4396 * need to force a reclean, you can pass true.
\r
4398 clean : function(forceReclean){
\r
4401 n = dom.firstChild,
\r
4404 if(Ext.Element.data(dom, 'isCleaned') && forceReclean !== true){
\r
4409 var nx = n.nextSibling;
\r
4410 if(n.nodeType == 3 && !/\S/.test(n.nodeValue)){
\r
4411 dom.removeChild(n);
\r
4413 n.nodeIndex = ++ni;
\r
4417 Ext.Element.data(dom, 'isCleaned', true);
\r
4422 * Direct access to the Updater {@link Ext.Updater#update} method. The method takes the same object
\r
4423 * parameter as {@link Ext.Updater#update}
\r
4424 * @return {Ext.Element} this
\r
4426 load : function(){
\r
4427 var um = this.getUpdater();
\r
4428 um.update.apply(um, arguments);
\r
4433 * Gets this element's {@link Ext.Updater Updater}
\r
4434 * @return {Ext.Updater} The Updater
\r
4436 getUpdater : function(){
\r
4437 return this.updateManager || (this.updateManager = new Ext.Updater(this));
\r
4441 * Update the innerHTML of this element, optionally searching for and processing scripts
\r
4442 * @param {String} html The new HTML
\r
4443 * @param {Boolean} loadScripts (optional) True to look for and process scripts (defaults to false)
\r
4444 * @param {Function} callback (optional) For async script loading you can be notified when the update completes
\r
4445 * @return {Ext.Element} this
\r
4447 update : function(html, loadScripts, callback){
\r
4448 html = html || "";
\r
4450 if(loadScripts !== true){
\r
4451 this.dom.innerHTML = html;
\r
4452 if(Ext.isFunction(callback)){
\r
4458 var id = Ext.id(),
\r
4461 html += '<span id="' + id + '"></span>';
\r
4463 Ext.lib.Event.onAvailable(id, function(){
\r
4464 var DOC = document,
\r
4465 hd = DOC.getElementsByTagName("head")[0],
\r
4466 re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
\r
4467 srcRe = /\ssrc=([\'\"])(.*?)\1/i,
\r
4468 typeRe = /\stype=([\'\"])(.*?)\1/i,
\r
4476 while((match = re.exec(html))){
\r
4478 srcMatch = attrs ? attrs.match(srcRe) : false;
\r
4479 if(srcMatch && srcMatch[2]){
\r
4480 s = DOC.createElement("script");
\r
4481 s.src = srcMatch[2];
\r
4482 typeMatch = attrs.match(typeRe);
\r
4483 if(typeMatch && typeMatch[2]){
\r
4484 s.type = typeMatch[2];
\r
4486 hd.appendChild(s);
\r
4487 }else if(match[2] && match[2].length > 0){
\r
4488 if(window.execScript) {
\r
4489 window.execScript(match[2]);
\r
4491 window.eval(match[2]);
\r
4495 el = DOC.getElementById(id);
\r
4496 if(el){Ext.removeNode(el);}
\r
4497 if(Ext.isFunction(callback)){
\r
4501 dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
\r
4506 * Creates a proxy element of this element
\r
4507 * @param {String/Object} config The class name of the proxy element or a DomHelper config object
\r
4508 * @param {String/HTMLElement} renderTo (optional) The element or element id to render the proxy to (defaults to document.body)
\r
4509 * @param {Boolean} matchBox (optional) True to align and size the proxy to this element now (defaults to false)
\r
4510 * @return {Ext.Element} The new proxy element
\r
4512 createProxy : function(config, renderTo, matchBox){
\r
4513 config = Ext.isObject(config) ? config : {tag : "div", cls: config};
\r
4516 proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
\r
4517 Ext.DomHelper.insertBefore(me.dom, config, true);
\r
4519 if(matchBox && me.setBox && me.getBox){ // check to make sure Element.position.js is loaded
\r
4520 proxy.setBox(me.getBox());
\r
4526 Ext.Element.prototype.getUpdateManager = Ext.Element.prototype.getUpdater;
\r
4529 Ext.Element.uncache = function(el){
\r
4530 for(var i = 0, a = arguments, len = a.length; i < len; i++) {
\r
4532 delete Ext.Element.cache[a[i].id || a[i]];
\r
4536 * @class Ext.Element
\r
4538 Ext.Element.addMethods({
\r
4540 * Gets the x,y coordinates specified by the anchor position on the element.
\r
4541 * @param {String} anchor (optional) The specified anchor position (defaults to "c"). See {@link #alignTo}
\r
4542 * for details on supported anchor positions.
\r
4543 * @param {Boolean} local (optional) True to get the local (element top/left-relative) anchor position instead
\r
4544 * of page coordinates
\r
4545 * @param {Object} size (optional) An object containing the size to use for calculating anchor position
\r
4546 * {width: (target width), height: (target height)} (defaults to the element's current size)
\r
4547 * @return {Array} [x, y] An array containing the element's x and y coordinates
\r
4549 getAnchorXY : function(anchor, local, s){
\r
4550 //Passing a different size is useful for pre-calculating anchors,
\r
4551 //especially for anchored animations that change the el size.
\r
4552 anchor = (anchor || "tl").toLowerCase();
\r
4556 vp = me.dom == document.body || me.dom == document,
\r
4557 w = s.width || vp ? Ext.lib.Dom.getViewWidth() : me.getWidth(),
\r
4558 h = s.height || vp ? Ext.lib.Dom.getViewHeight() : me.getHeight(),
\r
4562 scroll = me.getScroll(),
\r
4563 extraX = vp ? scroll.left : !local ? o[0] : 0,
\r
4564 extraY = vp ? scroll.top : !local ? o[1] : 0,
\r
4566 c : [r(w * 0.5), r(h * 0.5)],
\r
4567 t : [r(w * 0.5), 0],
\r
4568 l : [0, r(h * 0.5)],
\r
4569 r : [w, r(h * 0.5)],
\r
4570 b : [r(w * 0.5), h],
\r
4577 xy = hash[anchor];
\r
4578 return [xy[0] + extraX, xy[1] + extraY];
\r
4582 * Anchors an element to another element and realigns it when the window is resized.
\r
4583 * @param {Mixed} element The element to align to.
\r
4584 * @param {String} position The position to align to.
\r
4585 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4586 * @param {Boolean/Object} animate (optional) True for the default animation or a standard Element animation config object
\r
4587 * @param {Boolean/Number} monitorScroll (optional) True to monitor body scroll and reposition. If this parameter
\r
4588 * is a number, it is used as the buffer delay (defaults to 50ms).
\r
4589 * @param {Function} callback The function to call after the animation finishes
\r
4590 * @return {Ext.Element} this
\r
4592 anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
\r
4596 function action(){
\r
4597 Ext.fly(dom).alignTo(el, alignment, offsets, animate);
\r
4598 Ext.callback(callback, Ext.fly(dom));
\r
4601 Ext.EventManager.onWindowResize(action, me);
\r
4603 if(!Ext.isEmpty(monitorScroll)){
\r
4604 Ext.EventManager.on(window, 'scroll', action, me,
\r
4605 {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
\r
4607 action.call(me); // align immediately
\r
4612 * Gets the x,y coordinates to align this element with another element. See {@link #alignTo} for more info on the
\r
4613 * supported position values.
\r
4614 * @param {Mixed} element The element to align to.
\r
4615 * @param {String} position The position to align to.
\r
4616 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4617 * @return {Array} [x, y]
\r
4619 getAlignToXY : function(el, p, o){
\r
4622 if(!el || !el.dom){
\r
4623 throw "Element.alignToXY with an element that doesn't exist";
\r
4627 p = (p == "?" ? "tl-bl?" : (!/-/.test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
\r
4635 //constrain the aligned el to viewport if necessary
\r
4639 dw = Ext.lib.Dom.getViewWidth() -10, // 10px of margin for ie
\r
4640 dh = Ext.lib.Dom.getViewHeight()-10, // 10px of margin for ie
\r
4648 docElement = doc.documentElement,
\r
4649 docBody = doc.body,
\r
4650 scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
\r
4651 scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
\r
4652 c = false, //constrain to viewport
\r
4655 m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
\r
4658 throw "Element.alignTo with an invalid alignment " + p;
\r
4665 //Subtract the aligned el's internal xy from the target's offset xy
\r
4666 //plus custom offset to get the aligned el's new offset xy
\r
4667 a1 = me.getAnchorXY(p1, true);
\r
4668 a2 = el.getAnchorXY(p2, false);
\r
4670 x = a2[0] - a1[0] + o[0];
\r
4671 y = a2[1] - a1[1] + o[1];
\r
4674 w = me.getWidth();
\r
4675 h = me.getHeight();
\r
4676 r = el.getRegion();
\r
4677 //If we are at a viewport boundary and the aligned el is anchored on a target border that is
\r
4678 //perpendicular to the vp border, allow the aligned el to slide on that border,
\r
4679 //otherwise swap the aligned el to the opposite border of the target.
\r
4680 p1y = p1.charAt(0);
\r
4681 p1x = p1.charAt(p1.length-1);
\r
4682 p2y = p2.charAt(0);
\r
4683 p2x = p2.charAt(p2.length-1);
\r
4684 swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
\r
4685 swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
\r
4688 if (x + w > dw + scrollX) {
\r
4689 x = swapX ? r.left-w : dw+scrollX-w;
\r
4691 if (x < scrollX) {
\r
4692 x = swapX ? r.right : scrollX;
\r
4694 if (y + h > dh + scrollY) {
\r
4695 y = swapY ? r.top-h : dh+scrollY-h;
\r
4698 y = swapY ? r.bottom : scrollY;
\r
4705 * Aligns this element with another element relative to the specified anchor points. If the other element is the
\r
4706 * document it aligns it to the viewport.
\r
4707 * The position parameter is optional, and can be specified in any one of the following formats:
\r
4709 * <li><b>Blank</b>: Defaults to aligning the element's top-left corner to the target's bottom-left corner ("tl-bl").</li>
\r
4710 * <li><b>One anchor (deprecated)</b>: The passed anchor position is used as the target element's anchor point.
\r
4711 * The element being aligned will position its top-left corner (tl) to that point. <i>This method has been
\r
4712 * deprecated in favor of the newer two anchor syntax below</i>.</li>
\r
4713 * <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
4714 * element's anchor point, and the second value is used as the target's anchor point.</li>
\r
4716 * In addition to the anchor points, the position parameter also supports the "?" character. If "?" is passed at the end of
\r
4717 * the position string, the element will attempt to align as specified, but the position will be adjusted to constrain to
\r
4718 * the viewport if necessary. Note that the element being aligned might be swapped to align to a different position than
\r
4719 * that specified in order to enforce the viewport constraints.
\r
4720 * Following are all of the supported anchor positions:
\r
4723 ----- -----------------------------
\r
4724 tl The top left corner (default)
\r
4725 t The center of the top edge
\r
4726 tr The top right corner
\r
4727 l The center of the left edge
\r
4728 c In the center of the element
\r
4729 r The center of the right edge
\r
4730 bl The bottom left corner
\r
4731 b The center of the bottom edge
\r
4732 br The bottom right corner
\r
4736 // align el to other-el using the default positioning ("tl-bl", non-constrained)
\r
4737 el.alignTo("other-el");
\r
4739 // align the top left corner of el with the top right corner of other-el (constrained to viewport)
\r
4740 el.alignTo("other-el", "tr?");
\r
4742 // align the bottom right corner of el with the center left edge of other-el
\r
4743 el.alignTo("other-el", "br-l?");
\r
4745 // align the center of el with the bottom left corner of other-el and
\r
4746 // adjust the x position by -6 pixels (and the y position by 0)
\r
4747 el.alignTo("other-el", "c-bl", [-6, 0]);
\r
4749 * @param {Mixed} element The element to align to.
\r
4750 * @param {String} position The position to align to.
\r
4751 * @param {Array} offsets (optional) Offset the positioning by [x, y]
\r
4752 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
4753 * @return {Ext.Element} this
\r
4755 alignTo : function(element, position, offsets, animate){
\r
4757 return me.setXY(me.getAlignToXY(element, position, offsets),
\r
4758 me.preanim && !!animate ? me.preanim(arguments, 3) : false);
\r
4761 // private ==> used outside of core
\r
4762 adjustForConstraints : function(xy, parent, offsets){
\r
4763 return this.getConstrainToXY(parent || document, false, offsets, xy) || xy;
\r
4766 // private ==> used outside of core
\r
4767 getConstrainToXY : function(el, local, offsets, proposedXY){
\r
4768 var os = {top:0, left:0, bottom:0, right: 0};
\r
4770 return function(el, local, offsets, proposedXY){
\r
4772 offsets = offsets ? Ext.applyIf(offsets, os) : os;
\r
4774 var vw, vh, vx = 0, vy = 0;
\r
4775 if(el.dom == document.body || el.dom == document){
\r
4776 vw =Ext.lib.Dom.getViewWidth();
\r
4777 vh = Ext.lib.Dom.getViewHeight();
\r
4779 vw = el.dom.clientWidth;
\r
4780 vh = el.dom.clientHeight;
\r
4782 var vxy = el.getXY();
\r
4788 var s = el.getScroll();
\r
4790 vx += offsets.left + s.left;
\r
4791 vy += offsets.top + s.top;
\r
4793 vw -= offsets.right;
\r
4794 vh -= offsets.bottom;
\r
4799 var xy = proposedXY || (!local ? this.getXY() : [this.getLeft(true), this.getTop(true)]);
\r
4800 var x = xy[0], y = xy[1];
\r
4801 var w = this.dom.offsetWidth, h = this.dom.offsetHeight;
\r
4803 // only move it if it needs it
\r
4804 var moved = false;
\r
4806 // first validate right/bottom
\r
4815 // then make sure top/left isn't negative
\r
4824 return moved ? [x, y] : false;
\r
4830 // el = Ext.get(el);
\r
4831 // offsets = Ext.applyIf(offsets || {}, {top : 0, left : 0, bottom : 0, right : 0});
\r
4834 // doc = document,
\r
4835 // s = el.getScroll(),
\r
4836 // vxy = el.getXY(),
\r
4837 // vx = offsets.left + s.left,
\r
4838 // vy = offsets.top + s.top,
\r
4839 // vw = -offsets.right,
\r
4840 // vh = -offsets.bottom,
\r
4843 // xy = proposedXY || (!local ? me.getXY() : [me.getLeft(true), me.getTop(true)]),
\r
4846 // w = me.dom.offsetWidth, h = me.dom.offsetHeight,
\r
4847 // moved = false; // only move it if it needs it
\r
4850 // if(el.dom == doc.body || el.dom == doc){
\r
4851 // vw += Ext.lib.Dom.getViewWidth();
\r
4852 // vh += Ext.lib.Dom.getViewHeight();
\r
4854 // vw += el.dom.clientWidth;
\r
4855 // vh += el.dom.clientHeight;
\r
4862 // // first validate right/bottom
\r
4863 // if(x + w > vx + vw){
\r
4864 // x = vx + vw - w;
\r
4867 // if(y + h > vy + vh){
\r
4868 // y = vy + vh - h;
\r
4871 // // then make sure top/left isn't negative
\r
4880 // return moved ? [x, y] : false;
\r
4884 * Calculates the x, y to center this element on the screen
\r
4885 * @return {Array} The x, y values [x, y]
\r
4887 getCenterXY : function(){
\r
4888 return this.getAlignToXY(document, 'c-c');
\r
4892 * Centers the Element in either the viewport, or another Element.
\r
4893 * @param {Mixed} centerIn (optional) The element in which to center the element.
\r
4895 center : function(centerIn){
\r
4896 return this.alignTo(centerIn || document, 'c-c');
\r
4900 * @class Ext.Element
\r
4902 Ext.Element.addMethods(function(){
\r
4903 var PARENTNODE = 'parentNode',
\r
4904 NEXTSIBLING = 'nextSibling',
\r
4905 PREVIOUSSIBLING = 'previousSibling',
\r
4906 DQ = Ext.DomQuery,
\r
4911 * 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
4912 * @param {String} selector The simple selector to test
\r
4913 * @param {Number/Mixed} maxDepth (optional) The max depth to search as a number or element (defaults to 50 || document.body)
\r
4914 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
\r
4915 * @return {HTMLElement} The matching DOM node (or null if no match was found)
\r
4917 findParent : function(simpleSelector, maxDepth, returnEl){
\r
4919 b = document.body,
\r
4922 if(Ext.isGecko && Object.prototype.toString.call(p) == '[object XULElement]') {
\r
4925 maxDepth = maxDepth || 50;
\r
4926 if (isNaN(maxDepth)) {
\r
4927 stopEl = Ext.getDom(maxDepth);
\r
4928 maxDepth = Number.MAX_VALUE;
\r
4930 while(p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl){
\r
4931 if(DQ.is(p, simpleSelector)){
\r
4932 return returnEl ? GET(p) : p;
\r
4941 * Looks at parent nodes for a match of the passed simple selector (e.g. div.some-class or span:first-child)
\r
4942 * @param {String} selector The simple selector to test
\r
4943 * @param {Number/Mixed} maxDepth (optional) The max depth to
\r
4944 search as a number or element (defaults to 10 || document.body)
\r
4945 * @param {Boolean} returnEl (optional) True to return a Ext.Element object instead of DOM node
\r
4946 * @return {HTMLElement} The matching DOM node (or null if no match was found)
\r
4948 findParentNode : function(simpleSelector, maxDepth, returnEl){
\r
4949 var p = Ext.fly(this.dom.parentNode, '_internal');
\r
4950 return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
\r
4954 * 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
4955 * This is a shortcut for findParentNode() that always returns an Ext.Element.
\r
4956 * @param {String} selector The simple selector to test
\r
4957 * @param {Number/Mixed} maxDepth (optional) The max depth to
\r
4958 search as a number or element (defaults to 10 || document.body)
\r
4959 * @return {Ext.Element} The matching DOM node (or null if no match was found)
\r
4961 up : function(simpleSelector, maxDepth){
\r
4962 return this.findParentNode(simpleSelector, maxDepth, true);
\r
4966 * Creates a {@link Ext.CompositeElement} for child nodes based on the passed CSS selector (the selector should not contain an id).
\r
4967 * @param {String} selector The CSS selector
\r
4968 * @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
4969 * @return {CompositeElement/CompositeElementLite} The composite element
\r
4971 select : function(selector, unique){
\r
4972 return Ext.Element.select(selector, unique, this.dom);
\r
4976 * Selects child nodes based on the passed CSS selector (the selector should not contain an id).
\r
4977 * @param {String} selector The CSS selector
\r
4978 * @return {Array} An array of the matched nodes
\r
4980 query : function(selector, unique){
\r
4981 return DQ.select(selector, this.dom);
\r
4985 * Selects a single child at any depth below this element based on the passed CSS selector (the selector should not contain an id).
\r
4986 * @param {String} selector The CSS selector
\r
4987 * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
\r
4988 * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
\r
4990 child : function(selector, returnDom){
\r
4991 var n = DQ.selectNode(selector, this.dom);
\r
4992 return returnDom ? n : GET(n);
\r
4996 * Selects a single *direct* child based on the passed CSS selector (the selector should not contain an id).
\r
4997 * @param {String} selector The CSS selector
\r
4998 * @param {Boolean} returnDom (optional) True to return the DOM node instead of Ext.Element (defaults to false)
\r
4999 * @return {HTMLElement/Ext.Element} The child Ext.Element (or DOM node if returnDom = true)
\r
5001 down : function(selector, returnDom){
\r
5002 var n = DQ.selectNode(" > " + selector, this.dom);
\r
5003 return returnDom ? n : GET(n);
\r
5007 * Gets the parent node for this element, optionally chaining up trying to match a selector
\r
5008 * @param {String} selector (optional) Find a parent node that matches the passed simple selector
\r
5009 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
5010 * @return {Ext.Element/HTMLElement} The parent node or null
\r
5012 parent : function(selector, returnDom){
\r
5013 return this.matchNode(PARENTNODE, PARENTNODE, selector, returnDom);
\r
5017 * Gets the next sibling, skipping text nodes
\r
5018 * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
\r
5019 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
5020 * @return {Ext.Element/HTMLElement} The next sibling or null
\r
5022 next : function(selector, returnDom){
\r
5023 return this.matchNode(NEXTSIBLING, NEXTSIBLING, selector, returnDom);
\r
5027 * Gets the previous sibling, skipping text nodes
\r
5028 * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
\r
5029 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
5030 * @return {Ext.Element/HTMLElement} The previous sibling or null
\r
5032 prev : function(selector, returnDom){
\r
5033 return this.matchNode(PREVIOUSSIBLING, PREVIOUSSIBLING, selector, returnDom);
\r
5038 * Gets the first child, skipping text nodes
\r
5039 * @param {String} selector (optional) Find the next sibling that matches the passed simple selector
\r
5040 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
5041 * @return {Ext.Element/HTMLElement} The first child or null
\r
5043 first : function(selector, returnDom){
\r
5044 return this.matchNode(NEXTSIBLING, 'firstChild', selector, returnDom);
\r
5048 * Gets the last child, skipping text nodes
\r
5049 * @param {String} selector (optional) Find the previous sibling that matches the passed simple selector
\r
5050 * @param {Boolean} returnDom (optional) True to return a raw dom node instead of an Ext.Element
\r
5051 * @return {Ext.Element/HTMLElement} The last child or null
\r
5053 last : function(selector, returnDom){
\r
5054 return this.matchNode(PREVIOUSSIBLING, 'lastChild', selector, returnDom);
\r
5057 matchNode : function(dir, start, selector, returnDom){
\r
5058 var n = this.dom[start];
\r
5060 if(n.nodeType == 1 && (!selector || DQ.is(n, selector))){
\r
5061 return !returnDom ? GET(n) : n;
\r
5069 * @class Ext.Element
\r
5071 Ext.Element.addMethods(
\r
5073 var GETDOM = Ext.getDom,
\r
5075 DH = Ext.DomHelper;
\r
5079 * Appends the passed element(s) to this element
\r
5080 * @param {String/HTMLElement/Array/Element/CompositeElement} el
\r
5081 * @return {Ext.Element} this
\r
5083 appendChild: function(el){
\r
5084 return GET(el).appendTo(this);
\r
5088 * Appends this element to the passed element
\r
5089 * @param {Mixed} el The new parent element
\r
5090 * @return {Ext.Element} this
\r
5092 appendTo: function(el){
\r
5093 GETDOM(el).appendChild(this.dom);
\r
5098 * Inserts this element before the passed element in the DOM
\r
5099 * @param {Mixed} el The element before which this element will be inserted
\r
5100 * @return {Ext.Element} this
\r
5102 insertBefore: function(el){
\r
5103 (el = GETDOM(el)).parentNode.insertBefore(this.dom, el);
\r
5108 * Inserts this element after the passed element in the DOM
\r
5109 * @param {Mixed} el The element to insert after
\r
5110 * @return {Ext.Element} this
\r
5112 insertAfter: function(el){
\r
5113 (el = GETDOM(el)).parentNode.insertBefore(this.dom, el.nextSibling);
\r
5118 * Inserts (or creates) an element (or DomHelper config) as the first child of this element
\r
5119 * @param {Mixed/Object} el The id or element to insert or a DomHelper config to create and insert
\r
5120 * @return {Ext.Element} The new child
\r
5122 insertFirst: function(el, returnDom){
\r
5124 if(el.nodeType || el.dom || typeof el == 'string'){ // element
\r
5126 this.dom.insertBefore(el, this.dom.firstChild);
\r
5127 return !returnDom ? GET(el) : el;
\r
5128 }else{ // dh config
\r
5129 return this.createChild(el, this.dom.firstChild, returnDom);
\r
5134 * Replaces the passed element with this element
\r
5135 * @param {Mixed} el The element to replace
\r
5136 * @return {Ext.Element} this
\r
5138 replace: function(el){
\r
5140 this.insertBefore(el);
\r
5146 * Replaces this element with the passed element
\r
5147 * @param {Mixed/Object} el The new element or a DomHelper config of an element to create
\r
5148 * @return {Ext.Element} this
\r
5150 replaceWith: function(el){
\r
5152 Element = Ext.Element;
\r
5153 if(el.nodeType || el.dom || typeof el == 'string'){
\r
5155 me.dom.parentNode.insertBefore(el, me.dom);
\r
5157 el = DH.insertBefore(me.dom, el);
\r
5160 delete Element.cache[me.id];
\r
5161 Ext.removeNode(me.dom);
\r
5162 me.id = Ext.id(me.dom = el);
\r
5163 return Element.cache[me.id] = me;
\r
5167 * Creates the passed DomHelper config and appends it to this element or optionally inserts it before the passed child element.
\r
5168 * @param {Object} config DomHelper element config object. If no tag is specified (e.g., {tag:'input'}) then a div will be
\r
5169 * automatically generated with the specified attributes.
\r
5170 * @param {HTMLElement} insertBefore (optional) a child element of this element
\r
5171 * @param {Boolean} returnDom (optional) true to return the dom node instead of creating an Element
\r
5172 * @return {Ext.Element} The new child element
\r
5174 createChild: function(config, insertBefore, returnDom){
\r
5175 config = config || {tag:'div'};
\r
5176 return insertBefore ?
\r
5177 DH.insertBefore(insertBefore, config, returnDom !== true) :
\r
5178 DH[!this.dom.firstChild ? 'overwrite' : 'append'](this.dom, config, returnDom !== true);
\r
5182 * Creates and wraps this element with another element
\r
5183 * @param {Object} config (optional) DomHelper element config object for the wrapper element or null for an empty div
\r
5184 * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
\r
5185 * @return {HTMLElement/Element} The newly created wrapper element
\r
5187 wrap: function(config, returnDom){
\r
5188 var newEl = DH.insertBefore(this.dom, config || {tag: "div"}, !returnDom);
\r
5189 newEl.dom ? newEl.dom.appendChild(this.dom) : newEl.appendChild(this.dom);
\r
5194 * Inserts an html fragment into this element
\r
5195 * @param {String} where Where to insert the html in relation to this element - beforeBegin, afterBegin, beforeEnd, afterEnd.
\r
5196 * @param {String} html The HTML fragment
\r
5197 * @param {Boolean} returnEl (optional) True to return an Ext.Element (defaults to false)
\r
5198 * @return {HTMLElement/Ext.Element} The inserted node (or nearest related if more than 1 inserted)
\r
5200 insertHtml : function(where, html, returnEl){
\r
5201 var el = DH.insertHtml(where, this.dom, html);
\r
5202 return returnEl ? Ext.get(el) : el;
\r
5206 * @class Ext.Element
\r
5208 Ext.apply(Ext.Element.prototype, function() {
\r
5209 var GETDOM = Ext.getDom,
\r
5211 DH = Ext.DomHelper;
\r
5215 * Inserts (or creates) the passed element (or DomHelper config) as a sibling of this element
\r
5216 * @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
5217 * @param {String} where (optional) 'before' or 'after' defaults to before
\r
5218 * @param {Boolean} returnDom (optional) True to return the raw DOM element instead of Ext.Element
\r
5219 * @return {Ext.Element} the inserted Element
\r
5221 insertSibling: function(el, where, returnDom){
\r
5225 if(Ext.isArray(el)){
\r
5226 Ext.each(el, function(e) {
\r
5227 rt = me.insertSibling(e, where, returnDom);
\r
5232 where = (where || 'before').toLowerCase();
\r
5235 if(el.nodeType || el.dom){
\r
5236 rt = me.dom.parentNode.insertBefore(GETDOM(el), where == 'before' ? me.dom : me.dom.nextSibling);
\r
5241 if (where == 'after' && !me.dom.nextSibling) {
\r
5242 rt = DH.append(me.dom.parentNode, el, !returnDom);
\r
5244 rt = DH[where == 'after' ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
\r
5251 * @class Ext.Element
5253 Ext.Element.addMethods(function(){
5254 // local style camelizing for speed
5256 camelRe = /(-[a-z])/gi,
5258 view = document.defaultView,
5259 propFloat = Ext.isIE ? 'styleFloat' : 'cssFloat',
5260 opacityRe = /alpha\(opacity=(.*)\)/i,
5261 trimRe = /^\s+|\s+$/g,
5263 PADDING = "padding",
5273 ISCLIPPED = 'isClipped',
5274 OVERFLOW = 'overflow',
5275 OVERFLOWX = 'overflow-x',
5276 OVERFLOWY = 'overflow-y',
5277 ORIGINALCLIP = 'originalClip',
5278 // special markup used throughout Ext when box wrapping elements
5279 borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
5280 paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
5281 margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
5282 data = Ext.Element.data;
5286 function camelFn(m, a) {
5287 return a.charAt(1).toUpperCase();
5290 function chkCache(prop) {
5291 return propCache[prop] || (propCache[prop] = prop == 'float' ? propFloat : prop.replace(camelRe, camelFn));
5295 // private ==> used by Fx
5296 adjustWidth : function(width) {
5298 var isNum = Ext.isNumber(width);
5299 if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
5300 width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
5302 return (isNum && width < 0) ? 0 : width;
5305 // private ==> used by Fx
5306 adjustHeight : function(height) {
5308 var isNum = Ext.isNumber(height);
5309 if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
5310 height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
5312 return (isNum && height < 0) ? 0 : height;
5317 * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.
5318 * @param {String/Array} className The CSS class to add, or an array of classes
5319 * @return {Ext.Element} this
5321 addClass : function(className){
5323 Ext.each(className, function(v) {
5324 me.dom.className += (!me.hasClass(v) && v ? " " + v : "");
5330 * Adds one or more CSS classes to this element and removes the same class(es) from all siblings.
5331 * @param {String/Array} className The CSS class to add, or an array of classes
5332 * @return {Ext.Element} this
5334 radioClass : function(className){
5335 Ext.each(this.dom.parentNode.childNodes, function(v) {
5336 if(v.nodeType == 1) {
5337 Ext.fly(v, '_internal').removeClass(className);
5340 return this.addClass(className);
5344 * Removes one or more CSS classes from the element.
5345 * @param {String/Array} className The CSS class to remove, or an array of classes
5346 * @return {Ext.Element} this
5348 removeClass : function(className){
5350 if (me.dom && me.dom.className) {
5351 Ext.each(className, function(v) {
5352 me.dom.className = me.dom.className.replace(
5353 classReCache[v] = classReCache[v] || new RegExp('(?:^|\\s+)' + v + '(?:\\s+|$)', "g"),
5361 * Toggles the specified CSS class on this element (removes it if it already exists, otherwise adds it).
5362 * @param {String} className The CSS class to toggle
5363 * @return {Ext.Element} this
5365 toggleClass : function(className){
5366 return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
5370 * Checks if the specified CSS class exists on this element's DOM node.
5371 * @param {String} className The CSS class to check for
5372 * @return {Boolean} True if the class exists, else false
5374 hasClass : function(className){
5375 return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
5379 * Replaces a CSS class on the element with another. If the old name does not exist, the new name will simply be added.
5380 * @param {String} oldClassName The CSS class to replace
5381 * @param {String} newClassName The replacement CSS class
5382 * @return {Ext.Element} this
5384 replaceClass : function(oldClassName, newClassName){
5385 return this.removeClass(oldClassName).addClass(newClassName);
5388 isStyle : function(style, val) {
5389 return this.getStyle(style) == val;
5393 * Normalizes currentStyle and computedStyle.
5394 * @param {String} property The style property whose value is returned.
5395 * @return {String} The current value of the style property for this element.
5397 getStyle : function(){
5398 return view && view.getComputedStyle ?
5404 if(el == document) return null;
5405 prop = chkCache(prop);
5406 out = (v = el.style[prop]) ? v :
5407 (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
5409 // Webkit returns rgb values for transparent.
5410 if(Ext.isWebKit && out == 'rgba(0, 0, 0, 0)'){
5411 out = 'transparent';
5420 if(el == document) return null;
5421 if (prop == 'opacity') {
5422 if (el.style.filter.match) {
5423 if(m = el.style.filter.match(opacityRe)){
5424 var fv = parseFloat(m[1]);
5426 return fv ? fv / 100 : 0;
5432 prop = chkCache(prop);
5433 return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
5438 * Return the CSS color for the specified CSS attribute. rgb, 3 digit (like #fff) and valid values
5439 * are convert to standard 6 digit hex color.
5440 * @param {String} attr The css attribute
5441 * @param {String} defaultValue The default value to use when a valid color isn't found
5442 * @param {String} prefix (optional) defaults to #. Use an empty string when working with
5445 getColor : function(attr, defaultValue, prefix){
5446 var v = this.getStyle(attr),
5447 color = Ext.isDefined(prefix) ? prefix : '#',
5450 if(!v || /transparent|inherit/.test(v)){
5451 return defaultValue;
5454 Ext.each(v.slice(4, v.length -1).split(','), function(s){
5455 h = parseInt(s, 10);
5456 color += (h < 16 ? '0' : '') + h.toString(16);
5459 v = v.replace('#', '');
5460 color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
5462 return(color.length > 5 ? color.toLowerCase() : defaultValue);
5466 * Wrapper for setting style properties, also takes single object parameter of multiple styles.
5467 * @param {String/Object} property The style property to be set, or an object of multiple styles.
5468 * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
5469 * @return {Ext.Element} this
5471 setStyle : function(prop, value){
5475 if (!Ext.isObject(prop)) {
5480 for (style in prop) {
5481 value = prop[style];
5482 style == 'opacity' ?
5483 this.setOpacity(value) :
5484 this.dom.style[chkCache(style)] = value;
5490 * Set the opacity of the element
5491 * @param {Float} opacity The new opacity. 0 = transparent, .5 = 50% visibile, 1 = fully visible, etc
5492 * @param {Boolean/Object} animate (optional) a standard Element animation config object or <tt>true</tt> for
5493 * the default animation (<tt>{duration: .35, easing: 'easeIn'}</tt>)
5494 * @return {Ext.Element} this
5496 setOpacity : function(opacity, animate){
5500 if(!animate || !me.anim){
5502 var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
5503 val = s.filter.replace(opacityRe, '').replace(trimRe, '');
5506 s.filter = val + (val.length > 0 ? ' ' : '') + opac;
5508 s.opacity = opacity;
5511 me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
5517 * Clears any opacity settings from this element. Required in some cases for IE.
5518 * @return {Ext.Element} this
5520 clearOpacity : function(){
5521 var style = this.dom.style;
5523 if(!Ext.isEmpty(style.filter)){
5524 style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
5527 style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
5533 * Returns the offset height of the element
5534 * @param {Boolean} contentHeight (optional) true to get the height minus borders and padding
5535 * @return {Number} The element's height
5537 getHeight : function(contentHeight){
5540 hidden = Ext.isIE && me.isStyle('display', 'none'),
5541 h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
5543 h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
5544 return h < 0 ? 0 : h;
5548 * Returns the offset width of the element
5549 * @param {Boolean} contentWidth (optional) true to get the width minus borders and padding
5550 * @return {Number} The element's width
5552 getWidth : function(contentWidth){
5555 hidden = Ext.isIE && me.isStyle('display', 'none'),
5556 w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
5557 w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
5558 return w < 0 ? 0 : w;
5562 * Set the width of this Element.
5563 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
5564 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
5565 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
5567 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
5568 * @return {Ext.Element} this
5570 setWidth : function(width, animate){
5572 width = me.adjustWidth(width);
5573 !animate || !me.anim ?
5574 me.dom.style.width = me.addUnits(width) :
5575 me.anim({width : {to : width}}, me.preanim(arguments, 1));
5580 * Set the height of this Element.
5582 // change the height to 200px and animate with default configuration
5583 Ext.fly('elementId').setHeight(200, true);
5585 // change the height to 150px and animate with a custom configuration
5586 Ext.fly('elId').setHeight(150, {
5587 duration : .5, // animation will have a duration of .5 seconds
5588 // will change the content to "finished"
5589 callback: function(){ this.{@link #update}("finished"); }
5592 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
5593 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels.)</li>
5594 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
5596 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
5597 * @return {Ext.Element} this
5599 setHeight : function(height, animate){
5601 height = me.adjustHeight(height);
5602 !animate || !me.anim ?
5603 me.dom.style.height = me.addUnits(height) :
5604 me.anim({height : {to : height}}, me.preanim(arguments, 1));
5609 * Gets the width of the border(s) for the specified side(s)
5610 * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
5611 * passing <tt>'lr'</tt> would get the border <b><u>l</u></b>eft width + the border <b><u>r</u></b>ight width.
5612 * @return {Number} The width of the sides passed added together
5614 getBorderWidth : function(side){
5615 return this.addStyles(side, borders);
5619 * Gets the width of the padding(s) for the specified side(s)
5620 * @param {String} side Can be t, l, r, b or any combination of those to add multiple values. For example,
5621 * passing <tt>'lr'</tt> would get the padding <b><u>l</u></b>eft + the padding <b><u>r</u></b>ight.
5622 * @return {Number} The padding of the sides passed added together
5624 getPadding : function(side){
5625 return this.addStyles(side, paddings);
5629 * Store the current overflow setting and clip overflow on the element - use <tt>{@link #unclip}</tt> to remove
5630 * @return {Ext.Element} this
5636 if(!data(dom, ISCLIPPED)){
5637 data(dom, ISCLIPPED, true);
5638 data(dom, ORIGINALCLIP, {
5639 o: me.getStyle(OVERFLOW),
5640 x: me.getStyle(OVERFLOWX),
5641 y: me.getStyle(OVERFLOWY)
5643 me.setStyle(OVERFLOW, HIDDEN);
5644 me.setStyle(OVERFLOWX, HIDDEN);
5645 me.setStyle(OVERFLOWY, HIDDEN);
5651 * Return clipping (overflow) to original clipping before <tt>{@link #clip}</tt> was called
5652 * @return {Ext.Element} this
5654 unclip : function(){
5658 if(data(dom, ISCLIPPED)){
5659 data(dom, ISCLIPPED, false);
5660 var o = data(dom, ORIGINALCLIP);
5662 me.setStyle(OVERFLOW, o.o);
5665 me.setStyle(OVERFLOWX, o.x);
5668 me.setStyle(OVERFLOWY, o.y);
5675 addStyles : function(sides, styles){
5678 Ext.each(sides.match(/\w/g), function(s) {
5679 if (s = parseInt(this.getStyle(styles[s]), 10)) {
5692 * @class Ext.Element
\r
5695 // special markup used throughout Ext when box wrapping elements
\r
5696 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
5698 Ext.Element.addMethods(function(){
\r
5699 var INTERNAL = "_internal";
\r
5702 * More flexible version of {@link #setStyle} for setting style properties.
\r
5703 * @param {String/Object/Function} styles A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
\r
5704 * a function which returns such a specification.
\r
5705 * @return {Ext.Element} this
\r
5707 applyStyles : function(style){
\r
5708 Ext.DomHelper.applyStyles(this.dom, style);
\r
5713 * Returns an object with properties matching the styles requested.
\r
5714 * For example, el.getStyles('color', 'font-size', 'width') might return
\r
5715 * {'color': '#FFFFFF', 'font-size': '13px', 'width': '100px'}.
\r
5716 * @param {String} style1 A style name
\r
5717 * @param {String} style2 A style name
\r
5718 * @param {String} etc.
\r
5719 * @return {Object} The style object
\r
5721 getStyles : function(){
\r
5723 Ext.each(arguments, function(v) {
\r
5724 ret[v] = this.getStyle(v);
\r
5730 getStyleSize : function(){
\r
5736 if(s.width && s.width != 'auto'){
\r
5737 w = parseInt(s.width, 10);
\r
5738 if(me.isBorderBox()){
\r
5739 w -= me.getFrameWidth('lr');
\r
5742 if(s.height && s.height != 'auto'){
\r
5743 h = parseInt(s.height, 10);
\r
5744 if(me.isBorderBox()){
\r
5745 h -= me.getFrameWidth('tb');
\r
5748 return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
\r
5751 // private ==> used by ext full
\r
5752 setOverflow : function(v){
\r
5753 var dom = this.dom;
\r
5754 if(v=='auto' && Ext.isMac && Ext.isGecko2){ // work around stupid FF 2.0/Mac scroll bar bug
\r
5755 dom.style.overflow = 'hidden';
\r
5756 (function(){dom.style.overflow = 'auto';}).defer(1);
\r
5758 dom.style.overflow = v;
\r
5763 * <p>Wraps the specified element with a special 9 element markup/CSS block that renders by default as
\r
5764 * a gray container with a gradient background, rounded corners and a 4-way shadow.</p>
\r
5765 * <p>This special markup is used throughout Ext when box wrapping elements ({@link Ext.Button},
\r
5766 * {@link Ext.Panel} when <tt>{@link Ext.Panel#frame frame=true}</tt>, {@link Ext.Window}). The markup
\r
5767 * is of this form:</p>
\r
5769 Ext.Element.boxMarkup =
\r
5770 '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div>
\r
5771 <div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div>
\r
5772 <div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
\r
5774 * <p>Example usage:</p>
\r
5777 Ext.get("foo").boxWrap();
\r
5779 // You can also add a custom class and use CSS inheritance rules to customize the box look.
\r
5780 // 'x-box-blue' is a built-in alternative -- look at the related CSS definitions as an example
\r
5781 // for how to create a custom box wrap style.
\r
5782 Ext.get("foo").boxWrap().addClass("x-box-blue");
\r
5784 * @param {String} class (optional) A base CSS class to apply to the containing wrapper element
\r
5785 * (defaults to <tt>'x-box'</tt>). Note that there are a number of CSS rules that are dependent on
\r
5786 * this name to make the overall effect work, so if you supply an alternate base class, make sure you
\r
5787 * also supply all of the necessary rules.
\r
5788 * @return {Ext.Element} this
\r
5790 boxWrap : function(cls){
\r
5791 cls = cls || 'x-box';
\r
5792 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
5793 Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
\r
5798 * Set the size of this Element. If animation is true, both width and height will be animated concurrently.
\r
5799 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
\r
5800 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels).</li>
\r
5801 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
\r
5802 * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
\r
5804 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
\r
5805 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels).</li>
\r
5806 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
\r
5808 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
5809 * @return {Ext.Element} this
\r
5811 setSize : function(width, height, animate){
\r
5813 if(Ext.isObject(width)){ // in case of object from getSize()
\r
5814 height = width.height;
\r
5815 width = width.width;
\r
5817 width = me.adjustWidth(width);
\r
5818 height = me.adjustHeight(height);
\r
5819 if(!animate || !me.anim){
\r
5820 me.dom.style.width = me.addUnits(width);
\r
5821 me.dom.style.height = me.addUnits(height);
\r
5823 me.anim({width: {to: width}, height: {to: height}}, me.preanim(arguments, 2));
\r
5829 * Returns either the offsetHeight or the height of this element based on CSS height adjusted by padding or borders
\r
5830 * when needed to simulate offsetHeight when offsets aren't available. This may not work on display:none elements
\r
5831 * if a height has not been set using CSS.
\r
5832 * @return {Number}
\r
5834 getComputedHeight : function(){
\r
5836 h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
\r
5838 h = parseInt(me.getStyle('height'), 10) || 0;
\r
5839 if(!me.isBorderBox()){
\r
5840 h += me.getFrameWidth('tb');
\r
5847 * Returns either the offsetWidth or the width of this element based on CSS width adjusted by padding or borders
\r
5848 * when needed to simulate offsetWidth when offsets aren't available. This may not work on display:none elements
\r
5849 * if a width has not been set using CSS.
\r
5850 * @return {Number}
\r
5852 getComputedWidth : function(){
\r
5853 var w = Math.max(this.dom.offsetWidth, this.dom.clientWidth);
\r
5855 w = parseInt(this.getStyle('width'), 10) || 0;
\r
5856 if(!this.isBorderBox()){
\r
5857 w += this.getFrameWidth('lr');
\r
5864 * Returns the sum width of the padding and borders for the passed "sides". See getBorderWidth()
\r
5865 for more information about the sides.
\r
5866 * @param {String} sides
\r
5867 * @return {Number}
\r
5869 getFrameWidth : function(sides, onlyContentBox){
\r
5870 return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
\r
5874 * Sets up event handlers to add and remove a css class when the mouse is over this element
\r
5875 * @param {String} className
\r
5876 * @return {Ext.Element} this
\r
5878 addClassOnOver : function(className){
\r
5881 Ext.fly(this, INTERNAL).addClass(className);
\r
5884 Ext.fly(this, INTERNAL).removeClass(className);
\r
5891 * Sets up event handlers to add and remove a css class when this element has the focus
\r
5892 * @param {String} className
\r
5893 * @return {Ext.Element} this
\r
5895 addClassOnFocus : function(className){
\r
5896 this.on("focus", function(){
\r
5897 Ext.fly(this, INTERNAL).addClass(className);
\r
5899 this.on("blur", function(){
\r
5900 Ext.fly(this, INTERNAL).removeClass(className);
\r
5906 * 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
5907 * @param {String} className
\r
5908 * @return {Ext.Element} this
\r
5910 addClassOnClick : function(className){
\r
5911 var dom = this.dom;
\r
5912 this.on("mousedown", function(){
\r
5913 Ext.fly(dom, INTERNAL).addClass(className);
\r
5914 var d = Ext.getDoc(),
\r
5916 Ext.fly(dom, INTERNAL).removeClass(className);
\r
5917 d.removeListener("mouseup", fn);
\r
5919 d.on("mouseup", fn);
\r
5925 * Returns the width and height of the viewport.
\r
5927 var vpSize = Ext.getBody().getViewSize();
\r
5929 // all Windows created afterwards will have a default value of 90% height and 95% width
\r
5930 Ext.Window.override({
\r
5931 width: vpSize.width * 0.9,
\r
5932 height: vpSize.height * 0.95
\r
5934 // To handle window resizing you would have to hook onto onWindowResize.
\r
5936 * @return {Object} An object containing the viewport's size {width: (viewport width), height: (viewport height)}
\r
5938 getViewSize : function(){
\r
5939 var doc = document,
\r
5941 extdom = Ext.lib.Dom,
\r
5942 isDoc = (d == doc || d == doc.body);
\r
5943 return { width : (isDoc ? extdom.getViewWidth() : d.clientWidth),
\r
5944 height : (isDoc ? extdom.getViewHeight() : d.clientHeight) };
\r
5948 * Returns the size of the element.
\r
5949 * @param {Boolean} contentSize (optional) true to get the width/size minus borders and padding
\r
5950 * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
\r
5952 getSize : function(contentSize){
\r
5953 return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
\r
5957 * Forces the browser to repaint this element
\r
5958 * @return {Ext.Element} this
\r
5960 repaint : function(){
\r
5961 var dom = this.dom;
\r
5962 this.addClass("x-repaint");
\r
5963 setTimeout(function(){
\r
5964 Ext.fly(dom).removeClass("x-repaint");
\r
5970 * Disables text selection for this element (normalized across browsers)
\r
5971 * @return {Ext.Element} this
\r
5973 unselectable : function(){
\r
5974 this.dom.unselectable = "on";
\r
5975 return this.swallowEvent("selectstart", true).
\r
5976 applyStyles("-moz-user-select:none;-khtml-user-select:none;").
\r
5977 addClass("x-unselectable");
\r
5981 * Returns an object with properties top, left, right and bottom representing the margins of this element unless sides is passed,
\r
5982 * then it returns the calculated width of the sides (see getPadding)
\r
5983 * @param {String} sides (optional) Any combination of l, r, t, b to get the sum of those sides
\r
5984 * @return {Object/Number}
\r
5986 getMargins : function(side){
\r
5989 hash = {t:"top", l:"left", r:"right", b: "bottom"},
\r
5993 for (key in me.margins){
\r
5994 o[hash[key]] = parseInt(me.getStyle(me.margins[key]), 10) || 0;
\r
5998 return me.addStyles.call(me, side, me.margins);
\r
6003 * @class Ext.Element
\r
6006 var D = Ext.lib.Dom,
\r
6010 BOTTOM = "bottom",
\r
6011 POSITION = "position",
\r
6012 STATIC = "static",
\r
6013 RELATIVE = "relative",
\r
6015 ZINDEX = "z-index";
\r
6017 Ext.Element.addMethods({
\r
6019 * 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
6020 * @return {Number} The X position of the element
\r
6022 getX : function(){
\r
6023 return D.getX(this.dom);
\r
6027 * 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
6028 * @return {Number} The Y position of the element
\r
6030 getY : function(){
\r
6031 return D.getY(this.dom);
\r
6035 * 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
6036 * @return {Array} The XY position of the element
\r
6038 getXY : function(){
\r
6039 return D.getXY(this.dom);
\r
6043 * 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
6044 * @param {Mixed} element The element to get the offsets from.
\r
6045 * @return {Array} The XY page offsets (e.g. [100, -200])
\r
6047 getOffsetsTo : function(el){
\r
6048 var o = this.getXY(),
\r
6049 e = Ext.fly(el, '_internal').getXY();
\r
6050 return [o[0]-e[0],o[1]-e[1]];
\r
6054 * 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
6055 * @param {Number} The X position of the element
\r
6056 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6057 * @return {Ext.Element} this
\r
6059 setX : function(x, animate){
\r
6060 return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1));
\r
6064 * 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
6065 * @param {Number} The Y position of the element
\r
6066 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6067 * @return {Ext.Element} this
\r
6069 setY : function(y, animate){
\r
6070 return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1));
\r
6074 * Sets the element's left position directly using CSS style (instead of {@link #setX}).
\r
6075 * @param {String} left The left CSS property value
\r
6076 * @return {Ext.Element} this
\r
6078 setLeft : function(left){
\r
6079 this.setStyle(LEFT, this.addUnits(left));
\r
6084 * Sets the element's top position directly using CSS style (instead of {@link #setY}).
\r
6085 * @param {String} top The top CSS property value
\r
6086 * @return {Ext.Element} this
\r
6088 setTop : function(top){
\r
6089 this.setStyle(TOP, this.addUnits(top));
\r
6094 * Sets the element's CSS right style.
\r
6095 * @param {String} right The right CSS property value
\r
6096 * @return {Ext.Element} this
\r
6098 setRight : function(right){
\r
6099 this.setStyle(RIGHT, this.addUnits(right));
\r
6104 * Sets the element's CSS bottom style.
\r
6105 * @param {String} bottom The bottom CSS property value
\r
6106 * @return {Ext.Element} this
\r
6108 setBottom : function(bottom){
\r
6109 this.setStyle(BOTTOM, this.addUnits(bottom));
\r
6114 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
6115 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
6116 * @param {Array} pos Contains X & Y [x, y] values for new position (coordinates are page-based)
\r
6117 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6118 * @return {Ext.Element} this
\r
6120 setXY : function(pos, animate){
\r
6122 if(!animate || !me.anim){
\r
6123 D.setXY(me.dom, pos);
\r
6125 me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
\r
6131 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
6132 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
6133 * @param {Number} x X value for new position (coordinates are page-based)
\r
6134 * @param {Number} y Y value for new position (coordinates are page-based)
\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 setLocation : function(x, y, animate){
\r
6139 return this.setXY([x, y], this.animTest(arguments, animate, 2));
\r
6143 * Sets the position of the element in page coordinates, regardless of how the element is positioned.
\r
6144 * The element must be part of the DOM tree to have page coordinates (display:none or elements not appended return false).
\r
6145 * @param {Number} x X value for new position (coordinates are page-based)
\r
6146 * @param {Number} y Y value for new position (coordinates are page-based)
\r
6147 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6148 * @return {Ext.Element} this
\r
6150 moveTo : function(x, y, animate){
\r
6151 return this.setXY([x, y], this.animTest(arguments, animate, 2));
\r
6155 * Gets the left X coordinate
\r
6156 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6157 * @return {Number}
\r
6159 getLeft : function(local){
\r
6160 return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
\r
6164 * Gets the right X coordinate of the element (element X position + element width)
\r
6165 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6166 * @return {Number}
\r
6168 getRight : function(local){
\r
6170 return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
\r
6174 * Gets the top Y coordinate
\r
6175 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6176 * @return {Number}
\r
6178 getTop : function(local) {
\r
6179 return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
\r
6183 * Gets the bottom Y coordinate of the element (element Y position + element height)
\r
6184 * @param {Boolean} local True to get the local css position instead of page coordinate
\r
6185 * @return {Number}
\r
6187 getBottom : function(local){
\r
6189 return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
\r
6193 * Initializes positioning on this element. If a desired position is not passed, it will make the
\r
6194 * the element positioned relative IF it is not already positioned.
\r
6195 * @param {String} pos (optional) Positioning to use "relative", "absolute" or "fixed"
\r
6196 * @param {Number} zIndex (optional) The zIndex to apply
\r
6197 * @param {Number} x (optional) Set the page X position
\r
6198 * @param {Number} y (optional) Set the page Y position
\r
6200 position : function(pos, zIndex, x, y){
\r
6203 if(!pos && me.isStyle(POSITION, STATIC)){
\r
6204 me.setStyle(POSITION, RELATIVE);
\r
6206 me.setStyle(POSITION, pos);
\r
6209 me.setStyle(ZINDEX, zIndex);
\r
6211 if(x || y) me.setXY([x || false, y || false]);
\r
6215 * Clear positioning back to the default when the document was loaded
\r
6216 * @param {String} value (optional) The value to use for the left,right,top,bottom, defaults to '' (empty string). You could use 'auto'.
\r
6217 * @return {Ext.Element} this
\r
6219 clearPositioning : function(value){
\r
6220 value = value || '';
\r
6233 * Gets an object with all CSS positioning properties. Useful along with setPostioning to get
\r
6234 * snapshot before performing an update and then restoring the element.
\r
6235 * @return {Object}
\r
6237 getPositioning : function(){
\r
6238 var l = this.getStyle(LEFT);
\r
6239 var t = this.getStyle(TOP);
\r
6241 "position" : this.getStyle(POSITION),
\r
6243 "right" : l ? "" : this.getStyle(RIGHT),
\r
6245 "bottom" : t ? "" : this.getStyle(BOTTOM),
\r
6246 "z-index" : this.getStyle(ZINDEX)
\r
6251 * Set positioning with an object returned by getPositioning().
\r
6252 * @param {Object} posCfg
\r
6253 * @return {Ext.Element} this
\r
6255 setPositioning : function(pc){
\r
6257 style = me.dom.style;
\r
6261 if(pc.right == AUTO){
\r
6264 if(pc.bottom == AUTO){
\r
6265 style.bottom = "";
\r
6272 * Translates the passed page coordinates into left/top css values for this element
\r
6273 * @param {Number/Array} x The page x or an array containing [x, y]
\r
6274 * @param {Number} y (optional) The page y, required if x is not an array
\r
6275 * @return {Object} An object with left and top properties. e.g. {left: (value), top: (value)}
\r
6277 translatePoints : function(x, y){
\r
6278 y = isNaN(x[1]) ? y : x[1];
\r
6279 x = isNaN(x[0]) ? x : x[0];
\r
6281 relative = me.isStyle(POSITION, RELATIVE),
\r
6283 l = parseInt(me.getStyle(LEFT), 10),
\r
6284 t = parseInt(me.getStyle(TOP), 10);
\r
6286 l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
\r
6287 t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
\r
6289 return {left: (x - o[0] + l), top: (y - o[1] + t)};
\r
6292 animTest : function(args, animate, i) {
\r
6293 return !!animate && this.preanim ? this.preanim(args, i) : false;
\r
6297 * @class Ext.Element
\r
6299 Ext.Element.addMethods({
\r
6301 * 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
6302 * @param {Object} box The box to fill {x, y, width, height}
\r
6303 * @param {Boolean} adjust (optional) Whether to adjust for box-model issues automatically
\r
6304 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6305 * @return {Ext.Element} this
\r
6307 setBox : function(box, adjust, animate){
\r
6311 if((adjust && !me.autoBoxAdjust) && !me.isBorderBox()){
\r
6312 w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
\r
6313 h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
\r
6315 me.setBounds(box.x, box.y, w, h, me.animTest.call(me, arguments, animate, 2));
\r
6320 * Return a box {x, y, width, height} that can be used to set another elements
\r
6321 * size/location to match this element.
\r
6322 * @param {Boolean} contentBox (optional) If true a box for the content of the element is returned.
\r
6323 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page x/y.
\r
6324 * @return {Object} box An object in the format {x, y, width, height}
\r
6326 getBox : function(contentBox, local) {
\r
6331 getBorderWidth = me.getBorderWidth,
\r
6332 getPadding = me.getPadding,
\r
6340 left = parseInt(me.getStyle("left"), 10) || 0;
\r
6341 top = parseInt(me.getStyle("top"), 10) || 0;
\r
6344 var el = me.dom, w = el.offsetWidth, h = el.offsetHeight, bx;
\r
6346 bx = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: w, height: h};
\r
6348 l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
\r
6349 r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
\r
6350 t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
\r
6351 b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
\r
6352 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
6354 bx.right = bx.x + bx.width;
\r
6355 bx.bottom = bx.y + bx.height;
\r
6360 * Move this element relative to its current position.
\r
6361 * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
\r
6362 * @param {Number} distance How far to move the element in pixels
\r
6363 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6364 * @return {Ext.Element} this
\r
6366 move : function(direction, distance, animate){
\r
6371 left = [x - distance, y],
\r
6372 right = [x + distance, y],
\r
6373 top = [x, y - distance],
\r
6374 bottom = [x, y + distance],
\r
6388 direction = direction.toLowerCase();
\r
6389 me.moveTo(hash[direction][0], hash[direction][1], me.animTest.call(me, arguments, animate, 2));
\r
6393 * Quick set left and top adding default units
\r
6394 * @param {String} left The left CSS property value
\r
6395 * @param {String} top The top CSS property value
\r
6396 * @return {Ext.Element} this
\r
6398 setLeftTop : function(left, top){
\r
6400 style = me.dom.style;
\r
6401 style.left = me.addUnits(left);
\r
6402 style.top = me.addUnits(top);
\r
6407 * Returns the region of the given element.
\r
6408 * The element must be part of the DOM tree to have a region (display:none or elements not appended return false).
\r
6409 * @return {Region} A Ext.lib.Region containing "top, left, bottom, right" member data.
\r
6411 getRegion : function(){
\r
6412 return Ext.lib.Dom.getRegion(this.dom);
\r
6416 * 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
6417 * @param {Number} x X value for new position (coordinates are page-based)
\r
6418 * @param {Number} y Y value for new position (coordinates are page-based)
\r
6419 * @param {Mixed} width The new width. This may be one of:<div class="mdetail-params"><ul>
\r
6420 * <li>A Number specifying the new width in this Element's {@link #defaultUnit}s (by default, pixels)</li>
\r
6421 * <li>A String used to set the CSS width style. Animation may <b>not</b> be used.
\r
6423 * @param {Mixed} height The new height. This may be one of:<div class="mdetail-params"><ul>
\r
6424 * <li>A Number specifying the new height in this Element's {@link #defaultUnit}s (by default, pixels)</li>
\r
6425 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
\r
6427 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6428 * @return {Ext.Element} this
\r
6430 setBounds : function(x, y, width, height, animate){
\r
6432 if (!animate || !me.anim) {
\r
6433 me.setSize(width, height);
\r
6434 me.setLocation(x, y);
\r
6436 me.anim({points: {to: [x, y]},
\r
6437 width: {to: me.adjustWidth(width)},
\r
6438 height: {to: me.adjustHeight(height)}},
\r
6439 me.preanim(arguments, 4),
\r
6446 * 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
6447 * @param {Ext.lib.Region} region The region to fill
\r
6448 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6449 * @return {Ext.Element} this
\r
6451 setRegion : function(region, animate) {
\r
6452 return this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, this.animTest.call(this, arguments, animate, 1));
\r
6455 * @class Ext.Element
\r
6457 Ext.Element.addMethods({
\r
6459 * Returns true if this element is scrollable.
\r
6460 * @return {Boolean}
\r
6462 isScrollable : function(){
\r
6463 var dom = this.dom;
\r
6464 return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
\r
6468 * 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
6469 * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
\r
6470 * @param {Number} value The new scroll value.
\r
6471 * @return {Element} this
\r
6473 scrollTo : function(side, value){
\r
6474 this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
\r
6479 * Returns the current scroll position of the element.
\r
6480 * @return {Object} An object containing the scroll position in the format {left: (scrollLeft), top: (scrollTop)}
\r
6482 getScroll : function(){
\r
6483 var d = this.dom,
\r
6486 docElement = doc.documentElement,
\r
6491 if(d == doc || d == body){
\r
6492 if(Ext.isIE && Ext.isStrict){
\r
6493 l = docElement.scrollLeft;
\r
6494 t = docElement.scrollTop;
\r
6496 l = window.pageXOffset;
\r
6497 t = window.pageYOffset;
\r
6499 ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
\r
6501 ret = {left: d.scrollLeft, top: d.scrollTop};
\r
6506 * @class Ext.Element
\r
6508 Ext.Element.addMethods({
\r
6510 * 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
6511 * @param {String} side Either "left" for scrollLeft values or "top" for scrollTop values.
\r
6512 * @param {Number} value The new scroll value
\r
6513 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6514 * @return {Element} this
\r
6516 scrollTo : function(side, value, animate){
\r
6517 var top = /top/i.test(side), //check if we're scrolling top or left
\r
6518 prop = 'scroll' + (top ? 'Left' : 'Top'), // if scrolling top, we need to grab scrollLeft, if left, scrollTop
\r
6521 if (!animate || !me.anim) {
\r
6522 dom[prop] = value;
\r
6524 me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}},
\r
6525 me.preanim(arguments, 2), 'scroll');
\r
6531 * Scrolls this element into view within the passed container.
\r
6532 * @param {Mixed} container (optional) The container element to scroll (defaults to document.body). Should be a
\r
6533 * string (id), dom node, or Ext.Element.
\r
6534 * @param {Boolean} hscroll (optional) False to disable horizontal scroll (defaults to true)
\r
6535 * @return {Ext.Element} this
\r
6537 scrollIntoView : function(container, hscroll){
\r
6538 var c = Ext.getDom(container) || Ext.getBody().dom,
\r
6540 o = this.getOffsetsTo(c),
\r
6541 l = o[0] + c.scrollLeft,
\r
6542 t = o[1] + c.scrollTop,
\r
6543 b = t + el.offsetHeight,
\r
6544 r = l + el.offsetWidth,
\r
6545 ch = c.clientHeight,
\r
6546 ct = parseInt(c.scrollTop, 10),
\r
6547 cl = parseInt(c.scrollLeft, 10),
\r
6549 cr = cl + c.clientWidth;
\r
6551 if (el.offsetHeight > ch || t < ct) {
\r
6553 } else if (b > cb){
\r
6554 c.scrollTop = b-ch;
\r
6556 c.scrollTop = c.scrollTop; // corrects IE, other browsers will ignore
\r
6558 if(hscroll !== false){
\r
6559 if(el.offsetWidth > c.clientWidth || l < cl){
\r
6562 c.scrollLeft = r - c.clientWidth;
\r
6564 c.scrollLeft = c.scrollLeft;
\r
6570 scrollChildIntoView : function(child, hscroll){
\r
6571 Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
\r
6575 * Scrolls this element the specified direction. Does bounds checking to make sure the scroll is
\r
6576 * within this element's scrollable range.
\r
6577 * @param {String} direction Possible values are: "l" (or "left"), "r" (or "right"), "t" (or "top", or "up"), "b" (or "bottom", or "down").
\r
6578 * @param {Number} distance How far to scroll the element in pixels
\r
6579 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6580 * @return {Boolean} Returns true if a scroll was triggered or false if the element
\r
6581 * was scrolled as far as it could go.
\r
6583 scroll : function(direction, distance, animate){
\r
6584 if(!this.isScrollable()){
\r
6587 var el = this.dom,
\r
6588 l = el.scrollLeft, t = el.scrollTop,
\r
6589 w = el.scrollWidth, h = el.scrollHeight,
\r
6590 cw = el.clientWidth, ch = el.clientHeight,
\r
6591 scrolled = false, v,
\r
6593 l: Math.min(l + distance, w-cw),
\r
6594 r: v = Math.max(l - distance, 0),
\r
6595 t: Math.max(t - distance, 0),
\r
6596 b: Math.min(t + distance, h-ch)
\r
6601 direction = direction.substr(0, 1);
\r
6602 if((v = hash[direction]) > -1){
\r
6604 this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.preanim(arguments, 2));
\r
6609 * @class Ext.Element
\r
6612 * Visibility mode constant for use with {@link #setVisibilityMode}. Use visibility to hide element
\r
6616 Ext.Element.VISIBILITY = 1;
\r
6618 * Visibility mode constant for use with {@link #setVisibilityMode}. Use display to hide element
\r
6622 Ext.Element.DISPLAY = 2;
\r
6624 Ext.Element.addMethods(function(){
\r
6625 var VISIBILITY = "visibility",
\r
6626 DISPLAY = "display",
\r
6627 HIDDEN = "hidden",
\r
6629 ORIGINALDISPLAY = 'originalDisplay',
\r
6630 VISMODE = 'visibilityMode',
\r
6631 ELDISPLAY = Ext.Element.DISPLAY,
\r
6632 data = Ext.Element.data,
\r
6633 getDisplay = function(dom){
\r
6634 var d = data(dom, ORIGINALDISPLAY);
\r
6635 if(d === undefined){
\r
6636 data(dom, ORIGINALDISPLAY, d = '');
\r
6640 getVisMode = function(dom){
\r
6641 var m = data(dom, VISMODE);
\r
6642 if(m === undefined){
\r
6643 data(dom, VISMODE, m = 1)
\r
6650 * The element's default display mode (defaults to "")
\r
6653 originalDisplay : "",
\r
6654 visibilityMode : 1,
\r
6657 * Sets the element's visibility mode. When setVisible() is called it
\r
6658 * will use this to determine whether to set the visibility or the display property.
\r
6659 * @param {Number} visMode Ext.Element.VISIBILITY or Ext.Element.DISPLAY
\r
6660 * @return {Ext.Element} this
\r
6662 setVisibilityMode : function(visMode){
\r
6663 data(this.dom, VISMODE, visMode);
\r
6668 * Perform custom animation on this element.
\r
6669 * <div><ul class="mdetail-params">
\r
6670 * <li><u>Animation Properties</u></li>
\r
6672 * <p>The Animation Control Object enables gradual transitions for any member of an
\r
6673 * element's style object that takes a numeric value including but not limited to
\r
6674 * these properties:</p><div><ul class="mdetail-params">
\r
6675 * <li><tt>bottom, top, left, right</tt></li>
\r
6676 * <li><tt>height, width</tt></li>
\r
6677 * <li><tt>margin, padding</tt></li>
\r
6678 * <li><tt>borderWidth</tt></li>
\r
6679 * <li><tt>opacity</tt></li>
\r
6680 * <li><tt>fontSize</tt></li>
\r
6681 * <li><tt>lineHeight</tt></li>
\r
6685 * <li><u>Animation Property Attributes</u></li>
\r
6687 * <p>Each Animation Property is a config object with optional properties:</p>
\r
6688 * <div><ul class="mdetail-params">
\r
6689 * <li><tt>by</tt>* : relative change - start at current value, change by this value</li>
\r
6690 * <li><tt>from</tt> : ignore current value, start from this value</li>
\r
6691 * <li><tt>to</tt>* : start at current value, go to this value</li>
\r
6692 * <li><tt>unit</tt> : any allowable unit specification</li>
\r
6693 * <p>* do not specify both <tt>to</tt> and <tt>by</tt> for an animation property</p>
\r
6696 * <li><u>Animation Types</u></li>
\r
6698 * <p>The supported animation types:</p><div><ul class="mdetail-params">
\r
6699 * <li><tt>'run'</tt> : Default
\r
6701 var el = Ext.get('complexEl');
\r
6703 // animation control object
\r
6705 borderWidth: {to: 3, from: 0},
\r
6706 opacity: {to: .3, from: 1},
\r
6707 height: {to: 50, from: el.getHeight()},
\r
6708 width: {to: 300, from: el.getWidth()},
\r
6709 top : {by: - 100, unit: 'px'},
\r
6711 0.35, // animation duration
\r
6713 'easeOut', // easing method
\r
6714 'run' // animation type ('run','color','motion','scroll')
\r
6718 * <li><tt>'color'</tt>
\r
6719 * <p>Animates transition of background, text, or border colors.</p>
\r
6722 // animation control object
\r
6724 color: { to: '#06e' },
\r
6725 backgroundColor: { to: '#e06' }
\r
6727 0.35, // animation duration
\r
6729 'easeOut', // easing method
\r
6730 'color' // animation type ('run','color','motion','scroll')
\r
6735 * <li><tt>'motion'</tt>
\r
6736 * <p>Animates the motion of an element to/from specific points using optional bezier
\r
6737 * way points during transit.</p>
\r
6740 // animation control object
\r
6742 borderWidth: {to: 3, from: 0},
\r
6743 opacity: {to: .3, from: 1},
\r
6744 height: {to: 50, from: el.getHeight()},
\r
6745 width: {to: 300, from: el.getWidth()},
\r
6746 top : {by: - 100, unit: 'px'},
\r
6748 to: [50, 100], // go to this point
\r
6749 control: [ // optional bezier way points
\r
6755 3000, // animation duration (milliseconds!)
\r
6757 'easeOut', // easing method
\r
6758 'motion' // animation type ('run','color','motion','scroll')
\r
6762 * <li><tt>'scroll'</tt>
\r
6763 * <p>Animate horizontal or vertical scrolling of an overflowing page element.</p>
\r
6766 // animation control object
\r
6768 scroll: {to: [400, 300]}
\r
6770 0.35, // animation duration
\r
6772 'easeOut', // easing method
\r
6773 'scroll' // animation type ('run','color','motion','scroll')
\r
6781 * @param {Object} args The animation control args
\r
6782 * @param {Float} duration (optional) How long the animation lasts in seconds (defaults to <tt>.35</tt>)
\r
6783 * @param {Function} onComplete (optional) Function to call when animation completes
\r
6784 * @param {String} easing (optional) {@link Ext.Fx#easing} method to use (defaults to <tt>'easeOut'</tt>)
\r
6785 * @param {String} animType (optional) <tt>'run'</tt> is the default. Can also be <tt>'color'</tt>,
\r
6786 * <tt>'motion'</tt>, or <tt>'scroll'</tt>
\r
6787 * @return {Ext.Element} this
\r
6789 animate : function(args, duration, onComplete, easing, animType){
\r
6790 this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
\r
6795 * @private Internal animation call
\r
6797 anim : function(args, opt, animType, defaultDur, defaultEase, cb){
\r
6798 animType = animType || 'run';
\r
6801 anim = Ext.lib.Anim[animType](
\r
6804 (opt.duration || defaultDur) || .35,
\r
6805 (opt.easing || defaultEase) || 'easeOut',
\r
6807 if(cb) cb.call(me);
\r
6808 if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
\r
6816 // private legacy anim prep
\r
6817 preanim : function(a, i){
\r
6818 return !a[i] ? false : (Ext.isObject(a[i]) ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
\r
6822 * Checks whether the element is currently visible using both visibility and display properties.
\r
6823 * @return {Boolean} True if the element is currently visible, else false
\r
6825 isVisible : function() {
\r
6826 return !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE);
\r
6830 * Sets the visibility of the element (see details). If the visibilityMode is set to Element.DISPLAY, it will use
\r
6831 * the display property to hide the element, otherwise it uses visibility. The default is to hide and show using the visibility property.
\r
6832 * @param {Boolean} visible Whether the element is visible
\r
6833 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6834 * @return {Ext.Element} this
\r
6836 setVisible : function(visible, animate){
\r
6839 isDisplay = getVisMode(this.dom) == ELDISPLAY;
\r
6841 if (!animate || !me.anim) {
\r
6843 me.setDisplayed(visible);
\r
6846 dom.style.visibility = visible ? "visible" : HIDDEN;
\r
6849 // closure for composites
\r
6851 me.setOpacity(.01);
\r
6852 me.setVisible(true);
\r
6854 me.anim({opacity: { to: (visible?1:0) }},
\r
6855 me.preanim(arguments, 1),
\r
6861 dom.style[isDisplay ? DISPLAY : VISIBILITY] = (isDisplay) ? NONE : HIDDEN;
\r
6862 Ext.fly(dom).setOpacity(1);
\r
6870 * Toggles the element's visibility or display, depending on visibility mode.
\r
6871 * @param {Boolean/Object} animate (optional) True for the default animation, or a standard Element animation config object
\r
6872 * @return {Ext.Element} this
\r
6874 toggle : function(animate){
\r
6876 me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
\r
6881 * Sets the CSS display property. Uses originalDisplay if the specified value is a boolean true.
\r
6882 * @param {Mixed} value Boolean value to display the element using its default display, or a string to set the display directly.
\r
6883 * @return {Ext.Element} this
\r
6885 setDisplayed : function(value) {
\r
6886 if(typeof value == "boolean"){
\r
6887 value = value ? getDisplay(this.dom) : NONE;
\r
6889 this.setStyle(DISPLAY, value);
\r
6894 fixDisplay : function(){
\r
6896 if(me.isStyle(DISPLAY, NONE)){
\r
6897 me.setStyle(VISIBILITY, HIDDEN);
\r
6898 me.setStyle(DISPLAY, getDisplay(this.dom)); // first try reverting to default
\r
6899 if(me.isStyle(DISPLAY, NONE)){ // if that fails, default to block
\r
6900 me.setStyle(DISPLAY, "block");
\r
6906 * Hide this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
\r
6907 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6908 * @return {Ext.Element} this
\r
6910 hide : function(animate){
\r
6911 this.setVisible(false, this.preanim(arguments, 0));
\r
6916 * Show this element - Uses display mode to determine whether to use "display" or "visibility". See {@link #setVisible}.
\r
6917 * @param {Boolean/Object} animate (optional) true for the default animation or a standard Element animation config object
\r
6918 * @return {Ext.Element} this
\r
6920 show : function(animate){
\r
6921 this.setVisible(true, this.preanim(arguments, 0));
\r
6926 * @class Ext.Element
\r
6928 Ext.Element.addMethods(
\r
6930 var VISIBILITY = "visibility",
\r
6931 DISPLAY = "display",
\r
6932 HIDDEN = "hidden",
\r
6934 XMASKED = "x-masked",
\r
6935 XMASKEDRELATIVE = "x-masked-relative",
\r
6936 data = Ext.Element.data;
\r
6940 * Checks whether the element is currently visible using both visibility and display properties.
\r
6941 * @param {Boolean} deep (optional) True to walk the dom and see if parent elements are hidden (defaults to false)
\r
6942 * @return {Boolean} True if the element is currently visible, else false
\r
6944 isVisible : function(deep) {
\r
6945 var vis = !this.isStyle(VISIBILITY,HIDDEN) && !this.isStyle(DISPLAY,NONE),
\r
6946 p = this.dom.parentNode;
\r
6947 if(deep !== true || !vis){
\r
6950 while(p && !/body/i.test(p.tagName)){
\r
6951 if(!Ext.fly(p, '_isVisible').isVisible()){
\r
6960 * Returns true if display is not "none"
\r
6961 * @return {Boolean}
\r
6963 isDisplayed : function() {
\r
6964 return !this.isStyle(DISPLAY, NONE);
\r
6968 * Convenience method for setVisibilityMode(Element.DISPLAY)
\r
6969 * @param {String} display (optional) What to set display to when visible
\r
6970 * @return {Ext.Element} this
\r
6972 enableDisplayMode : function(display){
\r
6973 this.setVisibilityMode(Ext.Element.DISPLAY);
\r
6974 if(!Ext.isEmpty(display)){
\r
6975 data(this.dom, 'originalDisplay', display);
\r
6981 * Puts a mask over this element to disable user interaction. Requires core.css.
\r
6982 * This method can only be applied to elements which accept child nodes.
\r
6983 * @param {String} msg (optional) A message to display in the mask
\r
6984 * @param {String} msgCls (optional) A css class to apply to the msg element
\r
6985 * @return {Element} The mask element
\r
6987 mask : function(msg, msgCls){
\r
6990 dh = Ext.DomHelper,
\r
6991 EXTELMASKMSG = "ext-el-mask-msg",
\r
6995 if(me.getStyle("position") == "static"){
\r
6996 me.addClass(XMASKEDRELATIVE);
\r
6998 if((el = data(dom, 'maskMsg'))){
\r
7001 if((el = data(dom, 'mask'))){
\r
7005 mask = dh.append(dom, {cls : "ext-el-mask"}, true);
\r
7006 data(dom, 'mask', mask);
\r
7008 me.addClass(XMASKED);
\r
7009 mask.setDisplayed(true);
\r
7010 if(typeof msg == 'string'){
\r
7011 var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
\r
7012 data(dom, 'maskMsg', mm);
\r
7013 mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
\r
7014 mm.dom.firstChild.innerHTML = msg;
\r
7015 mm.setDisplayed(true);
\r
7018 if(Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto'){ // ie will not expand full height automatically
\r
7019 mask.setSize(undefined, me.getHeight());
\r
7025 * Removes a previously applied mask.
\r
7027 unmask : function(){
\r
7030 mask = data(dom, 'mask'),
\r
7031 maskMsg = data(dom, 'maskMsg');
\r
7035 data(dom, 'maskMsg', undefined);
\r
7038 data(dom, 'mask', undefined);
\r
7040 me.removeClass([XMASKED, XMASKEDRELATIVE]);
\r
7044 * Returns true if this element is masked
\r
7045 * @return {Boolean}
\r
7047 isMasked : function(){
\r
7048 var m = data(this.dom, 'mask');
\r
7049 return m && m.isVisible();
\r
7053 * Creates an iframe shim for this element to keep selects and other windowed objects from
\r
7054 * showing through.
\r
7055 * @return {Ext.Element} The new shim element
\r
7057 createShim : function(){
\r
7058 var el = document.createElement('iframe'),
\r
7060 el.frameBorder = '0';
\r
7061 el.className = 'ext-shim';
\r
7062 el.src = Ext.SSL_SECURE_URL;
\r
7063 shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
\r
7064 shim.autoBoxAdjust = false;
\r
7069 * @class Ext.Element
\r
7071 Ext.Element.addMethods({
\r
7073 * Convenience method for constructing a KeyMap
\r
7074 * @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
7075 * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
\r
7076 * @param {Function} fn The function to call
\r
7077 * @param {Object} scope (optional) The scope of the function
\r
7078 * @return {Ext.KeyMap} The KeyMap created
\r
7080 addKeyListener : function(key, fn, scope){
\r
7082 if(!Ext.isObject(key) || Ext.isArray(key)){
\r
7091 shift : key.shift,
\r
7098 return new Ext.KeyMap(this, config);
\r
7102 * Creates a KeyMap for this element
\r
7103 * @param {Object} config The KeyMap config. See {@link Ext.KeyMap} for more details
\r
7104 * @return {Ext.KeyMap} The KeyMap created
\r
7106 addKeyMap : function(config){
\r
7107 return new Ext.KeyMap(this, config);
\r
7112 UNDEFINED = undefined,
\r
7119 BOTTOM = "bottom",
\r
7122 HEIGHT = "height",
\r
7124 POINTS = "points",
\r
7125 HIDDEN = "hidden",
\r
7126 ABSOLUTE = "absolute",
\r
7127 VISIBLE = "visible",
\r
7128 MOTION = "motion",
\r
7129 POSITION = "position",
\r
7130 EASEOUT = "easeOut",
\r
7132 * Use a light flyweight here since we are using so many callbacks and are always assured a DOM element
\r
7134 flyEl = new Ext.Element.Flyweight(),
\r
7136 getObject = function(o){
\r
7139 fly = function(dom){
\r
7141 flyEl.id = Ext.id(dom);
\r
7145 * Queueing now stored outside of the element due to closure issues
\r
7147 getQueue = function(id){
\r
7151 return queues[id];
\r
7153 setQueue = function(id, value){
\r
7154 queues[id] = value;
\r
7157 //Notifies Element that fx methods are available
\r
7158 Ext.enableFx = TRUE;
\r
7162 * <p>A class to provide basic animation and visual effects support. <b>Note:</b> This class is automatically applied
\r
7163 * to the {@link Ext.Element} interface when included, so all effects calls should be performed via {@link Ext.Element}.
\r
7164 * Conversely, since the effects are not actually defined in {@link Ext.Element}, Ext.Fx <b>must</b> be
\r
7165 * {@link Ext#enableFx included} in order for the Element effects to work.</p><br/>
\r
7167 * <p><b><u>Method Chaining</u></b></p>
\r
7168 * <p>It is important to note that although the Fx methods and many non-Fx Element methods support "method chaining" in that
\r
7169 * they return the Element object itself as the method return value, it is not always possible to mix the two in a single
\r
7170 * method chain. The Fx methods use an internal effects queue so that each effect can be properly timed and sequenced.
\r
7171 * Non-Fx methods, on the other hand, have no such internal queueing and will always execute immediately. For this reason,
\r
7172 * 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
7173 * expected results and should be done with care. Also see <tt>{@link #callback}</tt>.</p><br/>
\r
7175 * <p><b><u>Anchor Options for Motion Effects</u></b></p>
\r
7176 * <p>Motion effects support 8-way anchoring, meaning that you can choose one of 8 different anchor points on the Element
\r
7177 * that will serve as either the start or end point of the animation. Following are all of the supported anchor positions:</p>
\r
7180 ----- -----------------------------
\r
7181 tl The top left corner
\r
7182 t The center of the top edge
\r
7183 tr The top right corner
\r
7184 l The center of the left edge
\r
7185 r The center of the right edge
\r
7186 bl The bottom left corner
\r
7187 b The center of the bottom edge
\r
7188 br The bottom right corner
\r
7190 * <b>Note</b>: some Fx methods accept specific custom config parameters. The options shown in the Config Options
\r
7191 * section below are common options that can be passed to any Fx method unless otherwise noted.</b>
\r
7193 * @cfg {Function} callback A function called when the effect is finished. Note that effects are queued internally by the
\r
7194 * Fx class, so a callback is not required to specify another effect -- effects can simply be chained together
\r
7195 * and called in sequence (see note for <b><u>Method Chaining</u></b> above), for example:<pre><code>
\r
7196 * el.slideIn().highlight();
\r
7198 * The callback is intended for any additional code that should run once a particular effect has completed. The Element
\r
7199 * being operated upon is passed as the first parameter.
\r
7201 * @cfg {Object} scope The scope (<code>this</code> reference) in which the <tt>{@link #callback}</tt> function is executed. Defaults to the browser window.
\r
7203 * @cfg {String} easing A valid Ext.lib.Easing value for the effect:</p><div class="mdetail-params"><ul>
\r
7204 * <li><b><tt>backBoth</tt></b></li>
\r
7205 * <li><b><tt>backIn</tt></b></li>
\r
7206 * <li><b><tt>backOut</tt></b></li>
\r
7207 * <li><b><tt>bounceBoth</tt></b></li>
\r
7208 * <li><b><tt>bounceIn</tt></b></li>
\r
7209 * <li><b><tt>bounceOut</tt></b></li>
\r
7210 * <li><b><tt>easeBoth</tt></b></li>
\r
7211 * <li><b><tt>easeBothStrong</tt></b></li>
\r
7212 * <li><b><tt>easeIn</tt></b></li>
\r
7213 * <li><b><tt>easeInStrong</tt></b></li>
\r
7214 * <li><b><tt>easeNone</tt></b></li>
\r
7215 * <li><b><tt>easeOut</tt></b></li>
\r
7216 * <li><b><tt>easeOutStrong</tt></b></li>
\r
7217 * <li><b><tt>elasticBoth</tt></b></li>
\r
7218 * <li><b><tt>elasticIn</tt></b></li>
\r
7219 * <li><b><tt>elasticOut</tt></b></li>
\r
7222 * @cfg {String} afterCls A css class to apply after the effect
\r
7223 * @cfg {Number} duration The length of time (in seconds) that the effect should last
\r
7225 * @cfg {Number} endOpacity Only applicable for {@link #fadeIn} or {@link #fadeOut}, a number between
\r
7226 * <tt>0</tt> and <tt>1</tt> inclusive to configure the ending opacity value.
\r
7228 * @cfg {Boolean} remove Whether the Element should be removed from the DOM and destroyed after the effect finishes
\r
7229 * @cfg {Boolean} useDisplay Whether to use the <i>display</i> CSS property instead of <i>visibility</i> when hiding Elements (only applies to
\r
7230 * effects that end with the element being visually hidden, ignored otherwise)
\r
7231 * @cfg {String/Object/Function} afterStyle A style specification string, e.g. <tt>"width:100px"</tt>, or an object
\r
7232 * in the form <tt>{width:"100px"}</tt>, or a function which returns such a specification that will be applied to the
\r
7233 * Element after the effect finishes.
\r
7234 * @cfg {Boolean} block Whether the effect should block other effects from queueing while it runs
\r
7235 * @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
7236 * @cfg {Boolean} stopFx Whether preceding effects should be stopped and removed before running current effect (only applies to non blocking effects)
\r
7240 // private - calls the function taking arguments from the argHash based on the key. Returns the return value of the function.
\r
7241 // this is useful for replacing switch statements (for example).
\r
7242 switchStatements : function(key, fn, argHash){
\r
7243 return fn.apply(this, argHash[key]);
\r
7247 * Slides the element into view. An anchor point can be optionally passed to set the point of
\r
7248 * origin for the slide effect. This function automatically handles wrapping the element with
\r
7249 * a fixed-size container if needed. See the Fx class overview for valid anchor point options.
\r
7252 // default: slide the element in from the top
\r
7255 // custom: slide the element in from the right with a 2-second duration
\r
7256 el.slideIn('r', { duration: 2 });
\r
7258 // common config options shown with default values
\r
7260 easing: 'easeOut',
\r
7264 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
\r
7265 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7266 * @return {Ext.Element} The Element
\r
7268 slideIn : function(anchor, o){
\r
7284 anchor = anchor || "t";
\r
7286 me.queueFx(o, function(){
\r
7287 xy = fly(dom).getXY();
\r
7288 // fix display to visibility
\r
7289 fly(dom).fixDisplay();
\r
7291 // restore values after effect
\r
7292 r = fly(dom).getFxRestore();
\r
7293 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
\r
7294 b.right = b.x + b.width;
\r
7295 b.bottom = b.y + b.height;
\r
7297 // fixed size for slide
\r
7298 fly(dom).setWidth(b.width).setHeight(b.height);
\r
7301 wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
\r
7303 st.visibility = VISIBLE;
\r
7304 st.position = ABSOLUTE;
\r
7306 // clear out temp styles after slide and unwrap
\r
7308 fly(dom).fxUnwrap(wrap, r.pos, o);
\r
7309 st.width = r.width;
\r
7310 st.height = r.height;
\r
7311 fly(dom).afterFx(o);
\r
7314 // time to calculate the positions
\r
7315 pt = {to: [b.x, b.y]};
\r
7316 bw = {to: b.width};
\r
7317 bh = {to: b.height};
\r
7319 function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
\r
7321 fly(wrap).setWidth(ww).setHeight(wh);
\r
7322 if(fly(wrap)[sXY]){
\r
7323 fly(wrap)[sXY](sXYval);
\r
7325 style[s1] = style[s2] = "0";
\r
7338 args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
\r
7339 t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
\r
7340 l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
\r
7341 r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
\r
7342 b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
\r
7343 tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
\r
7344 bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
\r
7345 br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
\r
7346 tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
\r
7349 st.visibility = VISIBLE;
\r
7352 arguments.callee.anim = fly(wrap).fxanim(args,
\r
7363 * Slides the element out of view. An anchor point can be optionally passed to set the end point
\r
7364 * for the slide effect. When the effect is completed, the element will be hidden (visibility =
\r
7365 * 'hidden') but block elements will still take up space in the document. The element must be removed
\r
7366 * from the DOM using the 'remove' config option if desired. This function automatically handles
\r
7367 * wrapping the element with a fixed-size container if needed. See the Fx class overview for valid anchor point options.
\r
7370 // default: slide the element out to the top
\r
7373 // custom: slide the element out to the right with a 2-second duration
\r
7374 el.slideOut('r', { duration: 2 });
\r
7376 // common config options shown with default values
\r
7377 el.slideOut('t', {
\r
7378 easing: 'easeOut',
\r
7384 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to top: 't')
\r
7385 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7386 * @return {Ext.Element} The Element
\r
7388 slideOut : function(anchor, o){
\r
7400 anchor = anchor || "t";
\r
7402 me.queueFx(o, function(){
\r
7404 // restore values after effect
\r
7405 r = fly(dom).getFxRestore();
\r
7406 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
\r
7407 b.right = b.x + b.width;
\r
7408 b.bottom = b.y + b.height;
\r
7410 // fixed size for slide
\r
7411 fly(dom).setWidth(b.width).setHeight(b.height);
\r
7414 wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
\r
7416 st.visibility = VISIBLE;
\r
7417 st.position = ABSOLUTE;
\r
7418 fly(wrap).setWidth(b.width).setHeight(b.height);
\r
7421 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7422 fly(dom).fxUnwrap(wrap, r.pos, o);
\r
7423 st.width = r.width;
\r
7424 st.height = r.height;
\r
7425 fly(dom).afterFx(o);
\r
7428 function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
\r
7431 style[s1] = style[s2] = "0";
\r
7443 a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
\r
7444 t : [st, LEFT, BOTTOM, HEIGHT, zero],
\r
7445 l : [st, RIGHT, TOP, WIDTH, zero],
\r
7446 r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
\r
7447 b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
\r
7448 tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
\r
7449 bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
\r
7450 br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
\r
7451 tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
\r
7454 arguments.callee.anim = fly(wrap).fxanim(a,
\r
7465 * Fades the element out while slowly expanding it in all directions. When the effect is completed, the
\r
7466 * element will be hidden (visibility = 'hidden') but block elements will still take up space in the document.
\r
7467 * The element must be removed from the DOM using the 'remove' config option if desired.
\r
7473 // common config options shown with default values
\r
7475 easing: 'easeOut',
\r
7481 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7482 * @return {Ext.Element} The Element
\r
7484 puff : function(o){
\r
7493 me.queueFx(o, function(){
\r
7494 width = fly(dom).getWidth();
\r
7495 height = fly(dom).getHeight();
\r
7496 fly(dom).clearOpacity();
\r
7499 // restore values after effect
\r
7500 r = fly(dom).getFxRestore();
\r
7503 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7504 fly(dom).clearOpacity();
\r
7505 fly(dom).setPositioning(r.pos);
\r
7506 st.width = r.width;
\r
7507 st.height = r.height;
\r
7509 fly(dom).afterFx(o);
\r
7512 arguments.callee.anim = fly(dom).fxanim({
\r
7513 width : {to : fly(dom).adjustWidth(width * 2)},
\r
7514 height : {to : fly(dom).adjustHeight(height * 2)},
\r
7515 points : {by : [-width * .5, -height * .5]},
\r
7516 opacity : {to : 0},
\r
7517 fontSize: {to : 200, unit: "%"}
\r
7529 * Blinks the element as if it was clicked and then collapses on its center (similar to switching off a television).
\r
7530 * When the effect is completed, the element will be hidden (visibility = 'hidden') but block elements will still
\r
7531 * take up space in the document. The element must be removed from the DOM using the 'remove' config option if desired.
\r
7537 // all config options shown with default values
\r
7545 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7546 * @return {Ext.Element} The Element
\r
7548 switchOff : function(o){
\r
7555 me.queueFx(o, function(){
\r
7556 fly(dom).clearOpacity();
\r
7559 // restore values after effect
\r
7560 r = fly(dom).getFxRestore();
\r
7563 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7564 fly(dom).clearOpacity();
\r
7565 fly(dom).setPositioning(r.pos);
\r
7566 st.width = r.width;
\r
7567 st.height = r.height;
\r
7568 fly(dom).afterFx(o);
\r
7571 fly(dom).fxanim({opacity : {to : 0.3}},
\r
7577 fly(dom).clearOpacity();
\r
7580 height : {to : 1},
\r
7581 points : {by : [0, fly(dom).getHeight() * .5]}
\r
7595 * Highlights the Element by setting a color (applies to the background-color by default, but can be
\r
7596 * changed using the "attr" config option) and then fading back to the original color. If no original
\r
7597 * color is available, you should provide the "endColor" config option which will be cleared after the animation.
\r
7600 // default: highlight background to yellow
\r
7603 // custom: highlight foreground text to blue for 2 seconds
\r
7604 el.highlight("0000ff", { attr: 'color', duration: 2 });
\r
7606 // common config options shown with default values
\r
7607 el.highlight("ffff9c", {
\r
7608 attr: "background-color", //can be any valid CSS property (attribute) that supports a color value
\r
7609 endColor: (current color) or "ffffff",
\r
7614 * @param {String} color (optional) The highlight color. Should be a 6 char hex color without the leading # (defaults to yellow: 'ffff9c')
\r
7615 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7616 * @return {Ext.Element} The Element
\r
7618 highlight : function(color, o){
\r
7622 attr = o.attr || "backgroundColor",
\r
7626 me.queueFx(o, function(){
\r
7627 fly(dom).clearOpacity();
\r
7631 dom.style[attr] = restore;
\r
7632 fly(dom).afterFx(o);
\r
7634 restore = dom.style[attr];
\r
7635 a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
\r
7636 arguments.callee.anim = fly(dom).fxanim(a,
\r
7647 * Shows a ripple of exploding, attenuating borders to draw attention to an Element.
\r
7650 // default: a single light blue ripple
\r
7653 // custom: 3 red ripples lasting 3 seconds total
\r
7654 el.frame("ff0000", 3, { duration: 3 });
\r
7656 // common config options shown with default values
\r
7657 el.frame("C3DAF9", 1, {
\r
7658 duration: 1 //duration of each individual ripple.
\r
7659 // Note: Easing is not configurable and will be ignored if included
\r
7662 * @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
7663 * @param {Number} count (optional) The number of ripples to display (defaults to 1)
\r
7664 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7665 * @return {Ext.Element} The Element
\r
7667 frame : function(color, count, o){
\r
7674 me.queueFx(o, function(){
\r
7675 color = color || '#C3DAF9'
\r
7676 if(color.length == 6){
\r
7677 color = '#' + color;
\r
7679 count = count || 1;
\r
7682 var xy = fly(dom).getXY(),
\r
7683 b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
\r
7684 queue = function(){
\r
7685 proxy = fly(document.body || document.documentElement).createChild({
\r
7687 position : ABSOLUTE,
\r
7688 'z-index': 35000, // yee haw
\r
7689 border : '0px solid ' + color
\r
7692 return proxy.queueFx({}, animFn);
\r
7696 arguments.callee.anim = {
\r
7698 stop: function() {
\r
7704 function animFn(){
\r
7705 var scale = Ext.isBorderBox ? 2 : 1;
\r
7706 active = proxy.anim({
\r
7707 top : {from : b.y, to : b.y - 20},
\r
7708 left : {from : b.x, to : b.x - 20},
\r
7709 borderWidth : {from : 0, to : 10},
\r
7710 opacity : {from : 1, to : 0},
\r
7711 height : {from : b.height, to : b.height + 20 * scale},
\r
7712 width : {from : b.width, to : b.width + 20 * scale}
\r
7714 duration: o.duration || 1,
\r
7715 callback: function() {
\r
7717 --count > 0 ? queue() : fly(dom).afterFx(o);
\r
7720 arguments.callee.anim = {
\r
7733 * Creates a pause before any subsequent queued effects begin. If there are
\r
7734 * no effects queued after the pause it will have no effect.
\r
7739 * @param {Number} seconds The length of time to pause (in seconds)
\r
7740 * @return {Ext.Element} The Element
\r
7742 pause : function(seconds){
\r
7743 var dom = this.dom,
\r
7746 this.queueFx({}, function(){
\r
7747 t = setTimeout(function(){
\r
7748 fly(dom).afterFx({});
\r
7749 }, seconds * 1000);
\r
7750 arguments.callee.anim = {
\r
7754 fly(dom).afterFx({});
\r
7762 * Fade an element in (from transparent to opaque). The ending opacity can be specified
\r
7763 * using the <tt>{@link #endOpacity}</tt> config option.
\r
7766 // default: fade in from opacity 0 to 100%
\r
7769 // custom: fade in from opacity 0 to 75% over 2 seconds
\r
7770 el.fadeIn({ endOpacity: .75, duration: 2});
\r
7772 // common config options shown with default values
\r
7774 endOpacity: 1, //can be any value between 0 and 1 (e.g. .5)
\r
7775 easing: 'easeOut',
\r
7779 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7780 * @return {Ext.Element} The Element
\r
7782 fadeIn : function(o){
\r
7786 to = o.endOpacity || 1;
\r
7788 me.queueFx(o, function(){
\r
7789 fly(dom).setOpacity(0);
\r
7790 fly(dom).fixDisplay();
\r
7791 dom.style.visibility = VISIBLE;
\r
7792 arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
\r
7793 o, NULL, .5, EASEOUT, function(){
\r
7795 fly(dom).clearOpacity();
\r
7797 fly(dom).afterFx(o);
\r
7804 * Fade an element out (from opaque to transparent). The ending opacity can be specified
\r
7805 * using the <tt>{@link #endOpacity}</tt> config option. Note that IE may require
\r
7806 * <tt>{@link #useDisplay}:true</tt> in order to redisplay correctly.
\r
7809 // default: fade out from the element's current opacity to 0
\r
7812 // custom: fade out from the element's current opacity to 25% over 2 seconds
\r
7813 el.fadeOut({ endOpacity: .25, duration: 2});
\r
7815 // common config options shown with default values
\r
7817 endOpacity: 0, //can be any value between 0 and 1 (e.g. .5)
\r
7818 easing: 'easeOut',
\r
7824 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7825 * @return {Ext.Element} The Element
\r
7827 fadeOut : function(o){
\r
7831 style = dom.style,
\r
7832 to = o.endOpacity || 0;
\r
7834 me.queueFx(o, function(){
\r
7835 arguments.callee.anim = fly(dom).fxanim({
\r
7836 opacity : {to : to}},
\r
7843 Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
\r
7844 style.display = "none" :
\r
7845 style.visibility = HIDDEN;
\r
7847 fly(dom).clearOpacity();
\r
7849 fly(dom).afterFx(o);
\r
7856 * Animates the transition of an element's dimensions from a starting height/width
\r
7857 * to an ending height/width. This method is a convenience implementation of {@link shift}.
\r
7860 // change height and width to 100x100 pixels
\r
7861 el.scale(100, 100);
\r
7863 // common config options shown with default values. The height and width will default to
\r
7864 // the element's existing values if passed as null.
\r
7866 [element's width],
\r
7867 [element's height], {
\r
7868 easing: 'easeOut',
\r
7873 * @param {Number} width The new width (pass undefined to keep the original width)
\r
7874 * @param {Number} height The new height (pass undefined to keep the original height)
\r
7875 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7876 * @return {Ext.Element} The Element
\r
7878 scale : function(w, h, o){
\r
7879 this.shift(Ext.apply({}, o, {
\r
7887 * Animates the transition of any combination of an element's dimensions, xy position and/or opacity.
\r
7888 * Any of these properties not specified in the config object will not be changed. This effect
\r
7889 * requires that at least one new dimension, position or opacity setting must be passed in on
\r
7890 * the config object in order for the function to have any effect.
\r
7893 // slide the element horizontally to x position 200 while changing the height and opacity
\r
7894 el.shift({ x: 200, height: 50, opacity: .8 });
\r
7896 // common config options shown with default values.
\r
7898 width: [element's width],
\r
7899 height: [element's height],
\r
7900 x: [element's x position],
\r
7901 y: [element's y position],
\r
7902 opacity: [element's opacity],
\r
7903 easing: 'easeOut',
\r
7907 * @param {Object} options Object literal with any of the Fx config options
\r
7908 * @return {Ext.Element} The Element
\r
7910 shift : function(o){
\r
7912 var dom = this.dom,
\r
7915 this.queueFx(o, function(){
\r
7916 for (var prop in o) {
\r
7917 if (o[prop] != UNDEFINED) {
\r
7918 a[prop] = {to : o[prop]};
\r
7922 a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
\r
7923 a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
\r
7925 if (a.x || a.y || a.xy) {
\r
7926 a.points = a.xy ||
\r
7927 {to : [ a.x ? a.x.to : fly(dom).getX(),
\r
7928 a.y ? a.y.to : fly(dom).getY()]};
\r
7931 arguments.callee.anim = fly(dom).fxanim(a,
\r
7937 fly(dom).afterFx(o);
\r
7944 * Slides the element while fading it out of view. An anchor point can be optionally passed to set the
\r
7945 * ending point of the effect.
\r
7948 // default: slide the element downward while fading out
\r
7951 // custom: slide the element out to the right with a 2-second duration
\r
7952 el.ghost('r', { duration: 2 });
\r
7954 // common config options shown with default values
\r
7956 easing: 'easeOut',
\r
7962 * @param {String} anchor (optional) One of the valid Fx anchor positions (defaults to bottom: 'b')
\r
7963 * @param {Object} options (optional) Object literal with any of the Fx config options
\r
7964 * @return {Ext.Element} The Element
\r
7966 ghost : function(anchor, o){
\r
7971 a = {opacity: {to: 0}, points: {}},
\r
7977 anchor = anchor || "b";
\r
7979 me.queueFx(o, function(){
\r
7980 // restore values after effect
\r
7981 r = fly(dom).getFxRestore();
\r
7982 w = fly(dom).getWidth();
\r
7983 h = fly(dom).getHeight();
\r
7986 o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
\r
7987 fly(dom).clearOpacity();
\r
7988 fly(dom).setPositioning(r.pos);
\r
7989 st.width = r.width;
\r
7990 st.height = r.height;
\r
7991 fly(dom).afterFx(o);
\r
7994 pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
\r
8005 arguments.callee.anim = fly(dom).fxanim(a,
\r
8015 * Ensures that all effects queued after syncFx is called on the element are
\r
8016 * run concurrently. This is the opposite of {@link #sequenceFx}.
\r
8017 * @return {Ext.Element} The Element
\r
8019 syncFx : function(){
\r
8021 me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
\r
8023 concurrent : TRUE,
\r
8030 * Ensures that all effects queued after sequenceFx is called on the element are
\r
8031 * run in sequence. This is the opposite of {@link #syncFx}.
\r
8032 * @return {Ext.Element} The Element
\r
8034 sequenceFx : function(){
\r
8036 me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
\r
8038 concurrent : FALSE,
\r
8045 nextFx : function(){
\r
8046 var ef = getQueue(this.dom.id)[0];
\r
8053 * Returns true if the element has any effects actively running or queued, else returns false.
\r
8054 * @return {Boolean} True if element has active effects, else false
\r
8056 hasActiveFx : function(){
\r
8057 return getQueue(this.dom.id)[0];
\r
8061 * Stops any running effects and clears the element's internal effects queue if it contains
\r
8062 * any additional effects that haven't started yet.
\r
8063 * @return {Ext.Element} The Element
\r
8065 stopFx : function(finish){
\r
8068 if(me.hasActiveFx()){
\r
8069 var cur = getQueue(id)[0];
\r
8070 if(cur && cur.anim){
\r
8071 if(cur.anim.isAnimated){
\r
8072 setQueue(id, [cur]); //clear
\r
8073 cur.anim.stop(finish !== undefined ? finish : TRUE);
\r
8083 beforeFx : function(o){
\r
8084 if(this.hasActiveFx() && !o.concurrent){
\r
8095 * Returns true if the element is currently blocking so that no other effect can be queued
\r
8096 * until this effect is finished, else returns false if blocking is not set. This is commonly
\r
8097 * used to ensure that an effect initiated by a user action runs to completion prior to the
\r
8098 * same effect being restarted (e.g., firing only one effect even if the user clicks several times).
\r
8099 * @return {Boolean} True if blocking, else false
\r
8101 hasFxBlock : function(){
\r
8102 var q = getQueue(this.dom.id);
\r
8103 return q && q[0] && q[0].block;
\r
8107 queueFx : function(o, fn){
\r
8108 var me = fly(this.dom);
\r
8109 if(!me.hasFxBlock()){
\r
8110 Ext.applyIf(o, me.fxDefaults);
\r
8111 if(!o.concurrent){
\r
8112 var run = me.beforeFx(o);
\r
8113 fn.block = o.block;
\r
8114 getQueue(me.dom.id).push(fn);
\r
8126 fxWrap : function(pos, o, vis){
\r
8127 var dom = this.dom,
\r
8130 if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
\r
8131 if(o.fixPosition){
\r
8132 wrapXY = fly(dom).getXY();
\r
8134 var div = document.createElement("div");
\r
8135 div.style.visibility = vis;
\r
8136 wrap = dom.parentNode.insertBefore(div, dom);
\r
8137 fly(wrap).setPositioning(pos);
\r
8138 if(fly(wrap).isStyle(POSITION, "static")){
\r
8139 fly(wrap).position("relative");
\r
8141 fly(dom).clearPositioning('auto');
\r
8143 wrap.appendChild(dom);
\r
8145 fly(wrap).setXY(wrapXY);
\r
8152 fxUnwrap : function(wrap, pos, o){
\r
8153 var dom = this.dom;
\r
8154 fly(dom).clearPositioning();
\r
8155 fly(dom).setPositioning(pos);
\r
8157 var pn = fly(wrap).dom.parentNode;
8158 pn.insertBefore(dom, wrap);
\r
8159 fly(wrap).remove();
\r
8164 getFxRestore : function(){
\r
8165 var st = this.dom.style;
\r
8166 return {pos: this.getPositioning(), width: st.width, height : st.height};
\r
8170 afterFx : function(o){
\r
8171 var dom = this.dom,
\r
8174 fly(dom).setStyle(o.afterStyle);
\r
8177 fly(dom).addClass(o.afterCls);
\r
8179 if(o.remove == TRUE){
\r
8180 fly(dom).remove();
\r
8183 o.callback.call(o.scope, fly(dom));
\r
8185 if(!o.concurrent){
\r
8186 getQueue(id).shift();
\r
8187 fly(dom).nextFx();
\r
8192 fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
\r
8193 animType = animType || 'run';
\r
8195 var anim = Ext.lib.Anim[animType](
\r
8198 (opt.duration || defaultDur) || .35,
\r
8199 (opt.easing || defaultEase) || EASEOUT,
\r
8208 // backwards compat
\r
8209 Ext.Fx.resize = Ext.Fx.scale;
\r
8211 //When included, Ext.Fx is automatically applied to Element so that all basic
\r
8212 //effects are available directly via the Element API
\r
8213 Ext.Element.addMethods(Ext.Fx);
\r
8216 * @class Ext.CompositeElementLite
\r
8217 * <p>This class encapsulates a <i>collection</i> of DOM elements, providing methods to filter
\r
8218 * members, or to perform collective actions upon the whole set.</p>
\r
8219 * <p>Although they are not listed, this class supports all of the methods of {@link Ext.Element} and
\r
8220 * {@link Ext.Fx}. The methods from these classes will be performed on all the elements in this collection.</p>
\r
8221 * Example:<pre><code>
\r
8222 var els = Ext.select("#some-el div.some-class");
\r
8223 // or select directly from an existing element
\r
8224 var el = Ext.get('some-el');
\r
8225 el.select('div.some-class');
\r
8227 els.setWidth(100); // all elements become 100 width
\r
8228 els.hide(true); // all elements fade out and hide
\r
8230 els.setWidth(100).hide(true);
\r
8233 Ext.CompositeElementLite = function(els, root){
\r
8235 * <p>The Array of DOM elements which this CompositeElement encapsulates. Read-only.</p>
\r
8236 * <p>This will not <i>usually</i> be accessed in developers' code, but developers wishing
\r
8237 * to augment the capabilities of the CompositeElementLite class may use it when adding
\r
8238 * methods to the class.</p>
\r
8239 * <p>For example to add the <code>nextAll</code> method to the class to <b>add</b> all
\r
8240 * following siblings of selected elements, the code would be</p><code><pre>
\r
8241 Ext.override(Ext.CompositeElementLite, {
\r
8242 nextAll: function() {
\r
8243 var els = this.elements, i, l = els.length, n, r = [], ri = -1;
\r
8245 // Loop through all elements in this Composite, accumulating
\r
8246 // an Array of all siblings.
\r
8247 for (i = 0; i < l; i++) {
\r
8248 for (n = els[i].nextSibling; n; n = n.nextSibling) {
\r
8253 // Add all found siblings to this Composite
\r
8254 return this.add(r);
\r
8258 * @property elements
\r
8260 this.elements = [];
\r
8261 this.add(els, root);
\r
8262 this.el = new Ext.Element.Flyweight();
\r
8265 Ext.CompositeElementLite.prototype = {
\r
8266 isComposite: true,
\r
8268 * Returns the number of elements in this Composite.
\r
8271 getCount : function(){
\r
8272 return this.elements.length;
\r
8275 * Adds elements to this Composite object.
\r
8276 * @param {Mixed} els Either an Array of DOM elements to add, or another Composite object who's elements should be added.
\r
8277 * @return {CompositeElement} This Composite object.
\r
8279 add : function(els){
\r
8281 if (Ext.isArray(els)) {
\r
8282 this.elements = this.elements.concat(els);
\r
8284 var yels = this.elements;
\r
8285 Ext.each(els, function(e) {
\r
8292 invoke : function(fn, args){
\r
8293 var els = this.elements,
\r
8295 Ext.each(els, function(e) {
\r
8297 Ext.Element.prototype[fn].apply(el, args);
\r
8302 * Returns a flyweight Element of the dom element object at the specified index
\r
8303 * @param {Number} index
\r
8304 * @return {Ext.Element}
\r
8306 item : function(index){
\r
8308 if(!me.elements[index]){
\r
8311 me.el.dom = me.elements[index];
\r
8315 // fixes scope with flyweight
\r
8316 addListener : function(eventName, handler, scope, opt){
\r
8317 Ext.each(this.elements, function(e) {
\r
8318 Ext.EventManager.on(e, eventName, handler, scope || e, opt);
\r
8323 * <p>Calls the passed function for each element in this composite.</p>
\r
8324 * @param {Function} fn The function to call. The function is passed the following parameters:<ul>
\r
8325 * <li><b>el</b> : Element<div class="sub-desc">The current Element in the iteration.
\r
8326 * <b>This is the flyweight (shared) Ext.Element instance, so if you require a
\r
8327 * a reference to the dom node, use el.dom.</b></div></li>
\r
8328 * <li><b>c</b> : Composite<div class="sub-desc">This Composite object.</div></li>
\r
8329 * <li><b>idx</b> : Number<div class="sub-desc">The zero-based index in the iteration.</div></li>
\r
8331 * @param {Object} scope (optional) The scope (<i>this</i> reference) in which the function is executed. (defaults to the Element)
\r
8332 * @return {CompositeElement} this
\r
8334 each : function(fn, scope){
\r
8338 Ext.each(me.elements, function(e,i) {
\r
8340 return fn.call(scope || el, el, me, i);
\r
8346 * Clears this Composite and adds the elements passed.
\r
8347 * @param {Mixed} els Either an array of DOM elements, or another Composite from which to fill this Composite.
\r
8348 * @return {CompositeElement} this
\r
8350 fill : function(els){
\r
8358 * Filters this composite to only elements that match the passed selector.
\r
8359 * @param {String/Function} selector A string CSS selector or a comparison function.
\r
8360 * The comparison function will be called with the following arguments:<ul>
\r
8361 * <li><code>el</code> : Ext.Element<div class="sub-desc">The current DOM element.</div></li>
\r
8362 * <li><code>index</code> : Number<div class="sub-desc">The current index within the collection.</div></li>
\r
8364 * @return {CompositeElement} this
\r
8366 filter : function(selector){
\r
8369 fn = Ext.isFunction(selector) ? selector
\r
8371 return el.is(selector);
\r
8373 me.each(function(el, self, i){
\r
8374 if(fn(el, i) !== false){
\r
8375 els[els.length] = el.dom;
\r
8383 * Find the index of the passed element within the composite collection.
\r
8384 * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
\r
8385 * @return Number The index of the passed Ext.Element in the composite collection, or -1 if not found.
\r
8387 indexOf : function(el){
\r
8388 return this.elements.indexOf(Ext.getDom(el));
\r
8392 * Replaces the specified element with the passed element.
\r
8393 * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
\r
8395 * @param {Mixed} replacement The id of an element or the Element itself.
\r
8396 * @param {Boolean} domReplace (Optional) True to remove and replace the element in the document too.
\r
8397 * @return {CompositeElement} this
\r
8399 replaceElement : function(el, replacement, domReplace){
\r
8400 var index = !isNaN(el) ? el : this.indexOf(el),
\r
8403 replacement = Ext.getDom(replacement);
\r
8405 d = this.elements[index];
\r
8406 d.parentNode.insertBefore(replacement, d);
\r
8407 Ext.removeNode(d);
\r
8409 this.elements.splice(index, 1, replacement);
\r
8415 * Removes all elements.
\r
8417 clear : function(){
\r
8418 this.elements = [];
\r
8422 Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
\r
8426 ElProto = Ext.Element.prototype,
\r
8427 CelProto = Ext.CompositeElementLite.prototype;
\r
8429 for(fnName in ElProto){
\r
8430 if(Ext.isFunction(ElProto[fnName])){
\r
8431 (function(fnName){
\r
8432 CelProto[fnName] = CelProto[fnName] || function(){
\r
8433 return this.invoke(fnName, arguments);
\r
8435 }).call(CelProto, fnName);
\r
8442 Ext.Element.selectorFunction = Ext.DomQuery.select;
\r
8446 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8447 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8448 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8449 * @param {String/Array} selector The CSS selector or an array of elements
\r
8450 * @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
8451 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8452 * @return {CompositeElementLite/CompositeElement}
\r
8453 * @member Ext.Element
\r
8456 Ext.Element.select = function(selector, unique, root){
\r
8458 if(typeof selector == "string"){
\r
8459 els = Ext.Element.selectorFunction(selector, root);
\r
8460 }else if(selector.length !== undefined){
\r
8463 throw "Invalid selector";
\r
8465 return new Ext.CompositeElementLite(els);
\r
8468 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8469 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8470 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8471 * @param {String/Array} selector The CSS selector or an array of elements
\r
8472 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8473 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8474 * @return {CompositeElementLite/CompositeElement}
\r
8478 Ext.select = Ext.Element.select;/**
\r
8479 * @class Ext.CompositeElementLite
\r
8481 Ext.apply(Ext.CompositeElementLite.prototype, {
\r
8482 addElements : function(els, root){
\r
8486 if(typeof els == "string"){
\r
8487 els = Ext.Element.selectorFunction(els, root);
\r
8489 var yels = this.elements;
\r
8490 Ext.each(els, function(e) {
\r
8491 yels.push(Ext.get(e));
\r
8497 * Returns the first Element
\r
8498 * @return {Ext.Element}
\r
8500 first : function(){
\r
8501 return this.item(0);
\r
8505 * Returns the last Element
\r
8506 * @return {Ext.Element}
\r
8508 last : function(){
\r
8509 return this.item(this.getCount()-1);
\r
8513 * Returns true if this composite contains the passed element
\r
8514 * @param el {Mixed} The id of an element, or an Ext.Element, or an HtmlElement to find within the composite collection.
\r
8517 contains : function(el){
\r
8518 return this.indexOf(el) != -1;
\r
8522 * Removes the specified element(s).
\r
8523 * @param {Mixed} el The id of an element, the Element itself, the index of the element in this composite
\r
8524 * or an array of any of those.
\r
8525 * @param {Boolean} removeDom (optional) True to also remove the element from the document
\r
8526 * @return {CompositeElement} this
\r
8528 removeElement : function(keys, removeDom){
\r
8530 els = this.elements,
\r
8532 Ext.each(keys, function(val){
\r
8533 if ((el = (els[val] || els[val = me.indexOf(val)]))) {
\r
8538 Ext.removeNode(el);
\r
8541 els.splice(val, 1);
\r
8548 * @class Ext.CompositeElement
\r
8549 * @extends Ext.CompositeElementLite
\r
8550 * Standard composite class. Creates a Ext.Element for every element in the collection.
\r
8552 * <b>NOTE: Although they are not listed, this class supports all of the set/update methods of Ext.Element. All Ext.Element
\r
8553 * actions will be performed on all the elements in this collection.</b>
\r
8555 * All methods return <i>this</i> and can be chained.
\r
8557 var els = Ext.select("#some-el div.some-class", true);
\r
8558 // or select directly from an existing element
\r
8559 var el = Ext.get('some-el');
\r
8560 el.select('div.some-class', true);
\r
8562 els.setWidth(100); // all elements become 100 width
\r
8563 els.hide(true); // all elements fade out and hide
\r
8565 els.setWidth(100).hide(true);
\r
8568 Ext.CompositeElement = function(els, root){
\r
8569 this.elements = [];
\r
8570 this.add(els, root);
\r
8573 Ext.extend(Ext.CompositeElement, Ext.CompositeElementLite, {
\r
8574 invoke : function(fn, args){
\r
8575 Ext.each(this.elements, function(e) {
\r
8576 Ext.Element.prototype[fn].apply(e, args);
\r
8582 * Adds elements to this composite.
\r
8583 * @param {String/Array} els A string CSS selector, an array of elements or an element
\r
8584 * @return {CompositeElement} this
\r
8586 add : function(els, root){
\r
8590 if(typeof els == "string"){
\r
8591 els = Ext.Element.selectorFunction(els, root);
\r
8593 var yels = this.elements;
\r
8594 Ext.each(els, function(e) {
\r
8595 yels.push(Ext.get(e));
\r
8601 * Returns the Element object at the specified index
\r
8602 * @param {Number} index
\r
8603 * @return {Ext.Element}
\r
8605 item : function(index){
\r
8606 return this.elements[index] || null;
\r
8610 indexOf : function(el){
\r
8611 return this.elements.indexOf(Ext.get(el));
\r
8614 filter : function(selector){
\r
8618 Ext.each(me.elements, function(el) {
\r
8619 if(el.is(selector)){
\r
8620 out.push(Ext.get(el));
\r
8623 me.elements = out;
\r
8628 * Iterates each <code>element</code> in this <code>composite</code>
\r
8629 * calling the supplied function using {@link Ext#each}.
\r
8630 * @param {Function} fn The function to be called with each
\r
8631 * <code>element</code>. If the supplied function returns <tt>false</tt>,
\r
8632 * iteration stops. This function is called with the following arguments:
\r
8633 * <div class="mdetail-params"><ul>
\r
8634 * <li><code>element</code> : <i>Object</i>
\r
8635 * <div class="sub-desc">The element at the current <code>index</code>
\r
8636 * in the <code>composite</code></div></li>
\r
8637 * <li><code>composite</code> : <i>Object</i>
\r
8638 * <div class="sub-desc">This composite.</div></li>
\r
8639 * <li><code>index</code> : <i>Number</i>
\r
8640 * <div class="sub-desc">The current index within the <code>composite</code>
\r
8643 * @param {Object} scope (optional) The scope to call the specified function.
\r
8644 * Defaults to the <code>element</code> at the current <code>index</code>
\r
8645 * within the composite.
\r
8646 * @return {CompositeElement} this
\r
8648 each : function(fn, scope){
\r
8649 Ext.each(this.elements, function(e, i){
\r
8650 return fn.call(scope || e, e, this, i);
\r
8657 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8658 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8659 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8660 * @param {String/Array} selector The CSS selector or an array of elements
\r
8661 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8662 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8663 * @return {CompositeElementLite/CompositeElement}
\r
8664 * @member Ext.Element
\r
8667 Ext.Element.select = function(selector, unique, root){
\r
8669 if(typeof selector == "string"){
\r
8670 els = Ext.Element.selectorFunction(selector, root);
\r
8671 }else if(selector.length !== undefined){
\r
8674 throw "Invalid selector";
\r
8677 return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
\r
8681 * Selects elements based on the passed CSS selector to enable {@link Ext.Element Element} methods
\r
8682 * to be applied to many related elements in one statement through the returned {@link Ext.CompositeElement CompositeElement} or
\r
8683 * {@link Ext.CompositeElementLite CompositeElementLite} object.
\r
8684 * @param {String/Array} selector The CSS selector or an array of elements
\r
8685 * @param {Boolean} unique (optional) true to create a unique Ext.Element for each element (defaults to a shared flyweight object)
\r
8686 * @param {HTMLElement/String} root (optional) The root element of the query or id of the root
\r
8687 * @return {CompositeElementLite/CompositeElement}
\r
8688 * @member Ext.Element
\r
8691 Ext.select = Ext.Element.select;(function(){
\r
8692 var BEFOREREQUEST = "beforerequest",
\r
8693 REQUESTCOMPLETE = "requestcomplete",
\r
8694 REQUESTEXCEPTION = "requestexception",
\r
8695 UNDEFINED = undefined,
\r
8702 * @class Ext.data.Connection
\r
8703 * @extends Ext.util.Observable
\r
8704 * <p>The class encapsulates a connection to the page's originating domain, allowing requests to be made
\r
8705 * either to a configured URL, or to a URL specified at request time.</p>
\r
8706 * <p>Requests made by this class are asynchronous, and will return immediately. No data from
\r
8707 * the server will be available to the statement immediately following the {@link #request} call.
\r
8708 * To process returned data, use a
\r
8709 * <a href="#request-option-success" ext:member="request-option-success" ext:cls="Ext.data.Connection">success callback</a>
\r
8710 * in the request options object,
\r
8711 * or an {@link #requestcomplete event listener}.</p>
\r
8712 * <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
8713 * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
\r
8714 * manner with the DOM <tt><form></tt> element temporarily modified to have its
\r
8715 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
\r
8716 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
\r
8717 * but removed after the return data has been gathered.</p>
\r
8718 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
\r
8719 * server is using JSON to send the return object, then the
\r
8720 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
\r
8721 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
\r
8722 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
\r
8723 * "<" as "&lt;", "&" as "&amp;" etc.</p>
\r
8724 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
\r
8725 * is created containing a <tt>responseText</tt> property in order to conform to the
\r
8726 * requirements of event handlers and callbacks.</p>
\r
8727 * <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
8728 * and some server technologies (notably JEE) may require some custom processing in order to
\r
8729 * retrieve parameter names and parameter values from the packet content.</p>
\r
8731 * @param {Object} config a configuration object.
\r
8733 Ext.data.Connection = function(config){
\r
8734 Ext.apply(this, config);
\r
8737 * @event beforerequest
\r
8738 * Fires before a network request is made to retrieve a data object.
\r
8739 * @param {Connection} conn This Connection object.
\r
8740 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8744 * @event requestcomplete
\r
8745 * Fires if the request was successfully completed.
\r
8746 * @param {Connection} conn This Connection object.
\r
8747 * @param {Object} response The XHR object containing the response data.
\r
8748 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
\r
8750 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8754 * @event requestexception
\r
8755 * Fires if an error HTTP status was returned from the server.
\r
8756 * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
\r
8757 * for details of HTTP status codes.
\r
8758 * @param {Connection} conn This Connection object.
\r
8759 * @param {Object} response The XHR object containing the response data.
\r
8760 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
\r
8762 * @param {Object} options The options config object passed to the {@link #request} method.
\r
8766 Ext.data.Connection.superclass.constructor.call(this);
\r
8769 Ext.extend(Ext.data.Connection, Ext.util.Observable, {
\r
8771 * @cfg {String} url (Optional) <p>The default URL to be used for requests to the server. Defaults to undefined.</p>
\r
8772 * <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
8773 * (<code><b>this</b></code> reference) of the function is the <code>scope</code> option passed to the {@link #request} method.</p>
\r
8776 * @cfg {Object} extraParams (Optional) An object containing properties which are used as
\r
8777 * extra parameters to each request made by this object. (defaults to undefined)
\r
8780 * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
\r
8781 * to each request made by this object. (defaults to undefined)
\r
8784 * @cfg {String} method (Optional) The default HTTP method to be used for requests.
\r
8785 * (defaults to undefined; if not set, but {@link #request} params are present, POST will be used;
\r
8786 * otherwise, GET will be used.)
\r
8789 * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
\r
8793 * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
\r
8799 * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
\r
8802 disableCaching: true,
\r
8805 * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
\r
8806 * through a cache buster. Defaults to '_dc'
\r
8809 disableCachingParam: '_dc',
\r
8812 * <p>Sends an HTTP request to a remote server.</p>
\r
8813 * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
\r
8814 * return before the response has been received. Process any returned data
\r
8815 * in a callback function.</p>
\r
8817 Ext.Ajax.request({
\r
8818 url: 'ajax_demo/sample.json',
\r
8819 success: function(response, opts) {
\r
8820 var obj = Ext.decode(response.responseText);
\r
8823 failure: function(response, opts) {
\r
8824 console.log('server-side failure with status code ' + response.status);
\r
8828 * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
\r
8829 * @param {Object} options An object which may contain the following properties:<ul>
\r
8830 * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
\r
8831 * which to send the request, or a function to call which returns a URL string. The scope of the
\r
8832 * function is specified by the <tt>scope</tt> option. Defaults to the configured
\r
8833 * <tt>{@link #url}</tt>.</div></li>
\r
8834 * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
\r
8835 * An object containing properties which are used as parameters to the
\r
8836 * request, a url encoded string or a function to call to get either. The scope of the function
\r
8837 * is specified by the <tt>scope</tt> option.</div></li>
\r
8838 * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
\r
8839 * for the request. Defaults to the configured method, or if no method was configured,
\r
8840 * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that
\r
8841 * the method name is case-sensitive and should be all caps.</div></li>
\r
8842 * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
\r
8843 * function to be called upon receipt of the HTTP response. The callback is
\r
8844 * called regardless of success or failure and is passed the following
\r
8846 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8847 * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
\r
8848 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
\r
8849 * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
\r
8850 * accessing elements of the response.</div></li>
\r
8851 * </ul></div></li>
\r
8852 * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
\r
8853 * to be called upon success of the request. The callback is passed the following
\r
8855 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
\r
8856 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8857 * </ul></div></li>
\r
8858 * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
\r
8859 * to be called upon failure of the request. The callback is passed the
\r
8860 * following parameters:<ul>
\r
8861 * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
\r
8862 * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
\r
8863 * </ul></div></li>
\r
8864 * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
\r
8865 * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
\r
8866 * specified as functions from which to draw values, then this also serves as the scope for those function calls.
\r
8867 * Defaults to the browser window.</div></li>
\r
8868 * <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
8869 * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt><form></tt>
\r
8870 * Element or the id of the <tt><form></tt> to pull parameters from.</div></li>
\r
8871 * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
\r
8872 * with the <tt>form</tt> option</b>.
\r
8873 * <p>True if the form object is a file upload (will be set automatically if the form was
\r
8874 * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
\r
8875 * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
\r
8876 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
\r
8877 * DOM <tt><form></tt> element temporarily modified to have its
\r
8878 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
\r
8879 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
\r
8880 * but removed after the return data has been gathered.</p>
\r
8881 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
\r
8882 * server is using JSON to send the return object, then the
\r
8883 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
\r
8884 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
\r
8885 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
\r
8886 * is created containing a <tt>responseText</tt> property in order to conform to the
\r
8887 * requirements of event handlers and callbacks.</p>
\r
8888 * <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
8889 * and some server technologies (notably JEE) may require some custom processing in order to
\r
8890 * retrieve parameter names and parameter values from the packet content.</p>
\r
8892 * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
\r
8893 * headers to set for the request.</div></li>
\r
8894 * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
\r
8895 * to use for the post. Note: This will be used instead of params for the post
\r
8896 * data. Any params will be appended to the URL.</div></li>
\r
8897 * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
\r
8898 * data to use as the post. Note: This will be used instead of params for the post
\r
8899 * data. Any params will be appended to the URL.</div></li>
\r
8900 * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
\r
8901 * to add a unique cache-buster param to GET requests.</div></li>
\r
8903 * <p>The options object may also contain any other property which might be needed to perform
\r
8904 * postprocessing in a callback because it is passed to callback functions.</p>
\r
8905 * @return {Number} transactionId The id of the server transaction. This may be used
\r
8906 * to cancel the request.
\r
8908 request : function(o){
\r
8910 if(me.fireEvent(BEFOREREQUEST, me, o)){
\r
8912 if(!Ext.isEmpty(o.indicatorText)){
\r
8913 me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
\r
8915 if(me.indicatorText) {
\r
8916 Ext.getDom(o.el).innerHTML = me.indicatorText;
\r
8918 o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
\r
8919 Ext.getDom(o.el).innerHTML = response.responseText;
\r
8924 url = o.url || me.url,
\r
8926 cb = {success: me.handleResponse,
\r
8927 failure: me.handleFailure,
\r
8929 argument: {options: o},
\r
8930 timeout : o.timeout || me.timeout
\r
8936 if (Ext.isFunction(p)) {
\r
8937 p = p.call(o.scope||WINDOW, o);
\r
8940 p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
\r
8942 if (Ext.isFunction(url)) {
\r
8943 url = url.call(o.scope || WINDOW, o);
\r
8946 if((form = Ext.getDom(o.form))){
\r
8947 url = url || form.action;
\r
8948 if(o.isUpload || /multipart\/form-data/i.test(form.getAttribute("enctype"))) {
\r
8949 return me.doFormUpload.call(me, o, p, url);
\r
8951 serForm = Ext.lib.Ajax.serializeForm(form);
\r
8952 p = p ? (p + '&' + serForm) : serForm;
\r
8955 method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
\r
8957 if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
\r
8958 var dcp = o.disableCachingParam || me.disableCachingParam;
\r
8959 url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
\r
8962 o.headers = Ext.apply(o.headers || {}, me.defaultHeaders || {});
\r
8964 if(o.autoAbort === true || me.autoAbort) {
\r
8968 if((method == GET || o.xmlData || o.jsonData) && p){
\r
8969 url = Ext.urlAppend(url, p);
\r
8972 return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
\r
8974 return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
\r
8979 * Determine whether this object has a request outstanding.
\r
8980 * @param {Number} transactionId (Optional) defaults to the last transaction
\r
8981 * @return {Boolean} True if there is an outstanding request.
\r
8983 isLoading : function(transId){
\r
8984 return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
\r
8988 * Aborts any outstanding request.
\r
8989 * @param {Number} transactionId (Optional) defaults to the last transaction
\r
8991 abort : function(transId){
\r
8992 if(transId || this.isLoading()){
\r
8993 Ext.lib.Ajax.abort(transId || this.transId);
\r
8998 handleResponse : function(response){
\r
8999 this.transId = false;
\r
9000 var options = response.argument.options;
\r
9001 response.argument = options ? options.argument : null;
\r
9002 this.fireEvent(REQUESTCOMPLETE, this, response, options);
\r
9003 if(options.success){
\r
9004 options.success.call(options.scope, response, options);
\r
9006 if(options.callback){
\r
9007 options.callback.call(options.scope, options, true, response);
\r
9012 handleFailure : function(response, e){
\r
9013 this.transId = false;
\r
9014 var options = response.argument.options;
\r
9015 response.argument = options ? options.argument : null;
\r
9016 this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
\r
9017 if(options.failure){
\r
9018 options.failure.call(options.scope, response, options);
\r
9020 if(options.callback){
\r
9021 options.callback.call(options.scope, options, false, response);
\r
9026 doFormUpload : function(o, ps, url){
\r
9027 var id = Ext.id(),
\r
9029 frame = doc.createElement('iframe'),
\r
9030 form = Ext.getDom(o.form),
\r
9033 encoding = 'multipart/form-data',
\r
9035 target: form.target,
\r
9036 method: form.method,
\r
9037 encoding: form.encoding,
\r
9038 enctype: form.enctype,
\r
9039 action: form.action
\r
9042 Ext.fly(frame).set({
\r
9046 src: Ext.SSL_SECURE_URL // for IE
\r
9048 doc.body.appendChild(frame);
\r
9050 // This is required so that IE doesn't pop the response up in a new window.
\r
9052 document.frames[id].name = id;
\r
9055 Ext.fly(form).set({
\r
9058 enctype: encoding,
\r
9059 encoding: encoding,
\r
9060 action: url || buf.action
\r
9063 // add dynamic params
\r
9064 Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
\r
9065 hd = doc.createElement('input');
\r
9071 form.appendChild(hd);
\r
9077 // bogus response object
\r
9078 r = {responseText : '',
\r
9079 responseXML : null,
\r
9080 argument : o.argument},
\r
9085 doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
\r
9088 if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){ // json response wrapped in textarea
\r
9089 r.responseText = firstChild.value;
\r
9091 r.responseText = doc.body.innerHTML;
\r
9094 //in IE the document may still have a body even if returns XML.
\r
9095 r.responseXML = doc.XMLDocument || doc;
\r
9100 Ext.EventManager.removeListener(frame, LOAD, cb, me);
\r
9102 me.fireEvent(REQUESTCOMPLETE, me, r, o);
\r
9104 function runCallback(fn, scope, args){
\r
9105 if(Ext.isFunction(fn)){
\r
9106 fn.apply(scope, args);
\r
9110 runCallback(o.success, o.scope, [r, o]);
\r
9111 runCallback(o.callback, o.scope, [o, true, r]);
\r
9113 if(!me.debugUploads){
\r
9114 setTimeout(function(){Ext.removeNode(frame);}, 100);
\r
9118 Ext.EventManager.on(frame, LOAD, cb, this);
\r
9121 Ext.fly(form).set(buf);
\r
9122 Ext.each(hiddens, function(h) {
\r
9123 Ext.removeNode(h);
\r
9131 * @extends Ext.data.Connection
\r
9132 * <p>The global Ajax request class that provides a simple way to make Ajax requests
\r
9133 * with maximum flexibility.</p>
\r
9134 * <p>Since Ext.Ajax is a singleton, you can set common properties/events for it once
\r
9135 * and override them at the request function level only if necessary.</p>
\r
9136 * <p>Common <b>Properties</b> you may want to set are:<div class="mdetail-params"><ul>
\r
9137 * <li><b><tt>{@link #method}</tt></b><p class="sub-desc"></p></li>
\r
9138 * <li><b><tt>{@link #extraParams}</tt></b><p class="sub-desc"></p></li>
\r
9139 * <li><b><tt>{@link #url}</tt></b><p class="sub-desc"></p></li>
\r
9142 // Default headers to pass in every request
\r
9143 Ext.Ajax.defaultHeaders = {
\r
9144 'Powered-By': 'Ext'
\r
9148 * <p>Common <b>Events</b> you may want to set are:<div class="mdetail-params"><ul>
\r
9149 * <li><b><tt>{@link Ext.data.Connection#beforerequest beforerequest}</tt></b><p class="sub-desc"></p></li>
\r
9150 * <li><b><tt>{@link Ext.data.Connection#requestcomplete requestcomplete}</tt></b><p class="sub-desc"></p></li>
\r
9151 * <li><b><tt>{@link Ext.data.Connection#requestexception requestexception}</tt></b><p class="sub-desc"></p></li>
\r
9154 // Example: show a spinner during all Ajax requests
\r
9155 Ext.Ajax.on('beforerequest', this.showSpinner, this);
\r
9156 Ext.Ajax.on('requestcomplete', this.hideSpinner, this);
\r
9157 Ext.Ajax.on('requestexception', this.hideSpinner, this);
\r
9160 * <p>An example request:</p>
\r
9163 Ext.Ajax.{@link Ext.data.Connection#request request}({
\r
9168 'my-header': 'foo'
\r
9170 params: { foo: 'bar' }
\r
9173 // Simple ajax form submission
\r
9174 Ext.Ajax.{@link Ext.data.Connection#request request}({
\r
9175 form: 'some-form',
\r
9182 Ext.Ajax = new Ext.data.Connection({
\r
9184 * @cfg {String} url @hide
\r
9187 * @cfg {Object} extraParams @hide
\r
9190 * @cfg {Object} defaultHeaders @hide
\r
9193 * @cfg {String} method (Optional) @hide
\r
9196 * @cfg {Number} timeout (Optional) @hide
\r
9199 * @cfg {Boolean} autoAbort (Optional) @hide
\r
9203 * @cfg {Boolean} disableCaching (Optional) @hide
\r
9207 * @property disableCaching
\r
9208 * True to add a unique cache-buster param to GET requests. (defaults to true)
\r
9213 * The default URL to be used for requests to the server. (defaults to undefined)
\r
9214 * If the server receives all requests through one URL, setting this once is easier than
\r
9215 * entering it on every request.
\r
9219 * @property extraParams
\r
9220 * An object containing properties which are used as extra parameters to each request made
\r
9221 * by this object (defaults to undefined). Session information and other data that you need
\r
9222 * to pass with each request are commonly put here.
\r
9226 * @property defaultHeaders
\r
9227 * An object containing request headers which are added to each request made by this object
\r
9228 * (defaults to undefined).
\r
9232 * @property method
\r
9233 * The default HTTP method to be used for requests. Note that this is case-sensitive and
\r
9234 * should be all caps (defaults to undefined; if not set but params are present will use
\r
9235 * <tt>"POST"</tt>, otherwise will use <tt>"GET"</tt>.)
\r
9239 * @property timeout
\r
9240 * The timeout in milliseconds to be used for requests. (defaults to 30000)
\r
9245 * @property autoAbort
\r
9246 * Whether a new request should abort any pending requests. (defaults to false)
\r
9249 autoAbort : false,
\r
9252 * Serialize the passed form into a url encoded string
\r
9253 * @param {String/HTMLElement} form
\r
9254 * @return {String}
\r
9256 serializeForm : function(form){
\r
9257 return Ext.lib.Ajax.serializeForm(form);
\r
9261 * @class Ext.Updater
9262 * @extends Ext.util.Observable
9263 * Provides AJAX-style update capabilities for Element objects. Updater can be used to {@link #update}
9264 * an {@link Ext.Element} once, or you can use {@link #startAutoRefresh} to set up an auto-updating
9265 * {@link Ext.Element Element} on a specific interval.<br><br>
9268 * var el = Ext.get("foo"); // Get Ext.Element object
9269 * var mgr = el.getUpdater();
9271 url: "http://myserver.com/index.php",
9278 * mgr.formUpdate("myFormId", "http://myserver.com/index.php");
9280 * // or directly (returns the same Updater instance)
9281 * var mgr = new Ext.Updater("myElementId");
9282 * mgr.startAutoRefresh(60, "http://myserver.com/index.php");
9283 * mgr.on("update", myFcnNeedsToKnow);
9285 * // short handed call directly from the element object
9286 * Ext.get("foo").load({
9289 params: "param1=foo&param2=bar",
9290 text: "Loading Foo..."
9294 * Create new Updater directly.
9295 * @param {Mixed} el The element to update
9296 * @param {Boolean} forceNew (optional) By default the constructor checks to see if the passed element already
9297 * has an Updater and if it does it returns the same instance. This will skip that check (useful for extending this class).
9299 Ext.UpdateManager = Ext.Updater = Ext.extend(Ext.util.Observable,
9301 var BEFOREUPDATE = "beforeupdate",
9303 FAILURE = "failure";
9306 function processSuccess(response){
9308 me.transaction = null;
9309 if (response.argument.form && response.argument.reset) {
9310 try { // put in try/catch since some older FF releases had problems with this
9311 response.argument.form.reset();
9314 if (me.loadScripts) {
9315 me.renderer.render(me.el, response, me,
9316 updateComplete.createDelegate(me, [response]));
9318 me.renderer.render(me.el, response, me);
9319 updateComplete.call(me, response);
9324 function updateComplete(response, type, success){
9325 this.fireEvent(type || UPDATE, this.el, response);
9326 if(Ext.isFunction(response.argument.callback)){
9327 response.argument.callback.call(response.argument.scope, this.el, Ext.isEmpty(success) ? true : false, response, response.argument.options);
9332 function processFailure(response){
9333 updateComplete.call(this, response, FAILURE, !!(this.transaction = null));
9337 constructor: function(el, forceNew){
9340 if(!forceNew && el.updateManager){
9341 return el.updateManager;
9344 * The Element object
9349 * Cached url to use for refreshes. Overwritten every time update() is called unless "discardUrl" param is set to true.
9352 me.defaultUrl = null;
9356 * @event beforeupdate
9357 * Fired before an update is made, return false from your handler and the update is cancelled.
9358 * @param {Ext.Element} el
9359 * @param {String/Object/Function} url
9360 * @param {String/Object} params
9365 * Fired after successful update is made.
9366 * @param {Ext.Element} el
9367 * @param {Object} oResponseObject The response Object
9372 * Fired on update failure.
9373 * @param {Ext.Element} el
9374 * @param {Object} oResponseObject The response Object
9379 Ext.apply(me, Ext.Updater.defaults);
9381 * Blank page URL to use with SSL file uploads (defaults to {@link Ext.Updater.defaults#sslBlankUrl}).
9382 * @property sslBlankUrl
9386 * Whether to append unique parameter on get request to disable caching (defaults to {@link Ext.Updater.defaults#disableCaching}).
9387 * @property disableCaching
9391 * Text for loading indicator (defaults to {@link Ext.Updater.defaults#indicatorText}).
9392 * @property indicatorText
9396 * Whether to show indicatorText when loading (defaults to {@link Ext.Updater.defaults#showLoadIndicator}).
9397 * @property showLoadIndicator
9401 * Timeout for requests or form posts in seconds (defaults to {@link Ext.Updater.defaults#timeout}).
9406 * True to process scripts in the output (defaults to {@link Ext.Updater.defaults#loadScripts}).
9407 * @property loadScripts
9412 * Transaction object of the current executing transaction, or null if there is no active transaction.
9414 me.transaction = null;
9416 * Delegate for refresh() prebound to "this", use myUpdater.refreshDelegate.createCallback(arg1, arg2) to bind arguments
9419 me.refreshDelegate = me.refresh.createDelegate(me);
9421 * Delegate for update() prebound to "this", use myUpdater.updateDelegate.createCallback(arg1, arg2) to bind arguments
9424 me.updateDelegate = me.update.createDelegate(me);
9426 * Delegate for formUpdate() prebound to "this", use myUpdater.formUpdateDelegate.createCallback(arg1, arg2) to bind arguments
9429 me.formUpdateDelegate = (me.formUpdate || function(){}).createDelegate(me);
9432 * The renderer for this Updater (defaults to {@link Ext.Updater.BasicRenderer}).
9434 me.renderer = me.renderer || me.getDefaultRenderer();
9436 Ext.Updater.superclass.constructor.call(me);
9440 * Sets the content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
9441 * @param {Object} renderer The object implementing the render() method
9443 setRenderer : function(renderer){
9444 this.renderer = renderer;
9448 * Returns the current content renderer for this Updater. See {@link Ext.Updater.BasicRenderer#render} for more details.
9451 getRenderer : function(){
9452 return this.renderer;
9456 * This is an overrideable method which returns a reference to a default
9457 * renderer class if none is specified when creating the Ext.Updater.
9458 * Defaults to {@link Ext.Updater.BasicRenderer}
9460 getDefaultRenderer: function() {
9461 return new Ext.Updater.BasicRenderer();
9465 * Sets the default URL used for updates.
9466 * @param {String/Function} defaultUrl The url or a function to call to get the url
9468 setDefaultUrl : function(defaultUrl){
9469 this.defaultUrl = defaultUrl;
9473 * Get the Element this Updater is bound to
9474 * @return {Ext.Element} The element
9481 * Performs an <b>asynchronous</b> request, updating this element with the response.
9482 * If params are specified it uses POST, otherwise it uses GET.<br><br>
9483 * <b>Note:</b> Due to the asynchronous nature of remote server requests, the Element
9484 * will not have been fully updated when the function returns. To post-process the returned
9485 * data, use the callback option, or an <b><tt>update</tt></b> event handler.
9486 * @param {Object} options A config object containing any of the following options:<ul>
9487 * <li>url : <b>String/Function</b><p class="sub-desc">The URL to request or a function which
9488 * <i>returns</i> the URL (defaults to the value of {@link Ext.Ajax#url} if not specified).</p></li>
9489 * <li>method : <b>String</b><p class="sub-desc">The HTTP method to
9490 * use. Defaults to POST if the <tt>params</tt> argument is present, otherwise GET.</p></li>
9491 * <li>params : <b>String/Object/Function</b><p class="sub-desc">The
9492 * parameters to pass to the server (defaults to none). These may be specified as a url-encoded
9493 * string, or as an object containing properties which represent parameters,
9494 * or as a function, which returns such an object.</p></li>
9495 * <li>scripts : <b>Boolean</b><p class="sub-desc">If <tt>true</tt>
9496 * any <script> tags embedded in the response text will be extracted
9497 * and executed (defaults to {@link Ext.Updater.defaults#loadScripts}). If this option is specified,
9498 * the callback will be called <i>after</i> the execution of the scripts.</p></li>
9499 * <li>callback : <b>Function</b><p class="sub-desc">A function to
9500 * be called when the response from the server arrives. The following
9501 * parameters are passed:<ul>
9502 * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
9503 * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
9504 * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li>
9505 * <li><b>options</b> : Object<p class="sub-desc">The config object passed to the update call.</p></li></ul>
9507 * <li>scope : <b>Object</b><p class="sub-desc">The scope in which
9508 * to execute the callback (The callback's <tt>this</tt> reference.) If the
9509 * <tt>params</tt> argument is a function, this scope is used for that function also.</p></li>
9510 * <li>discardUrl : <b>Boolean</b><p class="sub-desc">By default, the URL of this request becomes
9511 * the default URL for this Updater object, and will be subsequently used in {@link #refresh}
9512 * calls. To bypass this behavior, pass <tt>discardUrl:true</tt> (defaults to false).</p></li>
9513 * <li>timeout : <b>Number</b><p class="sub-desc">The number of seconds to wait for a response before
9514 * timing out (defaults to {@link Ext.Updater.defaults#timeout}).</p></li>
9515 * <li>text : <b>String</b><p class="sub-desc">The text to use as the innerHTML of the
9516 * {@link Ext.Updater.defaults#indicatorText} div (defaults to 'Loading...'). To replace the entire div, not
9517 * just the text, override {@link Ext.Updater.defaults#indicatorText} directly.</p></li>
9518 * <li>nocache : <b>Boolean</b><p class="sub-desc">Only needed for GET
9519 * requests, this option causes an extra, auto-generated parameter to be appended to the request
9520 * to defeat caching (defaults to {@link Ext.Updater.defaults#disableCaching}).</p></li></ul>
9525 url: "your-url.php",
9526 params: {param1: "foo", param2: "bar"}, // or a URL encoded string
9527 callback: yourFunction,
9528 scope: yourObject, //(optional scope)
9533 scripts: false // Save time by avoiding RegExp execution.
9537 update : function(url, params, callback, discardUrl){
9542 if(me.fireEvent(BEFOREUPDATE, me.el, url, params) !== false){
9543 if(Ext.isObject(url)){ // must be config object
9546 params = params || cfg.params;
9547 callback = callback || cfg.callback;
9548 discardUrl = discardUrl || cfg.discardUrl;
9549 callerScope = cfg.scope;
9550 if(!Ext.isEmpty(cfg.nocache)){me.disableCaching = cfg.nocache;};
9551 if(!Ext.isEmpty(cfg.text)){me.indicatorText = '<div class="loading-indicator">'+cfg.text+"</div>";};
9552 if(!Ext.isEmpty(cfg.scripts)){me.loadScripts = cfg.scripts;};
9553 if(!Ext.isEmpty(cfg.timeout)){me.timeout = cfg.timeout;};
9558 me.defaultUrl = url;
9560 if(Ext.isFunction(url)){
9564 var o = Ext.apply({}, {
9566 params: (Ext.isFunction(params) && callerScope) ? params.createDelegate(callerScope) : params,
9567 success: processSuccess,
9568 failure: processFailure,
9570 callback: undefined,
9571 timeout: (me.timeout*1000),
9572 disableCaching: me.disableCaching,
9577 "callback": callback,
9578 "scope": callerScope || window,
9583 me.transaction = Ext.Ajax.request(o);
9588 * <p>Performs an async form post, updating this element with the response. If the form has the attribute
9589 * enctype="<a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form-data</a>", it assumes it's a file upload.
9590 * Uses this.sslBlankUrl for SSL file uploads to prevent IE security warning.</p>
9591 * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
9592 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
9593 * DOM <tt><form></tt> element temporarily modified to have its
9594 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
9595 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
9596 * but removed after the return data has been gathered.</p>
9597 * <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>
9598 * and some server technologies (notably JEE) may require some custom processing in order to
9599 * retrieve parameter names and parameter values from the packet content.</p>
9600 * @param {String/HTMLElement} form The form Id or form element
9601 * @param {String} url (optional) The url to pass the form to. If omitted the action attribute on the form will be used.
9602 * @param {Boolean} reset (optional) Whether to try to reset the form after the update
9603 * @param {Function} callback (optional) Callback when transaction is complete. The following
9604 * parameters are passed:<ul>
9605 * <li><b>el</b> : Ext.Element<p class="sub-desc">The Element being updated.</p></li>
9606 * <li><b>success</b> : Boolean<p class="sub-desc">True for success, false for failure.</p></li>
9607 * <li><b>response</b> : XMLHttpRequest<p class="sub-desc">The XMLHttpRequest which processed the update.</p></li></ul>
9609 formUpdate : function(form, url, reset, callback){
9611 if(me.fireEvent(BEFOREUPDATE, me.el, form, url) !== false){
9612 if(Ext.isFunction(url)){
9615 form = Ext.getDom(form)
9616 me.transaction = Ext.Ajax.request({
9619 success: processSuccess,
9620 failure: processFailure,
9622 timeout: (me.timeout*1000),
9626 "callback": callback,
9630 me.showLoading.defer(1, me);
9635 * Set this element to auto refresh. Can be canceled by calling {@link #stopAutoRefresh}.
9636 * @param {Number} interval How often to update (in seconds).
9637 * @param {String/Object/Function} url (optional) The url for this request, a config object in the same format
9638 * supported by {@link #load}, or a function to call to get the url (defaults to the last used url). Note that while
9639 * the url used in a load call can be reused by this method, other load config options will not be reused and must be
9640 * sepcified as part of a config object passed as this paramter if needed.
9641 * @param {String/Object} params (optional) The parameters to pass as either a url encoded string
9642 * "¶m1=1¶m2=2" or as an object {param1: 1, param2: 2}
9643 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
9644 * @param {Boolean} refreshNow (optional) Whether to execute the refresh now, or wait the interval
9646 startAutoRefresh : function(interval, url, params, callback, refreshNow){
9649 me.update(url || me.defaultUrl, params, callback, true);
9651 if(me.autoRefreshProcId){
9652 clearInterval(me.autoRefreshProcId);
9654 me.autoRefreshProcId = setInterval(me.update.createDelegate(me, [url || me.defaultUrl, params, callback, true]), interval * 1000);
9658 * Stop auto refresh on this element.
9660 stopAutoRefresh : function(){
9661 if(this.autoRefreshProcId){
9662 clearInterval(this.autoRefreshProcId);
9663 delete this.autoRefreshProcId;
9668 * Returns true if the Updater is currently set to auto refresh its content (see {@link #startAutoRefresh}), otherwise false.
9670 isAutoRefreshing : function(){
9671 return !!this.autoRefreshProcId;
9675 * Display the element's "loading" state. By default, the element is updated with {@link #indicatorText}. This
9676 * method may be overridden to perform a custom action while this Updater is actively updating its contents.
9678 showLoading : function(){
9679 if(this.showLoadIndicator){
9680 this.el.dom.innerHTML = this.indicatorText;
9685 * Aborts the currently executing transaction, if any.
9688 if(this.transaction){
9689 Ext.Ajax.abort(this.transaction);
9694 * Returns true if an update is in progress, otherwise false.
9697 isUpdating : function(){
9698 return this.transaction ? Ext.Ajax.isLoading(this.transaction) : false;
9702 * Refresh the element with the last used url or defaultUrl. If there is no url, it returns immediately
9703 * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
9705 refresh : function(callback){
9706 if(this.defaultUrl){
9707 this.update(this.defaultUrl, null, callback, true);
9714 * @class Ext.Updater.defaults
9715 * The defaults collection enables customizing the default properties of Updater
9717 Ext.Updater.defaults = {
9719 * Timeout for requests or form posts in seconds (defaults to 30 seconds).
9724 * True to append a unique parameter to GET requests to disable caching (defaults to false).
9727 disableCaching : false,
9729 * Whether or not to show {@link #indicatorText} during loading (defaults to true).
9732 showLoadIndicator : true,
9734 * Text for loading indicator (defaults to '<div class="loading-indicator">Loading...</div>').
9737 indicatorText : '<div class="loading-indicator">Loading...</div>',
9739 * True to process scripts by default (defaults to false).
9742 loadScripts : false,
9744 * Blank page URL to use with SSL file uploads (defaults to {@link Ext#SSL_SECURE_URL} if set, or "javascript:false").
9747 sslBlankUrl : Ext.SSL_SECURE_URL
9752 * Static convenience method. <b>This method is deprecated in favor of el.load({url:'foo.php', ...})</b>.
9754 * <pre><code>Ext.Updater.updateElement("my-div", "stuff.php");</code></pre>
9755 * @param {Mixed} el The element to update
9756 * @param {String} url The url
9757 * @param {String/Object} params (optional) Url encoded param string or an object of name/value pairs
9758 * @param {Object} options (optional) A config object with any of the Updater properties you want to set - for
9759 * example: {disableCaching:true, indicatorText: "Loading data..."}
9762 * @member Ext.Updater
9764 Ext.Updater.updateElement = function(el, url, params, options){
9765 var um = Ext.get(el).getUpdater();
9766 Ext.apply(um, options);
9767 um.update(url, params, options ? options.callback : null);
9771 * @class Ext.Updater.BasicRenderer
9772 * Default Content renderer. Updates the elements innerHTML with the responseText.
9774 Ext.Updater.BasicRenderer = function(){};
9776 Ext.Updater.BasicRenderer.prototype = {
9778 * This is called when the transaction is completed and it's time to update the element - The BasicRenderer
9779 * updates the elements innerHTML with the responseText - To perform a custom render (i.e. XML or JSON processing),
9780 * create an object with a "render(el, response)" method and pass it to setRenderer on the Updater.
9781 * @param {Ext.Element} el The element being rendered
9782 * @param {Object} response The XMLHttpRequest object
9783 * @param {Updater} updateManager The calling update manager
9784 * @param {Function} callback A callback that will need to be called if loadScripts is true on the Updater
9786 render : function(el, response, updateManager, callback){
9787 el.update(response.responseText, updateManager.loadScripts, callback);
9792 * The date parsing and formatting syntax contains a subset of
9793 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
9794 * supported will provide results equivalent to their PHP versions.
9796 * The following is a list of all currently supported formats:
9798 Format Description Example returned values
9799 ------ ----------------------------------------------------------------------- -----------------------
9800 d Day of the month, 2 digits with leading zeros 01 to 31
9801 D A short textual representation of the day of the week Mon to Sun
9802 j Day of the month without leading zeros 1 to 31
9803 l A full textual representation of the day of the week Sunday to Saturday
9804 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
9805 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
9806 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
9807 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
9808 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
9809 F A full textual representation of a month, such as January or March January to December
9810 m Numeric representation of a month, with leading zeros 01 to 12
9811 M A short textual representation of a month Jan to Dec
9812 n Numeric representation of a month, without leading zeros 1 to 12
9813 t Number of days in the given month 28 to 31
9814 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
9815 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
9816 belongs to the previous or next year, that year is used instead)
9817 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
9818 y A two digit representation of a year Examples: 99 or 03
9819 a Lowercase Ante meridiem and Post meridiem am or pm
9820 A Uppercase Ante meridiem and Post meridiem AM or PM
9821 g 12-hour format of an hour without leading zeros 1 to 12
9822 G 24-hour format of an hour without leading zeros 0 to 23
9823 h 12-hour format of an hour with leading zeros 01 to 12
9824 H 24-hour format of an hour with leading zeros 00 to 23
9825 i Minutes, with leading zeros 00 to 59
9826 s Seconds, with leading zeros 00 to 59
9827 u Decimal fraction of a second Examples:
9828 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
9829 100 (i.e. 0.100s) or
9830 999 (i.e. 0.999s) or
9831 999876543210 (i.e. 0.999876543210s)
9832 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
9833 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
9834 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
9835 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
9838 1) If unspecified, the month / day defaults to the current month / day, 1991 or
9839 the time defaults to midnight, while the timezone defaults to the 1992-10 or
9840 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
9841 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
9842 are optional. 1995-07-18T17:21:28-02:00 or
9843 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
9844 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
9845 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
9846 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
9847 date-time granularity which are supported, or see 2000-02-13T21:25:33
9848 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
9849 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
9850 M$ Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
9851 \/Date(1238606590509+0800)\/
9854 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
9857 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
9859 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
9860 document.write(dt.format('Y-m-d')); // 2007-01-10
9861 document.write(dt.format('F j, Y, g:i a')); // January 10, 2007, 3:05 pm
9862 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
9865 * Here are some standard date/time patterns that you might find helpful. They
9866 * are not part of the source of Date.js, but to use them you can simply copy this
9867 * block of code into any script that is included after Date.js and they will also become
9868 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
9871 ISO8601Long:"Y-m-d H:i:s",
9872 ISO8601Short:"Y-m-d",
9874 LongDate: "l, F d, Y",
9875 FullDateTime: "l, F d, Y g:i:s A",
9878 LongTime: "g:i:s A",
9879 SortableDateTime: "Y-m-d\\TH:i:s",
9880 UniversalSortableDateTime: "Y-m-d H:i:sO",
9887 var dt = new Date();
9888 document.write(dt.format(Date.patterns.ShortDate));
9890 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
9891 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
9895 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
9896 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
9897 * They generate precompiled functions from format patterns instead of parsing and
9898 * processing each pattern every time a date is formatted. These functions are available
9899 * on every Date object.
9905 * Global flag which determines if strict date parsing should be used.
9906 * Strict date parsing will not roll-over invalid dates, which is the
9907 * default behaviour of javascript Date objects.
9908 * (see {@link #parseDate} for more information)
9909 * Defaults to <tt>false</tt>.
9913 Date.useStrict = false;
9916 // create private copy of Ext's String.format() method
9917 // - to remove unnecessary dependency
9918 // - to resolve namespace conflict with M$-Ajax's implementation
9919 function xf(format) {
9920 var args = Array.prototype.slice.call(arguments, 1);
9921 return format.replace(/\{(\d+)\}/g, function(m, i) {
9928 Date.formatCodeToRegex = function(character, currentGroup) {
9929 // Note: currentGroup - position in regex result array (see notes for Date.parseCodes below)
9930 var p = Date.parseCodes[character];
9933 p = typeof p == 'function'? p() : p;
9934 Date.parseCodes[character] = p; // reassign function result to prevent repeated execution
9937 return p? Ext.applyIf({
9938 c: p.c? xf(p.c, currentGroup || "{0}") : p.c
9942 s:Ext.escapeRe(character) // treat unrecognised characters as literals
9946 // private shorthand for Date.formatCodeToRegex since we'll be using it fairly often
9947 var $f = Date.formatCodeToRegex;
9951 * <p>An object hash in which each property is a date parsing function. The property name is the
9952 * format string which that function parses.</p>
9953 * <p>This object is automatically populated with date parsing functions as
9954 * date formats are requested for Ext standard formatting strings.</p>
9955 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
9956 * may be used as a format string to {@link #parseDate}.<p>
9957 * <p>Example:</p><pre><code>
9958 Date.parseFunctions['x-date-format'] = myDateParser;
9960 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
9961 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
9962 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
9963 * (i.e. prevent javascript Date "rollover") (The default must be false).
9964 * Invalid date strings should return null when parsed.</div></li>
9966 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
9967 * formatting function must be placed into the {@link #formatFunctions} property.
9968 * @property parseFunctions
9973 "M$": function(input, strict) {
9974 // note: the timezone offset is ignored since the M$ Ajax server sends
9975 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
9976 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
9977 var r = (input || '').match(re);
9978 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
9984 * <p>An object hash in which each property is a date formatting function. The property name is the
9985 * format string which corresponds to the produced formatted date string.</p>
9986 * <p>This object is automatically populated with date formatting functions as
9987 * date formats are requested for Ext standard formatting strings.</p>
9988 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
9989 * may be used as a format string to {@link #format}. Example:</p><pre><code>
9990 Date.formatFunctions['x-date-format'] = myDateFormatter;
9992 * <p>A formatting function should return a string repesentation of the passed Date object:<div class="mdetail-params"><ul>
9993 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
9995 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
9996 * parsing function must be placed into the {@link #parseFunctions} property.
9997 * @property formatFunctions
10003 // UTC milliseconds since Unix epoch (M$-AJAX serialized date format (MRSF))
10004 return '\\/Date(' + this.getTime() + ')\\/';
10011 * Date interval constant
10018 * Date interval constant
10025 * Date interval constant
10031 /** Date interval constant
10038 * Date interval constant
10045 * Date interval constant
10052 * Date interval constant
10059 * <p>An object hash containing default date values used during date parsing.</p>
10060 * <p>The following properties are available:<div class="mdetail-params"><ul>
10061 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
10062 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
10063 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
10064 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
10065 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
10066 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
10067 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
10069 * <p>Override these properties to customize the default date values used by the {@link #parseDate} method.</p>
10070 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
10071 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
10072 * It is the responsiblity of the developer to account for this.</b></p>
10075 // set default day value to the first day of the month
10076 Date.defaults.d = 1;
10078 // parse a February date string containing only year and month values.
10079 // setting the default day value to 1 prevents weird date rollover issues
10080 // when attempting to parse the following date string on, for example, March 31st 2009.
10081 Date.parseDate('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
10083 * @property defaults
10090 * An array of textual day names.
10091 * Override these values for international dates.
10095 'SundayInYourLang',
10096 'MondayInYourLang',
10114 * An array of textual month names.
10115 * Override these values for international dates.
10118 Date.monthNames = [
10143 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
10144 * Override these values for international dates.
10147 Date.monthNumbers = {
10148 'ShortJanNameInYourLang':0,
10149 'ShortFebNameInYourLang':1,
10172 * Get the short month name for the given month number.
10173 * Override this function for international dates.
10174 * @param {Number} month A zero-based javascript month number.
10175 * @return {String} The short month name.
10178 getShortMonthName : function(month) {
10179 return Date.monthNames[month].substring(0, 3);
10183 * Get the short day name for the given day number.
10184 * Override this function for international dates.
10185 * @param {Number} day A zero-based javascript day number.
10186 * @return {String} The short day name.
10189 getShortDayName : function(day) {
10190 return Date.dayNames[day].substring(0, 3);
10194 * Get the zero-based javascript month number for the given short/full month name.
10195 * Override this function for international dates.
10196 * @param {String} name The short/full month name.
10197 * @return {Number} The zero-based javascript month number.
10200 getMonthNumber : function(name) {
10201 // handle camel casing for english month names (since the keys for the Date.monthNumbers hash are case sensitive)
10202 return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
10206 * The base format-code to formatting-function hashmap used by the {@link #format} method.
10207 * Formatting functions are strings (or functions which return strings) which
10208 * will return the appropriate value when evaluated in the context of the Date object
10209 * from which the {@link #format} method is called.
10210 * Add to / override these mappings for custom date formatting.
10211 * Note: Date.format() treats characters as literals if an appropriate mapping cannot be found.
10214 Date.formatCodes.x = "String.leftPad(this.getDate(), 2, '0')";
10215 (new Date()).format("X"); // returns the current day of the month
10221 d: "String.leftPad(this.getDate(), 2, '0')",
10222 D: "Date.getShortDayName(this.getDay())", // get localised short day name
10223 j: "this.getDate()",
10224 l: "Date.dayNames[this.getDay()]",
10225 N: "(this.getDay() ? this.getDay() : 7)",
10226 S: "this.getSuffix()",
10227 w: "this.getDay()",
10228 z: "this.getDayOfYear()",
10229 W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
10230 F: "Date.monthNames[this.getMonth()]",
10231 m: "String.leftPad(this.getMonth() + 1, 2, '0')",
10232 M: "Date.getShortMonthName(this.getMonth())", // get localised short month name
10233 n: "(this.getMonth() + 1)",
10234 t: "this.getDaysInMonth()",
10235 L: "(this.isLeapYear() ? 1 : 0)",
10236 o: "(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",
10237 Y: "this.getFullYear()",
10238 y: "('' + this.getFullYear()).substring(2, 4)",
10239 a: "(this.getHours() < 12 ? 'am' : 'pm')",
10240 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
10241 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
10242 G: "this.getHours()",
10243 h: "String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
10244 H: "String.leftPad(this.getHours(), 2, '0')",
10245 i: "String.leftPad(this.getMinutes(), 2, '0')",
10246 s: "String.leftPad(this.getSeconds(), 2, '0')",
10247 u: "String.leftPad(this.getMilliseconds(), 3, '0')",
10248 O: "this.getGMTOffset()",
10249 P: "this.getGMTOffset(true)",
10250 T: "this.getTimezone()",
10251 Z: "(this.getTimezoneOffset() * -60)",
10253 c: function() { // ISO-8601 -- GMT format
10254 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
10255 var e = c.charAt(i);
10256 code.push(e == "T" ? "'T'" : Date.getFormatCode(e)); // treat T as a character literal
10258 return code.join(" + ");
10261 c: function() { // ISO-8601 -- UTC format
10263 "this.getUTCFullYear()", "'-'",
10264 "String.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
10265 "String.leftPad(this.getUTCDate(), 2, '0')",
10267 "String.leftPad(this.getUTCHours(), 2, '0')", "':'",
10268 "String.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
10269 "String.leftPad(this.getUTCSeconds(), 2, '0')",
10275 U: "Math.round(this.getTime() / 1000)"
10279 * Checks if the passed Date parameters will cause a javascript Date "rollover".
10280 * @param {Number} year 4-digit year
10281 * @param {Number} month 1-based month-of-year
10282 * @param {Number} day Day of month
10283 * @param {Number} hour (optional) Hour
10284 * @param {Number} minute (optional) Minute
10285 * @param {Number} second (optional) Second
10286 * @param {Number} millisecond (optional) Millisecond
10287 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
10290 isValid : function(y, m, d, h, i, s, ms) {
10297 var dt = new Date(y, m - 1, d, h, i, s, ms);
10299 return y == dt.getFullYear() &&
10300 m == dt.getMonth() + 1 &&
10301 d == dt.getDate() &&
10302 h == dt.getHours() &&
10303 i == dt.getMinutes() &&
10304 s == dt.getSeconds() &&
10305 ms == dt.getMilliseconds();
10309 * Parses the passed string using the specified date format.
10310 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
10311 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
10312 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
10313 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
10314 * Keep in mind that the input date string must precisely match the specified format string
10315 * in order for the parse operation to be successful (failed parse operations return a null value).
10316 * <p>Example:</p><pre><code>
10317 //dt = Fri May 25 2007 (current date)
10318 var dt = new Date();
10320 //dt = Thu May 25 2006 (today's month/day in 2006)
10321 dt = Date.parseDate("2006", "Y");
10323 //dt = Sun Jan 15 2006 (all date parts specified)
10324 dt = Date.parseDate("2006-01-15", "Y-m-d");
10326 //dt = Sun Jan 15 2006 15:20:01
10327 dt = Date.parseDate("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
10329 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
10330 dt = Date.parseDate("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
10332 * @param {String} input The raw date string.
10333 * @param {String} format The expected date string format.
10334 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
10335 (defaults to false). Invalid date strings will return null when parsed.
10336 * @return {Date} The parsed Date.
10339 parseDate : function(input, format, strict) {
10340 var p = Date.parseFunctions;
10341 if (p[format] == null) {
10342 Date.createParser(format);
10344 return p[format](input, Ext.isDefined(strict) ? strict : Date.useStrict);
10348 getFormatCode : function(character) {
10349 var f = Date.formatCodes[character];
10352 f = typeof f == 'function'? f() : f;
10353 Date.formatCodes[character] = f; // reassign function result to prevent repeated execution
10356 // note: unknown characters are treated as literals
10357 return f || ("'" + String.escape(character) + "'");
10361 createFormat : function(format) {
10366 for (var i = 0; i < format.length; ++i) {
10367 ch = format.charAt(i);
10368 if (!special && ch == "\\") {
10370 } else if (special) {
10372 code.push("'" + String.escape(ch) + "'");
10374 code.push(Date.getFormatCode(ch))
10377 Date.formatFunctions[format] = new Function("return " + code.join('+'));
10381 createParser : function() {
10383 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
10384 "def = Date.defaults,",
10385 "results = String(input).match(Date.parseRegexes[{0}]);", // either null, or an array of matched strings
10390 "if(u != null){", // i.e. unix time is defined
10391 "v = new Date(u * 1000);", // give top priority to UNIX time
10393 // create Date object representing midnight of the current day;
10394 // this will provide us with our date defaults
10395 // (note: clearTime() handles Daylight Saving Time automatically)
10396 "dt = (new Date()).clearTime();",
10398 // date calculations (note: these calculations create a dependency on Ext.num())
10399 "y = y >= 0? y : Ext.num(def.y, dt.getFullYear());",
10400 "m = m >= 0? m : Ext.num(def.m - 1, dt.getMonth());",
10401 "d = d >= 0? d : Ext.num(def.d, dt.getDate());",
10403 // time calculations (note: these calculations create a dependency on Ext.num())
10404 "h = h || Ext.num(def.h, dt.getHours());",
10405 "i = i || Ext.num(def.i, dt.getMinutes());",
10406 "s = s || Ext.num(def.s, dt.getSeconds());",
10407 "ms = ms || Ext.num(def.ms, dt.getMilliseconds());",
10409 "if(z >= 0 && y >= 0){",
10410 // both the year and zero-based day of year are defined and >= 0.
10411 // these 2 values alone provide sufficient info to create a full date object
10413 // create Date object representing January 1st for the given year
10414 "v = new Date(y, 0, 1, h, i, s, ms);",
10416 // then add day of year, checking for Date "rollover" if necessary
10417 "v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);",
10418 "}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
10419 "v = null;", // invalid date, so return null
10421 // plain old Date object
10422 "v = new Date(y, m, d, h, i, s, ms);",
10428 // favour UTC offset over GMT offset
10430 // reset to UTC, then add offset
10431 "v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
10433 // reset to GMT, then add offset
10434 "v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
10441 return function(format) {
10442 var regexNum = Date.parseRegexes.length,
10449 for (var i = 0; i < format.length; ++i) {
10450 ch = format.charAt(i);
10451 if (!special && ch == "\\") {
10453 } else if (special) {
10455 regex.push(String.escape(ch));
10457 var obj = $f(ch, currentGroup);
10458 currentGroup += obj.g;
10460 if (obj.g && obj.c) {
10466 Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", "i");
10467 Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
10475 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
10476 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
10477 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
10481 c:"d = parseInt(results[{0}], 10);\n",
10482 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
10486 c:"d = parseInt(results[{0}], 10);\n",
10487 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
10490 for (var a = [], i = 0; i < 7; a.push(Date.getShortDayName(i)), ++i); // get localised short day names
10494 s:"(?:" + a.join("|") +")"
10501 s:"(?:" + Date.dayNames.join("|") + ")"
10507 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
10512 s:"(?:st|nd|rd|th)"
10517 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
10521 c:"z = parseInt(results[{0}], 10);\n",
10522 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
10527 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
10532 c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
10533 s:"(" + Date.monthNames.join("|") + ")"
10537 for (var a = [], i = 0; i < 12; a.push(Date.getShortMonthName(i)), ++i); // get localised short month names
10538 return Ext.applyIf({
10539 s:"(" + a.join("|") + ")"
10544 c:"m = parseInt(results[{0}], 10) - 1;\n",
10545 s:"(\\d{2})" // month number with leading zeros (01 - 12)
10549 c:"m = parseInt(results[{0}], 10) - 1;\n",
10550 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
10555 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
10567 c:"y = parseInt(results[{0}], 10);\n",
10568 s:"(\\d{4})" // 4-digit year
10572 c:"var ty = parseInt(results[{0}], 10);\n"
10573 + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
10578 c:"if (results[{0}] == 'am') {\n"
10579 + "if (!h || h == 12) { h = 0; }\n"
10580 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
10585 c:"if (results[{0}] == 'AM') {\n"
10586 + "if (!h || h == 12) { h = 0; }\n"
10587 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
10595 c:"h = parseInt(results[{0}], 10);\n",
10596 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
10603 c:"h = parseInt(results[{0}], 10);\n",
10604 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
10608 c:"i = parseInt(results[{0}], 10);\n",
10609 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
10613 c:"s = parseInt(results[{0}], 10);\n",
10614 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
10618 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
10619 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
10624 "o = results[{0}];",
10625 "var sn = o.substring(0,1),", // get + / - sign
10626 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
10627 "mn = o.substring(3,5) % 60;", // get minutes
10628 "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
10630 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
10635 "o = results[{0}];",
10636 "var sn = o.substring(0,1),", // get + / - sign
10637 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
10638 "mn = o.substring(4,6) % 60;", // get minutes
10639 "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
10641 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
10646 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
10650 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
10651 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
10652 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
10657 $f("Y", 1), // year
10658 $f("m", 2), // month
10660 $f("h", 4), // hour
10661 $f("i", 5), // minute
10662 $f("s", 6), // second
10663 {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)
10664 {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
10665 "if(results[8]) {", // timezone specified
10666 "if(results[8] == 'Z'){",
10668 "}else if (results[8].indexOf(':') > -1){",
10669 $f("P", 8).c, // timezone offset with colon separator
10671 $f("O", 8).c, // timezone offset without colon separator
10677 for (var i = 0, l = arr.length; i < l; ++i) {
10678 calc.push(arr[i].c);
10685 arr[0].s, // year (required)
10686 "(?:", "-", arr[1].s, // month (optional)
10687 "(?:", "-", arr[2].s, // day (optional)
10689 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
10690 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
10691 "(?::", arr[5].s, ")?", // seconds (optional)
10692 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
10693 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
10702 c:"u = parseInt(results[{0}], 10);\n",
10703 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
10710 Ext.apply(Date.prototype, {
10712 dateFormat : function(format) {
10713 if (Date.formatFunctions[format] == null) {
10714 Date.createFormat(format);
10716 return Date.formatFunctions[format].call(this);
10720 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
10722 * Note: The date string returned by the javascript Date object's toString() method varies
10723 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
10724 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
10725 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
10726 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
10727 * from the GMT offset portion of the date string.
10728 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
10730 getTimezone : function() {
10731 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
10733 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
10734 // 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)
10735 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
10736 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
10737 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
10739 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
10740 // step 1: (?:\((.*)\) -- find timezone in parentheses
10741 // 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
10742 // step 3: remove all non uppercase characters found in step 1 and 2
10743 return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
10747 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
10748 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
10749 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
10751 getGMTOffset : function(colon) {
10752 return (this.getTimezoneOffset() > 0 ? "-" : "+")
10753 + String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0")
10754 + (colon ? ":" : "")
10755 + String.leftPad(Math.abs(this.getTimezoneOffset() % 60), 2, "0");
10759 * Get the numeric day number of the year, adjusted for leap year.
10760 * @return {Number} 0 to 364 (365 in leap years).
10762 getDayOfYear: function() {
10765 m = this.getMonth(),
10768 for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
10769 num += d.getDaysInMonth();
10771 return num + this.getDate() - 1;
10775 * Get the numeric ISO-8601 week number of the year.
10776 * (equivalent to the format specifier 'W', but without a leading zero).
10777 * @return {Number} 1 to 53
10779 getWeekOfYear : function() {
10780 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
10781 var ms1d = 864e5, // milliseconds in a day
10782 ms7d = 7 * ms1d; // milliseconds in a week
10784 return function() { // return a closure so constants get calculated only once
10785 var DC3 = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 3) / ms1d, // an Absolute Day Number
10786 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
10787 Wyr = new Date(AWN * ms7d).getUTCFullYear();
10789 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
10794 * Checks if the current date falls within a leap year.
10795 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
10797 isLeapYear : function() {
10798 var year = this.getFullYear();
10799 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
10803 * Get the first day of the current month, adjusted for leap year. The returned value
10804 * is the numeric day index within the week (0-6) which can be used in conjunction with
10805 * the {@link #monthNames} array to retrieve the textual day name.
10808 var dt = new Date('1/10/2007');
10809 document.write(Date.dayNames[dt.getFirstDayOfMonth()]); //output: 'Monday'
10811 * @return {Number} The day number (0-6).
10813 getFirstDayOfMonth : function() {
10814 var day = (this.getDay() - (this.getDate() - 1)) % 7;
10815 return (day < 0) ? (day + 7) : day;
10819 * Get the last day of the current month, adjusted for leap year. The returned value
10820 * is the numeric day index within the week (0-6) which can be used in conjunction with
10821 * the {@link #monthNames} array to retrieve the textual day name.
10824 var dt = new Date('1/10/2007');
10825 document.write(Date.dayNames[dt.getLastDayOfMonth()]); //output: 'Wednesday'
10827 * @return {Number} The day number (0-6).
10829 getLastDayOfMonth : function() {
10830 return this.getLastDateOfMonth().getDay();
10835 * Get the date of the first day of the month in which this date resides.
10838 getFirstDateOfMonth : function() {
10839 return new Date(this.getFullYear(), this.getMonth(), 1);
10843 * Get the date of the last day of the month in which this date resides.
10846 getLastDateOfMonth : function() {
10847 return new Date(this.getFullYear(), this.getMonth(), this.getDaysInMonth());
10851 * Get the number of days in the current month, adjusted for leap year.
10852 * @return {Number} The number of days in the month.
10854 getDaysInMonth: function() {
10855 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
10857 return function() { // return a closure for efficiency
10858 var m = this.getMonth();
10860 return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
10865 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
10866 * @return {String} 'st, 'nd', 'rd' or 'th'.
10868 getSuffix : function() {
10869 switch (this.getDate()) {
10886 * Creates and returns a new Date instance with the exact same date value as the called instance.
10887 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
10888 * variable will also be changed. When the intention is to create a new variable that will not
10889 * modify the original instance, you should create a clone.
10891 * Example of correctly cloning a date:
10894 var orig = new Date('10/1/2006');
10897 document.write(orig); //returns 'Thu Oct 05 2006'!
10900 var orig = new Date('10/1/2006');
10901 var copy = orig.clone();
10903 document.write(orig); //returns 'Thu Oct 01 2006'
10905 * @return {Date} The new Date instance.
10907 clone : function() {
10908 return new Date(this.getTime());
10912 * Checks if the current date is affected by Daylight Saving Time (DST).
10913 * @return {Boolean} True if the current date is affected by DST.
10915 isDST : function() {
10916 // adapted from http://extjs.com/forum/showthread.php?p=247172#post247172
10917 // courtesy of @geoffrey.mcgill
10918 return new Date(this.getFullYear(), 0, 1).getTimezoneOffset() != this.getTimezoneOffset();
10922 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
10923 * automatically adjusting for Daylight Saving Time (DST) where applicable.
10924 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
10925 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
10926 * @return {Date} this or the clone.
10928 clearTime : function(clone) {
10930 return this.clone().clearTime();
10933 // get current date before clearing time
10934 var d = this.getDate();
10938 this.setMinutes(0);
10939 this.setSeconds(0);
10940 this.setMilliseconds(0);
10942 if (this.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
10943 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
10944 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
10946 // increment hour until cloned date == current date
10947 for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
10950 this.setHours(c.getHours());
10957 * Provides a convenient method for performing basic date arithmetic. This method
10958 * does not modify the Date instance being called - it creates and returns
10959 * a new Date instance containing the resulting date value.
10964 var dt = new Date('10/29/2006').add(Date.DAY, 5);
10965 document.write(dt); //returns 'Fri Nov 03 2006 00:00:00'
10967 // Negative values will be subtracted:
10968 var dt2 = new Date('10/1/2006').add(Date.DAY, -5);
10969 document.write(dt2); //returns 'Tue Sep 26 2006 00:00:00'
10971 // You can even chain several calls together in one line:
10972 var dt3 = new Date('10/1/2006').add(Date.DAY, 5).add(Date.HOUR, 8).add(Date.MINUTE, -30);
10973 document.write(dt3); //returns 'Fri Oct 06 2006 07:30:00'
10976 * @param {String} interval A valid date interval enum value.
10977 * @param {Number} value The amount to add to the current date.
10978 * @return {Date} The new Date instance.
10980 add : function(interval, value) {
10981 var d = this.clone();
10982 if (!interval || value === 0) return d;
10984 switch(interval.toLowerCase()) {
10986 d.setMilliseconds(this.getMilliseconds() + value);
10989 d.setSeconds(this.getSeconds() + value);
10992 d.setMinutes(this.getMinutes() + value);
10995 d.setHours(this.getHours() + value);
10998 d.setDate(this.getDate() + value);
11001 var day = this.getDate();
11003 day = Math.min(day, this.getFirstDateOfMonth().add('mo', value).getLastDateOfMonth().getDate());
11006 d.setMonth(this.getMonth() + value);
11009 d.setFullYear(this.getFullYear() + value);
11016 * Checks if this date falls on or between the given start and end dates.
11017 * @param {Date} start Start date
11018 * @param {Date} end End date
11019 * @return {Boolean} true if this date falls on or between the given start and end dates.
11021 between : function(start, end) {
11022 var t = this.getTime();
11023 return start.getTime() <= t && t <= end.getTime();
11029 * Formats a date given the supplied format string.
11030 * @param {String} format The format string.
11031 * @return {String} The formatted date.
11034 Date.prototype.format = Date.prototype.dateFormat;
11038 if (Ext.isSafari && (navigator.userAgent.match(/WebKit\/(\d+)/)[1] || NaN) < 420) {
11039 Ext.apply(Date.prototype, {
11040 _xMonth : Date.prototype.setMonth,
11041 _xDate : Date.prototype.setDate,
11043 // Bug in Safari 1.3, 2.0 (WebKit build < 420)
11044 // Date.setMonth does not work consistently if iMonth is not 0-11
11045 setMonth : function(num) {
11047 var n = Math.ceil(-num),
11048 back_year = Math.ceil(n / 12),
11049 month = (n % 12) ? 12 - n % 12 : 0;
11051 this.setFullYear(this.getFullYear() - back_year);
11053 return this._xMonth(month);
11055 return this._xMonth(num);
11059 // Bug in setDate() method (resolved in WebKit build 419.3, so to be safe we target Webkit builds < 420)
11060 // The parameter for Date.setDate() is converted to a signed byte integer in Safari
11061 // http://brianary.blogspot.com/2006/03/safari-date-bug.html
11062 setDate : function(d) {
11063 // use setTime() to workaround setDate() bug
11064 // subtract current day of month in milliseconds, then add desired day of month in milliseconds
11065 return this.setTime(this.getTime() - (this.getDate() - d) * 864e5);
11072 /* Some basic Date tests... (requires Firebug)
11074 Date.parseDate('', 'c'); // call Date.parseDate() once to force computation of regex string so we can console.log() it
11075 console.log('Insane Regex for "c" format: %o', Date.parseCodes.c.s); // view the insane regex for the "c" format specifier
11078 console.group('Standard Date.parseDate() Tests');
11079 console.log('Date.parseDate("2009-01-05T11:38:56", "c") = %o', Date.parseDate("2009-01-05T11:38:56", "c")); // assumes browser's timezone setting
11080 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
11081 console.log('Date.parseDate("2009-03-03T13:36:54,101000Z", "c") = %o', Date.parseDate("2009-03-03T13:36:54,101000Z", "c")); // UTC
11082 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
11083 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
11084 console.groupEnd();
11086 // ISO-8601 format as specified in http://www.w3.org/TR/NOTE-datetime
11087 // -- accepts ALL 6 levels of date-time granularity
11088 console.group('ISO-8601 Granularity Test (see http://www.w3.org/TR/NOTE-datetime)');
11089 console.log('Date.parseDate("1997", "c") = %o', Date.parseDate("1997", "c")); // YYYY (e.g. 1997)
11090 console.log('Date.parseDate("1997-07", "c") = %o', Date.parseDate("1997-07", "c")); // YYYY-MM (e.g. 1997-07)
11091 console.log('Date.parseDate("1997-07-16", "c") = %o', Date.parseDate("1997-07-16", "c")); // YYYY-MM-DD (e.g. 1997-07-16)
11092 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)
11093 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)
11094 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)
11095 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)
11096 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
11097 console.groupEnd();
11100 * @class Ext.util.DelayedTask
11101 * <p> The DelayedTask class provides a convenient way to "buffer" the execution of a method,
11102 * performing setTimeout where a new timeout cancels the old timeout. When called, the
11103 * task will wait the specified time period before executing. If durng that time period,
11104 * the task is called again, the original call will be cancelled. This continues so that
11105 * the function is only called a single time for each iteration.</p>
11106 * <p>This method is especially useful for things like detecting whether a user has finished
11107 * typing in a text field. An example would be performing validation on a keypress. You can
11108 * use this class to buffer the keypress events for a certain number of milliseconds, and
11109 * perform only if they stop for that amount of time. Usage:</p><pre><code>
11110 var task = new Ext.util.DelayedTask(function(){
11111 alert(Ext.getDom('myInputField').value.length);
11113 // Wait 500ms before calling our function. If the user presses another key
11114 // during that 500ms, it will be cancelled and we'll wait another 500ms.
11115 Ext.get('myInputField').on('keypress', function(){
11116 task.{@link #delay}(500);
11119 * <p>Note that we are using a DelayedTask here to illustrate a point. The configuration
11120 * option <tt>buffer</tt> for {@link Ext.util.Observable#addListener addListener/on} will
11121 * also setup a delayed task for you to buffer events.</p>
11122 * @constructor The parameters to this constructor serve as defaults and are not required.
11123 * @param {Function} fn (optional) The default function to call.
11124 * @param {Object} scope The default scope (The <code><b>this</b></code> reference) in which the
11125 * function is called. If not specified, <code>this</code> will refer to the browser window.
11126 * @param {Array} args (optional) The default Array of arguments.
11128 Ext.util.DelayedTask = function(fn, scope, args){
11134 fn.apply(scope, args || []);
11138 * Cancels any pending timeout and queues a new one
11139 * @param {Number} delay The milliseconds to delay
11140 * @param {Function} newFn (optional) Overrides function passed to constructor
11141 * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
11142 * is specified, <code>this</code> will refer to the browser window.
11143 * @param {Array} newArgs (optional) Overrides args passed to constructor
11145 me.delay = function(delay, newFn, newScope, newArgs){
11148 scope = newScope || scope;
11149 args = newArgs || args;
11150 id = setInterval(call, delay);
11154 * Cancel the last queued timeout
11156 me.cancel = function(){
11163 * @class Ext.util.MixedCollection
\r
11164 * @extends Ext.util.Observable
\r
11165 * A Collection class that maintains both numeric indexes and keys and exposes events.
\r
11167 * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
\r
11168 * function should add function references to the collection. Defaults to
\r
11169 * <tt>false</tt>.
\r
11170 * @param {Function} keyFn A function that can accept an item of the type(s) stored in this MixedCollection
\r
11171 * and return the key value for that item. This is used when available to look up the key on items that
\r
11172 * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is
\r
11173 * equivalent to providing an implementation for the {@link #getKey} method.
\r
11175 Ext.util.MixedCollection = function(allowFunctions, keyFn){
\r
11183 * Fires when the collection is cleared.
\r
11188 * Fires when an item is added to the collection.
\r
11189 * @param {Number} index The index at which the item was added.
\r
11190 * @param {Object} o The item added.
\r
11191 * @param {String} key The key associated with the added item.
\r
11196 * Fires when an item is replaced in the collection.
\r
11197 * @param {String} key he key associated with the new added.
\r
11198 * @param {Object} old The item being replaced.
\r
11199 * @param {Object} new The new item.
\r
11204 * Fires when an item is removed from the collection.
\r
11205 * @param {Object} o The item being removed.
\r
11206 * @param {String} key (optional) The key associated with the removed item.
\r
11211 this.allowFunctions = allowFunctions === true;
\r
11213 this.getKey = keyFn;
\r
11215 Ext.util.MixedCollection.superclass.constructor.call(this);
\r
11218 Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
\r
11221 * @cfg {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll}
\r
11222 * function should add function references to the collection. Defaults to
\r
11223 * <tt>false</tt>.
\r
11225 allowFunctions : false,
\r
11228 * Adds an item to the collection. Fires the {@link #add} event when complete.
\r
11229 * @param {String} key <p>The key to associate with the item, or the new item.</p>
\r
11230 * <p>If a {@link #getKey} implementation was specified for this MixedCollection,
\r
11231 * or if the key of the stored items is in a property called <tt><b>id</b></tt>,
\r
11232 * the MixedCollection will be able to <i>derive</i> the key for the new item.
\r
11233 * In this case just pass the new item in this parameter.</p>
\r
11234 * @param {Object} o The item to add.
\r
11235 * @return {Object} The item added.
\r
11237 add : function(key, o){
\r
11238 if(arguments.length == 1){
\r
11239 o = arguments[0];
\r
11240 key = this.getKey(o);
\r
11242 if(typeof key != 'undefined' && key !== null){
\r
11243 var old = this.map[key];
\r
11244 if(typeof old != 'undefined'){
\r
11245 return this.replace(key, o);
\r
11247 this.map[key] = o;
\r
11250 this.items.push(o);
\r
11251 this.keys.push(key);
\r
11252 this.fireEvent('add', this.length-1, o, key);
\r
11257 * MixedCollection has a generic way to fetch keys if you implement getKey. The default implementation
\r
11258 * simply returns <b><code>item.id</code></b> but you can provide your own implementation
\r
11259 * to return a different value as in the following examples:<pre><code>
\r
11261 var mc = new Ext.util.MixedCollection();
\r
11262 mc.add(someEl.dom.id, someEl);
\r
11263 mc.add(otherEl.dom.id, otherEl);
\r
11267 var mc = new Ext.util.MixedCollection();
\r
11268 mc.getKey = function(el){
\r
11269 return el.dom.id;
\r
11274 // or via the constructor
\r
11275 var mc = new Ext.util.MixedCollection(false, function(el){
\r
11276 return el.dom.id;
\r
11281 * @param {Object} item The item for which to find the key.
\r
11282 * @return {Object} The key for the passed item.
\r
11284 getKey : function(o){
\r
11289 * Replaces an item in the collection. Fires the {@link #replace} event when complete.
\r
11290 * @param {String} key <p>The key associated with the item to replace, or the replacement item.</p>
\r
11291 * <p>If you supplied a {@link #getKey} implementation for this MixedCollection, or if the key
\r
11292 * of your stored items is in a property called <tt><b>id</b></tt>, then the MixedCollection
\r
11293 * will be able to <i>derive</i> the key of the replacement item. If you want to replace an item
\r
11294 * with one having the same key value, then just pass the replacement item in this parameter.</p>
\r
11295 * @param o {Object} o (optional) If the first parameter passed was a key, the item to associate
\r
11297 * @return {Object} The new item.
\r
11299 replace : function(key, o){
\r
11300 if(arguments.length == 1){
\r
11301 o = arguments[0];
\r
11302 key = this.getKey(o);
\r
11304 var old = this.map[key];
\r
11305 if(typeof key == 'undefined' || key === null || typeof old == 'undefined'){
\r
11306 return this.add(key, o);
\r
11308 var index = this.indexOfKey(key);
\r
11309 this.items[index] = o;
\r
11310 this.map[key] = o;
\r
11311 this.fireEvent('replace', key, old, o);
\r
11316 * Adds all elements of an Array or an Object to the collection.
\r
11317 * @param {Object/Array} objs An Object containing properties which will be added
\r
11318 * to the collection, or an Array of values, each of which are added to the collection.
\r
11319 * Functions references will be added to the collection if <code>{@link #allowFunctions}</code>
\r
11320 * has been set to <tt>true</tt>.
\r
11322 addAll : function(objs){
\r
11323 if(arguments.length > 1 || Ext.isArray(objs)){
\r
11324 var args = arguments.length > 1 ? arguments : objs;
\r
11325 for(var i = 0, len = args.length; i < len; i++){
\r
11326 this.add(args[i]);
\r
11329 for(var key in objs){
\r
11330 if(this.allowFunctions || typeof objs[key] != 'function'){
\r
11331 this.add(key, objs[key]);
\r
11338 * Executes the specified function once for every item in the collection, passing the following arguments:
\r
11339 * <div class="mdetail-params"><ul>
\r
11340 * <li><b>item</b> : Mixed<p class="sub-desc">The collection item</p></li>
\r
11341 * <li><b>index</b> : Number<p class="sub-desc">The item's index</p></li>
\r
11342 * <li><b>length</b> : Number<p class="sub-desc">The total number of items in the collection</p></li>
\r
11344 * The function should return a boolean value. Returning false from the function will stop the iteration.
\r
11345 * @param {Function} fn The function to execute for each item.
\r
11346 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11348 each : function(fn, scope){
\r
11349 var items = [].concat(this.items); // each safe for removal
\r
11350 for(var i = 0, len = items.length; i < len; i++){
\r
11351 if(fn.call(scope || items[i], items[i], i, len) === false){
\r
11358 * Executes the specified function once for every key in the collection, passing each
\r
11359 * key, and its associated item as the first two parameters.
\r
11360 * @param {Function} fn The function to execute for each item.
\r
11361 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11363 eachKey : function(fn, scope){
\r
11364 for(var i = 0, len = this.keys.length; i < len; i++){
\r
11365 fn.call(scope || window, this.keys[i], this.items[i], i, len);
\r
11370 * Returns the first item in the collection which elicits a true return value from the
\r
11371 * passed selection function.
\r
11372 * @param {Function} fn The selection function to execute for each item.
\r
11373 * @param {Object} scope (optional) The scope in which to execute the function.
\r
11374 * @return {Object} The first item in the collection which returned true from the selection function.
\r
11376 find : function(fn, scope){
\r
11377 for(var i = 0, len = this.items.length; i < len; i++){
\r
11378 if(fn.call(scope || window, this.items[i], this.keys[i])){
\r
11379 return this.items[i];
\r
11386 * Inserts an item at the specified index in the collection. Fires the {@link #add} event when complete.
\r
11387 * @param {Number} index The index to insert the item at.
\r
11388 * @param {String} key The key to associate with the new item, or the item itself.
\r
11389 * @param {Object} o (optional) If the second parameter was a key, the new item.
\r
11390 * @return {Object} The item inserted.
\r
11392 insert : function(index, key, o){
\r
11393 if(arguments.length == 2){
\r
11394 o = arguments[1];
\r
11395 key = this.getKey(o);
\r
11397 if(this.containsKey(key)){
\r
11398 this.suspendEvents();
\r
11399 this.removeKey(key);
\r
11400 this.resumeEvents();
\r
11402 if(index >= this.length){
\r
11403 return this.add(key, o);
\r
11406 this.items.splice(index, 0, o);
\r
11407 if(typeof key != 'undefined' && key !== null){
\r
11408 this.map[key] = o;
\r
11410 this.keys.splice(index, 0, key);
\r
11411 this.fireEvent('add', index, o, key);
\r
11416 * Remove an item from the collection.
\r
11417 * @param {Object} o The item to remove.
\r
11418 * @return {Object} The item removed or false if no item was removed.
\r
11420 remove : function(o){
\r
11421 return this.removeAt(this.indexOf(o));
\r
11425 * Remove an item from a specified index in the collection. Fires the {@link #remove} event when complete.
\r
11426 * @param {Number} index The index within the collection of the item to remove.
\r
11427 * @return {Object} The item removed or false if no item was removed.
\r
11429 removeAt : function(index){
\r
11430 if(index < this.length && index >= 0){
\r
11432 var o = this.items[index];
\r
11433 this.items.splice(index, 1);
\r
11434 var key = this.keys[index];
\r
11435 if(typeof key != 'undefined'){
\r
11436 delete this.map[key];
\r
11438 this.keys.splice(index, 1);
\r
11439 this.fireEvent('remove', o, key);
\r
11446 * Removed an item associated with the passed key fom the collection.
\r
11447 * @param {String} key The key of the item to remove.
\r
11448 * @return {Object} The item removed or false if no item was removed.
\r
11450 removeKey : function(key){
\r
11451 return this.removeAt(this.indexOfKey(key));
\r
11455 * Returns the number of items in the collection.
\r
11456 * @return {Number} the number of items in the collection.
\r
11458 getCount : function(){
\r
11459 return this.length;
\r
11463 * Returns index within the collection of the passed Object.
\r
11464 * @param {Object} o The item to find the index of.
\r
11465 * @return {Number} index of the item. Returns -1 if not found.
\r
11467 indexOf : function(o){
\r
11468 return this.items.indexOf(o);
\r
11472 * Returns index within the collection of the passed key.
\r
11473 * @param {String} key The key to find the index of.
\r
11474 * @return {Number} index of the key.
\r
11476 indexOfKey : function(key){
\r
11477 return this.keys.indexOf(key);
\r
11481 * Returns the item associated with the passed key OR index.
\r
11482 * Key has priority over index. This is the equivalent
\r
11483 * of calling {@link #key} first, then if nothing matched calling {@link #itemAt}.
\r
11484 * @param {String/Number} key The key or index of the item.
\r
11485 * @return {Object} If the item is found, returns the item. If the item was not found, returns <tt>undefined</tt>.
\r
11486 * If an item was found, but is a Class, returns <tt>null</tt>.
\r
11488 item : function(key){
\r
11489 var mk = this.map[key],
\r
11490 item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
\r
11491 return !Ext.isFunction(item) || this.allowFunctions ? item : null; // for prototype!
\r
11495 * Returns the item at the specified index.
\r
11496 * @param {Number} index The index of the item.
\r
11497 * @return {Object} The item at the specified index.
\r
11499 itemAt : function(index){
\r
11500 return this.items[index];
\r
11504 * Returns the item associated with the passed key.
\r
11505 * @param {String/Number} key The key of the item.
\r
11506 * @return {Object} The item associated with the passed key.
\r
11508 key : function(key){
\r
11509 return this.map[key];
\r
11513 * Returns true if the collection contains the passed Object as an item.
\r
11514 * @param {Object} o The Object to look for in the collection.
\r
11515 * @return {Boolean} True if the collection contains the Object as an item.
\r
11517 contains : function(o){
\r
11518 return this.indexOf(o) != -1;
\r
11522 * Returns true if the collection contains the passed Object as a key.
\r
11523 * @param {String} key The key to look for in the collection.
\r
11524 * @return {Boolean} True if the collection contains the Object as a key.
\r
11526 containsKey : function(key){
\r
11527 return typeof this.map[key] != 'undefined';
\r
11531 * Removes all items from the collection. Fires the {@link #clear} event when complete.
\r
11533 clear : function(){
\r
11538 this.fireEvent('clear');
\r
11542 * Returns the first item in the collection.
\r
11543 * @return {Object} the first item in the collection..
\r
11545 first : function(){
\r
11546 return this.items[0];
\r
11550 * Returns the last item in the collection.
\r
11551 * @return {Object} the last item in the collection..
\r
11553 last : function(){
\r
11554 return this.items[this.length-1];
\r
11559 * @param {String} property Property to sort by ('key', 'value', or 'index')
\r
11560 * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'.
\r
11561 * @param {Function} fn (optional) Comparison function that defines the sort order.
\r
11562 * Defaults to sorting by numeric value.
\r
11564 _sort : function(property, dir, fn){
\r
11567 dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
\r
11568 c = [], k = this.keys, items = this.items;
\r
11570 fn = fn || function(a, b){
\r
11573 for(i = 0, len = items.length; i < len; i++){
\r
11574 c[c.length] = {key: k[i], value: items[i], index: i};
\r
11576 c.sort(function(a, b){
\r
11577 var v = fn(a[property], b[property]) * dsc;
\r
11579 v = (a.index < b.index ? -1 : 1);
\r
11583 for(i = 0, len = c.length; i < len; i++){
\r
11584 items[i] = c[i].value;
\r
11587 this.fireEvent('sort', this);
\r
11591 * Sorts this collection by <b>item</b> value with the passed comparison function.
\r
11592 * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
\r
11593 * @param {Function} fn (optional) Comparison function that defines the sort order.
\r
11594 * Defaults to sorting by numeric value.
\r
11596 sort : function(dir, fn){
\r
11597 this._sort('value', dir, fn);
\r
11601 * Sorts this collection by <b>key</b>s.
\r
11602 * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'.
\r
11603 * @param {Function} fn (optional) Comparison function that defines the sort order.
\r
11604 * Defaults to sorting by case insensitive string.
\r
11606 keySort : function(dir, fn){
\r
11607 this._sort('key', dir, fn || function(a, b){
\r
11608 var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
\r
11609 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
\r
11614 * Returns a range of items in this collection
\r
11615 * @param {Number} startIndex (optional) The starting index. Defaults to 0.
\r
11616 * @param {Number} endIndex (optional) The ending index. Defaults to the last item.
\r
11617 * @return {Array} An array of items
\r
11619 getRange : function(start, end){
\r
11620 var items = this.items;
\r
11621 if(items.length < 1){
\r
11624 start = start || 0;
\r
11625 end = Math.min(typeof end == 'undefined' ? this.length-1 : end, this.length-1);
\r
11627 if(start <= end){
\r
11628 for(i = start; i <= end; i++) {
\r
11629 r[r.length] = items[i];
\r
11632 for(i = start; i >= end; i--) {
\r
11633 r[r.length] = items[i];
\r
11640 * Filter the <i>objects</i> in this collection by a specific property.
\r
11641 * Returns a new collection that has been filtered.
\r
11642 * @param {String} property A property on your objects
\r
11643 * @param {String/RegExp} value Either string that the property values
\r
11644 * should start with or a RegExp to test against the property
\r
11645 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
\r
11646 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison (defaults to False).
\r
11647 * @return {MixedCollection} The new filtered collection
\r
11649 filter : function(property, value, anyMatch, caseSensitive){
\r
11650 if(Ext.isEmpty(value, false)){
\r
11651 return this.clone();
\r
11653 value = this.createValueMatcher(value, anyMatch, caseSensitive);
\r
11654 return this.filterBy(function(o){
\r
11655 return o && value.test(o[property]);
\r
11660 * Filter by a function. Returns a <i>new</i> collection that has been filtered.
\r
11661 * The passed function will be called with each object in the collection.
\r
11662 * If the function returns true, the value is included otherwise it is filtered.
\r
11663 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key)
\r
11664 * @param {Object} scope (optional) The scope of the function (defaults to this)
\r
11665 * @return {MixedCollection} The new filtered collection
\r
11667 filterBy : function(fn, scope){
\r
11668 var r = new Ext.util.MixedCollection();
\r
11669 r.getKey = this.getKey;
\r
11670 var k = this.keys, it = this.items;
\r
11671 for(var i = 0, len = it.length; i < len; i++){
\r
11672 if(fn.call(scope||this, it[i], k[i])){
\r
11673 r.add(k[i], it[i]);
\r
11680 * Finds the index of the first matching object in this collection by a specific property/value.
\r
11681 * @param {String} property The name of a property on your objects.
\r
11682 * @param {String/RegExp} value A string that the property values
\r
11683 * should start with or a RegExp to test against the property.
\r
11684 * @param {Number} start (optional) The index to start searching at (defaults to 0).
\r
11685 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning.
\r
11686 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison.
\r
11687 * @return {Number} The matched index or -1
\r
11689 findIndex : function(property, value, start, anyMatch, caseSensitive){
\r
11690 if(Ext.isEmpty(value, false)){
\r
11693 value = this.createValueMatcher(value, anyMatch, caseSensitive);
\r
11694 return this.findIndexBy(function(o){
\r
11695 return o && value.test(o[property]);
\r
11700 * Find the index of the first matching object in this collection by a function.
\r
11701 * If the function returns <i>true</i> it is considered a match.
\r
11702 * @param {Function} fn The function to be called, it will receive the args o (the object), k (the key).
\r
11703 * @param {Object} scope (optional) The scope of the function (defaults to this).
\r
11704 * @param {Number} start (optional) The index to start searching at (defaults to 0).
\r
11705 * @return {Number} The matched index or -1
\r
11707 findIndexBy : function(fn, scope, start){
\r
11708 var k = this.keys, it = this.items;
\r
11709 for(var i = (start||0), len = it.length; i < len; i++){
\r
11710 if(fn.call(scope||this, it[i], k[i])){
\r
11718 createValueMatcher : function(value, anyMatch, caseSensitive){
\r
11719 if(!value.exec){ // not a regex
\r
11720 value = String(value);
\r
11721 value = new RegExp((anyMatch === true ? '' : '^') + Ext.escapeRe(value), caseSensitive ? '' : 'i');
\r
11727 * Creates a shallow copy of this collection
\r
11728 * @return {MixedCollection}
\r
11730 clone : function(){
\r
11731 var r = new Ext.util.MixedCollection();
\r
11732 var k = this.keys, it = this.items;
\r
11733 for(var i = 0, len = it.length; i < len; i++){
\r
11734 r.add(k[i], it[i]);
\r
11736 r.getKey = this.getKey;
\r
11741 * This method calls {@link #item item()}.
\r
11742 * Returns the item associated with the passed key OR index. Key has priority
\r
11743 * over index. This is the equivalent of calling {@link #key} first, then if
\r
11744 * nothing matched calling {@link #itemAt}.
\r
11745 * @param {String/Number} key The key or index of the item.
\r
11746 * @return {Object} If the item is found, returns the item. If the item was
\r
11747 * not found, returns <tt>undefined</tt>. If an item was found, but is a Class,
\r
11748 * returns <tt>null</tt>.
\r
11750 Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;/**
11751 * @class Ext.util.JSON
11752 * Modified version of Douglas Crockford"s json.js that doesn"t
11753 * mess with the Object prototype
11754 * http://www.json.org/js.html
11757 Ext.util.JSON = new (function(){
11758 var useHasOwn = !!{}.hasOwnProperty,
11759 isNative = function() {
11760 var useNative = null;
11762 return function() {
11763 if (useNative === null) {
11764 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
11770 pad = function(n) {
11771 return n < 10 ? "0" + n : n;
11773 doDecode = function(json){
11774 return eval("(" + json + ')');
11776 doEncode = function(o){
11777 if(!Ext.isDefined(o) || o === null){
11779 }else if(Ext.isArray(o)){
11780 return encodeArray(o);
11781 }else if(Ext.isDate(o)){
11782 return Ext.util.JSON.encodeDate(o);
11783 }else if(Ext.isString(o)){
11784 return encodeString(o);
11785 }else if(typeof o == "number"){
11786 //don't use isNumber here, since finite checks happen inside isNumber
11787 return isFinite(o) ? String(o) : "null";
11788 }else if(Ext.isBoolean(o)){
11791 var a = ["{"], b, i, v;
11793 // don't encode DOM objects
11794 if(!o.getElementsByTagName){
11795 if(!useHasOwn || o.hasOwnProperty(i)) {
11797 switch (typeof v) {
11806 a.push(doEncode(i), ":",
11807 v === null ? "null" : doEncode(v));
11826 encodeString = function(s){
11827 if (/["\\\x00-\x1f]/.test(s)) {
11828 return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
11833 c = b.charCodeAt();
11835 Math.floor(c / 16).toString(16) +
11836 (c % 16).toString(16);
11839 return '"' + s + '"';
11841 encodeArray = function(o){
11842 var a = ["["], b, i, l = o.length, v;
11843 for (i = 0; i < l; i += 1) {
11845 switch (typeof v) {
11854 a.push(v === null ? "null" : Ext.util.JSON.encode(v));
11862 this.encodeDate = function(o){
11863 return '"' + o.getFullYear() + "-" +
11864 pad(o.getMonth() + 1) + "-" +
11865 pad(o.getDate()) + "T" +
11866 pad(o.getHours()) + ":" +
11867 pad(o.getMinutes()) + ":" +
11868 pad(o.getSeconds()) + '"';
11872 * Encodes an Object, Array or other value
11873 * @param {Mixed} o The variable to encode
11874 * @return {String} The JSON string
11876 this.encode = function() {
11878 return function(o) {
11880 // setup encoding function on first access
11881 ec = isNative() ? JSON.stringify : doEncode;
11889 * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
11890 * @param {String} json The JSON string
11891 * @return {Object} The resulting object
11893 this.decode = function() {
11895 return function(json) {
11897 // setup decoding function on first access
11898 dc = isNative() ? JSON.parse : doDecode;
11906 * Shorthand for {@link Ext.util.JSON#encode}
11907 * @param {Mixed} o The variable to encode
11908 * @return {String} The JSON string
11912 Ext.encode = Ext.util.JSON.encode;
11914 * Shorthand for {@link Ext.util.JSON#decode}
11915 * @param {String} json The JSON string
11916 * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
11917 * @return {Object} The resulting object
11921 Ext.decode = Ext.util.JSON.decode;
11923 * @class Ext.util.Format
\r
11924 * Reusable data formatting functions
\r
11927 Ext.util.Format = function(){
\r
11928 var trimRe = /^\s+|\s+$/g;
\r
11931 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
\r
11932 * @param {String} value The string to truncate
\r
11933 * @param {Number} length The maximum length to allow before truncating
\r
11934 * @param {Boolean} word True to try to find a common work break
\r
11935 * @return {String} The converted text
\r
11937 ellipsis : function(value, len, word){
\r
11938 if(value && value.length > len){
\r
11940 var vs = value.substr(0, len - 2);
\r
11941 var index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
\r
11942 if(index == -1 || index < (len - 15)){
\r
11943 return value.substr(0, len - 3) + "...";
\r
11945 return vs.substr(0, index) + "...";
\r
11948 return value.substr(0, len - 3) + "...";
\r
11955 * Checks a reference and converts it to empty string if it is undefined
\r
11956 * @param {Mixed} value Reference to check
\r
11957 * @return {Mixed} Empty string if converted, otherwise the original value
\r
11959 undef : function(value){
\r
11960 return value !== undefined ? value : "";
\r
11964 * Checks a reference and converts it to the default value if it's empty
\r
11965 * @param {Mixed} value Reference to check
\r
11966 * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
\r
11967 * @return {String}
\r
11969 defaultValue : function(value, defaultValue){
\r
11970 return value !== undefined && value !== '' ? value : defaultValue;
\r
11974 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
\r
11975 * @param {String} value The string to encode
\r
11976 * @return {String} The encoded text
\r
11978 htmlEncode : function(value){
\r
11979 return !value ? value : String(value).replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """);
\r
11983 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
\r
11984 * @param {String} value The string to decode
\r
11985 * @return {String} The decoded text
\r
11987 htmlDecode : function(value){
\r
11988 return !value ? value : String(value).replace(/>/g, ">").replace(/</g, "<").replace(/"/g, '"').replace(/&/g, "&");
\r
11992 * Trims any whitespace from either side of a string
\r
11993 * @param {String} value The text to trim
\r
11994 * @return {String} The trimmed text
\r
11996 trim : function(value){
\r
11997 return String(value).replace(trimRe, "");
\r
12001 * Returns a substring from within an original string
\r
12002 * @param {String} value The original text
\r
12003 * @param {Number} start The start index of the substring
\r
12004 * @param {Number} length The length of the substring
\r
12005 * @return {String} The substring
\r
12007 substr : function(value, start, length){
\r
12008 return String(value).substr(start, length);
\r
12012 * Converts a string to all lower case letters
\r
12013 * @param {String} value The text to convert
\r
12014 * @return {String} The converted text
\r
12016 lowercase : function(value){
\r
12017 return String(value).toLowerCase();
\r
12021 * Converts a string to all upper case letters
\r
12022 * @param {String} value The text to convert
\r
12023 * @return {String} The converted text
\r
12025 uppercase : function(value){
\r
12026 return String(value).toUpperCase();
\r
12030 * Converts the first character only of a string to upper case
\r
12031 * @param {String} value The text to convert
\r
12032 * @return {String} The converted text
\r
12034 capitalize : function(value){
\r
12035 return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
\r
12039 call : function(value, fn){
\r
12040 if(arguments.length > 2){
\r
12041 var args = Array.prototype.slice.call(arguments, 2);
\r
12042 args.unshift(value);
\r
12043 return eval(fn).apply(window, args);
\r
12045 return eval(fn).call(window, value);
\r
12050 * Format a number as US currency
\r
12051 * @param {Number/String} value The numeric value to format
\r
12052 * @return {String} The formatted currency string
\r
12054 usMoney : function(v){
\r
12055 v = (Math.round((v-0)*100))/100;
\r
12056 v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
\r
12058 var ps = v.split('.');
\r
12059 var whole = ps[0];
\r
12060 var sub = ps[1] ? '.'+ ps[1] : '.00';
\r
12061 var r = /(\d+)(\d{3})/;
\r
12062 while (r.test(whole)) {
\r
12063 whole = whole.replace(r, '$1' + ',' + '$2');
\r
12066 if(v.charAt(0) == '-'){
\r
12067 return '-$' + v.substr(1);
\r
12073 * Parse a value into a formatted date using the specified format pattern.
\r
12074 * @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
12075 * @param {String} format (optional) Any valid date format string (defaults to 'm/d/Y')
\r
12076 * @return {String} The formatted date string
\r
12078 date : function(v, format){
\r
12082 if(!Ext.isDate(v)){
\r
12083 v = new Date(Date.parse(v));
\r
12085 return v.dateFormat(format || "m/d/Y");
\r
12089 * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
\r
12090 * @param {String} format Any valid date format string
\r
12091 * @return {Function} The date formatting function
\r
12093 dateRenderer : function(format){
\r
12094 return function(v){
\r
12095 return Ext.util.Format.date(v, format);
\r
12100 stripTagsRE : /<\/?[^>]+>/gi,
\r
12103 * Strips all HTML tags
\r
12104 * @param {Mixed} value The text from which to strip tags
\r
12105 * @return {String} The stripped text
\r
12107 stripTags : function(v){
\r
12108 return !v ? v : String(v).replace(this.stripTagsRE, "");
\r
12111 stripScriptsRe : /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
\r
12114 * Strips all script tags
\r
12115 * @param {Mixed} value The text from which to strip script tags
\r
12116 * @return {String} The stripped text
\r
12118 stripScripts : function(v){
\r
12119 return !v ? v : String(v).replace(this.stripScriptsRe, "");
\r
12123 * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
\r
12124 * @param {Number/String} size The numeric value to format
\r
12125 * @return {String} The formatted file size
\r
12127 fileSize : function(size){
\r
12128 if(size < 1024) {
\r
12129 return size + " bytes";
\r
12130 } else if(size < 1048576) {
\r
12131 return (Math.round(((size*10) / 1024))/10) + " KB";
\r
12133 return (Math.round(((size*10) / 1048576))/10) + " MB";
\r
12138 * It does simple math for use in a template, for example:<pre><code>
\r
12139 * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
\r
12141 * @return {Function} A function that operates on the passed value.
\r
12143 math : function(){
\r
12145 return function(v, a){
\r
12147 fns[a] = new Function('v', 'return v ' + a + ';');
\r
12149 return fns[a](v);
\r
12154 * Rounds the passed number to the required decimal precision.
\r
12155 * @param {Number/String} value The numeric value to round.
\r
12156 * @param {Number} precision The number of decimal places to which to round the first parameter's value.
\r
12157 * @return {Number} The rounded value.
\r
12159 round : function(value, precision) {
\r
12160 var result = Number(value);
\r
12161 if (typeof precision == 'number') {
\r
12162 precision = Math.pow(10, precision);
\r
12163 result = Math.round(value * precision) / precision;
\r
12169 * Formats the number according to the format string.
\r
12170 * <div style="margin-left:40px">examples (123456.789):
\r
12171 * <div style="margin-left:10px">
\r
12172 * 0 - (123456) show only digits, no precision<br>
\r
12173 * 0.00 - (123456.78) show only digits, 2 precision<br>
\r
12174 * 0.0000 - (123456.7890) show only digits, 4 precision<br>
\r
12175 * 0,000 - (123,456) show comma and digits, no precision<br>
\r
12176 * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
\r
12177 * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
\r
12178 * To reverse the grouping (,) and decimal (.) for international numbers, add /i to the end.
\r
12179 * For example: 0.000,00/i
\r
12181 * @param {Number} v The number to format.
\r
12182 * @param {String} format The way you would like to format this text.
\r
12183 * @return {String} The formatted number.
\r
12185 number: function(v, format) {
\r
12189 v = Ext.num(v, NaN);
\r
12199 if(format.substr(format.length - 2) == '/i'){
\r
12200 format = format.substr(0, format.length - 2);
\r
12206 var hasComma = format.indexOf(comma) != -1,
\r
12207 psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
\r
12209 if(1 < psplit.length){
\r
12210 v = v.toFixed(psplit[1].length);
\r
12211 }else if(2 < psplit.length){
\r
12212 throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
\r
12214 v = v.toFixed(0);
\r
12217 var fnum = v.toString();
\r
12219 psplit = fnum.split('.');
\r
12221 var cnum = psplit[0], parr = [], j = cnum.length, m = Math.floor(j / 3), n = cnum.length % 3 || 3;
\r
12223 for(var i = 0; i < j; i += n){
\r
12227 parr[parr.length] = cnum.substr(i, n);
\r
12230 fnum = parr.join(comma);
\r
12232 fnum += dec + psplit[1];
\r
12236 return (neg ? '-' : '') + format.replace(/[\d,?\.?]+/, fnum);
\r
12240 * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
\r
12241 * @param {String} format Any valid number format string for {@link #number}
\r
12242 * @return {Function} The number formatting function
\r
12244 numberRenderer : function(format){
\r
12245 return function(v){
\r
12246 return Ext.util.Format.number(v, format);
\r
12251 * Selectively do a plural form of a word based on a numeric value. For example, in a template,
\r
12252 * {commentCount:plural("Comment")} would result in "1 Comment" if commentCount was 1 or would be "x Comments"
\r
12253 * if the value is 0 or greater than 1.
\r
12254 * @param {Number} value The value to compare against
\r
12255 * @param {String} singular The singular form of the word
\r
12256 * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
\r
12258 plural : function(v, s, p){
\r
12259 return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
\r
12263 * Converts newline characters to the HTML tag <br/>
\r
12264 * @param {String} The string value to format.
\r
12265 * @return {String} The string with embedded <br/> tags in place of newlines.
\r
12267 nl2br : function(v){
\r
12268 return v === undefined || v === null ? '' : v.replace(/\n/g, '<br/>');
\r
12272 * @class Ext.XTemplate
12273 * @extends Ext.Template
12274 * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
12275 * <li>Autofilling arrays using templates and sub-templates</li>
12276 * <li>Conditional processing with basic comparison operators</li>
12277 * <li>Basic math function support</li>
12278 * <li>Execute arbitrary inline code with special built-in template variables</li>
12279 * <li>Custom member functions</li>
12280 * <li>Many special tags and built-in operators that aren't defined as part of
12281 * the API, but are supported in the templates that can be created</li>
12283 * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
12284 * <li>{@link Ext.DataView}</li>
12285 * <li>{@link Ext.ListView}</li>
12286 * <li>{@link Ext.form.ComboBox}</li>
12287 * <li>{@link Ext.grid.TemplateColumn}</li>
12288 * <li>{@link Ext.grid.GroupingView}</li>
12289 * <li>{@link Ext.menu.Item}</li>
12290 * <li>{@link Ext.layout.MenuLayout}</li>
12291 * <li>{@link Ext.ColorPalette}</li>
12294 * <p>For example usage {@link #XTemplate see the constructor}.</p>
12297 * The {@link Ext.Template#Template Ext.Template constructor} describes
12298 * the acceptable parameters to pass to the constructor. The following
12299 * examples demonstrate all of the supported features.</p>
12301 * <div class="mdetail-params"><ul>
12303 * <li><b><u>Sample Data</u></b>
12304 * <div class="sub-desc">
12305 * <p>This is the data object used for reference in each code example:</p>
12308 name: 'Jack Slocum',
12309 title: 'Lead Developer',
12310 company: 'Ext JS, LLC',
12311 email: 'jack@extjs.com',
12312 address: '4 Red Bulls Drive',
12316 drinks: ['Red Bull', 'Coffee', 'Water'],
12318 name: 'Sara Grace',
12324 name: 'John James',
12333 * <li><b><u>Auto filling of arrays</u></b>
12334 * <div class="sub-desc">
12335 * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
12336 * to process the provided data object:
12338 * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
12339 * repeating the template block inside the <tt>tpl</tt> tag for each item in the
12341 * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
12342 * <li>While processing an array, the special variable <tt>{#}</tt>
12343 * will provide the current array index + 1 (starts at 1, not 0).</li>
12347 <tpl <b>for</b>=".">...</tpl> // loop through array at root node
12348 <tpl <b>for</b>="foo">...</tpl> // loop through array at foo node
12349 <tpl <b>for</b>="foo.bar">...</tpl> // loop through array at foo.bar node
12351 * Using the sample data above:
12353 var tpl = new Ext.XTemplate(
12355 '<tpl <b>for</b>=".">', // process the data.kids node
12356 '<p>{#}. {name}</p>', // use current array index to autonumber
12359 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
12361 * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
12362 * to access specified members of the provided data object to populate the template:</p>
12364 var tpl = new Ext.XTemplate(
12365 '<p>Name: {name}</p>',
12366 '<p>Title: {title}</p>',
12367 '<p>Company: {company}</p>',
12369 '<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
12370 '<p>{name}</p>',
12373 tpl.overwrite(panel.body, data); // pass the root node of the data object
12375 * <p>Flat arrays that contain values (and not objects) can be auto-rendered
12376 * using the special <b><tt>{.}</tt></b> variable inside a loop. This variable
12377 * will represent the value of the array at the current index:</p>
12379 var tpl = new Ext.XTemplate(
12380 '<p>{name}\'s favorite beverages:</p>',
12381 '<tpl for="drinks">',
12382 '<div> - {.}</div>',
12385 tpl.overwrite(panel.body, data);
12387 * <p>When processing a sub-template, for example while looping through a child array,
12388 * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
12390 var tpl = new Ext.XTemplate(
12391 '<p>Name: {name}</p>',
12393 '<tpl for="kids">',
12394 '<tpl if="age > 1">',
12395 '<p>{name}</p>',
12396 '<p>Dad: {<b>parent</b>.name}</p>',
12400 tpl.overwrite(panel.body, data);
12406 * <li><b><u>Conditional processing with basic comparison operators</u></b>
12407 * <div class="sub-desc">
12408 * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
12409 * to provide conditional checks for deciding whether or not to render specific
12410 * parts of the template. Notes:<div class="sub-desc"><ul>
12411 * <li>Double quotes must be encoded if used within the conditional</li>
12412 * <li>There is no <tt>else</tt> operator — if needed, two opposite
12413 * <tt>if</tt> statements should be used.</li>
12416 <tpl if="age > 1 && age < 10">Child</tpl>
12417 <tpl if="age >= 10 && age < 18">Teenager</tpl>
12418 <tpl <b>if</b>="this.isGirl(name)">...</tpl>
12419 <tpl <b>if</b>="id==\'download\'">...</tpl>
12420 <tpl <b>if</b>="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
12422 <tpl if="name == "Jack"">Hello</tpl>
12423 // encode " if it is part of the condition, e.g.
12424 <tpl if="name == &quot;Jack&quot;">Hello</tpl>
12426 * Using the sample data above:
12428 var tpl = new Ext.XTemplate(
12429 '<p>Name: {name}</p>',
12431 '<tpl for="kids">',
12432 '<tpl if="age > 1">',
12433 '<p>{name}</p>',
12437 tpl.overwrite(panel.body, data);
12443 * <li><b><u>Basic math support</u></b>
12444 * <div class="sub-desc">
12445 * <p>The following basic math operators may be applied directly on numeric
12446 * data values:</p><pre>
12451 var tpl = new Ext.XTemplate(
12452 '<p>Name: {name}</p>',
12454 '<tpl for="kids">',
12455 '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
12456 '<p>{#}: {name}</p>', // <-- Auto-number each item
12457 '<p>In 5 Years: {age+5}</p>', // <-- Basic math
12458 '<p>Dad: {parent.name}</p>',
12462 tpl.overwrite(panel.body, data);
12468 * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
12469 * <div class="sub-desc">
12470 * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
12471 * in the scope of the template. There are some special variables available in that code:
12473 * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
12474 * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
12475 * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
12476 * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
12477 * loop you are in (1-based).</li>
12478 * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
12479 * of the array you are looping.</li>
12480 * <li><b><tt>fm</tt></b>: An alias for <tt>Ext.util.Format</tt>.</li>
12482 * This example demonstrates basic row striping using an inline code block and the
12483 * <tt>xindex</tt> variable:</p>
12485 var tpl = new Ext.XTemplate(
12486 '<p>Name: {name}</p>',
12487 '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
12489 '<tpl for="kids">',
12490 '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
12495 tpl.overwrite(panel.body, data);
12500 * <li><b><u>Template member functions</u></b>
12501 * <div class="sub-desc">
12502 * <p>One or more member functions can be specified in a configuration
12503 * object passed into the XTemplate constructor for more complex processing:</p>
12505 var tpl = new Ext.XTemplate(
12506 '<p>Name: {name}</p>',
12508 '<tpl for="kids">',
12509 '<tpl if="this.isGirl(name)">',
12510 '<p>Girl: {name} - {age}</p>',
12512 // use opposite if statement to simulate 'else' processing:
12513 '<tpl if="this.isGirl(name) == false">',
12514 '<p>Boy: {name} - {age}</p>',
12516 '<tpl if="this.isBaby(age)">',
12517 '<p>{name} is a baby!</p>',
12519 '</tpl></p>',
12521 // XTemplate configuration:
12523 disableFormats: true,
12524 // member functions:
12525 isGirl: function(name){
12526 return name == 'Sara Grace';
12528 isBaby: function(age){
12533 tpl.overwrite(panel.body, data);
12540 * @param {Mixed} config
12542 Ext.XTemplate = function(){
12543 Ext.XTemplate.superclass.constructor.apply(this, arguments);
12547 re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
12548 nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
12549 ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
12550 execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
12558 RETURN = 'return ',
12559 WITHVALUES = 'with(values){ ';
12561 s = ['<tpl>', s, '</tpl>'].join('');
12563 while((m = s.match(re))){
12564 var m2 = m[0].match(nameRe),
12565 m3 = m[0].match(ifRe),
12566 m4 = m[0].match(execRe),
12570 name = m2 && m2[1] ? m2[1] : '';
12573 exp = m3 && m3[1] ? m3[1] : null;
12575 fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
12579 exp = m4 && m4[1] ? m4[1] : null;
12581 exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
12586 case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
12587 case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
12588 default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
12598 s = s.replace(m[0], '{xtpl'+ id + '}');
12601 Ext.each(tpls, function(t) {
12604 me.master = tpls[tpls.length-1];
12607 Ext.extend(Ext.XTemplate, Ext.Template, {
12609 re : /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
12611 codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
12614 applySubTemplate : function(id, values, parent, xindex, xcount){
12620 if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
12621 (t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
12624 vs = t.target ? t.target.call(me, values, parent) : values;
12626 parent = t.target ? values : parent;
12627 if(t.target && Ext.isArray(vs)){
12628 Ext.each(vs, function(v, i) {
12629 buf[buf.length] = t.compiled.call(me, v, parent, i+1, len);
12631 return buf.join('');
12633 return t.compiled.call(me, vs, parent, xindex, xcount);
12637 compileTpl : function(tpl){
12638 var fm = Ext.util.Format,
12639 useF = this.disableFormats !== true,
12640 sep = Ext.isGecko ? "+" : ",",
12643 function fn(m, name, format, args, math){
12644 if(name.substr(0, 4) == 'xtpl'){
12645 return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
12650 }else if(name === '#'){
12652 }else if(name.indexOf('.') != -1){
12655 v = "values['" + name + "']";
12658 v = '(' + v + math + ')';
12660 if (format && useF) {
12661 args = args ? ',' + args : "";
12662 if(format.substr(0, 5) != "this."){
12663 format = "fm." + format + '(';
12665 format = 'this.call("'+ format.substr(5) + '", ';
12669 args= ''; format = "("+v+" === undefined ? '' : ";
12671 return "'"+ sep + format + v + args + ")"+sep+"'";
12674 function codeFn(m, code){
12675 // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
12676 return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'";
12679 // branched to use + in gecko and [].join() in others
12681 body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
12682 tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
12685 body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
12686 body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
12687 body.push("'].join('');};");
12688 body = body.join('');
12695 * Returns an HTML fragment of this template with the specified values applied.
12696 * @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'})
12697 * @return {String} The HTML fragment
12699 applyTemplate : function(values){
12700 return this.master.compiled.call(this, values, {}, 1, 1);
12704 * Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
12705 * @return {Function} The compiled function
12707 compile : function(){return this;}
12714 * @property disableFormats
12724 * Alias for {@link #applyTemplate}
12725 * Returns an HTML fragment of this template with the specified values applied.
12726 * @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'})
12727 * @return {String} The HTML fragment
12728 * @member Ext.XTemplate
12731 Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
12734 * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
12735 * @param {String/HTMLElement} el A DOM element or its id
12736 * @return {Ext.Template} The created template
12739 Ext.XTemplate.from = function(el){
12740 el = Ext.getDom(el);
12741 return new Ext.XTemplate(el.value || el.innerHTML);
12743 * @class Ext.util.CSS
\r
12744 * Utility class for manipulating CSS rules
\r
12747 Ext.util.CSS = function(){
\r
12748 var rules = null;
\r
12749 var doc = document;
\r
12751 var camelRe = /(-[a-z])/gi;
\r
12752 var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
\r
12756 * Creates a stylesheet from a text blob of rules.
\r
12757 * These rules will be wrapped in a STYLE tag and appended to the HEAD of the document.
\r
12758 * @param {String} cssText The text containing the css rules
\r
12759 * @param {String} id An id to add to the stylesheet for later removal
\r
12760 * @return {StyleSheet}
\r
12762 createStyleSheet : function(cssText, id){
\r
12764 var head = doc.getElementsByTagName("head")[0];
\r
12765 var rules = doc.createElement("style");
\r
12766 rules.setAttribute("type", "text/css");
\r
12768 rules.setAttribute("id", id);
\r
12771 head.appendChild(rules);
\r
12772 ss = rules.styleSheet;
\r
12773 ss.cssText = cssText;
\r
12776 rules.appendChild(doc.createTextNode(cssText));
\r
12778 rules.cssText = cssText;
\r
12780 head.appendChild(rules);
\r
12781 ss = rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
\r
12783 this.cacheStyleSheet(ss);
\r
12788 * Removes a style or link tag by id
\r
12789 * @param {String} id The id of the tag
\r
12791 removeStyleSheet : function(id){
\r
12792 var existing = doc.getElementById(id);
\r
12794 existing.parentNode.removeChild(existing);
\r
12799 * Dynamically swaps an existing stylesheet reference for a new one
\r
12800 * @param {String} id The id of an existing link tag to remove
\r
12801 * @param {String} url The href of the new stylesheet to include
\r
12803 swapStyleSheet : function(id, url){
\r
12804 this.removeStyleSheet(id);
\r
12805 var ss = doc.createElement("link");
\r
12806 ss.setAttribute("rel", "stylesheet");
\r
12807 ss.setAttribute("type", "text/css");
\r
12808 ss.setAttribute("id", id);
\r
12809 ss.setAttribute("href", url);
\r
12810 doc.getElementsByTagName("head")[0].appendChild(ss);
\r
12814 * Refresh the rule cache if you have dynamically added stylesheets
\r
12815 * @return {Object} An object (hash) of rules indexed by selector
\r
12817 refreshCache : function(){
\r
12818 return this.getRules(true);
\r
12822 cacheStyleSheet : function(ss){
\r
12826 try{// try catch for cross domain access issue
\r
12827 var ssRules = ss.cssRules || ss.rules;
\r
12828 for(var j = ssRules.length-1; j >= 0; --j){
\r
12829 rules[ssRules[j].selectorText.toLowerCase()] = ssRules[j];
\r
12835 * Gets all css rules for the document
\r
12836 * @param {Boolean} refreshCache true to refresh the internal cache
\r
12837 * @return {Object} An object (hash) of rules indexed by selector
\r
12839 getRules : function(refreshCache){
\r
12840 if(rules === null || refreshCache){
\r
12842 var ds = doc.styleSheets;
\r
12843 for(var i =0, len = ds.length; i < len; i++){
\r
12845 this.cacheStyleSheet(ds[i]);
\r
12853 * Gets an an individual CSS rule by selector(s)
\r
12854 * @param {String/Array} selector The CSS selector or an array of selectors to try. The first selector that is found is returned.
\r
12855 * @param {Boolean} refreshCache true to refresh the internal cache if you have recently updated any rules or added styles dynamically
\r
12856 * @return {CSSRule} The CSS rule or null if one is not found
\r
12858 getRule : function(selector, refreshCache){
\r
12859 var rs = this.getRules(refreshCache);
\r
12860 if(!Ext.isArray(selector)){
\r
12861 return rs[selector.toLowerCase()];
\r
12863 for(var i = 0; i < selector.length; i++){
\r
12864 if(rs[selector[i]]){
\r
12865 return rs[selector[i].toLowerCase()];
\r
12873 * Updates a rule property
\r
12874 * @param {String/Array} selector If it's an array it tries each selector until it finds one. Stops immediately once one is found.
\r
12875 * @param {String} property The css property
\r
12876 * @param {String} value The new value for the property
\r
12877 * @return {Boolean} true If a rule was found and updated
\r
12879 updateRule : function(selector, property, value){
\r
12880 if(!Ext.isArray(selector)){
\r
12881 var rule = this.getRule(selector);
\r
12883 rule.style[property.replace(camelRe, camelFn)] = value;
\r
12887 for(var i = 0; i < selector.length; i++){
\r
12888 if(this.updateRule(selector[i], property, value)){
\r
12897 @class Ext.util.ClickRepeater
12898 @extends Ext.util.Observable
12900 A wrapper class which can be applied to any element. Fires a "click" event while the
12901 mouse is pressed. The interval between firings may be specified in the config but
12902 defaults to 20 milliseconds.
12904 Optionally, a CSS class may be applied to the element during the time it is pressed.
12906 @cfg {Mixed} el The element to act as a button.
12907 @cfg {Number} delay The initial delay before the repeating event begins firing.
12908 Similar to an autorepeat key delay.
12909 @cfg {Number} interval The interval between firings of the "click" event. Default 20 ms.
12910 @cfg {String} pressClass A CSS class name to be applied to the element while pressed.
12911 @cfg {Boolean} accelerate True if autorepeating should start slowly and accelerate.
12912 "interval" and "delay" are ignored.
12913 @cfg {Boolean} preventDefault True to prevent the default click event
12914 @cfg {Boolean} stopDefault True to stop the default click event
12917 2007-02-02 jvs Original code contributed by Nige "Animal" White
12918 2007-02-02 jvs Renamed to ClickRepeater
12919 2007-02-03 jvs Modifications for FF Mac and Safari
12922 @param {Mixed} el The element to listen on
12923 @param {Object} config
12925 Ext.util.ClickRepeater = function(el, config)
12927 this.el = Ext.get(el);
12928 this.el.unselectable();
12930 Ext.apply(this, config);
12935 * Fires when the mouse button is depressed.
12936 * @param {Ext.util.ClickRepeater} this
12941 * Fires on a specified interval during the time the element is pressed.
12942 * @param {Ext.util.ClickRepeater} this
12947 * Fires when the mouse key is released.
12948 * @param {Ext.util.ClickRepeater} this
12953 if(!this.disabled){
12954 this.disabled = true;
12958 // allow inline handler
12960 this.on("click", this.handler, this.scope || this);
12963 Ext.util.ClickRepeater.superclass.constructor.call(this);
12966 Ext.extend(Ext.util.ClickRepeater, Ext.util.Observable, {
12969 preventDefault : true,
12970 stopDefault : false,
12974 * Enables the repeater and allows events to fire.
12976 enable: function(){
12978 this.el.on('mousedown', this.handleMouseDown, this);
12979 if(this.preventDefault || this.stopDefault){
12980 this.el.on('click', this.eventOptions, this);
12983 this.disabled = false;
12987 * Disables the repeater and stops events from firing.
12989 disable: function(/* private */ force){
12990 if(force || !this.disabled){
12991 clearTimeout(this.timer);
12992 if(this.pressClass){
12993 this.el.removeClass(this.pressClass);
12995 Ext.getDoc().un('mouseup', this.handleMouseUp, this);
12996 this.el.removeAllListeners();
12998 this.disabled = true;
13002 * Convenience function for setting disabled/enabled by boolean.
13003 * @param {Boolean} disabled
13005 setDisabled: function(disabled){
13006 this[disabled ? 'disable' : 'enable']();
13009 eventOptions: function(e){
13010 if(this.preventDefault){
13011 e.preventDefault();
13013 if(this.stopDefault){
13019 destroy : function() {
13020 this.disable(true);
13021 Ext.destroy(this.el);
13022 this.purgeListeners();
13026 handleMouseDown : function(){
13027 clearTimeout(this.timer);
13029 if(this.pressClass){
13030 this.el.addClass(this.pressClass);
13032 this.mousedownTime = new Date();
13034 Ext.getDoc().on("mouseup", this.handleMouseUp, this);
13035 this.el.on("mouseout", this.handleMouseOut, this);
13037 this.fireEvent("mousedown", this);
13038 this.fireEvent("click", this);
13040 // Do not honor delay or interval if acceleration wanted.
13041 if (this.accelerate) {
13044 this.timer = this.click.defer(this.delay || this.interval, this);
13048 click : function(){
13049 this.fireEvent("click", this);
13050 this.timer = this.click.defer(this.accelerate ?
13051 this.easeOutExpo(this.mousedownTime.getElapsed(),
13055 this.interval, this);
13058 easeOutExpo : function (t, b, c, d) {
13059 return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
13063 handleMouseOut : function(){
13064 clearTimeout(this.timer);
13065 if(this.pressClass){
13066 this.el.removeClass(this.pressClass);
13068 this.el.on("mouseover", this.handleMouseReturn, this);
13072 handleMouseReturn : function(){
13073 this.el.un("mouseover", this.handleMouseReturn, this);
13074 if(this.pressClass){
13075 this.el.addClass(this.pressClass);
13081 handleMouseUp : function(){
13082 clearTimeout(this.timer);
13083 this.el.un("mouseover", this.handleMouseReturn, this);
13084 this.el.un("mouseout", this.handleMouseOut, this);
13085 Ext.getDoc().un("mouseup", this.handleMouseUp, this);
13086 this.el.removeClass(this.pressClass);
13087 this.fireEvent("mouseup", this);
13090 * @class Ext.KeyNav
13091 * <p>Provides a convenient wrapper for normalized keyboard navigation. KeyNav allows you to bind
13092 * navigation keys to function calls that will get called when the keys are pressed, providing an easy
13093 * way to implement custom navigation schemes for any UI component.</p>
13094 * <p>The following are all of the possible keys that can be implemented: enter, left, right, up, down, tab, esc,
13095 * pageUp, pageDown, del, home, end. Usage:</p>
13097 var nav = new Ext.KeyNav("my-element", {
13098 "left" : function(e){
13099 this.moveLeft(e.ctrlKey);
13101 "right" : function(e){
13102 this.moveRight(e.ctrlKey);
13104 "enter" : function(e){
13111 * @param {Mixed} el The element to bind to
13112 * @param {Object} config The config
13114 Ext.KeyNav = function(el, config){
13115 this.el = Ext.get(el);
13116 Ext.apply(this, config);
13117 if(!this.disabled){
13118 this.disabled = true;
13123 Ext.KeyNav.prototype = {
13125 * @cfg {Boolean} disabled
13126 * True to disable this KeyNav instance (defaults to false)
13130 * @cfg {String} defaultEventAction
13131 * The method to call on the {@link Ext.EventObject} after this KeyNav intercepts a key. Valid values are
13132 * {@link Ext.EventObject#stopEvent}, {@link Ext.EventObject#preventDefault} and
13133 * {@link Ext.EventObject#stopPropagation} (defaults to 'stopEvent')
13135 defaultEventAction: "stopEvent",
13137 * @cfg {Boolean} forceKeyDown
13138 * Handle the keydown event instead of keypress (defaults to false). KeyNav automatically does this for IE since
13139 * IE does not propagate special keys on keypress, but setting this to true will force other browsers to also
13140 * handle keydown instead of keypress.
13142 forceKeyDown : false,
13145 relay : function(e){
13146 var k = e.getKey();
13147 var h = this.keyToHandler[k];
13149 if(this.doRelay(e, this[h], h) !== true){
13150 e[this.defaultEventAction]();
13156 doRelay : function(e, h, hname){
13157 return h.call(this.scope || this, e);
13160 // possible handlers
13174 // quick lookup hash
13190 stopKeyUp: function(e) {
13191 var k = e.getKey();
13193 if (k >= 37 && k <= 40) {
13194 // *** bugfix - safari 2.x fires 2 keyup events on cursor keys
13195 // *** (note: this bugfix sacrifices the "keyup" event originating from keyNav elements in Safari 2)
13201 * Enable this KeyNav
13203 enable: function() {
13204 if (this.disabled) {
13205 if (Ext.isSafari2) {
13206 // call stopKeyUp() on "keyup" event
13207 this.el.on('keyup', this.stopKeyUp, this);
13210 this.el.on(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
13211 this.disabled = false;
13216 * Disable this KeyNav
13218 disable: function() {
13219 if (!this.disabled) {
13220 if (Ext.isSafari2) {
13221 // remove "keyup" event handler
13222 this.el.un('keyup', this.stopKeyUp, this);
13225 this.el.un(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
13226 this.disabled = true;
13231 * Convenience function for setting disabled/enabled by boolean.
13232 * @param {Boolean} disabled
13234 setDisabled : function(disabled){
13235 this[disabled ? "disable" : "enable"]();
13239 isKeydown: function(){
13240 return this.forceKeyDown || Ext.EventManager.useKeydown;
13244 * @class Ext.KeyMap
\r
13245 * Handles mapping keys to actions for an element. One key map can be used for multiple actions.
\r
13246 * The constructor accepts the same config object as defined by {@link #addBinding}.
\r
13247 * If you bind a callback function to a KeyMap, anytime the KeyMap handles an expected key
\r
13248 * combination it will call the function with this signature (if the match is a multi-key
\r
13249 * combination the callback will still be called only once): (String key, Ext.EventObject e)
\r
13250 * A KeyMap can also handle a string representation of keys.<br />
\r
13253 // map one key by key code
\r
13254 var map = new Ext.KeyMap("my-element", {
\r
13255 key: 13, // or Ext.EventObject.ENTER
\r
13260 // map multiple keys to one action by string
\r
13261 var map = new Ext.KeyMap("my-element", {
\r
13267 // map multiple keys to multiple actions by strings and array of codes
\r
13268 var map = new Ext.KeyMap("my-element", [
\r
13271 fn: function(){ alert("Return was pressed"); }
\r
13274 fn: function(){ alert('a, b or c was pressed'); }
\r
13279 fn: function(){ alert('Control + shift + tab was pressed.'); }
\r
13283 * <b>Note: A KeyMap starts enabled</b>
\r
13285 * @param {Mixed} el The element to bind to
\r
13286 * @param {Object} config The config (see {@link #addBinding})
\r
13287 * @param {String} eventName (optional) The event to bind to (defaults to "keydown")
\r
13289 Ext.KeyMap = function(el, config, eventName){
\r
13290 this.el = Ext.get(el);
\r
13291 this.eventName = eventName || "keydown";
\r
13292 this.bindings = [];
\r
13294 this.addBinding(config);
\r
13299 Ext.KeyMap.prototype = {
\r
13301 * True to stop the event from bubbling and prevent the default browser action if the
\r
13302 * key was handled by the KeyMap (defaults to false)
\r
13305 stopEvent : false,
\r
13308 * Add a new binding to this KeyMap. The following config object properties are supported:
\r
13310 Property Type Description
\r
13311 ---------- --------------- ----------------------------------------------------------------------
\r
13312 key String/Array A single keycode or an array of keycodes to handle
\r
13313 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
13314 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
13315 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
13316 handler Function The function to call when KeyMap finds the expected key combination
\r
13317 fn Function Alias of handler (for backwards-compatibility)
\r
13318 scope Object The scope of the callback function
\r
13319 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
13324 // Create a KeyMap
\r
13325 var map = new Ext.KeyMap(document, {
\r
13326 key: Ext.EventObject.ENTER,
\r
13331 //Add a new binding to the existing KeyMap later
\r
13339 * @param {Object/Array} config A single KeyMap config or an array of configs
\r
13341 addBinding : function(config){
\r
13342 if(Ext.isArray(config)){
\r
13343 Ext.each(config, function(c){
\r
13344 this.addBinding(c);
\r
13348 var keyCode = config.key,
\r
13349 fn = config.fn || config.handler,
\r
13350 scope = config.scope;
\r
13352 if (config.stopEvent) {
\r
13353 this.stopEvent = config.stopEvent;
\r
13356 if(typeof keyCode == "string"){
\r
13358 var keyString = keyCode.toUpperCase();
\r
13359 for(var j = 0, len = keyString.length; j < len; j++){
\r
13360 ks.push(keyString.charCodeAt(j));
\r
13364 var keyArray = Ext.isArray(keyCode);
\r
13366 var handler = function(e){
\r
13367 if(this.checkModifiers(config, e)){
\r
13368 var k = e.getKey();
\r
13370 for(var i = 0, len = keyCode.length; i < len; i++){
\r
13371 if(keyCode[i] == k){
\r
13372 if(this.stopEvent){
\r
13375 fn.call(scope || window, k, e);
\r
13380 if(k == keyCode){
\r
13381 if(this.stopEvent){
\r
13384 fn.call(scope || window, k, e);
\r
13389 this.bindings.push(handler);
\r
13393 checkModifiers: function(config, e){
\r
13394 var val, key, keys = ['shift', 'ctrl', 'alt'];
\r
13395 for (var i = 0, len = keys.length; i < len; ++i){
\r
13397 val = config[key];
\r
13398 if(!(val === undefined || (val === e[key + 'Key']))){
\r
13406 * Shorthand for adding a single key listener
\r
13407 * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the
\r
13408 * following options:
\r
13409 * {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
\r
13410 * @param {Function} fn The function to call
\r
13411 * @param {Object} scope (optional) The scope of the function
\r
13413 on : function(key, fn, scope){
\r
13414 var keyCode, shift, ctrl, alt;
\r
13415 if(typeof key == "object" && !Ext.isArray(key)){
\r
13416 keyCode = key.key;
\r
13417 shift = key.shift;
\r
13423 this.addBinding({
\r
13434 handleKeyDown : function(e){
\r
13435 if(this.enabled){ //just in case
\r
13436 var b = this.bindings;
\r
13437 for(var i = 0, len = b.length; i < len; i++){
\r
13438 b[i].call(this, e);
\r
13444 * Returns true if this KeyMap is enabled
\r
13445 * @return {Boolean}
\r
13447 isEnabled : function(){
\r
13448 return this.enabled;
\r
13452 * Enables this KeyMap
\r
13454 enable: function(){
\r
13455 if(!this.enabled){
\r
13456 this.el.on(this.eventName, this.handleKeyDown, this);
\r
13457 this.enabled = true;
\r
13462 * Disable this KeyMap
\r
13464 disable: function(){
\r
13465 if(this.enabled){
\r
13466 this.el.removeListener(this.eventName, this.handleKeyDown, this);
\r
13467 this.enabled = false;
\r
13472 * Convenience function for setting disabled/enabled by boolean.
\r
13473 * @param {Boolean} disabled
\r
13475 setDisabled : function(disabled){
\r
13476 this[disabled ? "disable" : "enable"]();
\r
13479 * @class Ext.util.TextMetrics
13480 * Provides precise pixel measurements for blocks of text so that you can determine exactly how high and
13481 * wide, in pixels, a given block of text will be. Note that when measuring text, it should be plain text and
13482 * should not contain any HTML, otherwise it may not be measured correctly.
13485 Ext.util.TextMetrics = function(){
13489 * Measures the size of the specified text
13490 * @param {String/HTMLElement} el The element, dom node or id from which to copy existing CSS styles
13491 * that can affect the size of the rendered text
13492 * @param {String} text The text to measure
13493 * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
13494 * in order to accurately measure the text height
13495 * @return {Object} An object containing the text's size {width: (width), height: (height)}
13497 measure : function(el, text, fixedWidth){
13499 shared = Ext.util.TextMetrics.Instance(el, fixedWidth);
13502 shared.setFixedWidth(fixedWidth || 'auto');
13503 return shared.getSize(text);
13507 * Return a unique TextMetrics instance that can be bound directly to an element and reused. This reduces
13508 * the overhead of multiple calls to initialize the style properties on each measurement.
13509 * @param {String/HTMLElement} el The element, dom node or id that the instance will be bound to
13510 * @param {Number} fixedWidth (optional) If the text will be multiline, you have to set a fixed width
13511 * in order to accurately measure the text height
13512 * @return {Ext.util.TextMetrics.Instance} instance The new instance
13514 createInstance : function(el, fixedWidth){
13515 return Ext.util.TextMetrics.Instance(el, fixedWidth);
13520 Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
13521 var ml = new Ext.Element(document.createElement('div'));
13522 document.body.appendChild(ml.dom);
13523 ml.position('absolute');
13524 ml.setLeftTop(-1000, -1000);
13528 ml.setWidth(fixedWidth);
13533 * Returns the size of the specified text based on the internal element's style and width properties
13534 * @param {String} text The text to measure
13535 * @return {Object} An object containing the text's size {width: (width), height: (height)}
13537 getSize : function(text){
13539 var s = ml.getSize();
13545 * Binds this TextMetrics instance to an element from which to copy existing CSS styles
13546 * that can affect the size of the rendered text
13547 * @param {String/HTMLElement} el The element, dom node or id
13549 bind : function(el){
13551 Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
13556 * Sets a fixed width on the internal measurement element. If the text will be multiline, you have
13557 * to set a fixed width in order to accurately measure the text height.
13558 * @param {Number} width The width to set on the element
13560 setFixedWidth : function(width){
13561 ml.setWidth(width);
13565 * Returns the measured width of the specified text
13566 * @param {String} text The text to measure
13567 * @return {Number} width The width in pixels
13569 getWidth : function(text){
13570 ml.dom.style.width = 'auto';
13571 return this.getSize(text).width;
13575 * Returns the measured height of the specified text. For multiline text, be sure to call
13576 * {@link #setFixedWidth} if necessary.
13577 * @param {String} text The text to measure
13578 * @return {Number} height The height in pixels
13580 getHeight : function(text){
13581 return this.getSize(text).height;
13585 instance.bind(bindTo);
13590 Ext.Element.addMethods({
13592 * Returns the width in pixels of the passed text, or the width of the text in this Element.
13593 * @param {String} text The text to measure. Defaults to the innerHTML of the element.
13594 * @param {Number} min (Optional) The minumum value to return.
13595 * @param {Number} max (Optional) The maximum value to return.
13596 * @return {Number} The text width in pixels.
13597 * @member Ext.Element getTextWidth
13599 getTextWidth : function(text, min, max){
13600 return (Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width).constrain(min || 0, max || 1000000);
13604 * @class Ext.util.Cookies
\r
13605 * Utility class for managing and interacting with cookies.
\r
13608 Ext.util.Cookies = {
\r
13610 * Create a cookie with the specified name and value. Additional settings
\r
13611 * for the cookie may be optionally specified (for example: expiration,
\r
13612 * access restriction, SSL).
\r
13613 * @param {String} name The name of the cookie to set.
\r
13614 * @param {Mixed} value The value to set for the cookie.
\r
13615 * @param {Object} expires (Optional) Specify an expiration date the
\r
13616 * cookie is to persist until. Note that the specified Date object will
\r
13617 * be converted to Greenwich Mean Time (GMT).
\r
13618 * @param {String} path (Optional) Setting a path on the cookie restricts
\r
13619 * access to pages that match that path. Defaults to all pages (<tt>'/'</tt>).
\r
13620 * @param {String} domain (Optional) Setting a domain restricts access to
\r
13621 * pages on a given domain (typically used to allow cookie access across
\r
13622 * subdomains). For example, "extjs.com" will create a cookie that can be
\r
13623 * accessed from any subdomain of extjs.com, including www.extjs.com,
\r
13624 * support.extjs.com, etc.
\r
13625 * @param {Boolean} secure (Optional) Specify true to indicate that the cookie
\r
13626 * should only be accessible via SSL on a page using the HTTPS protocol.
\r
13627 * Defaults to <tt>false</tt>. Note that this will only work if the page
\r
13628 * calling this code uses the HTTPS protocol, otherwise the cookie will be
\r
13629 * created with default options.
\r
13631 set : function(name, value){
\r
13632 var argv = arguments;
\r
13633 var argc = arguments.length;
\r
13634 var expires = (argc > 2) ? argv[2] : null;
\r
13635 var path = (argc > 3) ? argv[3] : '/';
\r
13636 var domain = (argc > 4) ? argv[4] : null;
\r
13637 var secure = (argc > 5) ? argv[5] : false;
\r
13638 document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
\r
13642 * Retrieves cookies that are accessible by the current page. If a cookie
\r
13643 * does not exist, <code>get()</code> returns <tt>null</tt>. The following
\r
13644 * example retrieves the cookie called "valid" and stores the String value
\r
13645 * in the variable <tt>validStatus</tt>.
\r
13647 * var validStatus = Ext.util.Cookies.get("valid");
\r
13649 * @param {String} name The name of the cookie to get
\r
13650 * @return {Mixed} Returns the cookie value for the specified name;
\r
13651 * null if the cookie name does not exist.
\r
13653 get : function(name){
\r
13654 var arg = name + "=";
\r
13655 var alen = arg.length;
\r
13656 var clen = document.cookie.length;
\r
13661 if(document.cookie.substring(i, j) == arg){
\r
13662 return Ext.util.Cookies.getCookieVal(j);
\r
13664 i = document.cookie.indexOf(" ", i) + 1;
\r
13673 * Removes a cookie with the provided name from the browser
\r
13674 * if found by setting its expiration date to sometime in the past.
\r
13675 * @param {String} name The name of the cookie to remove
\r
13677 clear : function(name){
\r
13678 if(Ext.util.Cookies.get(name)){
\r
13679 document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
\r
13685 getCookieVal : function(offset){
\r
13686 var endstr = document.cookie.indexOf(";", offset);
\r
13687 if(endstr == -1){
\r
13688 endstr = document.cookie.length;
\r
13690 return unescape(document.cookie.substring(offset, endstr));
\r
13693 * Framework-wide error-handler. Developers can override this method to provide
13694 * custom exception-handling. Framework errors will often extend from the base
13696 * @param {Object/Error} e The thrown exception object.
13698 Ext.handleError = function(e) {
13705 * <p>A base error class. Future implementations are intended to provide more
13706 * robust error handling throughout the framework (<b>in the debug build only</b>)
13707 * to check for common errors and problems. The messages issued by this class
13708 * will aid error checking. Error checks will be automatically removed in the
13709 * production build so that performance is not negatively impacted.</p>
13710 * <p>Some sample messages currently implemented:</p><pre>
13711 "DataProxy attempted to execute an API-action but found an undefined
13712 url / function. Please review your Proxy url/api-configuration."
13714 "Could not locate your "root" property in your server response.
13715 Please review your JsonReader config to ensure the config-property
13716 "root" matches the property your server-response. See the JsonReader
13717 docs for additional assistance."
13719 * <p>An example of the code used for generating error messages:</p><pre><code>
13728 function generateError(data) {
13729 throw new Ext.Error('foo-error', data);
13732 * @param {String} message
13734 Ext.Error = function(message) {
13735 // Try to read the message from Ext.Error.lang
13736 this.message = (this.lang[message]) ? this.lang[message] : message;
13738 Ext.Error.prototype = new Error();
13739 Ext.apply(Ext.Error.prototype, {
13740 // protected. Extensions place their error-strings here.
13748 getName : function() {
13755 getMessage : function() {
13756 return this.message;
13762 toJson : function() {
13763 return Ext.encode(this);
13768 * @class Ext.ComponentMgr
13769 * <p>Provides a registry of all Components (instances of {@link Ext.Component} or any subclass
13770 * thereof) on a page so that they can be easily accessed by {@link Ext.Component component}
13771 * {@link Ext.Component#id id} (see {@link #get}, or the convenience method {@link Ext#getCmp Ext.getCmp}).</p>
13772 * <p>This object also provides a registry of available Component <i>classes</i>
13773 * indexed by a mnemonic code known as the Component's {@link Ext.Component#xtype xtype}.
13774 * The <code>{@link Ext.Component#xtype xtype}</code> provides a way to avoid instantiating child Components
13775 * when creating a full, nested config object for a complete Ext page.</p>
13776 * <p>A child Component may be specified simply as a <i>config object</i>
13777 * as long as the correct <code>{@link Ext.Component#xtype xtype}</code> is specified so that if and when the Component
13778 * needs rendering, the correct type can be looked up for lazy instantiation.</p>
13779 * <p>For a list of all available <code>{@link Ext.Component#xtype xtypes}</code>, see {@link Ext.Component}.</p>
13782 Ext.ComponentMgr = function(){
13783 var all = new Ext.util.MixedCollection();
13789 * Registers a component.
13790 * @param {Ext.Component} c The component
13792 register : function(c){
13797 * Unregisters a component.
13798 * @param {Ext.Component} c The component
13800 unregister : function(c){
13805 * Returns a component by {@link Ext.Component#id id}.
13806 * For additional details see {@link Ext.util.MixedCollection#get}.
13807 * @param {String} id The component {@link Ext.Component#id id}
13808 * @return Ext.Component The Component, <code>undefined</code> if not found, or <code>null</code> if a
13811 get : function(id){
13812 return all.get(id);
13816 * Registers a function that will be called when a specified component is added to ComponentMgr
13817 * @param {String} id The component {@link Ext.Component#id id}
13818 * @param {Function} fn The callback function
13819 * @param {Object} scope The scope of the callback
13821 onAvailable : function(id, fn, scope){
13822 all.on("add", function(index, o){
13824 fn.call(scope || o, o);
13825 all.un("add", fn, scope);
13831 * The MixedCollection used internally for the component cache. An example usage may be subscribing to
13832 * events on the MixedCollection to monitor addition or removal. Read-only.
13833 * @type {MixedCollection}
13838 * Checks if a Component type is registered.
13839 * @param {Ext.Component} xtype The mnemonic string by which the Component class may be looked up
13840 * @return {Boolean} Whether the type is registered.
13842 isRegistered : function(xtype){
13843 return types[xtype] !== undefined;
13847 * <p>Registers a new Component constructor, keyed by a new
13848 * {@link Ext.Component#xtype}.</p>
13849 * <p>Use this method (or its alias {@link Ext#reg Ext.reg}) to register new
13850 * subclasses of {@link Ext.Component} so that lazy instantiation may be used when specifying
13851 * child Components.
13852 * see {@link Ext.Container#items}</p>
13853 * @param {String} xtype The mnemonic string by which the Component class may be looked up.
13854 * @param {Constructor} cls The new Component class.
13856 registerType : function(xtype, cls){
13857 types[xtype] = cls;
13862 * Creates a new Component from the specified config object using the
13863 * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate.
13864 * @param {Object} config A configuration object for the Component you wish to create.
13865 * @param {Constructor} defaultType The constructor to provide the default Component type if
13866 * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
13867 * @return {Ext.Component} The newly instantiated Component.
13869 create : function(config, defaultType){
13870 return config.render ? config : new types[config.xtype || defaultType](config);
13874 * <p>Registers a new Plugin constructor, keyed by a new
13875 * {@link Ext.Component#ptype}.</p>
13876 * <p>Use this method (or its alias {@link Ext#preg Ext.preg}) to register new
13877 * plugins for {@link Ext.Component}s so that lazy instantiation may be used when specifying
13879 * @param {String} ptype The mnemonic string by which the Plugin class may be looked up.
13880 * @param {Constructor} cls The new Plugin class.
13882 registerPlugin : function(ptype, cls){
13883 ptypes[ptype] = cls;
13888 * Creates a new Plugin from the specified config object using the
13889 * config object's {@link Ext.component#ptype ptype} to determine the class to instantiate.
13890 * @param {Object} config A configuration object for the Plugin you wish to create.
13891 * @param {Constructor} defaultType The constructor to provide the default Plugin type if
13892 * the config object does not contain a <code>ptype</code>. (Optional if the config contains a <code>ptype</code>).
13893 * @return {Ext.Component} The newly instantiated Plugin.
13895 createPlugin : function(config, defaultType){
13896 return new ptypes[config.ptype || defaultType](config);
13902 * Shorthand for {@link Ext.ComponentMgr#registerType}
13903 * @param {String} xtype The {@link Ext.component#xtype mnemonic string} by which the Component class
13904 * may be looked up.
13905 * @param {Constructor} cls The new Component class.
13909 Ext.reg = Ext.ComponentMgr.registerType; // this will be called a lot internally, shorthand to keep the bytes down
13911 * Shorthand for {@link Ext.ComponentMgr#registerPlugin}
13912 * @param {String} ptype The {@link Ext.component#ptype mnemonic string} by which the Plugin class
13913 * may be looked up.
13914 * @param {Constructor} cls The new Plugin class.
13918 Ext.preg = Ext.ComponentMgr.registerPlugin;
13920 * Shorthand for {@link Ext.ComponentMgr#create}
13921 * Creates a new Component from the specified config object using the
13922 * config object's {@link Ext.component#xtype xtype} to determine the class to instantiate.
13923 * @param {Object} config A configuration object for the Component you wish to create.
13924 * @param {Constructor} defaultType The constructor to provide the default Component type if
13925 * the config object does not contain a <code>xtype</code>. (Optional if the config contains a <code>xtype</code>).
13926 * @return {Ext.Component} The newly instantiated Component.
13930 Ext.create = Ext.ComponentMgr.create;/**
13931 * @class Ext.Component
13932 * @extends Ext.util.Observable
13933 * <p>Base class for all Ext components. All subclasses of Component may participate in the automated
13934 * Ext component lifecycle of creation, rendering and destruction which is provided by the {@link Ext.Container Container} class.
13935 * Components may be added to a Container through the {@link Ext.Container#items items} config option at the time the Container is created,
13936 * or they may be added dynamically via the {@link Ext.Container#add add} method.</p>
13937 * <p>The Component base class has built-in support for basic hide/show and enable/disable behavior.</p>
13938 * <p>All Components are registered with the {@link Ext.ComponentMgr} on construction so that they can be referenced at any time via
13939 * {@link Ext#getCmp}, passing the {@link #id}.</p>
13940 * <p>All user-developed visual widgets that are required to participate in automated lifecycle and size management should subclass Component (or
13941 * {@link Ext.BoxComponent} if managed box model handling is required, ie height and width management).</p>
13942 * <p>See the <a href="http://extjs.com/learn/Tutorial:Creating_new_UI_controls">Creating new UI controls</a> tutorial for details on how
13943 * and to either extend or augment ExtJs base classes to create custom Components.</p>
13944 * <p>Every component has a specific xtype, which is its Ext-specific type name, along with methods for checking the
13945 * xtype like {@link #getXType} and {@link #isXType}. This is the list of all valid xtypes:</p>
13948 ------------- ------------------
13949 box {@link Ext.BoxComponent}
13950 button {@link Ext.Button}
13951 buttongroup {@link Ext.ButtonGroup}
13952 colorpalette {@link Ext.ColorPalette}
13953 component {@link Ext.Component}
13954 container {@link Ext.Container}
13955 cycle {@link Ext.CycleButton}
13956 dataview {@link Ext.DataView}
13957 datepicker {@link Ext.DatePicker}
13958 editor {@link Ext.Editor}
13959 editorgrid {@link Ext.grid.EditorGridPanel}
13960 flash {@link Ext.FlashComponent}
13961 grid {@link Ext.grid.GridPanel}
13962 listview {@link Ext.ListView}
13963 panel {@link Ext.Panel}
13964 progress {@link Ext.ProgressBar}
13965 propertygrid {@link Ext.grid.PropertyGrid}
13966 slider {@link Ext.Slider}
13967 spacer {@link Ext.Spacer}
13968 splitbutton {@link Ext.SplitButton}
13969 tabpanel {@link Ext.TabPanel}
13970 treepanel {@link Ext.tree.TreePanel}
13971 viewport {@link Ext.ViewPort}
13972 window {@link Ext.Window}
13975 ---------------------------------------
13976 paging {@link Ext.PagingToolbar}
13977 toolbar {@link Ext.Toolbar}
13978 tbbutton {@link Ext.Toolbar.Button} (deprecated; use button)
13979 tbfill {@link Ext.Toolbar.Fill}
13980 tbitem {@link Ext.Toolbar.Item}
13981 tbseparator {@link Ext.Toolbar.Separator}
13982 tbspacer {@link Ext.Toolbar.Spacer}
13983 tbsplit {@link Ext.Toolbar.SplitButton} (deprecated; use splitbutton)
13984 tbtext {@link Ext.Toolbar.TextItem}
13987 ---------------------------------------
13988 menu {@link Ext.menu.Menu}
13989 colormenu {@link Ext.menu.ColorMenu}
13990 datemenu {@link Ext.menu.DateMenu}
13991 menubaseitem {@link Ext.menu.BaseItem}
13992 menucheckitem {@link Ext.menu.CheckItem}
13993 menuitem {@link Ext.menu.Item}
13994 menuseparator {@link Ext.menu.Separator}
13995 menutextitem {@link Ext.menu.TextItem}
13998 ---------------------------------------
13999 form {@link Ext.FormPanel}
14000 checkbox {@link Ext.form.Checkbox}
14001 checkboxgroup {@link Ext.form.CheckboxGroup}
14002 combo {@link Ext.form.ComboBox}
14003 datefield {@link Ext.form.DateField}
14004 displayfield {@link Ext.form.DisplayField}
14005 field {@link Ext.form.Field}
14006 fieldset {@link Ext.form.FieldSet}
14007 hidden {@link Ext.form.Hidden}
14008 htmleditor {@link Ext.form.HtmlEditor}
14009 label {@link Ext.form.Label}
14010 numberfield {@link Ext.form.NumberField}
14011 radio {@link Ext.form.Radio}
14012 radiogroup {@link Ext.form.RadioGroup}
14013 textarea {@link Ext.form.TextArea}
14014 textfield {@link Ext.form.TextField}
14015 timefield {@link Ext.form.TimeField}
14016 trigger {@link Ext.form.TriggerField}
14019 ---------------------------------------
14020 chart {@link Ext.chart.Chart}
14021 barchart {@link Ext.chart.BarChart}
14022 cartesianchart {@link Ext.chart.CartesianChart}
14023 columnchart {@link Ext.chart.ColumnChart}
14024 linechart {@link Ext.chart.LineChart}
14025 piechart {@link Ext.chart.PieChart}
14028 ---------------------------------------
14029 arraystore {@link Ext.data.ArrayStore}
14030 directstore {@link Ext.data.DirectStore}
14031 groupingstore {@link Ext.data.GroupingStore}
14032 jsonstore {@link Ext.data.JsonStore}
14033 simplestore {@link Ext.data.SimpleStore} (deprecated; use arraystore)
14034 store {@link Ext.data.Store}
14035 xmlstore {@link Ext.data.XmlStore}
14038 * @param {Ext.Element/String/Object} config The configuration options may be specified as either:
14039 * <div class="mdetail-params"><ul>
14040 * <li><b>an element</b> :
14041 * <p class="sub-desc">it is set as the internal element and its id used as the component id</p></li>
14042 * <li><b>a string</b> :
14043 * <p class="sub-desc">it is assumed to be the id of an existing element and is used as the component id</p></li>
14044 * <li><b>anything else</b> :
14045 * <p class="sub-desc">it is assumed to be a standard config object and is applied to the component</p></li>
14048 Ext.Component = function(config){
14049 config = config || {};
14050 if(config.initialConfig){
14051 if(config.isAction){ // actions
14052 this.baseAction = config;
14054 config = config.initialConfig; // component cloning / action set up
14055 }else if(config.tagName || config.dom || Ext.isString(config)){ // element object
14056 config = {applyTo: config, id: config.id || config};
14060 * This Component's initial configuration specification. Read-only.
14062 * @property initialConfig
14064 this.initialConfig = config;
14066 Ext.apply(this, config);
14070 * Fires after the component is disabled.
14071 * @param {Ext.Component} this
14076 * Fires after the component is enabled.
14077 * @param {Ext.Component} this
14081 * @event beforeshow
14082 * Fires before the component is shown by calling the {@link #show} method.
14083 * Return false from an event handler to stop the show.
14084 * @param {Ext.Component} this
14089 * Fires after the component is shown when calling the {@link #show} method.
14090 * @param {Ext.Component} this
14094 * @event beforehide
14095 * Fires before the component is hidden by calling the {@link #hide} method.
14096 * Return false from an event handler to stop the hide.
14097 * @param {Ext.Component} this
14102 * Fires after the component is hidden.
14103 * Fires after the component is hidden when calling the {@link #hide} method.
14104 * @param {Ext.Component} this
14108 * @event beforerender
14109 * Fires before the component is {@link #rendered}. Return false from an
14110 * event handler to stop the {@link #render}.
14111 * @param {Ext.Component} this
14116 * Fires after the component markup is {@link #rendered}.
14117 * @param {Ext.Component} this
14121 * @event afterrender
14122 * <p>Fires after the component rendering is finished.</p>
14123 * <p>The afterrender event is fired after this Component has been {@link #rendered}, been postprocesed
14124 * by any afterRender method defined for the Component, and, if {@link #stateful}, after state
14125 * has been restored.</p>
14126 * @param {Ext.Component} this
14130 * @event beforedestroy
14131 * Fires before the component is {@link #destroy}ed. Return false from an event handler to stop the {@link #destroy}.
14132 * @param {Ext.Component} this
14137 * Fires after the component is {@link #destroy}ed.
14138 * @param {Ext.Component} this
14142 * @event beforestaterestore
14143 * Fires before the state of the component is restored. Return false from an event handler to stop the restore.
14144 * @param {Ext.Component} this
14145 * @param {Object} state The hash of state values returned from the StateProvider. If this
14146 * event is not vetoed, then the state object is passed to <b><tt>applyState</tt></b>. By default,
14147 * that simply copies property values into this Component. The method maybe overriden to
14148 * provide custom state restoration.
14150 'beforestaterestore',
14152 * @event staterestore
14153 * Fires after the state of the component is restored.
14154 * @param {Ext.Component} this
14155 * @param {Object} state The hash of state values returned from the StateProvider. This is passed
14156 * to <b><tt>applyState</tt></b>. By default, that simply copies property values into this
14157 * Component. The method maybe overriden to provide custom state restoration.
14161 * @event beforestatesave
14162 * Fires before the state of the component is saved to the configured state provider. Return false to stop the save.
14163 * @param {Ext.Component} this
14164 * @param {Object} state The hash of state values. This is determined by calling
14165 * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
14166 * developer to return whetever representation of state is required, by default, Ext.Component
14167 * has a null implementation.
14172 * Fires after the state of the component is saved to the configured state provider.
14173 * @param {Ext.Component} this
14174 * @param {Object} state The hash of state values. This is determined by calling
14175 * <b><tt>getState()</tt></b> on the Component. This method must be provided by the
14176 * developer to return whetever representation of state is required, by default, Ext.Component
14177 * has a null implementation.
14182 Ext.ComponentMgr.register(this);
14183 Ext.Component.superclass.constructor.call(this);
14185 if(this.baseAction){
14186 this.baseAction.addComponent(this);
14189 this.initComponent();
14192 if(Ext.isArray(this.plugins)){
14193 for(var i = 0, len = this.plugins.length; i < len; i++){
14194 this.plugins[i] = this.initPlugin(this.plugins[i]);
14197 this.plugins = this.initPlugin(this.plugins);
14201 if(this.stateful !== false){
14202 this.initState(config);
14206 this.applyToMarkup(this.applyTo);
14207 delete this.applyTo;
14208 }else if(this.renderTo){
14209 this.render(this.renderTo);
14210 delete this.renderTo;
14215 Ext.Component.AUTO_ID = 1000;
14217 Ext.extend(Ext.Component, Ext.util.Observable, {
14218 // Configs below are used for all Components when rendered by FormLayout.
14220 * @cfg {String} fieldLabel <p>The label text to display next to this Component (defaults to '').</p>
14221 * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container which
14222 * has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout manager (e.g.
14223 * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>).</p><br>
14224 * <p>Also see <tt>{@link #hideLabel}</tt> and
14225 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
14226 * Example use:<pre><code>
14227 new Ext.FormPanel({
14229 renderTo: Ext.getBody(),
14231 xtype: 'textfield',
14238 * @cfg {String} labelStyle <p>A CSS style specification string to apply directly to this field's
14239 * label. Defaults to the container's labelStyle value if set (e.g.,
14240 * <tt>{@link Ext.layout.FormLayout#labelStyle}</tt> , or '').</p>
14241 * <br><p><b>Note</b>: see the note for <code>{@link #clearCls}</code>.</p><br>
14242 * <p>Also see <code>{@link #hideLabel}</code> and
14243 * <code>{@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</code></p>
14244 * Example use:<pre><code>
14245 new Ext.FormPanel({
14247 renderTo: Ext.getBody(),
14249 xtype: 'textfield',
14250 fieldLabel: 'Name',
14251 labelStyle: 'font-weight:bold;'
14257 * @cfg {String} labelSeparator <p>The separator to display after the text of each
14258 * <tt>{@link #fieldLabel}</tt>. This property may be configured at various levels.
14259 * The order of precedence is:
14260 * <div class="mdetail-params"><ul>
14261 * <li>field / component level</li>
14262 * <li>container level</li>
14263 * <li>{@link Ext.layout.FormLayout#labelSeparator layout level} (defaults to colon <tt>':'</tt>)</li>
14265 * To display no separator for this field's label specify empty string ''.</p>
14266 * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
14267 * <p>Also see <tt>{@link #hideLabel}</tt> and
14268 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}.</p>
14269 * Example use:<pre><code>
14270 new Ext.FormPanel({
14272 renderTo: Ext.getBody(),
14274 labelSeparator: '~' // layout config has lowest priority (defaults to ':')
14276 {@link Ext.layout.FormLayout#labelSeparator labelSeparator}: '>>', // config at container level
14278 xtype: 'textfield',
14279 fieldLabel: 'Field 1',
14280 labelSeparator: '...' // field/component level config supersedes others
14282 xtype: 'textfield',
14283 fieldLabel: 'Field 2' // labelSeparator will be '='
14289 * @cfg {Boolean} hideLabel <p><tt>true</tt> to completely hide the label element
14290 * ({@link #fieldLabel label} and {@link #labelSeparator separator}). Defaults to <tt>false</tt>.
14291 * By default, even if you do not specify a <tt>{@link #fieldLabel}</tt> the space will still be
14292 * reserved so that the field will line up with other fields that do have labels.
14293 * Setting this to <tt>true</tt> will cause the field to not reserve that space.</p>
14294 * <br><p><b>Note</b>: see the note for <tt>{@link #clearCls}</tt>.</p><br>
14295 * Example use:<pre><code>
14296 new Ext.FormPanel({
14298 renderTo: Ext.getBody(),
14307 * @cfg {String} clearCls <p>The CSS class used to to apply to the special clearing div rendered
14308 * directly after each form field wrapper to provide field clearing (defaults to
14309 * <tt>'x-form-clear-left'</tt>).</p>
14310 * <br><p><b>Note</b>: this config is only used when this Component is rendered by a Container
14311 * which has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout
14312 * manager (e.g. {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) and either a
14313 * <tt>{@link #fieldLabel}</tt> is specified or <tt>isFormField=true</tt> is specified.</p><br>
14314 * <p>See {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} also.</p>
14317 * @cfg {String} itemCls
14318 * <p><b>Note</b>: this config is only used when this Component is rendered by a Container which
14319 * has been configured to use the <b>{@link Ext.layout.FormLayout FormLayout}</b> layout manager (e.g.
14320 * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>).</p><br>
14321 * <p>An additional CSS class to apply to the div wrapping the form item
14322 * element of this field. If supplied, <tt>itemCls</tt> at the <b>field</b> level will override
14323 * the default <tt>itemCls</tt> supplied at the <b>container</b> level. The value specified for
14324 * <tt>itemCls</tt> will be added to the default class (<tt>'x-form-item'</tt>).</p>
14325 * <p>Since it is applied to the item wrapper (see
14326 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl}), it allows
14327 * you to write standard CSS rules that can apply to the field, the label (if specified), or
14328 * any other element within the markup for the field.</p>
14329 * <br><p><b>Note</b>: see the note for <tt>{@link #fieldLabel}</tt>.</p><br>
14330 * Example use:<pre><code>
14331 // Apply a style to the field's label:
14333 .required .x-form-item-label {font-weight:bold;color:red;}
14336 new Ext.FormPanel({
14338 renderTo: Ext.getBody(),
14340 xtype: 'textfield',
14341 fieldLabel: 'Name',
14342 itemCls: 'required' //this label will be styled
14344 xtype: 'textfield',
14345 fieldLabel: 'Favorite Color'
14351 // Configs below are used for all Components when rendered by AnchorLayout.
14353 * @cfg {String} anchor <p><b>Note</b>: this config is only used when this Component is rendered
14354 * by a Container which has been configured to use an <b>{@link Ext.layout.AnchorLayout AnchorLayout}</b>
14355 * based layout manager, for example:<div class="mdetail-params"><ul>
14356 * <li>{@link Ext.form.FormPanel}</li>
14357 * <li>specifying <code>layout: 'anchor' // or 'form', or 'absolute'</code></li>
14359 * <p>See {@link Ext.layout.AnchorLayout}.{@link Ext.layout.AnchorLayout#anchor anchor} also.</p>
14364 * <p>The <b>unique</b> id of this component (defaults to an {@link #getId auto-assigned id}).
14365 * You should assign an id if you need to be able to access the component later and you do
14366 * not have an object reference available (e.g., using {@link Ext}.{@link Ext#getCmp getCmp}).</p>
14367 * <p>Note that this id will also be used as the element id for the containing HTML element
14368 * that is rendered to the page for this component. This allows you to write id-based CSS
14369 * rules to style the specific instance of this component uniquely, and also to select
14370 * sub-elements using this component's id as the parent.</p>
14371 * <p><b>Note</b>: to avoid complications imposed by a unique <tt>id</tt> also see
14372 * <code>{@link #itemId}</code> and <code>{@link #ref}</code>.</p>
14373 * <p><b>Note</b>: to access the container of an item see <code>{@link #ownerCt}</code>.</p>
14376 * @cfg {String} itemId
14377 * <p>An <tt>itemId</tt> can be used as an alternative way to get a reference to a component
14378 * when no object reference is available. Instead of using an <code>{@link #id}</code> with
14379 * {@link Ext}.{@link Ext#getCmp getCmp}, use <code>itemId</code> with
14380 * {@link Ext.Container}.{@link Ext.Container#getComponent getComponent} which will retrieve
14381 * <code>itemId</code>'s or <tt>{@link #id}</tt>'s. Since <code>itemId</code>'s are an index to the
14382 * container's internal MixedCollection, the <code>itemId</code> is scoped locally to the container --
14383 * avoiding potential conflicts with {@link Ext.ComponentMgr} which requires a <b>unique</b>
14384 * <code>{@link #id}</code>.</p>
14386 var c = new Ext.Panel({ //
14387 {@link Ext.BoxComponent#height height}: 300,
14388 {@link #renderTo}: document.body,
14389 {@link Ext.Container#layout layout}: 'auto',
14390 {@link Ext.Container#items items}: [
14393 {@link Ext.Panel#title title}: 'Panel 1',
14394 {@link Ext.BoxComponent#height height}: 150
14398 {@link Ext.Panel#title title}: 'Panel 2',
14399 {@link Ext.BoxComponent#height height}: 150
14403 p1 = c.{@link Ext.Container#getComponent getComponent}('p1'); // not the same as {@link Ext#getCmp Ext.getCmp()}
14404 p2 = p1.{@link #ownerCt}.{@link Ext.Container#getComponent getComponent}('p2'); // reference via a sibling
14406 * <p>Also see <tt>{@link #id}</tt> and <code>{@link #ref}</code>.</p>
14407 * <p><b>Note</b>: to access the container of an item see <tt>{@link #ownerCt}</tt>.</p>
14410 * @cfg {String} xtype
14411 * The registered <tt>xtype</tt> to create. This config option is not used when passing
14412 * a config object into a constructor. This config option is used only when
14413 * lazy instantiation is being used, and a child item of a Container is being
14414 * specified not as a fully instantiated Component, but as a <i>Component config
14415 * object</i>. The <tt>xtype</tt> will be looked up at render time up to determine what
14416 * type of child Component to create.<br><br>
14417 * The predefined xtypes are listed {@link Ext.Component here}.
14419 * If you subclass Components to create your own Components, you may register
14420 * them using {@link Ext.ComponentMgr#registerType} in order to be able to
14421 * take advantage of lazy instantiation and rendering.
14424 * @cfg {String} ptype
14425 * The registered <tt>ptype</tt> to create. This config option is not used when passing
14426 * a config object into a constructor. This config option is used only when
14427 * lazy instantiation is being used, and a Plugin is being
14428 * specified not as a fully instantiated Component, but as a <i>Component config
14429 * object</i>. The <tt>ptype</tt> will be looked up at render time up to determine what
14430 * type of Plugin to create.<br><br>
14431 * If you create your own Plugins, you may register them using
14432 * {@link Ext.ComponentMgr#registerPlugin} in order to be able to
14433 * take advantage of lazy instantiation and rendering.
14436 * @cfg {String} cls
14437 * An optional extra CSS class that will be added to this component's Element (defaults to ''). This can be
14438 * useful for adding customized styles to the component or any of its children using standard CSS rules.
14441 * @cfg {String} overCls
14442 * An optional extra CSS class that will be added to this component's Element when the mouse moves
14443 * over the Element, and removed when the mouse moves out. (defaults to ''). This can be
14444 * useful for adding customized 'active' or 'hover' styles to the component or any of its children using standard CSS rules.
14447 * @cfg {String} style
14448 * A custom style specification to be applied to this component's Element. Should be a valid argument to
14449 * {@link Ext.Element#applyStyles}.
14452 title: 'Some Title',
14453 renderTo: Ext.getBody(),
14454 width: 400, height: 300,
14460 marginBottom: '10px'
14467 marginBottom: '10px'
14475 * @cfg {String} ctCls
14476 * <p>An optional extra CSS class that will be added to this component's container. This can be useful for
14477 * adding customized styles to the container or any of its children using standard CSS rules. See
14478 * {@link Ext.layout.ContainerLayout}.{@link Ext.layout.ContainerLayout#extraCls extraCls} also.</p>
14479 * <p><b>Note</b>: <tt>ctCls</tt> defaults to <tt>''</tt> except for the following class
14480 * which assigns a value by default:
14481 * <div class="mdetail-params"><ul>
14482 * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-layout-ct'</tt></li>
14484 * To configure the above Class with an extra CSS class append to the default. For example,
14485 * for BoxLayout (Hbox and Vbox):<pre><code>
14486 * ctCls: 'x-box-layout-ct custom-class'
14491 * @cfg {Boolean} disabled
14492 * Render this component disabled (default is false).
14496 * @cfg {Boolean} hidden
14497 * Render this component hidden (default is false). If <tt>true</tt>, the
14498 * {@link #hide} method will be called internally.
14502 * @cfg {Object/Array} plugins
14503 * An object or array of objects that will provide custom functionality for this component. The only
14504 * requirement for a valid plugin is that it contain an init method that accepts a reference of type Ext.Component.
14505 * When a component is created, if any plugins are available, the component will call the init method on each
14506 * plugin, passing a reference to itself. Each plugin can then call methods or respond to events on the
14507 * component as needed to provide its functionality.
14510 * @cfg {Mixed} applyTo
14511 * <p>Specify the id of the element, a DOM element or an existing Element corresponding to a DIV
14512 * that is already present in the document that specifies some structural markup for this
14513 * component.</p><div><ul>
14514 * <li><b>Description</b> : <ul>
14515 * <div class="sub-desc">When <tt>applyTo</tt> is used, constituent parts of the component can also be specified
14516 * by id or CSS class name within the main element, and the component being created may attempt
14517 * to create its subcomponents from that markup if applicable.</div>
14519 * <li><b>Notes</b> : <ul>
14520 * <div class="sub-desc">When using this config, a call to render() is not required.</div>
14521 * <div class="sub-desc">If applyTo is specified, any value passed for {@link #renderTo} will be ignored and the target
14522 * element's parent node will automatically be used as the component's container.</div>
14527 * @cfg {Mixed} renderTo
14528 * <p>Specify the id of the element, a DOM element or an existing Element that this component
14529 * will be rendered into.</p><div><ul>
14530 * <li><b>Notes</b> : <ul>
14531 * <div class="sub-desc">Do <u>not</u> use this option if the Component is to be a child item of
14532 * a {@link Ext.Container Container}. It is the responsibility of the
14533 * {@link Ext.Container Container}'s {@link Ext.Container#layout layout manager}
14534 * to render and manage its child items.</div>
14535 * <div class="sub-desc">When using this config, a call to render() is not required.</div>
14538 * <p>See <tt>{@link #render}</tt> also.</p>
14541 * @cfg {Boolean} stateful
14542 * <p>A flag which causes the Component to attempt to restore the state of
14543 * internal properties from a saved state on startup. The component must have
14544 * either a <code>{@link #stateId}</code> or <code>{@link #id}</code> assigned
14545 * for state to be managed. Auto-generated ids are not guaranteed to be stable
14546 * across page loads and cannot be relied upon to save and restore the same
14547 * state for a component.<p>
14548 * <p>For state saving to work, the state manager's provider must have been
14549 * set to an implementation of {@link Ext.state.Provider} which overrides the
14550 * {@link Ext.state.Provider#set set} and {@link Ext.state.Provider#get get}
14551 * methods to save and recall name/value pairs. A built-in implementation,
14552 * {@link Ext.state.CookieProvider} is available.</p>
14553 * <p>To set the state provider for the current page:</p>
14555 Ext.state.Manager.setProvider(new Ext.state.CookieProvider({
14556 expires: new Date(new Date().getTime()+(1000*60*60*24*7)), //7 days from now
14559 * <p>A stateful Component attempts to save state when one of the events
14560 * listed in the <code>{@link #stateEvents}</code> configuration fires.</p>
14561 * <p>To save state, a stateful Component first serializes its state by
14562 * calling <b><code>getState</code></b>. By default, this function does
14563 * nothing. The developer must provide an implementation which returns an
14564 * object hash which represents the Component's restorable state.</p>
14565 * <p>The value yielded by getState is passed to {@link Ext.state.Manager#set}
14566 * which uses the configured {@link Ext.state.Provider} to save the object
14567 * keyed by the Component's <code>{@link stateId}</code>, or, if that is not
14568 * specified, its <code>{@link #id}</code>.</p>
14569 * <p>During construction, a stateful Component attempts to <i>restore</i>
14570 * its state by calling {@link Ext.state.Manager#get} passing the
14571 * <code>{@link #stateId}</code>, or, if that is not specified, the
14572 * <code>{@link #id}</code>.</p>
14573 * <p>The resulting object is passed to <b><code>applyState</code></b>.
14574 * The default implementation of <code>applyState</code> simply copies
14575 * properties into the object, but a developer may override this to support
14576 * more behaviour.</p>
14577 * <p>You can perform extra processing on state save and restore by attaching
14578 * handlers to the {@link #beforestaterestore}, {@link #staterestore},
14579 * {@link #beforestatesave} and {@link #statesave} events.</p>
14582 * @cfg {String} stateId
14583 * The unique id for this component to use for state management purposes
14584 * (defaults to the component id if one was set, otherwise null if the
14585 * component is using a generated id).
14586 * <p>See <code>{@link #stateful}</code> for an explanation of saving and
14587 * restoring Component state.</p>
14590 * @cfg {Array} stateEvents
14591 * <p>An array of events that, when fired, should trigger this component to
14592 * save its state (defaults to none). <code>stateEvents</code> may be any type
14593 * of event supported by this component, including browser or custom events
14594 * (e.g., <tt>['click', 'customerchange']</tt>).</p>
14595 * <p>See <code>{@link #stateful}</code> for an explanation of saving and
14596 * restoring Component state.</p>
14599 * @cfg {Mixed} autoEl
14600 * <p>A tag name or {@link Ext.DomHelper DomHelper} spec used to create the {@link #getEl Element} which will
14601 * encapsulate this Component.</p>
14602 * <p>You do not normally need to specify this. For the base classes {@link Ext.Component}, {@link Ext.BoxComponent},
14603 * and {@link Ext.Container}, this defaults to <b><tt>'div'</tt></b>. The more complex Ext classes use a more complex
14604 * DOM structure created by their own onRender methods.</p>
14605 * <p>This is intended to allow the developer to create application-specific utility Components encapsulated by
14606 * different DOM elements. Example usage:</p><pre><code>
14611 src: 'http://www.example.com/example.jpg'
14617 html: 'autoEl is cool!'
14620 xtype: 'container',
14622 cls: 'ux-unordered-list',
14626 html: 'First list item'
14634 * @cfg {String} disabledClass
14635 * CSS class added to the component when it is disabled (defaults to 'x-item-disabled').
14637 disabledClass : 'x-item-disabled',
14639 * @cfg {Boolean} allowDomMove
14640 * Whether the component can move the Dom node when rendering (defaults to true).
14642 allowDomMove : true,
14644 * @cfg {Boolean} autoShow
14645 * True if the component should check for hidden classes (e.g. 'x-hidden' or 'x-hide-display') and remove
14646 * them on render (defaults to false).
14650 * @cfg {String} hideMode
14651 * <p>How this component should be hidden. Supported values are <tt>'visibility'</tt>
14652 * (css visibility), <tt>'offsets'</tt> (negative offset position) and <tt>'display'</tt>
14653 * (css display).</p>
14654 * <br><p><b>Note</b>: the default of <tt>'display'</tt> is generally preferred
14655 * since items are automatically laid out when they are first shown (no sizing
14656 * is done while hidden).</p>
14658 hideMode : 'display',
14660 * @cfg {Boolean} hideParent
14661 * True to hide and show the component's container when hide/show is called on the component, false to hide
14662 * and show the component itself (defaults to false). For example, this can be used as a shortcut for a hide
14663 * button on a window by setting hide:true on the button when adding it to its parent container.
14665 hideParent : false,
14667 * <p>The {@link Ext.Element} which encapsulates this Component. Read-only.</p>
14668 * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
14669 * that may be overridden using the <code>{@link #autoEl}</code> config.</p>
14670 * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
14671 * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
14672 * for this Component's own Observable events), see the {@link Ext.util.Observable#listeners listeners}
14673 * config for a suggestion, or use a render listener directly:</p><pre><code>
14675 title: 'The Clickable Panel',
14677 render: function(p) {
14678 // Append the Panel to the click handler's argument list.
14679 p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
14681 single: true // Remove the listener after first invocation
14685 * <p>See also <tt>{@link #getEl getEl}</p>
14686 * @type Ext.Element
14690 * This Component's owner {@link Ext.Container Container} (defaults to undefined, and is set automatically when
14691 * this Component is added to a Container). Read-only.
14692 * <p><b>Note</b>: to access items within the Container see <tt>{@link #itemId}</tt>.</p>
14693 * @type Ext.Container
14694 * @property ownerCt
14697 * True if this component is hidden. Read-only.
14702 * True if this component is disabled. Read-only.
14704 * @property disabled
14707 * True if this component has been rendered. Read-only.
14709 * @property rendered
14714 ctype : 'Ext.Component',
14720 getActionEl : function(){
14721 return this[this.actionMode];
14724 initPlugin : function(p){
14725 if(p.ptype && !Ext.isFunction(p.init)){
14726 p = Ext.ComponentMgr.createPlugin(p);
14727 }else if(Ext.isString(p)){
14728 p = Ext.ComponentMgr.createPlugin({
14737 * Function to be implemented by Component subclasses to be part of standard component initialization flow (it is empty by default).
14739 // Traditional constructor:
14740 Ext.Foo = function(config){
14741 // call superclass constructor:
14742 Ext.Foo.superclass.constructor.call(this, config);
14748 Ext.extend(Ext.Foo, Ext.Bar, {
14752 // initComponent replaces the constructor:
14753 Ext.Foo = Ext.extend(Ext.Bar, {
14754 initComponent : function(){
14755 // call superclass initComponent
14756 Ext.Container.superclass.initComponent.call(this);
14765 initComponent : Ext.emptyFn,
14768 * <p>Render this Component into the passed HTML element.</p>
14769 * <p><b>If you are using a {@link Ext.Container Container} object to house this Component, then
14770 * do not use the render method.</b></p>
14771 * <p>A Container's child Components are rendered by that Container's
14772 * {@link Ext.Container#layout layout} manager when the Container is first rendered.</p>
14773 * <p>Certain layout managers allow dynamic addition of child components. Those that do
14774 * include {@link Ext.layout.CardLayout}, {@link Ext.layout.AnchorLayout},
14775 * {@link Ext.layout.FormLayout}, {@link Ext.layout.TableLayout}.</p>
14776 * <p>If the Container is already rendered when a new child Component is added, you may need to call
14777 * the Container's {@link Ext.Container#doLayout doLayout} to refresh the view which causes any
14778 * unrendered child Components to be rendered. This is required so that you can add multiple
14779 * child components if needed while only refreshing the layout once.</p>
14780 * <p>When creating complex UIs, it is important to remember that sizing and positioning
14781 * of child items is the responsibility of the Container's {@link Ext.Container#layout layout} manager.
14782 * If you expect child items to be sized in response to user interactions, you must
14783 * configure the Container with a layout manager which creates and manages the type of layout you
14784 * have in mind.</p>
14785 * <p><b>Omitting the Container's {@link Ext.Container#layout layout} config means that a basic
14786 * layout manager is used which does nothing but render child components sequentially into the
14787 * Container. No sizing or positioning will be performed in this situation.</b></p>
14788 * @param {Element/HTMLElement/String} container (optional) The element this Component should be
14789 * rendered into. If it is being created from existing markup, this should be omitted.
14790 * @param {String/Number} position (optional) The element ID or DOM node index within the container <b>before</b>
14791 * which this component will be inserted (defaults to appending to the end of the container)
14793 render : function(container, position){
14794 if(!this.rendered && this.fireEvent('beforerender', this) !== false){
14795 if(!container && this.el){
14796 this.el = Ext.get(this.el);
14797 container = this.el.dom.parentNode;
14798 this.allowDomMove = false;
14800 this.container = Ext.get(container);
14802 this.container.addClass(this.ctCls);
14804 this.rendered = true;
14805 if(position !== undefined){
14806 if(Ext.isNumber(position)){
14807 position = this.container.dom.childNodes[position];
14809 position = Ext.getDom(position);
14812 this.onRender(this.container, position || null);
14814 this.el.removeClass(['x-hidden','x-hide-' + this.hideMode]);
14817 this.el.addClass(this.cls);
14821 this.el.applyStyles(this.style);
14825 this.el.addClassOnOver(this.overCls);
14827 this.fireEvent('render', this);
14828 this.afterRender(this.container);
14830 // call this so we don't fire initial hide events.
14834 // pass silent so the event doesn't fire the first time.
14835 this.disable(true);
14838 if(this.stateful !== false){
14839 this.initStateEvents();
14842 this.fireEvent('afterrender', this);
14847 initRef : function(){
14849 * @cfg {String} ref
14850 * <p>A path specification, relative to the Component's {@link #ownerCt} specifying into which
14851 * ancestor Container to place a named reference to this Component.</p>
14852 * <p>The ancestor axis can be traversed by using '/' characters in the path.
14853 * For example, to put a reference to a Toolbar Button into <i>the Panel which owns the Toolbar</i>:</p><pre><code>
14854 var myGrid = new Ext.grid.EditorGridPanel({
14855 title: 'My EditorGridPanel',
14857 colModel: myColModel,
14860 handler: saveChanges,
14862 ref: '../saveButton'
14865 afteredit: function() {
14866 // The button reference is in the GridPanel
14867 myGrid.saveButton.enable();
14872 * <p>In the code above, if the ref had been <code>'saveButton'</code> the reference would
14873 * have been placed into the Toolbar. Each '/' in the ref moves up one level from the
14874 * Component's {@link #ownerCt}.</p>
14877 var levels = this.ref.split('/');
14878 var last = levels.length, i = 0;
14886 t[levels[--i]] = this;
14891 initState : function(config){
14892 if(Ext.state.Manager){
14893 var id = this.getStateId();
14895 var state = Ext.state.Manager.get(id);
14897 if(this.fireEvent('beforestaterestore', this, state) !== false){
14898 this.applyState(state);
14899 this.fireEvent('staterestore', this, state);
14907 getStateId : function(){
14908 return this.stateId || ((this.id.indexOf('ext-comp-') == 0 || this.id.indexOf('ext-gen') == 0) ? null : this.id);
14912 initStateEvents : function(){
14913 if(this.stateEvents){
14914 for(var i = 0, e; e = this.stateEvents[i]; i++){
14915 this.on(e, this.saveState, this, {delay:100});
14921 applyState : function(state){
14923 Ext.apply(this, state);
14928 getState : function(){
14933 saveState : function(){
14934 if(Ext.state.Manager && this.stateful !== false){
14935 var id = this.getStateId();
14937 var state = this.getState();
14938 if(this.fireEvent('beforestatesave', this, state) !== false){
14939 Ext.state.Manager.set(id, state);
14940 this.fireEvent('statesave', this, state);
14947 * Apply this component to existing markup that is valid. With this function, no call to render() is required.
14948 * @param {String/HTMLElement} el
14950 applyToMarkup : function(el){
14951 this.allowDomMove = false;
14952 this.el = Ext.get(el);
14953 this.render(this.el.dom.parentNode);
14957 * Adds a CSS class to the component's underlying element.
14958 * @param {string} cls The CSS class name to add
14959 * @return {Ext.Component} this
14961 addClass : function(cls){
14963 this.el.addClass(cls);
14965 this.cls = this.cls ? this.cls + ' ' + cls : cls;
14971 * Removes a CSS class from the component's underlying element.
14972 * @param {string} cls The CSS class name to remove
14973 * @return {Ext.Component} this
14975 removeClass : function(cls){
14977 this.el.removeClass(cls);
14978 }else if(this.cls){
14979 this.cls = this.cls.split(' ').remove(cls).join(' ');
14985 // default function is not really useful
14986 onRender : function(ct, position){
14987 if(!this.el && this.autoEl){
14988 if(Ext.isString(this.autoEl)){
14989 this.el = document.createElement(this.autoEl);
14991 var div = document.createElement('div');
14992 Ext.DomHelper.overwrite(div, this.autoEl);
14993 this.el = div.firstChild;
14996 this.el.id = this.getId();
15000 this.el = Ext.get(this.el);
15001 if(this.allowDomMove !== false){
15002 ct.dom.insertBefore(this.el.dom, position);
15008 getAutoCreate : function(){
15009 var cfg = Ext.isObject(this.autoCreate) ?
15010 this.autoCreate : Ext.apply({}, this.defaultAutoCreate);
15011 if(this.id && !cfg.id){
15018 afterRender : Ext.emptyFn,
15021 * Destroys this component by purging any event listeners, removing the component's element from the DOM,
15022 * removing the component from its {@link Ext.Container} (if applicable) and unregistering it from
15023 * {@link Ext.ComponentMgr}. Destruction is generally handled automatically by the framework and this method
15024 * should usually not need to be called directly.
15027 destroy : function(){
15028 if(!this.isDestroyed){
15029 if(this.fireEvent('beforedestroy', this) !== false){
15030 this.beforeDestroy();
15032 this.el.removeAllListeners();
15034 if(this.actionMode == 'container' || this.removeMode == 'container'){
15035 this.container.remove();
15039 Ext.ComponentMgr.unregister(this);
15040 this.fireEvent('destroy', this);
15041 this.purgeListeners();
15042 this.isDestroyed = true;
15048 beforeDestroy : Ext.emptyFn,
15051 onDestroy : Ext.emptyFn,
15054 * <p>Returns the {@link Ext.Element} which encapsulates this Component.</p>
15055 * <p>This will <i>usually</i> be a <DIV> element created by the class's onRender method, but
15056 * that may be overridden using the {@link #autoEl} config.</p>
15057 * <br><p><b>Note</b>: this element will not be available until this Component has been rendered.</p><br>
15058 * <p>To add listeners for <b>DOM events</b> to this Component (as opposed to listeners
15059 * for this Component's own Observable events), see the {@link #listeners} config for a suggestion,
15060 * or use a render listener directly:</p><pre><code>
15062 title: 'The Clickable Panel',
15064 render: function(p) {
15065 // Append the Panel to the click handler's argument list.
15066 p.getEl().on('click', handlePanelClick.createDelegate(null, [p], true));
15068 single: true // Remove the listener after first invocation
15072 * @return {Ext.Element} The Element which encapsulates this Component.
15074 getEl : function(){
15079 * Returns the <code>id</code> of this component or automatically generates and
15080 * returns an <code>id</code> if an <code>id</code> is not defined yet:<pre><code>
15081 * 'ext-comp-' + (++Ext.Component.AUTO_ID)
15083 * @return {String} id
15085 getId : function(){
15086 return this.id || (this.id = 'ext-comp-' + (++Ext.Component.AUTO_ID));
15090 * Returns the <code>{@link #itemId}</code> of this component. If an
15091 * <code>{@link #itemId}</code> was not assigned through configuration the
15092 * <code>id</code> is returned using <code>{@link #getId}</code>.
15095 getItemId : function(){
15096 return this.itemId || this.getId();
15100 * Try to focus this component.
15101 * @param {Boolean} selectText (optional) If applicable, true to also select the text in this component
15102 * @param {Boolean/Number} delay (optional) Delay the focus this number of milliseconds (true for 10 milliseconds)
15103 * @return {Ext.Component} this
15105 focus : function(selectText, delay){
15107 this.focus.defer(Ext.isNumber(delay) ? delay : 10, this, [selectText, false]);
15112 if(selectText === true){
15113 this.el.dom.select();
15128 * Disable this component and fire the 'disable' event.
15129 * @return {Ext.Component} this
15131 disable : function(/* private */ silent){
15135 this.disabled = true;
15136 if(silent !== true){
15137 this.fireEvent('disable', this);
15143 onDisable : function(){
15144 this.getActionEl().addClass(this.disabledClass);
15145 this.el.dom.disabled = true;
15149 * Enable this component and fire the 'enable' event.
15150 * @return {Ext.Component} this
15152 enable : function(){
15156 this.disabled = false;
15157 this.fireEvent('enable', this);
15162 onEnable : function(){
15163 this.getActionEl().removeClass(this.disabledClass);
15164 this.el.dom.disabled = false;
15168 * Convenience function for setting disabled/enabled by boolean.
15169 * @param {Boolean} disabled
15170 * @return {Ext.Component} this
15172 setDisabled : function(disabled){
15173 return this[disabled ? 'disable' : 'enable']();
15177 * Show this component. Listen to the '{@link #beforeshow}' event and return
15178 * <tt>false</tt> to cancel showing the component. Fires the '{@link #show}'
15179 * event after showing the component.
15180 * @return {Ext.Component} this
15183 if(this.fireEvent('beforeshow', this) !== false){
15184 this.hidden = false;
15185 if(this.autoRender){
15186 this.render(Ext.isBoolean(this.autoRender) ? Ext.getBody() : this.autoRender);
15191 this.fireEvent('show', this);
15197 onShow : function(){
15198 this.getVisibilityEl().removeClass('x-hide-' + this.hideMode);
15202 * Hide this component. Listen to the '{@link #beforehide}' event and return
15203 * <tt>false</tt> to cancel hiding the component. Fires the '{@link #hide}'
15204 * event after hiding the component. Note this method is called internally if
15205 * the component is configured to be <code>{@link #hidden}</code>.
15206 * @return {Ext.Component} this
15209 if(this.fireEvent('beforehide', this) !== false){
15211 this.fireEvent('hide', this);
15217 doHide: function(){
15218 this.hidden = true;
15225 onHide : function(){
15226 this.getVisibilityEl().addClass('x-hide-' + this.hideMode);
15230 getVisibilityEl : function(){
15231 return this.hideParent ? this.container : this.getActionEl();
15235 * Convenience function to hide or show this component by boolean.
15236 * @param {Boolean} visible True to show, false to hide
15237 * @return {Ext.Component} this
15239 setVisible : function(visible){
15240 return this[visible ? 'show' : 'hide']();
15244 * Returns true if this component is visible.
15245 * @return {Boolean} True if this component is visible, false otherwise.
15247 isVisible : function(){
15248 return this.rendered && this.getVisibilityEl().isVisible();
15252 * Clone the current component using the original config values passed into this instance by default.
15253 * @param {Object} overrides A new config containing any properties to override in the cloned version.
15254 * An id property can be passed on this object, otherwise one will be generated to avoid duplicates.
15255 * @return {Ext.Component} clone The cloned copy of this component
15257 cloneConfig : function(overrides){
15258 overrides = overrides || {};
15259 var id = overrides.id || Ext.id();
15260 var cfg = Ext.applyIf(overrides, this.initialConfig);
15261 cfg.id = id; // prevent dup id
15262 return new this.constructor(cfg);
15266 * Gets the xtype for this component as registered with {@link Ext.ComponentMgr}. For a list of all
15267 * available xtypes, see the {@link Ext.Component} header. Example usage:
15269 var t = new Ext.form.TextField();
15270 alert(t.getXType()); // alerts 'textfield'
15272 * @return {String} The xtype
15274 getXType : function(){
15275 return this.constructor.xtype;
15279 * <p>Tests whether or not this Component is of a specific xtype. This can test whether this Component is descended
15280 * from the xtype (default) or whether it is directly of the xtype specified (shallow = true).</p>
15281 * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
15282 * to participate in determination of inherited xtypes.</b></p>
15283 * <p>For a list of all available xtypes, see the {@link Ext.Component} header.</p>
15284 * <p>Example usage:</p>
15286 var t = new Ext.form.TextField();
15287 var isText = t.isXType('textfield'); // true
15288 var isBoxSubclass = t.isXType('box'); // true, descended from BoxComponent
15289 var isBoxInstance = t.isXType('box', true); // false, not a direct BoxComponent instance
15291 * @param {String} xtype The xtype to check for this Component
15292 * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
15293 * the default), or true to check whether this Component is directly of the specified xtype.
15294 * @return {Boolean} True if this component descends from the specified xtype, false otherwise.
15296 isXType : function(xtype, shallow){
15297 //assume a string by default
15298 if (Ext.isFunction(xtype)){
15299 xtype = xtype.xtype; //handle being passed the class, e.g. Ext.Component
15300 }else if (Ext.isObject(xtype)){
15301 xtype = xtype.constructor.xtype; //handle being passed an instance
15304 return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1 : this.constructor.xtype == xtype;
15308 * <p>Returns this Component's xtype hierarchy as a slash-delimited string. For a list of all
15309 * available xtypes, see the {@link Ext.Component} header.</p>
15310 * <p><b>If using your own subclasses, be aware that a Component must register its own xtype
15311 * to participate in determination of inherited xtypes.</b></p>
15312 * <p>Example usage:</p>
15314 var t = new Ext.form.TextField();
15315 alert(t.getXTypes()); // alerts 'component/box/field/textfield'
15317 * @return {String} The xtype hierarchy string
15319 getXTypes : function(){
15320 var tc = this.constructor;
15322 var c = [], sc = this;
15323 while(sc && sc.constructor.xtype){
15324 c.unshift(sc.constructor.xtype);
15325 sc = sc.constructor.superclass;
15328 tc.xtypes = c.join('/');
15334 * Find a container above this component at any level by a custom function. If the passed function returns
15335 * true, the container will be returned.
15336 * @param {Function} fn The custom function to call with the arguments (container, this component).
15337 * @return {Ext.Container} The first Container for which the custom function returns true
15339 findParentBy : function(fn) {
15340 for (var p = this.ownerCt; (p != null) && !fn(p, this); p = p.ownerCt);
15345 * Find a container above this component at any level by xtype or class
15346 * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
15347 * @return {Ext.Container} The first Container which matches the given xtype or class
15349 findParentByType : function(xtype) {
15350 return Ext.isFunction(xtype) ?
15351 this.findParentBy(function(p){
15352 return p.constructor === xtype;
15354 this.findParentBy(function(p){
15355 return p.constructor.xtype === xtype;
15359 getDomPositionEl : function(){
15360 return this.getPositionEl ? this.getPositionEl() : this.getEl();
15364 purgeListeners : function(){
15365 Ext.Component.superclass.purgeListeners.call(this);
15367 this.on('beforedestroy', this.clearMons, this, {single: true});
15372 clearMons : function(){
15373 Ext.each(this.mons, function(m){
15374 m.item.un(m.ename, m.fn, m.scope);
15380 createMons: function(){
15383 this.on('beforedestroy', this.clearMons, this, {single: true});
15387 // internal function for auto removal of assigned event handlers on destruction
15388 mon : function(item, ename, fn, scope, opt){
15390 if(Ext.isObject(ename)){
15391 var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
15395 if(propRe.test(e)){
15398 if(Ext.isFunction(o[e])){
15401 item: item, ename: e, fn: o[e], scope: o.scope
15403 item.on(e, o[e], o.scope, o);
15405 // individual options
15407 item: item, ename: e, fn: o[e], scope: o.scope
15416 item: item, ename: ename, fn: fn, scope: scope
15418 item.on(ename, fn, scope, opt);
15421 // protected, opposite of mon
15422 mun : function(item, ename, fn, scope){
15425 for(var i = 0, len = this.mons.length; i < len; ++i){
15426 mon = this.mons[i];
15427 if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){
15428 this.mons.splice(i, 1);
15429 item.un(ename, fn, scope);
15438 * Returns the next component in the owning container
15439 * @return Ext.Component
15441 nextSibling : function(){
15443 var index = this.ownerCt.items.indexOf(this);
15444 if(index != -1 && index+1 < this.ownerCt.items.getCount()){
15445 return this.ownerCt.items.itemAt(index+1);
15452 * Returns the previous component in the owning container
15453 * @return Ext.Component
15455 previousSibling : function(){
15457 var index = this.ownerCt.items.indexOf(this);
15459 return this.ownerCt.items.itemAt(index-1);
15466 * Provides the link for Observable's fireEvent method to bubble up the ownership hierarchy.
15467 * @return {Ext.Container} the Container which owns this Component.
15469 getBubbleTarget : function(){
15470 return this.ownerCt;
15474 Ext.reg('component', Ext.Component);
15476 * @class Ext.Action
\r
15477 * <p>An Action is a piece of reusable functionality that can be abstracted out of any particular component so that it
\r
15478 * can be usefully shared among multiple components. Actions let you share handlers, configuration options and UI
\r
15479 * updates across any components that support the Action interface (primarily {@link Ext.Toolbar}, {@link Ext.Button}
\r
15480 * and {@link Ext.menu.Menu} components).</p>
\r
15481 * <p>Aside from supporting the config object interface, any component that needs to use Actions must also support
\r
15482 * the following method list, as these will be called as needed by the Action class: setText(string), setIconCls(string),
\r
15483 * setDisabled(boolean), setVisible(boolean) and setHandler(function).</p>
\r
15484 * Example usage:<br>
\r
15486 // Define the shared action. Each component below will have the same
\r
15487 // display text and icon, and will display the same message on click.
\r
15488 var action = new Ext.Action({
\r
15489 {@link #text}: 'Do something',
\r
15490 {@link #handler}: function(){
\r
15491 Ext.Msg.alert('Click', 'You did something.');
\r
15493 {@link #iconCls}: 'do-something',
\r
15494 {@link #itemId}: 'myAction'
\r
15497 var panel = new Ext.Panel({
\r
15498 title: 'Actions',
\r
15502 // Add the action directly to a toolbar as a menu button
\r
15505 text: 'Action Menu',
\r
15506 // Add the action to a menu as a text item
\r
15511 // Add the action to the panel body as a standard button
\r
15512 new Ext.Button(action)
\r
15514 renderTo: Ext.getBody()
\r
15517 // Change the text for all components using the action
\r
15518 action.setText('Something else');
\r
15520 // Reference an action through a container using the itemId
\r
15521 var btn = panel.getComponent('myAction');
\r
15522 var aRef = btn.baseAction;
\r
15523 aRef.setText('New text');
\r
15526 * @param {Object} config The configuration options
\r
15528 Ext.Action = function(config){
\r
15529 this.initialConfig = config;
\r
15530 this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
\r
15534 Ext.Action.prototype = {
\r
15536 * @cfg {String} text The text to set for all components using this action (defaults to '').
\r
15539 * @cfg {String} iconCls
\r
15540 * The CSS class selector that specifies a background image to be used as the header icon for
\r
15541 * all components using this action (defaults to '').
\r
15542 * <p>An example of specifying a custom icon class would be something like:
\r
15543 * </p><pre><code>
\r
15544 // specify the property in the config for the class:
\r
15546 iconCls: 'do-something'
\r
15548 // css class that specifies background image to be used as the icon image:
\r
15549 .do-something { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
\r
15553 * @cfg {Boolean} disabled True to disable all components using this action, false to enable them (defaults to false).
\r
15556 * @cfg {Boolean} hidden True to hide all components using this action, false to show them (defaults to false).
\r
15559 * @cfg {Function} handler The function that will be invoked by each component tied to this action
\r
15560 * when the component's primary event is triggered (defaults to undefined).
\r
15563 * @cfg {String} itemId
\r
15564 * See {@link Ext.Component}.{@link Ext.Component#itemId itemId}.
\r
15567 * @cfg {Object} scope The scope in which the {@link #handler} function will execute.
\r
15574 * Sets the text to be displayed by all components using this action.
\r
15575 * @param {String} text The text to display
\r
15577 setText : function(text){
\r
15578 this.initialConfig.text = text;
\r
15579 this.callEach('setText', [text]);
\r
15583 * Gets the text currently displayed by all components using this action.
\r
15585 getText : function(){
\r
15586 return this.initialConfig.text;
\r
15590 * Sets the icon CSS class for all components using this action. The class should supply
\r
15591 * a background image that will be used as the icon image.
\r
15592 * @param {String} cls The CSS class supplying the icon image
\r
15594 setIconClass : function(cls){
\r
15595 this.initialConfig.iconCls = cls;
\r
15596 this.callEach('setIconClass', [cls]);
\r
15600 * Gets the icon CSS class currently used by all components using this action.
\r
15602 getIconClass : function(){
\r
15603 return this.initialConfig.iconCls;
\r
15607 * Sets the disabled state of all components using this action. Shortcut method
\r
15608 * for {@link #enable} and {@link #disable}.
\r
15609 * @param {Boolean} disabled True to disable the component, false to enable it
\r
15611 setDisabled : function(v){
\r
15612 this.initialConfig.disabled = v;
\r
15613 this.callEach('setDisabled', [v]);
\r
15617 * Enables all components using this action.
\r
15619 enable : function(){
\r
15620 this.setDisabled(false);
\r
15624 * Disables all components using this action.
\r
15626 disable : function(){
\r
15627 this.setDisabled(true);
\r
15631 * Returns true if the components using this action are currently disabled, else returns false.
\r
15633 isDisabled : function(){
\r
15634 return this.initialConfig.disabled;
\r
15638 * Sets the hidden state of all components using this action. Shortcut method
\r
15639 * for <code>{@link #hide}</code> and <code>{@link #show}</code>.
\r
15640 * @param {Boolean} hidden True to hide the component, false to show it
\r
15642 setHidden : function(v){
\r
15643 this.initialConfig.hidden = v;
\r
15644 this.callEach('setVisible', [!v]);
\r
15648 * Shows all components using this action.
\r
15650 show : function(){
\r
15651 this.setHidden(false);
\r
15655 * Hides all components using this action.
\r
15657 hide : function(){
\r
15658 this.setHidden(true);
\r
15662 * Returns true if the components using this action are currently hidden, else returns false.
\r
15664 isHidden : function(){
\r
15665 return this.initialConfig.hidden;
\r
15669 * Sets the function that will be called by each component using this action when its primary event is triggered.
\r
15670 * @param {Function} fn The function that will be invoked by the action's components. The function
\r
15671 * will be called with no arguments.
\r
15672 * @param {Object} scope The scope in which the function will execute
\r
15674 setHandler : function(fn, scope){
\r
15675 this.initialConfig.handler = fn;
\r
15676 this.initialConfig.scope = scope;
\r
15677 this.callEach('setHandler', [fn, scope]);
\r
15681 * Executes the specified function once for each component currently tied to this action. The function passed
\r
15682 * in should accept a single argument that will be an object that supports the basic Action config/method interface.
\r
15683 * @param {Function} fn The function to execute for each component
\r
15684 * @param {Object} scope The scope in which the function will execute
\r
15686 each : function(fn, scope){
\r
15687 Ext.each(this.items, fn, scope);
\r
15691 callEach : function(fnName, args){
\r
15692 var cs = this.items;
\r
15693 for(var i = 0, len = cs.length; i < len; i++){
\r
15694 cs[i][fnName].apply(cs[i], args);
\r
15699 addComponent : function(comp){
\r
15700 this.items.push(comp);
\r
15701 comp.on('destroy', this.removeComponent, this);
\r
15705 removeComponent : function(comp){
\r
15706 this.items.remove(comp);
\r
15710 * Executes this action manually using the handler function specified in the original config object
\r
15711 * or the handler function set with <code>{@link #setHandler}</code>. Any arguments passed to this
\r
15712 * function will be passed on to the handler function.
\r
15713 * @param {Mixed} arg1 (optional) Variable number of arguments passed to the handler function
\r
15714 * @param {Mixed} arg2 (optional)
\r
15715 * @param {Mixed} etc... (optional)
\r
15717 execute : function(){
\r
15718 this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments);
\r
15723 * @extends Ext.Element
15724 * An extended {@link Ext.Element} object that supports a shadow and shim, constrain to viewport and
15725 * automatic maintaining of shadow/shim positions.
15726 * @cfg {Boolean} shim False to disable the iframe shim in browsers which need one (defaults to true)
15727 * @cfg {String/Boolean} shadow True to automatically create an {@link Ext.Shadow}, or a string indicating the
15728 * shadow's display {@link Ext.Shadow#mode}. False to disable the shadow. (defaults to false)
15729 * @cfg {Object} dh DomHelper object config to create element with (defaults to {tag: 'div', cls: 'x-layer'}).
15730 * @cfg {Boolean} constrain False to disable constrain to viewport (defaults to true)
15731 * @cfg {String} cls CSS class to add to the element
15732 * @cfg {Number} zindex Starting z-index (defaults to 11000)
15733 * @cfg {Number} shadowOffset Number of pixels to offset the shadow (defaults to 4)
15734 * @cfg {Boolean} useDisplay
15735 * Defaults to use css offsets to hide the Layer. Specify <tt>true</tt>
15736 * to use css style <tt>'display:none;'</tt> to hide the Layer.
15738 * @param {Object} config An object with config options.
15739 * @param {String/HTMLElement} existingEl (optional) Uses an existing DOM element. If the element is not found it creates it.
15742 Ext.Layer = function(config, existingEl){
15743 config = config || {};
15744 var dh = Ext.DomHelper;
15745 var cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
15747 this.dom = Ext.getDom(existingEl);
15750 var o = config.dh || {tag: 'div', cls: 'x-layer'};
15751 this.dom = dh.append(pel, o);
15754 this.addClass(config.cls);
15756 this.constrain = config.constrain !== false;
15757 this.setVisibilityMode(Ext.Element.VISIBILITY);
15759 this.id = this.dom.id = config.id;
15761 this.id = Ext.id(this.dom);
15763 this.zindex = config.zindex || this.getZIndex();
15764 this.position('absolute', this.zindex);
15766 this.shadowOffset = config.shadowOffset || 4;
15767 this.shadow = new Ext.Shadow({
15768 offset : this.shadowOffset,
15769 mode : config.shadow
15772 this.shadowOffset = 0;
15774 this.useShim = config.shim !== false && Ext.useShims;
15775 this.useDisplay = config.useDisplay;
15779 var supr = Ext.Element.prototype;
15781 // shims are shared among layer to keep from having 100 iframes
15784 Ext.extend(Ext.Layer, Ext.Element, {
15786 getZIndex : function(){
15787 return this.zindex || parseInt((this.getShim() || this).getStyle('z-index'), 10) || 11000;
15790 getShim : function(){
15797 var shim = shims.shift();
15799 shim = this.createShim();
15800 shim.enableDisplayMode('block');
15801 shim.dom.style.display = 'none';
15802 shim.dom.style.visibility = 'visible';
15804 var pn = this.dom.parentNode;
15805 if(shim.dom.parentNode != pn){
15806 pn.insertBefore(shim.dom, this.dom);
15808 shim.setStyle('z-index', this.getZIndex()-2);
15813 hideShim : function(){
15815 this.shim.setDisplayed(false);
15816 shims.push(this.shim);
15821 disableShadow : function(){
15823 this.shadowDisabled = true;
15824 this.shadow.hide();
15825 this.lastShadowOffset = this.shadowOffset;
15826 this.shadowOffset = 0;
15830 enableShadow : function(show){
15832 this.shadowDisabled = false;
15833 this.shadowOffset = this.lastShadowOffset;
15834 delete this.lastShadowOffset;
15842 // this code can execute repeatedly in milliseconds (i.e. during a drag) so
15843 // code size was sacrificed for effeciency (e.g. no getBox/setBox, no XY calls)
15844 sync : function(doShow){
15845 var sw = this.shadow;
15846 if(!this.updating && this.isVisible() && (sw || this.useShim)){
15847 var sh = this.getShim();
15849 var w = this.getWidth(),
15850 h = this.getHeight();
15852 var l = this.getLeft(true),
15853 t = this.getTop(true);
15855 if(sw && !this.shadowDisabled){
15856 if(doShow && !sw.isVisible()){
15859 sw.realign(l, t, w, h);
15865 // fit the shim behind the shadow, so it is shimmed too
15866 var a = sw.adjusts, s = sh.dom.style;
15867 s.left = (Math.min(l, l+a.l))+'px';
15868 s.top = (Math.min(t, t+a.t))+'px';
15869 s.width = (w+a.w)+'px';
15870 s.height = (h+a.h)+'px';
15877 sh.setLeftTop(l, t);
15884 destroy : function(){
15887 this.shadow.hide();
15889 this.removeAllListeners();
15890 Ext.removeNode(this.dom);
15891 Ext.Element.uncache(this.id);
15894 remove : function(){
15899 beginUpdate : function(){
15900 this.updating = true;
15904 endUpdate : function(){
15905 this.updating = false;
15910 hideUnders : function(negOffset){
15912 this.shadow.hide();
15918 constrainXY : function(){
15919 if(this.constrain){
15920 var vw = Ext.lib.Dom.getViewWidth(),
15921 vh = Ext.lib.Dom.getViewHeight();
15922 var s = Ext.getDoc().getScroll();
15924 var xy = this.getXY();
15925 var x = xy[0], y = xy[1];
15926 var so = this.shadowOffset;
15927 var w = this.dom.offsetWidth+so, h = this.dom.offsetHeight+so;
15928 // only move it if it needs it
15930 // first validate right/bottom
15931 if((x + w) > vw+s.left){
15935 if((y + h) > vh+s.top){
15939 // then make sure top/left isn't negative
15950 var ay = this.avoidY;
15951 if(y <= ay && (y+h) >= ay){
15957 supr.setXY.call(this, xy);
15964 isVisible : function(){
15965 return this.visible;
15969 showAction : function(){
15970 this.visible = true; // track visibility to prevent getStyle calls
15971 if(this.useDisplay === true){
15972 this.setDisplayed('');
15973 }else if(this.lastXY){
15974 supr.setXY.call(this, this.lastXY);
15975 }else if(this.lastLT){
15976 supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
15981 hideAction : function(){
15982 this.visible = false;
15983 if(this.useDisplay === true){
15984 this.setDisplayed(false);
15986 this.setLeftTop(-10000,-10000);
15990 // overridden Element method
15991 setVisible : function(v, a, d, c, e){
15996 var cb = function(){
16001 }.createDelegate(this);
16002 supr.setVisible.call(this, true, true, d, cb, e);
16005 this.hideUnders(true);
16014 }.createDelegate(this);
16016 supr.setVisible.call(this, v, a, d, cb, e);
16026 storeXY : function(xy){
16027 delete this.lastLT;
16031 storeLeftTop : function(left, top){
16032 delete this.lastXY;
16033 this.lastLT = [left, top];
16037 beforeFx : function(){
16038 this.beforeAction();
16039 return Ext.Layer.superclass.beforeFx.apply(this, arguments);
16043 afterFx : function(){
16044 Ext.Layer.superclass.afterFx.apply(this, arguments);
16045 this.sync(this.isVisible());
16049 beforeAction : function(){
16050 if(!this.updating && this.shadow){
16051 this.shadow.hide();
16055 // overridden Element method
16056 setLeft : function(left){
16057 this.storeLeftTop(left, this.getTop(true));
16058 supr.setLeft.apply(this, arguments);
16063 setTop : function(top){
16064 this.storeLeftTop(this.getLeft(true), top);
16065 supr.setTop.apply(this, arguments);
16070 setLeftTop : function(left, top){
16071 this.storeLeftTop(left, top);
16072 supr.setLeftTop.apply(this, arguments);
16077 setXY : function(xy, a, d, c, e){
16079 this.beforeAction();
16081 var cb = this.createCB(c);
16082 supr.setXY.call(this, xy, a, d, cb, e);
16090 createCB : function(c){
16101 // overridden Element method
16102 setX : function(x, a, d, c, e){
16103 this.setXY([x, this.getY()], a, d, c, e);
16107 // overridden Element method
16108 setY : function(y, a, d, c, e){
16109 this.setXY([this.getX(), y], a, d, c, e);
16113 // overridden Element method
16114 setSize : function(w, h, a, d, c, e){
16115 this.beforeAction();
16116 var cb = this.createCB(c);
16117 supr.setSize.call(this, w, h, a, d, cb, e);
16124 // overridden Element method
16125 setWidth : function(w, a, d, c, e){
16126 this.beforeAction();
16127 var cb = this.createCB(c);
16128 supr.setWidth.call(this, w, a, d, cb, e);
16135 // overridden Element method
16136 setHeight : function(h, a, d, c, e){
16137 this.beforeAction();
16138 var cb = this.createCB(c);
16139 supr.setHeight.call(this, h, a, d, cb, e);
16146 // overridden Element method
16147 setBounds : function(x, y, w, h, a, d, c, e){
16148 this.beforeAction();
16149 var cb = this.createCB(c);
16151 this.storeXY([x, y]);
16152 supr.setXY.call(this, [x, y]);
16153 supr.setSize.call(this, w, h, a, d, cb, e);
16156 supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
16162 * Sets the z-index of this layer and adjusts any shadow and shim z-indexes. The layer z-index is automatically
16163 * incremented by two more than the value passed in so that it always shows above any shadow or shim (the shadow
16164 * element, if any, will be assigned z-index + 1, and the shim element, if any, will be assigned the unmodified z-index).
16165 * @param {Number} zindex The new z-index to set
16166 * @return {this} The Layer
16168 setZIndex : function(zindex){
16169 this.zindex = zindex;
16170 this.setStyle('z-index', zindex + 2);
16172 this.shadow.setZIndex(zindex + 1);
16175 this.shim.setStyle('z-index', zindex);
16181 * @class Ext.Shadow
16182 * Simple class that can provide a shadow effect for any element. Note that the element MUST be absolutely positioned,
16183 * and the shadow does not provide any shimming. This should be used only in simple cases -- for more advanced
16184 * functionality that can also provide the same shadow effect, see the {@link Ext.Layer} class.
16186 * Create a new Shadow
16187 * @param {Object} config The config object
16189 Ext.Shadow = function(config){
16190 Ext.apply(this, config);
16191 if(typeof this.mode != "string"){
16192 this.mode = this.defaultMode;
16194 var o = this.offset, a = {h: 0};
16195 var rad = Math.floor(this.offset/2);
16196 switch(this.mode.toLowerCase()){ // all this hideous nonsense calculates the various offsets for shadows
16202 a.l -= this.offset + rad;
16203 a.t -= this.offset + rad;
16214 a.l -= (this.offset - rad);
16215 a.t -= this.offset + rad;
16217 a.w -= (this.offset - rad)*2;
16228 a.l -= (this.offset - rad);
16229 a.t -= (this.offset - rad);
16231 a.w -= (this.offset + rad + 1);
16232 a.h -= (this.offset + rad);
16241 Ext.Shadow.prototype = {
16243 * @cfg {String} mode
16244 * The shadow display mode. Supports the following options:<div class="mdetail-params"><ul>
16245 * <li><b><tt>sides</tt></b> : Shadow displays on both sides and bottom only</li>
16246 * <li><b><tt>frame</tt></b> : Shadow displays equally on all four sides</li>
16247 * <li><b><tt>drop</tt></b> : Traditional bottom-right drop shadow</li>
16251 * @cfg {String} offset
16252 * The number of pixels to offset the shadow from the element (defaults to <tt>4</tt>)
16257 defaultMode: "drop",
16260 * Displays the shadow under the target element
16261 * @param {Mixed} targetEl The id or element under which the shadow should display
16263 show : function(target){
16264 target = Ext.get(target);
16266 this.el = Ext.Shadow.Pool.pull();
16267 if(this.el.dom.nextSibling != target.dom){
16268 this.el.insertBefore(target);
16271 this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10)-1);
16273 this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")";
16276 target.getLeft(true),
16277 target.getTop(true),
16281 this.el.dom.style.display = "block";
16285 * Returns true if the shadow is visible, else false
16287 isVisible : function(){
16288 return this.el ? true : false;
16292 * Direct alignment when values are already available. Show must be called at least once before
16293 * calling this method to ensure it is initialized.
16294 * @param {Number} left The target element left position
16295 * @param {Number} top The target element top position
16296 * @param {Number} width The target element width
16297 * @param {Number} height The target element height
16299 realign : function(l, t, w, h){
16303 var a = this.adjusts, d = this.el.dom, s = d.style;
16305 s.left = (l+a.l)+"px";
16306 s.top = (t+a.t)+"px";
16307 var sw = (w+a.w), sh = (h+a.h), sws = sw +"px", shs = sh + "px";
16308 if(s.width != sws || s.height != shs){
16312 var cn = d.childNodes;
16313 var sww = Math.max(0, (sw-12))+"px";
16314 cn[0].childNodes[1].style.width = sww;
16315 cn[1].childNodes[1].style.width = sww;
16316 cn[2].childNodes[1].style.width = sww;
16317 cn[1].style.height = Math.max(0, (sh-12))+"px";
16323 * Hides this shadow
16327 this.el.dom.style.display = "none";
16328 Ext.Shadow.Pool.push(this.el);
16334 * Adjust the z-index of this shadow
16335 * @param {Number} zindex The new z-index
16337 setZIndex : function(z){
16340 this.el.setStyle("z-index", z);
16345 // Private utility class that manages the internal Shadow cache
16346 Ext.Shadow.Pool = function(){
16348 var markup = Ext.isIE ?
16349 '<div class="x-ie-shadow"></div>' :
16350 '<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>';
16353 var sh = p.shift();
16355 sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
16356 sh.autoBoxAdjust = false;
16361 push : function(sh){
16366 * @class Ext.BoxComponent
16367 * @extends Ext.Component
16368 * <p>Base class for any {@link Ext.Component Component} that is to be sized as a box, using width and height.</p>
16369 * <p>BoxComponent provides automatic box model adjustments for sizing and positioning and will work correctly
16370 * within the Component rendering model.</p>
16371 * <p>A BoxComponent may be created as a custom Component which encapsulates any HTML element, either a pre-existing
16372 * element, or one that is created to your specifications at render time. Usually, to participate in layouts,
16373 * a Component will need to be a <b>Box</b>Component in order to have its width and height managed.</p>
16374 * <p>To use a pre-existing element as a BoxComponent, configure it so that you preset the <b>el</b> property to the
16375 * element to reference:<pre><code>
16376 var pageHeader = new Ext.BoxComponent({
16377 el: 'my-header-div'
16379 * This may then be {@link Ext.Container#add added} to a {@link Ext.Container Container} as a child item.</p>
16380 * <p>To create a BoxComponent based around a HTML element to be created at render time, use the
16381 * {@link Ext.Component#autoEl autoEl} config option which takes the form of a
16382 * {@link Ext.DomHelper DomHelper} specification:<pre><code>
16383 var myImage = new Ext.BoxComponent({
16386 src: '/images/my-image.jpg'
16388 });</code></pre></p>
16390 * @param {Ext.Element/String/Object} config The configuration options.
16393 Ext.BoxComponent = Ext.extend(Ext.Component, {
16395 // tabTip config is used when a BoxComponent is a child of a TabPanel
16397 * @cfg {String} tabTip
16398 * <p><b>Note</b>: this config is only used when this BoxComponent is a child item of a TabPanel.</p>
16399 * A string to be used as innerHTML (html tags are accepted) to show in a tooltip when mousing over
16400 * the associated tab selector element. {@link Ext.QuickTips}.init()
16401 * must be called in order for the tips to render.
16403 // Configs below are used for all Components when rendered by BorderLayout.
16405 * @cfg {String} region <p><b>Note</b>: this config is only used when this BoxComponent is rendered
16406 * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
16407 * layout manager (e.g. specifying <tt>layout:'border'</tt>).</p><br>
16408 * <p>See {@link Ext.layout.BorderLayout} also.</p>
16410 // margins config is used when a BoxComponent is rendered by BorderLayout or BoxLayout.
16412 * @cfg {Object} margins <p><b>Note</b>: this config is only used when this BoxComponent is rendered
16413 * by a Container which has been configured to use the <b>{@link Ext.layout.BorderLayout BorderLayout}</b>
16414 * or one of the two <b>{@link Ext.layout.BoxLayout BoxLayout} subclasses.</b></p>
16415 * <p>An object containing margins to apply to this BoxComponent in the
16416 * format:</p><pre><code>
16419 right: (right margin),
16420 bottom: (bottom margin),
16421 left: (left margin)
16423 * <p>May also be a string containing space-separated, numeric margin values. The order of the
16424 * sides associated with each value matches the way CSS processes margin values:</p>
16425 * <p><div class="mdetail-params"><ul>
16426 * <li>If there is only one value, it applies to all sides.</li>
16427 * <li>If there are two values, the top and bottom borders are set to the first value and the
16428 * right and left are set to the second.</li>
16429 * <li>If there are three values, the top is set to the first value, the left and right are set
16430 * to the second, and the bottom is set to the third.</li>
16431 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
16433 * <p>Defaults to:</p><pre><code>
16434 * {top:0, right:0, bottom:0, left:0}
16439 * The local x (left) coordinate for this component if contained within a positioning container.
16443 * The local y (top) coordinate for this component if contained within a positioning container.
16446 * @cfg {Number} pageX
16447 * The page level x coordinate for this component if contained within a positioning container.
16450 * @cfg {Number} pageY
16451 * The page level y coordinate for this component if contained within a positioning container.
16454 * @cfg {Number} height
16455 * The height of this component in pixels (defaults to auto).
16456 * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
16459 * @cfg {Number} width
16460 * The width of this component in pixels (defaults to auto).
16461 * <b>Note</b> to express this dimension as a percentage or offset see {@link Ext.Component#anchor}.
16464 * @cfg {Boolean} autoHeight
16465 * <p>True to use height:'auto', false to use fixed height (or allow it to be managed by its parent
16466 * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
16467 * <p><b>Note</b>: Although many components inherit this config option, not all will
16468 * function as expected with a height of 'auto'. Setting autoHeight:true means that the
16469 * browser will manage height based on the element's contents, and that Ext will not manage it at all.</p>
16470 * <p>If the <i>browser</i> is managing the height, be aware that resizes performed by the browser in response
16471 * to changes within the structure of the Component cannot be detected. Therefore changes to the height might
16472 * result in elements needing to be synchronized with the new height. Example:</p><pre><code>
16473 var w = new Ext.Window({
16478 title: 'Collapse Me',
16483 beforecollapse: function() {
16484 w.el.shadow.hide();
16486 beforeexpand: function() {
16487 w.el.shadow.hide();
16489 collapse: function() {
16492 expand: function() {
16501 * @cfg {Boolean} autoWidth
16502 * <p>True to use width:'auto', false to use fixed width (or allow it to be managed by its parent
16503 * Container's {@link Ext.Container#layout layout manager}. Defaults to false.</p>
16504 * <p><b>Note</b>: Although many components inherit this config option, not all will
16505 * function as expected with a width of 'auto'. Setting autoWidth:true means that the
16506 * browser will manage width based on the element's contents, and that Ext will not manage it at all.</p>
16507 * <p>If the <i>browser</i> is managing the width, be aware that resizes performed by the browser in response
16508 * to changes within the structure of the Component cannot be detected. Therefore changes to the width might
16509 * result in elements needing to be synchronized with the new width. For example, where the target element is:</p><pre><code>
16510 <div id='grid-container' style='margin-left:25%;width:50%'></div>
16512 * A Panel rendered into that target element must listen for browser window resize in order to relay its
16513 * child items when the browser changes its width:<pre><code>
16514 var myPanel = new Ext.Panel({
16515 renderTo: 'grid-container',
16516 monitorResize: true, // relay on browser resize
16538 /* // private internal config
16539 * {Boolean} deferHeight
16540 * True to defer height calculations to an external component, false to allow this component to set its own
16541 * height (defaults to false).
16545 initComponent : function(){
16546 Ext.BoxComponent.superclass.initComponent.call(this);
16550 * Fires after the component is resized.
16551 * @param {Ext.Component} this
16552 * @param {Number} adjWidth The box-adjusted width that was set
16553 * @param {Number} adjHeight The box-adjusted height that was set
16554 * @param {Number} rawWidth The width that was originally specified
16555 * @param {Number} rawHeight The height that was originally specified
16560 * Fires after the component is moved.
16561 * @param {Ext.Component} this
16562 * @param {Number} x The new x position
16563 * @param {Number} y The new y position
16569 // private, set in afterRender to signify that the component has been rendered
16571 // private, used to defer height settings to subclasses
16572 deferHeight: false,
16575 * Sets the width and height of this BoxComponent. This method fires the {@link #resize} event. This method can accept
16576 * either width and height as separate arguments, or you can pass a size object like <code>{width:10, height:20}</code>.
16577 * @param {Mixed} width The new width to set. This may be one of:<div class="mdetail-params"><ul>
16578 * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16579 * <li>A String used to set the CSS width style.</li>
16580 * <li>A size object in the format <code>{width: widthValue, height: heightValue}</code>.</li>
16581 * <li><code>undefined</code> to leave the width unchanged.</li>
16583 * @param {Mixed} height The new height to set (not required if a size object is passed as the first arg).
16584 * This may be one of:<div class="mdetail-params"><ul>
16585 * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16586 * <li>A String used to set the CSS height style. Animation may <b>not</b> be used.</li>
16587 * <li><code>undefined</code> to leave the height unchanged.</li>
16589 * @return {Ext.BoxComponent} this
16591 setSize : function(w, h){
16592 // support for standard size objects
16593 if(typeof w == 'object'){
16598 if(!this.boxReady){
16604 // prevent recalcs when not needed
16605 if(this.cacheSizes !== false && this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
16608 this.lastSize = {width: w, height: h};
16609 var adj = this.adjustSize(w, h);
16610 var aw = adj.width, ah = adj.height;
16611 if(aw !== undefined || ah !== undefined){ // this code is nasty but performs better with floaters
16612 var rz = this.getResizeEl();
16613 if(!this.deferHeight && aw !== undefined && ah !== undefined){
16614 rz.setSize(aw, ah);
16615 }else if(!this.deferHeight && ah !== undefined){
16617 }else if(aw !== undefined){
16620 this.onResize(aw, ah, w, h);
16621 this.fireEvent('resize', this, aw, ah, w, h);
16627 * Sets the width of the component. This method fires the {@link #resize} event.
16628 * @param {Number} width The new width to setThis may be one of:<div class="mdetail-params"><ul>
16629 * <li>A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16630 * <li>A String used to set the CSS width style.</li>
16632 * @return {Ext.BoxComponent} this
16634 setWidth : function(width){
16635 return this.setSize(width);
16639 * Sets the height of the component. This method fires the {@link #resize} event.
16640 * @param {Number} height The new height to set. This may be one of:<div class="mdetail-params"><ul>
16641 * <li>A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).</li>
16642 * <li>A String used to set the CSS height style.</li>
16643 * <li><i>undefined</i> to leave the height unchanged.</li>
16645 * @return {Ext.BoxComponent} this
16647 setHeight : function(height){
16648 return this.setSize(undefined, height);
16652 * Gets the current size of the component's underlying element.
16653 * @return {Object} An object containing the element's size {width: (element width), height: (element height)}
16655 getSize : function(){
16656 return this.getResizeEl().getSize();
16660 * Gets the current width of the component's underlying element.
16663 getWidth : function(){
16664 return this.getResizeEl().getWidth();
16668 * Gets the current height of the component's underlying element.
16671 getHeight : function(){
16672 return this.getResizeEl().getHeight();
16676 * Gets the current size of the component's underlying element, including space taken by its margins.
16677 * @return {Object} An object containing the element's size {width: (element width + left/right margins), height: (element height + top/bottom margins)}
16679 getOuterSize : function(){
16680 var el = this.getResizeEl();
16681 return {width: el.getWidth() + el.getMargins('lr'),
16682 height: el.getHeight() + el.getMargins('tb')};
16686 * Gets the current XY position of the component's underlying element.
16687 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
16688 * @return {Array} The XY position of the element (e.g., [100, 200])
16690 getPosition : function(local){
16691 var el = this.getPositionEl();
16692 if(local === true){
16693 return [el.getLeft(true), el.getTop(true)];
16695 return this.xy || el.getXY();
16699 * Gets the current box measurements of the component's underlying element.
16700 * @param {Boolean} local (optional) If true the element's left and top are returned instead of page XY (defaults to false)
16701 * @return {Object} box An object in the format {x, y, width, height}
16703 getBox : function(local){
16704 var pos = this.getPosition(local);
16705 var s = this.getSize();
16712 * Sets the current box measurements of the component's underlying element.
16713 * @param {Object} box An object in the format {x, y, width, height}
16714 * @return {Ext.BoxComponent} this
16716 updateBox : function(box){
16717 this.setSize(box.width, box.height);
16718 this.setPagePosition(box.x, box.y);
16723 * <p>Returns the outermost Element of this Component which defines the Components overall size.</p>
16724 * <p><i>Usually</i> this will return the same Element as <code>{@link #getEl}</code>,
16725 * but in some cases, a Component may have some more wrapping Elements around its main
16726 * active Element.</p>
16727 * <p>An example is a ComboBox. It is encased in a <i>wrapping</i> Element which
16728 * contains both the <code><input></code> Element (which is what would be returned
16729 * by its <code>{@link #getEl}</code> method, <i>and</i> the trigger button Element.
16730 * This Element is returned as the <code>resizeEl</code>.
16732 getResizeEl : function(){
16733 return this.resizeEl || this.el;
16737 getPositionEl : function(){
16738 return this.positionEl || this.el;
16742 * Sets the left and top of the component. To set the page XY position instead, use {@link #setPagePosition}.
16743 * This method fires the {@link #move} event.
16744 * @param {Number} left The new left
16745 * @param {Number} top The new top
16746 * @return {Ext.BoxComponent} this
16748 setPosition : function(x, y){
16749 if(x && typeof x[1] == 'number'){
16755 if(!this.boxReady){
16758 var adj = this.adjustPosition(x, y);
16759 var ax = adj.x, ay = adj.y;
16761 var el = this.getPositionEl();
16762 if(ax !== undefined || ay !== undefined){
16763 if(ax !== undefined && ay !== undefined){
16764 el.setLeftTop(ax, ay);
16765 }else if(ax !== undefined){
16767 }else if(ay !== undefined){
16770 this.onPosition(ax, ay);
16771 this.fireEvent('move', this, ax, ay);
16777 * Sets the page XY position of the component. To set the left and top instead, use {@link #setPosition}.
16778 * This method fires the {@link #move} event.
16779 * @param {Number} x The new x position
16780 * @param {Number} y The new y position
16781 * @return {Ext.BoxComponent} this
16783 setPagePosition : function(x, y){
16784 if(x && typeof x[1] == 'number'){
16790 if(!this.boxReady){
16793 if(x === undefined || y === undefined){ // cannot translate undefined points
16796 var p = this.getPositionEl().translatePoints(x, y);
16797 this.setPosition(p.left, p.top);
16802 afterRender : function(){
16803 Ext.BoxComponent.superclass.afterRender.call(this);
16805 this.resizeEl = Ext.get(this.resizeEl);
16807 if(this.positionEl){
16808 this.positionEl = Ext.get(this.positionEl);
16810 this.boxReady = true;
16811 this.setSize(this.width, this.height);
16812 if(this.x || this.y){
16813 this.setPosition(this.x, this.y);
16814 }else if(this.pageX || this.pageY){
16815 this.setPagePosition(this.pageX, this.pageY);
16820 * Force the component's size to recalculate based on the underlying element's current height and width.
16821 * @return {Ext.BoxComponent} this
16823 syncSize : function(){
16824 delete this.lastSize;
16825 this.setSize(this.autoWidth ? undefined : this.getResizeEl().getWidth(), this.autoHeight ? undefined : this.getResizeEl().getHeight());
16830 * Called after the component is resized, this method is empty by default but can be implemented by any
16831 * subclass that needs to perform custom logic after a resize occurs.
16832 * @param {Number} adjWidth The box-adjusted width that was set
16833 * @param {Number} adjHeight The box-adjusted height that was set
16834 * @param {Number} rawWidth The width that was originally specified
16835 * @param {Number} rawHeight The height that was originally specified
16837 onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
16842 * Called after the component is moved, this method is empty by default but can be implemented by any
16843 * subclass that needs to perform custom logic after a move occurs.
16844 * @param {Number} x The new x position
16845 * @param {Number} y The new y position
16847 onPosition : function(x, y){
16852 adjustSize : function(w, h){
16853 if(this.autoWidth){
16856 if(this.autoHeight){
16859 return {width : w, height: h};
16863 adjustPosition : function(x, y){
16864 return {x : x, y: y};
16867 Ext.reg('box', Ext.BoxComponent);
16871 * @class Ext.Spacer
16872 * @extends Ext.BoxComponent
16873 * <p>Used to provide a sizable space in a layout.</p>
16875 * @param {Object} config
16877 Ext.Spacer = Ext.extend(Ext.BoxComponent, {
16880 Ext.reg('spacer', Ext.Spacer);/**
\r
16881 * @class Ext.SplitBar
\r
16882 * @extends Ext.util.Observable
\r
16883 * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized).
\r
16887 var split = new Ext.SplitBar("elementToDrag", "elementToSize",
\r
16888 Ext.SplitBar.HORIZONTAL, Ext.SplitBar.LEFT);
\r
16889 split.setAdapter(new Ext.SplitBar.AbsoluteLayoutAdapter("container"));
\r
16890 split.minSize = 100;
\r
16891 split.maxSize = 600;
\r
16892 split.animate = true;
\r
16893 split.on('moved', splitterMoved);
\r
16896 * Create a new SplitBar
\r
16897 * @param {Mixed} dragElement The element to be dragged and act as the SplitBar.
\r
16898 * @param {Mixed} resizingElement The element to be resized based on where the SplitBar element is dragged
\r
16899 * @param {Number} orientation (optional) Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
\r
16900 * @param {Number} placement (optional) Either Ext.SplitBar.LEFT or Ext.SplitBar.RIGHT for horizontal or
\r
16901 Ext.SplitBar.TOP or Ext.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial
\r
16902 position of the SplitBar).
\r
16904 Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
\r
16907 this.el = Ext.get(dragElement, true);
\r
16908 this.el.dom.unselectable = "on";
\r
16910 this.resizingEl = Ext.get(resizingElement, true);
\r
16914 * The orientation of the split. Either Ext.SplitBar.HORIZONTAL or Ext.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
\r
16915 * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
\r
16918 this.orientation = orientation || Ext.SplitBar.HORIZONTAL;
\r
16921 * The increment, in pixels by which to move this SplitBar. When <i>undefined</i>, the SplitBar moves smoothly.
\r
16923 * @property tickSize
\r
16926 * The minimum size of the resizing element. (Defaults to 0)
\r
16929 this.minSize = 0;
\r
16932 * The maximum size of the resizing element. (Defaults to 2000)
\r
16935 this.maxSize = 2000;
\r
16938 * Whether to animate the transition to the new size
\r
16941 this.animate = false;
\r
16944 * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
\r
16947 this.useShim = false;
\r
16950 this.shim = null;
\r
16952 if(!existingProxy){
\r
16954 this.proxy = Ext.SplitBar.createProxy(this.orientation);
\r
16956 this.proxy = Ext.get(existingProxy).dom;
\r
16959 this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
\r
16962 this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
\r
16965 this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
\r
16968 this.dragSpecs = {};
\r
16971 * @private The adapter to use to positon and resize elements
\r
16973 this.adapter = new Ext.SplitBar.BasicLayoutAdapter();
\r
16974 this.adapter.init(this);
\r
16976 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
16978 this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);
\r
16979 this.el.addClass("x-splitbar-h");
\r
16982 this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);
\r
16983 this.el.addClass("x-splitbar-v");
\r
16989 * Fires when the splitter is moved (alias for {@link #moved})
\r
16990 * @param {Ext.SplitBar} this
\r
16991 * @param {Number} newSize the new width or height
\r
16996 * Fires when the splitter is moved
\r
16997 * @param {Ext.SplitBar} this
\r
16998 * @param {Number} newSize the new width or height
\r
17002 * @event beforeresize
\r
17003 * Fires before the splitter is dragged
\r
17004 * @param {Ext.SplitBar} this
\r
17011 Ext.SplitBar.superclass.constructor.call(this);
\r
17014 Ext.extend(Ext.SplitBar, Ext.util.Observable, {
\r
17015 onStartProxyDrag : function(x, y){
\r
17016 this.fireEvent("beforeresize", this);
\r
17017 this.overlay = Ext.DomHelper.append(document.body, {cls: "x-drag-overlay", html: " "}, true);
\r
17018 this.overlay.unselectable();
\r
17019 this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
\r
17020 this.overlay.show();
\r
17021 Ext.get(this.proxy).setDisplayed("block");
\r
17022 var size = this.adapter.getElementSize(this);
\r
17023 this.activeMinSize = this.getMinimumSize();
\r
17024 this.activeMaxSize = this.getMaximumSize();
\r
17025 var c1 = size - this.activeMinSize;
\r
17026 var c2 = Math.max(this.activeMaxSize - size, 0);
\r
17027 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
17028 this.dd.resetConstraints();
\r
17029 this.dd.setXConstraint(
\r
17030 this.placement == Ext.SplitBar.LEFT ? c1 : c2,
\r
17031 this.placement == Ext.SplitBar.LEFT ? c2 : c1,
\r
17034 this.dd.setYConstraint(0, 0);
\r
17036 this.dd.resetConstraints();
\r
17037 this.dd.setXConstraint(0, 0);
\r
17038 this.dd.setYConstraint(
\r
17039 this.placement == Ext.SplitBar.TOP ? c1 : c2,
\r
17040 this.placement == Ext.SplitBar.TOP ? c2 : c1,
\r
17044 this.dragSpecs.startSize = size;
\r
17045 this.dragSpecs.startPoint = [x, y];
\r
17046 Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
\r
17050 * @private Called after the drag operation by the DDProxy
\r
17052 onEndProxyDrag : function(e){
\r
17053 Ext.get(this.proxy).setDisplayed(false);
\r
17054 var endPoint = Ext.lib.Event.getXY(e);
\r
17055 if(this.overlay){
\r
17056 Ext.destroy(this.overlay);
\r
17057 delete this.overlay;
\r
17060 if(this.orientation == Ext.SplitBar.HORIZONTAL){
\r
17061 newSize = this.dragSpecs.startSize +
\r
17062 (this.placement == Ext.SplitBar.LEFT ?
\r
17063 endPoint[0] - this.dragSpecs.startPoint[0] :
\r
17064 this.dragSpecs.startPoint[0] - endPoint[0]
\r
17067 newSize = this.dragSpecs.startSize +
\r
17068 (this.placement == Ext.SplitBar.TOP ?
\r
17069 endPoint[1] - this.dragSpecs.startPoint[1] :
\r
17070 this.dragSpecs.startPoint[1] - endPoint[1]
\r
17073 newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
\r
17074 if(newSize != this.dragSpecs.startSize){
\r
17075 if(this.fireEvent('beforeapply', this, newSize) !== false){
\r
17076 this.adapter.setElementSize(this, newSize);
\r
17077 this.fireEvent("moved", this, newSize);
\r
17078 this.fireEvent("resize", this, newSize);
\r
17084 * Get the adapter this SplitBar uses
\r
17085 * @return The adapter object
\r
17087 getAdapter : function(){
\r
17088 return this.adapter;
\r
17092 * Set the adapter this SplitBar uses
\r
17093 * @param {Object} adapter A SplitBar adapter object
\r
17095 setAdapter : function(adapter){
\r
17096 this.adapter = adapter;
\r
17097 this.adapter.init(this);
\r
17101 * Gets the minimum size for the resizing element
\r
17102 * @return {Number} The minimum size
\r
17104 getMinimumSize : function(){
\r
17105 return this.minSize;
\r
17109 * Sets the minimum size for the resizing element
\r
17110 * @param {Number} minSize The minimum size
\r
17112 setMinimumSize : function(minSize){
\r
17113 this.minSize = minSize;
\r
17117 * Gets the maximum size for the resizing element
\r
17118 * @return {Number} The maximum size
\r
17120 getMaximumSize : function(){
\r
17121 return this.maxSize;
\r
17125 * Sets the maximum size for the resizing element
\r
17126 * @param {Number} maxSize The maximum size
\r
17128 setMaximumSize : function(maxSize){
\r
17129 this.maxSize = maxSize;
\r
17133 * Sets the initialize size for the resizing element
\r
17134 * @param {Number} size The initial size
\r
17136 setCurrentSize : function(size){
\r
17137 var oldAnimate = this.animate;
\r
17138 this.animate = false;
\r
17139 this.adapter.setElementSize(this, size);
\r
17140 this.animate = oldAnimate;
\r
17144 * Destroy this splitbar.
\r
17145 * @param {Boolean} removeEl True to remove the element
\r
17147 destroy : function(removeEl){
\r
17148 Ext.destroy(this.shim, Ext.get(this.proxy));
\r
17151 this.el.remove();
\r
17153 this.purgeListeners();
\r
17158 * @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
17160 Ext.SplitBar.createProxy = function(dir){
\r
17161 var proxy = new Ext.Element(document.createElement("div"));
\r
17162 proxy.unselectable();
\r
17163 var cls = 'x-splitbar-proxy';
\r
17164 proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
\r
17165 document.body.appendChild(proxy.dom);
\r
17166 return proxy.dom;
\r
17170 * @class Ext.SplitBar.BasicLayoutAdapter
\r
17171 * Default Adapter. It assumes the splitter and resizing element are not positioned
\r
17172 * elements and only gets/sets the width of the element. Generally used for table based layouts.
\r
17174 Ext.SplitBar.BasicLayoutAdapter = function(){
\r
17177 Ext.SplitBar.BasicLayoutAdapter.prototype = {
\r
17178 // do nothing for now
\r
17179 init : function(s){
\r
17183 * Called before drag operations to get the current size of the resizing element.
\r
17184 * @param {Ext.SplitBar} s The SplitBar using this adapter
\r
17186 getElementSize : function(s){
\r
17187 if(s.orientation == Ext.SplitBar.HORIZONTAL){
\r
17188 return s.resizingEl.getWidth();
\r
17190 return s.resizingEl.getHeight();
\r
17195 * Called after drag operations to set the size of the resizing element.
\r
17196 * @param {Ext.SplitBar} s The SplitBar using this adapter
\r
17197 * @param {Number} newSize The new size to set
\r
17198 * @param {Function} onComplete A function to be invoked when resizing is complete
\r
17200 setElementSize : function(s, newSize, onComplete){
\r
17201 if(s.orientation == Ext.SplitBar.HORIZONTAL){
\r
17203 s.resizingEl.setWidth(newSize);
\r
17205 onComplete(s, newSize);
\r
17208 s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');
\r
17213 s.resizingEl.setHeight(newSize);
\r
17215 onComplete(s, newSize);
\r
17218 s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');
\r
17225 *@class Ext.SplitBar.AbsoluteLayoutAdapter
\r
17226 * @extends Ext.SplitBar.BasicLayoutAdapter
\r
17227 * Adapter that moves the splitter element to align with the resized sizing element.
\r
17228 * Used with an absolute positioned SplitBar.
\r
17229 * @param {Mixed} container The container that wraps around the absolute positioned content. If it's
\r
17230 * document.body, make sure you assign an id to the body element.
\r
17232 Ext.SplitBar.AbsoluteLayoutAdapter = function(container){
\r
17233 this.basic = new Ext.SplitBar.BasicLayoutAdapter();
\r
17234 this.container = Ext.get(container);
\r
17237 Ext.SplitBar.AbsoluteLayoutAdapter.prototype = {
\r
17238 init : function(s){
\r
17239 this.basic.init(s);
\r
17242 getElementSize : function(s){
\r
17243 return this.basic.getElementSize(s);
\r
17246 setElementSize : function(s, newSize, onComplete){
\r
17247 this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
\r
17250 moveSplitter : function(s){
\r
17251 var yes = Ext.SplitBar;
\r
17252 switch(s.placement){
\r
17254 s.el.setX(s.resizingEl.getRight());
\r
17257 s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");
\r
17260 s.el.setY(s.resizingEl.getBottom());
\r
17263 s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
\r
17270 * Orientation constant - Create a vertical SplitBar
\r
17274 Ext.SplitBar.VERTICAL = 1;
\r
17277 * Orientation constant - Create a horizontal SplitBar
\r
17281 Ext.SplitBar.HORIZONTAL = 2;
\r
17284 * Placement constant - The resizing element is to the left of the splitter element
\r
17288 Ext.SplitBar.LEFT = 1;
\r
17291 * Placement constant - The resizing element is to the right of the splitter element
\r
17295 Ext.SplitBar.RIGHT = 2;
\r
17298 * Placement constant - The resizing element is positioned above the splitter element
\r
17302 Ext.SplitBar.TOP = 3;
\r
17305 * Placement constant - The resizing element is positioned under splitter element
\r
17309 Ext.SplitBar.BOTTOM = 4;
\r
17311 * @class Ext.Container
\r
17312 * @extends Ext.BoxComponent
\r
17313 * <p>Base class for any {@link Ext.BoxComponent} that may contain other Components. Containers handle the
\r
17314 * basic behavior of containing items, namely adding, inserting and removing items.</p>
\r
17316 * <p>The most commonly used Container classes are {@link Ext.Panel}, {@link Ext.Window} and {@link Ext.TabPanel}.
\r
17317 * If you do not need the capabilities offered by the aforementioned classes you can create a lightweight
\r
17318 * Container to be encapsulated by an HTML element to your specifications by using the
\r
17319 * <tt><b>{@link Ext.Component#autoEl autoEl}</b></tt> config option. This is a useful technique when creating
\r
17320 * embedded {@link Ext.layout.ColumnLayout column} layouts inside {@link Ext.form.FormPanel FormPanels}
\r
17321 * for example.</p>
\r
17323 * <p>The code below illustrates both how to explicitly create a Container, and how to implicitly
\r
17324 * create one using the <b><tt>'container'</tt></b> xtype:<pre><code>
\r
17325 // explicitly create a Container
\r
17326 var embeddedColumns = new Ext.Container({
\r
17327 autoEl: 'div', // This is the default
\r
17328 layout: 'column',
\r
17330 // implicitly create Container by specifying xtype
\r
17331 xtype: 'container',
\r
17332 autoEl: 'div', // This is the default.
\r
17334 columnWidth: 0.5,
\r
17339 // The two items below will be Ext.Containers, each encapsulated by a <DIV> element.
\r
17342 xtype: 'datefield',
\r
17343 name: 'startDate',
\r
17344 fieldLabel: 'Start date'
\r
17348 xtype: 'datefield',
\r
17350 fieldLabel: 'End date'
\r
17353 });</code></pre></p>
\r
17355 * <p><u><b>Layout</b></u></p>
\r
17356 * <p>Container classes delegate the rendering of child Components to a layout
\r
17357 * manager class which must be configured into the Container using the
\r
17358 * <code><b>{@link #layout}</b></code> configuration property.</p>
\r
17359 * <p>When either specifying child <code>{@link #items}</code> of a Container,
\r
17360 * or dynamically {@link #add adding} Components to a Container, remember to
\r
17361 * consider how you wish the Container to arrange those child elements, and
\r
17362 * whether those child elements need to be sized using one of Ext's built-in
\r
17363 * <b><code>{@link #layout}</code></b> schemes. By default, Containers use the
\r
17364 * {@link Ext.layout.ContainerLayout ContainerLayout} scheme which only
\r
17365 * renders child components, appending them one after the other inside the
\r
17366 * Container, and <b>does not apply any sizing</b> at all.</p>
\r
17367 * <p>A common mistake is when a developer neglects to specify a
\r
17368 * <b><code>{@link #layout}</code></b> (e.g. widgets like GridPanels or
\r
17369 * TreePanels are added to Containers for which no <tt><b>{@link #layout}</b></tt>
\r
17370 * has been specified). If a Container is left to use the default
\r
17371 * {@link Ext.layout.ContainerLayout ContainerLayout} scheme, none of its
\r
17372 * child components will be resized, or changed in any way when the Container
\r
17373 * is resized.</p>
\r
17374 * <p>Certain layout managers allow dynamic addition of child components.
\r
17375 * Those that do include {@link Ext.layout.CardLayout},
\r
17376 * {@link Ext.layout.AnchorLayout}, {@link Ext.layout.FormLayout}, and
\r
17377 * {@link Ext.layout.TableLayout}. For example:<pre><code>
\r
17378 // Create the GridPanel.
\r
17379 var myNewGrid = new Ext.grid.GridPanel({
\r
17381 columns: myColumnModel,
\r
17382 title: 'Results', // the title becomes the title of the tab
\r
17385 myTabPanel.add(myNewGrid); // {@link Ext.TabPanel} implicitly uses {@link Ext.layout.CardLayout CardLayout}
\r
17386 myTabPanel.{@link Ext.TabPanel#setActiveTab setActiveTab}(myNewGrid);
\r
17387 * </code></pre></p>
\r
17388 * <p>The example above adds a newly created GridPanel to a TabPanel. Note that
\r
17389 * a TabPanel uses {@link Ext.layout.CardLayout} as its layout manager which
\r
17390 * means all its child items are sized to {@link Ext.layout.FitLayout fit}
\r
17391 * exactly into its client area.
\r
17392 * <p><b><u>Overnesting is a common problem</u></b>.
\r
17393 * An example of overnesting occurs when a GridPanel is added to a TabPanel
\r
17394 * by wrapping the GridPanel <i>inside</i> a wrapping Panel (that has no
\r
17395 * <tt><b>{@link #layout}</b></tt> specified) and then add that wrapping Panel
\r
17396 * to the TabPanel. The point to realize is that a GridPanel <b>is</b> a
\r
17397 * Component which can be added directly to a Container. If the wrapping Panel
\r
17398 * has no <tt><b>{@link #layout}</b></tt> configuration, then the overnested
\r
17399 * GridPanel will not be sized as expected.<p>
\r
17401 * <p><u><b>Adding via remote configuration</b></u></p>
\r
17403 * <p>A server side script can be used to add Components which are generated dynamically on the server.
\r
17404 * An example of adding a GridPanel to a TabPanel where the GridPanel is generated by the server
\r
17405 * based on certain parameters:
\r
17406 * </p><pre><code>
\r
17407 // execute an Ajax request to invoke server side script:
\r
17408 Ext.Ajax.request({
\r
17409 url: 'gen-invoice-grid.php',
\r
17410 // send additional parameters to instruct server script
\r
17412 startDate: Ext.getCmp('start-date').getValue(),
\r
17413 endDate: Ext.getCmp('end-date').getValue()
\r
17415 // process the response object to add it to the TabPanel:
\r
17416 success: function(xhr) {
\r
17417 var newComponent = eval(xhr.responseText); // see discussion below
\r
17418 myTabPanel.add(newComponent); // add the component to the TabPanel
\r
17419 myTabPanel.setActiveTab(newComponent);
\r
17421 failure: function() {
\r
17422 Ext.Msg.alert("Grid create failed", "Server communication failure");
\r
17426 * <p>The server script needs to return an executable Javascript statement which, when processed
\r
17427 * using <tt>eval()</tt>, will return either a config object with an {@link Ext.Component#xtype xtype},
\r
17428 * or an instantiated Component. The server might return this for example:</p><pre><code>
\r
17430 function formatDate(value){
\r
17431 return value ? value.dateFormat('M d, Y') : '';
\r
17434 var store = new Ext.data.Store({
\r
17435 url: 'get-invoice-data.php',
\r
17437 startDate: '01/01/2008',
\r
17438 endDate: '01/31/2008'
\r
17440 reader: new Ext.data.JsonReader({
\r
17441 record: 'transaction',
\r
17442 idProperty: 'id',
\r
17443 totalRecords: 'total'
\r
17447 {name: 'date', type: 'date', dateFormat: 'm/d/Y'},
\r
17448 {name: 'value', type: 'float'}
\r
17452 var grid = new Ext.grid.GridPanel({
\r
17453 title: 'Invoice Report',
\r
17454 bbar: new Ext.PagingToolbar(store),
\r
17457 {header: "Customer", width: 250, dataIndex: 'customer', sortable: true},
\r
17458 {header: "Invoice Number", width: 120, dataIndex: 'invNo', sortable: true},
\r
17459 {header: "Invoice Date", width: 100, dataIndex: 'date', renderer: formatDate, sortable: true},
\r
17460 {header: "Value", width: 120, dataIndex: 'value', renderer: 'usMoney', sortable: true}
\r
17464 return grid; // return instantiated component
\r
17467 * <p>When the above code fragment is passed through the <tt>eval</tt> function in the success handler
\r
17468 * of the Ajax request, the code is executed by the Javascript processor, and the anonymous function
\r
17469 * runs, and returns the instantiated grid component.</p>
\r
17470 * <p>Note: since the code above is <i>generated</i> by a server script, the <tt>baseParams</tt> for
\r
17471 * the Store, the metadata to allow generation of the Record layout, and the ColumnModel
\r
17472 * can all be generated into the code since these are all known on the server.</p>
\r
17474 * @xtype container
\r
17476 Ext.Container = Ext.extend(Ext.BoxComponent, {
\r
17478 * @cfg {Boolean} monitorResize
\r
17479 * True to automatically monitor window resize events to handle anything that is sensitive to the current size
\r
17480 * of the viewport. This value is typically managed by the chosen <code>{@link #layout}</code> and should not need
\r
17481 * to be set manually.
\r
17484 * @cfg {String/Object} layout
\r
17485 * <p><b>*Important</b>: In order for child items to be correctly sized and
\r
17486 * positioned, typically a layout manager <b>must</b> be specified through
\r
17487 * the <code>layout</code> configuration option.</p>
\r
17488 * <br><p>The sizing and positioning of child {@link items} is the responsibility of
\r
17489 * the Container's layout manager which creates and manages the type of layout
\r
17490 * you have in mind. For example:</p><pre><code>
\r
17492 width:300, height: 300,
\r
17493 layout: 'fit', // explicitly set layout manager: override the default (layout:'auto')
\r
17495 title: 'Panel inside a Window'
\r
17499 * <p>If the {@link #layout} configuration is not explicitly specified for
\r
17500 * a general purpose container (e.g. Container or Panel) the
\r
17501 * {@link Ext.layout.ContainerLayout default layout manager} will be used
\r
17502 * which does nothing but render child components sequentially into the
\r
17503 * Container (no sizing or positioning will be performed in this situation).
\r
17504 * Some container classes implicitly specify a default layout
\r
17505 * (e.g. FormPanel specifies <code>layout:'form'</code>). Other specific
\r
17506 * purpose classes internally specify/manage their internal layout (e.g.
\r
17507 * GridPanel, TabPanel, TreePanel, Toolbar, Menu, etc.).</p>
\r
17508 * <br><p><b><code>layout</code></b> may be specified as either as an Object or
\r
17509 * as a String:</p><div><ul class="mdetail-params">
\r
17511 * <li><u>Specify as an Object</u></li>
\r
17512 * <div><ul class="mdetail-params">
\r
17513 * <li>Example usage:</li>
\r
17522 * <li><tt><b>type</b></tt></li>
\r
17523 * <br/><p>The layout type to be used for this container. If not specified,
\r
17524 * a default {@link Ext.layout.ContainerLayout} will be created and used.</p>
\r
17525 * <br/><p>Valid layout <tt>type</tt> values are:</p>
\r
17526 * <div class="sub-desc"><ul class="mdetail-params">
\r
17527 * <li><tt><b>{@link Ext.layout.AbsoluteLayout absolute}</b></tt></li>
\r
17528 * <li><tt><b>{@link Ext.layout.AccordionLayout accordion}</b></tt></li>
\r
17529 * <li><tt><b>{@link Ext.layout.AnchorLayout anchor}</b></tt></li>
\r
17530 * <li><tt><b>{@link Ext.layout.ContainerLayout auto}</b></tt> <b>Default</b></li>
\r
17531 * <li><tt><b>{@link Ext.layout.BorderLayout border}</b></tt></li>
\r
17532 * <li><tt><b>{@link Ext.layout.CardLayout card}</b></tt></li>
\r
17533 * <li><tt><b>{@link Ext.layout.ColumnLayout column}</b></tt></li>
\r
17534 * <li><tt><b>{@link Ext.layout.FitLayout fit}</b></tt></li>
\r
17535 * <li><tt><b>{@link Ext.layout.FormLayout form}</b></tt></li>
\r
17536 * <li><tt><b>{@link Ext.layout.HBoxLayout hbox}</b></tt></li>
\r
17537 * <li><tt><b>{@link Ext.layout.MenuLayout menu}</b></tt></li>
\r
17538 * <li><tt><b>{@link Ext.layout.TableLayout table}</b></tt></li>
\r
17539 * <li><tt><b>{@link Ext.layout.ToolbarLayout toolbar}</b></tt></li>
\r
17540 * <li><tt><b>{@link Ext.layout.VBoxLayout vbox}</b></tt></li>
\r
17543 * <li>Layout specific configuration properties</li>
\r
17544 * <br/><p>Additional layout specific configuration properties may also be
\r
17545 * specified. For complete details regarding the valid config options for
\r
17546 * each layout type, see the layout class corresponding to the <tt>type</tt>
\r
17551 * <li><u>Specify as a String</u></li>
\r
17552 * <div><ul class="mdetail-params">
\r
17553 * <li>Example usage:</li>
\r
17561 * <li><tt><b>layout</b></tt></li>
\r
17562 * <br/><p>The layout <tt>type</tt> to be used for this container (see list
\r
17563 * of valid layout type values above).</p><br/>
\r
17564 * <li><tt><b>{@link #layoutConfig}</b></tt></li>
\r
17565 * <br/><p>Additional layout specific configuration properties. For complete
\r
17566 * details regarding the valid config options for each layout type, see the
\r
17567 * layout class corresponding to the <tt>layout</tt> specified.</p>
\r
17568 * </ul></div></ul></div>
\r
17571 * @cfg {Object} layoutConfig
\r
17572 * This is a config object containing properties specific to the chosen
\r
17573 * <b><code>{@link #layout}</code></b> if <b><code>{@link #layout}</code></b>
\r
17574 * has been specified as a <i>string</i>.</p>
\r
17577 * @cfg {Boolean/Number} bufferResize
\r
17578 * When set to true (50 milliseconds) or a number of milliseconds, the layout assigned for this container will buffer
\r
17579 * the frequency it calculates and does a re-layout of components. This is useful for heavy containers or containers
\r
17580 * with a large quantity of sub-components for which frequent layout calls would be expensive. Defaults to <tt>50</tt>.
\r
17582 bufferResize: 50,
\r
17585 * @cfg {String/Number} activeItem
\r
17586 * A string component id or the numeric index of the component that should be initially activated within the
\r
17587 * container's layout on render. For example, activeItem: 'item-1' or activeItem: 0 (index 0 = the first
\r
17588 * item in the container's collection). activeItem only applies to layout styles that can display
\r
17589 * items one at a time (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout} and
\r
17590 * {@link Ext.layout.FitLayout}). Related to {@link Ext.layout.ContainerLayout#activeItem}.
\r
17593 * @cfg {Object/Array} items
\r
17594 * <pre><b>** IMPORTANT</b>: be sure to <b>{@link #layout specify a <code>layout</code>} if needed ! **</b></pre>
\r
17595 * <p>A single item, or an array of child Components to be added to this container,
\r
17596 * for example:</p>
\r
17598 // specifying a single item
\r
17600 layout: 'fit', // specify a layout!
\r
17602 // specifying multiple items
\r
17603 items: [{...}, {...}],
\r
17604 layout: 'anchor', // specify a layout!
\r
17606 * <p>Each item may be:</p>
\r
17607 * <div><ul class="mdetail-params">
\r
17608 * <li>any type of object based on {@link Ext.Component}</li>
\r
17609 * <li>a fully instanciated object or</li>
\r
17610 * <li>an object literal that:</li>
\r
17611 * <div><ul class="mdetail-params">
\r
17612 * <li>has a specified <code>{@link Ext.Component#xtype xtype}</code></li>
\r
17613 * <li>the {@link Ext.Component#xtype} specified is associated with the Component
\r
17614 * desired and should be chosen from one of the available xtypes as listed
\r
17615 * in {@link Ext.Component}.</li>
\r
17616 * <li>If an <code>{@link Ext.Component#xtype xtype}</code> is not explicitly
\r
17617 * specified, the {@link #defaultType} for that Container is used.</li>
\r
17618 * <li>will be "lazily instanciated", avoiding the overhead of constructing a fully
\r
17619 * instanciated Component object</li>
\r
17620 * </ul></div></ul></div>
\r
17621 * <p><b>Notes</b>:</p>
\r
17622 * <div><ul class="mdetail-params">
\r
17623 * <li>Ext uses lazy rendering. Child Components will only be rendered
\r
17624 * should it become necessary. Items are automatically laid out when they are first
\r
17625 * shown (no sizing is done while hidden), or in response to a {@link #doLayout} call.</li>
\r
17626 * <li>Do not specify <code>{@link Ext.Panel#contentEl contentEl}</code>/
\r
17627 * <code>{@link Ext.Panel#html html}</code> with <code>items</code>.</li>
\r
17631 * @cfg {Object} defaults
\r
17632 * <p>A config object that will be applied to all components added to this container either via the {@link #items}
\r
17633 * config or via the {@link #add} or {@link #insert} methods. The <tt>defaults</tt> config can contain any
\r
17634 * number of name/value property pairs to be added to each item, and should be valid for the types of items
\r
17635 * being added to the container. For example, to automatically apply padding to the body of each of a set of
\r
17636 * contained {@link Ext.Panel} items, you could pass: <tt>defaults: {bodyStyle:'padding:15px'}</tt>.</p><br/>
\r
17637 * <p><b>Note</b>: <tt>defaults</tt> will not be applied to config objects if the option is already specified.
\r
17638 * For example:</p><pre><code>
\r
17639 defaults: { // defaults are applied to items, not the container
\r
17644 xtype: 'panel', // defaults <b>do not</b> have precedence over
\r
17645 id: 'panel1', // options in config objects, so the defaults
\r
17646 autoScroll: false // will not be applied here, panel1 will be autoScroll:false
\r
17648 new Ext.Panel({ // defaults <b>do</b> have precedence over options
\r
17649 id: 'panel2', // options in components, so the defaults
\r
17650 autoScroll: false // will be applied here, panel2 will be autoScroll:true.
\r
17657 /** @cfg {Boolean} autoDestroy
\r
17658 * If true the container will automatically destroy any contained component that is removed from it, else
\r
17659 * destruction must be handled manually (defaults to true).
\r
17661 autoDestroy : true,
\r
17663 /** @cfg {Boolean} forceLayout
\r
17664 * If true the container will force a layout initially even if hidden or collapsed. This option
\r
17665 * is useful for forcing forms to render in collapsed or hidden containers. (defaults to false).
\r
17667 forceLayout: false,
\r
17669 /** @cfg {Boolean} hideBorders
\r
17670 * True to hide the borders of each contained component, false to defer to the component's existing
\r
17671 * border settings (defaults to false).
\r
17673 /** @cfg {String} defaultType
\r
17674 * <p>The default {@link Ext.Component xtype} of child Components to create in this Container when
\r
17675 * a child item is specified as a raw configuration object, rather than as an instantiated Component.</p>
\r
17676 * <p>Defaults to <tt>'panel'</tt>, except {@link Ext.menu.Menu} which defaults to <tt>'menuitem'</tt>,
\r
17677 * and {@link Ext.Toolbar} and {@link Ext.ButtonGroup} which default to <tt>'button'</tt>.</p>
\r
17679 defaultType : 'panel',
\r
17681 /** @cfg {String} resizeEvent
\r
17682 * The event to listen to for resizing in layouts. Defaults to <tt>'resize'</tt>.
\r
17684 resizeEvent: 'resize',
\r
17687 * @cfg {Array} bubbleEvents
\r
17688 * <p>An array of events that, when fired, should be bubbled to any parent container.
\r
17689 * Defaults to <tt>['add', 'remove']</tt>.
\r
17691 bubbleEvents: ['add', 'remove'],
\r
17694 initComponent : function(){
\r
17695 Ext.Container.superclass.initComponent.call(this);
\r
17699 * @event afterlayout
\r
17700 * Fires when the components in this container are arranged by the associated layout manager.
\r
17701 * @param {Ext.Container} this
\r
17702 * @param {ContainerLayout} layout The ContainerLayout implementation for this container
\r
17706 * @event beforeadd
\r
17707 * Fires before any {@link Ext.Component} is added or inserted into the container.
\r
17708 * A handler can return false to cancel the add.
\r
17709 * @param {Ext.Container} this
\r
17710 * @param {Ext.Component} component The component being added
\r
17711 * @param {Number} index The index at which the component will be added to the container's items collection
\r
17715 * @event beforeremove
\r
17716 * Fires before any {@link Ext.Component} is removed from the container. A handler can return
\r
17717 * false to cancel the remove.
\r
17718 * @param {Ext.Container} this
\r
17719 * @param {Ext.Component} component The component being removed
\r
17725 * Fires after any {@link Ext.Component} is added or inserted into the container.
\r
17726 * @param {Ext.Container} this
\r
17727 * @param {Ext.Component} component The component that was added
\r
17728 * @param {Number} index The index at which the component was added to the container's items collection
\r
17734 * Fires after any {@link Ext.Component} is removed from the container.
\r
17735 * @param {Ext.Container} this
\r
17736 * @param {Ext.Component} component The component that was removed
\r
17741 this.enableBubble(this.bubbleEvents);
\r
17744 * The collection of components in this container as a {@link Ext.util.MixedCollection}
\r
17745 * @type MixedCollection
\r
17746 * @property items
\r
17748 var items = this.items;
\r
17750 delete this.items;
\r
17756 initItems : function(){
\r
17758 this.items = new Ext.util.MixedCollection(false, this.getComponentId);
\r
17759 this.getLayout(); // initialize the layout
\r
17764 setLayout : function(layout){
\r
17765 if(this.layout && this.layout != layout){
\r
17766 this.layout.setContainer(null);
\r
17768 this.initItems();
\r
17769 this.layout = layout;
\r
17770 layout.setContainer(this);
\r
17773 afterRender: function(){
\r
17774 Ext.Container.superclass.afterRender.call(this);
\r
17775 if(!this.layout){
\r
17776 this.layout = 'auto';
\r
17778 if(Ext.isObject(this.layout) && !this.layout.layout){
\r
17779 this.layoutConfig = this.layout;
\r
17780 this.layout = this.layoutConfig.type;
\r
17782 if(Ext.isString(this.layout)){
\r
17783 this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);
\r
17785 this.setLayout(this.layout);
\r
17787 if(this.activeItem !== undefined){
\r
17788 var item = this.activeItem;
\r
17789 delete this.activeItem;
\r
17790 this.layout.setActiveItem(item);
\r
17792 if(!this.ownerCt){
\r
17793 // force a layout if no ownerCt is set
\r
17794 this.doLayout(false, true);
\r
17796 if(this.monitorResize === true){
\r
17797 Ext.EventManager.onWindowResize(this.doLayout, this, [false]);
\r
17802 * <p>Returns the Element to be used to contain the child Components of this Container.</p>
\r
17803 * <p>An implementation is provided which returns the Container's {@link #getEl Element}, but
\r
17804 * if there is a more complex structure to a Container, this may be overridden to return
\r
17805 * the element into which the {@link #layout layout} renders child Components.</p>
\r
17806 * @return {Ext.Element} The Element to render child Components into.
\r
17808 getLayoutTarget : function(){
\r
17812 // private - used as the key lookup function for the items collection
\r
17813 getComponentId : function(comp){
\r
17814 return comp.getItemId();
\r
17818 * <p>Adds {@link Ext.Component Component}(s) to this Container.</p>
\r
17819 * <br><p><b>Description</b></u> :
\r
17820 * <div><ul class="mdetail-params">
\r
17821 * <li>Fires the {@link #beforeadd} event before adding</li>
\r
17822 * <li>The Container's {@link #defaults default config values} will be applied
\r
17823 * accordingly (see <code>{@link #defaults}</code> for details).</li>
\r
17824 * <li>Fires the {@link #add} event after the component has been added.</li>
\r
17826 * <br><p><b>Notes</b></u> :
\r
17827 * <div><ul class="mdetail-params">
\r
17828 * <li>If the Container is <i>already rendered</i> when <tt>add</tt>
\r
17829 * is called, you may need to call {@link #doLayout} to refresh the view which causes
\r
17830 * any unrendered child Components to be rendered. This is required so that you can
\r
17831 * <tt>add</tt> multiple child components if needed while only refreshing the layout
\r
17832 * once. For example:<pre><code>
\r
17833 var tb = new {@link Ext.Toolbar}();
\r
17834 tb.render(document.body); // toolbar is rendered
\r
17835 tb.add({text:'Button 1'}); // add multiple items ({@link #defaultType} for {@link Ext.Toolbar Toolbar} is 'button')
\r
17836 tb.add({text:'Button 2'});
\r
17837 tb.{@link #doLayout}(); // refresh the layout
\r
17838 * </code></pre></li>
\r
17839 * <li><i>Warning:</i> Containers directly managed by the BorderLayout layout manager
\r
17840 * may not be removed or added. See the Notes for {@link Ext.layout.BorderLayout BorderLayout}
\r
17841 * for more details.</li>
\r
17843 * @param {Object/Array} component
\r
17844 * <p>Either a single component or an Array of components to add. See
\r
17845 * <code>{@link #items}</code> for additional information.</p>
\r
17846 * @param {Object} (Optional) component_2
\r
17847 * @param {Object} (Optional) component_n
\r
17848 * @return {Ext.Component} component The Component (or config object) that was added.
\r
17850 add : function(comp){
\r
17851 this.initItems();
\r
17852 var args = arguments.length > 1;
\r
17853 if(args || Ext.isArray(comp)){
\r
17854 Ext.each(args ? arguments : comp, function(c){
\r
17859 var c = this.lookupComponent(this.applyDefaults(comp));
\r
17860 var pos = this.items.length;
\r
17861 if(this.fireEvent('beforeadd', this, c, pos) !== false && this.onBeforeAdd(c) !== false){
\r
17862 this.items.add(c);
\r
17863 c.ownerCt = this;
\r
17865 this.fireEvent('add', this, c, pos);
\r
17870 onAdd : function(c){
\r
17871 // Empty template method
\r
17875 * Inserts a Component into this Container at a specified index. Fires the
\r
17876 * {@link #beforeadd} event before inserting, then fires the {@link #add} event after the
\r
17877 * Component has been inserted.
\r
17878 * @param {Number} index The index at which the Component will be inserted
\r
17879 * into the Container's items collection
\r
17880 * @param {Ext.Component} component The child Component to insert.<br><br>
\r
17881 * Ext uses lazy rendering, and will only render the inserted Component should
\r
17882 * it become necessary.<br><br>
\r
17883 * A Component config object may be passed in order to avoid the overhead of
\r
17884 * constructing a real Component object if lazy rendering might mean that the
\r
17885 * inserted Component will not be rendered immediately. To take advantage of
\r
17886 * this 'lazy instantiation', set the {@link Ext.Component#xtype} config
\r
17887 * property to the registered type of the Component wanted.<br><br>
\r
17888 * For a list of all available xtypes, see {@link Ext.Component}.
\r
17889 * @return {Ext.Component} component The Component (or config object) that was
\r
17890 * inserted with the Container's default config values applied.
\r
17892 insert : function(index, comp){
\r
17893 this.initItems();
\r
17894 var a = arguments, len = a.length;
\r
17896 for(var i = len-1; i >= 1; --i) {
\r
17897 this.insert(index, a[i]);
\r
17901 var c = this.lookupComponent(this.applyDefaults(comp));
\r
17902 index = Math.min(index, this.items.length);
\r
17903 if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
\r
17904 if(c.ownerCt == this){
\r
17905 this.items.remove(c);
\r
17907 this.items.insert(index, c);
\r
17908 c.ownerCt = this;
\r
17910 this.fireEvent('add', this, c, index);
\r
17916 applyDefaults : function(c){
\r
17917 if(this.defaults){
\r
17918 if(Ext.isString(c)){
\r
17919 c = Ext.ComponentMgr.get(c);
\r
17920 Ext.apply(c, this.defaults);
\r
17921 }else if(!c.events){
\r
17922 Ext.applyIf(c, this.defaults);
\r
17924 Ext.apply(c, this.defaults);
\r
17931 onBeforeAdd : function(item){
\r
17932 if(item.ownerCt){
\r
17933 item.ownerCt.remove(item, false);
\r
17935 if(this.hideBorders === true){
\r
17936 item.border = (item.border === true);
\r
17941 * Removes a component from this container. Fires the {@link #beforeremove} event before removing, then fires
\r
17942 * the {@link #remove} event after the component has been removed.
\r
17943 * @param {Component/String} component The component reference or id to remove.
\r
17944 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
\r
17945 * Defaults to the value of this Container's {@link #autoDestroy} config.
\r
17946 * @return {Ext.Component} component The Component that was removed.
\r
17948 remove : function(comp, autoDestroy){
\r
17949 this.initItems();
\r
17950 var c = this.getComponent(comp);
\r
17951 if(c && this.fireEvent('beforeremove', this, c) !== false){
\r
17952 delete c.ownerCt;
\r
17953 if(this.layout && this.rendered){
\r
17954 this.layout.onRemove(c);
\r
17956 this.onRemove(c);
\r
17957 this.items.remove(c);
\r
17958 if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
\r
17961 this.fireEvent('remove', this, c);
\r
17966 onRemove: function(c){
\r
17967 // Empty template method
\r
17971 * Removes all components from this container.
\r
17972 * @param {Boolean} autoDestroy (optional) True to automatically invoke the removed Component's {@link Ext.Component#destroy} function.
\r
17973 * Defaults to the value of this Container's {@link #autoDestroy} config.
\r
17974 * @return {Array} Array of the destroyed components
\r
17976 removeAll: function(autoDestroy){
\r
17977 this.initItems();
\r
17978 var item, rem = [], items = [];
\r
17979 this.items.each(function(i){
\r
17982 for (var i = 0, len = rem.length; i < len; ++i){
\r
17984 this.remove(item, autoDestroy);
\r
17985 if(item.ownerCt !== this){
\r
17986 items.push(item);
\r
17993 * Examines this container's <code>{@link #items}</code> <b>property</b>
\r
17994 * and gets a direct child component of this container.
\r
17995 * @param {String/Number} comp This parameter may be any of the following:
\r
17996 * <div><ul class="mdetail-params">
\r
17997 * <li>a <b><tt>String</tt></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
\r
17998 * or <code>{@link Ext.Component#id id}</code> of the child component </li>
\r
17999 * <li>a <b><tt>Number</tt></b> : representing the position of the child component
\r
18000 * within the <code>{@link #items}</code> <b>property</b></li>
\r
18002 * <p>For additional information see {@link Ext.util.MixedCollection#get}.
\r
18003 * @return Ext.Component The component (if found).
\r
18005 getComponent : function(comp){
\r
18006 if(Ext.isObject(comp)){
\r
18007 comp = comp.getItemId();
\r
18009 return this.items.get(comp);
\r
18013 lookupComponent : function(comp){
\r
18014 if(Ext.isString(comp)){
\r
18015 return Ext.ComponentMgr.get(comp);
\r
18016 }else if(!comp.events){
\r
18017 return this.createComponent(comp);
\r
18023 createComponent : function(config){
\r
18024 return Ext.create(config, this.defaultType);
\r
18028 canLayout: function() {
\r
18029 var el = this.getVisibilityEl();
\r
18030 return el && !el.isStyle("display", "none");
\r
18035 * Force this container's layout to be recalculated. A call to this function is required after adding a new component
\r
18036 * to an already rendered container, or possibly after changing sizing/position properties of child components.
\r
18037 * @param {Boolean} shallow (optional) True to only calc the layout of this component, and let child components auto
\r
18038 * calc layouts as required (defaults to false, which calls doLayout recursively for each subcontainer)
\r
18039 * @param {Boolean} force (optional) True to force a layout to occur, even if the item is hidden.
\r
18040 * @return {Ext.Container} this
\r
18042 doLayout: function(shallow, force){
\r
18043 var rendered = this.rendered;
\r
18044 forceLayout = force || this.forceLayout;
\r
18046 if(!this.canLayout() || this.collapsed){
\r
18047 this.deferLayout = this.deferLayout || !shallow;
\r
18048 if(!forceLayout){
\r
18051 shallow = shallow && !this.deferLayout;
\r
18053 delete this.deferLayout;
\r
18055 if(rendered && this.layout){
\r
18056 this.layout.layout();
\r
18058 if(shallow !== true && this.items){
\r
18059 var cs = this.items.items;
\r
18060 for(var i = 0, len = cs.length; i < len; i++){
\r
18063 c.doLayout(false, forceLayout);
\r
18068 this.onLayout(shallow, forceLayout);
\r
18070 // Initial layout completed
\r
18071 this.hasLayout = true;
\r
18072 delete this.forceLayout;
\r
18076 onLayout : Ext.emptyFn,
\r
18079 shouldBufferLayout: function(){
\r
18081 * Returns true if the container should buffer a layout.
\r
18082 * This is true only if the container has previously been laid out
\r
18083 * and has a parent container that is pending a layout.
\r
18085 var hl = this.hasLayout;
\r
18086 if(this.ownerCt){
\r
18087 // Only ever buffer if we've laid out the first time and we have one pending.
\r
18088 return hl ? !this.hasLayoutPending() : false;
\r
18090 // Never buffer initial layout
\r
18095 hasLayoutPending: function(){
\r
18096 // Traverse hierarchy to see if any parent container has a pending layout.
\r
18097 var pending = false;
\r
18098 this.ownerCt.bubble(function(c){
\r
18099 if(c.layoutPending){
\r
18107 onShow : function(){
\r
18108 Ext.Container.superclass.onShow.call(this);
\r
18109 if(this.deferLayout !== undefined){
\r
18110 this.doLayout(true);
\r
18115 * Returns the layout currently in use by the container. If the container does not currently have a layout
\r
18116 * set, a default {@link Ext.layout.ContainerLayout} will be created and set as the container's layout.
\r
18117 * @return {ContainerLayout} layout The container's layout
\r
18119 getLayout : function(){
\r
18120 if(!this.layout){
\r
18121 var layout = new Ext.layout.ContainerLayout(this.layoutConfig);
\r
18122 this.setLayout(layout);
\r
18124 return this.layout;
\r
18128 beforeDestroy : function(){
\r
18130 Ext.destroy.apply(Ext, this.items.items);
\r
18132 if(this.monitorResize){
\r
18133 Ext.EventManager.removeResizeListener(this.doLayout, this);
\r
18135 Ext.destroy(this.layout);
\r
18136 Ext.Container.superclass.beforeDestroy.call(this);
\r
18140 * Bubbles up the component/container heirarchy, calling the specified function with each component. The scope (<i>this</i>) of
\r
18141 * function call will be the scope provided or the current component. The arguments to the function
\r
18142 * will be the args provided or the current component. If the function returns false at any point,
\r
18143 * the bubble is stopped.
\r
18144 * @param {Function} fn The function to call
\r
18145 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
18146 * @param {Array} args (optional) The args to call the function with (default to passing the current component)
\r
18147 * @return {Ext.Container} this
\r
18149 bubble : function(fn, scope, args){
\r
18152 if(fn.apply(scope || p, args || [p]) === false){
\r
18161 * Cascades down the component/container heirarchy from this component (called first), calling the specified function with
\r
18162 * each component. The scope (<i>this</i>) of
\r
18163 * function call will be the scope provided or the current component. The arguments to the function
\r
18164 * will be the args provided or the current component. If the function returns false at any point,
\r
18165 * the cascade is stopped on that branch.
\r
18166 * @param {Function} fn The function to call
\r
18167 * @param {Object} scope (optional) The scope of the function (defaults to current component)
\r
18168 * @param {Array} args (optional) The args to call the function with (defaults to passing the current component)
\r
18169 * @return {Ext.Container} this
\r
18171 cascade : function(fn, scope, args){
\r
18172 if(fn.apply(scope || this, args || [this]) !== false){
\r
18174 var cs = this.items.items;
\r
18175 for(var i = 0, len = cs.length; i < len; i++){
\r
18176 if(cs[i].cascade){
\r
18177 cs[i].cascade(fn, scope, args);
\r
18179 fn.apply(scope || cs[i], args || [cs[i]]);
\r
18188 * Find a component under this container at any level by id
\r
18189 * @param {String} id
\r
18190 * @return Ext.Component
\r
18192 findById : function(id){
\r
18193 var m, ct = this;
\r
18194 this.cascade(function(c){
\r
18195 if(ct != c && c.id === id){
\r
18200 return m || null;
\r
18204 * Find a component under this container at any level by xtype or class
\r
18205 * @param {String/Class} xtype The xtype string for a component, or the class of the component directly
\r
18206 * @param {Boolean} shallow (optional) False to check whether this Component is descended from the xtype (this is
\r
18207 * the default), or true to check whether this Component is directly of the specified xtype.
\r
18208 * @return {Array} Array of Ext.Components
\r
18210 findByType : function(xtype, shallow){
\r
18211 return this.findBy(function(c){
\r
18212 return c.isXType(xtype, shallow);
\r
18217 * Find a component under this container at any level by property
\r
18218 * @param {String} prop
\r
18219 * @param {String} value
\r
18220 * @return {Array} Array of Ext.Components
\r
18222 find : function(prop, value){
\r
18223 return this.findBy(function(c){
\r
18224 return c[prop] === value;
\r
18229 * Find a component under this container at any level by a custom function. If the passed function returns
\r
18230 * true, the component will be included in the results. The passed function is called with the arguments (component, this container).
\r
18231 * @param {Function} fn The function to call
\r
18232 * @param {Object} scope (optional)
\r
18233 * @return {Array} Array of Ext.Components
\r
18235 findBy : function(fn, scope){
\r
18236 var m = [], ct = this;
\r
18237 this.cascade(function(c){
\r
18238 if(ct != c && fn.call(scope || c, c, ct) === true){
\r
18246 * Get a component contained by this container (alias for items.get(key))
\r
18247 * @param {String/Number} key The index or id of the component
\r
18248 * @return {Ext.Component} Ext.Component
\r
18250 get : function(key){
\r
18251 return this.items.get(key);
\r
18255 Ext.Container.LAYOUTS = {};
\r
18256 Ext.reg('container', Ext.Container);
\r
18258 * @class Ext.layout.ContainerLayout
18259 * <p>The ContainerLayout class is the default layout manager delegated by {@link Ext.Container} to
18260 * render any child Components when no <tt>{@link Ext.Container#layout layout}</tt> is configured into
18261 * a {@link Ext.Container Container}. ContainerLayout provides the basic foundation for all other layout
18262 * classes in Ext. It simply renders all child Components into the Container, performing no sizing or
18263 * positioning services. To utilize a layout that provides sizing and positioning of child Components,
18264 * specify an appropriate <tt>{@link Ext.Container#layout layout}</tt>.</p>
18265 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
18266 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
18268 Ext.layout.ContainerLayout = function(config){
18269 Ext.apply(this, config);
18272 Ext.layout.ContainerLayout.prototype = {
18274 * @cfg {String} extraCls
18275 * <p>An optional extra CSS class that will be added to the container. This can be useful for adding
18276 * customized styles to the container or any of its children using standard CSS rules. See
18277 * {@link Ext.Component}.{@link Ext.Component#ctCls ctCls} also.</p>
18278 * <p><b>Note</b>: <tt>extraCls</tt> defaults to <tt>''</tt> except for the following classes
18279 * which assign a value by default:
18280 * <div class="mdetail-params"><ul>
18281 * <li>{@link Ext.layout.AbsoluteLayout Absolute Layout} : <tt>'x-abs-layout-item'</tt></li>
18282 * <li>{@link Ext.layout.Box Box Layout} : <tt>'x-box-item'</tt></li>
18283 * <li>{@link Ext.layout.ColumnLayout Column Layout} : <tt>'x-column'</tt></li>
18285 * To configure the above Classes with an extra CSS class append to the default. For example,
18286 * for ColumnLayout:<pre><code>
18287 * extraCls: 'x-column custom-class'
18292 * @cfg {Boolean} renderHidden
18293 * True to hide each contained item on render (defaults to false).
18297 * A reference to the {@link Ext.Component} that is active. For example, <pre><code>
18298 * if(myPanel.layout.activeItem.id == 'item-1') { ... }
18300 * <tt>activeItem</tt> only applies to layout styles that can display items one at a time
18301 * (like {@link Ext.layout.AccordionLayout}, {@link Ext.layout.CardLayout}
18302 * and {@link Ext.layout.FitLayout}). Read-only. Related to {@link Ext.Container#activeItem}.
18303 * @type {Ext.Component}
18304 * @property activeItem
18308 monitorResize:false,
18313 layout : function(){
18314 var target = this.container.getLayoutTarget();
18315 this.onLayout(this.container, target);
18316 this.container.fireEvent('afterlayout', this.container, this);
18320 onLayout : function(ct, target){
18321 this.renderAll(ct, target);
18325 isValidParent : function(c, target){
18326 return target && c.getDomPositionEl().dom.parentNode == (target.dom || target);
18330 renderAll : function(ct, target){
18331 var items = ct.items.items;
18332 for(var i = 0, len = items.length; i < len; i++) {
18334 if(c && (!c.rendered || !this.isValidParent(c, target))){
18335 this.renderItem(c, i, target);
18341 renderItem : function(c, position, target){
18342 if(c && !c.rendered){
18343 c.render(target, position);
18344 this.configureItem(c, position);
18345 }else if(c && !this.isValidParent(c, target)){
18346 if(Ext.isNumber(position)){
18347 position = target.dom.childNodes[position];
18349 target.dom.insertBefore(c.getDomPositionEl().dom, position || null);
18350 c.container = target;
18351 this.configureItem(c, position);
18356 configureItem: function(c, position){
18358 var t = c.getPositionEl ? c.getPositionEl() : c;
18359 t.addClass(this.extraCls);
18361 if (this.renderHidden && c != this.activeItem) {
18364 if(c.doLayout && this.forceLayout){
18365 c.doLayout(false, true);
18369 onRemove: function(c){
18370 if(this.activeItem == c){
18371 delete this.activeItem;
18373 if(c.rendered && this.extraCls){
18374 var t = c.getPositionEl ? c.getPositionEl() : c;
18375 t.removeClass(this.extraCls);
18380 onResize: function(){
18381 var ct = this.container,
18387 if(b = ct.bufferResize){
18388 // Only allow if we should buffer the layout
18389 if(ct.shouldBufferLayout()){
18390 if(!this.resizeTask){
18391 this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this);
18392 this.resizeBuffer = Ext.isNumber(b) ? b : 50;
18394 ct.layoutPending = true;
18395 this.resizeTask.delay(this.resizeBuffer);
18403 runLayout: function(){
18404 var ct = this.container;
18406 delete ct.layoutPending;
18410 setContainer : function(ct){
18411 if(this.monitorResize && ct != this.container){
18412 var old = this.container;
18414 old.un(old.resizeEvent, this.onResize, this);
18417 ct.on(ct.resizeEvent, this.onResize, this);
18420 this.container = ct;
18424 parseMargins : function(v){
18425 if(Ext.isNumber(v)){
18428 var ms = v.split(' ');
18429 var len = ms.length;
18443 top:parseInt(ms[0], 10) || 0,
18444 right:parseInt(ms[1], 10) || 0,
18445 bottom:parseInt(ms[2], 10) || 0,
18446 left:parseInt(ms[3], 10) || 0
18451 * The {@link Ext.Template Ext.Template} used by Field rendering layout classes (such as
18452 * {@link Ext.layout.FormLayout}) to create the DOM structure of a fully wrapped,
18453 * labeled and styled form Field. A default Template is supplied, but this may be
18454 * overriden to create custom field structures. The template processes values returned from
18455 * {@link Ext.layout.FormLayout#getTemplateArgs}.
18456 * @property fieldTpl
18457 * @type Ext.Template
18459 fieldTpl: (function() {
18460 var t = new Ext.Template(
18461 '<div class="x-form-item {itemCls}" tabIndex="-1">',
18462 '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
18463 '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
18464 '</div><div class="{clearCls}"></div>',
18467 t.disableFormats = true;
18468 return t.compile();
18472 * Destroys this layout. This is a template method that is empty by default, but should be implemented
18473 * by subclasses that require explicit destruction to purge event handlers or remove DOM nodes.
18476 destroy : Ext.emptyFn
18478 Ext.Container.LAYOUTS['auto'] = Ext.layout.ContainerLayout;/**
\r
18479 * @class Ext.layout.FitLayout
\r
18480 * @extends Ext.layout.ContainerLayout
\r
18481 * <p>This is a base class for layouts that contain <b>a single item</b> that automatically expands to fill the layout's
\r
18482 * container. This class is intended to be extended or created via the <tt>layout:'fit'</tt> {@link Ext.Container#layout}
\r
18483 * config, and should generally not need to be created directly via the new keyword.</p>
\r
18484 * <p>FitLayout does not have any direct config options (other than inherited ones). To fit a panel to a container
\r
18485 * using FitLayout, simply set layout:'fit' on the container and add a single panel to it. If the container has
\r
18486 * multiple panels, only the first one will be displayed. Example usage:</p>
\r
18488 var p = new Ext.Panel({
\r
18489 title: 'Fit Layout',
\r
18492 title: 'Inner Panel',
\r
18493 html: '<p>This is the inner panel content</p>',
\r
18499 Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18501 monitorResize:true,
\r
18504 onLayout : function(ct, target){
\r
18505 Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
\r
18506 if(!this.container.collapsed){
\r
18507 var sz = (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getViewSize() : target.getStyleSize();
\r
18508 this.setItemSize(this.activeItem || ct.items.itemAt(0), sz);
\r
18513 setItemSize : function(item, size){
\r
18514 if(item && size.height > 0){ // display none?
\r
18515 item.setSize(size);
\r
18519 Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;/**
\r
18520 * @class Ext.layout.CardLayout
\r
18521 * @extends Ext.layout.FitLayout
\r
18522 * <p>This layout manages multiple child Components, each fitted to the Container, where only a single child Component can be
\r
18523 * visible at any given time. This layout style is most commonly used for wizards, tab implementations, etc.
\r
18524 * This class is intended to be extended or created via the layout:'card' {@link Ext.Container#layout} config,
\r
18525 * and should generally not need to be created directly via the new keyword.</p>
\r
18526 * <p>The CardLayout's focal method is {@link #setActiveItem}. Since only one panel is displayed at a time,
\r
18527 * the only way to move from one Component to the next is by calling setActiveItem, passing the id or index of
\r
18528 * the next panel to display. The layout itself does not provide a user interface for handling this navigation,
\r
18529 * so that functionality must be provided by the developer.</p>
\r
18530 * <p>In the following example, a simplistic wizard setup is demonstrated. A button bar is added
\r
18531 * to the footer of the containing panel to provide navigation buttons. The buttons will be handled by a
\r
18532 * common navigation routine -- for this example, the implementation of that routine has been ommitted since
\r
18533 * it can be any type of custom logic. Note that other uses of a CardLayout (like a tab control) would require a
\r
18534 * completely different implementation. For serious implementations, a better approach would be to extend
\r
18535 * CardLayout to provide the custom functionality needed. Example usage:</p>
\r
18537 var navHandler = function(direction){
\r
18538 // This routine could contain business logic required to manage the navigation steps.
\r
18539 // It would call setActiveItem as needed, manage navigation button state, handle any
\r
18540 // branching logic that might be required, handle alternate actions like cancellation
\r
18541 // or finalization, etc. A complete wizard implementation could get pretty
\r
18542 // sophisticated depending on the complexity required, and should probably be
\r
18543 // done as a subclass of CardLayout in a real-world implementation.
\r
18546 var card = new Ext.Panel({
\r
18547 title: 'Example Wizard',
\r
18549 activeItem: 0, // make sure the active item is set on the container config!
\r
18550 bodyStyle: 'padding:15px',
\r
18552 // applied to each contained panel
\r
18555 // just an example of one possible navigation scheme, using buttons
\r
18560 handler: navHandler.createDelegate(this, [-1]),
\r
18563 '->', // greedy spacer so that the buttons are aligned to each side
\r
18567 handler: navHandler.createDelegate(this, [1])
\r
18570 // the panels (or "cards") within the layout
\r
18573 html: '<h1>Welcome to the Wizard!</h1><p>Step 1 of 3</p>'
\r
18576 html: '<p>Step 2 of 3</p>'
\r
18579 html: '<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>'
\r
18584 Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
\r
18586 * @cfg {Boolean} deferredRender
\r
18587 * True to render each contained item at the time it becomes active, false to render all contained items
\r
18588 * as soon as the layout is rendered (defaults to false). If there is a significant amount of content or
\r
18589 * a lot of heavy controls being rendered into panels that are not displayed by default, setting this to
\r
18590 * true might improve performance.
\r
18592 deferredRender : false,
\r
18595 * @cfg {Boolean} layoutOnCardChange
\r
18596 * True to force a layout of the active item when the active card is changed. Defaults to false.
\r
18598 layoutOnCardChange : false,
\r
18601 * @cfg {Boolean} renderHidden @hide
\r
18604 renderHidden : true,
\r
18606 constructor: function(config){
\r
18607 Ext.layout.CardLayout.superclass.constructor.call(this, config);
\r
18608 this.forceLayout = (this.deferredRender === false);
\r
18612 * Sets the active (visible) item in the layout.
\r
18613 * @param {String/Number} item The string component id or numeric index of the item to activate
\r
18615 setActiveItem : function(item){
\r
18616 item = this.container.getComponent(item);
\r
18617 if(this.activeItem != item){
\r
18618 if(this.activeItem){
\r
18619 this.activeItem.hide();
\r
18621 var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
\r
18622 this.activeItem = item;
\r
18632 renderAll : function(ct, target){
\r
18633 if(this.deferredRender){
\r
18634 this.renderItem(this.activeItem, undefined, target);
\r
18636 Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
\r
18640 Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;/**
\r
18641 * @class Ext.layout.AnchorLayout
\r
18642 * @extends Ext.layout.ContainerLayout
\r
18643 * <p>This is a layout that enables anchoring of contained elements relative to the container's dimensions.
\r
18644 * If the container is resized, all anchored items are automatically rerendered according to their
\r
18645 * <b><tt>{@link #anchor}</tt></b> rules.</p>
\r
18646 * <p>This class is intended to be extended or created via the layout:'anchor' {@link Ext.Container#layout}
\r
18647 * config, and should generally not need to be created directly via the new keyword.</p>
\r
18648 * <p>AnchorLayout does not have any direct config options (other than inherited ones). By default,
\r
18649 * AnchorLayout will calculate anchor measurements based on the size of the container itself. However, the
\r
18650 * container using the AnchorLayout can supply an anchoring-specific config property of <b>anchorSize</b>.
\r
18651 * If anchorSize is specifed, the layout will use it as a virtual container for the purposes of calculating
\r
18652 * anchor measurements based on it instead, allowing the container to be sized independently of the anchoring
\r
18653 * logic if necessary. For example:</p>
\r
18655 var viewport = new Ext.Viewport({
\r
18657 anchorSize: {width:800, height:600},
\r
18660 html:'Content 1',
\r
18662 anchor:'right 20%'
\r
18665 html:'Content 2',
\r
18670 html:'Content 3',
\r
18672 anchor:'-100 50%'
\r
18677 Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18679 * @cfg {String} anchor
\r
18680 * <p>This configuation option is to be applied to <b>child <tt>items</tt></b> of a container managed by
\r
18681 * this layout (ie. configured with <tt>layout:'anchor'</tt>).</p><br/>
\r
18683 * <p>This value is what tells the layout how an item should be anchored to the container. <tt>items</tt>
\r
18684 * added to an AnchorLayout accept an anchoring-specific config property of <b>anchor</b> which is a string
\r
18685 * containing two values: the horizontal anchor value and the vertical anchor value (for example, '100% 50%').
\r
18686 * The following types of anchor values are supported:<div class="mdetail-params"><ul>
\r
18688 * <li><b>Percentage</b> : Any value between 1 and 100, expressed as a percentage.<div class="sub-desc">
\r
18689 * The first anchor is the percentage width that the item should take up within the container, and the
\r
18690 * second is the percentage height. For example:<pre><code>
\r
18691 // two values specified
\r
18692 anchor: '100% 50%' // render item complete width of the container and
\r
18693 // 1/2 height of the container
\r
18694 // one value specified
\r
18695 anchor: '100%' // the width value; the height will default to auto
\r
18696 * </code></pre></div></li>
\r
18698 * <li><b>Offsets</b> : Any positive or negative integer value.<div class="sub-desc">
\r
18699 * This is a raw adjustment where the first anchor is the offset from the right edge of the container,
\r
18700 * and the second is the offset from the bottom edge. For example:<pre><code>
\r
18701 // two values specified
\r
18702 anchor: '-50 -100' // render item the complete width of the container
\r
18703 // minus 50 pixels and
\r
18704 // the complete height minus 100 pixels.
\r
18705 // one value specified
\r
18706 anchor: '-50' // anchor value is assumed to be the right offset value
\r
18707 // bottom offset will default to 0
\r
18708 * </code></pre></div></li>
\r
18710 * <li><b>Sides</b> : Valid values are <tt>'right'</tt> (or <tt>'r'</tt>) and <tt>'bottom'</tt>
\r
18711 * (or <tt>'b'</tt>).<div class="sub-desc">
\r
18712 * Either the container must have a fixed size or an anchorSize config value defined at render time in
\r
18713 * order for these to have any effect.</div></li>
\r
18715 * <li><b>Mixed</b> : <div class="sub-desc">
\r
18716 * Anchor values can also be mixed as needed. For example, to render the width offset from the container
\r
18717 * right edge by 50 pixels and 75% of the container's height use:
\r
18719 anchor: '-50 75%'
\r
18720 * </code></pre></div></li>
\r
18727 monitorResize:true,
\r
18730 getAnchorViewSize : function(ct, target){
\r
18731 return target.dom == document.body ?
\r
18732 target.getViewSize() : target.getStyleSize();
\r
18736 onLayout : function(ct, target){
\r
18737 Ext.layout.AnchorLayout.superclass.onLayout.call(this, ct, target);
\r
18739 var size = this.getAnchorViewSize(ct, target);
\r
18741 var w = size.width, h = size.height;
\r
18743 if(w < 20 && h < 20){
\r
18747 // find the container anchoring size
\r
18749 if(ct.anchorSize){
\r
18750 if(typeof ct.anchorSize == 'number'){
\r
18751 aw = ct.anchorSize;
\r
18753 aw = ct.anchorSize.width;
\r
18754 ah = ct.anchorSize.height;
\r
18757 aw = ct.initialConfig.width;
\r
18758 ah = ct.initialConfig.height;
\r
18761 var cs = ct.items.items, len = cs.length, i, c, a, cw, ch;
\r
18762 for(i = 0; i < len; i++){
\r
18765 a = c.anchorSpec;
\r
18766 if(!a){ // cache all anchor values
\r
18767 var vs = c.anchor.split(' ');
\r
18768 c.anchorSpec = a = {
\r
18769 right: this.parseAnchor(vs[0], c.initialConfig.width, aw),
\r
18770 bottom: this.parseAnchor(vs[1], c.initialConfig.height, ah)
\r
18773 cw = a.right ? this.adjustWidthAnchor(a.right(w), c) : undefined;
\r
18774 ch = a.bottom ? this.adjustHeightAnchor(a.bottom(h), c) : undefined;
\r
18777 c.setSize(cw || undefined, ch || undefined);
\r
18784 parseAnchor : function(a, start, cstart){
\r
18785 if(a && a != 'none'){
\r
18787 if(/^(r|right|b|bottom)$/i.test(a)){ // standard anchor
\r
18788 var diff = cstart - start;
\r
18789 return function(v){
\r
18795 }else if(a.indexOf('%') != -1){
\r
18796 var ratio = parseFloat(a.replace('%', ''))*.01; // percentage
\r
18797 return function(v){
\r
18800 return Math.floor(v*ratio);
\r
18804 a = parseInt(a, 10);
\r
18805 if(!isNaN(a)){ // simple offset adjustment
\r
18806 return function(v){
\r
18819 adjustWidthAnchor : function(value, comp){
\r
18824 adjustHeightAnchor : function(value, comp){
\r
18829 * @property activeItem
\r
18833 Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;/**
\r
18834 * @class Ext.layout.ColumnLayout
\r
18835 * @extends Ext.layout.ContainerLayout
\r
18836 * <p>This is the layout style of choice for creating structural layouts in a multi-column format where the width of
\r
18837 * each column can be specified as a percentage or fixed width, but the height is allowed to vary based on the content.
\r
18838 * This class is intended to be extended or created via the layout:'column' {@link Ext.Container#layout} config,
\r
18839 * and should generally not need to be created directly via the new keyword.</p>
\r
18840 * <p>ColumnLayout does not have any direct config options (other than inherited ones), but it does support a
\r
18841 * specific config property of <b><tt>columnWidth</tt></b> that can be included in the config of any panel added to it. The
\r
18842 * layout will use the columnWidth (if present) or width of each panel during layout to determine how to size each panel.
\r
18843 * If width or columnWidth is not specified for a given panel, its width will default to the panel's width (or auto).</p>
\r
18844 * <p>The width property is always evaluated as pixels, and must be a number greater than or equal to 1.
\r
18845 * The columnWidth property is always evaluated as a percentage, and must be a decimal value greater than 0 and
\r
18846 * less than 1 (e.g., .25).</p>
\r
18847 * <p>The basic rules for specifying column widths are pretty simple. The logic makes two passes through the
\r
18848 * set of contained panels. During the first layout pass, all panels that either have a fixed width or none
\r
18849 * specified (auto) are skipped, but their widths are subtracted from the overall container width. During the second
\r
18850 * pass, all panels with columnWidths are assigned pixel widths in proportion to their percentages based on
\r
18851 * the total <b>remaining</b> container width. In other words, percentage width panels are designed to fill the space
\r
18852 * left over by all the fixed-width and/or auto-width panels. Because of this, while you can specify any number of columns
\r
18853 * with different percentages, the columnWidths must always add up to 1 (or 100%) when added together, otherwise your
\r
18854 * layout may not render as expected. Example usage:</p>
\r
18856 // All columns are percentages -- they must add up to 1
\r
18857 var p = new Ext.Panel({
\r
18858 title: 'Column Layout - Percentage Only',
\r
18861 title: 'Column 1',
\r
18862 columnWidth: .25
\r
18864 title: 'Column 2',
\r
18867 title: 'Column 3',
\r
18872 // Mix of width and columnWidth -- all columnWidth values must add up
\r
18873 // to 1. The first column will take up exactly 120px, and the last two
\r
18874 // columns will fill the remaining container width.
\r
18875 var p = new Ext.Panel({
\r
18876 title: 'Column Layout - Mixed',
\r
18879 title: 'Column 1',
\r
18882 title: 'Column 2',
\r
18885 title: 'Column 3',
\r
18891 Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
18893 monitorResize:true,
\r
18895 extraCls: 'x-column',
\r
18897 scrollOffset : 0,
\r
18900 isValidParent : function(c, target){
\r
18901 return (c.getPositionEl ? c.getPositionEl() : c.getEl()).dom.parentNode == this.innerCt.dom;
\r
18905 onLayout : function(ct, target){
\r
18906 var cs = ct.items.items, len = cs.length, c, i;
\r
18908 if(!this.innerCt){
\r
18909 target.addClass('x-column-layout-ct');
\r
18911 // the innerCt prevents wrapping and shuffling while
\r
18912 // the container is resizing
\r
18913 this.innerCt = target.createChild({cls:'x-column-inner'});
\r
18914 this.innerCt.createChild({cls:'x-clear'});
\r
18916 this.renderAll(ct, this.innerCt);
\r
18918 var size = Ext.isIE && target.dom != Ext.getBody().dom ? target.getStyleSize() : target.getViewSize();
\r
18920 if(size.width < 1 && size.height < 1){ // display none?
\r
18924 var w = size.width - target.getPadding('lr') - this.scrollOffset,
\r
18925 h = size.height - target.getPadding('tb'),
\r
18928 this.innerCt.setWidth(w);
\r
18930 // some columns can be percentages while others are fixed
\r
18931 // so we need to make 2 passes
\r
18933 for(i = 0; i < len; i++){
\r
18935 if(!c.columnWidth){
\r
18936 pw -= (c.getSize().width + c.getEl().getMargins('lr'));
\r
18940 pw = pw < 0 ? 0 : pw;
\r
18942 for(i = 0; i < len; i++){
\r
18944 if(c.columnWidth){
\r
18945 c.setSize(Math.floor(c.columnWidth*pw) - c.getEl().getMargins('lr'));
\r
18951 * @property activeItem
\r
18956 Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;/**
18957 * @class Ext.layout.BorderLayout
18958 * @extends Ext.layout.ContainerLayout
18959 * <p>This is a multi-pane, application-oriented UI layout style that supports multiple
18960 * nested panels, automatic {@link Ext.layout.BorderLayout.Region#split split} bars between
18961 * {@link Ext.layout.BorderLayout.Region#BorderLayout.Region regions} and built-in
18962 * {@link Ext.layout.BorderLayout.Region#collapsible expanding and collapsing} of regions.</p>
18963 * <p>This class is intended to be extended or created via the <tt>layout:'border'</tt>
18964 * {@link Ext.Container#layout} config, and should generally not need to be created directly
18965 * via the new keyword.</p>
18966 * <p>BorderLayout does not have any direct config options (other than inherited ones).
18967 * All configuration options available for customizing the BorderLayout are at the
18968 * {@link Ext.layout.BorderLayout.Region} and {@link Ext.layout.BorderLayout.SplitRegion}
18970 * <p>Example usage:</p>
18972 var myBorderPanel = new Ext.Panel({
18973 {@link Ext.Component#renderTo renderTo}: document.body,
18974 {@link Ext.BoxComponent#width width}: 700,
18975 {@link Ext.BoxComponent#height height}: 500,
18976 {@link Ext.Panel#title title}: 'Border Layout',
18977 {@link Ext.Container#layout layout}: 'border',
18978 {@link Ext.Container#items items}: [{
18979 {@link Ext.Panel#title title}: 'South Region is resizable',
18980 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'south', // position for region
18981 {@link Ext.BoxComponent#height height}: 100,
18982 {@link Ext.layout.BorderLayout.Region#split split}: true, // enable resizing
18983 {@link Ext.SplitBar#minSize minSize}: 75, // defaults to {@link Ext.layout.BorderLayout.Region#minHeight 50}
18984 {@link Ext.SplitBar#maxSize maxSize}: 150,
18985 {@link Ext.layout.BorderLayout.Region#margins margins}: '0 5 5 5'
18987 // xtype: 'panel' implied by default
18988 {@link Ext.Panel#title title}: 'West Region is collapsible',
18989 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}:'west',
18990 {@link Ext.layout.BorderLayout.Region#margins margins}: '5 0 0 5',
18991 {@link Ext.BoxComponent#width width}: 200,
18992 {@link Ext.layout.BorderLayout.Region#collapsible collapsible}: true, // make collapsible
18993 {@link Ext.layout.BorderLayout.Region#cmargins cmargins}: '5 5 0 5', // adjust top margin when collapsed
18994 {@link Ext.Component#id id}: 'west-region-container',
18995 {@link Ext.Container#layout layout}: 'fit',
18996 {@link Ext.Panel#unstyled unstyled}: true
18998 {@link Ext.Panel#title title}: 'Center Region',
18999 {@link Ext.layout.BorderLayout.Region#BorderLayout.Region region}: 'center', // center region is required, no width/height specified
19000 {@link Ext.Component#xtype xtype}: 'container',
19001 {@link Ext.Container#layout layout}: 'fit',
19002 {@link Ext.layout.BorderLayout.Region#margins margins}: '5 5 0 0'
19006 * <p><b><u>Notes</u></b>:</p><div class="mdetail-params"><ul>
19007 * <li>Any container using the BorderLayout <b>must</b> have a child item with <tt>region:'center'</tt>.
19008 * The child item in the center region will always be resized to fill the remaining space not used by
19009 * the other regions in the layout.</li>
19010 * <li>Any child items with a region of <tt>west</tt> or <tt>east</tt> must have <tt>width</tt> defined
19011 * (an integer representing the number of pixels that the region should take up).</li>
19012 * <li>Any child items with a region of <tt>north</tt> or <tt>south</tt> must have <tt>height</tt> defined.</li>
19013 * <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
19014 * Components within a BorderLayout, have them wrapped by an additional Container which is directly
19015 * managed by the BorderLayout. If the region is to be collapsible, the Container used directly
19016 * by the BorderLayout manager should be a Panel. In the following example a Container (an Ext.Panel)
19017 * is added to the west region:
19018 * <div style="margin-left:16px"><pre><code>
19019 wrc = {@link Ext#getCmp Ext.getCmp}('west-region-container');
19020 wrc.{@link Ext.Panel#removeAll removeAll}();
19021 wrc.{@link Ext.Container#add add}({
19022 title: 'Added Panel',
19023 html: 'Some content'
19025 wrc.{@link Ext.Container#doLayout doLayout}();
19026 * </code></pre></div>
19028 * <li> To reference a {@link Ext.layout.BorderLayout.Region Region}:
19029 * <div style="margin-left:16px"><pre><code>
19030 wr = myBorderPanel.layout.west;
19031 * </code></pre></div>
19035 Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
19037 monitorResize:true,
19042 onLayout : function(ct, target){
19044 if(!this.rendered){
19045 target.addClass('x-border-layout-ct');
19046 var items = ct.items.items;
19048 for(var i = 0, len = items.length; i < len; i++) {
19050 var pos = c.region;
19054 c.collapsed = false;
19056 c.render(target, i);
19057 c.getDomPositionEl().addClass('x-border-panel');
19059 this[pos] = pos != 'center' && c.split ?
19060 new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
19061 new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
19062 this[pos].render(target, c);
19064 this.rendered = true;
19067 var size = target.getViewSize();
19068 if(size.width < 20 || size.height < 20){ // display none?
19070 this.restoreCollapsed = collapsed;
19073 }else if(this.restoreCollapsed){
19074 collapsed = this.restoreCollapsed;
19075 delete this.restoreCollapsed;
19078 var w = size.width, h = size.height;
19079 var centerW = w, centerH = h, centerY = 0, centerX = 0;
19081 var n = this.north, s = this.south, west = this.west, e = this.east, c = this.center;
19082 if(!c && Ext.layout.BorderLayout.WARN !== false){
19083 throw 'No center region defined in BorderLayout ' + ct.id;
19086 if(n && n.isVisible()){
19087 var b = n.getSize();
19088 var m = n.getMargins();
19089 b.width = w - (m.left+m.right);
19092 centerY = b.height + b.y + m.bottom;
19093 centerH -= centerY;
19096 if(s && s.isVisible()){
19097 var b = s.getSize();
19098 var m = s.getMargins();
19099 b.width = w - (m.left+m.right);
19101 var totalHeight = (b.height + m.top + m.bottom);
19102 b.y = h - totalHeight + m.top;
19103 centerH -= totalHeight;
19106 if(west && west.isVisible()){
19107 var b = west.getSize();
19108 var m = west.getMargins();
19109 b.height = centerH - (m.top+m.bottom);
19111 b.y = centerY + m.top;
19112 var totalWidth = (b.width + m.left + m.right);
19113 centerX += totalWidth;
19114 centerW -= totalWidth;
19115 west.applyLayout(b);
19117 if(e && e.isVisible()){
19118 var b = e.getSize();
19119 var m = e.getMargins();
19120 b.height = centerH - (m.top+m.bottom);
19121 var totalWidth = (b.width + m.left + m.right);
19122 b.x = w - totalWidth + m.left;
19123 b.y = centerY + m.top;
19124 centerW -= totalWidth;
19128 var m = c.getMargins();
19130 x: centerX + m.left,
19131 y: centerY + m.top,
19132 width: centerW - (m.left+m.right),
19133 height: centerH - (m.top+m.bottom)
19135 c.applyLayout(centerBox);
19138 for(var i = 0, len = collapsed.length; i < len; i++){
19139 collapsed[i].collapse(false);
19142 if(Ext.isIE && Ext.isStrict){ // workaround IE strict repainting issue
19147 destroy: function() {
19148 var r = ['north', 'south', 'east', 'west'];
19149 for (var i = 0; i < r.length; i++) {
19150 var region = this[r[i]];
19152 if(region.destroy){
19154 }else if (region.split){
19155 region.split.destroy(true);
19159 Ext.layout.BorderLayout.superclass.destroy.call(this);
19163 * @property activeItem
19169 * @class Ext.layout.BorderLayout.Region
19170 * <p>This is a region of a {@link Ext.layout.BorderLayout BorderLayout} that acts as a subcontainer
19171 * within the layout. Each region has its own {@link Ext.layout.ContainerLayout layout} that is
19172 * independent of other regions and the containing BorderLayout, and can be any of the
19173 * {@link Ext.layout.ContainerLayout valid Ext layout types}.</p>
19174 * <p>Region size is managed automatically and cannot be changed by the user -- for
19175 * {@link #split resizable regions}, see {@link Ext.layout.BorderLayout.SplitRegion}.</p>
19177 * Create a new Region.
19178 * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
19179 * @param {Object} config The configuration options
19180 * @param {String} position The region position. Valid values are: <tt>north</tt>, <tt>south</tt>,
19181 * <tt>east</tt>, <tt>west</tt> and <tt>center</tt>. Every {@link Ext.layout.BorderLayout BorderLayout}
19182 * <b>must have a center region</b> for the primary content -- all other regions are optional.
19184 Ext.layout.BorderLayout.Region = function(layout, config, pos){
19185 Ext.apply(this, config);
19186 this.layout = layout;
19187 this.position = pos;
19189 if(typeof this.margins == 'string'){
19190 this.margins = this.layout.parseMargins(this.margins);
19192 this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
19193 if(this.collapsible){
19194 if(typeof this.cmargins == 'string'){
19195 this.cmargins = this.layout.parseMargins(this.cmargins);
19197 if(this.collapseMode == 'mini' && !this.cmargins){
19198 this.cmargins = {left:0,top:0,right:0,bottom:0};
19200 this.cmargins = Ext.applyIf(this.cmargins || {},
19201 pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
19206 Ext.layout.BorderLayout.Region.prototype = {
19208 * @cfg {Boolean} animFloat
19209 * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
19210 * panel that will close again once the user mouses out of that panel (or clicks out if
19211 * <tt>{@link #autoHide} = false</tt>). Setting <tt>{@link #animFloat} = false</tt> will
19212 * prevent the open and close of these floated panels from being animated (defaults to <tt>true</tt>).
19215 * @cfg {Boolean} autoHide
19216 * When a collapsed region's bar is clicked, the region's panel will be displayed as a floated
19217 * panel. If <tt>autoHide = true</tt>, the panel will automatically hide after the user mouses
19218 * out of the panel. If <tt>autoHide = false</tt>, the panel will continue to display until the
19219 * user clicks outside of the panel (defaults to <tt>true</tt>).
19222 * @cfg {String} collapseMode
19223 * <tt>collapseMode</tt> supports two configuration values:<div class="mdetail-params"><ul>
19224 * <li><b><tt>undefined</tt></b> (default)<div class="sub-desc">By default, {@link #collapsible}
19225 * regions are collapsed by clicking the expand/collapse tool button that renders into the region's
19226 * title bar.</div></li>
19227 * <li><b><tt>'mini'</tt></b><div class="sub-desc">Optionally, when <tt>collapseMode</tt> is set to
19228 * <tt>'mini'</tt> the region's split bar will also display a small collapse button in the center of
19229 * the bar. In <tt>'mini'</tt> mode the region will collapse to a thinner bar than in normal mode.
19232 * <p><b>Note</b>: if a collapsible region does not have a title bar, then set <tt>collapseMode =
19233 * 'mini'</tt> and <tt>{@link #split} = true</tt> in order for the region to be {@link #collapsible}
19234 * by the user as the expand/collapse tool button (that would go in the title bar) will not be rendered.</p>
19235 * <p>See also <tt>{@link #cmargins}</tt>.</p>
19238 * @cfg {Object} margins
19239 * An object containing margins to apply to the region when in the expanded state in the
19240 * format:<pre><code>
19243 right: (right margin),
19244 bottom: (bottom margin),
19245 left: (left margin)
19247 * <p>May also be a string containing space-separated, numeric margin values. The order of the
19248 * sides associated with each value matches the way CSS processes margin values:</p>
19249 * <p><div class="mdetail-params"><ul>
19250 * <li>If there is only one value, it applies to all sides.</li>
19251 * <li>If there are two values, the top and bottom borders are set to the first value and the
19252 * right and left are set to the second.</li>
19253 * <li>If there are three values, the top is set to the first value, the left and right are set
19254 * to the second, and the bottom is set to the third.</li>
19255 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
19257 * <p>Defaults to:</p><pre><code>
19258 * {top:0, right:0, bottom:0, left:0}
19262 * @cfg {Object} cmargins
19263 * An object containing margins to apply to the region when in the collapsed state in the
19264 * format:<pre><code>
19267 right: (right margin),
19268 bottom: (bottom margin),
19269 left: (left margin)
19271 * <p>May also be a string containing space-separated, numeric margin values. The order of the
19272 * sides associated with each value matches the way CSS processes margin values.</p>
19274 * <li>If there is only one value, it applies to all sides.</li>
19275 * <li>If there are two values, the top and bottom borders are set to the first value and the
19276 * right and left are set to the second.</li>
19277 * <li>If there are three values, the top is set to the first value, the left and right are set
19278 * to the second, and the bottom is set to the third.</li>
19279 * <li>If there are four values, they apply to the top, right, bottom, and left, respectively.</li>
19283 * @cfg {Boolean} collapsible
19284 * <p><tt>true</tt> to allow the user to collapse this region (defaults to <tt>false</tt>). If
19285 * <tt>true</tt>, an expand/collapse tool button will automatically be rendered into the title
19286 * bar of the region, otherwise the button will not be shown.</p>
19287 * <p><b>Note</b>: that a title bar is required to display the collapse/expand toggle button -- if
19288 * no <tt>title</tt> is specified for the region's panel, the region will only be collapsible if
19289 * <tt>{@link #collapseMode} = 'mini'</tt> and <tt>{@link #split} = true</tt>.
19291 collapsible : false,
19293 * @cfg {Boolean} split
19294 * <p><tt>true</tt> to create a {@link Ext.layout.BorderLayout.SplitRegion SplitRegion} and
19295 * display a 5px wide {@link Ext.SplitBar} between this region and its neighbor, allowing the user to
19296 * resize the regions dynamically. Defaults to <tt>false</tt> creating a
19297 * {@link Ext.layout.BorderLayout.Region Region}.</p><br>
19298 * <p><b>Notes</b>:</p><div class="mdetail-params"><ul>
19299 * <li>this configuration option is ignored if <tt>region='center'</tt></li>
19300 * <li>when <tt>split == true</tt>, it is common to specify a
19301 * <tt>{@link Ext.SplitBar#minSize minSize}</tt> and <tt>{@link Ext.SplitBar#maxSize maxSize}</tt>
19302 * for the {@link Ext.BoxComponent BoxComponent} representing the region. These are not native
19303 * configs of {@link Ext.BoxComponent BoxComponent}, and are used only by this class.</li>
19304 * <li>if <tt>{@link #collapseMode} = 'mini'</tt> requires <tt>split = true</tt> to reserve space
19305 * for the collapse tool</tt></li>
19310 * @cfg {Boolean} floatable
19311 * <tt>true</tt> to allow clicking a collapsed region's bar to display the region's panel floated
19312 * above the layout, <tt>false</tt> to force the user to fully expand a collapsed region by
19313 * clicking the expand button to see it again (defaults to <tt>true</tt>).
19317 * @cfg {Number} minWidth
19318 * <p>The minimum allowable width in pixels for this region (defaults to <tt>50</tt>).
19319 * <tt>maxWidth</tt> may also be specified.</p><br>
19320 * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
19321 * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
19322 * <tt>minWidth</tt> / <tt>maxWidth</tt>.</p>
19326 * @cfg {Number} minHeight
19327 * The minimum allowable height in pixels for this region (defaults to <tt>50</tt>)
19328 * <tt>maxHeight</tt> may also be specified.</p><br>
19329 * <p><b>Note</b>: setting the <tt>{@link Ext.SplitBar#minSize minSize}</tt> /
19330 * <tt>{@link Ext.SplitBar#maxSize maxSize}</tt> supersedes any specified
19331 * <tt>minHeight</tt> / <tt>maxHeight</tt>.</p>
19336 defaultMargins : {left:0,top:0,right:0,bottom:0},
19338 defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
19340 defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
19341 floatingZIndex: 100,
19344 * True if this region is collapsed. Read-only.
19348 isCollapsed : false,
19351 * This region's panel. Read-only.
19356 * This region's layout. Read-only.
19361 * This region's layout position (north, south, east, west or center). Read-only.
19363 * @property position
19367 render : function(ct, p){
19369 p.el.enableDisplayMode();
19370 this.targetEl = ct;
19373 var gs = p.getState, ps = this.position;
19374 p.getState = function(){
19375 return Ext.apply(gs.call(p) || {}, this.state);
19376 }.createDelegate(this);
19378 if(ps != 'center'){
19379 p.allowQueuedExpand = false;
19381 beforecollapse: this.beforeCollapse,
19382 collapse: this.onCollapse,
19383 beforeexpand: this.beforeExpand,
19384 expand: this.onExpand,
19389 if(this.collapsible || this.floatable){
19390 p.collapseEl = 'el';
19391 p.slideAnchor = this.getSlideAnchor();
19393 if(p.tools && p.tools.toggle){
19394 p.tools.toggle.addClass('x-tool-collapse-'+ps);
19395 p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
19401 getCollapsedEl : function(){
19402 if(!this.collapsedEl){
19403 if(!this.toolTemplate){
19404 var tt = new Ext.Template(
19405 '<div class="x-tool x-tool-{id}"> </div>'
19407 tt.disableFormats = true;
19409 Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
19411 this.collapsedEl = this.targetEl.createChild({
19412 cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
19413 id: this.panel.id + '-xcollapsed'
19415 this.collapsedEl.enableDisplayMode('block');
19417 if(this.collapseMode == 'mini'){
19418 this.collapsedEl.addClass('x-layout-cmini-'+this.position);
19419 this.miniCollapsedEl = this.collapsedEl.createChild({
19420 cls: "x-layout-mini x-layout-mini-"+this.position, html: " "
19422 this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
19423 this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
19424 this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
19426 if(this.collapsible !== false && !this.hideCollapseTool) {
19427 var t = this.toolTemplate.append(
19428 this.collapsedEl.dom,
19429 {id:'expand-'+this.position}, true);
19430 t.addClassOnOver('x-tool-expand-'+this.position+'-over');
19431 t.on('click', this.onExpandClick, this, {stopEvent:true});
19433 if(this.floatable !== false || this.titleCollapse){
19434 this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
19435 this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
19439 return this.collapsedEl;
19443 onExpandClick : function(e){
19445 this.afterSlideIn();
19446 this.panel.expand(false);
19448 this.panel.expand();
19453 onCollapseClick : function(e){
19454 this.panel.collapse();
19458 beforeCollapse : function(p, animate){
19459 this.lastAnim = animate;
19461 this.splitEl.hide();
19463 this.getCollapsedEl().show();
19464 this.panel.el.setStyle('z-index', 100);
19465 this.isCollapsed = true;
19466 this.layout.layout();
19470 onCollapse : function(animate){
19471 this.panel.el.setStyle('z-index', 1);
19472 if(this.lastAnim === false || this.panel.animCollapse === false){
19473 this.getCollapsedEl().dom.style.visibility = 'visible';
19475 this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
19477 this.state.collapsed = true;
19478 this.panel.saveState();
19482 beforeExpand : function(animate){
19483 var c = this.getCollapsedEl();
19485 if(this.position == 'east' || this.position == 'west'){
19486 this.panel.setSize(undefined, c.getHeight());
19488 this.panel.setSize(c.getWidth(), undefined);
19491 c.dom.style.visibility = 'hidden';
19492 this.panel.el.setStyle('z-index', this.floatingZIndex);
19496 onExpand : function(){
19497 this.isCollapsed = false;
19499 this.splitEl.show();
19501 this.layout.layout();
19502 this.panel.el.setStyle('z-index', 1);
19503 this.state.collapsed = false;
19504 this.panel.saveState();
19508 collapseClick : function(e){
19510 e.stopPropagation();
19513 e.stopPropagation();
19519 onHide : function(){
19520 if(this.isCollapsed){
19521 this.getCollapsedEl().hide();
19522 }else if(this.splitEl){
19523 this.splitEl.hide();
19528 onShow : function(){
19529 if(this.isCollapsed){
19530 this.getCollapsedEl().show();
19531 }else if(this.splitEl){
19532 this.splitEl.show();
19537 * True if this region is currently visible, else false.
19538 * @return {Boolean}
19540 isVisible : function(){
19541 return !this.panel.hidden;
19545 * Returns the current margins for this region. If the region is collapsed, the
19546 * {@link #cmargins} (collapsed margins) value will be returned, otherwise the
19547 * {@link #margins} value will be returned.
19548 * @return {Object} An object containing the element's margins: <tt>{left: (left
19549 * margin), top: (top margin), right: (right margin), bottom: (bottom margin)}</tt>
19551 getMargins : function(){
19552 return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
19556 * Returns the current size of this region. If the region is collapsed, the size of the
19557 * collapsedEl will be returned, otherwise the size of the region's panel will be returned.
19558 * @return {Object} An object containing the element's size: <tt>{width: (element width),
19559 * height: (element height)}</tt>
19561 getSize : function(){
19562 return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
19566 * Sets the specified panel as the container element for this region.
19567 * @param {Ext.Panel} panel The new panel
19569 setPanel : function(panel){
19570 this.panel = panel;
19574 * Returns the minimum allowable width for this region.
19575 * @return {Number} The minimum width
19577 getMinWidth: function(){
19578 return this.minWidth;
19582 * Returns the minimum allowable height for this region.
19583 * @return {Number} The minimum height
19585 getMinHeight: function(){
19586 return this.minHeight;
19590 applyLayoutCollapsed : function(box){
19591 var ce = this.getCollapsedEl();
19592 ce.setLeftTop(box.x, box.y);
19593 ce.setSize(box.width, box.height);
19597 applyLayout : function(box){
19598 if(this.isCollapsed){
19599 this.applyLayoutCollapsed(box);
19601 this.panel.setPosition(box.x, box.y);
19602 this.panel.setSize(box.width, box.height);
19607 beforeSlide: function(){
19608 this.panel.beforeEffect();
19612 afterSlide : function(){
19613 this.panel.afterEffect();
19617 initAutoHide : function(){
19618 if(this.autoHide !== false){
19619 if(!this.autoHideHd){
19620 var st = new Ext.util.DelayedTask(this.slideIn, this);
19621 this.autoHideHd = {
19622 "mouseout": function(e){
19623 if(!e.within(this.el, true)){
19627 "mouseover" : function(e){
19633 this.el.on(this.autoHideHd);
19638 clearAutoHide : function(){
19639 if(this.autoHide !== false){
19640 this.el.un("mouseout", this.autoHideHd.mouseout);
19641 this.el.un("mouseover", this.autoHideHd.mouseover);
19646 clearMonitor : function(){
19647 Ext.getDoc().un("click", this.slideInIf, this);
19651 * If this Region is {@link #floatable}, this method slides this Region into full visibility <i>over the top
19652 * of the center Region</i> where it floats until either {@link #slideIn} is called, or other regions of the layout
19653 * are clicked, or the mouse exits the Region.
19655 slideOut : function(){
19656 if(this.isSlid || this.el.hasActiveFx()){
19659 this.isSlid = true;
19660 var ts = this.panel.tools;
19661 if(ts && ts.toggle){
19665 if(this.position == 'east' || this.position == 'west'){
19666 this.panel.setSize(undefined, this.collapsedEl.getHeight());
19668 this.panel.setSize(this.collapsedEl.getWidth(), undefined);
19670 this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
19671 this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
19672 this.el.setStyle("z-index", this.floatingZIndex+2);
19673 this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
19674 if(this.animFloat !== false){
19675 this.beforeSlide();
19676 this.el.slideIn(this.getSlideAnchor(), {
19677 callback: function(){
19679 this.initAutoHide();
19680 Ext.getDoc().on("click", this.slideInIf, this);
19686 this.initAutoHide();
19687 Ext.getDoc().on("click", this.slideInIf, this);
19692 afterSlideIn : function(){
19693 this.clearAutoHide();
19694 this.isSlid = false;
19695 this.clearMonitor();
19696 this.el.setStyle("z-index", "");
19697 this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
19698 this.el.dom.style.left = this.restoreLT[0];
19699 this.el.dom.style.top = this.restoreLT[1];
19701 var ts = this.panel.tools;
19702 if(ts && ts.toggle){
19708 * If this Region is {@link #floatable}, and this Region has been slid into floating visibility, then this method slides
19709 * this region back into its collapsed state.
19711 slideIn : function(cb){
19712 if(!this.isSlid || this.el.hasActiveFx()){
19716 this.isSlid = false;
19717 if(this.animFloat !== false){
19718 this.beforeSlide();
19719 this.el.slideOut(this.getSlideAnchor(), {
19720 callback: function(){
19723 this.afterSlideIn();
19731 this.afterSlideIn();
19736 slideInIf : function(e){
19737 if(!e.within(this.el)){
19767 getAnchor : function(){
19768 return this.anchors[this.position];
19772 getCollapseAnchor : function(){
19773 return this.canchors[this.position];
19777 getSlideAnchor : function(){
19778 return this.sanchors[this.position];
19782 getAlignAdj : function(){
19783 var cm = this.cmargins;
19784 switch(this.position){
19801 getExpandAdj : function(){
19802 var c = this.collapsedEl, cm = this.cmargins;
19803 switch(this.position){
19805 return [-(cm.right+c.getWidth()+cm.left), 0];
19808 return [cm.right+c.getWidth()+cm.left, 0];
19811 return [0, -(cm.top+cm.bottom+c.getHeight())];
19814 return [0, cm.top+cm.bottom+c.getHeight()];
19821 * @class Ext.layout.BorderLayout.SplitRegion
19822 * @extends Ext.layout.BorderLayout.Region
19823 * <p>This is a specialized type of {@link Ext.layout.BorderLayout.Region BorderLayout region} that
19824 * has a built-in {@link Ext.SplitBar} for user resizing of regions. The movement of the split bar
19825 * is configurable to move either {@link #tickSize smooth or incrementally}.</p>
19827 * Create a new SplitRegion.
19828 * @param {Layout} layout The {@link Ext.layout.BorderLayout BorderLayout} instance that is managing this Region.
19829 * @param {Object} config The configuration options
19830 * @param {String} position The region position. Valid values are: north, south, east, west and center. Every
19831 * BorderLayout must have a center region for the primary content -- all other regions are optional.
19833 Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
19834 Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
19836 this.applyLayout = this.applyFns[pos];
19839 Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
19841 * @cfg {Number} tickSize
19842 * The increment, in pixels by which to move this Region's {@link Ext.SplitBar SplitBar}.
19843 * By default, the {@link Ext.SplitBar SplitBar} moves smoothly.
19846 * @cfg {String} splitTip
19847 * The tooltip to display when the user hovers over a
19848 * {@link Ext.layout.BorderLayout.Region#collapsible non-collapsible} region's split bar
19849 * (defaults to <tt>"Drag to resize."</tt>). Only applies if
19850 * <tt>{@link #useSplitTips} = true</tt>.
19852 splitTip : "Drag to resize.",
19854 * @cfg {String} collapsibleSplitTip
19855 * The tooltip to display when the user hovers over a
19856 * {@link Ext.layout.BorderLayout.Region#collapsible collapsible} region's split bar
19857 * (defaults to "Drag to resize. Double click to hide."). Only applies if
19858 * <tt>{@link #useSplitTips} = true</tt>.
19860 collapsibleSplitTip : "Drag to resize. Double click to hide.",
19862 * @cfg {Boolean} useSplitTips
19863 * <tt>true</tt> to display a tooltip when the user hovers over a region's split bar
19864 * (defaults to <tt>false</tt>). The tooltip text will be the value of either
19865 * <tt>{@link #splitTip}</tt> or <tt>{@link #collapsibleSplitTip}</tt> as appropriate.
19867 useSplitTips : false,
19872 orientation: Ext.SplitBar.VERTICAL,
19873 placement: Ext.SplitBar.TOP,
19874 maxFn : 'getVMaxSize',
19875 minProp: 'minHeight',
19876 maxProp: 'maxHeight'
19879 orientation: Ext.SplitBar.VERTICAL,
19880 placement: Ext.SplitBar.BOTTOM,
19881 maxFn : 'getVMaxSize',
19882 minProp: 'minHeight',
19883 maxProp: 'maxHeight'
19886 orientation: Ext.SplitBar.HORIZONTAL,
19887 placement: Ext.SplitBar.RIGHT,
19888 maxFn : 'getHMaxSize',
19889 minProp: 'minWidth',
19890 maxProp: 'maxWidth'
19893 orientation: Ext.SplitBar.HORIZONTAL,
19894 placement: Ext.SplitBar.LEFT,
19895 maxFn : 'getHMaxSize',
19896 minProp: 'minWidth',
19897 maxProp: 'maxWidth'
19903 west : function(box){
19904 if(this.isCollapsed){
19905 return this.applyLayoutCollapsed(box);
19907 var sd = this.splitEl.dom, s = sd.style;
19908 this.panel.setPosition(box.x, box.y);
19909 var sw = sd.offsetWidth;
19910 s.left = (box.x+box.width-sw)+'px';
19911 s.top = (box.y)+'px';
19912 s.height = Math.max(0, box.height)+'px';
19913 this.panel.setSize(box.width-sw, box.height);
19915 east : function(box){
19916 if(this.isCollapsed){
19917 return this.applyLayoutCollapsed(box);
19919 var sd = this.splitEl.dom, s = sd.style;
19920 var sw = sd.offsetWidth;
19921 this.panel.setPosition(box.x+sw, box.y);
19922 s.left = (box.x)+'px';
19923 s.top = (box.y)+'px';
19924 s.height = Math.max(0, box.height)+'px';
19925 this.panel.setSize(box.width-sw, box.height);
19927 north : function(box){
19928 if(this.isCollapsed){
19929 return this.applyLayoutCollapsed(box);
19931 var sd = this.splitEl.dom, s = sd.style;
19932 var sh = sd.offsetHeight;
19933 this.panel.setPosition(box.x, box.y);
19934 s.left = (box.x)+'px';
19935 s.top = (box.y+box.height-sh)+'px';
19936 s.width = Math.max(0, box.width)+'px';
19937 this.panel.setSize(box.width, box.height-sh);
19939 south : function(box){
19940 if(this.isCollapsed){
19941 return this.applyLayoutCollapsed(box);
19943 var sd = this.splitEl.dom, s = sd.style;
19944 var sh = sd.offsetHeight;
19945 this.panel.setPosition(box.x, box.y+sh);
19946 s.left = (box.x)+'px';
19947 s.top = (box.y)+'px';
19948 s.width = Math.max(0, box.width)+'px';
19949 this.panel.setSize(box.width, box.height-sh);
19954 render : function(ct, p){
19955 Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
19957 var ps = this.position;
19959 this.splitEl = ct.createChild({
19960 cls: "x-layout-split x-layout-split-"+ps, html: " ",
19961 id: this.panel.id + '-xsplit'
19964 if(this.collapseMode == 'mini'){
19965 this.miniSplitEl = this.splitEl.createChild({
19966 cls: "x-layout-mini x-layout-mini-"+ps, html: " "
19968 this.miniSplitEl.addClassOnOver('x-layout-mini-over');
19969 this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
19972 var s = this.splitSettings[ps];
19974 this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
19975 this.split.tickSize = this.tickSize;
19976 this.split.placement = s.placement;
19977 this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
19978 this.split.minSize = this.minSize || this[s.minProp];
19979 this.split.on("beforeapply", this.onSplitMove, this);
19980 this.split.useShim = this.useShim === true;
19981 this.maxSize = this.maxSize || this[s.maxProp];
19984 this.splitEl.hide();
19987 if(this.useSplitTips){
19988 this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
19990 if(this.collapsible){
19991 this.splitEl.on("dblclick", this.onCollapseClick, this);
19995 //docs inherit from superclass
19996 getSize : function(){
19997 if(this.isCollapsed){
19998 return this.collapsedEl.getSize();
20000 var s = this.panel.getSize();
20001 if(this.position == 'north' || this.position == 'south'){
20002 s.height += this.splitEl.dom.offsetHeight;
20004 s.width += this.splitEl.dom.offsetWidth;
20010 getHMaxSize : function(){
20011 var cmax = this.maxSize || 10000;
20012 var center = this.layout.center;
20013 return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
20017 getVMaxSize : function(){
20018 var cmax = this.maxSize || 10000;
20019 var center = this.layout.center;
20020 return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
20024 onSplitMove : function(split, newSize){
20025 var s = this.panel.getSize();
20026 this.lastSplitSize = newSize;
20027 if(this.position == 'north' || this.position == 'south'){
20028 this.panel.setSize(s.width, newSize);
20029 this.state.height = newSize;
20031 this.panel.setSize(newSize, s.height);
20032 this.state.width = newSize;
20034 this.layout.layout();
20035 this.panel.saveState();
20040 * Returns a reference to the split bar in use by this region.
20041 * @return {Ext.SplitBar} The split bar
20043 getSplitBar : function(){
20048 destroy : function() {
20057 Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;/**
20058 * @class Ext.layout.FormLayout
20059 * @extends Ext.layout.AnchorLayout
20060 * <p>This layout manager is specifically designed for rendering and managing child Components of
20061 * {@link Ext.form.FormPanel forms}. It is responsible for rendering the labels of
20062 * {@link Ext.form.Field Field}s.</p>
20064 * <p>This layout manager is used when a Container is configured with the <tt>layout:'form'</tt>
20065 * {@link Ext.Container#layout layout} config option, and should generally not need to be created directly
20066 * via the new keyword. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
20068 * <p>In an application, it will usually be preferrable to use a {@link Ext.form.FormPanel FormPanel}
20069 * (which is configured with FormLayout as its layout class by default) since it also provides built-in
20070 * functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form.</p>
20072 * <p>A {@link Ext.Container Container} <i>using</i> the FormLayout layout manager (e.g.
20073 * {@link Ext.form.FormPanel} or specifying <tt>layout:'form'</tt>) can also accept the following
20074 * layout-specific config properties:<div class="mdetail-params"><ul>
20075 * <li><b><tt>{@link Ext.form.FormPanel#hideLabels hideLabels}</tt></b></li>
20076 * <li><b><tt>{@link Ext.form.FormPanel#labelAlign labelAlign}</tt></b></li>
20077 * <li><b><tt>{@link Ext.form.FormPanel#labelPad labelPad}</tt></b></li>
20078 * <li><b><tt>{@link Ext.form.FormPanel#labelSeparator labelSeparator}</tt></b></li>
20079 * <li><b><tt>{@link Ext.form.FormPanel#labelWidth labelWidth}</tt></b></li>
20082 * <p>Any Component (including Fields) managed by FormLayout accepts the following as a config option:
20083 * <div class="mdetail-params"><ul>
20084 * <li><b><tt>{@link Ext.Component#anchor anchor}</tt></b></li>
20087 * <p>Any Component managed by FormLayout may be rendered as a form field (with an associated label) by
20088 * configuring it with a non-null <b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b>. Components configured
20089 * in this way may be configured with the following options which affect the way the FormLayout renders them:
20090 * <div class="mdetail-params"><ul>
20091 * <li><b><tt>{@link Ext.Component#clearCls clearCls}</tt></b></li>
20092 * <li><b><tt>{@link Ext.Component#fieldLabel fieldLabel}</tt></b></li>
20093 * <li><b><tt>{@link Ext.Component#hideLabel hideLabel}</tt></b></li>
20094 * <li><b><tt>{@link Ext.Component#itemCls itemCls}</tt></b></li>
20095 * <li><b><tt>{@link Ext.Component#labelSeparator labelSeparator}</tt></b></li>
20096 * <li><b><tt>{@link Ext.Component#labelStyle labelStyle}</tt></b></li>
20099 * <p>Example usage:</p>
20101 // Required if showing validation messages
20102 Ext.QuickTips.init();
20104 // While you can create a basic Panel with layout:'form', practically
20105 // you should usually use a FormPanel to also get its form functionality
20106 // since it already creates a FormLayout internally.
20107 var form = new Ext.form.FormPanel({
20108 title: 'Form Layout',
20109 bodyStyle: 'padding:15px',
20111 defaultType: 'textfield',
20113 // applied to each contained item
20118 fieldLabel: 'First Name',
20121 {@link Ext.Component#labelSeparator labelSeparator}: ':' // override labelSeparator layout config
20123 fieldLabel: 'Last Name',
20126 fieldLabel: 'Email',
20131 hideLabel: true, // override hideLabels layout config
20141 {@link #labelSeparator}: '~' // superseded by assignment below
20143 // config options applicable to container when layout='form':
20145 labelAlign: 'left', // or 'right' or 'top'
20146 {@link Ext.form.FormPanel#labelSeparator labelSeparator}: '>>', // takes precedence over layoutConfig value
20147 labelWidth: 65, // defaults to 100
20148 labelPad: 8 // defaults to 5, must specify labelWidth to be honored
20152 Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
20155 * @cfg {String} labelSeparator
20156 * See {@link Ext.form.FormPanel}.{@link Ext.form.FormPanel#labelSeparator labelSeparator}. Configuration
20157 * of this property at the <b>container</b> level takes precedence.
20159 labelSeparator : ':',
20162 * Read only. The CSS style specification string added to field labels in this layout if not
20163 * otherwise {@link Ext.Component#labelStyle specified by each contained field}.
20165 * @property labelStyle
20169 * @cfg {Boolean} trackLabels
20170 * True to show/hide the field label when the field is hidden. Defaults to <tt>false</tt>.
20172 trackLabels: false,
20175 onRemove: function(c){
20176 Ext.layout.FormLayout.superclass.onRemove.call(this, c);
20177 if(this.trackLabels && !this.isHide(c)){
20178 c.un('show', this.onFieldShow, this);
20179 c.un('hide', this.onFieldHide, this);
20181 // check for itemCt, since we may be removing a fieldset or something similar
20182 var el = c.getPositionEl(),
20183 ct = c.getItemCt && c.getItemCt();
20184 if(c.rendered && ct){
20185 el.insertAfter(ct);
20187 Ext.destroyMembers(c, 'label', 'itemCt');
20188 if(c.customItemCt){
20189 Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
20195 setContainer : function(ct){
20196 Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
20198 ct.addClass('x-form-label-'+ct.labelAlign);
20203 labelStyle: 'display:none',
20204 elementStyle: 'padding-left:0;',
20208 this.labelSeparator = ct.labelSeparator || this.labelSeparator;
20209 ct.labelWidth = ct.labelWidth || 100;
20210 if(Ext.isNumber(ct.labelWidth)){
20211 var pad = Ext.isNumber(ct.labelPad) ? ct.labelPad : 5;
20213 labelAdjust: ct.labelWidth + pad,
20214 labelStyle: 'width:' + ct.labelWidth + 'px;',
20215 elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
20218 if(ct.labelAlign == 'top'){
20220 labelStyle: 'width:auto;',
20222 elementStyle: 'padding-left:0;'
20228 isHide: function(c){
20229 return c.hideLabel || this.container.hideLabels;
20232 onFieldShow: function(c){
20233 c.getItemCt().removeClass('x-hide-' + c.hideMode);
20236 onFieldHide: function(c){
20237 c.getItemCt().addClass('x-hide-' + c.hideMode);
20241 getLabelStyle: function(s){
20242 var ls = '', items = [this.labelStyle, s];
20243 for (var i = 0, len = items.length; i < len; ++i){
20246 if (ls.substr(-1, 1) != ';'){
20255 * @cfg {Ext.Template} fieldTpl
20256 * A {@link Ext.Template#compile compile}d {@link Ext.Template} for rendering
20257 * the fully wrapped, labeled and styled form Field. Defaults to:</p><pre><code>
20259 '<div class="x-form-item {itemCls}" tabIndex="-1">',
20260 '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
20261 '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
20262 '</div><div class="{clearCls}"></div>',
20266 * <p>This may be specified to produce a different DOM structure when rendering form Fields.</p>
20267 * <p>A description of the properties within the template follows:</p><div class="mdetail-params"><ul>
20268 * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
20269 * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
20270 * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
20271 * supplied at the container level.</div></li>
20272 * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
20273 * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
20274 * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
20275 * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
20276 * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
20277 * field (defaults to <tt>''</tt>)</div></li>
20278 * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
20279 * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
20280 * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
20281 * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
20282 * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
20283 * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
20285 * <p>Also see <tt>{@link #getTemplateArgs}</tt></p>
20289 renderItem : function(c, position, target){
20290 if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
20291 var args = this.getTemplateArgs(c);
20292 if(Ext.isNumber(position)){
20293 position = target.dom.childNodes[position] || null;
20296 c.itemCt = this.fieldTpl.insertBefore(position, args, true);
20298 c.itemCt = this.fieldTpl.append(target, args, true);
20301 c.render('x-form-el-' + c.id);
20302 }else if(!this.isValidParent(c, target)){
20303 Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
20306 // Non form fields don't have getItemCt, apply it here
20307 // This will get cleaned up in onRemove
20309 getItemCt: function(){
20315 c.label = c.getItemCt().child('label.x-form-item-label');
20316 if(this.trackLabels && !this.isHide(c)){
20318 this.onFieldHide(c);
20322 show: this.onFieldShow,
20323 hide: this.onFieldHide
20326 this.configureItem(c);
20328 Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
20333 * <p>Provides template arguments for rendering the fully wrapped, labeled and styled form Field.</p>
20334 * <p>This method returns an object hash containing properties used by the layout's {@link #fieldTpl}
20335 * to create a correctly wrapped, labeled and styled form Field. This may be overriden to
20336 * create custom layouts. The properties which must be returned are:</p><div class="mdetail-params"><ul>
20337 * <li><b><tt>itemCls</tt></b> : String<div class="sub-desc">The CSS class applied to the outermost div wrapper
20338 * that contains this field label and field element (the default class is <tt>'x-form-item'</tt> and <tt>itemCls</tt>
20339 * will be added to that). If supplied, <tt>itemCls</tt> at the field level will override the default <tt>itemCls</tt>
20340 * supplied at the container level.</div></li>
20341 * <li><b><tt>id</tt></b> : String<div class="sub-desc">The id of the Field</div></li>
20342 * <li><b><tt>{@link #labelStyle}</tt></b> : String<div class="sub-desc">
20343 * A CSS style specification string to add to the field label for this field (defaults to <tt>''</tt> or the
20344 * {@link #labelStyle layout's value for <tt>labelStyle</tt>}).</div></li>
20345 * <li><b><tt>label</tt></b> : String<div class="sub-desc">The text to display as the label for this
20346 * field (defaults to <tt>''</tt>)</div></li>
20347 * <li><b><tt>{@link #labelSeparator}</tt></b> : String<div class="sub-desc">The separator to display after
20348 * the text of the label for this field (defaults to a colon <tt>':'</tt> or the
20349 * {@link #labelSeparator layout's value for labelSeparator}). To hide the separator use empty string ''.</div></li>
20350 * <li><b><tt>elementStyle</tt></b> : String<div class="sub-desc">The styles text for the input element's wrapper.</div></li>
20351 * <li><b><tt>clearCls</tt></b> : String<div class="sub-desc">The CSS class to apply to the special clearing div
20352 * rendered directly after each form field wrapper (defaults to <tt>'x-form-clear-left'</tt>)</div></li>
20354 * @param field The {@link Field Ext.form.Field} being rendered.
20355 * @return An object hash containing the properties required to render the Field.
20357 getTemplateArgs: function(field) {
20358 var noLabelSep = !field.fieldLabel || field.hideLabel;
20361 label: field.fieldLabel,
20362 labelStyle: this.getLabelStyle(field.labelStyle),
20363 elementStyle: this.elementStyle||'',
20364 labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator),
20365 itemCls: (field.itemCls||this.container.itemCls||'') + (field.hideLabel ? ' x-hide-label' : ''),
20366 clearCls: field.clearCls || 'x-form-clear-left'
20371 adjustWidthAnchor: function(value, c){
20372 if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
20373 var adjust = Ext.isIE6 || (Ext.isIE && !Ext.isStrict);
20374 return value - this.labelAdjust + (adjust ? -3 : 0);
20379 adjustHeightAnchor : function(value, c){
20380 if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
20381 return value - c.label.getHeight();
20387 isValidParent : function(c, target){
20388 return target && this.container.getEl().contains(c.getDomPositionEl());
20392 * @property activeItem
20397 Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;/**
\r
20398 * @class Ext.layout.AccordionLayout
\r
20399 * @extends Ext.layout.FitLayout
\r
20400 * <p>This is a layout that manages multiple Panels in an expandable accordion style such that only
\r
20401 * <b>one Panel can be expanded at any given time</b>. Each Panel has built-in support for expanding and collapsing.</p>
\r
20402 * <p>Note: Only Ext.Panels <b>and all subclasses of Ext.Panel</b> may be used in an accordion layout Container.</p>
\r
20403 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
\r
20404 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
\r
20405 * <p>Example usage:</p>
\r
20407 var accordion = new Ext.Panel({
\r
20408 title: 'Accordion Layout',
\r
20409 layout:'accordion',
\r
20411 // applied to each contained panel
\r
20412 bodyStyle: 'padding:15px'
\r
20415 // layout-specific configs go here
\r
20416 titleCollapse: false,
\r
20418 activeOnTop: true
\r
20421 title: 'Panel 1',
\r
20422 html: '<p>Panel content!</p>'
\r
20424 title: 'Panel 2',
\r
20425 html: '<p>Panel content!</p>'
\r
20427 title: 'Panel 3',
\r
20428 html: '<p>Panel content!</p>'
\r
20433 Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
\r
20435 * @cfg {Boolean} fill
\r
20436 * True to adjust the active item's height to fill the available space in the container, false to use the
\r
20437 * item's current height, or auto height if not explicitly set (defaults to true).
\r
20441 * @cfg {Boolean} autoWidth
\r
20442 * True to set each contained item's width to 'auto', false to use the item's current width (defaults to true).
\r
20443 * Note that some components, in particular the {@link Ext.grid.GridPanel grid}, will not function properly within
\r
20444 * layouts if they have auto width, so in such cases this config should be set to false.
\r
20446 autoWidth : true,
\r
20448 * @cfg {Boolean} titleCollapse
\r
20449 * True to allow expand/collapse of each contained panel by clicking anywhere on the title bar, false to allow
\r
20450 * expand/collapse only when the toggle tool button is clicked (defaults to true). When set to false,
\r
20451 * {@link #hideCollapseTool} should be false also.
\r
20453 titleCollapse : true,
\r
20455 * @cfg {Boolean} hideCollapseTool
\r
20456 * True to hide the contained panels' collapse/expand toggle buttons, false to display them (defaults to false).
\r
20457 * When set to true, {@link #titleCollapse} should be true also.
\r
20459 hideCollapseTool : false,
\r
20461 * @cfg {Boolean} collapseFirst
\r
20462 * True to make sure the collapse/expand toggle button always renders first (to the left of) any other tools
\r
20463 * in the contained panels' title bars, false to render it last (defaults to false).
\r
20465 collapseFirst : false,
\r
20467 * @cfg {Boolean} animate
\r
20468 * True to slide the contained panels open and closed during expand/collapse using animation, false to open and
\r
20469 * close directly with no animation (defaults to false). Note: to defer to the specific config setting of each
\r
20470 * contained panel for this property, set this to undefined at the layout level.
\r
20474 * @cfg {Boolean} sequence
\r
20475 * <b>Experimental</b>. If animate is set to true, this will result in each animation running in sequence.
\r
20477 sequence : false,
\r
20479 * @cfg {Boolean} activeOnTop
\r
20480 * True to swap the position of each panel as it is expanded so that it becomes the first item in the container,
\r
20481 * false to keep the panels in the rendered order. <b>This is NOT compatible with "animate:true"</b> (defaults to false).
\r
20483 activeOnTop : false,
\r
20485 renderItem : function(c){
\r
20486 if(this.animate === false){
\r
20487 c.animCollapse = false;
\r
20489 c.collapsible = true;
\r
20490 if(this.autoWidth){
\r
20491 c.autoWidth = true;
\r
20493 if(this.titleCollapse){
\r
20494 c.titleCollapse = true;
\r
20496 if(this.hideCollapseTool){
\r
20497 c.hideCollapseTool = true;
\r
20499 if(this.collapseFirst !== undefined){
\r
20500 c.collapseFirst = this.collapseFirst;
\r
20502 if(!this.activeItem && !c.collapsed){
\r
20503 this.activeItem = c;
\r
20504 }else if(this.activeItem && this.activeItem != c){
\r
20505 c.collapsed = true;
\r
20507 Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
\r
20508 c.header.addClass('x-accordion-hd');
\r
20509 c.on('beforeexpand', this.beforeExpand, this);
\r
20512 onRemove: function(c){
\r
20513 Ext.layout.AccordionLayout.superclass.onRemove.call(this, c);
\r
20515 c.header.removeClass('x-accordion-hd');
\r
20517 c.un('beforeexpand', this.beforeExpand, this);
\r
20521 beforeExpand : function(p, anim){
\r
20522 var ai = this.activeItem;
\r
20524 if(this.sequence){
\r
20525 delete this.activeItem;
\r
20526 if (!ai.collapsed){
\r
20527 ai.collapse({callback:function(){
\r
20528 p.expand(anim || true);
\r
20529 }, scope: this});
\r
20533 ai.collapse(this.animate);
\r
20536 this.activeItem = p;
\r
20537 if(this.activeOnTop){
\r
20538 p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
\r
20544 setItemSize : function(item, size){
\r
20545 if(this.fill && item){
\r
20547 this.container.items.each(function(p){
\r
20549 hh += p.header.getHeight();
\r
20552 size.height -= hh;
\r
20553 item.setSize(size);
\r
20558 * Sets the active (expanded) item in the layout.
\r
20559 * @param {String/Number} item The string component id or numeric index of the item to activate
\r
20561 setActiveItem : function(item){
\r
20562 item = this.container.getComponent(item);
\r
20563 if(this.activeItem != item){
\r
20564 if(item.rendered && item.collapsed){
\r
20567 this.activeItem = item;
\r
20573 Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
\r
20575 //backwards compat
\r
20576 Ext.layout.Accordion = Ext.layout.AccordionLayout;/**
\r
20577 * @class Ext.layout.TableLayout
\r
20578 * @extends Ext.layout.ContainerLayout
\r
20579 * <p>This layout allows you to easily render content into an HTML table. The total number of columns can be
\r
20580 * specified, and rowspan and colspan can be used to create complex layouts within the table.
\r
20581 * This class is intended to be extended or created via the layout:'table' {@link Ext.Container#layout} config,
\r
20582 * and should generally not need to be created directly via the new keyword.</p>
\r
20583 * <p>Note that when creating a layout via config, the layout-specific config properties must be passed in via
\r
20584 * the {@link Ext.Container#layoutConfig} object which will then be applied internally to the layout. In the
\r
20585 * case of TableLayout, the only valid layout config property is {@link #columns}. However, the items added to a
\r
20586 * TableLayout can supply the following table-specific config properties:</p>
\r
20588 * <li><b>rowspan</b> Applied to the table cell containing the item.</li>
\r
20589 * <li><b>colspan</b> Applied to the table cell containing the item.</li>
\r
20590 * <li><b>cellId</b> An id applied to the table cell containing the item.</li>
\r
20591 * <li><b>cellCls</b> A CSS class name added to the table cell containing the item.</li>
\r
20593 * <p>The basic concept of building up a TableLayout is conceptually very similar to building up a standard
\r
20594 * HTML table. You simply add each panel (or "cell") that you want to include along with any span attributes
\r
20595 * specified as the special config properties of rowspan and colspan which work exactly like their HTML counterparts.
\r
20596 * Rather than explicitly creating and nesting rows and columns as you would in HTML, you simply specify the
\r
20597 * total column count in the layoutConfig and start adding panels in their natural order from left to right,
\r
20598 * top to bottom. The layout will automatically figure out, based on the column count, rowspans and colspans,
\r
20599 * how to position each panel within the table. Just like with HTML tables, your rowspans and colspans must add
\r
20600 * up correctly in your overall layout or you'll end up with missing and/or extra cells! Example usage:</p>
\r
20602 // This code will generate a layout table that is 3 columns by 2 rows
\r
20603 // with some spanning included. The basic layout will be:
\r
20604 // +--------+-----------------+
\r
20606 // | |--------+--------|
\r
20608 // +--------+--------+--------+
\r
20609 var table = new Ext.Panel({
\r
20610 title: 'Table Layout',
\r
20613 // applied to each contained panel
\r
20614 bodyStyle:'padding:20px'
\r
20617 // The total column count must be specified here
\r
20621 html: '<p>Cell A content</p>',
\r
20624 html: '<p>Cell B content</p>',
\r
20627 html: '<p>Cell C content</p>',
\r
20628 cellCls: 'highlight'
\r
20630 html: '<p>Cell D content</p>'
\r
20635 Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
20637 * @cfg {Number} columns
\r
20638 * The total number of columns to create in the table for this layout. If not specified, all Components added to
\r
20639 * this layout will be rendered into a single row using one column per Component.
\r
20643 monitorResize:false,
\r
20646 * @cfg {Object} tableAttrs
\r
20647 * <p>An object containing properties which are added to the {@link Ext.DomHelper DomHelper} specification
\r
20648 * used to create the layout's <tt><table></tt> element. Example:</p><pre><code>
\r
20665 setContainer : function(ct){
\r
20666 Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
\r
20668 this.currentRow = 0;
\r
20669 this.currentColumn = 0;
\r
20674 onLayout : function(ct, target){
\r
20675 var cs = ct.items.items, len = cs.length, c, i;
\r
20678 target.addClass('x-table-layout-ct');
\r
20680 this.table = target.createChild(
\r
20681 Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
\r
20683 this.renderAll(ct, target);
\r
20687 getRow : function(index){
\r
20688 var row = this.table.tBodies[0].childNodes[index];
\r
20690 row = document.createElement('tr');
\r
20691 this.table.tBodies[0].appendChild(row);
\r
20697 getNextCell : function(c){
\r
20698 var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
\r
20699 var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
\r
20700 for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
\r
20701 if(!this.cells[rowIndex]){
\r
20702 this.cells[rowIndex] = [];
\r
20704 for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
\r
20705 this.cells[rowIndex][colIndex] = true;
\r
20708 var td = document.createElement('td');
\r
20710 td.id = c.cellId;
\r
20712 var cls = 'x-table-layout-cell';
\r
20714 cls += ' ' + c.cellCls;
\r
20716 td.className = cls;
\r
20718 td.colSpan = c.colspan;
\r
20721 td.rowSpan = c.rowspan;
\r
20723 this.getRow(curRow).appendChild(td);
\r
20728 getNextNonSpan: function(colIndex, rowIndex){
\r
20729 var cols = this.columns;
\r
20730 while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
\r
20731 if(cols && colIndex >= cols){
\r
20738 return [colIndex, rowIndex];
\r
20742 renderItem : function(c, position, target){
\r
20743 if(c && !c.rendered){
\r
20744 c.render(this.getNextCell(c));
\r
20745 this.configureItem(c, position);
\r
20746 }else if(c && !this.isValidParent(c, target)){
\r
20747 var container = this.getNextCell(c);
\r
20748 container.insertBefore(c.getDomPositionEl().dom, null);
\r
20749 c.container = Ext.get(container);
\r
20750 this.configureItem(c, position);
\r
20755 isValidParent : function(c, target){
\r
20756 return c.getDomPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
\r
20760 * @property activeItem
\r
20765 Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;/**
\r
20766 * @class Ext.layout.AbsoluteLayout
\r
20767 * @extends Ext.layout.AnchorLayout
\r
20768 * <p>This is a layout that inherits the anchoring of <b>{@link Ext.layout.AnchorLayout}</b> and adds the
\r
20769 * ability for x/y positioning using the standard x and y component config options.</p>
\r
20770 * <p>This class is intended to be extended or created via the <tt><b>{@link Ext.Container#layout layout}</b></tt>
\r
20771 * configuration property. See <tt><b>{@link Ext.Container#layout}</b></tt> for additional details.</p>
\r
20772 * <p>Example usage:</p>
\r
20774 var form = new Ext.form.FormPanel({
\r
20775 title: 'Absolute Layout',
\r
20776 layout:'absolute',
\r
20778 // layout-specific configs go here
\r
20779 extraCls: 'x-abs-layout-item',
\r
20781 baseCls: 'x-plain',
\r
20782 url:'save-form.php',
\r
20783 defaultType: 'textfield',
\r
20793 anchor:'100%' // anchor width by percentage
\r
20803 anchor: '100%' // anchor width by percentage
\r
20807 xtype: 'textarea',
\r
20809 anchor: '100% 100%' // anchor width and height
\r
20814 Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
\r
20816 extraCls: 'x-abs-layout-item',
\r
20818 onLayout : function(ct, target){
\r
20819 target.position();
\r
20820 this.paddingLeft = target.getPadding('l');
\r
20821 this.paddingTop = target.getPadding('t');
\r
20823 Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
\r
20827 adjustWidthAnchor : function(value, comp){
\r
20828 return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
\r
20832 adjustHeightAnchor : function(value, comp){
\r
20833 return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
\r
20836 * @property activeItem
\r
20840 Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
20842 * @class Ext.layout.BoxLayout
\r
20843 * @extends Ext.layout.ContainerLayout
\r
20844 * <p>Base Class for HBoxLayout and VBoxLayout Classes. Generally it should not need to be used directly.</p>
\r
20846 Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
20848 * @cfg {Object} defaultMargins
\r
20849 * <p>If the individual contained items do not have a <tt>margins</tt>
\r
20850 * property specified, the default margins from this property will be
\r
20851 * applied to each item.</p>
\r
20852 * <br><p>This property may be specified as an object containing margins
\r
20853 * to apply in the format:</p><pre><code>
\r
20855 top: (top margin),
\r
20856 right: (right margin),
\r
20857 bottom: (bottom margin),
\r
20858 left: (left margin)
\r
20860 * <p>This property may also be specified as a string containing
\r
20861 * space-separated, numeric margin values. The order of the sides associated
\r
20862 * with each value matches the way CSS processes margin values:</p>
\r
20863 * <div class="mdetail-params"><ul>
\r
20864 * <li>If there is only one value, it applies to all sides.</li>
\r
20865 * <li>If there are two values, the top and bottom borders are set to the
\r
20866 * first value and the right and left are set to the second.</li>
\r
20867 * <li>If there are three values, the top is set to the first value, the left
\r
20868 * and right are set to the second, and the bottom is set to the third.</li>
\r
20869 * <li>If there are four values, they apply to the top, right, bottom, and
\r
20870 * left, respectively.</li>
\r
20872 * <p>Defaults to:</p><pre><code>
\r
20873 * {top:0, right:0, bottom:0, left:0}
\r
20876 defaultMargins : {left:0,top:0,right:0,bottom:0},
\r
20878 * @cfg {String} padding
\r
20879 * <p>Sets the padding to be applied to all child items managed by this layout.</p>
\r
20880 * <p>This property must be specified as a string containing
\r
20881 * space-separated, numeric padding values. The order of the sides associated
\r
20882 * with each value matches the way CSS processes padding values:</p>
\r
20883 * <div class="mdetail-params"><ul>
\r
20884 * <li>If there is only one value, it applies to all sides.</li>
\r
20885 * <li>If there are two values, the top and bottom borders are set to the
\r
20886 * first value and the right and left are set to the second.</li>
\r
20887 * <li>If there are three values, the top is set to the first value, the left
\r
20888 * and right are set to the second, and the bottom is set to the third.</li>
\r
20889 * <li>If there are four values, they apply to the top, right, bottom, and
\r
20890 * left, respectively.</li>
\r
20892 * <p>Defaults to: <code>"0"</code></p>
\r
20895 // documented in subclasses
\r
20899 monitorResize : true,
\r
20900 scrollOffset : 0,
\r
20901 extraCls : 'x-box-item',
\r
20902 ctCls : 'x-box-layout-ct',
\r
20903 innerCls : 'x-box-inner',
\r
20905 constructor : function(config){
\r
20906 Ext.layout.BoxLayout.superclass.constructor.call(this, config);
\r
20907 if(Ext.isString(this.defaultMargins)){
\r
20908 this.defaultMargins = this.parseMargins(this.defaultMargins);
\r
20913 isValidParent : function(c, target){
\r
20914 return c.getEl().dom.parentNode == this.innerCt.dom;
\r
20918 onLayout : function(ct, target){
\r
20919 var cs = ct.items.items, len = cs.length, c, i, last = len-1, cm;
\r
20921 if(!this.innerCt){
\r
20922 target.addClass(this.ctCls);
\r
20924 // the innerCt prevents wrapping and shuffling while
\r
20925 // the container is resizing
\r
20926 this.innerCt = target.createChild({cls:this.innerCls});
\r
20927 this.padding = this.parseMargins(this.padding);
\r
20929 this.renderAll(ct, this.innerCt);
\r
20933 renderItem : function(c){
\r
20934 if(Ext.isString(c.margins)){
\r
20935 c.margins = this.parseMargins(c.margins);
\r
20936 }else if(!c.margins){
\r
20937 c.margins = this.defaultMargins;
\r
20939 Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
\r
20942 getTargetSize : function(target){
20943 return (Ext.isIE6 && Ext.isStrict && target.dom == document.body) ? target.getStyleSize() : target.getViewSize();
\r
20946 getItems: function(ct){
\r
20948 ct.items.each(function(c){
\r
20949 if(c.isVisible()){
\r
20957 * @property activeItem
\r
20963 * @class Ext.layout.VBoxLayout
\r
20964 * @extends Ext.layout.BoxLayout
\r
20965 * <p>A layout that arranges items vertically down a Container. This layout optionally divides available vertical
\r
20966 * space between child items containing a numeric <code>flex</code> configuration.</p>
\r
20967 * This layout may also be used to set the widths of child items by configuring it with the {@link #align} option.
\r
20969 Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
\r
20971 * @cfg {String} align
\r
20972 * Controls how the child items of the container are aligned. Acceptable configuration values for this
\r
20974 * <div class="mdetail-params"><ul>
\r
20975 * <li><b><tt>left</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned horizontally
\r
20976 * at the <b>left</b> side of the container</div></li>
\r
20977 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are aligned horizontally at the
\r
20978 * <b>mid-width</b> of the container</div></li>
\r
20979 * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched horizontally to fill
\r
20980 * the width of the container</div></li>
\r
20981 * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched horizontally to
\r
20982 * the size of the largest item.</div></li>
\r
20985 align : 'left', // left, center, stretch, strechmax
\r
20987 * @cfg {String} pack
\r
20988 * Controls how the child items of the container are packed together. Acceptable configuration values
\r
20989 * for this property are:
\r
20990 * <div class="mdetail-params"><ul>
\r
20991 * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
\r
20992 * <b>top</b> side of container</div></li>
\r
20993 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
\r
20994 * <b>mid-height</b> of container</div></li>
\r
20995 * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>bottom</b>
\r
20996 * side of container</div></li>
\r
21000 * @cfg {Number} flex
\r
21001 * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
\r
21002 * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>vertically</b>
\r
21003 * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
\r
21004 * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
\r
21005 * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
\r
21009 onLayout : function(ct, target){
\r
21010 Ext.layout.VBoxLayout.superclass.onLayout.call(this, ct, target);
\r
21013 var cs = this.getItems(ct), cm, ch, margin,
\r
21014 size = this.getTargetSize(target),
\r
21015 w = size.width - target.getPadding('lr'),
\r
21016 h = size.height - target.getPadding('tb') - this.scrollOffset,
\r
21017 l = this.padding.left, t = this.padding.top,
\r
21018 isStart = this.pack == 'start',
\r
21019 isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
\r
21020 stretchWidth = w - (this.padding.left + this.padding.right),
\r
21027 Ext.each(cs, function(c){
\r
21029 totalFlex += c.flex || 0;
\r
21030 ch = c.getHeight();
\r
21031 margin = cm.top + cm.bottom;
\r
21032 extraHeight += ch + margin;
\r
21033 flexHeight += margin + (c.flex ? 0 : ch);
\r
21034 maxWidth = Math.max(maxWidth, c.getWidth() + cm.left + cm.right);
\r
21036 extraHeight = h - extraHeight - this.padding.top - this.padding.bottom;
\r
21038 var innerCtWidth = maxWidth + this.padding.left + this.padding.right;
\r
21039 switch(this.align){
\r
21041 this.innerCt.setSize(w, h);
\r
21043 case 'stretchmax':
\r
21045 this.innerCt.setSize(innerCtWidth, h);
\r
21048 this.innerCt.setSize(w = Math.max(w, innerCtWidth), h);
\r
21052 var availHeight = Math.max(0, h - this.padding.top - this.padding.bottom - flexHeight),
\r
21053 leftOver = availHeight,
\r
21057 availableWidth = Math.max(0, w - this.padding.left - this.padding.right);
\r
21060 Ext.each(cs, function(c){
\r
21061 if(isStart && c.flex){
\r
21062 ch = Math.floor(availHeight * (c.flex / totalFlex));
\r
21064 heights.push(ch);
\r
21068 if(this.pack == 'center'){
\r
21069 t += extraHeight ? extraHeight / 2 : 0;
\r
21070 }else if(this.pack == 'end'){
\r
21071 t += extraHeight;
\r
21073 Ext.each(cs, function(c){
\r
21076 c.setPosition(l + cm.left, t);
\r
21077 if(isStart && c.flex){
\r
21078 ch = Math.max(0, heights[idx++] + (leftOver-- > 0 ? 1 : 0));
\r
21080 restore.push(c.getWidth());
\r
21082 c.setSize(availableWidth, ch);
\r
21084 ch = c.getHeight();
\r
21086 t += ch + cm.bottom;
\r
21090 Ext.each(cs, function(c){
\r
21092 if(this.align == 'stretch'){
\r
21093 c.setWidth((stretchWidth - (cm.left + cm.right)).constrain(
\r
21094 c.minWidth || 0, c.maxWidth || 1000000));
\r
21095 }else if(this.align == 'stretchmax'){
\r
21096 c.setWidth((maxWidth - (cm.left + cm.right)).constrain(
\r
21097 c.minWidth || 0, c.maxWidth || 1000000));
\r
21099 if(this.align == 'center'){
\r
21100 var diff = availableWidth - (c.getWidth() + cm.left + cm.right);
\r
21102 c.setPosition(l + cm.left + (diff/2), c.y);
\r
21105 if(isStart && c.flex){
\r
21106 c.setWidth(restore[idx++]);
\r
21112 * @property activeItem
\r
21117 Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
\r
21120 * @class Ext.layout.HBoxLayout
\r
21121 * @extends Ext.layout.BoxLayout
\r
21122 * <p>A layout that arranges items horizontally across a Container. This layout optionally divides available horizontal
\r
21123 * space between child items containing a numeric <code>flex</code> configuration.</p>
\r
21124 * This layout may also be used to set the heights of child items by configuring it with the {@link #align} option.
\r
21126 Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
\r
21128 * @cfg {String} align
\r
21129 * Controls how the child items of the container are aligned. Acceptable configuration values for this
\r
21131 * <div class="mdetail-params"><ul>
\r
21132 * <li><b><tt>top</tt></b> : <b>Default</b><div class="sub-desc">child items are aligned vertically
\r
21133 * at the <b>left</b> side of the container</div></li>
\r
21134 * <li><b><tt>middle</tt></b> : <div class="sub-desc">child items are aligned vertically at the
\r
21135 * <b>mid-height</b> of the container</div></li>
\r
21136 * <li><b><tt>stretch</tt></b> : <div class="sub-desc">child items are stretched vertically to fill
\r
21137 * the height of the container</div></li>
\r
21138 * <li><b><tt>stretchmax</tt></b> : <div class="sub-desc">child items are stretched vertically to
\r
21139 * the size of the largest item.</div></li>
\r
21141 align : 'top', // top, middle, stretch, strechmax
\r
21143 * @cfg {String} pack
\r
21144 * Controls how the child items of the container are packed together. Acceptable configuration values
\r
21145 * for this property are:
\r
21146 * <div class="mdetail-params"><ul>
\r
21147 * <li><b><tt>start</tt></b> : <b>Default</b><div class="sub-desc">child items are packed together at
\r
21148 * <b>left</b> side of container</div></li>
\r
21149 * <li><b><tt>center</tt></b> : <div class="sub-desc">child items are packed together at
\r
21150 * <b>mid-width</b> of container</div></li>
\r
21151 * <li><b><tt>end</tt></b> : <div class="sub-desc">child items are packed together at <b>right</b>
\r
21152 * side of container</div></li>
\r
21156 * @cfg {Number} flex
\r
21157 * This configuation option is to be applied to <b>child <tt>items</tt></b> of the container managed
\r
21158 * by this layout. Each child item with a <tt>flex</tt> property will be flexed <b>horizontally</b>
\r
21159 * according to each item's <b>relative</b> <tt>flex</tt> value compared to the sum of all items with
\r
21160 * a <tt>flex</tt> value specified. Any child items that have either a <tt>flex = 0</tt> or
\r
21161 * <tt>flex = undefined</tt> will not be 'flexed' (the initial size will not be changed).
\r
21165 onLayout : function(ct, target){
\r
21166 Ext.layout.HBoxLayout.superclass.onLayout.call(this, ct, target);
\r
21168 var cs = this.getItems(ct), cm, cw, margin,
\r
21169 size = this.getTargetSize(target),
\r
21170 w = size.width - target.getPadding('lr') - this.scrollOffset,
\r
21171 h = size.height - target.getPadding('tb'),
\r
21172 l = this.padding.left, t = this.padding.top,
\r
21173 isStart = this.pack == 'start',
\r
21174 isRestore = ['stretch', 'stretchmax'].indexOf(this.align) == -1,
\r
21175 stretchHeight = h - (this.padding.top + this.padding.bottom),
\r
21182 Ext.each(cs, function(c){
\r
21184 totalFlex += c.flex || 0;
\r
21185 cw = c.getWidth();
\r
21186 margin = cm.left + cm.right;
\r
21187 extraWidth += cw + margin;
\r
21188 flexWidth += margin + (c.flex ? 0 : cw);
\r
21189 maxHeight = Math.max(maxHeight, c.getHeight() + cm.top + cm.bottom);
\r
21191 extraWidth = w - extraWidth - this.padding.left - this.padding.right;
\r
21193 var innerCtHeight = maxHeight + this.padding.top + this.padding.bottom;
\r
21194 switch(this.align){
\r
21196 this.innerCt.setSize(w, h);
\r
21198 case 'stretchmax':
\r
21200 this.innerCt.setSize(w, innerCtHeight);
\r
21203 this.innerCt.setSize(w, h = Math.max(h, innerCtHeight));
\r
21208 var availWidth = Math.max(0, w - this.padding.left - this.padding.right - flexWidth),
\r
21209 leftOver = availWidth,
\r
21213 availableHeight = Math.max(0, h - this.padding.top - this.padding.bottom);
\r
21216 Ext.each(cs, function(c){
\r
21217 if(isStart && c.flex){
\r
21218 cw = Math.floor(availWidth * (c.flex / totalFlex));
\r
21224 if(this.pack == 'center'){
\r
21225 l += extraWidth ? extraWidth / 2 : 0;
\r
21226 }else if(this.pack == 'end'){
\r
21229 Ext.each(cs, function(c){
\r
21232 c.setPosition(l, t + cm.top);
\r
21233 if(isStart && c.flex){
\r
21234 cw = Math.max(0, widths[idx++] + (leftOver-- > 0 ? 1 : 0));
\r
21236 restore.push(c.getHeight());
\r
21238 c.setSize(cw, availableHeight);
\r
21240 cw = c.getWidth();
\r
21242 l += cw + cm.right;
\r
21246 Ext.each(cs, function(c){
\r
21247 var cm = c.margins;
\r
21248 if(this.align == 'stretch'){
\r
21249 c.setHeight((stretchHeight - (cm.top + cm.bottom)).constrain(
\r
21250 c.minHeight || 0, c.maxHeight || 1000000));
\r
21251 }else if(this.align == 'stretchmax'){
\r
21252 c.setHeight((maxHeight - (cm.top + cm.bottom)).constrain(
\r
21253 c.minHeight || 0, c.maxHeight || 1000000));
\r
21255 if(this.align == 'middle'){
\r
21256 var diff = availableHeight - (c.getHeight() + cm.top + cm.bottom);
\r
21258 c.setPosition(c.x, t + cm.top + (diff/2));
\r
21261 if(isStart && c.flex){
\r
21262 c.setHeight(restore[idx++]);
\r
21269 * @property activeItem
\r
21274 Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
21276 * @class Ext.Viewport
\r
21277 * @extends Ext.Container
\r
21278 * <p>A specialized container representing the viewable application area (the browser viewport).</p>
\r
21279 * <p>The Viewport renders itself to the document body, and automatically sizes itself to the size of
\r
21280 * the browser viewport and manages window resizing. There may only be one Viewport created
\r
21281 * in a page. Inner layouts are available by virtue of the fact that all {@link Ext.Panel Panel}s
\r
21282 * added to the Viewport, either through its {@link #items}, or through the items, or the {@link #add}
\r
21283 * method of any of its child Panels may themselves have a layout.</p>
\r
21284 * <p>The Viewport does not provide scrolling, so child Panels within the Viewport should provide
\r
21285 * for scrolling if needed using the {@link #autoScroll} config.</p>
\r
21286 * <p>An example showing a classic application border layout:</p><pre><code>
\r
21287 new Ext.Viewport({
\r
21288 layout: 'border',
\r
21291 html: '<h1 class="x-panel-header">Page Title</h1>',
\r
21292 autoHeight: true,
\r
21294 margins: '0 0 5 0'
\r
21297 collapsible: true,
\r
21298 title: 'Navigation',
\r
21300 // the west region might typically utilize a {@link Ext.tree.TreePanel TreePanel} or a Panel with {@link Ext.layout.AccordionLayout Accordion layout}
\r
21303 title: 'Title for Panel',
\r
21304 collapsible: true,
\r
21305 html: 'Information goes here',
\r
21311 title: 'Title for the Grid Panel',
\r
21312 collapsible: true,
\r
21316 // remaining grid configuration not shown ...
\r
21317 // notice that the GridPanel is added directly as the region
\r
21318 // it is not "overnested" inside another Panel
\r
21320 region: 'center',
\r
21321 xtype: 'tabpanel', // TabPanel itself has no title
\r
21323 title: 'Default Tab',
\r
21324 html: 'The first tab\'s content. Others may be added dynamically'
\r
21330 * Create a new Viewport
\r
21331 * @param {Object} config The config object
\r
21332 * @xtype viewport
\r
21334 Ext.Viewport = Ext.extend(Ext.Container, {
\r
21336 * Privatize config options which, if used, would interfere with the
\r
21337 * correct operation of the Viewport as the sole manager of the
\r
21338 * layout of the document body.
\r
21341 * @cfg {Mixed} applyTo @hide
\r
21344 * @cfg {Boolean} allowDomMove @hide
\r
21347 * @cfg {Boolean} hideParent @hide
\r
21350 * @cfg {Mixed} renderTo @hide
\r
21353 * @cfg {Boolean} hideParent @hide
\r
21356 * @cfg {Number} height @hide
\r
21359 * @cfg {Number} width @hide
\r
21362 * @cfg {Boolean} autoHeight @hide
\r
21365 * @cfg {Boolean} autoWidth @hide
\r
21368 * @cfg {Boolean} deferHeight @hide
\r
21371 * @cfg {Boolean} monitorResize @hide
\r
21373 initComponent : function() {
\r
21374 Ext.Viewport.superclass.initComponent.call(this);
\r
21375 document.getElementsByTagName('html')[0].className += ' x-viewport';
\r
21376 this.el = Ext.getBody();
\r
21377 this.el.setHeight = Ext.emptyFn;
\r
21378 this.el.setWidth = Ext.emptyFn;
\r
21379 this.el.setSize = Ext.emptyFn;
\r
21380 this.el.dom.scroll = 'no';
\r
21381 this.allowDomMove = false;
\r
21382 this.autoWidth = true;
\r
21383 this.autoHeight = true;
\r
21384 Ext.EventManager.onWindowResize(this.fireResize, this);
\r
21385 this.renderTo = this.el;
\r
21388 fireResize : function(w, h){
\r
21389 this.fireEvent('resize', this, w, h, w, h);
\r
21392 Ext.reg('viewport', Ext.Viewport);/**
21394 * @extends Ext.Container
21395 * <p>Panel is a container that has specific functionality and structural components that make
21396 * it the perfect building block for application-oriented user interfaces.</p>
21397 * <p>Panels are, by virtue of their inheritance from {@link Ext.Container}, capable
21398 * of being configured with a {@link Ext.Container#layout layout}, and containing child Components.</p>
21399 * <p>When either specifying child {@link Ext.Component#items items} of a Panel, or dynamically {@link Ext.Container#add adding} Components
21400 * to a Panel, remember to consider how you wish the Panel to arrange those child elements, and whether
21401 * those child elements need to be sized using one of Ext's built-in <code><b>{@link Ext.Container#layout layout}</b></code> schemes. By
21402 * default, Panels use the {@link Ext.layout.ContainerLayout ContainerLayout} scheme. This simply renders
21403 * child components, appending them one after the other inside the Container, and <b>does not apply any sizing</b>
21405 * <p>A Panel may also contain {@link #bbar bottom} and {@link #tbar top} toolbars, along with separate
21406 * {@link #header}, {@link #footer} and {@link #body} sections (see {@link #frame} for additional
21407 * information).</p>
21408 * <p>Panel also provides built-in {@link #collapsible expandable and collapsible behavior}, along with
21409 * a variety of {@link #tools prebuilt tool buttons} that can be wired up to provide other customized
21410 * behavior. Panels can be easily dropped into any {@link Ext.Container Container} or layout, and the
21411 * layout and rendering pipeline is {@link Ext.Container#add completely managed by the framework}.</p>
21413 * @param {Object} config The config object
21416 Ext.Panel = Ext.extend(Ext.Container, {
21418 * The Panel's header {@link Ext.Element Element}. Read-only.
21419 * <p>This Element is used to house the {@link #title} and {@link #tools}</p>
21420 * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
21421 * @type Ext.Element
21425 * The Panel's body {@link Ext.Element Element} which may be used to contain HTML content.
21426 * The content may be specified in the {@link #html} config, or it may be loaded using the
21427 * {@link autoLoad} config, or through the Panel's {@link #getUpdater Updater}. Read-only.
21428 * <p>If this is used to load visible HTML elements in either way, then
21429 * the Panel may not be used as a Layout for hosting nested Panels.</p>
21430 * <p>If this Panel is intended to be used as the host of a Layout (See {@link #layout}
21431 * then the body Element must not be loaded or changed - it is under the control
21432 * of the Panel's Layout.
21433 * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
21434 * @type Ext.Element
21438 * The Panel's bwrap {@link Ext.Element Element} used to contain other Panel elements
21439 * (tbar, body, bbar, footer). See {@link #bodyCfg}. Read-only.
21440 * @type Ext.Element
21444 * True if this panel is collapsed. Read-only.
21446 * @property collapsed
21449 * @cfg {Object} bodyCfg
21450 * <p>A {@link Ext.DomHelper DomHelper} element specification object may be specified for any
21451 * Panel Element.</p>
21452 * <p>By default, the Default element in the table below will be used for the html markup to
21453 * create a child element with the commensurate Default class name (<code>baseCls</code> will be
21454 * replaced by <code>{@link #baseCls}</code>):</p>
21456 * Panel Default Default Custom Additional Additional
21457 * Element element class element class style
21458 * ======== ========================== ========= ============== ===========
21459 * {@link #header} div {@link #baseCls}+'-header' {@link #headerCfg} headerCssClass headerStyle
21460 * {@link #bwrap} div {@link #baseCls}+'-bwrap' {@link #bwrapCfg} bwrapCssClass bwrapStyle
21461 * + tbar div {@link #baseCls}+'-tbar' {@link #tbarCfg} tbarCssClass tbarStyle
21462 * + {@link #body} div {@link #baseCls}+'-body' {@link #bodyCfg} {@link #bodyCssClass} {@link #bodyStyle}
21463 * + bbar div {@link #baseCls}+'-bbar' {@link #bbarCfg} bbarCssClass bbarStyle
21464 * + {@link #footer} div {@link #baseCls}+'-footer' {@link #footerCfg} footerCssClass footerStyle
21466 * <p>Configuring a Custom element may be used, for example, to force the {@link #body} Element
21467 * to use a different form of markup than is created by default. An example of this might be
21468 * to {@link Ext.Element#createChild create a child} Panel containing a custom content, such as
21469 * a header, or forcing centering of all Panel content by having the body be a <center>
21473 title: 'Message Title',
21474 renderTo: Ext.getBody(),
21475 width: 200, height: 130,
21478 cls: 'x-panel-body', // Default class not applied if Custom element specified
21483 cls: 'x-panel-footer' // same as the Default class
21484 html: 'footer html'
21486 footerCssClass: 'custom-footer', // additional css class, see {@link Ext.element#addClass addClass}
21487 footerStyle: 'background-color:red' // see {@link #bodyStyle}
21490 * <p>The example above also explicitly creates a <code>{@link #footer}</code> with custom markup and
21491 * styling applied.</p>
21494 * @cfg {Object} headerCfg
21495 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
21496 * of this Panel's {@link #header} Element. See <code>{@link #bodyCfg}</code> also.</p>
21499 * @cfg {Object} bwrapCfg
21500 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
21501 * of this Panel's {@link #bwrap} Element. See <code>{@link #bodyCfg}</code> also.</p>
21504 * @cfg {Object} tbarCfg
21505 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
21506 * of this Panel's {@link #tbar} Element. See <code>{@link #bodyCfg}</code> also.</p>
21509 * @cfg {Object} bbarCfg
21510 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
21511 * of this Panel's {@link #bbar} Element. See <code>{@link #bodyCfg}</code> also.</p>
21514 * @cfg {Object} footerCfg
21515 * <p>A {@link Ext.DomHelper DomHelper} element specification object specifying the element structure
21516 * of this Panel's {@link #footer} Element. See <code>{@link #bodyCfg}</code> also.</p>
21519 * @cfg {Boolean} closable
21520 * Panels themselves do not directly support being closed, but some Panel subclasses do (like
21521 * {@link Ext.Window}) or a Panel Class within an {@link Ext.TabPanel}. Specify <code>true</code>
21522 * to enable closing in such situations. Defaults to <code>false</code>.
21525 * The Panel's footer {@link Ext.Element Element}. Read-only.
21526 * <p>This Element is used to house the Panel's <code>{@link #buttons}</code> or <code>{@link #fbar}</code>.</p>
21527 * <br><p><b>Note</b>: see the Note for <code>{@link Ext.Component#el el}</code> also.</p>
21528 * @type Ext.Element
21532 * @cfg {Mixed} applyTo
21533 * <p>The id of the node, a DOM node or an existing Element corresponding to a DIV that is already present in
21534 * the document that specifies some panel-specific structural markup. When <code>applyTo</code> is used,
21535 * constituent parts of the panel can be specified by CSS class name within the main element, and the panel
21536 * will automatically create those components from that markup. Any required components not specified in the
21537 * markup will be autogenerated if necessary.</p>
21538 * <p>The following class names are supported (baseCls will be replaced by {@link #baseCls}):</p>
21539 * <ul><li>baseCls + '-header'</li>
21540 * <li>baseCls + '-header-text'</li>
21541 * <li>baseCls + '-bwrap'</li>
21542 * <li>baseCls + '-tbar'</li>
21543 * <li>baseCls + '-body'</li>
21544 * <li>baseCls + '-bbar'</li>
21545 * <li>baseCls + '-footer'</li></ul>
21546 * <p>Using this config, a call to render() is not required. If applyTo is specified, any value passed for
21547 * {@link #renderTo} will be ignored and the target element's parent node will automatically be used as the
21548 * panel's container.</p>
21551 * @cfg {Object/Array} tbar
21552 * <p>The top toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
21553 * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
21554 * To access the top toolbar after render, use {@link #getTopToolbar}.</p>
21555 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> be updated by a load
21556 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
21557 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
21558 * submission parameters are collected from the DOM tree.</p>
21561 * @cfg {Object/Array} bbar
21562 * <p>The bottom toolbar of the panel. This can be a {@link Ext.Toolbar} object, a toolbar config, or an array of
21563 * buttons/button configs to be added to the toolbar. Note that this is not available as a property after render.
21564 * To access the bottom toolbar after render, use {@link #getBottomToolbar}.</p>
21565 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> be updated by a load
21566 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
21567 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
21568 * submission parameters are collected from the DOM tree.</p>
21570 /** @cfg {Object/Array} fbar
21571 * <p>A {@link Ext.Toolbar Toolbar} object, a Toolbar config, or an array of
21572 * {@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>
21573 * <p>After render, the <code>fbar</code> property will be an {@link Ext.Toolbar Toolbar} instance.</p>
21574 * <p>If <code>{@link #buttons}</code> are specified, they will supersede the <code>fbar</code> configuration property.</p>
21575 * The Panel's <code>{@link #buttonAlign}</code> configuration affects the layout of these items, for example:
21577 var w = new Ext.Window({
21580 bbar: new Ext.Toolbar({
21587 {@link #buttonAlign}: 'left', // anything but 'center' or 'right' and you can use '-', and '->'
21588 // to control the alignment of fbar items
21596 * <p><b>Note:</b> Although a Toolbar may contain Field components, these will <b>not</b> be updated by a load
21597 * of an ancestor FormPanel. A Panel's toolbars are not part of the standard Container->Component hierarchy, and
21598 * so are not scanned to collect form items. However, the values <b>will</b> be submitted because form
21599 * submission parameters are collected from the DOM tree.</p>
21602 * @cfg {Boolean} header
21603 * <code>true</code> to create the Panel's header element explicitly, <code>false</code> to skip creating
21604 * it. If a <code>{@link #title}</code> is set the header will be created automatically, otherwise it will not.
21605 * If a <code>{@link #title}</code> is set but <code>header</code> is explicitly set to <code>false</code>, the header
21606 * will not be rendered.
21609 * @cfg {Boolean} footer
21610 * <code>true</code> to create the footer element explicitly, false to skip creating it. The footer
21611 * will be created automatically if <code>{@link #buttons}</code> or a <code>{@link #fbar}</code> have
21612 * been configured. See <code>{@link #bodyCfg}</code> for an example.
21615 * @cfg {String} title
21616 * The title text to be used as innerHTML (html tags are accepted) to display in the panel
21617 * <code>{@link #header}</code> (defaults to ''). When a <code>title</code> is specified the
21618 * <code>{@link #header}</code> element will automatically be created and displayed unless
21619 * {@link #header} is explicitly set to <code>false</code>. If you do not want to specify a
21620 * <code>title</code> at config time, but you may want one later, you must either specify a non-empty
21621 * <code>title</code> (a blank space ' ' will do) or <code>header:true</code> so that the container
21622 * element will get created.
21625 * @cfg {Array} buttons
21626 * <code>buttons</code> will be used as <code>{@link Ext.Container#items items}</code> for the toolbar in
21627 * the footer (<code>{@link #fbar}</code>). Typically the value of this configuration property will be
21628 * an array of {@link Ext.Button}s or {@link Ext.Button} configuration objects.
21629 * If an item is configured with <code>minWidth</code> or the Panel is configured with <code>minButtonWidth</code>,
21630 * that width will be applied to the item.
21633 * @cfg {Object/String/Function} autoLoad
21634 * A valid url spec according to the Updater {@link Ext.Updater#update} method.
21635 * If autoLoad is not null, the panel will attempt to load its contents
21636 * immediately upon render.<p>
21637 * The URL will become the default URL for this panel's {@link #body} element,
21638 * so it may be {@link Ext.Element#refresh refresh}ed at any time.</p>
21641 * @cfg {Boolean} frame
21642 * <code>false</code> by default to render with plain 1px square borders. <code>true</code> to render with
21643 * 9 elements, complete with custom rounded corners (also see {@link Ext.Element#boxWrap}).
21644 * <p>The template generated for each condition is depicted below:</p><pre><code>
21647 <div id="developer-specified-id-goes-here" class="x-panel">
21649 <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:false)</span></div>
21651 <div class="x-panel-bwrap">
21652 <div class="x-panel-body"><p>html value goes here</p></div>
21656 // frame = true (create 9 elements)
21657 <div id="developer-specified-id-goes-here" class="x-panel">
21658 <div class="x-panel-tl"><div class="x-panel-tr"><div class="x-panel-tc">
21659 <div class="x-panel-header"><span class="x-panel-header-text">Title: (frame:true)</span></div>
21660 </div></div></div>
21662 <div class="x-panel-bwrap">
21663 <div class="x-panel-ml"><div class="x-panel-mr"><div class="x-panel-mc">
21664 <div class="x-panel-body"><p>html value goes here</p></div>
21665 </div></div></div>
21667 <div class="x-panel-bl"><div class="x-panel-br"><div class="x-panel-bc"/>
21668 </div></div></div>
21673 * @cfg {Boolean} border
21674 * True to display the borders of the panel's body element, false to hide them (defaults to true). By default,
21675 * the border is a 2px wide inset border, but this can be further altered by setting {@link #bodyBorder} to false.
21678 * @cfg {Boolean} bodyBorder
21679 * True to display an interior border on the body element of the panel, false to hide it (defaults to true).
21680 * This only applies when {@link #border} == true. If border == true and bodyBorder == false, the border will display
21681 * as a 1px wide inset border, giving the entire body element an inset appearance.
21684 * @cfg {String/Object/Function} bodyCssClass
21685 * Additional css class selector to be applied to the {@link #body} element in the format expected by
21686 * {@link Ext.Element#addClass} (defaults to null). See {@link #bodyCfg}.
21689 * @cfg {String/Object/Function} bodyStyle
21690 * Custom CSS styles to be applied to the {@link #body} element in the format expected by
21691 * {@link Ext.Element#applyStyles} (defaults to null). See {@link #bodyCfg}.
21694 * @cfg {String} iconCls
21695 * The CSS class selector that specifies a background image to be used as the header icon (defaults to '').
21696 * <p>An example of specifying a custom icon class would be something like:
21698 // specify the property in the config for the class:
21702 // css class that specifies background image to be used as the icon image:
21703 .my-icon { background-image: url(../images/my-icon.gif) 0 6px no-repeat !important; }
21707 * @cfg {Boolean} collapsible
21708 * True to make the panel collapsible and have the expand/collapse toggle button automatically rendered into
21709 * the header tool button area, false to keep the panel statically sized with no button (defaults to false).
21712 * @cfg {Array} tools
21713 * An array of tool button configs to be added to the header tool area. When rendered, each tool is
21714 * stored as an {@link Ext.Element Element} referenced by a public property called <code><b></b>tools.<i><tool-type></i></code>
21715 * <p>Each tool config may contain the following properties:
21716 * <div class="mdetail-params"><ul>
21717 * <li><b>id</b> : String<div class="sub-desc"><b>Required.</b> The type
21718 * of tool to create. By default, this assigns a CSS class of the form <code>x-tool-<i><tool-type></i></code> to the
21719 * resulting tool Element. Ext provides CSS rules, and an icon sprite containing images for the tool types listed below.
21720 * The developer may implement custom tools by supplying alternate CSS rules and background images:
21722 * <div class="x-tool x-tool-toggle" style="float:left; margin-right:5;"> </div><div><code> toggle</code> (Created by default when {@link #collapsible} is <code>true</code>)</div>
21723 * <div class="x-tool x-tool-close" style="float:left; margin-right:5;"> </div><div><code> close</code></div>
21724 * <div class="x-tool x-tool-minimize" style="float:left; margin-right:5;"> </div><div><code> minimize</code></div>
21725 * <div class="x-tool x-tool-maximize" style="float:left; margin-right:5;"> </div><div><code> maximize</code></div>
21726 * <div class="x-tool x-tool-restore" style="float:left; margin-right:5;"> </div><div><code> restore</code></div>
21727 * <div class="x-tool x-tool-gear" style="float:left; margin-right:5;"> </div><div><code> gear</code></div>
21728 * <div class="x-tool x-tool-pin" style="float:left; margin-right:5;"> </div><div><code> pin</code></div>
21729 * <div class="x-tool x-tool-unpin" style="float:left; margin-right:5;"> </div><div><code> unpin</code></div>
21730 * <div class="x-tool x-tool-right" style="float:left; margin-right:5;"> </div><div><code> right</code></div>
21731 * <div class="x-tool x-tool-left" style="float:left; margin-right:5;"> </div><div><code> left</code></div>
21732 * <div class="x-tool x-tool-up" style="float:left; margin-right:5;"> </div><div><code> up</code></div>
21733 * <div class="x-tool x-tool-down" style="float:left; margin-right:5;"> </div><div><code> down</code></div>
21734 * <div class="x-tool x-tool-refresh" style="float:left; margin-right:5;"> </div><div><code> refresh</code></div>
21735 * <div class="x-tool x-tool-minus" style="float:left; margin-right:5;"> </div><div><code> minus</code></div>
21736 * <div class="x-tool x-tool-plus" style="float:left; margin-right:5;"> </div><div><code> plus</code></div>
21737 * <div class="x-tool x-tool-help" style="float:left; margin-right:5;"> </div><div><code> help</code></div>
21738 * <div class="x-tool x-tool-search" style="float:left; margin-right:5;"> </div><div><code> search</code></div>
21739 * <div class="x-tool x-tool-save" style="float:left; margin-right:5;"> </div><div><code> save</code></div>
21740 * <div class="x-tool x-tool-print" style="float:left; margin-right:5;"> </div><div><code> print</code></div>
21742 * <li><b>handler</b> : Function<div class="sub-desc"><b>Required.</b> The function to
21743 * call when clicked. Arguments passed are:<ul>
21744 * <li><b>event</b> : Ext.EventObject<div class="sub-desc">The click event.</div></li>
21745 * <li><b>toolEl</b> : Ext.Element<div class="sub-desc">The tool Element.</div></li>
21746 * <li><b>panel</b> : Ext.Panel<div class="sub-desc">The host Panel</div></li>
21747 * <li><b>tc</b> : Ext.Panel<div class="sub-desc">The tool configuration object</div></li>
21749 * <li><b>stopEvent</b> : Boolean<div class="sub-desc">Defaults to true. Specify as false to allow click event to propagate.</div></li>
21750 * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the handler.</div></li>
21751 * <li><b>qtip</b> : String/Object<div class="sub-desc">A tip string, or
21752 * a config argument to {@link Ext.QuickTip#register}</div></li>
21753 * <li><b>hidden</b> : Boolean<div class="sub-desc">True to initially render hidden.</div></li>
21754 * <li><b>on</b> : Object<div class="sub-desc">A listener config object specifiying
21755 * event listeners in the format of an argument to {@link #addListener}</div></li>
21757 * <p>Note that, apart from the toggle tool which is provided when a panel is collapsible, these
21758 * tools only provide the visual button. Any required functionality must be provided by adding
21759 * handlers that implement the necessary behavior.</p>
21760 * <p>Example usage:</p>
21764 qtip: 'Refresh form Data',
21766 handler: function(event, toolEl, panel){
21773 handler: function(event, toolEl, panel){
21778 * <p>For the custom id of <code>'help'</code> define two relevant css classes with a link to
21779 * a 15x15 image:</p>
21781 .x-tool-help {background-image: url(images/help.png);}
21782 .x-tool-help-over {background-image: url(images/help_over.png);}
21783 // if using an image sprite:
21784 .x-tool-help {background-image: url(images/help.png) no-repeat 0 0;}
21785 .x-tool-help-over {background-position:-15px 0;}
21789 * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
21790 * <p>A Template used to create {@link #tools} in the {@link #header} Element. Defaults to:</p><pre><code>
21791 new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>')</code></pre>
21792 * <p>This may may be overridden to provide a custom DOM structure for tools based upon a more
21793 * complex XTemplate. The template's data is a single tool configuration object (Not the entire Array)
21794 * as specified in {@link #tools}. In the following example an <a> tag is used to provide a
21795 * visual indication when hovering over the tool:</p><pre><code>
21796 var win = new Ext.Window({
21799 href: '/MyPdfDoc.pdf'
21801 toolTemplate: new Ext.XTemplate(
21802 '<tpl if="id==\'download\'">',
21803 '<a class="x-tool x-tool-pdf" href="{href}"></a>',
21805 '<tpl if="id!=\'download\'">',
21806 '<div class="x-tool x-tool-{id}">&#160;</div>',
21813 * <p>Note that the CSS class 'x-tool-pdf' should have an associated style rule which provides an
21814 * appropriate background image, something like:</p>
21816 a.x-tool-pdf {background-image: url(../shared/extjs/images/pdf.gif)!important;}
21820 * @cfg {Boolean} hideCollapseTool
21821 * <code>true</code> to hide the expand/collapse toggle button when <code>{@link #collapsible} == true</code>,
21822 * <code>false</code> to display it (defaults to <code>false</code>).
21825 * @cfg {Boolean} titleCollapse
21826 * <code>true</code> to allow expanding and collapsing the panel (when <code>{@link #collapsible} = true</code>)
21827 * by clicking anywhere in the header bar, <code>false</code>) to allow it only by clicking to tool button
21828 * (defaults to <code>false</code>)). If this panel is a child item of a border layout also see the
21829 * {@link Ext.layout.BorderLayout.Region BorderLayout.Region}
21830 * <code>{@link Ext.layout.BorderLayout.Region#floatable floatable}</code> config option.
21833 * @cfg {Boolean} autoScroll
21834 * <code>true</code> to use overflow:'auto' on the panel's body element and show scroll bars automatically when
21835 * necessary, <code>false</code> to clip any overflowing content (defaults to <code>false</code>).
21838 * @cfg {Mixed} floating
21839 * <p>This property is used to configure the underlying {@link Ext.Layer}. Acceptable values for this
21840 * configuration property are:</p><div class="mdetail-params"><ul>
21841 * <li><b><code>false</code></b> : <b>Default.</b><div class="sub-desc">Display the panel inline where it is
21842 * rendered.</div></li>
21843 * <li><b><code>true</code></b> : <div class="sub-desc">Float the panel (absolute position it with automatic
21844 * shimming and shadow).<ul>
21845 * <div class="sub-desc">Setting floating to true will create an Ext.Layer for this panel and display the
21846 * panel at negative offsets so that it is hidden.</div>
21847 * <div class="sub-desc">Since the panel will be absolute positioned, the position must be set explicitly
21848 * <i>after</i> render (e.g., <code>myPanel.setPosition(100,100);</code>).</div>
21849 * <div class="sub-desc"><b>Note</b>: when floating a panel you should always assign a fixed width,
21850 * otherwise it will be auto width and will expand to fill to the right edge of the viewport.</div>
21852 * <li><b><code>{@link Ext.Layer object}</code></b> : <div class="sub-desc">The specified object will be used
21853 * as the configuration object for the {@link Ext.Layer} that will be created.</div></li>
21857 * @cfg {Boolean/String} shadow
21858 * <code>true</code> (or a valid Ext.Shadow {@link Ext.Shadow#mode} value) to display a shadow behind the
21859 * panel, <code>false</code> to display no shadow (defaults to <code>'sides'</code>). Note that this option
21860 * only applies when <code>{@link #floating} = true</code>.
21863 * @cfg {Number} shadowOffset
21864 * The number of pixels to offset the shadow if displayed (defaults to <code>4</code>). Note that this
21865 * option only applies when <code>{@link #floating} = true</code>.
21868 * @cfg {Boolean} shim
21869 * <code>false</code> to disable the iframe shim in browsers which need one (defaults to <code>true</code>).
21870 * Note that this option only applies when <code>{@link #floating} = true</code>.
21873 * @cfg {String/Object} html
21874 * An HTML fragment, or a {@link Ext.DomHelper DomHelper} specification to use as the panel's body
21875 * content (defaults to ''). The HTML content is added by the Panel's {@link #afterRender} method,
21876 * and so the document will not contain this HTML at the time the {@link #render} event is fired.
21877 * This content is inserted into the body <i>before</i> any configured {@link #contentEl} is appended.
21880 * @cfg {String} contentEl
21881 * <p>Optional. Specify an existing HTML element, or the <code>id</code> of an existing HTML element to use as this Panel's
21882 * <code><b>{@link #body}</b></code> content.</p>
21884 * <li><b>Description</b> :
21885 * <div class="sub-desc">This config option is used to take an existing HTML element and place it in the body
21886 * of a new panel (it simply moves the specified DOM element into the body element of the Panel
21887 * <i>after the Panel is rendered</i> to use as the content (it is not going to be the actual panel itself).</div></li>
21888 * <li><b>Notes</b> :
21889 * <div class="sub-desc">The specified HTML element is appended to the Panel's {@link #body} Element by the
21890 * Panel's <code>afterRender</code> method <i>after any configured {@link #html HTML} has
21891 * been inserted</i>, and so the document will not contain this element at the time the
21892 * {@link #render} event is fired.</div>
21893 * <div class="sub-desc">The specified HTML element used will not participate in any <code><b>{@link Ext.Container#layout layout}</b></code>
21894 * scheme that the Panel may use. It is just HTML. Layouts operate on child <code><b>{@link Ext.Container#items items}</b></code>.</div>
21895 * <div class="sub-desc">Add either the <code>x-hidden</code> or the <code>x-hide-display</code> CSS class to
21896 * prevent a brief flicker of the content before it is rendered to the panel.</div></li>
21900 * @cfg {Object/Array} keys
21901 * A {@link Ext.KeyMap} config object (in the format expected by {@link Ext.KeyMap#addBinding}
21902 * used to assign custom key handling to this panel (defaults to <code>null</code>).
21905 * @cfg {Boolean/Object} draggable
21906 * <p><code>true</code> to enable dragging of this Panel (defaults to <code>false</code>).</p>
21907 * <p>For custom drag/drop implementations, an <b>Ext.Panel.DD</b> config could also be passed
21908 * in this config instead of <code>true</code>. Ext.Panel.DD is an internal, undocumented class which
21909 * moves a proxy Element around in place of the Panel's element, but provides no other behaviour
21910 * during dragging or on drop. It is a subclass of {@link Ext.dd.DragSource}, so behaviour may be
21911 * added by implementing the interface methods of {@link Ext.dd.DragDrop} e.g.:
21917 renderTo: Ext.getBody(),
21923 // Config option of Ext.Panel.DD class.
21924 // It's a floating Panel, so do not show a placeholder proxy in the original position.
21925 insertProxy: false,
21927 // Called for each mousemove event while dragging the DD object.
21928 onDrag : function(e){
21929 // Record the x,y position of the drag proxy so that we can
21930 // position the Panel at end of drag.
21931 var pel = this.proxy.getEl();
21932 this.x = pel.getLeft(true);
21933 this.y = pel.getTop(true);
21935 // Keep the Shadow aligned if there is one.
21936 var s = this.panel.getEl().shadow;
21938 s.realign(this.x, this.y, pel.getWidth(), pel.getHeight());
21942 // Called on the mouseup event.
21943 endDrag : function(e){
21944 this.panel.setPosition(this.x, this.y);
21951 * @cfg {Boolean} disabled
21952 * Render this panel disabled (default is <code>false</code>). An important note when using the disabled
21953 * config on panels is that IE will often fail to initialize the disabled mask element correectly if
21954 * the panel's layout has not yet completed by the time the Panel is disabled during the render process.
21955 * If you experience this issue, you may need to instead use the {@link #afterlayout} event to initialize
21956 * the disabled state:
21965 single: true // important, as many layouts can occur
21972 * @cfg {Boolean} autoHeight
21973 * <code>true</code> to use height:'auto', <code>false</code> to use fixed height (defaults to <code>false</code>).
21974 * <b>Note</b>: Setting <code>autoHeight: true</code> means that the browser will manage the panel's height
21975 * based on its contents, and that Ext will not manage it at all. If the panel is within a layout that
21976 * manages dimensions (<code>fit</code>, <code>border</code>, etc.) then setting <code>autoHeight: true</code>
21977 * can cause issues with scrolling and will not generally work as expected since the panel will take
21978 * on the height of its contents rather than the height required by the Ext layout.
21983 * @cfg {String} baseCls
21984 * The base CSS class to apply to this panel's element (defaults to <code>'x-panel'</code>).
21985 * <p>Another option available by default is to specify <code>'x-plain'</code> which strips all styling
21986 * except for required attributes for Ext layouts to function (e.g. overflow:hidden).
21987 * See <code>{@link #unstyled}</code> also.</p>
21989 baseCls : 'x-panel',
21991 * @cfg {String} collapsedCls
21992 * A CSS class to add to the panel's element after it has been collapsed (defaults to
21993 * <code>'x-panel-collapsed'</code>).
21995 collapsedCls : 'x-panel-collapsed',
21997 * @cfg {Boolean} maskDisabled
21998 * <code>true</code> to mask the panel when it is {@link #disabled}, <code>false</code> to not mask it (defaults
21999 * to <code>true</code>). Either way, the panel will always tell its contained elements to disable themselves
22000 * when it is disabled, but masking the panel can provide an additional visual cue that the panel is
22003 maskDisabled : true,
22005 * @cfg {Boolean} animCollapse
22006 * <code>true</code> to animate the transition when the panel is collapsed, <code>false</code> to skip the
22007 * animation (defaults to <code>true</code> if the {@link Ext.Fx} class is available, otherwise <code>false</code>).
22009 animCollapse : Ext.enableFx,
22011 * @cfg {Boolean} headerAsText
22012 * <code>true</code> to display the panel <code>{@link #title}</code> in the <code>{@link #header}</code>,
22013 * <code>false</code> to hide it (defaults to <code>true</code>).
22015 headerAsText : true,
22017 * @cfg {String} buttonAlign
22018 * The alignment of any {@link #buttons} added to this panel. Valid values are <code>'right'</code>,
22019 * <code>'left'</code> and <code>'center'</code> (defaults to <code>'right'</code>).
22021 buttonAlign : 'right',
22023 * @cfg {Boolean} collapsed
22024 * <code>true</code> to render the panel collapsed, <code>false</code> to render it expanded (defaults to
22025 * <code>false</code>).
22029 * @cfg {Boolean} collapseFirst
22030 * <code>true</code> to make sure the collapse/expand toggle button always renders first (to the left of)
22031 * any other tools in the panel's title bar, <code>false</code> to render it last (defaults to <code>true</code>).
22033 collapseFirst : true,
22035 * @cfg {Number} minButtonWidth
22036 * Minimum width in pixels of all {@link #buttons} in this panel (defaults to <code>75</code>)
22038 minButtonWidth : 75,
22040 * @cfg {Boolean} unstyled
22041 * Overrides the <code>{@link #baseCls}</code> setting to <code>{@link #baseCls} = 'x-plain'</code> which renders
22042 * the panel unstyled except for required attributes for Ext layouts to function (e.g. overflow:hidden).
22045 * @cfg {String} elements
22046 * A comma-delimited list of panel elements to initialize when the panel is rendered. Normally, this list will be
22047 * generated automatically based on the items added to the panel at config time, but sometimes it might be useful to
22048 * make sure a structural element is rendered even if not specified at config time (for example, you may want
22049 * to add a button or toolbar dynamically after the panel has been rendered). Adding those elements to this
22050 * list will allocate the required placeholders in the panel when it is rendered. Valid values are<div class="mdetail-params"><ul>
22051 * <li><code>header</code></li>
22052 * <li><code>tbar</code> (top bar)</li>
22053 * <li><code>body</code></li>
22054 * <li><code>bbar</code> (bottom bar)</li>
22055 * <li><code>footer</code></li>
22057 * Defaults to '<code>body</code>'.
22061 * @cfg {Boolean} preventBodyReset
22062 * Defaults to <code>false</code>. When set to <code>true</code>, an extra css class <code>'x-panel-normal'</code>
22063 * will be added to the panel's element, effectively applying css styles suggested by the W3C
22064 * (see http://www.w3.org/TR/CSS21/sample.html) to the Panel's <b>body</b> element (not the header,
22067 preventBodyReset : false,
22069 /** @cfg {String} resizeEvent
22070 * The event to listen to for resizing in layouts. Defaults to <tt>'bodyresize'</tt>.
22072 resizeEvent: 'bodyresize',
22074 // protected - these could be used to customize the behavior of the window,
22075 // but changing them would not be useful without further mofifications and
22076 // could lead to unexpected or undesirable results.
22077 toolTarget : 'header',
22078 collapseEl : 'bwrap',
22080 disabledClass : '',
22082 // private, notify box this class will handle heights
22083 deferHeight : true,
22089 collapseDefaults : {
22094 initComponent : function(){
22095 Ext.Panel.superclass.initComponent.call(this);
22099 * @event bodyresize
22100 * Fires after the Panel has been resized.
22101 * @param {Ext.Panel} p the Panel which has been resized.
22102 * @param {Number} width The Panel's new width.
22103 * @param {Number} height The Panel's new height.
22107 * @event titlechange
22108 * Fires after the Panel title has been {@link #title set} or {@link #setTitle changed}.
22109 * @param {Ext.Panel} p the Panel which has had its title changed.
22110 * @param {String} The new title.
22114 * @event iconchange
22115 * Fires after the Panel icon class has been {@link #iconCls set} or {@link #setIconClass changed}.
22116 * @param {Ext.Panel} p the Panel which has had its {@link #iconCls icon class} changed.
22117 * @param {String} The new icon class.
22118 * @param {String} The old icon class.
22123 * Fires after the Panel has been collapsed.
22124 * @param {Ext.Panel} p the Panel that has been collapsed.
22129 * Fires after the Panel has been expanded.
22130 * @param {Ext.Panel} p The Panel that has been expanded.
22134 * @event beforecollapse
22135 * Fires before the Panel is collapsed. A handler can return false to cancel the collapse.
22136 * @param {Ext.Panel} p the Panel being collapsed.
22137 * @param {Boolean} animate True if the collapse is animated, else false.
22141 * @event beforeexpand
22142 * Fires before the Panel is expanded. A handler can return false to cancel the expand.
22143 * @param {Ext.Panel} p The Panel being expanded.
22144 * @param {Boolean} animate True if the expand is animated, else false.
22148 * @event beforeclose
22149 * Fires before the Panel is closed. Note that Panels do not directly support being closed, but some
22150 * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel. This event only
22151 * applies to such subclasses.
22152 * A handler can return false to cancel the close.
22153 * @param {Ext.Panel} p The Panel being closed.
22158 * Fires after the Panel is closed. Note that Panels do not directly support being closed, but some
22159 * Panel subclasses do (like {@link Ext.Window}) or a Panel within a Ext.TabPanel.
22160 * @param {Ext.Panel} p The Panel that has been closed.
22165 * Fires after the Panel has been visually activated.
22166 * Note that Panels do not directly support being activated, but some Panel subclasses
22167 * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
22168 * activate and deactivate events under the control of the TabPanel.
22169 * @param {Ext.Panel} p The Panel that has been activated.
22173 * @event deactivate
22174 * Fires after the Panel has been visually deactivated.
22175 * Note that Panels do not directly support being deactivated, but some Panel subclasses
22176 * do (like {@link Ext.Window}). Panels which are child Components of a TabPanel fire the
22177 * activate and deactivate events under the control of the TabPanel.
22178 * @param {Ext.Panel} p The Panel that has been deactivated.
22184 this.baseCls = 'x-plain';
22189 this.elements += ',tbar';
22190 if(Ext.isObject(this.tbar)){
22191 this.topToolbar = this.tbar;
22196 this.elements += ',bbar';
22197 if(Ext.isObject(this.bbar)){
22198 this.bottomToolbar = this.bbar;
22203 if(this.header === true){
22204 this.elements += ',header';
22205 delete this.header;
22206 }else if(this.headerCfg || (this.title && this.header !== false)){
22207 this.elements += ',header';
22210 if(this.footerCfg || this.footer === true){
22211 this.elements += ',footer';
22212 delete this.footer;
22216 this.elements += ',footer';
22217 var btns = this.buttons;
22219 * This Panel's Array of buttons as created from the <code>{@link #buttons}</code>
22220 * config property. Read only.
22222 * @property buttons
22225 Ext.each(btns, function(btn){
22226 if(btn.render){ // button instance
22227 this.buttons.push(btn);
22228 }else if(btn.xtype){
22229 this.buttons.push(Ext.create(btn, 'button'));
22231 this.addButton(btn);
22236 this.elements += ',footer';
22239 this.on('render', this.doAutoLoad, this, {delay:10});
22244 createElement : function(name, pnode){
22246 pnode.appendChild(this[name].dom);
22250 if(name === 'bwrap' || this.elements.indexOf(name) != -1){
22251 if(this[name+'Cfg']){
22252 this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
22254 var el = document.createElement('div');
22255 el.className = this[name+'Cls'];
22256 this[name] = Ext.get(pnode.appendChild(el));
22258 if(this[name+'CssClass']){
22259 this[name].addClass(this[name+'CssClass']);
22261 if(this[name+'Style']){
22262 this[name].applyStyles(this[name+'Style']);
22268 onRender : function(ct, position){
22269 Ext.Panel.superclass.onRender.call(this, ct, position);
22270 this.createClasses();
22278 if(this.collapsible && !this.hideCollapseTool){
22279 this.tools = this.tools ? this.tools.slice(0) : [];
22280 this.tools[this.collapseFirst?'unshift':'push']({
22282 handler : this.toggleCollapse,
22289 this.elements += (this.header !== false) ? ',header' : '';
22293 el.addClass(this.baseCls);
22294 if(d.firstChild){ // existing markup
22295 this.header = el.down('.'+this.headerCls);
22296 this.bwrap = el.down('.'+this.bwrapCls);
22297 var cp = this.bwrap ? this.bwrap : el;
22298 this.tbar = cp.down('.'+this.tbarCls);
22299 this.body = cp.down('.'+this.bodyCls);
22300 this.bbar = cp.down('.'+this.bbarCls);
22301 this.footer = cp.down('.'+this.footerCls);
22302 this.fromMarkup = true;
22304 if (this.preventBodyReset === true) {
22305 el.addClass('x-panel-reset');
22308 el.addClass(this.cls);
22312 this.elements += ',footer';
22315 // This block allows for maximum flexibility and performance when using existing markup
22317 // framing requires special markup
22319 el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
22321 this.createElement('header', d.firstChild.firstChild.firstChild);
22322 this.createElement('bwrap', d);
22324 // append the mid and bottom frame to the bwrap
22325 bw = this.bwrap.dom;
22326 var ml = d.childNodes[1], bl = d.childNodes[2];
22327 bw.appendChild(ml);
22328 bw.appendChild(bl);
22330 var mc = bw.firstChild.firstChild.firstChild;
22331 this.createElement('tbar', mc);
22332 this.createElement('body', mc);
22333 this.createElement('bbar', mc);
22334 this.createElement('footer', bw.lastChild.firstChild.firstChild);
22337 this.bwrap.dom.lastChild.className += ' x-panel-nofooter';
22340 * Store a reference to this element so:
22341 * a) We aren't looking it up all the time
22342 * b) The last element is reported incorrectly when using a loadmask
22344 this.ft = Ext.get(this.bwrap.dom.lastChild);
22345 this.mc = Ext.get(this.bwrap.dom.firstChild.firstChild.firstChild);
22347 this.createElement('header', d);
22348 this.createElement('bwrap', d);
22350 // append the mid and bottom frame to the bwrap
22351 bw = this.bwrap.dom;
22352 this.createElement('tbar', bw);
22353 this.createElement('body', bw);
22354 this.createElement('bbar', bw);
22355 this.createElement('footer', bw);
22358 this.body.addClass(this.bodyCls + '-noheader');
22360 this.tbar.addClass(this.tbarCls + '-noheader');
22365 if(Ext.isDefined(this.padding)){
22366 this.body.setStyle('padding', this.body.addUnits(this.padding));
22369 if(this.border === false){
22370 this.el.addClass(this.baseCls + '-noborder');
22371 this.body.addClass(this.bodyCls + '-noborder');
22373 this.header.addClass(this.headerCls + '-noborder');
22376 this.footer.addClass(this.footerCls + '-noborder');
22379 this.tbar.addClass(this.tbarCls + '-noborder');
22382 this.bbar.addClass(this.bbarCls + '-noborder');
22386 if(this.bodyBorder === false){
22387 this.body.addClass(this.bodyCls + '-noborder');
22390 this.bwrap.enableDisplayMode('block');
22393 this.header.unselectable();
22395 // for tools, we need to wrap any existing header markup
22396 if(this.headerAsText){
22397 this.header.dom.innerHTML =
22398 '<span class="' + this.headerTextCls + '">'+this.header.dom.innerHTML+'</span>';
22401 this.setIconClass(this.iconCls);
22407 this.makeFloating(this.floating);
22410 if(this.collapsible && this.titleCollapse && this.header){
22411 this.mon(this.header, 'click', this.toggleCollapse, this);
22412 this.header.setStyle('cursor', 'pointer');
22415 this.addTool.apply(this, ts);
22418 if(this.buttons && this.buttons.length > 0){
22419 this.fbar = new Ext.Toolbar({
22420 items: this.buttons,
22421 toolbarCls: 'x-panel-fbar'
22424 this.toolbars = [];
22426 this.fbar = Ext.create(this.fbar, 'toolbar');
22427 this.fbar.enableOverflow = false;
22428 if(this.fbar.items){
22429 this.fbar.items.each(function(c){
22430 c.minWidth = c.minWidth || this.minButtonWidth;
22433 this.fbar.toolbarCls = 'x-panel-fbar';
22435 var bct = this.footer.createChild({cls: 'x-panel-btns x-panel-btns-'+this.buttonAlign});
22436 this.fbar.ownerCt = this;
22437 this.fbar.render(bct);
22438 bct.createChild({cls:'x-clear'});
22439 this.toolbars.push(this.fbar);
22442 if(this.tbar && this.topToolbar){
22443 if(Ext.isArray(this.topToolbar)){
22444 this.topToolbar = new Ext.Toolbar(this.topToolbar);
22445 }else if(!this.topToolbar.events){
22446 this.topToolbar = Ext.create(this.topToolbar, 'toolbar');
22448 this.topToolbar.ownerCt = this;
22449 this.topToolbar.render(this.tbar);
22450 this.toolbars.push(this.topToolbar);
22452 if(this.bbar && this.bottomToolbar){
22453 if(Ext.isArray(this.bottomToolbar)){
22454 this.bottomToolbar = new Ext.Toolbar(this.bottomToolbar);
22455 }else if(!this.bottomToolbar.events){
22456 this.bottomToolbar = Ext.create(this.bottomToolbar, 'toolbar');
22458 this.bottomToolbar.ownerCt = this;
22459 this.bottomToolbar.render(this.bbar);
22460 this.toolbars.push(this.bottomToolbar);
22465 * Sets the CSS class that provides the icon image for this panel. This method will replace any existing
22466 * icon class if one has already been set and fire the {@link #iconchange} event after completion.
22467 * @param {String} cls The new CSS class name
22469 setIconClass : function(cls){
22470 var old = this.iconCls;
22471 this.iconCls = cls;
22472 if(this.rendered && this.header){
22474 this.header.addClass('x-panel-icon');
22475 this.header.replaceClass(old, this.iconCls);
22477 var hd = this.header,
22478 img = hd.child('img.x-panel-inline-icon');
22480 Ext.fly(img).replaceClass(old, this.iconCls);
22482 Ext.DomHelper.insertBefore(hd.dom.firstChild, {
22483 tag:'img', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
22488 this.fireEvent('iconchange', this, cls, old);
22492 makeFloating : function(cfg){
22493 this.floating = true;
22494 this.el = new Ext.Layer(
22495 Ext.isObject(cfg) ? cfg : {
22496 shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
22497 shadowOffset: this.shadowOffset,
22499 shim: this.shim === false ? false : undefined
22505 * Returns the {@link Ext.Toolbar toolbar} from the top (<code>{@link #tbar}</code>) section of the panel.
22506 * @return {Ext.Toolbar} The toolbar
22508 getTopToolbar : function(){
22509 return this.topToolbar;
22513 * Returns the {@link Ext.Toolbar toolbar} from the bottom (<code>{@link #bbar}</code>) section of the panel.
22514 * @return {Ext.Toolbar} The toolbar
22516 getBottomToolbar : function(){
22517 return this.bottomToolbar;
22521 * Adds a button to this panel. Note that this method must be called prior to rendering. The preferred
22522 * approach is to add buttons via the {@link #buttons} config.
22523 * @param {String/Object} config A valid {@link Ext.Button} config. A string will become the text for a default
22524 * button config, an object will be treated as a button config object.
22525 * @param {Function} handler The function to be called on button {@link Ext.Button#click}
22526 * @param {Object} scope The scope to use for the button handler function
22527 * @return {Ext.Button} The button that was added
22529 addButton : function(config, handler, scope){
22533 minWidth: this.minButtonWidth,
22536 if(Ext.isString(config)){
22539 Ext.apply(bc, config);
22541 var btn = new Ext.Button(bc);
22545 this.buttons.push(btn);
22550 addTool : function(){
22551 if(!this.rendered){
22555 Ext.each(arguments, function(arg){
22556 this.tools.push(arg)
22560 // nowhere to render tools!
22561 if(!this[this.toolTarget]){
22564 if(!this.toolTemplate){
22565 // initialize the global tool template on first use
22566 var tt = new Ext.Template(
22567 '<div class="x-tool x-tool-{id}"> </div>'
22569 tt.disableFormats = true;
22571 Ext.Panel.prototype.toolTemplate = tt;
22573 for(var i = 0, a = arguments, len = a.length; i < len; i++) {
22575 if(!this.tools[tc.id]){
22576 var overCls = 'x-tool-'+tc.id+'-over';
22577 var t = this.toolTemplate.insertFirst((tc.align !== 'left') ? this[this.toolTarget] : this[this.toolTarget].child('span'), tc, true);
22578 this.tools[tc.id] = t;
22579 t.enableDisplayMode('block');
22580 this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this));
22582 this.mon(t, tc.on);
22588 if(Ext.isObject(tc.qtip)){
22589 Ext.QuickTips.register(Ext.apply({
22593 t.dom.qtip = tc.qtip;
22596 t.addClassOnOver(overCls);
22601 onLayout : function(shallow, force){
22602 if(this.hasLayout && this.toolbars.length > 0){
22603 Ext.each(this.toolbars, function(tb){
22604 tb.doLayout(undefined, force);
22610 syncHeight : function(){
22611 var h = this.toolbarHeight,
22613 lsh = this.lastSize.height;
22615 if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){
22620 if(h != this.getToolbarHeight()){
22621 h = Math.max(0, this.adjustBodyHeight(lsh - this.getFrameHeight()));
22624 this.toolbarHeight = this.getToolbarHeight();
22625 this.onBodyResize(sz.width, sz.height);
22630 onShow : function(){
22632 return this.el.show();
22634 Ext.Panel.superclass.onShow.call(this);
22638 onHide : function(){
22640 return this.el.hide();
22642 Ext.Panel.superclass.onHide.call(this);
22646 createToolHandler : function(t, tc, overCls, panel){
22647 return function(e){
22648 t.removeClass(overCls);
22649 if(tc.stopEvent !== false){
22653 tc.handler.call(tc.scope || t, e, t, panel, tc);
22659 afterRender : function(){
22660 if(this.floating && !this.hidden){
22664 this.setTitle(this.title);
22666 this.setAutoScroll();
22668 this.body.update(Ext.isObject(this.html) ?
22669 Ext.DomHelper.markup(this.html) :
22673 if(this.contentEl){
22674 var ce = Ext.getDom(this.contentEl);
22675 Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']);
22676 this.body.dom.appendChild(ce);
22678 if(this.collapsed){
22679 this.collapsed = false;
22680 this.collapse(false);
22682 Ext.Panel.superclass.afterRender.call(this); // do sizing calcs last
22687 setAutoScroll : function(){
22688 if(this.rendered && this.autoScroll){
22689 var el = this.body || this.el;
22691 el.setOverflow('auto');
22697 getKeyMap : function(){
22699 this.keyMap = new Ext.KeyMap(this.el, this.keys);
22701 return this.keyMap;
22705 initEvents : function(){
22709 if(this.draggable){
22710 this.initDraggable();
22712 if(this.toolbars.length > 0){
22713 Ext.each(this.toolbars, function(tb){
22717 afterlayout: this.syncHeight,
22718 remove: this.syncHeight
22728 initDraggable : function(){
22730 * <p>If this Panel is configured {@link #draggable}, this property will contain
22731 * an instance of {@link Ext.dd.DragSource} which handles dragging the Panel.</p>
22732 * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
22733 * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
22734 * @type Ext.dd.DragSource.
22737 this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable);
22741 beforeEffect : function(anim){
22743 this.el.beforeAction();
22745 if(anim !== false){
22746 this.el.addClass('x-panel-animated');
22751 afterEffect : function(anim){
22753 if(anim !== false){
22754 this.el.removeClass('x-panel-animated');
22758 // private - wraps up an animation param with internal callbacks
22759 createEffect : function(a, cb, scope){
22767 }else if(!a.callback){
22769 }else { // wrap it up
22770 o.callback = function(){
22772 Ext.callback(a.callback, a.scope);
22775 return Ext.applyIf(o, a);
22779 * Collapses the panel body so that it becomes hidden. Fires the {@link #beforecollapse} event which will
22780 * cancel the collapse action if it returns false.
22781 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22782 * {@link #animCollapse} panel config)
22783 * @return {Ext.Panel} this
22785 collapse : function(animate){
22786 if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
22789 var doAnim = animate === true || (animate !== false && this.animCollapse);
22790 this.beforeEffect(doAnim);
22791 this.onCollapse(doAnim, animate);
22796 onCollapse : function(doAnim, animArg){
22798 this[this.collapseEl].slideOut(this.slideAnchor,
22799 Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this),
22800 this.collapseDefaults));
22802 this[this.collapseEl].hide();
22803 this.afterCollapse(false);
22808 afterCollapse : function(anim){
22809 this.collapsed = true;
22810 this.el.addClass(this.collapsedCls);
22811 this.afterEffect(anim);
22812 this.fireEvent('collapse', this);
22816 * Expands the panel body so that it becomes visible. Fires the {@link #beforeexpand} event which will
22817 * cancel the expand action if it returns false.
22818 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22819 * {@link #animCollapse} panel config)
22820 * @return {Ext.Panel} this
22822 expand : function(animate){
22823 if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){
22826 var doAnim = animate === true || (animate !== false && this.animCollapse);
22827 this.el.removeClass(this.collapsedCls);
22828 this.beforeEffect(doAnim);
22829 this.onExpand(doAnim, animate);
22834 onExpand : function(doAnim, animArg){
22836 this[this.collapseEl].slideIn(this.slideAnchor,
22837 Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
22838 this.expandDefaults));
22840 this[this.collapseEl].show();
22841 this.afterExpand(false);
22846 afterExpand : function(anim){
22847 this.collapsed = false;
22848 this.afterEffect(anim);
22849 if(Ext.isDefined(this.deferLayout)){
22850 this.doLayout(true);
22852 this.fireEvent('expand', this);
22856 * Shortcut for performing an {@link #expand} or {@link #collapse} based on the current state of the panel.
22857 * @param {Boolean} animate True to animate the transition, else false (defaults to the value of the
22858 * {@link #animCollapse} panel config)
22859 * @return {Ext.Panel} this
22861 toggleCollapse : function(animate){
22862 this[this.collapsed ? 'expand' : 'collapse'](animate);
22867 onDisable : function(){
22868 if(this.rendered && this.maskDisabled){
22871 Ext.Panel.superclass.onDisable.call(this);
22875 onEnable : function(){
22876 if(this.rendered && this.maskDisabled){
22879 Ext.Panel.superclass.onEnable.call(this);
22883 onResize : function(w, h){
22884 if(Ext.isDefined(w) || Ext.isDefined(h)){
22885 if(!this.collapsed){
22886 if(Ext.isNumber(w)){
22887 w = this.adjustBodyWidth(w - this.getFrameWidth());
22889 this.tbar.setWidth(w);
22890 if(this.topToolbar){
22891 this.topToolbar.setSize(w);
22895 this.bbar.setWidth(w);
22896 if(this.bottomToolbar){
22897 this.bottomToolbar.setSize(w);
22903 strict = Ext.isStrict;
22904 if(this.buttonAlign == 'left'){
22905 fWidth = w - f.container.getFrameWidth('lr');
22907 //center/right alignment off in webkit
22908 if(Ext.isIE || Ext.isWebKit){
22909 //center alignment ok on webkit.
22910 //right broken in both, center on IE
22911 if(!(this.buttonAlign == 'center' && Ext.isWebKit) && (!strict || (!Ext.isIE8 && strict))){
22913 f.setWidth(f.getEl().child('.x-toolbar-ct').getWidth());
22922 f.setWidth(fWidth);
22924 this.body.setWidth(w);
22925 }else if(w == 'auto'){
22926 this.body.setWidth(w);
22929 if(Ext.isNumber(h)){
22930 h = Math.max(0, this.adjustBodyHeight(h - this.getFrameHeight()));
22931 this.body.setHeight(h);
22932 }else if(h == 'auto'){
22933 this.body.setHeight(h);
22936 if(this.disabled && this.el._mask){
22937 this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
22940 this.queuedBodySize = {width: w, height: h};
22941 if(!this.queuedExpand && this.allowQueuedExpand !== false){
22942 this.queuedExpand = true;
22943 this.on('expand', function(){
22944 delete this.queuedExpand;
22945 this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
22947 }, this, {single:true});
22950 this.onBodyResize(w, h);
22956 onBodyResize: function(w, h){
22957 this.fireEvent('bodyresize', this, w, h);
22961 getToolbarHeight: function(){
22964 Ext.each(this.toolbars, function(tb){
22965 h += tb.getHeight();
22972 adjustBodyHeight : function(h){
22977 adjustBodyWidth : function(w){
22982 onPosition : function(){
22987 * Returns the width in pixels of the framing elements of this panel (not including the body width). To
22988 * retrieve the body width see {@link #getInnerWidth}.
22989 * @return {Number} The frame width
22991 getFrameWidth : function(){
22992 var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr');
22995 var l = this.bwrap.dom.firstChild;
22996 w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
22997 w += this.mc.getFrameWidth('lr');
23003 * Returns the height in pixels of the framing elements of this panel (including any top and bottom bars and
23004 * header and footer elements, but not including the body height). To retrieve the body height see {@link #getInnerHeight}.
23005 * @return {Number} The frame height
23007 getFrameHeight : function(){
23008 var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
23009 h += (this.tbar ? this.tbar.getHeight() : 0) +
23010 (this.bbar ? this.bbar.getHeight() : 0);
23013 h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
23015 h += (this.header ? this.header.getHeight() : 0) +
23016 (this.footer ? this.footer.getHeight() : 0);
23022 * Returns the width in pixels of the body element (not including the width of any framing elements).
23023 * For the frame width see {@link #getFrameWidth}.
23024 * @return {Number} The body width
23026 getInnerWidth : function(){
23027 return this.getSize().width - this.getFrameWidth();
23031 * Returns the height in pixels of the body element (not including the height of any framing elements).
23032 * For the frame height see {@link #getFrameHeight}.
23033 * @return {Number} The body height
23035 getInnerHeight : function(){
23036 return this.getSize().height - this.getFrameHeight();
23040 syncShadow : function(){
23042 this.el.sync(true);
23047 getLayoutTarget : function(){
23052 * <p>Sets the title text for the panel and optionally the {@link #iconCls icon class}.</p>
23053 * <p>In order to be able to set the title, a header element must have been created
23054 * for the Panel. This is triggered either by configuring the Panel with a non-blank <code>{@link #title}</code>,
23055 * or configuring it with <code><b>{@link #header}: true</b></code>.</p>
23056 * @param {String} title The title text to set
23057 * @param {String} iconCls (optional) {@link #iconCls iconCls} A user-defined CSS class that provides the icon image for this panel
23059 setTitle : function(title, iconCls){
23060 this.title = title;
23061 if(this.header && this.headerAsText){
23062 this.header.child('span').update(title);
23065 this.setIconClass(iconCls);
23067 this.fireEvent('titlechange', this, title);
23072 * Get the {@link Ext.Updater} for this panel. Enables you to perform Ajax updates of this panel's body.
23073 * @return {Ext.Updater} The Updater
23075 getUpdater : function(){
23076 return this.body.getUpdater();
23080 * Loads this content panel immediately with content returned from an XHR call.
23081 * @param {Object/String/Function} config A config object containing any of the following options:
23084 url: 'your-url.php',
23085 params: {param1: 'foo', param2: 'bar'}, // or a URL encoded string
23086 callback: yourFunction,
23087 scope: yourObject, // optional scope for the callback
23090 text: 'Loading...',
23095 * The only required property is url. The optional properties nocache, text and scripts
23096 * are shorthand for disableCaching, indicatorText and loadScripts and are used to set their
23097 * associated property on this panel Updater instance.
23098 * @return {Ext.Panel} this
23101 var um = this.body.getUpdater();
23102 um.update.apply(um, arguments);
23107 beforeDestroy : function(){
23109 this.header.removeAllListeners();
23110 if(this.headerAsText){
23111 Ext.Element.uncache(this.header.child('span'));
23114 Ext.Element.uncache(
23125 for(var k in this.tools){
23126 Ext.destroy(this.tools[k]);
23130 for(var b in this.buttons){
23131 Ext.destroy(this.buttons[b]);
23135 Ext.destroy(this.toolbars);
23137 Ext.destroy(this.topToolbar, this.bottomToolbar);
23139 Ext.Panel.superclass.beforeDestroy.call(this);
23143 createClasses : function(){
23144 this.headerCls = this.baseCls + '-header';
23145 this.headerTextCls = this.baseCls + '-header-text';
23146 this.bwrapCls = this.baseCls + '-bwrap';
23147 this.tbarCls = this.baseCls + '-tbar';
23148 this.bodyCls = this.baseCls + '-body';
23149 this.bbarCls = this.baseCls + '-bbar';
23150 this.footerCls = this.baseCls + '-footer';
23154 createGhost : function(cls, useShim, appendTo){
23155 var el = document.createElement('div');
23156 el.className = 'x-panel-ghost ' + (cls ? cls : '');
23158 el.appendChild(this.el.dom.firstChild.cloneNode(true));
23160 Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
23161 el.style.width = this.el.dom.offsetWidth + 'px';;
23163 this.container.dom.appendChild(el);
23165 Ext.getDom(appendTo).appendChild(el);
23167 if(useShim !== false && this.el.useShim !== false){
23168 var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
23172 return new Ext.Element(el);
23177 doAutoLoad : function(){
23178 var u = this.body.getUpdater();
23180 u.setRenderer(this.renderer);
23182 u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad});
23186 * Retrieve a tool by id.
23187 * @param {String} id
23188 * @return {Object} tool
23190 getTool : function(id) {
23191 return this.tools[id];
23195 * @cfg {String} autoEl @hide
23198 Ext.reg('panel', Ext.Panel);
23200 * @class Ext.Editor
23201 * @extends Ext.Component
23202 * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.
23204 * Create a new Editor
23205 * @param {Object} config The config object
23208 Ext.Editor = function(field, config){
23210 this.field = Ext.create(field.field, 'textfield');
23211 config = Ext.apply({}, field); // copy so we don't disturb original config
23212 delete config.field;
23214 this.field = field;
23216 Ext.Editor.superclass.constructor.call(this, config);
23219 Ext.extend(Ext.Editor, Ext.Component, {
23221 * @cfg {Ext.form.Field} field
23222 * The Field object (or descendant) or config object for field
23225 * @cfg {Boolean} allowBlur
23226 * True to {@link #completeEdit complete the editing process} if in edit mode when the
23227 * field is blurred. Defaults to <tt>false</tt>.
23230 * @cfg {Boolean/String} autoSize
23231 * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only,
23232 * or "height" to adopt the height only, "none" to always use the field dimensions. (defaults to false)
23235 * @cfg {Boolean} revertInvalid
23236 * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
23237 * validation fails (defaults to true)
23240 * @cfg {Boolean} ignoreNoChange
23241 * True to skip the edit completion process (no save, no events fired) if the user completes an edit and
23242 * the value has not changed (defaults to false). Applies only to string values - edits for other data types
23243 * will never be ignored.
23246 * @cfg {Boolean} hideEl
23247 * False to keep the bound element visible while the editor is displayed (defaults to true)
23250 * @cfg {Mixed} value
23251 * The data value of the underlying field (defaults to "")
23255 * @cfg {String} alignment
23256 * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "c-c?").
23260 * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
23261 * for bottom-right shadow (defaults to "frame")
23265 * @cfg {Boolean} constrain True to constrain the editor to the viewport
23269 * @cfg {Boolean} swallowKeys Handle the keydown/keypress events so they don't propagate (defaults to true)
23271 swallowKeys : true,
23273 * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed. Defaults to <tt>true</tt>.
23275 completeOnEnter : true,
23277 * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed. Defaults to <tt>true</tt>.
23279 cancelOnEsc : true,
23281 * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
23285 initComponent : function(){
23286 Ext.Editor.superclass.initComponent.call(this);
23289 * @event beforestartedit
23290 * Fires when editing is initiated, but before the value changes. Editing can be canceled by returning
23291 * false from the handler of this event.
23292 * @param {Editor} this
23293 * @param {Ext.Element} boundEl The underlying element bound to this editor
23294 * @param {Mixed} value The field value being set
23299 * Fires when this editor is displayed
23300 * @param {Ext.Element} boundEl The underlying element bound to this editor
23301 * @param {Mixed} value The starting field value
23305 * @event beforecomplete
23306 * Fires after a change has been made to the field, but before the change is reflected in the underlying
23307 * field. Saving the change to the field can be canceled by returning false from the handler of this event.
23308 * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
23309 * event will not fire since no edit actually occurred.
23310 * @param {Editor} this
23311 * @param {Mixed} value The current field value
23312 * @param {Mixed} startValue The original field value
23317 * Fires after editing is complete and any changed value has been written to the underlying field.
23318 * @param {Editor} this
23319 * @param {Mixed} value The current field value
23320 * @param {Mixed} startValue The original field value
23324 * @event canceledit
23325 * Fires after editing has been canceled and the editor's value has been reset.
23326 * @param {Editor} this
23327 * @param {Mixed} value The user-entered field value that was discarded
23328 * @param {Mixed} startValue The original field value that was set back into the editor after cancel
23332 * @event specialkey
23333 * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed. You can check
23334 * {@link Ext.EventObject#getKey} to determine which key was pressed.
23335 * @param {Ext.form.Field} this
23336 * @param {Ext.EventObject} e The event object
23343 onRender : function(ct, position){
23344 this.el = new Ext.Layer({
23345 shadow: this.shadow,
23349 shadowOffset: this.shadowOffset || 4,
23351 constrain: this.constrain
23354 this.el.setZIndex(this.zIndex);
23356 this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");
23357 if(this.field.msgTarget != 'title'){
23358 this.field.msgTarget = 'qtip';
23360 this.field.inEditor = true;
23361 this.mon(this.field, {
23364 specialkey: this.onSpecialKey
23366 if(this.field.grow){
23367 this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1});
23369 this.field.render(this.el).show();
23370 this.field.getEl().dom.name = '';
23371 if(this.swallowKeys){
23372 this.field.el.swallowEvent([
23373 'keypress', // *** Opera
23374 'keydown' // *** all other browsers
23380 onSpecialKey : function(field, e){
23381 var key = e.getKey(),
23382 complete = this.completeOnEnter && key == e.ENTER,
23383 cancel = this.cancelOnEsc && key == e.ESC;
23384 if(complete || cancel){
23387 this.completeEdit();
23391 if(field.triggerBlur){
23392 field.triggerBlur();
23395 this.fireEvent('specialkey', field, e);
23399 * Starts the editing process and shows the editor.
23400 * @param {Mixed} el The element to edit
23401 * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
23402 * to the innerHTML of el.
23404 startEdit : function(el, value){
23406 this.completeEdit();
23408 this.boundEl = Ext.get(el);
23409 var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
23410 if(!this.rendered){
23411 this.render(this.parentEl || document.body);
23413 if(this.fireEvent("beforestartedit", this, this.boundEl, v) !== false){
23414 this.startValue = v;
23415 this.field.setValue(v);
23417 this.el.alignTo(this.boundEl, this.alignment);
23418 this.editing = true;
23424 doAutoSize : function(){
23426 var sz = this.boundEl.getSize(),
23427 fs = this.field.getSize();
23429 switch(this.autoSize){
23431 this.setSize(sz.width, fs.height);
23434 this.setSize(fs.width, sz.height);
23437 this.setSize(fs.width, fs.height);
23440 this.setSize(sz.width, sz.height);
23446 * Sets the height and width of this editor.
23447 * @param {Number} width The new width
23448 * @param {Number} height The new height
23450 setSize : function(w, h){
23451 delete this.field.lastSize;
23452 this.field.setSize(w, h);
23454 if(Ext.isGecko2 || Ext.isOpera){
23455 // prevent layer scrollbars
23456 this.el.setSize(w, h);
23463 * Realigns the editor to the bound field based on the current alignment config value.
23465 realign : function(){
23466 this.el.alignTo(this.boundEl, this.alignment);
23470 * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
23471 * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)
23473 completeEdit : function(remainVisible){
23477 var v = this.getValue();
23478 if(!this.field.isValid()){
23479 if(this.revertInvalid !== false){
23480 this.cancelEdit(remainVisible);
23484 if(String(v) === String(this.startValue) && this.ignoreNoChange){
23485 this.hideEdit(remainVisible);
23488 if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
23489 v = this.getValue();
23490 if(this.updateEl && this.boundEl){
23491 this.boundEl.update(v);
23493 this.hideEdit(remainVisible);
23494 this.fireEvent("complete", this, v, this.startValue);
23499 onShow : function(){
23501 if(this.hideEl !== false){
23502 this.boundEl.hide();
23504 this.field.show().focus(false, true);
23505 this.fireEvent("startedit", this.boundEl, this.startValue);
23509 * Cancels the editing process and hides the editor without persisting any changes. The field value will be
23510 * reverted to the original starting value.
23511 * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after
23512 * cancel (defaults to false)
23514 cancelEdit : function(remainVisible){
23516 var v = this.getValue();
23517 this.setValue(this.startValue);
23518 this.hideEdit(remainVisible);
23519 this.fireEvent("canceledit", this, v, this.startValue);
23524 hideEdit: function(remainVisible){
23525 if(remainVisible !== true){
23526 this.editing = false;
23532 onBlur : function(){
23533 if(this.allowBlur !== true && this.editing){
23534 this.completeEdit();
23539 onHide : function(){
23541 this.completeEdit();
23545 if(this.field.collapse){
23546 this.field.collapse();
23549 if(this.hideEl !== false){
23550 this.boundEl.show();
23555 * Sets the data value of the editor
23556 * @param {Mixed} value Any valid value supported by the underlying field
23558 setValue : function(v){
23559 this.field.setValue(v);
23563 * Gets the data value of the editor
23564 * @return {Mixed} The data value
23566 getValue : function(){
23567 return this.field.getValue();
23570 beforeDestroy : function(){
23571 Ext.destroy(this.field);
23575 Ext.reg('editor', Ext.Editor);/**
23576 * @class Ext.ColorPalette
23577 * @extends Ext.Component
23578 * Simple color palette class for choosing colors. The palette can be rendered to any container.<br />
23579 * Here's an example of typical usage:
23581 var cp = new Ext.ColorPalette({value:'993300'}); // initial selected color
23582 cp.render('my-div');
23584 cp.on('select', function(palette, selColor){
23585 // do something with selColor
23589 * Create a new ColorPalette
23590 * @param {Object} config The config object
23591 * @xtype colorpalette
23593 Ext.ColorPalette = function(config){
23594 Ext.ColorPalette.superclass.constructor.call(this, config);
23598 * Fires when a color is selected
23599 * @param {ColorPalette} this
23600 * @param {String} color The 6-digit color hex code (without the # symbol)
23606 this.on('select', this.handler, this.scope, true);
23609 Ext.extend(Ext.ColorPalette, Ext.Component, {
23611 * @cfg {String} tpl An existing XTemplate instance to be used in place of the default template for rendering the component.
23614 * @cfg {String} itemCls
23615 * The CSS class to apply to the containing element (defaults to 'x-color-palette')
23617 itemCls : 'x-color-palette',
23619 * @cfg {String} value
23620 * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol). Note that
23621 * the hex codes are case-sensitive.
23624 clickEvent :'click',
23626 ctype : 'Ext.ColorPalette',
23629 * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the {@link #select} event
23631 allowReselect : false,
23634 * <p>An array of 6-digit color hex code strings (without the # symbol). This array can contain any number
23635 * of colors, and each hex code should be unique. The width of the palette is controlled via CSS by adjusting
23636 * the width property of the 'x-color-palette' class (or assigning a custom class), so you can balance the number
23637 * of colors with the width setting until the box is symmetrical.</p>
23638 * <p>You can override individual colors if needed:</p>
23640 var cp = new Ext.ColorPalette();
23641 cp.colors[0] = 'FF0000'; // change the first box to red
23644 Or you can provide a custom array of your own for complete control:
23646 var cp = new Ext.ColorPalette();
23647 cp.colors = ['000000', '993300', '333300'];
23652 '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
23653 '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
23654 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
23655 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
23656 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
23660 * @cfg {Function} handler
23661 * Optional. A function that will handle the select event of this palette.
23662 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
23663 * <li><code>palette</code> : ColorPalette<div class="sub-desc">The {@link #palette Ext.ColorPalette}.</div></li>
23664 * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>
23668 * @cfg {Object} scope
23669 * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
23670 * function will be called. Defaults to this ColorPalette instance.
23674 onRender : function(container, position){
23675 var t = this.tpl || new Ext.XTemplate(
23676 '<tpl for="."><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" unselectable="on"> </span></em></a></tpl>'
23678 var el = document.createElement('div');
23679 el.id = this.getId();
23680 el.className = this.itemCls;
23681 t.overwrite(el, this.colors);
23682 container.dom.insertBefore(el, position);
23683 this.el = Ext.get(el);
23684 this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'});
23685 if(this.clickEvent != 'click'){
23686 this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true});
23691 afterRender : function(){
23692 Ext.ColorPalette.superclass.afterRender.call(this);
23694 var s = this.value;
23701 handleClick : function(e, t){
23702 e.preventDefault();
23703 if(!this.disabled){
23704 var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
23705 this.select(c.toUpperCase());
23710 * Selects the specified color in the palette (fires the {@link #select} event)
23711 * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
23713 select : function(color){
23714 color = color.replace('#', '');
23715 if(color != this.value || this.allowReselect){
23718 el.child('a.color-'+this.value).removeClass('x-color-palette-sel');
23720 el.child('a.color-'+color).addClass('x-color-palette-sel');
23721 this.value = color;
23722 this.fireEvent('select', this, color);
23727 * @cfg {String} autoEl @hide
23730 Ext.reg('colorpalette', Ext.ColorPalette);
23732 * @class Ext.DatePicker
\r
23733 * @extends Ext.Component
\r
23734 * Simple date picker class.
\r
23736 * Create a new DatePicker
\r
23737 * @param {Object} config The config object
\r
23738 * @xtype datepicker
\r
23740 Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
\r
23742 * @cfg {String} todayText
\r
23743 * The text to display on the button that selects the current date (defaults to <tt>'Today'</tt>)
\r
23745 todayText : 'Today',
\r
23747 * @cfg {String} okText
\r
23748 * The text to display on the ok button (defaults to <tt>' OK '</tt> to give the user extra clicking room)
\r
23750 okText : ' OK ',
\r
23752 * @cfg {String} cancelText
\r
23753 * The text to display on the cancel button (defaults to <tt>'Cancel'</tt>)
\r
23755 cancelText : 'Cancel',
\r
23757 * @cfg {Function} handler
\r
23758 * Optional. A function that will handle the select event of this picker.
\r
23759 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
\r
23760 * <li><code>picker</code> : DatePicker<div class="sub-desc">The Ext.DatePicker.</div></li>
\r
23761 * <li><code>date</code> : Date<div class="sub-desc">The selected date.</div></li>
\r
23765 * @cfg {Object} scope
\r
23766 * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
\r
23767 * function will be called. Defaults to this DatePicker instance.
\r
23770 * @cfg {String} todayTip
\r
23771 * The tooltip to display for the button that selects the current date (defaults to <tt>'{current date} (Spacebar)'</tt>)
\r
23773 todayTip : '{0} (Spacebar)',
\r
23775 * @cfg {String} minText
\r
23776 * The error text to display if the minDate validation fails (defaults to <tt>'This date is before the minimum date'</tt>)
\r
23778 minText : 'This date is before the minimum date',
\r
23780 * @cfg {String} maxText
\r
23781 * The error text to display if the maxDate validation fails (defaults to <tt>'This date is after the maximum date'</tt>)
\r
23783 maxText : 'This date is after the maximum date',
\r
23785 * @cfg {String} format
\r
23786 * The default date format string which can be overriden for localization support. The format must be
\r
23787 * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/y'</tt>).
\r
23789 format : 'm/d/y',
\r
23791 * @cfg {String} disabledDaysText
\r
23792 * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
\r
23794 disabledDaysText : 'Disabled',
\r
23796 * @cfg {String} disabledDatesText
\r
23797 * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
\r
23799 disabledDatesText : 'Disabled',
\r
23801 * @cfg {Array} monthNames
\r
23802 * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
\r
23804 monthNames : Date.monthNames,
\r
23806 * @cfg {Array} dayNames
\r
23807 * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
\r
23809 dayNames : Date.dayNames,
\r
23811 * @cfg {String} nextText
\r
23812 * The next month navigation button tooltip (defaults to <tt>'Next Month (Control+Right)'</tt>)
\r
23814 nextText : 'Next Month (Control+Right)',
\r
23816 * @cfg {String} prevText
\r
23817 * The previous month navigation button tooltip (defaults to <tt>'Previous Month (Control+Left)'</tt>)
\r
23819 prevText : 'Previous Month (Control+Left)',
\r
23821 * @cfg {String} monthYearText
\r
23822 * The header month selector tooltip (defaults to <tt>'Choose a month (Control+Up/Down to move years)'</tt>)
\r
23824 monthYearText : 'Choose a month (Control+Up/Down to move years)',
\r
23826 * @cfg {Number} startDay
\r
23827 * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
\r
23831 * @cfg {Boolean} showToday
\r
23832 * False to hide the footer area containing the Today button and disable the keyboard handler for spacebar
\r
23833 * that selects the current date (defaults to <tt>true</tt>).
\r
23835 showToday : true,
\r
23837 * @cfg {Date} minDate
\r
23838 * Minimum allowable date (JavaScript date object, defaults to null)
\r
23841 * @cfg {Date} maxDate
\r
23842 * Maximum allowable date (JavaScript date object, defaults to null)
\r
23845 * @cfg {Array} disabledDays
\r
23846 * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
\r
23849 * @cfg {RegExp} disabledDatesRE
\r
23850 * JavaScript regular expression used to disable a pattern of dates (defaults to null). The {@link #disabledDates}
\r
23851 * config will generate this regex internally, but if you specify disabledDatesRE it will take precedence over the
\r
23852 * disabledDates value.
\r
23855 * @cfg {Array} disabledDates
\r
23856 * An array of 'dates' to disable, as strings. These strings will be used to build a dynamic regular
\r
23857 * expression so they are very powerful. Some examples:
\r
23859 * <li>['03/08/2003', '09/16/2003'] would disable those exact dates</li>
\r
23860 * <li>['03/08', '09/16'] would disable those days for every year</li>
\r
23861 * <li>['^03/08'] would only match the beginning (useful if you are using short years)</li>
\r
23862 * <li>['03/../2006'] would disable every day in March 2006</li>
\r
23863 * <li>['^03'] would disable every day in every March</li>
\r
23865 * Note that the format of the dates included in the array should exactly match the {@link #format} config.
\r
23866 * In order to support regular expressions, if you are using a date format that has '.' in it, you will have to
\r
23867 * escape the dot when restricting dates. For example: ['03\\.08\\.03'].
\r
23871 initComponent : function(){
\r
23872 Ext.DatePicker.superclass.initComponent.call(this);
\r
23874 this.value = this.value ?
\r
23875 this.value.clearTime(true) : new Date().clearTime();
\r
23880 * Fires when a date is selected
\r
23881 * @param {DatePicker} this
\r
23882 * @param {Date} date The selected date
\r
23887 if(this.handler){
\r
23888 this.on('select', this.handler, this.scope || this);
\r
23891 this.initDisabledDays();
\r
23895 initDisabledDays : function(){
\r
23896 if(!this.disabledDatesRE && this.disabledDates){
\r
23897 var dd = this.disabledDates,
\r
23898 len = dd.length - 1,
\r
23901 Ext.each(dd, function(d, i){
\r
23902 re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
\r
23907 this.disabledDatesRE = new RegExp(re + ')');
\r
23912 * Replaces any existing disabled dates with new values and refreshes the DatePicker.
\r
23913 * @param {Array/RegExp} disabledDates An array of date strings (see the {@link #disabledDates} config
\r
23914 * for details on supported values), or a JavaScript regular expression used to disable a pattern of dates.
\r
23916 setDisabledDates : function(dd){
\r
23917 if(Ext.isArray(dd)){
\r
23918 this.disabledDates = dd;
\r
23919 this.disabledDatesRE = null;
\r
23921 this.disabledDatesRE = dd;
\r
23923 this.initDisabledDays();
\r
23924 this.update(this.value, true);
\r
23928 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
\r
23929 * @param {Array} disabledDays An array of disabled day indexes. See the {@link #disabledDays} config
\r
23930 * for details on supported values.
\r
23932 setDisabledDays : function(dd){
\r
23933 this.disabledDays = dd;
\r
23934 this.update(this.value, true);
\r
23938 * Replaces any existing {@link #minDate} with the new value and refreshes the DatePicker.
\r
23939 * @param {Date} value The minimum date that can be selected
\r
23941 setMinDate : function(dt){
\r
23942 this.minDate = dt;
\r
23943 this.update(this.value, true);
\r
23947 * Replaces any existing {@link #maxDate} with the new value and refreshes the DatePicker.
\r
23948 * @param {Date} value The maximum date that can be selected
\r
23950 setMaxDate : function(dt){
\r
23951 this.maxDate = dt;
\r
23952 this.update(this.value, true);
\r
23956 * Sets the value of the date field
\r
23957 * @param {Date} value The date to set
\r
23959 setValue : function(value){
\r
23960 this.value = value.clearTime(true);
\r
23961 this.update(this.value);
\r
23965 * Gets the current selected value of the date field
\r
23966 * @return {Date} The selected date
\r
23968 getValue : function(){
\r
23969 return this.value;
\r
23973 focus : function(){
\r
23974 this.update(this.activeDate);
\r
23978 onEnable: function(initial){
\r
23979 Ext.DatePicker.superclass.onEnable.call(this);
\r
23980 this.doDisabled(false);
\r
23981 this.update(initial ? this.value : this.activeDate);
\r
23983 this.el.repaint();
\r
23989 onDisable : function(){
\r
23990 Ext.DatePicker.superclass.onDisable.call(this);
\r
23991 this.doDisabled(true);
\r
23992 if(Ext.isIE && !Ext.isIE8){
\r
23993 /* Really strange problem in IE6/7, when disabled, have to explicitly
\r
23994 * repaint each of the nodes to get them to display correctly, simply
\r
23995 * calling repaint on the main element doesn't appear to be enough.
\r
23997 Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){
\r
23998 Ext.fly(el).repaint();
\r
24004 doDisabled : function(disabled){
\r
24005 this.keyNav.setDisabled(disabled);
\r
24006 this.prevRepeater.setDisabled(disabled);
\r
24007 this.nextRepeater.setDisabled(disabled);
\r
24008 if(this.showToday){
\r
24009 this.todayKeyListener.setDisabled(disabled);
\r
24010 this.todayBtn.setDisabled(disabled);
\r
24015 onRender : function(container, position){
\r
24017 '<table cellspacing="0">',
\r
24018 '<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
24019 '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],
\r
24020 dn = this.dayNames,
\r
24022 for(i = 0; i < 7; i++){
\r
24023 var d = this.startDay+i;
\r
24027 m.push('<th><span>', dn[d].substr(0,1), '</span></th>');
\r
24029 m[m.length] = '</tr></thead><tbody><tr>';
\r
24030 for(i = 0; i < 42; i++) {
\r
24031 if(i % 7 === 0 && i !== 0){
\r
24032 m[m.length] = '</tr><tr>';
\r
24034 m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';
\r
24036 m.push('</tr></tbody></table></td></tr>',
\r
24037 this.showToday ? '<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>' : '',
\r
24038 '</table><div class="x-date-mp"></div>');
\r
24040 var el = document.createElement('div');
\r
24041 el.className = 'x-date-picker';
\r
24042 el.innerHTML = m.join('');
\r
24044 container.dom.insertBefore(el, position);
\r
24046 this.el = Ext.get(el);
\r
24047 this.eventEl = Ext.get(el.firstChild);
\r
24049 this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), {
\r
24050 handler: this.showPrevMonth,
\r
24052 preventDefault:true,
\r
24056 this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), {
\r
24057 handler: this.showNextMonth,
\r
24059 preventDefault:true,
\r
24063 this.monthPicker = this.el.down('div.x-date-mp');
\r
24064 this.monthPicker.enableDisplayMode('block');
\r
24066 this.keyNav = new Ext.KeyNav(this.eventEl, {
\r
24067 'left' : function(e){
\r
24069 this.showPrevMonth();
\r
24071 this.update(this.activeDate.add('d', -1));
\r
24075 'right' : function(e){
\r
24077 this.showNextMonth();
\r
24079 this.update(this.activeDate.add('d', 1));
\r
24083 'up' : function(e){
\r
24085 this.showNextYear();
\r
24087 this.update(this.activeDate.add('d', -7));
\r
24091 'down' : function(e){
\r
24093 this.showPrevYear();
\r
24095 this.update(this.activeDate.add('d', 7));
\r
24099 'pageUp' : function(e){
\r
24100 this.showNextMonth();
\r
24103 'pageDown' : function(e){
\r
24104 this.showPrevMonth();
\r
24107 'enter' : function(e){
\r
24108 e.stopPropagation();
\r
24115 this.el.unselectable();
\r
24117 this.cells = this.el.select('table.x-date-inner tbody td');
\r
24118 this.textNodes = this.el.query('table.x-date-inner tbody span');
\r
24120 this.mbtn = new Ext.Button({
\r
24122 tooltip: this.monthYearText,
\r
24123 renderTo: this.el.child('td.x-date-middle', true)
\r
24125 this.mbtn.el.child('em').addClass('x-btn-arrow');
\r
24127 if(this.showToday){
\r
24128 this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this);
\r
24129 var today = (new Date()).dateFormat(this.format);
\r
24130 this.todayBtn = new Ext.Button({
\r
24131 renderTo: this.el.child('td.x-date-bottom', true),
\r
24132 text: String.format(this.todayText, today),
\r
24133 tooltip: String.format(this.todayTip, today),
\r
24134 handler: this.selectToday,
\r
24138 this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this);
\r
24139 this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'});
\r
24140 this.mon(this.mbtn, 'click', this.showMonthPicker, this);
\r
24141 this.onEnable(true);
\r
24145 createMonthPicker : function(){
\r
24146 if(!this.monthPicker.dom.firstChild){
\r
24147 var buf = ['<table border="0" cellspacing="0">'];
\r
24148 for(var i = 0; i < 6; i++){
\r
24150 '<tr><td class="x-date-mp-month"><a href="#">', Date.getShortMonthName(i), '</a></td>',
\r
24151 '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', Date.getShortMonthName(i + 6), '</a></td>',
\r
24153 '<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
24154 '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
\r
24158 '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
\r
24160 '</button><button type="button" class="x-date-mp-cancel">',
\r
24162 '</button></td></tr>',
\r
24165 this.monthPicker.update(buf.join(''));
\r
24167 this.mon(this.monthPicker, 'click', this.onMonthClick, this);
\r
24168 this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);
\r
24170 this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
\r
24171 this.mpYears = this.monthPicker.select('td.x-date-mp-year');
\r
24173 this.mpMonths.each(function(m, a, i){
\r
24176 m.dom.xmonth = 5 + Math.round(i * 0.5);
\r
24178 m.dom.xmonth = Math.round((i-1) * 0.5);
\r
24185 showMonthPicker : function(){
\r
24186 if(!this.disabled){
\r
24187 this.createMonthPicker();
\r
24188 var size = this.el.getSize();
\r
24189 this.monthPicker.setSize(size);
\r
24190 this.monthPicker.child('table').setSize(size);
\r
24192 this.mpSelMonth = (this.activeDate || this.value).getMonth();
\r
24193 this.updateMPMonth(this.mpSelMonth);
\r
24194 this.mpSelYear = (this.activeDate || this.value).getFullYear();
\r
24195 this.updateMPYear(this.mpSelYear);
\r
24197 this.monthPicker.slideIn('t', {duration:0.2});
\r
24202 updateMPYear : function(y){
\r
24204 var ys = this.mpYears.elements;
\r
24205 for(var i = 1; i <= 10; i++){
\r
24206 var td = ys[i-1], y2;
\r
24208 y2 = y + Math.round(i * 0.5);
\r
24209 td.firstChild.innerHTML = y2;
\r
24212 y2 = y - (5-Math.round(i * 0.5));
\r
24213 td.firstChild.innerHTML = y2;
\r
24216 this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
\r
24221 updateMPMonth : function(sm){
\r
24222 this.mpMonths.each(function(m, a, i){
\r
24223 m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
\r
24228 selectMPMonth : function(m){
\r
24233 onMonthClick : function(e, t){
\r
24235 var el = new Ext.Element(t), pn;
\r
24236 if(el.is('button.x-date-mp-cancel')){
\r
24237 this.hideMonthPicker();
\r
24239 else if(el.is('button.x-date-mp-ok')){
\r
24240 var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate());
\r
24241 if(d.getMonth() != this.mpSelMonth){
\r
24242 // 'fix' the JS rolling date conversion if needed
\r
24243 d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth();
\r
24246 this.hideMonthPicker();
\r
24248 else if((pn = el.up('td.x-date-mp-month', 2))){
\r
24249 this.mpMonths.removeClass('x-date-mp-sel');
\r
24250 pn.addClass('x-date-mp-sel');
\r
24251 this.mpSelMonth = pn.dom.xmonth;
\r
24253 else if((pn = el.up('td.x-date-mp-year', 2))){
\r
24254 this.mpYears.removeClass('x-date-mp-sel');
\r
24255 pn.addClass('x-date-mp-sel');
\r
24256 this.mpSelYear = pn.dom.xyear;
\r
24258 else if(el.is('a.x-date-mp-prev')){
\r
24259 this.updateMPYear(this.mpyear-10);
\r
24261 else if(el.is('a.x-date-mp-next')){
\r
24262 this.updateMPYear(this.mpyear+10);
\r
24267 onMonthDblClick : function(e, t){
\r
24269 var el = new Ext.Element(t), pn;
\r
24270 if((pn = el.up('td.x-date-mp-month', 2))){
\r
24271 this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
\r
24272 this.hideMonthPicker();
\r
24274 else if((pn = el.up('td.x-date-mp-year', 2))){
\r
24275 this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
\r
24276 this.hideMonthPicker();
\r
24281 hideMonthPicker : function(disableAnim){
\r
24282 if(this.monthPicker){
\r
24283 if(disableAnim === true){
\r
24284 this.monthPicker.hide();
\r
24286 this.monthPicker.slideOut('t', {duration:0.2});
\r
24292 showPrevMonth : function(e){
\r
24293 this.update(this.activeDate.add('mo', -1));
\r
24297 showNextMonth : function(e){
\r
24298 this.update(this.activeDate.add('mo', 1));
\r
24302 showPrevYear : function(){
\r
24303 this.update(this.activeDate.add('y', -1));
\r
24307 showNextYear : function(){
\r
24308 this.update(this.activeDate.add('y', 1));
\r
24312 handleMouseWheel : function(e){
\r
24314 if(!this.disabled){
\r
24315 var delta = e.getWheelDelta();
\r
24317 this.showPrevMonth();
\r
24318 } else if(delta < 0){
\r
24319 this.showNextMonth();
\r
24325 handleDateClick : function(e, t){
\r
24327 if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){
\r
24328 this.setValue(new Date(t.dateValue));
\r
24329 this.fireEvent('select', this, this.value);
\r
24334 selectToday : function(){
\r
24335 if(this.todayBtn && !this.todayBtn.disabled){
\r
24336 this.setValue(new Date().clearTime());
\r
24337 this.fireEvent('select', this, this.value);
\r
24342 update : function(date, forceRefresh){
\r
24343 if(this.rendered){
\r
24344 var vd = this.activeDate, vis = this.isVisible();
\r
24345 this.activeDate = date;
\r
24346 if(!forceRefresh && vd && this.el){
\r
24347 var t = date.getTime();
\r
24348 if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
\r
24349 this.cells.removeClass('x-date-selected');
\r
24350 this.cells.each(function(c){
\r
24351 if(c.dom.firstChild.dateValue == t){
\r
24352 c.addClass('x-date-selected');
\r
24354 Ext.fly(c.dom.firstChild).focus(50);
\r
24362 var days = date.getDaysInMonth(),
\r
24363 firstOfMonth = date.getFirstDateOfMonth(),
\r
24364 startingPos = firstOfMonth.getDay()-this.startDay;
\r
24366 if(startingPos < 0){
\r
24367 startingPos += 7;
\r
24369 days += startingPos;
\r
24371 var pm = date.add('mo', -1),
\r
24372 prevStart = pm.getDaysInMonth()-startingPos,
\r
24373 cells = this.cells.elements,
\r
24374 textEls = this.textNodes,
\r
24375 // convert everything to numbers so it's fast
\r
24377 d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime(),
\r
24378 today = new Date().clearTime().getTime(),
\r
24379 sel = date.clearTime(true).getTime(),
\r
24380 min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY,
\r
24381 max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY,
\r
24382 ddMatch = this.disabledDatesRE,
\r
24383 ddText = this.disabledDatesText,
\r
24384 ddays = this.disabledDays ? this.disabledDays.join('') : false,
\r
24385 ddaysText = this.disabledDaysText,
\r
24386 format = this.format;
\r
24388 if(this.showToday){
\r
24389 var td = new Date().clearTime(),
\r
24390 disable = (td < min || td > max ||
\r
24391 (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||
\r
24392 (ddays && ddays.indexOf(td.getDay()) != -1));
\r
24394 if(!this.disabled){
\r
24395 this.todayBtn.setDisabled(disable);
\r
24396 this.todayKeyListener[disable ? 'disable' : 'enable']();
\r
24400 var setCellClass = function(cal, cell){
\r
24402 var t = d.getTime();
\r
24403 cell.firstChild.dateValue = t;
\r
24405 cell.className += ' x-date-today';
\r
24406 cell.title = cal.todayText;
\r
24409 cell.className += ' x-date-selected';
\r
24411 Ext.fly(cell.firstChild).focus(50);
\r
24416 cell.className = ' x-date-disabled';
\r
24417 cell.title = cal.minText;
\r
24421 cell.className = ' x-date-disabled';
\r
24422 cell.title = cal.maxText;
\r
24426 if(ddays.indexOf(d.getDay()) != -1){
\r
24427 cell.title = ddaysText;
\r
24428 cell.className = ' x-date-disabled';
\r
24431 if(ddMatch && format){
\r
24432 var fvalue = d.dateFormat(format);
\r
24433 if(ddMatch.test(fvalue)){
\r
24434 cell.title = ddText.replace('%0', fvalue);
\r
24435 cell.className = ' x-date-disabled';
\r
24441 for(; i < startingPos; i++) {
\r
24442 textEls[i].innerHTML = (++prevStart);
\r
24443 d.setDate(d.getDate()+1);
\r
24444 cells[i].className = 'x-date-prevday';
\r
24445 setCellClass(this, cells[i]);
\r
24447 for(; i < days; i++){
\r
24448 var intDay = i - startingPos + 1;
\r
24449 textEls[i].innerHTML = (intDay);
\r
24450 d.setDate(d.getDate()+1);
\r
24451 cells[i].className = 'x-date-active';
\r
24452 setCellClass(this, cells[i]);
\r
24454 var extraDays = 0;
\r
24455 for(; i < 42; i++) {
\r
24456 textEls[i].innerHTML = (++extraDays);
\r
24457 d.setDate(d.getDate()+1);
\r
24458 cells[i].className = 'x-date-nextday';
\r
24459 setCellClass(this, cells[i]);
\r
24462 this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear());
\r
24464 if(!this.internalRender){
\r
24465 var main = this.el.dom.firstChild,
\r
24466 w = main.offsetWidth;
\r
24467 this.el.setWidth(w + this.el.getBorderWidth('lr'));
\r
24468 Ext.fly(main).setWidth(w);
\r
24469 this.internalRender = true;
\r
24470 // opera does not respect the auto grow header center column
\r
24471 // then, after it gets a width opera refuses to recalculate
\r
24472 // without a second pass
\r
24473 if(Ext.isOpera && !this.secondPass){
\r
24474 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px';
\r
24475 this.secondPass = true;
\r
24476 this.update.defer(10, this, [date]);
\r
24483 beforeDestroy : function() {
\r
24484 if(this.rendered){
\r
24485 this.keyNav.disable();
\r
24486 this.keyNav = null;
\r
24488 this.leftClickRpt,
\r
24489 this.rightClickRpt,
\r
24490 this.monthPicker,
\r
24499 * @cfg {String} autoEl @hide
\r
24503 Ext.reg('datepicker', Ext.DatePicker);
\r
24505 * @class Ext.LoadMask
24506 * A simple utility class for generically masking elements while loading data. If the {@link #store}
24507 * config option is specified, the masking will be automatically synchronized with the store's loading
24508 * process and the mask element will be cached for reuse. For all other elements, this mask will replace the
24509 * element's Updater load indicator and will be destroyed after the initial load.
24510 * <p>Example usage:</p>
24513 var myMask = new Ext.LoadMask(Ext.getBody(), {msg:"Please wait..."});
24517 * Create a new LoadMask
24518 * @param {Mixed} el The element or DOM node, or its id
24519 * @param {Object} config The config object
24521 Ext.LoadMask = function(el, config){
24522 this.el = Ext.get(el);
24523 Ext.apply(this, config);
24525 this.store.on('beforeload', this.onBeforeLoad, this);
24526 this.store.on('load', this.onLoad, this);
24527 this.store.on('exception', this.onLoad, this);
24528 this.removeMask = Ext.value(this.removeMask, false);
24530 var um = this.el.getUpdater();
24531 um.showLoadIndicator = false; // disable the default indicator
24532 um.on('beforeupdate', this.onBeforeLoad, this);
24533 um.on('update', this.onLoad, this);
24534 um.on('failure', this.onLoad, this);
24535 this.removeMask = Ext.value(this.removeMask, true);
24539 Ext.LoadMask.prototype = {
24541 * @cfg {Ext.data.Store} store
24542 * Optional Store to which the mask is bound. The mask is displayed when a load request is issued, and
24543 * hidden on either load sucess, or load fail.
24546 * @cfg {Boolean} removeMask
24547 * True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
24548 * False to persist the mask element reference for multiple uses (e.g., for paged data widgets). Defaults to false.
24551 * @cfg {String} msg
24552 * The text to display in a centered loading message box (defaults to 'Loading...')
24554 msg : 'Loading...',
24556 * @cfg {String} msgCls
24557 * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
24559 msgCls : 'x-mask-loading',
24562 * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
24568 * Disables the mask to prevent it from being displayed
24570 disable : function(){
24571 this.disabled = true;
24575 * Enables the mask so that it can be displayed
24577 enable : function(){
24578 this.disabled = false;
24582 onLoad : function(){
24583 this.el.unmask(this.removeMask);
24587 onBeforeLoad : function(){
24588 if(!this.disabled){
24589 this.el.mask(this.msg, this.msgCls);
24594 * Show this LoadMask over the configured Element.
24597 this.onBeforeLoad();
24601 * Hide this LoadMask.
24608 destroy : function(){
24610 this.store.un('beforeload', this.onBeforeLoad, this);
24611 this.store.un('load', this.onLoad, this);
24612 this.store.un('exception', this.onLoad, this);
24614 var um = this.el.getUpdater();
24615 um.un('beforeupdate', this.onBeforeLoad, this);
24616 um.un('update', this.onLoad, this);
24617 um.un('failure', this.onLoad, this);
24621 * @class Ext.Slider
\r
24622 * @extends Ext.BoxComponent
\r
24623 * Slider which supports vertical or horizontal orientation, keyboard adjustments,
\r
24624 * configurable snapping, axis clicking and animation. Can be added as an item to
\r
24625 * any container. Example usage:
\r
24628 renderTo: Ext.getBody(),
\r
24637 Ext.Slider = Ext.extend(Ext.BoxComponent, {
\r
24639 * @cfg {Number} value The value to initialize the slider with. Defaults to minValue.
\r
24642 * @cfg {Boolean} vertical Orient the Slider vertically rather than horizontally, defaults to false.
\r
24646 * @cfg {Number} minValue The minimum value for the Slider. Defaults to 0.
\r
24650 * @cfg {Number} maxValue The maximum value for the Slider. Defaults to 100.
\r
24654 * @cfg {Number/Boolean} decimalPrecision.
\r
24655 * <p>The number of decimal places to which to round the Slider's value. Defaults to 0.</p>
\r
24656 * <p>To disable rounding, configure as <tt><b>false</b></tt>.</p>
\r
24658 decimalPrecision: 0,
\r
24660 * @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
24664 * @cfg {Number} increment How many units to change the slider when adjusting by drag and drop. Use this option to enable 'snapping'.
\r
24668 clickRange: [5,15],
\r
24670 * @cfg {Boolean} clickToChange Determines whether or not clicking on the Slider axis will change the slider. Defaults to true
\r
24672 clickToChange : true,
\r
24674 * @cfg {Boolean} animate Turn on or off animation. Defaults to true
\r
24679 * True while the thumb is in a drag operation
\r
24684 // private override
\r
24685 initComponent : function(){
\r
24686 if(!Ext.isDefined(this.value)){
\r
24687 this.value = this.minValue;
\r
24689 Ext.Slider.superclass.initComponent.call(this);
\r
24690 this.keyIncrement = Math.max(this.increment, this.keyIncrement);
\r
24693 * @event beforechange
\r
24694 * Fires before the slider value is changed. By returning false from an event handler,
\r
24695 * you can cancel the event and prevent the slider from changing.
\r
24696 * @param {Ext.Slider} slider The slider
\r
24697 * @param {Number} newValue The new value which the slider is being changed to.
\r
24698 * @param {Number} oldValue The old value which the slider was previously.
\r
24703 * Fires when the slider value is changed.
\r
24704 * @param {Ext.Slider} slider The slider
\r
24705 * @param {Number} newValue The new value which the slider has been changed to.
\r
24709 * @event changecomplete
\r
24710 * Fires when the slider value is changed by the user and any drag operations have completed.
\r
24711 * @param {Ext.Slider} slider The slider
\r
24712 * @param {Number} newValue The new value which the slider has been changed to.
\r
24714 'changecomplete',
\r
24716 * @event dragstart
\r
24717 * Fires after a drag operation has started.
\r
24718 * @param {Ext.Slider} slider The slider
\r
24719 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24724 * Fires continuously during the drag operation while the mouse is moving.
\r
24725 * @param {Ext.Slider} slider The slider
\r
24726 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24731 * Fires after the drag operation has completed.
\r
24732 * @param {Ext.Slider} slider The slider
\r
24733 * @param {Ext.EventObject} e The event fired from Ext.dd.DragTracker
\r
24738 if(this.vertical){
\r
24739 Ext.apply(this, Ext.Slider.Vertical);
\r
24743 // private override
\r
24744 onRender : function(){
\r
24746 cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
\r
24747 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
24749 Ext.Slider.superclass.onRender.apply(this, arguments);
\r
24750 this.endEl = this.el.first();
\r
24751 this.innerEl = this.endEl.first();
\r
24752 this.thumb = this.innerEl.first();
\r
24753 this.halfThumb = (this.vertical ? this.thumb.getHeight() : this.thumb.getWidth())/2;
\r
24754 this.focusEl = this.thumb.next();
\r
24755 this.initEvents();
\r
24758 // private override
\r
24759 initEvents : function(){
\r
24760 this.thumb.addClassOnOver('x-slider-thumb-over');
\r
24761 this.mon(this.el, {
\r
24763 mousedown: this.onMouseDown,
\r
24764 keydown: this.onKeyDown
\r
24767 this.focusEl.swallowEvent("click", true);
\r
24769 this.tracker = new Ext.dd.DragTracker({
\r
24770 onBeforeStart: this.onBeforeDragStart.createDelegate(this),
\r
24771 onStart: this.onDragStart.createDelegate(this),
\r
24772 onDrag: this.onDrag.createDelegate(this),
\r
24773 onEnd: this.onDragEnd.createDelegate(this),
\r
24777 this.tracker.initEl(this.thumb);
\r
24778 this.on('beforedestroy', this.tracker.destroy, this.tracker);
\r
24781 // private override
\r
24782 onMouseDown : function(e){
\r
24783 if(this.disabled) {return;}
\r
24784 if(this.clickToChange && e.target != this.thumb.dom){
\r
24785 var local = this.innerEl.translatePoints(e.getXY());
\r
24786 this.onClickChange(local);
\r
24792 onClickChange : function(local){
\r
24793 if(local.top > this.clickRange[0] && local.top < this.clickRange[1]){
\r
24794 this.setValue(Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
\r
24799 onKeyDown : function(e){
\r
24800 if(this.disabled){e.preventDefault();return;}
\r
24801 var k = e.getKey();
\r
24807 this.setValue(this.maxValue, undefined, true);
\r
24809 this.setValue(this.value+this.keyIncrement, undefined, true);
\r
24816 this.setValue(this.minValue, undefined, true);
\r
24818 this.setValue(this.value-this.keyIncrement, undefined, true);
\r
24822 e.preventDefault();
\r
24827 doSnap : function(value){
\r
24828 if(!this.increment || this.increment == 1 || !value) {
\r
24831 var newValue = value, inc = this.increment;
\r
24832 var m = value % inc;
\r
24837 }else if(m * 2 < -inc){
\r
24841 return newValue.constrain(this.minValue, this.maxValue);
\r
24845 afterRender : function(){
\r
24846 Ext.Slider.superclass.afterRender.apply(this, arguments);
\r
24847 if(this.value !== undefined){
\r
24848 var v = this.normalizeValue(this.value);
\r
24849 if(v !== this.value){
\r
24850 delete this.value;
\r
24851 this.setValue(v, false);
\r
24853 this.moveThumb(this.translateValue(v), false);
\r
24859 getRatio : function(){
\r
24860 var w = this.innerEl.getWidth();
\r
24861 var v = this.maxValue - this.minValue;
\r
24862 return v == 0 ? w : (w/v);
\r
24866 normalizeValue : function(v){
\r
24867 v = this.doSnap(v);
\r
24868 v = Ext.util.Format.round(v, this.decimalPrecision);
\r
24869 v = v.constrain(this.minValue, this.maxValue);
\r
24874 * Programmatically sets the value of the Slider. Ensures that the value is constrained within
\r
24875 * the minValue and maxValue.
\r
24876 * @param {Number} value The value to set the slider to. (This will be constrained within minValue and maxValue)
\r
24877 * @param {Boolean} animate Turn on or off animation, defaults to true
\r
24879 setValue : function(v, animate, changeComplete){
\r
24880 v = this.normalizeValue(v);
\r
24881 if(v !== this.value && this.fireEvent('beforechange', this, v, this.value) !== false){
\r
24883 this.moveThumb(this.translateValue(v), animate !== false);
\r
24884 this.fireEvent('change', this, v);
\r
24885 if(changeComplete){
\r
24886 this.fireEvent('changecomplete', this, v);
\r
24892 translateValue : function(v){
\r
24893 var ratio = this.getRatio();
\r
24894 return (v * ratio)-(this.minValue * ratio)-this.halfThumb;
\r
24897 reverseValue : function(pos){
\r
24898 var ratio = this.getRatio();
\r
24899 return (pos+this.halfThumb+(this.minValue * ratio))/ratio;
\r
24903 moveThumb: function(v, animate){
\r
24904 if(!animate || this.animate === false){
\r
24905 this.thumb.setLeft(v);
\r
24907 this.thumb.shift({left: v, stopFx: true, duration:.35});
\r
24912 focus : function(){
\r
24913 this.focusEl.focus(10);
\r
24917 onBeforeDragStart : function(e){
\r
24918 return !this.disabled;
\r
24922 onDragStart: function(e){
\r
24923 this.thumb.addClass('x-slider-thumb-drag');
\r
24924 this.dragging = true;
\r
24925 this.dragStartValue = this.value;
\r
24926 this.fireEvent('dragstart', this, e);
\r
24930 onDrag: function(e){
\r
24931 var pos = this.innerEl.translatePoints(this.tracker.getXY());
\r
24932 this.setValue(Ext.util.Format.round(this.reverseValue(pos.left), this.decimalPrecision), false);
\r
24933 this.fireEvent('drag', this, e);
\r
24937 onDragEnd: function(e){
\r
24938 this.thumb.removeClass('x-slider-thumb-drag');
\r
24939 this.dragging = false;
\r
24940 this.fireEvent('dragend', this, e);
\r
24941 if(this.dragStartValue != this.value){
\r
24942 this.fireEvent('changecomplete', this, this.value);
\r
24947 onResize : function(w, h){
\r
24948 this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
\r
24949 this.syncThumb();
\r
24953 onDisable: function(){
\r
24954 Ext.Slider.superclass.onDisable.call(this);
\r
24955 this.thumb.addClass(this.disabledClass);
\r
24957 //IE breaks when using overflow visible and opacity other than 1.
\r
24958 //Create a place holder for the thumb and display it.
\r
24959 var xy = this.thumb.getXY();
\r
24960 this.thumb.hide();
\r
24961 this.innerEl.addClass(this.disabledClass).dom.disabled = true;
\r
24962 if (!this.thumbHolder){
\r
24963 this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
\r
24965 this.thumbHolder.show().setXY(xy);
\r
24970 onEnable: function(){
\r
24971 Ext.Slider.superclass.onEnable.call(this);
\r
24972 this.thumb.removeClass(this.disabledClass);
\r
24974 this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
\r
24975 if (this.thumbHolder){
\r
24976 this.thumbHolder.hide();
\r
24978 this.thumb.show();
\r
24979 this.syncThumb();
\r
24984 * Synchronizes the thumb position to the proper proportion of the total component width based
\r
24985 * on the current slider {@link #value}. This will be called automatically when the Slider
\r
24986 * is resized by a layout, but if it is rendered auto width, this method can be called from
\r
24987 * another resize handler to sync the Slider if necessary.
\r
24989 syncThumb : function(){
\r
24990 if(this.rendered){
\r
24991 this.moveThumb(this.translateValue(this.value));
\r
24996 * Returns the current value of the slider
\r
24997 * @return {Number} The current value of the slider
\r
24999 getValue : function(){
\r
25000 return this.value;
\r
25003 Ext.reg('slider', Ext.Slider);
\r
25005 // private class to support vertical sliders
\r
25006 Ext.Slider.Vertical = {
\r
25007 onResize : function(w, h){
\r
25008 this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
\r
25009 this.syncThumb();
\r
25012 getRatio : function(){
\r
25013 var h = this.innerEl.getHeight();
\r
25014 var v = this.maxValue - this.minValue;
\r
25018 moveThumb: function(v, animate){
\r
25019 if(!animate || this.animate === false){
\r
25020 this.thumb.setBottom(v);
\r
25022 this.thumb.shift({bottom: v, stopFx: true, duration:.35});
\r
25026 onDrag: function(e){
\r
25027 var pos = this.innerEl.translatePoints(this.tracker.getXY());
\r
25028 var bottom = this.innerEl.getHeight()-pos.top;
\r
25029 this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), false);
\r
25030 this.fireEvent('drag', this, e);
\r
25033 onClickChange : function(local){
\r
25034 if(local.left > this.clickRange[0] && local.left < this.clickRange[1]){
\r
25035 var bottom = this.innerEl.getHeight()-local.top;
\r
25036 this.setValue(this.minValue + Ext.util.Format.round(bottom/this.getRatio(), this.decimalPrecision), undefined, true);
\r
25040 * @class Ext.ProgressBar
\r
25041 * @extends Ext.BoxComponent
\r
25042 * <p>An updateable progress bar component. The progress bar supports two different modes: manual and automatic.</p>
\r
25043 * <p>In manual mode, you are responsible for showing, updating (via {@link #updateProgress}) and clearing the
\r
25044 * progress bar as needed from your own code. This method is most appropriate when you want to show progress
\r
25045 * throughout an operation that has predictable points of interest at which you can update the control.</p>
\r
25046 * <p>In automatic mode, you simply call {@link #wait} and let the progress bar run indefinitely, only clearing it
\r
25047 * once the operation is complete. You can optionally have the progress bar wait for a specific amount of time
\r
25048 * and then clear itself. Automatic mode is most appropriate for timed operations or asynchronous operations in
\r
25049 * which you have no need for indicating intermediate progress.</p>
\r
25050 * @cfg {Float} value A floating point value between 0 and 1 (e.g., .5, defaults to 0)
\r
25051 * @cfg {String} text The progress bar text (defaults to '')
\r
25052 * @cfg {Mixed} textEl The element to render the progress text to (defaults to the progress
\r
25053 * bar's internal text element)
\r
25054 * @cfg {String} id The progress bar element's id (defaults to an auto-generated id)
\r
25055 * @xtype progress
\r
25057 Ext.ProgressBar = Ext.extend(Ext.BoxComponent, {
\r
25059 * @cfg {String} baseCls
\r
25060 * The base CSS class to apply to the progress bar's wrapper element (defaults to 'x-progress')
\r
25062 baseCls : 'x-progress',
\r
25065 * @cfg {Boolean} animate
\r
25066 * True to animate the progress bar during transitions (defaults to false)
\r
25071 waitTimer : null,
\r
25074 initComponent : function(){
\r
25075 Ext.ProgressBar.superclass.initComponent.call(this);
\r
25079 * Fires after each update interval
\r
25080 * @param {Ext.ProgressBar} this
\r
25081 * @param {Number} The current progress value
\r
25082 * @param {String} The current progress text
\r
25089 onRender : function(ct, position){
\r
25090 var tpl = new Ext.Template(
\r
25091 '<div class="{cls}-wrap">',
\r
25092 '<div class="{cls}-inner">',
\r
25093 '<div class="{cls}-bar">',
\r
25094 '<div class="{cls}-text">',
\r
25095 '<div> </div>',
\r
25098 '<div class="{cls}-text {cls}-text-back">',
\r
25099 '<div> </div>',
\r
25105 this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true)
\r
25106 : tpl.append(ct, {cls: this.baseCls}, true);
\r
25109 this.el.dom.id = this.id;
\r
25111 var inner = this.el.dom.firstChild;
\r
25112 this.progressBar = Ext.get(inner.firstChild);
\r
25115 //use an external text el
\r
25116 this.textEl = Ext.get(this.textEl);
\r
25117 delete this.textTopEl;
\r
25119 //setup our internal layered text els
\r
25120 this.textTopEl = Ext.get(this.progressBar.dom.firstChild);
\r
25121 var textBackEl = Ext.get(inner.childNodes[1]);
\r
25122 this.textTopEl.setStyle("z-index", 99).addClass('x-hidden');
\r
25123 this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]);
\r
25124 this.textEl.setWidth(inner.offsetWidth);
\r
25126 this.progressBar.setHeight(inner.offsetHeight);
\r
25130 afterRender : function(){
\r
25131 Ext.ProgressBar.superclass.afterRender.call(this);
\r
25133 this.updateProgress(this.value, this.text);
\r
25135 this.updateText(this.text);
\r
25140 * Updates the progress bar value, and optionally its text. If the text argument is not specified,
\r
25141 * any existing text value will be unchanged. To blank out existing text, pass ''. Note that even
\r
25142 * if the progress bar value exceeds 1, it will never automatically reset -- you are responsible for
\r
25143 * determining when the progress is complete and calling {@link #reset} to clear and/or hide the control.
\r
25144 * @param {Float} value (optional) A floating point value between 0 and 1 (e.g., .5, defaults to 0)
\r
25145 * @param {String} text (optional) The string to display in the progress text element (defaults to '')
\r
25146 * @param {Boolean} animate (optional) Whether to animate the transition of the progress bar. If this value is
\r
25147 * not specified, the default for the class is used (default to false)
\r
25148 * @return {Ext.ProgressBar} this
\r
25150 updateProgress : function(value, text, animate){
\r
25151 this.value = value || 0;
\r
25153 this.updateText(text);
\r
25155 if(this.rendered){
\r
25156 var w = Math.floor(value*this.el.dom.firstChild.offsetWidth);
\r
25157 this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate));
\r
25158 if(this.textTopEl){
\r
25159 //textTopEl should be the same width as the bar so overflow will clip as the bar moves
\r
25160 this.textTopEl.removeClass('x-hidden').setWidth(w);
\r
25163 this.fireEvent('update', this, value, text);
\r
25168 * Initiates an auto-updating progress bar. A duration can be specified, in which case the progress
\r
25169 * bar will automatically reset after a fixed amount of time and optionally call a callback function
\r
25170 * if specified. If no duration is passed in, then the progress bar will run indefinitely and must
\r
25171 * be manually cleared by calling {@link #reset}. The wait method accepts a config object with
\r
25172 * the following properties:
\r
25174 Property Type Description
\r
25175 ---------- ------------ ----------------------------------------------------------------------
\r
25176 duration Number The length of time in milliseconds that the progress bar should
\r
25177 run before resetting itself (defaults to undefined, in which case it
\r
25178 will run indefinitely until reset is called)
\r
25179 interval Number The length of time in milliseconds between each progress update
\r
25180 (defaults to 1000 ms)
\r
25181 animate Boolean Whether to animate the transition of the progress bar. If this value is
\r
25182 not specified, the default for the class is used.
\r
25183 increment Number The number of progress update segments to display within the progress
\r
25184 bar (defaults to 10). If the bar reaches the end and is still
\r
25185 updating, it will automatically wrap back to the beginning.
\r
25186 text String Optional text to display in the progress bar element (defaults to '').
\r
25187 fn Function A callback function to execute after the progress bar finishes auto-
\r
25188 updating. The function will be called with no arguments. This function
\r
25189 will be ignored if duration is not specified since in that case the
\r
25190 progress bar can only be stopped programmatically, so any required function
\r
25191 should be called by the same code after it resets the progress bar.
\r
25192 scope Object The scope that is passed to the callback function (only applies when
\r
25193 duration and fn are both passed).
\r
25198 var p = new Ext.ProgressBar({
\r
25199 renderTo: 'my-el'
\r
25202 //Wait for 5 seconds, then update the status el (progress bar will auto-reset)
\r
25204 interval: 100, //bar will move fast!
\r
25207 text: 'Updating...',
\r
25210 Ext.fly('status').update('Done!');
\r
25214 //Or update indefinitely until some async action completes, then reset manually
\r
25216 myAction.on('complete', function(){
\r
25218 Ext.fly('status').update('Done!');
\r
25221 * @param {Object} config (optional) Configuration options
\r
25222 * @return {Ext.ProgressBar} this
\r
25224 wait : function(o){
\r
25225 if(!this.waitTimer){
\r
25226 var scope = this;
\r
25228 this.updateText(o.text);
\r
25229 this.waitTimer = Ext.TaskMgr.start({
\r
25230 run: function(i){
\r
25231 var inc = o.increment || 10;
\r
25233 this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
\r
25235 interval: o.interval || 1000,
\r
25236 duration: o.duration,
\r
25237 onStop: function(){
\r
25239 o.fn.apply(o.scope || this);
\r
25250 * Returns true if the progress bar is currently in a {@link #wait} operation
\r
25251 * @return {Boolean} True if waiting, else false
\r
25253 isWaiting : function(){
\r
25254 return this.waitTimer !== null;
\r
25258 * Updates the progress bar text. If specified, textEl will be updated, otherwise the progress
\r
25259 * bar itself will display the updated text.
\r
25260 * @param {String} text (optional) The string to display in the progress text element (defaults to '')
\r
25261 * @return {Ext.ProgressBar} this
\r
25263 updateText : function(text){
\r
25264 this.text = text || ' ';
\r
25265 if(this.rendered){
\r
25266 this.textEl.update(this.text);
\r
25272 * Synchronizes the inner bar width to the proper proportion of the total componet width based
\r
25273 * on the current progress {@link #value}. This will be called automatically when the ProgressBar
\r
25274 * is resized by a layout, but if it is rendered auto width, this method can be called from
\r
25275 * another resize handler to sync the ProgressBar if necessary.
\r
25277 syncProgressBar : function(){
\r
25279 this.updateProgress(this.value, this.text);
\r
25285 * Sets the size of the progress bar.
\r
25286 * @param {Number} width The new width in pixels
\r
25287 * @param {Number} height The new height in pixels
\r
25288 * @return {Ext.ProgressBar} this
\r
25290 setSize : function(w, h){
\r
25291 Ext.ProgressBar.superclass.setSize.call(this, w, h);
\r
25292 if(this.textTopEl){
\r
25293 var inner = this.el.dom.firstChild;
\r
25294 this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
\r
25296 this.syncProgressBar();
\r
25301 * Resets the progress bar value to 0 and text to empty string. If hide = true, the progress
\r
25302 * bar will also be hidden (using the {@link #hideMode} property internally).
\r
25303 * @param {Boolean} hide (optional) True to hide the progress bar (defaults to false)
\r
25304 * @return {Ext.ProgressBar} this
\r
25306 reset : function(hide){
\r
25307 this.updateProgress(0);
\r
25308 if(this.textTopEl){
\r
25309 this.textTopEl.addClass('x-hidden');
\r
25311 if(this.waitTimer){
\r
25312 this.waitTimer.onStop = null; //prevent recursion
\r
25313 Ext.TaskMgr.stop(this.waitTimer);
\r
25314 this.waitTimer = null;
\r
25316 if(hide === true){
\r
25322 Ext.reg('progress', Ext.ProgressBar);/*
25323 * These classes are derivatives of the similarly named classes in the YUI Library.
25324 * The original license:
25325 * Copyright (c) 2006, Yahoo! Inc. All rights reserved.
25326 * Code licensed under the BSD License:
25327 * http://developer.yahoo.net/yui/license.txt
25332 var Event=Ext.EventManager;
25333 var Dom=Ext.lib.Dom;
25336 * @class Ext.dd.DragDrop
25337 * Defines the interface and base operation of items that that can be
25338 * dragged or can be drop targets. It was designed to be extended, overriding
25339 * the event handlers for startDrag, onDrag, onDragOver and onDragOut.
25340 * Up to three html elements can be associated with a DragDrop instance:
25342 * <li>linked element: the element that is passed into the constructor.
25343 * This is the element which defines the boundaries for interaction with
25344 * other DragDrop objects.</li>
25345 * <li>handle element(s): The drag operation only occurs if the element that
25346 * was clicked matches a handle element. By default this is the linked
25347 * element, but there are times that you will want only a portion of the
25348 * linked element to initiate the drag operation, and the setHandleElId()
25349 * method provides a way to define this.</li>
25350 * <li>drag element: this represents the element that would be moved along
25351 * with the cursor during a drag operation. By default, this is the linked
25352 * element itself as in {@link Ext.dd.DD}. setDragElId() lets you define
25353 * a separate element that would be moved, as in {@link Ext.dd.DDProxy}.
25356 * This class should not be instantiated until the onload event to ensure that
25357 * the associated elements are available.
25358 * The following would define a DragDrop obj that would interact with any
25359 * other DragDrop obj in the "group1" group:
25361 * dd = new Ext.dd.DragDrop("div1", "group1");
25363 * Since none of the event handlers have been implemented, nothing would
25364 * actually happen if you were to run the code above. Normally you would
25365 * override this class or one of the default implementations, but you can
25366 * also override the methods you want on an instance of the class...
25368 * dd.onDragDrop = function(e, id) {
25369 * alert("dd was dropped on " + id);
25373 * @param {String} id of the element that is linked to this instance
25374 * @param {String} sGroup the group of related DragDrop objects
25375 * @param {object} config an object containing configurable attributes
25376 * Valid properties for DragDrop:
25377 * padding, isTarget, maintainOffset, primaryButtonOnly
25379 Ext.dd.DragDrop = function(id, sGroup, config) {
25381 this.init(id, sGroup, config);
25385 Ext.dd.DragDrop.prototype = {
25388 * Set to false to enable a DragDrop object to fire drag events while dragging
25389 * over its own Element. Defaults to true - DragDrop objects do not by default
25390 * fire drag events to themselves.
25391 * @property ignoreSelf
25396 * The id of the element associated with this object. This is what we
25397 * refer to as the "linked element" because the size and position of
25398 * this element is used to determine when the drag and drop objects have
25406 * Configuration attributes passed into the constructor
25413 * The id of the element that will be dragged. By default this is same
25414 * as the linked element , but could be changed to another element. Ex:
25416 * @property dragElId
25423 * The ID of the element that initiates the drag operation. By default
25424 * this is the linked element, but could be changed to be a child of this
25425 * element. This lets us do things like only starting the drag when the
25426 * header element within the linked html element is clicked.
25427 * @property handleElId
25434 * An object who's property names identify HTML tags to be considered invalid as drag handles.
25435 * A non-null property value identifies the tag as invalid. Defaults to the
25436 * following value which prevents drag operations from being initiated by <a> elements:<pre><code>
25440 * @property invalidHandleTypes
25443 invalidHandleTypes: null,
25446 * An object who's property names identify the IDs of elements to be considered invalid as drag handles.
25447 * A non-null property value identifies the ID as invalid. For example, to prevent
25448 * dragging from being initiated on element ID "foo", use:<pre><code>
25452 * @property invalidHandleIds
25455 invalidHandleIds: null,
25458 * An Array of CSS class names for elements to be considered in valid as drag handles.
25459 * @property invalidHandleClasses
25462 invalidHandleClasses: null,
25465 * The linked element's absolute X position at the time the drag was
25467 * @property startPageX
25474 * The linked element's absolute X position at the time the drag was
25476 * @property startPageY
25483 * The group defines a logical collection of DragDrop objects that are
25484 * related. Instances only get events when interacting with other
25485 * DragDrop object in the same group. This lets us define multiple
25486 * groups using a single DragDrop subclass if we want.
25488 * @type object An object in the format {'group1':true, 'group2':true}
25493 * Individual drag/drop instances can be locked. This will prevent
25494 * onmousedown start drag.
25502 * Lock this instance
25505 lock: function() { this.locked = true; },
25508 * When set to true, other DD objects in cooperating DDGroups do not receive
25509 * notification events when this DD object is dragged over them. Defaults to false.
25510 * @property moveOnly
25516 * Unlock this instace
25519 unlock: function() { this.locked = false; },
25522 * By default, all instances can be a drop target. This can be disabled by
25523 * setting isTarget to false.
25524 * @property isTarget
25530 * The padding configured for this drag and drop object for calculating
25531 * the drop zone intersection with this object.
25532 * @property padding
25533 * @type int[] An array containing the 4 padding values: [top, right, bottom, left]
25538 * Cached reference to the linked element
25539 * @property _domRef
25545 * Internal typeof flag
25546 * @property __ygDragDrop
25549 __ygDragDrop: true,
25552 * Set to true when horizontal contraints are applied
25553 * @property constrainX
25560 * Set to true when vertical contraints are applied
25561 * @property constrainY
25568 * The left constraint
25576 * The right constraint
25584 * The up constraint
25593 * The down constraint
25601 * Maintain offsets when we resetconstraints. Set to true when you want
25602 * the position of the element relative to its parent to stay the same
25603 * when the page changes
25605 * @property maintainOffset
25608 maintainOffset: false,
25611 * Array of pixel locations the element will snap to if we specified a
25612 * horizontal graduation/interval. This array is generated automatically
25613 * when you define a tick interval.
25620 * Array of pixel locations the element will snap to if we specified a
25621 * vertical graduation/interval. This array is generated automatically
25622 * when you define a tick interval.
25629 * By default the drag and drop instance will only respond to the primary
25630 * button click (left button for a right-handed mouse). Set to true to
25631 * allow drag and drop to start with any mouse click that is propogated
25633 * @property primaryButtonOnly
25636 primaryButtonOnly: true,
25639 * The availabe property is false until the linked dom element is accessible.
25640 * @property available
25646 * By default, drags can only be initiated if the mousedown occurs in the
25647 * region the linked element is. This is done in part to work around a
25648 * bug in some browsers that mis-report the mousedown if the previous
25649 * mouseup happened outside of the window. This property is set to true
25650 * if outer handles are defined.
25652 * @property hasOuterHandles
25656 hasOuterHandles: false,
25659 * Code that executes immediately before the startDrag event
25660 * @method b4StartDrag
25663 b4StartDrag: function(x, y) { },
25666 * Abstract method called after a drag/drop object is clicked
25667 * and the drag or mousedown time thresholds have beeen met.
25668 * @method startDrag
25669 * @param {int} X click location
25670 * @param {int} Y click location
25672 startDrag: function(x, y) { /* override this */ },
25675 * Code that executes immediately before the onDrag event
25679 b4Drag: function(e) { },
25682 * Abstract method called during the onMouseMove event while dragging an
25685 * @param {Event} e the mousemove event
25687 onDrag: function(e) { /* override this */ },
25690 * Abstract method called when this element fist begins hovering over
25691 * another DragDrop obj
25692 * @method onDragEnter
25693 * @param {Event} e the mousemove event
25694 * @param {String|DragDrop[]} id In POINT mode, the element
25695 * id this is hovering over. In INTERSECT mode, an array of one or more
25696 * dragdrop items being hovered over.
25698 onDragEnter: function(e, id) { /* override this */ },
25701 * Code that executes immediately before the onDragOver event
25702 * @method b4DragOver
25705 b4DragOver: function(e) { },
25708 * Abstract method called when this element is hovering over another
25710 * @method onDragOver
25711 * @param {Event} e the mousemove event
25712 * @param {String|DragDrop[]} id In POINT mode, the element
25713 * id this is hovering over. In INTERSECT mode, an array of dd items
25714 * being hovered over.
25716 onDragOver: function(e, id) { /* override this */ },
25719 * Code that executes immediately before the onDragOut event
25720 * @method b4DragOut
25723 b4DragOut: function(e) { },
25726 * Abstract method called when we are no longer hovering over an element
25727 * @method onDragOut
25728 * @param {Event} e the mousemove event
25729 * @param {String|DragDrop[]} id In POINT mode, the element
25730 * id this was hovering over. In INTERSECT mode, an array of dd items
25731 * that the mouse is no longer over.
25733 onDragOut: function(e, id) { /* override this */ },
25736 * Code that executes immediately before the onDragDrop event
25737 * @method b4DragDrop
25740 b4DragDrop: function(e) { },
25743 * Abstract method called when this item is dropped on another DragDrop
25745 * @method onDragDrop
25746 * @param {Event} e the mouseup event
25747 * @param {String|DragDrop[]} id In POINT mode, the element
25748 * id this was dropped on. In INTERSECT mode, an array of dd items this
25751 onDragDrop: function(e, id) { /* override this */ },
25754 * Abstract method called when this item is dropped on an area with no
25756 * @method onInvalidDrop
25757 * @param {Event} e the mouseup event
25759 onInvalidDrop: function(e) { /* override this */ },
25762 * Code that executes immediately before the endDrag event
25763 * @method b4EndDrag
25766 b4EndDrag: function(e) { },
25769 * Fired when we are done dragging the object
25771 * @param {Event} e the mouseup event
25773 endDrag: function(e) { /* override this */ },
25776 * Code executed immediately before the onMouseDown event
25777 * @method b4MouseDown
25778 * @param {Event} e the mousedown event
25781 b4MouseDown: function(e) { },
25784 * Event handler that fires when a drag/drop obj gets a mousedown
25785 * @method onMouseDown
25786 * @param {Event} e the mousedown event
25788 onMouseDown: function(e) { /* override this */ },
25791 * Event handler that fires when a drag/drop obj gets a mouseup
25792 * @method onMouseUp
25793 * @param {Event} e the mouseup event
25795 onMouseUp: function(e) { /* override this */ },
25798 * Override the onAvailable method to do what is needed after the initial
25799 * position was determined.
25800 * @method onAvailable
25802 onAvailable: function () {
25806 * Provides default constraint padding to "constrainTo" elements (defaults to {left: 0, right:0, top:0, bottom:0}).
25809 defaultPadding : {left:0, right:0, top:0, bottom:0},
25812 * Initializes the drag drop object's constraints to restrict movement to a certain element.
25816 var dd = new Ext.dd.DDProxy("dragDiv1", "proxytest",
25817 { dragElId: "existingProxyDiv" });
25818 dd.startDrag = function(){
25819 this.constrainTo("parent-id");
25822 * Or you can initalize it using the {@link Ext.Element} object:
25824 Ext.get("dragDiv1").initDDProxy("proxytest", {dragElId: "existingProxyDiv"}, {
25825 startDrag : function(){
25826 this.constrainTo("parent-id");
25830 * @param {Mixed} constrainTo The element to constrain to.
25831 * @param {Object/Number} pad (optional) Pad provides a way to specify "padding" of the constraints,
25832 * and can be either a number for symmetrical padding (4 would be equal to {left:4, right:4, top:4, bottom:4}) or
25833 * an object containing the sides to pad. For example: {right:10, bottom:10}
25834 * @param {Boolean} inContent (optional) Constrain the draggable in the content box of the element (inside padding and borders)
25836 constrainTo : function(constrainTo, pad, inContent){
25837 if(typeof pad == "number"){
25838 pad = {left: pad, right:pad, top:pad, bottom:pad};
25840 pad = pad || this.defaultPadding;
25841 var b = Ext.get(this.getEl()).getBox();
25842 var ce = Ext.get(constrainTo);
25843 var s = ce.getScroll();
25844 var c, cd = ce.dom;
25845 if(cd == document.body){
25846 c = { x: s.left, y: s.top, width: Ext.lib.Dom.getViewWidth(), height: Ext.lib.Dom.getViewHeight()};
25848 var xy = ce.getXY();
25849 c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
25853 var topSpace = b.y - c.y;
25854 var leftSpace = b.x - c.x;
25856 this.resetConstraints();
25857 this.setXConstraint(leftSpace - (pad.left||0), // left
25858 c.width - leftSpace - b.width - (pad.right||0), //right
25861 this.setYConstraint(topSpace - (pad.top||0), //top
25862 c.height - topSpace - b.height - (pad.bottom||0), //bottom
25868 * Returns a reference to the linked element
25870 * @return {HTMLElement} the html element
25872 getEl: function() {
25873 if (!this._domRef) {
25874 this._domRef = Ext.getDom(this.id);
25877 return this._domRef;
25881 * Returns a reference to the actual element to drag. By default this is
25882 * the same as the html element, but it can be assigned to another
25883 * element. An example of this can be found in Ext.dd.DDProxy
25884 * @method getDragEl
25885 * @return {HTMLElement} the html element
25887 getDragEl: function() {
25888 return Ext.getDom(this.dragElId);
25892 * Sets up the DragDrop object. Must be called in the constructor of any
25893 * Ext.dd.DragDrop subclass
25895 * @param id the id of the linked element
25896 * @param {String} sGroup the group of related items
25897 * @param {object} config configuration attributes
25899 init: function(id, sGroup, config) {
25900 this.initTarget(id, sGroup, config);
25901 Event.on(this.id, "mousedown", this.handleMouseDown, this);
25902 // Event.on(this.id, "selectstart", Event.preventDefault);
25906 * Initializes Targeting functionality only... the object does not
25907 * get a mousedown handler.
25908 * @method initTarget
25909 * @param id the id of the linked element
25910 * @param {String} sGroup the group of related items
25911 * @param {object} config configuration attributes
25913 initTarget: function(id, sGroup, config) {
25915 // configuration attributes
25916 this.config = config || {};
25918 // create a local reference to the drag and drop manager
25919 this.DDM = Ext.dd.DDM;
25920 // initialize the groups array
25923 // assume that we have an element reference instead of an id if the
25924 // parameter is not a string
25925 if (typeof id !== "string") {
25932 // add to an interaction group
25933 this.addToGroup((sGroup) ? sGroup : "default");
25935 // We don't want to register this as the handle with the manager
25936 // so we just set the id rather than calling the setter.
25937 this.handleElId = id;
25939 // the linked element is the element that gets dragged by default
25940 this.setDragElId(id);
25942 // by default, clicked anchors will not start drag operations.
25943 this.invalidHandleTypes = { A: "A" };
25944 this.invalidHandleIds = {};
25945 this.invalidHandleClasses = [];
25947 this.applyConfig();
25949 this.handleOnAvailable();
25953 * Applies the configuration parameters that were passed into the constructor.
25954 * This is supposed to happen at each level through the inheritance chain. So
25955 * a DDProxy implentation will execute apply config on DDProxy, DD, and
25956 * DragDrop in order to get all of the parameters that are available in
25958 * @method applyConfig
25960 applyConfig: function() {
25962 // configurable properties:
25963 // padding, isTarget, maintainOffset, primaryButtonOnly
25964 this.padding = this.config.padding || [0, 0, 0, 0];
25965 this.isTarget = (this.config.isTarget !== false);
25966 this.maintainOffset = (this.config.maintainOffset);
25967 this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
25972 * Executed when the linked element is available
25973 * @method handleOnAvailable
25976 handleOnAvailable: function() {
25977 this.available = true;
25978 this.resetConstraints();
25979 this.onAvailable();
25983 * Configures the padding for the target zone in px. Effectively expands
25984 * (or reduces) the virtual object size for targeting calculations.
25985 * Supports css-style shorthand; if only one parameter is passed, all sides
25986 * will have that padding, and if only two are passed, the top and bottom
25987 * will have the first param, the left and right the second.
25988 * @method setPadding
25989 * @param {int} iTop Top pad
25990 * @param {int} iRight Right pad
25991 * @param {int} iBot Bot pad
25992 * @param {int} iLeft Left pad
25994 setPadding: function(iTop, iRight, iBot, iLeft) {
25995 // this.padding = [iLeft, iRight, iTop, iBot];
25996 if (!iRight && 0 !== iRight) {
25997 this.padding = [iTop, iTop, iTop, iTop];
25998 } else if (!iBot && 0 !== iBot) {
25999 this.padding = [iTop, iRight, iTop, iRight];
26001 this.padding = [iTop, iRight, iBot, iLeft];
26006 * Stores the initial placement of the linked element.
26007 * @method setInitPosition
26008 * @param {int} diffX the X offset, default 0
26009 * @param {int} diffY the Y offset, default 0
26011 setInitPosition: function(diffX, diffY) {
26012 var el = this.getEl();
26014 if (!this.DDM.verifyEl(el)) {
26018 var dx = diffX || 0;
26019 var dy = diffY || 0;
26021 var p = Dom.getXY( el );
26023 this.initPageX = p[0] - dx;
26024 this.initPageY = p[1] - dy;
26026 this.lastPageX = p[0];
26027 this.lastPageY = p[1];
26030 this.setStartPosition(p);
26034 * Sets the start position of the element. This is set when the obj
26035 * is initialized, the reset when a drag is started.
26036 * @method setStartPosition
26037 * @param pos current position (from previous lookup)
26040 setStartPosition: function(pos) {
26041 var p = pos || Dom.getXY( this.getEl() );
26042 this.deltaSetXY = null;
26044 this.startPageX = p[0];
26045 this.startPageY = p[1];
26049 * Add this instance to a group of related drag/drop objects. All
26050 * instances belong to at least one group, and can belong to as many
26051 * groups as needed.
26052 * @method addToGroup
26053 * @param sGroup {string} the name of the group
26055 addToGroup: function(sGroup) {
26056 this.groups[sGroup] = true;
26057 this.DDM.regDragDrop(this, sGroup);
26061 * Remove's this instance from the supplied interaction group
26062 * @method removeFromGroup
26063 * @param {string} sGroup The group to drop
26065 removeFromGroup: function(sGroup) {
26066 if (this.groups[sGroup]) {
26067 delete this.groups[sGroup];
26070 this.DDM.removeDDFromGroup(this, sGroup);
26074 * Allows you to specify that an element other than the linked element
26075 * will be moved with the cursor during a drag
26076 * @method setDragElId
26077 * @param id {string} the id of the element that will be used to initiate the drag
26079 setDragElId: function(id) {
26080 this.dragElId = id;
26084 * Allows you to specify a child of the linked element that should be
26085 * used to initiate the drag operation. An example of this would be if
26086 * you have a content div with text and links. Clicking anywhere in the
26087 * content area would normally start the drag operation. Use this method
26088 * to specify that an element inside of the content div is the element
26089 * that starts the drag operation.
26090 * @method setHandleElId
26091 * @param id {string} the id of the element that will be used to
26092 * initiate the drag.
26094 setHandleElId: function(id) {
26095 if (typeof id !== "string") {
26098 this.handleElId = id;
26099 this.DDM.regHandle(this.id, id);
26103 * Allows you to set an element outside of the linked element as a drag
26105 * @method setOuterHandleElId
26106 * @param id the id of the element that will be used to initiate the drag
26108 setOuterHandleElId: function(id) {
26109 if (typeof id !== "string") {
26112 Event.on(id, "mousedown",
26113 this.handleMouseDown, this);
26114 this.setHandleElId(id);
26116 this.hasOuterHandles = true;
26120 * Remove all drag and drop hooks for this element
26123 unreg: function() {
26124 Event.un(this.id, "mousedown",
26125 this.handleMouseDown);
26126 this._domRef = null;
26127 this.DDM._remove(this);
26130 destroy : function(){
26135 * Returns true if this instance is locked, or the drag drop mgr is locked
26136 * (meaning that all drag/drop is disabled on the page.)
26138 * @return {boolean} true if this obj or all drag/drop is locked, else
26141 isLocked: function() {
26142 return (this.DDM.isLocked() || this.locked);
26146 * Fired when this object is clicked
26147 * @method handleMouseDown
26149 * @param {Ext.dd.DragDrop} oDD the clicked dd object (this dd obj)
26152 handleMouseDown: function(e, oDD){
26153 if (this.primaryButtonOnly && e.button != 0) {
26157 if (this.isLocked()) {
26161 this.DDM.refreshCache(this.groups);
26163 var pt = new Ext.lib.Point(Ext.lib.Event.getPageX(e), Ext.lib.Event.getPageY(e));
26164 if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) ) {
26166 if (this.clickValidator(e)) {
26168 // set the initial element position
26169 this.setStartPosition();
26172 this.b4MouseDown(e);
26173 this.onMouseDown(e);
26175 this.DDM.handleMouseDown(e, this);
26177 this.DDM.stopEvent(e);
26185 clickValidator: function(e) {
26186 var target = e.getTarget();
26187 return ( this.isValidHandleChild(target) &&
26188 (this.id == this.handleElId ||
26189 this.DDM.handleWasClicked(target, this.id)) );
26193 * Allows you to specify a tag name that should not start a drag operation
26194 * when clicked. This is designed to facilitate embedding links within a
26195 * drag handle that do something other than start the drag.
26196 * @method addInvalidHandleType
26197 * @param {string} tagName the type of element to exclude
26199 addInvalidHandleType: function(tagName) {
26200 var type = tagName.toUpperCase();
26201 this.invalidHandleTypes[type] = type;
26205 * Lets you to specify an element id for a child of a drag handle
26206 * that should not initiate a drag
26207 * @method addInvalidHandleId
26208 * @param {string} id the element id of the element you wish to ignore
26210 addInvalidHandleId: function(id) {
26211 if (typeof id !== "string") {
26214 this.invalidHandleIds[id] = id;
26218 * Lets you specify a css class of elements that will not initiate a drag
26219 * @method addInvalidHandleClass
26220 * @param {string} cssClass the class of the elements you wish to ignore
26222 addInvalidHandleClass: function(cssClass) {
26223 this.invalidHandleClasses.push(cssClass);
26227 * Unsets an excluded tag name set by addInvalidHandleType
26228 * @method removeInvalidHandleType
26229 * @param {string} tagName the type of element to unexclude
26231 removeInvalidHandleType: function(tagName) {
26232 var type = tagName.toUpperCase();
26233 // this.invalidHandleTypes[type] = null;
26234 delete this.invalidHandleTypes[type];
26238 * Unsets an invalid handle id
26239 * @method removeInvalidHandleId
26240 * @param {string} id the id of the element to re-enable
26242 removeInvalidHandleId: function(id) {
26243 if (typeof id !== "string") {
26246 delete this.invalidHandleIds[id];
26250 * Unsets an invalid css class
26251 * @method removeInvalidHandleClass
26252 * @param {string} cssClass the class of the element(s) you wish to
26255 removeInvalidHandleClass: function(cssClass) {
26256 for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
26257 if (this.invalidHandleClasses[i] == cssClass) {
26258 delete this.invalidHandleClasses[i];
26264 * Checks the tag exclusion list to see if this click should be ignored
26265 * @method isValidHandleChild
26266 * @param {HTMLElement} node the HTMLElement to evaluate
26267 * @return {boolean} true if this is a valid tag type, false if not
26269 isValidHandleChild: function(node) {
26272 // var n = (node.nodeName == "#text") ? node.parentNode : node;
26275 nodeName = node.nodeName.toUpperCase();
26277 nodeName = node.nodeName;
26279 valid = valid && !this.invalidHandleTypes[nodeName];
26280 valid = valid && !this.invalidHandleIds[node.id];
26282 for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
26283 valid = !Ext.fly(node).hasClass(this.invalidHandleClasses[i]);
26292 * Create the array of horizontal tick marks if an interval was specified
26293 * in setXConstraint().
26294 * @method setXTicks
26297 setXTicks: function(iStartX, iTickSize) {
26299 this.xTickSize = iTickSize;
26303 for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
26305 this.xTicks[this.xTicks.length] = i;
26310 for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
26312 this.xTicks[this.xTicks.length] = i;
26317 this.xTicks.sort(this.DDM.numericSort) ;
26321 * Create the array of vertical tick marks if an interval was specified in
26322 * setYConstraint().
26323 * @method setYTicks
26326 setYTicks: function(iStartY, iTickSize) {
26328 this.yTickSize = iTickSize;
26332 for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
26334 this.yTicks[this.yTicks.length] = i;
26339 for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
26341 this.yTicks[this.yTicks.length] = i;
26346 this.yTicks.sort(this.DDM.numericSort) ;
26350 * By default, the element can be dragged any place on the screen. Use
26351 * this method to limit the horizontal travel of the element. Pass in
26352 * 0,0 for the parameters if you want to lock the drag to the y axis.
26353 * @method setXConstraint
26354 * @param {int} iLeft the number of pixels the element can move to the left
26355 * @param {int} iRight the number of pixels the element can move to the
26357 * @param {int} iTickSize optional parameter for specifying that the
26359 * should move iTickSize pixels at a time.
26361 setXConstraint: function(iLeft, iRight, iTickSize) {
26362 this.leftConstraint = iLeft;
26363 this.rightConstraint = iRight;
26365 this.minX = this.initPageX - iLeft;
26366 this.maxX = this.initPageX + iRight;
26367 if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
26369 this.constrainX = true;
26373 * Clears any constraints applied to this instance. Also clears ticks
26374 * since they can't exist independent of a constraint at this time.
26375 * @method clearConstraints
26377 clearConstraints: function() {
26378 this.constrainX = false;
26379 this.constrainY = false;
26384 * Clears any tick interval defined for this instance
26385 * @method clearTicks
26387 clearTicks: function() {
26388 this.xTicks = null;
26389 this.yTicks = null;
26390 this.xTickSize = 0;
26391 this.yTickSize = 0;
26395 * By default, the element can be dragged any place on the screen. Set
26396 * this to limit the vertical travel of the element. Pass in 0,0 for the
26397 * parameters if you want to lock the drag to the x axis.
26398 * @method setYConstraint
26399 * @param {int} iUp the number of pixels the element can move up
26400 * @param {int} iDown the number of pixels the element can move down
26401 * @param {int} iTickSize optional parameter for specifying that the
26402 * element should move iTickSize pixels at a time.
26404 setYConstraint: function(iUp, iDown, iTickSize) {
26405 this.topConstraint = iUp;
26406 this.bottomConstraint = iDown;
26408 this.minY = this.initPageY - iUp;
26409 this.maxY = this.initPageY + iDown;
26410 if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
26412 this.constrainY = true;
26417 * resetConstraints must be called if you manually reposition a dd element.
26418 * @method resetConstraints
26419 * @param {boolean} maintainOffset
26421 resetConstraints: function() {
26424 // Maintain offsets if necessary
26425 if (this.initPageX || this.initPageX === 0) {
26426 // figure out how much this thing has moved
26427 var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
26428 var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
26430 this.setInitPosition(dx, dy);
26432 // This is the first time we have detected the element's position
26434 this.setInitPosition();
26437 if (this.constrainX) {
26438 this.setXConstraint( this.leftConstraint,
26439 this.rightConstraint,
26443 if (this.constrainY) {
26444 this.setYConstraint( this.topConstraint,
26445 this.bottomConstraint,
26451 * Normally the drag element is moved pixel by pixel, but we can specify
26452 * that it move a number of pixels at a time. This method resolves the
26453 * location when we have it set up like this.
26455 * @param {int} val where we want to place the object
26456 * @param {int[]} tickArray sorted array of valid points
26457 * @return {int} the closest tick
26460 getTick: function(val, tickArray) {
26463 // If tick interval is not defined, it is effectively 1 pixel,
26464 // so we return the value passed to us.
26466 } else if (tickArray[0] >= val) {
26467 // The value is lower than the first tick, so we return the first
26469 return tickArray[0];
26471 for (var i=0, len=tickArray.length; i<len; ++i) {
26473 if (tickArray[next] && tickArray[next] >= val) {
26474 var diff1 = val - tickArray[i];
26475 var diff2 = tickArray[next] - val;
26476 return (diff2 > diff1) ? tickArray[i] : tickArray[next];
26480 // The value is larger than the last tick, so we return the last
26482 return tickArray[tickArray.length - 1];
26489 * @return {string} string representation of the dd obj
26491 toString: function() {
26492 return ("DragDrop " + this.id);
26499 * The drag and drop utility provides a framework for building drag and drop
26500 * applications. In addition to enabling drag and drop for specific elements,
26501 * the drag and drop elements are tracked by the manager class, and the
26502 * interactions between the various elements are tracked during the drag and
26503 * the implementing code is notified about these important moments.
26506 // Only load the library once. Rewriting the manager class would orphan
26507 // existing drag and drop instances.
26508 if (!Ext.dd.DragDropMgr) {
26511 * @class Ext.dd.DragDropMgr
26512 * DragDropMgr is a singleton that tracks the element interaction for
26513 * all DragDrop items in the window. Generally, you will not call
26514 * this class directly, but it does have helper methods that could
26515 * be useful in your DragDrop implementations.
26518 Ext.dd.DragDropMgr = function() {
26520 var Event = Ext.EventManager;
26525 * Two dimensional Array of registered DragDrop objects. The first
26526 * dimension is the DragDrop item group, the second the DragDrop
26529 * @type {string: string}
26536 * Array of element ids defined as drag handles. Used to determine
26537 * if the element that generated the mousedown event is actually the
26538 * handle and not the html element itself.
26539 * @property handleIds
26540 * @type {string: string}
26547 * the DragDrop object that is currently being dragged
26548 * @property dragCurrent
26556 * the DragDrop object(s) that are being hovered over
26557 * @property dragOvers
26565 * the X distance between the cursor and the object being dragged
26574 * the Y distance between the cursor and the object being dragged
26583 * Flag to determine if we should prevent the default behavior of the
26584 * events we define. By default this is true, but this can be set to
26585 * false if you need the default behavior (not recommended)
26586 * @property preventDefault
26590 preventDefault: true,
26593 * Flag to determine if we should stop the propagation of the events
26594 * we generate. This is true by default but you may want to set it to
26595 * false if the html element contains other features that require the
26597 * @property stopPropagation
26601 stopPropagation: true,
26604 * Internal flag that is set to true when drag and drop has been
26606 * @property initialized
26610 initialized: false,
26613 * All drag and drop can be disabled.
26621 * Called the first time an element is registered.
26627 this.initialized = true;
26631 * In point mode, drag and drop interaction is defined by the
26632 * location of the cursor during the drag/drop
26640 * In intersect mode, drag and drop interaction is defined by the
26641 * overlap of two or more drag and drop objects.
26642 * @property INTERSECT
26649 * The current drag and drop mode. Default: POINT
26657 * Runs method on all drag and drop objects
26658 * @method _execOnAll
26662 _execOnAll: function(sMethod, args) {
26663 for (var i in this.ids) {
26664 for (var j in this.ids[i]) {
26665 var oDD = this.ids[i][j];
26666 if (! this.isTypeOfDD(oDD)) {
26669 oDD[sMethod].apply(oDD, args);
26675 * Drag and drop initialization. Sets up the global event handlers
26680 _onLoad: function() {
26685 Event.on(document, "mouseup", this.handleMouseUp, this, true);
26686 Event.on(document, "mousemove", this.handleMouseMove, this, true);
26687 Event.on(window, "unload", this._onUnload, this, true);
26688 Event.on(window, "resize", this._onResize, this, true);
26689 // Event.on(window, "mouseout", this._test);
26694 * Reset constraints on all drag and drop objs
26695 * @method _onResize
26699 _onResize: function(e) {
26700 this._execOnAll("resetConstraints", []);
26704 * Lock all drag and drop functionality
26708 lock: function() { this.locked = true; },
26711 * Unlock all drag and drop functionality
26715 unlock: function() { this.locked = false; },
26718 * Is drag and drop locked?
26720 * @return {boolean} True if drag and drop is locked, false otherwise.
26723 isLocked: function() { return this.locked; },
26726 * Location cache that is set for all drag drop objects when a drag is
26727 * initiated, cleared when the drag is finished.
26728 * @property locationCache
26735 * Set useCache to false if you want to force object the lookup of each
26736 * drag and drop linked element constantly during a drag.
26737 * @property useCache
26744 * The number of pixels that the mouse needs to move after the
26745 * mousedown before the drag is initiated. Default=3;
26746 * @property clickPixelThresh
26750 clickPixelThresh: 3,
26753 * The number of milliseconds after the mousedown event to initiate the
26754 * drag if we don't get a mouseup event. Default=1000
26755 * @property clickTimeThresh
26759 clickTimeThresh: 350,
26762 * Flag that indicates that either the drag pixel threshold or the
26763 * mousdown time threshold has been met
26764 * @property dragThreshMet
26769 dragThreshMet: false,
26772 * Timeout used for the click time threshold
26773 * @property clickTimeout
26778 clickTimeout: null,
26781 * The X position of the mousedown event stored for later use when a
26782 * drag threshold is met.
26791 * The Y position of the mousedown event stored for later use when a
26792 * drag threshold is met.
26801 * Each DragDrop instance must be registered with the DragDropMgr.
26802 * This is executed in DragDrop.init()
26803 * @method regDragDrop
26804 * @param {DragDrop} oDD the DragDrop object to register
26805 * @param {String} sGroup the name of the group this element belongs to
26808 regDragDrop: function(oDD, sGroup) {
26809 if (!this.initialized) { this.init(); }
26811 if (!this.ids[sGroup]) {
26812 this.ids[sGroup] = {};
26814 this.ids[sGroup][oDD.id] = oDD;
26818 * Removes the supplied dd instance from the supplied group. Executed
26819 * by DragDrop.removeFromGroup, so don't call this function directly.
26820 * @method removeDDFromGroup
26824 removeDDFromGroup: function(oDD, sGroup) {
26825 if (!this.ids[sGroup]) {
26826 this.ids[sGroup] = {};
26829 var obj = this.ids[sGroup];
26830 if (obj && obj[oDD.id]) {
26831 delete obj[oDD.id];
26836 * Unregisters a drag and drop item. This is executed in
26837 * DragDrop.unreg, use that method instead of calling this directly.
26842 _remove: function(oDD) {
26843 for (var g in oDD.groups) {
26844 if (g && this.ids[g] && this.ids[g][oDD.id]) {
26845 delete this.ids[g][oDD.id];
26848 delete this.handleIds[oDD.id];
26852 * Each DragDrop handle element must be registered. This is done
26853 * automatically when executing DragDrop.setHandleElId()
26854 * @method regHandle
26855 * @param {String} sDDId the DragDrop id this element is a handle for
26856 * @param {String} sHandleId the id of the element that is the drag
26860 regHandle: function(sDDId, sHandleId) {
26861 if (!this.handleIds[sDDId]) {
26862 this.handleIds[sDDId] = {};
26864 this.handleIds[sDDId][sHandleId] = sHandleId;
26868 * Utility function to determine if a given element has been
26869 * registered as a drag drop item.
26870 * @method isDragDrop
26871 * @param {String} id the element id to check
26872 * @return {boolean} true if this element is a DragDrop item,
26876 isDragDrop: function(id) {
26877 return ( this.getDDById(id) ) ? true : false;
26881 * Returns the drag and drop instances that are in all groups the
26882 * passed in instance belongs to.
26883 * @method getRelated
26884 * @param {DragDrop} p_oDD the obj to get related data for
26885 * @param {boolean} bTargetsOnly if true, only return targetable objs
26886 * @return {DragDrop[]} the related instances
26889 getRelated: function(p_oDD, bTargetsOnly) {
26891 for (var i in p_oDD.groups) {
26892 for (var j in this.ids[i]) {
26893 var dd = this.ids[i][j];
26894 if (! this.isTypeOfDD(dd)) {
26897 if (!bTargetsOnly || dd.isTarget) {
26898 oDDs[oDDs.length] = dd;
26907 * Returns true if the specified dd target is a legal target for
26908 * the specifice drag obj
26909 * @method isLegalTarget
26910 * @param {DragDrop} the drag obj
26911 * @param {DragDrop} the target
26912 * @return {boolean} true if the target is a legal target for the
26916 isLegalTarget: function (oDD, oTargetDD) {
26917 var targets = this.getRelated(oDD, true);
26918 for (var i=0, len=targets.length;i<len;++i) {
26919 if (targets[i].id == oTargetDD.id) {
26928 * My goal is to be able to transparently determine if an object is
26929 * typeof DragDrop, and the exact subclass of DragDrop. typeof
26930 * returns "object", oDD.constructor.toString() always returns
26931 * "DragDrop" and not the name of the subclass. So for now it just
26932 * evaluates a well-known variable in DragDrop.
26933 * @method isTypeOfDD
26934 * @param {Object} the object to evaluate
26935 * @return {boolean} true if typeof oDD = DragDrop
26938 isTypeOfDD: function (oDD) {
26939 return (oDD && oDD.__ygDragDrop);
26943 * Utility function to determine if a given element has been
26944 * registered as a drag drop handle for the given Drag Drop object.
26946 * @param {String} id the element id to check
26947 * @return {boolean} true if this element is a DragDrop handle, false
26951 isHandle: function(sDDId, sHandleId) {
26952 return ( this.handleIds[sDDId] &&
26953 this.handleIds[sDDId][sHandleId] );
26957 * Returns the DragDrop instance for a given id
26958 * @method getDDById
26959 * @param {String} id the id of the DragDrop object
26960 * @return {DragDrop} the drag drop object, null if it is not found
26963 getDDById: function(id) {
26964 for (var i in this.ids) {
26965 if (this.ids[i][id]) {
26966 return this.ids[i][id];
26973 * Fired after a registered DragDrop object gets the mousedown event.
26974 * Sets up the events required to track the object being dragged
26975 * @method handleMouseDown
26976 * @param {Event} e the event
26977 * @param oDD the DragDrop object being dragged
26981 handleMouseDown: function(e, oDD) {
26983 Ext.QuickTips.disable();
26985 if(this.dragCurrent){
26986 // the original browser mouseup wasn't handled (e.g. outside FF browser window)
26987 // so clean up first to avoid breaking the next drag
26988 this.handleMouseUp(e);
26991 this.currentTarget = e.getTarget();
26992 this.dragCurrent = oDD;
26994 var el = oDD.getEl();
26996 // track start position
26997 this.startX = e.getPageX();
26998 this.startY = e.getPageY();
27000 this.deltaX = this.startX - el.offsetLeft;
27001 this.deltaY = this.startY - el.offsetTop;
27003 this.dragThreshMet = false;
27005 this.clickTimeout = setTimeout(
27007 var DDM = Ext.dd.DDM;
27008 DDM.startDrag(DDM.startX, DDM.startY);
27010 this.clickTimeThresh );
27014 * Fired when either the drag pixel threshol or the mousedown hold
27015 * time threshold has been met.
27016 * @method startDrag
27017 * @param x {int} the X position of the original mousedown
27018 * @param y {int} the Y position of the original mousedown
27021 startDrag: function(x, y) {
27022 clearTimeout(this.clickTimeout);
27023 if (this.dragCurrent) {
27024 this.dragCurrent.b4StartDrag(x, y);
27025 this.dragCurrent.startDrag(x, y);
27027 this.dragThreshMet = true;
27031 * Internal function to handle the mouseup event. Will be invoked
27032 * from the context of the document.
27033 * @method handleMouseUp
27034 * @param {Event} e the event
27038 handleMouseUp: function(e) {
27041 Ext.QuickTips.enable();
27043 if (! this.dragCurrent) {
27047 clearTimeout(this.clickTimeout);
27049 if (this.dragThreshMet) {
27050 this.fireEvents(e, true);
27060 * Utility to stop event propagation and event default, if these
27061 * features are turned on.
27062 * @method stopEvent
27063 * @param {Event} e the event as returned by this.getEvent()
27066 stopEvent: function(e){
27067 if(this.stopPropagation) {
27068 e.stopPropagation();
27071 if (this.preventDefault) {
27072 e.preventDefault();
27077 * Internal function to clean up event handlers after the drag
27078 * operation is complete
27080 * @param {Event} e the event
27084 stopDrag: function(e) {
27085 // Fire the drag end event for the item that was dragged
27086 if (this.dragCurrent) {
27087 if (this.dragThreshMet) {
27088 this.dragCurrent.b4EndDrag(e);
27089 this.dragCurrent.endDrag(e);
27092 this.dragCurrent.onMouseUp(e);
27095 this.dragCurrent = null;
27096 this.dragOvers = {};
27100 * Internal function to handle the mousemove event. Will be invoked
27101 * from the context of the html element.
27103 * @TODO figure out what we can do about mouse events lost when the
27104 * user drags objects beyond the window boundary. Currently we can
27105 * detect this in internet explorer by verifying that the mouse is
27106 * down during the mousemove event. Firefox doesn't give us the
27107 * button state on the mousemove event.
27108 * @method handleMouseMove
27109 * @param {Event} e the event
27113 handleMouseMove: function(e) {
27114 if (! this.dragCurrent) {
27117 // var button = e.which || e.button;
27119 // check for IE mouseup outside of page boundary
27120 if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
27122 return this.handleMouseUp(e);
27125 if (!this.dragThreshMet) {
27126 var diffX = Math.abs(this.startX - e.getPageX());
27127 var diffY = Math.abs(this.startY - e.getPageY());
27128 if (diffX > this.clickPixelThresh ||
27129 diffY > this.clickPixelThresh) {
27130 this.startDrag(this.startX, this.startY);
27134 if (this.dragThreshMet) {
27135 this.dragCurrent.b4Drag(e);
27136 this.dragCurrent.onDrag(e);
27137 if(!this.dragCurrent.moveOnly){
27138 this.fireEvents(e, false);
27148 * Iterates over all of the DragDrop elements to find ones we are
27149 * hovering over or dropping on
27150 * @method fireEvents
27151 * @param {Event} e the event
27152 * @param {boolean} isDrop is this a drop op or a mouseover op?
27156 fireEvents: function(e, isDrop) {
27157 var dc = this.dragCurrent;
27159 // If the user did the mouse up outside of the window, we could
27160 // get here even though we have ended the drag.
27161 if (!dc || dc.isLocked()) {
27165 var pt = e.getPoint();
27167 // cache the previous dragOver array
27173 var enterEvts = [];
27175 // Check to see if the object(s) we were hovering over is no longer
27176 // being hovered over so we can fire the onDragOut event
27177 for (var i in this.dragOvers) {
27179 var ddo = this.dragOvers[i];
27181 if (! this.isTypeOfDD(ddo)) {
27185 if (! this.isOverTarget(pt, ddo, this.mode)) {
27186 outEvts.push( ddo );
27189 oldOvers[i] = true;
27190 delete this.dragOvers[i];
27193 for (var sGroup in dc.groups) {
27195 if ("string" != typeof sGroup) {
27199 for (i in this.ids[sGroup]) {
27200 var oDD = this.ids[sGroup][i];
27201 if (! this.isTypeOfDD(oDD)) {
27205 if (oDD.isTarget && !oDD.isLocked() && ((oDD != dc) || (dc.ignoreSelf === false))) {
27206 if (this.isOverTarget(pt, oDD, this.mode)) {
27207 // look for drop interactions
27209 dropEvts.push( oDD );
27210 // look for drag enter and drag over interactions
27213 // initial drag over: dragEnter fires
27214 if (!oldOvers[oDD.id]) {
27215 enterEvts.push( oDD );
27216 // subsequent drag overs: dragOver fires
27218 overEvts.push( oDD );
27221 this.dragOvers[oDD.id] = oDD;
27229 if (outEvts.length) {
27230 dc.b4DragOut(e, outEvts);
27231 dc.onDragOut(e, outEvts);
27234 if (enterEvts.length) {
27235 dc.onDragEnter(e, enterEvts);
27238 if (overEvts.length) {
27239 dc.b4DragOver(e, overEvts);
27240 dc.onDragOver(e, overEvts);
27243 if (dropEvts.length) {
27244 dc.b4DragDrop(e, dropEvts);
27245 dc.onDragDrop(e, dropEvts);
27249 // fire dragout events
27251 for (i=0, len=outEvts.length; i<len; ++i) {
27252 dc.b4DragOut(e, outEvts[i].id);
27253 dc.onDragOut(e, outEvts[i].id);
27256 // fire enter events
27257 for (i=0,len=enterEvts.length; i<len; ++i) {
27258 // dc.b4DragEnter(e, oDD.id);
27259 dc.onDragEnter(e, enterEvts[i].id);
27262 // fire over events
27263 for (i=0,len=overEvts.length; i<len; ++i) {
27264 dc.b4DragOver(e, overEvts[i].id);
27265 dc.onDragOver(e, overEvts[i].id);
27268 // fire drop events
27269 for (i=0, len=dropEvts.length; i<len; ++i) {
27270 dc.b4DragDrop(e, dropEvts[i].id);
27271 dc.onDragDrop(e, dropEvts[i].id);
27276 // notify about a drop that did not find a target
27277 if (isDrop && !dropEvts.length) {
27278 dc.onInvalidDrop(e);
27284 * Helper function for getting the best match from the list of drag
27285 * and drop objects returned by the drag and drop events when we are
27286 * in INTERSECT mode. It returns either the first object that the
27287 * cursor is over, or the object that has the greatest overlap with
27288 * the dragged element.
27289 * @method getBestMatch
27290 * @param {DragDrop[]} dds The array of drag and drop objects
27292 * @return {DragDrop} The best single match
27295 getBestMatch: function(dds) {
27297 // Return null if the input is not what we expect
27298 //if (!dds || !dds.length || dds.length == 0) {
27300 // If there is only one item, it wins
27301 //} else if (dds.length == 1) {
27303 var len = dds.length;
27308 // Loop through the targeted items
27309 for (var i=0; i<len; ++i) {
27311 // If the cursor is over the object, it wins. If the
27312 // cursor is over multiple matches, the first one we come
27314 if (dd.cursorIsOver) {
27317 // Otherwise the object with the most overlap wins
27320 winner.overlap.getArea() < dd.overlap.getArea()) {
27331 * Refreshes the cache of the top-left and bottom-right points of the
27332 * drag and drop objects in the specified group(s). This is in the
27333 * format that is stored in the drag and drop instance, so typical
27336 * Ext.dd.DragDropMgr.refreshCache(ddinstance.groups);
27340 * Ext.dd.DragDropMgr.refreshCache({group1:true, group2:true});
27342 * @TODO this really should be an indexed array. Alternatively this
27343 * method could accept both.
27344 * @method refreshCache
27345 * @param {Object} groups an associative array of groups to refresh
27348 refreshCache: function(groups) {
27349 for (var sGroup in groups) {
27350 if ("string" != typeof sGroup) {
27353 for (var i in this.ids[sGroup]) {
27354 var oDD = this.ids[sGroup][i];
27356 if (this.isTypeOfDD(oDD)) {
27357 // if (this.isTypeOfDD(oDD) && oDD.isTarget) {
27358 var loc = this.getLocation(oDD);
27360 this.locationCache[oDD.id] = loc;
27362 delete this.locationCache[oDD.id];
27363 // this will unregister the drag and drop object if
27364 // the element is not in a usable state
27373 * This checks to make sure an element exists and is in the DOM. The
27374 * main purpose is to handle cases where innerHTML is used to remove
27375 * drag and drop objects from the DOM. IE provides an 'unspecified
27376 * error' when trying to access the offsetParent of such an element
27378 * @param {HTMLElement} el the element to check
27379 * @return {boolean} true if the element looks usable
27382 verifyEl: function(el) {
27387 parent = el.offsetParent;
27390 parent = el.offsetParent;
27401 * Returns a Region object containing the drag and drop element's position
27402 * and size, including the padding configured for it
27403 * @method getLocation
27404 * @param {DragDrop} oDD the drag and drop object to get the
27406 * @return {Ext.lib.Region} a Region object representing the total area
27407 * the element occupies, including any padding
27408 * the instance is configured for.
27411 getLocation: function(oDD) {
27412 if (! this.isTypeOfDD(oDD)) {
27416 var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l;
27419 pos= Ext.lib.Dom.getXY(el);
27427 x2 = x1 + el.offsetWidth;
27429 y2 = y1 + el.offsetHeight;
27431 t = y1 - oDD.padding[0];
27432 r = x2 + oDD.padding[1];
27433 b = y2 + oDD.padding[2];
27434 l = x1 - oDD.padding[3];
27436 return new Ext.lib.Region( t, r, b, l );
27440 * Checks the cursor location to see if it over the target
27441 * @method isOverTarget
27442 * @param {Ext.lib.Point} pt The point to evaluate
27443 * @param {DragDrop} oTarget the DragDrop object we are inspecting
27444 * @return {boolean} true if the mouse is over the target
27448 isOverTarget: function(pt, oTarget, intersect) {
27449 // use cache if available
27450 var loc = this.locationCache[oTarget.id];
27451 if (!loc || !this.useCache) {
27452 loc = this.getLocation(oTarget);
27453 this.locationCache[oTarget.id] = loc;
27461 oTarget.cursorIsOver = loc.contains( pt );
27463 // DragDrop is using this as a sanity check for the initial mousedown
27464 // in this case we are done. In POINT mode, if the drag obj has no
27465 // contraints, we are also done. Otherwise we need to evaluate the
27466 // location of the target as related to the actual location of the
27467 // dragged element.
27468 var dc = this.dragCurrent;
27469 if (!dc || !dc.getTargetCoord ||
27470 (!intersect && !dc.constrainX && !dc.constrainY)) {
27471 return oTarget.cursorIsOver;
27474 oTarget.overlap = null;
27476 // Get the current location of the drag element, this is the
27477 // location of the mouse event less the delta that represents
27478 // where the original mousedown happened on the element. We
27479 // need to consider constraints and ticks as well.
27480 var pos = dc.getTargetCoord(pt.x, pt.y);
27482 var el = dc.getDragEl();
27483 var curRegion = new Ext.lib.Region( pos.y,
27484 pos.x + el.offsetWidth,
27485 pos.y + el.offsetHeight,
27488 var overlap = curRegion.intersect(loc);
27491 oTarget.overlap = overlap;
27492 return (intersect) ? true : oTarget.cursorIsOver;
27499 * unload event handler
27500 * @method _onUnload
27504 _onUnload: function(e, me) {
27505 Ext.dd.DragDropMgr.unregAll();
27509 * Cleans up the drag and drop events and objects.
27514 unregAll: function() {
27516 if (this.dragCurrent) {
27518 this.dragCurrent = null;
27521 this._execOnAll("unreg", []);
27523 for (var i in this.elementCache) {
27524 delete this.elementCache[i];
27527 this.elementCache = {};
27532 * A cache of DOM elements
27533 * @property elementCache
27540 * Get the wrapper for the DOM element specified
27541 * @method getElWrapper
27542 * @param {String} id the id of the element to get
27543 * @return {Ext.dd.DDM.ElementWrapper} the wrapped element
27545 * @deprecated This wrapper isn't that useful
27548 getElWrapper: function(id) {
27549 var oWrapper = this.elementCache[id];
27550 if (!oWrapper || !oWrapper.el) {
27551 oWrapper = this.elementCache[id] =
27552 new this.ElementWrapper(Ext.getDom(id));
27558 * Returns the actual DOM element
27559 * @method getElement
27560 * @param {String} id the id of the elment to get
27561 * @return {Object} The element
27562 * @deprecated use Ext.lib.Ext.getDom instead
27565 getElement: function(id) {
27566 return Ext.getDom(id);
27570 * Returns the style property for the DOM element (i.e.,
27571 * document.getElById(id).style)
27573 * @param {String} id the id of the elment to get
27574 * @return {Object} The style property of the element
27575 * @deprecated use Ext.lib.Dom instead
27578 getCss: function(id) {
27579 var el = Ext.getDom(id);
27580 return (el) ? el.style : null;
27584 * Inner class for cached elements
27585 * @class DragDropMgr.ElementWrapper
27590 ElementWrapper: function(el) {
27595 this.el = el || null;
27600 this.id = this.el && el.id;
27602 * A reference to the style property
27605 this.css = this.el && el.style;
27609 * Returns the X position of an html element
27611 * @param el the element for which to get the position
27612 * @return {int} the X coordinate
27614 * @deprecated use Ext.lib.Dom.getX instead
27617 getPosX: function(el) {
27618 return Ext.lib.Dom.getX(el);
27622 * Returns the Y position of an html element
27624 * @param el the element for which to get the position
27625 * @return {int} the Y coordinate
27626 * @deprecated use Ext.lib.Dom.getY instead
27629 getPosY: function(el) {
27630 return Ext.lib.Dom.getY(el);
27634 * Swap two nodes. In IE, we use the native method, for others we
27635 * emulate the IE behavior
27637 * @param n1 the first node to swap
27638 * @param n2 the other node to swap
27641 swapNode: function(n1, n2) {
27645 var p = n2.parentNode;
27646 var s = n2.nextSibling;
27649 p.insertBefore(n1, n2);
27650 } else if (n2 == n1.nextSibling) {
27651 p.insertBefore(n2, n1);
27653 n1.parentNode.replaceChild(n2, n1);
27654 p.insertBefore(n1, s);
27660 * Returns the current scroll position
27661 * @method getScroll
27665 getScroll: function () {
27666 var t, l, dde=document.documentElement, db=document.body;
27667 if (dde && (dde.scrollTop || dde.scrollLeft)) {
27669 l = dde.scrollLeft;
27676 return { top: t, left: l };
27680 * Returns the specified element style property
27682 * @param {HTMLElement} el the element
27683 * @param {string} styleProp the style property
27684 * @return {string} The value of the style property
27685 * @deprecated use Ext.lib.Dom.getStyle
27688 getStyle: function(el, styleProp) {
27689 return Ext.fly(el).getStyle(styleProp);
27693 * Gets the scrollTop
27694 * @method getScrollTop
27695 * @return {int} the document's scrollTop
27698 getScrollTop: function () { return this.getScroll().top; },
27701 * Gets the scrollLeft
27702 * @method getScrollLeft
27703 * @return {int} the document's scrollTop
27706 getScrollLeft: function () { return this.getScroll().left; },
27709 * Sets the x/y position of an element to the location of the
27712 * @param {HTMLElement} moveEl The element to move
27713 * @param {HTMLElement} targetEl The position reference element
27716 moveToEl: function (moveEl, targetEl) {
27717 var aCoord = Ext.lib.Dom.getXY(targetEl);
27718 Ext.lib.Dom.setXY(moveEl, aCoord);
27722 * Numeric array sort function
27723 * @method numericSort
27726 numericSort: function(a, b) { return (a - b); },
27730 * @property _timeoutCount
27737 * Trying to make the load order less important. Without this we get
27738 * an error if this file is loaded before the Event Utility.
27739 * @method _addListeners
27743 _addListeners: function() {
27744 var DDM = Ext.dd.DDM;
27745 if ( Ext.lib.Event && document ) {
27748 if (DDM._timeoutCount > 2000) {
27750 setTimeout(DDM._addListeners, 10);
27751 if (document && document.body) {
27752 DDM._timeoutCount += 1;
27759 * Recursively searches the immediate parent and all child nodes for
27760 * the handle element in order to determine wheter or not it was
27762 * @method handleWasClicked
27763 * @param node the html element to inspect
27766 handleWasClicked: function(node, id) {
27767 if (this.isHandle(id, node.id)) {
27770 // check to see if this is a text node child of the one we want
27771 var p = node.parentNode;
27774 if (this.isHandle(id, p.id)) {
27789 // shorter alias, save a few bytes
27790 Ext.dd.DDM = Ext.dd.DragDropMgr;
27791 Ext.dd.DDM._addListeners();
27797 * A DragDrop implementation where the linked element follows the
27798 * mouse cursor during a drag.
27799 * @extends Ext.dd.DragDrop
27801 * @param {String} id the id of the linked element
27802 * @param {String} sGroup the group of related DragDrop items
27803 * @param {object} config an object containing configurable attributes
27804 * Valid properties for DD:
27807 Ext.dd.DD = function(id, sGroup, config) {
27809 this.init(id, sGroup, config);
27813 Ext.extend(Ext.dd.DD, Ext.dd.DragDrop, {
27816 * When set to true, the utility automatically tries to scroll the browser
27817 * window when a drag and drop element is dragged near the viewport boundary.
27818 * Defaults to true.
27825 * Sets the pointer offset to the distance between the linked element's top
27826 * left corner and the location the element was clicked
27827 * @method autoOffset
27828 * @param {int} iPageX the X coordinate of the click
27829 * @param {int} iPageY the Y coordinate of the click
27831 autoOffset: function(iPageX, iPageY) {
27832 var x = iPageX - this.startPageX;
27833 var y = iPageY - this.startPageY;
27834 this.setDelta(x, y);
27838 * Sets the pointer offset. You can call this directly to force the
27839 * offset to be in a particular location (e.g., pass in 0,0 to set it
27840 * to the center of the object)
27842 * @param {int} iDeltaX the distance from the left
27843 * @param {int} iDeltaY the distance from the top
27845 setDelta: function(iDeltaX, iDeltaY) {
27846 this.deltaX = iDeltaX;
27847 this.deltaY = iDeltaY;
27851 * Sets the drag element to the location of the mousedown or click event,
27852 * maintaining the cursor location relative to the location on the element
27853 * that was clicked. Override this if you want to place the element in a
27854 * location other than where the cursor is.
27855 * @method setDragElPos
27856 * @param {int} iPageX the X coordinate of the mousedown or drag event
27857 * @param {int} iPageY the Y coordinate of the mousedown or drag event
27859 setDragElPos: function(iPageX, iPageY) {
27860 // the first time we do this, we are going to check to make sure
27861 // the element has css positioning
27863 var el = this.getDragEl();
27864 this.alignElWithMouse(el, iPageX, iPageY);
27868 * Sets the element to the location of the mousedown or click event,
27869 * maintaining the cursor location relative to the location on the element
27870 * that was clicked. Override this if you want to place the element in a
27871 * location other than where the cursor is.
27872 * @method alignElWithMouse
27873 * @param {HTMLElement} el the element to move
27874 * @param {int} iPageX the X coordinate of the mousedown or drag event
27875 * @param {int} iPageY the Y coordinate of the mousedown or drag event
27877 alignElWithMouse: function(el, iPageX, iPageY) {
27878 var oCoord = this.getTargetCoord(iPageX, iPageY);
27879 var fly = el.dom ? el : Ext.fly(el, '_dd');
27880 if (!this.deltaSetXY) {
27881 var aCoord = [oCoord.x, oCoord.y];
27883 var newLeft = fly.getLeft(true);
27884 var newTop = fly.getTop(true);
27885 this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
27887 fly.setLeftTop(oCoord.x + this.deltaSetXY[0], oCoord.y + this.deltaSetXY[1]);
27890 this.cachePosition(oCoord.x, oCoord.y);
27891 this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
27896 * Saves the most recent position so that we can reset the constraints and
27897 * tick marks on-demand. We need to know this so that we can calculate the
27898 * number of pixels the element is offset from its original position.
27899 * @method cachePosition
27900 * @param iPageX the current x position (optional, this just makes it so we
27901 * don't have to look it up again)
27902 * @param iPageY the current y position (optional, this just makes it so we
27903 * don't have to look it up again)
27905 cachePosition: function(iPageX, iPageY) {
27907 this.lastPageX = iPageX;
27908 this.lastPageY = iPageY;
27910 var aCoord = Ext.lib.Dom.getXY(this.getEl());
27911 this.lastPageX = aCoord[0];
27912 this.lastPageY = aCoord[1];
27917 * Auto-scroll the window if the dragged object has been moved beyond the
27918 * visible window boundary.
27919 * @method autoScroll
27920 * @param {int} x the drag element's x position
27921 * @param {int} y the drag element's y position
27922 * @param {int} h the height of the drag element
27923 * @param {int} w the width of the drag element
27926 autoScroll: function(x, y, h, w) {
27929 // The client height
27930 var clientH = Ext.lib.Dom.getViewHeight();
27932 // The client width
27933 var clientW = Ext.lib.Dom.getViewWidth();
27935 // The amt scrolled down
27936 var st = this.DDM.getScrollTop();
27938 // The amt scrolled right
27939 var sl = this.DDM.getScrollLeft();
27941 // Location of the bottom of the element
27944 // Location of the right of the element
27947 // The distance from the cursor to the bottom of the visible area,
27948 // adjusted so that we don't scroll if the cursor is beyond the
27949 // element drag constraints
27950 var toBot = (clientH + st - y - this.deltaY);
27952 // The distance from the cursor to the right of the visible area
27953 var toRight = (clientW + sl - x - this.deltaX);
27956 // How close to the edge the cursor must be before we scroll
27957 // var thresh = (document.all) ? 100 : 40;
27960 // How many pixels to scroll per autoscroll op. This helps to reduce
27961 // clunky scrolling. IE is more sensitive about this ... it needs this
27962 // value to be higher.
27963 var scrAmt = (document.all) ? 80 : 30;
27965 // Scroll down if we are near the bottom of the visible page and the
27966 // obj extends below the crease
27967 if ( bot > clientH && toBot < thresh ) {
27968 window.scrollTo(sl, st + scrAmt);
27971 // Scroll up if the window is scrolled down and the top of the object
27972 // goes above the top border
27973 if ( y < st && st > 0 && y - st < thresh ) {
27974 window.scrollTo(sl, st - scrAmt);
27977 // Scroll right if the obj is beyond the right border and the cursor is
27978 // near the border.
27979 if ( right > clientW && toRight < thresh ) {
27980 window.scrollTo(sl + scrAmt, st);
27983 // Scroll left if the window has been scrolled to the right and the obj
27984 // extends past the left border
27985 if ( x < sl && sl > 0 && x - sl < thresh ) {
27986 window.scrollTo(sl - scrAmt, st);
27992 * Finds the location the element should be placed if we want to move
27993 * it to where the mouse location less the click offset would place us.
27994 * @method getTargetCoord
27995 * @param {int} iPageX the X coordinate of the click
27996 * @param {int} iPageY the Y coordinate of the click
27997 * @return an object that contains the coordinates (Object.x and Object.y)
28000 getTargetCoord: function(iPageX, iPageY) {
28003 var x = iPageX - this.deltaX;
28004 var y = iPageY - this.deltaY;
28006 if (this.constrainX) {
28007 if (x < this.minX) { x = this.minX; }
28008 if (x > this.maxX) { x = this.maxX; }
28011 if (this.constrainY) {
28012 if (y < this.minY) { y = this.minY; }
28013 if (y > this.maxY) { y = this.maxY; }
28016 x = this.getTick(x, this.xTicks);
28017 y = this.getTick(y, this.yTicks);
28024 * Sets up config options specific to this class. Overrides
28025 * Ext.dd.DragDrop, but all versions of this method through the
28026 * inheritance chain are called
28028 applyConfig: function() {
28029 Ext.dd.DD.superclass.applyConfig.call(this);
28030 this.scroll = (this.config.scroll !== false);
28034 * Event that fires prior to the onMouseDown event. Overrides
28037 b4MouseDown: function(e) {
28038 // this.resetConstraints();
28039 this.autoOffset(e.getPageX(),
28044 * Event that fires prior to the onDrag event. Overrides
28047 b4Drag: function(e) {
28048 this.setDragElPos(e.getPageX(),
28052 toString: function() {
28053 return ("DD " + this.id);
28056 //////////////////////////////////////////////////////////////////////////
28057 // Debugging ygDragDrop events that can be overridden
28058 //////////////////////////////////////////////////////////////////////////
28060 startDrag: function(x, y) {
28063 onDrag: function(e) {
28066 onDragEnter: function(e, id) {
28069 onDragOver: function(e, id) {
28072 onDragOut: function(e, id) {
28075 onDragDrop: function(e, id) {
28078 endDrag: function(e) {
28085 * @class Ext.dd.DDProxy
28086 * A DragDrop implementation that inserts an empty, bordered div into
28087 * the document that follows the cursor during drag operations. At the time of
28088 * the click, the frame div is resized to the dimensions of the linked html
28089 * element, and moved to the exact location of the linked element.
28091 * References to the "frame" element refer to the single proxy element that
28092 * was created to be dragged in place of all DDProxy elements on the
28095 * @extends Ext.dd.DD
28097 * @param {String} id the id of the linked html element
28098 * @param {String} sGroup the group of related DragDrop objects
28099 * @param {object} config an object containing configurable attributes
28100 * Valid properties for DDProxy in addition to those in DragDrop:
28101 * resizeFrame, centerFrame, dragElId
28103 Ext.dd.DDProxy = function(id, sGroup, config) {
28105 this.init(id, sGroup, config);
28111 * The default drag frame div id
28112 * @property Ext.dd.DDProxy.dragElId
28116 Ext.dd.DDProxy.dragElId = "ygddfdiv";
28118 Ext.extend(Ext.dd.DDProxy, Ext.dd.DD, {
28121 * By default we resize the drag frame to be the same size as the element
28122 * we want to drag (this is to get the frame effect). We can turn it off
28123 * if we want a different behavior.
28124 * @property resizeFrame
28130 * By default the frame is positioned exactly where the drag element is, so
28131 * we use the cursor offset provided by Ext.dd.DD. Another option that works only if
28132 * you do not have constraints on the obj is to have the drag frame centered
28133 * around the cursor. Set centerFrame to true for this effect.
28134 * @property centerFrame
28137 centerFrame: false,
28140 * Creates the proxy element if it does not yet exist
28141 * @method createFrame
28143 createFrame: function() {
28145 var body = document.body;
28147 if (!body || !body.firstChild) {
28148 setTimeout( function() { self.createFrame(); }, 50 );
28152 var div = this.getDragEl();
28155 div = document.createElement("div");
28156 div.id = this.dragElId;
28159 s.position = "absolute";
28160 s.visibility = "hidden";
28162 s.border = "2px solid #aaa";
28165 // appendChild can blow up IE if invoked prior to the window load event
28166 // while rendering a table. It is possible there are other scenarios
28167 // that would cause this to happen as well.
28168 body.insertBefore(div, body.firstChild);
28173 * Initialization for the drag frame element. Must be called in the
28174 * constructor of all subclasses
28175 * @method initFrame
28177 initFrame: function() {
28178 this.createFrame();
28181 applyConfig: function() {
28182 Ext.dd.DDProxy.superclass.applyConfig.call(this);
28184 this.resizeFrame = (this.config.resizeFrame !== false);
28185 this.centerFrame = (this.config.centerFrame);
28186 this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
28190 * Resizes the drag frame to the dimensions of the clicked object, positions
28191 * it over the object, and finally displays it
28192 * @method showFrame
28193 * @param {int} iPageX X click position
28194 * @param {int} iPageY Y click position
28197 showFrame: function(iPageX, iPageY) {
28198 var el = this.getEl();
28199 var dragEl = this.getDragEl();
28200 var s = dragEl.style;
28202 this._resizeProxy();
28204 if (this.centerFrame) {
28205 this.setDelta( Math.round(parseInt(s.width, 10)/2),
28206 Math.round(parseInt(s.height, 10)/2) );
28209 this.setDragElPos(iPageX, iPageY);
28211 Ext.fly(dragEl).show();
28215 * The proxy is automatically resized to the dimensions of the linked
28216 * element when a drag is initiated, unless resizeFrame is set to false
28217 * @method _resizeProxy
28220 _resizeProxy: function() {
28221 if (this.resizeFrame) {
28222 var el = this.getEl();
28223 Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
28227 // overrides Ext.dd.DragDrop
28228 b4MouseDown: function(e) {
28229 var x = e.getPageX();
28230 var y = e.getPageY();
28231 this.autoOffset(x, y);
28232 this.setDragElPos(x, y);
28235 // overrides Ext.dd.DragDrop
28236 b4StartDrag: function(x, y) {
28237 // show the drag frame
28238 this.showFrame(x, y);
28241 // overrides Ext.dd.DragDrop
28242 b4EndDrag: function(e) {
28243 Ext.fly(this.getDragEl()).hide();
28246 // overrides Ext.dd.DragDrop
28247 // By default we try to move the element to the last location of the frame.
28248 // This is so that the default behavior mirrors that of Ext.dd.DD.
28249 endDrag: function(e) {
28251 var lel = this.getEl();
28252 var del = this.getDragEl();
28254 // Show the drag frame briefly so we can get its position
28255 del.style.visibility = "";
28258 // Hide the linked element before the move to get around a Safari
28260 lel.style.visibility = "hidden";
28261 Ext.dd.DDM.moveToEl(lel, del);
28262 del.style.visibility = "hidden";
28263 lel.style.visibility = "";
28268 beforeMove : function(){
28272 afterDrag : function(){
28276 toString: function() {
28277 return ("DDProxy " + this.id);
28282 * @class Ext.dd.DDTarget
28283 * A DragDrop implementation that does not move, but can be a drop
28284 * target. You would get the same result by simply omitting implementation
28285 * for the event callbacks, but this way we reduce the processing cost of the
28286 * event listener and the callbacks.
28287 * @extends Ext.dd.DragDrop
28289 * @param {String} id the id of the element that is a drop target
28290 * @param {String} sGroup the group of related DragDrop objects
28291 * @param {object} config an object containing configurable attributes
28292 * Valid properties for DDTarget in addition to those in
28296 Ext.dd.DDTarget = function(id, sGroup, config) {
28298 this.initTarget(id, sGroup, config);
28302 // Ext.dd.DDTarget.prototype = new Ext.dd.DragDrop();
28303 Ext.extend(Ext.dd.DDTarget, Ext.dd.DragDrop, {
28304 toString: function() {
28305 return ("DDTarget " + this.id);
28309 * @class Ext.dd.DragTracker
\r
28310 * @extends Ext.util.Observable
\r
28312 Ext.dd.DragTracker = function(config){
\r
28313 Ext.apply(this, config);
\r
28316 * @event mousedown
\r
28317 * @param {Object} this
\r
28318 * @param {Object} e event object
\r
28323 * @param {Object} this
\r
28324 * @param {Object} e event object
\r
28328 * @event mousemove
\r
28329 * @param {Object} this
\r
28330 * @param {Object} e event object
\r
28334 * @event dragstart
\r
28335 * @param {Object} this
\r
28336 * @param {Object} startXY the page coordinates of the event
\r
28341 * @param {Object} this
\r
28342 * @param {Object} e event object
\r
28347 * @param {Object} this
\r
28348 * @param {Object} e event object
\r
28353 this.dragRegion = new Ext.lib.Region(0,0,0,0);
\r
28356 this.initEl(this.el);
\r
28360 Ext.extend(Ext.dd.DragTracker, Ext.util.Observable, {
\r
28362 * @cfg {Boolean} active
\r
28363 * Defaults to <tt>false</tt>.
\r
28367 * @cfg {Number} tolerance
\r
28368 * Defaults to <tt>5</tt>.
\r
28372 * @cfg {Boolean/Number} autoStart
\r
28373 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to defer trigger start by 1000 ms.
\r
28374 * Specify a Number for the number of milliseconds to defer trigger start.
\r
28376 autoStart: false,
\r
28378 initEl: function(el){
\r
28379 this.el = Ext.get(el);
\r
28380 el.on('mousedown', this.onMouseDown, this,
\r
28381 this.delegate ? {delegate: this.delegate} : undefined);
\r
28384 destroy : function(){
\r
28385 this.el.un('mousedown', this.onMouseDown, this);
\r
28388 onMouseDown: function(e, target){
\r
28389 if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
\r
28390 this.startXY = this.lastXY = e.getXY();
\r
28391 this.dragTarget = this.delegate ? target : this.el.dom;
\r
28392 if(this.preventDefault !== false){
\r
28393 e.preventDefault();
\r
28395 var doc = Ext.getDoc();
\r
28396 doc.on('mouseup', this.onMouseUp, this);
\r
28397 doc.on('mousemove', this.onMouseMove, this);
\r
28398 doc.on('selectstart', this.stopSelect, this);
\r
28399 if(this.autoStart){
\r
28400 this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this);
\r
28405 onMouseMove: function(e, target){
\r
28406 // HACK: IE hack to see if button was released outside of window. */
\r
28407 if(this.active && Ext.isIE && !e.browserEvent.button){
\r
28408 e.preventDefault();
\r
28409 this.onMouseUp(e);
\r
28413 e.preventDefault();
\r
28414 var xy = e.getXY(), s = this.startXY;
\r
28415 this.lastXY = xy;
\r
28416 if(!this.active){
\r
28417 if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
\r
28418 this.triggerStart();
\r
28423 this.fireEvent('mousemove', this, e);
\r
28425 this.fireEvent('drag', this, e);
\r
28428 onMouseUp: function(e){
\r
28429 var doc = Ext.getDoc();
\r
28430 doc.un('mousemove', this.onMouseMove, this);
\r
28431 doc.un('mouseup', this.onMouseUp, this);
\r
28432 doc.un('selectstart', this.stopSelect, this);
\r
28433 e.preventDefault();
\r
28434 this.clearStart();
\r
28435 var wasActive = this.active;
\r
28436 this.active = false;
\r
28437 delete this.elRegion;
\r
28438 this.fireEvent('mouseup', this, e);
\r
28441 this.fireEvent('dragend', this, e);
\r
28445 triggerStart: function(isTimer){
\r
28446 this.clearStart();
\r
28447 this.active = true;
\r
28448 this.onStart(this.startXY);
\r
28449 this.fireEvent('dragstart', this, this.startXY);
\r
28452 clearStart : function(){
\r
28454 clearTimeout(this.timer);
\r
28455 delete this.timer;
\r
28459 stopSelect : function(e){
\r
28464 onBeforeStart : function(e){
\r
28468 onStart : function(xy){
\r
28472 onDrag : function(e){
\r
28476 onEnd : function(e){
\r
28480 getDragTarget : function(){
\r
28481 return this.dragTarget;
\r
28484 getDragCt : function(){
\r
28488 getXY : function(constrain){
\r
28489 return constrain ?
\r
28490 this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
\r
28493 getOffset : function(constrain){
\r
28494 var xy = this.getXY(constrain);
\r
28495 var s = this.startXY;
\r
28496 return [s[0]-xy[0], s[1]-xy[1]];
\r
28499 constrainModes: {
\r
28500 'point' : function(xy){
\r
28502 if(!this.elRegion){
\r
28503 this.elRegion = this.getDragCt().getRegion();
\r
28506 var dr = this.dragRegion;
\r
28510 dr.right = xy[0];
\r
28511 dr.bottom = xy[1];
\r
28513 dr.constrainTo(this.elRegion);
\r
28515 return [dr.left, dr.top];
\r
28519 * @class Ext.dd.ScrollManager
\r
28520 * <p>Provides automatic scrolling of overflow regions in the page during drag operations.</p>
\r
28521 * <p>The ScrollManager configs will be used as the defaults for any scroll container registered with it,
\r
28522 * but you can also override most of the configs per scroll container by adding a
\r
28523 * <tt>ddScrollConfig</tt> object to the target element that contains these properties: {@link #hthresh},
\r
28524 * {@link #vthresh}, {@link #increment} and {@link #frequency}. Example usage:
\r
28526 var el = Ext.get('scroll-ct');
\r
28527 el.ddScrollConfig = {
\r
28533 Ext.dd.ScrollManager.register(el);
\r
28535 * <b>Note: This class uses "Point Mode" and is untested in "Intersect Mode".</b>
\r
28538 Ext.dd.ScrollManager = function(){
\r
28539 var ddm = Ext.dd.DragDropMgr;
\r
28541 var dragEl = null;
\r
28544 var onStop = function(e){
\r
28549 var triggerRefresh = function(){
\r
28550 if(ddm.dragCurrent){
\r
28551 ddm.refreshCache(ddm.dragCurrent.groups);
\r
28555 var doScroll = function(){
\r
28556 if(ddm.dragCurrent){
\r
28557 var dds = Ext.dd.ScrollManager;
\r
28558 var inc = proc.el.ddScrollConfig ?
\r
28559 proc.el.ddScrollConfig.increment : dds.increment;
\r
28560 if(!dds.animate){
\r
28561 if(proc.el.scroll(proc.dir, inc)){
\r
28562 triggerRefresh();
\r
28565 proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
\r
28570 var clearProc = function(){
\r
28572 clearInterval(proc.id);
\r
28579 var startProc = function(el, dir){
\r
28583 var freq = (el.ddScrollConfig && el.ddScrollConfig.frequency) ?
\r
28584 el.ddScrollConfig.frequency : Ext.dd.ScrollManager.frequency;
\r
28585 proc.id = setInterval(doScroll, freq);
\r
28588 var onFire = function(e, isDrop){
\r
28589 if(isDrop || !ddm.dragCurrent){ return; }
\r
28590 var dds = Ext.dd.ScrollManager;
\r
28591 if(!dragEl || dragEl != ddm.dragCurrent){
\r
28592 dragEl = ddm.dragCurrent;
\r
28593 // refresh regions on drag start
\r
28594 dds.refreshCache();
\r
28597 var xy = Ext.lib.Event.getXY(e);
\r
28598 var pt = new Ext.lib.Point(xy[0], xy[1]);
\r
28599 for(var id in els){
\r
28600 var el = els[id], r = el._region;
\r
28601 var c = el.ddScrollConfig ? el.ddScrollConfig : dds;
\r
28602 if(r && r.contains(pt) && el.isScrollable()){
\r
28603 if(r.bottom - pt.y <= c.vthresh){
\r
28604 if(proc.el != el){
\r
28605 startProc(el, "down");
\r
28608 }else if(r.right - pt.x <= c.hthresh){
\r
28609 if(proc.el != el){
\r
28610 startProc(el, "left");
\r
28613 }else if(pt.y - r.top <= c.vthresh){
\r
28614 if(proc.el != el){
\r
28615 startProc(el, "up");
\r
28618 }else if(pt.x - r.left <= c.hthresh){
\r
28619 if(proc.el != el){
\r
28620 startProc(el, "right");
\r
28629 ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
\r
28630 ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
\r
28634 * Registers new overflow element(s) to auto scroll
\r
28635 * @param {Mixed/Array} el The id of or the element to be scrolled or an array of either
\r
28637 register : function(el){
\r
28638 if(Ext.isArray(el)){
\r
28639 for(var i = 0, len = el.length; i < len; i++) {
\r
28640 this.register(el[i]);
\r
28643 el = Ext.get(el);
\r
28649 * Unregisters overflow element(s) so they are no longer scrolled
\r
28650 * @param {Mixed/Array} el The id of or the element to be removed or an array of either
\r
28652 unregister : function(el){
\r
28653 if(Ext.isArray(el)){
\r
28654 for(var i = 0, len = el.length; i < len; i++) {
\r
28655 this.unregister(el[i]);
\r
28658 el = Ext.get(el);
\r
28659 delete els[el.id];
\r
28664 * The number of pixels from the top or bottom edge of a container the pointer needs to be to
\r
28665 * trigger scrolling (defaults to 25)
\r
28670 * The number of pixels from the right or left edge of a container the pointer needs to be to
\r
28671 * trigger scrolling (defaults to 25)
\r
28677 * The number of pixels to scroll in each scroll increment (defaults to 50)
\r
28683 * The frequency of scrolls in milliseconds (defaults to 500)
\r
28689 * True to animate the scroll (defaults to true)
\r
28695 * The animation duration in seconds -
\r
28696 * MUST BE less than Ext.dd.ScrollManager.frequency! (defaults to .4)
\r
28699 animDuration: .4,
\r
28702 * Manually trigger a cache refresh.
\r
28704 refreshCache : function(){
\r
28705 for(var id in els){
\r
28706 if(typeof els[id] == 'object'){ // for people extending the object prototype
\r
28707 els[id]._region = els[id].getRegion();
\r
28713 * @class Ext.dd.Registry
\r
28714 * Provides easy access to all drag drop components that are registered on a page. Items can be retrieved either
\r
28715 * directly by DOM node id, or by passing in the drag drop event that occurred and looking up the event target.
\r
28718 Ext.dd.Registry = function(){
\r
28719 var elements = {};
\r
28720 var handles = {};
\r
28721 var autoIdSeed = 0;
\r
28723 var getId = function(el, autogen){
\r
28724 if(typeof el == "string"){
\r
28728 if(!id && autogen !== false){
\r
28729 id = "extdd-" + (++autoIdSeed);
\r
28737 * Resgister a drag drop element
\r
28738 * @param {String/HTMLElement) element The id or DOM node to register
\r
28739 * @param {Object} data (optional) An custom data object that will be passed between the elements that are involved
\r
28740 * in drag drop operations. You can populate this object with any arbitrary properties that your own code
\r
28741 * knows how to interpret, plus there are some specific properties known to the Registry that should be
\r
28742 * populated in the data object (if applicable):
\r
28744 Value Description<br />
\r
28745 --------- ------------------------------------------<br />
\r
28746 handles Array of DOM nodes that trigger dragging<br />
\r
28747 for the element being registered<br />
\r
28748 isHandle True if the element passed in triggers<br />
\r
28749 dragging itself, else false
\r
28752 register : function(el, data){
\r
28753 data = data || {};
\r
28754 if(typeof el == "string"){
\r
28755 el = document.getElementById(el);
\r
28758 elements[getId(el)] = data;
\r
28759 if(data.isHandle !== false){
\r
28760 handles[data.ddel.id] = data;
\r
28762 if(data.handles){
\r
28763 var hs = data.handles;
\r
28764 for(var i = 0, len = hs.length; i < len; i++){
\r
28765 handles[getId(hs[i])] = data;
\r
28771 * Unregister a drag drop element
\r
28772 * @param {String/HTMLElement) element The id or DOM node to unregister
\r
28774 unregister : function(el){
\r
28775 var id = getId(el, false);
\r
28776 var data = elements[id];
\r
28778 delete elements[id];
\r
28779 if(data.handles){
\r
28780 var hs = data.handles;
\r
28781 for(var i = 0, len = hs.length; i < len; i++){
\r
28782 delete handles[getId(hs[i], false)];
\r
28789 * Returns the handle registered for a DOM Node by id
\r
28790 * @param {String/HTMLElement} id The DOM node or id to look up
\r
28791 * @return {Object} handle The custom handle data
\r
28793 getHandle : function(id){
\r
28794 if(typeof id != "string"){ // must be element?
\r
28797 return handles[id];
\r
28801 * Returns the handle that is registered for the DOM node that is the target of the event
\r
28802 * @param {Event} e The event
\r
28803 * @return {Object} handle The custom handle data
\r
28805 getHandleFromEvent : function(e){
\r
28806 var t = Ext.lib.Event.getTarget(e);
\r
28807 return t ? handles[t.id] : null;
\r
28811 * Returns a custom data object that is registered for a DOM node by id
\r
28812 * @param {String/HTMLElement} id The DOM node or id to look up
\r
28813 * @return {Object} data The custom data
\r
28815 getTarget : function(id){
\r
28816 if(typeof id != "string"){ // must be element?
\r
28819 return elements[id];
\r
28823 * Returns a custom data object that is registered for the DOM node that is the target of the event
\r
28824 * @param {Event} e The event
\r
28825 * @return {Object} data The custom data
\r
28827 getTargetFromEvent : function(e){
\r
28828 var t = Ext.lib.Event.getTarget(e);
\r
28829 return t ? elements[t.id] || handles[t.id] : null;
\r
28833 * @class Ext.dd.StatusProxy
\r
28834 * A specialized drag proxy that supports a drop status icon, {@link Ext.Layer} styles and auto-repair. This is the
\r
28835 * default drag proxy used by all Ext.dd components.
\r
28837 * @param {Object} config
\r
28839 Ext.dd.StatusProxy = function(config){
\r
28840 Ext.apply(this, config);
\r
28841 this.id = this.id || Ext.id();
\r
28842 this.el = new Ext.Layer({
\r
28844 id: this.id, tag: "div", cls: "x-dd-drag-proxy "+this.dropNotAllowed, children: [
\r
28845 {tag: "div", cls: "x-dd-drop-icon"},
\r
28846 {tag: "div", cls: "x-dd-drag-ghost"}
\r
28849 shadow: !config || config.shadow !== false
\r
28851 this.ghost = Ext.get(this.el.dom.childNodes[1]);
\r
28852 this.dropStatus = this.dropNotAllowed;
\r
28855 Ext.dd.StatusProxy.prototype = {
\r
28857 * @cfg {String} dropAllowed
\r
28858 * The CSS class to apply to the status element when drop is allowed (defaults to "x-dd-drop-ok").
\r
28860 dropAllowed : "x-dd-drop-ok",
\r
28862 * @cfg {String} dropNotAllowed
\r
28863 * The CSS class to apply to the status element when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
28865 dropNotAllowed : "x-dd-drop-nodrop",
\r
28868 * Updates the proxy's visual element to indicate the status of whether or not drop is allowed
\r
28869 * over the current target element.
\r
28870 * @param {String} cssClass The css class for the new drop status indicator image
\r
28872 setStatus : function(cssClass){
\r
28873 cssClass = cssClass || this.dropNotAllowed;
\r
28874 if(this.dropStatus != cssClass){
\r
28875 this.el.replaceClass(this.dropStatus, cssClass);
\r
28876 this.dropStatus = cssClass;
\r
28881 * Resets the status indicator to the default dropNotAllowed value
\r
28882 * @param {Boolean} clearGhost True to also remove all content from the ghost, false to preserve it
\r
28884 reset : function(clearGhost){
\r
28885 this.el.dom.className = "x-dd-drag-proxy " + this.dropNotAllowed;
\r
28886 this.dropStatus = this.dropNotAllowed;
\r
28888 this.ghost.update("");
\r
28893 * Updates the contents of the ghost element
\r
28894 * @param {String/HTMLElement} html The html that will replace the current innerHTML of the ghost element, or a
\r
28895 * DOM node to append as the child of the ghost element (in which case the innerHTML will be cleared first).
\r
28897 update : function(html){
\r
28898 if(typeof html == "string"){
\r
28899 this.ghost.update(html);
\r
28901 this.ghost.update("");
\r
28902 html.style.margin = "0";
\r
28903 this.ghost.dom.appendChild(html);
\r
28905 var el = this.ghost.dom.firstChild;
\r
28907 Ext.fly(el).setStyle('float', 'none');
\r
28912 * Returns the underlying proxy {@link Ext.Layer}
\r
28913 * @return {Ext.Layer} el
\r
28915 getEl : function(){
\r
28920 * Returns the ghost element
\r
28921 * @return {Ext.Element} el
\r
28923 getGhost : function(){
\r
28924 return this.ghost;
\r
28928 * Hides the proxy
\r
28929 * @param {Boolean} clear True to reset the status and clear the ghost contents, false to preserve them
\r
28931 hide : function(clear){
\r
28934 this.reset(true);
\r
28939 * Stops the repair animation if it's currently running
\r
28941 stop : function(){
\r
28942 if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
\r
28943 this.anim.stop();
\r
28948 * Displays this proxy
\r
28950 show : function(){
\r
28955 * Force the Layer to sync its shadow and shim positions to the element
\r
28957 sync : function(){
\r
28962 * Causes the proxy to return to its position of origin via an animation. Should be called after an
\r
28963 * invalid drop operation by the item being dragged.
\r
28964 * @param {Array} xy The XY position of the element ([x, y])
\r
28965 * @param {Function} callback The function to call after the repair is complete
\r
28966 * @param {Object} scope The scope in which to execute the callback
\r
28968 repair : function(xy, callback, scope){
\r
28969 this.callback = callback;
\r
28970 this.scope = scope;
\r
28971 if(xy && this.animRepair !== false){
\r
28972 this.el.addClass("x-dd-drag-repair");
\r
28973 this.el.hideUnders(true);
\r
28974 this.anim = this.el.shift({
\r
28975 duration: this.repairDuration || .5,
\r
28976 easing: 'easeOut',
\r
28979 callback: this.afterRepair,
\r
28983 this.afterRepair();
\r
28988 afterRepair : function(){
\r
28990 if(typeof this.callback == "function"){
\r
28991 this.callback.call(this.scope || this);
\r
28993 this.callback = null;
\r
28994 this.scope = null;
\r
28997 * @class Ext.dd.DragSource
\r
28998 * @extends Ext.dd.DDProxy
\r
28999 * A simple class that provides the basic implementation needed to make any element draggable.
\r
29001 * @param {Mixed} el The container element
\r
29002 * @param {Object} config
\r
29004 Ext.dd.DragSource = function(el, config){
\r
29005 this.el = Ext.get(el);
\r
29006 if(!this.dragData){
\r
29007 this.dragData = {};
\r
29010 Ext.apply(this, config);
\r
29013 this.proxy = new Ext.dd.StatusProxy();
\r
29015 Ext.dd.DragSource.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
\r
29016 {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true});
\r
29018 this.dragging = false;
\r
29021 Ext.extend(Ext.dd.DragSource, Ext.dd.DDProxy, {
\r
29023 * @cfg {String} ddGroup
\r
29024 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
29025 * interact with other drag drop objects in the same group (defaults to undefined).
\r
29028 * @cfg {String} dropAllowed
\r
29029 * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
\r
29031 dropAllowed : "x-dd-drop-ok",
\r
29033 * @cfg {String} dropNotAllowed
\r
29034 * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
29036 dropNotAllowed : "x-dd-drop-nodrop",
\r
29039 * Returns the data object associated with this drag source
\r
29040 * @return {Object} data An object containing arbitrary data
\r
29042 getDragData : function(e){
\r
29043 return this.dragData;
\r
29047 onDragEnter : function(e, id){
\r
29048 var target = Ext.dd.DragDropMgr.getDDById(id);
\r
29049 this.cachedTarget = target;
\r
29050 if(this.beforeDragEnter(target, e, id) !== false){
\r
29051 if(target.isNotifyTarget){
\r
29052 var status = target.notifyEnter(this, e, this.dragData);
\r
29053 this.proxy.setStatus(status);
\r
29055 this.proxy.setStatus(this.dropAllowed);
\r
29058 if(this.afterDragEnter){
\r
29060 * An empty function by default, but provided so that you can perform a custom action
\r
29061 * when the dragged item enters the drop target by providing an implementation.
\r
29062 * @param {Ext.dd.DragDrop} target The drop target
\r
29063 * @param {Event} e The event object
\r
29064 * @param {String} id The id of the dragged element
\r
29065 * @method afterDragEnter
\r
29067 this.afterDragEnter(target, e, id);
\r
29073 * An empty function by default, but provided so that you can perform a custom action
\r
29074 * before the dragged item enters the drop target and optionally cancel the onDragEnter.
\r
29075 * @param {Ext.dd.DragDrop} target The drop target
\r
29076 * @param {Event} e The event object
\r
29077 * @param {String} id The id of the dragged element
\r
29078 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
29080 beforeDragEnter : function(target, e, id){
\r
29085 alignElWithMouse: function() {
\r
29086 Ext.dd.DragSource.superclass.alignElWithMouse.apply(this, arguments);
\r
29087 this.proxy.sync();
\r
29091 onDragOver : function(e, id){
\r
29092 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
29093 if(this.beforeDragOver(target, e, id) !== false){
\r
29094 if(target.isNotifyTarget){
\r
29095 var status = target.notifyOver(this, e, this.dragData);
\r
29096 this.proxy.setStatus(status);
\r
29099 if(this.afterDragOver){
\r
29101 * An empty function by default, but provided so that you can perform a custom action
\r
29102 * while the dragged item is over the drop target by providing an implementation.
\r
29103 * @param {Ext.dd.DragDrop} target The drop target
\r
29104 * @param {Event} e The event object
\r
29105 * @param {String} id The id of the dragged element
\r
29106 * @method afterDragOver
\r
29108 this.afterDragOver(target, e, id);
\r
29114 * An empty function by default, but provided so that you can perform a custom action
\r
29115 * while the dragged item is over the drop target and optionally cancel the onDragOver.
\r
29116 * @param {Ext.dd.DragDrop} target The drop target
\r
29117 * @param {Event} e The event object
\r
29118 * @param {String} id The id of the dragged element
\r
29119 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
29121 beforeDragOver : function(target, e, id){
\r
29126 onDragOut : function(e, id){
\r
29127 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
29128 if(this.beforeDragOut(target, e, id) !== false){
\r
29129 if(target.isNotifyTarget){
\r
29130 target.notifyOut(this, e, this.dragData);
\r
29132 this.proxy.reset();
\r
29133 if(this.afterDragOut){
\r
29135 * An empty function by default, but provided so that you can perform a custom action
\r
29136 * after the dragged item is dragged out of the target without dropping.
\r
29137 * @param {Ext.dd.DragDrop} target The drop target
\r
29138 * @param {Event} e The event object
\r
29139 * @param {String} id The id of the dragged element
\r
29140 * @method afterDragOut
\r
29142 this.afterDragOut(target, e, id);
\r
29145 this.cachedTarget = null;
\r
29149 * An empty function by default, but provided so that you can perform a custom action before the dragged
\r
29150 * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
\r
29151 * @param {Ext.dd.DragDrop} target The drop target
\r
29152 * @param {Event} e The event object
\r
29153 * @param {String} id The id of the dragged element
\r
29154 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
29156 beforeDragOut : function(target, e, id){
\r
29161 onDragDrop : function(e, id){
\r
29162 var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
\r
29163 if(this.beforeDragDrop(target, e, id) !== false){
\r
29164 if(target.isNotifyTarget){
\r
29165 if(target.notifyDrop(this, e, this.dragData)){ // valid drop?
\r
29166 this.onValidDrop(target, e, id);
\r
29168 this.onInvalidDrop(target, e, id);
\r
29171 this.onValidDrop(target, e, id);
\r
29174 if(this.afterDragDrop){
\r
29176 * An empty function by default, but provided so that you can perform a custom action
\r
29177 * after a valid drag drop has occurred by providing an implementation.
\r
29178 * @param {Ext.dd.DragDrop} target The drop target
\r
29179 * @param {Event} e The event object
\r
29180 * @param {String} id The id of the dropped element
\r
29181 * @method afterDragDrop
\r
29183 this.afterDragDrop(target, e, id);
\r
29186 delete this.cachedTarget;
\r
29190 * An empty function by default, but provided so that you can perform a custom action before the dragged
\r
29191 * item is dropped onto the target and optionally cancel the onDragDrop.
\r
29192 * @param {Ext.dd.DragDrop} target The drop target
\r
29193 * @param {Event} e The event object
\r
29194 * @param {String} id The id of the dragged element
\r
29195 * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
\r
29197 beforeDragDrop : function(target, e, id){
\r
29202 onValidDrop : function(target, e, id){
\r
29203 this.hideProxy();
\r
29204 if(this.afterValidDrop){
\r
29206 * An empty function by default, but provided so that you can perform a custom action
\r
29207 * after a valid drop has occurred by providing an implementation.
\r
29208 * @param {Object} target The target DD
\r
29209 * @param {Event} e The event object
\r
29210 * @param {String} id The id of the dropped element
\r
29211 * @method afterInvalidDrop
\r
29213 this.afterValidDrop(target, e, id);
\r
29218 getRepairXY : function(e, data){
\r
29219 return this.el.getXY();
\r
29223 onInvalidDrop : function(target, e, id){
\r
29224 this.beforeInvalidDrop(target, e, id);
\r
29225 if(this.cachedTarget){
\r
29226 if(this.cachedTarget.isNotifyTarget){
\r
29227 this.cachedTarget.notifyOut(this, e, this.dragData);
\r
29229 this.cacheTarget = null;
\r
29231 this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
\r
29233 if(this.afterInvalidDrop){
\r
29235 * An empty function by default, but provided so that you can perform a custom action
\r
29236 * after an invalid drop has occurred by providing an implementation.
\r
29237 * @param {Event} e The event object
\r
29238 * @param {String} id The id of the dropped element
\r
29239 * @method afterInvalidDrop
\r
29241 this.afterInvalidDrop(e, id);
\r
29246 afterRepair : function(){
\r
29247 if(Ext.enableFx){
\r
29248 this.el.highlight(this.hlColor || "c3daf9");
\r
29250 this.dragging = false;
\r
29254 * An empty function by default, but provided so that you can perform a custom action after an invalid
\r
29255 * drop has occurred.
\r
29256 * @param {Ext.dd.DragDrop} target The drop target
\r
29257 * @param {Event} e The event object
\r
29258 * @param {String} id The id of the dragged element
\r
29259 * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
\r
29261 beforeInvalidDrop : function(target, e, id){
\r
29266 handleMouseDown : function(e){
\r
29267 if(this.dragging) {
\r
29270 var data = this.getDragData(e);
\r
29271 if(data && this.onBeforeDrag(data, e) !== false){
\r
29272 this.dragData = data;
\r
29273 this.proxy.stop();
\r
29274 Ext.dd.DragSource.superclass.handleMouseDown.apply(this, arguments);
\r
29279 * An empty function by default, but provided so that you can perform a custom action before the initial
\r
29280 * drag event begins and optionally cancel it.
\r
29281 * @param {Object} data An object containing arbitrary data to be shared with drop targets
\r
29282 * @param {Event} e The event object
\r
29283 * @return {Boolean} isValid True if the drag event is valid, else false to cancel
\r
29285 onBeforeDrag : function(data, e){
\r
29290 * An empty function by default, but provided so that you can perform a custom action once the initial
\r
29291 * drag event has begun. The drag cannot be canceled from this function.
\r
29292 * @param {Number} x The x position of the click on the dragged object
\r
29293 * @param {Number} y The y position of the click on the dragged object
\r
29295 onStartDrag : Ext.emptyFn,
\r
29297 // private override
\r
29298 startDrag : function(x, y){
\r
29299 this.proxy.reset();
\r
29300 this.dragging = true;
\r
29301 this.proxy.update("");
\r
29302 this.onInitDrag(x, y);
\r
29303 this.proxy.show();
\r
29307 onInitDrag : function(x, y){
\r
29308 var clone = this.el.dom.cloneNode(true);
\r
29309 clone.id = Ext.id(); // prevent duplicate ids
\r
29310 this.proxy.update(clone);
\r
29311 this.onStartDrag(x, y);
\r
29316 * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
\r
29317 * @return {Ext.dd.StatusProxy} proxy The StatusProxy
\r
29319 getProxy : function(){
\r
29320 return this.proxy;
\r
29324 * Hides the drag source's {@link Ext.dd.StatusProxy}
\r
29326 hideProxy : function(){
\r
29327 this.proxy.hide();
\r
29328 this.proxy.reset(true);
\r
29329 this.dragging = false;
\r
29333 triggerCacheRefresh : function(){
\r
29334 Ext.dd.DDM.refreshCache(this.groups);
\r
29337 // private - override to prevent hiding
\r
29338 b4EndDrag: function(e) {
\r
29341 // private - override to prevent moving
\r
29342 endDrag : function(e){
\r
29343 this.onEndDrag(this.dragData, e);
\r
29347 onEndDrag : function(data, e){
\r
29350 // private - pin to cursor
\r
29351 autoOffset : function(x, y) {
\r
29352 this.setDelta(-12, -20);
\r
29355 * @class Ext.dd.DropTarget
\r
29356 * @extends Ext.dd.DDTarget
\r
29357 * A simple class that provides the basic implementation needed to make any element a drop target that can have
\r
29358 * draggable items dropped onto it. The drop has no effect until an implementation of notifyDrop is provided.
\r
29360 * @param {Mixed} el The container element
\r
29361 * @param {Object} config
\r
29363 Ext.dd.DropTarget = function(el, config){
\r
29364 this.el = Ext.get(el);
\r
29366 Ext.apply(this, config);
\r
29368 if(this.containerScroll){
\r
29369 Ext.dd.ScrollManager.register(this.el);
\r
29372 Ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
\r
29373 {isTarget: true});
\r
29377 Ext.extend(Ext.dd.DropTarget, Ext.dd.DDTarget, {
\r
29379 * @cfg {String} ddGroup
\r
29380 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
29381 * interact with other drag drop objects in the same group (defaults to undefined).
\r
29384 * @cfg {String} overClass
\r
29385 * The CSS class applied to the drop target element while the drag source is over it (defaults to "").
\r
29388 * @cfg {String} dropAllowed
\r
29389 * The CSS class returned to the drag source when drop is allowed (defaults to "x-dd-drop-ok").
\r
29391 dropAllowed : "x-dd-drop-ok",
\r
29393 * @cfg {String} dropNotAllowed
\r
29394 * The CSS class returned to the drag source when drop is not allowed (defaults to "x-dd-drop-nodrop").
\r
29396 dropNotAllowed : "x-dd-drop-nodrop",
\r
29402 isNotifyTarget : true,
\r
29405 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source is now over the
\r
29406 * target. This default implementation adds the CSS class specified by overClass (if any) to the drop element
\r
29407 * and returns the dropAllowed config value. This method should be overridden if drop validation is required.
\r
29408 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29409 * @param {Event} e The event
\r
29410 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29411 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29412 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29414 notifyEnter : function(dd, e, data){
\r
29415 if(this.overClass){
\r
29416 this.el.addClass(this.overClass);
\r
29418 return this.dropAllowed;
\r
29422 * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the target.
\r
29423 * This method will be called on every mouse movement while the drag source is over the drop target.
\r
29424 * This default implementation simply returns the dropAllowed config value.
\r
29425 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29426 * @param {Event} e The event
\r
29427 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29428 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29429 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29431 notifyOver : function(dd, e, data){
\r
29432 return this.dropAllowed;
\r
29436 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the source has been dragged
\r
29437 * out of the target without dropping. This default implementation simply removes the CSS class specified by
\r
29438 * overClass (if any) from the drop element.
\r
29439 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29440 * @param {Event} e The event
\r
29441 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29443 notifyOut : function(dd, e, data){
\r
29444 if(this.overClass){
\r
29445 this.el.removeClass(this.overClass);
\r
29450 * The function a {@link Ext.dd.DragSource} calls once to notify this drop target that the dragged item has
\r
29451 * been dropped on it. This method has no default implementation and returns false, so you must provide an
\r
29452 * implementation that does something to process the drop event and returns true so that the drag source's
\r
29453 * repair action does not run.
\r
29454 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29455 * @param {Event} e The event
\r
29456 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29457 * @return {Boolean} True if the drop was valid, else false
\r
29459 notifyDrop : function(dd, e, data){
\r
29463 * @class Ext.dd.DragZone
\r
29464 * @extends Ext.dd.DragSource
\r
29465 * <p>This class provides a container DD instance that allows dragging of multiple child source nodes.</p>
\r
29466 * <p>This class does not move the drag target nodes, but a proxy element which may contain
\r
29467 * any DOM structure you wish. The DOM element to show in the proxy is provided by either a
\r
29468 * provided implementation of {@link #getDragData}, or by registered draggables registered with {@link Ext.dd.Registry}</p>
\r
29469 * <p>If you wish to provide draggability for an arbitrary number of DOM nodes, each of which represent some
\r
29470 * application object (For example nodes in a {@link Ext.DataView DataView}) then use of this class
\r
29471 * is the most efficient way to "activate" those nodes.</p>
\r
29472 * <p>By default, this class requires that draggable child nodes are registered with {@link Ext.dd.Registry}.
\r
29473 * However a simpler way to allow a DragZone to manage any number of draggable elements is to configure
\r
29474 * the DragZone with an implementation of the {@link #getDragData} method which interrogates the passed
\r
29475 * mouse event to see if it has taken place within an element, or class of elements. This is easily done
\r
29476 * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
\r
29477 * {@link Ext.DomQuery} selector. For example, to make the nodes of a DataView draggable, use the following
\r
29478 * technique. Knowledge of the use of the DataView is required:</p><pre><code>
\r
29479 myDataView.on('render', function() {
\r
29480 myDataView.dragZone = new Ext.dd.DragZone(myDataView.getEl(), {
\r
29482 // On receipt of a mousedown event, see if it is within a DataView node.
\r
29483 // Return a drag data object if so.
\r
29484 getDragData: function(e) {
\r
29486 // Use the DataView's own itemSelector (a mandatory property) to
\r
29487 // test if the mousedown is within one of the DataView's nodes.
\r
29488 var sourceEl = e.getTarget(myDataView.itemSelector, 10);
\r
29490 // If the mousedown is within a DataView node, clone the node to produce
\r
29491 // a ddel element for use by the drag proxy. Also add application data
\r
29492 // to the returned data object.
\r
29494 d = sourceEl.cloneNode(true);
\r
29498 sourceEl: sourceEl,
\r
29499 repairXY: Ext.fly(sourceEl).getXY(),
\r
29500 sourceStore: myDataView.store,
\r
29501 draggedRecord: v.getRecord(sourceEl)
\r
29506 // Provide coordinates for the proxy to slide back to on failed drag.
\r
29507 // This is the original XY coordinates of the draggable element captured
\r
29508 // in the getDragData method.
\r
29509 getRepairXY: function() {
\r
29510 return this.dragData.repairXY;
\r
29514 * See the {@link Ext.dd.DropZone DropZone} documentation for details about building a DropZone which
\r
29515 * cooperates with this DragZone.
\r
29517 * @param {Mixed} el The container element
\r
29518 * @param {Object} config
\r
29520 Ext.dd.DragZone = function(el, config){
\r
29521 Ext.dd.DragZone.superclass.constructor.call(this, el, config);
\r
29522 if(this.containerScroll){
\r
29523 Ext.dd.ScrollManager.register(this.el);
\r
29527 Ext.extend(Ext.dd.DragZone, Ext.dd.DragSource, {
\r
29529 * This property contains the data representing the dragged object. This data is set up by the implementation
\r
29530 * of the {@link #getDragData} method. It must contain a <tt>ddel</tt> property, but can contain
\r
29531 * any other data according to the application's needs.
\r
29533 * @property dragData
\r
29536 * @cfg {Boolean} containerScroll True to register this container with the Scrollmanager
\r
29537 * for auto scrolling during drag operations.
\r
29540 * @cfg {String} hlColor The color to use when visually highlighting the drag source in the afterRepair
\r
29541 * method after a failed drop (defaults to "c3daf9" - light blue)
\r
29545 * Called when a mousedown occurs in this container. Looks in {@link Ext.dd.Registry}
\r
29546 * for a valid target to drag based on the mouse down. Override this method
\r
29547 * to provide your own lookup logic (e.g. finding a child by class name). Make sure your returned
\r
29548 * object has a "ddel" attribute (with an HTML Element) for other functions to work.
\r
29549 * @param {EventObject} e The mouse down event
\r
29550 * @return {Object} The dragData
\r
29552 getDragData : function(e){
\r
29553 return Ext.dd.Registry.getHandleFromEvent(e);
\r
29557 * Called once drag threshold has been reached to initialize the proxy element. By default, it clones the
\r
29558 * this.dragData.ddel
\r
29559 * @param {Number} x The x position of the click on the dragged object
\r
29560 * @param {Number} y The y position of the click on the dragged object
\r
29561 * @return {Boolean} true to continue the drag, false to cancel
\r
29563 onInitDrag : function(x, y){
\r
29564 this.proxy.update(this.dragData.ddel.cloneNode(true));
\r
29565 this.onStartDrag(x, y);
\r
29570 * Called after a repair of an invalid drop. By default, highlights this.dragData.ddel
\r
29572 afterRepair : function(){
\r
29573 if(Ext.enableFx){
\r
29574 Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
\r
29576 this.dragging = false;
\r
29580 * Called before a repair of an invalid drop to get the XY to animate to. By default returns
\r
29581 * the XY of this.dragData.ddel
\r
29582 * @param {EventObject} e The mouse up event
\r
29583 * @return {Array} The xy location (e.g. [100, 200])
\r
29585 getRepairXY : function(e){
\r
29586 return Ext.Element.fly(this.dragData.ddel).getXY();
\r
29589 * @class Ext.dd.DropZone
\r
29590 * @extends Ext.dd.DropTarget
\r
29591 * <p>This class provides a container DD instance that allows dropping on multiple child target nodes.</p>
\r
29592 * <p>By default, this class requires that child nodes accepting drop are registered with {@link Ext.dd.Registry}.
\r
29593 * However a simpler way to allow a DropZone to manage any number of target elements is to configure the
\r
29594 * DropZone with an implementation of {@link #getTargetFromEvent} which interrogates the passed
\r
29595 * mouse event to see if it has taken place within an element, or class of elements. This is easily done
\r
29596 * by using the event's {@link Ext.EventObject#getTarget getTarget} method to identify a node based on a
\r
29597 * {@link Ext.DomQuery} selector.</p>
\r
29598 * <p>Once the DropZone has detected through calling getTargetFromEvent, that the mouse is over
\r
29599 * a drop target, that target is passed as the first parameter to {@link #onNodeEnter}, {@link #onNodeOver},
\r
29600 * {@link #onNodeOut}, {@link #onNodeDrop}. You may configure the instance of DropZone with implementations
\r
29601 * of these methods to provide application-specific behaviour for these events to update both
\r
29602 * application state, and UI state.</p>
\r
29603 * <p>For example to make a GridPanel a cooperating target with the example illustrated in
\r
29604 * {@link Ext.dd.DragZone DragZone}, the following technique might be used:</p><pre><code>
\r
29605 myGridPanel.on('render', function() {
\r
29606 myGridPanel.dropZone = new Ext.dd.DropZone(myGridPanel.getView().scroller, {
\r
29608 // If the mouse is over a grid row, return that node. This is
\r
29609 // provided as the "target" parameter in all "onNodeXXXX" node event handling functions
\r
29610 getTargetFromEvent: function(e) {
\r
29611 return e.getTarget(myGridPanel.getView().rowSelector);
\r
29614 // On entry into a target node, highlight that node.
\r
29615 onNodeEnter : function(target, dd, e, data){
\r
29616 Ext.fly(target).addClass('my-row-highlight-class');
\r
29619 // On exit from a target node, unhighlight that node.
\r
29620 onNodeOut : function(target, dd, e, data){
\r
29621 Ext.fly(target).removeClass('my-row-highlight-class');
\r
29624 // While over a target node, return the default drop allowed class which
\r
29625 // places a "tick" icon into the drag proxy.
\r
29626 onNodeOver : function(target, dd, e, data){
\r
29627 return Ext.dd.DropZone.prototype.dropAllowed;
\r
29630 // On node drop we can interrogate the target to find the underlying
\r
29631 // application object that is the real target of the dragged data.
\r
29632 // In this case, it is a Record in the GridPanel's Store.
\r
29633 // We can use the data set up by the DragZone's getDragData method to read
\r
29634 // any data we decided to attach in the DragZone's getDragData method.
\r
29635 onNodeDrop : function(target, dd, e, data){
\r
29636 var rowIndex = myGridPanel.getView().findRowIndex(target);
\r
29637 var r = myGridPanel.getStore().getAt(rowIndex);
\r
29638 Ext.Msg.alert('Drop gesture', 'Dropped Record id ' + data.draggedRecord.id +
\r
29639 ' on Record id ' + r.id);
\r
29645 * See the {@link Ext.dd.DragZone DragZone} documentation for details about building a DragZone which
\r
29646 * cooperates with this DropZone.
\r
29648 * @param {Mixed} el The container element
\r
29649 * @param {Object} config
\r
29651 Ext.dd.DropZone = function(el, config){
\r
29652 Ext.dd.DropZone.superclass.constructor.call(this, el, config);
\r
29655 Ext.extend(Ext.dd.DropZone, Ext.dd.DropTarget, {
\r
29657 * Returns a custom data object associated with the DOM node that is the target of the event. By default
\r
29658 * this looks up the event target in the {@link Ext.dd.Registry}, although you can override this method to
\r
29659 * provide your own custom lookup.
\r
29660 * @param {Event} e The event
\r
29661 * @return {Object} data The custom data
\r
29663 getTargetFromEvent : function(e){
\r
29664 return Ext.dd.Registry.getTargetFromEvent(e);
\r
29668 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has entered a drop node
\r
29669 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
\r
29670 * This method has no default implementation and should be overridden to provide
\r
29671 * node-specific processing if necessary.
\r
29672 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29673 * {@link #getTargetFromEvent} for this node)
\r
29674 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29675 * @param {Event} e The event
\r
29676 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29678 onNodeEnter : function(n, dd, e, data){
\r
29683 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is over a drop node
\r
29684 * that has either been registered or detected by a configured implementation of {@link #getTargetFromEvent}.
\r
29685 * The default implementation returns this.dropNotAllowed, so it should be
\r
29686 * overridden to provide the proper feedback.
\r
29687 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29688 * {@link #getTargetFromEvent} for this node)
\r
29689 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29690 * @param {Event} e The event
\r
29691 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29692 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29693 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29695 onNodeOver : function(n, dd, e, data){
\r
29696 return this.dropAllowed;
\r
29700 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dragged out of
\r
29701 * the drop node without dropping. This method has no default implementation and should be overridden to provide
\r
29702 * node-specific processing if necessary.
\r
29703 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29704 * {@link #getTargetFromEvent} for this node)
\r
29705 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29706 * @param {Event} e The event
\r
29707 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29709 onNodeOut : function(n, dd, e, data){
\r
29714 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped onto
\r
29715 * the drop node. The default implementation returns false, so it should be overridden to provide the
\r
29716 * appropriate processing of the drop event and return true so that the drag source's repair action does not run.
\r
29717 * @param {Object} nodeData The custom data associated with the drop node (this is the same value returned from
\r
29718 * {@link #getTargetFromEvent} for this node)
\r
29719 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29720 * @param {Event} e The event
\r
29721 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29722 * @return {Boolean} True if the drop was valid, else false
\r
29724 onNodeDrop : function(n, dd, e, data){
\r
29729 * Called while the DropZone determines that a {@link Ext.dd.DragSource} is being dragged over it,
\r
29730 * but not over any of its registered drop nodes. The default implementation returns this.dropNotAllowed, so
\r
29731 * it should be overridden to provide the proper feedback if necessary.
\r
29732 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29733 * @param {Event} e The event
\r
29734 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29735 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29736 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29738 onContainerOver : function(dd, e, data){
\r
29739 return this.dropNotAllowed;
\r
29743 * Called when the DropZone determines that a {@link Ext.dd.DragSource} has been dropped on it,
\r
29744 * but not on any of its registered drop nodes. The default implementation returns false, so it should be
\r
29745 * overridden to provide the appropriate processing of the drop event if you need the drop zone itself to
\r
29746 * be able to accept drops. It should return true when valid so that the drag source's repair action does not run.
\r
29747 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29748 * @param {Event} e The event
\r
29749 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29750 * @return {Boolean} True if the drop was valid, else false
\r
29752 onContainerDrop : function(dd, e, data){
\r
29757 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source is now over
\r
29758 * the zone. The default implementation returns this.dropNotAllowed and expects that only registered drop
\r
29759 * nodes can process drag drop operations, so if you need the drop zone itself to be able to process drops
\r
29760 * you should override this method and provide a custom implementation.
\r
29761 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29762 * @param {Event} e The event
\r
29763 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29764 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29765 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29767 notifyEnter : function(dd, e, data){
\r
29768 return this.dropNotAllowed;
\r
29772 * The function a {@link Ext.dd.DragSource} calls continuously while it is being dragged over the drop zone.
\r
29773 * This method will be called on every mouse movement while the drag source is over the drop zone.
\r
29774 * It will call {@link #onNodeOver} while the drag source is over a registered node, and will also automatically
\r
29775 * delegate to the appropriate node-specific methods as necessary when the drag source enters and exits
\r
29776 * registered nodes ({@link #onNodeEnter}, {@link #onNodeOut}). If the drag source is not currently over a
\r
29777 * registered node, it will call {@link #onContainerOver}.
\r
29778 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29779 * @param {Event} e The event
\r
29780 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29781 * @return {String} status The CSS class that communicates the drop status back to the source so that the
\r
29782 * underlying {@link Ext.dd.StatusProxy} can be updated
\r
29784 notifyOver : function(dd, e, data){
\r
29785 var n = this.getTargetFromEvent(e);
\r
29786 if(!n){ // not over valid drop target
\r
29787 if(this.lastOverNode){
\r
29788 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29789 this.lastOverNode = null;
\r
29791 return this.onContainerOver(dd, e, data);
\r
29793 if(this.lastOverNode != n){
\r
29794 if(this.lastOverNode){
\r
29795 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29797 this.onNodeEnter(n, dd, e, data);
\r
29798 this.lastOverNode = n;
\r
29800 return this.onNodeOver(n, dd, e, data);
\r
29804 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the source has been dragged
\r
29805 * out of the zone without dropping. If the drag source is currently over a registered node, the notification
\r
29806 * will be delegated to {@link #onNodeOut} for node-specific handling, otherwise it will be ignored.
\r
29807 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop target
\r
29808 * @param {Event} e The event
\r
29809 * @param {Object} data An object containing arbitrary data supplied by the drag zone
\r
29811 notifyOut : function(dd, e, data){
\r
29812 if(this.lastOverNode){
\r
29813 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29814 this.lastOverNode = null;
\r
29819 * The function a {@link Ext.dd.DragSource} calls once to notify this drop zone that the dragged item has
\r
29820 * been dropped on it. The drag zone will look up the target node based on the event passed in, and if there
\r
29821 * is a node registered for that event, it will delegate to {@link #onNodeDrop} for node-specific handling,
\r
29822 * otherwise it will call {@link #onContainerDrop}.
\r
29823 * @param {Ext.dd.DragSource} source The drag source that was dragged over this drop zone
\r
29824 * @param {Event} e The event
\r
29825 * @param {Object} data An object containing arbitrary data supplied by the drag source
\r
29826 * @return {Boolean} True if the drop was valid, else false
\r
29828 notifyDrop : function(dd, e, data){
\r
29829 if(this.lastOverNode){
\r
29830 this.onNodeOut(this.lastOverNode, dd, e, data);
\r
29831 this.lastOverNode = null;
\r
29833 var n = this.getTargetFromEvent(e);
\r
29835 this.onNodeDrop(n, dd, e, data) :
\r
29836 this.onContainerDrop(dd, e, data);
\r
29840 triggerCacheRefresh : function(){
\r
29841 Ext.dd.DDM.refreshCache(this.groups);
\r
29844 * @class Ext.Element
\r
29846 Ext.Element.addMethods({
\r
29848 * Initializes a {@link Ext.dd.DD} drag drop object for this element.
\r
29849 * @param {String} group The group the DD object is member of
\r
29850 * @param {Object} config The DD config object
\r
29851 * @param {Object} overrides An object containing methods to override/implement on the DD object
\r
29852 * @return {Ext.dd.DD} The DD object
\r
29854 initDD : function(group, config, overrides){
\r
29855 var dd = new Ext.dd.DD(Ext.id(this.dom), group, config);
\r
29856 return Ext.apply(dd, overrides);
\r
29860 * Initializes a {@link Ext.dd.DDProxy} object for this element.
\r
29861 * @param {String} group The group the DDProxy object is member of
\r
29862 * @param {Object} config The DDProxy config object
\r
29863 * @param {Object} overrides An object containing methods to override/implement on the DDProxy object
\r
29864 * @return {Ext.dd.DDProxy} The DDProxy object
\r
29866 initDDProxy : function(group, config, overrides){
\r
29867 var dd = new Ext.dd.DDProxy(Ext.id(this.dom), group, config);
\r
29868 return Ext.apply(dd, overrides);
\r
29872 * Initializes a {@link Ext.dd.DDTarget} object for this element.
\r
29873 * @param {String} group The group the DDTarget object is member of
\r
29874 * @param {Object} config The DDTarget config object
\r
29875 * @param {Object} overrides An object containing methods to override/implement on the DDTarget object
\r
29876 * @return {Ext.dd.DDTarget} The DDTarget object
\r
29878 initDDTarget : function(group, config, overrides){
\r
29879 var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config);
\r
29880 return Ext.apply(dd, overrides);
\r
29884 * @class Ext.data.Api
29886 * Ext.data.Api is a singleton designed to manage the data API including methods
29887 * for validating a developer's DataProxy API. Defines variables for CRUD actions
29888 * create, read, update and destroy in addition to a mapping of RESTful HTTP methods
29889 * GET, POST, PUT and DELETE to CRUD actions.
29892 Ext.data.Api = (function() {
29894 // private validActions. validActions is essentially an inverted hash of Ext.data.Api.actions, where value becomes the key.
29895 // Some methods in this singleton (e.g.: getActions, getVerb) will loop through actions with the code <code>for (var verb in this.actions)</code>
29896 // For efficiency, some methods will first check this hash for a match. Those methods which do acces validActions will cache their result here.
29897 // We cannot pre-define this hash since the developer may over-ride the actions at runtime.
29898 var validActions = {};
29902 * Defined actions corresponding to remote actions:
29905 create : 'create', // Text representing the remote-action to create records on server.
29906 read : 'read', // Text representing the remote-action to read/load data from server.
29907 update : 'update', // Text representing the remote-action to update records on server.
29908 destroy : 'destroy' // Text representing the remote-action to destroy records on server.
29911 * @property actions
29918 destroy : 'destroy'
29922 * Defined {CRUD action}:{HTTP method} pairs to associate HTTP methods with the
29923 * corresponding actions for {@link Ext.data.DataProxy#restful RESTful proxies}.
29942 * Returns true if supplied action-name is a valid API action defined in <code>{@link #actions}</code> constants
29943 * @param {String} action
29944 * @param {String[]}(Optional) List of available CRUD actions. Pass in list when executing multiple times for efficiency.
29945 * @return {Boolean}
29947 isAction : function(action) {
29948 return (Ext.data.Api.actions[action]) ? true : false;
29952 * 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
29953 * 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
29954 * 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
29955 * required. This method will cache discovered KEYS into the private validActions hash.
29956 * @param {String} name The runtime name of the action.
29957 * @return {String||null} returns the action-key, or verb of the user-action or null if invalid.
29960 getVerb : function(name) {
29961 if (validActions[name]) {
29962 return validActions[name]; // <-- found in cache. return immediately.
29964 for (var verb in this.actions) {
29965 if (this.actions[verb] === name) {
29966 validActions[name] = verb;
29970 return (validActions[name] !== undefined) ? validActions[name] : null;
29974 * Returns true if the supplied API is valid; that is, check that all keys match defined actions
29975 * otherwise returns an array of mistakes.
29976 * @return {String[]||true}
29978 isValid : function(api){
29980 var crud = this.actions; // <-- cache a copy of the actions.
29981 for (var action in api) {
29982 if (!(action in crud)) {
29983 invalid.push(action);
29986 return (!invalid.length) ? true : invalid;
29990 * Returns true if the supplied verb upon the supplied proxy points to a unique url in that none of the other api-actions
29991 * point to the same url. The question is important for deciding whether to insert the "xaction" HTTP parameter within an
29992 * Ajax request. This method is used internally and shouldn't generally need to be called directly.
29993 * @param {Ext.data.DataProxy} proxy
29994 * @param {String} verb
29995 * @return {Boolean}
29997 hasUniqueUrl : function(proxy, verb) {
29998 var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
30000 for (var action in proxy.api) {
30001 if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
30009 * This method is used internally by <tt>{@link Ext.data.DataProxy DataProxy}</tt> and should not generally need to be used directly.
30010 * Each action of a DataProxy api can be initially defined as either a String or an Object. When specified as an object,
30011 * one can explicitly define the HTTP method (GET|POST) to use for each CRUD action. This method will prepare the supplied API, setting
30012 * each action to the Object form. If your API-actions do not explicitly define the HTTP method, the "method" configuration-parameter will
30013 * be used. If the method configuration parameter is not specified, POST will be used.
30015 new Ext.data.HttpProxy({
30016 method: "POST", // <-- default HTTP method when not specified.
30018 create: 'create.php',
30021 destroy: 'destroy.php'
30025 // Alternatively, one can use the object-form to specify the API
30026 new Ext.data.HttpProxy({
30028 load: {url: 'read.php', method: 'GET'},
30029 create: 'create.php',
30030 destroy: 'destroy.php',
30036 * @param {Ext.data.DataProxy} proxy
30038 prepare : function(proxy) {
30040 proxy.api = {}; // <-- No api? create a blank one.
30042 for (var verb in this.actions) {
30043 var action = this.actions[verb];
30044 proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
30045 if (typeof(proxy.api[action]) == 'string') {
30046 proxy.api[action] = {
30047 url: proxy.api[action]
30054 * Prepares a supplied Proxy to be RESTful. Sets the HTTP method for each api-action to be one of
30055 * GET, POST, PUT, DELETE according to the defined {@link #restActions}.
30056 * @param {Ext.data.DataProxy} proxy
30058 restify : function(proxy) {
30059 proxy.restful = true;
30060 for (var verb in this.restActions) {
30061 proxy.api[this.actions[verb]].method = this.restActions[verb];
30063 // TODO: perhaps move this interceptor elsewhere? like into DataProxy, perhaps? Placed here
30064 // to satisfy initial 3.0 final release of REST features.
30065 proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) {
30066 var reader = o.reader;
30067 var res = new Ext.data.Response({
30072 switch (response.status) {
30073 case 200: // standard 200 response, send control back to HttpProxy#onWrite
30076 case 201: // entity created but no response returned
30077 //res[reader.meta.successProperty] = true;
30078 res.success = true;
30080 case 204: // no-content. Create a fake response.
30081 //res[reader.meta.successProperty] = true;
30082 //res[reader.meta.root] = null;
30083 res.success = true;
30091 if (res[reader.meta.successProperty] === true) {
30092 this.fireEvent("write", this, action, res[reader.meta.root], res, rs, o.request.arg);
30094 this.fireEvent('exception', this, 'remote', action, o, res, rs);
30097 if (res.success === true) {
30098 this.fireEvent("write", this, action, res.data, res, rs, o.request.arg);
30100 this.fireEvent('exception', this, 'remote', action, o, res, rs);
30102 o.request.callback.call(o.request.scope, res.data, res, res.success);
30104 return false; // <-- false to prevent intercepted function from running.
30111 * Ext.data.Response
30112 * Experimental. Do not use directly.
30114 Ext.data.Response = function(params, response) {
30115 Ext.apply(this, params, {
30119 Ext.data.Response.prototype = {
30126 getMessage : function() {
30127 return this.message;
30129 getSuccess : function() {
30130 return this.success;
30132 getStatus : function() {
30135 getRoot : function() {
30138 getRawResponse : function() {
30144 * @class Ext.data.Api.Error
30145 * @extends Ext.Error
30146 * Error class for Ext.data.Api errors
30148 Ext.data.Api.Error = Ext.extend(Ext.Error, {
30149 constructor : function(message, arg) {
30151 Ext.Error.call(this, message);
30153 name: 'Ext.data.Api'
30155 Ext.apply(Ext.data.Api.Error.prototype, {
30157 '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.',
30158 'invalid': 'received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
30159 'invalid-url': 'Invalid url. Please review your proxy configuration.',
30160 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
30167 * @class Ext.data.SortTypes
\r
30169 * Defines the default sorting (casting?) comparison functions used when sorting data.
\r
30171 Ext.data.SortTypes = {
\r
30173 * Default sort that does nothing
\r
30174 * @param {Mixed} s The value being converted
\r
30175 * @return {Mixed} The comparison value
\r
30177 none : function(s){
\r
30182 * The regular expression used to strip tags
\r
30186 stripTagsRE : /<\/?[^>]+>/gi,
\r
30189 * Strips all HTML tags to sort on text only
\r
30190 * @param {Mixed} s The value being converted
\r
30191 * @return {String} The comparison value
\r
30193 asText : function(s){
\r
30194 return String(s).replace(this.stripTagsRE, "");
\r
30198 * Strips all HTML tags to sort on text only - Case insensitive
\r
30199 * @param {Mixed} s The value being converted
\r
30200 * @return {String} The comparison value
\r
30202 asUCText : function(s){
\r
30203 return String(s).toUpperCase().replace(this.stripTagsRE, "");
\r
30207 * Case insensitive string
\r
30208 * @param {Mixed} s The value being converted
\r
30209 * @return {String} The comparison value
\r
30211 asUCString : function(s) {
\r
30212 return String(s).toUpperCase();
\r
30217 * @param {Mixed} s The value being converted
\r
30218 * @return {Number} The comparison value
\r
30220 asDate : function(s) {
\r
30224 if(Ext.isDate(s)){
\r
30225 return s.getTime();
\r
30227 return Date.parse(String(s));
\r
30232 * @param {Mixed} s The value being converted
\r
30233 * @return {Float} The comparison value
\r
30235 asFloat : function(s) {
\r
30236 var val = parseFloat(String(s).replace(/,/g, ""));
\r
30237 return isNaN(val) ? 0 : val;
\r
30241 * Integer sorting
\r
30242 * @param {Mixed} s The value being converted
\r
30243 * @return {Number} The comparison value
\r
30245 asInt : function(s) {
\r
30246 var val = parseInt(String(s).replace(/,/g, ""), 10);
\r
30247 return isNaN(val) ? 0 : val;
\r
30250 * @class Ext.data.Record
30251 * <p>Instances of this class encapsulate both Record <em>definition</em> information, and Record
30252 * <em>value</em> information for use in {@link Ext.data.Store} objects, or any code which needs
30253 * to access Records cached in an {@link Ext.data.Store} object.</p>
30254 * <p>Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
30255 * Instances are usually only created by {@link Ext.data.Reader} implementations when processing unformatted data
30257 * <p>Note that an instance of a Record class may only belong to one {@link Ext.data.Store Store} at a time.
30258 * In order to copy data from one Store to another, use the {@link #copy} method to create an exact
30259 * copy of the Record, and insert the new instance into the other Store.</p>
30260 * <p>When serializing a Record for submission to the server, be aware that it contains many private
30261 * properties, and also a reference to its owning Store which in turn holds references to its Records.
30262 * This means that a whole Record may not be encoded using {@link Ext.util.JSON.encode}. Instead, use the
30263 * <code>{@link #data}</code> and <code>{@link #id}</code> properties.</p>
30264 * <p>Record objects generated by this constructor inherit all the methods of Ext.data.Record listed below.</p>
30266 * <p>This constructor should not be used to create Record objects. Instead, use {@link #create} to
30267 * generate a subclass of Ext.data.Record configured with information about its constituent fields.<p>
30268 * <p><b>The generated constructor has the same signature as this constructor.</b></p>
30269 * @param {Object} data (Optional) An object, the properties of which provide values for the new Record's
30270 * fields. If not specified the <code>{@link Ext.data.Field#defaultValue defaultValue}</code>
30271 * for each field will be assigned.
30272 * @param {Object} id (Optional) The id of the Record. This id should be unique, and is used by the
30273 * {@link Ext.data.Store} object which owns the Record to index its collection of Records. If
30274 * an <code>id</code> is not specified a <b><code>{@link #phantom}</code></b> Record will be created
30275 * with an {@link #Record.id automatically generated id}.
30277 Ext.data.Record = function(data, id){
30278 // if no id, call the auto id method
30279 this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
30280 this.data = data || {};
30284 * Generate a constructor for a specific Record layout.
30285 * @param {Array} o An Array of <b>{@link Ext.data.Field Field}</b> definition objects.
30286 * The constructor generated by this method may be used to create new Record instances. The data
30287 * object must contain properties named after the {@link Ext.data.Field field}
30288 * <b><tt>{@link Ext.data.Field#name}s</tt></b>. Example usage:<pre><code>
30289 // create a Record constructor from a description of the fields
30290 var TopicRecord = Ext.data.Record.create([ // creates a subclass of Ext.data.Record
30291 {{@link Ext.data.Field#name name}: 'title', {@link Ext.data.Field#mapping mapping}: 'topic_title'},
30292 {name: 'author', mapping: 'username', allowBlank: false},
30293 {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
30294 {name: 'lastPost', mapping: 'post_time', type: 'date'},
30295 {name: 'lastPoster', mapping: 'user2'},
30296 {name: 'excerpt', mapping: 'post_text', allowBlank: false},
30297 // In the simplest case, if no properties other than <tt>name</tt> are required,
30298 // a field definition may consist of just a String for the field name.
30302 // create Record instance
30303 var myNewRecord = new TopicRecord(
30305 title: 'Do my job please',
30308 lastPost: new Date(),
30309 lastPoster: 'Animal',
30310 excerpt: 'No way dude!',
30313 id // optionally specify the id of the record otherwise {@link #Record.id one is auto-assigned}
30315 myStore.{@link Ext.data.Store#add add}(myNewRecord);
30318 * @return {function} A constructor which is used to create new Records according
30319 * to the definition. The constructor has the same signature as {@link #Record}.
30322 Ext.data.Record.create = function(o){
30323 var f = Ext.extend(Ext.data.Record, {});
30324 var p = f.prototype;
30325 p.fields = new Ext.util.MixedCollection(false, function(field){
30328 for(var i = 0, len = o.length; i < len; i++){
30329 p.fields.add(new Ext.data.Field(o[i]));
30331 f.getField = function(name){
30332 return p.fields.get(name);
30337 Ext.data.Record.PREFIX = 'ext-record';
30338 Ext.data.Record.AUTO_ID = 1;
30339 Ext.data.Record.EDIT = 'edit';
30340 Ext.data.Record.REJECT = 'reject';
30341 Ext.data.Record.COMMIT = 'commit';
30345 * Generates a sequential id. This method is typically called when a record is {@link #create}d
30346 * and {@link #Record no id has been specified}. The returned id takes the form:
30347 * <tt>{PREFIX}-{AUTO_ID}</tt>.<div class="mdetail-params"><ul>
30348 * <li><b><tt>PREFIX</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.PREFIX</tt>
30349 * (defaults to <tt>'ext-record'</tt>)</p></li>
30350 * <li><b><tt>AUTO_ID</tt></b> : String<p class="sub-desc"><tt>Ext.data.Record.AUTO_ID</tt>
30351 * (defaults to <tt>1</tt> initially)</p></li>
30353 * @param {Record} rec The record being created. The record does not exist, it's a {@link #phantom}.
30354 * @return {String} auto-generated string id, <tt>"ext-record-i++'</tt>;
30356 Ext.data.Record.id = function(rec) {
30357 rec.phantom = true;
30358 return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
30361 Ext.data.Record.prototype = {
30363 * <p><b>This property is stored in the Record definition's <u>prototype</u></b></p>
30364 * A MixedCollection containing the defined {@link Ext.data.Field Field}s for this Record. Read-only.
30366 * @type Ext.util.MixedCollection
30369 * An object hash representing the data for this Record. Every field name in the Record definition
30370 * is represented by a property of that name in this object. Note that unless you specified a field
30371 * with {@link Ext.data.Field#name name} "id" in the Record definition, this will <b>not</b> contain
30372 * an <tt>id</tt> property.
30377 * The unique ID of the Record {@link #Record as specified at construction time}.
30382 * Readonly flag - true if this Record has been modified.
30389 * This object contains a key and value storing the original values of all modified
30390 * fields or is null if no fields have been modified.
30391 * @property modified
30396 * <tt>false</tt> when the record does not yet exist in a server-side database (see
30397 * {@link #markDirty}). Any record which has a real database pk set as its id property
30398 * is NOT a phantom -- it's real.
30399 * @property phantom
30405 join : function(store){
30407 * The {@link Ext.data.Store} to which this Record belongs.
30409 * @type {Ext.data.Store}
30411 this.store = store;
30415 * Set the {@link Ext.data.Field#name named field} to the specified value. For example:
30417 // record has a field named 'firstname'
30418 var Employee = Ext.data.Record.{@link #create}([
30419 {name: 'firstname'},
30423 // update the 2nd record in the store:
30424 var rec = myStore.{@link Ext.data.Store#getAt getAt}(1);
30426 // set the value (shows dirty flag):
30427 rec.set('firstname', 'Betty');
30429 // commit the change (removes dirty flag):
30430 rec.{@link #commit}();
30432 // update the record in the store, bypass setting dirty flag,
30433 // and do not store the change in the {@link Ext.data.Store#getModifiedRecords modified records}
30434 rec.{@link #data}['firstname'] = 'Wilma'); // updates record, but not the view
30435 rec.{@link #commit}(); // updates the view
30437 * <b>Notes</b>:<div class="mdetail-params"><ul>
30438 * <li>If the store has a writer and <code>autoSave=true</code>, each set()
30439 * will execute an XHR to the server.</li>
30440 * <li>Use <code>{@link #beginEdit}</code> to prevent the store's <code>update</code>
30441 * event firing while using set().</li>
30442 * <li>Use <code>{@link #endEdit}</code> to have the store's <code>update</code>
30445 * @param {String} name The {@link Ext.data.Field#name name of the field} to set.
30446 * @param {Object} value The value to set the field to.
30448 set : function(name, value){
30449 var isObj = (typeof value === 'object');
30450 if(!isObj && String(this.data[name]) === String(value)){
30452 } else if (isObj && Ext.encode(this.data[name]) === Ext.encode(value)) {
30456 if(!this.modified){
30457 this.modified = {};
30459 if(typeof this.modified[name] == 'undefined'){
30460 this.modified[name] = this.data[name];
30462 this.data[name] = value;
30469 afterEdit: function(){
30471 this.store.afterEdit(this);
30476 afterReject: function(){
30478 this.store.afterReject(this);
30483 afterCommit: function(){
30485 this.store.afterCommit(this);
30490 * Get the value of the {@link Ext.data.Field#name named field}.
30491 * @param {String} name The {@link Ext.data.Field#name name of the field} to get the value of.
30492 * @return {Object} The value of the field.
30494 get : function(name){
30495 return this.data[name];
30499 * Begin an edit. While in edit mode, no events (e.g.. the <code>update</code> event)
30500 * are relayed to the containing store.
30501 * See also: <code>{@link #endEdit}</code> and <code>{@link #cancelEdit}</code>.
30503 beginEdit : function(){
30504 this.editing = true;
30505 this.modified = this.modified || {};
30509 * Cancels all changes made in the current edit operation.
30511 cancelEdit : function(){
30512 this.editing = false;
30513 delete this.modified;
30517 * End an edit. If any data was modified, the containing store is notified
30518 * (ie, the store's <code>update</code> event will fire).
30520 endEdit : function(){
30521 this.editing = false;
30528 * Usually called by the {@link Ext.data.Store} which owns the Record.
30529 * Rejects all changes made to the Record since either creation, or the last commit operation.
30530 * Modified fields are reverted to their original values.
30531 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
30532 * to have their code notified of reject operations.</p>
30533 * @param {Boolean} silent (optional) True to skip notification of the owning
30534 * store of the change (defaults to false)
30536 reject : function(silent){
30537 var m = this.modified;
30539 if(typeof m[n] != "function"){
30540 this.data[n] = m[n];
30543 this.dirty = false;
30544 delete this.modified;
30545 this.editing = false;
30546 if(silent !== true){
30547 this.afterReject();
30552 * Usually called by the {@link Ext.data.Store} which owns the Record.
30553 * Commits all changes made to the Record since either creation, or the last commit operation.
30554 * <p>Developers should subscribe to the {@link Ext.data.Store#update} event
30555 * to have their code notified of commit operations.</p>
30556 * @param {Boolean} silent (optional) True to skip notification of the owning
30557 * store of the change (defaults to false)
30559 commit : function(silent){
30560 this.dirty = false;
30561 delete this.modified;
30562 this.editing = false;
30563 if(silent !== true){
30564 this.afterCommit();
30569 * Gets a hash of only the fields that have been modified since this Record was created or commited.
30572 getChanges : function(){
30573 var m = this.modified, cs = {};
30575 if(m.hasOwnProperty(n)){
30576 cs[n] = this.data[n];
30583 hasError : function(){
30584 return this.error !== null;
30588 clearError : function(){
30593 * Creates a copy of this Record.
30594 * @param {String} id (optional) A new Record id, defaults to {@link #Record.id autogenerating an id}.
30595 * Note: if an <code>id</code> is not specified the copy created will be a
30596 * <code>{@link #phantom}</code> Record.
30599 copy : function(newId) {
30600 return new this.constructor(Ext.apply({}, this.data), newId || this.id);
30604 * Returns <tt>true</tt> if the passed field name has been <code>{@link #modified}</code>
30605 * since the load or last commit.
30606 * @param {String} fieldName {@link Ext.data.Field.{@link Ext.data.Field#name}
30607 * @return {Boolean}
30609 isModified : function(fieldName){
30610 return !!(this.modified && this.modified.hasOwnProperty(fieldName));
30614 * By default returns <tt>false</tt> if any {@link Ext.data.Field field} within the
30615 * record configured with <tt>{@link Ext.data.Field#allowBlank} = false</tt> returns
30616 * <tt>true</tt> from an {@link Ext}.{@link Ext#isEmpty isempty} test.
30617 * @return {Boolean}
30619 isValid : function() {
30620 return this.fields.find(function(f) {
30621 return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
30622 },this) ? false : true;
30626 * <p>Marks this <b>Record</b> as <code>{@link #dirty}</code>. This method
30627 * is used interally when adding <code>{@link #phantom}</code> records to a
30628 * {@link Ext.data.Store#writer writer enabled store}.</p>
30629 * <br><p>Marking a record <code>{@link #dirty}</code> causes the phantom to
30630 * be returned by {@link Ext.data.Store#getModifiedRecords} where it will
30631 * have a create action composed for it during {@link Ext.data.Store#save store save}
30634 markDirty : function(){
30636 if(!this.modified){
30637 this.modified = {};
30639 this.fields.each(function(f) {
30640 this.modified[f.name] = this.data[f.name];
30644 * @class Ext.StoreMgr
30645 * @extends Ext.util.MixedCollection
30646 * The default global group of stores.
30649 Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
30651 * @cfg {Object} listeners @hide
30655 * Registers one or more Stores with the StoreMgr. You do not normally need to register stores
30656 * manually. Any store initialized with a {@link Ext.data.Store#storeId} will be auto-registered.
30657 * @param {Ext.data.Store} store1 A Store instance
30658 * @param {Ext.data.Store} store2 (optional)
30659 * @param {Ext.data.Store} etc... (optional)
30661 register : function(){
30662 for(var i = 0, s; (s = arguments[i]); i++){
30668 * Unregisters one or more Stores with the StoreMgr
30669 * @param {String/Object} id1 The id of the Store, or a Store instance
30670 * @param {String/Object} id2 (optional)
30671 * @param {String/Object} etc... (optional)
30673 unregister : function(){
30674 for(var i = 0, s; (s = arguments[i]); i++){
30675 this.remove(this.lookup(s));
30680 * Gets a registered Store by id
30681 * @param {String/Object} id The id of the Store, or a Store instance
30682 * @return {Ext.data.Store}
30684 lookup : function(id){
30685 if(Ext.isArray(id)){
30686 var fields = ['field1'], expand = !Ext.isArray(id[0]);
30688 for(var i = 2, len = id[0].length; i <= len; ++i){
30689 fields.push('field' + i);
30692 return new Ext.data.ArrayStore({
30695 expandData: expand,
30701 return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
30704 // getKey implementation for MixedCollection
30705 getKey : function(o){
30709 * @class Ext.data.Store
30710 * @extends Ext.util.Observable
30711 * <p>The Store class encapsulates a client side cache of {@link Ext.data.Record Record}
30712 * objects which provide input data for Components such as the {@link Ext.grid.GridPanel GridPanel},
30713 * the {@link Ext.form.ComboBox ComboBox}, or the {@link Ext.DataView DataView}.</p>
30714 * <p><u>Retrieving Data</u></p>
30715 * <p>A Store object may access a data object using:<div class="mdetail-params"><ul>
30716 * <li>{@link #proxy configured implementation} of {@link Ext.data.DataProxy DataProxy}</li>
30717 * <li>{@link #data} to automatically pass in data</li>
30718 * <li>{@link #loadData} to manually pass in data</li>
30720 * <p><u>Reading Data</u></p>
30721 * <p>A Store object has no inherent knowledge of the format of the data object (it could be
30722 * an Array, XML, or JSON). A Store object uses an appropriate {@link #reader configured implementation}
30723 * of a {@link Ext.data.DataReader DataReader} to create {@link Ext.data.Record Record} instances from the data
30725 * <p><u>Store Types</u></p>
30726 * <p>There are several implementations of Store available which are customized for use with
30727 * a specific DataReader implementation. Here is an example using an ArrayStore which implicitly
30728 * creates a reader commensurate to an Array data object.</p>
30730 var myStore = new Ext.data.ArrayStore({
30731 fields: ['fullname', 'first'],
30732 idIndex: 0 // id for each record will be the first element
30735 * <p>For custom implementations create a basic {@link Ext.data.Store} configured as needed:</p>
30737 // create a {@link Ext.data.Record Record} constructor:
30738 var rt = Ext.data.Record.create([
30739 {name: 'fullname'},
30742 var myStore = new Ext.data.Store({
30743 // explicitly create reader
30744 reader: new Ext.data.ArrayReader(
30746 idIndex: 0 // id for each record will be the first element
30752 * <p>Load some data into store (note the data object is an array which corresponds to the reader):</p>
30755 [1, 'Fred Flintstone', 'Fred'], // note that id for the record is the first element
30756 [2, 'Barney Rubble', 'Barney']
30758 myStore.loadData(myData);
30760 * <p>Records are cached and made available through accessor functions. An example of adding
30761 * a record to the store:</p>
30763 var defaultData = {
30764 fullname: 'Full Name',
30765 first: 'First Name'
30767 var recId = 100; // provide unique id for the record
30768 var r = new myStore.recordType(defaultData, ++recId); // create new record
30769 myStore.{@link #insert}(0, r); // insert a new record into the store (also see {@link #add})
30771 * <p><u>Writing Data</u></p>
30772 * <p>And <b>new in Ext version 3</b>, use the new {@link Ext.data.DataWriter DataWriter} to create an automated, <a href="http://extjs.com/deploy/dev/examples/writer/writer.html">Writable Store</a>
30773 * along with <a href="http://extjs.com/deploy/dev/examples/restful/restful.html">RESTful features.</a>
30775 * Creates a new Store.
30776 * @param {Object} config A config object containing the objects needed for the Store to access data,
30777 * and read the data into Records.
30780 Ext.data.Store = function(config){
30781 this.data = new Ext.util.MixedCollection(false);
30782 this.data.getKey = function(o){
30786 * See the <code>{@link #baseParams corresponding configuration option}</code>
30787 * for a description of this property.
30788 * To modify this property see <code>{@link #setBaseParam}</code>.
30791 this.baseParams = {};
30793 // temporary removed-records cache
30796 if(config && config.data){
30797 this.inlineData = config.data;
30798 delete config.data;
30801 Ext.apply(this, config);
30803 this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
30805 if(this.url && !this.proxy){
30806 this.proxy = new Ext.data.HttpProxy({url: this.url});
30808 // If Store is RESTful, so too is the DataProxy
30809 if (this.restful === true && this.proxy) {
30810 // When operating RESTfully, a unique transaction is generated for each record.
30811 this.batch = false;
30812 Ext.data.Api.restify(this.proxy);
30815 if(this.reader){ // reader passed
30816 if(!this.recordType){
30817 this.recordType = this.reader.recordType;
30819 if(this.reader.onMetaChange){
30820 //this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
30821 this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
30823 if (this.writer) { // writer passed
30824 this.writer.meta = this.reader.meta;
30825 this.pruneModifiedRecords = true;
30830 * The {@link Ext.data.Record Record} constructor as supplied to (or created by) the
30831 * {@link Ext.data.DataReader Reader}. Read-only.
30832 * <p>If the Reader was constructed by passing in an Array of {@link Ext.data.Field} definition objects,
30833 * instead of a Record constructor, it will implicitly create a Record constructor from that Array (see
30834 * {@link Ext.data.Record}.{@link Ext.data.Record#create create} for additional details).</p>
30835 * <p>This property may be used to create new Records of the type held in this Store, for example:</p><pre><code>
30836 // create the data store
30837 var store = new Ext.data.ArrayStore({
30841 {name: 'price', type: 'float'},
30842 {name: 'change', type: 'float'},
30843 {name: 'pctChange', type: 'float'},
30844 {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
30847 store.loadData(myData);
30850 var grid = new Ext.grid.EditorGridPanel({
30852 colModel: new Ext.grid.ColumnModel({
30854 {id:'company', header: 'Company', width: 160, dataIndex: 'company'},
30855 {header: 'Price', renderer: 'usMoney', dataIndex: 'price'},
30856 {header: 'Change', renderer: change, dataIndex: 'change'},
30857 {header: '% Change', renderer: pctChange, dataIndex: 'pctChange'},
30858 {header: 'Last Updated', width: 85,
30859 renderer: Ext.util.Format.dateRenderer('m/d/Y'),
30860 dataIndex: 'lastChange'}
30867 autoExpandColumn: 'company', // match the id specified in the column model
30870 title:'Array Grid',
30872 text: 'Add Record',
30873 handler : function(){
30874 var defaultData = {
30876 company: 'New Company',
30877 lastChange: (new Date()).clearTime(),
30881 var recId = 3; // provide unique id
30882 var p = new store.recordType(defaultData, recId); // create new record
30883 grid.stopEditing();
30884 store.{@link #insert}(0, p); // insert a new record into the store (also see {@link #add})
30885 grid.startEditing(0, 0);
30890 * @property recordType
30894 if(this.recordType){
30896 * A {@link Ext.util.MixedCollection MixedCollection} containing the defined {@link Ext.data.Field Field}s
30897 * for the {@link Ext.data.Record Records} stored in this Store. Read-only.
30899 * @type Ext.util.MixedCollection
30901 this.fields = this.recordType.prototype.fields;
30903 this.modified = [];
30907 * @event datachanged
30908 * Fires when the data cache has changed in a bulk manner (e.g., it has been sorted, filtered, etc.) and a
30909 * widget that is using this Store as a Record cache should refresh its view.
30910 * @param {Store} this
30914 * @event metachange
30915 * Fires when this store's reader provides new metadata (fields). This is currently only supported for JsonReaders.
30916 * @param {Store} this
30917 * @param {Object} meta The JSON metadata
30922 * Fires when Records have been {@link #add}ed to the Store
30923 * @param {Store} this
30924 * @param {Ext.data.Record[]} records The array of Records added
30925 * @param {Number} index The index at which the record(s) were added
30930 * Fires when a Record has been {@link #remove}d from the Store
30931 * @param {Store} this
30932 * @param {Ext.data.Record} record The Record that was removed
30933 * @param {Number} index The index at which the record was removed
30938 * Fires when a Record has been updated
30939 * @param {Store} this
30940 * @param {Ext.data.Record} record The Record that was updated
30941 * @param {String} operation The update operation being performed. Value may be one of:
30943 Ext.data.Record.EDIT
30944 Ext.data.Record.REJECT
30945 Ext.data.Record.COMMIT
30951 * Fires when the data cache has been cleared.
30952 * @param {Store} this
30953 * @param {Record[]} The records that were cleared.
30958 * <p>Fires if an exception occurs in the Proxy during a remote request.
30959 * This event is relayed through the corresponding {@link Ext.data.DataProxy}.
30960 * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
30961 * for additional details.
30962 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
30967 * @event beforeload
30968 * Fires before a request is made for a new data object. If the beforeload handler returns
30969 * <tt>false</tt> the {@link #load} action will be canceled.
30970 * @param {Store} this
30971 * @param {Object} options The loading options that were specified (see {@link #load} for details)
30976 * Fires after a new set of Records has been loaded.
30977 * @param {Store} this
30978 * @param {Ext.data.Record[]} records The Records that were loaded
30979 * @param {Object} options The loading options that were specified (see {@link #load} for details)
30983 * @event loadexception
30984 * <p>This event is <b>deprecated</b> in favor of the catch-all <b><code>{@link #exception}</code></b>
30985 * event instead.</p>
30986 * <p>This event is relayed through the corresponding {@link Ext.data.DataProxy}.
30987 * See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
30988 * for additional details.
30989 * @param {misc} misc See {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#loadexception loadexception}
30994 * @event beforewrite
30995 * @param {Ext.data.Store} store
30996 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
30997 * @param {Record/Array[Record]} rs
30998 * @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)
30999 * @param {Object} arg The callback's arg object passed to the {@link #request} function
31004 * Fires if the server returns 200 after an Ext.data.Api.actions CRUD action.
31005 * Success of the action is determined in the <code>result['successProperty']</code>property (<b>NOTE</b> for RESTful stores,
31006 * a simple 20x response is sufficient for the actions "destroy" and "update". The "create" action should should return 200 along with a database pk).
31007 * @param {Ext.data.Store} store
31008 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
31009 * @param {Object} result The 'data' picked-out out of the response for convenience.
31010 * @param {Ext.Direct.Transaction} res
31011 * @param {Record/Record[]} rs Store's records, the subject(s) of the write-action
31017 this.relayEvents(this.proxy, ['loadexception', 'exception']);
31019 // With a writer set for the Store, we want to listen to add/remove events to remotely create/destroy records.
31023 add: this.createRecords,
31024 remove: this.destroyRecord,
31025 update: this.updateRecord,
31026 clear: this.onClear
31030 this.sortToggle = {};
31031 if(this.sortField){
31032 this.setDefaultSort(this.sortField, this.sortDir);
31033 }else if(this.sortInfo){
31034 this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
31037 Ext.data.Store.superclass.constructor.call(this);
31040 this.storeId = this.id;
31044 Ext.StoreMgr.register(this);
31046 if(this.inlineData){
31047 this.loadData(this.inlineData);
31048 delete this.inlineData;
31049 }else if(this.autoLoad){
31050 this.load.defer(10, this, [
31051 typeof this.autoLoad == 'object' ?
31052 this.autoLoad : undefined]);
31055 Ext.extend(Ext.data.Store, Ext.util.Observable, {
31057 * @cfg {String} storeId If passed, the id to use to register with the <b>{@link Ext.StoreMgr StoreMgr}</b>.
31058 * <p><b>Note</b>: if a (deprecated) <tt>{@link #id}</tt> is specified it will supersede the <tt>storeId</tt>
31062 * @cfg {String} url If a <tt>{@link #proxy}</tt> is not specified the <tt>url</tt> will be used to
31063 * implicitly configure a {@link Ext.data.HttpProxy HttpProxy} if an <tt>url</tt> is specified.
31064 * Typically this option, or the <code>{@link #data}</code> option will be specified.
31067 * @cfg {Boolean/Object} autoLoad If <tt>{@link #data}</tt> is not specified, and if <tt>autoLoad</tt>
31068 * is <tt>true</tt> or an <tt>Object</tt>, this store's {@link #load} method is automatically called
31069 * after creation. If the value of <tt>autoLoad</tt> is an <tt>Object</tt>, this <tt>Object</tt> will
31070 * be passed to the store's {@link #load} method.
31073 * @cfg {Ext.data.DataProxy} proxy The {@link Ext.data.DataProxy DataProxy} object which provides
31074 * access to a data object. See <code>{@link #url}</code>.
31077 * @cfg {Array} data An inline data object readable by the <code>{@link #reader}</code>.
31078 * Typically this option, or the <code>{@link #url}</code> option will be specified.
31081 * @cfg {Ext.data.DataReader} reader The {@link Ext.data.DataReader Reader} object which processes the
31082 * data object and returns an Array of {@link Ext.data.Record} objects which are cached keyed by their
31083 * <b><tt>{@link Ext.data.Record#id id}</tt></b> property.
31086 * @cfg {Ext.data.DataWriter} writer
31087 * <p>The {@link Ext.data.DataWriter Writer} object which processes a record object for being written
31088 * to the server-side database.</p>
31089 * <br><p>When a writer is installed into a Store the {@link #add}, {@link #remove}, and {@link #update}
31090 * events on the store are monitored in order to remotely {@link #createRecords create records},
31091 * {@link #destroyRecord destroy records}, or {@link #updateRecord update records}.</p>
31092 * <br><p>The proxy for this store will relay any {@link #writexception} events to this store.</p>
31093 * <br><p>Sample implementation:
31095 var writer = new {@link Ext.data.JsonWriter}({
31097 writeAllFields: true // write all fields, not just those that changed
31100 // Typical Store collecting the Proxy, Reader and Writer together.
31101 var store = new Ext.data.Store({
31106 writer: writer, // <-- plug a DataWriter into the store just as you would a Reader
31107 paramsAsHash: true,
31108 autoSave: false // <-- false to delay executing create, update, destroy requests
31109 // until specifically told to do so.
31111 * </code></pre></p>
31113 writer : undefined,
31115 * @cfg {Object} baseParams
31116 * <p>An object containing properties which are to be sent as parameters
31117 * for <i>every</i> HTTP request.</p>
31118 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
31119 * <p><b>Note</b>: <code>baseParams</code> may be superseded by any <code>params</code>
31120 * specified in a <code>{@link #load}</code> request, see <code>{@link #load}</code>
31121 * for more details.</p>
31122 * This property may be modified after creation using the <code>{@link #setBaseParam}</code>
31127 * @cfg {Object} sortInfo A config object to specify the sort order in the request of a Store's
31128 * {@link #load} operation. Note that for local sorting, the <tt>direction</tt> property is
31129 * case-sensitive. See also {@link #remoteSort} and {@link #paramNames}.
31130 * For example:<pre><code>
31132 field: 'fieldName',
31133 direction: 'ASC' // or 'DESC' (case sensitive for local sorting)
31138 * @cfg {boolean} remoteSort <tt>true</tt> if sorting is to be handled by requesting the <tt>{@link #proxy Proxy}</tt>
31139 * to provide a refreshed version of the data object in sorted order, as opposed to sorting the Record cache
31140 * in place (defaults to <tt>false</tt>).
31141 * <p>If <tt>remoteSort</tt> is <tt>true</tt>, then clicking on a {@link Ext.grid.Column Grid Column}'s
31142 * {@link Ext.grid.Column#header header} causes the current page to be requested from the server appending
31143 * the following two parameters to the <b><tt>{@link #load params}</tt></b>:<div class="mdetail-params"><ul>
31144 * <li><b><tt>sort</tt></b> : String<p class="sub-desc">The <tt>name</tt> (as specified in the Record's
31145 * {@link Ext.data.Field Field definition}) of the field to sort on.</p></li>
31146 * <li><b><tt>dir</tt></b> : String<p class="sub-desc">The direction of the sort, 'ASC' or 'DESC' (case-sensitive).</p></li>
31149 remoteSort : false,
31152 * @cfg {Boolean} autoDestroy <tt>true</tt> to destroy the store when the component the store is bound
31153 * to is destroyed (defaults to <tt>false</tt>).
31154 * <p><b>Note</b>: this should be set to true when using stores that are bound to only 1 component.</p>
31156 autoDestroy : false,
31159 * @cfg {Boolean} pruneModifiedRecords <tt>true</tt> to clear all modified record information each time
31160 * the store is loaded or when a record is removed (defaults to <tt>false</tt>). See {@link #getModifiedRecords}
31161 * for the accessor method to retrieve the modified records.
31163 pruneModifiedRecords : false,
31166 * Contains the last options object used as the parameter to the {@link #load} method. See {@link #load}
31167 * for the details of what this may contain. This may be useful for accessing any params which were used
31168 * to load the current Record cache.
31171 lastOptions : null,
31174 * @cfg {Boolean} autoSave
31175 * <p>Defaults to <tt>true</tt> causing the store to automatically {@link #save} records to
31176 * the server when a record is modified (ie: becomes 'dirty'). Specify <tt>false</tt> to manually call {@link #save}
31177 * to send all modifiedRecords to the server.</p>
31178 * <br><p><b>Note</b>: each CRUD action will be sent as a separate request.</p>
31183 * @cfg {Boolean} batch
31184 * <p>Defaults to <tt>true</tt> (unless <code>{@link #restful}:true</code>). Multiple
31185 * requests for each CRUD action (CREATE, READ, UPDATE and DESTROY) will be combined
31186 * and sent as one transaction. Only applies when <code>{@link #autoSave}</code> is set
31187 * to <tt>false</tt>.</p>
31188 * <br><p>If Store is RESTful, the DataProxy is also RESTful, and a unique transaction is
31189 * generated for each record.</p>
31194 * @cfg {Boolean} restful
31195 * Defaults to <tt>false</tt>. Set to <tt>true</tt> to have the Store and the set
31196 * Proxy operate in a RESTful manner. The store will automatically generate GET, POST,
31197 * PUT and DELETE requests to the server. The HTTP method used for any given CRUD
31198 * action is described in {@link Ext.data.Api#restActions}. For additional information
31199 * see {@link Ext.data.DataProxy#restful}.
31200 * <p><b>Note</b>: if <code>{@link #restful}:true</code> <code>batch</code> will
31201 * internally be set to <tt>false</tt>.</p>
31206 * @cfg {Object} paramNames
31207 * <p>An object containing properties which specify the names of the paging and
31208 * sorting parameters passed to remote servers when loading blocks of data. By default, this
31209 * object takes the following form:</p><pre><code>
31211 start : 'start', // The parameter name which specifies the start row
31212 limit : 'limit', // The parameter name which specifies number of rows to return
31213 sort : 'sort', // The parameter name which specifies the column to sort on
31214 dir : 'dir' // The parameter name which specifies the sort direction
31217 * <p>The server must produce the requested data block upon receipt of these parameter names.
31218 * If different parameter names are required, this property can be overriden using a configuration
31220 * <p>A {@link Ext.PagingToolbar PagingToolbar} bound to this Store uses this property to determine
31221 * the parameter names to use in its {@link #load requests}.
31223 paramNames : undefined,
31226 * @cfg {Object} defaultParamNames
31227 * Provides the default values for the {@link #paramNames} property. To globally modify the parameters
31228 * for all stores, this object should be changed on the store prototype.
31230 defaultParamNames : {
31238 * Destroys the store.
31240 destroy : function(){
31241 if(!this.isDestroyed){
31243 Ext.StoreMgr.unregister(this);
31247 Ext.destroy(this.proxy);
31248 this.reader = this.writer = null;
31249 this.purgeListeners();
31250 this.isDestroyed = true;
31255 * Add Records to the Store and fires the {@link #add} event. To add Records
31256 * to the store from a remote source use <code>{@link #load}({add:true})</code>.
31257 * See also <code>{@link #recordType}</code> and <code>{@link #insert}</code>.
31258 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects
31259 * to add to the cache. See {@link #recordType}.
31261 add : function(records){
31262 records = [].concat(records);
31263 if(records.length < 1){
31266 for(var i = 0, len = records.length; i < len; i++){
31267 records[i].join(this);
31269 var index = this.data.length;
31270 this.data.addAll(records);
31272 this.snapshot.addAll(records);
31274 this.fireEvent('add', this, records, index);
31278 * (Local sort only) Inserts the passed Record into the Store at the index where it
31279 * should go based on the current sort information.
31280 * @param {Ext.data.Record} record
31282 addSorted : function(record){
31283 var index = this.findInsertIndex(record);
31284 this.insert(index, record);
31288 * Remove a Record from the Store and fires the {@link #remove} event.
31289 * @param {Ext.data.Record} record The Ext.data.Record object to remove from the cache.
31291 remove : function(record){
31292 var index = this.data.indexOf(record);
31295 this.data.removeAt(index);
31296 if(this.pruneModifiedRecords){
31297 this.modified.remove(record);
31300 this.snapshot.remove(record);
31302 this.fireEvent('remove', this, record, index);
31307 * Remove a Record from the Store at the specified index. Fires the {@link #remove} event.
31308 * @param {Number} index The index of the record to remove.
31310 removeAt : function(index){
31311 this.remove(this.getAt(index));
31315 * Remove all Records from the Store and fires the {@link #clear} event.
31317 removeAll : function(){
31319 this.each(function(rec){
31324 this.snapshot.clear();
31326 if(this.pruneModifiedRecords){
31327 this.modified = [];
31329 this.fireEvent('clear', this, items);
31333 onClear: function(store, records){
31334 Ext.each(records, function(rec, index){
31335 this.destroyRecord(this, rec, index);
31340 * Inserts Records into the Store at the given index and fires the {@link #add} event.
31341 * See also <code>{@link #add}</code> and <code>{@link #addSorted}</code>.
31342 * @param {Number} index The start index at which to insert the passed Records.
31343 * @param {Ext.data.Record[]} records An Array of Ext.data.Record objects to add to the cache.
31345 insert : function(index, records){
31346 records = [].concat(records);
31347 for(var i = 0, len = records.length; i < len; i++){
31348 this.data.insert(index, records[i]);
31349 records[i].join(this);
31351 this.fireEvent('add', this, records, index);
31355 * Get the index within the cache of the passed Record.
31356 * @param {Ext.data.Record} record The Ext.data.Record object to find.
31357 * @return {Number} The index of the passed Record. Returns -1 if not found.
31359 indexOf : function(record){
31360 return this.data.indexOf(record);
31364 * Get the index within the cache of the Record with the passed id.
31365 * @param {String} id The id of the Record to find.
31366 * @return {Number} The index of the Record. Returns -1 if not found.
31368 indexOfId : function(id){
31369 return this.data.indexOfKey(id);
31373 * Get the Record with the specified id.
31374 * @param {String} id The id of the Record to find.
31375 * @return {Ext.data.Record} The Record with the passed id. Returns undefined if not found.
31377 getById : function(id){
31378 return this.data.key(id);
31382 * Get the Record at the specified index.
31383 * @param {Number} index The index of the Record to find.
31384 * @return {Ext.data.Record} The Record at the passed index. Returns undefined if not found.
31386 getAt : function(index){
31387 return this.data.itemAt(index);
31391 * Returns a range of Records between specified indices.
31392 * @param {Number} startIndex (optional) The starting index (defaults to 0)
31393 * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
31394 * @return {Ext.data.Record[]} An array of Records
31396 getRange : function(start, end){
31397 return this.data.getRange(start, end);
31401 storeOptions : function(o){
31402 o = Ext.apply({}, o);
31405 this.lastOptions = o;
31409 clearData: function(){
31410 this.data.each(function(rec) {
31417 * <p>Loads the Record cache from the configured <tt>{@link #proxy}</tt> using the configured <tt>{@link #reader}</tt>.</p>
31418 * <br><p>Notes:</p><div class="mdetail-params"><ul>
31419 * <li><b><u>Important</u></b>: loading is asynchronous! This call will return before the new data has been
31420 * loaded. To perform any post-processing where information from the load call is required, specify
31421 * the <tt>callback</tt> function to be called, or use a {@link Ext.util.Observable#listeners a 'load' event handler}.</li>
31422 * <li>If using {@link Ext.PagingToolbar remote paging}, the first load call must specify the <tt>start</tt> and <tt>limit</tt>
31423 * properties in the <code>options.params</code> property to establish the initial position within the
31424 * dataset, and the number of Records to cache on each read from the Proxy.</li>
31425 * <li>If using {@link #remoteSort remote sorting}, the configured <code>{@link #sortInfo}</code>
31426 * will be automatically included with the posted parameters according to the specified
31427 * <code>{@link #paramNames}</code>.</li>
31429 * @param {Object} options An object containing properties which control loading options:<ul>
31430 * <li><b><tt>params</tt></b> :Object<div class="sub-desc"><p>An object containing properties to pass as HTTP
31431 * parameters to a remote data source. <b>Note</b>: <code>params</code> will override any
31432 * <code>{@link #baseParams}</code> of the same name.</p>
31433 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
31434 * <li><b><tt>callback</tt></b> : Function<div class="sub-desc"><p>A function to be called after the Records
31435 * have been loaded. The <tt>callback</tt> is called after the load event and is passed the following arguments:<ul>
31436 * <li><tt>r</tt> : Ext.data.Record[]</li>
31437 * <li><tt>options</tt>: Options object from the load call</li>
31438 * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div></li>
31439 * <li><b><tt>scope</tt></b> : Object<div class="sub-desc"><p>Scope with which to call the callback (defaults
31440 * to the Store object)</p></div></li>
31441 * <li><b><tt>add</tt></b> : Boolean<div class="sub-desc"><p>Indicator to append loaded records rather than
31442 * replace the current cache. <b>Note</b>: see note for <tt>{@link #loadData}</tt></p></div></li>
31444 * @return {Boolean} If the <i>developer</i> provided <tt>{@link #beforeload}</tt> event handler returns
31445 * <tt>false</tt>, the load call will abort and will return <tt>false</tt>; otherwise will return <tt>true</tt>.
31447 load : function(options) {
31448 options = options || {};
31449 this.storeOptions(options);
31450 if(this.sortInfo && this.remoteSort){
31451 var pn = this.paramNames;
31452 options.params = options.params || {};
31453 options.params[pn.sort] = this.sortInfo.field;
31454 options.params[pn.dir] = this.sortInfo.direction;
31457 return this.execute('read', null, options); // <-- null represents rs. No rs for load actions.
31459 this.handleException(e);
31465 * updateRecord Should not be used directly. This method will be called automatically if a Writer is set.
31466 * Listens to 'update' event.
31467 * @param {Object} store
31468 * @param {Object} record
31469 * @param {Object} action
31472 updateRecord : function(store, record, action) {
31473 if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid))) {
31479 * Should not be used directly. Store#add will call this automatically if a Writer is set
31480 * @param {Object} store
31481 * @param {Object} rs
31482 * @param {Object} index
31485 createRecords : function(store, rs, index) {
31486 for (var i = 0, len = rs.length; i < len; i++) {
31487 if (rs[i].phantom && rs[i].isValid()) {
31488 rs[i].markDirty(); // <-- Mark new records dirty
31489 this.modified.push(rs[i]); // <-- add to modified
31492 if (this.autoSave === true) {
31498 * Destroys a record or records. Should not be used directly. It's called by Store#remove if a Writer is set.
31499 * @param {Store} this
31500 * @param {Ext.data.Record/Ext.data.Record[]}
31501 * @param {Number} index
31504 destroyRecord : function(store, record, index) {
31505 if (this.modified.indexOf(record) != -1) { // <-- handled already if @cfg pruneModifiedRecords == true
31506 this.modified.remove(record);
31508 if (!record.phantom) {
31509 this.removed.push(record);
31511 // since the record has already been removed from the store but the server request has not yet been executed,
31512 // must keep track of the last known index this record existed. If a server error occurs, the record can be
31513 // put back into the store. @see Store#createCallback where the record is returned when response status === false
31514 record.lastIndex = index;
31516 if (this.autoSave === true) {
31523 * This method should generally not be used directly. This method is called internally
31524 * by {@link #load}, or if a Writer is set will be called automatically when {@link #add},
31525 * {@link #remove}, or {@link #update} events fire.
31526 * @param {String} action Action name ('read', 'create', 'update', or 'destroy')
31527 * @param {Record/Record[]} rs
31528 * @param {Object} options
31532 execute : function(action, rs, options) {
31533 // blow up if action not Ext.data.CREATE, READ, UPDATE, DESTROY
31534 if (!Ext.data.Api.isAction(action)) {
31535 throw new Ext.data.Api.Error('execute', action);
31537 // make sure options has a params key
31538 options = Ext.applyIf(options||{}, {
31542 // have to separate before-events since load has a different signature than create,destroy and save events since load does not
31543 // include the rs (record resultset) parameter. Capture return values from the beforeaction into doRequest flag.
31544 var doRequest = true;
31546 if (action === 'read') {
31547 Ext.applyIf(options.params, this.baseParams);
31548 doRequest = this.fireEvent('beforeload', this, options);
31551 // if Writer is configured as listful, force single-record rs to be [{}] instead of {}
31552 // TODO Move listful rendering into DataWriter where the @cfg is defined. Should be easy now.
31553 if (this.writer.listful === true && this.restful !== true) {
31554 rs = (Ext.isArray(rs)) ? rs : [rs];
31556 // if rs has just a single record, shift it off so that Writer writes data as '{}' rather than '[{}]'
31557 else if (Ext.isArray(rs) && rs.length == 1) {
31560 // Write the action to options.params
31561 if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
31562 this.writer.write(action, options.params, rs);
31565 if (doRequest !== false) {
31566 // Send request to proxy.
31567 var params = Ext.apply({}, options.params, this.baseParams);
31568 if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
31569 params.xaction = action; // <-- really old, probaby unecessary.
31571 // Note: Up until this point we've been dealing with 'action' as a key from Ext.data.Api.actions.
31572 // We'll flip it now and send the value into DataProxy#request, since it's the value which maps to
31573 // the user's configured DataProxy#api
31574 this.proxy.request(Ext.data.Api.actions[action], rs, params, this.reader, this.createCallback(action, rs), this, options);
31580 * Saves all pending changes to the store. If the commensurate Ext.data.Api.actions action is not configured, then
31581 * the configured <code>{@link #url}</code> will be used.
31584 * --------------- --------------------
31585 * removed records Ext.data.Api.actions.destroy
31586 * phantom records Ext.data.Api.actions.create
31587 * {@link #getModifiedRecords modified records} Ext.data.Api.actions.update
31589 * @TODO: Create extensions of Error class and send associated Record with thrown exceptions.
31590 * e.g.: Ext.data.DataReader.Error or Ext.data.Error or Ext.data.DataProxy.Error, etc.
31592 save : function() {
31593 if (!this.writer) {
31594 throw new Ext.data.Store.Error('writer-undefined');
31597 // DESTROY: First check for removed records. Records in this.removed are guaranteed non-phantoms. @see Store#remove
31598 if (this.removed.length) {
31599 this.doTransaction('destroy', this.removed);
31602 // Check for modified records. Use a copy so Store#rejectChanges will work if server returns error.
31603 var rs = [].concat(this.getModifiedRecords());
31604 if (!rs.length) { // Bail-out if empty...
31608 // CREATE: Next check for phantoms within rs. splice-off and execute create.
31610 for (var i = rs.length-1; i >= 0; i--) {
31611 if (rs[i].phantom === true) {
31612 var rec = rs.splice(i, 1).shift();
31613 if (rec.isValid()) {
31614 phantoms.push(rec);
31616 } else if (!rs[i].isValid()) { // <-- while we're here, splice-off any !isValid real records
31620 // If we have valid phantoms, create them...
31621 if (phantoms.length) {
31622 this.doTransaction('create', phantoms);
31625 // UPDATE: And finally, if we're still here after splicing-off phantoms and !isValid real records, update the rest...
31627 this.doTransaction('update', rs);
31632 // private. Simply wraps call to Store#execute in try/catch. Defers to Store#handleException on error. Loops if batch: false
31633 doTransaction : function(action, rs) {
31634 function transaction(records) {
31636 this.execute(action, records);
31638 this.handleException(e);
31641 if (this.batch === false) {
31642 for (var i = 0, len = rs.length; i < len; i++) {
31643 transaction.call(this, rs[i]);
31646 transaction.call(this, rs);
31650 // @private callback-handler for remote CRUD actions
31651 // Do not override -- override loadRecords, onCreateRecords, onDestroyRecords and onUpdateRecords instead.
31652 createCallback : function(action, rs) {
31653 var actions = Ext.data.Api.actions;
31654 return (action == 'read') ? this.loadRecords : function(data, response, success) {
31655 // calls: onCreateRecords | onUpdateRecords | onDestroyRecords
31656 this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
31657 // If success === false here, exception will have been called in DataProxy
31658 if (success === true) {
31659 this.fireEvent('write', this, action, data, response, rs);
31664 // Clears records from modified array after an exception event.
31665 // NOTE: records are left marked dirty. Do we want to commit them even though they were not updated/realized?
31666 // TODO remove this method?
31667 clearModified : function(rs) {
31668 if (Ext.isArray(rs)) {
31669 for (var n=rs.length-1;n>=0;n--) {
31670 this.modified.splice(this.modified.indexOf(rs[n]), 1);
31673 this.modified.splice(this.modified.indexOf(rs), 1);
31677 // remap record ids in MixedCollection after records have been realized. @see Store#onCreateRecords, @see DataReader#realize
31678 reMap : function(record) {
31679 if (Ext.isArray(record)) {
31680 for (var i = 0, len = record.length; i < len; i++) {
31681 this.reMap(record[i]);
31684 delete this.data.map[record._phid];
31685 this.data.map[record.id] = record;
31686 var index = this.data.keys.indexOf(record._phid);
31687 this.data.keys.splice(index, 1, record.id);
31688 delete record._phid;
31692 // @protected onCreateRecord proxy callback for create action
31693 onCreateRecords : function(success, rs, data) {
31694 if (success === true) {
31696 this.reader.realize(rs, data);
31700 this.handleException(e);
31701 if (Ext.isArray(rs)) {
31702 // Recurse to run back into the try {}. DataReader#realize splices-off the rs until empty.
31703 this.onCreateRecords(success, rs, data);
31709 // @protected, onUpdateRecords proxy callback for update action
31710 onUpdateRecords : function(success, rs, data) {
31711 if (success === true) {
31713 this.reader.update(rs, data);
31715 this.handleException(e);
31716 if (Ext.isArray(rs)) {
31717 // Recurse to run back into the try {}. DataReader#update splices-off the rs until empty.
31718 this.onUpdateRecords(success, rs, data);
31724 // @protected onDestroyRecords proxy callback for destroy action
31725 onDestroyRecords : function(success, rs, data) {
31726 // splice each rec out of this.removed
31727 rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
31728 for (var i=0,len=rs.length;i<len;i++) {
31729 this.removed.splice(this.removed.indexOf(rs[i]), 1);
31731 if (success === false) {
31732 // put records back into store if remote destroy fails.
31733 // @TODO: Might want to let developer decide.
31734 for (i=rs.length-1;i>=0;i--) {
31735 this.insert(rs[i].lastIndex, rs[i]); // <-- lastIndex set in Store#destroyRecord
31740 // protected handleException. Possibly temporary until Ext framework has an exception-handler.
31741 handleException : function(e) {
31742 // @see core/Error.js
31743 Ext.handleError(e);
31747 * <p>Reloads the Record cache from the configured Proxy using the configured {@link Ext.data.Reader Reader} and
31748 * the options from the last load operation performed.</p>
31749 * <p><b>Note</b>: see the Important note in {@link #load}.</p>
31750 * @param {Object} options (optional) An <tt>Object</tt> containing {@link #load loading options} which may
31751 * override the options used in the last {@link #load} operation. See {@link #load} for details (defaults to
31752 * <tt>null</tt>, in which case the {@link #lastOptions} are used).
31754 reload : function(options){
31755 this.load(Ext.applyIf(options||{}, this.lastOptions));
31759 // Called as a callback by the Reader during a load operation.
31760 loadRecords : function(o, options, success){
31761 if(!o || success === false){
31762 if(success !== false){
31763 this.fireEvent('load', this, [], options);
31765 if(options.callback){
31766 options.callback.call(options.scope || this, [], options, false, o);
31770 var r = o.records, t = o.totalRecords || r.length;
31771 if(!options || options.add !== true){
31772 if(this.pruneModifiedRecords){
31773 this.modified = [];
31775 for(var i = 0, len = r.length; i < len; i++){
31779 this.data = this.snapshot;
31780 delete this.snapshot;
31783 this.data.addAll(r);
31784 this.totalLength = t;
31786 this.fireEvent('datachanged', this);
31788 this.totalLength = Math.max(t, this.data.length+r.length);
31791 this.fireEvent('load', this, r, options);
31792 if(options.callback){
31793 options.callback.call(options.scope || this, r, options, true);
31798 * Loads data from a passed data block and fires the {@link #load} event. A {@link Ext.data.Reader Reader}
31799 * which understands the format of the data must have been configured in the constructor.
31800 * @param {Object} data The data block from which to read the Records. The format of the data expected
31801 * is dependent on the type of {@link Ext.data.Reader Reader} that is configured and should correspond to
31802 * that {@link Ext.data.Reader Reader}'s <tt>{@link Ext.data.Reader#readRecords}</tt> parameter.
31803 * @param {Boolean} append (Optional) <tt>true</tt> to append the new Records rather the default to replace
31804 * the existing cache.
31805 * <b>Note</b>: that Records in a Store are keyed by their {@link Ext.data.Record#id id}, so added Records
31806 * with ids which are already present in the Store will <i>replace</i> existing Records. Only Records with
31807 * new, unique ids will be added.
31809 loadData : function(o, append){
31810 var r = this.reader.readRecords(o);
31811 this.loadRecords(r, {add: append}, true);
31815 * Gets the number of cached records.
31816 * <p>If using paging, this may not be the total size of the dataset. If the data object
31817 * used by the Reader contains the dataset size, then the {@link #getTotalCount} function returns
31818 * the dataset size. <b>Note</b>: see the Important note in {@link #load}.</p>
31819 * @return {Number} The number of Records in the Store's cache.
31821 getCount : function(){
31822 return this.data.length || 0;
31826 * Gets the total number of records in the dataset as returned by the server.
31827 * <p>If using paging, for this to be accurate, the data object used by the {@link #reader Reader}
31828 * must contain the dataset size. For remote data sources, the value for this property
31829 * (<tt>totalProperty</tt> for {@link Ext.data.JsonReader JsonReader},
31830 * <tt>totalRecords</tt> for {@link Ext.data.XmlReader XmlReader}) shall be returned by a query on the server.
31831 * <b>Note</b>: see the Important note in {@link #load}.</p>
31832 * @return {Number} The number of Records as specified in the data object passed to the Reader
31834 * <p><b>Note</b>: this value is not updated when changing the contents of the Store locally.</p>
31836 getTotalCount : function(){
31837 return this.totalLength || 0;
31841 * Returns an object describing the current sort state of this Store.
31842 * @return {Object} The sort state of the Store. An object with two properties:<ul>
31843 * <li><b>field : String<p class="sub-desc">The name of the field by which the Records are sorted.</p></li>
31844 * <li><b>direction : String<p class="sub-desc">The sort order, 'ASC' or 'DESC' (case-sensitive).</p></li>
31846 * See <tt>{@link #sortInfo}</tt> for additional details.
31848 getSortState : function(){
31849 return this.sortInfo;
31853 applySort : function(){
31854 if(this.sortInfo && !this.remoteSort){
31855 var s = this.sortInfo, f = s.field;
31856 this.sortData(f, s.direction);
31861 sortData : function(f, direction){
31862 direction = direction || 'ASC';
31863 var st = this.fields.get(f).sortType;
31864 var fn = function(r1, r2){
31865 var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
31866 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
31868 this.data.sort(direction, fn);
31869 if(this.snapshot && this.snapshot != this.data){
31870 this.snapshot.sort(direction, fn);
31875 * Sets the default sort column and order to be used by the next {@link #load} operation.
31876 * @param {String} fieldName The name of the field to sort by.
31877 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
31879 setDefaultSort : function(field, dir){
31880 dir = dir ? dir.toUpperCase() : 'ASC';
31881 this.sortInfo = {field: field, direction: dir};
31882 this.sortToggle[field] = dir;
31886 * Sort the Records.
31887 * If remote sorting is used, the sort is performed on the server, and the cache is reloaded. If local
31888 * sorting is used, the cache is sorted internally. See also {@link #remoteSort} and {@link #paramNames}.
31889 * @param {String} fieldName The name of the field to sort by.
31890 * @param {String} dir (optional) The sort order, 'ASC' or 'DESC' (case-sensitive, defaults to <tt>'ASC'</tt>)
31892 sort : function(fieldName, dir){
31893 var f = this.fields.get(fieldName);
31898 if(this.sortInfo && this.sortInfo.field == f.name){ // toggle sort dir
31899 dir = (this.sortToggle[f.name] || 'ASC').toggle('ASC', 'DESC');
31904 var st = (this.sortToggle) ? this.sortToggle[f.name] : null;
31905 var si = (this.sortInfo) ? this.sortInfo : null;
31907 this.sortToggle[f.name] = dir;
31908 this.sortInfo = {field: f.name, direction: dir};
31909 if(!this.remoteSort){
31911 this.fireEvent('datachanged', this);
31913 if (!this.load(this.lastOptions)) {
31915 this.sortToggle[f.name] = st;
31918 this.sortInfo = si;
31925 * Calls the specified function for each of the {@link Ext.data.Record Records} in the cache.
31926 * @param {Function} fn The function to call. The {@link Ext.data.Record Record} is passed as the first parameter.
31927 * Returning <tt>false</tt> aborts and exits the iteration.
31928 * @param {Object} scope (optional) The scope in which to call the function (defaults to the {@link Ext.data.Record Record}).
31930 each : function(fn, scope){
31931 this.data.each(fn, scope);
31935 * Gets all {@link Ext.data.Record records} modified since the last commit. Modified records are
31936 * persisted across load operations (e.g., during paging). <b>Note</b>: deleted records are not
31937 * included. See also <tt>{@link #pruneModifiedRecords}</tt> and
31938 * {@link Ext.data.Record}<tt>{@link Ext.data.Record#markDirty markDirty}.</tt>.
31939 * @return {Ext.data.Record[]} An array of {@link Ext.data.Record Records} containing outstanding
31940 * modifications. To obtain modified fields within a modified record see
31941 *{@link Ext.data.Record}<tt>{@link Ext.data.Record#modified modified}.</tt>.
31943 getModifiedRecords : function(){
31944 return this.modified;
31948 createFilterFn : function(property, value, anyMatch, caseSensitive){
31949 if(Ext.isEmpty(value, false)){
31952 value = this.data.createValueMatcher(value, anyMatch, caseSensitive);
31953 return function(r){
31954 return value.test(r.data[property]);
31959 * Sums the value of <tt>property</tt> for each {@link Ext.data.Record record} between <tt>start</tt>
31960 * and <tt>end</tt> and returns the result.
31961 * @param {String} property A field in each record
31962 * @param {Number} start (optional) The record index to start at (defaults to <tt>0</tt>)
31963 * @param {Number} end (optional) The last record index to include (defaults to length - 1)
31964 * @return {Number} The sum
31966 sum : function(property, start, end){
31967 var rs = this.data.items, v = 0;
31968 start = start || 0;
31969 end = (end || end === 0) ? end : rs.length-1;
31971 for(var i = start; i <= end; i++){
31972 v += (rs[i].data[property] || 0);
31978 * Filter the {@link Ext.data.Record records} by a specified property.
31979 * @param {String} field A field on your records
31980 * @param {String/RegExp} value Either a string that the field should begin with, or a RegExp to test
31981 * against the field.
31982 * @param {Boolean} anyMatch (optional) <tt>true</tt> to match any part not just the beginning
31983 * @param {Boolean} caseSensitive (optional) <tt>true</tt> for case sensitive comparison
31985 filter : function(property, value, anyMatch, caseSensitive){
31986 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
31987 return fn ? this.filterBy(fn) : this.clearFilter();
31991 * Filter by a function. The specified function will be called for each
31992 * Record in this Store. If the function returns <tt>true</tt> the Record is included,
31993 * otherwise it is filtered out.
31994 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
31995 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
31996 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
31997 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
31999 * @param {Object} scope (optional) The scope of the function (defaults to this)
32001 filterBy : function(fn, scope){
32002 this.snapshot = this.snapshot || this.data;
32003 this.data = this.queryBy(fn, scope||this);
32004 this.fireEvent('datachanged', this);
32008 * Query the records by a specified property.
32009 * @param {String} field A field on your records
32010 * @param {String/RegExp} value Either a string that the field
32011 * should begin with, or a RegExp to test against the field.
32012 * @param {Boolean} anyMatch (optional) True to match any part not just the beginning
32013 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
32014 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
32016 query : function(property, value, anyMatch, caseSensitive){
32017 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
32018 return fn ? this.queryBy(fn) : this.data.clone();
32022 * Query the cached records in this Store using a filtering function. The specified function
32023 * will be called with each record in this Store. If the function returns <tt>true</tt> the record is
32024 * included in the results.
32025 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
32026 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
32027 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
32028 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
32030 * @param {Object} scope (optional) The scope of the function (defaults to this)
32031 * @return {MixedCollection} Returns an Ext.util.MixedCollection of the matched records
32033 queryBy : function(fn, scope){
32034 var data = this.snapshot || this.data;
32035 return data.filterBy(fn, scope||this);
32039 * Finds the index of the first matching record in this store by a specific property/value.
32040 * @param {String} property A property on your objects
32041 * @param {String/RegExp} value Either a string that the property value
32042 * should begin with, or a RegExp to test against the property.
32043 * @param {Number} startIndex (optional) The index to start searching at
32044 * @param {Boolean} anyMatch (optional) True to match any part of the string, not just the beginning
32045 * @param {Boolean} caseSensitive (optional) True for case sensitive comparison
32046 * @return {Number} The matched index or -1
32048 find : function(property, value, start, anyMatch, caseSensitive){
32049 var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
32050 return fn ? this.data.findIndexBy(fn, null, start) : -1;
32054 * Finds the index of the first matching record in this store by a specific property/value.
32055 * @param {String} property A property on your objects
32056 * @param {String/RegExp} value The value to match against
32057 * @param {Number} startIndex (optional) The index to start searching at
32058 * @return {Number} The matched index or -1
32060 findExact: function(property, value, start){
32061 return this.data.findIndexBy(function(rec){
32062 return rec.get(property) === value;
32067 * Find the index of the first matching Record in this Store by a function.
32068 * If the function returns <tt>true</tt> it is considered a match.
32069 * @param {Function} fn The function to be called. It will be passed the following parameters:<ul>
32070 * <li><b>record</b> : Ext.data.Record<p class="sub-desc">The {@link Ext.data.Record record}
32071 * to test for filtering. Access field values using {@link Ext.data.Record#get}.</p></li>
32072 * <li><b>id</b> : Object<p class="sub-desc">The ID of the Record passed.</p></li>
32074 * @param {Object} scope (optional) The scope of the function (defaults to this)
32075 * @param {Number} startIndex (optional) The index to start searching at
32076 * @return {Number} The matched index or -1
32078 findBy : function(fn, scope, start){
32079 return this.data.findIndexBy(fn, scope, start);
32083 * Collects unique values for a particular dataIndex from this store.
32084 * @param {String} dataIndex The property to collect
32085 * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
32086 * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
32087 * @return {Array} An array of the unique values
32089 collect : function(dataIndex, allowNull, bypassFilter){
32090 var d = (bypassFilter === true && this.snapshot) ?
32091 this.snapshot.items : this.data.items;
32092 var v, sv, r = [], l = {};
32093 for(var i = 0, len = d.length; i < len; i++){
32094 v = d[i].data[dataIndex];
32096 if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
32105 * Revert to a view of the Record cache with no filtering applied.
32106 * @param {Boolean} suppressEvent If <tt>true</tt> the filter is cleared silently without firing the
32107 * {@link #datachanged} event.
32109 clearFilter : function(suppressEvent){
32110 if(this.isFiltered()){
32111 this.data = this.snapshot;
32112 delete this.snapshot;
32113 if(suppressEvent !== true){
32114 this.fireEvent('datachanged', this);
32120 * Returns true if this store is currently filtered
32121 * @return {Boolean}
32123 isFiltered : function(){
32124 return this.snapshot && this.snapshot != this.data;
32128 afterEdit : function(record){
32129 if(this.modified.indexOf(record) == -1){
32130 this.modified.push(record);
32132 this.fireEvent('update', this, record, Ext.data.Record.EDIT);
32136 afterReject : function(record){
32137 this.modified.remove(record);
32138 this.fireEvent('update', this, record, Ext.data.Record.REJECT);
32142 afterCommit : function(record){
32143 this.modified.remove(record);
32144 this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
32148 * Commit all Records with {@link #getModifiedRecords outstanding changes}. To handle updates for changes,
32149 * subscribe to the Store's {@link #update update event}, and perform updating when the third parameter is
32150 * Ext.data.Record.COMMIT.
32152 commitChanges : function(){
32153 var m = this.modified.slice(0);
32154 this.modified = [];
32155 for(var i = 0, len = m.length; i < len; i++){
32161 * {@link Ext.data.Record#reject Reject} outstanding changes on all {@link #getModifiedRecords modified records}.
32163 rejectChanges : function(){
32164 var m = this.modified.slice(0);
32165 this.modified = [];
32166 for(var i = 0, len = m.length; i < len; i++){
32169 var m = this.removed.slice(0).reverse();
32171 for(var i = 0, len = m.length; i < len; i++){
32172 this.insert(m[i].lastIndex||0, m[i]);
32178 onMetaChange : function(meta){
32179 this.recordType = this.reader.recordType;
32180 this.fields = this.recordType.prototype.fields;
32181 delete this.snapshot;
32182 if(this.reader.meta.sortInfo){
32183 this.sortInfo = this.reader.meta.sortInfo;
32184 }else if(this.sortInfo && !this.fields.get(this.sortInfo.field)){
32185 delete this.sortInfo;
32188 this.writer.meta = this.reader.meta;
32190 this.modified = [];
32191 this.fireEvent('metachange', this, this.reader.meta);
32195 findInsertIndex : function(record){
32196 this.suspendEvents();
32197 var data = this.data.clone();
32198 this.data.add(record);
32200 var index = this.data.indexOf(record);
32202 this.resumeEvents();
32207 * Set the value for a property name in this store's {@link #baseParams}. Usage:</p><pre><code>
32208 myStore.setBaseParam('foo', {bar:3});
32210 * @param {String} name Name of the property to assign
32211 * @param {Mixed} value Value to assign the <tt>name</tt>d property
32213 setBaseParam : function (name, value){
32214 this.baseParams = this.baseParams || {};
32215 this.baseParams[name] = value;
32219 Ext.reg('store', Ext.data.Store);
32222 * @class Ext.data.Store.Error
32223 * @extends Ext.Error
32224 * Store Error extension.
32225 * @param {String} name
32227 Ext.data.Store.Error = Ext.extend(Ext.Error, {
32228 name: 'Ext.data.Store'
32230 Ext.apply(Ext.data.Store.Error.prototype, {
32232 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
32237 * @class Ext.data.Field
32238 * <p>This class encapsulates the field definition information specified in the field definition objects
32239 * passed to {@link Ext.data.Record#create}.</p>
32240 * <p>Developers do not need to instantiate this class. Instances are created by {@link Ext.data.Record.create}
32241 * and cached in the {@link Ext.data.Record#fields fields} property of the created Record constructor's <b>prototype.</b></p>
32243 Ext.data.Field = function(config){
32244 if(typeof config == "string"){
32245 config = {name: config};
32247 Ext.apply(this, config);
32250 this.type = "auto";
32253 var st = Ext.data.SortTypes;
32254 // named sortTypes are supported, here we look them up
32255 if(typeof this.sortType == "string"){
32256 this.sortType = st[this.sortType];
32259 // set default sortType for strings and dates
32260 if(!this.sortType){
32263 this.sortType = st.asUCString;
32266 this.sortType = st.asDate;
32269 this.sortType = st.none;
32274 var stripRe = /[\$,%]/g;
32276 // prebuilt conversion function for this field, instead of
32277 // switching every time we're reading a value
32279 var cv, dateFormat = this.dateFormat;
32284 cv = function(v){ return v; };
32287 cv = function(v){ return (v === undefined || v === null) ? '' : String(v); };
32291 return v !== undefined && v !== null && v !== '' ?
32292 parseInt(String(v).replace(stripRe, ""), 10) : '';
32297 return v !== undefined && v !== null && v !== '' ?
32298 parseFloat(String(v).replace(stripRe, ""), 10) : '';
32303 cv = function(v){ return v === true || v === "true" || v == 1; };
32314 if(dateFormat == "timestamp"){
32315 return new Date(v*1000);
32317 if(dateFormat == "time"){
32318 return new Date(parseInt(v, 10));
32320 return Date.parseDate(v, dateFormat);
32322 var parsed = Date.parse(v);
32323 return parsed ? new Date(parsed) : null;
32332 Ext.data.Field.prototype = {
32334 * @cfg {String} name
32335 * The name by which the field is referenced within the Record. This is referenced by, for example,
32336 * the <tt>dataIndex</tt> property in column definition objects passed to {@link Ext.grid.ColumnModel}.
32337 * <p>Note: In the simplest case, if no properties other than <tt>name</tt> are required, a field
32338 * definition may consist of just a String for the field name.</p>
32341 * @cfg {String} type
32342 * (Optional) The data type for conversion to displayable value if <tt>{@link Ext.data.Field#convert convert}</tt>
32343 * has not been specified. Possible values are
32344 * <div class="mdetail-params"><ul>
32345 * <li>auto (Default, implies no conversion)</li>
32350 * <li>date</li></ul></div>
32353 * @cfg {Function} convert
32354 * (Optional) A function which converts the value provided by the Reader into an object that will be stored
32355 * in the Record. It is passed the following parameters:<div class="mdetail-params"><ul>
32356 * <li><b>v</b> : Mixed<div class="sub-desc">The data value as read by the Reader, if undefined will use
32357 * the configured <tt>{@link Ext.data.Field#defaultValue defaultValue}</tt>.</div></li>
32358 * <li><b>rec</b> : Mixed<div class="sub-desc">The data object containing the row as read by the Reader.
32359 * Depending on the Reader type, this could be an Array ({@link Ext.data.ArrayReader ArrayReader}), an object
32360 * ({@link Ext.data.JsonReader JsonReader}), or an XML element ({@link Ext.data.XMLReader XMLReader}).</div></li>
32363 // example of convert function
32364 function fullName(v, record){
32365 return record.name.last + ', ' + record.name.first;
32368 function location(v, record){
32369 return !record.city ? '' : (record.city + ', ' + record.state);
32372 var Dude = Ext.data.Record.create([
32373 {name: 'fullname', convert: fullName},
32374 {name: 'firstname', mapping: 'name.first'},
32375 {name: 'lastname', mapping: 'name.last'},
32376 {name: 'city', defaultValue: 'homeless'},
32378 {name: 'location', convert: location}
32381 // create the data store
32382 var store = new Ext.data.Store({
32383 reader: new Ext.data.JsonReader(
32387 totalProperty: 'total'
32395 name: { first: 'Fat', last: 'Albert' }
32396 // notice no city, state provided in data object
32399 name: { first: 'Barney', last: 'Rubble' },
32400 city: 'Bedrock', state: 'Stoneridge'
32403 name: { first: 'Cliff', last: 'Claven' },
32404 city: 'Boston', state: 'MA'
32410 * @cfg {String} dateFormat
32411 * (Optional) A format string for the {@link Date#parseDate Date.parseDate} function, or "timestamp" if the
32412 * value provided by the Reader is a UNIX timestamp, or "time" if the value provided by the Reader is a
32413 * javascript millisecond timestamp.
32417 * @cfg {Mixed} defaultValue
32418 * (Optional) The default value used <b>when a Record is being created by a {@link Ext.data.Reader Reader}</b>
32419 * when the item referenced by the <tt>{@link Ext.data.Field#mapping mapping}</tt> does not exist in the data
32420 * object (i.e. undefined). (defaults to "")
32424 * @cfg {String/Number} mapping
32425 * <p>(Optional) A path expression for use by the {@link Ext.data.DataReader} implementation
32426 * that is creating the {@link Ext.data.Record Record} to extract the Field value from the data object.
32427 * If the path expression is the same as the field name, the mapping may be omitted.</p>
32428 * <p>The form of the mapping expression depends on the Reader being used.</p>
32429 * <div class="mdetail-params"><ul>
32430 * <li>{@link Ext.data.JsonReader}<div class="sub-desc">The mapping is a string containing the javascript
32431 * 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>
32432 * <li>{@link Ext.data.XmlReader}<div class="sub-desc">The mapping is an {@link Ext.DomQuery} path to the data
32433 * item relative to the DOM element that represents the {@link Ext.data.XmlReader#record record}. Defaults to the field name.</div></li>
32434 * <li>{@link Ext.data.ArrayReader}<div class="sub-desc">The mapping is a number indicating the Array index
32435 * of the field's value. Defaults to the field specification's Array position.</div></li>
32437 * <p>If a more complex value extraction strategy is required, then configure the Field with a {@link #convert}
32438 * function. This is passed the whole row object, and may interrogate it in whatever way is necessary in order to
32439 * return the desired data.</p>
32443 * @cfg {Function} sortType
32444 * (Optional) A function which converts a Field's value to a comparable value in order to ensure
32445 * correct sort ordering. Predefined functions are provided in {@link Ext.data.SortTypes}. A custom
32446 * sort example:<pre><code>
32447 // current sort after sort we want
32448 // +-+------+ +-+------+
32449 // |1|First | |1|First |
32450 // |2|Last | |3|Second|
32451 // |3|Second| |2|Last |
32452 // +-+------+ +-+------+
32454 sortType: function(value) {
32455 switch (value.toLowerCase()) // native toLowerCase():
32457 case 'first': return 1;
32458 case 'second': return 2;
32466 * @cfg {String} sortDir
32467 * (Optional) Initial direction to sort (<tt>"ASC"</tt> or <tt>"DESC"</tt>). Defaults to
32472 * @cfg {Boolean} allowBlank
32473 * (Optional) Used for validating a {@link Ext.data.Record record}, defaults to <tt>true</tt>.
32474 * An empty value here will cause {@link Ext.data.Record}.{@link Ext.data.Record#isValid isValid}
32475 * to evaluate to <tt>false</tt>.
32479 * @class Ext.data.DataReader
\r
32480 * Abstract base class for reading structured data from a data source and converting
\r
32481 * it into an object containing {@link Ext.data.Record} objects and metadata for use
\r
32482 * by an {@link Ext.data.Store}. This class is intended to be extended and should not
\r
32483 * be created directly. For existing implementations, see {@link Ext.data.ArrayReader},
\r
32484 * {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}.
\r
32485 * @constructor Create a new DataReader
\r
32486 * @param {Object} meta Metadata configuration options (implementation-specific).
\r
32487 * @param {Array/Object} recordType
\r
32488 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
\r
32489 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
\r
32490 * constructor created using {@link Ext.data.Record#create}.</p>
\r
32492 Ext.data.DataReader = function(meta, recordType){
\r
32494 * This DataReader's configured metadata as passed to the constructor.
\r
32498 this.meta = meta;
\r
32500 * @cfg {Array/Object} fields
\r
32501 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
\r
32502 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
\r
32503 * constructor created from {@link Ext.data.Record#create}.</p>
\r
32505 this.recordType = Ext.isArray(recordType) ?
\r
32506 Ext.data.Record.create(recordType) : recordType;
\r
32508 // if recordType defined make sure extraction functions are defined
\r
32509 if (this.recordType){
\r
32510 this.buildExtractors();
\r
32514 Ext.data.DataReader.prototype = {
\r
32516 * @cfg {String} messageProperty [undefined] Optional name of a property within a server-response that represents a user-feedback message.
\r
32519 * Abstract method created in extension's buildExtractors impl.
\r
32521 getTotal: Ext.emptyFn,
\r
32523 * Abstract method created in extension's buildExtractors impl.
\r
32525 getRoot: Ext.emptyFn,
\r
32527 * Abstract method created in extension's buildExtractors impl.
\r
32529 getMessage: Ext.emptyFn,
\r
32531 * Abstract method created in extension's buildExtractors impl.
\r
32533 getSuccess: Ext.emptyFn,
\r
32535 * Abstract method created in extension's buildExtractors impl.
\r
32537 getId: Ext.emptyFn,
\r
32539 * Abstract method, overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
\r
32541 buildExtractors : Ext.emptyFn,
\r
32543 * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
\r
32545 extractData : Ext.emptyFn,
\r
32547 * Abstract method overridden in DataReader extensions such as {@link Ext.data.JsonReader} and {@link Ext.data.XmlReader}
\r
32549 extractValues : Ext.emptyFn,
\r
32552 * Used for un-phantoming a record after a successful database insert. Sets the records pk along with new data from server.
\r
32553 * You <b>must</b> return at least the database pk using the idProperty defined in your DataReader configuration. The incoming
\r
32554 * data from server will be merged with the data in the local record.
\r
32555 * In addition, you <b>must</b> return record-data from the server in the same order received.
\r
32556 * Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be suppressed.
\r
32557 * @param {Record/Record[]} record The phantom record to be realized.
\r
32558 * @param {Object/Object[]} data The new record data to apply. Must include the primary-key from database defined in idProperty field.
\r
32560 realize: function(rs, data){
\r
32561 if (Ext.isArray(rs)) {
\r
32562 for (var i = rs.length - 1; i >= 0; i--) {
\r
32564 if (Ext.isArray(data)) {
\r
32565 this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
\r
32568 // weird...rs is an array but data isn't?? recurse but just send in the whole invalid data object.
\r
32569 // the else clause below will detect !this.isData and throw exception.
\r
32570 this.realize(rs.splice(i,1).shift(), data);
\r
32575 // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
\r
32576 if (Ext.isArray(data) && data.length == 1) {
\r
32577 data = data.shift();
\r
32579 if (!this.isData(data)) {
\r
32580 // TODO: Let exception-handler choose to commit or not rather than blindly rs.commit() here.
\r
32582 throw new Ext.data.DataReader.Error('realize', rs);
\r
32584 rs.phantom = false; // <-- That's what it's all about
\r
32585 rs._phid = rs.id; // <-- copy phantom-id -> _phid, so we can remap in Store#onCreateRecords
\r
32586 rs.id = this.getId(data);
\r
32593 * Used for updating a non-phantom or "real" record's data with fresh data from server after remote-save.
\r
32594 * If returning data from multiple-records after a batch-update, you <b>must</b> return record-data from the server in
\r
32595 * the same order received. Will perform a commit as well, un-marking dirty-fields. Store's "update" event will be
\r
32596 * suppressed as the record receives fresh new data-hash
\r
32597 * @param {Record/Record[]} rs
\r
32598 * @param {Object/Object[]} data
\r
32600 update : function(rs, data) {
\r
32601 if (Ext.isArray(rs)) {
\r
32602 for (var i=rs.length-1; i >= 0; i--) {
\r
32603 if (Ext.isArray(data)) {
\r
32604 this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
\r
32607 // weird...rs is an array but data isn't?? recurse but just send in the whole data object.
\r
32608 // the else clause below will detect !this.isData and throw exception.
\r
32609 this.update(rs.splice(i,1).shift(), data);
\r
32614 // If rs is NOT an array but data IS, see if data contains just 1 record. If so extract it and carry on.
\r
32615 if (Ext.isArray(data) && data.length == 1) {
\r
32616 data = data.shift();
\r
32618 if (this.isData(data)) {
\r
32619 rs.data = Ext.apply(rs.data, data);
\r
32626 * Returns true if the supplied data-hash <b>looks</b> and quacks like data. Checks to see if it has a key
\r
32627 * corresponding to idProperty defined in your DataReader config containing non-empty pk.
\r
32628 * @param {Object} data
\r
32629 * @return {Boolean}
\r
32631 isData : function(data) {
\r
32632 return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
\r
32635 // private function a store will createSequence upon
\r
32636 onMetaChange : function(meta){
\r
32638 this.meta = meta;
\r
32639 this.recordType = Ext.data.Record.create(meta.fields);
\r
32640 this.buildExtractors();
\r
32645 * @class Ext.data.DataReader.Error
\r
32646 * @extends Ext.Error
\r
32647 * General error class for Ext.data.DataReader
\r
32649 Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
\r
32650 constructor : function(message, arg) {
\r
32652 Ext.Error.call(this, message);
\r
32654 name: 'Ext.data.DataReader'
\r
32656 Ext.apply(Ext.data.DataReader.Error.prototype, {
\r
32658 'update': "#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",
\r
32659 'realize': "#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.",
\r
32660 'invalid-response': "#readResponse received an invalid response from the server."
\r
32666 * Ext.data.Response
\r
32667 * A generic response class to normalize response-handling internally to the framework.
\r
32668 * TODO move to own file, add to jsb.
\r
32670 Ext.data.Response = function(params) {
\r
32671 Ext.apply(this, params);
\r
32673 Ext.data.Response.prototype = {
\r
32675 * @property {String} action {@link Ext.data.Api#actions}
\r
32677 action: undefined,
\r
32679 * @property {Boolean} success
\r
32681 success : undefined,
\r
32683 * @property {String} message
\r
32685 message : undefined,
\r
32687 * @property {Array/Object} data
\r
32691 * @property {Object} raw The raw response returned from server-code
\r
32695 * @property {Ext.data.Record/Ext.data.Record[]} record(s) related to the Request action
\r
32697 records: undefined
\r
32700 * @class Ext.data.DataWriter
32701 * <p>Ext.data.DataWriter facilitates create, update, and destroy actions between
32702 * an Ext.data.Store and a server-side framework. A Writer enabled Store will
32703 * automatically manage the Ajax requests to perform CRUD actions on a Store.</p>
32704 * <p>Ext.data.DataWriter is an abstract base class which is intended to be extended
32705 * and should not be created directly. For existing implementations, see
32706 * {@link Ext.data.JsonWriter}.</p>
32707 * <p>Creating a writer is simple:</p>
32709 var writer = new Ext.data.JsonWriter();
32711 * <p>The proxy for a writer enabled store can be configured with a simple <code>url</code>:</p>
32713 // Create a standard HttpProxy instance.
32714 var proxy = new Ext.data.HttpProxy({
32715 url: 'app.php/users'
32718 * <p>For finer grained control, the proxy may also be configured with an <code>api</code>:</p>
32720 // Use the api specification
32721 var proxy = new Ext.data.HttpProxy({
32723 read : 'app.php/users/read',
32724 create : 'app.php/users/create',
32725 update : 'app.php/users/update',
32726 destroy : 'app.php/users/destroy'
32730 * <p>Creating a Writer enabled store:</p>
32732 var store = new Ext.data.Store({
32738 * @constructor Create a new DataWriter
32739 * @param {Object} meta Metadata configuration options (implementation-specific)
32740 * @param {Object} recordType Either an Array of field definition objects as specified
32741 * in {@link Ext.data.Record#create}, or an {@link Ext.data.Record} object created
32742 * using {@link Ext.data.Record#create}.
32744 Ext.data.DataWriter = function(config){
32745 Ext.apply(this, config);
32747 Ext.data.DataWriter.prototype = {
32750 * @cfg {Boolean} writeAllFields
32751 * <tt>false</tt> by default. Set <tt>true</tt> to have DataWriter return ALL fields of a modified
32752 * record -- not just those that changed.
32753 * <tt>false</tt> to have DataWriter only request modified fields from a record.
32755 writeAllFields : false,
32757 * @cfg {Boolean} listful
32758 * <tt>false</tt> by default. Set <tt>true</tt> to have the DataWriter <b>always</b> write HTTP params as a list,
32759 * even when acting upon a single record.
32761 listful : false, // <-- listful is actually not used internally here in DataWriter. @see Ext.data.Store#execute.
32764 * Writes data in preparation for server-write action. Simply proxies to DataWriter#update, DataWriter#create
32765 * DataWriter#destroy.
32766 * @param {String} action [CREATE|UPDATE|DESTROY]
32767 * @param {Object} params The params-hash to write-to
32768 * @param {Record/Record[]} rs The recordset write.
32770 write : function(action, params, rs) {
32772 renderer = action + 'Record';
32773 // TODO implement @cfg listful here
32774 if (Ext.isArray(rs)) {
32775 Ext.each(rs, function(rec){
32776 data.push(this[renderer](rec));
32779 else if (rs instanceof Ext.data.Record) {
32780 data = this[renderer](rs);
32782 this.render(action, rs, params, data);
32786 * abstract method meant to be overridden by all DataWriter extensions. It's the extension's job to apply the "data" to the "params".
32787 * The data-object provided to render is populated with data according to the meta-info defined in the user's DataReader config,
32788 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
32789 * @param {Record[]} rs Store recordset
32790 * @param {Object} params Http params to be sent to server.
32791 * @param {Object} data object populated according to DataReader meta-data.
32793 render : Ext.emptyFn,
32796 * @cfg {Function} updateRecord Abstract method that should be implemented in all subclasses
32797 * (e.g.: {@link Ext.data.JsonWriter#updateRecord JsonWriter.updateRecord}
32799 updateRecord : Ext.emptyFn,
32802 * @cfg {Function} createRecord Abstract method that should be implemented in all subclasses
32803 * (e.g.: {@link Ext.data.JsonWriter#createRecord JsonWriter.createRecord})
32805 createRecord : Ext.emptyFn,
32808 * @cfg {Function} destroyRecord Abstract method that should be implemented in all subclasses
32809 * (e.g.: {@link Ext.data.JsonWriter#destroyRecord JsonWriter.destroyRecord})
32811 destroyRecord : Ext.emptyFn,
32814 * Converts a Record to a hash.
32818 toHash : function(rec) {
32819 var map = rec.fields.map,
32821 raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
32823 Ext.iterate(raw, function(prop, value){
32824 if((m = map[prop])){
32825 data[m.mapping ? m.mapping : m.name] = value;
32828 // we don't want to write Ext auto-generated id to hash. Careful not to remove it on Models not having auto-increment pk though.
32829 // We can tell its not auto-increment if the user defined a DataReader field for it *and* that field's value is non-empty.
32830 // we could also do a RegExp here for the Ext.data.Record AUTO_ID prefix.
32832 if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
32833 delete data[this.meta.idProperty];
32836 data[this.meta.idProperty] = rec.id
32841 * @class Ext.data.DataProxy
\r
32842 * @extends Ext.util.Observable
\r
32843 * <p>Abstract base class for implementations which provide retrieval of unformatted data objects.
\r
32844 * This class is intended to be extended and should not be created directly. For existing implementations,
\r
32845 * see {@link Ext.data.DirectProxy}, {@link Ext.data.HttpProxy}, {@link Ext.data.ScriptTagProxy} and
\r
32846 * {@link Ext.data.MemoryProxy}.</p>
\r
32847 * <p>DataProxy implementations are usually used in conjunction with an implementation of {@link Ext.data.DataReader}
\r
32848 * (of the appropriate type which knows how to parse the data object) to provide a block of
\r
32849 * {@link Ext.data.Records} to an {@link Ext.data.Store}.</p>
\r
32850 * <p>The parameter to a DataProxy constructor may be an {@link Ext.data.Connection} or can also be the
\r
32851 * config object to an {@link Ext.data.Connection}.</p>
\r
32852 * <p>Custom implementations must implement either the <code><b>doRequest</b></code> method (preferred) or the
\r
32853 * <code>load</code> method (deprecated). See
\r
32854 * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#doRequest doRequest} or
\r
32855 * {@link Ext.data.HttpProxy}.{@link Ext.data.HttpProxy#load load} for additional details.</p>
\r
32856 * <p><b><u>Example 1</u></b></p>
\r
32858 proxy: new Ext.data.ScriptTagProxy({
\r
32859 {@link Ext.data.Connection#url url}: 'http://extjs.com/forum/topics-remote.php'
\r
32862 * <p><b><u>Example 2</u></b></p>
\r
32864 proxy : new Ext.data.HttpProxy({
\r
32865 {@link Ext.data.Connection#method method}: 'GET',
\r
32866 {@link Ext.data.HttpProxy#prettyUrls prettyUrls}: false,
\r
32867 {@link Ext.data.Connection#url url}: 'local/default.php', // see options parameter for {@link Ext.Ajax#request}
\r
32869 // all actions except the following will use above url
\r
32870 create : 'local/new.php',
\r
32871 update : 'local/update.php'
\r
32876 Ext.data.DataProxy = function(conn){
\r
32877 // make sure we have a config object here to support ux proxies.
\r
32878 // All proxies should now send config into superclass constructor.
\r
32879 conn = conn || {};
\r
32881 // This line caused a bug when people use custom Connection object having its own request method.
\r
32882 // http://extjs.com/forum/showthread.php?t=67194. Have to set DataProxy config
\r
32883 //Ext.applyIf(this, conn);
\r
32885 this.api = conn.api;
\r
32886 this.url = conn.url;
\r
32887 this.restful = conn.restful;
\r
32888 this.listeners = conn.listeners;
\r
32891 this.prettyUrls = conn.prettyUrls;
\r
32894 * @cfg {Object} api
\r
32895 * Specific urls to call on CRUD action methods "read", "create", "update" and "destroy".
\r
32896 * Defaults to:<pre><code>
\r
32898 read : undefined,
\r
32899 create : undefined,
\r
32900 update : undefined,
\r
32901 destroy : undefined
\r
32904 * <p>The url is built based upon the action being executed <tt>[load|create|save|destroy]</tt>
\r
32905 * using the commensurate <tt>{@link #api}</tt> property, or if undefined default to the
\r
32906 * configured {@link Ext.data.Store}.{@link Ext.data.Store#url url}.</p><br>
\r
32907 * <p>For example:</p>
\r
32910 load : '/controller/load',
\r
32911 create : '/controller/new', // Server MUST return idProperty of new record
\r
32912 save : '/controller/update',
\r
32913 destroy : '/controller/destroy_action'
\r
32916 // Alternatively, one can use the object-form to specify each API-action
\r
32918 load: {url: 'read.php', method: 'GET'},
\r
32919 create: 'create.php',
\r
32920 destroy: 'destroy.php',
\r
32921 save: 'update.php'
\r
32924 * <p>If the specific URL for a given CRUD action is undefined, the CRUD action request
\r
32925 * will be directed to the configured <tt>{@link Ext.data.Connection#url url}</tt>.</p>
\r
32926 * <br><p><b>Note</b>: To modify the URL for an action dynamically the appropriate API
\r
32927 * property should be modified before the action is requested using the corresponding before
\r
32928 * action event. For example to modify the URL associated with the load action:
\r
32930 // modify the url for the action
\r
32933 fn: function (store, options) {
\r
32934 // use <tt>{@link Ext.data.HttpProxy#setUrl setUrl}</tt> to change the URL for *just* this request.
\r
32935 store.proxy.setUrl('changed1.php');
\r
32937 // set optional second parameter to true to make this URL change
\r
32938 // permanent, applying this URL for all subsequent requests.
\r
32939 store.proxy.setUrl('changed1.php', true);
\r
32941 // Altering the proxy API should be done using the public
\r
32942 // method <tt>{@link Ext.data.DataProxy#setApi setApi}</tt>.
\r
32943 store.proxy.setApi('read', 'changed2.php');
\r
32945 // Or set the entire API with a config-object.
\r
32946 // When using the config-object option, you must redefine the <b>entire</b>
\r
32947 // API -- not just a specific action of it.
\r
32948 store.proxy.setApi({
\r
32949 read : 'changed_read.php',
\r
32950 create : 'changed_create.php',
\r
32951 update : 'changed_update.php',
\r
32952 destroy : 'changed_destroy.php'
\r
32963 * @event exception
\r
32964 * <p>Fires if an exception occurs in the Proxy during a remote request. This event is relayed
\r
32965 * through a corresponding {@link Ext.data.Store}.{@link Ext.data.Store#exception exception},
\r
32966 * so any Store instance may observe this event.</p>
\r
32967 * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
\r
32968 * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of exception events from <b>all</b>
\r
32969 * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
\r
32970 * <p>This event can be fired for one of two reasons:</p>
\r
32971 * <div class="mdetail-params"><ul>
\r
32972 * <li>remote-request <b>failed</b> : <div class="sub-desc">
\r
32973 * The server did not return status === 200.
\r
32975 * <li>remote-request <b>succeeded</b> : <div class="sub-desc">
\r
32976 * The remote-request succeeded but the reader could not read the response.
\r
32977 * This means the server returned data, but the configured Reader threw an
\r
32978 * error while reading the response. In this case, this event will be
\r
32979 * raised and the caught error will be passed along into this event.
\r
32982 * <br><p>This event fires with two different contexts based upon the 2nd
\r
32983 * parameter <tt>type [remote|response]</tt>. The first four parameters
\r
32984 * are identical between the two contexts -- only the final two parameters
\r
32986 * @param {DataProxy} this The proxy that sent the request
\r
32987 * @param {String} type
\r
32988 * <p>The value of this parameter will be either <tt>'response'</tt> or <tt>'remote'</tt>.</p>
\r
32989 * <div class="mdetail-params"><ul>
\r
32990 * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
\r
32991 * <p>An <b>invalid</b> response from the server was returned: either 404,
\r
32992 * 500 or the response meta-data does not match that defined in the DataReader
\r
32993 * (e.g.: root, idProperty, successProperty).</p>
\r
32995 * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
\r
32996 * <p>A <b>valid</b> response was returned from the server having
\r
32997 * successProperty === false. This response might contain an error-message
\r
32998 * sent from the server. For example, the user may have failed
\r
32999 * authentication/authorization or a database validation error occurred.</p>
\r
33002 * @param {String} action Name of the action (see {@link Ext.data.Api#actions}.
\r
33003 * @param {Object} options The options for the action that were specified in the {@link #request}.
\r
33004 * @param {Object} response
\r
33005 * <p>The value of this parameter depends on the value of the <code>type</code> parameter:</p>
\r
33006 * <div class="mdetail-params"><ul>
\r
33007 * <li><b><tt>'response'</tt></b> : <div class="sub-desc">
\r
33008 * <p>The raw browser response object (e.g.: XMLHttpRequest)</p>
\r
33010 * <li><b><tt>'remote'</tt></b> : <div class="sub-desc">
\r
33011 * <p>The decoded response object sent from the server.</p>
\r
33014 * @param {Mixed} arg
\r
33015 * <p>The type and value of this parameter depends on the value of the <code>type</code> parameter:</p>
\r
33016 * <div class="mdetail-params"><ul>
\r
33017 * <li><b><tt>'response'</tt></b> : Error<div class="sub-desc">
\r
33018 * <p>The JavaScript Error object caught if the configured Reader could not read the data.
\r
33019 * If the remote request returns success===false, this parameter will be null.</p>
\r
33021 * <li><b><tt>'remote'</tt></b> : Record/Record[]<div class="sub-desc">
\r
33022 * <p>This parameter will only exist if the <tt>action</tt> was a <b>write</b> action
\r
33023 * (Ext.data.Api.actions.create|update|destroy).</p>
\r
33029 * @event beforeload
\r
33030 * Fires before a request to retrieve a data object.
\r
33031 * @param {DataProxy} this The proxy for the request
\r
33032 * @param {Object} params The params object passed to the {@link #request} function
\r
33037 * Fires before the load method's callback is called.
\r
33038 * @param {DataProxy} this The proxy for the request
\r
33039 * @param {Object} o The request transaction object
\r
33040 * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
\r
33044 * @event loadexception
\r
33045 * <p>This event is <b>deprecated</b>. The signature of the loadexception event
\r
33046 * varies depending on the proxy, use the catch-all {@link #exception} event instead.
\r
33047 * This event will fire in addition to the {@link #exception} event.</p>
\r
33048 * @param {misc} misc See {@link #exception}.
\r
33053 * @event beforewrite
\r
33054 * <p>Fires before a request is generated for one of the actions Ext.data.Api.actions.create|update|destroy</p>
\r
33055 * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
\r
33056 * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of beforewrite events from <b>all</b>
\r
33057 * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
\r
33058 * @param {DataProxy} this The proxy for the request
\r
33059 * @param {String} action [Ext.data.Api.actions.create|update|destroy]
\r
33060 * @param {Record/Array[Record]} rs The Record(s) to create|update|destroy.
\r
33061 * @param {Object} params The request <code>params</code> object. Edit <code>params</code> to add parameters to the request.
\r
33066 * <p>Fires before the request-callback is called</p>
\r
33067 * <p>In addition to being fired through the DataProxy instance that raised the event, this event is also fired
\r
33068 * through the Ext.data.DataProxy <i>class</i> to allow for centralized processing of write events from <b>all</b>
\r
33069 * DataProxies by attaching a listener to the Ext.data.Proxy class itself.</p>
\r
33070 * @param {DataProxy} this The proxy that sent the request
\r
33071 * @param {String} action [Ext.data.Api.actions.create|upate|destroy]
\r
33072 * @param {Object} data The data object extracted from the server-response
\r
33073 * @param {Object} response The decoded response from server
\r
33074 * @param {Record/Record{}} rs The records from Store
\r
33075 * @param {Object} options The callback's <tt>options</tt> property as passed to the {@link #request} function
\r
33079 Ext.data.DataProxy.superclass.constructor.call(this);
\r
33081 // Prepare the proxy api. Ensures all API-actions are defined with the Object-form.
\r
33083 Ext.data.Api.prepare(this);
\r
33085 if (e instanceof Ext.data.Api.Error) {
\r
33089 // relay each proxy's events onto Ext.data.DataProxy class for centralized Proxy-listening
\r
33090 Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
\r
33093 Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
\r
33095 * @cfg {Boolean} restful
\r
33096 * <p>Defaults to <tt>false</tt>. Set to <tt>true</tt> to operate in a RESTful manner.</p>
\r
33097 * <br><p> Note: this parameter will automatically be set to <tt>true</tt> if the
\r
33098 * {@link Ext.data.Store} it is plugged into is set to <code>restful: true</code>. If the
\r
33099 * Store is RESTful, there is no need to set this option on the proxy.</p>
\r
33100 * <br><p>RESTful implementations enable the serverside framework to automatically route
\r
33101 * actions sent to one url based upon the HTTP method, for example:
\r
33103 store: new Ext.data.Store({
\r
33105 proxy: new Ext.data.HttpProxy({url:'/users'}); // all requests sent to /users
\r
33109 * If there is no <code>{@link #api}</code> specified in the configuration of the proxy,
\r
33110 * all requests will be marshalled to a single RESTful url (/users) so the serverside
\r
33111 * framework can inspect the HTTP Method and act accordingly:
\r
33113 <u>Method</u> <u>url</u> <u>action</u>
\r
33114 POST /users create
\r
33116 PUT /users/23 update
\r
33117 DESTROY /users/23 delete
\r
33119 * <p>If set to <tt>true</tt>, a {@link Ext.data.Record#phantom non-phantom} record's
\r
33120 * {@link Ext.data.Record#id id} will be appended to the url. Some MVC (e.g., Ruby on Rails,
\r
33121 * Merb and Django) support segment based urls where the segments in the URL follow the
\r
33122 * Model-View-Controller approach:<pre><code>
\r
33123 * someSite.com/controller/action/id
\r
33125 * Where the segments in the url are typically:<div class="mdetail-params"><ul>
\r
33126 * <li>The first segment : represents the controller class that should be invoked.</li>
\r
33127 * <li>The second segment : represents the class function, or method, that should be called.</li>
\r
33128 * <li>The third segment : represents the ID (a variable typically passed to the method).</li>
\r
33129 * </ul></div></p>
\r
33130 * <br><p>Refer to <code>{@link Ext.data.DataProxy#api}</code> for additional information.</p>
\r
33135 * <p>Redefines the Proxy's API or a single action of an API. Can be called with two method signatures.</p>
\r
33136 * <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
33138 read : '/users/read',
\r
33139 create : '/users/create',
\r
33140 update : '/users/update',
\r
33141 destroy : '/users/destroy'
\r
33144 * <p>If called with two parameters, the first parameter should be a string specifying the API action to
\r
33145 * 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
33146 proxy.setApi(Ext.data.Api.actions.read, '/users/new_load_url');
\r
33148 * @param {String/Object} api An API specification object, or the name of an action.
\r
33149 * @param {String/Function} url The URL (or function if using DirectProxy) to call for the action.
\r
33151 setApi : function() {
\r
33152 if (arguments.length == 1) {
\r
33153 var valid = Ext.data.Api.isValid(arguments[0]);
\r
33154 if (valid === true) {
\r
33155 this.api = arguments[0];
\r
33158 throw new Ext.data.Api.Error('invalid', valid);
\r
33161 else if (arguments.length == 2) {
\r
33162 if (!Ext.data.Api.isAction(arguments[0])) {
\r
33163 throw new Ext.data.Api.Error('invalid', arguments[0]);
\r
33165 this.api[arguments[0]] = arguments[1];
\r
33167 Ext.data.Api.prepare(this);
\r
33171 * Returns true if the specified action is defined as a unique action in the api-config.
\r
33172 * request. If all API-actions are routed to unique urls, the xaction parameter is unecessary. However, if no api is defined
\r
33173 * and all Proxy actions are routed to DataProxy#url, the server-side will require the xaction parameter to perform a switch to
\r
33174 * the corresponding code for CRUD action.
\r
33175 * @param {String [Ext.data.Api.CREATE|READ|UPDATE|DESTROY]} action
\r
33176 * @return {Boolean}
\r
33178 isApiAction : function(action) {
\r
33179 return (this.api[action]) ? true : false;
\r
33183 * All proxy actions are executed through this method. Automatically fires the "before" + action event
\r
33184 * @param {String} action Name of the action
\r
33185 * @param {Ext.data.Record/Ext.data.Record[]/null} rs Will be null when action is 'load'
\r
33186 * @param {Object} params
\r
33187 * @param {Ext.data.DataReader} reader
\r
33188 * @param {Function} callback
\r
33189 * @param {Object} scope Scope with which to call the callback (defaults to the Proxy object)
\r
33190 * @param {Object} options Any options specified for the action (e.g. see {@link Ext.data.Store#load}.
\r
33192 request : function(action, rs, params, reader, callback, scope, options) {
\r
33193 if (!this.api[action] && !this.load) {
\r
33194 throw new Ext.data.DataProxy.Error('action-undefined', action);
\r
33196 params = params || {};
\r
33197 if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
\r
33198 this.doRequest.apply(this, arguments);
\r
33201 callback.call(scope || this, null, options, false);
\r
33207 * <b>Deprecated</b> load method using old method signature. See {@doRequest} for preferred method.
\r
33209 * @param {Object} params
\r
33210 * @param {Object} reader
\r
33211 * @param {Object} callback
\r
33212 * @param {Object} scope
\r
33213 * @param {Object} arg
\r
33218 * @cfg {Function} doRequest Abstract method that should be implemented in all subclasses
\r
33219 * (e.g.: {@link Ext.data.HttpProxy#doRequest HttpProxy.doRequest},
\r
33220 * {@link Ext.data.DirectProxy#doRequest DirectProxy.doRequest}).
\r
33222 doRequest : function(action, rs, params, reader, callback, scope, options) {
\r
33223 // default implementation of doRequest for backwards compatibility with 2.0 proxies.
\r
33224 // If we're executing here, the action is probably "load".
\r
33225 // Call with the pre-3.0 method signature.
\r
33226 this.load(params, reader, callback, scope, options);
\r
33231 * Sets the appropriate url based upon the action being executed. If restful is true, and only a single record is being acted upon,
\r
33232 * url will be built Rails-style, as in "/controller/action/32". restful will aply iff the supplied record is an
\r
33233 * instance of Ext.data.Record rather than an Array of them.
\r
33234 * @param {String} action The api action being executed [read|create|update|destroy]
\r
33235 * @param {Ext.data.Record/Array[Ext.data.Record]} The record or Array of Records being acted upon.
\r
33236 * @return {String} url
\r
33239 buildUrl : function(action, record) {
\r
33240 record = record || null;
\r
33241 var url = (this.api[action]) ? this.api[action].url : this.url;
\r
33243 throw new Ext.data.Api.Error('invalid-url', action);
\r
33246 // look for urls having "provides" suffix (from Rails/Merb and others...),
\r
33247 // e.g.: /users.json, /users.xml, etc
\r
33248 // with restful routes, we need urls like:
\r
33249 // PUT /users/1.json
\r
33250 // DELETE /users/1.json
\r
33251 var format = null;
\r
33252 var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
\r
33254 format = m[2]; // eg ".json"
\r
33255 url = m[1]; // eg: "/users"
\r
33257 // prettyUrls is deprectated in favor of restful-config
\r
33258 if ((this.prettyUrls === true || this.restful === true) && record instanceof Ext.data.Record && !record.phantom) {
\r
33259 url += '/' + record.id;
\r
33261 if (format) { // <-- append the request format if exists (ie: /users/update/69[.json])
\r
33268 * Destroys the proxy by purging any event listeners and cancelling any active requests.
\r
33270 destroy: function(){
\r
33271 this.purgeListeners();
\r
33275 // Apply the Observable prototype to the DataProxy class so that proxy instances can relay their
\r
33276 // events to the class. Allows for centralized listening of all proxy instances upon the DataProxy class.
\r
33277 Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
\r
33278 Ext.util.Observable.call(Ext.data.DataProxy);
\r
33281 * @class Ext.data.DataProxy.Error
\r
33282 * @extends Ext.Error
\r
33283 * DataProxy Error extension.
\r
33285 * @param {String} name
\r
33286 * @param {Record/Array[Record]/Array}
\r
33288 Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
\r
33289 constructor : function(message, arg) {
\r
33291 Ext.Error.call(this, message);
\r
33293 name: 'Ext.data.DataProxy'
\r
33295 Ext.apply(Ext.data.DataProxy.Error.prototype, {
\r
33297 'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.",
\r
33298 'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
\r
33304 * @class Ext.data.ScriptTagProxy
\r
33305 * @extends Ext.data.DataProxy
\r
33306 * An implementation of Ext.data.DataProxy that reads a data object from a URL which may be in a domain
\r
33307 * other than the originating domain of the running page.<br>
\r
33309 * <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
33310 * of the running page, you must use this class, rather than HttpProxy.</b><br>
\r
33312 * The content passed back from a server resource requested by a ScriptTagProxy <b>must</b> be executable JavaScript
\r
33313 * source code because it is used as the source inside a <script> tag.<br>
\r
33315 * In order for the browser to process the returned data, the server must wrap the data object
\r
33316 * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
\r
33317 * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
\r
33318 * depending on whether the callback name was passed:
\r
33321 boolean scriptTag = false;
\r
33322 String cb = request.getParameter("callback");
\r
33323 if (cb != null) {
\r
33324 scriptTag = true;
\r
33325 response.setContentType("text/javascript");
\r
33327 response.setContentType("application/x-json");
\r
33329 Writer out = response.getWriter();
\r
33331 out.write(cb + "(");
\r
33333 out.print(dataBlock.toJsonString());
\r
33340 * @param {Object} config A configuration object.
\r
33342 Ext.data.ScriptTagProxy = function(config){
\r
33343 Ext.apply(this, config);
\r
33345 Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
\r
33347 this.head = document.getElementsByTagName("head")[0];
\r
33350 * @event loadexception
\r
33351 * <b>Deprecated</b> in favor of 'exception' event.
\r
33352 * Fires if an exception occurs in the Proxy during data loading. This event can be fired for one of two reasons:
\r
33353 * <ul><li><b>The load call timed out.</b> This means the load callback did not execute within the time limit
\r
33354 * specified by {@link #timeout}. In this case, this event will be raised and the
\r
33355 * fourth parameter (read error) will be null.</li>
\r
33356 * <li><b>The load succeeded but the reader could not read the response.</b> This means the server returned
\r
33357 * data, but the configured Reader threw an error while reading the data. In this case, this event will be
\r
33358 * raised and the caught error will be passed along as the fourth parameter of this event.</li></ul>
\r
33359 * Note that this event is also relayed through {@link Ext.data.Store}, so you can listen for it directly
\r
33360 * on any Store instance.
\r
33361 * @param {Object} this
\r
33362 * @param {Object} options The loading options that were specified (see {@link #load} for details). If the load
\r
33363 * call timed out, this parameter will be null.
\r
33364 * @param {Object} arg The callback's arg object passed to the {@link #load} function
\r
33365 * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data.
\r
33366 * If the remote request returns success: false, this parameter will be null.
\r
33370 Ext.data.ScriptTagProxy.TRANS_ID = 1000;
\r
33372 Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
\r
33374 * @cfg {String} url The URL from which to request the data object.
\r
33377 * @cfg {Number} timeout (optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
\r
33381 * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
\r
33382 * the server the name of the callback function set up by the load call to process the returned data object.
\r
33383 * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
\r
33384 * javascript output which calls this named function passing the data object as its only parameter.
\r
33386 callbackParam : "callback",
\r
33388 * @cfg {Boolean} nocache (optional) Defaults to true. Disable caching by adding a unique parameter
\r
33389 * name to the request.
\r
33394 * HttpProxy implementation of DataProxy#doRequest
\r
33395 * @param {String} action
\r
33396 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is <tt>read</tt>, rs will be null
\r
33397 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
33398 * for the request to the remote server.
\r
33399 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
33400 * object into a block of Ext.data.Records.
\r
33401 * @param {Function} callback The function into which to pass the block of Ext.data.Records.
\r
33402 * The function must be passed <ul>
\r
33403 * <li>The Record block object</li>
\r
33404 * <li>The "arg" argument from the load function</li>
\r
33405 * <li>A boolean success indicator</li>
\r
33407 * @param {Object} scope The scope in which to call the callback
\r
33408 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
33410 doRequest : function(action, rs, params, reader, callback, scope, arg) {
\r
33411 var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
\r
33413 var url = this.buildUrl(action, rs);
\r
33415 throw new Ext.data.Api.Error('invalid-url', url);
\r
33417 url = Ext.urlAppend(url, p);
\r
33419 if(this.nocache){
\r
33420 url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
\r
33422 var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
\r
33426 cb : "stcCallback"+transId,
\r
33427 scriptId : "stcScript"+transId,
\r
33431 callback : callback,
\r
33435 window[trans.cb] = this.createCallback(action, rs, trans);
\r
33436 url += String.format("&{0}={1}", this.callbackParam, trans.cb);
\r
33437 if(this.autoAbort !== false){
\r
33441 trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
\r
33443 var script = document.createElement("script");
\r
33444 script.setAttribute("src", url);
\r
33445 script.setAttribute("type", "text/javascript");
\r
33446 script.setAttribute("id", trans.scriptId);
\r
33447 this.head.appendChild(script);
\r
33449 this.trans = trans;
\r
33452 // @private createCallback
\r
33453 createCallback : function(action, rs, trans) {
\r
33455 return function(res) {
\r
33456 self.trans = false;
\r
33457 self.destroyTrans(trans, true);
\r
33458 if (action === Ext.data.Api.actions.read) {
\r
33459 self.onRead.call(self, action, trans, res);
\r
33461 self.onWrite.call(self, action, trans, res, rs);
\r
33466 * Callback for read actions
\r
33467 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
33468 * @param {Object} trans The request transaction object
\r
33469 * @param {Object} res The server response
\r
33472 onRead : function(action, trans, res) {
\r
33475 result = trans.reader.readRecords(res);
\r
33477 // @deprecated: fire loadexception
\r
33478 this.fireEvent("loadexception", this, trans, res, e);
\r
33480 this.fireEvent('exception', this, 'response', action, trans, res, e);
\r
33481 trans.callback.call(trans.scope||window, null, trans.arg, false);
\r
33484 if (result.success === false) {
\r
33485 // @deprecated: fire old loadexception for backwards-compat.
\r
33486 this.fireEvent('loadexception', this, trans, res);
\r
33488 this.fireEvent('exception', this, 'remote', action, trans, res, null);
\r
33490 this.fireEvent("load", this, res, trans.arg);
\r
33492 trans.callback.call(trans.scope||window, result, trans.arg, result.success);
\r
33495 * Callback for write actions
\r
33496 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
33497 * @param {Object} trans The request transaction object
\r
33498 * @param {Object} res The server response
\r
33501 onWrite : function(action, trans, response, rs) {
\r
33502 var reader = trans.reader;
\r
33504 // though we already have a response object here in STP, run through readResponse to catch any meta-data exceptions.
\r
33505 var res = reader.readResponse(action, response);
\r
33507 this.fireEvent('exception', this, 'response', action, trans, res, e);
\r
33508 trans.callback.call(trans.scope||window, null, res, false);
\r
33511 if(!res.success === true){
\r
33512 this.fireEvent('exception', this, 'remote', action, trans, res, rs);
\r
33513 trans.callback.call(trans.scope||window, null, res, false);
\r
33516 this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
\r
33517 trans.callback.call(trans.scope||window, res.data, res, true);
\r
33521 isLoading : function(){
\r
33522 return this.trans ? true : false;
\r
33526 * Abort the current server request.
\r
33528 abort : function(){
\r
33529 if(this.isLoading()){
\r
33530 this.destroyTrans(this.trans);
\r
33535 destroyTrans : function(trans, isLoaded){
\r
33536 this.head.removeChild(document.getElementById(trans.scriptId));
\r
33537 clearTimeout(trans.timeoutId);
\r
33539 window[trans.cb] = undefined;
\r
33541 delete window[trans.cb];
\r
33544 // if hasn't been loaded, wait for load to remove it to prevent script error
\r
33545 window[trans.cb] = function(){
\r
33546 window[trans.cb] = undefined;
\r
33548 delete window[trans.cb];
\r
33555 handleFailure : function(trans){
\r
33556 this.trans = false;
\r
33557 this.destroyTrans(trans, false);
\r
33558 if (trans.action === Ext.data.Api.actions.read) {
\r
33559 // @deprecated firing loadexception
\r
33560 this.fireEvent("loadexception", this, null, trans.arg);
\r
33563 this.fireEvent('exception', this, 'response', trans.action, {
\r
33565 options: trans.arg
\r
33567 trans.callback.call(trans.scope||window, null, trans.arg, false);
\r
33571 destroy: function(){
\r
33573 Ext.data.ScriptTagProxy.superclass.destroy.call(this);
\r
33576 * @class Ext.data.HttpProxy
\r
33577 * @extends Ext.data.DataProxy
\r
33578 * <p>An implementation of {@link Ext.data.DataProxy} that processes data requests within the same
\r
33579 * domain of the originating page.</p>
\r
33580 * <p><b>Note</b>: this class cannot be used to retrieve data from a domain other
\r
33581 * than the domain from which the running page was served. For cross-domain requests, use a
\r
33582 * {@link Ext.data.ScriptTagProxy ScriptTagProxy}.</p>
\r
33583 * <p>Be aware that to enable the browser to parse an XML document, the server must set
\r
33584 * the Content-Type header in the HTTP response to "<tt>text/xml</tt>".</p>
\r
33586 * @param {Object} conn
\r
33587 * An {@link Ext.data.Connection} object, or options parameter to {@link Ext.Ajax#request}.
\r
33588 * <p>Note that if this HttpProxy is being used by a (@link Ext.data.Store Store}, then the
\r
33589 * Store's call to {@link #load} will override any specified <tt>callback</tt> and <tt>params</tt>
\r
33590 * options. In this case, use the Store's {@link Ext.data.Store#events events} to modify parameters,
\r
33591 * or react to loading events. The Store's {@link Ext.data.Store#baseParams baseParams} may also be
\r
33592 * used to pass parameters known at instantiation time.</p>
\r
33593 * <p>If an options parameter is passed, the singleton {@link Ext.Ajax} object will be used to make
\r
33594 * the request.</p>
\r
33596 Ext.data.HttpProxy = function(conn){
\r
33597 Ext.data.HttpProxy.superclass.constructor.call(this, conn);
\r
33600 * The Connection object (Or options parameter to {@link Ext.Ajax#request}) which this HttpProxy
\r
33601 * uses to make requests to the server. Properties of this object may be changed dynamically to
\r
33602 * change the way data is requested.
\r
33605 this.conn = conn;
\r
33607 // nullify the connection url. The url param has been copied to 'this' above. The connection
\r
33608 // url will be set during each execution of doRequest when buildUrl is called. This makes it easier for users to override the
\r
33609 // connection url during beforeaction events (ie: beforeload, beforewrite, etc).
\r
33610 // Url is always re-defined during doRequest.
\r
33611 this.conn.url = null;
\r
33613 this.useAjax = !conn || !conn.events;
\r
33615 // A hash containing active requests, keyed on action [Ext.data.Api.actions.create|read|update|destroy]
\r
33616 var actions = Ext.data.Api.actions;
\r
33617 this.activeRequest = {};
\r
33618 for (var verb in actions) {
\r
33619 this.activeRequest[actions[verb]] = undefined;
\r
33623 Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
\r
33625 * Return the {@link Ext.data.Connection} object being used by this Proxy.
\r
33626 * @return {Connection} The Connection object. This object may be used to subscribe to events on
\r
33627 * a finer-grained basis than the DataProxy events.
\r
33629 getConnection : function() {
\r
33630 return this.useAjax ? Ext.Ajax : this.conn;
\r
33634 * Used for overriding the url used for a single request. Designed to be called during a beforeaction event. Calling setUrl
\r
33635 * will override any urls set via the api configuration parameter. Set the optional parameter makePermanent to set the url for
\r
33636 * all subsequent requests. If not set to makePermanent, the next request will use the same url or api configuration defined
\r
33637 * in the initial proxy configuration.
\r
33638 * @param {String} url
\r
33639 * @param {Boolean} makePermanent (Optional) [false]
\r
33641 * (e.g.: beforeload, beforesave, etc).
\r
33643 setUrl : function(url, makePermanent) {
\r
33644 this.conn.url = url;
\r
33645 if (makePermanent === true) {
\r
33648 Ext.data.Api.prepare(this);
\r
33653 * HttpProxy implementation of DataProxy#doRequest
\r
33654 * @param {String} action The crud action type (create, read, update, destroy)
\r
33655 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
\r
33656 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
33657 * for the request to the remote server.
\r
33658 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
33659 * object into a block of Ext.data.Records.
\r
33660 * @param {Function} callback
\r
33661 * <div class="sub-desc"><p>A function to be called after the request.
\r
33662 * The <tt>callback</tt> is passed the following arguments:<ul>
\r
33663 * <li><tt>r</tt> : Ext.data.Record[] The block of Ext.data.Records.</li>
\r
33664 * <li><tt>options</tt>: Options object from the action request</li>
\r
33665 * <li><tt>success</tt>: Boolean success indicator</li></ul></p></div>
\r
33666 * @param {Object} scope The scope in which to call the callback
\r
33667 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
33669 doRequest : function(action, rs, params, reader, cb, scope, arg) {
\r
33671 method: (this.api[action]) ? this.api[action]['method'] : undefined,
\r
33678 callback : this.createCallback(action, rs),
\r
33682 // If possible, transmit data using jsonData || xmlData on Ext.Ajax.request (An installed DataWriter would have written it there.).
\r
33683 // Use std HTTP params otherwise.
\r
33684 // TODO wrap into 1 Ext.apply now?
\r
33685 if (params.jsonData) {
\r
33686 o.jsonData = params.jsonData;
\r
33687 } else if (params.xmlData) {
\r
33688 o.xmlData = params.xmlData;
\r
33690 o.params = params || {};
\r
33692 // Set the connection url. If this.conn.url is not null here,
\r
33693 // the user may have overridden the url during a beforeaction event-handler.
\r
33694 // this.conn.url is nullified after each request.
\r
33695 if (this.conn.url === null) {
\r
33696 this.conn.url = this.buildUrl(action, rs);
\r
33698 else if (this.restful === true && rs instanceof Ext.data.Record && !rs.phantom) { // <-- user must have intervened with #setApi or #setUrl
\r
33699 this.conn.url += '/' + rs.id;
\r
33701 if(this.useAjax){
\r
33703 Ext.applyIf(o, this.conn);
\r
33705 // If a currently running request is found for this action, abort it.
\r
33706 if (this.activeRequest[action]) {
\r
33708 // Disabled aborting activeRequest while implementing REST. activeRequest[action] will have to become an array
\r
33709 // TODO ideas anyone?
\r
33711 //Ext.Ajax.abort(this.activeRequest[action]);
\r
33713 this.activeRequest[action] = Ext.Ajax.request(o);
\r
33715 this.conn.request(o);
\r
33717 // request is sent, nullify the connection url in preparation for the next request
\r
33718 this.conn.url = null;
\r
33722 * Returns a callback function for a request. Note a special case is made for the
\r
33723 * read action vs all the others.
\r
33724 * @param {String} action [create|update|delete|load]
\r
33725 * @param {Ext.data.Record[]} rs The Store-recordset being acted upon
\r
33728 createCallback : function(action, rs) {
\r
33729 return function(o, success, response) {
\r
33730 this.activeRequest[action] = undefined;
\r
33732 if (action === Ext.data.Api.actions.read) {
\r
33733 // @deprecated: fire loadexception for backwards compat.
\r
33734 // TODO remove in 3.1
\r
33735 this.fireEvent('loadexception', this, o, response);
\r
33737 this.fireEvent('exception', this, 'response', action, o, response);
\r
33738 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
33741 if (action === Ext.data.Api.actions.read) {
\r
33742 this.onRead(action, o, response);
\r
33744 this.onWrite(action, o, response, rs);
\r
33750 * Callback for read action
\r
33751 * @param {String} action Action name as per {@link Ext.data.Api.actions#read}.
\r
33752 * @param {Object} o The request transaction object
\r
33753 * @param {Object} res The server response
\r
33754 * @fires loadexception (deprecated)
\r
33755 * @fires exception
\r
33759 onRead : function(action, o, response) {
\r
33762 result = o.reader.read(response);
\r
33764 // @deprecated: fire old loadexception for backwards-compat.
\r
33765 // TODO remove in 3.1
\r
33766 this.fireEvent('loadexception', this, o, response, e);
\r
33768 this.fireEvent('exception', this, 'response', action, o, response, e);
\r
33769 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
33772 if (result.success === false) {
\r
33773 // @deprecated: fire old loadexception for backwards-compat.
\r
33774 // TODO remove in 3.1
\r
33775 this.fireEvent('loadexception', this, o, response);
\r
33777 // Get DataReader read-back a response-object to pass along to exception event
\r
33778 var res = o.reader.readResponse(action, response);
\r
33779 this.fireEvent('exception', this, 'remote', action, o, res, null);
\r
33782 this.fireEvent('load', this, o, o.request.arg);
\r
33784 // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
\r
33785 // the calls to request.callback(...) in each will have to be made identical.
\r
33786 // NOTE reader.readResponse does not currently return Ext.data.Response
\r
33787 o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
\r
33790 * Callback for write actions
\r
33791 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
33792 * @param {Object} trans The request transaction object
\r
33793 * @param {Object} res The server response
\r
33794 * @fires exception
\r
33798 onWrite : function(action, o, response, rs) {
\r
33799 var reader = o.reader;
\r
33802 res = reader.readResponse(action, response);
\r
33804 this.fireEvent('exception', this, 'response', action, o, response, e);
\r
33805 o.request.callback.call(o.request.scope, null, o.request.arg, false);
\r
33808 if (res.success === false) {
\r
33809 this.fireEvent('exception', this, 'remote', action, o, res, rs);
\r
33811 this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
\r
33813 // TODO refactor onRead, onWrite to be more generalized now that we're dealing with Ext.data.Response instance
\r
33814 // the calls to request.callback(...) in each will have to be made similar.
\r
33815 // NOTE reader.readResponse does not currently return Ext.data.Response
\r
33816 o.request.callback.call(o.request.scope, res.data, res, res.success);
\r
33820 destroy: function(){
\r
33821 if(!this.useAjax){
\r
33822 this.conn.abort();
\r
33823 }else if(this.activeRequest){
\r
33824 var actions = Ext.data.Api.actions;
\r
33825 for (var verb in actions) {
\r
33826 if(this.activeRequest[actions[verb]]){
\r
33827 Ext.Ajax.abort(this.activeRequest[actions[verb]]);
\r
33831 Ext.data.HttpProxy.superclass.destroy.call(this);
\r
33834 * @class Ext.data.MemoryProxy
\r
33835 * @extends Ext.data.DataProxy
\r
33836 * An implementation of Ext.data.DataProxy that simply passes the data specified in its constructor
\r
33837 * to the Reader when its load method is called.
\r
33839 * @param {Object} data The data object which the Reader uses to construct a block of Ext.data.Records.
\r
33841 Ext.data.MemoryProxy = function(data){
\r
33842 // Must define a dummy api with "read" action to satisfy DataProxy#doRequest and Ext.data.Api#prepare *before* calling super
\r
33844 api[Ext.data.Api.actions.read] = true;
\r
33845 Ext.data.MemoryProxy.superclass.constructor.call(this, {
\r
33848 this.data = data;
\r
33851 Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
\r
33853 * @event loadexception
\r
33854 * Fires if an exception occurs in the Proxy during data loading. Note that this event is also relayed
\r
33855 * through {@link Ext.data.Store}, so you can listen for it directly on any Store instance.
\r
33856 * @param {Object} this
\r
33857 * @param {Object} arg The callback's arg object passed to the {@link #load} function
\r
33858 * @param {Object} null This parameter does not apply and will always be null for MemoryProxy
\r
33859 * @param {Error} e The JavaScript Error object caught if the configured Reader could not read the data
\r
33863 * MemoryProxy implementation of DataProxy#doRequest
\r
33864 * @param {String} action
\r
33865 * @param {Ext.data.Record/Ext.data.Record[]} rs If action is load, rs will be null
\r
33866 * @param {Object} params An object containing properties which are to be used as HTTP parameters
\r
33867 * for the request to the remote server.
\r
33868 * @param {Ext.data.DataReader} reader The Reader object which converts the data
\r
33869 * object into a block of Ext.data.Records.
\r
33870 * @param {Function} callback The function into which to pass the block of Ext.data.Records.
\r
33871 * The function must be passed <ul>
\r
33872 * <li>The Record block object</li>
\r
33873 * <li>The "arg" argument from the load function</li>
\r
33874 * <li>A boolean success indicator</li>
\r
33876 * @param {Object} scope The scope in which to call the callback
\r
33877 * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
\r
33879 doRequest : function(action, rs, params, reader, callback, scope, arg) {
\r
33880 // No implementation for CRUD in MemoryProxy. Assumes all actions are 'load'
\r
33881 params = params || {};
\r
33884 result = reader.readRecords(this.data);
\r
33886 // @deprecated loadexception
\r
33887 this.fireEvent("loadexception", this, null, arg, e);
\r
33889 this.fireEvent('exception', this, 'response', action, arg, null, e);
\r
33890 callback.call(scope, null, arg, false);
\r
33893 callback.call(scope, result, arg, true);
\r
33896 * @class Ext.data.JsonWriter
33897 * @extends Ext.data.DataWriter
33898 * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action.
33900 Ext.data.JsonWriter = function(config) {
33901 Ext.data.JsonWriter.superclass.constructor.call(this, config);
33903 // careful to respect "returnJson", renamed to "encode"
33904 // TODO: remove after v3 final release
33905 if (this.returnJson != undefined) {
33906 this.encode = this.returnJson;
33909 Ext.extend(Ext.data.JsonWriter, Ext.data.DataWriter, {
33911 * @cfg {Boolean} returnJson <b>Deprecated</b>. Use {@link Ext.data.JsonWriter#encode} instead.
33913 returnJson : undefined,
33915 * @cfg {Boolean} encode <tt>true</tt> to {@link Ext.util.JSON#encode encode} the
33916 * {@link Ext.data.DataWriter#toHash hashed data}. Defaults to <tt>true</tt>. When using
33917 * {@link Ext.data.DirectProxy}, set this to <tt>false</tt> since Ext.Direct.JsonProvider will perform
33918 * its own json-encoding. In addition, if you're using {@link Ext.data.HttpProxy}, setting to <tt>false</tt>
33919 * will cause HttpProxy to transmit data using the <b>jsonData</b> configuration-params of {@link Ext.Ajax#request}
33920 * instead of <b>params</b>. When using a {@link Ext.data.Store#restful} Store, some serverside frameworks are
33921 * tuned to expect data through the jsonData mechanism. In those cases, one will want to set <b>encode: <tt>false</tt></b>, as in
33922 * let the lower-level connection object (eg: Ext.Ajax) do the encoding.
33927 * Final action of a write event. Apply the written data-object to params.
33928 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
33929 * @param {Record[]} rs
33930 * @param {Object} http params
33931 * @param {Object} data object populated according to DataReader meta-data "root" and "idProperty"
33933 render : function(action, rs, params, data) {
33934 if (this.encode === true) {
33935 params[this.meta.root] = Ext.encode(data);
33937 params.jsonData = {};
33938 params.jsonData[this.meta.root] = data;
33942 * Implements abstract Ext.data.DataWriter#createRecord
33944 * @param {Ext.data.Record} rec
33947 createRecord : function(rec) {
33948 return this.toHash(rec);
33951 * Implements abstract Ext.data.DataWriter#updateRecord
33953 * @param {Ext.data.Record} rec
33956 updateRecord : function(rec) {
33957 return this.toHash(rec);
33961 * Implements abstract Ext.data.DataWriter#destroyRecord
33963 * @param {Ext.data.Record} rec
33966 destroyRecord : function(rec) {
33970 * @class Ext.data.JsonReader
33971 * @extends Ext.data.DataReader
33972 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects
33973 * from a JSON packet based on mappings in a provided {@link Ext.data.Record}
33975 * <p>Example code:</p>
33977 var myReader = new Ext.data.JsonReader({
33978 // metadata configuration options:
33979 {@link #idProperty}: 'id'
33980 {@link #root}: 'rows',
33981 {@link #totalProperty}: 'results',
33982 {@link Ext.data.DataReader#messageProperty}: "msg" // The element within the response that provides a user-feedback message (optional)
33984 // the fields config option will internally create an {@link Ext.data.Record}
33985 // constructor that provides mapping for reading the record data objects
33986 {@link Ext.data.DataReader#fields fields}: [
33987 // map Record's 'firstname' field to data object's key of same name
33989 // map Record's 'job' field to data object's 'occupation' key
33990 {name: 'job', mapping: 'occupation'}
33994 * <p>This would consume a JSON data object of the form:</p><pre><code>
33996 results: 2000, // Reader's configured {@link #totalProperty}
33997 rows: [ // Reader's configured {@link #root}
33998 // record data objects:
33999 { {@link #idProperty id}: 1, firstname: 'Bill', occupation: 'Gardener' },
34000 { {@link #idProperty id}: 2, firstname: 'Ben' , occupation: 'Horticulturalist' },
34005 * <p><b><u>Automatic configuration using metaData</u></b></p>
34006 * <p>It is possible to change a JsonReader's metadata at any time by including
34007 * a <b><tt>metaData</tt></b> property in the JSON data object. If the JSON data
34008 * object has a <b><tt>metaData</tt></b> property, a {@link Ext.data.Store Store}
34009 * object using this Reader will reconfigure itself to use the newly provided
34010 * field definition and fire its {@link Ext.data.Store#metachange metachange}
34011 * event. The metachange event handler may interrogate the <b><tt>metaData</tt></b>
34012 * property to perform any configuration required.</p>
34013 * <p>Note that reconfiguring a Store potentially invalidates objects which may
34014 * refer to Fields or Records which no longer exist.</p>
34015 * <p>To use this facility you would create the JsonReader like this:</p><pre><code>
34016 var myReader = new Ext.data.JsonReader();
34018 * <p>The first data packet from the server would configure the reader by
34019 * containing a <b><tt>metaData</tt></b> property <b>and</b> the data. For
34020 * example, the JSON data object might take the form:</p><pre><code>
34023 "{@link #idProperty}": "id",
34024 "{@link #root}": "rows",
34025 "{@link #totalProperty}": "results"
34026 "{@link #successProperty}": "success",
34027 "{@link Ext.data.DataReader#fields fields}": [
34029 {"name": "job", "mapping": "occupation"}
34031 // used by store to set its sortInfo
34036 // {@link Ext.PagingToolbar paging data} (if applicable)
34042 // Reader's configured {@link #successProperty}
34044 // Reader's configured {@link #totalProperty}
34046 // Reader's configured {@link #root}
34047 // (this data simulates 2 results {@link Ext.PagingToolbar per page})
34048 "rows": [ // <b>*Note:</b> this must be an Array
34049 { "id": 1, "name": "Bill", "occupation": "Gardener" },
34050 { "id": 2, "name": "Ben", "occupation": "Horticulturalist" }
34054 * <p>The <b><tt>metaData</tt></b> property in the JSON data object should contain:</p>
34055 * <div class="mdetail-params"><ul>
34056 * <li>any of the configuration options for this class</li>
34057 * <li>a <b><tt>{@link Ext.data.Record#fields fields}</tt></b> property which
34058 * the JsonReader will use as an argument to the
34059 * {@link Ext.data.Record#create data Record create method} in order to
34060 * configure the layout of the Records it will produce.</li>
34061 * <li>a <b><tt>{@link Ext.data.Store#sortInfo sortInfo}</tt></b> property
34062 * which the JsonReader will use to set the {@link Ext.data.Store}'s
34063 * {@link Ext.data.Store#sortInfo sortInfo} property</li>
34064 * <li>any custom properties needed</li>
34068 * Create a new JsonReader
34069 * @param {Object} meta Metadata configuration options.
34070 * @param {Array/Object} recordType
34071 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
34072 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
34073 * constructor created from {@link Ext.data.Record#create}.</p>
34075 Ext.data.JsonReader = function(meta, recordType){
34078 * @cfg {String} idProperty [id] Name of the property within a row object
34079 * that contains a record identifier value. Defaults to <tt>id</tt>
34082 * @cfg {String} successProperty [success] Name of the property from which to
34083 * retrieve the success attribute. Defaults to <tt>success</tt>. See
34084 * {@link Ext.data.DataProxy}.{@link Ext.data.DataProxy#exception exception}
34085 * for additional information.
34088 * @cfg {String} totalProperty [total] Name of the property from which to
34089 * retrieve the total number of records in the dataset. This is only needed
34090 * if the whole dataset is not passed in one go, but is being paged from
34091 * the remote server. Defaults to <tt>total</tt>.
34094 * @cfg {String} root [undefined] <b>Required</b>. The name of the property
34095 * which contains the Array of row objects. Defaults to <tt>undefined</tt>.
34096 * An exception will be thrown if the root property is undefined. The data
34097 * packet value for this property should be an empty array to clear the data
34100 Ext.applyIf(meta, {
34102 successProperty: 'success',
34103 totalProperty: 'total'
34106 Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
34108 Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
34110 * This JsonReader's metadata as passed to the constructor, or as passed in
34111 * the last data packet's <b><tt>metaData</tt></b> property.
34116 * This method is only used by a DataProxy which has retrieved data from a remote server.
34117 * @param {Object} response The XHR object which contains the JSON data in its responseText.
34118 * @return {Object} data A data block which is used by an Ext.data.Store object as
34119 * a cache of Ext.data.Records.
34121 read : function(response){
34122 var json = response.responseText;
34123 var o = Ext.decode(json);
34125 throw {message: 'JsonReader.read: Json object not found'};
34127 return this.readRecords(o);
34131 * Decode a json response from server.
34132 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
34133 * @param {Object} response
34134 * TODO: refactor code between JsonReader#readRecords, #readResponse into 1 method.
34135 * there's ugly duplication going on due to maintaining backwards compat. with 2.0. It's time to do this.
34137 readResponse : function(action, response) {
34138 var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
34140 throw new Ext.data.JsonReader.Error('response');
34143 var root = this.getRoot(o);
34144 if (action === Ext.data.Api.actions.create) {
34145 var def = Ext.isDefined(root);
34146 if (def && Ext.isEmpty(root)) {
34147 throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
34150 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
34154 // instantiate response object
34155 var res = new Ext.data.Response({
34157 success: this.getSuccess(o),
34158 data: this.extractData(root),
34159 message: this.getMessage(o),
34163 // blow up if no successProperty
34164 if (Ext.isEmpty(res.success)) {
34165 throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
34171 * Create a data block containing Ext.data.Records from a JSON object.
34172 * @param {Object} o An object which contains an Array of row objects in the property specified
34173 * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
34174 * which contains the total size of the dataset.
34175 * @return {Object} data A data block which is used by an Ext.data.Store object as
34176 * a cache of Ext.data.Records.
34178 readRecords : function(o){
34180 * After any data loads, the raw JSON data is available for further custom processing. If no data is
34181 * loaded or there is a load exception this property will be undefined.
34186 this.onMetaChange(o.metaData);
34188 var s = this.meta, Record = this.recordType,
34189 f = Record.prototype.fields, fi = f.items, fl = f.length, v;
34191 var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
34192 if(s.totalProperty){
34193 v = parseInt(this.getTotal(o), 10);
34198 if(s.successProperty){
34199 v = this.getSuccess(o);
34200 if(v === false || v === 'false'){
34205 // TODO return Ext.data.Response instance instead. @see #readResponse
34208 records : this.extractData(root, true), // <-- true to return [Ext.data.Record]
34209 totalRecords : totalRecords
34214 buildExtractors : function() {
34218 var s = this.meta, Record = this.recordType,
34219 f = Record.prototype.fields, fi = f.items, fl = f.length;
34221 if(s.totalProperty) {
34222 this.getTotal = this.createAccessor(s.totalProperty);
34224 if(s.successProperty) {
34225 this.getSuccess = this.createAccessor(s.successProperty);
34227 if (s.messageProperty) {
34228 this.getMessage = this.createAccessor(s.messageProperty);
34230 this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;};
34231 if (s.id || s.idProperty) {
34232 var g = this.createAccessor(s.id || s.idProperty);
34233 this.getId = function(rec) {
34235 return (r === undefined || r === '') ? null : r;
34238 this.getId = function(){return null;};
34241 for(var i = 0; i < fl; i++){
34243 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
34244 ef.push(this.createAccessor(map));
34251 * TODO This isn't used anywhere?? Don't we want to use this where possible instead of complex #createAccessor?
34253 simpleAccess : function(obj, subsc) {
34260 createAccessor : function(){
34262 return function(expr) {
34264 return(re.test(expr)) ?
34265 new Function('obj', 'return obj.' + expr) :
34270 return Ext.emptyFn;
34275 * returns extracted, type-cast rows of data. Iterates to call #extractValues for each row
34276 * @param {Object[]/Object} data-root from server response
34277 * @param {Boolean} returnRecords [false] Set true to return instances of Ext.data.Record
34280 extractData : function(root, returnRecords) {
34281 var rs = undefined;
34282 if (this.isData(root)) {
34285 if (Ext.isArray(root)) {
34286 var f = this.recordType.prototype.fields,
34290 if (returnRecords === true) {
34291 var Record = this.recordType;
34292 for (var i = 0; i < root.length; i++) {
34294 var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
34300 for (var i = 0; i < root.length; i++) {
34301 rs.push(this.extractValues(root[i], fi, fl));
34309 * type-casts a single row of raw-data from server
34310 * @param {Object} data
34311 * @param {Array} items
34312 * @param {Integer} len
34315 extractValues : function(data, items, len) {
34316 var f, values = {};
34317 for(var j = 0; j < len; j++){
34319 var v = this.ef[j](data);
34320 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
34327 * @class Ext.data.JsonReader.Error
34328 * Error class for JsonReader
34330 Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
34331 constructor : function(message, arg) {
34333 Ext.Error.call(this, message);
34335 name : 'Ext.data.JsonReader'
34337 Ext.apply(Ext.data.JsonReader.Error.prototype, {
34339 'response': 'An error occurred while json-decoding your server response',
34340 '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.',
34341 '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.',
34342 '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.',
34343 'root-empty': 'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.'
34347 * @class Ext.data.ArrayReader
34348 * @extends Ext.data.JsonReader
34349 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an Array.
34350 * Each element of that Array represents a row of data fields. The
34351 * fields are pulled into a Record object using as a subscript, the <code>mapping</code> property
34352 * of the field definition if it exists, or the field's ordinal position in the definition.</p>
34353 * <p>Example code:</p>
34355 var Employee = Ext.data.Record.create([
34356 {name: 'name', mapping: 1}, // "mapping" only needed if an "id" field is present which
34357 {name: 'occupation', mapping: 2} // precludes using the ordinal position as the index.
34359 var myReader = new Ext.data.ArrayReader({
34360 {@link #idIndex}: 0
34363 * <p>This would consume an Array like this:</p>
34365 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
34368 * Create a new ArrayReader
34369 * @param {Object} meta Metadata configuration options.
34370 * @param {Array/Object} recordType
34371 * <p>Either an Array of {@link Ext.data.Field Field} definition objects (which
34372 * will be passed to {@link Ext.data.Record#create}, or a {@link Ext.data.Record Record}
34373 * constructor created from {@link Ext.data.Record#create}.</p>
34375 Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
34377 * @cfg {String} successProperty
34381 * @cfg {Number} id (optional) The subscript within row Array that provides an ID for the Record.
34382 * Deprecated. Use {@link #idIndex} instead.
34385 * @cfg {Number} idIndex (optional) The subscript within row Array that provides an ID for the Record.
34388 * Create a data block containing Ext.data.Records from an Array.
34389 * @param {Object} o An Array of row objects which represents the dataset.
34390 * @return {Object} data A data block which is used by an Ext.data.Store object as
34391 * a cache of Ext.data.Records.
34393 readRecords : function(o){
34394 this.arrayData = o;
34396 sid = s ? Ext.num(s.idIndex, s.id) : null,
34397 recordType = this.recordType,
34398 fields = recordType.prototype.fields,
34402 if(!this.getRoot) {
34403 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p) {return p;};
34404 if(s.totalProperty) {
34405 this.getTotal = this.getJsonAccessor(s.totalProperty);
34409 var root = this.getRoot(o);
34411 for(var i = 0, len = root.length; i < len; i++) {
34414 id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
34415 for(var j = 0, jlen = fields.length; j < jlen; j++) {
34416 var f = fields.items[j],
34417 k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
34418 v = n[k] !== undefined ? n[k] : f.defaultValue;
34419 v = f.convert(v, n);
34420 values[f.name] = v;
34422 var record = new recordType(values, id);
34424 records[records.length] = record;
34427 var totalRecords = records.length;
34429 if(s.totalProperty) {
34430 v = parseInt(this.getTotal(o), 10);
34438 totalRecords : totalRecords
34442 * @class Ext.data.ArrayStore
34443 * @extends Ext.data.Store
34444 * <p>Formerly known as "SimpleStore".</p>
34445 * <p>Small helper class to make creating {@link Ext.data.Store}s from Array data easier.
34446 * An ArrayStore will be automatically configured with a {@link Ext.data.ArrayReader}.</p>
34447 * <p>A store configuration would be something like:<pre><code>
34448 var store = new Ext.data.ArrayStore({
34451 storeId: 'myStore',
34456 {name: 'price', type: 'float'},
34457 {name: 'change', type: 'float'},
34458 {name: 'pctChange', type: 'float'},
34459 {name: 'lastChange', type: 'date', dateFormat: 'n/j h:ia'}
34462 * </code></pre></p>
34463 * <p>This store is configured to consume a returned object of the form:<pre><code>
34465 ['3m Co',71.72,0.02,0.03,'9/1 12:00am'],
34466 ['Alcoa Inc',29.01,0.42,1.47,'9/1 12:00am'],
34467 ['Boeing Co.',75.43,0.53,0.71,'9/1 12:00am'],
34468 ['Hewlett-Packard Co.',36.53,-0.03,-0.08,'9/1 12:00am'],
34469 ['Wal-Mart Stores, Inc.',45.45,0.73,1.63,'9/1 12:00am']
34472 * An object literal of this form could also be used as the {@link #data} config option.</p>
34473 * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
34474 * <b>{@link Ext.data.ArrayReader ArrayReader}</b>.</p>
34476 * @param {Object} config
34477 * @xtype arraystore
34479 Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
34481 * @cfg {Ext.data.DataReader} reader @hide
34483 constructor: function(config){
34484 Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
34485 reader: new Ext.data.ArrayReader(config)
34489 loadData : function(data, append){
34490 if(this.expandData === true){
34492 for(var i = 0, len = data.length; i < len; i++){
34493 r[r.length] = [data[i]];
34497 Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
34500 Ext.reg('arraystore', Ext.data.ArrayStore);
34502 // backwards compat
34503 Ext.data.SimpleStore = Ext.data.ArrayStore;
34504 Ext.reg('simplestore', Ext.data.SimpleStore);/**
34505 * @class Ext.data.JsonStore
34506 * @extends Ext.data.Store
34507 * <p>Small helper class to make creating {@link Ext.data.Store}s from JSON data easier.
34508 * A JsonStore will be automatically configured with a {@link Ext.data.JsonReader}.</p>
34509 * <p>A store configuration would be something like:<pre><code>
34510 var store = new Ext.data.JsonStore({
34513 url: 'get-images.php',
34514 storeId: 'myStore',
34517 idProperty: 'name',
34518 fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
34520 * </code></pre></p>
34521 * <p>This store is configured to consume a returned object of the form:<pre><code>
34524 {name: 'Image one', url:'/GetImage.php?id=1', size:46.5, lastmod: new Date(2007, 10, 29)},
34525 {name: 'Image Two', url:'/GetImage.php?id=2', size:43.2, lastmod: new Date(2007, 10, 30)}
34529 * An object literal of this form could also be used as the {@link #data} config option.</p>
34530 * <p><b>*Note:</b> Although not listed here, this class accepts all of the configuration options of
34531 * <b>{@link Ext.data.JsonReader JsonReader}</b>.</p>
34533 * @param {Object} config
34536 Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
34538 * @cfg {Ext.data.DataReader} reader @hide
34540 constructor: function(config){
34541 Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
34542 reader: new Ext.data.JsonReader(config)
34546 Ext.reg('jsonstore', Ext.data.JsonStore);/**
34547 * @class Ext.data.XmlWriter
34548 * @extends Ext.data.DataWriter
34549 * DataWriter extension for writing an array or single {@link Ext.data.Record} object(s) in preparation for executing a remote CRUD action via XML.
34551 Ext.data.XmlWriter = function(params) {
34552 Ext.data.XmlWriter.superclass.constructor.apply(this, arguments);
34553 this.tpl = new Ext.XTemplate(this.tpl).compile();
34555 Ext.extend(Ext.data.XmlWriter, Ext.data.DataWriter, {
34557 * @cfg {String} root [records] The name of the root element when writing <b>multiple</b> records to the server. Each
34558 * xml-record written to the server will be wrapped in an element named after {@link Ext.data.XmlReader#record} property.
34561 <?xml version="1.0" encoding="UTF-8"?>
34562 <user><first>Barney</first></user>
34564 * However, when <b>multiple</b> records are written in a batch-operation, these records must be wrapped in a containing
34568 <?xml version="1.0" encoding="UTF-8"?>
34570 <first>Barney</first></user>
34571 <records><first>Barney</first></user>
34574 * Defaults to <tt>records</tt>
34578 * @cfg {String} xmlVersion [1.0] The <tt>version</tt> written to header of xml documents.
34579 <code><pre><?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
34581 xmlVersion : '1.0',
34583 * @cfg {String} xmlEncoding [ISO-8859-15] The <tt>encoding</tt> written to header of xml documents.
34584 <code><pre><?xml version="1.0" encoding="ISO-8859-15"?></pre></code>
34586 xmlEncoding: 'ISO-8859-15',
34588 * @cfg {String} tpl The xml template. Defaults to
34590 <?xml version="{version}" encoding="{encoding}"?>
34591 <tpl if="{[values.nodes.length>1]}"><{root}}>',
34592 <tpl for="records">
34593 <{parent.record}>
34594 <tpl for="fields">
34595 <{name}>{value}</{name}>
34597 </{parent.record}>
34599 <tpl if="{[values.records.length>1]}"></{root}}></tpl>
34602 // Break up encoding here in case it's being included by some kind of page that will parse it (eg. PHP)
34603 tpl: '<tpl for="."><' + '?xml version="{version}" encoding="{encoding}"?' + '><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}</tpl></tpl></tpl><tpl if="records.length>1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length>1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',
34606 * Final action of a write event. Apply the written data-object to params.
34607 * @param {String} action [Ext.data.Api.create|read|update|destroy]
34608 * @param {Ext.data.Record/Ext.data.Record[]} rs
34609 * @param {Object} http params
34610 * @param {Object/Object[]} rendered data.
34612 render : function(action, rs, params, data) {
34613 params.xmlData = this.tpl.applyTemplate({
34614 version: this.xmlVersion,
34615 encoding: this.xmlEncoding,
34616 record: this.meta.record,
34618 records: (Ext.isArray(rs)) ? data : [data]
34623 * Converts an Ext.data.Record to xml
34624 * @param {Ext.data.Record} rec
34625 * @return {String} rendered xml-element
34628 toXml : function(data) {
34630 Ext.iterate(data, function(k, v) {
34643 * @param {Ext.data.Record} rec
34644 * @return {String} xml element
34647 createRecord : function(rec) {
34648 return this.toXml(this.toHash(rec));
34653 * @param {Ext.data.Record} rec
34654 * @return {String} xml element
34657 updateRecord : function(rec) {
34658 return this.toXml(this.toHash(rec));
34663 * @param {Ext.data.Record} rec
34664 * @return {String} xml element
34666 destroyRecord : function(rec) {
34668 data[this.meta.idProperty] = rec.id;
34669 return this.toXml(data);
34674 * @class Ext.data.XmlReader
34675 * @extends Ext.data.DataReader
34676 * <p>Data reader class to create an Array of {@link Ext.data.Record} objects from an XML document
34677 * based on mappings in a provided {@link Ext.data.Record} constructor.</p>
34678 * <p><b>Note</b>: that in order for the browser to parse a returned XML document, the Content-Type
34679 * header in the HTTP response must be set to "text/xml" or "application/xml".</p>
34680 * <p>Example code:</p>
34682 var Employee = Ext.data.Record.create([
34683 {name: 'name', mapping: 'name'}, // "mapping" property not needed if it is the same as "name"
34684 {name: 'occupation'} // This field will use "occupation" as the mapping.
34686 var myReader = new Ext.data.XmlReader({
34687 totalProperty: "results", // The element which contains the total dataset size (optional)
34688 record: "row", // The repeated element which contains row information
34689 idProperty: "id" // The element within the row that provides an ID for the record (optional)
34690 messageProperty: "msg" // The element within the response that provides a user-feedback message (optional)
34694 * This would consume an XML file like this:
34696 <?xml version="1.0" encoding="UTF-8"?>
34698 <results>2</results>
34701 <name>Bill</name>
34702 <occupation>Gardener</occupation>
34706 <name>Ben</name>
34707 <occupation>Horticulturalist</occupation>
34711 * @cfg {String} totalProperty The DomQuery path from which to retrieve the total number of records
34712 * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
34713 * paged from the remote server.
34714 * @cfg {String} record The DomQuery path to the repeated element which contains record information.
34715 * @cfg {String} record The DomQuery path to the repeated element which contains record information.
34716 * @cfg {String} successProperty The DomQuery path to the success attribute used by forms.
34717 * @cfg {String} idPath The DomQuery path relative from the record element to the element that contains
34718 * a record identifier value.
34720 * Create a new XmlReader.
34721 * @param {Object} meta Metadata configuration options
34722 * @param {Object} recordType Either an Array of field definition objects as passed to
34723 * {@link Ext.data.Record#create}, or a Record constructor object created using {@link Ext.data.Record#create}.
34725 Ext.data.XmlReader = function(meta, recordType){
34728 // backwards compat, convert idPath to idProperty
34729 meta.idProperty = meta.idProperty || meta.idPath;
34731 Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields);
34733 Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
34735 * This method is only used by a DataProxy which has retrieved data from a remote server.
34736 * @param {Object} response The XHR object which contains the parsed XML document. The response is expected
34737 * to contain a property called <tt>responseXML</tt> which refers to an XML document object.
34738 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
34739 * a cache of Ext.data.Records.
34741 read : function(response){
34742 var doc = response.responseXML;
34744 throw {message: "XmlReader.read: XML Document not available"};
34746 return this.readRecords(doc);
34750 * Create a data block containing Ext.data.Records from an XML document.
34751 * @param {Object} doc A parsed XML document.
34752 * @return {Object} records A data block which is used by an {@link Ext.data.Store} as
34753 * a cache of Ext.data.Records.
34755 readRecords : function(doc){
34757 * After any data loads/reads, the raw XML Document is available for further custom processing.
34758 * @type XMLDocument
34760 this.xmlData = doc;
34762 var root = doc.documentElement || doc,
34767 if(this.meta.totalProperty){
34768 totalRecords = this.getTotal(root, 0);
34770 if(this.meta.successProperty){
34771 success = this.getSuccess(root);
34774 var records = this.extractData(q.select(this.meta.record, root), true); // <-- true to return Ext.data.Record[]
34776 // TODO return Ext.data.Response instance. @see #readResponse
34780 totalRecords : totalRecords || records.length
34785 * Decode a json response from server.
34786 * @param {String} action [{@link Ext.data.Api#actions} create|read|update|destroy]
34787 * @param {Ext.data.Response} response Returns an instance of {@link Ext.data.Response}
34789 readResponse : function(action, response) {
34790 var q = Ext.DomQuery,
34791 doc = response.responseXML;
34793 var res = new Ext.data.Response({
34795 success : this.getSuccess(doc),
34796 message: this.getMessage(doc),
34797 data: this.extractData(q.select(this.meta.record, doc) || q.select(this.meta.root, doc)),
34801 if (Ext.isEmpty(res.success)) {
34802 throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty);
34805 if (action === Ext.data.Api.actions.create) {
34806 var def = Ext.isDefined(res.data);
34807 if (def && Ext.isEmpty(res.data)) {
34808 throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
34811 throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
34817 getSuccess : function() {
34822 * build response-data extractor functions.
34826 buildExtractors : function() {
34831 Record = this.recordType,
34832 f = Record.prototype.fields,
34836 if(s.totalProperty) {
34837 this.getTotal = this.createAccessor(s.totalProperty);
34839 if(s.successProperty) {
34840 this.getSuccess = this.createAccessor(s.successProperty);
34842 if (s.messageProperty) {
34843 this.getMessage = this.createAccessor(s.messageProperty);
34845 this.getRoot = function(res) {
34846 return (!Ext.isEmpty(res[this.meta.record])) ? res[this.meta.record] : res[this.meta.root];
34848 if (s.idPath || s.idProperty) {
34849 var g = this.createAccessor(s.idPath || s.idProperty);
34850 this.getId = function(rec) {
34851 var id = g(rec) || rec.id;
34852 return (id === undefined || id === '') ? null : id;
34855 this.getId = function(){return null;};
34858 for(var i = 0; i < fl; i++){
34860 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
34861 ef.push(this.createAccessor(map));
34867 * Creates a function to return some particular key of data from a response.
34868 * @param {String} key
34869 * @return {Function}
34873 createAccessor : function(){
34874 var q = Ext.DomQuery;
34875 return function(key) {
34877 case this.meta.totalProperty:
34878 return function(root, def){
34879 return q.selectNumber(key, root, def);
34882 case this.meta.successProperty:
34883 return function(root, def) {
34884 var sv = q.selectValue(key, root, true);
34885 var success = sv !== false && sv !== 'false';
34890 return function(root, def) {
34891 return q.selectValue(key, root, def);
34899 * Extracts rows of record-data from server. iterates and calls #extractValues
34900 * TODO I don't care much for method-names of #extractData, #extractValues.
34901 * @param {Array} root
34902 * @param {Boolean} returnRecords When true, will return instances of Ext.data.Record; otherwise just hashes.
34906 extractData : function(root, returnRecords) {
34907 var Record = this.recordType,
34909 f = Record.prototype.fields,
34912 if (returnRecords === true) {
34913 for (var i = 0, len = root.length; i < len; i++) {
34914 var data = root[i],
34915 record = new Record(this.extractValues(data, fi, fl), this.getId(data));
34917 record.node = data;
34918 records.push(record);
34921 for (var i = 0, len = root.length; i < len; i++) {
34922 records.push(this.extractValues(root[i], fi, fl));
34929 * extracts values and type-casts a row of data from server, extracted by #extractData
34930 * @param {Hash} data
34931 * @param {Ext.data.Field[]} items
34932 * @param {Number} len
34936 extractValues : function(data, items, len) {
34937 var f, values = {};
34938 for(var j = 0; j < len; j++){
34940 var v = this.ef[j](data);
34941 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
34946 * @class Ext.data.XmlStore
\r
34947 * @extends Ext.data.Store
\r
34948 * <p>Small helper class to make creating {@link Ext.data.Store}s from XML data easier.
\r
34949 * A XmlStore will be automatically configured with a {@link Ext.data.XmlReader}.</p>
\r
34950 * <p>A store configuration would be something like:<pre><code>
\r
34951 var store = new Ext.data.XmlStore({
\r
34953 autoDestroy: true,
\r
34954 storeId: 'myStore',
\r
34955 url: 'sheldon.xml', // automatically configures a HttpProxy
\r
34956 // reader configs
\r
34957 record: 'Item', // records will have an "Item" tag
\r
34959 totalRecords: '@TotalResults'
\r
34961 // set up the fields mapping into the xml doc
\r
34962 // The first needs mapping, the others are very basic
\r
34963 {name: 'Author', mapping: 'ItemAttributes > Author'},
\r
34964 'Title', 'Manufacturer', 'ProductGroup'
\r
34967 * </code></pre></p>
\r
34968 * <p>This store is configured to consume a returned object of the form:<pre><code>
\r
34969 <?xml version="1.0" encoding="UTF-8"?>
\r
34970 <ItemSearchResponse xmlns="http://webservices.amazon.com/AWSECommerceService/2009-05-15">
\r
34973 <IsValid>True</IsValid>
\r
34974 <ItemSearchRequest>
\r
34975 <Author>Sidney Sheldon</Author>
\r
34976 <SearchIndex>Books</SearchIndex>
\r
34977 </ItemSearchRequest>
\r
34979 <TotalResults>203</TotalResults>
\r
34980 <TotalPages>21</TotalPages>
\r
34982 <ASIN>0446355453</ASIN>
\r
34983 <DetailPageURL>
\r
34984 http://www.amazon.com/
\r
34985 </DetailPageURL>
\r
34986 <ItemAttributes>
\r
34987 <Author>Sidney Sheldon</Author>
\r
34988 <Manufacturer>Warner Books</Manufacturer>
\r
34989 <ProductGroup>Book</ProductGroup>
\r
34990 <Title>Master of the Game</Title>
\r
34991 </ItemAttributes>
\r
34994 </ItemSearchResponse>
\r
34996 * An object literal of this form could also be used as the {@link #data} config option.</p>
\r
34997 * <p><b>Note:</b> Although not listed here, this class accepts all of the configuration options of
\r
34998 * <b>{@link Ext.data.XmlReader XmlReader}</b>.</p>
\r
35000 * @param {Object} config
\r
35001 * @xtype xmlstore
\r
35003 Ext.data.XmlStore = Ext.extend(Ext.data.Store, {
\r
35005 * @cfg {Ext.data.DataReader} reader @hide
\r
35007 constructor: function(config){
\r
35008 Ext.data.XmlStore.superclass.constructor.call(this, Ext.apply(config, {
\r
35009 reader: new Ext.data.XmlReader(config)
\r
35013 Ext.reg('xmlstore', Ext.data.XmlStore);/**
\r
35014 * @class Ext.data.GroupingStore
\r
35015 * @extends Ext.data.Store
\r
35016 * A specialized store implementation that provides for grouping records by one of the available fields. This
\r
35017 * is usually used in conjunction with an {@link Ext.grid.GroupingView} to proved the data model for
\r
35018 * a grouped GridPanel.
\r
35020 * Creates a new GroupingStore.
\r
35021 * @param {Object} config A config object containing the objects needed for the Store to access data,
\r
35022 * and read the data into Records.
\r
35023 * @xtype groupingstore
\r
35025 Ext.data.GroupingStore = Ext.extend(Ext.data.Store, {
\r
35028 constructor: function(config){
\r
35029 Ext.data.GroupingStore.superclass.constructor.call(this, config);
\r
35030 this.applyGroupField();
\r
35034 * @cfg {String} groupField
\r
35035 * The field name by which to sort the store's data (defaults to '').
\r
35038 * @cfg {Boolean} remoteGroup
\r
35039 * True if the grouping should apply on the server side, false if it is local only (defaults to false). If the
\r
35040 * grouping is local, it can be applied immediately to the data. If it is remote, then it will simply act as a
\r
35041 * helper, automatically sending the grouping field name as the 'groupBy' param with each XHR call.
\r
35043 remoteGroup : false,
\r
35045 * @cfg {Boolean} groupOnSort
\r
35046 * True to sort the data on the grouping field when a grouping operation occurs, false to sort based on the
\r
35047 * existing sort info (defaults to false).
\r
35049 groupOnSort:false,
\r
35051 groupDir : 'ASC',
\r
35054 * Clears any existing grouping and refreshes the data using the default sort.
\r
35056 clearGrouping : function(){
\r
35057 this.groupField = false;
\r
35058 if(this.remoteGroup){
\r
35059 if(this.baseParams){
\r
35060 delete this.baseParams.groupBy;
\r
35062 var lo = this.lastOptions;
\r
35063 if(lo && lo.params){
\r
35064 delete lo.params.groupBy;
\r
35068 this.applySort();
\r
35069 this.fireEvent('datachanged', this);
\r
35074 * Groups the data by the specified field.
\r
35075 * @param {String} field The field name by which to sort the store's data
\r
35076 * @param {Boolean} forceRegroup (optional) True to force the group to be refreshed even if the field passed
\r
35077 * in is the same as the current grouping field, false to skip grouping on the same field (defaults to false)
\r
35079 groupBy : function(field, forceRegroup, direction){
\r
35080 direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir;
\r
35081 if(this.groupField == field && this.groupDir == direction && !forceRegroup){
\r
35082 return; // already grouped by this field
\r
35084 this.groupField = field;
\r
35085 this.groupDir = direction;
\r
35086 this.applyGroupField();
\r
35087 if(this.groupOnSort){
\r
35088 this.sort(field, direction);
\r
35091 if(this.remoteGroup){
\r
35094 var si = this.sortInfo || {};
\r
35095 if(si.field != field || si.direction != direction){
\r
35096 this.applySort();
\r
35098 this.sortData(field, direction);
\r
35100 this.fireEvent('datachanged', this);
\r
35105 applyGroupField: function(){
\r
35106 if(this.remoteGroup){
\r
35107 if(!this.baseParams){
\r
35108 this.baseParams = {};
\r
35110 this.baseParams.groupBy = this.groupField;
\r
35111 this.baseParams.groupDir = this.groupDir;
\r
35116 applySort : function(){
\r
35117 Ext.data.GroupingStore.superclass.applySort.call(this);
\r
35118 if(!this.groupOnSort && !this.remoteGroup){
\r
35119 var gs = this.getGroupState();
\r
35120 if(gs && (gs != this.sortInfo.field || this.groupDir != this.sortInfo.direction)){
\r
35121 this.sortData(this.groupField, this.groupDir);
\r
35127 applyGrouping : function(alwaysFireChange){
\r
35128 if(this.groupField !== false){
\r
35129 this.groupBy(this.groupField, true, this.groupDir);
\r
35132 if(alwaysFireChange === true){
\r
35133 this.fireEvent('datachanged', this);
\r
35140 getGroupState : function(){
\r
35141 return this.groupOnSort && this.groupField !== false ?
\r
35142 (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField;
\r
35145 Ext.reg('groupingstore', Ext.data.GroupingStore);/**
\r
35146 * @class Ext.data.DirectProxy
\r
35147 * @extends Ext.data.DataProxy
\r
35149 Ext.data.DirectProxy = function(config){
\r
35150 Ext.apply(this, config);
\r
35151 if(typeof this.paramOrder == 'string'){
\r
35152 this.paramOrder = this.paramOrder.split(/[\s,|]/);
\r
35154 Ext.data.DirectProxy.superclass.constructor.call(this, config);
\r
35157 Ext.extend(Ext.data.DirectProxy, Ext.data.DataProxy, {
\r
35159 * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. A list of params to be executed
\r
35160 * server side. Specify the params in the order in which they must be executed on the server-side
\r
35161 * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
\r
35162 * comma, or pipe. For example,
\r
35163 * any of the following would be acceptable:<pre><code>
\r
35164 paramOrder: ['param1','param2','param3']
\r
35165 paramOrder: 'param1 param2 param3'
\r
35166 paramOrder: 'param1,param2,param3'
\r
35167 paramOrder: 'param1|param2|param'
\r
35170 paramOrder: undefined,
\r
35173 * @cfg {Boolean} paramsAsHash
\r
35174 * Send parameters as a collection of named arguments (defaults to <tt>true</tt>). Providing a
\r
35175 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
\r
35177 paramsAsHash: true,
\r
35180 * @cfg {Function} directFn
\r
35181 * Function to call when executing a request. directFn is a simple alternative to defining the api configuration-parameter
\r
35182 * for Store's which will not implement a full CRUD api.
\r
35184 directFn : undefined,
\r
35187 doRequest : function(action, rs, params, reader, callback, scope, options) {
\r
35189 var directFn = this.api[action] || this.directFn;
\r
35191 switch (action) {
\r
35192 case Ext.data.Api.actions.create:
\r
35193 args.push(params.jsonData[reader.meta.root]); // <-- create(Hash)
\r
35195 case Ext.data.Api.actions.read:
\r
35196 // If the method has no parameters, ignore the paramOrder/paramsAsHash.
\r
35197 if(directFn.directCfg.method.len > 0){
\r
35198 if(this.paramOrder){
\r
35199 for(var i = 0, len = this.paramOrder.length; i < len; i++){
\r
35200 args.push(params[this.paramOrder[i]]);
\r
35202 }else if(this.paramsAsHash){
\r
35203 args.push(params);
\r
35207 case Ext.data.Api.actions.update:
\r
35208 args.push(params.jsonData[reader.meta.root]); // <-- update(Hash/Hash[])
\r
35210 case Ext.data.Api.actions.destroy:
\r
35211 args.push(params.jsonData[reader.meta.root]); // <-- destroy(Int/Int[])
\r
35216 params : params || {},
\r
35218 callback : callback,
\r
35225 args.push(this.createCallback(action, rs, trans), this);
\r
35226 directFn.apply(window, args);
\r
35230 createCallback : function(action, rs, trans) {
\r
35231 return function(result, res) {
\r
35232 if (!res.status) {
\r
35233 // @deprecated fire loadexception
\r
35234 if (action === Ext.data.Api.actions.read) {
\r
35235 this.fireEvent("loadexception", this, trans, res, null);
\r
35237 this.fireEvent('exception', this, 'remote', action, trans, res, null);
\r
35238 trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
\r
35241 if (action === Ext.data.Api.actions.read) {
\r
35242 this.onRead(action, trans, result, res);
\r
35244 this.onWrite(action, trans, result, res, rs);
\r
35249 * Callback for read actions
\r
35250 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
35251 * @param {Object} trans The request transaction object
\r
35252 * @param {Object} res The server response
\r
35255 onRead : function(action, trans, result, res) {
\r
35258 records = trans.reader.readRecords(result);
\r
35261 // @deprecated: Fire old loadexception for backwards-compat.
\r
35262 this.fireEvent("loadexception", this, trans, res, ex);
\r
35264 this.fireEvent('exception', this, 'response', action, trans, res, ex);
\r
35265 trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
\r
35268 this.fireEvent("load", this, res, trans.request.arg);
\r
35269 trans.request.callback.call(trans.request.scope, records, trans.request.arg, true);
\r
35272 * Callback for write actions
\r
35273 * @param {String} action [Ext.data.Api.actions.create|read|update|destroy]
\r
35274 * @param {Object} trans The request transaction object
\r
35275 * @param {Object} res The server response
\r
35278 onWrite : function(action, trans, result, res, rs) {
\r
35279 var data = trans.reader.extractData(result);
\r
35280 this.fireEvent("write", this, action, data, res, rs, trans.request.arg);
\r
35281 trans.request.callback.call(trans.request.scope, data, res, true);
\r
35286 * @class Ext.data.DirectStore
\r
35287 * @extends Ext.data.Store
\r
35288 * <p>Small helper class to create an {@link Ext.data.Store} configured with an
\r
35289 * {@link Ext.data.DirectProxy} and {@link Ext.data.JsonReader} to make interacting
\r
35290 * with an {@link Ext.Direct} Server-side {@link Ext.direct.Provider Provider} easier.
\r
35291 * To create a different proxy/reader combination create a basic {@link Ext.data.Store}
\r
35292 * configured as needed.</p>
\r
35294 * <p><b>*Note:</b> Although they are not listed, this class inherits all of the config options of:</p>
\r
35295 * <div><ul class="mdetail-params">
\r
35296 * <li><b>{@link Ext.data.Store Store}</b></li>
\r
35297 * <div class="sub-desc"><ul class="mdetail-params">
\r
35300 * <li><b>{@link Ext.data.JsonReader JsonReader}</b></li>
\r
35301 * <div class="sub-desc"><ul class="mdetail-params">
\r
35302 * <li><tt><b>{@link Ext.data.JsonReader#root root}</b></tt></li>
\r
35303 * <li><tt><b>{@link Ext.data.JsonReader#idProperty idProperty}</b></tt></li>
\r
35304 * <li><tt><b>{@link Ext.data.JsonReader#totalProperty totalProperty}</b></tt></li>
\r
35307 * <li><b>{@link Ext.data.DirectProxy DirectProxy}</b></li>
\r
35308 * <div class="sub-desc"><ul class="mdetail-params">
\r
35309 * <li><tt><b>{@link Ext.data.DirectProxy#directFn directFn}</b></tt></li>
\r
35310 * <li><tt><b>{@link Ext.data.DirectProxy#paramOrder paramOrder}</b></tt></li>
\r
35311 * <li><tt><b>{@link Ext.data.DirectProxy#paramsAsHash paramsAsHash}</b></tt></li>
\r
35315 * @xtype directstore
\r
35318 * @param {Object} config
\r
35320 Ext.data.DirectStore = function(c){
\r
35321 // each transaction upon a singe record will generatie a distinct Direct transaction since Direct queues them into one Ajax request.
\r
35322 c.batchTransactions = false;
\r
35324 Ext.data.DirectStore.superclass.constructor.call(this, Ext.apply(c, {
\r
35325 proxy: (typeof(c.proxy) == 'undefined') ? new Ext.data.DirectProxy(Ext.copyTo({}, c, 'paramOrder,paramsAsHash,directFn,api')) : c.proxy,
\r
35326 reader: (typeof(c.reader) == 'undefined' && typeof(c.fields) == 'object') ? new Ext.data.JsonReader(Ext.copyTo({}, c, 'totalProperty,root,idProperty'), c.fields) : c.reader
\r
35329 Ext.extend(Ext.data.DirectStore, Ext.data.Store, {});
\r
35330 Ext.reg('directstore', Ext.data.DirectStore);
35332 * @class Ext.Direct
\r
35333 * @extends Ext.util.Observable
\r
35334 * <p><b><u>Overview</u></b></p>
\r
35336 * <p>Ext.Direct aims to streamline communication between the client and server
\r
35337 * by providing a single interface that reduces the amount of common code
\r
35338 * typically required to validate data and handle returned data packets
\r
35339 * (reading data, error conditions, etc).</p>
\r
35341 * <p>The Ext.direct namespace includes several classes for a closer integration
\r
35342 * with the server-side. The Ext.data namespace also includes classes for working
\r
35343 * with Ext.data.Stores which are backed by data from an Ext.Direct method.</p>
\r
35345 * <p><b><u>Specification</u></b></p>
\r
35347 * <p>For additional information consult the
\r
35348 * <a href="http://extjs.com/products/extjs/direct.php">Ext.Direct Specification</a>.</p>
\r
35350 * <p><b><u>Providers</u></b></p>
\r
35352 * <p>Ext.Direct uses a provider architecture, where one or more providers are
\r
35353 * used to transport data to and from the server. There are several providers
\r
35354 * that exist in the core at the moment:</p><div class="mdetail-params"><ul>
\r
35356 * <li>{@link Ext.direct.JsonProvider JsonProvider} for simple JSON operations</li>
\r
35357 * <li>{@link Ext.direct.PollingProvider PollingProvider} for repeated requests</li>
\r
35358 * <li>{@link Ext.direct.RemotingProvider RemotingProvider} exposes server side
\r
35359 * on the client.</li>
\r
35362 * <p>A provider does not need to be invoked directly, providers are added via
\r
35363 * {@link Ext.Direct}.{@link Ext.Direct#add add}.</p>
\r
35365 * <p><b><u>Router</u></b></p>
\r
35367 * <p>Ext.Direct utilizes a "router" on the server to direct requests from the client
\r
35368 * to the appropriate server-side method. Because the Ext.Direct API is completely
\r
35369 * platform-agnostic, you could completely swap out a Java based server solution
\r
35370 * and replace it with one that uses C# without changing the client side JavaScript
\r
35373 * <p><b><u>Server side events</u></b></p>
\r
35375 * <p>Custom events from the server may be handled by the client by adding
\r
35376 * listeners, for example:</p>
\r
35378 {"type":"event","name":"message","data":"Successfully polled at: 11:19:30 am"}
\r
35380 // add a handler for a 'message' event sent by the server
\r
35381 Ext.Direct.on('message', function(e){
\r
35382 out.append(String.format('<p><i>{0}</i></p>', e.data));
\r
35383 out.el.scrollTo('t', 100000, true);
\r
35388 Ext.Direct = Ext.extend(Ext.util.Observable, {
\r
35390 * Each event type implements a getData() method. The default event types are:
\r
35391 * <div class="mdetail-params"><ul>
\r
35392 * <li><b><tt>event</tt></b> : Ext.Direct.Event</li>
\r
35393 * <li><b><tt>exception</tt></b> : Ext.Direct.ExceptionEvent</li>
\r
35394 * <li><b><tt>rpc</tt></b> : Ext.Direct.RemotingEvent</li>
\r
35396 * @property eventTypes
\r
35401 * Four types of possible exceptions which can occur:
\r
35402 * <div class="mdetail-params"><ul>
\r
35403 * <li><b><tt>Ext.Direct.exceptions.TRANSPORT</tt></b> : 'xhr'</li>
\r
35404 * <li><b><tt>Ext.Direct.exceptions.PARSE</tt></b> : 'parse'</li>
\r
35405 * <li><b><tt>Ext.Direct.exceptions.LOGIN</tt></b> : 'login'</li>
\r
35406 * <li><b><tt>Ext.Direct.exceptions.SERVER</tt></b> : 'exception'</li>
\r
35408 * @property exceptions
\r
35412 TRANSPORT: 'xhr',
\r
35415 SERVER: 'exception'
\r
35419 constructor: function(){
\r
35423 * Fires after an event.
\r
35424 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
35425 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
35429 * @event exception
\r
35430 * Fires after an event exception.
\r
35431 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
35435 this.transactions = {};
\r
35436 this.providers = {};
\r
35440 * Adds an Ext.Direct Provider and creates the proxy or stub methods to execute server-side methods.
\r
35441 * If the provider is not already connected, it will auto-connect.
\r
35443 var pollProv = new Ext.direct.PollingProvider({
\r
35444 url: 'php/poll2.php'
\r
35447 Ext.Direct.addProvider(
\r
35449 "type":"remoting", // create a {@link Ext.direct.RemotingProvider}
\r
35450 "url":"php\/router.php", // url to connect to the Ext.Direct server-side router.
\r
35451 "actions":{ // each property within the actions object represents a Class
\r
35452 "TestAction":[ // array of methods within each server side Class
\r
35454 "name":"doEcho", // name of method
\r
35457 "name":"multiply",
\r
35461 "formHandler":true, // handle form on server with Ext.Direct.Transaction
\r
35465 "namespace":"myApplication",// namespace to create the Remoting Provider in
\r
35467 type: 'polling', // create a {@link Ext.direct.PollingProvider}
\r
35468 url: 'php/poll.php'
\r
35470 pollProv // reference to previously created instance
\r
35473 * @param {Object/Array} provider Accepts either an Array of Provider descriptions (an instance
\r
35474 * or config object for a Provider) or any number of Provider descriptions as arguments. Each
\r
35475 * Provider description instructs Ext.Direct how to create client-side stub methods.
\r
35477 addProvider : function(provider){
\r
35478 var a = arguments;
\r
35479 if(a.length > 1){
\r
35480 for(var i = 0, len = a.length; i < len; i++){
\r
35481 this.addProvider(a[i]);
\r
35486 // if provider has not already been instantiated
\r
35487 if(!provider.events){
\r
35488 provider = new Ext.Direct.PROVIDERS[provider.type](provider);
\r
35490 provider.id = provider.id || Ext.id();
\r
35491 this.providers[provider.id] = provider;
\r
35493 provider.on('data', this.onProviderData, this);
\r
35494 provider.on('exception', this.onProviderException, this);
\r
35497 if(!provider.isConnected()){
\r
35498 provider.connect();
\r
35505 * Retrieve a {@link Ext.direct.Provider provider} by the
\r
35506 * <b><tt>{@link Ext.direct.Provider#id id}</tt></b> specified when the provider is
\r
35507 * {@link #addProvider added}.
\r
35508 * @param {String} id Unique identifier assigned to the provider when calling {@link #addProvider}
\r
35510 getProvider : function(id){
\r
35511 return this.providers[id];
\r
35514 removeProvider : function(id){
\r
35515 var provider = id.id ? id : this.providers[id.id];
\r
35516 provider.un('data', this.onProviderData, this);
\r
35517 provider.un('exception', this.onProviderException, this);
\r
35518 delete this.providers[provider.id];
\r
35522 addTransaction: function(t){
\r
35523 this.transactions[t.tid] = t;
\r
35527 removeTransaction: function(t){
\r
35528 delete this.transactions[t.tid || t];
\r
35532 getTransaction: function(tid){
\r
35533 return this.transactions[tid.tid || tid];
\r
35536 onProviderData : function(provider, e){
\r
35537 if(Ext.isArray(e)){
\r
35538 for(var i = 0, len = e.length; i < len; i++){
\r
35539 this.onProviderData(provider, e[i]);
\r
35543 if(e.name && e.name != 'event' && e.name != 'exception'){
\r
35544 this.fireEvent(e.name, e);
\r
35545 }else if(e.type == 'exception'){
\r
35546 this.fireEvent('exception', e);
\r
35548 this.fireEvent('event', e, provider);
\r
35551 createEvent : function(response, extraProps){
\r
35552 return new Ext.Direct.eventTypes[response.type](Ext.apply(response, extraProps));
\r
35555 // overwrite impl. with static instance
\r
35556 Ext.Direct = new Ext.Direct();
\r
35558 Ext.Direct.TID = 1;
\r
35559 Ext.Direct.PROVIDERS = {};/**
\r
35560 * @class Ext.Direct.Transaction
\r
35561 * @extends Object
\r
35562 * <p>Supporting Class for Ext.Direct (not intended to be used directly).</p>
\r
35564 * @param {Object} config
\r
35566 Ext.Direct.Transaction = function(config){
\r
35567 Ext.apply(this, config);
\r
35568 this.tid = ++Ext.Direct.TID;
\r
35569 this.retryCount = 0;
\r
35571 Ext.Direct.Transaction.prototype = {
\r
35572 send: function(){
\r
35573 this.provider.queueTransaction(this);
\r
35576 retry: function(){
\r
35577 this.retryCount++;
\r
35581 getProvider: function(){
\r
35582 return this.provider;
\r
35584 };Ext.Direct.Event = function(config){
\r
35585 Ext.apply(this, config);
\r
35587 Ext.Direct.Event.prototype = {
\r
35589 getData: function(){
\r
35590 return this.data;
\r
35594 Ext.Direct.RemotingEvent = Ext.extend(Ext.Direct.Event, {
\r
35596 getTransaction: function(){
\r
35597 return this.transaction || Ext.Direct.getTransaction(this.tid);
\r
35601 Ext.Direct.ExceptionEvent = Ext.extend(Ext.Direct.RemotingEvent, {
\r
35603 type: 'exception'
\r
35606 Ext.Direct.eventTypes = {
\r
35607 'rpc': Ext.Direct.RemotingEvent,
\r
35608 'event': Ext.Direct.Event,
\r
35609 'exception': Ext.Direct.ExceptionEvent
\r
35613 * @class Ext.direct.Provider
\r
35614 * @extends Ext.util.Observable
\r
35615 * <p>Ext.direct.Provider is an abstract class meant to be extended.</p>
\r
35617 * <p>For example ExtJs implements the following subclasses:</p>
\r
35621 +---{@link Ext.direct.JsonProvider JsonProvider}
\r
35623 +---{@link Ext.direct.PollingProvider PollingProvider}
\r
35625 +---{@link Ext.direct.RemotingProvider RemotingProvider}
\r
35629 Ext.direct.Provider = Ext.extend(Ext.util.Observable, {
\r
35631 * @cfg {String} id
\r
35632 * The unique id of the provider (defaults to an {@link Ext#id auto-assigned id}).
\r
35633 * You should assign an id if you need to be able to access the provider later and you do
\r
35634 * not have an object reference available, for example:
\r
35636 Ext.Direct.addProvider(
\r
35639 url: 'php/poll.php',
\r
35640 id: 'poll-provider'
\r
35644 var p = {@link Ext.Direct Ext.Direct}.{@link Ext.Direct#getProvider getProvider}('poll-provider');
\r
35650 * @cfg {Number} priority
\r
35651 * Priority of the request. Lower is higher priority, <tt>0</tt> means "duplex" (always on).
\r
35652 * All Providers default to <tt>1</tt> except for PollingProvider which defaults to <tt>3</tt>.
\r
35657 * @cfg {String} type
\r
35658 * <b>Required</b>, <tt>undefined</tt> by default. The <tt>type</tt> of provider specified
\r
35659 * to {@link Ext.Direct Ext.Direct}.{@link Ext.Direct#addProvider addProvider} to create a
\r
35660 * new Provider. Acceptable values by default are:<div class="mdetail-params"><ul>
\r
35661 * <li><b><tt>polling</tt></b> : {@link Ext.direct.PollingProvider PollingProvider}</li>
\r
35662 * <li><b><tt>remoting</tt></b> : {@link Ext.direct.RemotingProvider RemotingProvider}</li>
\r
35667 constructor : function(config){
\r
35668 Ext.apply(this, config);
\r
35672 * Fires when the Provider connects to the server-side
\r
35673 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
35677 * @event disconnect
\r
35678 * Fires when the Provider disconnects from the server-side
\r
35679 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
35684 * Fires when the Provider receives data from the server-side
\r
35685 * @param {Ext.direct.Provider} provider The {@link Ext.direct.Provider Provider}.
\r
35686 * @param {event} e The {@link Ext.Direct#eventTypes Ext.Direct.Event type} that occurred.
\r
35690 * @event exception
\r
35691 * Fires when the Provider receives an exception from the server-side
\r
35695 Ext.direct.Provider.superclass.constructor.call(this, config);
\r
35699 * Returns whether or not the server-side is currently connected.
\r
35700 * Abstract method for subclasses to implement.
\r
35702 isConnected: function(){
\r
35707 * Abstract methods for subclasses to implement.
\r
35709 connect: Ext.emptyFn,
\r
35712 * Abstract methods for subclasses to implement.
\r
35714 disconnect: Ext.emptyFn
\r
35717 * @class Ext.direct.JsonProvider
\r
35718 * @extends Ext.direct.Provider
\r
35720 Ext.direct.JsonProvider = Ext.extend(Ext.direct.Provider, {
\r
35721 parseResponse: function(xhr){
\r
35722 if(!Ext.isEmpty(xhr.responseText)){
\r
35723 if(typeof xhr.responseText == 'object'){
\r
35724 return xhr.responseText;
\r
35726 return Ext.decode(xhr.responseText);
\r
35731 getEvents: function(xhr){
\r
35734 data = this.parseResponse(xhr);
\r
35736 var event = new Ext.Direct.ExceptionEvent({
\r
35739 code: Ext.Direct.exceptions.PARSE,
\r
35740 message: 'Error parsing json response: \n\n ' + data
\r
35745 if(Ext.isArray(data)){
\r
35746 for(var i = 0, len = data.length; i < len; i++){
\r
35747 events.push(Ext.Direct.createEvent(data[i]));
\r
35750 events.push(Ext.Direct.createEvent(data));
\r
35755 * @class Ext.direct.PollingProvider
\r
35756 * @extends Ext.direct.JsonProvider
\r
35758 * <p>Provides for repetitive polling of the server at distinct {@link #interval intervals}.
\r
35759 * The initial request for data originates from the client, and then is responded to by the
\r
35762 * <p>All configurations for the PollingProvider should be generated by the server-side
\r
35763 * API portion of the Ext.Direct stack.</p>
\r
35765 * <p>An instance of PollingProvider may be created directly via the new keyword or by simply
\r
35766 * specifying <tt>type = 'polling'</tt>. For example:</p>
\r
35768 var pollA = new Ext.direct.PollingProvider({
\r
35770 url: 'php/pollA.php',
\r
35772 Ext.Direct.addProvider(pollA);
\r
35773 pollA.disconnect();
\r
35775 Ext.Direct.addProvider(
\r
35778 url: 'php/pollB.php',
\r
35779 id: 'pollB-provider'
\r
35782 var pollB = Ext.Direct.getProvider('pollB-provider');
\r
35785 Ext.direct.PollingProvider = Ext.extend(Ext.direct.JsonProvider, {
\r
35787 * @cfg {Number} priority
\r
35788 * Priority of the request (defaults to <tt>3</tt>). See {@link Ext.direct.Provider#priority}.
\r
35790 // override default priority
\r
35794 * @cfg {Number} interval
\r
35795 * How often to poll the server-side in milliseconds (defaults to <tt>3000</tt> - every
\r
35801 * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
\r
35802 * on every polling request
\r
35806 * @cfg {String/Function} url
\r
35807 * The url which the PollingProvider should contact with each request. This can also be
\r
35808 * an imported Ext.Direct method which will accept the baseParams as its only argument.
\r
35812 constructor : function(config){
\r
35813 Ext.direct.PollingProvider.superclass.constructor.call(this, config);
\r
35816 * @event beforepoll
\r
35817 * Fired immediately before a poll takes place, an event handler can return false
\r
35818 * in order to cancel the poll.
\r
35819 * @param {Ext.direct.PollingProvider}
\r
35824 * This event has not yet been implemented.
\r
35825 * @param {Ext.direct.PollingProvider}
\r
35832 isConnected: function(){
\r
35833 return !!this.pollTask;
\r
35837 * Connect to the server-side and begin the polling process. To handle each
\r
35838 * response subscribe to the data event.
\r
35840 connect: function(){
\r
35841 if(this.url && !this.pollTask){
\r
35842 this.pollTask = Ext.TaskMgr.start({
\r
35844 if(this.fireEvent('beforepoll', this) !== false){
\r
35845 if(typeof this.url == 'function'){
\r
35846 this.url(this.baseParams);
\r
35848 Ext.Ajax.request({
\r
35850 callback: this.onData,
\r
35852 params: this.baseParams
\r
35857 interval: this.interval,
\r
35860 this.fireEvent('connect', this);
\r
35861 }else if(!this.url){
\r
35862 throw 'Error initializing PollingProvider, no url configured.';
\r
35867 * Disconnect from the server-side and stop the polling process. The disconnect
\r
35868 * event will be fired on a successful disconnect.
\r
35870 disconnect: function(){
\r
35871 if(this.pollTask){
\r
35872 Ext.TaskMgr.stop(this.pollTask);
\r
35873 delete this.pollTask;
\r
35874 this.fireEvent('disconnect', this);
\r
35879 onData: function(opt, success, xhr){
\r
35881 var events = this.getEvents(xhr);
\r
35882 for(var i = 0, len = events.length; i < len; i++){
\r
35883 var e = events[i];
\r
35884 this.fireEvent('data', this, e);
\r
35887 var e = new Ext.Direct.ExceptionEvent({
\r
35889 code: Ext.Direct.exceptions.TRANSPORT,
\r
35890 message: 'Unable to connect to the server.',
\r
35893 this.fireEvent('data', this, e);
\r
35898 Ext.Direct.PROVIDERS['polling'] = Ext.direct.PollingProvider;/**
\r
35899 * @class Ext.direct.RemotingProvider
\r
35900 * @extends Ext.direct.JsonProvider
\r
35902 * <p>The {@link Ext.direct.RemotingProvider RemotingProvider} exposes access to
\r
35903 * server side methods on the client (a remote procedure call (RPC) type of
\r
35904 * connection where the client can initiate a procedure on the server).</p>
\r
35906 * <p>This allows for code to be organized in a fashion that is maintainable,
\r
35907 * while providing a clear path between client and server, something that is
\r
35908 * not always apparent when using URLs.</p>
\r
35910 * <p>To accomplish this the server-side needs to describe what classes and methods
\r
35911 * are available on the client-side. This configuration will typically be
\r
35912 * outputted by the server-side Ext.Direct stack when the API description is built.</p>
\r
35914 Ext.direct.RemotingProvider = Ext.extend(Ext.direct.JsonProvider, {
\r
35916 * @cfg {Object} actions
\r
35917 * Object literal defining the server side actions and methods. For example, if
\r
35918 * the Provider is configured with:
\r
35920 "actions":{ // each property within the 'actions' object represents a server side Class
\r
35921 "TestAction":[ // array of methods within each server side Class to be
\r
35922 { // stubbed out on client
\r
35923 "name":"doEcho",
\r
35926 "name":"multiply",// name of method
\r
35927 "len":2 // The number of parameters that will be used to create an
\r
35928 // array of data to send to the server side function.
\r
35929 // Ensure the server sends back a Number, not a String.
\r
35932 "formHandler":true, // direct the client to use specialized form handling method
\r
35937 * <p>Note that a Store is not required, a server method can be called at any time.
\r
35938 * In the following example a <b>client side</b> handler is used to call the
\r
35939 * server side method "multiply" in the server-side "TestAction" Class:</p>
\r
35941 TestAction.multiply(
\r
35942 2, 4, // pass two arguments to server, so specify len=2
\r
35943 // callback function after the server is called
\r
35944 // result: the result returned by the server
\r
35945 // e: Ext.Direct.RemotingEvent object
\r
35946 function(result, e){
\r
35947 var t = e.getTransaction();
\r
35948 var action = t.action; // server side Class called
\r
35949 var method = t.method; // server side method called
\r
35951 var answer = Ext.encode(result); // 8
\r
35954 var msg = e.message; // failure message
\r
35959 * In the example above, the server side "multiply" function will be passed two
\r
35960 * arguments (2 and 4). The "multiply" method should return the value 8 which will be
\r
35961 * available as the <tt>result</tt> in the example above.
\r
35965 * @cfg {String/Object} namespace
\r
35966 * Namespace for the Remoting Provider (defaults to the browser global scope of <i>window</i>).
\r
35967 * Explicitly specify the namespace Object, or specify a String to have a
\r
35968 * {@link Ext#namespace namespace created} implicitly.
\r
35972 * @cfg {String} url
\r
35973 * <b>Required<b>. The url to connect to the {@link Ext.Direct} server-side router.
\r
35977 * @cfg {String} enableUrlEncode
\r
35978 * Specify which param will hold the arguments for the method.
\r
35979 * Defaults to <tt>'data'</tt>.
\r
35983 * @cfg {Number/Boolean} enableBuffer
\r
35984 * <p><tt>true</tt> or <tt>false</tt> to enable or disable combining of method
\r
35985 * calls. If a number is specified this is the amount of time in milliseconds
\r
35986 * to wait before sending a batched request (defaults to <tt>10</tt>).</p>
\r
35987 * <br><p>Calls which are received within the specified timeframe will be
\r
35988 * concatenated together and sent in a single request, optimizing the
\r
35989 * application by reducing the amount of round trips that have to be made
\r
35990 * to the server.</p>
\r
35992 enableBuffer: 10,
\r
35995 * @cfg {Number} maxRetries
\r
35996 * Number of times to re-attempt delivery on failure of a call. Defaults to <tt>1</tt>.
\r
36001 * @cfg {Number} timeout
\r
36002 * The timeout to use for each request. Defaults to <tt>undefined</tt>.
\r
36004 timeout: undefined,
\r
36006 constructor : function(config){
\r
36007 Ext.direct.RemotingProvider.superclass.constructor.call(this, config);
\r
36010 * @event beforecall
\r
36011 * Fires immediately before the client-side sends off the RPC call.
\r
36012 * By returning false from an event handler you can prevent the call from
\r
36014 * @param {Ext.direct.RemotingProvider} provider
\r
36015 * @param {Ext.Direct.Transaction} transaction
\r
36020 * Fires immediately after the request to the server-side is sent. This does
\r
36021 * NOT fire after the response has come back from the call.
\r
36022 * @param {Ext.direct.RemotingProvider} provider
\r
36023 * @param {Ext.Direct.Transaction} transaction
\r
36027 this.namespace = (Ext.isString(this.namespace)) ? Ext.ns(this.namespace) : this.namespace || window;
\r
36028 this.transactions = {};
\r
36029 this.callBuffer = [];
\r
36033 initAPI : function(){
\r
36034 var o = this.actions;
\r
36036 var cls = this.namespace[c] || (this.namespace[c] = {}),
\r
36038 for(var i = 0, len = ms.length; i < len; i++){
\r
36040 cls[m.name] = this.createMethod(c, m);
\r
36046 isConnected: function(){
\r
36047 return !!this.connected;
\r
36050 connect: function(){
\r
36053 this.connected = true;
\r
36054 this.fireEvent('connect', this);
\r
36055 }else if(!this.url){
\r
36056 throw 'Error initializing RemotingProvider, no url configured.';
\r
36060 disconnect: function(){
\r
36061 if(this.connected){
\r
36062 this.connected = false;
\r
36063 this.fireEvent('disconnect', this);
\r
36067 onData: function(opt, success, xhr){
\r
36069 var events = this.getEvents(xhr);
\r
36070 for(var i = 0, len = events.length; i < len; i++){
\r
36071 var e = events[i],
\r
36072 t = this.getTransaction(e);
\r
36073 this.fireEvent('data', this, e);
\r
36075 this.doCallback(t, e, true);
\r
36076 Ext.Direct.removeTransaction(t);
\r
36080 var ts = [].concat(opt.ts);
\r
36081 for(var i = 0, len = ts.length; i < len; i++){
\r
36082 var t = this.getTransaction(ts[i]);
\r
36083 if(t && t.retryCount < this.maxRetries){
\r
36086 var e = new Ext.Direct.ExceptionEvent({
\r
36089 code: Ext.Direct.exceptions.TRANSPORT,
\r
36090 message: 'Unable to connect to the server.',
\r
36093 this.fireEvent('data', this, e);
\r
36095 this.doCallback(t, e, false);
\r
36096 Ext.Direct.removeTransaction(t);
\r
36103 getCallData: function(t){
\r
36105 action: t.action,
\r
36106 method: t.method,
\r
36113 doSend : function(data){
\r
36116 callback: this.onData,
\r
36119 timeout: this.timeout
\r
36122 if(Ext.isArray(data)){
\r
36124 for(var i = 0, len = data.length; i < len; i++){
\r
36125 callData.push(this.getCallData(data[i]));
\r
36128 callData = this.getCallData(data);
\r
36131 if(this.enableUrlEncode){
\r
36133 params[Ext.isString(this.enableUrlEncode) ? this.enableUrlEncode : 'data'] = Ext.encode(callData);
\r
36134 o.params = params;
\r
36136 o.jsonData = callData;
\r
36138 Ext.Ajax.request(o);
\r
36141 combineAndSend : function(){
\r
36142 var len = this.callBuffer.length;
\r
36144 this.doSend(len == 1 ? this.callBuffer[0] : this.callBuffer);
\r
36145 this.callBuffer = [];
\r
36149 queueTransaction: function(t){
\r
36151 this.processForm(t);
\r
36154 this.callBuffer.push(t);
\r
36155 if(this.enableBuffer){
\r
36156 if(!this.callTask){
\r
36157 this.callTask = new Ext.util.DelayedTask(this.combineAndSend, this);
\r
36159 this.callTask.delay(Ext.isNumber(this.enableBuffer) ? this.enableBuffer : 10);
\r
36161 this.combineAndSend();
\r
36165 doCall : function(c, m, args){
\r
36166 var data = null, hs = args[m.len], scope = args[m.len+1];
\r
36169 data = args.slice(0, m.len);
\r
36172 var t = new Ext.Direct.Transaction({
\r
36178 cb: scope && Ext.isFunction(hs) ? hs.createDelegate(scope) : hs
\r
36181 if(this.fireEvent('beforecall', this, t) !== false){
\r
36182 Ext.Direct.addTransaction(t);
\r
36183 this.queueTransaction(t);
\r
36184 this.fireEvent('call', this, t);
\r
36188 doForm : function(c, m, form, callback, scope){
\r
36189 var t = new Ext.Direct.Transaction({
\r
36193 args:[form, callback, scope],
\r
36194 cb: scope && Ext.isFunction(callback) ? callback.createDelegate(scope) : callback,
\r
36198 if(this.fireEvent('beforecall', this, t) !== false){
\r
36199 Ext.Direct.addTransaction(t);
\r
36200 var isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data',
\r
36204 extMethod: m.name,
\r
36206 extUpload: String(isUpload)
\r
36209 // change made from typeof callback check to callback.params
\r
36210 // to support addl param passing in DirectSubmit EAC 6/2
\r
36212 form: Ext.getDom(form),
\r
36213 isUpload: isUpload,
\r
36214 params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
\r
36216 this.fireEvent('call', this, t);
\r
36217 this.processForm(t);
\r
36221 processForm: function(t){
\r
36222 Ext.Ajax.request({
\r
36224 params: t.params,
\r
36225 callback: this.onData,
\r
36228 isUpload: t.isUpload,
\r
36233 createMethod : function(c, m){
\r
36235 if(!m.formHandler){
\r
36237 this.doCall(c, m, Array.prototype.slice.call(arguments, 0));
\r
36238 }.createDelegate(this);
\r
36240 f = function(form, callback, scope){
\r
36241 this.doForm(c, m, form, callback, scope);
\r
36242 }.createDelegate(this);
\r
36251 getTransaction: function(opt){
\r
36252 return opt && opt.tid ? Ext.Direct.getTransaction(opt.tid) : null;
\r
36255 doCallback: function(t, e){
\r
36256 var fn = e.status ? 'success' : 'failure';
\r
36259 result = Ext.isDefined(e.result) ? e.result : e.data;
\r
36260 if(Ext.isFunction(hs)){
\r
36263 Ext.callback(hs[fn], hs.scope, [result, e]);
\r
36264 Ext.callback(hs.callback, hs.scope, [result, e]);
\r
36269 Ext.Direct.PROVIDERS['remoting'] = Ext.direct.RemotingProvider;/**
\r
36270 * @class Ext.Resizable
\r
36271 * @extends Ext.util.Observable
\r
36272 * <p>Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
\r
36273 * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
\r
36274 * 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
36275 * the element will be wrapped for you automatically.</p>
\r
36276 * <p>Here is the list of valid resize handles:</p>
\r
36278 Value Description
\r
36279 ------ -------------------
\r
36290 * <p>Here's an example showing the creation of a typical Resizable:</p>
\r
36292 var resizer = new Ext.Resizable('element-id', {
\r
36300 resizer.on('resize', myHandler);
\r
36302 * <p>To hide a particular handle, set its display to none in CSS, or through script:<br>
\r
36303 * resizer.east.setDisplayed(false);</p>
\r
36305 * Create a new resizable component
\r
36306 * @param {Mixed} el The id or element to resize
\r
36307 * @param {Object} config configuration options
\r
36309 Ext.Resizable = function(el, config){
\r
36310 this.el = Ext.get(el);
\r
36312 if(config && config.wrap){
\r
36313 config.resizeChild = this.el;
\r
36314 this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : {cls:'xresizable-wrap'});
\r
36315 this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
\r
36316 this.el.setStyle('overflow', 'hidden');
\r
36317 this.el.setPositioning(config.resizeChild.getPositioning());
\r
36318 config.resizeChild.clearPositioning();
\r
36319 if(!config.width || !config.height){
\r
36320 var csize = config.resizeChild.getSize();
\r
36321 this.el.setSize(csize.width, csize.height);
\r
36323 if(config.pinned && !config.adjustments){
\r
36324 config.adjustments = 'auto';
\r
36329 * The proxy Element that is resized in place of the real Element during the resize operation.
\r
36330 * This may be queried using {@link Ext.Element#getBox} to provide the new area to resize to.
\r
36332 * @type Ext.Element.
\r
36333 * @property proxy
\r
36335 this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
\r
36336 this.proxy.unselectable();
\r
36337 this.proxy.enableDisplayMode('block');
\r
36339 Ext.apply(this, config);
\r
36342 this.disableTrackOver = true;
\r
36343 this.el.addClass('x-resizable-pinned');
\r
36345 // if the element isn't positioned, make it relative
\r
36346 var position = this.el.getStyle('position');
\r
36347 if(position != 'absolute' && position != 'fixed'){
\r
36348 this.el.setStyle('position', 'relative');
\r
36350 if(!this.handles){ // no handles passed, must be legacy style
\r
36351 this.handles = 's,e,se';
\r
36352 if(this.multiDirectional){
\r
36353 this.handles += ',n,w';
\r
36356 if(this.handles == 'all'){
\r
36357 this.handles = 'n s e w ne nw se sw';
\r
36359 var hs = this.handles.split(/\s*?[,;]\s*?| /);
\r
36360 var ps = Ext.Resizable.positions;
\r
36361 for(var i = 0, len = hs.length; i < len; i++){
\r
36362 if(hs[i] && ps[hs[i]]){
\r
36363 var pos = ps[hs[i]];
\r
36364 this[pos] = new Ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent);
\r
36368 this.corner = this.southeast;
\r
36370 if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
\r
36371 this.updateBox = true;
\r
36374 this.activeHandle = null;
\r
36376 if(this.resizeChild){
\r
36377 if(typeof this.resizeChild == 'boolean'){
\r
36378 this.resizeChild = Ext.get(this.el.dom.firstChild, true);
\r
36380 this.resizeChild = Ext.get(this.resizeChild, true);
\r
36384 if(this.adjustments == 'auto'){
\r
36385 var rc = this.resizeChild;
\r
36386 var hw = this.west, he = this.east, hn = this.north, hs = this.south;
\r
36387 if(rc && (hw || hn)){
\r
36388 rc.position('relative');
\r
36389 rc.setLeft(hw ? hw.el.getWidth() : 0);
\r
36390 rc.setTop(hn ? hn.el.getHeight() : 0);
\r
36392 this.adjustments = [
\r
36393 (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
\r
36394 (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
\r
36398 if(this.draggable){
\r
36399 this.dd = this.dynamic ?
\r
36400 this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
\r
36401 this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
\r
36406 * @event beforeresize
\r
36407 * Fired before resize is allowed. Set {@link #enabled} to false to cancel resize.
\r
36408 * @param {Ext.Resizable} this
\r
36409 * @param {Ext.EventObject} e The mousedown event
\r
36414 * Fired after a resize.
\r
36415 * @param {Ext.Resizable} this
\r
36416 * @param {Number} width The new width
\r
36417 * @param {Number} height The new height
\r
36418 * @param {Ext.EventObject} e The mouseup event
\r
36423 if(this.width !== null && this.height !== null){
\r
36424 this.resizeTo(this.width, this.height);
\r
36426 this.updateChildSize();
\r
36429 this.el.dom.style.zoom = 1;
\r
36431 Ext.Resizable.superclass.constructor.call(this);
\r
36434 Ext.extend(Ext.Resizable, Ext.util.Observable, {
\r
36437 * @cfg {Array/String} adjustments String 'auto' or an array [width, height] with values to be <b>added</b> to the
\r
36438 * resize operation's new size (defaults to <tt>[0, 0]</tt>)
\r
36440 adjustments : [0, 0],
\r
36442 * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
\r
36446 * @cfg {Mixed} constrainTo Constrain the resize to a particular element
\r
36449 * @cfg {Boolean} disableTrackOver True to disable mouse tracking. This is only applied at config time. (defaults to false)
\r
36451 disableTrackOver : false,
\r
36453 * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
\r
36455 draggable: false,
\r
36457 * @cfg {Number} duration Animation duration if animate = true (defaults to 0.35)
\r
36461 * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
\r
36465 * @cfg {String} easing Animation easing if animate = true (defaults to <tt>'easingOutStrong'</tt>)
\r
36467 easing : 'easeOutStrong',
\r
36469 * @cfg {Boolean} enabled False to disable resizing (defaults to true)
\r
36473 * @property enabled Writable. False if resizing is disabled.
\r
36477 * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined).
\r
36478 * Specify either <tt>'all'</tt> or any of <tt>'n s e w ne nw se sw'</tt>.
\r
36482 * @cfg {Boolean} multiDirectional <b>Deprecated</b>. Deprecated style of adding multi-direction resize handles.
\r
36484 multiDirectional : false,
\r
36486 * @cfg {Number} height The height of the element in pixels (defaults to null)
\r
36490 * @cfg {Number} width The width of the element in pixels (defaults to null)
\r
36494 * @cfg {Number} heightIncrement The increment to snap the height resize in pixels
\r
36495 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
\r
36497 heightIncrement : 0,
\r
36499 * @cfg {Number} widthIncrement The increment to snap the width resize in pixels
\r
36500 * (only applies if <code>{@link #dynamic}==true</code>). Defaults to <tt>0</tt>.
\r
36502 widthIncrement : 0,
\r
36504 * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
\r
36508 * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
\r
36512 * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
\r
36514 maxHeight : 10000,
\r
36516 * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
\r
36518 maxWidth : 10000,
\r
36520 * @cfg {Number} minX The minimum x for the element (defaults to 0)
\r
36524 * @cfg {Number} minY The minimum x for the element (defaults to 0)
\r
36528 * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
\r
36529 * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
\r
36533 * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
\r
36534 * and width during resize (defaults to false)
\r
36536 preserveRatio : false,
\r
36538 * @cfg {Boolean/String/Element} resizeChild True to resize the first child, or id/element to resize (defaults to false)
\r
36540 resizeChild : false,
\r
36542 * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
\r
36544 transparent: false,
\r
36546 * @cfg {Ext.lib.Region} resizeRegion Constrain the resize to a particular region
\r
36549 * @cfg {Boolean} wrap True to wrap an element with a div if needed (required for textareas and images, defaults to false)
\r
36550 * in favor of the handles config option (defaults to false)
\r
36555 * Perform a manual resize and fires the 'resize' event.
\r
36556 * @param {Number} width
\r
36557 * @param {Number} height
\r
36559 resizeTo : function(width, height){
\r
36560 this.el.setSize(width, height);
\r
36561 this.updateChildSize();
\r
36562 this.fireEvent('resize', this, width, height, null);
\r
36566 startSizing : function(e, handle){
\r
36567 this.fireEvent('beforeresize', this, e);
\r
36568 if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
\r
36570 if(!this.overlay){
\r
36571 this.overlay = this.el.createProxy({tag: 'div', cls: 'x-resizable-overlay', html: ' '}, Ext.getBody());
\r
36572 this.overlay.unselectable();
\r
36573 this.overlay.enableDisplayMode('block');
\r
36574 this.overlay.on({
\r
36576 mousemove: this.onMouseMove,
\r
36577 mouseup: this.onMouseUp
\r
36580 this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));
\r
36582 this.resizing = true;
\r
36583 this.startBox = this.el.getBox();
\r
36584 this.startPoint = e.getXY();
\r
36585 this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
\r
36586 (this.startBox.y + this.startBox.height) - this.startPoint[1]];
\r
36588 this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
\r
36589 this.overlay.show();
\r
36591 if(this.constrainTo) {
\r
36592 var ct = Ext.get(this.constrainTo);
\r
36593 this.resizeRegion = ct.getRegion().adjust(
\r
36594 ct.getFrameWidth('t'),
\r
36595 ct.getFrameWidth('l'),
\r
36596 -ct.getFrameWidth('b'),
\r
36597 -ct.getFrameWidth('r')
\r
36601 this.proxy.setStyle('visibility', 'hidden'); // workaround display none
\r
36602 this.proxy.show();
\r
36603 this.proxy.setBox(this.startBox);
\r
36604 if(!this.dynamic){
\r
36605 this.proxy.setStyle('visibility', 'visible');
\r
36611 onMouseDown : function(handle, e){
\r
36612 if(this.enabled){
\r
36614 this.activeHandle = handle;
\r
36615 this.startSizing(e, handle);
\r
36620 onMouseUp : function(e){
\r
36621 this.activeHandle = null;
\r
36622 var size = this.resizeElement();
\r
36623 this.resizing = false;
\r
36624 this.handleOut();
\r
36625 this.overlay.hide();
\r
36626 this.proxy.hide();
\r
36627 this.fireEvent('resize', this, size.width, size.height, e);
\r
36631 updateChildSize : function(){
\r
36632 if(this.resizeChild){
\r
36633 var el = this.el;
\r
36634 var child = this.resizeChild;
\r
36635 var adj = this.adjustments;
\r
36636 if(el.dom.offsetWidth){
\r
36637 var b = el.getSize(true);
\r
36638 child.setSize(b.width+adj[0], b.height+adj[1]);
\r
36640 // Second call here for IE
\r
36641 // The first call enables instant resizing and
\r
36642 // the second call corrects scroll bars if they
\r
36645 setTimeout(function(){
\r
36646 if(el.dom.offsetWidth){
\r
36647 var b = el.getSize(true);
\r
36648 child.setSize(b.width+adj[0], b.height+adj[1]);
\r
36656 snap : function(value, inc, min){
\r
36657 if(!inc || !value){
\r
36660 var newValue = value;
\r
36661 var m = value % inc;
\r
36664 newValue = value + (inc-m);
\r
36666 newValue = value - m;
\r
36669 return Math.max(min, newValue);
\r
36673 * <p>Performs resizing of the associated Element. This method is called internally by this
\r
36674 * class, and should not be called by user code.</p>
\r
36675 * <p>If a Resizable is being used to resize an Element which encapsulates a more complex UI
\r
36676 * component such as a Panel, this method may be overridden by specifying an implementation
\r
36677 * as a config option to provide appropriate behaviour at the end of the resize operation on
\r
36678 * mouseup, for example resizing the Panel, and relaying the Panel's content.</p>
\r
36679 * <p>The new area to be resized to is available by examining the state of the {@link #proxy}
\r
36680 * Element. Example:
\r
36683 title: 'Resize me',
\r
36686 renderTo: Ext.getBody(),
\r
36692 render: function(p) {
\r
36693 new Ext.Resizable(p.getEl(), {
\r
36696 transparent: true,
\r
36697 resizeElement: function() {
\r
36698 var box = this.proxy.getBox();
\r
36699 p.updateBox(box);
\r
36711 resizeElement : function(){
\r
36712 var box = this.proxy.getBox();
\r
36713 if(this.updateBox){
\r
36714 this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
\r
36716 this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
\r
36718 this.updateChildSize();
\r
36719 if(!this.dynamic){
\r
36720 this.proxy.hide();
\r
36726 constrain : function(v, diff, m, mx){
\r
36727 if(v - diff < m){
\r
36729 }else if(v - diff > mx){
\r
36736 onMouseMove : function(e){
\r
36737 if(this.enabled && this.activeHandle){
\r
36738 try{// try catch so if something goes wrong the user doesn't get hung
\r
36740 if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
\r
36744 //var curXY = this.startPoint;
\r
36745 var curSize = this.curSize || this.startBox,
\r
36746 x = this.startBox.x, y = this.startBox.y,
\r
36749 w = curSize.width,
\r
36750 h = curSize.height,
\r
36753 mw = this.minWidth,
\r
36754 mh = this.minHeight,
\r
36755 mxw = this.maxWidth,
\r
36756 mxh = this.maxHeight,
\r
36757 wi = this.widthIncrement,
\r
36758 hi = this.heightIncrement,
\r
36759 eventXY = e.getXY(),
\r
36760 diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
\r
36761 diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
\r
36762 pos = this.activeHandle.position,
\r
36769 w = Math.min(Math.max(mw, w), mxw);
\r
36773 h = Math.min(Math.max(mh, h), mxh);
\r
36775 case 'southeast':
\r
36778 w = Math.min(Math.max(mw, w), mxw);
\r
36779 h = Math.min(Math.max(mh, h), mxh);
\r
36782 diffY = this.constrain(h, diffY, mh, mxh);
\r
36787 diffX = this.constrain(w, diffX, mw, mxw);
\r
36791 case 'northeast':
\r
36793 w = Math.min(Math.max(mw, w), mxw);
\r
36794 diffY = this.constrain(h, diffY, mh, mxh);
\r
36798 case 'northwest':
\r
36799 diffX = this.constrain(w, diffX, mw, mxw);
\r
36800 diffY = this.constrain(h, diffY, mh, mxh);
\r
36806 case 'southwest':
\r
36807 diffX = this.constrain(w, diffX, mw, mxw);
\r
36809 h = Math.min(Math.max(mh, h), mxh);
\r
36815 var sw = this.snap(w, wi, mw);
\r
36816 var sh = this.snap(h, hi, mh);
\r
36817 if(sw != w || sh != h){
\r
36819 case 'northeast':
\r
36825 case 'southwest':
\r
36831 case 'northwest':
\r
36840 if(this.preserveRatio){
\r
36842 case 'southeast':
\r
36845 h = Math.min(Math.max(mh, h), mxh);
\r
36850 w = Math.min(Math.max(mw, w), mxw);
\r
36853 case 'northeast':
\r
36855 w = Math.min(Math.max(mw, w), mxw);
\r
36861 w = Math.min(Math.max(mw, w), mxw);
\r
36863 x += (tw - w) / 2;
\r
36865 case 'southwest':
\r
36867 h = Math.min(Math.max(mh, h), mxh);
\r
36875 h = Math.min(Math.max(mh, h), mxh);
\r
36876 y += (th - h) / 2;
\r
36881 case 'northwest':
\r
36885 h = Math.min(Math.max(mh, h), mxh);
\r
36893 this.proxy.setBounds(x, y, w, h);
\r
36894 if(this.dynamic){
\r
36895 this.resizeElement();
\r
36902 handleOver : function(){
\r
36903 if(this.enabled){
\r
36904 this.el.addClass('x-resizable-over');
\r
36909 handleOut : function(){
\r
36910 if(!this.resizing){
\r
36911 this.el.removeClass('x-resizable-over');
\r
36916 * Returns the element this component is bound to.
\r
36917 * @return {Ext.Element}
\r
36919 getEl : function(){
\r
36924 * Returns the resizeChild element (or null).
\r
36925 * @return {Ext.Element}
\r
36927 getResizeChild : function(){
\r
36928 return this.resizeChild;
\r
36932 * Destroys this resizable. If the element was wrapped and
\r
36933 * removeEl is not true then the element remains.
\r
36934 * @param {Boolean} removeEl (optional) true to remove the element from the DOM
\r
36936 destroy : function(removeEl){
\r
36937 Ext.destroy(this.dd, this.overlay, this.proxy);
\r
36938 this.overlay = null;
\r
36939 this.proxy = null;
\r
36941 var ps = Ext.Resizable.positions;
\r
36942 for(var k in ps){
\r
36943 if(typeof ps[k] != 'function' && this[ps[k]]){
\r
36944 this[ps[k]].destroy();
\r
36948 this.el.update('');
\r
36949 Ext.destroy(this.el);
\r
36952 this.purgeListeners();
\r
36955 syncHandleHeight : function(){
\r
36956 var h = this.el.getHeight(true);
\r
36958 this.west.el.setHeight(h);
\r
36961 this.east.el.setHeight(h);
\r
36967 // hash to map config positions to true positions
\r
36968 Ext.Resizable.positions = {
\r
36969 n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
\r
36973 Ext.Resizable.Handle = function(rz, pos, disableTrackOver, transparent){
\r
36975 // only initialize the template if resizable is used
\r
36976 var tpl = Ext.DomHelper.createTemplate(
\r
36977 {tag: 'div', cls: 'x-resizable-handle x-resizable-handle-{0}'}
\r
36980 Ext.Resizable.Handle.prototype.tpl = tpl;
\r
36982 this.position = pos;
\r
36984 this.el = this.tpl.append(rz.el.dom, [this.position], true);
\r
36985 this.el.unselectable();
\r
36987 this.el.setOpacity(0);
\r
36989 this.el.on('mousedown', this.onMouseDown, this);
\r
36990 if(!disableTrackOver){
\r
36993 mouseover: this.onMouseOver,
\r
36994 mouseout: this.onMouseOut
\r
37000 Ext.Resizable.Handle.prototype = {
\r
37002 afterResize : function(rz){
\r
37006 onMouseDown : function(e){
\r
37007 this.rz.onMouseDown(this, e);
\r
37010 onMouseOver : function(e){
\r
37011 this.rz.handleOver(this, e);
\r
37014 onMouseOut : function(e){
\r
37015 this.rz.handleOut(this, e);
\r
37018 destroy : function(){
\r
37019 Ext.destroy(this.el);
\r
37024 * @class Ext.Window
37025 * @extends Ext.Panel
37026 * <p>A specialized panel intended for use as an application window. Windows are floated, {@link #resizable}, and
37027 * {@link #draggable} by default. Windows can be {@link #maximizable maximized} to fill the viewport,
37028 * restored to their prior size, and can be {@link #minimize}d.</p>
37029 * <p>Windows can also be linked to a {@link Ext.WindowGroup} or managed by the {@link Ext.WindowMgr} to provide
37030 * grouping, activation, to front, to back and other application-specific behavior.</p>
37031 * <p>By default, Windows will be rendered to document.body. To {@link #constrain} a Window to another element
37032 * specify {@link Ext.Component#renderTo renderTo}.</p>
37033 * <p><b>Note:</b> By default, the <code>{@link #closable close}</code> header tool <i>destroys</i> the Window resulting in
37034 * destruction of any child Components. This makes the Window object, and all its descendants <b>unusable</b>. To enable
37035 * re-use of a Window, use <b><code>{@link #closeAction closeAction: 'hide'}</code></b>.</p>
37037 * @param {Object} config The config object
37040 Ext.Window = Ext.extend(Ext.Panel, {
37043 * The X position of the left edge of the window on initial showing. Defaults to centering the Window within
37044 * the width of the Window's container {@link Ext.Element Element) (The Element that the Window is rendered to).
37048 * The Y position of the top edge of the window on initial showing. Defaults to centering the Window within
37049 * the height of the Window's container {@link Ext.Element Element) (The Element that the Window is rendered to).
37052 * @cfg {Boolean} modal
37053 * True to make the window modal and mask everything behind it when displayed, false to display it without
37054 * restricting access to other UI elements (defaults to false).
37057 * @cfg {String/Element} animateTarget
37058 * Id or element from which the window should animate while opening (defaults to null with no animation).
37061 * @cfg {String} resizeHandles
37062 * A valid {@link Ext.Resizable} handles config string (defaults to 'all'). Only applies when resizable = true.
37065 * @cfg {Ext.WindowGroup} manager
37066 * A reference to the WindowGroup that should manage this window (defaults to {@link Ext.WindowMgr}).
37069 * @cfg {String/Number/Button} defaultButton
37070 * The id / index of a button or a button instance to focus when this window received the focus.
37073 * @cfg {Function} onEsc
37074 * Allows override of the built-in processing for the escape key. Default action
37075 * is to close the Window (performing whatever action is specified in {@link #closeAction}.
37076 * To prevent the Window closing when the escape key is pressed, specify this as
37077 * Ext.emptyFn (See {@link Ext#emptyFn}).
37080 * @cfg {Boolean} collapsed
37081 * True to render the window collapsed, false to render it expanded (defaults to false). Note that if
37082 * {@link #expandOnShow} is true (the default) it will override the <tt>collapsed</tt> config and the window
37083 * will always be expanded when shown.
37086 * @cfg {Boolean} maximized
37087 * True to initially display the window in a maximized state. (Defaults to false).
37091 * @cfg {String} baseCls
37092 * The base CSS class to apply to this panel's element (defaults to 'x-window').
37094 baseCls : 'x-window',
37096 * @cfg {Boolean} resizable
37097 * True to allow user resizing at each edge and corner of the window, false to disable resizing (defaults to true).
37101 * @cfg {Boolean} draggable
37102 * True to allow the window to be dragged by the header bar, false to disable dragging (defaults to true). Note
37103 * that by default the window will be centered in the viewport, so if dragging is disabled the window may need
37104 * to be positioned programmatically after render (e.g., myWindow.setPosition(100, 100);).
37108 * @cfg {Boolean} closable
37109 * <p>True to display the 'close' tool button and allow the user to close the window, false to
37110 * hide the button and disallow closing the window (defaults to true).</p>
37111 * <p>By default, when close is requested by either clicking the close button in the header
37112 * or pressing ESC when the Window has focus, the {@link #close} method will be called. This
37113 * will <i>{@link Ext.Component#destroy destroy}</i> the Window and its content meaning that
37114 * it may not be reused.</p>
37115 * <p>To make closing a Window <i>hide</i> the Window so that it may be reused, set
37116 * {@link #closeAction} to 'hide'.
37120 * @cfg {String} closeAction
37121 * <p>The action to take when the close header tool is clicked:
37122 * <div class="mdetail-params"><ul>
37123 * <li><b><code>'{@link #close}'</code></b> : <b>Default</b><div class="sub-desc">
37124 * {@link #close remove} the window from the DOM and {@link Ext.Component#destroy destroy}
37125 * it and all descendant Components. The window will <b>not</b> be available to be
37126 * redisplayed via the {@link #show} method.
37128 * <li><b><code>'{@link #hide}'</code></b> : <div class="sub-desc">
37129 * {@link #hide} the window by setting visibility to hidden and applying negative offsets.
37130 * The window will be available to be redisplayed via the {@link #show} method.
37133 * <p><b>Note:</b> This setting does not affect the {@link #close} method
37134 * which will always {@link Ext.Component#destroy destroy} the window. To
37135 * programatically <i>hide</i> a window, call {@link #hide}.</p>
37137 closeAction : 'close',
37139 * @cfg {Boolean} constrain
37140 * True to constrain the window within its containing element, false to allow it to fall outside of its
37141 * containing element. By default the window will be rendered to document.body. To render and constrain the
37142 * window within another element specify {@link #renderTo}.
37143 * (defaults to false). Optionally the header only can be constrained using {@link #constrainHeader}.
37147 * @cfg {Boolean} constrainHeader
37148 * True to constrain the window header within its containing element (allowing the window body to fall outside
37149 * of its containing element) or false to allow the header to fall outside its containing element (defaults to
37150 * false). Optionally the entire window can be constrained using {@link #constrain}.
37152 constrainHeader : false,
37154 * @cfg {Boolean} plain
37155 * True to render the window body with a transparent background so that it will blend into the framing
37156 * elements, false to add a lighter background color to visually highlight the body element and separate it
37157 * more distinctly from the surrounding frame (defaults to false).
37161 * @cfg {Boolean} minimizable
37162 * True to display the 'minimize' tool button and allow the user to minimize the window, false to hide the button
37163 * and disallow minimizing the window (defaults to false). Note that this button provides no implementation --
37164 * the behavior of minimizing a window is implementation-specific, so the minimize event must be handled and a
37165 * custom minimize behavior implemented for this option to be useful.
37167 minimizable : false,
37169 * @cfg {Boolean} maximizable
37170 * True to display the 'maximize' tool button and allow the user to maximize the window, false to hide the button
37171 * and disallow maximizing the window (defaults to false). Note that when a window is maximized, the tool button
37172 * will automatically change to a 'restore' button with the appropriate behavior already built-in that will
37173 * restore the window to its previous size.
37175 maximizable : false,
37177 * @cfg {Number} minHeight
37178 * The minimum height in pixels allowed for this window (defaults to 100). Only applies when resizable = true.
37182 * @cfg {Number} minWidth
37183 * The minimum width in pixels allowed for this window (defaults to 200). Only applies when resizable = true.
37187 * @cfg {Boolean} expandOnShow
37188 * True to always expand the window when it is displayed, false to keep it in its current state (which may be
37189 * {@link #collapsed}) when displayed (defaults to true).
37191 expandOnShow : true,
37193 // inherited docs, same default
37194 collapsible : false,
37197 * @cfg {Boolean} initHidden
37198 * True to hide the window until show() is explicitly called (defaults to true).
37201 initHidden : undefined,
37204 * @cfg {Boolean} hidden
37205 * Render this component hidden (default is <tt>true</tt>). If <tt>true</tt>, the
37206 * {@link #hide} method will be called internally.
37211 * @cfg {Boolean} monitorResize @hide
37212 * This is automatically managed based on the value of constrain and constrainToHeader
37214 monitorResize : true,
37216 // The following configs are set to provide the basic functionality of a window.
37217 // Changing them would require additional code to handle correctly and should
37218 // usually only be done in subclasses that can provide custom behavior. Changing them
37219 // may have unexpected or undesirable results.
37220 /** @cfg {String} elements @hide */
37221 elements : 'header,body',
37222 /** @cfg {Boolean} frame @hide */
37224 /** @cfg {Boolean} floating @hide */
37228 initComponent : function(){
37230 Ext.Window.superclass.initComponent.call(this);
37234 * Fires after the window has been visually activated via {@link #setActive}.
37235 * @param {Ext.Window} this
37238 * @event deactivate
37239 * Fires after the window has been visually deactivated via {@link #setActive}.
37240 * @param {Ext.Window} this
37244 * Fires after the window has been resized.
37245 * @param {Ext.Window} this
37246 * @param {Number} width The window's new width
37247 * @param {Number} height The window's new height
37252 * Fires after the window has been maximized.
37253 * @param {Ext.Window} this
37258 * Fires after the window has been minimized.
37259 * @param {Ext.Window} this
37264 * Fires after the window has been restored to its original size after being maximized.
37265 * @param {Ext.Window} this
37269 // for backwards compat, this should be removed at some point
37270 if(Ext.isDefined(this.initHidden)){
37271 this.hidden = this.initHidden;
37273 if(this.hidden === false){
37274 this.hidden = true;
37280 getState : function(){
37281 return Ext.apply(Ext.Window.superclass.getState.call(this) || {}, this.getBox(true));
37285 onRender : function(ct, position){
37286 Ext.Window.superclass.onRender.call(this, ct, position);
37289 this.el.addClass('x-window-plain');
37292 // this element allows the Window to be focused for keyboard events
37293 this.focusEl = this.el.createChild({
37294 tag: 'a', href:'#', cls:'x-dlg-focus',
37295 tabIndex:'-1', html: ' '});
37296 this.focusEl.swallowEvent('click', true);
37298 this.proxy = this.el.createProxy('x-window-proxy');
37299 this.proxy.enableDisplayMode('block');
37302 this.mask = this.container.createChild({cls:'ext-el-mask'}, this.el.dom);
37303 this.mask.enableDisplayMode('block');
37305 this.mon(this.mask, 'click', this.focus, this);
37307 if(this.maximizable){
37308 this.mon(this.header, 'dblclick', this.toggleMaximize, this);
37313 initEvents : function(){
37314 Ext.Window.superclass.initEvents.call(this);
37315 if(this.animateTarget){
37316 this.setAnimateTarget(this.animateTarget);
37319 if(this.resizable){
37320 this.resizer = new Ext.Resizable(this.el, {
37321 minWidth: this.minWidth,
37322 minHeight:this.minHeight,
37323 handles: this.resizeHandles || 'all',
37325 resizeElement : this.resizerAction
37327 this.resizer.window = this;
37328 this.mon(this.resizer, 'beforeresize', this.beforeResize, this);
37331 if(this.draggable){
37332 this.header.addClass('x-window-draggable');
37334 this.mon(this.el, 'mousedown', this.toFront, this);
37335 this.manager = this.manager || Ext.WindowMgr;
37336 this.manager.register(this);
37337 if(this.maximized){
37338 this.maximized = false;
37342 var km = this.getKeyMap();
37343 km.on(27, this.onEsc, this);
37348 initDraggable : function(){
37350 * If this Window is configured {@link #draggable}, this property will contain
37351 * an instance of {@link Ext.dd.DD} which handles dragging the Window's DOM Element.
37355 this.dd = new Ext.Window.DD(this);
37359 onEsc : function(){
37360 this[this.closeAction]();
37364 beforeDestroy : function(){
37365 if (this.rendered){
37368 Ext.EventManager.removeResizeListener(this.doAnchor, this);
37369 Ext.EventManager.un(window, 'scroll', this.doAnchor, this);
37379 Ext.Window.superclass.beforeDestroy.call(this);
37383 onDestroy : function(){
37385 this.manager.unregister(this);
37387 Ext.Window.superclass.onDestroy.call(this);
37391 initTools : function(){
37392 if(this.minimizable){
37395 handler: this.minimize.createDelegate(this, [])
37398 if(this.maximizable){
37401 handler: this.maximize.createDelegate(this, [])
37405 handler: this.restore.createDelegate(this, []),
37412 handler: this[this.closeAction].createDelegate(this, [])
37418 resizerAction : function(){
37419 var box = this.proxy.getBox();
37421 this.window.handleResize(box);
37426 beforeResize : function(){
37427 this.resizer.minHeight = Math.max(this.minHeight, this.getFrameHeight() + 40); // 40 is a magic minimum content size?
37428 this.resizer.minWidth = Math.max(this.minWidth, this.getFrameWidth() + 40);
37429 this.resizeBox = this.el.getBox();
37433 updateHandles : function(){
37434 if(Ext.isIE && this.resizer){
37435 this.resizer.syncHandleHeight();
37441 handleResize : function(box){
37442 var rz = this.resizeBox;
37443 if(rz.x != box.x || rz.y != box.y){
37444 this.updateBox(box);
37449 this.updateHandles();
37455 * Focuses the window. If a defaultButton is set, it will receive focus, otherwise the
37456 * window itself will receive focus.
37458 focus : function(){
37459 var f = this.focusEl, db = this.defaultButton, t = typeof db;
37460 if(Ext.isDefined(db)){
37461 if(Ext.isNumber(db) && this.fbar){
37462 f = this.fbar.items.get(db);
37463 }else if(Ext.isString(db)){
37464 f = Ext.getCmp(db);
37469 f = f || this.focusEl;
37470 f.focus.defer(10, f);
37474 * Sets the target element from which the window should animate while opening.
37475 * @param {String/Element} el The target element or id
37477 setAnimateTarget : function(el){
37479 this.animateTarget = el;
37483 beforeShow : function(){
37484 delete this.el.lastXY;
37485 delete this.el.lastLT;
37486 if(this.x === undefined || this.y === undefined){
37487 var xy = this.el.getAlignToXY(this.container, 'c-c');
37488 var pos = this.el.translatePoints(xy[0], xy[1]);
37489 this.x = this.x === undefined? pos.left : this.x;
37490 this.y = this.y === undefined? pos.top : this.y;
37492 this.el.setLeftTop(this.x, this.y);
37494 if(this.expandOnShow){
37495 this.expand(false);
37499 Ext.getBody().addClass('x-body-masked');
37500 this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
37506 * Shows the window, rendering it first if necessary, or activates it and brings it to front if hidden.
37507 * @param {String/Element} animateTarget (optional) The target element or id from which the window should
37508 * animate while opening (defaults to null with no animation)
37509 * @param {Function} callback (optional) A callback function to call after the window is displayed
37510 * @param {Object} scope (optional) The scope in which to execute the callback
37511 * @return {Ext.Window} this
37513 show : function(animateTarget, cb, scope){
37514 if(!this.rendered){
37515 this.render(Ext.getBody());
37517 if(this.hidden === false){
37521 if(this.fireEvent('beforeshow', this) === false){
37525 this.on('show', cb, scope, {single:true});
37527 this.hidden = false;
37528 if(Ext.isDefined(animateTarget)){
37529 this.setAnimateTarget(animateTarget);
37532 if(this.animateTarget){
37541 afterShow : function(isAnim){
37543 this.el.setStyle('display', 'block');
37545 if(this.maximized){
37546 this.fitContainer();
37548 if(Ext.isMac && Ext.isGecko){ // work around stupid FF 2.0/Mac scroll bar bug
37549 this.cascade(this.setAutoScroll);
37552 if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
37553 Ext.EventManager.onWindowResize(this.onWindowResize, this);
37555 this.doConstrain();
37558 this.keyMap.enable();
37561 this.updateHandles();
37562 if(isAnim && (Ext.isIE || Ext.isWebKit)){
37563 var sz = this.getSize();
37564 this.onResize(sz.width, sz.height);
37566 this.fireEvent('show', this);
37570 animShow : function(){
37572 this.proxy.setBox(this.animateTarget.getBox());
37573 this.proxy.setOpacity(0);
37574 var b = this.getBox();
37575 this.el.setStyle('display', 'none');
37576 this.proxy.shift(Ext.apply(b, {
37577 callback: this.afterShow.createDelegate(this, [true], false),
37579 easing: 'easeNone',
37586 * Hides the window, setting it to invisible and applying negative offsets.
37587 * @param {String/Element} animateTarget (optional) The target element or id to which the window should
37588 * animate while hiding (defaults to null with no animation)
37589 * @param {Function} callback (optional) A callback function to call after the window is hidden
37590 * @param {Object} scope (optional) The scope in which to execute the callback
37591 * @return {Ext.Window} this
37593 hide : function(animateTarget, cb, scope){
37594 if(this.hidden || this.fireEvent('beforehide', this) === false){
37598 this.on('hide', cb, scope, {single:true});
37600 this.hidden = true;
37601 if(animateTarget !== undefined){
37602 this.setAnimateTarget(animateTarget);
37606 Ext.getBody().removeClass('x-body-masked');
37608 if(this.animateTarget){
37618 afterHide : function(){
37620 if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
37621 Ext.EventManager.removeResizeListener(this.onWindowResize, this);
37624 this.keyMap.disable();
37626 this.fireEvent('hide', this);
37630 animHide : function(){
37631 this.proxy.setOpacity(0.5);
37633 var tb = this.getBox(false);
37634 this.proxy.setBox(tb);
37636 this.proxy.shift(Ext.apply(this.animateTarget.getBox(), {
37637 callback: this.afterHide,
37640 easing: 'easeNone',
37646 onWindowResize : function(){
37647 if(this.maximized){
37648 this.fitContainer();
37651 this.mask.setSize('100%', '100%');
37652 var force = this.mask.dom.offsetHeight;
37653 this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
37655 this.doConstrain();
37659 doConstrain : function(){
37660 if(this.constrain || this.constrainHeader){
37662 if(this.constrain){
37664 right:this.el.shadowOffset,
37665 left:this.el.shadowOffset,
37666 bottom:this.el.shadowOffset
37669 var s = this.getSize();
37671 right:-(s.width - 100),
37672 bottom:-(s.height - 25)
37676 var xy = this.el.getConstrainToXY(this.container, true, offsets);
37678 this.setPosition(xy[0], xy[1]);
37683 // private - used for dragging
37684 ghost : function(cls){
37685 var ghost = this.createGhost(cls);
37686 var box = this.getBox(true);
37687 ghost.setLeftTop(box.x, box.y);
37688 ghost.setWidth(box.width);
37690 this.activeGhost = ghost;
37695 unghost : function(show, matchPosition){
37696 if(!this.activeGhost) {
37699 if(show !== false){
37702 if(Ext.isMac && Ext.isGecko){ // work around stupid FF 2.0/Mac scroll bar bug
37703 this.cascade(this.setAutoScroll);
37706 if(matchPosition !== false){
37707 this.setPosition(this.activeGhost.getLeft(true), this.activeGhost.getTop(true));
37709 this.activeGhost.hide();
37710 this.activeGhost.remove();
37711 delete this.activeGhost;
37715 * Placeholder method for minimizing the window. By default, this method simply fires the {@link #minimize} event
37716 * since the behavior of minimizing a window is application-specific. To implement custom minimize behavior,
37717 * either the minimize event can be handled or this method can be overridden.
37718 * @return {Ext.Window} this
37720 minimize : function(){
37721 this.fireEvent('minimize', this);
37726 * <p>Closes the Window, removes it from the DOM, {@link Ext.Component#destroy destroy}s
37727 * the Window object and all its descendant Components. The {@link Ext.Panel#beforeclose beforeclose}
37728 * event is fired before the close happens and will cancel the close action if it returns false.<p>
37729 * <p><b>Note:</b> This method is not affected by the {@link #closeAction} setting which
37730 * only affects the action triggered when clicking the {@link #closable 'close' tool in the header}.
37731 * To hide the Window without destroying it, call {@link #hide}.</p>
37733 close : function(){
37734 if(this.fireEvent('beforeclose', this) !== false){
37738 this.hide(null, this.doClose, this);
37744 doClose : function(){
37745 this.fireEvent('close', this);
37750 * Fits the window within its current container and automatically replaces
37751 * the {@link #maximizable 'maximize' tool button} with the 'restore' tool button.
37752 * Also see {@link #toggleMaximize}.
37753 * @return {Ext.Window} this
37755 maximize : function(){
37756 if(!this.maximized){
37757 this.expand(false);
37758 this.restoreSize = this.getSize();
37759 this.restorePos = this.getPosition(true);
37760 if (this.maximizable){
37761 this.tools.maximize.hide();
37762 this.tools.restore.show();
37764 this.maximized = true;
37765 this.el.disableShadow();
37770 if(this.collapsible){
37771 this.tools.toggle.hide();
37773 this.el.addClass('x-window-maximized');
37774 this.container.addClass('x-window-maximized-ct');
37776 this.setPosition(0, 0);
37777 this.fitContainer();
37778 this.fireEvent('maximize', this);
37784 * Restores a {@link #maximizable maximized} window back to its original
37785 * size and position prior to being maximized and also replaces
37786 * the 'restore' tool button with the 'maximize' tool button.
37787 * Also see {@link #toggleMaximize}.
37788 * @return {Ext.Window} this
37790 restore : function(){
37791 if(this.maximized){
37792 this.el.removeClass('x-window-maximized');
37793 this.tools.restore.hide();
37794 this.tools.maximize.show();
37795 this.setPosition(this.restorePos[0], this.restorePos[1]);
37796 this.setSize(this.restoreSize.width, this.restoreSize.height);
37797 delete this.restorePos;
37798 delete this.restoreSize;
37799 this.maximized = false;
37800 this.el.enableShadow(true);
37805 if(this.collapsible){
37806 this.tools.toggle.show();
37808 this.container.removeClass('x-window-maximized-ct');
37810 this.doConstrain();
37811 this.fireEvent('restore', this);
37817 * A shortcut method for toggling between {@link #maximize} and {@link #restore} based on the current maximized
37818 * state of the window.
37819 * @return {Ext.Window} this
37821 toggleMaximize : function(){
37822 return this[this.maximized ? 'restore' : 'maximize']();
37826 fitContainer : function(){
37827 var vs = this.container.getViewSize();
37828 this.setSize(vs.width, vs.height);
37832 // z-index is managed by the WindowManager and may be overwritten at any time
37833 setZIndex : function(index){
37835 this.mask.setStyle('z-index', index);
37837 this.el.setZIndex(++index);
37841 this.resizer.proxy.setStyle('z-index', ++index);
37844 this.lastZIndex = index;
37848 * Aligns the window to the specified element
37849 * @param {Mixed} element The element to align to.
37850 * @param {String} position The position to align to (see {@link Ext.Element#alignTo} for more details).
37851 * @param {Array} offsets (optional) Offset the positioning by [x, y]
37852 * @return {Ext.Window} this
37854 alignTo : function(element, position, offsets){
37855 var xy = this.el.getAlignToXY(element, position, offsets);
37856 this.setPagePosition(xy[0], xy[1]);
37861 * Anchors this window to another element and realigns it when the window is resized or scrolled.
37862 * @param {Mixed} element The element to align to.
37863 * @param {String} position The position to align to (see {@link Ext.Element#alignTo} for more details)
37864 * @param {Array} offsets (optional) Offset the positioning by [x, y]
37865 * @param {Boolean/Number} monitorScroll (optional) true to monitor body scroll and reposition. If this parameter
37866 * is a number, it is used as the buffer delay (defaults to 50ms).
37867 * @return {Ext.Window} this
37869 anchorTo : function(el, alignment, offsets, monitorScroll){
37871 Ext.EventManager.removeResizeListener(this.doAnchor, this);
37872 Ext.EventManager.un(window, 'scroll', this.doAnchor, this);
37874 this.doAnchor = function(){
37875 this.alignTo(el, alignment, offsets);
37877 Ext.EventManager.onWindowResize(this.doAnchor, this);
37879 var tm = typeof monitorScroll;
37880 if(tm != 'undefined'){
37881 Ext.EventManager.on(window, 'scroll', this.doAnchor, this,
37882 {buffer: tm == 'number' ? monitorScroll : 50});
37889 * Brings this window to the front of any other visible windows
37890 * @param {Boolean} e (optional) Specify <tt>false</tt> to prevent the window from being focused.
37891 * @return {Ext.Window} this
37893 toFront : function(e){
37894 if(this.manager.bringToFront(this)){
37895 if(!e || !e.getTarget().focus){
37903 * Makes this the active window by showing its shadow, or deactivates it by hiding its shadow. This method also
37904 * fires the {@link #activate} or {@link #deactivate} event depending on which action occurred. This method is
37905 * called internally by {@link Ext.WindowMgr}.
37906 * @param {Boolean} active True to activate the window, false to deactivate it (defaults to false)
37908 setActive : function(active){
37910 if(!this.maximized){
37911 this.el.enableShadow(true);
37913 this.fireEvent('activate', this);
37915 this.el.disableShadow();
37916 this.fireEvent('deactivate', this);
37921 * Sends this window to the back of (lower z-index than) any other visible windows
37922 * @return {Ext.Window} this
37924 toBack : function(){
37925 this.manager.sendToBack(this);
37930 * Centers this window in the viewport
37931 * @return {Ext.Window} this
37933 center : function(){
37934 var xy = this.el.getAlignToXY(this.container, 'c-c');
37935 this.setPagePosition(xy[0], xy[1]);
37940 * @cfg {Boolean} autoWidth @hide
37943 Ext.reg('window', Ext.Window);
37945 // private - custom Window DD implementation
37946 Ext.Window.DD = function(win){
37948 Ext.Window.DD.superclass.constructor.call(this, win.el.id, 'WindowDD-'+win.id);
37949 this.setHandleElId(win.header.id);
37950 this.scroll = false;
37953 Ext.extend(Ext.Window.DD, Ext.dd.DD, {
37955 headerOffsets:[100, 25],
37956 startDrag : function(){
37958 this.proxy = w.ghost();
37959 if(w.constrain !== false){
37960 var so = w.el.shadowOffset;
37961 this.constrainTo(w.container, {right: so, left: so, bottom: so});
37962 }else if(w.constrainHeader !== false){
37963 var s = this.proxy.getSize();
37964 this.constrainTo(w.container, {right: -(s.width-this.headerOffsets[0]), bottom: -(s.height-this.headerOffsets[1])});
37967 b4Drag : Ext.emptyFn,
37969 onDrag : function(e){
37970 this.alignElWithMouse(this.proxy, e.getPageX(), e.getPageY());
37973 endDrag : function(e){
37974 this.win.unghost();
37975 this.win.saveState();
37979 * @class Ext.WindowGroup
37980 * An object that represents a group of {@link Ext.Window} instances and provides z-order management
37981 * and window activation behavior.
37984 Ext.WindowGroup = function(){
37986 var accessList = [];
37990 var sortWindows = function(d1, d2){
37991 return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
37995 var orderWindows = function(){
37996 var a = accessList, len = a.length;
37998 a.sort(sortWindows);
37999 var seed = a[0].manager.zseed;
38000 for(var i = 0; i < len; i++){
38002 if(win && !win.hidden){
38003 win.setZIndex(seed + (i*10));
38011 var setActiveWin = function(win){
38014 front.setActive(false);
38018 win.setActive(true);
38024 var activateLast = function(){
38025 for(var i = accessList.length-1; i >=0; --i) {
38026 if(!accessList[i].hidden){
38027 setActiveWin(accessList[i]);
38031 // none to activate
38032 setActiveWin(null);
38037 * The starting z-index for windows (defaults to 9000)
38038 * @type Number The z-index value
38043 register : function(win){
38044 list[win.id] = win;
38045 accessList.push(win);
38046 win.on('hide', activateLast);
38050 unregister : function(win){
38051 delete list[win.id];
38052 win.un('hide', activateLast);
38053 accessList.remove(win);
38057 * Gets a registered window by id.
38058 * @param {String/Object} id The id of the window or a {@link Ext.Window} instance
38059 * @return {Ext.Window}
38061 get : function(id){
38062 return typeof id == "object" ? id : list[id];
38066 * Brings the specified window to the front of any other active windows.
38067 * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
38068 * @return {Boolean} True if the dialog was brought to the front, else false
38069 * if it was already in front
38071 bringToFront : function(win){
38072 win = this.get(win);
38074 win._lastAccess = new Date().getTime();
38082 * Sends the specified window to the back of other active windows.
38083 * @param {String/Object} win The id of the window or a {@link Ext.Window} instance
38084 * @return {Ext.Window} The window
38086 sendToBack : function(win){
38087 win = this.get(win);
38088 win._lastAccess = -(new Date().getTime());
38094 * Hides all windows in the group.
38096 hideAll : function(){
38097 for(var id in list){
38098 if(list[id] && typeof list[id] != "function" && list[id].isVisible()){
38105 * Gets the currently-active window in the group.
38106 * @return {Ext.Window} The active window
38108 getActive : function(){
38113 * Returns zero or more windows in the group using the custom search function passed to this method.
38114 * The function should accept a single {@link Ext.Window} reference as its only argument and should
38115 * return true if the window matches the search criteria, otherwise it should return false.
38116 * @param {Function} fn The search function
38117 * @param {Object} scope (optional) The scope in which to execute the function (defaults to the window
38118 * that gets passed to the function if not specified)
38119 * @return {Array} An array of zero or more matching windows
38121 getBy : function(fn, scope){
38123 for(var i = accessList.length-1; i >=0; --i) {
38124 var win = accessList[i];
38125 if(fn.call(scope||win, win) !== false){
38133 * Executes the specified function once for every window in the group, passing each
38134 * window as the only parameter. Returning false from the function will stop the iteration.
38135 * @param {Function} fn The function to execute for each item
38136 * @param {Object} scope (optional) The scope in which to execute the function
38138 each : function(fn, scope){
38139 for(var id in list){
38140 if(list[id] && typeof list[id] != "function"){
38141 if(fn.call(scope || list[id], list[id]) === false){
38152 * @class Ext.WindowMgr
38153 * @extends Ext.WindowGroup
38154 * The default global window group that is available automatically. To have more than one group of windows
38155 * with separate z-order stacks, create additional instances of {@link Ext.WindowGroup} as needed.
38158 Ext.WindowMgr = new Ext.WindowGroup();/**
\r
38159 * @class Ext.MessageBox
\r
38160 * <p>Utility class for generating different styles of message boxes. The alias Ext.Msg can also be used.<p/>
\r
38161 * <p>Note that the MessageBox is asynchronous. Unlike a regular JavaScript <code>alert</code> (which will halt
\r
38162 * browser execution), showing a MessageBox will not cause the code to stop. For this reason, if you have code
\r
38163 * that should only run <em>after</em> some user feedback from the MessageBox, you must use a callback function
\r
38164 * (see the <code>function</code> parameter for {@link #show} for more details).</p>
\r
38165 * <p>Example usage:</p>
\r
38168 Ext.Msg.alert('Status', 'Changes saved successfully.');
\r
38170 // Prompt for user data and process the result using a callback:
\r
38171 Ext.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
\r
38172 if (btn == 'ok'){
\r
38173 // process text value and close...
\r
38177 // Show a dialog using config options:
\r
38179 title:'Save Changes?',
\r
38180 msg: 'You are closing a tab that has unsaved changes. Would you like to save your changes?',
\r
38181 buttons: Ext.Msg.YESNOCANCEL,
\r
38182 fn: processResult,
\r
38184 icon: Ext.MessageBox.QUESTION
\r
38189 Ext.MessageBox = function(){
\r
38190 var dlg, opt, mask, waitTimer,
\r
38191 bodyEl, msgEl, textboxEl, textareaEl, progressBar, pp, iconEl, spacerEl,
\r
38192 buttons, activeTextEl, bwidth, bufferIcon = '', iconCls = '';
\r
38195 var handleButton = function(button){
\r
38196 if(dlg.isVisible()){
\r
38199 Ext.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value, opt], 1);
\r
38204 var handleHide = function(){
\r
38205 if(opt && opt.cls){
\r
38206 dlg.el.removeClass(opt.cls);
\r
38208 progressBar.reset();
\r
38212 var handleEsc = function(d, k, e){
\r
38213 if(opt && opt.closable !== false){
\r
38223 var updateButtons = function(b){
\r
38226 buttons["ok"].hide();
\r
38227 buttons["cancel"].hide();
\r
38228 buttons["yes"].hide();
\r
38229 buttons["no"].hide();
\r
38232 dlg.footer.dom.style.display = '';
\r
38233 for(var k in buttons){
\r
38234 if(!Ext.isFunction(buttons[k])){
\r
38236 buttons[k].show();
\r
38237 buttons[k].setText(Ext.isString(b[k]) ? b[k] : Ext.MessageBox.buttonText[k]);
\r
38238 width += buttons[k].el.getWidth()+15;
\r
38240 buttons[k].hide();
\r
38249 * Returns a reference to the underlying {@link Ext.Window} element
\r
38250 * @return {Ext.Window} The window
\r
38252 getDialog : function(titleText){
\r
38254 dlg = new Ext.Window({
\r
38255 autoCreate : true,
\r
38259 constrainHeader:true,
\r
38260 minimizable : false,
\r
38261 maximizable : false,
\r
38265 buttonAlign:"center",
\r
38272 close : function(){
\r
38273 if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){
\r
38274 handleButton("no");
\r
38276 handleButton("cancel");
\r
38281 var bt = this.buttonText;
\r
38282 //TODO: refactor this block into a buttons config to pass into the Window constructor
\r
38283 buttons["ok"] = dlg.addButton(bt["ok"], handleButton.createCallback("ok"));
\r
38284 buttons["yes"] = dlg.addButton(bt["yes"], handleButton.createCallback("yes"));
\r
38285 buttons["no"] = dlg.addButton(bt["no"], handleButton.createCallback("no"));
\r
38286 buttons["cancel"] = dlg.addButton(bt["cancel"], handleButton.createCallback("cancel"));
\r
38287 buttons["ok"].hideMode = buttons["yes"].hideMode = buttons["no"].hideMode = buttons["cancel"].hideMode = 'offsets';
\r
38288 dlg.render(document.body);
\r
38289 dlg.getEl().addClass('x-window-dlg');
\r
38291 bodyEl = dlg.body.createChild({
\r
38292 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>'
\r
38294 iconEl = Ext.get(bodyEl.dom.firstChild);
\r
38295 var contentEl = bodyEl.dom.childNodes[1];
\r
38296 msgEl = Ext.get(contentEl.firstChild);
\r
38297 textboxEl = Ext.get(contentEl.childNodes[2].firstChild);
\r
38298 textboxEl.enableDisplayMode();
\r
38299 textboxEl.addKeyListener([10,13], function(){
\r
38300 if(dlg.isVisible() && opt && opt.buttons){
\r
38301 if(opt.buttons.ok){
\r
38302 handleButton("ok");
\r
38303 }else if(opt.buttons.yes){
\r
38304 handleButton("yes");
\r
38308 textareaEl = Ext.get(contentEl.childNodes[2].childNodes[1]);
\r
38309 textareaEl.enableDisplayMode();
\r
38310 progressBar = new Ext.ProgressBar({
\r
38313 bodyEl.createChild({cls:'x-clear'});
\r
38319 * Updates the message box body text
\r
38320 * @param {String} text (optional) Replaces the message box element's innerHTML with the specified string (defaults to
\r
38321 * the XHTML-compliant non-breaking space character '&#160;')
\r
38322 * @return {Ext.MessageBox} this
\r
38324 updateText : function(text){
\r
38325 if(!dlg.isVisible() && !opt.width){
\r
38326 dlg.setSize(this.maxWidth, 100); // resize first so content is never clipped from previous shows
\r
38328 msgEl.update(text || ' ');
\r
38330 var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0;
\r
38331 var mw = msgEl.getWidth() + msgEl.getMargins('lr');
\r
38332 var fw = dlg.getFrameWidth('lr');
\r
38333 var bw = dlg.body.getFrameWidth('lr');
\r
38334 if (Ext.isIE && iw > 0){
\r
38335 //3 pixels get subtracted in the icon CSS for an IE margin issue,
\r
38336 //so we have to add it back here for the overall width to be consistent
\r
38339 var w = Math.max(Math.min(opt.width || iw+mw+fw+bw, this.maxWidth),
\r
38340 Math.max(opt.minWidth || this.minWidth, bwidth || 0));
\r
38342 if(opt.prompt === true){
\r
38343 activeTextEl.setWidth(w-iw-fw-bw);
\r
38345 if(opt.progress === true || opt.wait === true){
\r
38346 progressBar.setSize(w-iw-fw-bw);
\r
38348 if(Ext.isIE && w == bwidth){
\r
38349 w += 4; //Add offset when the content width is smaller than the buttons.
\r
38351 dlg.setSize(w, 'auto').center();
\r
38356 * Updates a progress-style message box's text and progress bar. Only relevant on message boxes
\r
38357 * initiated via {@link Ext.MessageBox#progress} or {@link Ext.MessageBox#wait},
\r
38358 * or by calling {@link Ext.MessageBox#show} with progress: true.
\r
38359 * @param {Number} value Any number between 0 and 1 (e.g., .5, defaults to 0)
\r
38360 * @param {String} progressText The progress text to display inside the progress bar (defaults to '')
\r
38361 * @param {String} msg The message box's body text is replaced with the specified string (defaults to undefined
\r
38362 * so that any existing body text will not get overwritten by default unless a new value is passed in)
\r
38363 * @return {Ext.MessageBox} this
\r
38365 updateProgress : function(value, progressText, msg){
\r
38366 progressBar.updateProgress(value, progressText);
\r
38368 this.updateText(msg);
\r
38374 * Returns true if the message box is currently displayed
\r
38375 * @return {Boolean} True if the message box is visible, else false
\r
38377 isVisible : function(){
\r
38378 return dlg && dlg.isVisible();
\r
38382 * Hides the message box if it is displayed
\r
38383 * @return {Ext.MessageBox} this
\r
38385 hide : function(){
\r
38386 var proxy = dlg ? dlg.activeGhost : null;
\r
38387 if(this.isVisible() || proxy){
\r
38391 // unghost is a private function, but i saw no better solution
\r
38392 // to fix the locking problem when dragging while it closes
\r
38393 dlg.unghost(false, false);
\r
38400 * Displays a new message box, or reinitializes an existing message box, based on the config options
\r
38401 * passed in. All display functions (e.g. prompt, alert, etc.) on MessageBox call this function internally,
\r
38402 * although those calls are basic shortcuts and do not support all of the config options allowed here.
\r
38403 * @param {Object} config The following config options are supported: <ul>
\r
38404 * <li><b>animEl</b> : String/Element<div class="sub-desc">An id or Element from which the message box should animate as it
\r
38405 * opens and closes (defaults to undefined)</div></li>
\r
38406 * <li><b>buttons</b> : Object/Boolean<div class="sub-desc">A button config object (e.g., Ext.MessageBox.OKCANCEL or {ok:'Foo',
\r
38407 * cancel:'Bar'}), or false to not show any buttons (defaults to false)</div></li>
\r
38408 * <li><b>closable</b> : Boolean<div class="sub-desc">False to hide the top-right close button (defaults to true). Note that
\r
38409 * progress and wait dialogs will ignore this property and always hide the close button as they can only
\r
38410 * be closed programmatically.</div></li>
\r
38411 * <li><b>cls</b> : String<div class="sub-desc">A custom CSS class to apply to the message box's container element</div></li>
\r
38412 * <li><b>defaultTextHeight</b> : Number<div class="sub-desc">The default height in pixels of the message box's multiline textarea
\r
38413 * if displayed (defaults to 75)</div></li>
\r
38414 * <li><b>fn</b> : Function<div class="sub-desc">A callback function which is called when the dialog is dismissed either
\r
38415 * by clicking on the configured buttons, or on the dialog close button, or by pressing
\r
38416 * the return button to enter input.
\r
38417 * <p>Progress and wait dialogs will ignore this option since they do not respond to user
\r
38418 * actions and can only be closed programmatically, so any required function should be called
\r
38419 * by the same code after it closes the dialog. Parameters passed:<ul>
\r
38420 * <li><b>buttonId</b> : String<div class="sub-desc">The ID of the button pressed, one of:<div class="sub-desc"><ul>
\r
38421 * <li><tt>ok</tt></li>
\r
38422 * <li><tt>yes</tt></li>
\r
38423 * <li><tt>no</tt></li>
\r
38424 * <li><tt>cancel</tt></li>
\r
38425 * </ul></div></div></li>
\r
38426 * <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>
\r
38427 * or <tt><a href="#show-option-multiline" ext:member="show-option-multiline" ext:cls="Ext.MessageBox">multiline</a></tt> is true</div></li>
\r
38428 * <li><b>opt</b> : Object<div class="sub-desc">The config object passed to show.</div></li>
\r
38429 * </ul></p></div></li>
\r
38430 * <li><b>scope</b> : Object<div class="sub-desc">The scope of the callback function</div></li>
\r
38431 * <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
\r
38432 * dialog (e.g. Ext.MessageBox.WARNING or 'custom-class') (defaults to '')</div></li>
\r
38433 * <li><b>iconCls</b> : String<div class="sub-desc">The standard {@link Ext.Window#iconCls} to
\r
38434 * add an optional header icon (defaults to '')</div></li>
\r
38435 * <li><b>maxWidth</b> : Number<div class="sub-desc">The maximum width in pixels of the message box (defaults to 600)</div></li>
\r
38436 * <li><b>minWidth</b> : Number<div class="sub-desc">The minimum width in pixels of the message box (defaults to 100)</div></li>
\r
38437 * <li><b>modal</b> : Boolean<div class="sub-desc">False to allow user interaction with the page while the message box is
\r
38438 * displayed (defaults to true)</div></li>
\r
38439 * <li><b>msg</b> : String<div class="sub-desc">A string that will replace the existing message box body text (defaults to the
\r
38440 * XHTML-compliant non-breaking space character '&#160;')</div></li>
\r
38441 * <li><a id="show-option-multiline"></a><b>multiline</b> : Boolean<div class="sub-desc">
\r
38442 * True to prompt the user to enter multi-line text (defaults to false)</div></li>
\r
38443 * <li><b>progress</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
\r
38444 * <li><b>progressText</b> : String<div class="sub-desc">The text to display inside the progress bar if progress = true (defaults to '')</div></li>
\r
38445 * <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>
\r
38446 * <li><b>proxyDrag</b> : Boolean<div class="sub-desc">True to display a lightweight proxy while dragging (defaults to false)</div></li>
\r
38447 * <li><b>title</b> : String<div class="sub-desc">The title text</div></li>
\r
38448 * <li><b>value</b> : String<div class="sub-desc">The string value to set into the active textbox element if displayed</div></li>
\r
38449 * <li><b>wait</b> : Boolean<div class="sub-desc">True to display a progress bar (defaults to false)</div></li>
\r
38450 * <li><b>waitConfig</b> : Object<div class="sub-desc">A {@link Ext.ProgressBar#waitConfig} object (applies only if wait = true)</div></li>
\r
38451 * <li><b>width</b> : Number<div class="sub-desc">The width of the dialog in pixels</div></li>
\r
38456 title: 'Address',
\r
38457 msg: 'Please enter your address:',
\r
38459 buttons: Ext.MessageBox.OKCANCEL,
\r
38462 animEl: 'addAddressBtn',
\r
38463 icon: Ext.MessageBox.INFO
\r
38466 * @return {Ext.MessageBox} this
\r
38468 show : function(options){
\r
38469 if(this.isVisible()){
\r
38473 var d = this.getDialog(opt.title || " ");
\r
38475 d.setTitle(opt.title || " ");
\r
38476 var allowClose = (opt.closable !== false && opt.progress !== true && opt.wait !== true);
\r
38477 d.tools.close.setDisplayed(allowClose);
\r
38478 activeTextEl = textboxEl;
\r
38479 opt.prompt = opt.prompt || (opt.multiline ? true : false);
\r
38481 if(opt.multiline){
\r
38482 textboxEl.hide();
\r
38483 textareaEl.show();
\r
38484 textareaEl.setHeight(Ext.isNumber(opt.multiline) ? opt.multiline : this.defaultTextHeight);
\r
38485 activeTextEl = textareaEl;
\r
38487 textboxEl.show();
\r
38488 textareaEl.hide();
\r
38491 textboxEl.hide();
\r
38492 textareaEl.hide();
\r
38494 activeTextEl.dom.value = opt.value || "";
\r
38496 d.focusEl = activeTextEl;
\r
38498 var bs = opt.buttons;
\r
38501 db = buttons["ok"];
\r
38502 }else if(bs && bs.yes){
\r
38503 db = buttons["yes"];
\r
38510 d.setIconClass(opt.iconCls);
\r
38512 this.setIcon(Ext.isDefined(opt.icon) ? opt.icon : bufferIcon);
\r
38513 bwidth = updateButtons(opt.buttons);
\r
38514 progressBar.setVisible(opt.progress === true || opt.wait === true);
\r
38515 this.updateProgress(0, opt.progressText);
\r
38516 this.updateText(opt.msg);
\r
38518 d.el.addClass(opt.cls);
\r
38520 d.proxyDrag = opt.proxyDrag === true;
\r
38521 d.modal = opt.modal !== false;
\r
38522 d.mask = opt.modal !== false ? mask : false;
\r
38523 if(!d.isVisible()){
\r
38524 // force it to the end of the z-index stack so it gets a cursor in FF
\r
38525 document.body.appendChild(dlg.el.dom);
\r
38526 d.setAnimateTarget(opt.animEl);
\r
38527 d.show(opt.animEl);
\r
38530 //workaround for window internally enabling keymap in afterShow
\r
38531 d.on('show', function(){
\r
38532 if(allowClose === true){
\r
38533 d.keyMap.enable();
\r
38535 d.keyMap.disable();
\r
38537 }, this, {single:true});
\r
38539 if(opt.wait === true){
\r
38540 progressBar.wait(opt.waitConfig);
\r
38546 * Adds the specified icon to the dialog. By default, the class 'ext-mb-icon' is applied for default
\r
38547 * styling, and the class passed in is expected to supply the background image url. Pass in empty string ('')
\r
38548 * to clear any existing icon. This method must be called before the MessageBox is shown.
\r
38549 * The following built-in icon classes are supported, but you can also pass in a custom class name:
\r
38551 Ext.MessageBox.INFO
\r
38552 Ext.MessageBox.WARNING
\r
38553 Ext.MessageBox.QUESTION
\r
38554 Ext.MessageBox.ERROR
\r
38556 * @param {String} icon A CSS classname specifying the icon's background image url, or empty string to clear the icon
\r
38557 * @return {Ext.MessageBox} this
\r
38559 setIcon : function(icon){
\r
38561 bufferIcon = icon;
\r
38564 bufferIcon = undefined;
\r
38565 if(icon && icon != ''){
\r
38566 iconEl.removeClass('x-hidden');
\r
38567 iconEl.replaceClass(iconCls, icon);
\r
38568 bodyEl.addClass('x-dlg-icon');
\r
38571 iconEl.replaceClass(iconCls, 'x-hidden');
\r
38572 bodyEl.removeClass('x-dlg-icon');
\r
38579 * Displays a message box with a progress bar. This message box has no buttons and is not closeable by
\r
38580 * the user. You are responsible for updating the progress bar as needed via {@link Ext.MessageBox#updateProgress}
\r
38581 * and closing the message box when the process is complete.
\r
38582 * @param {String} title The title bar text
\r
38583 * @param {String} msg The message box body text
\r
38584 * @param {String} progressText (optional) The text to display inside the progress bar (defaults to '')
\r
38585 * @return {Ext.MessageBox} this
\r
38587 progress : function(title, msg, progressText){
\r
38594 minWidth: this.minProgressWidth,
\r
38595 progressText: progressText
\r
38601 * Displays a message box with an infinitely auto-updating progress bar. This can be used to block user
\r
38602 * interaction while waiting for a long-running process to complete that does not have defined intervals.
\r
38603 * You are responsible for closing the message box when the process is complete.
\r
38604 * @param {String} msg The message box body text
\r
38605 * @param {String} title (optional) The title bar text
\r
38606 * @param {Object} config (optional) A {@link Ext.ProgressBar#waitConfig} object
\r
38607 * @return {Ext.MessageBox} this
\r
38609 wait : function(msg, title, config){
\r
38617 minWidth: this.minProgressWidth,
\r
38618 waitConfig: config
\r
38624 * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript alert prompt).
\r
38625 * If a callback function is passed it will be called after the user clicks the button, and the
\r
38626 * id of the button that was clicked will be passed as the only parameter to the callback
\r
38627 * (could also be the top-right close button).
\r
38628 * @param {String} title The title bar text
\r
38629 * @param {String} msg The message box body text
\r
38630 * @param {Function} fn (optional) The callback function invoked after the message box is closed
\r
38631 * @param {Object} scope (optional) The scope of the callback function
\r
38632 * @return {Ext.MessageBox} this
\r
38634 alert : function(title, msg, fn, scope){
\r
38638 buttons: this.OK,
\r
38646 * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's confirm).
\r
38647 * If a callback function is passed it will be called after the user clicks either button,
\r
38648 * and the id of the button that was clicked will be passed as the only parameter to the callback
\r
38649 * (could also be the top-right close button).
\r
38650 * @param {String} title The title bar text
\r
38651 * @param {String} msg The message box body text
\r
38652 * @param {Function} fn (optional) The callback function invoked after the message box is closed
\r
38653 * @param {Object} scope (optional) The scope of the callback function
\r
38654 * @return {Ext.MessageBox} this
\r
38656 confirm : function(title, msg, fn, scope){
\r
38660 buttons: this.YESNO,
\r
38663 icon: this.QUESTION
\r
38669 * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to JavaScript's prompt).
\r
38670 * The prompt can be a single-line or multi-line textbox. If a callback function is passed it will be called after the user
\r
38671 * clicks either button, and the id of the button that was clicked (could also be the top-right
\r
38672 * close button) and the text that was entered will be passed as the two parameters to the callback.
\r
38673 * @param {String} title The title bar text
\r
38674 * @param {String} msg The message box body text
\r
38675 * @param {Function} fn (optional) The callback function invoked after the message box is closed
\r
38676 * @param {Object} scope (optional) The scope of the callback function
\r
38677 * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight
\r
38678 * property, or the height in pixels to create the textbox (defaults to false / single-line)
\r
38679 * @param {String} value (optional) Default value of the text input element (defaults to '')
\r
38680 * @return {Ext.MessageBox} this
\r
38682 prompt : function(title, msg, fn, scope, multiline, value){
\r
38686 buttons: this.OKCANCEL,
\r
38691 multiline: multiline,
\r
38698 * Button config that displays a single OK button
\r
38703 * Button config that displays a single Cancel button
\r
38706 CANCEL : {cancel:true},
\r
38708 * Button config that displays OK and Cancel buttons
\r
38711 OKCANCEL : {ok:true, cancel:true},
\r
38713 * Button config that displays Yes and No buttons
\r
38716 YESNO : {yes:true, no:true},
\r
38718 * Button config that displays Yes, No and Cancel buttons
\r
38721 YESNOCANCEL : {yes:true, no:true, cancel:true},
\r
38723 * The CSS class that provides the INFO icon image
\r
38726 INFO : 'ext-mb-info',
\r
38728 * The CSS class that provides the WARNING icon image
\r
38731 WARNING : 'ext-mb-warning',
\r
38733 * The CSS class that provides the QUESTION icon image
\r
38736 QUESTION : 'ext-mb-question',
\r
38738 * The CSS class that provides the ERROR icon image
\r
38741 ERROR : 'ext-mb-error',
\r
38744 * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75)
\r
38747 defaultTextHeight : 75,
\r
38749 * The maximum width in pixels of the message box (defaults to 600)
\r
38754 * The minimum width in pixels of the message box (defaults to 100)
\r
38759 * The minimum width in pixels of the message box if it is a progress-style dialog. This is useful
\r
38760 * for setting a different minimum width than text-only dialogs may need (defaults to 250)
\r
38763 minProgressWidth : 250,
\r
38765 * An object containing the default button text strings that can be overriden for localized language support.
\r
38766 * Supported properties are: ok, cancel, yes and no. Generally you should include a locale-specific
\r
38767 * resource file for handling language support across the framework.
\r
38768 * Customize the default text like so: Ext.MessageBox.buttonText.yes = "oui"; //french
\r
38773 cancel : "Cancel",
\r
38781 * Shorthand for {@link Ext.MessageBox}
\r
38783 Ext.Msg = Ext.MessageBox;/**
\r
38784 * @class Ext.dd.PanelProxy
\r
38785 * A custom drag proxy implementation specific to {@link Ext.Panel}s. This class is primarily used internally
\r
38786 * for the Panel's drag drop implementation, and should never need to be created directly.
\r
38788 * @param panel The {@link Ext.Panel} to proxy for
\r
38789 * @param config Configuration options
\r
38791 Ext.dd.PanelProxy = function(panel, config){
\r
38792 this.panel = panel;
\r
38793 this.id = this.panel.id +'-ddproxy';
\r
38794 Ext.apply(this, config);
\r
38797 Ext.dd.PanelProxy.prototype = {
\r
38799 * @cfg {Boolean} insertProxy True to insert a placeholder proxy element while dragging the panel,
\r
38800 * false to drag with no proxy (defaults to true).
\r
38802 insertProxy : true,
\r
38804 // private overrides
\r
38805 setStatus : Ext.emptyFn,
\r
38806 reset : Ext.emptyFn,
\r
38807 update : Ext.emptyFn,
\r
38808 stop : Ext.emptyFn,
\r
38809 sync: Ext.emptyFn,
\r
38812 * Gets the proxy's element
\r
38813 * @return {Element} The proxy's element
\r
38815 getEl : function(){
\r
38816 return this.ghost;
\r
38820 * Gets the proxy's ghost element
\r
38821 * @return {Element} The proxy's ghost element
\r
38823 getGhost : function(){
\r
38824 return this.ghost;
\r
38828 * Gets the proxy's element
\r
38829 * @return {Element} The proxy's element
\r
38831 getProxy : function(){
\r
38832 return this.proxy;
\r
38836 * Hides the proxy
\r
38838 hide : function(){
\r
38841 this.proxy.remove();
\r
38842 delete this.proxy;
\r
38844 this.panel.el.dom.style.display = '';
\r
38845 this.ghost.remove();
\r
38846 delete this.ghost;
\r
38851 * Shows the proxy
\r
38853 show : function(){
\r
38855 this.ghost = this.panel.createGhost(undefined, undefined, Ext.getBody());
\r
38856 this.ghost.setXY(this.panel.el.getXY())
\r
38857 if(this.insertProxy){
\r
38858 this.proxy = this.panel.el.insertSibling({cls:'x-panel-dd-spacer'});
\r
38859 this.proxy.setSize(this.panel.getSize());
\r
38861 this.panel.el.dom.style.display = 'none';
\r
38866 repair : function(xy, callback, scope){
\r
38868 if(typeof callback == "function"){
\r
38869 callback.call(scope || this);
\r
38874 * Moves the proxy to a different position in the DOM. This is typically called while dragging the Panel
\r
38875 * to keep the proxy sync'd to the Panel's location.
\r
38876 * @param {HTMLElement} parentNode The proxy's parent DOM node
\r
38877 * @param {HTMLElement} before (optional) The sibling node before which the proxy should be inserted (defaults
\r
38878 * to the parent's last child if not specified)
\r
38880 moveProxy : function(parentNode, before){
\r
38882 parentNode.insertBefore(this.proxy.dom, before);
\r
38887 // private - DD implementation for Panels
\r
38888 Ext.Panel.DD = function(panel, cfg){
\r
38889 this.panel = panel;
\r
38890 this.dragData = {panel: panel};
\r
38891 this.proxy = new Ext.dd.PanelProxy(panel, cfg);
\r
38892 Ext.Panel.DD.superclass.constructor.call(this, panel.el, cfg);
\r
38893 var h = panel.header;
\r
38895 this.setHandleElId(h.id);
\r
38897 (h ? h : this.panel.body).setStyle('cursor', 'move');
\r
38898 this.scroll = false;
\r
38901 Ext.extend(Ext.Panel.DD, Ext.dd.DragSource, {
\r
38902 showFrame: Ext.emptyFn,
\r
38903 startDrag: Ext.emptyFn,
\r
38904 b4StartDrag: function(x, y) {
\r
38905 this.proxy.show();
\r
38907 b4MouseDown: function(e) {
\r
38908 var x = e.getPageX();
\r
38909 var y = e.getPageY();
\r
38910 this.autoOffset(x, y);
\r
38912 onInitDrag : function(x, y){
\r
38913 this.onStartDrag(x, y);
\r
38916 createFrame : Ext.emptyFn,
\r
38917 getDragEl : function(e){
\r
38918 return this.proxy.ghost.dom;
\r
38920 endDrag : function(e){
\r
38921 this.proxy.hide();
\r
38922 this.panel.saveState();
\r
38925 autoOffset : function(x, y) {
\r
38926 x -= this.startPageX;
\r
38927 y -= this.startPageY;
\r
38928 this.setDelta(x, y);
\r
38931 * @class Ext.state.Provider
38932 * Abstract base class for state provider implementations. This class provides methods
38933 * for encoding and decoding <b>typed</b> variables including dates and defines the
38934 * Provider interface.
38936 Ext.state.Provider = function(){
38938 * @event statechange
38939 * Fires when a state change occurs.
38940 * @param {Provider} this This state provider
38941 * @param {String} key The state key which was changed
38942 * @param {String} value The encoded value for the state
38944 this.addEvents("statechange");
38946 Ext.state.Provider.superclass.constructor.call(this);
38948 Ext.extend(Ext.state.Provider, Ext.util.Observable, {
38950 * Returns the current value for a key
38951 * @param {String} name The key name
38952 * @param {Mixed} defaultValue A default value to return if the key's value is not found
38953 * @return {Mixed} The state data
38955 get : function(name, defaultValue){
38956 return typeof this.state[name] == "undefined" ?
38957 defaultValue : this.state[name];
38961 * Clears a value from the state
38962 * @param {String} name The key name
38964 clear : function(name){
38965 delete this.state[name];
38966 this.fireEvent("statechange", this, name, null);
38970 * Sets the value for a key
38971 * @param {String} name The key name
38972 * @param {Mixed} value The value to set
38974 set : function(name, value){
38975 this.state[name] = value;
38976 this.fireEvent("statechange", this, name, value);
38980 * Decodes a string previously encoded with {@link #encodeValue}.
38981 * @param {String} value The value to decode
38982 * @return {Mixed} The decoded value
38984 decodeValue : function(cookie){
38985 var re = /^(a|n|d|b|s|o)\:(.*)$/;
38986 var matches = re.exec(unescape(cookie));
38987 if(!matches || !matches[1]) return; // non state cookie
38988 var type = matches[1];
38989 var v = matches[2];
38992 return parseFloat(v);
38994 return new Date(Date.parse(v));
39000 Ext.each(v.split('^'), function(val){
39001 all.push(this.decodeValue(val));
39008 Ext.each(v.split('^'), function(val){
39009 var kv = val.split('=');
39010 all[kv[0]] = this.decodeValue(kv[1]);
39020 * Encodes a value including type information. Decode with {@link #decodeValue}.
39021 * @param {Mixed} value The value to encode
39022 * @return {String} The encoded value
39024 encodeValue : function(v){
39026 if(typeof v == "number"){
39028 }else if(typeof v == "boolean"){
39029 enc = "b:" + (v ? "1" : "0");
39030 }else if(Ext.isDate(v)){
39031 enc = "d:" + v.toGMTString();
39032 }else if(Ext.isArray(v)){
39034 for(var i = 0, len = v.length; i < len; i++){
39035 flat += this.encodeValue(v[i]);
39036 if(i != len-1) flat += "^";
39039 }else if(typeof v == "object"){
39042 if(typeof v[key] != "function" && v[key] !== undefined){
39043 flat += key + "=" + this.encodeValue(v[key]) + "^";
39046 enc = "o:" + flat.substring(0, flat.length-1);
39050 return escape(enc);
39054 * @class Ext.state.Manager
\r
39055 * This is the global state manager. By default all components that are "state aware" check this class
\r
39056 * for state information if you don't pass them a custom state provider. In order for this class
\r
39057 * to be useful, it must be initialized with a provider when your application initializes. Example usage:
\r
39059 // in your initialization function
\r
39060 init : function(){
\r
39061 Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
\r
39062 var win = new Window(...);
\r
39063 win.restoreState();
\r
39068 Ext.state.Manager = function(){
\r
39069 var provider = new Ext.state.Provider();
\r
39073 * Configures the default state provider for your application
\r
39074 * @param {Provider} stateProvider The state provider to set
\r
39076 setProvider : function(stateProvider){
\r
39077 provider = stateProvider;
\r
39081 * Returns the current value for a key
\r
39082 * @param {String} name The key name
\r
39083 * @param {Mixed} defaultValue The default value to return if the key lookup does not match
\r
39084 * @return {Mixed} The state data
\r
39086 get : function(key, defaultValue){
\r
39087 return provider.get(key, defaultValue);
\r
39091 * Sets the value for a key
\r
39092 * @param {String} name The key name
\r
39093 * @param {Mixed} value The state data
\r
39095 set : function(key, value){
\r
39096 provider.set(key, value);
\r
39100 * Clears a value from the state
\r
39101 * @param {String} name The key name
\r
39103 clear : function(key){
\r
39104 provider.clear(key);
\r
39108 * Gets the currently configured state provider
\r
39109 * @return {Provider} The state provider
\r
39111 getProvider : function(){
\r
39117 * @class Ext.state.CookieProvider
\r
39118 * @extends Ext.state.Provider
\r
39119 * The default Provider implementation which saves state via cookies.
\r
39122 var cp = new Ext.state.CookieProvider({
\r
39123 path: "/cgi-bin/",
\r
39124 expires: new Date(new Date().getTime()+(1000*60*60*24*30)), //30 days
\r
39125 domain: "extjs.com"
\r
39127 Ext.state.Manager.setProvider(cp);
\r
39129 * @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
39130 * @cfg {Date} expires The cookie expiration date (defaults to 7 days from now)
\r
39131 * @cfg {String} domain The domain to save the cookie for. Note that you cannot specify a different domain than
\r
39132 * your page is on, but you can specify a sub-domain, or simply the domain itself like 'extjs.com' to include
\r
39133 * all sub-domains if you need to access cookies across different sub-domains (defaults to null which uses the same
\r
39134 * domain the page is running on including the 'www' like 'www.extjs.com')
\r
39135 * @cfg {Boolean} secure True if the site is using SSL (defaults to false)
\r
39137 * Create a new CookieProvider
\r
39138 * @param {Object} config The configuration object
\r
39140 Ext.state.CookieProvider = function(config){
\r
39141 Ext.state.CookieProvider.superclass.constructor.call(this);
\r
39143 this.expires = new Date(new Date().getTime()+(1000*60*60*24*7)); //7 days
\r
39144 this.domain = null;
\r
39145 this.secure = false;
\r
39146 Ext.apply(this, config);
\r
39147 this.state = this.readCookies();
\r
39150 Ext.extend(Ext.state.CookieProvider, Ext.state.Provider, {
\r
39152 set : function(name, value){
\r
39153 if(typeof value == "undefined" || value === null){
\r
39154 this.clear(name);
\r
39157 this.setCookie(name, value);
\r
39158 Ext.state.CookieProvider.superclass.set.call(this, name, value);
\r
39162 clear : function(name){
\r
39163 this.clearCookie(name);
\r
39164 Ext.state.CookieProvider.superclass.clear.call(this, name);
\r
39168 readCookies : function(){
\r
39169 var cookies = {};
\r
39170 var c = document.cookie + ";";
\r
39171 var re = /\s?(.*?)=(.*?);/g;
\r
39173 while((matches = re.exec(c)) != null){
\r
39174 var name = matches[1];
\r
39175 var value = matches[2];
\r
39176 if(name && name.substring(0,3) == "ys-"){
\r
39177 cookies[name.substr(3)] = this.decodeValue(value);
\r
39184 setCookie : function(name, value){
\r
39185 document.cookie = "ys-"+ name + "=" + this.encodeValue(value) +
\r
39186 ((this.expires == null) ? "" : ("; expires=" + this.expires.toGMTString())) +
\r
39187 ((this.path == null) ? "" : ("; path=" + this.path)) +
\r
39188 ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
\r
39189 ((this.secure == true) ? "; secure" : "");
\r
39193 clearCookie : function(name){
\r
39194 document.cookie = "ys-" + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
\r
39195 ((this.path == null) ? "" : ("; path=" + this.path)) +
\r
39196 ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
\r
39197 ((this.secure == true) ? "; secure" : "");
\r
39200 * @class Ext.DataView
39201 * @extends Ext.BoxComponent
39202 * A mechanism for displaying data using custom layout templates and formatting. DataView uses an {@link Ext.XTemplate}
39203 * as its internal templating mechanism, and is bound to an {@link Ext.data.Store}
39204 * so that as the data in the store changes the view is automatically updated to reflect the changes. The view also
39205 * provides built-in behavior for many common events that can occur for its contained items including click, doubleclick,
39206 * mouseover, mouseout, etc. as well as a built-in selection model. <b>In order to use these features, an {@link #itemSelector}
39207 * config must be provided for the DataView to determine what nodes it will be working with.</b>
39209 * <p>The example below binds a DataView to a {@link Ext.data.Store} and renders it into an {@link Ext.Panel}.</p>
39211 var store = new Ext.data.JsonStore({
39212 url: 'get-images.php',
39216 {name:'size', type: 'float'},
39217 {name:'lastmod', type:'date', dateFormat:'timestamp'}
39222 var tpl = new Ext.XTemplate(
39223 '<tpl for=".">',
39224 '<div class="thumb-wrap" id="{name}">',
39225 '<div class="thumb"><img src="{url}" title="{name}"></div>',
39226 '<span class="x-editable">{shortName}</span></div>',
39228 '<div class="x-clear"></div>'
39231 var panel = new Ext.Panel({
39238 title:'Simple DataView',
39240 items: new Ext.DataView({
39245 overClass:'x-view-over',
39246 itemSelector:'div.thumb-wrap',
39247 emptyText: 'No images to display'
39250 panel.render(document.body);
39253 * Create a new DataView
39254 * @param {Object} config The config object
39257 Ext.DataView = Ext.extend(Ext.BoxComponent, {
39259 * @cfg {String/Array} tpl
39260 * The HTML fragment or an array of fragments that will make up the template used by this DataView. This should
39261 * be specified in the same format expected by the constructor of {@link Ext.XTemplate}.
39264 * @cfg {Ext.data.Store} store
39265 * The {@link Ext.data.Store} to bind this DataView to.
39268 * @cfg {String} itemSelector
39269 * <b>This is a required setting</b>. A simple CSS selector (e.g. <tt>div.some-class</tt> or
39270 * <tt>span:first-child</tt>) that will be used to determine what nodes this DataView will be
39274 * @cfg {Boolean} multiSelect
39275 * True to allow selection of more than one item at a time, false to allow selection of only a single item
39276 * at a time or no selection at all, depending on the value of {@link #singleSelect} (defaults to false).
39279 * @cfg {Boolean} singleSelect
39280 * True to allow selection of exactly one item at a time, false to allow no selection at all (defaults to false).
39281 * Note that if {@link #multiSelect} = true, this value will be ignored.
39284 * @cfg {Boolean} simpleSelect
39285 * True to enable multiselection by clicking on multiple items without requiring the user to hold Shift or Ctrl,
39286 * false to force the user to hold Ctrl or Shift to select more than on item (defaults to false).
39289 * @cfg {String} overClass
39290 * A CSS class to apply to each item in the view on mouseover (defaults to undefined).
39293 * @cfg {String} loadingText
39294 * A string to display during data load operations (defaults to undefined). If specified, this text will be
39295 * displayed in a loading div and the view's contents will be cleared while loading, otherwise the view's
39296 * contents will continue to display normally until the new data is loaded and the contents are replaced.
39299 * @cfg {String} selectedClass
39300 * A CSS class to apply to each selected item in the view (defaults to 'x-view-selected').
39302 selectedClass : "x-view-selected",
39304 * @cfg {String} emptyText
39305 * The text to display in the view when there is no data to display (defaults to '').
39310 * @cfg {Boolean} deferEmptyText True to defer emptyText being applied until the store's first load
39312 deferEmptyText: true,
39314 * @cfg {Boolean} trackOver True to enable mouseenter and mouseleave events
39322 initComponent : function(){
39323 Ext.DataView.superclass.initComponent.call(this);
39324 if(Ext.isString(this.tpl) || Ext.isArray(this.tpl)){
39325 this.tpl = new Ext.XTemplate(this.tpl);
39330 * @event beforeclick
39331 * Fires before a click is processed. Returns false to cancel the default action.
39332 * @param {Ext.DataView} this
39333 * @param {Number} index The index of the target node
39334 * @param {HTMLElement} node The target node
39335 * @param {Ext.EventObject} e The raw event object
39340 * Fires when a template node is clicked.
39341 * @param {Ext.DataView} this
39342 * @param {Number} index The index of the target node
39343 * @param {HTMLElement} node The target node
39344 * @param {Ext.EventObject} e The raw event object
39348 * @event mouseenter
39349 * Fires when the mouse enters a template node. trackOver:true or an overCls must be set to enable this event.
39350 * @param {Ext.DataView} this
39351 * @param {Number} index The index of the target node
39352 * @param {HTMLElement} node The target node
39353 * @param {Ext.EventObject} e The raw event object
39357 * @event mouseleave
39358 * Fires when the mouse leaves a template node. trackOver:true or an overCls must be set to enable this event.
39359 * @param {Ext.DataView} this
39360 * @param {Number} index The index of the target node
39361 * @param {HTMLElement} node The target node
39362 * @param {Ext.EventObject} e The raw event object
39366 * @event containerclick
39367 * Fires when a click occurs and it is not on a template node.
39368 * @param {Ext.DataView} this
39369 * @param {Ext.EventObject} e The raw event object
39374 * Fires when a template node is double clicked.
39375 * @param {Ext.DataView} this
39376 * @param {Number} index The index of the target node
39377 * @param {HTMLElement} node The target node
39378 * @param {Ext.EventObject} e The raw event object
39382 * @event contextmenu
39383 * Fires when a template node is right clicked.
39384 * @param {Ext.DataView} this
39385 * @param {Number} index The index of the target node
39386 * @param {HTMLElement} node The target node
39387 * @param {Ext.EventObject} e The raw event object
39391 * @event containercontextmenu
39392 * Fires when a right click occurs that is not on a template node.
39393 * @param {Ext.DataView} this
39394 * @param {Ext.EventObject} e The raw event object
39396 "containercontextmenu",
39398 * @event selectionchange
39399 * Fires when the selected nodes change.
39400 * @param {Ext.DataView} this
39401 * @param {Array} selections Array of the selected nodes
39406 * @event beforeselect
39407 * Fires before a selection is made. If any handlers return false, the selection is cancelled.
39408 * @param {Ext.DataView} this
39409 * @param {HTMLElement} node The node to be selected
39410 * @param {Array} selections Array of currently selected nodes
39415 this.store = Ext.StoreMgr.lookup(this.store);
39416 this.all = new Ext.CompositeElementLite();
39417 this.selected = new Ext.CompositeElementLite();
39421 afterRender : function(){
39422 Ext.DataView.superclass.afterRender.call(this);
39424 this.mon(this.getTemplateTarget(), {
39425 "click": this.onClick,
39426 "dblclick": this.onDblClick,
39427 "contextmenu": this.onContextMenu,
39431 if(this.overClass || this.trackOver){
39432 this.mon(this.getTemplateTarget(), {
39433 "mouseover": this.onMouseOver,
39434 "mouseout": this.onMouseOut,
39440 this.bindStore(this.store, true);
39445 * Refreshes the view by reloading the data from the store and re-rendering the template.
39447 refresh : function(){
39448 this.clearSelections(false, true);
39449 var el = this.getTemplateTarget();
39451 var records = this.store.getRange();
39452 if(records.length < 1){
39453 if(!this.deferEmptyText || this.hasSkippedEmptyText){
39454 el.update(this.emptyText);
39458 this.tpl.overwrite(el, this.collectData(records, 0));
39459 this.all.fill(Ext.query(this.itemSelector, el.dom));
39460 this.updateIndexes(0);
39462 this.hasSkippedEmptyText = true;
39465 getTemplateTarget: function(){
39470 * Function which can be overridden to provide custom formatting for each Record that is used by this
39471 * DataView's {@link #tpl template} to render each node.
39472 * @param {Array/Object} data The raw data object that was used to create the Record.
39473 * @param {Number} recordIndex the index number of the Record being prepared for rendering.
39474 * @param {Record} record The Record being prepared for rendering.
39475 * @return {Array/Object} The formatted data in a format expected by the internal {@link #tpl template}'s overwrite() method.
39476 * (either an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'}))
39478 prepareData : function(data){
39483 * <p>Function which can be overridden which returns the data object passed to this
39484 * DataView's {@link #tpl template} to render the whole DataView.</p>
39485 * <p>This is usually an Array of data objects, each element of which is processed by an
39486 * {@link Ext.XTemplate XTemplate} which uses <tt>'<tpl for=".">'</tt> to iterate over its supplied
39487 * data object as an Array. However, <i>named</i> properties may be placed into the data object to
39488 * provide non-repeating data such as headings, totals etc.</p>
39489 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
39490 * @param {Number} startIndex the index number of the Record being prepared for rendering.
39491 * @return {Array} An Array of data objects to be processed by a repeating XTemplate. May also
39492 * contain <i>named</i> properties.
39494 collectData : function(records, startIndex){
39496 for(var i = 0, len = records.length; i < len; i++){
39497 r[r.length] = this.prepareData(records[i].data, startIndex+i, records[i]);
39503 bufferRender : function(records){
39504 var div = document.createElement('div');
39505 this.tpl.overwrite(div, this.collectData(records));
39506 return Ext.query(this.itemSelector, div);
39510 onUpdate : function(ds, record){
39511 var index = this.store.indexOf(record);
39513 var sel = this.isSelected(index);
39514 var original = this.all.elements[index];
39515 var node = this.bufferRender([record], index)[0];
39517 this.all.replaceElement(index, node, true);
39519 this.selected.replaceElement(original, node);
39520 this.all.item(index).addClass(this.selectedClass);
39522 this.updateIndexes(index, index);
39527 onAdd : function(ds, records, index){
39528 if(this.all.getCount() === 0){
39532 var nodes = this.bufferRender(records, index), n, a = this.all.elements;
39533 if(index < this.all.getCount()){
39534 n = this.all.item(index).insertSibling(nodes, 'before', true);
39535 a.splice.apply(a, [index, 0].concat(nodes));
39537 n = this.all.last().insertSibling(nodes, 'after', true);
39538 a.push.apply(a, nodes);
39540 this.updateIndexes(index);
39544 onRemove : function(ds, record, index){
39545 this.deselect(index);
39546 this.all.removeElement(index, true);
39547 this.updateIndexes(index);
39548 if (this.store.getCount() === 0){
39554 * Refreshes an individual node's data from the store.
39555 * @param {Number} index The item's data index in the store
39557 refreshNode : function(index){
39558 this.onUpdate(this.store, this.store.getAt(index));
39562 updateIndexes : function(startIndex, endIndex){
39563 var ns = this.all.elements;
39564 startIndex = startIndex || 0;
39565 endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
39566 for(var i = startIndex; i <= endIndex; i++){
39567 ns[i].viewIndex = i;
39572 * Returns the store associated with this DataView.
39573 * @return {Ext.data.Store} The store
39575 getStore : function(){
39580 * Changes the data store bound to this view and refreshes it.
39581 * @param {Store} store The store to bind to this view
39583 bindStore : function(store, initial){
39584 if(!initial && this.store){
39585 if(store !== this.store && this.store.autoDestroy){
39586 this.store.destroy();
39588 this.store.un("beforeload", this.onBeforeLoad, this);
39589 this.store.un("datachanged", this.refresh, this);
39590 this.store.un("add", this.onAdd, this);
39591 this.store.un("remove", this.onRemove, this);
39592 this.store.un("update", this.onUpdate, this);
39593 this.store.un("clear", this.refresh, this);
39600 store = Ext.StoreMgr.lookup(store);
39603 beforeload: this.onBeforeLoad,
39604 datachanged: this.refresh,
39606 remove: this.onRemove,
39607 update: this.onUpdate,
39608 clear: this.refresh
39611 this.store = store;
39618 * Returns the template node the passed child belongs to, or null if it doesn't belong to one.
39619 * @param {HTMLElement} node
39620 * @return {HTMLElement} The template node
39622 findItemFromChild : function(node){
39623 return Ext.fly(node).findParent(this.itemSelector, this.getTemplateTarget());
39627 onClick : function(e){
39628 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
39630 var index = this.indexOf(item);
39631 if(this.onItemClick(item, index, e) !== false){
39632 this.fireEvent("click", this, index, item, e);
39635 if(this.fireEvent("containerclick", this, e) !== false){
39636 this.onContainerClick(e);
39641 onContainerClick : function(e){
39642 this.clearSelections();
39646 onContextMenu : function(e){
39647 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
39649 this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
39651 this.fireEvent("containercontextmenu", this, e);
39656 onDblClick : function(e){
39657 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
39659 this.fireEvent("dblclick", this, this.indexOf(item), item, e);
39664 onMouseOver : function(e){
39665 var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
39666 if(item && item !== this.lastItem){
39667 this.lastItem = item;
39668 Ext.fly(item).addClass(this.overClass);
39669 this.fireEvent("mouseenter", this, this.indexOf(item), item, e);
39674 onMouseOut : function(e){
39676 if(!e.within(this.lastItem, true, true)){
39677 Ext.fly(this.lastItem).removeClass(this.overClass);
39678 this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);
39679 delete this.lastItem;
39685 onItemClick : function(item, index, e){
39686 if(this.fireEvent("beforeclick", this, index, item, e) === false){
39689 if(this.multiSelect){
39690 this.doMultiSelection(item, index, e);
39691 e.preventDefault();
39692 }else if(this.singleSelect){
39693 this.doSingleSelection(item, index, e);
39694 e.preventDefault();
39700 doSingleSelection : function(item, index, e){
39701 if(e.ctrlKey && this.isSelected(index)){
39702 this.deselect(index);
39704 this.select(index, false);
39709 doMultiSelection : function(item, index, e){
39710 if(e.shiftKey && this.last !== false){
39711 var last = this.last;
39712 this.selectRange(last, index, e.ctrlKey);
39713 this.last = last; // reset the last
39715 if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){
39716 this.deselect(index);
39718 this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);
39724 * Gets the number of selected nodes.
39725 * @return {Number} The node count
39727 getSelectionCount : function(){
39728 return this.selected.getCount();
39732 * Gets the currently selected nodes.
39733 * @return {Array} An array of HTMLElements
39735 getSelectedNodes : function(){
39736 return this.selected.elements;
39740 * Gets the indexes of the selected nodes.
39741 * @return {Array} An array of numeric indexes
39743 getSelectedIndexes : function(){
39744 var indexes = [], s = this.selected.elements;
39745 for(var i = 0, len = s.length; i < len; i++){
39746 indexes.push(s[i].viewIndex);
39752 * Gets an array of the selected records
39753 * @return {Array} An array of {@link Ext.data.Record} objects
39755 getSelectedRecords : function(){
39756 var r = [], s = this.selected.elements;
39757 for(var i = 0, len = s.length; i < len; i++){
39758 r[r.length] = this.store.getAt(s[i].viewIndex);
39764 * Gets an array of the records from an array of nodes
39765 * @param {Array} nodes The nodes to evaluate
39766 * @return {Array} records The {@link Ext.data.Record} objects
39768 getRecords : function(nodes){
39769 var r = [], s = nodes;
39770 for(var i = 0, len = s.length; i < len; i++){
39771 r[r.length] = this.store.getAt(s[i].viewIndex);
39777 * Gets a record from a node
39778 * @param {HTMLElement} node The node to evaluate
39779 * @return {Record} record The {@link Ext.data.Record} object
39781 getRecord : function(node){
39782 return this.store.getAt(node.viewIndex);
39786 * Clears all selections.
39787 * @param {Boolean} suppressEvent (optional) True to skip firing of the selectionchange event
39789 clearSelections : function(suppressEvent, skipUpdate){
39790 if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){
39792 this.selected.removeClass(this.selectedClass);
39794 this.selected.clear();
39796 if(!suppressEvent){
39797 this.fireEvent("selectionchange", this, this.selected.elements);
39803 * Returns true if the passed node is selected, else false.
39804 * @param {HTMLElement/Number} node The node or node index to check
39805 * @return {Boolean} True if selected, else false
39807 isSelected : function(node){
39808 return this.selected.contains(this.getNode(node));
39812 * Deselects a node.
39813 * @param {HTMLElement/Number} node The node to deselect
39815 deselect : function(node){
39816 if(this.isSelected(node)){
39817 node = this.getNode(node);
39818 this.selected.removeElement(node);
39819 if(this.last == node.viewIndex){
39822 Ext.fly(node).removeClass(this.selectedClass);
39823 this.fireEvent("selectionchange", this, this.selected.elements);
39828 * Selects a set of nodes.
39829 * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node,
39830 * id of a template node or an array of any of those to select
39831 * @param {Boolean} keepExisting (optional) true to keep existing selections
39832 * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
39834 select : function(nodeInfo, keepExisting, suppressEvent){
39835 if(Ext.isArray(nodeInfo)){
39837 this.clearSelections(true);
39839 for(var i = 0, len = nodeInfo.length; i < len; i++){
39840 this.select(nodeInfo[i], true, true);
39842 if(!suppressEvent){
39843 this.fireEvent("selectionchange", this, this.selected.elements);
39846 var node = this.getNode(nodeInfo);
39848 this.clearSelections(true);
39850 if(node && !this.isSelected(node)){
39851 if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){
39852 Ext.fly(node).addClass(this.selectedClass);
39853 this.selected.add(node);
39854 this.last = node.viewIndex;
39855 if(!suppressEvent){
39856 this.fireEvent("selectionchange", this, this.selected.elements);
39864 * Selects a range of nodes. All nodes between start and end are selected.
39865 * @param {Number} start The index of the first node in the range
39866 * @param {Number} end The index of the last node in the range
39867 * @param {Boolean} keepExisting (optional) True to retain existing selections
39869 selectRange : function(start, end, keepExisting){
39871 this.clearSelections(true);
39873 this.select(this.getNodes(start, end), true);
39877 * Gets a template node.
39878 * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
39879 * @return {HTMLElement} The node or null if it wasn't found
39881 getNode : function(nodeInfo){
39882 if(Ext.isString(nodeInfo)){
39883 return document.getElementById(nodeInfo);
39884 }else if(Ext.isNumber(nodeInfo)){
39885 return this.all.elements[nodeInfo];
39891 * Gets a range nodes.
39892 * @param {Number} start (optional) The index of the first node in the range
39893 * @param {Number} end (optional) The index of the last node in the range
39894 * @return {Array} An array of nodes
39896 getNodes : function(start, end){
39897 var ns = this.all.elements;
39898 start = start || 0;
39899 end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
39902 for(i = start; i <= end && ns[i]; i++){
39906 for(i = start; i >= end && ns[i]; i--){
39914 * Finds the index of the passed node.
39915 * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
39916 * @return {Number} The index of the node or -1
39918 indexOf : function(node){
39919 node = this.getNode(node);
39920 if(Ext.isNumber(node.viewIndex)){
39921 return node.viewIndex;
39923 return this.all.indexOf(node);
39927 onBeforeLoad : function(){
39928 if(this.loadingText){
39929 this.clearSelections(false, true);
39930 this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+'</div>');
39935 onDestroy : function(){
39936 Ext.DataView.superclass.onDestroy.call(this);
39937 this.bindStore(null);
39942 * Changes the data store bound to this view and refreshes it. (deprecated in favor of bindStore)
39943 * @param {Store} store The store to bind to this view
39945 Ext.DataView.prototype.setStore = Ext.DataView.prototype.bindStore;
39947 Ext.reg('dataview', Ext.DataView);/**
\r
39948 * @class Ext.ListView
\r
39949 * @extends Ext.DataView
\r
39950 * <p>Ext.ListView is a fast and light-weight implentation of a
\r
39951 * {@link Ext.grid.GridPanel Grid} like view with the following characteristics:</p>
\r
39952 * <div class="mdetail-params"><ul>
\r
39953 * <li>resizable columns</li>
\r
39954 * <li>selectable</li>
\r
39955 * <li>column widths are initially proportioned by percentage based on the container
\r
39956 * width and number of columns</li>
\r
39957 * <li>uses templates to render the data in any required format</li>
\r
39958 * <li>no horizontal scrolling</li>
\r
39959 * <li>no editing</li>
\r
39961 * <p>Example usage:</p>
\r
39963 // consume JSON of this form:
\r
39967 "name":"dance_fever.jpg",
\r
39969 "lastmod":1236974993000,
\r
39970 "url":"images\/thumbs\/dance_fever.jpg"
\r
39973 "name":"zack_sink.jpg",
\r
39975 "lastmod":1236974993000,
\r
39976 "url":"images\/thumbs\/zack_sink.jpg"
\r
39980 var store = new Ext.data.JsonStore({
\r
39981 url: 'get-images.php',
\r
39985 {name:'size', type: 'float'},
\r
39986 {name:'lastmod', type:'date', dateFormat:'timestamp'}
\r
39991 var listView = new Ext.ListView({
\r
39993 multiSelect: true,
\r
39994 emptyText: 'No images to display',
\r
39995 reserveScrollOffset: true,
\r
39999 dataIndex: 'name'
\r
40001 header: 'Last Modified',
\r
40003 dataIndex: 'lastmod',
\r
40004 tpl: '{lastmod:date("m-d h:i a")}'
\r
40007 dataIndex: 'size',
\r
40008 tpl: '{size:fileSize}', // format using Ext.util.Format.fileSize()
\r
40013 // put it in a Panel so it looks pretty
\r
40014 var panel = new Ext.Panel({
\r
40015 id:'images-view',
\r
40018 collapsible:true,
\r
40020 title:'Simple ListView <i>(0 items selected)</i>',
\r
40023 panel.render(document.body);
\r
40025 // little bit of feedback
\r
40026 listView.on('selectionchange', function(view, nodes){
\r
40027 var l = nodes.length;
\r
40028 var s = l != 1 ? 's' : '';
\r
40029 panel.setTitle('Simple ListView <i>('+l+' item'+s+' selected)</i>');
\r
40033 * @param {Object} config
\r
40034 * @xtype listview
\r
40036 Ext.ListView = Ext.extend(Ext.DataView, {
\r
40038 * Set this property to <tt>true</tt> to disable the header click handler disabling sort
\r
40039 * (defaults to <tt>false</tt>).
\r
40041 * @property disableHeaders
\r
40044 * @cfg {Boolean} hideHeaders
\r
40045 * <tt>true</tt> to hide the {@link #internalTpl header row} (defaults to <tt>false</tt> so
\r
40046 * the {@link #internalTpl header row} will be shown).
\r
40049 * @cfg {String} itemSelector
\r
40050 * Defaults to <tt>'dl'</tt> to work with the preconfigured <b><tt>{@link Ext.DataView#tpl tpl}</tt></b>.
\r
40051 * This setting specifies the CSS selector (e.g. <tt>div.some-class</tt> or <tt>span:first-child</tt>)
\r
40052 * that will be used to determine what nodes the ListView will be working with.
\r
40054 itemSelector: 'dl',
\r
40056 * @cfg {String} selectedClass The CSS class applied to a selected row (defaults to
\r
40057 * <tt>'x-list-selected'</tt>). An example overriding the default styling:
\r
40059 .x-list-selected {background-color: yellow;}
\r
40063 selectedClass:'x-list-selected',
\r
40065 * @cfg {String} overClass The CSS class applied when over a row (defaults to
\r
40066 * <tt>'x-list-over'</tt>). An example overriding the default styling:
\r
40068 .x-list-over {background-color: orange;}
\r
40072 overClass:'x-list-over',
\r
40074 * @cfg {Boolean} reserveScrollOffset
\r
40075 * By default will defer accounting for the configured <b><tt>{@link #scrollOffset}</tt></b>
\r
40076 * for 10 milliseconds. Specify <tt>true</tt> to account for the configured
\r
40077 * <b><tt>{@link #scrollOffset}</tt></b> immediately.
\r
40080 * @cfg {Number} scrollOffset The amount of space to reserve for the scrollbar (defaults to
\r
40081 * <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
\r
40084 scrollOffset : undefined,
\r
40086 * @cfg {Boolean/Object} columnResize
\r
40087 * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.ColumnResizer}
\r
40088 * to enable the columns to be resizable (defaults to <tt>true</tt>).
\r
40090 columnResize: true,
\r
40092 * @cfg {Array} columns An array of column configuration objects, for example:
\r
40096 dataIndex: 'size',
\r
40098 tpl: '{size:fileSize}',
\r
40102 * Acceptable properties for each column configuration object are:
\r
40103 * <div class="mdetail-params"><ul>
\r
40104 * <li><b><tt>align</tt></b> : String<div class="sub-desc">Set the CSS text-align property
\r
40105 * of the column. Defaults to <tt>'left'</tt>.</div></li>
\r
40106 * <li><b><tt>dataIndex</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
\r
40107 * {@link Ext.grid.Column#dataIndex dataIndex} for details.</div></li>
\r
40108 * <li><b><tt>header</tt></b> : String<div class="sub-desc">See {@link Ext.grid.Column}.
\r
40109 * {@link Ext.grid.Column#header header} for details.</div></li>
\r
40110 * <li><b><tt>tpl</tt></b> : String<div class="sub-desc">Specify a string to pass as the
\r
40111 * configuration string for {@link Ext.XTemplate}. By default an {@link Ext.XTemplate}
\r
40112 * will be implicitly created using the <tt>dataIndex</tt>.</div></li>
\r
40113 * <li><b><tt>width</tt></b> : Number<div class="sub-desc">Percentage of the container width
\r
40114 * this column should be allocated. Columns that have no width specified will be
\r
40115 * allocated with an equal percentage to fill 100% of the container width. To easily take
\r
40116 * advantage of the full container width, leave the width of at least one column undefined.
\r
40117 * Note that if you do not want to take up the full width of the container, the width of
\r
40118 * every column needs to be explicitly defined.</div></li>
\r
40122 * @cfg {Boolean/Object} columnSort
\r
40123 * Specify <tt>true</tt> or specify a configuration object for {@link Ext.ListView.Sorter}
\r
40124 * to enable the columns to be sortable (defaults to <tt>true</tt>).
\r
40126 columnSort: true,
\r
40128 * @cfg {String/Array} internalTpl
\r
40129 * The template to be used for the header row. See {@link #tpl} for more details.
\r
40133 * IE has issues when setting percentage based widths to 100%. Default to 99.
\r
40135 maxWidth: Ext.isIE ? 99 : 100,
\r
40137 initComponent : function(){
\r
40138 if(this.columnResize){
\r
40139 this.colResizer = new Ext.ListView.ColumnResizer(this.colResizer);
\r
40140 this.colResizer.init(this);
\r
40142 if(this.columnSort){
\r
40143 this.colSorter = new Ext.ListView.Sorter(this.columnSort);
\r
40144 this.colSorter.init(this);
\r
40146 if(!this.internalTpl){
\r
40147 this.internalTpl = new Ext.XTemplate(
\r
40148 '<div class="x-list-header"><div class="x-list-header-inner">',
\r
40149 '<tpl for="columns">',
\r
40150 '<div style="width:{width}%;text-align:{align};"><em unselectable="on" id="',this.id, '-xlhd-{#}">',
\r
40154 '<div class="x-clear"></div>',
\r
40156 '<div class="x-list-body"><div class="x-list-body-inner">',
\r
40161 this.tpl = new Ext.XTemplate(
\r
40162 '<tpl for="rows">',
\r
40164 '<tpl for="parent.columns">',
\r
40165 '<dt style="width:{width}%;text-align:{align};"><em unselectable="on">',
\r
40166 '{[values.tpl.apply(parent)]}',
\r
40169 '<div class="x-clear"></div>',
\r
40174 var cs = this.columns,
\r
40175 allocatedWidth = 0,
\r
40176 colsWithWidth = 0,
\r
40177 len = cs.length,
\r
40179 for(var i = 0; i < len; i++){
\r
40180 var c = Ext.apply({}, cs[i]);
\r
40182 c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}');
\r
40183 }else if(Ext.isString(c.tpl)){
\r
40184 c.tpl = new Ext.XTemplate(c.tpl);
\r
40186 c.align = c.align || 'left';
\r
40187 if(Ext.isNumber(c.width)){
\r
40189 allocatedWidth += c.width;
\r
40194 cs = this.columns = columns;
\r
40195 // auto calculate missing column widths
\r
40196 if(colsWithWidth < len){
\r
40197 var remaining = len - colsWithWidth;
\r
40198 if(allocatedWidth < this.maxWidth){
\r
40199 var perCol = ((this.maxWidth-allocatedWidth) / remaining);
\r
40200 for(var j = 0; j < len; j++){
\r
40202 if(!Ext.isNumber(c.width)){
\r
40203 c.width = perCol;
\r
40208 Ext.ListView.superclass.initComponent.call(this);
\r
40211 onRender : function(){
\r
40213 cls: 'x-list-wrap'
\r
40215 Ext.ListView.superclass.onRender.apply(this, arguments);
\r
40217 this.internalTpl.overwrite(this.el, {columns: this.columns});
\r
40219 this.innerBody = Ext.get(this.el.dom.childNodes[1].firstChild);
\r
40220 this.innerHd = Ext.get(this.el.dom.firstChild.firstChild);
\r
40222 if(this.hideHeaders){
\r
40223 this.el.dom.firstChild.style.display = 'none';
\r
40227 getTemplateTarget : function(){
\r
40228 return this.innerBody;
\r
40232 * <p>Function which can be overridden which returns the data object passed to this
\r
40233 * view's {@link #tpl template} to render the whole ListView. The returned object
\r
40234 * shall contain the following properties:</p>
\r
40235 * <div class="mdetail-params"><ul>
\r
40236 * <li><b>columns</b> : String<div class="sub-desc">See <tt>{@link #columns}</tt></div></li>
\r
40237 * <li><b>rows</b> : String<div class="sub-desc">See
\r
40238 * <tt>{@link Ext.DataView}.{@link Ext.DataView#collectData collectData}</div></li>
\r
40240 * @param {Array} records An Array of {@link Ext.data.Record}s to be rendered into the DataView.
\r
40241 * @param {Number} startIndex the index number of the Record being prepared for rendering.
\r
40242 * @return {Object} A data object containing properties to be processed by a repeating
\r
40243 * XTemplate as described above.
\r
40245 collectData : function(){
\r
40246 var rs = Ext.ListView.superclass.collectData.apply(this, arguments);
\r
40248 columns: this.columns,
\r
40253 verifyInternalSize : function(){
\r
40254 if(this.lastSize){
\r
40255 this.onResize(this.lastSize.width, this.lastSize.height);
\r
40260 onResize : function(w, h){
\r
40261 var bd = this.innerBody.dom;
\r
40262 var hd = this.innerHd.dom
\r
40266 var bdp = bd.parentNode;
\r
40267 if(Ext.isNumber(w)){
\r
40268 var sw = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
\r
40269 if(this.reserveScrollOffset || ((bdp.offsetWidth - bdp.clientWidth) > 10)){
\r
40270 bd.style.width = sw + 'px';
\r
40271 hd.style.width = sw + 'px';
\r
40273 bd.style.width = w + 'px';
\r
40274 hd.style.width = w + 'px';
\r
40275 setTimeout(function(){
\r
40276 if((bdp.offsetWidth - bdp.clientWidth) > 10){
\r
40277 bd.style.width = sw + 'px';
\r
40278 hd.style.width = sw + 'px';
\r
40283 if(Ext.isNumber(h)){
\r
40284 bdp.style.height = (h - hd.parentNode.offsetHeight) + 'px';
\r
40288 updateIndexes : function(){
\r
40289 Ext.ListView.superclass.updateIndexes.apply(this, arguments);
\r
40290 this.verifyInternalSize();
\r
40293 findHeaderIndex : function(hd){
\r
40294 hd = hd.dom || hd;
\r
40295 var pn = hd.parentNode, cs = pn.parentNode.childNodes;
\r
40296 for(var i = 0, c; c = cs[i]; i++){
\r
40304 setHdWidths : function(){
\r
40305 var els = this.innerHd.dom.getElementsByTagName('div');
\r
40306 for(var i = 0, cs = this.columns, len = cs.length; i < len; i++){
\r
40307 els[i].style.width = cs[i].width + '%';
\r
40312 Ext.reg('listview', Ext.ListView);/**
\r
40313 * @class Ext.ListView.ColumnResizer
\r
40314 * @extends Ext.util.Observable
\r
40315 * <p>Supporting Class for Ext.ListView.</p>
\r
40317 * @param {Object} config
\r
40319 Ext.ListView.ColumnResizer = Ext.extend(Ext.util.Observable, {
\r
40321 * @cfg {Number} minPct The minimum percentage to allot for any column (defaults to <tt>.05</tt>)
\r
40325 constructor: function(config){
\r
40326 Ext.apply(this, config);
\r
40327 Ext.ListView.ColumnResizer.superclass.constructor.call(this);
\r
40329 init : function(listView){
\r
40330 this.view = listView;
\r
40331 listView.on('render', this.initEvents, this);
\r
40334 initEvents : function(view){
\r
40335 view.mon(view.innerHd, 'mousemove', this.handleHdMove, this);
\r
40336 this.tracker = new Ext.dd.DragTracker({
\r
40337 onBeforeStart: this.onBeforeStart.createDelegate(this),
\r
40338 onStart: this.onStart.createDelegate(this),
\r
40339 onDrag: this.onDrag.createDelegate(this),
\r
40340 onEnd: this.onEnd.createDelegate(this),
\r
40344 this.tracker.initEl(view.innerHd);
\r
40345 view.on('beforedestroy', this.tracker.destroy, this.tracker);
\r
40348 handleHdMove : function(e, t){
\r
40350 x = e.getPageX(),
\r
40351 hd = e.getTarget('em', 3, true);
\r
40353 var r = hd.getRegion(),
\r
40354 ss = hd.dom.style,
\r
40355 pn = hd.dom.parentNode;
\r
40357 if(x - r.left <= hw && pn != pn.parentNode.firstChild){
\r
40358 this.activeHd = Ext.get(pn.previousSibling.firstChild);
\r
40359 ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
\r
40360 } else if(r.right - x <= hw && pn != pn.parentNode.lastChild.previousSibling){
\r
40361 this.activeHd = hd;
\r
40362 ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
\r
40364 delete this.activeHd;
\r
40370 onBeforeStart : function(e){
\r
40371 this.dragHd = this.activeHd;
\r
40372 return !!this.dragHd;
\r
40375 onStart: function(e){
\r
40376 this.view.disableHeaders = true;
\r
40377 this.proxy = this.view.el.createChild({cls:'x-list-resizer'});
\r
40378 this.proxy.setHeight(this.view.el.getHeight());
\r
40380 var x = this.tracker.getXY()[0],
\r
40381 w = this.view.innerHd.getWidth();
\r
40383 this.hdX = this.dragHd.getX();
\r
40384 this.hdIndex = this.view.findHeaderIndex(this.dragHd);
\r
40386 this.proxy.setX(this.hdX);
\r
40387 this.proxy.setWidth(x-this.hdX);
\r
40389 this.minWidth = w*this.minPct;
\r
40390 this.maxWidth = w - (this.minWidth*(this.view.columns.length-1-this.hdIndex));
\r
40393 onDrag: function(e){
\r
40394 var cursorX = this.tracker.getXY()[0];
\r
40395 this.proxy.setWidth((cursorX-this.hdX).constrain(this.minWidth, this.maxWidth));
\r
40398 onEnd: function(e){
\r
40399 var nw = this.proxy.getWidth();
\r
40400 this.proxy.remove();
\r
40402 var index = this.hdIndex,
\r
40404 cs = vw.columns,
\r
40406 w = this.view.innerHd.getWidth(),
\r
40407 minPct = this.minPct * 100;
\r
40408 pct = Math.ceil((nw * vw.maxWidth) / w),
\r
40409 diff = cs[index].width - pct,
\r
40410 each = Math.floor(diff / (len-1-index)),
\r
40411 mod = diff - (each * (len-1-index));
\r
40413 for(var i = index+1; i < len; i++){
\r
40414 var cw = cs[i].width + each,
\r
40415 ncw = Math.max(minPct, cw);
\r
40419 cs[i].width = ncw;
\r
40421 cs[index].width = pct;
\r
40422 cs[index+1].width += mod;
\r
40423 delete this.dragHd;
\r
40424 vw.setHdWidths();
\r
40426 setTimeout(function(){
\r
40427 vw.disableHeaders = false;
\r
40431 * @class Ext.ListView.Sorter
\r
40432 * @extends Ext.util.Observable
\r
40433 * <p>Supporting Class for Ext.ListView.</p>
\r
40435 * @param {Object} config
\r
40437 Ext.ListView.Sorter = Ext.extend(Ext.util.Observable, {
\r
40439 * @cfg {Array} sortClasses
\r
40440 * The CSS classes applied to a header when it is sorted. (defaults to <tt>["sort-asc", "sort-desc"]</tt>)
\r
40442 sortClasses : ["sort-asc", "sort-desc"],
\r
40444 constructor: function(config){
\r
40445 Ext.apply(this, config);
\r
40446 Ext.ListView.Sorter.superclass.constructor.call(this);
\r
40449 init : function(listView){
\r
40450 this.view = listView;
\r
40451 listView.on('render', this.initEvents, this);
\r
40454 initEvents : function(view){
\r
40455 view.mon(view.innerHd, 'click', this.onHdClick, this);
\r
40456 view.innerHd.setStyle('cursor', 'pointer');
\r
40457 view.mon(view.store, 'datachanged', this.updateSortState, this);
\r
40458 this.updateSortState.defer(10, this, [view.store]);
\r
40461 updateSortState : function(store){
\r
40462 var state = store.getSortState();
\r
40466 this.sortState = state;
\r
40467 var cs = this.view.columns, sortColumn = -1;
\r
40468 for(var i = 0, len = cs.length; i < len; i++){
\r
40469 if(cs[i].dataIndex == state.field){
\r
40474 if(sortColumn != -1){
\r
40475 var sortDir = state.direction;
\r
40476 this.updateSortIcon(sortColumn, sortDir);
\r
40480 updateSortIcon : function(col, dir){
\r
40481 var sc = this.sortClasses;
\r
40482 var hds = this.view.innerHd.select('em').removeClass(sc);
\r
40483 hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
\r
40486 onHdClick : function(e){
\r
40487 var hd = e.getTarget('em', 3);
\r
40488 if(hd && !this.view.disableHeaders){
\r
40489 var index = this.view.findHeaderIndex(hd);
\r
40490 this.view.store.sort(this.view.columns[index].dataIndex);
\r
40494 * @class Ext.TabPanel
40495 * <p>A basic tab container. TabPanels can be used exactly like a standard {@link Ext.Panel}
40496 * for layout purposes, but also have special support for containing child Components
40497 * (<tt>{@link Ext.Container#items items}</tt>) that are managed using a
40498 * {@link Ext.layout.CardLayout CardLayout layout manager}, and displayed as separate tabs.</p>
40500 * <b>Note:</b> By default, a tab's close tool <i>destroys</i> the child tab Component
40501 * and all its descendants. This makes the child tab Component, and all its descendants <b>unusable</b>. To enable
40502 * re-use of a tab, configure the TabPanel with <b><code>{@link #autoDestroy autoDestroy: false}</code></b>.
40504 * <p><b><u>TabPanel header/footer elements</u></b></p>
40505 * <p>TabPanels use their {@link Ext.Panel#header header} or {@link Ext.Panel#footer footer} element
40506 * (depending on the {@link #tabPosition} configuration) to accommodate the tab selector buttons.
40507 * This means that a TabPanel will not display any configured title, and will not display any
40508 * configured header {@link Ext.Panel#tools tools}.</p>
40509 * <p>To display a header, embed the TabPanel in a {@link Ext.Panel Panel} which uses
40510 * <b><tt>{@link Ext.Container#layout layout:'fit'}</tt></b>.</p>
40512 * <p><b><u>Tab Events</u></b></p>
40513 * <p>There is no actual tab class — each tab is simply a {@link Ext.BoxComponent Component}
40514 * such as a {@link Ext.Panel Panel}. However, when rendered in a TabPanel, each child Component
40515 * can fire additional events that only exist for tabs and are not available from other Components.
40516 * These events are:</p>
40517 * <div><ul class="mdetail-params">
40518 * <li><tt><b>{@link Ext.Panel#activate activate}</b></tt> : Fires when this Component becomes
40519 * the active tab.</li>
40520 * <li><tt><b>{@link Ext.Panel#deactivate deactivate}</b></tt> : Fires when the Component that
40521 * was the active tab becomes deactivated.</li>
40523 * <p><b><u>Creating TabPanels from Code</u></b></p>
40524 * <p>TabPanels can be created and rendered completely in code, as in this example:</p>
40526 var tabs = new Ext.TabPanel({
40527 renderTo: Ext.getBody(),
40531 html: 'A simple tab'
40534 html: 'Another one'
40538 * <p><b><u>Creating TabPanels from Existing Markup</u></b></p>
40539 * <p>TabPanels can also be rendered from pre-existing markup in a couple of ways.</p>
40540 * <div><ul class="mdetail-params">
40542 * <li>Pre-Structured Markup</li>
40543 * <div class="sub-desc">
40544 * <p>A container div with one or more nested tab divs with class <tt>'x-tab'</tt> can be rendered entirely
40545 * from existing markup (See the {@link #autoTabs} example).</p>
40548 * <li>Un-Structured Markup</li>
40549 * <div class="sub-desc">
40550 * <p>A TabPanel can also be rendered from markup that is not strictly structured by simply specifying by id
40551 * which elements should be the container and the tabs. Using this method tab content can be pulled from different
40552 * elements within the page by id regardless of page structure. For example:</p>
40554 var tabs = new Ext.TabPanel({
40555 renderTo: 'my-tabs',
40558 {contentEl:'tab1', title:'Tab 1'},
40559 {contentEl:'tab2', title:'Tab 2'}
40563 // Note that the tabs do not have to be nested within the container (although they can be)
40564 <div id="my-tabs"></div>
40565 <div id="tab1" class="x-hide-display">A simple tab</div>
40566 <div id="tab2" class="x-hide-display">Another one</div>
40568 * Note that the tab divs in this example contain the class <tt>'x-hide-display'</tt> so that they can be rendered
40569 * deferred without displaying outside the tabs. You could alternately set <tt>{@link #deferredRender} = false </tt>
40570 * to render all content tabs on page load.
40575 * @extends Ext.Panel
40577 * @param {Object} config The configuration options
40580 Ext.TabPanel = Ext.extend(Ext.Panel, {
40582 * @cfg {Boolean} layoutOnTabChange
40583 * Set to true to force a layout of the active tab when the tab is changed. Defaults to false.
40584 * See {@link Ext.layout.CardLayout}.<code>{@link Ext.layout.CardLayout#layoutOnCardChange layoutOnCardChange}</code>.
40587 * @cfg {String} tabCls <b>This config option is used on <u>child Components</u> of ths TabPanel.</b> A CSS
40588 * class name applied to the tab strip item representing the child Component, allowing special
40589 * styling to be applied.
40592 * @cfg {Boolean} monitorResize True to automatically monitor window resize events and rerender the layout on
40593 * browser resize (defaults to true).
40595 monitorResize : true,
40597 * @cfg {Boolean} deferredRender
40598 * <p><tt>true</tt> by default to defer the rendering of child <tt>{@link Ext.Container#items items}</tt>
40599 * to the browsers DOM until a tab is activated. <tt>false</tt> will render all contained
40600 * <tt>{@link Ext.Container#items items}</tt> as soon as the {@link Ext.layout.CardLayout layout}
40601 * is rendered. If there is a significant amount of content or a lot of heavy controls being
40602 * rendered into panels that are not displayed by default, setting this to <tt>true</tt> might
40603 * improve performance.</p>
40604 * <br><p>The <tt>deferredRender</tt> property is internally passed to the layout manager for
40605 * TabPanels ({@link Ext.layout.CardLayout}) as its {@link Ext.layout.CardLayout#deferredRender}
40606 * configuration value.</p>
40607 * <br><p><b>Note</b>: leaving <tt>deferredRender</tt> as <tt>true</tt> means that the content
40608 * within an unactivated tab will not be available. For example, this means that if the TabPanel
40609 * is within a {@link Ext.form.FormPanel form}, then until a tab is activated, any Fields within
40610 * unactivated tabs will not be rendered, and will therefore not be submitted and will not be
40611 * available to either {@link Ext.form.BasicForm#getValues getValues} or
40612 * {@link Ext.form.BasicForm#setValues setValues}.</p>
40614 deferredRender : true,
40616 * @cfg {Number} tabWidth The initial width in pixels of each new tab (defaults to 120).
40620 * @cfg {Number} minTabWidth The minimum width in pixels for each tab when {@link #resizeTabs} = true (defaults to 30).
40624 * @cfg {Boolean} resizeTabs True to automatically resize each tab so that the tabs will completely fill the
40625 * tab strip (defaults to false). Setting this to true may cause specific widths that might be set per tab to
40626 * be overridden in order to fit them all into view (although {@link #minTabWidth} will always be honored).
40628 resizeTabs : false,
40630 * @cfg {Boolean} enableTabScroll True to enable scrolling to tabs that may be invisible due to overflowing the
40631 * overall TabPanel width. Only available with tabPosition:'top' (defaults to false).
40633 enableTabScroll : false,
40635 * @cfg {Number} scrollIncrement The number of pixels to scroll each time a tab scroll button is pressed
40636 * (defaults to <tt>100</tt>, or if <tt>{@link #resizeTabs} = true</tt>, the calculated tab width). Only
40637 * applies when <tt>{@link #enableTabScroll} = true</tt>.
40639 scrollIncrement : 0,
40641 * @cfg {Number} scrollRepeatInterval Number of milliseconds between each scroll while a tab scroll button is
40642 * continuously pressed (defaults to <tt>400</tt>).
40644 scrollRepeatInterval : 400,
40646 * @cfg {Float} scrollDuration The number of milliseconds that each scroll animation should last (defaults
40647 * to <tt>.35</tt>). Only applies when <tt>{@link #animScroll} = true</tt>.
40649 scrollDuration : 0.35,
40651 * @cfg {Boolean} animScroll True to animate tab scrolling so that hidden tabs slide smoothly into view (defaults
40652 * to <tt>true</tt>). Only applies when <tt>{@link #enableTabScroll} = true</tt>.
40656 * @cfg {String} tabPosition The position where the tab strip should be rendered (defaults to <tt>'top'</tt>).
40657 * The only other supported value is <tt>'bottom'</tt>. <b>Note</b>: tab scrolling is only supported for
40658 * <tt>tabPosition: 'top'</tt>.
40660 tabPosition : 'top',
40662 * @cfg {String} baseCls The base CSS class applied to the panel (defaults to <tt>'x-tab-panel'</tt>).
40664 baseCls : 'x-tab-panel',
40666 * @cfg {Boolean} autoTabs
40667 * <p><tt>true</tt> to query the DOM for any divs with a class of 'x-tab' to be automatically converted
40668 * to tabs and added to this panel (defaults to <tt>false</tt>). Note that the query will be executed within
40669 * the scope of the container element only (so that multiple tab panels from markup can be supported via this
40671 * <p>This method is only possible when the markup is structured correctly as a container with nested divs
40672 * containing the class <tt>'x-tab'</tt>. To create TabPanels without these limitations, or to pull tab content
40673 * from other elements on the page, see the example at the top of the class for generating tabs from markup.</p>
40674 * <p>There are a couple of things to note when using this method:<ul>
40675 * <li>When using the <tt>autoTabs</tt> config (as opposed to passing individual tab configs in the TabPanel's
40676 * {@link #items} collection), you must use <tt>{@link #applyTo}</tt> to correctly use the specified <tt>id</tt>
40677 * as the tab container. The <tt>autoTabs</tt> method <em>replaces</em> existing content with the TabPanel
40679 * <li>Make sure that you set <tt>{@link #deferredRender}: false</tt> so that the content elements for each
40680 * tab will be rendered into the TabPanel immediately upon page load, otherwise they will not be transformed
40681 * until each tab is activated and will be visible outside the TabPanel.</li>
40682 * </ul>Example usage:</p>
40684 var tabs = new Ext.TabPanel({
40685 applyTo: 'my-tabs',
40687 deferredRender: false,
40691 // This markup will be converted to a TabPanel from the code above
40692 <div id="my-tabs">
40693 <div class="x-tab" title="Tab 1">A simple tab</div>
40694 <div class="x-tab" title="Tab 2">Another one</div>
40700 * @cfg {String} autoTabSelector The CSS selector used to search for tabs in existing markup when
40701 * <tt>{@link #autoTabs} = true</tt> (defaults to <tt>'div.x-tab'</tt>). This can be any valid selector
40702 * supported by {@link Ext.DomQuery#select}. Note that the query will be executed within the scope of this
40703 * tab panel only (so that multiple tab panels from markup can be supported on a page).
40705 autoTabSelector : 'div.x-tab',
40707 * @cfg {String/Number} activeTab A string id or the numeric index of the tab that should be initially
40708 * activated on render (defaults to none).
40712 * @cfg {Number} tabMargin The number of pixels of space to calculate into the sizing and scrolling of
40713 * tabs. If you change the margin in CSS, you will need to update this value so calculations are correct
40714 * with either <tt>{@link #resizeTabs}</tt> or scrolling tabs. (defaults to <tt>2</tt>)
40718 * @cfg {Boolean} plain </tt>true</tt> to render the tab strip without a background container image
40719 * (defaults to <tt>false</tt>).
40723 * @cfg {Number} wheelIncrement For scrolling tabs, the number of pixels to increment on mouse wheel
40724 * scrolling (defaults to <tt>20</tt>).
40726 wheelIncrement : 20,
40729 * This is a protected property used when concatenating tab ids to the TabPanel id for internal uniqueness.
40730 * It does not generally need to be changed, but can be if external code also uses an id scheme that can
40731 * potentially clash with this one.
40733 idDelimiter : '__',
40736 itemCls : 'x-tab-item',
40738 // private config overrides
40740 headerAsText : false,
40745 initComponent : function(){
40746 this.frame = false;
40747 Ext.TabPanel.superclass.initComponent.call(this);
40750 * @event beforetabchange
40751 * Fires before the active tab changes. Handlers can <tt>return false</tt> to cancel the tab change.
40752 * @param {TabPanel} this
40753 * @param {Panel} newTab The tab being activated
40754 * @param {Panel} currentTab The current active tab
40759 * Fires after the active tab has changed.
40760 * @param {TabPanel} this
40761 * @param {Panel} tab The new active tab
40765 * @event contextmenu
40766 * Relays the contextmenu event from a tab selector element in the tab strip.
40767 * @param {TabPanel} this
40768 * @param {Panel} tab The target tab
40769 * @param {EventObject} e
40774 * @cfg {Object} layoutConfig
40775 * TabPanel implicitly uses {@link Ext.layout.CardLayout} as its layout manager.
40776 * <code>layoutConfig</code> may be used to configure this layout manager.
40777 * <code>{@link #deferredRender}</code> and <code>{@link #layoutOnTabChange}</code>
40778 * configured on the TabPanel will be applied as configs to the layout manager.
40780 this.setLayout(new Ext.layout.CardLayout(Ext.apply({
40781 layoutOnCardChange: this.layoutOnTabChange,
40782 deferredRender: this.deferredRender
40783 }, this.layoutConfig)));
40785 if(this.tabPosition == 'top'){
40786 this.elements += ',header';
40787 this.stripTarget = 'header';
40789 this.elements += ',footer';
40790 this.stripTarget = 'footer';
40793 this.stack = Ext.TabPanel.AccessStack();
40799 onRender : function(ct, position){
40800 Ext.TabPanel.superclass.onRender.call(this, ct, position);
40803 var pos = this.tabPosition == 'top' ? 'header' : 'footer';
40804 this[pos].addClass('x-tab-panel-'+pos+'-plain');
40807 var st = this[this.stripTarget];
40809 this.stripWrap = st.createChild({cls:'x-tab-strip-wrap', cn:{
40810 tag:'ul', cls:'x-tab-strip x-tab-strip-'+this.tabPosition}});
40812 var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
40813 this.stripSpacer = st.createChild({cls:'x-tab-strip-spacer'}, beforeEl);
40814 this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
40816 this.edge = this.strip.createChild({tag:'li', cls:'x-tab-edge'});
40817 this.strip.createChild({cls:'x-clear'});
40819 this.body.addClass('x-tab-panel-body-'+this.tabPosition);
40822 * @cfg {Template/XTemplate} itemTpl <p>(Optional) A {@link Ext.Template Template} or
40823 * {@link Ext.XTemplate XTemplate} which may be provided to process the data object returned from
40824 * <tt>{@link #getTemplateArgs}</tt> to produce a clickable selector element in the tab strip.</p>
40825 * <p>The main element created should be a <tt><li></tt> element. In order for a click event on
40826 * a selector element to be connected to its item, it must take its <i>id</i> from the TabPanel's
40827 * native <tt>{@link #getTemplateArgs}</tt>.</p>
40828 * <p>The child element which contains the title text must be marked by the CSS class
40829 * <tt>x-tab-strip-inner</tt>.</p>
40830 * <p>To enable closability, the created element should contain an element marked by the CSS class
40831 * <tt>x-tab-strip-close</tt>.</p>
40832 * <p>If a custom <tt>itemTpl</tt> is supplied, it is the developer's responsibility to create CSS
40833 * style rules to create the desired appearance.</p>
40834 * Below is an example of how to create customized tab selector items:<pre><code>
40836 renderTo: document.body,
40839 enableTabScroll: true,
40842 defaults: {autoScroll:true},
40843 itemTpl: new Ext.XTemplate(
40844 '<li class="{cls}" id="{id}" style="overflow:hidden">',
40845 '<tpl if="closable">',
40846 '<a class="x-tab-strip-close" onclick="return false;"></a>',
40848 '<a class="x-tab-right" href="#" onclick="return false;" style="padding-left:6px">',
40849 '<em class="x-tab-left">',
40850 '<span class="x-tab-strip-inner">',
40851 '<img src="{src}" style="float:left;margin:3px 3px 0 0">',
40852 '<span style="margin-left:20px" class="x-tab-strip-text {iconCls}">{text} {extra}</span>',
40858 getTemplateArgs: function(item) {
40859 // Call the native method to collect the base data. Like the ID!
40860 var result = Ext.TabPanel.prototype.getTemplateArgs.call(this, item);
40862 // Add stuff used in our template
40863 return Ext.apply(result, {
40864 closable: item.closable,
40866 extra: item.extraText || ''
40870 title: 'New Tab 1',
40871 iconSrc: '../shared/icons/fam/grid.png',
40872 html: 'Tab Body 1',
40875 title: 'New Tab 2',
40876 iconSrc: '../shared/icons/fam/grid.png',
40877 html: 'Tab Body 2',
40878 extraText: 'Extra stuff in the tab button'
40884 var tt = new Ext.Template(
40885 '<li class="{cls}" id="{id}"><a class="x-tab-strip-close" onclick="return false;"></a>',
40886 '<a class="x-tab-right" href="#" onclick="return false;"><em class="x-tab-left">',
40887 '<span class="x-tab-strip-inner"><span class="x-tab-strip-text {iconCls}">{text}</span></span>',
40890 tt.disableFormats = true;
40892 Ext.TabPanel.prototype.itemTpl = tt;
40895 this.items.each(this.initTab, this);
40899 afterRender : function(){
40900 Ext.TabPanel.superclass.afterRender.call(this);
40902 this.readTabs(false);
40904 if(this.activeTab !== undefined){
40905 var item = Ext.isObject(this.activeTab) ? this.activeTab : this.items.get(this.activeTab);
40906 delete this.activeTab;
40907 this.setActiveTab(item);
40912 initEvents : function(){
40913 Ext.TabPanel.superclass.initEvents.call(this);
40914 this.mon(this.strip, {
40916 mousedown: this.onStripMouseDown,
40917 contextmenu: this.onStripContextMenu
40919 if(this.enableTabScroll){
40920 this.mon(this.strip, 'mousewheel', this.onWheel, this);
40925 findTargets : function(e){
40927 var itemEl = e.getTarget('li', this.strip);
40929 item = this.getComponent(itemEl.id.split(this.idDelimiter)[1]);
40939 close : e.getTarget('.x-tab-strip-close', this.strip),
40946 onStripMouseDown : function(e){
40947 if(e.button !== 0){
40950 e.preventDefault();
40951 var t = this.findTargets(e);
40953 if (t.item.fireEvent('beforeclose', t.item) !== false) {
40954 t.item.fireEvent('close', t.item);
40955 delete t.item.tabEl;
40956 this.remove(t.item);
40960 if(t.item && t.item != this.activeTab){
40961 this.setActiveTab(t.item);
40966 onStripContextMenu : function(e){
40967 e.preventDefault();
40968 var t = this.findTargets(e);
40970 this.fireEvent('contextmenu', this, t.item, e);
40975 * True to scan the markup in this tab panel for <tt>{@link #autoTabs}</tt> using the
40976 * <tt>{@link #autoTabSelector}</tt>
40977 * @param {Boolean} removeExisting True to remove existing tabs
40979 readTabs : function(removeExisting){
40980 if(removeExisting === true){
40981 this.items.each(function(item){
40985 var tabs = this.el.query(this.autoTabSelector);
40986 for(var i = 0, len = tabs.length; i < len; i++){
40988 var title = tab.getAttribute('title');
40989 tab.removeAttribute('title');
40998 initTab : function(item, index){
40999 var before = this.strip.dom.childNodes[index];
41000 var p = this.getTemplateArgs(item);
41002 this.itemTpl.insertBefore(before, p) :
41003 this.itemTpl.append(this.strip, p);
41005 Ext.fly(el).addClassOnOver('x-tab-strip-over');
41008 Ext.fly(el).child('span.x-tab-strip-text', true).qtip = item.tabTip;
41012 item.on('disable', this.onItemDisabled, this);
41013 item.on('enable', this.onItemEnabled, this);
41014 item.on('titlechange', this.onItemTitleChanged, this);
41015 item.on('iconchange', this.onItemIconChanged, this);
41016 item.on('beforeshow', this.onBeforeShowItem, this);
41020 * <p>Provides template arguments for rendering a tab selector item in the tab strip.</p>
41021 * <p>This method returns an object hash containing properties used by the TabPanel's <tt>{@link #itemTpl}</tt>
41022 * to create a formatted, clickable tab selector element. The properties which must be returned
41023 * are:</p><div class="mdetail-params"><ul>
41024 * <li><b>id</b> : String<div class="sub-desc">A unique identifier which links to the item</div></li>
41025 * <li><b>text</b> : String<div class="sub-desc">The text to display</div></li>
41026 * <li><b>cls</b> : String<div class="sub-desc">The CSS class name</div></li>
41027 * <li><b>iconCls</b> : String<div class="sub-desc">A CSS class to provide appearance for an icon.</div></li>
41029 * @param {BoxComponent} item The {@link Ext.BoxComponent BoxComponent} for which to create a selector element in the tab strip.
41030 * @return {Object} An object hash containing the properties required to render the selector element.
41032 getTemplateArgs : function(item) {
41033 var cls = item.closable ? 'x-tab-strip-closable' : '';
41035 cls += ' x-item-disabled';
41038 cls += ' x-tab-with-icon';
41041 cls += ' ' + item.tabCls;
41045 id: this.id + this.idDelimiter + item.getItemId(),
41048 iconCls: item.iconCls || ''
41053 onAdd : function(c){
41054 Ext.TabPanel.superclass.onAdd.call(this, c);
41056 var items = this.items;
41057 this.initTab(c, items.indexOf(c));
41058 if(items.getCount() == 1){
41061 this.delegateUpdates();
41066 onBeforeAdd : function(item){
41067 var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);
41069 this.setActiveTab(item);
41072 Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);
41073 var es = item.elements;
41074 item.elements = es ? es.replace(',header', '') : es;
41075 item.border = (item.border === true);
41079 onRemove : function(c){
41080 Ext.TabPanel.superclass.onRemove.call(this, c);
41081 Ext.destroy(Ext.get(this.getTabEl(c)));
41082 this.stack.remove(c);
41083 c.un('disable', this.onItemDisabled, this);
41084 c.un('enable', this.onItemEnabled, this);
41085 c.un('titlechange', this.onItemTitleChanged, this);
41086 c.un('iconchange', this.onItemIconChanged, this);
41087 c.un('beforeshow', this.onBeforeShowItem, this);
41088 if(c == this.activeTab){
41089 var next = this.stack.next();
41091 this.setActiveTab(next);
41092 }else if(this.items.getCount() > 0){
41093 this.setActiveTab(0);
41095 this.activeTab = null;
41098 this.delegateUpdates();
41102 onBeforeShowItem : function(item){
41103 if(item != this.activeTab){
41104 this.setActiveTab(item);
41110 onItemDisabled : function(item){
41111 var el = this.getTabEl(item);
41113 Ext.fly(el).addClass('x-item-disabled');
41115 this.stack.remove(item);
41119 onItemEnabled : function(item){
41120 var el = this.getTabEl(item);
41122 Ext.fly(el).removeClass('x-item-disabled');
41127 onItemTitleChanged : function(item){
41128 var el = this.getTabEl(item);
41130 Ext.fly(el).child('span.x-tab-strip-text', true).innerHTML = item.title;
41135 onItemIconChanged : function(item, iconCls, oldCls){
41136 var el = this.getTabEl(item);
41139 el.child('span.x-tab-strip-text').replaceClass(oldCls, iconCls);
41140 el[Ext.isEmpty(iconCls) ? 'removeClass' : 'addClass']('x-tab-with-icon');
41145 * Gets the DOM element for the tab strip item which activates the child panel with the specified
41146 * ID. Access this to change the visual treatment of the item, for example by changing the CSS class name.
41147 * @param {Panel/Number/String} tab The tab component, or the tab's index, or the tabs id or itemId.
41148 * @return {HTMLElement} The DOM node
41150 getTabEl : function(item){
41151 return document.getElementById(this.id + this.idDelimiter + this.getComponent(item).getItemId());
41155 onResize : function(){
41156 Ext.TabPanel.superclass.onResize.apply(this, arguments);
41157 this.delegateUpdates();
41161 * Suspends any internal calculations or scrolling while doing a bulk operation. See {@link #endUpdate}
41163 beginUpdate : function(){
41164 this.suspendUpdates = true;
41168 * Resumes calculations and scrolling at the end of a bulk operation. See {@link #beginUpdate}
41170 endUpdate : function(){
41171 this.suspendUpdates = false;
41172 this.delegateUpdates();
41176 * Hides the tab strip item for the passed tab
41177 * @param {Number/String/Panel} item The tab index, id or item
41179 hideTabStripItem : function(item){
41180 item = this.getComponent(item);
41181 var el = this.getTabEl(item);
41183 el.style.display = 'none';
41184 this.delegateUpdates();
41186 this.stack.remove(item);
41190 * Unhides the tab strip item for the passed tab
41191 * @param {Number/String/Panel} item The tab index, id or item
41193 unhideTabStripItem : function(item){
41194 item = this.getComponent(item);
41195 var el = this.getTabEl(item);
41197 el.style.display = '';
41198 this.delegateUpdates();
41203 delegateUpdates : function(){
41204 if(this.suspendUpdates){
41207 if(this.resizeTabs && this.rendered){
41208 this.autoSizeTabs();
41210 if(this.enableTabScroll && this.rendered){
41211 this.autoScrollTabs();
41216 autoSizeTabs : function(){
41217 var count = this.items.length;
41218 var ce = this.tabPosition != 'bottom' ? 'header' : 'footer';
41219 var ow = this[ce].dom.offsetWidth;
41220 var aw = this[ce].dom.clientWidth;
41222 if(!this.resizeTabs || count < 1 || !aw){ // !aw for display:none
41226 var each = Math.max(Math.min(Math.floor((aw-4) / count) - this.tabMargin, this.tabWidth), this.minTabWidth); // -4 for float errors in IE
41227 this.lastTabWidth = each;
41228 var lis = this.strip.query("li:not([className^=x-tab-edge])");
41229 for(var i = 0, len = lis.length; i < len; i++) {
41231 var inner = Ext.fly(li).child('.x-tab-strip-inner', true);
41232 var tw = li.offsetWidth;
41233 var iw = inner.offsetWidth;
41234 inner.style.width = (each - (tw-iw)) + 'px';
41239 adjustBodyWidth : function(w){
41241 this.header.setWidth(w);
41244 this.footer.setWidth(w);
41250 * Sets the specified tab as the active tab. This method fires the {@link #beforetabchange} event which
41251 * can <tt>return false</tt> to cancel the tab change.
41252 * @param {String/Number} item
41253 * The id or tab Panel to activate. This parameter may be any of the following:
41254 * <div><ul class="mdetail-params">
41255 * <li>a <b><tt>String</tt></b> : representing the <code>{@link Ext.Component#itemId itemId}</code>
41256 * or <code>{@link Ext.Component#id id}</code> of the child component </li>
41257 * <li>a <b><tt>Number</tt></b> : representing the position of the child component
41258 * within the <code>{@link Ext.Container#items items}</code> <b>property</b></li>
41260 * <p>For additional information see {@link Ext.util.MixedCollection#get}.
41262 setActiveTab : function(item){
41263 item = this.getComponent(item);
41264 if(!item || this.fireEvent('beforetabchange', this, item, this.activeTab) === false){
41267 if(!this.rendered){
41268 this.activeTab = item;
41271 if(this.activeTab != item){
41272 if(this.activeTab){
41273 var oldEl = this.getTabEl(this.activeTab);
41275 Ext.fly(oldEl).removeClass('x-tab-strip-active');
41277 this.activeTab.fireEvent('deactivate', this.activeTab);
41279 var el = this.getTabEl(item);
41280 Ext.fly(el).addClass('x-tab-strip-active');
41281 this.activeTab = item;
41282 this.stack.add(item);
41284 this.layout.setActiveItem(item);
41285 if(this.scrolling){
41286 this.scrollToTab(item, this.animScroll);
41289 item.fireEvent('activate', item);
41290 this.fireEvent('tabchange', this, item);
41295 * Gets the currently active tab.
41296 * @return {Panel} The active tab
41298 getActiveTab : function(){
41299 return this.activeTab || null;
41303 * Gets the specified tab by id.
41304 * @param {String} id The tab id
41305 * @return {Panel} The tab
41307 getItem : function(item){
41308 return this.getComponent(item);
41312 autoScrollTabs : function(){
41313 this.pos = this.tabPosition=='bottom' ? this.footer : this.header;
41314 var count = this.items.length;
41315 var ow = this.pos.dom.offsetWidth;
41316 var tw = this.pos.dom.clientWidth;
41318 var wrap = this.stripWrap;
41320 var cw = wd.offsetWidth;
41321 var pos = this.getScrollPos();
41322 var l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos;
41324 if(!this.enableTabScroll || count < 1 || cw < 20){ // 20 to prevent display:none issues
41330 if(this.scrolling){
41331 this.scrolling = false;
41332 this.pos.removeClass('x-tab-scrolling');
41333 this.scrollLeft.hide();
41334 this.scrollRight.hide();
41335 // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari
41336 if(Ext.isAir || Ext.isWebKit){
41337 wd.style.marginLeft = '';
41338 wd.style.marginRight = '';
41342 if(!this.scrolling){
41343 this.pos.addClass('x-tab-scrolling');
41344 // See here: http://extjs.com/forum/showthread.php?t=49308&highlight=isSafari
41345 if(Ext.isAir || Ext.isWebKit){
41346 wd.style.marginLeft = '18px';
41347 wd.style.marginRight = '18px';
41350 tw -= wrap.getMargins('lr');
41351 wrap.setWidth(tw > 20 ? tw : 20);
41352 if(!this.scrolling){
41353 if(!this.scrollLeft){
41354 this.createScrollers();
41356 this.scrollLeft.show();
41357 this.scrollRight.show();
41360 this.scrolling = true;
41361 if(pos > (l-tw)){ // ensure it stays within bounds
41362 wd.scrollLeft = l-tw;
41363 }else{ // otherwise, make sure the active tab is still visible
41364 this.scrollToTab(this.activeTab, false);
41366 this.updateScrollButtons();
41371 createScrollers : function(){
41372 this.pos.addClass('x-tab-scrolling-' + this.tabPosition);
41373 var h = this.stripWrap.dom.offsetHeight;
41376 var sl = this.pos.insertFirst({
41377 cls:'x-tab-scroller-left'
41380 sl.addClassOnOver('x-tab-scroller-left-over');
41381 this.leftRepeater = new Ext.util.ClickRepeater(sl, {
41382 interval : this.scrollRepeatInterval,
41383 handler: this.onScrollLeft,
41386 this.scrollLeft = sl;
41389 var sr = this.pos.insertFirst({
41390 cls:'x-tab-scroller-right'
41393 sr.addClassOnOver('x-tab-scroller-right-over');
41394 this.rightRepeater = new Ext.util.ClickRepeater(sr, {
41395 interval : this.scrollRepeatInterval,
41396 handler: this.onScrollRight,
41399 this.scrollRight = sr;
41403 getScrollWidth : function(){
41404 return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos();
41408 getScrollPos : function(){
41409 return parseInt(this.stripWrap.dom.scrollLeft, 10) || 0;
41413 getScrollArea : function(){
41414 return parseInt(this.stripWrap.dom.clientWidth, 10) || 0;
41418 getScrollAnim : function(){
41419 return {duration:this.scrollDuration, callback: this.updateScrollButtons, scope: this};
41423 getScrollIncrement : function(){
41424 return this.scrollIncrement || (this.resizeTabs ? this.lastTabWidth+2 : 100);
41428 * Scrolls to a particular tab if tab scrolling is enabled
41429 * @param {Panel} item The item to scroll to
41430 * @param {Boolean} animate True to enable animations
41433 scrollToTab : function(item, animate){
41434 if(!item){ return; }
41435 var el = this.getTabEl(item);
41436 var pos = this.getScrollPos(), area = this.getScrollArea();
41437 var left = Ext.fly(el).getOffsetsTo(this.stripWrap)[0] + pos;
41438 var right = left + el.offsetWidth;
41440 this.scrollTo(left, animate);
41441 }else if(right > (pos + area)){
41442 this.scrollTo(right - area, animate);
41447 scrollTo : function(pos, animate){
41448 this.stripWrap.scrollTo('left', pos, animate ? this.getScrollAnim() : false);
41450 this.updateScrollButtons();
41454 onWheel : function(e){
41455 var d = e.getWheelDelta()*this.wheelIncrement*-1;
41458 var pos = this.getScrollPos();
41459 var newpos = pos + d;
41460 var sw = this.getScrollWidth()-this.getScrollArea();
41462 var s = Math.max(0, Math.min(sw, newpos));
41464 this.scrollTo(s, false);
41469 onScrollRight : function(){
41470 var sw = this.getScrollWidth()-this.getScrollArea();
41471 var pos = this.getScrollPos();
41472 var s = Math.min(sw, pos + this.getScrollIncrement());
41474 this.scrollTo(s, this.animScroll);
41479 onScrollLeft : function(){
41480 var pos = this.getScrollPos();
41481 var s = Math.max(0, pos - this.getScrollIncrement());
41483 this.scrollTo(s, this.animScroll);
41488 updateScrollButtons : function(){
41489 var pos = this.getScrollPos();
41490 this.scrollLeft[pos === 0 ? 'addClass' : 'removeClass']('x-tab-scroller-left-disabled');
41491 this.scrollRight[pos >= (this.getScrollWidth()-this.getScrollArea()) ? 'addClass' : 'removeClass']('x-tab-scroller-right-disabled');
41495 beforeDestroy : function() {
41497 this.items.each(function(item){
41498 if(item && item.tabEl){
41499 Ext.get(item.tabEl).removeAllListeners();
41505 this.strip.removeAllListeners();
41507 Ext.TabPanel.superclass.beforeDestroy.apply(this);
41511 * @cfg {Boolean} collapsible
41515 * @cfg {String} header
41519 * @cfg {Boolean} headerAsText
41531 * @cfg {Array} tools
41535 * @cfg {Array} toolTemplate
41539 * @cfg {Boolean} hideCollapseTool
41543 * @cfg {Boolean} titleCollapse
41547 * @cfg {Boolean} collapsed
41551 * @cfg {String} layout
41555 * @cfg {Boolean} preventBodyReset
41559 Ext.reg('tabpanel', Ext.TabPanel);
41562 * See {@link #setActiveTab}. Sets the specified tab as the active tab. This method fires
41563 * the {@link #beforetabchange} event which can <tt>return false</tt> to cancel the tab change.
41564 * @param {String/Panel} tab The id or tab Panel to activate
41567 Ext.TabPanel.prototype.activate = Ext.TabPanel.prototype.setActiveTab;
41569 // private utility class used by TabPanel
41570 Ext.TabPanel.AccessStack = function(){
41573 add : function(item){
41575 if(items.length > 10){
41580 remove : function(item){
41582 for(var i = 0, len = items.length; i < len; i++) {
41583 if(items[i] != item){
41591 return items.pop();
41596 * @class Ext.Button
41597 * @extends Ext.BoxComponent
41598 * Simple Button class
41599 * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
41600 * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
41601 * CSS property of the button by default, so if you want a mixed icon/text button, set cls:'x-btn-text-icon')
41602 * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event).
41603 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
41604 * <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li>
41605 * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
41607 * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width).
41608 * See also {@link Ext.Panel}.<tt>{@link Ext.Panel#minButtonWidth minButtonWidth}</tt>.
41609 * @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
41610 * @cfg {Boolean} hidden True to start hidden (defaults to false)
41611 * @cfg {Boolean} disabled True to start disabled (defaults to false)
41612 * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
41613 * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
41614 * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
41615 * a {@link Ext.util.ClickRepeater ClickRepeater} config object (defaults to false).
41617 * Create a new button
41618 * @param {Object} config The config object
41621 Ext.Button = Ext.extend(Ext.BoxComponent, {
41623 * Read-only. True if this button is hidden
41628 * Read-only. True if this button is disabled
41633 * Read-only. True if this button is pressed (only if enableToggle = true)
41639 * @cfg {Number} tabIndex Set a DOM tabIndex for this button (defaults to undefined)
41643 * @cfg {Boolean} allowDepress
41644 * False to not allow a pressed Button to be depressed (defaults to undefined). Only valid when {@link #enableToggle} is true.
41648 * @cfg {Boolean} enableToggle
41649 * True to enable pressed/not pressed toggling (defaults to false)
41651 enableToggle : false,
41653 * @cfg {Function} toggleHandler
41654 * Function called when a Button with {@link #enableToggle} set to true is clicked. Two arguments are passed:<ul class="mdetail-params">
41655 * <li><b>button</b> : Ext.Button<div class="sub-desc">this Button object</div></li>
41656 * <li><b>state</b> : Boolean<div class="sub-desc">The next state if the Button, true means pressed.</div></li>
41660 * @cfg {Mixed} menu
41661 * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
41664 * @cfg {String} menuAlign
41665 * The position to align the menu to (see {@link Ext.Element#alignTo} for more details, defaults to 'tl-bl?').
41667 menuAlign : 'tl-bl?',
41670 * @cfg {String} overflowText If used in a {@link Ext.Toolbar Toolbar}, the
41671 * text to be used if this item is shown in the overflow menu. See also
41672 * {@link Ext.Toolbar.Item}.<code>{@link Ext.Toolbar.Item#overflowText overflowText}</code>.
41675 * @cfg {String} iconCls
41676 * A css class which sets a background image to be used as the icon for this button
41679 * @cfg {String} type
41680 * submit, reset or button - defaults to 'button'
41685 menuClassTarget : 'tr:nth(2)',
41688 * @cfg {String} clickEvent
41689 * The DOM event that will fire the handler of the button. This can be any valid event name (dblclick, contextmenu).
41690 * Defaults to <tt>'click'</tt>.
41692 clickEvent : 'click',
41695 * @cfg {Boolean} handleMouseEvents
41696 * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
41698 handleMouseEvents : true,
41701 * @cfg {String} tooltipType
41702 * The type of tooltip to use. Either 'qtip' (default) for QuickTips or 'title' for title attribute.
41704 tooltipType : 'qtip',
41707 * @cfg {String} buttonSelector
41708 * <p>(Optional) A {@link Ext.DomQuery DomQuery} selector which is used to extract the active, clickable element from the
41709 * DOM structure created.</p>
41710 * <p>When a custom {@link #template} is used, you must ensure that this selector results in the selection of
41711 * a focussable element.</p>
41712 * <p>Defaults to <b><tt>'button:first-child'</tt></b>.</p>
41714 buttonSelector : 'button:first-child',
41717 * @cfg {String} scale
41718 * <p>(Optional) The size of the Button. Three values are allowed:</p>
41719 * <ul class="mdetail-params">
41720 * <li>'small'<div class="sub-desc">Results in the button element being 16px high.</div></li>
41721 * <li>'medium'<div class="sub-desc">Results in the button element being 24px high.</div></li>
41722 * <li>'large'<div class="sub-desc">Results in the button element being 32px high.</div></li>
41724 * <p>Defaults to <b><tt>'small'</tt></b>.</p>
41729 * @cfg {Object} scope The scope (<tt><b>this</b></tt> reference) in which the
41730 * <code>{@link #handler}</code> and <code>{@link #toggleHandler}</code> is
41731 * executed. Defaults to this Button.
41735 * @cfg {String} iconAlign
41736 * <p>(Optional) The side of the Button box to render the icon. Four values are allowed:</p>
41737 * <ul class="mdetail-params">
41738 * <li>'top'<div class="sub-desc"></div></li>
41739 * <li>'right'<div class="sub-desc"></div></li>
41740 * <li>'bottom'<div class="sub-desc"></div></li>
41741 * <li>'left'<div class="sub-desc"></div></li>
41743 * <p>Defaults to <b><tt>'left'</tt></b>.</p>
41745 iconAlign : 'left',
41748 * @cfg {String} arrowAlign
41749 * <p>(Optional) The side of the Button box to render the arrow if the button has an associated {@link #menu}.
41750 * Two values are allowed:</p>
41751 * <ul class="mdetail-params">
41752 * <li>'right'<div class="sub-desc"></div></li>
41753 * <li>'bottom'<div class="sub-desc"></div></li>
41755 * <p>Defaults to <b><tt>'right'</tt></b>.</p>
41757 arrowAlign : 'right',
41760 * @cfg {Ext.Template} template (Optional)
41761 * <p>A {@link Ext.Template Template} used to create the Button's DOM structure.</p>
41762 * Instances, or subclasses which need a different DOM structure may provide a different
41763 * template layout in conjunction with an implementation of {@link #getTemplateArgs}.
41764 * @type Ext.Template
41765 * @property template
41768 * @cfg {String} cls
41769 * A CSS class string to apply to the button's main element.
41774 * The {@link Ext.menu.Menu Menu} object associated with this Button when configured with the {@link #menu} config option.
41777 initComponent : function(){
41778 Ext.Button.superclass.initComponent.call(this);
41783 * Fires when this button is clicked
41784 * @param {Button} this
41785 * @param {EventObject} e The click event
41790 * Fires when the 'pressed' state of this button changes (only if enableToggle = true)
41791 * @param {Button} this
41792 * @param {Boolean} pressed
41797 * Fires when the mouse hovers over the button
41798 * @param {Button} this
41799 * @param {Event} e The event object
41804 * Fires when the mouse exits the button
41805 * @param {Button} this
41806 * @param {Event} e The event object
41811 * If this button has a menu, this event fires when it is shown
41812 * @param {Button} this
41813 * @param {Menu} menu
41818 * If this button has a menu, this event fires when it is hidden
41819 * @param {Button} this
41820 * @param {Menu} menu
41824 * @event menutriggerover
41825 * If this button has a menu, this event fires when the mouse enters the menu triggering element
41826 * @param {Button} this
41827 * @param {Menu} menu
41828 * @param {EventObject} e
41832 * @event menutriggerout
41833 * If this button has a menu, this event fires when the mouse leaves the menu triggering element
41834 * @param {Button} this
41835 * @param {Menu} menu
41836 * @param {EventObject} e
41841 this.menu = Ext.menu.MenuMgr.get(this.menu);
41843 if(Ext.isString(this.toggleGroup)){
41844 this.enableToggle = true;
41849 * <p>This method returns an object which provides substitution parameters for the {@link #template Template} used
41850 * to create this Button's DOM structure.</p>
41851 * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their
41852 * own implementation of this method.</p>
41853 * <p>The default implementation which provides data for the default {@link #template} returns an Array containing the
41854 * following items:</p><div class="mdetail-params"><ul>
41855 * <li>The Button's {@link #text}</li>
41856 * <li>The <button>'s {@link #type}</li>
41857 * <li>The {@link iconCls} applied to the <button> {@link #btnEl element}</li>
41858 * <li>The {@link #cls} applied to the Button's main {@link #getEl Element}</li>
41859 * <li>A CSS class name controlling the Button's {@link #scale} and {@link #iconAlign icon alignment}</li>
41860 * <li>A CSS class name which applies an arrow to the Button if configured with a {@link #menu}</li>
41862 * @return {Object} Substitution data for a Template.
41864 getTemplateArgs : function(){
41865 var cls = (this.cls || '');
41866 cls += (this.iconCls || this.icon) ? (this.text ? ' x-btn-text-icon' : ' x-btn-icon') : ' x-btn-noicon';
41868 cls += ' x-btn-pressed';
41870 return [this.text || ' ', this.type, this.iconCls || '', cls, 'x-btn-' + this.scale + ' x-btn-icon-' + this.scale + '-' + this.iconAlign, this.getMenuClass()];
41874 getMenuClass : function(){
41875 return this.menu ? (this.arrowAlign != 'bottom' ? 'x-btn-arrow' : 'x-btn-arrow-bottom') : '';
41879 onRender : function(ct, position){
41880 if(!this.template){
41881 if(!Ext.Button.buttonTemplate){
41882 // hideous table template
41883 Ext.Button.buttonTemplate = new Ext.Template(
41884 '<table cellspacing="0" class="x-btn {3}"><tbody class="{4}">',
41885 '<tr><td class="x-btn-tl"><i> </i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i> </i></td></tr>',
41886 '<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>',
41887 '<tr><td class="x-btn-bl"><i> </i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i> </i></td></tr>',
41888 "</tbody></table>");
41889 Ext.Button.buttonTemplate.compile();
41891 this.template = Ext.Button.buttonTemplate;
41894 var btn, targs = this.getTemplateArgs();
41897 btn = this.template.insertBefore(position, targs, true);
41899 btn = this.template.append(ct, targs, true);
41902 * An {@link Ext.Element Element} encapsulating the Button's clickable element. By default,
41903 * this references a <tt><button></tt> element. Read only.
41904 * @type Ext.Element
41907 this.btnEl = btn.child(this.buttonSelector);
41908 this.mon(this.btnEl, {
41910 focus: this.onFocus,
41914 this.initButtonEl(btn, this.btnEl);
41916 Ext.ButtonToggleMgr.register(this);
41920 initButtonEl : function(btn, btnEl){
41924 var d = this.el.dom,
41925 c = Ext.Element.cache;
41928 d.id = this.el.id = this.id;
41932 btnEl.setStyle('background-image', 'url(' +this.icon +')');
41934 if(this.tabIndex !== undefined){
41935 btnEl.dom.tabIndex = this.tabIndex;
41938 this.setTooltip(this.tooltip, true);
41941 if(this.handleMouseEvents){
41944 mouseover: this.onMouseOver,
41945 mousedown: this.onMouseDown
41948 // new functionality for monitoring on the document level
41949 //this.mon(btn, 'mouseout', this.onMouseOut, this);
41953 this.mon(this.menu, {
41955 show: this.onMenuShow,
41956 hide: this.onMenuHide
41961 var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
41962 this.mon(repeater, 'click', this.onClick, this);
41964 this.mon(btn, this.clickEvent, this.onClick, this);
41968 afterRender : function(){
41969 Ext.Button.superclass.afterRender.call(this);
41970 this.doAutoWidth();
41974 * Sets the CSS class that provides a background image to use as the button's icon. This method also changes
41975 * the value of the {@link iconCls} config internally.
41976 * @param {String} cls The CSS class providing the icon image
41977 * @return {Ext.Button} this
41979 setIconClass : function(cls){
41981 this.btnEl.replaceClass(this.iconCls, cls);
41983 this.iconCls = cls;
41988 * Sets the tooltip for this Button.
41989 * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul>
41990 * <li><b>String</b> : A string to be used as innerHTML (html tags are accepted) to show in a tooltip</li>
41991 * <li><b>Object</b> : A configuration object for {@link Ext.QuickTips#register}.</li>
41993 * @return {Ext.Button} this
41995 setTooltip : function(tooltip, /* private */ initial){
42000 if(Ext.isObject(tooltip)){
42001 Ext.QuickTips.register(Ext.apply({
42002 target: this.btnEl.id
42004 this.tooltip = tooltip;
42006 this.btnEl.dom[this.tooltipType] = tooltip;
42009 this.tooltip = tooltip;
42015 clearTip : function(){
42016 if(Ext.isObject(this.tooltip)){
42017 Ext.QuickTips.unregister(this.btnEl);
42022 beforeDestroy : function(){
42026 Ext.destroy(this.menu, this.repeater);
42030 onDestroy : function(){
42031 var doc = Ext.getDoc();
42032 doc.un('mouseover', this.monitorMouseOver, this);
42033 doc.un('mouseup', this.onMouseUp, this);
42035 Ext.ButtonToggleMgr.unregister(this);
42040 doAutoWidth : function(){
42041 if(this.el && this.text && this.width === undefined){
42042 this.el.setWidth('auto');
42043 if(Ext.isIE7 && Ext.isStrict){
42044 var ib = this.btnEl;
42045 if(ib && ib.getWidth() > 20){
42047 ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));
42051 if(this.el.getWidth() < this.minWidth){
42052 this.el.setWidth(this.minWidth);
42059 * Assigns this Button's click handler
42060 * @param {Function} handler The function to call when the button is clicked
42061 * @param {Object} scope (optional) Scope for the function passed in. Defaults to this Button.
42062 * @return {Ext.Button} this
42064 setHandler : function(handler, scope){
42065 this.handler = handler;
42066 this.scope = scope;
42071 * Sets this Button's text
42072 * @param {String} text The button text
42073 * @return {Ext.Button} this
42075 setText : function(text){
42078 this.el.child('td.x-btn-mc ' + this.buttonSelector).update(text);
42080 this.doAutoWidth();
42085 * Gets the text for this Button
42086 * @return {String} The button text
42088 getText : function(){
42093 * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
42094 * @param {Boolean} state (optional) Force a particular state
42095 * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
42096 * @return {Ext.Button} this
42098 toggle : function(state, suppressEvent){
42099 state = state === undefined ? !this.pressed : !!state;
42100 if(state != this.pressed){
42102 this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed');
42104 this.pressed = state;
42105 if(!suppressEvent){
42106 this.fireEvent('toggle', this, state);
42107 if(this.toggleHandler){
42108 this.toggleHandler.call(this.scope || this, this, state);
42118 focus : function(){
42119 this.btnEl.focus();
42123 onDisable : function(){
42124 this.onDisableChange(true);
42128 onEnable : function(){
42129 this.onDisableChange(false);
42132 onDisableChange : function(disabled){
42134 if(!Ext.isIE6 || !this.text){
42135 this.el[disabled ? 'addClass' : 'removeClass'](this.disabledClass);
42137 this.el.dom.disabled = disabled;
42139 this.disabled = disabled;
42143 * Show this button's menu (if it has one)
42145 showMenu : function(){
42146 if(this.rendered && this.menu){
42148 Ext.QuickTips.getQuickTip().cancelShow(this.btnEl);
42150 this.menu.show(this.el, this.menuAlign);
42156 * Hide this button's menu (if it has one)
42158 hideMenu : function(){
42166 * Returns true if the button has a menu and it is visible
42167 * @return {Boolean}
42169 hasVisibleMenu : function(){
42170 return this.menu && this.menu.isVisible();
42174 onClick : function(e){
42176 e.preventDefault();
42178 if(e.button !== 0){
42181 if(!this.disabled){
42182 if(this.enableToggle && (this.allowDepress !== false || !this.pressed)){
42185 if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){
42188 this.fireEvent('click', this, e);
42190 //this.el.removeClass('x-btn-over');
42191 this.handler.call(this.scope || this, this, e);
42197 isMenuTriggerOver : function(e, internal){
42198 return this.menu && !internal;
42202 isMenuTriggerOut : function(e, internal){
42203 return this.menu && !internal;
42207 onMouseOver : function(e){
42208 if(!this.disabled){
42209 var internal = e.within(this.el, true);
42211 this.el.addClass('x-btn-over');
42212 if(!this.monitoringMouseOver){
42213 Ext.getDoc().on('mouseover', this.monitorMouseOver, this);
42214 this.monitoringMouseOver = true;
42216 this.fireEvent('mouseover', this, e);
42218 if(this.isMenuTriggerOver(e, internal)){
42219 this.fireEvent('menutriggerover', this, this.menu, e);
42225 monitorMouseOver : function(e){
42226 if(e.target != this.el.dom && !e.within(this.el)){
42227 if(this.monitoringMouseOver){
42228 Ext.getDoc().un('mouseover', this.monitorMouseOver, this);
42229 this.monitoringMouseOver = false;
42231 this.onMouseOut(e);
42236 onMouseOut : function(e){
42237 var internal = e.within(this.el) && e.target != this.el.dom;
42238 this.el.removeClass('x-btn-over');
42239 this.fireEvent('mouseout', this, e);
42240 if(this.isMenuTriggerOut(e, internal)){
42241 this.fireEvent('menutriggerout', this, this.menu, e);
42245 onFocus : function(e){
42246 if(!this.disabled){
42247 this.el.addClass('x-btn-focus');
42251 onBlur : function(e){
42252 this.el.removeClass('x-btn-focus');
42256 getClickEl : function(e, isUp){
42261 onMouseDown : function(e){
42262 if(!this.disabled && e.button === 0){
42263 this.getClickEl(e).addClass('x-btn-click');
42264 Ext.getDoc().on('mouseup', this.onMouseUp, this);
42268 onMouseUp : function(e){
42269 if(e.button === 0){
42270 this.getClickEl(e, true).removeClass('x-btn-click');
42271 Ext.getDoc().un('mouseup', this.onMouseUp, this);
42275 onMenuShow : function(e){
42276 this.ignoreNextClick = 0;
42277 this.el.addClass('x-btn-menu-active');
42278 this.fireEvent('menushow', this, this.menu);
42281 onMenuHide : function(e){
42282 this.el.removeClass('x-btn-menu-active');
42283 this.ignoreNextClick = this.restoreClick.defer(250, this);
42284 this.fireEvent('menuhide', this, this.menu);
42288 restoreClick : function(){
42289 this.ignoreNextClick = 0;
42295 * @cfg {String} autoEl @hide
42298 Ext.reg('button', Ext.Button);
42300 // Private utility class used by Button
42301 Ext.ButtonToggleMgr = function(){
42304 function toggleGroup(btn, state){
42306 var g = groups[btn.toggleGroup];
42307 for(var i = 0, l = g.length; i < l; i++){
42309 g[i].toggle(false);
42316 register : function(btn){
42317 if(!btn.toggleGroup){
42320 var g = groups[btn.toggleGroup];
42322 g = groups[btn.toggleGroup] = [];
42325 btn.on('toggle', toggleGroup);
42328 unregister : function(btn){
42329 if(!btn.toggleGroup){
42332 var g = groups[btn.toggleGroup];
42335 btn.un('toggle', toggleGroup);
42340 * Gets the pressed button in the passed group or null
42341 * @param {String} group
42344 getPressed : function(group){
42345 var g = groups[group];
42347 for(var i = 0, len = g.length; i < len; i++){
42348 if(g[i].pressed === true){
42357 * @class Ext.SplitButton
\r
42358 * @extends Ext.Button
\r
42359 * A split button that provides a built-in dropdown arrow that can fire an event separately from the default
\r
42360 * click event of the button. Typically this would be used to display a dropdown menu that provides additional
\r
42361 * options to the primary button action, but any custom handler can provide the arrowclick implementation. Example usage:
\r
42363 // display a dropdown menu:
\r
42364 new Ext.SplitButton({
\r
42365 renderTo: 'button-ct', // the container id
\r
42367 handler: optionsHandler, // handle a click on the button itself
\r
42368 menu: new Ext.menu.Menu({
\r
42370 // these items will render as dropdown menu items when the arrow is clicked:
\r
42371 {text: 'Item 1', handler: item1Handler},
\r
42372 {text: 'Item 2', handler: item2Handler}
\r
42377 // Instead of showing a menu, you provide any type of custom
\r
42378 // functionality you want when the dropdown arrow is clicked:
\r
42379 new Ext.SplitButton({
\r
42380 renderTo: 'button-ct',
\r
42382 handler: optionsHandler,
\r
42383 arrowHandler: myCustomHandler
\r
42386 * @cfg {Function} arrowHandler A function called when the arrow button is clicked (can be used instead of click event)
\r
42387 * @cfg {String} arrowTooltip The title attribute of the arrow
\r
42389 * Create a new menu button
\r
42390 * @param {Object} config The config object
\r
42391 * @xtype splitbutton
\r
42393 Ext.SplitButton = Ext.extend(Ext.Button, {
\r
42395 arrowSelector : 'em',
\r
42399 initComponent : function(){
\r
42400 Ext.SplitButton.superclass.initComponent.call(this);
\r
42402 * @event arrowclick
\r
42403 * Fires when this button's arrow is clicked
\r
42404 * @param {MenuButton} this
\r
42405 * @param {EventObject} e The click event
\r
42407 this.addEvents("arrowclick");
\r
42411 onRender : function(){
\r
42412 Ext.SplitButton.superclass.onRender.apply(this, arguments);
\r
42413 if(this.arrowTooltip){
\r
42414 this.el.child(this.arrowSelector).dom[this.tooltipType] = this.arrowTooltip;
\r
42419 * Sets this button's arrow click handler.
\r
42420 * @param {Function} handler The function to call when the arrow is clicked
\r
42421 * @param {Object} scope (optional) Scope for the function passed above
\r
42423 setArrowHandler : function(handler, scope){
\r
42424 this.arrowHandler = handler;
\r
42425 this.scope = scope;
\r
42428 getMenuClass : function(){
\r
42429 return 'x-btn-split' + (this.arrowAlign == 'bottom' ? '-bottom' : '');
\r
42432 isClickOnArrow : function(e){
\r
42433 return this.arrowAlign != 'bottom' ?
\r
42434 e.getPageX() > this.el.child(this.buttonSelector).getRegion().right :
\r
42435 e.getPageY() > this.el.child(this.buttonSelector).getRegion().bottom;
\r
42439 onClick : function(e, t){
\r
42440 e.preventDefault();
\r
42441 if(!this.disabled){
\r
42442 if(this.isClickOnArrow(e)){
\r
42443 if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){
\r
42446 this.fireEvent("arrowclick", this, e);
\r
42447 if(this.arrowHandler){
\r
42448 this.arrowHandler.call(this.scope || this, this, e);
\r
42451 if(this.enableToggle){
\r
42454 this.fireEvent("click", this, e);
\r
42455 if(this.handler){
\r
42456 this.handler.call(this.scope || this, this, e);
\r
42463 isMenuTriggerOver : function(e){
\r
42464 return this.menu && e.target.tagName == 'em';
\r
42468 isMenuTriggerOut : function(e, internal){
\r
42469 return this.menu && e.target.tagName != 'em';
\r
42473 Ext.reg('splitbutton', Ext.SplitButton);/**
\r
42474 * @class Ext.CycleButton
\r
42475 * @extends Ext.SplitButton
\r
42476 * A specialized SplitButton that contains a menu of {@link Ext.menu.CheckItem} elements. The button automatically
\r
42477 * cycles through each menu item on click, raising the button's {@link #change} event (or calling the button's
\r
42478 * {@link #changeHandler} function, if supplied) for the active menu item. Clicking on the arrow section of the
\r
42479 * button displays the dropdown menu just like a normal SplitButton. Example usage:
\r
42481 var btn = new Ext.CycleButton({
\r
42483 prependText: 'View as ',
\r
42485 text:'text only',
\r
42486 iconCls:'view-text',
\r
42490 iconCls:'view-html'
\r
42492 changeHandler:function(btn, item){
\r
42493 Ext.Msg.alert('Change View', item.text);
\r
42498 * Create a new split button
\r
42499 * @param {Object} config The config object
\r
42502 Ext.CycleButton = Ext.extend(Ext.SplitButton, {
\r
42504 * @cfg {Array} items An array of {@link Ext.menu.CheckItem} <b>config</b> objects to be used when creating the
\r
42505 * button's menu items (e.g., {text:'Foo', iconCls:'foo-icon'})
\r
42508 * @cfg {Boolean} showText True to display the active item's text as the button text (defaults to false)
\r
42511 * @cfg {String} prependText A static string to prepend before the active item's text when displayed as the
\r
42512 * button's text (only applies when showText = true, defaults to '')
\r
42515 * @cfg {Function} changeHandler A callback function that will be invoked each time the active menu
\r
42516 * item in the button's menu has changed. If this callback is not supplied, the SplitButton will instead
\r
42517 * fire the {@link #change} event on active item change. The changeHandler function will be called with the
\r
42518 * following argument list: (SplitButton this, Ext.menu.CheckItem item)
\r
42521 * @cfg {String} forceIcon A css class which sets an image to be used as the static icon for this button. This
\r
42522 * icon will always be displayed regardless of which item is selected in the dropdown list. This overrides the
\r
42523 * default behavior of changing the button's icon to match the selected item's icon on change.
\r
42528 * The {@link Ext.menu.Menu Menu} object used to display the {@link Ext.menu.CheckItem CheckItems} representing the available choices.
\r
42532 getItemText : function(item){
\r
42533 if(item && this.showText === true){
\r
42535 if(this.prependText){
\r
42536 text += this.prependText;
\r
42538 text += item.text;
\r
42541 return undefined;
\r
42545 * Sets the button's active menu item.
\r
42546 * @param {Ext.menu.CheckItem} item The item to activate
\r
42547 * @param {Boolean} suppressEvent True to prevent the button's change event from firing (defaults to false)
\r
42549 setActiveItem : function(item, suppressEvent){
\r
42550 if(!Ext.isObject(item)){
\r
42551 item = this.menu.getComponent(item);
\r
42554 if(!this.rendered){
\r
42555 this.text = this.getItemText(item);
\r
42556 this.iconCls = item.iconCls;
\r
42558 var t = this.getItemText(item);
\r
42562 this.setIconClass(item.iconCls);
\r
42564 this.activeItem = item;
\r
42565 if(!item.checked){
\r
42566 item.setChecked(true, true);
\r
42568 if(this.forceIcon){
\r
42569 this.setIconClass(this.forceIcon);
\r
42571 if(!suppressEvent){
\r
42572 this.fireEvent('change', this, item);
\r
42578 * Gets the currently active menu item.
\r
42579 * @return {Ext.menu.CheckItem} The active item
\r
42581 getActiveItem : function(){
\r
42582 return this.activeItem;
\r
42586 initComponent : function(){
\r
42590 * Fires after the button's active menu item has changed. Note that if a {@link #changeHandler} function
\r
42591 * is set on this CycleButton, it will be called instead on active item change and this change event will
\r
42593 * @param {Ext.CycleButton} this
\r
42594 * @param {Ext.menu.CheckItem} item The menu item that was selected
\r
42599 if(this.changeHandler){
\r
42600 this.on('change', this.changeHandler, this.scope||this);
\r
42601 delete this.changeHandler;
\r
42604 this.itemCount = this.items.length;
\r
42606 this.menu = {cls:'x-cycle-menu', items:[]};
\r
42608 Ext.each(this.items, function(item, i){
\r
42609 Ext.apply(item, {
\r
42610 group: item.group || this.id,
\r
42612 checkHandler: this.checkHandler,
\r
42614 checked: item.checked || false
\r
42616 this.menu.items.push(item);
\r
42617 if(item.checked){
\r
42621 this.setActiveItem(checked, true);
\r
42622 Ext.CycleButton.superclass.initComponent.call(this);
\r
42624 this.on('click', this.toggleSelected, this);
\r
42628 checkHandler : function(item, pressed){
\r
42630 this.setActiveItem(item);
\r
42635 * This is normally called internally on button click, but can be called externally to advance the button's
\r
42636 * active item programmatically to the next one in the menu. If the current item is the last one in the menu
\r
42637 * the active item will be set to the first item in the menu.
\r
42639 toggleSelected : function(){
\r
42640 var m = this.menu;
\r
42642 // layout if we haven't before so the items are active
\r
42643 if(!m.hasLayout){
\r
42647 var nextIdx, checkItem;
\r
42648 for (var i = 1; i < this.itemCount; i++) {
\r
42649 nextIdx = (this.activeItem.itemIndex + i) % this.itemCount;
\r
42650 // check the potential item
\r
42651 checkItem = m.items.itemAt(nextIdx);
\r
42652 // if its not disabled then check it.
\r
42653 if (!checkItem.disabled) {
\r
42654 checkItem.setChecked(true);
\r
42660 Ext.reg('cycle', Ext.CycleButton);/**
\r
42661 * @class Ext.layout.ToolbarLayout
\r
42662 * @extends Ext.layout.ContainerLayout
\r
42663 * Layout manager implicitly used by Ext.Toolbar.
\r
42665 Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
42666 monitorResize : true,
\r
42667 triggerWidth : 18,
\r
42668 lastOverflow : false,
\r
42670 noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
\r
42672 onLayout : function(ct, target){
\r
42673 if(!this.leftTr){
\r
42674 target.addClass('x-toolbar-layout-ct');
\r
42675 target.insertHtml('beforeEnd',
\r
42676 '<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
42677 this.leftTr = target.child('tr.x-toolbar-left-row', true);
\r
42678 this.rightTr = target.child('tr.x-toolbar-right-row', true);
\r
42679 this.extrasTr = target.child('tr.x-toolbar-extras-row', true);
\r
42681 var side = this.leftTr;
\r
42684 var items = ct.items.items;
\r
42685 for(var i = 0, len = items.length, c; i < len; i++, pos++) {
\r
42688 side = this.rightTr;
\r
42690 }else if(!c.rendered){
\r
42691 c.render(this.insertCell(c, side, pos));
\r
42693 if(!c.xtbHidden && !this.isValidParent(c, side.childNodes[pos])){
\r
42694 var td = this.insertCell(c, side, pos);
\r
42695 td.appendChild(c.getDomPositionEl().dom);
\r
42696 c.container = Ext.get(td);
\r
42700 //strip extra empty cells
\r
42701 this.cleanup(this.leftTr);
\r
42702 this.cleanup(this.rightTr);
\r
42703 this.cleanup(this.extrasTr);
\r
42704 this.fitToSize(target);
\r
42707 cleanup : function(row){
\r
42708 var cn = row.childNodes;
\r
42709 for(var i = cn.length-1, c; i >= 0 && (c = cn[i]); i--){
\r
42710 if(!c.firstChild){
\r
42711 row.removeChild(c);
\r
42716 insertCell : function(c, side, pos){
\r
42717 var td = document.createElement('td');
\r
42718 td.className='x-toolbar-cell';
\r
42719 side.insertBefore(td, side.childNodes[pos]||null);
\r
42723 hideItem : function(item){
\r
42724 var h = (this.hiddens = this.hiddens || []);
\r
42726 item.xtbHidden = true;
\r
42727 item.xtbWidth = item.getDomPositionEl().dom.parentNode.offsetWidth;
\r
42731 unhideItem : function(item){
\r
42733 item.xtbHidden = false;
\r
42734 this.hiddens.remove(item);
\r
42735 if(this.hiddens.length < 1){
\r
42736 delete this.hiddens;
\r
42740 getItemWidth : function(c){
\r
42741 return c.hidden ? (c.xtbWidth || 0) : c.getDomPositionEl().dom.parentNode.offsetWidth;
\r
42744 fitToSize : function(t){
\r
42745 if(this.container.enableOverflow === false){
\r
42748 var w = t.dom.clientWidth;
\r
42749 var lw = this.lastWidth || 0;
\r
42750 this.lastWidth = w;
\r
42751 var iw = t.dom.firstChild.offsetWidth;
\r
42753 var clipWidth = w - this.triggerWidth;
\r
42754 var hideIndex = -1;
\r
42756 if(iw > w || (this.hiddens && w >= lw)){
\r
42757 var i, items = this.container.items.items, len = items.length, c;
\r
42758 var loopWidth = 0;
\r
42759 for(i = 0; i < len; i++) {
\r
42762 loopWidth += this.getItemWidth(c);
\r
42763 if(loopWidth > clipWidth){
\r
42764 if(!c.xtbHidden){
\r
42765 this.hideItem(c);
\r
42769 this.unhideItem(c);
\r
42775 if(this.hiddens){
\r
42777 if(!this.lastOverflow){
\r
42778 this.container.fireEvent('overflowchange', this.container, true);
\r
42779 this.lastOverflow = true;
\r
42781 }else if(this.more){
\r
42782 this.clearMenu();
\r
42783 this.more.destroy();
\r
42784 delete this.more;
\r
42785 if(this.lastOverflow){
\r
42786 this.container.fireEvent('overflowchange', this.container, false);
\r
42787 this.lastOverflow = false;
\r
42792 createMenuConfig : function(c, hideOnClick){
\r
42793 var cfg = Ext.apply({}, c.initialConfig),
\r
42794 group = c.toggleGroup;
\r
42797 text: c.overflowText || c.text,
\r
42798 iconCls: c.iconCls,
\r
42800 itemId: c.itemId,
\r
42801 disabled: c.disabled,
\r
42802 handler: c.handler,
\r
42805 hideOnClick: hideOnClick
\r
42807 if(group || c.enableToggle){
\r
42810 checked: c.pressed,
\r
42812 checkchange: function(item, checked){
\r
42813 c.toggle(checked);
\r
42818 delete cfg.xtype;
\r
42824 addComponentToMenu : function(m, c){
\r
42825 if(c instanceof Ext.Toolbar.Separator){
\r
42827 }else if(Ext.isFunction(c.isXType)){
\r
42828 if(c.isXType('splitbutton')){
\r
42829 m.add(this.createMenuConfig(c, true));
\r
42830 }else if(c.isXType('button')){
\r
42831 m.add(this.createMenuConfig(c, !c.menu));
\r
42832 }else if(c.isXType('buttongroup')){
\r
42833 c.items.each(function(item){
\r
42834 this.addComponentToMenu(m, item);
\r
42840 clearMenu : function(){
\r
42841 var m = this.moreMenu;
\r
42842 if(m && m.items){
\r
42843 m.items.each(function(item){
\r
42844 delete item.menu;
\r
42850 beforeMoreShow : function(m){
\r
42851 var h = this.container.items.items,
\r
42855 needsSep = function(group, item){
\r
42856 return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
\r
42859 this.clearMenu();
\r
42861 for(var i = 0; i < len; i++){
\r
42864 if(prev && (needsSep(c, prev) || needsSep(prev, c))){
\r
42867 this.addComponentToMenu(m, c);
\r
42871 // put something so the menu isn't empty
\r
42872 // if no compatible items found
\r
42873 if(m.items.length < 1){
\r
42874 m.add(this.noItemsMenuText);
\r
42878 initMore : function(){
\r
42880 this.moreMenu = new Ext.menu.Menu({
\r
42882 beforeshow: this.beforeMoreShow,
\r
42886 this.moreMenu.ownerCt = this.container;
\r
42887 this.more = new Ext.Button({
\r
42888 iconCls: 'x-toolbar-more-icon',
\r
42889 cls: 'x-toolbar-more',
\r
42890 menu: this.moreMenu
\r
42892 var td = this.insertCell(this.more, this.extrasTr, 100);
\r
42893 this.more.render(td);
\r
42897 destroy : function(){
\r
42898 Ext.destroy(this.more, this.moreMenu);
\r
42899 Ext.layout.ToolbarLayout.superclass.destroy.call(this);
\r
42902 * @property activeItem
\r
42907 Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout;
\r
42910 * @class Ext.Toolbar
\r
42911 * @extends Ext.Container
\r
42912 * <p>Basic Toolbar class. Although the <tt>{@link Ext.Container#defaultType defaultType}</tt> for Toolbar
\r
42913 * is <tt>{@link Ext.Button button}</tt>, Toolbar elements (child items for the Toolbar container) may
\r
42914 * be virtually any type of Component. Toolbar elements can be created explicitly via their constructors,
\r
42915 * or implicitly via their xtypes, and can be <tt>{@link #add}</tt>ed dynamically.</p>
\r
42916 * <p>Some items have shortcut strings for creation:</p>
\r
42918 <u>Shortcut</u> <u>xtype</u> <u>Class</u> <u>Description</u>
\r
42919 '->' 'tbfill' {@link Ext.Toolbar.Fill} begin using the right-justified button container
\r
42920 '-' 'tbseparator' {@link Ext.Toolbar.Separator} add a vertical separator bar between toolbar items
\r
42921 ' ' 'tbspacer' {@link Ext.Toolbar.Spacer} add horiztonal space between elements
\r
42924 * Example usage of various elements:
\r
42926 var tb = new Ext.Toolbar({
\r
42927 renderTo: document.body,
\r
42932 // xtype: 'button', // default for Toolbars, same as 'tbbutton'
\r
42936 xtype: 'splitbutton', // same as 'tbsplitbutton'
\r
42937 text: 'Split Button'
\r
42939 // begin using the right-justified button container
\r
42940 '->', // same as {xtype: 'tbfill'}, // Ext.Toolbar.Fill
\r
42942 xtype: 'textfield',
\r
42944 emptyText: 'enter search term'
\r
42946 // add a vertical separator bar between toolbar items
\r
42947 '-', // same as {xtype: 'tbseparator'} to create Ext.Toolbar.Separator
\r
42948 'text 1', // same as {xtype: 'tbtext', text: 'text1'} to create Ext.Toolbar.TextItem
\r
42949 {xtype: 'tbspacer'},// same as ' ' to create Ext.Toolbar.Spacer
\r
42951 {xtype: 'tbspacer', width: 50}, // add a 50px space
\r
42956 * Example adding a ComboBox within a menu of a button:
\r
42958 // ComboBox creation
\r
42959 var combo = new Ext.form.ComboBox({
\r
42960 store: new Ext.data.ArrayStore({
\r
42961 autoDestroy: true,
\r
42962 fields: ['initials', 'fullname'],
\r
42964 ['FF', 'Fred Flintstone'],
\r
42965 ['BR', 'Barney Rubble']
\r
42968 displayField: 'fullname',
\r
42971 forceSelection: true,
\r
42972 triggerAction: 'all',
\r
42973 emptyText: 'Select a name...',
\r
42974 selectOnFocus: true,
\r
42976 getListParent: function() {
\r
42977 return this.el.up('.x-menu');
\r
42979 iconCls: 'no-icon' //use iconCls if placing within menu to shift to right side of menu
\r
42982 // put ComboBox in a Menu
\r
42983 var menu = new Ext.menu.Menu({
\r
42986 combo // A Field in a Menu
\r
42990 // add a Button with the menu
\r
42992 text:'Button w/ Menu',
\r
42993 menu: menu // assign menu by instance
\r
42998 * Creates a new Toolbar
\r
42999 * @param {Object/Array} config A config object or an array of buttons to <tt>{@link #add}</tt>
\r
43002 Ext.Toolbar = function(config){
\r
43003 if(Ext.isArray(config)){
\r
43004 config = {items: config, layout: 'toolbar'};
\r
43006 config = Ext.apply({
\r
43007 layout: 'toolbar'
\r
43009 if(config.buttons) {
\r
43010 config.items = config.buttons;
\r
43013 Ext.Toolbar.superclass.constructor.call(this, config);
\r
43018 var T = Ext.Toolbar;
\r
43020 Ext.extend(T, Ext.Container, {
\r
43022 defaultType: 'button',
\r
43025 * @cfg {String/Object} layout
\r
43026 * This class assigns a default layout (<code>layout:'<b>toolbar</b>'</code>).
\r
43027 * Developers <i>may</i> override this configuration option if another layout
\r
43028 * is required (the constructor must be passed a configuration object in this
\r
43029 * case instead of an array).
\r
43030 * See {@link Ext.Container#layout} for additional information.
\r
43033 trackMenus : true,
\r
43034 internalDefaults: {removeMode: 'container', hideParent: true},
\r
43035 toolbarCls: 'x-toolbar',
\r
43037 initComponent : function(){
\r
43038 T.superclass.initComponent.call(this);
\r
43041 * @event overflowchange
\r
43042 * Fires after the overflow state has changed.
\r
43043 * @param {Object} c The Container
\r
43044 * @param {Boolean} lastOverflow overflow state
\r
43046 this.addEvents('overflowchange');
\r
43050 onRender : function(ct, position){
\r
43052 if(!this.autoCreate){
\r
43053 this.autoCreate = {
\r
43054 cls: this.toolbarCls + ' x-small-editor'
\r
43057 this.el = ct.createChild(Ext.apply({ id: this.id },this.autoCreate), position);
\r
43058 Ext.Toolbar.superclass.onRender.apply(this, arguments);
\r
43063 * <p>Adds element(s) to the toolbar -- this function takes a variable number of
\r
43064 * arguments of mixed type and adds them to the toolbar.</p>
\r
43065 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43066 * @param {Mixed} arg1 The following types of arguments are all valid:<br />
\r
43068 * <li>{@link Ext.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
\r
43069 * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
\r
43070 * <li>Field: Any form field (equivalent to {@link #addField})</li>
\r
43071 * <li>Item: Any subclass of {@link Ext.Toolbar.Item} (equivalent to {@link #addItem})</li>
\r
43072 * <li>String: Any generic string (gets wrapped in a {@link Ext.Toolbar.TextItem}, equivalent to {@link #addText}).
\r
43073 * Note that there are a few special strings that are treated differently as explained next.</li>
\r
43074 * <li>'-': Creates a separator element (equivalent to {@link #addSeparator})</li>
\r
43075 * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
\r
43076 * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
\r
43078 * @param {Mixed} arg2
\r
43079 * @param {Mixed} etc.
\r
43084 lookupComponent : function(c){
\r
43085 if(Ext.isString(c)){
\r
43087 c = new T.Separator();
\r
43088 }else if(c == ' '){
\r
43089 c = new T.Spacer();
\r
43090 }else if(c == '->'){
\r
43091 c = new T.Fill();
\r
43093 c = new T.TextItem(c);
\r
43095 this.applyDefaults(c);
\r
43097 if(c.isFormField || c.render){ // some kind of form field, some kind of Toolbar.Item
\r
43098 c = this.constructItem(c);
\r
43099 }else if(c.tag){ // DomHelper spec
\r
43100 c = new T.Item({autoEl: c});
\r
43101 }else if(c.tagName){ // element
\r
43102 c = new T.Item({el:c});
\r
43103 }else if(Ext.isObject(c)){ // must be button config?
\r
43104 c = c.xtype ? this.constructItem(c) : this.constructButton(c);
\r
43111 applyDefaults : function(c){
\r
43112 if(!Ext.isString(c)){
\r
43113 c = Ext.Toolbar.superclass.applyDefaults.call(this, c);
\r
43114 var d = this.internalDefaults;
\r
43116 Ext.applyIf(c.initialConfig, d);
\r
43119 Ext.applyIf(c, d);
\r
43126 constructItem : function(item, type){
\r
43127 return Ext.create(item, type || this.defaultType);
\r
43131 * Adds a separator
\r
43132 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43133 * @return {Ext.Toolbar.Item} The separator {@link Ext.Toolbar.Item item}
\r
43135 addSeparator : function(){
\r
43136 return this.add(new T.Separator());
\r
43140 * Adds a spacer element
\r
43141 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43142 * @return {Ext.Toolbar.Spacer} The spacer item
\r
43144 addSpacer : function(){
\r
43145 return this.add(new T.Spacer());
\r
43149 * Forces subsequent additions into the float:right toolbar
\r
43150 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43152 addFill : function(){
\r
43153 this.add(new T.Fill());
\r
43157 * Adds any standard HTML element to the toolbar
\r
43158 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43159 * @param {Mixed} el The element or id of the element to add
\r
43160 * @return {Ext.Toolbar.Item} The element's item
\r
43162 addElement : function(el){
\r
43163 return this.addItem(new T.Item({el:el}));
\r
43167 * Adds any Toolbar.Item or subclass
\r
43168 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43169 * @param {Ext.Toolbar.Item} item
\r
43170 * @return {Ext.Toolbar.Item} The item
\r
43172 addItem : function(item){
\r
43173 return this.add.apply(this, arguments);
\r
43177 * Adds a button (or buttons). See {@link Ext.Button} for more info on the config.
\r
43178 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43179 * @param {Object/Array} config A button config or array of configs
\r
43180 * @return {Ext.Button/Array}
\r
43182 addButton : function(config){
\r
43183 if(Ext.isArray(config)){
\r
43184 var buttons = [];
\r
43185 for(var i = 0, len = config.length; i < len; i++) {
\r
43186 buttons.push(this.addButton(config[i]));
\r
43190 return this.add(this.constructButton(config));
\r
43194 * Adds text to the toolbar
\r
43195 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43196 * @param {String} text The text to add
\r
43197 * @return {Ext.Toolbar.Item} The element's item
\r
43199 addText : function(text){
\r
43200 return this.addItem(new T.TextItem(text));
\r
43204 * Adds a new element to the toolbar from the passed {@link Ext.DomHelper} config
\r
43205 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43206 * @param {Object} config
\r
43207 * @return {Ext.Toolbar.Item} The element's item
\r
43209 addDom : function(config){
\r
43210 return this.add(new T.Item({autoEl: config}));
\r
43214 * Adds a dynamically rendered Ext.form field (TextField, ComboBox, etc). Note: the field should not have
\r
43215 * been rendered yet. For a field that has already been rendered, use {@link #addElement}.
\r
43216 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43217 * @param {Ext.form.Field} field
\r
43218 * @return {Ext.Toolbar.Item}
\r
43220 addField : function(field){
\r
43221 return this.add(field);
\r
43225 * Inserts any {@link Ext.Toolbar.Item}/{@link Ext.Button} at the specified index.
\r
43226 * <br><p><b>Note</b>: See the notes within {@link Ext.Container#add}.</p>
\r
43227 * @param {Number} index The index where the item is to be inserted
\r
43228 * @param {Object/Ext.Toolbar.Item/Ext.Button/Array} item The button, or button config object to be
\r
43229 * inserted, or an array of buttons/configs.
\r
43230 * @return {Ext.Button/Item}
\r
43232 insertButton : function(index, item){
\r
43233 if(Ext.isArray(item)){
\r
43234 var buttons = [];
\r
43235 for(var i = 0, len = item.length; i < len; i++) {
\r
43236 buttons.push(this.insertButton(index + i, item[i]));
\r
43240 return Ext.Toolbar.superclass.insert.call(this, index, item);
\r
43244 initMenuTracking : function(item){
\r
43245 if(this.trackMenus && item.menu){
\r
43247 'menutriggerover' : this.onButtonTriggerOver,
\r
43248 'menushow' : this.onButtonMenuShow,
\r
43249 'menuhide' : this.onButtonMenuHide,
\r
43256 constructButton : function(item){
\r
43257 var b = item.events ? item : this.constructItem(item, item.split ? 'splitbutton' : this.defaultType);
\r
43258 this.initMenuTracking(b);
\r
43263 onDisable : function(){
\r
43264 this.items.each(function(item){
\r
43265 if(item.disable){
\r
43272 onEnable : function(){
\r
43273 this.items.each(function(item){
\r
43281 onButtonTriggerOver : function(btn){
\r
43282 if(this.activeMenuBtn && this.activeMenuBtn != btn){
\r
43283 this.activeMenuBtn.hideMenu();
\r
43285 this.activeMenuBtn = btn;
\r
43290 onButtonMenuShow : function(btn){
\r
43291 this.activeMenuBtn = btn;
\r
43295 onButtonMenuHide : function(btn){
\r
43296 delete this.activeMenuBtn;
\r
43299 Ext.reg('toolbar', Ext.Toolbar);
\r
43302 * @class Ext.Toolbar.Item
\r
43303 * @extends Ext.BoxComponent
\r
43304 * The base class that other non-interacting Toolbar Item classes should extend in order to
\r
43305 * get some basic common toolbar item functionality.
\r
43307 * Creates a new Item
\r
43308 * @param {HTMLElement} el
\r
43311 T.Item = Ext.extend(Ext.BoxComponent, {
\r
43312 hideParent: true, // Hiding a Toolbar.Item hides its containing TD
\r
43313 enable:Ext.emptyFn,
\r
43314 disable:Ext.emptyFn,
\r
43315 focus:Ext.emptyFn
\r
43317 * @cfg {String} overflowText Text to be used for the menu if the item is overflowed.
\r
43320 Ext.reg('tbitem', T.Item);
\r
43323 * @class Ext.Toolbar.Separator
\r
43324 * @extends Ext.Toolbar.Item
\r
43325 * A simple class that adds a vertical separator bar between toolbar items
\r
43326 * (css class:<tt>'xtb-sep'</tt>). Example usage:
\r
43331 {xtype: 'tbseparator'}, // or '-'
\r
43337 * Creates a new Separator
\r
43338 * @xtype tbseparator
\r
43340 T.Separator = Ext.extend(T.Item, {
\r
43341 onRender : function(ct, position){
\r
43342 this.el = ct.createChild({tag:'span', cls:'xtb-sep'}, position);
\r
43345 Ext.reg('tbseparator', T.Separator);
\r
43348 * @class Ext.Toolbar.Spacer
\r
43349 * @extends Ext.Toolbar.Item
\r
43350 * A simple element that adds extra horizontal space between items in a toolbar.
\r
43351 * By default a 2px wide space is added via css specification:<pre><code>
\r
43352 .x-toolbar .xtb-spacer {
\r
43356 * <p>Example usage:</p>
\r
43361 {xtype: 'tbspacer'}, // or ' '
\r
43363 // space width is also configurable via javascript
\r
43364 {xtype: 'tbspacer', width: 50}, // add a 50px space
\r
43370 * Creates a new Spacer
\r
43371 * @xtype tbspacer
\r
43373 T.Spacer = Ext.extend(T.Item, {
\r
43375 * @cfg {Number} width
\r
43376 * The width of the spacer in pixels (defaults to 2px via css style <tt>.x-toolbar .xtb-spacer</tt>).
\r
43379 onRender : function(ct, position){
\r
43380 this.el = ct.createChild({tag:'div', cls:'xtb-spacer', style: this.width?'width:'+this.width+'px':''}, position);
\r
43383 Ext.reg('tbspacer', T.Spacer);
\r
43386 * @class Ext.Toolbar.Fill
\r
43387 * @extends Ext.Toolbar.Spacer
\r
43388 * A non-rendering placeholder item which instructs the Toolbar's Layout to begin using
\r
43389 * the right-justified button container.
\r
43394 {xtype: 'tbfill'}, // or '->'
\r
43400 * Creates a new Fill
\r
43403 T.Fill = Ext.extend(T.Item, {
\r
43405 render : Ext.emptyFn,
\r
43408 Ext.reg('tbfill', T.Fill);
\r
43411 * @class Ext.Toolbar.TextItem
\r
43412 * @extends Ext.Toolbar.Item
\r
43413 * A simple class that renders text directly into a toolbar
\r
43414 * (with css class:<tt>'xtb-text'</tt>). Example usage:
\r
43418 {xtype: 'tbtext', text: 'Item 1'} // or simply 'Item 1'
\r
43423 * Creates a new TextItem
\r
43424 * @param {String/Object} text A text string, or a config object containing a <tt>text</tt> property
\r
43427 T.TextItem = Ext.extend(T.Item, {
\r
43429 * @cfg {String} text The text to be used as innerHTML (html tags are accepted)
\r
43432 constructor: function(config){
\r
43433 T.TextItem.superclass.constructor.call(this, Ext.isString(config) ? {text: config} : config);
\r
43437 onRender : function(ct, position) {
\r
43438 this.autoEl = {cls: 'xtb-text', html: this.text || ''};
\r
43439 T.TextItem.superclass.onRender.call(this, ct, position);
\r
43443 * Updates this item's text, setting the text to be used as innerHTML.
\r
43444 * @param {String} t The text to display (html accepted).
\r
43446 setText : function(t) {
\r
43447 if(this.rendered){
\r
43448 this.el.update(t);
\r
43454 Ext.reg('tbtext', T.TextItem);
\r
43456 // backwards compat
\r
43457 T.Button = Ext.extend(Ext.Button, {});
\r
43458 T.SplitButton = Ext.extend(Ext.SplitButton, {});
\r
43459 Ext.reg('tbbutton', T.Button);
\r
43460 Ext.reg('tbsplit', T.SplitButton);
\r
43464 * @class Ext.ButtonGroup
\r
43465 * @extends Ext.Panel
\r
43466 * Container for a group of buttons. Example usage:
\r
43468 var p = new Ext.Panel({
\r
43469 title: 'Panel with Button Group',
\r
43472 renderTo: document.body,
\r
43473 html: 'whatever',
\r
43475 xtype: 'buttongroup',
\r
43476 {@link #columns}: 3,
\r
43477 title: 'Clipboard',
\r
43481 rowspan: 3, iconCls: 'add',
\r
43482 iconAlign: 'top',
\r
43483 cls: 'x-btn-as-arrow'
\r
43485 xtype:'splitbutton',
\r
43486 text: 'Menu Button',
\r
43490 iconAlign: 'top',
\r
43491 arrowAlign:'bottom',
\r
43492 menu: [{text: 'Menu Item 1'}]
\r
43494 xtype:'splitbutton', text: 'Cut', iconCls: 'add16', menu: [{text: 'Cut Menu Item'}]
\r
43496 text: 'Copy', iconCls: 'add16'
\r
43498 text: 'Format', iconCls: 'add16'
\r
43503 * @xtype buttongroup
\r
43505 Ext.ButtonGroup = Ext.extend(Ext.Panel, {
\r
43507 * @cfg {Number} columns The <tt>columns</tt> configuration property passed to the
\r
43508 * {@link #layout configured layout manager}. See {@link Ext.layout.TableLayout#columns}.
\r
43511 * @cfg {String} baseCls Defaults to <tt>'x-btn-group'</tt>. See {@link Ext.Panel#baseCls}.
\r
43513 baseCls: 'x-btn-group',
\r
43515 * @cfg {String} layout Defaults to <tt>'table'</tt>. See {@link Ext.Container#layout}.
\r
43518 defaultType: 'button',
\r
43520 * @cfg {Boolean} frame Defaults to <tt>true</tt>. See {@link Ext.Panel#frame}.
\r
43523 internalDefaults: {removeMode: 'container', hideParent: true},
\r
43525 initComponent : function(){
\r
43526 this.layoutConfig = this.layoutConfig || {};
\r
43527 Ext.applyIf(this.layoutConfig, {
\r
43528 columns : this.columns
\r
43531 this.addClass('x-btn-group-notitle');
\r
43533 this.on('afterlayout', this.onAfterLayout, this);
\r
43534 Ext.ButtonGroup.superclass.initComponent.call(this);
\r
43537 applyDefaults : function(c){
\r
43538 c = Ext.ButtonGroup.superclass.applyDefaults.call(this, c);
\r
43539 var d = this.internalDefaults;
\r
43541 Ext.applyIf(c.initialConfig, d);
\r
43544 Ext.applyIf(c, d);
\r
43549 onAfterLayout : function(){
\r
43550 var bodyWidth = this.body.getFrameWidth('lr') + this.body.dom.firstChild.offsetWidth;
\r
43551 this.body.setWidth(bodyWidth);
\r
43552 this.el.setWidth(bodyWidth + this.getFrameWidth());
\r
43555 * @cfg {Array} tools @hide
\r
43559 Ext.reg('buttongroup', Ext.ButtonGroup);
\r
43561 * @class Ext.PagingToolbar
43562 * @extends Ext.Toolbar
43563 * <p>As the amount of records increases, the time required for the browser to render
43564 * them increases. Paging is used to reduce the amount of data exchanged with the client.
43565 * Note: if there are more records/rows than can be viewed in the available screen area, vertical
43566 * scrollbars will be added.</p>
43567 * <p>Paging is typically handled on the server side (see exception below). The client sends
43568 * parameters to the server side, which the server needs to interpret and then respond with the
43569 * approprate data.</p>
43570 * <p><b>Ext.PagingToolbar</b> is a specialized toolbar that is bound to a {@link Ext.data.Store}
43571 * and provides automatic paging control. This Component {@link Ext.data.Store#load load}s blocks
43572 * of data into the <tt>{@link #store}</tt> by passing {@link Ext.data.Store#paramNames paramNames} used for
43573 * paging criteria.</p>
43574 * <p>PagingToolbar is typically used as one of the Grid's toolbars:</p>
43576 Ext.QuickTips.init(); // to display button quicktips
43578 var myStore = new Ext.data.Store({
43579 reader: new Ext.data.JsonReader({
43580 {@link Ext.data.JsonReader#totalProperty totalProperty}: 'results',
43586 var myPageSize = 25; // server script should only send back 25 items at a time
43588 var grid = new Ext.grid.GridPanel({
43591 bbar: new Ext.PagingToolbar({
43592 {@link #store}: myStore, // grid and PagingToolbar using same store
43593 {@link #displayInfo}: true,
43594 {@link #pageSize}: myPageSize,
43595 {@link #prependButtons}: true,
43603 * <p>To use paging, pass the paging requirements to the server when the store is first loaded.</p>
43607 // specify params for the first page load if using paging
43616 * <p>If using {@link Ext.data.Store#autoLoad store's autoLoad} configuration:</p>
43618 var myStore = new Ext.data.Store({
43619 {@link Ext.data.Store#autoLoad autoLoad}: {params:{start: 0, limit: 25}},
43624 * <p>The packet sent back from the server would have this form:</p>
43629 "rows": [ // <b>*Note:</b> this must be an Array
43630 { "id": 1, "name": "Bill", "occupation": "Gardener" },
43631 { "id": 2, "name": "Ben", "occupation": "Horticulturalist" },
43633 { "id": 25, "name": "Sue", "occupation": "Botanist" }
43637 * <p><u>Paging with Local Data</u></p>
43638 * <p>Paging can also be accomplished with local data using extensions:</p>
43639 * <div class="mdetail-params"><ul>
43640 * <li><a href="http://extjs.com/forum/showthread.php?t=71532">Ext.ux.data.PagingStore</a></li>
43641 * <li>Paging Memory Proxy (examples/ux/PagingMemoryProxy.js)</li>
43643 * @constructor Create a new PagingToolbar
43644 * @param {Object} config The config object
43649 var T = Ext.Toolbar;
43651 Ext.PagingToolbar = Ext.extend(Ext.Toolbar, {
43653 * @cfg {Ext.data.Store} store
43654 * The {@link Ext.data.Store} the paging toolbar should use as its data source (required).
43657 * @cfg {Boolean} displayInfo
43658 * <tt>true</tt> to display the displayMsg (defaults to <tt>false</tt>)
43661 * @cfg {Number} pageSize
43662 * The number of records to display per page (defaults to <tt>20</tt>)
43666 * @cfg {Boolean} prependButtons
43667 * <tt>true</tt> to insert any configured <tt>items</tt> <i>before</i> the paging buttons.
43668 * Defaults to <tt>false</tt>.
43671 * @cfg {String} displayMsg
43672 * The paging status message to display (defaults to <tt>'Displaying {0} - {1} of {2}'</tt>).
43673 * Note that this string is formatted using the braced numbers <tt>{0}-{2}</tt> as tokens
43674 * that are replaced by the values for start, end and total respectively. These tokens should
43675 * be preserved when overriding this string if showing those values is desired.
43677 displayMsg : 'Displaying {0} - {1} of {2}',
43679 * @cfg {String} emptyMsg
43680 * The message to display when no records are found (defaults to 'No data to display')
43682 emptyMsg : 'No data to display',
43684 * @cfg {String} beforePageText
43685 * The text displayed before the input item (defaults to <tt>'Page'</tt>).
43687 beforePageText : 'Page',
43689 * @cfg {String} afterPageText
43690 * Customizable piece of the default paging text (defaults to <tt>'of {0}'</tt>). Note that
43691 * this string is formatted using <tt>{0}</tt> as a token that is replaced by the number of
43692 * total pages. This token should be preserved when overriding this string if showing the
43693 * total page count is desired.
43695 afterPageText : 'of {0}',
43697 * @cfg {String} firstText
43698 * The quicktip text displayed for the first page button (defaults to <tt>'First Page'</tt>).
43699 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
43701 firstText : 'First Page',
43703 * @cfg {String} prevText
43704 * The quicktip text displayed for the previous page button (defaults to <tt>'Previous Page'</tt>).
43705 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
43707 prevText : 'Previous Page',
43709 * @cfg {String} nextText
43710 * The quicktip text displayed for the next page button (defaults to <tt>'Next Page'</tt>).
43711 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
43713 nextText : 'Next Page',
43715 * @cfg {String} lastText
43716 * The quicktip text displayed for the last page button (defaults to <tt>'Last Page'</tt>).
43717 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
43719 lastText : 'Last Page',
43721 * @cfg {String} refreshText
43722 * The quicktip text displayed for the Refresh button (defaults to <tt>'Refresh'</tt>).
43723 * <b>Note</b>: quick tips must be initialized for the quicktip to show.
43725 refreshText : 'Refresh',
43728 * <p><b>Deprecated</b>. <code>paramNames</code> should be set in the <b>data store</b>
43729 * (see {@link Ext.data.Store#paramNames}).</p>
43730 * <br><p>Object mapping of parameter names used for load calls, initially set to:</p>
43731 * <pre>{start: 'start', limit: 'limit'}</pre>
43733 * @property paramNames
43738 * The number of records to display per page. See also <tt>{@link #cursor}</tt>.
43740 * @property pageSize
43744 * Indicator for the record position. This property might be used to get the active page
43745 * number for example:<pre><code>
43746 * // t is reference to the paging toolbar instance
43747 * var activePage = Math.ceil((t.cursor + t.pageSize) / t.pageSize);
43753 initComponent : function(){
43754 var pagingItems = [this.first = new T.Button({
43755 tooltip: this.firstText,
43756 overflowText: this.firstText,
43757 iconCls: 'x-tbar-page-first',
43759 handler: this.moveFirst,
43761 }), this.prev = new T.Button({
43762 tooltip: this.prevText,
43763 overflowText: this.prevText,
43764 iconCls: 'x-tbar-page-prev',
43766 handler: this.movePrevious,
43768 }), '-', this.beforePageText,
43769 this.inputItem = new Ext.form.NumberField({
43770 cls: 'x-tbar-page-number',
43771 allowDecimals: false,
43772 allowNegative: false,
43773 enableKeyEvents: true,
43774 selectOnFocus: true,
43777 keydown: this.onPagingKeyDown,
43778 blur: this.onPagingBlur
43780 }), this.afterTextItem = new T.TextItem({
43781 text: String.format(this.afterPageText, 1)
43782 }), '-', this.next = new T.Button({
43783 tooltip: this.nextText,
43784 overflowText: this.nextText,
43785 iconCls: 'x-tbar-page-next',
43787 handler: this.moveNext,
43789 }), this.last = new T.Button({
43790 tooltip: this.lastText,
43791 overflowText: this.lastText,
43792 iconCls: 'x-tbar-page-last',
43794 handler: this.moveLast,
43796 }), '-', this.refresh = new T.Button({
43797 tooltip: this.refreshText,
43798 overflowText: this.refreshText,
43799 iconCls: 'x-tbar-loading',
43800 handler: this.doRefresh,
43805 var userItems = this.items || this.buttons || [];
43806 if (this.prependButtons) {
43807 this.items = userItems.concat(pagingItems);
43809 this.items = pagingItems.concat(userItems);
43811 delete this.buttons;
43812 if(this.displayInfo){
43813 this.items.push('->');
43814 this.items.push(this.displayItem = new T.TextItem({}));
43816 Ext.PagingToolbar.superclass.initComponent.call(this);
43820 * Fires after the active page has been changed.
43821 * @param {Ext.PagingToolbar} this
43822 * @param {Object} pageData An object that has these properties:<ul>
43823 * <li><code>total</code> : Number <div class="sub-desc">The total number of records in the dataset as
43824 * returned by the server</div></li>
43825 * <li><code>activePage</code> : Number <div class="sub-desc">The current page number</div></li>
43826 * <li><code>pages</code> : Number <div class="sub-desc">The total number of pages (calculated from
43827 * the total number of records in the dataset as returned by the server and the current {@link #pageSize})</div></li>
43832 * @event beforechange
43833 * Fires just before the active page is changed.
43834 * Return false to prevent the active page from being changed.
43835 * @param {Ext.PagingToolbar} this
43836 * @param {Object} params An object hash of the parameters which the PagingToolbar will send when
43837 * loading the required page. This will contain:<ul>
43838 * <li><code>start</code> : Number <div class="sub-desc">The starting row number for the next page of records to
43839 * be retrieved from the server</div></li>
43840 * <li><code>limit</code> : Number <div class="sub-desc">The number of records to be retrieved from the server</div></li>
43842 * <p>(note: the names of the <b>start</b> and <b>limit</b> properties are determined
43843 * by the store's {@link Ext.data.Store#paramNames paramNames} property.)</p>
43844 * <p>Parameters may be added as required in the event handler.</p>
43848 this.on('afterlayout', this.onFirstLayout, this, {single: true});
43850 this.bindStore(this.store, true);
43854 onFirstLayout : function(){
43856 this.onLoad.apply(this, this.dsLoaded);
43861 updateInfo : function(){
43862 if(this.displayItem){
43863 var count = this.store.getCount();
43864 var msg = count == 0 ?
43868 this.cursor+1, this.cursor+count, this.store.getTotalCount()
43870 this.displayItem.setText(msg);
43875 onLoad : function(store, r, o){
43876 if(!this.rendered){
43877 this.dsLoaded = [store, r, o];
43880 var p = this.getParams();
43881 this.cursor = (o.params && o.params[p.start]) ? o.params[p.start] : 0;
43882 var d = this.getPageData(), ap = d.activePage, ps = d.pages;
43884 this.afterTextItem.setText(String.format(this.afterPageText, d.pages));
43885 this.inputItem.setValue(ap);
43886 this.first.setDisabled(ap == 1);
43887 this.prev.setDisabled(ap == 1);
43888 this.next.setDisabled(ap == ps);
43889 this.last.setDisabled(ap == ps);
43890 this.refresh.enable();
43892 this.fireEvent('change', this, d);
43896 getPageData : function(){
43897 var total = this.store.getTotalCount();
43900 activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize),
43901 pages : total < this.pageSize ? 1 : Math.ceil(total/this.pageSize)
43906 * Change the active page
43907 * @param {Integer} page The page to display
43909 changePage : function(page){
43910 this.doLoad(((page-1) * this.pageSize).constrain(0, this.store.getTotalCount()));
43914 onLoadError : function(){
43915 if(!this.rendered){
43918 this.refresh.enable();
43922 readPage : function(d){
43923 var v = this.inputItem.getValue(), pageNum;
43924 if (!v || isNaN(pageNum = parseInt(v, 10))) {
43925 this.inputItem.setValue(d.activePage);
43931 onPagingFocus : function(){
43932 this.inputItem.select();
43936 onPagingBlur : function(e){
43937 this.inputItem.setValue(this.getPageData().activePage);
43941 onPagingKeyDown : function(field, e){
43942 var k = e.getKey(), d = this.getPageData(), pageNum;
43943 if (k == e.RETURN) {
43945 pageNum = this.readPage(d);
43946 if(pageNum !== false){
43947 pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1;
43948 this.doLoad(pageNum * this.pageSize);
43950 }else if (k == e.HOME || k == e.END){
43952 pageNum = k == e.HOME ? 1 : d.pages;
43953 field.setValue(pageNum);
43954 }else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN){
43956 if((pageNum = this.readPage(d))){
43957 var increment = e.shiftKey ? 10 : 1;
43958 if(k == e.DOWN || k == e.PAGEDOWN){
43961 pageNum += increment;
43962 if(pageNum >= 1 & pageNum <= d.pages){
43963 field.setValue(pageNum);
43970 getParams : function(){
43971 //retain backwards compat, allow params on the toolbar itself, if they exist.
43972 return this.paramNames || this.store.paramNames;
43976 getParams : function(){
43977 //retain backwards compat, allow params on the toolbar itself, if they exist.
43978 return this.paramNames || this.store.paramNames;
43982 beforeLoad : function(){
43983 if(this.rendered && this.refresh){
43984 this.refresh.disable();
43989 doLoad : function(start){
43990 var o = {}, pn = this.getParams();
43991 o[pn.start] = start;
43992 o[pn.limit] = this.pageSize;
43993 if(this.fireEvent('beforechange', this, o) !== false){
43994 this.store.load({params:o});
43999 * Move to the first page, has the same effect as clicking the 'first' button.
44001 moveFirst : function(){
44006 * Move to the previous page, has the same effect as clicking the 'previous' button.
44008 movePrevious : function(){
44009 this.doLoad(Math.max(0, this.cursor-this.pageSize));
44013 * Move to the next page, has the same effect as clicking the 'next' button.
44015 moveNext : function(){
44016 this.doLoad(this.cursor+this.pageSize);
44020 * Move to the last page, has the same effect as clicking the 'last' button.
44022 moveLast : function(){
44023 var total = this.store.getTotalCount(),
44024 extra = total % this.pageSize;
44026 this.doLoad(extra ? (total - extra) : total - this.pageSize);
44030 * Refresh the current page, has the same effect as clicking the 'refresh' button.
44032 doRefresh : function(){
44033 this.doLoad(this.cursor);
44037 * Binds the paging toolbar to the specified {@link Ext.data.Store}
44038 * @param {Store} store The store to bind to this toolbar
44039 * @param {Boolean} initial (Optional) true to not remove listeners
44041 bindStore : function(store, initial){
44043 if(!initial && this.store){
44044 if(store !== this.store && this.store.autoDestroy){
44045 this.store.destroy();
44047 this.store.un('beforeload', this.beforeLoad, this);
44048 this.store.un('load', this.onLoad, this);
44049 this.store.un('exception', this.onLoadError, this);
44056 store = Ext.StoreMgr.lookup(store);
44059 beforeload: this.beforeLoad,
44061 exception: this.onLoadError
44065 this.store = store;
44067 this.onLoad(store, null, {});
44072 * Unbinds the paging toolbar from the specified {@link Ext.data.Store} <b>(deprecated)</b>
44073 * @param {Ext.data.Store} store The data store to unbind
44075 unbind : function(store){
44076 this.bindStore(null);
44080 * Binds the paging toolbar to the specified {@link Ext.data.Store} <b>(deprecated)</b>
44081 * @param {Ext.data.Store} store The data store to bind
44083 bind : function(store){
44084 this.bindStore(store);
44088 onDestroy : function(){
44089 this.bindStore(null);
44090 Ext.PagingToolbar.superclass.onDestroy.call(this);
44095 Ext.reg('paging', Ext.PagingToolbar);/**
\r
44096 * @class Ext.History
\r
44097 * @extends Ext.util.Observable
\r
44098 * History management component that allows you to register arbitrary tokens that signify application
\r
44099 * history state on navigation actions. You can then handle the history {@link #change} event in order
\r
44100 * to reset your application UI to the appropriate state when the user navigates forward or backward through
\r
44101 * the browser history stack.
\r
44104 Ext.History = (function () {
\r
44105 var iframe, hiddenField;
\r
44106 var ready = false;
\r
44107 var currentToken;
\r
44109 function getHash() {
\r
44110 var href = top.location.href, i = href.indexOf("#");
\r
44111 return i >= 0 ? href.substr(i + 1) : null;
\r
44114 function doSave() {
\r
44115 hiddenField.value = currentToken;
\r
44118 function handleStateChange(token) {
\r
44119 currentToken = token;
\r
44120 Ext.History.fireEvent('change', token);
\r
44123 function updateIFrame (token) {
\r
44124 var html = ['<html><body><div id="state">',token,'</div></body></html>'].join('');
\r
44126 var doc = iframe.contentWindow.document;
\r
44136 function checkIFrame() {
\r
44137 if (!iframe.contentWindow || !iframe.contentWindow.document) {
\r
44138 setTimeout(checkIFrame, 10);
\r
44142 var doc = iframe.contentWindow.document;
\r
44143 var elem = doc.getElementById("state");
\r
44144 var token = elem ? elem.innerText : null;
\r
44146 var hash = getHash();
\r
44148 setInterval(function () {
\r
44150 doc = iframe.contentWindow.document;
\r
44151 elem = doc.getElementById("state");
\r
44153 var newtoken = elem ? elem.innerText : null;
\r
44155 var newHash = getHash();
\r
44157 if (newtoken !== token) {
\r
44158 token = newtoken;
\r
44159 handleStateChange(token);
\r
44160 top.location.hash = token;
\r
44163 } else if (newHash !== hash) {
\r
44165 updateIFrame(newHash);
\r
44172 Ext.History.fireEvent('ready', Ext.History);
\r
44175 function startUp() {
\r
44176 currentToken = hiddenField.value ? hiddenField.value : getHash();
\r
44181 var hash = getHash();
\r
44182 setInterval(function () {
\r
44183 var newHash = getHash();
\r
44184 if (newHash !== hash) {
\r
44186 handleStateChange(hash);
\r
44191 Ext.History.fireEvent('ready', Ext.History);
\r
44197 * The id of the hidden field required for storing the current history token.
\r
44201 fieldId: 'x-history-field',
\r
44203 * The id of the iframe required by IE to manage the history stack.
\r
44207 iframeId: 'x-history-frame',
\r
44212 * Initialize the global History instance.
\r
44213 * @param {Boolean} onReady (optional) A callback function that will be called once the history
\r
44214 * component is fully initialized.
\r
44215 * @param {Object} scope (optional) The callback scope
\r
44217 init: function (onReady, scope) {
\r
44219 Ext.callback(onReady, scope, [this]);
\r
44222 if(!Ext.isReady){
\r
44223 Ext.onReady(function(){
\r
44224 Ext.History.init(onReady, scope);
\r
44228 hiddenField = Ext.getDom(Ext.History.fieldId);
\r
44230 iframe = Ext.getDom(Ext.History.iframeId);
\r
44232 this.addEvents('ready', 'change');
\r
44234 this.on('ready', onReady, scope, {single:true});
\r
44240 * Add a new token to the history stack. This can be any arbitrary value, although it would
\r
44241 * commonly be the concatenation of a component id and another id marking the specifc history
\r
44242 * state of that component. Example usage:
\r
44244 // Handle tab changes on a TabPanel
\r
44245 tabPanel.on('tabchange', function(tabPanel, tab){
\r
44246 Ext.History.add(tabPanel.id + ':' + tab.id);
\r
44249 * @param {String} token The value that defines a particular application-specific history state
\r
44250 * @param {Boolean} preventDuplicates When true, if the passed token matches the current token
\r
44251 * it will not save a new history step. Set to false if the same state can be saved more than once
\r
44252 * at the same history stack location (defaults to true).
\r
44254 add: function (token, preventDup) {
\r
44255 if(preventDup !== false){
\r
44256 if(this.getToken() == token){
\r
44261 return updateIFrame(token);
\r
44263 top.location.hash = token;
\r
44269 * Programmatically steps back one step in browser history (equivalent to the user pressing the Back button).
\r
44271 back: function(){
\r
44276 * Programmatically steps forward one step in browser history (equivalent to the user pressing the Forward button).
\r
44278 forward: function(){
\r
44283 * Retrieves the currently-active history token.
\r
44284 * @return {String} The token
\r
44286 getToken: function() {
\r
44287 return ready ? currentToken : getHash();
\r
44291 Ext.apply(Ext.History, new Ext.util.Observable());/**
\r
44293 * @extends Ext.Panel
\r
44295 * This is the base class for {@link Ext.QuickTip} and {@link Ext.Tooltip} that provides the basic layout and
\r
44296 * positioning that all tip-based classes require. This class can be used directly for simple, statically-positioned
\r
44297 * tips that are displayed programmatically, or it can be extended to provide custom tip implementations.
\r
44299 * Create a new Tip
\r
44300 * @param {Object} config The configuration options
\r
44302 Ext.Tip = Ext.extend(Ext.Panel, {
\r
44304 * @cfg {Boolean} closable True to render a close tool button into the tooltip header (defaults to false).
\r
44307 * @cfg {Number} width
\r
44308 * Width in pixels of the tip (defaults to auto). Width will be ignored if it exceeds the bounds of
\r
44309 * {@link #minWidth} or {@link #maxWidth}. The maximum supported value is 500.
\r
44312 * @cfg {Number} minWidth The minimum width of the tip in pixels (defaults to 40).
\r
44316 * @cfg {Number} maxWidth The maximum width of the tip in pixels (defaults to 300). The maximum supported value is 500.
\r
44320 * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
\r
44321 * for bottom-right shadow (defaults to "sides").
\r
44323 shadow : "sides",
\r
44325 * @cfg {String} defaultAlign <b>Experimental</b>. The default {@link Ext.Element#alignTo} anchor position value
\r
44326 * for this tip relative to its element of origin (defaults to "tl-bl?").
\r
44328 defaultAlign : "tl-bl?",
\r
44329 autoRender: true,
\r
44330 quickShowInterval : 250,
\r
44332 // private panel overrides
\r
44335 baseCls: 'x-tip',
\r
44336 floating:{shadow:true,shim:true,useDisplay:true,constrain:false},
\r
44339 closeAction: 'hide',
\r
44342 initComponent : function(){
\r
44343 Ext.Tip.superclass.initComponent.call(this);
\r
44344 if(this.closable && !this.title){
\r
44345 this.elements += ',header';
\r
44350 afterRender : function(){
\r
44351 Ext.Tip.superclass.afterRender.call(this);
\r
44352 if(this.closable){
\r
44355 handler: this[this.closeAction],
\r
44362 * Shows this tip at the specified XY position. Example usage:
\r
44364 // Show the tip at x:50 and y:100
\r
44365 tip.showAt([50,100]);
\r
44367 * @param {Array} xy An array containing the x and y coordinates
\r
44369 showAt : function(xy){
\r
44370 Ext.Tip.superclass.show.call(this);
\r
44371 if(this.measureWidth !== false && (!this.initialConfig || typeof this.initialConfig.width != 'number')){
\r
44372 this.doAutoWidth();
\r
44374 if(this.constrainPosition){
\r
44375 xy = this.el.adjustForConstraints(xy);
\r
44377 this.setPagePosition(xy[0], xy[1]);
\r
44381 doAutoWidth : function(){
\r
44382 var bw = this.body.getTextWidth();
\r
44384 bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
\r
44386 bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr");
\r
44387 this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
\r
44389 // IE7 repaint bug on initial show
\r
44390 if(Ext.isIE7 && !this.repainted){
\r
44391 this.el.repaint();
\r
44392 this.repainted = true;
\r
44397 * <b>Experimental</b>. Shows this tip at a position relative to another element using a standard {@link Ext.Element#alignTo}
\r
44398 * anchor position value. Example usage:
\r
44400 // Show the tip at the default position ('tl-br?')
\r
44401 tip.showBy('my-el');
\r
44403 // Show the tip's top-left corner anchored to the element's top-right corner
\r
44404 tip.showBy('my-el', 'tl-tr');
\r
44406 * @param {Mixed} el An HTMLElement, Ext.Element or string id of the target element to align to
\r
44407 * @param {String} position (optional) A valid {@link Ext.Element#alignTo} anchor position (defaults to 'tl-br?' or
\r
44408 * {@link #defaultAlign} if specified).
\r
44410 showBy : function(el, pos){
\r
44411 if(!this.rendered){
\r
44412 this.render(Ext.getBody());
\r
44414 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
\r
44417 initDraggable : function(){
\r
44418 this.dd = new Ext.Tip.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable);
\r
44419 this.header.addClass('x-tip-draggable');
\r
44423 Ext.reg('tip', Ext.Tip);
\r
44425 // private - custom Tip DD implementation
\r
44426 Ext.Tip.DD = function(tip, config){
\r
44427 Ext.apply(this, config);
\r
44429 Ext.Tip.DD.superclass.constructor.call(this, tip.el.id, 'WindowDD-'+tip.id);
\r
44430 this.setHandleElId(tip.header.id);
\r
44431 this.scroll = false;
\r
44434 Ext.extend(Ext.Tip.DD, Ext.dd.DD, {
\r
44437 headerOffsets:[100, 25],
\r
44438 startDrag : function(){
\r
44439 this.tip.el.disableShadow();
\r
44441 endDrag : function(e){
\r
44442 this.tip.el.enableShadow(true);
\r
44445 * @class Ext.ToolTip
\r
44446 * @extends Ext.Tip
\r
44447 * A standard tooltip implementation for providing additional information when hovering over a target element.
\r
44450 * Create a new Tooltip
\r
44451 * @param {Object} config The configuration options
\r
44453 Ext.ToolTip = Ext.extend(Ext.Tip, {
\r
44455 * When a Tooltip is configured with the <code>{@link #delegate}</code>
\r
44456 * option to cause selected child elements of the <code>{@link #target}</code>
\r
44457 * Element to each trigger a seperate show event, this property is set to
\r
44458 * the DOM element which triggered the show.
\r
44459 * @type DOMElement
\r
44460 * @property triggerElement
\r
44463 * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to monitor
\r
44464 * for mouseover events to trigger showing this ToolTip.
\r
44467 * @cfg {Boolean} autoHide True to automatically hide the tooltip after the
\r
44468 * mouse exits the target element or after the <code>{@link #dismissDelay}</code>
\r
44469 * has expired if set (defaults to true). If <code>{@link closable} = true</code>
\r
44470 * a close tool button will be rendered into the tooltip header.
\r
44473 * @cfg {Number} showDelay Delay in milliseconds before the tooltip displays
\r
44474 * after the mouse enters the target element (defaults to 500)
\r
44478 * @cfg {Number} hideDelay Delay in milliseconds after the mouse exits the
\r
44479 * target element but before the tooltip actually hides (defaults to 200).
\r
44480 * Set to 0 for the tooltip to hide immediately.
\r
44484 * @cfg {Number} dismissDelay Delay in milliseconds before the tooltip
\r
44485 * automatically hides (defaults to 5000). To disable automatic hiding, set
\r
44486 * dismissDelay = 0.
\r
44488 dismissDelay : 5000,
\r
44490 * @cfg {Array} mouseOffset An XY offset from the mouse position where the
\r
44491 * tooltip should be shown (defaults to [15,18]).
\r
44494 * @cfg {Boolean} trackMouse True to have the tooltip follow the mouse as it
\r
44495 * moves over the target element (defaults to false).
\r
44497 trackMouse : false,
\r
44499 * @cfg {Boolean} anchorToTarget True to anchor the tooltip to the target
\r
44500 * element, false to anchor it relative to the mouse coordinates (defaults
\r
44501 * to true). When <code>anchorToTarget</code> is true, use
\r
44502 * <code>{@link #defaultAlign}</code> to control tooltip alignment to the
\r
44503 * target element. When <code>anchorToTarget</code> is false, use
\r
44504 * <code>{@link #anchorPosition}</code> instead to control alignment.
\r
44506 anchorToTarget : true,
\r
44508 * @cfg {Number} anchorOffset A numeric pixel value used to offset the
\r
44509 * default position of the anchor arrow (defaults to 0). When the anchor
\r
44510 * position is on the top or bottom of the tooltip, <code>anchorOffset</code>
\r
44511 * will be used as a horizontal offset. Likewise, when the anchor position
\r
44512 * is on the left or right side, <code>anchorOffset</code> will be used as
\r
44513 * a vertical offset.
\r
44515 anchorOffset : 0,
\r
44517 * @cfg {String} delegate <p>Optional. A {@link Ext.DomQuery DomQuery}
\r
44518 * selector which allows selection of individual elements within the
\r
44519 * <code>{@link #target}</code> element to trigger showing and hiding the
\r
44520 * ToolTip as the mouse moves within the target.</p>
\r
44521 * <p>When specified, the child element of the target which caused a show
\r
44522 * event is placed into the <code>{@link #triggerElement}</code> property
\r
44523 * before the ToolTip is shown.</p>
\r
44524 * <p>This may be useful when a Component has regular, repeating elements
\r
44525 * in it, each of which need a Tooltip which contains information specific
\r
44526 * to that element. For example:</p><pre><code>
\r
44527 var myGrid = new Ext.grid.gridPanel(gridConfig);
\r
44528 myGrid.on('render', function(grid) {
\r
44529 var store = grid.getStore(); // Capture the Store.
\r
44530 var view = grid.getView(); // Capture the GridView.
\r
44531 myGrid.tip = new Ext.ToolTip({
\r
44532 target: view.mainBody, // The overall target element.
\r
44533 delegate: '.x-grid3-row', // Each grid row causes its own seperate show and hide.
\r
44534 trackMouse: true, // Moving within the row should not hide the tip.
\r
44535 renderTo: document.body, // Render immediately so that tip.body can be
\r
44536 // referenced prior to the first show.
\r
44537 listeners: { // Change content dynamically depending on which element
\r
44538 // triggered the show.
\r
44539 beforeshow: function updateTipBody(tip) {
\r
44540 var rowIndex = view.findRowIndex(tip.triggerElement);
\r
44541 tip.body.dom.innerHTML = 'Over Record ID ' + store.getAt(rowIndex).id;
\r
44550 targetCounter : 0,
\r
44552 constrainPosition : false,
\r
44555 initComponent : function(){
\r
44556 Ext.ToolTip.superclass.initComponent.call(this);
\r
44557 this.lastActive = new Date();
\r
44558 this.initTarget(this.target);
\r
44559 this.origAnchor = this.anchor;
\r
44563 onRender : function(ct, position){
\r
44564 Ext.ToolTip.superclass.onRender.call(this, ct, position);
\r
44565 this.anchorCls = 'x-tip-anchor-' + this.getAnchorPosition();
\r
44566 this.anchorEl = this.el.createChild({
\r
44567 cls: 'x-tip-anchor ' + this.anchorCls
\r
44572 afterRender : function(){
\r
44573 Ext.ToolTip.superclass.afterRender.call(this);
\r
44574 this.anchorEl.setStyle('z-index', this.el.getZIndex() + 1);
\r
44578 * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse moves over the element.
\r
44579 * @param {Mixed} t The Element, HtmlElement, or ID of an element to bind to
\r
44581 initTarget : function(target){
\r
44583 if((t = Ext.get(target))){
\r
44585 var tg = Ext.get(this.target);
\r
44586 this.mun(tg, 'mouseover', this.onTargetOver, this);
\r
44587 this.mun(tg, 'mouseout', this.onTargetOut, this);
\r
44588 this.mun(tg, 'mousemove', this.onMouseMove, this);
\r
44591 mouseover: this.onTargetOver,
\r
44592 mouseout: this.onTargetOut,
\r
44593 mousemove: this.onMouseMove,
\r
44599 this.anchorTarget = this.target;
\r
44604 onMouseMove : function(e){
\r
44605 var t = this.delegate ? e.getTarget(this.delegate) : this.triggerElement = true;
\r
44607 this.targetXY = e.getXY();
\r
44608 if (t === this.triggerElement) {
\r
44609 if(!this.hidden && this.trackMouse){
\r
44610 this.setPagePosition(this.getTargetXY());
\r
44614 this.lastActive = new Date(0);
\r
44615 this.onTargetOver(e);
\r
44617 } else if (!this.closable && this.isVisible()) {
\r
44623 getTargetXY : function(){
\r
44624 if(this.delegate){
\r
44625 this.anchorTarget = this.triggerElement;
\r
44628 this.targetCounter++;
\r
44629 var offsets = this.getOffsets();
\r
44630 var xy = (this.anchorToTarget && !this.trackMouse) ?
\r
44631 this.el.getAlignToXY(this.anchorTarget, this.getAnchorAlign()) :
\r
44634 var dw = Ext.lib.Dom.getViewWidth()-5;
\r
44635 var dh = Ext.lib.Dom.getViewHeight()-5;
\r
44636 var scrollX = (document.documentElement.scrollLeft || document.body.scrollLeft || 0)+5;
\r
44637 var scrollY = (document.documentElement.scrollTop || document.body.scrollTop || 0)+5;
\r
44639 var axy = [xy[0] + offsets[0], xy[1] + offsets[1]];
\r
44640 var sz = this.getSize();
\r
44641 this.anchorEl.removeClass(this.anchorCls);
\r
44643 if(this.targetCounter < 2){
\r
44644 if(axy[0] < scrollX){
\r
44645 if(this.anchorToTarget){
\r
44646 this.defaultAlign = 'l-r';
\r
44647 if(this.mouseOffset){this.mouseOffset[0] *= -1;}
\r
44649 this.anchor = 'left';
\r
44650 return this.getTargetXY();
\r
44652 if(axy[0]+sz.width > dw){
\r
44653 if(this.anchorToTarget){
\r
44654 this.defaultAlign = 'r-l';
\r
44655 if(this.mouseOffset){this.mouseOffset[0] *= -1;}
\r
44657 this.anchor = 'right';
\r
44658 return this.getTargetXY();
\r
44660 if(axy[1] < scrollY){
\r
44661 if(this.anchorToTarget){
\r
44662 this.defaultAlign = 't-b';
\r
44663 if(this.mouseOffset){this.mouseOffset[1] *= -1;}
\r
44665 this.anchor = 'top';
\r
44666 return this.getTargetXY();
\r
44668 if(axy[1]+sz.height > dh){
\r
44669 if(this.anchorToTarget){
\r
44670 this.defaultAlign = 'b-t';
\r
44671 if(this.mouseOffset){this.mouseOffset[1] *= -1;}
\r
44673 this.anchor = 'bottom';
\r
44674 return this.getTargetXY();
\r
44678 this.anchorCls = 'x-tip-anchor-'+this.getAnchorPosition();
\r
44679 this.anchorEl.addClass(this.anchorCls);
\r
44680 this.targetCounter = 0;
\r
44683 var mouseOffset = this.getMouseOffset();
\r
44684 return [this.targetXY[0]+mouseOffset[0], this.targetXY[1]+mouseOffset[1]];
\r
44688 getMouseOffset : function(){
\r
44689 var offset = this.anchor ? [0,0] : [15,18];
\r
44690 if(this.mouseOffset){
\r
44691 offset[0] += this.mouseOffset[0];
\r
44692 offset[1] += this.mouseOffset[1];
\r
44698 getAnchorPosition : function(){
\r
44700 this.tipAnchor = this.anchor.charAt(0);
\r
44702 var m = this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
\r
44704 throw 'AnchorTip.defaultAlign is invalid';
\r
44706 this.tipAnchor = m[1].charAt(0);
\r
44709 switch(this.tipAnchor){
\r
44710 case 't': return 'top';
\r
44711 case 'b': return 'bottom';
\r
44712 case 'r': return 'right';
\r
44718 getAnchorAlign : function(){
\r
44719 switch(this.anchor){
\r
44720 case 'top' : return 'tl-bl';
\r
44721 case 'left' : return 'tl-tr';
\r
44722 case 'right': return 'tr-tl';
\r
44723 default : return 'bl-tl';
\r
44728 getOffsets : function(){
\r
44729 var offsets, ap = this.getAnchorPosition().charAt(0);
\r
44730 if(this.anchorToTarget && !this.trackMouse){
\r
44733 offsets = [0, 9];
\r
44736 offsets = [0, -13];
\r
44739 offsets = [-13, 0];
\r
44742 offsets = [9, 0];
\r
44748 offsets = [-15-this.anchorOffset, 30];
\r
44751 offsets = [-19-this.anchorOffset, -13-this.el.dom.offsetHeight];
\r
44754 offsets = [-15-this.el.dom.offsetWidth, -13-this.anchorOffset];
\r
44757 offsets = [25, -13-this.anchorOffset];
\r
44761 var mouseOffset = this.getMouseOffset();
\r
44762 offsets[0] += mouseOffset[0];
\r
44763 offsets[1] += mouseOffset[1];
\r
44769 onTargetOver : function(e){
\r
44770 if(this.disabled || e.within(this.target.dom, true)){
\r
44773 var t = e.getTarget(this.delegate);
\r
44775 this.triggerElement = t;
\r
44776 this.clearTimer('hide');
\r
44777 this.targetXY = e.getXY();
\r
44778 this.delayShow();
\r
44783 delayShow : function(){
\r
44784 if(this.hidden && !this.showTimer){
\r
44785 if(this.lastActive.getElapsed() < this.quickShowInterval){
\r
44788 this.showTimer = this.show.defer(this.showDelay, this);
\r
44790 }else if(!this.hidden && this.autoHide !== false){
\r
44796 onTargetOut : function(e){
\r
44797 if(this.disabled || e.within(this.target.dom, true)){
\r
44800 this.clearTimer('show');
\r
44801 if(this.autoHide !== false){
\r
44802 this.delayHide();
\r
44807 delayHide : function(){
\r
44808 if(!this.hidden && !this.hideTimer){
\r
44809 this.hideTimer = this.hide.defer(this.hideDelay, this);
\r
44814 * Hides this tooltip if visible.
\r
44816 hide: function(){
\r
44817 this.clearTimer('dismiss');
\r
44818 this.lastActive = new Date();
\r
44819 if(this.anchorEl){
\r
44820 this.anchorEl.hide();
\r
44822 Ext.ToolTip.superclass.hide.call(this);
\r
44823 delete this.triggerElement;
\r
44827 * Shows this tooltip at the current event target XY position.
\r
44829 show : function(){
\r
44831 // pre-show it off screen so that the el will have dimensions
\r
44832 // for positioning calcs when getting xy next
\r
44833 this.showAt([-1000,-1000]);
\r
44834 this.origConstrainPosition = this.constrainPosition;
\r
44835 this.constrainPosition = false;
\r
44836 this.anchor = this.origAnchor;
\r
44838 this.showAt(this.getTargetXY());
\r
44841 this.syncAnchor();
\r
44842 this.anchorEl.show();
\r
44843 this.constrainPosition = this.origConstrainPosition;
\r
44845 this.anchorEl.hide();
\r
44850 showAt : function(xy){
\r
44851 this.lastActive = new Date();
\r
44852 this.clearTimers();
\r
44853 Ext.ToolTip.superclass.showAt.call(this, xy);
\r
44854 if(this.dismissDelay && this.autoHide !== false){
\r
44855 this.dismissTimer = this.hide.defer(this.dismissDelay, this);
\r
44857 if(this.anchor && !this.anchorEl.isVisible()){
\r
44858 this.syncAnchor();
\r
44859 this.anchorEl.show();
\r
44864 syncAnchor : function(){
\r
44865 var anchorPos, targetPos, offset;
\r
44866 switch(this.tipAnchor.charAt(0)){
\r
44869 targetPos = 'tl';
\r
44870 offset = [20+this.anchorOffset, 2];
\r
44874 targetPos = 'tr';
\r
44875 offset = [-2, 11+this.anchorOffset];
\r
44879 targetPos = 'bl';
\r
44880 offset = [20+this.anchorOffset, -2];
\r
44884 targetPos = 'tl';
\r
44885 offset = [2, 11+this.anchorOffset];
\r
44888 this.anchorEl.alignTo(this.el, anchorPos+'-'+targetPos, offset);
\r
44892 setPagePosition : function(x, y){
\r
44893 Ext.ToolTip.superclass.setPagePosition.call(this, x, y);
\r
44895 this.syncAnchor();
\r
44900 clearTimer : function(name){
\r
44901 name = name + 'Timer';
\r
44902 clearTimeout(this[name]);
\r
44903 delete this[name];
\r
44907 clearTimers : function(){
\r
44908 this.clearTimer('show');
\r
44909 this.clearTimer('dismiss');
\r
44910 this.clearTimer('hide');
\r
44914 onShow : function(){
\r
44915 Ext.ToolTip.superclass.onShow.call(this);
\r
44916 Ext.getDoc().on('mousedown', this.onDocMouseDown, this);
\r
44920 onHide : function(){
\r
44921 Ext.ToolTip.superclass.onHide.call(this);
\r
44922 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
\r
44926 onDocMouseDown : function(e){
\r
44927 if(this.autoHide !== true && !this.closable && !e.within(this.el.dom)){
\r
44929 this.enable.defer(100, this);
\r
44934 onDisable : function(){
\r
44935 this.clearTimers();
\r
44940 adjustPosition : function(x, y){
\r
44941 if(this.contstrainPosition){
\r
44942 var ay = this.targetXY[1], h = this.getSize().height;
\r
44943 if(y <= ay && (y+h) >= ay){
\r
44947 return {x : x, y: y};
\r
44951 onDestroy : function(){
\r
44952 Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
\r
44953 Ext.ToolTip.superclass.onDestroy.call(this);
\r
44957 Ext.reg('tooltip', Ext.ToolTip);/**
\r
44958 * @class Ext.QuickTip
\r
44959 * @extends Ext.ToolTip
\r
44960 * @xtype quicktip
\r
44961 * A specialized tooltip class for tooltips that can be specified in markup and automatically managed by the global
\r
44962 * {@link Ext.QuickTips} instance. See the QuickTips class header for additional usage details and examples.
\r
44964 * Create a new Tip
\r
44965 * @param {Object} config The configuration options
\r
44967 Ext.QuickTip = Ext.extend(Ext.ToolTip, {
\r
44969 * @cfg {Mixed} target The target HTMLElement, Ext.Element or id to associate with this quicktip (defaults to the document).
\r
44972 * @cfg {Boolean} interceptTitles True to automatically use the element's DOM title value if available (defaults to false).
\r
44974 interceptTitles : false,
\r
44978 namespace : "ext",
\r
44979 attribute : "qtip",
\r
44980 width : "qwidth",
\r
44981 target : "target",
\r
44982 title : "qtitle",
\r
44985 align : "qalign",
\r
44986 anchor : "anchor"
\r
44990 initComponent : function(){
\r
44991 this.target = this.target || Ext.getDoc();
\r
44992 this.targets = this.targets || {};
\r
44993 Ext.QuickTip.superclass.initComponent.call(this);
\r
44997 * Configures a new quick tip instance and assigns it to a target element. The following config values are
\r
44998 * supported (for example usage, see the {@link Ext.QuickTips} class header):
\r
44999 * <div class="mdetail-params"><ul>
\r
45000 * <li>autoHide</li>
\r
45002 * <li>dismissDelay (overrides the singleton value)</li>
\r
45003 * <li>target (required)</li>
\r
45004 * <li>text (required)</li>
\r
45006 * <li>width</li></ul></div>
\r
45007 * @param {Object} config The config object
\r
45009 register : function(config){
\r
45010 var cs = Ext.isArray(config) ? config : arguments;
\r
45011 for(var i = 0, len = cs.length; i < len; i++){
\r
45013 var target = c.target;
\r
45015 if(Ext.isArray(target)){
\r
45016 for(var j = 0, jlen = target.length; j < jlen; j++){
\r
45017 this.targets[Ext.id(target[j])] = c;
\r
45020 this.targets[Ext.id(target)] = c;
\r
45027 * Removes this quick tip from its element and destroys it.
\r
45028 * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
\r
45030 unregister : function(el){
\r
45031 delete this.targets[Ext.id(el)];
\r
45035 * Hides a visible tip or cancels an impending show for a particular element.
\r
45036 * @param {String/HTMLElement/Element} el The element that is the target of the tip.
\r
45038 cancelShow: function(el){
\r
45039 var at = this.activeTarget;
\r
45040 el = Ext.get(el).dom;
\r
45041 if(this.isVisible()){
\r
45042 if(at && at.el == el){
\r
45045 }else if(at && at.el == el){
\r
45046 this.clearTimer('show');
\r
45051 getTipCfg: function(e) {
\r
45052 var t = e.getTarget(),
\r
45055 if(this.interceptTitles && t.title){
\r
45058 t.removeAttribute("title");
\r
45059 e.preventDefault();
\r
45061 cfg = this.tagConfig;
\r
45062 ttp = t.qtip || Ext.fly(t).getAttribute(cfg.attribute, cfg.namespace);
\r
45068 onTargetOver : function(e){
\r
45069 if(this.disabled){
\r
45072 this.targetXY = e.getXY();
\r
45073 var t = e.getTarget();
\r
45074 if(!t || t.nodeType !== 1 || t == document || t == document.body){
\r
45077 if(this.activeTarget && ((t == this.activeTarget.el) || Ext.fly(this.activeTarget.el).contains(t))){
\r
45078 this.clearTimer('hide');
\r
45082 if(t && this.targets[t.id]){
\r
45083 this.activeTarget = this.targets[t.id];
\r
45084 this.activeTarget.el = t;
\r
45085 this.anchor = this.activeTarget.anchor;
\r
45087 this.anchorTarget = t;
\r
45089 this.delayShow();
\r
45092 var ttp, et = Ext.fly(t), cfg = this.tagConfig, ns = cfg.namespace;
\r
45093 if(ttp = this.getTipCfg(e)){
\r
45094 var autoHide = et.getAttribute(cfg.hide, ns);
\r
45095 this.activeTarget = {
\r
45098 width: et.getAttribute(cfg.width, ns),
\r
45099 autoHide: autoHide != "user" && autoHide !== 'false',
\r
45100 title: et.getAttribute(cfg.title, ns),
\r
45101 cls: et.getAttribute(cfg.cls, ns),
\r
45102 align: et.getAttribute(cfg.align, ns)
\r
45105 this.anchor = et.getAttribute(cfg.anchor, ns);
\r
45107 this.anchorTarget = t;
\r
45109 this.delayShow();
\r
45114 onTargetOut : function(e){
\r
45116 // If moving within the current target, and it does not have a new tip, ignore the mouseout
\r
45117 if (this.activeTarget && e.within(this.activeTarget.el) && !this.getTipCfg(e)) {
\r
45121 this.clearTimer('show');
\r
45122 if(this.autoHide !== false){
\r
45123 this.delayHide();
\r
45128 showAt : function(xy){
\r
45129 var t = this.activeTarget;
\r
45131 if(!this.rendered){
\r
45132 this.render(Ext.getBody());
\r
45133 this.activeTarget = t;
\r
45136 this.setWidth(t.width);
\r
45137 this.body.setWidth(this.adjustBodyWidth(t.width - this.getFrameWidth()));
\r
45138 this.measureWidth = false;
\r
45140 this.measureWidth = true;
\r
45142 this.setTitle(t.title || '');
\r
45143 this.body.update(t.text);
\r
45144 this.autoHide = t.autoHide;
\r
45145 this.dismissDelay = t.dismissDelay || this.dismissDelay;
\r
45146 if(this.lastCls){
\r
45147 this.el.removeClass(this.lastCls);
\r
45148 delete this.lastCls;
\r
45151 this.el.addClass(t.cls);
\r
45152 this.lastCls = t.cls;
\r
45155 this.constrainPosition = false;
\r
45156 }else if(t.align){ // TODO: this doesn't seem to work consistently
\r
45157 xy = this.el.getAlignToXY(t.el, t.align);
\r
45158 this.constrainPosition = false;
\r
45160 this.constrainPosition = true;
\r
45163 Ext.QuickTip.superclass.showAt.call(this, xy);
\r
45167 hide: function(){
\r
45168 delete this.activeTarget;
\r
45169 Ext.QuickTip.superclass.hide.call(this);
\r
45172 Ext.reg('quicktip', Ext.QuickTip);/**
\r
45173 * @class Ext.QuickTips
\r
45174 * <p>Provides attractive and customizable tooltips for any element. The QuickTips
\r
45175 * singleton is used to configure and manage tooltips globally for multiple elements
\r
45176 * in a generic manner. To create individual tooltips with maximum customizability,
\r
45177 * you should consider either {@link Ext.Tip} or {@link Ext.ToolTip}.</p>
\r
45178 * <p>Quicktips can be configured via tag attributes directly in markup, or by
\r
45179 * registering quick tips programmatically via the {@link #register} method.</p>
\r
45180 * <p>The singleton's instance of {@link Ext.QuickTip} is available via
\r
45181 * {@link #getQuickTip}, and supports all the methods, and all the all the
\r
45182 * configuration properties of Ext.QuickTip. These settings will apply to all
\r
45183 * tooltips shown by the singleton.</p>
\r
45184 * <p>Below is the summary of the configuration properties which can be used.
\r
45185 * For detailed descriptions see {@link #getQuickTip}</p>
\r
45186 * <p><b>QuickTips singleton configs (all are optional)</b></p>
\r
45187 * <div class="mdetail-params"><ul><li>dismissDelay</li>
\r
45188 * <li>hideDelay</li>
\r
45189 * <li>maxWidth</li>
\r
45190 * <li>minWidth</li>
\r
45191 * <li>showDelay</li>
\r
45192 * <li>trackMouse</li></ul></div>
\r
45193 * <p><b>Target element configs (optional unless otherwise noted)</b></p>
\r
45194 * <div class="mdetail-params"><ul><li>autoHide</li>
\r
45196 * <li>dismissDelay (overrides singleton value)</li>
\r
45197 * <li>target (required)</li>
\r
45198 * <li>text (required)</li>
\r
45200 * <li>width</li></ul></div>
\r
45201 * <p>Here is an example showing how some of these config options could be used:</p>
\r
45203 // Init the singleton. Any tag-based quick tips will start working.
\r
45204 Ext.QuickTips.init();
\r
45206 // Apply a set of config properties to the singleton
\r
45207 Ext.apply(Ext.QuickTips.getQuickTip(), {
\r
45214 // Manually register a quick tip for a specific element
\r
45215 Ext.QuickTips.register({
\r
45216 target: 'my-div',
\r
45217 title: 'My Tooltip',
\r
45218 text: 'This tooltip was added in code',
\r
45223 * <p>To register a quick tip in markup, you simply add one or more of the valid QuickTip attributes prefixed with
\r
45224 * the <b>ext:</b> namespace. The HTML element itself is automatically set as the quick tip target. Here is the summary
\r
45225 * of supported attributes (optional unless otherwise noted):</p>
\r
45226 * <ul><li><b>hide</b>: Specifying "user" is equivalent to setting autoHide = false. Any other value will be the
\r
45227 * same as autoHide = true.</li>
\r
45228 * <li><b>qclass</b>: A CSS class to be applied to the quick tip (equivalent to the 'cls' target element config).</li>
\r
45229 * <li><b>qtip (required)</b>: The quick tip text (equivalent to the 'text' target element config).</li>
\r
45230 * <li><b>qtitle</b>: The quick tip title (equivalent to the 'title' target element config).</li>
\r
45231 * <li><b>qwidth</b>: The quick tip width (equivalent to the 'width' target element config).</li></ul>
\r
45232 * <p>Here is an example of configuring an HTML element to display a tooltip from markup:</p>
\r
45234 // Add a quick tip to an HTML button
\r
45235 <input type="button" value="OK" ext:qtitle="OK Button" ext:qwidth="100"
\r
45236 ext:qtip="This is a quick tip from markup!"></input>
\r
45240 Ext.QuickTips = function(){
\r
45241 var tip, locks = [];
\r
45244 * Initialize the global QuickTips instance and prepare any quick tips.
\r
45245 * @param {Boolean} autoRender True to render the QuickTips container immediately to preload images. (Defaults to true)
\r
45247 init : function(autoRender){
\r
45249 if(!Ext.isReady){
\r
45250 Ext.onReady(function(){
\r
45251 Ext.QuickTips.init(autoRender);
\r
45255 tip = new Ext.QuickTip({elements:'header,body'});
\r
45256 if(autoRender !== false){
\r
45257 tip.render(Ext.getBody());
\r
45263 * Enable quick tips globally.
\r
45265 enable : function(){
\r
45268 if(locks.length < 1){
\r
45275 * Disable quick tips globally.
\r
45277 disable : function(){
\r
45285 * Returns true if quick tips are enabled, else false.
\r
45286 * @return {Boolean}
\r
45288 isEnabled : function(){
\r
45289 return tip !== undefined && !tip.disabled;
\r
45293 * Gets the global QuickTips instance.
\r
45295 getQuickTip : function(){
\r
45300 * Configures a new quick tip instance and assigns it to a target element. See
\r
45301 * {@link Ext.QuickTip#register} for details.
\r
45302 * @param {Object} config The config object
\r
45304 register : function(){
\r
45305 tip.register.apply(tip, arguments);
\r
45309 * Removes any registered quick tip from the target element and destroys it.
\r
45310 * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
\r
45312 unregister : function(){
\r
45313 tip.unregister.apply(tip, arguments);
\r
45317 * Alias of {@link #register}.
\r
45318 * @param {Object} config The config object
\r
45320 tips :function(){
\r
45321 tip.register.apply(tip, arguments);
\r
45325 * @class Ext.tree.TreePanel
\r
45326 * @extends Ext.Panel
\r
45327 * <p>The TreePanel provides tree-structured UI representation of tree-structured data.</p>
\r
45328 * <p>{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata
\r
45329 * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.</p>
\r
45330 * <p><b>A TreePanel must have a {@link #root} node before it is rendered.</b> This may either be
\r
45331 * specified using the {@link #root} config option, or using the {@link #setRootNode} method.
\r
45332 * <p>An example of tree rendered to an existing div:</p><pre><code>
\r
45333 var tree = new Ext.tree.TreePanel({
\r
45334 renderTo: 'tree-div',
\r
45336 autoScroll: true,
\r
45339 containerScroll: true,
\r
45341 // auto create TreeLoader
\r
45342 dataUrl: 'get-nodes.php',
\r
45345 nodeType: 'async',
\r
45347 draggable: false,
\r
45352 tree.getRootNode().expand();
\r
45354 * <p>The example above would work with a data packet similar to this:</p><pre><code>
\r
45356 "text": "adapter",
\r
45357 "id": "source\/adapter",
\r
45361 "id": "source\/dd",
\r
45364 "text": "debug.js",
\r
45365 "id": "source\/debug.js",
\r
45370 * <p>An example of tree within a Viewport:</p><pre><code>
\r
45371 new Ext.Viewport({
\r
45372 layout: 'border',
\r
45375 collapsible: true,
\r
45376 title: 'Navigation',
\r
45377 xtype: 'treepanel',
\r
45379 autoScroll: true,
\r
45381 loader: new Ext.tree.TreeLoader(),
\r
45382 root: new Ext.tree.AsyncTreeNode({
\r
45385 text: 'Menu Option 1',
\r
45388 text: 'Menu Option 2',
\r
45391 text: 'Menu Option 3',
\r
45395 rootVisible: false,
\r
45397 click: function(n) {
\r
45398 Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');
\r
45402 region: 'center',
\r
45403 xtype: 'tabpanel',
\r
45404 // remaining code not shown ...
\r
45409 * @cfg {Ext.tree.TreeNode} root The root node for the tree.
\r
45410 * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)
\r
45411 * @cfg {Boolean} lines <tt>false</tt> to disable tree lines (defaults to <tt>true</tt>)
\r
45412 * @cfg {Boolean} enableDD <tt>true</tt> to enable drag and drop
\r
45413 * @cfg {Boolean} enableDrag <tt>true</tt> to enable just drag
\r
45414 * @cfg {Boolean} enableDrop <tt>true</tt> to enable just drop
\r
45415 * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance
\r
45416 * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance
\r
45417 * @cfg {String} ddGroup The DD group this TreePanel belongs to
\r
45418 * @cfg {Boolean} ddAppendOnly <tt>true</tt> if the tree should only allow append drops (use for trees which are sorted)
\r
45419 * @cfg {Boolean} ddScroll <tt>true</tt> to enable body scrolling
\r
45420 * @cfg {Boolean} containerScroll <tt>true</tt> to register this container with ScrollManager
\r
45421 * @cfg {Boolean} hlDrop <tt>false</tt> to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})
\r
45422 * @cfg {String} hlColor The color of the node highlight (defaults to <tt>'C3DAF9'</tt>)
\r
45423 * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})
\r
45424 * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded
\r
45425 * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})
\r
45426 * @cfg {Boolean} trackMouseOver <tt>false</tt> to disable mouse over highlighting
\r
45427 * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel
\r
45428 * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to <tt>'/'</tt>)
\r
45429 * @cfg {Boolean} useArrows <tt>true</tt> to use Vista-style arrows in the tree (defaults to <tt>false</tt>)
\r
45430 * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
\r
45433 * @param {Object} config
\r
45434 * @xtype treepanel
\r
45436 Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
\r
45437 rootVisible : true,
\r
45438 animate: Ext.enableFx,
\r
45440 enableDD : false,
\r
45441 hlDrop : Ext.enableFx,
\r
45442 pathSeparator: "/",
\r
45445 * @cfg {Array} bubbleEvents
\r
45446 * <p>An array of events that, when fired, should be bubbled to any parent container.
\r
45447 * Defaults to <tt>['add', 'remove']</tt>.
\r
45449 bubbleEvents: [],
\r
45451 initComponent : function(){
\r
45452 Ext.tree.TreePanel.superclass.initComponent.call(this);
\r
45454 if(!this.eventModel){
\r
45455 this.eventModel = new Ext.tree.TreeEventModel(this);
\r
45458 // initialize the loader
\r
45459 var l = this.loader;
\r
45461 l = new Ext.tree.TreeLoader({
\r
45462 dataUrl: this.dataUrl,
\r
45463 requestMethod: this.requestMethod
\r
45465 }else if(typeof l == 'object' && !l.load){
\r
45466 l = new Ext.tree.TreeLoader(l);
\r
45470 this.nodeHash = {};
\r
45473 * The root node of this tree.
\r
45474 * @type Ext.tree.TreeNode
\r
45478 var r = this.root;
\r
45479 delete this.root;
\r
45480 this.setRootNode(r);
\r
45488 * Fires when a new child node is appended to a node in this tree.
\r
45489 * @param {Tree} tree The owner tree
\r
45490 * @param {Node} parent The parent node
\r
45491 * @param {Node} node The newly appended node
\r
45492 * @param {Number} index The index of the newly appended node
\r
45497 * Fires when a child node is removed from a node in this tree.
\r
45498 * @param {Tree} tree The owner tree
\r
45499 * @param {Node} parent The parent node
\r
45500 * @param {Node} node The child node removed
\r
45504 * @event movenode
\r
45505 * Fires when a node is moved to a new location in the tree
\r
45506 * @param {Tree} tree The owner tree
\r
45507 * @param {Node} node The node moved
\r
45508 * @param {Node} oldParent The old parent of this node
\r
45509 * @param {Node} newParent The new parent of this node
\r
45510 * @param {Number} index The index it was moved to
\r
45515 * Fires when a new child node is inserted in a node in this tree.
\r
45516 * @param {Tree} tree The owner tree
\r
45517 * @param {Node} parent The parent node
\r
45518 * @param {Node} node The child node inserted
\r
45519 * @param {Node} refNode The child node the node was inserted before
\r
45523 * @event beforeappend
\r
45524 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
\r
45525 * @param {Tree} tree The owner tree
\r
45526 * @param {Node} parent The parent node
\r
45527 * @param {Node} node The child node to be appended
\r
45531 * @event beforeremove
\r
45532 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
\r
45533 * @param {Tree} tree The owner tree
\r
45534 * @param {Node} parent The parent node
\r
45535 * @param {Node} node The child node to be removed
\r
45539 * @event beforemovenode
\r
45540 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
\r
45541 * @param {Tree} tree The owner tree
\r
45542 * @param {Node} node The node being moved
\r
45543 * @param {Node} oldParent The parent of the node
\r
45544 * @param {Node} newParent The new parent the node is moving to
\r
45545 * @param {Number} index The index it is being moved to
\r
45547 "beforemovenode",
\r
45549 * @event beforeinsert
\r
45550 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
\r
45551 * @param {Tree} tree The owner tree
\r
45552 * @param {Node} parent The parent node
\r
45553 * @param {Node} node The child node to be inserted
\r
45554 * @param {Node} refNode The child node the node is being inserted before
\r
45559 * @event beforeload
\r
45560 * Fires before a node is loaded, return false to cancel
\r
45561 * @param {Node} node The node being loaded
\r
45566 * Fires when a node is loaded
\r
45567 * @param {Node} node The node that was loaded
\r
45571 * @event textchange
\r
45572 * Fires when the text for a node is changed
\r
45573 * @param {Node} node The node
\r
45574 * @param {String} text The new text
\r
45575 * @param {String} oldText The old text
\r
45579 * @event beforeexpandnode
\r
45580 * Fires before a node is expanded, return false to cancel.
\r
45581 * @param {Node} node The node
\r
45582 * @param {Boolean} deep
\r
45583 * @param {Boolean} anim
\r
45585 "beforeexpandnode",
\r
45587 * @event beforecollapsenode
\r
45588 * Fires before a node is collapsed, return false to cancel.
\r
45589 * @param {Node} node The node
\r
45590 * @param {Boolean} deep
\r
45591 * @param {Boolean} anim
\r
45593 "beforecollapsenode",
\r
45595 * @event expandnode
\r
45596 * Fires when a node is expanded
\r
45597 * @param {Node} node The node
\r
45601 * @event disabledchange
\r
45602 * Fires when the disabled status of a node changes
\r
45603 * @param {Node} node The node
\r
45604 * @param {Boolean} disabled
\r
45606 "disabledchange",
\r
45608 * @event collapsenode
\r
45609 * Fires when a node is collapsed
\r
45610 * @param {Node} node The node
\r
45614 * @event beforeclick
\r
45615 * Fires before click processing on a node. Return false to cancel the default action.
\r
45616 * @param {Node} node The node
\r
45617 * @param {Ext.EventObject} e The event object
\r
45622 * Fires when a node is clicked
\r
45623 * @param {Node} node The node
\r
45624 * @param {Ext.EventObject} e The event object
\r
45628 * @event checkchange
\r
45629 * Fires when a node with a checkbox's checked property changes
\r
45630 * @param {Node} this This node
\r
45631 * @param {Boolean} checked
\r
45635 * @event beforedblclick
\r
45636 * Fires before double click processing on a node. Return false to cancel the default action.
\r
45637 * @param {Node} node The node
\r
45638 * @param {Ext.EventObject} e The event object
\r
45640 "beforedblclick",
\r
45642 * @event dblclick
\r
45643 * Fires when a node is double clicked
\r
45644 * @param {Node} node The node
\r
45645 * @param {Ext.EventObject} e The event object
\r
45649 * @event contextmenu
\r
45650 * Fires when a node is right clicked. To display a context menu in response to this
\r
45651 * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add
\r
45652 * a handler for this event:<pre><code>
\r
45653 new Ext.tree.TreePanel({
\r
45654 title: 'My TreePanel',
\r
45655 root: new Ext.tree.AsyncTreeNode({
\r
45656 text: 'The Root',
\r
45658 { text: 'Child node 1', leaf: true },
\r
45659 { text: 'Child node 2', leaf: true }
\r
45662 contextMenu: new Ext.menu.Menu({
\r
45664 id: 'delete-node',
\r
45665 text: 'Delete Node'
\r
45668 itemclick: function(item) {
\r
45669 switch (item.id) {
\r
45670 case 'delete-node':
\r
45671 var n = item.parentMenu.contextNode;
\r
45672 if (n.parentNode) {
\r
45681 contextmenu: function(node, e) {
\r
45682 // Register the context node with the menu so that a Menu Item's handler function can access
\r
45683 // it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.
\r
45685 var c = node.getOwnerTree().contextMenu;
\r
45686 c.contextNode = node;
\r
45687 c.showAt(e.getXY());
\r
45692 * @param {Node} node The node
\r
45693 * @param {Ext.EventObject} e The event object
\r
45697 * @event beforechildrenrendered
\r
45698 * Fires right before the child nodes for a node are rendered
\r
45699 * @param {Node} node The node
\r
45701 "beforechildrenrendered",
\r
45703 * @event startdrag
\r
45704 * Fires when a node starts being dragged
\r
45705 * @param {Ext.tree.TreePanel} this
\r
45706 * @param {Ext.tree.TreeNode} node
\r
45707 * @param {event} e The raw browser event
\r
45712 * Fires when a drag operation is complete
\r
45713 * @param {Ext.tree.TreePanel} this
\r
45714 * @param {Ext.tree.TreeNode} node
\r
45715 * @param {event} e The raw browser event
\r
45719 * @event dragdrop
\r
45720 * Fires when a dragged node is dropped on a valid DD target
\r
45721 * @param {Ext.tree.TreePanel} this
\r
45722 * @param {Ext.tree.TreeNode} node
\r
45723 * @param {DD} dd The dd it was dropped on
\r
45724 * @param {event} e The raw browser event
\r
45728 * @event beforenodedrop
\r
45729 * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
\r
45730 * passed to handlers has the following properties:<br />
\r
45731 * <ul style="padding:5px;padding-left:16px;">
\r
45732 * <li>tree - The TreePanel</li>
\r
45733 * <li>target - The node being targeted for the drop</li>
\r
45734 * <li>data - The drag data from the drag source</li>
\r
45735 * <li>point - The point of the drop - append, above or below</li>
\r
45736 * <li>source - The drag source</li>
\r
45737 * <li>rawEvent - Raw mouse event</li>
\r
45738 * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
\r
45739 * to be inserted by setting them on this object.</li>
\r
45740 * <li>cancel - Set this to true to cancel the drop.</li>
\r
45741 * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true
\r
45742 * will prevent the animated "repair" from appearing.</li>
\r
45744 * @param {Object} dropEvent
\r
45746 "beforenodedrop",
\r
45748 * @event nodedrop
\r
45749 * Fires after a DD object is dropped on a node in this tree. The dropEvent
\r
45750 * passed to handlers has the following properties:<br />
\r
45751 * <ul style="padding:5px;padding-left:16px;">
\r
45752 * <li>tree - The TreePanel</li>
\r
45753 * <li>target - The node being targeted for the drop</li>
\r
45754 * <li>data - The drag data from the drag source</li>
\r
45755 * <li>point - The point of the drop - append, above or below</li>
\r
45756 * <li>source - The drag source</li>
\r
45757 * <li>rawEvent - Raw mouse event</li>
\r
45758 * <li>dropNode - Dropped node(s).</li>
\r
45760 * @param {Object} dropEvent
\r
45764 * @event nodedragover
\r
45765 * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent
\r
45766 * passed to handlers has the following properties:<br />
\r
45767 * <ul style="padding:5px;padding-left:16px;">
\r
45768 * <li>tree - The TreePanel</li>
\r
45769 * <li>target - The node being targeted for the drop</li>
\r
45770 * <li>data - The drag data from the drag source</li>
\r
45771 * <li>point - The point of the drop - append, above or below</li>
\r
45772 * <li>source - The drag source</li>
\r
45773 * <li>rawEvent - Raw mouse event</li>
\r
45774 * <li>dropNode - Drop node(s) provided by the source.</li>
\r
45775 * <li>cancel - Set this to true to signal drop not allowed.</li>
\r
45777 * @param {Object} dragOverEvent
\r
45781 if(this.singleExpand){
\r
45782 this.on("beforeexpandnode", this.restrictExpand, this);
\r
45787 proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){
\r
45788 if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){
\r
45789 ename = ename+'node';
\r
45791 // args inline for performance while bubbling events
\r
45792 return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);
\r
45797 * Returns this root node for this tree
\r
45800 getRootNode : function(){
\r
45801 return this.root;
\r
45805 * Sets the root node for this tree. If the TreePanel has already rendered a root node, the
\r
45806 * previous root node (and all of its descendants) are destroyed before the new root node is rendered.
\r
45807 * @param {Node} node
\r
45810 setRootNode : function(node){
\r
45811 Ext.destroy(this.root);
\r
45812 if(!node.render){ // attributes passed
\r
45813 node = this.loader.createNode(node);
\r
45815 this.root = node;
\r
45816 node.ownerTree = this;
\r
45817 node.isRoot = true;
\r
45818 this.registerNode(node);
\r
45819 if(!this.rootVisible){
\r
45820 var uiP = node.attributes.uiProvider;
\r
45821 node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);
\r
45823 if (this.innerCt) {
\r
45824 this.innerCt.update('');
\r
45825 this.afterRender();
\r
45831 * Gets a node in this tree by its id
\r
45832 * @param {String} id
\r
45835 getNodeById : function(id){
\r
45836 return this.nodeHash[id];
\r
45840 registerNode : function(node){
\r
45841 this.nodeHash[node.id] = node;
\r
45845 unregisterNode : function(node){
\r
45846 delete this.nodeHash[node.id];
\r
45850 toString : function(){
\r
45851 return "[Tree"+(this.id?" "+this.id:"")+"]";
\r
45855 restrictExpand : function(node){
\r
45856 var p = node.parentNode;
\r
45858 if(p.expandedChild && p.expandedChild.parentNode == p){
\r
45859 p.expandedChild.collapse();
\r
45861 p.expandedChild = node;
\r
45866 * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. "id")
\r
45867 * @param {String} attribute (optional) Defaults to null (return the actual nodes)
\r
45868 * @param {TreeNode} startNode (optional) The node to start from, defaults to the root
\r
45869 * @return {Array}
\r
45871 getChecked : function(a, startNode){
\r
45872 startNode = startNode || this.root;
\r
45874 var f = function(){
\r
45875 if(this.attributes.checked){
\r
45876 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
\r
45879 startNode.cascade(f);
\r
45884 * Returns the container element for this TreePanel.
\r
45885 * @return {Element} The container element for this TreePanel.
\r
45887 getEl : function(){
\r
45892 * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.
\r
45893 * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.
\r
45895 getLoader : function(){
\r
45896 return this.loader;
\r
45900 * Expand all nodes
\r
45902 expandAll : function(){
\r
45903 this.root.expand(true);
\r
45907 * Collapse all nodes
\r
45909 collapseAll : function(){
\r
45910 this.root.collapse(true);
\r
45914 * Returns the selection model used by this TreePanel.
\r
45915 * @return {TreeSelectionModel} The selection model used by this TreePanel
\r
45917 getSelectionModel : function(){
\r
45918 if(!this.selModel){
\r
45919 this.selModel = new Ext.tree.DefaultSelectionModel();
\r
45921 return this.selModel;
\r
45925 * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
\r
45926 * @param {String} path
\r
45927 * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
\r
45928 * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
\r
45929 * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
\r
45931 expandPath : function(path, attr, callback){
\r
45932 attr = attr || "id";
\r
45933 var keys = path.split(this.pathSeparator);
\r
45934 var curNode = this.root;
\r
45935 if(curNode.attributes[attr] != keys[1]){ // invalid root
\r
45937 callback(false, null);
\r
45942 var f = function(){
\r
45943 if(++index == keys.length){
\r
45945 callback(true, curNode);
\r
45949 var c = curNode.findChild(attr, keys[index]);
\r
45952 callback(false, curNode);
\r
45957 c.expand(false, false, f);
\r
45959 curNode.expand(false, false, f);
\r
45963 * 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
45964 * @param {String} path
\r
45965 * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
\r
45966 * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
\r
45967 * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
\r
45969 selectPath : function(path, attr, callback){
\r
45970 attr = attr || "id";
\r
45971 var keys = path.split(this.pathSeparator),
\r
45973 if(keys.length > 1){
\r
45974 var f = function(success, node){
\r
45975 if(success && node){
\r
45976 var n = node.findChild(attr, v);
\r
45980 callback(true, n);
\r
45982 }else if(callback){
\r
45983 callback(false, n);
\r
45987 callback(false, n);
\r
45991 this.expandPath(keys.join(this.pathSeparator), attr, f);
\r
45993 this.root.select();
\r
45995 callback(true, this.root);
\r
46001 * Returns the underlying Element for this tree
\r
46002 * @return {Ext.Element} The Element
\r
46004 getTreeEl : function(){
\r
46005 return this.body;
\r
46009 onRender : function(ct, position){
\r
46010 Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);
\r
46011 this.el.addClass('x-tree');
\r
46012 this.innerCt = this.body.createChild({tag:"ul",
\r
46013 cls:"x-tree-root-ct " +
\r
46014 (this.useArrows ? 'x-tree-arrows' : this.lines ? "x-tree-lines" : "x-tree-no-lines")});
\r
46018 initEvents : function(){
\r
46019 Ext.tree.TreePanel.superclass.initEvents.call(this);
\r
46021 if(this.containerScroll){
\r
46022 Ext.dd.ScrollManager.register(this.body);
\r
46024 if((this.enableDD || this.enableDrop) && !this.dropZone){
\r
46026 * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})
\r
46027 * @property dropZone
\r
46028 * @type Ext.tree.TreeDropZone
\r
46030 this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {
\r
46031 ddGroup: this.ddGroup || "TreeDD", appendOnly: this.ddAppendOnly === true
\r
46034 if((this.enableDD || this.enableDrag) && !this.dragZone){
\r
46036 * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})
\r
46037 * @property dragZone
\r
46038 * @type Ext.tree.TreeDragZone
\r
46040 this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {
\r
46041 ddGroup: this.ddGroup || "TreeDD",
\r
46042 scroll: this.ddScroll
\r
46045 this.getSelectionModel().init(this);
\r
46049 afterRender : function(){
\r
46050 Ext.tree.TreePanel.superclass.afterRender.call(this);
\r
46051 this.root.render();
\r
46052 if(!this.rootVisible){
\r
46053 this.root.renderChildren();
\r
46057 onDestroy : function(){
\r
46058 if(this.rendered){
\r
46059 this.body.removeAllListeners();
\r
46060 Ext.dd.ScrollManager.unregister(this.body);
\r
46061 if(this.dropZone){
\r
46062 this.dropZone.unreg();
\r
46064 if(this.dragZone){
\r
46065 this.dragZone.unreg();
\r
46068 this.root.destroy();
\r
46069 this.nodeHash = null;
\r
46070 Ext.tree.TreePanel.superclass.onDestroy.call(this);
\r
46074 * @cfg {String/Number} activeItem
\r
46078 * @cfg {Boolean} autoDestroy
\r
46082 * @cfg {Object/String/Function} autoLoad
\r
46086 * @cfg {Boolean} autoWidth
\r
46090 * @cfg {Boolean/Number} bufferResize
\r
46094 * @cfg {String} defaultType
\r
46098 * @cfg {Object} defaults
\r
46102 * @cfg {Boolean} hideBorders
\r
46106 * @cfg {Mixed} items
\r
46110 * @cfg {String} layout
\r
46114 * @cfg {Object} layoutConfig
\r
46118 * @cfg {Boolean} monitorResize
\r
46122 * @property items
\r
46126 * @method cascade
\r
46130 * @method doLayout
\r
46142 * @method findById
\r
46146 * @method findByType
\r
46150 * @method getComponent
\r
46154 * @method getLayout
\r
46158 * @method getUpdater
\r
46178 * @method removeAll
\r
46182 * @event afterLayout
\r
46186 * @event beforeadd
\r
46190 * @event beforeremove
\r
46201 * @cfg {String} allowDomMove @hide
\r
46204 * @cfg {String} autoEl @hide
\r
46207 * @cfg {String} applyTo @hide
\r
46210 * @cfg {String} contentEl @hide
\r
46213 * @cfg {String} disabledClass @hide
\r
46216 * @cfg {String} elements @hide
\r
46219 * @cfg {String} html @hide
\r
46222 * @cfg {Boolean} preventBodyReset
\r
46226 * @property disabled
\r
46230 * @method applyToMarkup
\r
46238 * @method disable
\r
46242 * @method setDisabled
\r
46247 Ext.tree.TreePanel.nodeTypes = {};
\r
46249 Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
\r
46250 this.tree = tree;
\r
46251 this.tree.on('render', this.initEvents, this);
\r
46254 Ext.tree.TreeEventModel.prototype = {
\r
46255 initEvents : function(){
\r
46256 var t = this.tree;
\r
46258 if(t.trackMouseOver !== false){
\r
46259 t.mon(t.innerCt, {
\r
46261 mouseover: this.delegateOver,
\r
46262 mouseout: this.delegateOut
\r
46265 t.mon(t.getTreeEl(), {
\r
46267 click: this.delegateClick,
\r
46268 dblclick: this.delegateDblClick,
\r
46269 contextmenu: this.delegateContextMenu
\r
46273 getNode : function(e){
\r
46275 if(t = e.getTarget('.x-tree-node-el', 10)){
\r
46276 var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');
\r
46278 return this.tree.getNodeById(id);
\r
46284 getNodeTarget : function(e){
\r
46285 var t = e.getTarget('.x-tree-node-icon', 1);
\r
46287 t = e.getTarget('.x-tree-node-el', 6);
\r
46292 delegateOut : function(e, t){
\r
46293 if(!this.beforeEvent(e)){
\r
46296 if(e.getTarget('.x-tree-ec-icon', 1)){
\r
46297 var n = this.getNode(e);
\r
46298 this.onIconOut(e, n);
\r
46299 if(n == this.lastEcOver){
\r
46300 delete this.lastEcOver;
\r
46303 if((t = this.getNodeTarget(e)) && !e.within(t, true)){
\r
46304 this.onNodeOut(e, this.getNode(e));
\r
46308 delegateOver : function(e, t){
\r
46309 if(!this.beforeEvent(e)){
\r
46312 if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF
\r
46313 Ext.getBody().on('mouseover', this.trackExit, this);
\r
46314 this.trackingDoc = true;
\r
46316 if(this.lastEcOver){ // prevent hung highlight
\r
46317 this.onIconOut(e, this.lastEcOver);
\r
46318 delete this.lastEcOver;
\r
46320 if(e.getTarget('.x-tree-ec-icon', 1)){
\r
46321 this.lastEcOver = this.getNode(e);
\r
46322 this.onIconOver(e, this.lastEcOver);
\r
46324 if(t = this.getNodeTarget(e)){
\r
46325 this.onNodeOver(e, this.getNode(e));
\r
46329 trackExit : function(e){
\r
46330 if(this.lastOverNode && !e.within(this.lastOverNode.ui.getEl())){
\r
46331 this.onNodeOut(e, this.lastOverNode);
\r
46332 delete this.lastOverNode;
\r
46333 Ext.getBody().un('mouseover', this.trackExit, this);
\r
46334 this.trackingDoc = false;
\r
46338 delegateClick : function(e, t){
\r
46339 if(!this.beforeEvent(e)){
\r
46343 if(e.getTarget('input[type=checkbox]', 1)){
\r
46344 this.onCheckboxClick(e, this.getNode(e));
\r
46346 else if(e.getTarget('.x-tree-ec-icon', 1)){
\r
46347 this.onIconClick(e, this.getNode(e));
\r
46349 else if(this.getNodeTarget(e)){
\r
46350 this.onNodeClick(e, this.getNode(e));
\r
46354 delegateDblClick : function(e, t){
\r
46355 if(this.beforeEvent(e) && this.getNodeTarget(e)){
\r
46356 this.onNodeDblClick(e, this.getNode(e));
\r
46360 delegateContextMenu : function(e, t){
\r
46361 if(this.beforeEvent(e) && this.getNodeTarget(e)){
\r
46362 this.onNodeContextMenu(e, this.getNode(e));
\r
46366 onNodeClick : function(e, node){
\r
46367 node.ui.onClick(e);
\r
46370 onNodeOver : function(e, node){
\r
46371 this.lastOverNode = node;
\r
46372 node.ui.onOver(e);
\r
46375 onNodeOut : function(e, node){
\r
46376 node.ui.onOut(e);
\r
46379 onIconOver : function(e, node){
\r
46380 node.ui.addClass('x-tree-ec-over');
\r
46383 onIconOut : function(e, node){
\r
46384 node.ui.removeClass('x-tree-ec-over');
\r
46387 onIconClick : function(e, node){
\r
46388 node.ui.ecClick(e);
\r
46391 onCheckboxClick : function(e, node){
\r
46392 node.ui.onCheckChange(e);
\r
46395 onNodeDblClick : function(e, node){
\r
46396 node.ui.onDblClick(e);
\r
46399 onNodeContextMenu : function(e, node){
\r
46400 node.ui.onContextMenu(e);
\r
46403 beforeEvent : function(e){
\r
46404 if(this.disabled){
\r
46411 disable: function(){
\r
46412 this.disabled = true;
\r
46415 enable: function(){
\r
46416 this.disabled = false;
\r
46419 * @class Ext.tree.DefaultSelectionModel
\r
46420 * @extends Ext.util.Observable
\r
46421 * The default single selection for a TreePanel.
\r
46423 Ext.tree.DefaultSelectionModel = function(config){
\r
46424 this.selNode = null;
\r
46428 * @event selectionchange
\r
46429 * Fires when the selected node changes
\r
46430 * @param {DefaultSelectionModel} this
\r
46431 * @param {TreeNode} node the new selection
\r
46433 "selectionchange",
\r
46436 * @event beforeselect
\r
46437 * Fires before the selected node changes, return false to cancel the change
\r
46438 * @param {DefaultSelectionModel} this
\r
46439 * @param {TreeNode} node the new selection
\r
46440 * @param {TreeNode} node the old selection
\r
46445 Ext.apply(this, config);
\r
46446 Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
\r
46449 Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
\r
46450 init : function(tree){
\r
46451 this.tree = tree;
\r
46452 tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
\r
46453 tree.on("click", this.onNodeClick, this);
\r
46456 onNodeClick : function(node, e){
\r
46457 this.select(node);
\r
46462 * @param {TreeNode} node The node to select
\r
46463 * @return {TreeNode} The selected node
\r
46465 select : function(node, /* private*/ selectNextNode){
\r
46466 // If node is hidden, select the next node in whatever direction was being moved in.
\r
46467 if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
\r
46468 return selectNextNode.call(this, node);
\r
46470 var last = this.selNode;
\r
46471 if(node == last){
\r
46472 node.ui.onSelectedChange(true);
\r
46473 }else if(this.fireEvent('beforeselect', this, node, last) !== false){
\r
46475 last.ui.onSelectedChange(false);
\r
46477 this.selNode = node;
\r
46478 node.ui.onSelectedChange(true);
\r
46479 this.fireEvent("selectionchange", this, node, last);
\r
46485 * Deselect a node.
\r
46486 * @param {TreeNode} node The node to unselect
\r
46488 unselect : function(node){
\r
46489 if(this.selNode == node){
\r
46490 this.clearSelections();
\r
46495 * Clear all selections
\r
46497 clearSelections : function(){
\r
46498 var n = this.selNode;
\r
46500 n.ui.onSelectedChange(false);
\r
46501 this.selNode = null;
\r
46502 this.fireEvent("selectionchange", this, null);
\r
46508 * Get the selected node
\r
46509 * @return {TreeNode} The selected node
\r
46511 getSelectedNode : function(){
\r
46512 return this.selNode;
\r
46516 * Returns true if the node is selected
\r
46517 * @param {TreeNode} node The node to check
\r
46518 * @return {Boolean}
\r
46520 isSelected : function(node){
\r
46521 return this.selNode == node;
\r
46525 * Selects the node above the selected node in the tree, intelligently walking the nodes
\r
46526 * @return TreeNode The new selection
\r
46528 selectPrevious : function(/* private */ s){
\r
46529 if(!(s = s || this.selNode || this.lastSelNode)){
\r
46532 // Here we pass in the current function to select to indicate the direction we're moving
\r
46533 var ps = s.previousSibling;
\r
46535 if(!ps.isExpanded() || ps.childNodes.length < 1){
\r
46536 return this.select(ps, this.selectPrevious);
\r
46538 var lc = ps.lastChild;
\r
46539 while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
\r
46540 lc = lc.lastChild;
\r
46542 return this.select(lc, this.selectPrevious);
\r
46544 } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
\r
46545 return this.select(s.parentNode, this.selectPrevious);
\r
46551 * Selects the node above the selected node in the tree, intelligently walking the nodes
\r
46552 * @return TreeNode The new selection
\r
46554 selectNext : function(/* private */ s){
\r
46555 if(!(s = s || this.selNode || this.lastSelNode)){
\r
46558 // Here we pass in the current function to select to indicate the direction we're moving
\r
46559 if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
\r
46560 return this.select(s.firstChild, this.selectNext);
\r
46561 }else if(s.nextSibling){
\r
46562 return this.select(s.nextSibling, this.selectNext);
\r
46563 }else if(s.parentNode){
\r
46565 s.parentNode.bubble(function(){
\r
46566 if(this.nextSibling){
\r
46567 newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
\r
46576 onKeyDown : function(e){
\r
46577 var s = this.selNode || this.lastSelNode;
\r
46578 // undesirable, but required
\r
46583 var k = e.getKey();
\r
46587 this.selectNext();
\r
46591 this.selectPrevious();
\r
46594 e.preventDefault();
\r
46595 if(s.hasChildNodes()){
\r
46596 if(!s.isExpanded()){
\r
46598 }else if(s.firstChild){
\r
46599 this.select(s.firstChild, e);
\r
46604 e.preventDefault();
\r
46605 if(s.hasChildNodes() && s.isExpanded()){
\r
46607 }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
\r
46608 this.select(s.parentNode, e);
\r
46616 * @class Ext.tree.MultiSelectionModel
\r
46617 * @extends Ext.util.Observable
\r
46618 * Multi selection for a TreePanel.
\r
46620 Ext.tree.MultiSelectionModel = function(config){
\r
46621 this.selNodes = [];
\r
46622 this.selMap = {};
\r
46625 * @event selectionchange
\r
46626 * Fires when the selected nodes change
\r
46627 * @param {MultiSelectionModel} this
\r
46628 * @param {Array} nodes Array of the selected nodes
\r
46630 "selectionchange"
\r
46632 Ext.apply(this, config);
\r
46633 Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
\r
46636 Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
\r
46637 init : function(tree){
\r
46638 this.tree = tree;
\r
46639 tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
\r
46640 tree.on("click", this.onNodeClick, this);
\r
46643 onNodeClick : function(node, e){
\r
46644 if(e.ctrlKey && this.isSelected(node)){
\r
46645 this.unselect(node);
\r
46647 this.select(node, e, e.ctrlKey);
\r
46653 * @param {TreeNode} node The node to select
\r
46654 * @param {EventObject} e (optional) An event associated with the selection
\r
46655 * @param {Boolean} keepExisting True to retain existing selections
\r
46656 * @return {TreeNode} The selected node
\r
46658 select : function(node, e, keepExisting){
\r
46659 if(keepExisting !== true){
\r
46660 this.clearSelections(true);
\r
46662 if(this.isSelected(node)){
\r
46663 this.lastSelNode = node;
\r
46666 this.selNodes.push(node);
\r
46667 this.selMap[node.id] = node;
\r
46668 this.lastSelNode = node;
\r
46669 node.ui.onSelectedChange(true);
\r
46670 this.fireEvent("selectionchange", this, this.selNodes);
\r
46675 * Deselect a node.
\r
46676 * @param {TreeNode} node The node to unselect
\r
46678 unselect : function(node){
\r
46679 if(this.selMap[node.id]){
\r
46680 node.ui.onSelectedChange(false);
\r
46681 var sn = this.selNodes;
\r
46682 var index = sn.indexOf(node);
\r
46684 this.selNodes.splice(index, 1);
\r
46686 delete this.selMap[node.id];
\r
46687 this.fireEvent("selectionchange", this, this.selNodes);
\r
46692 * Clear all selections
\r
46694 clearSelections : function(suppressEvent){
\r
46695 var sn = this.selNodes;
\r
46696 if(sn.length > 0){
\r
46697 for(var i = 0, len = sn.length; i < len; i++){
\r
46698 sn[i].ui.onSelectedChange(false);
\r
46700 this.selNodes = [];
\r
46701 this.selMap = {};
\r
46702 if(suppressEvent !== true){
\r
46703 this.fireEvent("selectionchange", this, this.selNodes);
\r
46709 * Returns true if the node is selected
\r
46710 * @param {TreeNode} node The node to check
\r
46711 * @return {Boolean}
\r
46713 isSelected : function(node){
\r
46714 return this.selMap[node.id] ? true : false;
\r
46718 * Returns an array of the selected nodes
\r
46719 * @return {Array}
\r
46721 getSelectedNodes : function(){
\r
46722 return this.selNodes;
\r
46725 onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
\r
46727 selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
\r
46729 selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
\r
46731 * @class Ext.data.Tree
\r
46732 * @extends Ext.util.Observable
\r
46733 * Represents a tree data structure and bubbles all the events for its nodes. The nodes
\r
46734 * in the tree have most standard DOM functionality.
\r
46736 * @param {Node} root (optional) The root node
\r
46738 Ext.data.Tree = function(root){
\r
46739 this.nodeHash = {};
\r
46741 * The root node for this tree
\r
46744 this.root = null;
\r
46746 this.setRootNode(root);
\r
46751 * Fires when a new child node is appended to a node in this tree.
\r
46752 * @param {Tree} tree The owner tree
\r
46753 * @param {Node} parent The parent node
\r
46754 * @param {Node} node The newly appended node
\r
46755 * @param {Number} index The index of the newly appended node
\r
46760 * Fires when a child node is removed from a node in this tree.
\r
46761 * @param {Tree} tree The owner tree
\r
46762 * @param {Node} parent The parent node
\r
46763 * @param {Node} node The child node removed
\r
46768 * Fires when a node is moved to a new location in the tree
\r
46769 * @param {Tree} tree The owner tree
\r
46770 * @param {Node} node The node moved
\r
46771 * @param {Node} oldParent The old parent of this node
\r
46772 * @param {Node} newParent The new parent of this node
\r
46773 * @param {Number} index The index it was moved to
\r
46778 * Fires when a new child node is inserted in a node in this tree.
\r
46779 * @param {Tree} tree The owner tree
\r
46780 * @param {Node} parent The parent node
\r
46781 * @param {Node} node The child node inserted
\r
46782 * @param {Node} refNode The child node the node was inserted before
\r
46786 * @event beforeappend
\r
46787 * Fires before a new child is appended to a node in this tree, return false to cancel the append.
\r
46788 * @param {Tree} tree The owner tree
\r
46789 * @param {Node} parent The parent node
\r
46790 * @param {Node} node The child node to be appended
\r
46794 * @event beforeremove
\r
46795 * Fires before a child is removed from a node in this tree, return false to cancel the remove.
\r
46796 * @param {Tree} tree The owner tree
\r
46797 * @param {Node} parent The parent node
\r
46798 * @param {Node} node The child node to be removed
\r
46802 * @event beforemove
\r
46803 * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
\r
46804 * @param {Tree} tree The owner tree
\r
46805 * @param {Node} node The node being moved
\r
46806 * @param {Node} oldParent The parent of the node
\r
46807 * @param {Node} newParent The new parent the node is moving to
\r
46808 * @param {Number} index The index it is being moved to
\r
46812 * @event beforeinsert
\r
46813 * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
\r
46814 * @param {Tree} tree The owner tree
\r
46815 * @param {Node} parent The parent node
\r
46816 * @param {Node} node The child node to be inserted
\r
46817 * @param {Node} refNode The child node the node is being inserted before
\r
46822 Ext.data.Tree.superclass.constructor.call(this);
\r
46825 Ext.extend(Ext.data.Tree, Ext.util.Observable, {
\r
46827 * @cfg {String} pathSeparator
\r
46828 * The token used to separate paths in node ids (defaults to '/').
\r
46830 pathSeparator: "/",
\r
46833 proxyNodeEvent : function(){
\r
46834 return this.fireEvent.apply(this, arguments);
\r
46838 * Returns the root node for this tree.
\r
46841 getRootNode : function(){
\r
46842 return this.root;
\r
46846 * Sets the root node for this tree.
\r
46847 * @param {Node} node
\r
46850 setRootNode : function(node){
\r
46851 this.root = node;
\r
46852 node.ownerTree = this;
\r
46853 node.isRoot = true;
\r
46854 this.registerNode(node);
\r
46859 * Gets a node in this tree by its id.
\r
46860 * @param {String} id
\r
46863 getNodeById : function(id){
\r
46864 return this.nodeHash[id];
\r
46868 registerNode : function(node){
\r
46869 this.nodeHash[node.id] = node;
\r
46873 unregisterNode : function(node){
\r
46874 delete this.nodeHash[node.id];
\r
46877 toString : function(){
\r
46878 return "[Tree"+(this.id?" "+this.id:"")+"]";
\r
46883 * @class Ext.data.Node
\r
46884 * @extends Ext.util.Observable
\r
46885 * @cfg {Boolean} leaf true if this node is a leaf and does not have children
\r
46886 * @cfg {String} id The id for this node. If one is not specified, one is generated.
\r
46888 * @param {Object} attributes The attributes/config for the node
\r
46890 Ext.data.Node = function(attributes){
\r
46892 * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
\r
46895 this.attributes = attributes || {};
\r
46896 this.leaf = this.attributes.leaf;
\r
46898 * The node id. @type String
\r
46900 this.id = this.attributes.id;
\r
46902 this.id = Ext.id(null, "xnode-");
\r
46903 this.attributes.id = this.id;
\r
46906 * All child nodes of this node. @type Array
\r
46908 this.childNodes = [];
\r
46909 if(!this.childNodes.indexOf){ // indexOf is a must
\r
46910 this.childNodes.indexOf = function(o){
\r
46911 for(var i = 0, len = this.length; i < len; i++){
\r
46912 if(this[i] == o){
\r
46920 * The parent node for this node. @type Node
\r
46922 this.parentNode = null;
\r
46924 * The first direct child node of this node, or null if this node has no child nodes. @type Node
\r
46926 this.firstChild = null;
\r
46928 * The last direct child node of this node, or null if this node has no child nodes. @type Node
\r
46930 this.lastChild = null;
\r
46932 * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
\r
46934 this.previousSibling = null;
\r
46936 * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
\r
46938 this.nextSibling = null;
\r
46943 * Fires when a new child node is appended
\r
46944 * @param {Tree} tree The owner tree
\r
46945 * @param {Node} this This node
\r
46946 * @param {Node} node The newly appended node
\r
46947 * @param {Number} index The index of the newly appended node
\r
46952 * Fires when a child node is removed
\r
46953 * @param {Tree} tree The owner tree
\r
46954 * @param {Node} this This node
\r
46955 * @param {Node} node The removed node
\r
46960 * Fires when this node is moved to a new location in the tree
\r
46961 * @param {Tree} tree The owner tree
\r
46962 * @param {Node} this This node
\r
46963 * @param {Node} oldParent The old parent of this node
\r
46964 * @param {Node} newParent The new parent of this node
\r
46965 * @param {Number} index The index it was moved to
\r
46970 * Fires when a new child node is inserted.
\r
46971 * @param {Tree} tree The owner tree
\r
46972 * @param {Node} this This node
\r
46973 * @param {Node} node The child node inserted
\r
46974 * @param {Node} refNode The child node the node was inserted before
\r
46978 * @event beforeappend
\r
46979 * Fires before a new child is appended, return false to cancel the append.
\r
46980 * @param {Tree} tree The owner tree
\r
46981 * @param {Node} this This node
\r
46982 * @param {Node} node The child node to be appended
\r
46984 "beforeappend" : true,
\r
46986 * @event beforeremove
\r
46987 * Fires before a child is removed, return false to cancel the remove.
\r
46988 * @param {Tree} tree The owner tree
\r
46989 * @param {Node} this This node
\r
46990 * @param {Node} node The child node to be removed
\r
46992 "beforeremove" : true,
\r
46994 * @event beforemove
\r
46995 * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
\r
46996 * @param {Tree} tree The owner tree
\r
46997 * @param {Node} this This node
\r
46998 * @param {Node} oldParent The parent of this node
\r
46999 * @param {Node} newParent The new parent this node is moving to
\r
47000 * @param {Number} index The index it is being moved to
\r
47002 "beforemove" : true,
\r
47004 * @event beforeinsert
\r
47005 * Fires before a new child is inserted, return false to cancel the insert.
\r
47006 * @param {Tree} tree The owner tree
\r
47007 * @param {Node} this This node
\r
47008 * @param {Node} node The child node to be inserted
\r
47009 * @param {Node} refNode The child node the node is being inserted before
\r
47011 "beforeinsert" : true
\r
47013 this.listeners = this.attributes.listeners;
\r
47014 Ext.data.Node.superclass.constructor.call(this);
\r
47017 Ext.extend(Ext.data.Node, Ext.util.Observable, {
\r
47019 fireEvent : function(evtName){
\r
47020 // first do standard event for this node
\r
47021 if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
\r
47024 // then bubble it up to the tree if the event wasn't cancelled
\r
47025 var ot = this.getOwnerTree();
\r
47027 if(ot.proxyNodeEvent.apply(ot, arguments) === false){
\r
47035 * Returns true if this node is a leaf
\r
47036 * @return {Boolean}
\r
47038 isLeaf : function(){
\r
47039 return this.leaf === true;
\r
47043 setFirstChild : function(node){
\r
47044 this.firstChild = node;
\r
47048 setLastChild : function(node){
\r
47049 this.lastChild = node;
\r
47054 * Returns true if this node is the last child of its parent
\r
47055 * @return {Boolean}
\r
47057 isLast : function(){
\r
47058 return (!this.parentNode ? true : this.parentNode.lastChild == this);
\r
47062 * Returns true if this node is the first child of its parent
\r
47063 * @return {Boolean}
\r
47065 isFirst : function(){
\r
47066 return (!this.parentNode ? true : this.parentNode.firstChild == this);
\r
47070 * Returns true if this node has one or more child nodes, else false.
\r
47071 * @return {Boolean}
\r
47073 hasChildNodes : function(){
\r
47074 return !this.isLeaf() && this.childNodes.length > 0;
\r
47078 * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
\r
47079 * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
\r
47080 * @return {Boolean}
\r
47082 isExpandable : function(){
\r
47083 return this.attributes.expandable || this.hasChildNodes();
\r
47087 * Insert node(s) as the last child node of this node.
\r
47088 * @param {Node/Array} node The node or Array of nodes to append
\r
47089 * @return {Node} The appended node if single append, or null if an array was passed
\r
47091 appendChild : function(node){
\r
47092 var multi = false;
\r
47093 if(Ext.isArray(node)){
\r
47095 }else if(arguments.length > 1){
\r
47096 multi = arguments;
\r
47098 // if passed an array or multiple args do them one by one
\r
47100 for(var i = 0, len = multi.length; i < len; i++) {
\r
47101 this.appendChild(multi[i]);
\r
47104 if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
\r
47107 var index = this.childNodes.length;
\r
47108 var oldParent = node.parentNode;
\r
47109 // it's a move, make sure we move it cleanly
\r
47111 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
\r
47114 oldParent.removeChild(node);
\r
47116 index = this.childNodes.length;
\r
47118 this.setFirstChild(node);
\r
47120 this.childNodes.push(node);
\r
47121 node.parentNode = this;
\r
47122 var ps = this.childNodes[index-1];
\r
47124 node.previousSibling = ps;
\r
47125 ps.nextSibling = node;
\r
47127 node.previousSibling = null;
\r
47129 node.nextSibling = null;
\r
47130 this.setLastChild(node);
\r
47131 node.setOwnerTree(this.getOwnerTree());
\r
47132 this.fireEvent("append", this.ownerTree, this, node, index);
\r
47134 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
\r
47141 * Removes a child node from this node.
\r
47142 * @param {Node} node The node to remove
\r
47143 * @return {Node} The removed node
\r
47145 removeChild : function(node){
\r
47146 var index = this.childNodes.indexOf(node);
\r
47150 if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
\r
47154 // remove it from childNodes collection
\r
47155 this.childNodes.splice(index, 1);
\r
47157 // update siblings
\r
47158 if(node.previousSibling){
\r
47159 node.previousSibling.nextSibling = node.nextSibling;
\r
47161 if(node.nextSibling){
\r
47162 node.nextSibling.previousSibling = node.previousSibling;
\r
47165 // update child refs
\r
47166 if(this.firstChild == node){
\r
47167 this.setFirstChild(node.nextSibling);
\r
47169 if(this.lastChild == node){
\r
47170 this.setLastChild(node.previousSibling);
\r
47173 node.setOwnerTree(null);
\r
47174 // clear any references from the node
\r
47175 node.parentNode = null;
\r
47176 node.previousSibling = null;
\r
47177 node.nextSibling = null;
\r
47178 this.fireEvent("remove", this.ownerTree, this, node);
\r
47183 * Inserts the first node before the second node in this nodes childNodes collection.
\r
47184 * @param {Node} node The node to insert
\r
47185 * @param {Node} refNode The node to insert before (if null the node is appended)
\r
47186 * @return {Node} The inserted node
\r
47188 insertBefore : function(node, refNode){
\r
47189 if(!refNode){ // like standard Dom, refNode can be null for append
\r
47190 return this.appendChild(node);
\r
47193 if(node == refNode){
\r
47197 if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
\r
47200 var index = this.childNodes.indexOf(refNode);
\r
47201 var oldParent = node.parentNode;
\r
47202 var refIndex = index;
\r
47204 // when moving internally, indexes will change after remove
\r
47205 if(oldParent == this && this.childNodes.indexOf(node) < index){
\r
47209 // it's a move, make sure we move it cleanly
\r
47211 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
\r
47214 oldParent.removeChild(node);
\r
47216 if(refIndex === 0){
\r
47217 this.setFirstChild(node);
\r
47219 this.childNodes.splice(refIndex, 0, node);
\r
47220 node.parentNode = this;
\r
47221 var ps = this.childNodes[refIndex-1];
\r
47223 node.previousSibling = ps;
\r
47224 ps.nextSibling = node;
\r
47226 node.previousSibling = null;
\r
47228 node.nextSibling = refNode;
\r
47229 refNode.previousSibling = node;
\r
47230 node.setOwnerTree(this.getOwnerTree());
\r
47231 this.fireEvent("insert", this.ownerTree, this, node, refNode);
\r
47233 node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
\r
47239 * Removes this node from its parent
\r
47240 * @return {Node} this
\r
47242 remove : function(){
\r
47243 this.parentNode.removeChild(this);
\r
47248 * Returns the child node at the specified index.
\r
47249 * @param {Number} index
\r
47252 item : function(index){
\r
47253 return this.childNodes[index];
\r
47257 * Replaces one child node in this node with another.
\r
47258 * @param {Node} newChild The replacement node
\r
47259 * @param {Node} oldChild The node to replace
\r
47260 * @return {Node} The replaced node
\r
47262 replaceChild : function(newChild, oldChild){
\r
47263 var s = oldChild ? oldChild.nextSibling : null;
\r
47264 this.removeChild(oldChild);
\r
47265 this.insertBefore(newChild, s);
\r
47270 * Returns the index of a child node
\r
47271 * @param {Node} node
\r
47272 * @return {Number} The index of the node or -1 if it was not found
\r
47274 indexOf : function(child){
\r
47275 return this.childNodes.indexOf(child);
\r
47279 * Returns the tree this node is in.
\r
47282 getOwnerTree : function(){
\r
47283 // if it doesn't have one, look for one
\r
47284 if(!this.ownerTree){
\r
47288 this.ownerTree = p.ownerTree;
\r
47291 p = p.parentNode;
\r
47294 return this.ownerTree;
\r
47298 * Returns depth of this node (the root node has a depth of 0)
\r
47299 * @return {Number}
\r
47301 getDepth : function(){
\r
47304 while(p.parentNode){
\r
47306 p = p.parentNode;
\r
47312 setOwnerTree : function(tree){
\r
47313 // if it is a move, we need to update everyone
\r
47314 if(tree != this.ownerTree){
\r
47315 if(this.ownerTree){
\r
47316 this.ownerTree.unregisterNode(this);
\r
47318 this.ownerTree = tree;
\r
47319 var cs = this.childNodes;
\r
47320 for(var i = 0, len = cs.length; i < len; i++) {
\r
47321 cs[i].setOwnerTree(tree);
\r
47324 tree.registerNode(this);
\r
47330 * Changes the id of this node.
\r
47331 * @param {String} id The new id for the node.
\r
47333 setId: function(id){
\r
47334 if(id !== this.id){
\r
47335 var t = this.ownerTree;
\r
47337 t.unregisterNode(this);
\r
47339 this.id = this.attributes.id = id;
\r
47341 t.registerNode(this);
\r
47343 this.onIdChange(id);
\r
47348 onIdChange: Ext.emptyFn,
\r
47351 * Returns the path for this node. The path can be used to expand or select this node programmatically.
\r
47352 * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
\r
47353 * @return {String} The path
\r
47355 getPath : function(attr){
\r
47356 attr = attr || "id";
\r
47357 var p = this.parentNode;
\r
47358 var b = [this.attributes[attr]];
\r
47360 b.unshift(p.attributes[attr]);
\r
47361 p = p.parentNode;
\r
47363 var sep = this.getOwnerTree().pathSeparator;
\r
47364 return sep + b.join(sep);
\r
47368 * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
47369 * function call will be the scope provided or the current node. The arguments to the function
\r
47370 * will be the args provided or the current node. If the function returns false at any point,
\r
47371 * the bubble is stopped.
\r
47372 * @param {Function} fn The function to call
\r
47373 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
47374 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
47376 bubble : function(fn, scope, args){
\r
47379 if(fn.apply(scope || p, args || [p]) === false){
\r
47382 p = p.parentNode;
\r
47387 * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
47388 * function call will be the scope provided or the current node. The arguments to the function
\r
47389 * will be the args provided or the current node. If the function returns false at any point,
\r
47390 * the cascade is stopped on that branch.
\r
47391 * @param {Function} fn The function to call
\r
47392 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
47393 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
47395 cascade : function(fn, scope, args){
\r
47396 if(fn.apply(scope || this, args || [this]) !== false){
\r
47397 var cs = this.childNodes;
\r
47398 for(var i = 0, len = cs.length; i < len; i++) {
\r
47399 cs[i].cascade(fn, scope, args);
\r
47405 * Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
\r
47406 * function call will be the scope provided or the current node. The arguments to the function
\r
47407 * will be the args provided or the current node. If the function returns false at any point,
\r
47408 * the iteration stops.
\r
47409 * @param {Function} fn The function to call
\r
47410 * @param {Object} scope (optional) The scope of the function (defaults to current node)
\r
47411 * @param {Array} args (optional) The args to call the function with (default to passing the current node)
\r
47413 eachChild : function(fn, scope, args){
\r
47414 var cs = this.childNodes;
\r
47415 for(var i = 0, len = cs.length; i < len; i++) {
\r
47416 if(fn.apply(scope || this, args || [cs[i]]) === false){
\r
47423 * Finds the first child that has the attribute with the specified value.
\r
47424 * @param {String} attribute The attribute name
\r
47425 * @param {Mixed} value The value to search for
\r
47426 * @return {Node} The found child or null if none was found
\r
47428 findChild : function(attribute, value){
\r
47429 var cs = this.childNodes;
\r
47430 for(var i = 0, len = cs.length; i < len; i++) {
\r
47431 if(cs[i].attributes[attribute] == value){
\r
47439 * Finds the first child by a custom function. The child matches if the function passed
\r
47441 * @param {Function} fn
\r
47442 * @param {Object} scope (optional)
\r
47443 * @return {Node} The found child or null if none was found
\r
47445 findChildBy : function(fn, scope){
\r
47446 var cs = this.childNodes;
\r
47447 for(var i = 0, len = cs.length; i < len; i++) {
\r
47448 if(fn.call(scope||cs[i], cs[i]) === true){
\r
47456 * Sorts this nodes children using the supplied sort function
\r
47457 * @param {Function} fn
\r
47458 * @param {Object} scope (optional)
\r
47460 sort : function(fn, scope){
\r
47461 var cs = this.childNodes;
\r
47462 var len = cs.length;
\r
47464 var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
\r
47466 for(var i = 0; i < len; i++){
\r
47468 n.previousSibling = cs[i-1];
\r
47469 n.nextSibling = cs[i+1];
\r
47471 this.setFirstChild(n);
\r
47474 this.setLastChild(n);
\r
47481 * Returns true if this node is an ancestor (at any point) of the passed node.
\r
47482 * @param {Node} node
\r
47483 * @return {Boolean}
\r
47485 contains : function(node){
\r
47486 return node.isAncestor(this);
\r
47490 * Returns true if the passed node is an ancestor (at any point) of this node.
\r
47491 * @param {Node} node
\r
47492 * @return {Boolean}
\r
47494 isAncestor : function(node){
\r
47495 var p = this.parentNode;
\r
47500 p = p.parentNode;
\r
47505 toString : function(){
\r
47506 return "[Node"+(this.id?" "+this.id:"")+"]";
\r
47509 * @class Ext.tree.TreeNode
\r
47510 * @extends Ext.data.Node
\r
47511 * @cfg {String} text The text for this node
\r
47512 * @cfg {Boolean} expanded true to start the node expanded
\r
47513 * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)
\r
47514 * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)
\r
47515 * @cfg {Boolean} disabled true to start the node disabled
\r
47516 * @cfg {String} icon The path to an icon for the node. The preferred way to do this
\r
47517 * is to use the cls or iconCls attributes and add the icon via a CSS background image.
\r
47518 * @cfg {String} cls A css class to be added to the node
\r
47519 * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
\r
47520 * @cfg {String} href URL of the link used for the node (defaults to #)
\r
47521 * @cfg {String} hrefTarget target frame for the link
\r
47522 * @cfg {Boolean} hidden True to render hidden. (Defaults to false).
\r
47523 * @cfg {String} qtip An Ext QuickTip for the node
\r
47524 * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty
\r
47525 * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
\r
47526 * @cfg {Boolean} singleClickExpand True for single click expand on this node
\r
47527 * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Ext.tree.TreeNodeUI)
\r
47528 * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
\r
47529 * (defaults to undefined with no checkbox rendered)
\r
47530 * @cfg {Boolean} draggable True to make this node draggable (defaults to false)
\r
47531 * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)
\r
47532 * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)
\r
47533 * @cfg {Boolean} editable False to not allow this node to be edited by an (@link Ext.tree.TreeEditor} (defaults to true)
\r
47535 * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
\r
47537 Ext.tree.TreeNode = function(attributes){
\r
47538 attributes = attributes || {};
\r
47539 if(typeof attributes == 'string'){
\r
47540 attributes = {text: attributes};
\r
47542 this.childrenRendered = false;
\r
47543 this.rendered = false;
\r
47544 Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
\r
47545 this.expanded = attributes.expanded === true;
\r
47546 this.isTarget = attributes.isTarget !== false;
\r
47547 this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
\r
47548 this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
\r
47551 * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.
\r
47554 this.text = attributes.text;
\r
47556 * True if this node is disabled.
\r
47559 this.disabled = attributes.disabled === true;
\r
47561 * True if this node is hidden.
\r
47564 this.hidden = attributes.hidden === true;
\r
47568 * @event textchange
\r
47569 * Fires when the text for this node is changed
\r
47570 * @param {Node} this This node
\r
47571 * @param {String} text The new text
\r
47572 * @param {String} oldText The old text
\r
47576 * @event beforeexpand
\r
47577 * Fires before this node is expanded, return false to cancel.
\r
47578 * @param {Node} this This node
\r
47579 * @param {Boolean} deep
\r
47580 * @param {Boolean} anim
\r
47584 * @event beforecollapse
\r
47585 * Fires before this node is collapsed, return false to cancel.
\r
47586 * @param {Node} this This node
\r
47587 * @param {Boolean} deep
\r
47588 * @param {Boolean} anim
\r
47590 'beforecollapse',
\r
47593 * Fires when this node is expanded
\r
47594 * @param {Node} this This node
\r
47598 * @event disabledchange
\r
47599 * Fires when the disabled status of this node changes
\r
47600 * @param {Node} this This node
\r
47601 * @param {Boolean} disabled
\r
47603 'disabledchange',
\r
47605 * @event collapse
\r
47606 * Fires when this node is collapsed
\r
47607 * @param {Node} this This node
\r
47611 * @event beforeclick
\r
47612 * Fires before click processing. Return false to cancel the default action.
\r
47613 * @param {Node} this This node
\r
47614 * @param {Ext.EventObject} e The event object
\r
47619 * Fires when this node is clicked
\r
47620 * @param {Node} this This node
\r
47621 * @param {Ext.EventObject} e The event object
\r
47625 * @event checkchange
\r
47626 * Fires when a node with a checkbox's checked property changes
\r
47627 * @param {Node} this This node
\r
47628 * @param {Boolean} checked
\r
47632 * @event beforedblclick
\r
47633 * Fires before double click processing. Return false to cancel the default action.
\r
47634 * @param {Node} this This node
\r
47635 * @param {Ext.EventObject} e The event object
\r
47637 'beforedblclick',
\r
47639 * @event dblclick
\r
47640 * Fires when this node is double clicked
\r
47641 * @param {Node} this This node
\r
47642 * @param {Ext.EventObject} e The event object
\r
47646 * @event contextmenu
\r
47647 * Fires when this node is right clicked
\r
47648 * @param {Node} this This node
\r
47649 * @param {Ext.EventObject} e The event object
\r
47653 * @event beforechildrenrendered
\r
47654 * Fires right before the child nodes for this node are rendered
\r
47655 * @param {Node} this This node
\r
47657 'beforechildrenrendered'
\r
47660 var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
\r
47663 * Read-only. The UI for this node
\r
47664 * @type TreeNodeUI
\r
47666 this.ui = new uiClass(this);
\r
47668 Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
\r
47669 preventHScroll : true,
\r
47671 * Returns true if this node is expanded
\r
47672 * @return {Boolean}
\r
47674 isExpanded : function(){
\r
47675 return this.expanded;
\r
47679 * Returns the UI object for this node.
\r
47680 * @return {TreeNodeUI} The object which is providing the user interface for this tree
\r
47681 * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance
\r
47682 * of {@link Ext.tree.TreeNodeUI}
\r
47684 getUI : function(){
\r
47688 getLoader : function(){
\r
47690 return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : new Ext.tree.TreeLoader());
\r
47693 // private override
\r
47694 setFirstChild : function(node){
\r
47695 var of = this.firstChild;
\r
47696 Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
\r
47697 if(this.childrenRendered && of && node != of){
\r
47698 of.renderIndent(true, true);
\r
47700 if(this.rendered){
\r
47701 this.renderIndent(true, true);
\r
47705 // private override
\r
47706 setLastChild : function(node){
\r
47707 var ol = this.lastChild;
\r
47708 Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
\r
47709 if(this.childrenRendered && ol && node != ol){
\r
47710 ol.renderIndent(true, true);
\r
47712 if(this.rendered){
\r
47713 this.renderIndent(true, true);
\r
47717 // these methods are overridden to provide lazy rendering support
\r
47718 // private override
\r
47719 appendChild : function(n){
\r
47720 if(!n.render && !Ext.isArray(n)){
\r
47721 n = this.getLoader().createNode(n);
\r
47723 var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
\r
47724 if(node && this.childrenRendered){
\r
47727 this.ui.updateExpandIcon();
\r
47731 // private override
\r
47732 removeChild : function(node){
\r
47733 this.ownerTree.getSelectionModel().unselect(node);
\r
47734 Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
\r
47735 // if it's been rendered remove dom node
\r
47736 if(this.childrenRendered){
\r
47737 node.ui.remove();
\r
47739 if(this.childNodes.length < 1){
\r
47740 this.collapse(false, false);
\r
47742 this.ui.updateExpandIcon();
\r
47744 if(!this.firstChild && !this.isHiddenRoot()) {
\r
47745 this.childrenRendered = false;
\r
47750 // private override
\r
47751 insertBefore : function(node, refNode){
\r
47752 if(!node.render){
\r
47753 node = this.getLoader().createNode(node);
\r
47755 var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
\r
47756 if(newNode && refNode && this.childrenRendered){
\r
47759 this.ui.updateExpandIcon();
\r
47764 * Sets the text for this node
\r
47765 * @param {String} text
\r
47767 setText : function(text){
\r
47768 var oldText = this.text;
\r
47769 this.text = text;
\r
47770 this.attributes.text = text;
\r
47771 if(this.rendered){ // event without subscribing
\r
47772 this.ui.onTextChange(this, text, oldText);
\r
47774 this.fireEvent('textchange', this, text, oldText);
\r
47778 * Triggers selection of this node
\r
47780 select : function(){
\r
47781 this.getOwnerTree().getSelectionModel().select(this);
\r
47785 * Triggers deselection of this node
\r
47787 unselect : function(){
\r
47788 this.getOwnerTree().getSelectionModel().unselect(this);
\r
47792 * Returns true if this node is selected
\r
47793 * @return {Boolean}
\r
47795 isSelected : function(){
\r
47796 return this.getOwnerTree().getSelectionModel().isSelected(this);
\r
47800 * Expand this node.
\r
47801 * @param {Boolean} deep (optional) True to expand all children as well
\r
47802 * @param {Boolean} anim (optional) false to cancel the default animation
\r
47803 * @param {Function} callback (optional) A callback to be called when
\r
47804 * expanding this node completes (does not wait for deep expand to complete).
\r
47805 * Called with 1 parameter, this node.
\r
47806 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
47808 expand : function(deep, anim, callback, scope){
\r
47809 if(!this.expanded){
\r
47810 if(this.fireEvent('beforeexpand', this, deep, anim) === false){
\r
47813 if(!this.childrenRendered){
\r
47814 this.renderChildren();
\r
47816 this.expanded = true;
\r
47817 if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
\r
47818 this.ui.animExpand(function(){
\r
47819 this.fireEvent('expand', this);
\r
47820 this.runCallback(callback, scope || this, [this]);
\r
47821 if(deep === true){
\r
47822 this.expandChildNodes(true);
\r
47824 }.createDelegate(this));
\r
47827 this.ui.expand();
\r
47828 this.fireEvent('expand', this);
\r
47829 this.runCallback(callback, scope || this, [this]);
\r
47832 this.runCallback(callback, scope || this, [this]);
\r
47834 if(deep === true){
\r
47835 this.expandChildNodes(true);
\r
47839 runCallback : function(cb, scope, args){
\r
47840 if(Ext.isFunction(cb)){
\r
47841 cb.apply(scope, args);
\r
47845 isHiddenRoot : function(){
\r
47846 return this.isRoot && !this.getOwnerTree().rootVisible;
\r
47850 * Collapse this node.
\r
47851 * @param {Boolean} deep (optional) True to collapse all children as well
\r
47852 * @param {Boolean} anim (optional) false to cancel the default animation
\r
47853 * @param {Function} callback (optional) A callback to be called when
\r
47854 * expanding this node completes (does not wait for deep expand to complete).
\r
47855 * Called with 1 parameter, this node.
\r
47856 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
47858 collapse : function(deep, anim, callback, scope){
\r
47859 if(this.expanded && !this.isHiddenRoot()){
\r
47860 if(this.fireEvent('beforecollapse', this, deep, anim) === false){
\r
47863 this.expanded = false;
\r
47864 if((this.getOwnerTree().animate && anim !== false) || anim){
\r
47865 this.ui.animCollapse(function(){
\r
47866 this.fireEvent('collapse', this);
\r
47867 this.runCallback(callback, scope || this, [this]);
\r
47868 if(deep === true){
\r
47869 this.collapseChildNodes(true);
\r
47871 }.createDelegate(this));
\r
47874 this.ui.collapse();
\r
47875 this.fireEvent('collapse', this);
\r
47876 this.runCallback(callback, scope || this, [this]);
\r
47878 }else if(!this.expanded){
\r
47879 this.runCallback(callback, scope || this, [this]);
\r
47881 if(deep === true){
\r
47882 var cs = this.childNodes;
\r
47883 for(var i = 0, len = cs.length; i < len; i++) {
\r
47884 cs[i].collapse(true, false);
\r
47890 delayedExpand : function(delay){
\r
47891 if(!this.expandProcId){
\r
47892 this.expandProcId = this.expand.defer(delay, this);
\r
47897 cancelExpand : function(){
\r
47898 if(this.expandProcId){
\r
47899 clearTimeout(this.expandProcId);
\r
47901 this.expandProcId = false;
\r
47905 * Toggles expanded/collapsed state of the node
\r
47907 toggle : function(){
\r
47908 if(this.expanded){
\r
47916 * Ensures all parent nodes are expanded, and if necessary, scrolls
\r
47917 * the node into view.
\r
47918 * @param {Function} callback (optional) A function to call when the node has been made visible.
\r
47919 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
47921 ensureVisible : function(callback, scope){
\r
47922 var tree = this.getOwnerTree();
\r
47923 tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
\r
47924 var node = tree.getNodeById(this.id); // Somehow if we don't do this, we lose changes that happened to node in the meantime
\r
47925 tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
\r
47926 this.runCallback(callback, scope || this, [this]);
\r
47927 }.createDelegate(this));
\r
47931 * Expand all child nodes
\r
47932 * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
\r
47934 expandChildNodes : function(deep){
\r
47935 var cs = this.childNodes;
\r
47936 for(var i = 0, len = cs.length; i < len; i++) {
\r
47937 cs[i].expand(deep);
\r
47942 * Collapse all child nodes
\r
47943 * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
\r
47945 collapseChildNodes : function(deep){
\r
47946 var cs = this.childNodes;
\r
47947 for(var i = 0, len = cs.length; i < len; i++) {
\r
47948 cs[i].collapse(deep);
\r
47953 * Disables this node
\r
47955 disable : function(){
\r
47956 this.disabled = true;
\r
47958 if(this.rendered && this.ui.onDisableChange){ // event without subscribing
\r
47959 this.ui.onDisableChange(this, true);
\r
47961 this.fireEvent('disabledchange', this, true);
\r
47965 * Enables this node
\r
47967 enable : function(){
\r
47968 this.disabled = false;
\r
47969 if(this.rendered && this.ui.onDisableChange){ // event without subscribing
\r
47970 this.ui.onDisableChange(this, false);
\r
47972 this.fireEvent('disabledchange', this, false);
\r
47976 renderChildren : function(suppressEvent){
\r
47977 if(suppressEvent !== false){
\r
47978 this.fireEvent('beforechildrenrendered', this);
\r
47980 var cs = this.childNodes;
\r
47981 for(var i = 0, len = cs.length; i < len; i++){
\r
47982 cs[i].render(true);
\r
47984 this.childrenRendered = true;
\r
47988 sort : function(fn, scope){
\r
47989 Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
\r
47990 if(this.childrenRendered){
\r
47991 var cs = this.childNodes;
\r
47992 for(var i = 0, len = cs.length; i < len; i++){
\r
47993 cs[i].render(true);
\r
47999 render : function(bulkRender){
\r
48000 this.ui.render(bulkRender);
\r
48001 if(!this.rendered){
\r
48002 // make sure it is registered
\r
48003 this.getOwnerTree().registerNode(this);
\r
48004 this.rendered = true;
\r
48005 if(this.expanded){
\r
48006 this.expanded = false;
\r
48007 this.expand(false, false);
\r
48013 renderIndent : function(deep, refresh){
\r
48015 this.ui.childIndent = null;
\r
48017 this.ui.renderIndent();
\r
48018 if(deep === true && this.childrenRendered){
\r
48019 var cs = this.childNodes;
\r
48020 for(var i = 0, len = cs.length; i < len; i++){
\r
48021 cs[i].renderIndent(true, refresh);
\r
48026 beginUpdate : function(){
\r
48027 this.childrenRendered = false;
\r
48030 endUpdate : function(){
\r
48031 if(this.expanded && this.rendered){
\r
48032 this.renderChildren();
\r
48036 destroy : function(){
\r
48037 if(this.childNodes){
\r
48038 for(var i = 0,l = this.childNodes.length; i < l; i++){
\r
48039 this.childNodes[i].destroy();
\r
48041 this.childNodes = null;
\r
48043 if(this.ui.destroy){
\r
48044 this.ui.destroy();
\r
48049 onIdChange : function(id){
\r
48050 this.ui.onIdChange(id);
\r
48054 Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**
\r
48055 * @class Ext.tree.AsyncTreeNode
\r
48056 * @extends Ext.tree.TreeNode
\r
48057 * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
\r
48059 * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
\r
48061 Ext.tree.AsyncTreeNode = function(config){
\r
48062 this.loaded = config && config.loaded === true;
\r
48063 this.loading = false;
\r
48064 Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
\r
48066 * @event beforeload
\r
48067 * Fires before this node is loaded, return false to cancel
\r
48068 * @param {Node} this This node
\r
48070 this.addEvents('beforeload', 'load');
\r
48073 * Fires when this node is loaded
\r
48074 * @param {Node} this This node
\r
48077 * The loader used by this node (defaults to using the tree's defined loader)
\r
48078 * @type TreeLoader
\r
48079 * @property loader
\r
48082 Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
\r
48083 expand : function(deep, anim, callback, scope){
\r
48084 if(this.loading){ // if an async load is already running, waiting til it's done
\r
48086 var f = function(){
\r
48087 if(!this.loading){ // done loading
\r
48088 clearInterval(timer);
\r
48089 this.expand(deep, anim, callback, scope);
\r
48091 }.createDelegate(this);
\r
48092 timer = setInterval(f, 200);
\r
48095 if(!this.loaded){
\r
48096 if(this.fireEvent("beforeload", this) === false){
\r
48099 this.loading = true;
\r
48100 this.ui.beforeLoad(this);
\r
48101 var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
\r
48103 loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
\r
48107 Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
\r
48111 * Returns true if this node is currently loading
\r
48112 * @return {Boolean}
\r
48114 isLoading : function(){
\r
48115 return this.loading;
\r
48118 loadComplete : function(deep, anim, callback, scope){
\r
48119 this.loading = false;
\r
48120 this.loaded = true;
\r
48121 this.ui.afterLoad(this);
\r
48122 this.fireEvent("load", this);
\r
48123 this.expand(deep, anim, callback, scope);
\r
48127 * Returns true if this node has been loaded
\r
48128 * @return {Boolean}
\r
48130 isLoaded : function(){
\r
48131 return this.loaded;
\r
48134 hasChildNodes : function(){
\r
48135 if(!this.isLeaf() && !this.loaded){
\r
48138 return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
\r
48143 * Trigger a reload for this node
\r
48144 * @param {Function} callback
\r
48145 * @param {Object} scope (optional) The scope in which to execute the callback.
\r
48147 reload : function(callback, scope){
\r
48148 this.collapse(false, false);
\r
48149 while(this.firstChild){
\r
48150 this.removeChild(this.firstChild).destroy();
\r
48152 this.childrenRendered = false;
\r
48153 this.loaded = false;
\r
48154 if(this.isHiddenRoot()){
\r
48155 this.expanded = false;
\r
48157 this.expand(false, false, callback, scope);
\r
48161 Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**
\r
48162 * @class Ext.tree.TreeNodeUI
\r
48163 * This class provides the default UI implementation for Ext TreeNodes.
\r
48164 * The TreeNode UI implementation is separate from the
\r
48165 * tree implementation, and allows customizing of the appearance of
\r
48166 * tree nodes.<br>
\r
48168 * If you are customizing the Tree's user interface, you
\r
48169 * may need to extend this class, but you should never need to instantiate this class.<br>
\r
48171 * This class provides access to the user interface components of an Ext TreeNode, through
\r
48172 * {@link Ext.tree.TreeNode#getUI}
\r
48174 Ext.tree.TreeNodeUI = function(node){
\r
48175 this.node = node;
\r
48176 this.rendered = false;
\r
48177 this.animating = false;
\r
48178 this.wasLeaf = true;
\r
48179 this.ecc = 'x-tree-ec-icon x-tree-elbow';
\r
48180 this.emptyIcon = Ext.BLANK_IMAGE_URL;
\r
48183 Ext.tree.TreeNodeUI.prototype = {
\r
48185 removeChild : function(node){
\r
48186 if(this.rendered){
\r
48187 this.ctNode.removeChild(node.ui.getEl());
\r
48192 beforeLoad : function(){
\r
48193 this.addClass("x-tree-node-loading");
\r
48197 afterLoad : function(){
\r
48198 this.removeClass("x-tree-node-loading");
\r
48202 onTextChange : function(node, text, oldText){
\r
48203 if(this.rendered){
\r
48204 this.textNode.innerHTML = text;
\r
48209 onDisableChange : function(node, state){
\r
48210 this.disabled = state;
\r
48211 if (this.checkbox) {
\r
48212 this.checkbox.disabled = state;
\r
48215 this.addClass("x-tree-node-disabled");
\r
48217 this.removeClass("x-tree-node-disabled");
\r
48222 onSelectedChange : function(state){
\r
48225 this.addClass("x-tree-selected");
\r
48228 this.removeClass("x-tree-selected");
\r
48233 onMove : function(tree, node, oldParent, newParent, index, refNode){
\r
48234 this.childIndent = null;
\r
48235 if(this.rendered){
\r
48236 var targetNode = newParent.ui.getContainer();
\r
48237 if(!targetNode){//target not rendered
\r
48238 this.holder = document.createElement("div");
\r
48239 this.holder.appendChild(this.wrap);
\r
48242 var insertBefore = refNode ? refNode.ui.getEl() : null;
\r
48243 if(insertBefore){
\r
48244 targetNode.insertBefore(this.wrap, insertBefore);
\r
48246 targetNode.appendChild(this.wrap);
\r
48248 this.node.renderIndent(true, oldParent != newParent);
\r
48253 * Adds one or more CSS classes to the node's UI element.
\r
48254 * Duplicate classes are automatically filtered out.
\r
48255 * @param {String/Array} className The CSS class to add, or an array of classes
\r
48257 addClass : function(cls){
\r
48259 Ext.fly(this.elNode).addClass(cls);
\r
48264 * Removes one or more CSS classes from the node's UI element.
\r
48265 * @param {String/Array} className The CSS class to remove, or an array of classes
\r
48267 removeClass : function(cls){
\r
48269 Ext.fly(this.elNode).removeClass(cls);
\r
48274 remove : function(){
\r
48275 if(this.rendered){
\r
48276 this.holder = document.createElement("div");
\r
48277 this.holder.appendChild(this.wrap);
\r
48282 fireEvent : function(){
\r
48283 return this.node.fireEvent.apply(this.node, arguments);
\r
48287 initEvents : function(){
\r
48288 this.node.on("move", this.onMove, this);
\r
48290 if(this.node.disabled){
\r
48291 this.onDisableChange(this.node, true);
\r
48293 if(this.node.hidden){
\r
48296 var ot = this.node.getOwnerTree();
\r
48297 var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
\r
48298 if(dd && (!this.node.isRoot || ot.rootVisible)){
\r
48299 Ext.dd.Registry.register(this.elNode, {
\r
48301 handles: this.getDDHandles(),
\r
48308 getDDHandles : function(){
\r
48309 return [this.iconNode, this.textNode, this.elNode];
\r
48313 * Hides this node.
\r
48315 hide : function(){
\r
48316 this.node.hidden = true;
\r
48318 this.wrap.style.display = "none";
\r
48323 * Shows this node.
\r
48325 show : function(){
\r
48326 this.node.hidden = false;
\r
48328 this.wrap.style.display = "";
\r
48333 onContextMenu : function(e){
\r
48334 if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
\r
48335 e.preventDefault();
\r
48337 this.fireEvent("contextmenu", this.node, e);
\r
48342 onClick : function(e){
\r
48343 if(this.dropping){
\r
48347 if(this.fireEvent("beforeclick", this.node, e) !== false){
\r
48348 var a = e.getTarget('a');
\r
48349 if(!this.disabled && this.node.attributes.href && a){
\r
48350 this.fireEvent("click", this.node, e);
\r
48352 }else if(a && e.ctrlKey){
\r
48355 e.preventDefault();
\r
48356 if(this.disabled){
\r
48360 if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
\r
48361 this.node.toggle();
\r
48364 this.fireEvent("click", this.node, e);
\r
48371 onDblClick : function(e){
\r
48372 e.preventDefault();
\r
48373 if(this.disabled){
\r
48376 if(this.fireEvent("beforedblclick", this.node, e) !== false){
\r
48377 if(this.checkbox){
\r
48378 this.toggleCheck();
\r
48380 if(!this.animating && this.node.isExpandable()){
\r
48381 this.node.toggle();
\r
48383 this.fireEvent("dblclick", this.node, e);
\r
48387 onOver : function(e){
\r
48388 this.addClass('x-tree-node-over');
\r
48391 onOut : function(e){
\r
48392 this.removeClass('x-tree-node-over');
\r
48396 onCheckChange : function(){
\r
48397 var checked = this.checkbox.checked;
\r
48399 this.checkbox.defaultChecked = checked;
\r
48400 this.node.attributes.checked = checked;
\r
48401 this.fireEvent('checkchange', this.node, checked);
\r
48405 ecClick : function(e){
\r
48406 if(!this.animating && this.node.isExpandable()){
\r
48407 this.node.toggle();
\r
48412 startDrop : function(){
\r
48413 this.dropping = true;
\r
48416 // delayed drop so the click event doesn't get fired on a drop
\r
48417 endDrop : function(){
\r
48418 setTimeout(function(){
\r
48419 this.dropping = false;
\r
48420 }.createDelegate(this), 50);
\r
48424 expand : function(){
\r
48425 this.updateExpandIcon();
\r
48426 this.ctNode.style.display = "";
\r
48430 focus : function(){
\r
48431 if(!this.node.preventHScroll){
\r
48432 try{this.anchor.focus();
\r
48436 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
\r
48437 var l = noscroll.scrollLeft;
\r
48438 this.anchor.focus();
\r
48439 noscroll.scrollLeft = l;
\r
48445 * Sets the checked status of the tree node to the passed value, or, if no value was passed,
\r
48446 * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
\r
48447 * @param {Boolean} (optional) The new checked status.
\r
48449 toggleCheck : function(value){
\r
48450 var cb = this.checkbox;
\r
48452 cb.checked = (value === undefined ? !cb.checked : value);
\r
48453 this.onCheckChange();
\r
48458 blur : function(){
\r
48460 this.anchor.blur();
\r
48465 animExpand : function(callback){
\r
48466 var ct = Ext.get(this.ctNode);
\r
48468 if(!this.node.isExpandable()){
\r
48469 this.updateExpandIcon();
\r
48470 this.ctNode.style.display = "";
\r
48471 Ext.callback(callback);
\r
48474 this.animating = true;
\r
48475 this.updateExpandIcon();
\r
48477 ct.slideIn('t', {
\r
48478 callback : function(){
\r
48479 this.animating = false;
\r
48480 Ext.callback(callback);
\r
48483 duration: this.node.ownerTree.duration || .25
\r
48488 highlight : function(){
\r
48489 var tree = this.node.getOwnerTree();
\r
48490 Ext.fly(this.wrap).highlight(
\r
48491 tree.hlColor || "C3DAF9",
\r
48492 {endColor: tree.hlBaseColor}
\r
48497 collapse : function(){
\r
48498 this.updateExpandIcon();
\r
48499 this.ctNode.style.display = "none";
\r
48503 animCollapse : function(callback){
\r
48504 var ct = Ext.get(this.ctNode);
\r
48505 ct.enableDisplayMode('block');
\r
48508 this.animating = true;
\r
48509 this.updateExpandIcon();
\r
48511 ct.slideOut('t', {
\r
48512 callback : function(){
\r
48513 this.animating = false;
\r
48514 Ext.callback(callback);
\r
48517 duration: this.node.ownerTree.duration || .25
\r
48522 getContainer : function(){
\r
48523 return this.ctNode;
\r
48527 getEl : function(){
\r
48528 return this.wrap;
\r
48532 appendDDGhost : function(ghostNode){
\r
48533 ghostNode.appendChild(this.elNode.cloneNode(true));
\r
48537 getDDRepairXY : function(){
\r
48538 return Ext.lib.Dom.getXY(this.iconNode);
\r
48542 onRender : function(){
\r
48547 render : function(bulkRender){
\r
48548 var n = this.node, a = n.attributes;
\r
48549 var targetNode = n.parentNode ?
\r
48550 n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
\r
48552 if(!this.rendered){
\r
48553 this.rendered = true;
\r
48555 this.renderElements(n, a, targetNode, bulkRender);
\r
48558 if(this.textNode.setAttributeNS){
\r
48559 this.textNode.setAttributeNS("ext", "qtip", a.qtip);
\r
48561 this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
\r
48564 this.textNode.setAttribute("ext:qtip", a.qtip);
\r
48566 this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
\r
48569 }else if(a.qtipCfg){
\r
48570 a.qtipCfg.target = Ext.id(this.textNode);
\r
48571 Ext.QuickTips.register(a.qtipCfg);
\r
48573 this.initEvents();
\r
48574 if(!this.node.expanded){
\r
48575 this.updateExpandIcon(true);
\r
48578 if(bulkRender === true) {
\r
48579 targetNode.appendChild(this.wrap);
\r
48585 renderElements : function(n, a, targetNode, bulkRender){
\r
48586 // add some indent caching, this helps performance when rendering a large tree
\r
48587 this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
\r
48589 var cb = typeof a.checked == 'boolean';
\r
48591 var href = a.href ? a.href : Ext.isGecko ? "" : "#";
\r
48592 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
48593 '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
\r
48594 '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
\r
48595 '<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
48596 cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
\r
48597 '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
\r
48598 a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
\r
48599 '<ul class="x-tree-node-ct" style="display:none;"></ul>',
\r
48600 "</li>"].join('');
\r
48603 if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
\r
48604 this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
\r
48606 this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
\r
48609 this.elNode = this.wrap.childNodes[0];
\r
48610 this.ctNode = this.wrap.childNodes[1];
\r
48611 var cs = this.elNode.childNodes;
\r
48612 this.indentNode = cs[0];
\r
48613 this.ecNode = cs[1];
\r
48614 this.iconNode = cs[2];
\r
48617 this.checkbox = cs[3];
\r
48619 this.checkbox.defaultChecked = this.checkbox.checked;
\r
48622 this.anchor = cs[index];
\r
48623 this.textNode = cs[index].firstChild;
\r
48627 * Returns the <a> element that provides focus for the node's UI.
\r
48628 * @return {HtmlElement} The DOM anchor element.
\r
48630 getAnchor : function(){
\r
48631 return this.anchor;
\r
48635 * Returns the text node.
\r
48636 * @return {HtmlNode} The DOM text node.
\r
48638 getTextEl : function(){
\r
48639 return this.textNode;
\r
48643 * Returns the icon <img> element.
\r
48644 * @return {HtmlElement} The DOM image element.
\r
48646 getIconEl : function(){
\r
48647 return this.iconNode;
\r
48651 * Returns the checked status of the node. If the node was rendered with no
\r
48652 * checkbox, it returns false.
\r
48653 * @return {Boolean} The checked flag.
\r
48655 isChecked : function(){
\r
48656 return this.checkbox ? this.checkbox.checked : false;
\r
48660 updateExpandIcon : function(){
\r
48661 if(this.rendered){
\r
48662 var n = this.node, c1, c2;
\r
48663 var cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow";
\r
48664 var hasChild = n.hasChildNodes();
\r
48665 if(hasChild || n.attributes.expandable){
\r
48668 c1 = "x-tree-node-collapsed";
\r
48669 c2 = "x-tree-node-expanded";
\r
48672 c1 = "x-tree-node-expanded";
\r
48673 c2 = "x-tree-node-collapsed";
\r
48675 if(this.wasLeaf){
\r
48676 this.removeClass("x-tree-node-leaf");
\r
48677 this.wasLeaf = false;
\r
48679 if(this.c1 != c1 || this.c2 != c2){
\r
48680 Ext.fly(this.elNode).replaceClass(c1, c2);
\r
48681 this.c1 = c1; this.c2 = c2;
\r
48684 if(!this.wasLeaf){
\r
48685 Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-leaf");
\r
48688 this.wasLeaf = true;
\r
48691 var ecc = "x-tree-ec-icon "+cls;
\r
48692 if(this.ecc != ecc){
\r
48693 this.ecNode.className = ecc;
\r
48700 onIdChange: function(id){
\r
48701 if(this.rendered){
\r
48702 this.elNode.setAttribute('ext:tree-node-id', id);
\r
48707 getChildIndent : function(){
\r
48708 if(!this.childIndent){
\r
48710 var p = this.node;
\r
48712 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
\r
48713 if(!p.isLast()) {
\r
48714 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
\r
48716 buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
\r
48719 p = p.parentNode;
\r
48721 this.childIndent = buf.join("");
\r
48723 return this.childIndent;
\r
48727 renderIndent : function(){
\r
48728 if(this.rendered){
\r
48730 var p = this.node.parentNode;
\r
48732 indent = p.ui.getChildIndent();
\r
48734 if(this.indentMarkup != indent){ // don't rerender if not required
\r
48735 this.indentNode.innerHTML = indent;
\r
48736 this.indentMarkup = indent;
\r
48738 this.updateExpandIcon();
\r
48742 destroy : function(){
\r
48744 Ext.dd.Registry.unregister(this.elNode.id);
\r
48746 delete this.elNode;
\r
48747 delete this.ctNode;
\r
48748 delete this.indentNode;
\r
48749 delete this.ecNode;
\r
48750 delete this.iconNode;
\r
48751 delete this.checkbox;
\r
48752 delete this.anchor;
\r
48753 delete this.textNode;
\r
48755 if (this.holder){
\r
48756 delete this.wrap;
\r
48757 Ext.removeNode(this.holder);
\r
48758 delete this.holder;
\r
48760 Ext.removeNode(this.wrap);
\r
48761 delete this.wrap;
\r
48767 * @class Ext.tree.RootTreeNodeUI
\r
48768 * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
\r
48769 * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
\r
48771 * If you are customizing the Tree's user interface, you
\r
48772 * may need to extend this class, but you should never need to instantiate this class.<br>
\r
48774 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
\r
48776 render : function(){
\r
48777 if(!this.rendered){
\r
48778 var targetNode = this.node.ownerTree.innerCt.dom;
\r
48779 this.node.expanded = true;
\r
48780 targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
\r
48781 this.wrap = this.ctNode = targetNode.firstChild;
\r
48784 collapse : Ext.emptyFn,
\r
48785 expand : Ext.emptyFn
\r
48787 * @class Ext.tree.TreeLoader
\r
48788 * @extends Ext.util.Observable
\r
48789 * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
\r
48790 * nodes from a specified URL. The response must be a JavaScript Array definition
\r
48791 * whose elements are node definition objects. e.g.:
\r
48795 text: 'A leaf Node',
\r
48799 text: 'A folder Node',
\r
48802 text: 'A child Node',
\r
48808 * A server request is sent, and child nodes are loaded only when a node is expanded.
\r
48809 * The loading node's id is passed to the server under the parameter name "node" to
\r
48810 * enable the server to produce the correct child nodes.
\r
48812 * To pass extra parameters, an event handler may be attached to the "beforeload"
\r
48813 * event, and the parameters specified in the TreeLoader's baseParams property:
\r
48815 myTreeLoader.on("beforeload", function(treeLoader, node) {
\r
48816 this.baseParams.category = node.attributes.category;
\r
48819 * This would pass an HTTP parameter called "category" to the server containing
\r
48820 * the value of the Node's "category" attribute.
\r
48822 * Creates a new Treeloader.
\r
48823 * @param {Object} config A config object containing config properties.
\r
48825 Ext.tree.TreeLoader = function(config){
\r
48826 this.baseParams = {};
\r
48827 Ext.apply(this, config);
\r
48831 * @event beforeload
\r
48832 * Fires before a network request is made to retrieve the Json text which specifies a node's children.
\r
48833 * @param {Object} This TreeLoader object.
\r
48834 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
48835 * @param {Object} callback The callback function specified in the {@link #load} call.
\r
48840 * Fires when the node has been successfuly loaded.
\r
48841 * @param {Object} This TreeLoader object.
\r
48842 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
48843 * @param {Object} response The response object containing the data from the server.
\r
48847 * @event loadexception
\r
48848 * Fires if the network request failed.
\r
48849 * @param {Object} This TreeLoader object.
\r
48850 * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
\r
48851 * @param {Object} response The response object containing the data from the server.
\r
48855 Ext.tree.TreeLoader.superclass.constructor.call(this);
\r
48856 if(typeof this.paramOrder == 'string'){
\r
48857 this.paramOrder = this.paramOrder.split(/[\s,|]/);
\r
48861 Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
\r
48863 * @cfg {String} dataUrl The URL from which to request a Json string which
\r
48864 * specifies an array of node definition objects representing the child nodes
\r
48868 * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
\r
48871 * @cfg {String} url Equivalent to {@link #dataUrl}.
\r
48874 * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
\r
48877 * @cfg {Object} baseParams (optional) An object containing properties which
\r
48878 * specify HTTP parameters to be passed to each request for child nodes.
\r
48881 * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
\r
48882 * created by this loader. If the attributes sent by the server have an attribute in this object,
\r
48883 * they take priority.
\r
48886 * @cfg {Object} uiProviders (optional) An object containing properties which
\r
48887 * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
\r
48888 * <i>uiProvider</i> attribute of a returned child node is a string rather
\r
48889 * than a reference to a TreeNodeUI implementation, then that string value
\r
48890 * is used as a property name in the uiProviders object.
\r
48892 uiProviders : {},
\r
48895 * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
\r
48896 * child nodes before loading.
\r
48898 clearOnLoad : true,
\r
48901 * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.
\r
48902 * A list of params to be executed
\r
48903 * server side. Specify the params in the order in which they must be executed on the server-side
\r
48904 * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
\r
48905 * comma, or pipe. For example,
\r
48906 * any of the following would be acceptable:<pre><code>
\r
48907 paramOrder: ['param1','param2','param3']
\r
48908 paramOrder: 'param1 param2 param3'
\r
48909 paramOrder: 'param1,param2,param3'
\r
48910 paramOrder: 'param1|param2|param'
\r
48913 paramOrder: undefined,
\r
48916 * @cfg {Boolean} paramsAsHash Only used when using directFn.
\r
48917 * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a
\r
48918 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
\r
48920 paramsAsHash: false,
\r
48923 * @cfg {Function} directFn
\r
48924 * Function to call when executing a request.
\r
48926 directFn : undefined,
\r
48929 * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
\r
48930 * This is called automatically when a node is expanded, but may be used to reload
\r
48931 * a node (or append new children if the {@link #clearOnLoad} option is false.)
\r
48932 * @param {Ext.tree.TreeNode} node
\r
48933 * @param {Function} callback
\r
48934 * @param (Object) scope
\r
48936 load : function(node, callback, scope){
\r
48937 if(this.clearOnLoad){
\r
48938 while(node.firstChild){
\r
48939 node.removeChild(node.firstChild);
\r
48942 if(this.doPreload(node)){ // preloaded json children
\r
48943 this.runCallback(callback, scope || node, [node]);
\r
48944 }else if(this.directFn || this.dataUrl || this.url){
\r
48945 this.requestData(node, callback, scope || node);
\r
48949 doPreload : function(node){
\r
48950 if(node.attributes.children){
\r
48951 if(node.childNodes.length < 1){ // preloaded?
\r
48952 var cs = node.attributes.children;
\r
48953 node.beginUpdate();
\r
48954 for(var i = 0, len = cs.length; i < len; i++){
\r
48955 var cn = node.appendChild(this.createNode(cs[i]));
\r
48956 if(this.preloadChildren){
\r
48957 this.doPreload(cn);
\r
48960 node.endUpdate();
\r
48967 getParams: function(node){
\r
48968 var buf = [], bp = this.baseParams;
\r
48969 if(this.directFn){
\r
48970 buf.push(node.id);
\r
48972 if(this.paramOrder){
\r
48973 for(var i = 0, len = this.paramOrder.length; i < len; i++){
\r
48974 buf.push(bp[this.paramOrder[i]]);
\r
48976 }else if(this.paramsAsHash){
\r
48982 for(var key in bp){
\r
48983 if(!Ext.isFunction(bp[key])){
\r
48984 buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
\r
48987 buf.push("node=", encodeURIComponent(node.id));
\r
48988 return buf.join("");
\r
48992 requestData : function(node, callback, scope){
\r
48993 if(this.fireEvent("beforeload", this, node, callback) !== false){
\r
48994 if(this.directFn){
\r
48995 var args = this.getParams(node);
\r
48996 args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
\r
48997 this.directFn.apply(window, args);
\r
48999 this.transId = Ext.Ajax.request({
\r
49000 method:this.requestMethod,
\r
49001 url: this.dataUrl||this.url,
\r
49002 success: this.handleResponse,
\r
49003 failure: this.handleFailure,
\r
49005 argument: {callback: callback, node: node, scope: scope},
\r
49006 params: this.getParams(node)
\r
49010 // if the load is cancelled, make sure we notify
\r
49011 // the node that we are done
\r
49012 this.runCallback(callback, scope || node, []);
\r
49016 processDirectResponse: function(result, response, args){
\r
49017 if(response.status){
\r
49018 this.handleResponse({
\r
49019 responseData: Ext.isArray(result) ? result : null,
\r
49020 responseText: result,
\r
49024 this.handleFailure({
\r
49031 runCallback: function(cb, scope, args){
\r
49032 if(Ext.isFunction(cb)){
\r
49033 cb.apply(scope, args);
\r
49037 isLoading : function(){
\r
49038 return !!this.transId;
\r
49041 abort : function(){
\r
49042 if(this.isLoading()){
\r
49043 Ext.Ajax.abort(this.transId);
\r
49048 * <p>Override this function for custom TreeNode node implementation, or to
\r
49049 * modify the attributes at creation time.</p>
\r
49050 * Example:<pre><code>
\r
49051 new Ext.tree.TreePanel({
\r
49053 loader: new Ext.tree.TreeLoader({
\r
49055 createNode: function(attr) {
\r
49056 // Allow consolidation consignments to have
\r
49057 // consignments dropped into them.
\r
49058 if (attr.isConsolidation) {
\r
49059 attr.iconCls = 'x-consol',
\r
49060 attr.allowDrop = true;
\r
49062 return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
\r
49068 * @param attr {Object} The attributes from which to create the new node.
\r
49070 createNode : function(attr){
\r
49071 // apply baseAttrs, nice idea Corey!
\r
49072 if(this.baseAttrs){
\r
49073 Ext.applyIf(attr, this.baseAttrs);
\r
49075 if(this.applyLoader !== false && !attr.loader){
\r
49076 attr.loader = this;
\r
49078 if(typeof attr.uiProvider == 'string'){
\r
49079 attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
\r
49081 if(attr.nodeType){
\r
49082 return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
\r
49084 return attr.leaf ?
\r
49085 new Ext.tree.TreeNode(attr) :
\r
49086 new Ext.tree.AsyncTreeNode(attr);
\r
49090 processResponse : function(response, node, callback, scope){
\r
49091 var json = response.responseText;
\r
49093 var o = response.responseData || Ext.decode(json);
\r
49094 node.beginUpdate();
\r
49095 for(var i = 0, len = o.length; i < len; i++){
\r
49096 var n = this.createNode(o[i]);
\r
49098 node.appendChild(n);
\r
49101 node.endUpdate();
\r
49102 this.runCallback(callback, scope || node, [node]);
\r
49104 this.handleFailure(response);
\r
49108 handleResponse : function(response){
\r
49109 this.transId = false;
\r
49110 var a = response.argument;
\r
49111 this.processResponse(response, a.node, a.callback, a.scope);
\r
49112 this.fireEvent("load", this, a.node, response);
\r
49115 handleFailure : function(response){
\r
49116 this.transId = false;
\r
49117 var a = response.argument;
\r
49118 this.fireEvent("loadexception", this, a.node, response);
\r
49119 this.runCallback(a.callback, a.scope || a.node, [a.node]);
\r
49122 * @class Ext.tree.TreeFilter
49123 * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
49124 * @param {TreePanel} tree
49125 * @param {Object} config (optional)
49127 Ext.tree.TreeFilter = function(tree, config){
49129 this.filtered = {};
49130 Ext.apply(this, config);
49133 Ext.tree.TreeFilter.prototype = {
49140 * Filter the data by a specific attribute.
49141 * @param {String/RegExp} value Either string that the attribute value
49142 * should start with or a RegExp to test against the attribute
49143 * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
49144 * @param {TreeNode} startNode (optional) The node to start the filter at.
49146 filter : function(value, attr, startNode){
49147 attr = attr || "text";
49149 if(typeof value == "string"){
49150 var vlen = value.length;
49151 // auto clear empty filter
49152 if(vlen == 0 && this.clearBlank){
49156 value = value.toLowerCase();
49158 return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
49160 }else if(value.exec){ // regex?
49162 return value.test(n.attributes[attr]);
49165 throw 'Illegal filter type, must be string or regex';
49167 this.filterBy(f, null, startNode);
49171 * Filter by a function. The passed function will be called with each
49172 * node in the tree (or from the startNode). If the function returns true, the node is kept
49173 * otherwise it is filtered. If a node is filtered, its children are also filtered.
49174 * @param {Function} fn The filter function
49175 * @param {Object} scope (optional) The scope of the function (defaults to the current node)
49177 filterBy : function(fn, scope, startNode){
49178 startNode = startNode || this.tree.root;
49179 if(this.autoClear){
49182 var af = this.filtered, rv = this.reverse;
49183 var f = function(n){
49184 if(n == startNode){
49190 var m = fn.call(scope || n, n);
49198 startNode.cascade(f);
49201 if(typeof id != "function"){
49203 if(n && n.parentNode){
49204 n.parentNode.removeChild(n);
49212 * Clears the current filter. Note: with the "remove" option
49213 * set a filter cannot be cleared.
49215 clear : function(){
49217 var af = this.filtered;
49219 if(typeof id != "function"){
49226 this.filtered = {};
49230 * @class Ext.tree.TreeSorter
\r
49231 * Provides sorting of nodes in a {@link Ext.tree.TreePanel}. The TreeSorter automatically monitors events on the
\r
49232 * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
\r
49233 * Example usage:<br />
\r
49235 new Ext.tree.TreeSorter(myTree, {
\r
49236 folderSort: true,
\r
49238 sortType: function(node) {
\r
49239 // sort by a custom, typed attribute:
\r
49240 return parseInt(node.id, 10);
\r
49245 * @param {TreePanel} tree
\r
49246 * @param {Object} config
\r
49248 Ext.tree.TreeSorter = function(tree, config){
\r
49250 * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
\r
49253 * @cfg {String} property The named attribute on the node to sort by (defaults to "text"). Note that this
\r
49254 * property is only used if no {@link #sortType} function is specified, otherwise it is ignored.
\r
49257 * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")
\r
49260 * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")
\r
49263 * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)
\r
49266 * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting. The function
\r
49267 * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return
\r
49268 * the node's sort value cast to the specific data type required for sorting. This could be used, for example, when
\r
49269 * a node's text (or other attribute) should be sorted as a date or numeric value. See the class description for
\r
49270 * example usage. Note that if a sortType is specified, any {@link #property} config will be ignored.
\r
49273 Ext.apply(this, config);
\r
49274 tree.on("beforechildrenrendered", this.doSort, this);
\r
49275 tree.on("append", this.updateSort, this);
\r
49276 tree.on("insert", this.updateSort, this);
\r
49277 tree.on("textchange", this.updateSortParent, this);
\r
49279 var dsc = this.dir && this.dir.toLowerCase() == "desc";
\r
49280 var p = this.property || "text";
\r
49281 var sortType = this.sortType;
\r
49282 var fs = this.folderSort;
\r
49283 var cs = this.caseSensitive === true;
\r
49284 var leafAttr = this.leafAttr || 'leaf';
\r
49286 this.sortFn = function(n1, n2){
\r
49288 if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
\r
49291 if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
\r
49295 var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
\r
49296 var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
\r
49298 return dsc ? +1 : -1;
\r
49299 }else if(v1 > v2){
\r
49300 return dsc ? -1 : +1;
\r
49307 Ext.tree.TreeSorter.prototype = {
\r
49308 doSort : function(node){
\r
49309 node.sort(this.sortFn);
\r
49312 compareNodes : function(n1, n2){
\r
49313 return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
\r
49316 updateSort : function(tree, node){
\r
49317 if(node.childrenRendered){
\r
49318 this.doSort.defer(1, this, [node]);
\r
49322 updateSortParent : function(node){
\r
49323 var p = node.parentNode;
\r
49324 if(p && p.childrenRendered){
\r
49325 this.doSort.defer(1, this, [p]);
\r
49329 * @class Ext.tree.TreeDropZone
\r
49330 * @extends Ext.dd.DropZone
\r
49332 * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
\r
49333 * @param {Object} config
\r
49335 if(Ext.dd.DropZone){
\r
49337 Ext.tree.TreeDropZone = function(tree, config){
\r
49339 * @cfg {Boolean} allowParentInsert
\r
49340 * Allow inserting a dragged node between an expanded parent node and its first child that will become a
\r
49341 * sibling of the parent when dropped (defaults to false)
\r
49343 this.allowParentInsert = config.allowParentInsert || false;
\r
49345 * @cfg {String} allowContainerDrop
\r
49346 * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
\r
49348 this.allowContainerDrop = config.allowContainerDrop || false;
\r
49350 * @cfg {String} appendOnly
\r
49351 * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
\r
49353 this.appendOnly = config.appendOnly || false;
\r
49355 Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
\r
49357 * The TreePanel for this drop zone
\r
49358 * @type Ext.tree.TreePanel
\r
49361 this.tree = tree;
\r
49363 * Arbitrary data that can be associated with this tree and will be included in the event object that gets
\r
49364 * passed to any nodedragover event handler (defaults to {})
\r
49365 * @type Ext.tree.TreePanel
\r
49368 this.dragOverData = {};
\r
49370 this.lastInsertClass = "x-tree-no-status";
\r
49373 Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
\r
49375 * @cfg {String} ddGroup
\r
49376 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
49377 * interact with other drag drop objects in the same group (defaults to 'TreeDD').
\r
49379 ddGroup : "TreeDD",
\r
49382 * @cfg {String} expandDelay
\r
49383 * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
\r
49384 * over the target (defaults to 1000)
\r
49386 expandDelay : 1000,
\r
49389 expandNode : function(node){
\r
49390 if(node.hasChildNodes() && !node.isExpanded()){
\r
49391 node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
\r
49396 queueExpand : function(node){
\r
49397 this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
\r
49401 cancelExpand : function(){
\r
49402 if(this.expandProcId){
\r
49403 clearTimeout(this.expandProcId);
\r
49404 this.expandProcId = false;
\r
49409 isValidDropPoint : function(n, pt, dd, e, data){
\r
49410 if(!n || !data){ return false; }
\r
49411 var targetNode = n.node;
\r
49412 var dropNode = data.node;
\r
49413 // default drop rules
\r
49414 if(!(targetNode && targetNode.isTarget && pt)){
\r
49417 if(pt == "append" && targetNode.allowChildren === false){
\r
49420 if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
\r
49423 if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
\r
49426 // reuse the object
\r
49427 var overEvent = this.dragOverData;
\r
49428 overEvent.tree = this.tree;
\r
49429 overEvent.target = targetNode;
\r
49430 overEvent.data = data;
\r
49431 overEvent.point = pt;
\r
49432 overEvent.source = dd;
\r
49433 overEvent.rawEvent = e;
\r
49434 overEvent.dropNode = dropNode;
\r
49435 overEvent.cancel = false;
\r
49436 var result = this.tree.fireEvent("nodedragover", overEvent);
\r
49437 return overEvent.cancel === false && result !== false;
\r
49441 getDropPoint : function(e, n, dd){
\r
49444 return tn.allowChildren !== false ? "append" : false; // always append for root
\r
49446 var dragEl = n.ddel;
\r
49447 var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
\r
49448 var y = Ext.lib.Event.getPageY(e);
\r
49449 var noAppend = tn.allowChildren === false || tn.isLeaf();
\r
49450 if(this.appendOnly || tn.parentNode.allowChildren === false){
\r
49451 return noAppend ? false : "append";
\r
49453 var noBelow = false;
\r
49454 if(!this.allowParentInsert){
\r
49455 noBelow = tn.hasChildNodes() && tn.isExpanded();
\r
49457 var q = (b - t) / (noAppend ? 2 : 3);
\r
49458 if(y >= t && y < (t + q)){
\r
49460 }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
\r
49468 onNodeEnter : function(n, dd, e, data){
\r
49469 this.cancelExpand();
\r
49472 onContainerOver : function(dd, e, data) {
\r
49473 if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
\r
49474 return this.dropAllowed;
\r
49476 return this.dropNotAllowed;
\r
49480 onNodeOver : function(n, dd, e, data){
\r
49481 var pt = this.getDropPoint(e, n, dd);
\r
49482 var node = n.node;
\r
49484 // auto node expand check
\r
49485 if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
\r
49486 this.queueExpand(node);
\r
49487 }else if(pt != "append"){
\r
49488 this.cancelExpand();
\r
49491 // set the insert point style on the target node
\r
49492 var returnCls = this.dropNotAllowed;
\r
49493 if(this.isValidDropPoint(n, pt, dd, e, data)){
\r
49497 if(pt == "above"){
\r
49498 returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
\r
49499 cls = "x-tree-drag-insert-above";
\r
49500 }else if(pt == "below"){
\r
49501 returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
\r
49502 cls = "x-tree-drag-insert-below";
\r
49504 returnCls = "x-tree-drop-ok-append";
\r
49505 cls = "x-tree-drag-append";
\r
49507 if(this.lastInsertClass != cls){
\r
49508 Ext.fly(el).replaceClass(this.lastInsertClass, cls);
\r
49509 this.lastInsertClass = cls;
\r
49513 return returnCls;
\r
49517 onNodeOut : function(n, dd, e, data){
\r
49518 this.cancelExpand();
\r
49519 this.removeDropIndicators(n);
\r
49523 onNodeDrop : function(n, dd, e, data){
\r
49524 var point = this.getDropPoint(e, n, dd);
\r
49525 var targetNode = n.node;
\r
49526 targetNode.ui.startDrop();
\r
49527 if(!this.isValidDropPoint(n, point, dd, e, data)){
\r
49528 targetNode.ui.endDrop();
\r
49531 // first try to find the drop node
\r
49532 var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
\r
49533 return this.processDrop(targetNode, data, point, dd, e, dropNode);
\r
49536 onContainerDrop : function(dd, e, data){
\r
49537 if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
\r
49538 var targetNode = this.tree.getRootNode();
\r
49539 targetNode.ui.startDrop();
\r
49540 var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
\r
49541 return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
\r
49547 processDrop: function(target, data, point, dd, e, dropNode){
\r
49548 var dropEvent = {
\r
49549 tree : this.tree,
\r
49555 dropNode: dropNode,
\r
49556 cancel: !dropNode,
\r
49557 dropStatus: false
\r
49559 var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
\r
49560 if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
\r
49561 target.ui.endDrop();
\r
49562 return dropEvent.dropStatus;
\r
49565 target = dropEvent.target;
\r
49566 if(point == 'append' && !target.isExpanded()){
\r
49567 target.expand(false, null, function(){
\r
49568 this.completeDrop(dropEvent);
\r
49569 }.createDelegate(this));
\r
49571 this.completeDrop(dropEvent);
\r
49577 completeDrop : function(de){
\r
49578 var ns = de.dropNode, p = de.point, t = de.target;
\r
49579 if(!Ext.isArray(ns)){
\r
49583 for(var i = 0, len = ns.length; i < len; i++){
\r
49585 if(p == "above"){
\r
49586 t.parentNode.insertBefore(n, t);
\r
49587 }else if(p == "below"){
\r
49588 t.parentNode.insertBefore(n, t.nextSibling);
\r
49590 t.appendChild(n);
\r
49594 if(Ext.enableFx && this.tree.hlDrop){
\r
49595 n.ui.highlight();
\r
49598 this.tree.fireEvent("nodedrop", de);
\r
49602 afterNodeMoved : function(dd, data, e, targetNode, dropNode){
\r
49603 if(Ext.enableFx && this.tree.hlDrop){
\r
49604 dropNode.ui.focus();
\r
49605 dropNode.ui.highlight();
\r
49607 this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
\r
49611 getTree : function(){
\r
49612 return this.tree;
\r
49616 removeDropIndicators : function(n){
\r
49619 Ext.fly(el).removeClass([
\r
49620 "x-tree-drag-insert-above",
\r
49621 "x-tree-drag-insert-below",
\r
49622 "x-tree-drag-append"]);
\r
49623 this.lastInsertClass = "_noclass";
\r
49628 beforeDragDrop : function(target, e, id){
\r
49629 this.cancelExpand();
\r
49634 afterRepair : function(data){
\r
49635 if(data && Ext.enableFx){
\r
49636 data.node.ui.highlight();
\r
49638 this.hideProxy();
\r
49643 * @class Ext.tree.TreeDragZone
\r
49644 * @extends Ext.dd.DragZone
\r
49646 * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging
\r
49647 * @param {Object} config
\r
49649 if(Ext.dd.DragZone){
\r
49650 Ext.tree.TreeDragZone = function(tree, config){
\r
49651 Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
\r
49653 * The TreePanel for this drag zone
\r
49654 * @type Ext.tree.TreePanel
\r
49657 this.tree = tree;
\r
49660 Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
\r
49662 * @cfg {String} ddGroup
\r
49663 * A named drag drop group to which this object belongs. If a group is specified, then this object will only
\r
49664 * interact with other drag drop objects in the same group (defaults to 'TreeDD').
\r
49666 ddGroup : "TreeDD",
\r
49669 onBeforeDrag : function(data, e){
\r
49670 var n = data.node;
\r
49671 return n && n.draggable && !n.disabled;
\r
49675 onInitDrag : function(e){
\r
49676 var data = this.dragData;
\r
49677 this.tree.getSelectionModel().select(data.node);
\r
49678 this.tree.eventModel.disable();
\r
49679 this.proxy.update("");
\r
49680 data.node.ui.appendDDGhost(this.proxy.ghost.dom);
\r
49681 this.tree.fireEvent("startdrag", this.tree, data.node, e);
\r
49685 getRepairXY : function(e, data){
\r
49686 return data.node.ui.getDDRepairXY();
\r
49690 onEndDrag : function(data, e){
\r
49691 this.tree.eventModel.enable.defer(100, this.tree.eventModel);
\r
49692 this.tree.fireEvent("enddrag", this.tree, data.node, e);
\r
49696 onValidDrop : function(dd, e, id){
\r
49697 this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
\r
49698 this.hideProxy();
\r
49702 beforeInvalidDrop : function(e, id){
\r
49703 // this scrolls the original position back into view
\r
49704 var sm = this.tree.getSelectionModel();
\r
49705 sm.clearSelections();
\r
49706 sm.select(this.dragData.node);
\r
49710 afterRepair : function(){
\r
49711 if (Ext.enableFx && this.tree.hlDrop) {
\r
49712 Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
\r
49714 this.dragging = false;
\r
49718 * @class Ext.tree.TreeEditor
49719 * @extends Ext.Editor
49720 * Provides editor functionality for inline tree node editing. Any valid {@link Ext.form.Field} subclass can be used
49721 * as the editor field.
49723 * @param {TreePanel} tree
49724 * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
49725 * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
49726 * @param {Object} config (optional) A TreeEditor config object
49728 Ext.tree.TreeEditor = function(tree, fc, config){
49730 var field = fc.events ? fc : new Ext.form.TextField(fc);
49731 Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
49735 if(!tree.rendered){
49736 tree.on('render', this.initEditor, this);
49738 this.initEditor(tree);
49742 Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
49744 * @cfg {String} alignment
49745 * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
49751 * @cfg {Boolean} hideEl
49752 * True to hide the bound element while the editor is displayed (defaults to false)
49756 * @cfg {String} cls
49757 * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
49759 cls: "x-small-editor x-tree-editor",
49761 * @cfg {Boolean} shim
49762 * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
49768 * @cfg {Number} maxWidth
49769 * The maximum width in pixels of the editor field (defaults to 250). Note that if the maxWidth would exceed
49770 * the containing tree element's size, it will be automatically limited for you to the container width, taking
49771 * scroll and client offsets into account prior to each edit.
49775 * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
49776 * editing on the current node (defaults to 350). If two clicks occur on the same node within this time span,
49777 * the editor for the node will display, otherwise it will be processed as a regular click.
49781 initEditor : function(tree){
49782 tree.on('beforeclick', this.beforeNodeClick, this);
49783 tree.on('dblclick', this.onNodeDblClick, this);
49784 this.on('complete', this.updateNode, this);
49785 this.on('beforestartedit', this.fitToTree, this);
49786 this.on('startedit', this.bindScroll, this, {delay:10});
49787 this.on('specialkey', this.onSpecialKey, this);
49791 fitToTree : function(ed, el){
49792 var td = this.tree.getTreeEl().dom, nd = el.dom;
49793 if(td.scrollLeft > nd.offsetLeft){ // ensure the node left point is visible
49794 td.scrollLeft = nd.offsetLeft;
49798 (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
49799 this.setSize(w, '');
49803 * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
49804 * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
49806 triggerEdit : function(node, defer){
49807 this.completeEdit();
49808 if(node.attributes.editable !== false){
49810 * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
49811 * @type Ext.tree.TreeNode
49812 * @property editNode
49814 this.editNode = node;
49815 if(this.tree.autoScroll){
49816 Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
49818 var value = node.text || '';
49819 if (!Ext.isGecko && Ext.isEmpty(node.text)){
49820 node.setText(' ');
49822 this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
49828 bindScroll : function(){
49829 this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
49833 beforeNodeClick : function(node, e){
49834 clearTimeout(this.autoEditTimer);
49835 if(this.tree.getSelectionModel().isSelected(node)){
49837 return this.triggerEdit(node);
49841 onNodeDblClick : function(node, e){
49842 clearTimeout(this.autoEditTimer);
49846 updateNode : function(ed, value){
49847 this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
49848 this.editNode.setText(value);
49852 onHide : function(){
49853 Ext.tree.TreeEditor.superclass.onHide.call(this);
49855 this.editNode.ui.focus.defer(50, this.editNode.ui);
49860 onSpecialKey : function(field, e){
49861 var k = e.getKey();
49865 }else if(k == e.ENTER && !e.hasModifier()){
49867 this.completeEdit();
49870 });/*! SWFObject v2.2 <http://code.google.com/p/swfobject/>
\r
49871 is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
\r
49874 var swfobject = function() {
\r
49876 var UNDEF = "undefined",
\r
49877 OBJECT = "object",
\r
49878 SHOCKWAVE_FLASH = "Shockwave Flash",
\r
49879 SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
\r
49880 FLASH_MIME_TYPE = "application/x-shockwave-flash",
\r
49881 EXPRESS_INSTALL_ID = "SWFObjectExprInst",
\r
49882 ON_READY_STATE_CHANGE = "onreadystatechange",
\r
49889 domLoadFnArr = [main],
\r
49892 listenersArr = [],
\r
49893 storedAltContent,
\r
49894 storedAltContentId,
\r
49895 storedCallbackFn,
\r
49896 storedCallbackObj,
\r
49897 isDomLoaded = false,
\r
49898 isExpressInstallActive = false,
\r
49899 dynamicStylesheet,
\r
49900 dynamicStylesheetMedia,
\r
49901 autoHideShow = true,
\r
49903 /* Centralized function for browser feature detection
\r
49904 - User agent string detection is only used when no good alternative is possible
\r
49905 - Is executed directly for optimal performance
\r
49907 ua = function() {
\r
49908 var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
\r
49909 u = nav.userAgent.toLowerCase(),
\r
49910 p = nav.platform.toLowerCase(),
\r
49911 windows = p ? /win/.test(p) : /win/.test(u),
\r
49912 mac = p ? /mac/.test(p) : /mac/.test(u),
\r
49913 webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
\r
49914 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
49915 playerVersion = [0,0,0],
\r
49917 if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
\r
49918 d = nav.plugins[SHOCKWAVE_FLASH].description;
\r
49919 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
49921 ie = false; // cascaded feature detection for Internet Explorer
\r
49922 d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
\r
49923 playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
\r
49924 playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
\r
49925 playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
\r
49928 else if (typeof win.ActiveXObject != UNDEF) {
\r
49930 var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
\r
49931 if (a) { // a will return null when ActiveX is disabled
\r
49932 d = a.GetVariable("$version");
\r
49934 ie = true; // cascaded feature detection for Internet Explorer
\r
49935 d = d.split(" ")[1].split(",");
\r
49936 playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
\r
49942 return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
\r
49945 /* Cross-browser onDomLoad
\r
49946 - Will fire an event as soon as the DOM of a web page is loaded
\r
49947 - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/
\r
49948 - Regular onload serves as fallback
\r
49950 onDomLoad = function() {
\r
49951 if (!ua.w3) { return; }
\r
49952 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
49953 callDomLoadFunctions();
\r
49955 if (!isDomLoaded) {
\r
49956 if (typeof doc.addEventListener != UNDEF) {
\r
49957 doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
\r
49959 if (ua.ie && ua.win) {
\r
49960 doc.attachEvent(ON_READY_STATE_CHANGE, function() {
\r
49961 if (doc.readyState == "complete") {
\r
49962 doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
\r
49963 callDomLoadFunctions();
\r
49966 if (win == top) { // if not inside an iframe
\r
49968 if (isDomLoaded) { return; }
\r
49970 doc.documentElement.doScroll("left");
\r
49973 setTimeout(arguments.callee, 0);
\r
49976 callDomLoadFunctions();
\r
49982 if (isDomLoaded) { return; }
\r
49983 if (!/loaded|complete/.test(doc.readyState)) {
\r
49984 setTimeout(arguments.callee, 0);
\r
49987 callDomLoadFunctions();
\r
49990 addLoadEvent(callDomLoadFunctions);
\r
49994 function callDomLoadFunctions() {
\r
49995 if (isDomLoaded) { return; }
\r
49996 try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early
\r
49997 var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
\r
49998 t.parentNode.removeChild(t);
\r
50000 catch (e) { return; }
\r
50001 isDomLoaded = true;
\r
50002 var dl = domLoadFnArr.length;
\r
50003 for (var i = 0; i < dl; i++) {
\r
50004 domLoadFnArr[i]();
\r
50008 function addDomLoadEvent(fn) {
\r
50009 if (isDomLoaded) {
\r
50013 domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
\r
50017 /* Cross-browser onload
\r
50018 - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
\r
50019 - Will fire an event as soon as a web page including all of its assets are loaded
\r
50021 function addLoadEvent(fn) {
\r
50022 if (typeof win.addEventListener != UNDEF) {
\r
50023 win.addEventListener("load", fn, false);
\r
50025 else if (typeof doc.addEventListener != UNDEF) {
\r
50026 doc.addEventListener("load", fn, false);
\r
50028 else if (typeof win.attachEvent != UNDEF) {
\r
50029 addListener(win, "onload", fn);
\r
50031 else if (typeof win.onload == "function") {
\r
50032 var fnOld = win.onload;
\r
50033 win.onload = function() {
\r
50044 - Will preferably execute onDomLoad, otherwise onload (as a fallback)
\r
50046 function main() {
\r
50048 testPlayerVersion();
\r
50055 /* Detect the Flash Player version for non-Internet Explorer browsers
\r
50056 - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description:
\r
50057 a. Both release and build numbers can be detected
\r
50058 b. Avoid wrong descriptions by corrupt installers provided by Adobe
\r
50059 c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports
\r
50060 - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available
\r
50062 function testPlayerVersion() {
\r
50063 var b = doc.getElementsByTagName("body")[0];
\r
50064 var o = createElement(OBJECT);
\r
50065 o.setAttribute("type", FLASH_MIME_TYPE);
\r
50066 var t = b.appendChild(o);
\r
50070 if (typeof t.GetVariable != UNDEF) {
\r
50071 var d = t.GetVariable("$version");
\r
50073 d = d.split(" ")[1].split(",");
\r
50074 ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
\r
50077 else if (counter < 10) {
\r
50079 setTimeout(arguments.callee, 10);
\r
50082 b.removeChild(o);
\r
50092 /* Perform Flash Player and SWF version matching; static publishing only
\r
50094 function matchVersions() {
\r
50095 var rl = regObjArr.length;
\r
50097 for (var i = 0; i < rl; i++) { // for each registered object element
\r
50098 var id = regObjArr[i].id;
\r
50099 var cb = regObjArr[i].callbackFn;
\r
50100 var cbObj = {success:false, id:id};
\r
50101 if (ua.pv[0] > 0) {
\r
50102 var obj = getElementById(id);
\r
50104 if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match!
\r
50105 setVisibility(id, true);
\r
50107 cbObj.success = true;
\r
50108 cbObj.ref = getObjectById(id);
\r
50112 else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported
\r
50114 att.data = regObjArr[i].expressInstall;
\r
50115 att.width = obj.getAttribute("width") || "0";
\r
50116 att.height = obj.getAttribute("height") || "0";
\r
50117 if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
\r
50118 if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
\r
50119 // parse HTML object param element's name-value pairs
\r
50121 var p = obj.getElementsByTagName("param");
\r
50122 var pl = p.length;
\r
50123 for (var j = 0; j < pl; j++) {
\r
50124 if (p[j].getAttribute("name").toLowerCase() != "movie") {
\r
50125 par[p[j].getAttribute("name")] = p[j].getAttribute("value");
\r
50128 showExpressInstall(att, par, id, cb);
\r
50130 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
50131 displayAltContent(obj);
\r
50132 if (cb) { cb(cbObj); }
\r
50136 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
50137 setVisibility(id, true);
\r
50139 var o = getObjectById(id); // test whether there is an HTML object element or not
\r
50140 if (o && typeof o.SetVariable != UNDEF) {
\r
50141 cbObj.success = true;
\r
50151 function getObjectById(objectIdStr) {
\r
50153 var o = getElementById(objectIdStr);
\r
50154 if (o && o.nodeName == "OBJECT") {
\r
50155 if (typeof o.SetVariable != UNDEF) {
\r
50159 var n = o.getElementsByTagName(OBJECT)[0];
\r
50168 /* Requirements for Adobe Express Install
\r
50169 - only one instance can be active at a time
\r
50170 - fp 6.0.65 or higher
\r
50171 - Win/Mac OS only
\r
50172 - no Webkit engines older than version 312
\r
50174 function canExpressInstall() {
\r
50175 return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
\r
50178 /* Show the Adobe Express Install dialog
\r
50179 - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
\r
50181 function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
\r
50182 isExpressInstallActive = true;
\r
50183 storedCallbackFn = callbackFn || null;
\r
50184 storedCallbackObj = {success:false, id:replaceElemIdStr};
\r
50185 var obj = getElementById(replaceElemIdStr);
\r
50187 if (obj.nodeName == "OBJECT") { // static publishing
\r
50188 storedAltContent = abstractAltContent(obj);
\r
50189 storedAltContentId = null;
\r
50191 else { // dynamic publishing
\r
50192 storedAltContent = obj;
\r
50193 storedAltContentId = replaceElemIdStr;
\r
50195 att.id = EXPRESS_INSTALL_ID;
\r
50196 if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
\r
50197 if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
\r
50198 doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
\r
50199 var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
\r
50200 fv = "MMredirectURL=" + win.location.toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
\r
50201 if (typeof par.flashvars != UNDEF) {
\r
50202 par.flashvars += "&" + fv;
\r
50205 par.flashvars = fv;
\r
50207 // 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
50208 // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
\r
50209 if (ua.ie && ua.win && obj.readyState != 4) {
\r
50210 var newObj = createElement("div");
\r
50211 replaceElemIdStr += "SWFObjectNew";
\r
50212 newObj.setAttribute("id", replaceElemIdStr);
\r
50213 obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf
\r
50214 obj.style.display = "none";
\r
50216 if (obj.readyState == 4) {
\r
50217 obj.parentNode.removeChild(obj);
\r
50220 setTimeout(arguments.callee, 10);
\r
50224 createSWF(att, par, replaceElemIdStr);
\r
50228 /* Functions to abstract and display alternative content
\r
50230 function displayAltContent(obj) {
\r
50231 if (ua.ie && ua.win && obj.readyState != 4) {
\r
50232 // 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
50233 // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
\r
50234 var el = createElement("div");
\r
50235 obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content
\r
50236 el.parentNode.replaceChild(abstractAltContent(obj), el);
\r
50237 obj.style.display = "none";
\r
50239 if (obj.readyState == 4) {
\r
50240 obj.parentNode.removeChild(obj);
\r
50243 setTimeout(arguments.callee, 10);
\r
50248 obj.parentNode.replaceChild(abstractAltContent(obj), obj);
\r
50252 function abstractAltContent(obj) {
\r
50253 var ac = createElement("div");
\r
50254 if (ua.win && ua.ie) {
\r
50255 ac.innerHTML = obj.innerHTML;
\r
50258 var nestedObj = obj.getElementsByTagName(OBJECT)[0];
\r
50260 var c = nestedObj.childNodes;
\r
50262 var cl = c.length;
\r
50263 for (var i = 0; i < cl; i++) {
\r
50264 if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
\r
50265 ac.appendChild(c[i].cloneNode(true));
\r
50274 /* Cross-browser dynamic SWF creation
\r
50276 function createSWF(attObj, parObj, id) {
\r
50277 var r, el = getElementById(id);
\r
50278 if (ua.wk && ua.wk < 312) { return r; }
\r
50280 if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
\r
50283 if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML
\r
50285 for (var i in attObj) {
\r
50286 if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries
\r
50287 if (i.toLowerCase() == "data") {
\r
50288 parObj.movie = attObj[i];
\r
50290 else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
\r
50291 att += ' class="' + attObj[i] + '"';
\r
50293 else if (i.toLowerCase() != "classid") {
\r
50294 att += ' ' + i + '="' + attObj[i] + '"';
\r
50299 for (var j in parObj) {
\r
50300 if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries
\r
50301 par += '<param name="' + j + '" value="' + parObj[j] + '" />';
\r
50304 el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
\r
50305 objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only)
\r
50306 r = getElementById(attObj.id);
\r
50308 else { // well-behaving browsers
\r
50309 var o = createElement(OBJECT);
\r
50310 o.setAttribute("type", FLASH_MIME_TYPE);
\r
50311 for (var m in attObj) {
\r
50312 if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries
\r
50313 if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
\r
50314 o.setAttribute("class", attObj[m]);
\r
50316 else if (m.toLowerCase() != "classid") { // filter out IE specific attribute
\r
50317 o.setAttribute(m, attObj[m]);
\r
50321 for (var n in parObj) {
\r
50322 if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element
\r
50323 createObjParam(o, n, parObj[n]);
\r
50326 el.parentNode.replaceChild(o, el);
\r
50333 function createObjParam(el, pName, pValue) {
\r
50334 var p = createElement("param");
\r
50335 p.setAttribute("name", pName);
\r
50336 p.setAttribute("value", pValue);
\r
50337 el.appendChild(p);
\r
50340 /* Cross-browser SWF removal
\r
50341 - Especially needed to safely and completely remove a SWF in Internet Explorer
\r
50343 function removeSWF(id) {
\r
50344 var obj = getElementById(id);
\r
50345 if (obj && obj.nodeName == "OBJECT") {
\r
50346 if (ua.ie && ua.win) {
\r
50347 obj.style.display = "none";
\r
50349 if (obj.readyState == 4) {
\r
50350 removeObjectInIE(id);
\r
50353 setTimeout(arguments.callee, 10);
\r
50358 obj.parentNode.removeChild(obj);
\r
50363 function removeObjectInIE(id) {
\r
50364 var obj = getElementById(id);
\r
50366 for (var i in obj) {
\r
50367 if (typeof obj[i] == "function") {
\r
50371 obj.parentNode.removeChild(obj);
\r
50375 /* Functions to optimize JavaScript compression
\r
50377 function getElementById(id) {
\r
50380 el = doc.getElementById(id);
\r
50386 function createElement(el) {
\r
50387 return doc.createElement(el);
\r
50390 /* Updated attachEvent function for Internet Explorer
\r
50391 - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
\r
50393 function addListener(target, eventType, fn) {
\r
50394 target.attachEvent(eventType, fn);
\r
50395 listenersArr[listenersArr.length] = [target, eventType, fn];
\r
50398 /* Flash Player and SWF content version matching
\r
50400 function hasPlayerVersion(rv) {
\r
50401 var pv = ua.pv, v = rv.split(".");
\r
50402 v[0] = parseInt(v[0], 10);
\r
50403 v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
\r
50404 v[2] = parseInt(v[2], 10) || 0;
\r
50405 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
50408 /* Cross-browser dynamic CSS creation
\r
50409 - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
\r
50411 function createCSS(sel, decl, media, newStyle) {
\r
50412 if (ua.ie && ua.mac) { return; }
\r
50413 var h = doc.getElementsByTagName("head")[0];
\r
50414 if (!h) { return; } // to also support badly authored HTML pages that lack a head element
\r
50415 var m = (media && typeof media == "string") ? media : "screen";
\r
50417 dynamicStylesheet = null;
\r
50418 dynamicStylesheetMedia = null;
\r
50420 if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
\r
50421 // create dynamic stylesheet + get a global reference to it
\r
50422 var s = createElement("style");
\r
50423 s.setAttribute("type", "text/css");
\r
50424 s.setAttribute("media", m);
\r
50425 dynamicStylesheet = h.appendChild(s);
\r
50426 if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
\r
50427 dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
\r
50429 dynamicStylesheetMedia = m;
\r
50431 // add style rule
\r
50432 if (ua.ie && ua.win) {
\r
50433 if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
\r
50434 dynamicStylesheet.addRule(sel, decl);
\r
50438 if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
\r
50439 dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
\r
50444 function setVisibility(id, isVisible) {
\r
50445 if (!autoHideShow) { return; }
\r
50446 var v = isVisible ? "visible" : "hidden";
\r
50447 if (isDomLoaded && getElementById(id)) {
\r
50448 getElementById(id).style.visibility = v;
\r
50451 createCSS("#" + id, "visibility:" + v);
\r
50455 /* Filter to avoid XSS attacks
\r
50457 function urlEncodeIfNecessary(s) {
\r
50458 var regex = /[\\\"<>\.;]/;
\r
50459 var hasBadChars = regex.exec(s) != null;
\r
50460 return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
\r
50463 /* 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
50465 var cleanup = function() {
\r
50466 if (ua.ie && ua.win) {
\r
50467 window.attachEvent("onunload", function() {
\r
50468 // remove listeners to avoid memory leaks
\r
50469 var ll = listenersArr.length;
\r
50470 for (var i = 0; i < ll; i++) {
\r
50471 listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
\r
50473 // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
\r
50474 var il = objIdArr.length;
\r
50475 for (var j = 0; j < il; j++) {
\r
50476 removeSWF(objIdArr[j]);
\r
50478 // cleanup library's main closures to avoid memory leaks
\r
50479 for (var k in ua) {
\r
50483 for (var l in swfobject) {
\r
50484 swfobject[l] = null;
\r
50486 swfobject = null;
\r
50493 - Reference: http://code.google.com/p/swfobject/wiki/documentation
\r
50495 registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
\r
50496 if (ua.w3 && objectIdStr && swfVersionStr) {
\r
50498 regObj.id = objectIdStr;
\r
50499 regObj.swfVersion = swfVersionStr;
\r
50500 regObj.expressInstall = xiSwfUrlStr;
\r
50501 regObj.callbackFn = callbackFn;
\r
50502 regObjArr[regObjArr.length] = regObj;
\r
50503 setVisibility(objectIdStr, false);
\r
50505 else if (callbackFn) {
\r
50506 callbackFn({success:false, id:objectIdStr});
\r
50510 getObjectById: function(objectIdStr) {
\r
50512 return getObjectById(objectIdStr);
\r
50516 embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
\r
50517 var callbackObj = {success:false, id:replaceElemIdStr};
\r
50518 if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
\r
50519 setVisibility(replaceElemIdStr, false);
\r
50520 addDomLoadEvent(function() {
\r
50521 widthStr += ""; // auto-convert to string
\r
50524 if (attObj && typeof attObj === OBJECT) {
\r
50525 for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs
\r
50526 att[i] = attObj[i];
\r
50529 att.data = swfUrlStr;
\r
50530 att.width = widthStr;
\r
50531 att.height = heightStr;
\r
50533 if (parObj && typeof parObj === OBJECT) {
\r
50534 for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs
\r
50535 par[j] = parObj[j];
\r
50538 if (flashvarsObj && typeof flashvarsObj === OBJECT) {
\r
50539 for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs
\r
50540 if (typeof par.flashvars != UNDEF) {
\r
50541 par.flashvars += "&" + k + "=" + flashvarsObj[k];
\r
50544 par.flashvars = k + "=" + flashvarsObj[k];
\r
50548 if (hasPlayerVersion(swfVersionStr)) { // create SWF
\r
50549 var obj = createSWF(att, par, replaceElemIdStr);
\r
50550 if (att.id == replaceElemIdStr) {
\r
50551 setVisibility(replaceElemIdStr, true);
\r
50553 callbackObj.success = true;
\r
50554 callbackObj.ref = obj;
\r
50556 else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install
\r
50557 att.data = xiSwfUrlStr;
\r
50558 showExpressInstall(att, par, replaceElemIdStr, callbackFn);
\r
50561 else { // show alternative content
\r
50562 setVisibility(replaceElemIdStr, true);
\r
50564 if (callbackFn) { callbackFn(callbackObj); }
\r
50567 else if (callbackFn) { callbackFn(callbackObj); }
\r
50570 switchOffAutoHideShow: function() {
\r
50571 autoHideShow = false;
\r
50576 getFlashPlayerVersion: function() {
\r
50577 return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
\r
50580 hasFlashPlayerVersion: hasPlayerVersion,
\r
50582 createSWF: function(attObj, parObj, replaceElemIdStr) {
\r
50584 return createSWF(attObj, parObj, replaceElemIdStr);
\r
50587 return undefined;
\r
50591 showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
\r
50592 if (ua.w3 && canExpressInstall()) {
\r
50593 showExpressInstall(att, par, replaceElemIdStr, callbackFn);
\r
50597 removeSWF: function(objElemIdStr) {
\r
50599 removeSWF(objElemIdStr);
\r
50603 createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
\r
50605 createCSS(selStr, declStr, mediaStr, newStyleBoolean);
\r
50609 addDomLoadEvent: addDomLoadEvent,
\r
50611 addLoadEvent: addLoadEvent,
\r
50613 getQueryParamValue: function(param) {
\r
50614 var q = doc.location.search || doc.location.hash;
\r
50616 if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark
\r
50617 if (param == null) {
\r
50618 return urlEncodeIfNecessary(q);
\r
50620 var pairs = q.split("&");
\r
50621 for (var i = 0; i < pairs.length; i++) {
\r
50622 if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
\r
50623 return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
\r
50630 // For internal usage only
\r
50631 expressInstallCallback: function() {
\r
50632 if (isExpressInstallActive) {
\r
50633 var obj = getElementById(EXPRESS_INSTALL_ID);
\r
50634 if (obj && storedAltContent) {
\r
50635 obj.parentNode.replaceChild(storedAltContent, obj);
\r
50636 if (storedAltContentId) {
\r
50637 setVisibility(storedAltContentId, true);
\r
50638 if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
\r
50640 if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
\r
50642 isExpressInstallActive = false;
\r
50648 * @class Ext.FlashComponent
50649 * @extends Ext.BoxComponent
50653 Ext.FlashComponent = Ext.extend(Ext.BoxComponent, {
50655 * @cfg {String} flashVersion
50656 * Indicates the version the flash content was published for. Defaults to <tt>'9.0.45'</tt>.
50658 flashVersion : '9.0.45',
50661 * @cfg {String} backgroundColor
50662 * The background color of the chart. Defaults to <tt>'#ffffff'</tt>.
50664 backgroundColor: '#ffffff',
50667 * @cfg {String} wmode
50668 * The wmode of the flash object. This can be used to control layering. Defaults to <tt>'opaque'</tt>.
50673 * @cfg {Object} flashVars
50674 * A set of key value pairs to be passed to the flash object as flash variables. Defaults to <tt>undefined</tt>.
50676 flashVars: undefined,
50679 * @cfg {Object} flashParams
50680 * A set of key value pairs to be passed to the flash object as parameters. Possible parameters can be found here:
50681 * http://kb2.adobe.com/cps/127/tn_12701.html Defaults to <tt>undefined</tt>.
50683 flashParams: undefined,
50686 * @cfg {String} url
50687 * The URL of the chart to include. Defaults to <tt>undefined</tt>.
50695 * @cfg {Boolean} expressInstall
50696 * True to prompt the user to install flash if not installed. Note that this uses
50697 * Ext.FlashComponent.EXPRESS_INSTALL_URL, which should be set to the local resource. Defaults to <tt>false</tt>.
50699 expressInstall: false,
50701 initComponent : function(){
50702 Ext.FlashComponent.superclass.initComponent.call(this);
50704 this.addEvents('initialize');
50707 onRender : function(){
50708 Ext.FlashComponent.superclass.onRender.apply(this, arguments);
50710 var params = Ext.apply({
50711 allowScriptAccess: 'always',
50712 bgcolor: this.backgroundColor,
50714 }, this.flashParams), vars = Ext.apply({
50715 allowedDomain: document.location.hostname,
50716 elementID: this.getId(),
50717 eventHandler: 'Ext.FlashEventProxy.onEvent'
50718 }, this.flashVars);
50720 new swfobject.embedSWF(this.url, this.id, this.swfWidth, this.swfHeight, this.flashVersion,
50721 this.expressInstall ? Ext.FlashComponent.EXPRESS_INSTALL_URL : undefined, vars, params);
50723 this.swf = Ext.getDom(this.id);
50724 this.el = Ext.get(this.swf);
50727 getSwfId : function(){
50728 return this.swfId || (this.swfId = "extswf" + (++Ext.Component.AUTO_ID));
50731 getId : function(){
50732 return this.id || (this.id = "extflashcmp" + (++Ext.Component.AUTO_ID));
50735 onFlashEvent : function(e){
50743 e.component = this;
50744 this.fireEvent(e.type.toLowerCase().replace(/event$/, ''), e);
50747 initSwf : function(){
50748 this.onSwfReady(!!this.isInitialized);
50749 this.isInitialized = true;
50750 this.fireEvent('initialize', this);
50753 beforeDestroy: function(){
50755 swfobject.removeSWF(this.swf.id);
50757 Ext.FlashComponent.superclass.beforeDestroy.call(this);
50760 onSwfReady : Ext.emptyFn
50764 * Sets the url for installing flash if it doesn't exist. This should be set to a local resource.
50768 Ext.FlashComponent.EXPRESS_INSTALL_URL = 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf';
50770 Ext.reg('flash', Ext.FlashComponent);/**
\r
50771 * @class Ext.FlashProxy
\r
50774 Ext.FlashEventProxy = {
\r
50775 onEvent : function(id, e){
\r
50776 var fp = Ext.getCmp(id);
\r
50778 fp.onFlashEvent(e);
\r
50780 arguments.callee.defer(10, this, [id, e]);
\r
50784 * @class Ext.chart.Chart
\r
50785 * @extends Ext.FlashComponent
\r
50786 * The Ext.chart package provides the capability to visualize data with flash based charting.
\r
50787 * Each chart binds directly to an Ext.data.Store enabling automatic updates of the chart.
\r
50792 Ext.chart.Chart = Ext.extend(Ext.FlashComponent, {
\r
50793 refreshBuffer: 100,
\r
50796 * @cfg {Object} chartStyle
\r
50797 * Sets styles for this chart. Contains a number of default values. Modifying this property will override
\r
50798 * the base styles on the chart.
\r
50802 animationEnabled: true,
\r
50828 * @cfg {String} url
\r
50829 * The url to load the chart from. This defaults to Ext.chart.Chart.CHART_URL, which should
\r
50830 * be modified to point to the local charts resource.
\r
50834 * @cfg {Object} extraStyle
\r
50835 * Contains extra styles that will be added or overwritten to the default chartStyle. Defaults to <tt>null</tt>.
\r
50837 extraStyle: null,
\r
50840 * @cfg {Boolean} disableCaching
\r
50841 * True to add a "cache buster" to the end of the chart url. Defaults to true for Opera and IE.
\r
50843 disableCaching: Ext.isIE || Ext.isOpera,
\r
50844 disableCacheParam: '_dc',
\r
50846 initComponent : function(){
\r
50847 Ext.chart.Chart.superclass.initComponent.call(this);
\r
50849 this.url = Ext.chart.Chart.CHART_URL;
\r
50851 if(this.disableCaching){
\r
50852 this.url = Ext.urlAppend(this.url, String.format('{0}={1}', this.disableCacheParam, new Date().getTime()));
\r
50858 'itemdoubleclick',
\r
50863 this.store = Ext.StoreMgr.lookup(this.store);
\r
50867 * Sets a single style value on the Chart instance.
\r
50869 * @param name {String} Name of the Chart style value to change.
\r
50870 * @param value {Object} New value to pass to the Chart style.
\r
50872 setStyle: function(name, value){
\r
50873 this.swf.setStyle(name, Ext.encode(value));
\r
50877 * Resets all styles on the Chart instance.
\r
50879 * @param styles {Object} Initializer for all Chart styles.
\r
50881 setStyles: function(styles){
\r
50882 this.swf.setStyles(Ext.encode(styles));
\r
50886 * Sets the styles on all series in the Chart.
\r
50888 * @param styles {Array} Initializer for all Chart series styles.
\r
50890 setSeriesStyles: function(styles){
\r
50892 Ext.each(styles, function(style){
\r
50893 s.push(Ext.encode(style));
\r
50895 this.swf.setSeriesStyles(s);
\r
50898 setCategoryNames : function(names){
\r
50899 this.swf.setCategoryNames(names);
\r
50902 setTipRenderer : function(fn){
\r
50903 var chart = this;
\r
50904 this.tipFnName = this.createFnProxy(function(item, index, series){
\r
50905 var record = chart.store.getAt(index);
\r
50906 return fn(chart, record, index, series);
\r
50907 }, this.tipFnName);
\r
50908 this.swf.setDataTipFunction(this.tipFnName);
\r
50911 setSeries : function(series){
\r
50912 this.series = series;
\r
50917 * Changes the data store bound to this chart and refreshes it.
\r
50918 * @param {Store} store The store to bind to this chart
\r
50920 bindStore : function(store, initial){
\r
50921 if(!initial && this.store){
\r
50922 if(store !== this.store && this.store.autoDestroy){
\r
50923 this.store.destroy();
\r
50925 this.store.un("datachanged", this.refresh, this);
\r
50926 this.store.un("add", this.delayRefresh, this);
\r
50927 this.store.un("remove", this.delayRefresh, this);
\r
50928 this.store.un("update", this.delayRefresh, this);
\r
50929 this.store.un("clear", this.refresh, this);
\r
50933 store = Ext.StoreMgr.lookup(store);
\r
50936 datachanged: this.refresh,
\r
50937 add: this.delayRefresh,
\r
50938 remove: this.delayRefresh,
\r
50939 update: this.delayRefresh,
\r
50940 clear: this.refresh
\r
50943 this.store = store;
\r
50944 if(store && !initial){
\r
50949 onSwfReady : function(isReset){
\r
50950 Ext.chart.Chart.superclass.onSwfReady.call(this, isReset);
\r
50951 this.swf.setType(this.type);
\r
50953 if(this.chartStyle){
\r
50954 this.setStyles(Ext.apply({}, this.extraStyle, this.chartStyle));
\r
50957 if(this.categoryNames){
\r
50958 this.setCategoryNames(this.categoryNames);
\r
50961 if(this.tipRenderer){
\r
50962 this.setTipRenderer(this.tipRenderer);
\r
50965 this.bindStore(this.store, true);
\r
50967 this.refresh.defer(10, this);
\r
50970 delayRefresh : function(){
\r
50971 if(!this.refreshTask){
\r
50972 this.refreshTask = new Ext.util.DelayedTask(this.refresh, this);
\r
50974 this.refreshTask.delay(this.refreshBuffer);
\r
50977 refresh : function(){
\r
50978 var styleChanged = false;
\r
50979 // convert the store data into something YUI charts can understand
\r
50980 var data = [], rs = this.store.data.items;
\r
50981 for(var j = 0, len = rs.length; j < len; j++){
\r
50982 data[j] = rs[j].data;
\r
50984 //make a copy of the series definitions so that we aren't
\r
50985 //editing them directly.
\r
50986 var dataProvider = [];
\r
50987 var seriesCount = 0;
\r
50988 var currentSeries = null;
\r
50991 seriesCount = this.series.length;
\r
50992 for(i = 0; i < seriesCount; i++){
\r
50993 currentSeries = this.series[i];
\r
50994 var clonedSeries = {};
\r
50995 for(var prop in currentSeries){
\r
50996 if(prop == "style" && currentSeries.style !== null){
\r
50997 clonedSeries.style = Ext.encode(currentSeries.style);
\r
50998 styleChanged = true;
\r
50999 //we don't want to modify the styles again next time
\r
51000 //so null out the style property.
\r
51001 // this causes issues
\r
51002 // currentSeries.style = null;
\r
51004 clonedSeries[prop] = currentSeries[prop];
\r
51007 dataProvider.push(clonedSeries);
\r
51011 if(seriesCount > 0){
\r
51012 for(i = 0; i < seriesCount; i++){
\r
51013 currentSeries = dataProvider[i];
\r
51014 if(!currentSeries.type){
\r
51015 currentSeries.type = this.type;
\r
51017 currentSeries.dataProvider = data;
\r
51020 dataProvider.push({type: this.type, dataProvider: data});
\r
51022 this.swf.setDataProvider(dataProvider);
\r
51025 createFnProxy : function(fn, old){
\r
51027 delete window[old];
\r
51029 var fnName = "extFnProxy" + (++Ext.chart.Chart.PROXY_FN_ID);
\r
51030 window[fnName] = fn;
\r
51034 onDestroy: function(){
\r
51035 Ext.chart.Chart.superclass.onDestroy.call(this);
\r
51036 this.bindStore(null);
\r
51037 var tip = this.tipFnName;
\r
51038 if(!Ext.isEmpty(tip)){
\r
51039 delete window[tip];
\r
51043 Ext.reg('chart', Ext.chart.Chart);
\r
51044 Ext.chart.Chart.PROXY_FN_ID = 0;
\r
51047 * Sets the url to load the chart from. This should be set to a local resource.
\r
51051 Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.7.0/build/charts/assets/charts.swf';
\r
51054 * @class Ext.chart.PieChart
\r
51055 * @extends Ext.chart.Chart
\r
51057 * @xtype piechart
\r
51059 Ext.chart.PieChart = Ext.extend(Ext.chart.Chart, {
\r
51062 onSwfReady : function(isReset){
\r
51063 Ext.chart.PieChart.superclass.onSwfReady.call(this, isReset);
\r
51065 this.setDataField(this.dataField);
\r
51066 this.setCategoryField(this.categoryField);
\r
51069 setDataField : function(field){
\r
51070 this.dataField = field;
\r
51071 this.swf.setDataField(field);
\r
51074 setCategoryField : function(field){
\r
51075 this.categoryField = field;
\r
51076 this.swf.setCategoryField(field);
\r
51079 Ext.reg('piechart', Ext.chart.PieChart);
\r
51082 * @class Ext.chart.CartesianChart
\r
51083 * @extends Ext.chart.Chart
\r
51085 * @xtype cartesianchart
\r
51087 Ext.chart.CartesianChart = Ext.extend(Ext.chart.Chart, {
\r
51088 onSwfReady : function(isReset){
\r
51089 Ext.chart.CartesianChart.superclass.onSwfReady.call(this, isReset);
\r
51092 this.setXField(this.xField);
\r
51095 this.setYField(this.yField);
\r
51098 this.setXAxis(this.xAxis);
\r
51101 this.setYAxis(this.yAxis);
\r
51105 setXField : function(value){
\r
51106 this.xField = value;
\r
51107 this.swf.setHorizontalField(value);
\r
51110 setYField : function(value){
\r
51111 this.yField = value;
\r
51112 this.swf.setVerticalField(value);
\r
51115 setXAxis : function(value){
\r
51116 this.xAxis = this.createAxis('xAxis', value);
\r
51117 this.swf.setHorizontalAxis(this.xAxis);
\r
51120 setYAxis : function(value){
\r
51121 this.yAxis = this.createAxis('yAxis', value);
\r
51122 this.swf.setVerticalAxis(this.yAxis);
\r
51125 createAxis : function(axis, value){
\r
51126 var o = Ext.apply({}, value), oldFn = null;
\r
51128 oldFn = this[axis].labelFunction;
\r
51130 if(o.labelRenderer){
\r
51131 var fn = o.labelRenderer;
\r
51132 o.labelFunction = this.createFnProxy(function(v){
\r
51135 delete o.labelRenderer;
\r
51140 Ext.reg('cartesianchart', Ext.chart.CartesianChart);
\r
51143 * @class Ext.chart.LineChart
\r
51144 * @extends Ext.chart.CartesianChart
\r
51146 * @xtype linechart
\r
51148 Ext.chart.LineChart = Ext.extend(Ext.chart.CartesianChart, {
\r
51151 Ext.reg('linechart', Ext.chart.LineChart);
\r
51154 * @class Ext.chart.ColumnChart
\r
51155 * @extends Ext.chart.CartesianChart
\r
51157 * @xtype columnchart
\r
51159 Ext.chart.ColumnChart = Ext.extend(Ext.chart.CartesianChart, {
\r
51162 Ext.reg('columnchart', Ext.chart.ColumnChart);
\r
51165 * @class Ext.chart.StackedColumnChart
\r
51166 * @extends Ext.chart.CartesianChart
\r
51168 * @xtype stackedcolumnchart
\r
51170 Ext.chart.StackedColumnChart = Ext.extend(Ext.chart.CartesianChart, {
\r
51171 type: 'stackcolumn'
\r
51173 Ext.reg('stackedcolumnchart', Ext.chart.StackedColumnChart);
\r
51176 * @class Ext.chart.BarChart
\r
51177 * @extends Ext.chart.CartesianChart
\r
51179 * @xtype barchart
\r
51181 Ext.chart.BarChart = Ext.extend(Ext.chart.CartesianChart, {
\r
51184 Ext.reg('barchart', Ext.chart.BarChart);
\r
51187 * @class Ext.chart.StackedBarChart
\r
51188 * @extends Ext.chart.CartesianChart
\r
51190 * @xtype stackedbarchart
\r
51192 Ext.chart.StackedBarChart = Ext.extend(Ext.chart.CartesianChart, {
\r
51195 Ext.reg('stackedbarchart', Ext.chart.StackedBarChart);
\r
51200 * @class Ext.chart.Axis
\r
51201 * Defines a CartesianChart's vertical or horizontal axis.
\r
51204 Ext.chart.Axis = function(config){
\r
51205 Ext.apply(this, config);
\r
51208 Ext.chart.Axis.prototype =
\r
51211 * The type of axis.
\r
51219 * The direction in which the axis is drawn. May be "horizontal" or "vertical".
\r
51221 * @property orientation
\r
51224 orientation: "horizontal",
\r
51227 * If true, the items on the axis will be drawn in opposite direction.
\r
51229 * @property reverse
\r
51235 * A string reference to the globally-accessible function that may be called to
\r
51236 * determine each of the label values for this axis.
\r
51238 * @property labelFunction
\r
51241 labelFunction: null,
\r
51244 * If true, labels that overlap previously drawn labels on the axis will be hidden.
\r
51246 * @property hideOverlappingLabels
\r
51249 hideOverlappingLabels: true
\r
51253 * @class Ext.chart.NumericAxis
\r
51254 * @extends Ext.chart.Axis
\r
51255 * A type of axis whose units are measured in numeric values.
\r
51258 Ext.chart.NumericAxis = Ext.extend(Ext.chart.Axis, {
\r
51262 * The minimum value drawn by the axis. If not set explicitly, the axis minimum
\r
51263 * will be calculated automatically.
\r
51265 * @property minimum
\r
51271 * The maximum value drawn by the axis. If not set explicitly, the axis maximum
\r
51272 * will be calculated automatically.
\r
51274 * @property maximum
\r
51280 * The spacing between major intervals on this axis.
\r
51282 * @property majorUnit
\r
51288 * The spacing between minor intervals on this axis.
\r
51290 * @property minorUnit
\r
51296 * If true, the labels, ticks, gridlines, and other objects will snap to
\r
51297 * the nearest major or minor unit. If false, their position will be based
\r
51298 * on the minimum value.
\r
51300 * @property snapToUnits
\r
51303 snapToUnits: true,
\r
51306 * If true, and the bounds are calculated automatically, either the minimum or
\r
51307 * maximum will be set to zero.
\r
51309 * @property alwaysShowZero
\r
51312 alwaysShowZero: true,
\r
51315 * The scaling algorithm to use on this axis. May be "linear" or "logarithmic".
\r
51317 * @property scale
\r
51324 * @class Ext.chart.TimeAxis
\r
51325 * @extends Ext.chart.Axis
\r
51326 * A type of axis whose units are measured in time-based values.
\r
51329 Ext.chart.TimeAxis = Ext.extend(Ext.chart.Axis, {
\r
51333 * The minimum value drawn by the axis. If not set explicitly, the axis minimum
\r
51334 * will be calculated automatically.
\r
51336 * @property minimum
\r
51342 * The maximum value drawn by the axis. If not set explicitly, the axis maximum
\r
51343 * will be calculated automatically.
\r
51345 * @property maximum
\r
51351 * The spacing between major intervals on this axis.
\r
51353 * @property majorUnit
\r
51359 * The time unit used by the majorUnit.
\r
51361 * @property majorTimeUnit
\r
51364 majorTimeUnit: null,
\r
51367 * The spacing between minor intervals on this axis.
\r
51369 * @property majorUnit
\r
51375 * The time unit used by the minorUnit.
\r
51377 * @property majorTimeUnit
\r
51380 minorTimeUnit: null,
\r
51383 * If true, the labels, ticks, gridlines, and other objects will snap to
\r
51384 * the nearest major or minor unit. If false, their position will be based
\r
51385 * on the minimum value.
\r
51387 * @property snapToUnits
\r
51390 snapToUnits: true
\r
51394 * @class Ext.chart.CategoryAxis
\r
51395 * @extends Ext.chart.Axis
\r
51396 * A type of axis that displays items in categories.
\r
51399 Ext.chart.CategoryAxis = Ext.extend(Ext.chart.Axis, {
\r
51400 type: "category",
\r
51403 * A list of category names to display along this axis.
\r
51405 * @property categoryNames
\r
51408 categoryNames: null
\r
51412 * @class Ext.chart.Series
\r
51413 * Series class for the charts widget.
\r
51416 Ext.chart.Series = function(config) { Ext.apply(this, config); };
\r
51418 Ext.chart.Series.prototype =
\r
51421 * The type of series.
\r
51429 * The human-readable name of the series.
\r
51431 * @property displayName
\r
51434 displayName: null
\r
51438 * @class Ext.chart.CartesianSeries
\r
51439 * @extends Ext.chart.Series
\r
51440 * CartesianSeries class for the charts widget.
\r
51443 Ext.chart.CartesianSeries = Ext.extend(Ext.chart.Series, {
\r
51445 * The field used to access the x-axis value from the items from the data source.
\r
51447 * @property xField
\r
51453 * The field used to access the y-axis value from the items from the data source.
\r
51455 * @property yField
\r
51462 * @class Ext.chart.ColumnSeries
\r
51463 * @extends Ext.chart.CartesianSeries
\r
51464 * ColumnSeries class for the charts widget.
\r
51467 Ext.chart.ColumnSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
51472 * @class Ext.chart.LineSeries
\r
51473 * @extends Ext.chart.CartesianSeries
\r
51474 * LineSeries class for the charts widget.
\r
51477 Ext.chart.LineSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
51482 * @class Ext.chart.BarSeries
\r
51483 * @extends Ext.chart.CartesianSeries
\r
51484 * BarSeries class for the charts widget.
\r
51487 Ext.chart.BarSeries = Ext.extend(Ext.chart.CartesianSeries, {
\r
51493 * @class Ext.chart.PieSeries
\r
51494 * @extends Ext.chart.Series
\r
51495 * PieSeries class for the charts widget.
\r
51498 Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, {
\r
51501 categoryField: null
\r
51503 * @class Ext.layout.MenuLayout
\r
51504 * @extends Ext.layout.ContainerLayout
\r
51505 * <p>Layout manager used by {@link Ext.menu.Menu}. Generally this class should not need to be used directly.</p>
\r
51507 Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {
\r
51508 monitorResize : true,
\r
51510 setContainer : function(ct){
\r
51511 this.monitorResize = !ct.floating;
\r
51512 // This event is only fired by the menu in IE, used so we don't couple
\r
51513 // the menu with the layout.
\r
51514 ct.on('autosize', this.doAutoSize, this);
\r
51515 Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);
\r
51518 renderItem : function(c, position, target){
\r
51519 if (!this.itemTpl) {
\r
51520 this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
\r
51521 '<li id="{itemId}" class="{itemCls}">',
\r
51522 '<tpl if="needsIcon">',
\r
51523 '<img src="{icon}" class="{iconCls}"/>',
\r
51529 if(c && !c.rendered){
\r
51530 if(Ext.isNumber(position)){
\r
51531 position = target.dom.childNodes[position];
\r
51533 var a = this.getItemArgs(c);
\r
51535 // The Component's positionEl is the <li> it is rendered into
\r
51536 c.render(c.positionEl = position ?
\r
51537 this.itemTpl.insertBefore(position, a, true) :
\r
51538 this.itemTpl.append(target, a, true));
\r
51540 // Link the containing <li> to the item.
\r
51541 c.positionEl.menuItemId = c.getItemId();
\r
51543 // If rendering a regular Component, and it needs an icon,
\r
51544 // move the Component rightwards.
\r
51545 if (!a.isMenuItem && a.needsIcon) {
\r
51546 c.positionEl.addClass('x-menu-list-item-indent');
\r
51548 this.configureItem(c, position);
\r
51549 }else if(c && !this.isValidParent(c, target)){
\r
51550 if(Ext.isNumber(position)){
\r
51551 position = target.dom.childNodes[position];
\r
51553 target.dom.insertBefore(c.getActionEl().dom, position || null);
\r
51557 getItemArgs : function(c) {
\r
51558 var isMenuItem = c instanceof Ext.menu.Item;
\r
51560 isMenuItem: isMenuItem,
\r
51561 needsIcon: !isMenuItem && (c.icon || c.iconCls),
\r
51562 icon: c.icon || Ext.BLANK_IMAGE_URL,
\r
51563 iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
\r
51564 itemId: 'x-menu-el-' + c.id,
\r
51565 itemCls: 'x-menu-list-item '
\r
51569 // Valid if the Component is in a <li> which is part of our target <ul>
\r
51570 isValidParent : function(c, target) {
\r
51571 return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);
\r
51574 onLayout : function(ct, target){
\r
51575 this.renderAll(ct, target);
\r
51576 this.doAutoSize();
\r
51579 doAutoSize : function(){
\r
51580 var ct = this.container, w = ct.width;
\r
51584 }else if(Ext.isIE){
\r
51585 ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8) ? 'auto' : ct.minWidth);
\r
51586 var el = ct.getEl(), t = el.dom.offsetWidth; // force recalc
\r
51587 ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));
\r
51592 Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;
\r
51595 * @class Ext.menu.Menu
\r
51596 * @extends Ext.Container
\r
51597 * <p>A menu object. This is the container to which you may add menu items. Menu can also serve as a base class
\r
51598 * when you want a specialized menu based off of another component (like {@link Ext.menu.DateMenu} for example).</p>
\r
51599 * <p>Menus may contain either {@link Ext.menu.Item menu items}, or general {@link Ext.Component Component}s.</p>
\r
51600 * <p>To make a contained general {@link Ext.Component Component} line up with other {@link Ext.menu.Item menu items}
\r
51601 * specify <tt>iconCls: 'no-icon'</tt>. This reserves a space for an icon, and indents the Component in line
\r
51602 * with the other menu items. See {@link Ext.form.ComboBox}.{@link Ext.form.ComboBox#getListParent getListParent}
\r
51603 * for an example.</p>
\r
51604 * <p>By default, Menus are absolutely positioned, floating Components. By configuring a Menu with
\r
51605 * <b><tt>{@link #floating}:false</tt></b>, a Menu may be used as child of a Container.</p>
\r
51609 Ext.menu.Menu = Ext.extend(Ext.Container, {
\r
51611 * @cfg {Object} defaults
\r
51612 * A config object that will be applied to all items added to this container either via the {@link #items}
\r
51613 * config or via the {@link #add} method. The defaults config can contain any number of
\r
51614 * name/value property pairs to be added to each item, and should be valid for the types of items
\r
51615 * being added to the menu.
\r
51618 * @cfg {Mixed} items
\r
51619 * An array of items to be added to this menu. Menus may contain either {@link Ext.menu.Item menu items},
\r
51620 * or general {@link Ext.Component Component}s.
\r
51623 * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
\r
51627 * @cfg {Boolean/String} shadow True or 'sides' for the default effect, 'frame' for 4-way shadow, and 'drop'
\r
51628 * for bottom-right shadow (defaults to 'sides')
\r
51630 shadow : 'sides',
\r
51632 * @cfg {String} subMenuAlign The {@link Ext.Element#alignTo} anchor position value to use for submenus of
\r
51633 * this menu (defaults to 'tl-tr?')
\r
51635 subMenuAlign : 'tl-tr?',
\r
51637 * @cfg {String} defaultAlign The default {@link Ext.Element#alignTo} anchor position value for this menu
\r
51638 * relative to its element of origin (defaults to 'tl-bl?')
\r
51640 defaultAlign : 'tl-bl?',
\r
51642 * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
\r
51644 allowOtherMenus : false,
\r
51646 * @cfg {Boolean} ignoreParentClicks True to ignore clicks on any item in this menu that is a parent item (displays
\r
51647 * a submenu) so that the submenu is not dismissed when clicking the parent item (defaults to false).
\r
51649 ignoreParentClicks : false,
\r
51651 * @cfg {Boolean} enableScrolling True to allow the menu container to have scroller controls if the menu is too long (defaults to true).
\r
51653 enableScrolling : true,
\r
51655 * @cfg {Number} maxHeight The maximum height of the menu. Only applies when enableScrolling is set to True (defaults to null).
\r
51657 maxHeight : null,
\r
51659 * @cfg {Number} scrollIncrement The amount to scroll the menu. Only applies when enableScrolling is set to True (defaults to 24).
\r
51661 scrollIncrement : 24,
\r
51663 * @cfg {Boolean} showSeparator True to show the icon separator. (defaults to true).
\r
51665 showSeparator : true,
\r
51667 * @cfg {Array} defaultOffsets An array specifying the [x, y] offset in pixels by which to
\r
51668 * change the default Menu popup position after aligning according to the {@link #defaultAlign}
\r
51669 * configuration. Defaults to <tt>[0, 0]</tt>.
\r
51671 defaultOffsets : [0, 0],
\r
51674 * @cfg {Boolean} plain
\r
51675 * True to remove the incised line down the left side of the menu. Defaults to <tt>false</tt>.
\r
51680 * @cfg {Boolean} floating
\r
51681 * <p>By default, a Menu configured as <b><code>floating:true</code></b>
\r
51682 * will be rendered as an {@link Ext.Layer} (an absolutely positioned,
\r
51683 * floating Component with zindex=15000).
\r
51684 * If configured as <b><code>floating:false</code></b>, the Menu may be
\r
51685 * used as child item of another Container instead of a free-floating
\r
51686 * {@link Ext.Layer Layer}.
\r
51694 * @cfg {String/Object} layout
\r
51695 * This class assigns a default layout (<code>layout:'<b>menu</b>'</code>).
\r
51696 * Developers <i>may</i> override this configuration option if another layout is required.
\r
51697 * See {@link Ext.Container#layout} for additional information.
\r
51700 hideMode : 'offsets', // Important for laying out Components
\r
51701 scrollerHeight : 8,
\r
51702 autoLayout : true, // Provided for backwards compat
\r
51703 defaultType : 'menuitem',
\r
51705 initComponent : function(){
\r
51706 if(Ext.isArray(this.initialConfig)){
\r
51707 Ext.apply(this, {items:this.initialConfig});
\r
51712 * Fires when this menu is clicked (or when the enter key is pressed while it is active)
\r
51713 * @param {Ext.menu.Menu} this
\r
51714 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
51715 * @param {Ext.EventObject} e
\r
51719 * @event mouseover
\r
51720 * Fires when the mouse is hovering over this menu
\r
51721 * @param {Ext.menu.Menu} this
\r
51722 * @param {Ext.EventObject} e
\r
51723 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
51727 * @event mouseout
\r
51728 * Fires when the mouse exits this menu
\r
51729 * @param {Ext.menu.Menu} this
\r
51730 * @param {Ext.EventObject} e
\r
51731 * @param {Ext.menu.Item} menuItem The menu item that was clicked
\r
51735 * @event itemclick
\r
51736 * Fires when a menu item contained in this menu is clicked
\r
51737 * @param {Ext.menu.BaseItem} baseItem The BaseItem that was clicked
\r
51738 * @param {Ext.EventObject} e
\r
51742 Ext.menu.MenuMgr.register(this);
\r
51743 if(this.floating){
\r
51744 Ext.EventManager.onWindowResize(this.hide, this);
\r
51746 if(this.initialConfig.hidden !== false){
\r
51747 this.hidden = false;
\r
51749 this.internalDefaults = {hideOnClick: false};
\r
51751 Ext.menu.Menu.superclass.initComponent.call(this);
\r
51752 if(this.autoLayout){
\r
51754 add: this.doLayout,
\r
51755 remove: this.doLayout,
\r
51762 getLayoutTarget : function() {
\r
51767 onRender : function(ct, position){
\r
51769 ct = Ext.getBody();
\r
51773 id: this.getId(),
\r
51774 cls: 'x-menu ' + ((this.floating) ? 'x-menu-floating x-layer ' : '') + (this.cls || '') + (this.plain ? ' x-menu-plain' : '') + (this.showSeparator ? '' : ' x-menu-nosep'),
\r
51775 style: this.style,
\r
51777 {tag: 'a', cls: 'x-menu-focus', href: '#', onclick: 'return false;', tabIndex: '-1'},
\r
51778 {tag: 'ul', cls: 'x-menu-list'}
\r
51781 if(this.floating){
\r
51782 this.el = new Ext.Layer({
\r
51783 shadow: this.shadow,
\r
51785 constrain: false,
\r
51790 this.el = ct.createChild(dh);
\r
51792 Ext.menu.Menu.superclass.onRender.call(this, ct, position);
\r
51794 if(!this.keyNav){
\r
51795 this.keyNav = new Ext.menu.MenuNav(this);
\r
51797 // generic focus element
\r
51798 this.focusEl = this.el.child('a.x-menu-focus');
\r
51799 this.ul = this.el.child('ul.x-menu-list');
\r
51800 this.mon(this.ul, {
\r
51802 click: this.onClick,
\r
51803 mouseover: this.onMouseOver,
\r
51804 mouseout: this.onMouseOut
\r
51806 if(this.enableScrolling){
\r
51807 this.mon(this.el, {
\r
51809 delegate: '.x-menu-scroller',
\r
51810 click: this.onScroll,
\r
51811 mouseover: this.deactivateActive
\r
51817 findTargetItem : function(e){
\r
51818 var t = e.getTarget('.x-menu-list-item', this.ul, true);
\r
51819 if(t && t.menuItemId){
\r
51820 return this.items.get(t.menuItemId);
\r
51825 onClick : function(e){
\r
51826 var t = this.findTargetItem(e);
\r
51828 if(t.isFormField){
\r
51829 this.setActiveItem(t);
\r
51830 }else if(t instanceof Ext.menu.BaseItem){
\r
51831 if(t.menu && this.ignoreParentClicks){
\r
51833 e.preventDefault();
\r
51834 }else if(t.onClick){
\r
51836 this.fireEvent('click', this, t, e);
\r
51843 setActiveItem : function(item, autoExpand){
\r
51844 if(item != this.activeItem){
\r
51845 this.deactivateActive();
\r
51846 if((this.activeItem = item).isFormField){
\r
51849 item.activate(autoExpand);
\r
51851 }else if(autoExpand){
\r
51852 item.expandMenu();
\r
51856 deactivateActive : function(){
\r
51857 var a = this.activeItem;
\r
51859 if(a.isFormField){
\r
51860 //Fields cannot deactivate, but Combos must collapse
\r
51867 delete this.activeItem;
\r
51872 tryActivate : function(start, step){
\r
51873 var items = this.items;
\r
51874 for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
\r
51875 var item = items.get(i);
\r
51876 if(!item.disabled && (item.canActivate || item.isFormField)){
\r
51877 this.setActiveItem(item, false);
\r
51885 onMouseOver : function(e){
\r
51886 var t = this.findTargetItem(e);
\r
51888 if(t.canActivate && !t.disabled){
\r
51889 this.setActiveItem(t, true);
\r
51892 this.over = true;
\r
51893 this.fireEvent('mouseover', this, e, t);
\r
51897 onMouseOut : function(e){
\r
51898 var t = this.findTargetItem(e);
\r
51900 if(t == this.activeItem && t.shouldDeactivate && t.shouldDeactivate(e)){
\r
51901 this.activeItem.deactivate();
\r
51902 delete this.activeItem;
\r
51905 this.over = false;
\r
51906 this.fireEvent('mouseout', this, e, t);
\r
51910 onScroll : function(e, t){
\r
51914 var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
\r
51915 ul.scrollTop += this.scrollIncrement * (top ? -1 : 1);
\r
51916 if(top ? ul.scrollTop <= 0 : ul.scrollTop + this.activeMax >= ul.scrollHeight){
\r
51917 this.onScrollerOut(null, t);
\r
51922 onScrollerIn : function(e, t){
\r
51923 var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
\r
51924 if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){
\r
51925 Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);
\r
51930 onScrollerOut : function(e, t){
\r
51931 Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);
\r
51935 * If <code>{@link #floating}=true</code>, shows this menu relative to
\r
51936 * another element using {@link #showat}, otherwise uses {@link Ext.Component#show}.
\r
51937 * @param {Mixed} element The element to align to
\r
51938 * @param {String} position (optional) The {@link Ext.Element#alignTo} anchor position to use in aligning to
\r
51939 * the element (defaults to this.defaultAlign)
\r
51940 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
51942 show : function(el, pos, parentMenu){
\r
51943 if(this.floating){
\r
51944 this.parentMenu = parentMenu;
\r
51947 this.doLayout(false, true);
\r
51949 this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu);
\r
51951 Ext.menu.Menu.superclass.show.call(this);
\r
51956 * Displays this menu at a specific xy position and fires the 'show' event if a
\r
51957 * handler for the 'beforeshow' event does not return false cancelling the operation.
\r
51958 * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
\r
51959 * @param {Ext.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
\r
51961 showAt : function(xy, parentMenu){
\r
51962 if(this.fireEvent('beforeshow', this) !== false){
\r
51963 this.parentMenu = parentMenu;
\r
51967 if(this.enableScrolling){
\r
51968 // set the position so we can figure out the constrain value.
\r
51969 this.el.setXY(xy);
\r
51970 //constrain the value, keep the y coordinate the same
\r
51971 this.constrainScroll(xy[1]);
\r
51972 xy = [this.el.adjustForConstraints(xy)[0], xy[1]];
\r
51974 //constrain to the viewport.
\r
51975 xy = this.el.adjustForConstraints(xy);
\r
51977 this.el.setXY(xy);
\r
51979 Ext.menu.Menu.superclass.onShow.call(this);
\r
51981 // internal event, used so we don't couple the layout to the menu
\r
51982 this.fireEvent('autosize', this);
\r
51984 this.el.repaint();
\r
51987 this.hidden = false;
\r
51989 this.fireEvent('show', this);
\r
51993 constrainScroll : function(y){
\r
51994 var max, full = this.ul.setHeight('auto').getHeight();
\r
51995 if(this.floating){
\r
51996 max = this.maxHeight ? this.maxHeight : Ext.fly(this.el.dom.parentNode).getViewSize().height - y;
\r
51998 max = this.getHeight();
\r
52000 if(full > max && max > 0){
\r
52001 this.activeMax = max - this.scrollerHeight * 2 - this.el.getFrameWidth('tb') - Ext.num(this.el.shadowOffset, 0);
\r
52002 this.ul.setHeight(this.activeMax);
\r
52003 this.createScrollers();
\r
52004 this.el.select('.x-menu-scroller').setDisplayed('');
\r
52006 this.ul.setHeight(full);
\r
52007 this.el.select('.x-menu-scroller').setDisplayed('none');
\r
52009 this.ul.dom.scrollTop = 0;
\r
52012 createScrollers : function(){
\r
52013 if(!this.scroller){
\r
52014 this.scroller = {
\r
52016 top: this.el.insertFirst({
\r
52018 cls: 'x-menu-scroller x-menu-scroller-top',
\r
52021 bottom: this.el.createChild({
\r
52023 cls: 'x-menu-scroller x-menu-scroller-bottom',
\r
52027 this.scroller.top.hover(this.onScrollerIn, this.onScrollerOut, this);
\r
52028 this.scroller.topRepeater = new Ext.util.ClickRepeater(this.scroller.top, {
\r
52030 click: this.onScroll.createDelegate(this, [null, this.scroller.top], false)
\r
52033 this.scroller.bottom.hover(this.onScrollerIn, this.onScrollerOut, this);
\r
52034 this.scroller.bottomRepeater = new Ext.util.ClickRepeater(this.scroller.bottom, {
\r
52036 click: this.onScroll.createDelegate(this, [null, this.scroller.bottom], false)
\r
52042 onLayout : function(){
\r
52043 if(this.isVisible()){
\r
52044 if(this.enableScrolling){
\r
52045 this.constrainScroll(this.el.getTop());
\r
52047 if(this.floating){
\r
52053 focus : function(){
\r
52054 if(!this.hidden){
\r
52055 this.doFocus.defer(50, this);
\r
52059 doFocus : function(){
\r
52060 if(!this.hidden){
\r
52061 this.focusEl.focus();
\r
52066 * Hides this menu and optionally all parent menus
\r
52067 * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
\r
52069 hide : function(deep){
\r
52070 this.deepHide = deep;
\r
52071 Ext.menu.Menu.superclass.hide.call(this);
\r
52072 delete this.deepHide;
\r
52076 onHide : function(){
\r
52077 Ext.menu.Menu.superclass.onHide.call(this);
\r
52078 this.deactivateActive();
\r
52079 if(this.el && this.floating){
\r
52082 var pm = this.parentMenu;
\r
52083 if(this.deepHide === true && pm){
\r
52087 pm.deactivateActive();
\r
52093 lookupComponent : function(c){
\r
52094 if(Ext.isString(c)){
\r
52095 c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);
\r
52096 this.applyDefaults(c);
\r
52098 if(Ext.isObject(c)){
\r
52099 c = this.getMenuItem(c);
\r
52100 }else if(c.tagName || c.el){ // element. Wrap it.
\r
52101 c = new Ext.BoxComponent({
\r
52109 applyDefaults : function(c){
\r
52110 if(!Ext.isString(c)){
\r
52111 c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);
\r
52112 var d = this.internalDefaults;
\r
52115 Ext.applyIf(c.initialConfig, d);
\r
52118 Ext.applyIf(c, d);
\r
52126 getMenuItem : function(config){
\r
52127 if(!config.isXType){
\r
52128 if(!config.xtype && Ext.isBoolean(config.checked)){
\r
52129 return new Ext.menu.CheckItem(config)
\r
52131 return Ext.create(config, this.defaultType);
\r
52137 * Adds a separator bar to the menu
\r
52138 * @return {Ext.menu.Item} The menu item that was added
\r
52140 addSeparator : function(){
\r
52141 return this.add(new Ext.menu.Separator());
\r
52145 * Adds an {@link Ext.Element} object to the menu
\r
52146 * @param {Mixed} el The element or DOM node to add, or its id
\r
52147 * @return {Ext.menu.Item} The menu item that was added
\r
52149 addElement : function(el){
\r
52150 return this.add(new Ext.menu.BaseItem(el));
\r
52154 * Adds an existing object based on {@link Ext.menu.BaseItem} to the menu
\r
52155 * @param {Ext.menu.Item} item The menu item to add
\r
52156 * @return {Ext.menu.Item} The menu item that was added
\r
52158 addItem : function(item){
\r
52159 return this.add(item);
\r
52163 * Creates a new {@link Ext.menu.Item} based an the supplied config object and adds it to the menu
\r
52164 * @param {Object} config A MenuItem config object
\r
52165 * @return {Ext.menu.Item} The menu item that was added
\r
52167 addMenuItem : function(config){
\r
52168 return this.add(this.getMenuItem(config));
\r
52172 * Creates a new {@link Ext.menu.TextItem} with the supplied text and adds it to the menu
\r
52173 * @param {String} text The text to display in the menu item
\r
52174 * @return {Ext.menu.Item} The menu item that was added
\r
52176 addText : function(text){
\r
52177 return this.add(new Ext.menu.TextItem(text));
\r
52181 onDestroy : function(){
\r
52182 Ext.menu.Menu.superclass.onDestroy.call(this);
\r
52183 Ext.menu.MenuMgr.unregister(this);
\r
52184 Ext.EventManager.removeResizeListener(this.hide, this);
\r
52185 if(this.keyNav) {
\r
52186 this.keyNav.disable();
\r
52188 var s = this.scroller;
\r
52190 Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
\r
52200 Ext.reg('menu', Ext.menu.Menu);
\r
52202 // MenuNav is a private utility class used internally by the Menu
\r
52203 Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
\r
52204 function up(e, m){
\r
52205 if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
\r
52206 m.tryActivate(m.items.length-1, -1);
\r
52209 function down(e, m){
\r
52210 if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
\r
52211 m.tryActivate(0, 1);
\r
52215 constructor : function(menu){
\r
52216 Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);
\r
52217 this.scope = this.menu = menu;
\r
52220 doRelay : function(e, h){
\r
52221 var k = e.getKey();
\r
52222 // Keystrokes within a form Field (e.g.: down in a Combo) do not navigate. Allow only TAB
\r
52223 if (this.menu.activeItem && this.menu.activeItem.isFormField && k != e.TAB) {
\r
52226 if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
\r
52227 this.menu.tryActivate(0, 1);
\r
52230 return h.call(this.scope || this, e, this.menu);
\r
52233 tab: function(e, m) {
\r
52235 if (e.shiftKey) {
\r
52246 right : function(e, m){
\r
52247 if(m.activeItem){
\r
52248 m.activeItem.expandMenu(true);
\r
52252 left : function(e, m){
\r
52254 if(m.parentMenu && m.parentMenu.activeItem){
\r
52255 m.parentMenu.activeItem.activate();
\r
52259 enter : function(e, m){
\r
52260 if(m.activeItem){
\r
52261 e.stopPropagation();
\r
52262 m.activeItem.onClick(e);
\r
52263 m.fireEvent('click', this, m.activeItem);
\r
52270 * @class Ext.menu.MenuMgr
52271 * Provides a common registry of all menu items on a page so that they can be easily accessed by id.
52274 Ext.menu.MenuMgr = function(){
52275 var menus, active, groups = {}, attached = false, lastShow = new Date();
52277 // private - called when first menu is created
52280 active = new Ext.util.MixedCollection();
52281 Ext.getDoc().addKeyListener(27, function(){
52282 if(active.length > 0){
52289 function hideAll(){
52290 if(active && active.length > 0){
52291 var c = active.clone();
52292 c.each(function(m){
52299 function onHide(m){
52301 if(active.length < 1){
52302 Ext.getDoc().un("mousedown", onMouseDown);
52308 function onShow(m){
52309 var last = active.last();
52310 lastShow = new Date();
52313 Ext.getDoc().on("mousedown", onMouseDown);
52317 m.getEl().setZIndex(parseInt(m.parentMenu.getEl().getStyle("z-index"), 10) + 3);
52318 m.parentMenu.activeChild = m;
52319 }else if(last && last.isVisible()){
52320 m.getEl().setZIndex(parseInt(last.getEl().getStyle("z-index"), 10) + 3);
52325 function onBeforeHide(m){
52327 m.activeChild.hide();
52329 if(m.autoHideTimer){
52330 clearTimeout(m.autoHideTimer);
52331 delete m.autoHideTimer;
52336 function onBeforeShow(m){
52337 var pm = m.parentMenu;
52338 if(!pm && !m.allowOtherMenus){
52340 }else if(pm && pm.activeChild){
52341 pm.activeChild.hide();
52346 function onMouseDown(e){
52347 if(lastShow.getElapsed() > 50 && active.length > 0 && !e.getTarget(".x-menu")){
52353 function onBeforeCheck(mi, state){
52355 var g = groups[mi.group];
52356 for(var i = 0, l = g.length; i < l; i++){
52358 g[i].setChecked(false);
52367 * Hides all menus that are currently visible
52369 hideAll : function(){
52374 register : function(menu){
52378 menus[menu.id] = menu;
52379 menu.on("beforehide", onBeforeHide);
52380 menu.on("hide", onHide);
52381 menu.on("beforeshow", onBeforeShow);
52382 menu.on("show", onShow);
52383 var g = menu.group;
52384 if(g && menu.events["checkchange"]){
52388 groups[g].push(menu);
52389 menu.on("checkchange", onCheck);
52394 * Returns a {@link Ext.menu.Menu} object
52395 * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
52396 * be used to generate and return a new Menu instance.
52397 * @return {Ext.menu.Menu} The specified menu, or null if none are found
52399 get : function(menu){
52400 if(typeof menu == "string"){ // menu id
52401 if(!menus){ // not initialized, no menus to return
52404 return menus[menu];
52405 }else if(menu.events){ // menu instance
52407 }else if(typeof menu.length == 'number'){ // array of menu items?
52408 return new Ext.menu.Menu({items:menu});
52409 }else{ // otherwise, must be a config
52410 return Ext.create(menu, 'menu');
52415 unregister : function(menu){
52416 delete menus[menu.id];
52417 menu.un("beforehide", onBeforeHide);
52418 menu.un("hide", onHide);
52419 menu.un("beforeshow", onBeforeShow);
52420 menu.un("show", onShow);
52421 var g = menu.group;
52422 if(g && menu.events["checkchange"]){
52423 groups[g].remove(menu);
52424 menu.un("checkchange", onCheck);
52429 registerCheckable : function(menuItem){
52430 var g = menuItem.group;
52435 groups[g].push(menuItem);
52436 menuItem.on("beforecheckchange", onBeforeCheck);
52441 unregisterCheckable : function(menuItem){
52442 var g = menuItem.group;
52444 groups[g].remove(menuItem);
52445 menuItem.un("beforecheckchange", onBeforeCheck);
52449 getCheckedItem : function(groupId){
52450 var g = groups[groupId];
52452 for(var i = 0, l = g.length; i < l; i++){
52461 setCheckedItem : function(groupId, itemId){
52462 var g = groups[groupId];
52464 for(var i = 0, l = g.length; i < l; i++){
52465 if(g[i].id == itemId){
52466 g[i].setChecked(true);
52475 * @class Ext.menu.BaseItem
52476 * @extends Ext.Component
52477 * The base class for all items that render into menus. BaseItem provides default rendering, activated state
52478 * management and base configuration options shared by all menu components.
52480 * Creates a new BaseItem
52481 * @param {Object} config Configuration options
52482 * @xtype menubaseitem
52484 Ext.menu.BaseItem = function(config){
52485 Ext.menu.BaseItem.superclass.constructor.call(this, config);
52490 * Fires when this item is clicked
52491 * @param {Ext.menu.BaseItem} this
52492 * @param {Ext.EventObject} e
52497 * Fires when this item is activated
52498 * @param {Ext.menu.BaseItem} this
52502 * @event deactivate
52503 * Fires when this item is deactivated
52504 * @param {Ext.menu.BaseItem} this
52510 this.on("click", this.handler, this.scope);
52514 Ext.extend(Ext.menu.BaseItem, Ext.Component, {
52516 * @property parentMenu
52517 * @type Ext.menu.Menu
52518 * The parent Menu of this Item.
52521 * @cfg {Function} handler
52522 * A function that will handle the click event of this menu item (optional).
52523 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
52524 * <li><code>b</code> : Item<div class="sub-desc">This menu Item.</div></li>
52525 * <li><code>e</code> : EventObject<div class="sub-desc">The click event.</div></li>
52529 * @cfg {Object} scope
52530 * The scope (<tt><b>this</b></tt> reference) in which the handler function will be called.
52533 * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to false)
52535 canActivate : false,
52537 * @cfg {String} activeClass The CSS class to use when the item becomes activated (defaults to "x-menu-item-active")
52539 activeClass : "x-menu-item-active",
52541 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to true)
52543 hideOnClick : true,
52545 * @cfg {Number} clickHideDelay Length of time in milliseconds to wait before hiding after a click (defaults to 100)
52547 clickHideDelay : 1,
52550 ctype : "Ext.menu.BaseItem",
52553 actionMode : "container",
52556 onRender : function(container, position){
52557 Ext.menu.BaseItem.superclass.onRender.apply(this, arguments);
52558 if(this.ownerCt && this.ownerCt instanceof Ext.menu.Menu){
52559 this.parentMenu = this.ownerCt;
52561 this.container.addClass('x-menu-list-item');
52562 this.mon(this.el, 'click', this.onClick, this);
52563 this.mon(this.el, 'mouseenter', this.activate, this);
52564 this.mon(this.el, 'mouseleave', this.deactivate, this);
52569 * Sets the function that will handle click events for this item (equivalent to passing in the {@link #handler}
52570 * config property). If an existing handler is already registered, it will be unregistered for you.
52571 * @param {Function} handler The function that should be called on click
52572 * @param {Object} scope The scope that should be passed to the handler
52574 setHandler : function(handler, scope){
52576 this.un("click", this.handler, this.scope);
52578 this.on("click", this.handler = handler, this.scope = scope);
52582 onClick : function(e){
52583 if(!this.disabled && this.fireEvent("click", this, e) !== false
52584 && (this.parentMenu && this.parentMenu.fireEvent("itemclick", this, e) !== false)){
52585 this.handleClick(e);
52592 activate : function(){
52596 var li = this.container;
52597 li.addClass(this.activeClass);
52598 this.region = li.getRegion().adjust(2, 2, -2, -2);
52599 this.fireEvent("activate", this);
52604 deactivate : function(){
52605 this.container.removeClass(this.activeClass);
52606 this.fireEvent("deactivate", this);
52610 shouldDeactivate : function(e){
52611 return !this.region || !this.region.contains(e.getPoint());
52615 handleClick : function(e){
52616 var pm = this.parentMenu;
52617 if(this.hideOnClick){
52619 pm.hide.defer(this.clickHideDelay, pm, [true]);
52621 pm.deactivateActive();
52626 // private. Do nothing
52627 expandMenu : Ext.emptyFn,
52629 // private. Do nothing
52630 hideMenu : Ext.emptyFn
52632 Ext.reg('menubaseitem', Ext.menu.BaseItem);/**
52633 * @class Ext.menu.TextItem
52634 * @extends Ext.menu.BaseItem
52635 * Adds a static text string to a menu, usually used as either a heading or group separator.
52637 * Creates a new TextItem
52638 * @param {Object/String} config If config is a string, it is used as the text to display, otherwise it
52639 * is applied as a config object (and should contain a <tt>text</tt> property).
52640 * @xtype menutextitem
52642 Ext.menu.TextItem = function(cfg){
52643 if(typeof cfg == 'string'){
52646 Ext.menu.TextItem.superclass.constructor.call(this, cfg);
52649 Ext.extend(Ext.menu.TextItem, Ext.menu.BaseItem, {
52651 * @cfg {String} text The text to display for this item (defaults to '')
52654 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
52656 hideOnClick : false,
52658 * @cfg {String} itemCls The default CSS class to use for text items (defaults to "x-menu-text")
52660 itemCls : "x-menu-text",
52663 onRender : function(){
52664 var s = document.createElement("span");
52665 s.className = this.itemCls;
52666 s.innerHTML = this.text;
52668 Ext.menu.TextItem.superclass.onRender.apply(this, arguments);
52671 Ext.reg('menutextitem', Ext.menu.TextItem);/**
52672 * @class Ext.menu.Separator
52673 * @extends Ext.menu.BaseItem
52674 * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
52675 * add one of these by using "-" in you call to add() or in your items config rather than creating one directly.
52677 * @param {Object} config Configuration options
52678 * @xtype menuseparator
52680 Ext.menu.Separator = function(config){
52681 Ext.menu.Separator.superclass.constructor.call(this, config);
52684 Ext.extend(Ext.menu.Separator, Ext.menu.BaseItem, {
52686 * @cfg {String} itemCls The default CSS class to use for separators (defaults to "x-menu-sep")
52688 itemCls : "x-menu-sep",
52690 * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
52692 hideOnClick : false,
52695 * @cfg {String} activeClass
52701 onRender : function(li){
52702 var s = document.createElement("span");
52703 s.className = this.itemCls;
52704 s.innerHTML = " ";
52706 li.addClass("x-menu-sep-li");
52707 Ext.menu.Separator.superclass.onRender.apply(this, arguments);
52710 Ext.reg('menuseparator', Ext.menu.Separator);/**
\r
52711 * @class Ext.menu.Item
\r
52712 * @extends Ext.menu.BaseItem
\r
52713 * A base class for all menu items that require menu-related functionality (like sub-menus) and are not static
\r
52714 * display items. Item extends the base functionality of {@link Ext.menu.BaseItem} by adding menu-specific
\r
52715 * activation and click handling.
\r
52717 * Creates a new Item
\r
52718 * @param {Object} config Configuration options
\r
52719 * @xtype menuitem
\r
52721 Ext.menu.Item = function(config){
\r
52722 Ext.menu.Item.superclass.constructor.call(this, config);
\r
52724 this.menu = Ext.menu.MenuMgr.get(this.menu);
\r
52727 Ext.extend(Ext.menu.Item, Ext.menu.BaseItem, {
\r
52730 * @type Ext.menu.Menu
\r
52731 * The submenu associated with this Item if one was configured.
\r
52734 * @cfg {Mixed} menu (optional) Either an instance of {@link Ext.menu.Menu} or the config object for an
\r
52735 * {@link Ext.menu.Menu} which acts as the submenu when this item is activated.
\r
52738 * @cfg {String} icon The path to an icon to display in this item (defaults to Ext.BLANK_IMAGE_URL). If
\r
52739 * icon is specified {@link #iconCls} should not be.
\r
52742 * @cfg {String} iconCls A CSS class that specifies a background image that will be used as the icon for
\r
52743 * this item (defaults to ''). If iconCls is specified {@link #icon} should not be.
\r
52746 * @cfg {String} text The text to display in this item (defaults to '').
\r
52749 * @cfg {String} href The href attribute to use for the underlying anchor link (defaults to '#').
\r
52752 * @cfg {String} hrefTarget The target attribute to use for the underlying anchor link (defaults to '').
\r
52755 * @cfg {String} itemCls The default CSS class to use for menu items (defaults to 'x-menu-item')
\r
52757 itemCls : 'x-menu-item',
\r
52759 * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to true)
\r
52761 canActivate : true,
\r
52763 * @cfg {Number} showDelay Length of time in milliseconds to wait before showing this item (defaults to 200)
\r
52766 // doc'd in BaseItem
\r
52770 ctype: 'Ext.menu.Item',
\r
52773 onRender : function(container, position){
\r
52774 if (!this.itemTpl) {
\r
52775 this.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(
\r
52776 '<a id="{id}" class="{cls}" hidefocus="true" unselectable="on" href="{href}"',
\r
52777 '<tpl if="hrefTarget">',
\r
52778 ' target="{hrefTarget}"',
\r
52781 '<img src="{icon}" class="x-menu-item-icon {iconCls}"/>',
\r
52782 '<span class="x-menu-item-text">{text}</span>',
\r
52786 var a = this.getTemplateArgs();
\r
52787 this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true);
\r
52788 this.iconEl = this.el.child('img.x-menu-item-icon');
\r
52789 this.textEl = this.el.child('.x-menu-item-text');
\r
52790 Ext.menu.Item.superclass.onRender.call(this, container, position);
\r
52793 getTemplateArgs: function() {
\r
52796 cls: this.itemCls + (this.menu ? ' x-menu-item-arrow' : '') + (this.cls ? ' ' + this.cls : ''),
\r
52797 href: this.href || '#',
\r
52798 hrefTarget: this.hrefTarget,
\r
52799 icon: this.icon || Ext.BLANK_IMAGE_URL,
\r
52800 iconCls: this.iconCls || '',
\r
52801 text: this.itemText||this.text||' '
\r
52806 * Sets the text to display in this menu item
\r
52807 * @param {String} text The text to display
\r
52809 setText : function(text){
\r
52810 this.text = text||' ';
\r
52811 if(this.rendered){
\r
52812 this.textEl.update(this.text);
\r
52813 this.parentMenu.layout.doAutoSize();
\r
52818 * Sets the CSS class to apply to the item's icon element
\r
52819 * @param {String} cls The CSS class to apply
\r
52821 setIconClass : function(cls){
\r
52822 var oldCls = this.iconCls;
\r
52823 this.iconCls = cls;
\r
52824 if(this.rendered){
\r
52825 this.iconEl.replaceClass(oldCls, this.iconCls);
\r
52830 beforeDestroy: function(){
\r
52832 this.menu.destroy();
\r
52834 Ext.menu.Item.superclass.beforeDestroy.call(this);
\r
52838 handleClick : function(e){
\r
52839 if(!this.href){ // if no link defined, stop the event automatically
\r
52842 Ext.menu.Item.superclass.handleClick.apply(this, arguments);
\r
52846 activate : function(autoExpand){
\r
52847 if(Ext.menu.Item.superclass.activate.apply(this, arguments)){
\r
52850 this.expandMenu();
\r
52857 shouldDeactivate : function(e){
\r
52858 if(Ext.menu.Item.superclass.shouldDeactivate.call(this, e)){
\r
52859 if(this.menu && this.menu.isVisible()){
\r
52860 return !this.menu.getEl().getRegion().contains(e.getPoint());
\r
52868 deactivate : function(){
\r
52869 Ext.menu.Item.superclass.deactivate.apply(this, arguments);
\r
52874 expandMenu : function(autoActivate){
\r
52875 if(!this.disabled && this.menu){
\r
52876 clearTimeout(this.hideTimer);
\r
52877 delete this.hideTimer;
\r
52878 if(!this.menu.isVisible() && !this.showTimer){
\r
52879 this.showTimer = this.deferExpand.defer(this.showDelay, this, [autoActivate]);
\r
52880 }else if (this.menu.isVisible() && autoActivate){
\r
52881 this.menu.tryActivate(0, 1);
\r
52887 deferExpand : function(autoActivate){
\r
52888 delete this.showTimer;
\r
52889 this.menu.show(this.container, this.parentMenu.subMenuAlign || 'tl-tr?', this.parentMenu);
\r
52890 if(autoActivate){
\r
52891 this.menu.tryActivate(0, 1);
\r
52896 hideMenu : function(){
\r
52897 clearTimeout(this.showTimer);
\r
52898 delete this.showTimer;
\r
52899 if(!this.hideTimer && this.menu && this.menu.isVisible()){
\r
52900 this.hideTimer = this.deferHide.defer(this.hideDelay, this);
\r
52905 deferHide : function(){
\r
52906 delete this.hideTimer;
\r
52907 if(this.menu.over){
\r
52908 this.parentMenu.setActiveItem(this, false);
\r
52910 this.menu.hide();
\r
52914 Ext.reg('menuitem', Ext.menu.Item);/**
52915 * @class Ext.menu.CheckItem
52916 * @extends Ext.menu.Item
52917 * Adds a menu item that contains a checkbox by default, but can also be part of a radio group.
52919 * Creates a new CheckItem
52920 * @param {Object} config Configuration options
52921 * @xtype menucheckitem
52923 Ext.menu.CheckItem = function(config){
52924 Ext.menu.CheckItem.superclass.constructor.call(this, config);
52927 * @event beforecheckchange
52928 * Fires before the checked value is set, providing an opportunity to cancel if needed
52929 * @param {Ext.menu.CheckItem} this
52930 * @param {Boolean} checked The new checked value that will be set
52932 "beforecheckchange" ,
52934 * @event checkchange
52935 * Fires after the checked value has been set
52936 * @param {Ext.menu.CheckItem} this
52937 * @param {Boolean} checked The checked value that was set
52942 * A function that handles the checkchange event. The function is undefined by default, but if an implementation
52943 * is provided, it will be called automatically when the checkchange event fires.
52944 * @param {Ext.menu.CheckItem} this
52945 * @param {Boolean} checked The checked value that was set
52946 * @method checkHandler
52948 if(this.checkHandler){
52949 this.on('checkchange', this.checkHandler, this.scope);
52951 Ext.menu.MenuMgr.registerCheckable(this);
52953 Ext.extend(Ext.menu.CheckItem, Ext.menu.Item, {
52955 * @cfg {String} group
52956 * All check items with the same group name will automatically be grouped into a single-select
52957 * radio button group (defaults to '')
52960 * @cfg {String} itemCls The default CSS class to use for check items (defaults to "x-menu-item x-menu-check-item")
52962 itemCls : "x-menu-item x-menu-check-item",
52964 * @cfg {String} groupClass The default CSS class to use for radio group check items (defaults to "x-menu-group-item")
52966 groupClass : "x-menu-group-item",
52969 * @cfg {Boolean} checked True to initialize this checkbox as checked (defaults to false). Note that
52970 * if this checkbox is part of a radio group (group = true) only the last item in the group that is
52971 * initialized with checked = true will be rendered as checked.
52976 ctype: "Ext.menu.CheckItem",
52979 onRender : function(c){
52980 Ext.menu.CheckItem.superclass.onRender.apply(this, arguments);
52982 this.el.addClass(this.groupClass);
52985 this.checked = false;
52986 this.setChecked(true, true);
52991 destroy : function(){
52992 Ext.menu.MenuMgr.unregisterCheckable(this);
52993 Ext.menu.CheckItem.superclass.destroy.apply(this, arguments);
52997 * Set the checked state of this item
52998 * @param {Boolean} checked The new checked value
52999 * @param {Boolean} suppressEvent (optional) True to prevent the checkchange event from firing (defaults to false)
53001 setChecked : function(state, suppressEvent){
53002 if(this.checked != state && this.fireEvent("beforecheckchange", this, state) !== false){
53003 if(this.container){
53004 this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
53006 this.checked = state;
53007 if(suppressEvent !== true){
53008 this.fireEvent("checkchange", this, state);
53014 handleClick : function(e){
53015 if(!this.disabled && !(this.checked && this.group)){// disable unselect on radio item
53016 this.setChecked(!this.checked);
53018 Ext.menu.CheckItem.superclass.handleClick.apply(this, arguments);
53021 Ext.reg('menucheckitem', Ext.menu.CheckItem);/**
\r
53022 * @class Ext.menu.DateMenu
\r
53023 * @extends Ext.menu.Menu
\r
53024 * <p>A menu containing an {@link Ext.DatePicker} Component.</p>
\r
53025 * <p>Notes:</p><div class="mdetail-params"><ul>
\r
53026 * <li>Although not listed here, the <b>constructor</b> for this class
\r
53027 * accepts all of the configuration options of <b>{@link Ext.DatePicker}</b>.</li>
\r
53028 * <li>If subclassing DateMenu, any configuration options for the DatePicker must be
\r
53029 * applied to the <tt><b>initialConfig</b></tt> property of the DateMenu.
\r
53030 * Applying {@link Ext.DatePicker DatePicker} configuration settings to
\r
53031 * <b><tt>this</tt></b> will <b>not</b> affect the DatePicker's configuration.</li>
\r
53033 * @xtype datemenu
\r
53035 Ext.menu.DateMenu = Ext.extend(Ext.menu.Menu, {
\r
53037 * @cfg {Boolean} enableScrolling
\r
53040 enableScrolling : false,
\r
53042 * @cfg {Function} handler
\r
53043 * Optional. A function that will handle the select event of this menu.
\r
53044 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
\r
53045 * <li><code>picker</code> : DatePicker<div class="sub-desc">The Ext.DatePicker.</div></li>
\r
53046 * <li><code>date</code> : Date<div class="sub-desc">The selected date.</div></li>
\r
53050 * @cfg {Object} scope
\r
53051 * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
\r
53052 * function will be called. Defaults to this DateMenu instance.
\r
53055 * @cfg {Boolean} hideOnClick
\r
53056 * False to continue showing the menu after a date is selected, defaults to true.
\r
53058 hideOnClick : true,
\r
53061 * @cfg {String} pickerId
\r
53062 * An id to assign to the underlying date picker. Defaults to <tt>null</tt>.
\r
53067 * @cfg {Number} maxHeight
\r
53071 * @cfg {Number} scrollIncrement
\r
53075 * The {@link Ext.DatePicker} instance for this DateMenu
\r
53076 * @property picker
\r
53077 * @type DatePicker
\r
53079 cls : 'x-date-menu',
\r
53087 * @event itemclick
\r
53091 initComponent : function(){
\r
53092 this.on('beforeshow', this.onBeforeShow, this);
\r
53093 if(this.strict = (Ext.isIE7 && Ext.isStrict)){
\r
53094 this.on('show', this.onShow, this, {single: true, delay: 20});
\r
53096 Ext.apply(this, {
\r
53098 showSeparator: false,
\r
53099 items: this.picker = new Ext.DatePicker(Ext.applyIf({
\r
53100 internalRender: this.strict || !Ext.isIE,
\r
53101 ctCls: 'x-menu-date-item',
\r
53102 id: this.pickerId
\r
53103 }, this.initialConfig))
\r
53105 this.picker.purgeListeners();
\r
53106 Ext.menu.DateMenu.superclass.initComponent.call(this);
\r
53109 * Fires when a date is selected from the {@link #picker Ext.DatePicker}
\r
53110 * @param {DatePicker} picker The {@link #picker Ext.DatePicker}
\r
53111 * @param {Date} date The selected date
\r
53113 this.relayEvents(this.picker, ['select']);
\r
53114 this.on('select', this.menuHide, this);
\r
53115 if(this.handler){
\r
53116 this.on('select', this.handler, this.scope || this);
\r
53120 menuHide : function() {
\r
53121 if(this.hideOnClick){
\r
53126 onBeforeShow : function(){
\r
53128 this.picker.hideMonthPicker(true);
\r
53132 onShow : function(){
\r
53133 var el = this.picker.getEl();
\r
53134 el.setWidth(el.getWidth()); //nasty hack for IE7 strict mode
\r
53137 Ext.reg('datemenu', Ext.menu.DateMenu);
\r
53139 * @class Ext.menu.ColorMenu
\r
53140 * @extends Ext.menu.Menu
\r
53141 * <p>A menu containing a {@link Ext.ColorPalette} Component.</p>
\r
53142 * <p>Notes:</p><div class="mdetail-params"><ul>
\r
53143 * <li>Although not listed here, the <b>constructor</b> for this class
\r
53144 * accepts all of the configuration options of <b>{@link Ext.ColorPalette}</b>.</li>
\r
53145 * <li>If subclassing ColorMenu, any configuration options for the ColorPalette must be
\r
53146 * applied to the <tt><b>initialConfig</b></tt> property of the ColorMenu.
\r
53147 * Applying {@link Ext.ColorPalette ColorPalette} configuration settings to
\r
53148 * <b><tt>this</tt></b> will <b>not</b> affect the ColorPalette's configuration.</li>
\r
53150 * @xtype colormenu
\r
53152 Ext.menu.ColorMenu = Ext.extend(Ext.menu.Menu, {
\r
53154 * @cfg {Boolean} enableScrolling
\r
53157 enableScrolling : false,
\r
53159 * @cfg {Function} handler
\r
53160 * Optional. A function that will handle the select event of this menu.
\r
53161 * The handler is passed the following parameters:<div class="mdetail-params"><ul>
\r
53162 * <li><code>palette</code> : ColorPalette<div class="sub-desc">The {@link #palette Ext.ColorPalette}.</div></li>
\r
53163 * <li><code>color</code> : String<div class="sub-desc">The 6-digit color hex code (without the # symbol).</div></li>
\r
53167 * @cfg {Object} scope
\r
53168 * The scope (<tt><b>this</b></tt> reference) in which the <code>{@link #handler}</code>
\r
53169 * function will be called. Defaults to this ColorMenu instance.
\r
53173 * @cfg {Boolean} hideOnClick
\r
53174 * False to continue showing the menu after a color is selected, defaults to true.
\r
53176 hideOnClick : true,
\r
53178 cls : 'x-color-menu',
\r
53181 * @cfg {String} paletteId
\r
53182 * An id to assign to the underlying color palette. Defaults to <tt>null</tt>.
\r
53184 paletteId : null,
\r
53187 * @cfg {Number} maxHeight
\r
53191 * @cfg {Number} scrollIncrement
\r
53195 * @property palette
\r
53196 * @type ColorPalette
\r
53197 * The {@link Ext.ColorPalette} instance for this ColorMenu
\r
53207 * @event itemclick
\r
53211 initComponent : function(){
\r
53212 Ext.apply(this, {
\r
53214 showSeparator: false,
\r
53215 items: this.palette = new Ext.ColorPalette(Ext.applyIf({
\r
53216 id: this.paletteId
\r
53217 }, this.initialConfig))
\r
53219 this.palette.purgeListeners();
\r
53220 Ext.menu.ColorMenu.superclass.initComponent.call(this);
\r
53223 * Fires when a color is selected from the {@link #palette Ext.ColorPalette}
\r
53224 * @param {Ext.ColorPalette} palette The {@link #palette Ext.ColorPalette}
\r
53225 * @param {String} color The 6-digit color hex code (without the # symbol)
\r
53227 this.relayEvents(this.palette, ['select']);
\r
53228 this.on('select', this.menuHide, this);
\r
53229 if(this.handler){
\r
53230 this.on('select', this.handler, this.scope || this);
\r
53234 menuHide : function(){
\r
53235 if(this.hideOnClick){
\r
53240 Ext.reg('colormenu', Ext.menu.ColorMenu);
\r
53242 * @class Ext.form.Field
53243 * @extends Ext.BoxComponent
53244 * Base class for form fields that provides default event handling, sizing, value handling and other functionality.
53246 * Creates a new Field
53247 * @param {Object} config Configuration options
53250 Ext.form.Field = Ext.extend(Ext.BoxComponent, {
53252 * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password, file (defaults
53253 * to 'text'). The types 'file' and 'password' must be used to render those field types currently -- there are
53254 * no separate Ext components for those. Note that if you use <tt>inputType:'file'</tt>, {@link #emptyText}
53255 * is not supported and should be avoided.
53258 * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered,
53259 * not those which are built via applyTo (defaults to undefined).
53262 * @cfg {Mixed} value A value to initialize this field with (defaults to undefined).
53265 * @cfg {String} name The field's HTML name attribute (defaults to '').
53266 * <b>Note</b>: this property must be set if this field is to be automatically included with
53267 * {@link Ext.form.BasicForm#submit form submit()}.
53270 * @cfg {String} cls A custom CSS class to apply to the field's underlying element (defaults to '').
53274 * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to 'x-form-invalid')
53276 invalidClass : 'x-form-invalid',
53278 * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided
53279 * (defaults to 'The value in this field is invalid')
53281 invalidText : 'The value in this field is invalid',
53283 * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to 'x-form-focus')
53285 focusClass : 'x-form-focus',
53287 * @cfg {Boolean} preventMark
53288 * <tt>true</tt> to disable {@link #markInvalid marking the field invalid}.
53289 * Defaults to <tt>false</tt>.
53292 * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
53293 automatic validation (defaults to 'keyup').
53295 validationEvent : 'keyup',
53297 * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
53299 validateOnBlur : true,
53301 * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation
53302 * is initiated (defaults to 250)
53304 validationDelay : 250,
53306 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
53307 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
53308 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
53309 * <pre><code>{tag: 'input', type: 'text', size: '20', autocomplete: 'off'}</code></pre>
53311 defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'},
53313 * @cfg {String} fieldClass The default CSS class for the field (defaults to 'x-form-field')
53315 fieldClass : 'x-form-field',
53317 * @cfg {String} msgTarget The location where error text should display. Should be one of the following values
53318 * (defaults to 'qtip'):
53321 ----------- ----------------------------------------------------------------------
53322 qtip Display a quick tip when the user hovers over the field
53323 title Display a default browser title attribute popup
53324 under Add a block div beneath the field containing the error text
53325 side Add an error icon to the right of the field with a popup on hover
53326 [element id] Add the error text directly to the innerHTML of the specified element
53329 msgTarget : 'qtip',
53331 * @cfg {String} msgFx <b>Experimental</b> The effect used when displaying a validation message under the field
53332 * (defaults to 'normal').
53336 * @cfg {Boolean} readOnly <tt>true</tt> to mark the field as readOnly in HTML
53337 * (defaults to <tt>false</tt>).
53338 * <br><p><b>Note</b>: this only sets the element's readOnly DOM attribute.
53339 * Setting <code>readOnly=true</code>, for example, will not disable triggering a
53340 * ComboBox or DateField; it gives you the option of forcing the user to choose
53341 * via the trigger without typing in the text box. To hide the trigger use
53342 * <code>{@link Ext.form.TriggerField#hideTrigger hideTrigger}</code>.</p>
53346 * @cfg {Boolean} disabled True to disable the field (defaults to false).
53347 * <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>,
53348 * disabled Fields will not be {@link Ext.form.BasicForm#submit submitted}.</p>
53353 isFormField : true,
53362 initComponent : function(){
53363 Ext.form.Field.superclass.initComponent.call(this);
53367 * Fires when this field receives input focus.
53368 * @param {Ext.form.Field} this
53373 * Fires when this field loses input focus.
53374 * @param {Ext.form.Field} this
53378 * @event specialkey
53379 * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.
53380 * To handle other keys see {@link Ext.Panel#keys} or {@link Ext.KeyMap}.
53381 * You can check {@link Ext.EventObject#getKey} to determine which key was pressed.
53382 * For example: <pre><code>
53383 var form = new Ext.form.FormPanel({
53386 fieldLabel: 'Field 1',
53390 fieldLabel: 'Field 2',
53393 specialkey: function(field, e){
53394 // e.HOME, e.END, e.PAGE_UP, e.PAGE_DOWN,
53395 // e.TAB, e.ESC, arrow keys: e.LEFT, e.RIGHT, e.UP, e.DOWN
53396 if (e.{@link Ext.EventObject#getKey getKey()} == e.ENTER) {
53397 var form = field.ownerCt.getForm();
53407 * @param {Ext.form.Field} this
53408 * @param {Ext.EventObject} e The event object
53413 * Fires just before the field blurs if the field value has changed.
53414 * @param {Ext.form.Field} this
53415 * @param {Mixed} newValue The new value
53416 * @param {Mixed} oldValue The original value
53421 * Fires after the field has been marked as invalid.
53422 * @param {Ext.form.Field} this
53423 * @param {String} msg The validation message
53428 * Fires after the field has been validated with no errors.
53429 * @param {Ext.form.Field} this
53436 * Returns the {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
53437 * attribute of the field if available.
53438 * @return {String} name The field {@link Ext.form.Field#name name} or {@link Ext.form.ComboBox#hiddenName hiddenName}
53440 getName : function(){
53441 return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || '';
53445 onRender : function(ct, position){
53447 var cfg = this.getAutoCreate();
53450 cfg.name = this.name || this.id;
53452 if(this.inputType){
53453 cfg.type = this.inputType;
53457 Ext.form.Field.superclass.onRender.call(this, ct, position);
53459 var type = this.el.dom.type;
53461 if(type == 'password'){
53464 this.el.addClass('x-form-'+type);
53467 this.el.dom.readOnly = true;
53469 if(this.tabIndex !== undefined){
53470 this.el.dom.setAttribute('tabIndex', this.tabIndex);
53473 this.el.addClass([this.fieldClass, this.cls]);
53477 getItemCt : function(){
53478 return this.itemCt;
53482 initValue : function(){
53483 if(this.value !== undefined){
53484 this.setValue(this.value);
53485 }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){
53486 this.setValue(this.el.dom.value);
53489 * The original value of the field as configured in the {@link #value} configuration, or
53490 * as loaded by the last form load operation if the form's {@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
53491 * setting is <code>true</code>.
53493 * @property originalValue
53495 this.originalValue = this.getValue();
53499 * <p>Returns true if the value of this Field has been changed from its original value.
53500 * Will return false if the field is disabled or has not been rendered yet.</p>
53501 * <p>Note that if the owning {@link Ext.form.BasicForm form} was configured with
53502 * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
53503 * then the <i>original value</i> is updated when the values are loaded by
53504 * {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#setValues setValues}.</p>
53505 * @return {Boolean} True if this field has been changed from its original value (and
53506 * is not disabled), false otherwise.
53508 isDirty : function() {
53509 if(this.disabled || !this.rendered) {
53512 return String(this.getValue()) !== String(this.originalValue);
53516 afterRender : function(){
53517 Ext.form.Field.superclass.afterRender.call(this);
53523 fireKey : function(e){
53524 if(e.isSpecialKey()){
53525 this.fireEvent('specialkey', this, e);
53530 * Resets the current field value to the originally loaded value and clears any validation messages.
53531 * See {@link Ext.form.BasicForm}.{@link Ext.form.BasicForm#trackResetOnLoad trackResetOnLoad}
53533 reset : function(){
53534 this.setValue(this.originalValue);
53535 this.clearInvalid();
53539 initEvents : function(){
53540 this.mon(this.el, Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.fireKey, this);
53541 this.mon(this.el, 'focus', this.onFocus, this);
53543 // standardise buffer across all browsers + OS-es for consistent event order.
53544 // (the 10ms buffer for Editors fixes a weird FF/Win editor issue when changing OS window focus)
53545 this.mon(this.el, 'blur', this.onBlur, this, this.inEditor ? {buffer:10} : null);
53549 preFocus: Ext.emptyFn,
53552 onFocus : function(){
53554 if(this.focusClass){
53555 this.el.addClass(this.focusClass);
53557 if(!this.hasFocus){
53558 this.hasFocus = true;
53559 this.startValue = this.getValue();
53560 this.fireEvent('focus', this);
53565 beforeBlur : Ext.emptyFn,
53568 onBlur : function(){
53570 if(this.focusClass){
53571 this.el.removeClass(this.focusClass);
53573 this.hasFocus = false;
53574 if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent != 'blur')){
53577 var v = this.getValue();
53578 if(String(v) !== String(this.startValue)){
53579 this.fireEvent('change', this, v, this.startValue);
53581 this.fireEvent('blur', this);
53586 postBlur : Ext.emptyFn,
53589 * Returns whether or not the field value is currently valid by
53590 * {@link #validateValue validating} the {@link #processValue processed value}
53591 * of the field. <b>Note</b>: {@link #disabled} fields are ignored.
53592 * @param {Boolean} preventMark True to disable marking the field invalid
53593 * @return {Boolean} True if the value is valid, else false
53595 isValid : function(preventMark){
53599 var restore = this.preventMark;
53600 this.preventMark = preventMark === true;
53601 var v = this.validateValue(this.processValue(this.getRawValue()));
53602 this.preventMark = restore;
53607 * Validates the field value
53608 * @return {Boolean} True if the value is valid, else false
53610 validate : function(){
53611 if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
53612 this.clearInvalid();
53619 * This method should only be overridden if necessary to prepare raw values
53620 * for validation (see {@link #validate} and {@link #isValid}). This method
53621 * is expected to return the processed value for the field which will
53622 * be used for validation (see validateValue method).
53623 * @param {Mixed} value
53625 processValue : function(value){
53631 * Subclasses should provide the validation implementation by overriding this
53632 * @param {Mixed} value
53634 validateValue : function(value){
53639 * Mark this field as invalid, using {@link #msgTarget} to determine how to
53640 * display the error and applying {@link #invalidClass} to the field's element.
53641 * <b>Note</b>: this method does not actually make the field
53642 * {@link #isValid invalid}.
53643 * @param {String} msg (optional) The validation message (defaults to {@link #invalidText})
53645 markInvalid : function(msg){
53646 if(!this.rendered || this.preventMark){ // not rendered
53649 msg = msg || this.invalidText;
53651 var mt = this.getMessageHandler();
53653 mt.mark(this, msg);
53654 }else if(this.msgTarget){
53655 this.el.addClass(this.invalidClass);
53656 var t = Ext.getDom(this.msgTarget);
53659 t.style.display = this.msgDisplay;
53662 this.fireEvent('invalid', this, msg);
53666 * Clear any invalid styles/messages for this field
53668 clearInvalid : function(){
53669 if(!this.rendered || this.preventMark){ // not rendered
53672 this.el.removeClass(this.invalidClass);
53673 var mt = this.getMessageHandler();
53676 }else if(this.msgTarget){
53677 this.el.removeClass(this.invalidClass);
53678 var t = Ext.getDom(this.msgTarget);
53681 t.style.display = 'none';
53684 this.fireEvent('valid', this);
53688 getMessageHandler : function(){
53689 return Ext.form.MessageTargets[this.msgTarget];
53693 getErrorCt : function(){
53694 return this.el.findParent('.x-form-element', 5, true) || // use form element wrap if available
53695 this.el.findParent('.x-form-field-wrap', 5, true); // else direct field wrap
53699 alignErrorIcon : function(){
53700 this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]);
53704 * Returns the raw data value which may or may not be a valid, defined value. To return a normalized value see {@link #getValue}.
53705 * @return {Mixed} value The field value
53707 getRawValue : function(){
53708 var v = this.rendered ? this.el.getValue() : Ext.value(this.value, '');
53709 if(v === this.emptyText){
53716 * Returns the normalized data value (undefined or emptyText will be returned as ''). To return the raw value see {@link #getRawValue}.
53717 * @return {Mixed} value The field value
53719 getValue : function(){
53720 if(!this.rendered) {
53723 var v = this.el.getValue();
53724 if(v === this.emptyText || v === undefined){
53731 * Sets the underlying DOM field's value directly, bypassing validation. To set the value with validation see {@link #setValue}.
53732 * @param {Mixed} value The value to set
53733 * @return {Mixed} value The field value that is set
53735 setRawValue : function(v){
53736 return this.rendered ? (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)) : '';
53740 * Sets a data value into the field and validates it. To set the value directly without validation see {@link #setRawValue}.
53741 * @param {Mixed} value The value to set
53742 * @return {Ext.form.Field} this
53744 setValue : function(v){
53747 this.el.dom.value = (Ext.isEmpty(v) ? '' : v);
53753 // private, does not work for all fields
53754 append : function(v){
53755 this.setValue([this.getValue(), v].join(''));
53759 * @cfg {Boolean} autoWidth @hide
53762 * @cfg {Boolean} autoHeight @hide
53766 * @cfg {String} autoEl @hide
53771 Ext.form.MessageTargets = {
53773 mark: function(field, msg){
53774 field.el.addClass(field.invalidClass);
53775 field.el.dom.qtip = msg;
53776 field.el.dom.qclass = 'x-form-invalid-tip';
53777 if(Ext.QuickTips){ // fix for floating editors interacting with DND
53778 Ext.QuickTips.enable();
53781 clear: function(field){
53782 field.el.removeClass(field.invalidClass);
53783 field.el.dom.qtip = '';
53787 mark: function(field, msg){
53788 field.el.addClass(field.invalidClass);
53789 field.el.dom.title = msg;
53791 clear: function(field){
53792 field.el.dom.title = '';
53796 mark: function(field, msg){
53797 field.el.addClass(field.invalidClass);
53798 if(!field.errorEl){
53799 var elp = field.getErrorCt();
53800 if(!elp){ // field has no container el
53801 field.el.dom.title = msg;
53804 field.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
53805 field.errorEl.setWidth(elp.getWidth(true)-20);
53807 field.errorEl.update(msg);
53808 Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field);
53810 clear: function(field){
53811 field.el.removeClass(field.invalidClass);
53813 Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field);
53815 field.el.dom.title = '';
53820 mark: function(field, msg){
53821 field.el.addClass(field.invalidClass);
53822 if(!field.errorIcon){
53823 var elp = field.getErrorCt();
53824 if(!elp){ // field has no container el
53825 field.el.dom.title = msg;
53828 field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
53830 field.alignErrorIcon();
53831 field.errorIcon.dom.qtip = msg;
53832 field.errorIcon.dom.qclass = 'x-form-invalid-tip';
53833 field.errorIcon.show();
53834 field.on('resize', field.alignErrorIcon, field);
53836 clear: function(field){
53837 field.el.removeClass(field.invalidClass);
53838 if(field.errorIcon){
53839 field.errorIcon.dom.qtip = '';
53840 field.errorIcon.hide();
53841 field.un('resize', field.alignErrorIcon, field);
53843 field.el.dom.title = '';
53849 // anything other than normal should be considered experimental
53850 Ext.form.Field.msgFx = {
53852 show: function(msgEl, f){
53853 msgEl.setDisplayed('block');
53856 hide : function(msgEl, f){
53857 msgEl.setDisplayed(false).update('');
53862 show: function(msgEl, f){
53863 msgEl.slideIn('t', {stopFx:true});
53866 hide : function(msgEl, f){
53867 msgEl.slideOut('t', {stopFx:true,useDisplay:true});
53872 show: function(msgEl, f){
53873 msgEl.fixDisplay();
53874 msgEl.alignTo(f.el, 'tl-tr');
53875 msgEl.slideIn('l', {stopFx:true});
53878 hide : function(msgEl, f){
53879 msgEl.slideOut('l', {stopFx:true,useDisplay:true});
53883 Ext.reg('field', Ext.form.Field);
53885 * @class Ext.form.TextField
53886 * @extends Ext.form.Field
53887 * <p>Basic text field. Can be used as a direct replacement for traditional text inputs,
53888 * or as the base class for more sophisticated input controls (like {@link Ext.form.TextArea}
53889 * and {@link Ext.form.ComboBox}).</p>
53890 * <p><b><u>Validation</u></b></p>
53891 * <p>The validation procedure is described in the documentation for {@link #validateValue}.</p>
53892 * <p><b><u>Alter Validation Behavior</u></b></p>
53893 * <p>Validation behavior for each field can be configured:</p>
53894 * <div class="mdetail-params"><ul>
53895 * <li><code>{@link Ext.form.TextField#invalidText invalidText}</code> : the default validation message to
53896 * show if any validation step above does not provide a message when invalid</li>
53897 * <li><code>{@link Ext.form.TextField#maskRe maskRe}</code> : filter out keystrokes before any validation occurs</li>
53898 * <li><code>{@link Ext.form.TextField#stripCharsRe stripCharsRe}</code> : filter characters after being typed in,
53899 * but before being validated</li>
53900 * <li><code>{@link Ext.form.Field#invalidClass invalidClass}</code> : alternate style when invalid</li>
53901 * <li><code>{@link Ext.form.Field#validateOnBlur validateOnBlur}</code>,
53902 * <code>{@link Ext.form.Field#validationDelay validationDelay}</code>, and
53903 * <code>{@link Ext.form.Field#validationEvent validationEvent}</code> : modify how/when validation is triggered</li>
53906 * @constructor Creates a new TextField
53907 * @param {Object} config Configuration options
53911 Ext.form.TextField = Ext.extend(Ext.form.Field, {
53913 * @cfg {String} vtypeText A custom error message to display in place of the default message provided
53914 * for the <b><code>{@link #vtype}</code></b> currently set for this field (defaults to <tt>''</tt>). <b>Note</b>:
53915 * only applies if <b><code>{@link #vtype}</code></b> is set, else ignored.
53918 * @cfg {RegExp} stripCharsRe A JavaScript RegExp object used to strip unwanted content from the value
53919 * before validation (defaults to <tt>null</tt>).
53922 * @cfg {Boolean} grow <tt>true</tt> if this field should automatically grow and shrink to its content
53923 * (defaults to <tt>false</tt>)
53927 * @cfg {Number} growMin The minimum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
53932 * @cfg {Number} growMax The maximum width to allow when <code><b>{@link #grow}</b> = true</code> (defaults
53937 * @cfg {String} vtype A validation type name as defined in {@link Ext.form.VTypes} (defaults to <tt>null</tt>)
53941 * @cfg {RegExp} maskRe An input mask regular expression that will be used to filter keystrokes that do
53942 * not match (defaults to <tt>null</tt>)
53946 * @cfg {Boolean} disableKeyFilter Specify <tt>true</tt> to disable input keystroke filtering (defaults
53947 * to <tt>false</tt>)
53949 disableKeyFilter : false,
53951 * @cfg {Boolean} allowBlank Specify <tt>false</tt> to validate that the value's length is > 0 (defaults to
53956 * @cfg {Number} minLength Minimum input field length required (defaults to <tt>0</tt>)
53960 * @cfg {Number} maxLength Maximum input field length allowed by validation (defaults to Number.MAX_VALUE).
53961 * This behavior is intended to provide instant feedback to the user by improving usability to allow pasting
53962 * and editing or overtyping and back tracking. To restrict the maximum number of characters that can be
53963 * entered into the field use <tt><b>{@link Ext.form.Field#autoCreate autoCreate}</b></tt> to add
53964 * any attributes you want to a field, for example:<pre><code>
53965 var myField = new Ext.form.NumberField({
53968 fieldLabel: 'Mobile',
53969 maxLength: 16, // for validation
53970 autoCreate: {tag: 'input', type: 'text', size: '20', autocomplete: 'off', maxlength: '10'}
53974 maxLength : Number.MAX_VALUE,
53976 * @cfg {String} minLengthText Error text to display if the <b><tt>{@link #minLength minimum length}</tt></b>
53977 * validation fails (defaults to <tt>'The minimum length for this field is {minLength}'</tt>)
53979 minLengthText : 'The minimum length for this field is {0}',
53981 * @cfg {String} maxLengthText Error text to display if the <b><tt>{@link #maxLength maximum length}</tt></b>
53982 * validation fails (defaults to <tt>'The maximum length for this field is {maxLength}'</tt>)
53984 maxLengthText : 'The maximum length for this field is {0}',
53986 * @cfg {Boolean} selectOnFocus <tt>true</tt> to automatically select any existing field text when the field
53987 * receives input focus (defaults to <tt>false</tt>)
53989 selectOnFocus : false,
53991 * @cfg {String} blankText The error text to display if the <b><tt>{@link #allowBlank}</tt></b> validation
53992 * fails (defaults to <tt>'This field is required'</tt>)
53994 blankText : 'This field is required',
53996 * @cfg {Function} validator
53997 * <p>A custom validation function to be called during field validation ({@link #validateValue})
53998 * (defaults to <tt>null</tt>). If specified, this function will be called first, allowing the
53999 * developer to override the default validation process.</p>
54000 * <br><p>This function will be passed the following Parameters:</p>
54001 * <div class="mdetail-params"><ul>
54002 * <li><code>value</code>: <i>Mixed</i>
54003 * <div class="sub-desc">The current field value</div></li>
54005 * <br><p>This function is to Return:</p>
54006 * <div class="mdetail-params"><ul>
54007 * <li><code>true</code>: <i>Boolean</i>
54008 * <div class="sub-desc"><code>true</code> if the value is valid</div></li>
54009 * <li><code>msg</code>: <i>String</i>
54010 * <div class="sub-desc">An error message if the value is invalid</div></li>
54015 * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation
54016 * (defaults to <tt>null</tt>). If the test fails, the field will be marked invalid using
54017 * <b><tt>{@link #regexText}</tt></b>.
54021 * @cfg {String} regexText The error text to display if <b><tt>{@link #regex}</tt></b> is used and the
54022 * test fails during validation (defaults to <tt>''</tt>)
54026 * @cfg {String} emptyText The default text to place into an empty field (defaults to <tt>null</tt>).
54027 * <b>Note</b>: that this value will be submitted to the server if this field is enabled and configured
54028 * with a {@link #name}.
54032 * @cfg {String} emptyClass The CSS class to apply to an empty field to style the <b><tt>{@link #emptyText}</tt></b>
54033 * (defaults to <tt>'x-form-empty-field'</tt>). This class is automatically added and removed as needed
54034 * depending on the current field value.
54036 emptyClass : 'x-form-empty-field',
54039 * @cfg {Boolean} enableKeyEvents <tt>true</tt> to enable the proxying of key events for the HTML input
54040 * field (defaults to <tt>false</tt>)
54043 initComponent : function(){
54044 Ext.form.TextField.superclass.initComponent.call(this);
54048 * Fires when the <tt><b>{@link #autoSize}</b></tt> function is triggered. The field may or
54049 * may not have actually changed size according to the default logic, but this event provides
54050 * a hook for the developer to apply additional logic at runtime to resize the field if needed.
54051 * @param {Ext.form.Field} this This text field
54052 * @param {Number} width The new field width
54058 * Keydown input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
54060 * @param {Ext.form.TextField} this This text field
54061 * @param {Ext.EventObject} e
54066 * Keyup input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
54068 * @param {Ext.form.TextField} this This text field
54069 * @param {Ext.EventObject} e
54074 * Keypress input field event. This event only fires if <tt><b>{@link #enableKeyEvents}</b></tt>
54076 * @param {Ext.form.TextField} this This text field
54077 * @param {Ext.EventObject} e
54084 initEvents : function(){
54085 Ext.form.TextField.superclass.initEvents.call(this);
54086 if(this.validationEvent == 'keyup'){
54087 this.validationTask = new Ext.util.DelayedTask(this.validate, this);
54088 this.mon(this.el, 'keyup', this.filterValidation, this);
54090 else if(this.validationEvent !== false && this.validationEvent != 'blur'){
54091 this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay});
54093 if(this.selectOnFocus || this.emptyText){
54094 this.mon(this.el, 'mousedown', this.onMouseDown, this);
54096 if(this.emptyText){
54097 this.applyEmptyText();
54100 if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){
54101 this.mon(this.el, 'keypress', this.filterKeys, this);
54104 this.mon(this.el, 'keyup', this.onKeyUpBuffered, this, {buffer: 50});
54105 this.mon(this.el, 'click', this.autoSize, this);
54107 if(this.enableKeyEvents){
54108 this.mon(this.el, {
54110 keyup: this.onKeyUp,
54111 keydown: this.onKeyDown,
54112 keypress: this.onKeyPress
54117 onMouseDown: function(e){
54118 if(!this.hasFocus){
54119 this.mon(this.el, 'mouseup', Ext.emptyFn, this, { single: true, preventDefault: true });
54123 processValue : function(value){
54124 if(this.stripCharsRe){
54125 var newValue = value.replace(this.stripCharsRe, '');
54126 if(newValue !== value){
54127 this.setRawValue(newValue);
54134 filterValidation : function(e){
54135 if(!e.isNavKeyPress()){
54136 this.validationTask.delay(this.validationDelay);
54141 onDisable: function(){
54142 Ext.form.TextField.superclass.onDisable.call(this);
54144 this.el.dom.unselectable = 'on';
54149 onEnable: function(){
54150 Ext.form.TextField.superclass.onEnable.call(this);
54152 this.el.dom.unselectable = '';
54157 onKeyUpBuffered : function(e){
54158 if(!e.isNavKeyPress()){
54164 onKeyUp : function(e){
54165 this.fireEvent('keyup', this, e);
54169 onKeyDown : function(e){
54170 this.fireEvent('keydown', this, e);
54174 onKeyPress : function(e){
54175 this.fireEvent('keypress', this, e);
54179 * Resets the current field value to the originally-loaded value and clears any validation messages.
54180 * Also adds <tt><b>{@link #emptyText}</b></tt> and <tt><b>{@link #emptyClass}</b></tt> if the
54181 * original value was blank.
54183 reset : function(){
54184 Ext.form.TextField.superclass.reset.call(this);
54185 this.applyEmptyText();
54188 applyEmptyText : function(){
54189 if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){
54190 this.setRawValue(this.emptyText);
54191 this.el.addClass(this.emptyClass);
54196 preFocus : function(){
54198 if(this.emptyText){
54199 if(el.dom.value == this.emptyText){
54200 this.setRawValue('');
54202 el.removeClass(this.emptyClass);
54204 if(this.selectOnFocus){
54210 postBlur : function(){
54211 this.applyEmptyText();
54215 filterKeys : function(e){
54216 // special keys don't generate charCodes, so leave them alone
54217 if(e.ctrlKey || e.isSpecialKey()){
54221 if(!this.maskRe.test(String.fromCharCode(e.getCharCode()))){
54226 setValue : function(v){
54227 if(this.emptyText && this.el && !Ext.isEmpty(v)){
54228 this.el.removeClass(this.emptyClass);
54230 Ext.form.TextField.superclass.setValue.apply(this, arguments);
54231 this.applyEmptyText();
54237 * <p>Validates a value according to the field's validation rules and marks the field as invalid
54238 * if the validation fails. Validation rules are processed in the following order:</p>
54239 * <div class="mdetail-params"><ul>
54241 * <li><b>1. Field specific validator</b>
54242 * <div class="sub-desc">
54243 * <p>A validator offers a way to customize and reuse a validation specification.
54244 * If a field is configured with a <code>{@link #validator}</code>
54245 * function, it will be passed the current field value. The <code>{@link #validator}</code>
54246 * function is expected to return either:
54247 * <div class="mdetail-params"><ul>
54248 * <li>Boolean <tt>true</tt> if the value is valid (validation continues).</li>
54249 * <li>a String to represent the invalid message if invalid (validation halts).</li>
54253 * <li><b>2. Basic Validation</b>
54254 * <div class="sub-desc">
54255 * <p>If the <code>{@link #validator}</code> has not halted validation,
54256 * basic validation proceeds as follows:</p>
54258 * <div class="mdetail-params"><ul>
54260 * <li><code>{@link #allowBlank}</code> : (Invalid message =
54261 * <code>{@link #emptyText}</code>)<div class="sub-desc">
54262 * Depending on the configuration of <code>{@link #allowBlank}</code>, a
54263 * blank field will cause validation to halt at this step and return
54264 * Boolean true or false accordingly.
54267 * <li><code>{@link #minLength}</code> : (Invalid message =
54268 * <code>{@link #minLengthText}</code>)<div class="sub-desc">
54269 * If the passed value does not satisfy the <code>{@link #minLength}</code>
54270 * specified, validation halts.
54273 * <li><code>{@link #maxLength}</code> : (Invalid message =
54274 * <code>{@link #maxLengthText}</code>)<div class="sub-desc">
54275 * If the passed value does not satisfy the <code>{@link #maxLength}</code>
54276 * specified, validation halts.
54282 * <li><b>3. Preconfigured Validation Types (VTypes)</b>
54283 * <div class="sub-desc">
54284 * <p>If none of the prior validation steps halts validation, a field
54285 * configured with a <code>{@link #vtype}</code> will utilize the
54286 * corresponding {@link Ext.form.VTypes VTypes} validation function.
54287 * If invalid, either the field's <code>{@link #vtypeText}</code> or
54288 * the VTypes vtype Text property will be used for the invalid message.
54289 * Keystrokes on the field will be filtered according to the VTypes
54290 * vtype Mask property.</p>
54293 * <li><b>4. Field specific regex test</b>
54294 * <div class="sub-desc">
54295 * <p>If none of the prior validation steps halts validation, a field's
54296 * configured <code>{@link #regex}</code> test will be processed.
54297 * The invalid message for this test is configured with
54298 * <code>{@link #regexText}</code>.</p>
54301 * @param {Mixed} value The value to validate
54302 * @return {Boolean} True if the value is valid, else false
54304 validateValue : function(value){
54305 if(Ext.isFunction(this.validator)){
54306 var msg = this.validator(value);
54308 this.markInvalid(msg);
54312 if(value.length < 1 || value === this.emptyText){ // if it's blank
54313 if(this.allowBlank){
54314 this.clearInvalid();
54317 this.markInvalid(this.blankText);
54321 if(value.length < this.minLength){
54322 this.markInvalid(String.format(this.minLengthText, this.minLength));
54325 if(value.length > this.maxLength){
54326 this.markInvalid(String.format(this.maxLengthText, this.maxLength));
54330 var vt = Ext.form.VTypes;
54331 if(!vt[this.vtype](value, this)){
54332 this.markInvalid(this.vtypeText || vt[this.vtype +'Text']);
54336 if(this.regex && !this.regex.test(value)){
54337 this.markInvalid(this.regexText);
54344 * Selects text in this field
54345 * @param {Number} start (optional) The index where the selection should start (defaults to 0)
54346 * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
54348 selectText : function(start, end){
54349 var v = this.getRawValue();
54350 var doFocus = false;
54352 start = start === undefined ? 0 : start;
54353 end = end === undefined ? v.length : end;
54354 var d = this.el.dom;
54355 if(d.setSelectionRange){
54356 d.setSelectionRange(start, end);
54357 }else if(d.createTextRange){
54358 var range = d.createTextRange();
54359 range.moveStart('character', start);
54360 range.moveEnd('character', end-v.length);
54363 doFocus = Ext.isGecko || Ext.isOpera;
54373 * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
54374 * This only takes effect if <tt><b>{@link #grow}</b> = true</tt>, and fires the {@link #autosize} event.
54376 autoSize : function(){
54377 if(!this.grow || !this.rendered){
54381 this.metrics = Ext.util.TextMetrics.createInstance(this.el);
54384 var v = el.dom.value;
54385 var d = document.createElement('div');
54386 d.appendChild(document.createTextNode(v));
54391 var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
54392 this.el.setWidth(w);
54393 this.fireEvent('autosize', this, w);
54396 onDestroy: function(){
54397 if(this.validationTask){
54398 this.validationTask.cancel();
54399 this.validationTask = null;
54401 Ext.form.TextField.superclass.onDestroy.call(this);
54404 Ext.reg('textfield', Ext.form.TextField);
54406 * @class Ext.form.TriggerField
\r
54407 * @extends Ext.form.TextField
\r
54408 * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
\r
54409 * The trigger has no default action, so you must assign a function to implement the trigger click handler by
\r
54410 * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox
\r
54411 * for which you can provide a custom implementation. For example:
\r
54413 var trigger = new Ext.form.TriggerField();
\r
54414 trigger.onTriggerClick = myTriggerFn;
\r
54415 trigger.applyToMarkup('my-field');
\r
54418 * However, in general you will most likely want to use TriggerField as the base class for a reusable component.
\r
54419 * {@link Ext.form.DateField} and {@link Ext.form.ComboBox} are perfect examples of this.
\r
54422 * Create a new TriggerField.
\r
54423 * @param {Object} config Configuration options (valid {@Ext.form.TextField} config options will also be applied
\r
54424 * to the base TextField)
\r
54427 Ext.form.TriggerField = Ext.extend(Ext.form.TextField, {
\r
54429 * @cfg {String} triggerClass
\r
54430 * An additional CSS class used to style the trigger button. The trigger will always get the
\r
54431 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
\r
54434 * @cfg {Mixed} triggerConfig
\r
54435 * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the
\r
54436 * trigger element for this Field. (Optional).</p>
\r
54437 * <p>Specify this when you need a customized element to act as the trigger button for a TriggerField.</p>
\r
54438 * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing, positioning
\r
54439 * and appearance of the trigger. Defaults to:</p>
\r
54440 * <pre><code>{tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass}</code></pre>
\r
54443 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
\r
54444 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
\r
54445 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
\r
54446 * <pre><code>{tag: "input", type: "text", size: "16", autocomplete: "off"}</code></pre>
\r
54448 defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"},
\r
54450 * @cfg {Boolean} hideTrigger <tt>true</tt> to hide the trigger element and display only the base
\r
54451 * text field (defaults to <tt>false</tt>)
\r
54453 hideTrigger:false,
\r
54455 * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field,
\r
54456 * the field will only respond to a click on the trigger to set the value. (defaults to <tt>true</tt>)
\r
54460 * @cfg {String} wrapFocusClass The class added to the to the wrap of the trigger element. Defaults to
\r
54461 * <tt>x-trigger-wrap-focus</tt>.
\r
54463 wrapFocusClass: 'x-trigger-wrap-focus',
\r
54466 * @method autoSize
\r
54468 autoSize: Ext.emptyFn,
\r
54470 monitorTab : true,
\r
54472 deferHeight : true,
\r
54474 mimicing : false,
\r
54476 actionMode: 'wrap',
\r
54478 defaultTriggerWidth: 17,
\r
54481 onResize : function(w, h){
\r
54482 Ext.form.TriggerField.superclass.onResize.call(this, w, h);
\r
54483 var tw = this.getTriggerWidth();
\r
54484 if(Ext.isNumber(w)){
\r
54485 this.el.setWidth(w - tw);
\r
54487 this.wrap.setWidth(this.el.getWidth() + tw);
\r
54490 getTriggerWidth: function(){
\r
54491 var tw = this.trigger.getWidth();
\r
54492 if(!this.hideTrigger && tw === 0){
\r
54493 tw = this.defaultTriggerWidth;
\r
54499 alignErrorIcon : function(){
\r
54501 this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
\r
54506 onRender : function(ct, position){
\r
54507 this.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
\r
54508 Ext.form.TriggerField.superclass.onRender.call(this, ct, position);
\r
54510 this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
\r
54511 this.trigger = this.wrap.createChild(this.triggerConfig ||
\r
54512 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
\r
54513 if(this.hideTrigger){
\r
54514 this.trigger.setDisplayed(false);
\r
54516 this.initTrigger();
\r
54518 this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
\r
54520 if(!this.editable){
\r
54521 this.editable = true;
\r
54522 this.setEditable(false);
\r
54524 this.resizeEl = this.positionEl = this.wrap;
\r
54527 afterRender : function(){
\r
54528 Ext.form.TriggerField.superclass.afterRender.call(this);
\r
54532 initTrigger : function(){
\r
54533 this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true});
\r
54534 this.trigger.addClassOnOver('x-form-trigger-over');
\r
54535 this.trigger.addClassOnClick('x-form-trigger-click');
\r
54539 onDestroy : function(){
\r
54540 Ext.destroy(this.trigger, this.wrap);
\r
54541 if (this.mimicing){
\r
54542 this.doc.un('mousedown', this.mimicBlur, this);
\r
54544 Ext.form.TriggerField.superclass.onDestroy.call(this);
\r
54548 onFocus : function(){
\r
54549 Ext.form.TriggerField.superclass.onFocus.call(this);
\r
54550 if(!this.mimicing){
\r
54551 this.wrap.addClass(this.wrapFocusClass);
\r
54552 this.mimicing = true;
\r
54553 this.doc.on('mousedown', this.mimicBlur, this, {delay: 10});
\r
54554 if(this.monitorTab){
\r
54555 this.on('specialkey', this.checkTab, this);
\r
54561 checkTab : function(me, e){
\r
54562 if(e.getKey() == e.TAB){
\r
54563 this.triggerBlur();
\r
54568 onBlur : Ext.emptyFn,
\r
54571 mimicBlur : function(e){
\r
54572 if(!this.isDestroyed && !this.wrap.contains(e.target) && this.validateBlur(e)){
\r
54573 this.triggerBlur();
\r
54578 triggerBlur : function(){
\r
54579 this.mimicing = false;
\r
54580 this.doc.un('mousedown', this.mimicBlur, this);
\r
54581 if(this.monitorTab && this.el){
\r
54582 this.un('specialkey', this.checkTab, this);
\r
54584 Ext.form.TriggerField.superclass.onBlur.call(this);
\r
54586 this.wrap.removeClass(this.wrapFocusClass);
\r
54590 beforeBlur : Ext.emptyFn,
\r
54593 * Allow or prevent the user from directly editing the field text. If false is passed,
\r
54594 * the user will only be able to modify the field using the trigger. This method
\r
54595 * is the runtime equivalent of setting the 'editable' config option at config time.
\r
54596 * @param {Boolean} value True to allow the user to directly edit the field text
\r
54598 setEditable : function(value){
\r
54599 if(value == this.editable){
\r
54602 this.editable = value;
\r
54604 this.el.addClass('x-trigger-noedit').on('click', this.onTriggerClick, this).dom.setAttribute('readOnly', true);
\r
54606 this.el.removeClass('x-trigger-noedit').un('click', this.onTriggerClick, this).dom.removeAttribute('readOnly');
\r
54611 // This should be overriden by any subclass that needs to check whether or not the field can be blurred.
\r
54612 validateBlur : function(e){
\r
54617 * The function that should handle the trigger's click event. This method does nothing by default
\r
54618 * until overridden by an implementing function. See Ext.form.ComboBox and Ext.form.DateField for
\r
54619 * sample implementations.
\r
54621 * @param {EventObject} e
\r
54623 onTriggerClick : Ext.emptyFn
\r
54626 * @cfg {Boolean} grow @hide
\r
54629 * @cfg {Number} growMin @hide
\r
54632 * @cfg {Number} growMax @hide
\r
54637 * @class Ext.form.TwinTriggerField
\r
54638 * @extends Ext.form.TriggerField
\r
54639 * TwinTriggerField is not a public class to be used directly. It is meant as an abstract base class
\r
54640 * to be extended by an implementing class. For an example of implementing this class, see the custom
\r
54641 * SearchField implementation here:
\r
54642 * <a href="http://extjs.com/deploy/ext/examples/form/custom.html">http://extjs.com/deploy/ext/examples/form/custom.html</a>
\r
54644 Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, {
\r
54646 * @cfg {Mixed} triggerConfig
\r
54647 * <p>A {@link Ext.DomHelper DomHelper} config object specifying the structure of the trigger elements
\r
54648 * for this Field. (Optional).</p>
\r
54649 * <p>Specify this when you need a customized element to contain the two trigger elements for this Field.
\r
54650 * Each trigger element must be marked by the CSS class <tt>x-form-trigger</tt> (also see
\r
54651 * <tt>{@link #trigger1Class}</tt> and <tt>{@link #trigger2Class}</tt>).</p>
\r
54652 * <p>Note that when using this option, it is the developer's responsibility to ensure correct sizing,
\r
54653 * positioning and appearance of the triggers.</p>
\r
54656 * @cfg {String} trigger1Class
\r
54657 * An additional CSS class used to style the trigger button. The trigger will always get the
\r
54658 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
\r
54661 * @cfg {String} trigger2Class
\r
54662 * An additional CSS class used to style the trigger button. The trigger will always get the
\r
54663 * class <tt>'x-form-trigger'</tt> by default and <tt>triggerClass</tt> will be <b>appended</b> if specified.
\r
54666 initComponent : function(){
\r
54667 Ext.form.TwinTriggerField.superclass.initComponent.call(this);
\r
54669 this.triggerConfig = {
\r
54670 tag:'span', cls:'x-form-twin-triggers', cn:[
\r
54671 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
\r
54672 {tag: "img", src: Ext.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
\r
54676 getTrigger : function(index){
\r
54677 return this.triggers[index];
\r
54680 initTrigger : function(){
\r
54681 var ts = this.trigger.select('.x-form-trigger', true);
\r
54682 var triggerField = this;
\r
54683 ts.each(function(t, all, index){
\r
54684 var triggerIndex = 'Trigger'+(index+1);
\r
54685 t.hide = function(){
\r
54686 var w = triggerField.wrap.getWidth();
\r
54687 this.dom.style.display = 'none';
\r
54688 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
\r
54689 this['hidden' + triggerIndex] = true;
\r
54691 t.show = function(){
\r
54692 var w = triggerField.wrap.getWidth();
\r
54693 this.dom.style.display = '';
\r
54694 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
\r
54695 this['hidden' + triggerIndex] = false;
\r
54698 if(this['hide'+triggerIndex]){
\r
54699 t.dom.style.display = 'none';
\r
54700 this['hidden' + triggerIndex] = true;
\r
54702 this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
\r
54703 t.addClassOnOver('x-form-trigger-over');
\r
54704 t.addClassOnClick('x-form-trigger-click');
\r
54706 this.triggers = ts.elements;
\r
54709 getTriggerWidth: function(){
\r
54711 Ext.each(this.triggers, function(t, index){
\r
54712 var triggerIndex = 'Trigger' + (index + 1),
\r
54713 w = t.getWidth();
\r
54714 if(w === 0 && !this['hidden' + triggerIndex]){
\r
54715 tw += this.defaultTriggerWidth;
\r
54724 onDestroy : function() {
\r
54725 Ext.destroy(this.triggers);
\r
54726 Ext.form.TwinTriggerField.superclass.onDestroy.call(this);
\r
54730 * The function that should handle the trigger's click event. This method does nothing by default
\r
54731 * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
\r
54732 * for additional information.
\r
54734 * @param {EventObject} e
\r
54736 onTrigger1Click : Ext.emptyFn,
\r
54738 * The function that should handle the trigger's click event. This method does nothing by default
\r
54739 * until overridden by an implementing function. See {@link Ext.form.TriggerField#onTriggerClick}
\r
54740 * for additional information.
\r
54742 * @param {EventObject} e
\r
54744 onTrigger2Click : Ext.emptyFn
\r
54746 Ext.reg('trigger', Ext.form.TriggerField);/**
54747 * @class Ext.form.TextArea
54748 * @extends Ext.form.TextField
54749 * Multiline text field. Can be used as a direct replacement for traditional textarea fields, plus adds
54750 * support for auto-sizing.
54752 * Creates a new TextArea
54753 * @param {Object} config Configuration options
54756 Ext.form.TextArea = Ext.extend(Ext.form.TextField, {
54758 * @cfg {Number} growMin The minimum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
54759 * (defaults to <tt>60</tt>)
54763 * @cfg {Number} growMax The maximum height to allow when <tt>{@link Ext.form.TextField#grow grow}=true</tt>
54764 * (defaults to <tt>1000</tt>)
54767 growAppend : ' \n ',
54768 growPad : Ext.isWebKit ? -6 : 0,
54770 enterIsSpecial : false,
54773 * @cfg {Boolean} preventScrollbars <tt>true</tt> to prevent scrollbars from appearing regardless of how much text is
54774 * in the field. This option is only relevant when {@link #grow} is <tt>true</tt>. Equivalent to setting overflow: hidden, defaults to
54777 preventScrollbars: false,
54779 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or true for a default
54780 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
54781 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
54782 * <pre><code>{tag: "textarea", style: "width:100px;height:60px;", autocomplete: "off"}</code></pre>
54786 onRender : function(ct, position){
54788 this.defaultAutoCreate = {
54790 style:"width:100px;height:60px;",
54791 autocomplete: "off"
54794 Ext.form.TextArea.superclass.onRender.call(this, ct, position);
54796 this.textSizeEl = Ext.DomHelper.append(document.body, {
54797 tag: "pre", cls: "x-form-grow-sizer"
54799 if(this.preventScrollbars){
54800 this.el.setStyle("overflow", "hidden");
54802 this.el.setHeight(this.growMin);
54806 onDestroy : function(){
54807 Ext.destroy(this.textSizeEl);
54808 Ext.form.TextArea.superclass.onDestroy.call(this);
54811 fireKey : function(e){
54812 if(e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() != e.ENTER || e.hasModifier()))){
54813 this.fireEvent("specialkey", this, e);
54818 onKeyUp : function(e){
54819 if(!e.isNavKeyPress() || e.getKey() == e.ENTER){
54822 Ext.form.TextArea.superclass.onKeyUp.call(this, e);
54826 * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
54827 * This only takes effect if grow = true, and fires the {@link #autosize} event if the height changes.
54829 autoSize: function(){
54830 if(!this.grow || !this.textSizeEl){
54834 var v = el.dom.value;
54835 var ts = this.textSizeEl;
54837 ts.appendChild(document.createTextNode(v));
54839 Ext.fly(ts).setWidth(this.el.getWidth());
54841 v = "  ";
54843 v += this.growAppend;
54845 v = v.replace(/\n/g, '<br />');
54849 var h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin) + this.growPad);
54850 if(h != this.lastHeight){
54851 this.lastHeight = h;
54852 this.el.setHeight(h);
54853 this.fireEvent("autosize", this, h);
54857 Ext.reg('textarea', Ext.form.TextArea);/**
54858 * @class Ext.form.NumberField
54859 * @extends Ext.form.TextField
54860 * Numeric text field that provides automatic keystroke filtering and numeric validation.
54862 * Creates a new NumberField
54863 * @param {Object} config Configuration options
54864 * @xtype numberfield
54866 Ext.form.NumberField = Ext.extend(Ext.form.TextField, {
54868 * @cfg {RegExp} stripCharsRe @hide
54871 * @cfg {RegExp} maskRe @hide
54874 * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
54876 fieldClass: "x-form-field x-form-num-field",
54878 * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
54880 allowDecimals : true,
54882 * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
54884 decimalSeparator : ".",
54886 * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
54888 decimalPrecision : 2,
54890 * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
54892 allowNegative : true,
54894 * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
54896 minValue : Number.NEGATIVE_INFINITY,
54898 * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
54900 maxValue : Number.MAX_VALUE,
54902 * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
54904 minText : "The minimum value for this field is {0}",
54906 * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
54908 maxText : "The maximum value for this field is {0}",
54910 * @cfg {String} nanText Error text to display if the value is not a valid number. For example, this can happen
54911 * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
54913 nanText : "{0} is not a valid number",
54915 * @cfg {String} baseChars The base set of characters to evaluate as valid numbers (defaults to '0123456789').
54917 baseChars : "0123456789",
54920 initEvents : function(){
54921 var allowed = this.baseChars + '';
54922 if (this.allowDecimals) {
54923 allowed += this.decimalSeparator;
54925 if (this.allowNegative) {
54928 this.maskRe = new RegExp('[' + Ext.escapeRe(allowed) + ']');
54929 Ext.form.NumberField.superclass.initEvents.call(this);
54933 validateValue : function(value){
54934 if(!Ext.form.NumberField.superclass.validateValue.call(this, value)){
54937 if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
54940 value = String(value).replace(this.decimalSeparator, ".");
54942 this.markInvalid(String.format(this.nanText, value));
54945 var num = this.parseValue(value);
54946 if(num < this.minValue){
54947 this.markInvalid(String.format(this.minText, this.minValue));
54950 if(num > this.maxValue){
54951 this.markInvalid(String.format(this.maxText, this.maxValue));
54957 getValue : function(){
54958 return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)));
54961 setValue : function(v){
54962 v = typeof v == 'number' ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
54963 v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
54964 return Ext.form.NumberField.superclass.setValue.call(this, v);
54968 parseValue : function(value){
54969 value = parseFloat(String(value).replace(this.decimalSeparator, "."));
54970 return isNaN(value) ? '' : value;
54974 fixPrecision : function(value){
54975 var nan = isNaN(value);
54976 if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
54977 return nan ? '' : value;
54979 return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
54982 beforeBlur : function(){
54983 var v = this.parseValue(this.getRawValue());
54984 if(!Ext.isEmpty(v)){
54985 this.setValue(this.fixPrecision(v));
54989 Ext.reg('numberfield', Ext.form.NumberField);/**
54990 * @class Ext.form.DateField
54991 * @extends Ext.form.TriggerField
54992 * Provides a date input field with a {@link Ext.DatePicker} dropdown and automatic date validation.
54994 * Create a new DateField
54995 * @param {Object} config
54998 Ext.form.DateField = Ext.extend(Ext.form.TriggerField, {
55000 * @cfg {String} format
55001 * The default date format string which can be overriden for localization support. The format must be
55002 * valid according to {@link Date#parseDate} (defaults to <tt>'m/d/Y'</tt>).
55006 * @cfg {String} altFormats
55007 * Multiple date formats separated by "<tt>|</tt>" to try when parsing a user input value and it
55008 * does not match the defined format (defaults to
55009 * <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>).
55011 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",
55013 * @cfg {String} disabledDaysText
55014 * The tooltip to display when the date falls on a disabled day (defaults to <tt>'Disabled'</tt>)
55016 disabledDaysText : "Disabled",
55018 * @cfg {String} disabledDatesText
55019 * The tooltip text to display when the date falls on a disabled date (defaults to <tt>'Disabled'</tt>)
55021 disabledDatesText : "Disabled",
55023 * @cfg {String} minText
55024 * The error text to display when the date in the cell is before <tt>{@link #minValue}</tt> (defaults to
55025 * <tt>'The date in this field must be after {minValue}'</tt>).
55027 minText : "The date in this field must be equal to or after {0}",
55029 * @cfg {String} maxText
55030 * The error text to display when the date in the cell is after <tt>{@link #maxValue}</tt> (defaults to
55031 * <tt>'The date in this field must be before {maxValue}'</tt>).
55033 maxText : "The date in this field must be equal to or before {0}",
55035 * @cfg {String} invalidText
55036 * The error text to display when the date in the field is invalid (defaults to
55037 * <tt>'{value} is not a valid date - it must be in the format {format}'</tt>).
55039 invalidText : "{0} is not a valid date - it must be in the format {1}",
55041 * @cfg {String} triggerClass
55042 * An additional CSS class used to style the trigger button. The trigger will always get the
55043 * class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
55044 * (defaults to <tt>'x-form-date-trigger'</tt> which displays a calendar icon).
55046 triggerClass : 'x-form-date-trigger',
55048 * @cfg {Boolean} showToday
55049 * <tt>false</tt> to hide the footer area of the DatePicker containing the Today button and disable
55050 * the keyboard handler for spacebar that selects the current date (defaults to <tt>true</tt>).
55054 * @cfg {Date/String} minValue
55055 * The minimum allowed date. Can be either a Javascript date object or a string date in a
55056 * valid format (defaults to null).
55059 * @cfg {Date/String} maxValue
55060 * The maximum allowed date. Can be either a Javascript date object or a string date in a
55061 * valid format (defaults to null).
55064 * @cfg {Array} disabledDays
55065 * An array of days to disable, 0 based (defaults to null). Some examples:<pre><code>
55066 // disable Sunday and Saturday:
55067 disabledDays: [0, 6]
55068 // disable weekdays:
55069 disabledDays: [1,2,3,4,5]
55073 * @cfg {Array} disabledDates
55074 * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
55075 * expression so they are very powerful. Some examples:<pre><code>
55076 // disable these exact dates:
55077 disabledDates: ["03/08/2003", "09/16/2003"]
55078 // disable these days for every year:
55079 disabledDates: ["03/08", "09/16"]
55080 // only match the beginning (useful if you are using short years):
55081 disabledDates: ["^03/08"]
55082 // disable every day in March 2006:
55083 disabledDates: ["03/../2006"]
55084 // disable every day in every March:
55085 disabledDates: ["^03"]
55087 * Note that the format of the dates included in the array should exactly match the {@link #format} config.
55088 * In order to support regular expressions, if you are using a {@link #format date format} that has "." in
55089 * it, you will have to escape the dot when restricting dates. For example: <tt>["03\\.08\\.03"]</tt>.
55092 * @cfg {String/Object} autoCreate
55093 * A {@link Ext.DomHelper DomHelper element specification object}, or <tt>true</tt> for the default element
55094 * specification object:<pre><code>
55095 * autoCreate: {tag: "input", type: "text", size: "10", autocomplete: "off"}
55100 defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"},
55102 initComponent : function(){
55103 Ext.form.DateField.superclass.initComponent.call(this);
55108 * Fires when a date is selected via the date picker.
55109 * @param {Ext.form.DateField} this
55110 * @param {Date} date The date that was selected
55115 if(Ext.isString(this.minValue)){
55116 this.minValue = this.parseDate(this.minValue);
55118 if(Ext.isString(this.maxValue)){
55119 this.maxValue = this.parseDate(this.maxValue);
55121 this.disabledDatesRE = null;
55122 this.initDisabledDays();
55126 initDisabledDays : function(){
55127 if(this.disabledDates){
55128 var dd = this.disabledDates,
55129 len = dd.length - 1,
55132 Ext.each(dd, function(d, i){
55133 re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
55138 this.disabledDatesRE = new RegExp(re + ')');
55143 * Replaces any existing disabled dates with new values and refreshes the DatePicker.
55144 * @param {Array} disabledDates An array of date strings (see the <tt>{@link #disabledDates}</tt> config
55145 * for details on supported values) used to disable a pattern of dates.
55147 setDisabledDates : function(dd){
55148 this.disabledDates = dd;
55149 this.initDisabledDays();
55151 this.menu.picker.setDisabledDates(this.disabledDatesRE);
55156 * Replaces any existing disabled days (by index, 0-6) with new values and refreshes the DatePicker.
55157 * @param {Array} disabledDays An array of disabled day indexes. See the <tt>{@link #disabledDays}</tt>
55158 * config for details on supported values.
55160 setDisabledDays : function(dd){
55161 this.disabledDays = dd;
55163 this.menu.picker.setDisabledDays(dd);
55168 * Replaces any existing <tt>{@link #minValue}</tt> with the new value and refreshes the DatePicker.
55169 * @param {Date} value The minimum date that can be selected
55171 setMinValue : function(dt){
55172 this.minValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
55174 this.menu.picker.setMinDate(this.minValue);
55179 * Replaces any existing <tt>{@link #maxValue}</tt> with the new value and refreshes the DatePicker.
55180 * @param {Date} value The maximum date that can be selected
55182 setMaxValue : function(dt){
55183 this.maxValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
55185 this.menu.picker.setMaxDate(this.maxValue);
55190 validateValue : function(value){
55191 value = this.formatDate(value);
55192 if(!Ext.form.DateField.superclass.validateValue.call(this, value)){
55195 if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
55198 var svalue = value;
55199 value = this.parseDate(value);
55201 this.markInvalid(String.format(this.invalidText, svalue, this.format));
55204 var time = value.getTime();
55205 if(this.minValue && time < this.minValue.getTime()){
55206 this.markInvalid(String.format(this.minText, this.formatDate(this.minValue)));
55209 if(this.maxValue && time > this.maxValue.getTime()){
55210 this.markInvalid(String.format(this.maxText, this.formatDate(this.maxValue)));
55213 if(this.disabledDays){
55214 var day = value.getDay();
55215 for(var i = 0; i < this.disabledDays.length; i++) {
55216 if(day === this.disabledDays[i]){
55217 this.markInvalid(this.disabledDaysText);
55222 var fvalue = this.formatDate(value);
55223 if(this.disabledDatesRE && this.disabledDatesRE.test(fvalue)){
55224 this.markInvalid(String.format(this.disabledDatesText, fvalue));
55231 // Provides logic to override the default TriggerField.validateBlur which just returns true
55232 validateBlur : function(){
55233 return !this.menu || !this.menu.isVisible();
55237 * Returns the current date value of the date field.
55238 * @return {Date} The date value
55240 getValue : function(){
55241 return this.parseDate(Ext.form.DateField.superclass.getValue.call(this)) || "";
55245 * Sets the value of the date field. You can pass a date object or any string that can be
55246 * parsed into a valid date, using <tt>{@link #format}</tt> as the date format, according
55247 * to the same rules as {@link Date#parseDate} (the default format used is <tt>"m/d/Y"</tt>).
55250 //All of these calls set the same date value (May 4, 2006)
55252 //Pass a date object:
55253 var dt = new Date('5/4/2006');
55254 dateField.setValue(dt);
55256 //Pass a date string (default format):
55257 dateField.setValue('05/04/2006');
55259 //Pass a date string (custom format):
55260 dateField.format = 'Y-m-d';
55261 dateField.setValue('2006-05-04');
55263 * @param {String/Date} date The date or valid date string
55264 * @return {Ext.form.Field} this
55266 setValue : function(date){
55267 return Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
55271 parseDate : function(value){
55272 if(!value || Ext.isDate(value)){
55275 var v = Date.parseDate(value, this.format);
55276 if(!v && this.altFormats){
55277 if(!this.altFormatsArray){
55278 this.altFormatsArray = this.altFormats.split("|");
55280 for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){
55281 v = Date.parseDate(value, this.altFormatsArray[i]);
55288 onDestroy : function(){
55289 Ext.destroy(this.menu);
55290 Ext.form.DateField.superclass.onDestroy.call(this);
55294 formatDate : function(date){
55295 return Ext.isDate(date) ? date.dateFormat(this.format) : date;
55299 * @method onTriggerClick
55303 // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker
55304 onTriggerClick : function(){
55308 if(this.menu == null){
55309 this.menu = new Ext.menu.DateMenu({
55314 Ext.apply(this.menu.picker, {
55315 minDate : this.minValue,
55316 maxDate : this.maxValue,
55317 disabledDatesRE : this.disabledDatesRE,
55318 disabledDatesText : this.disabledDatesText,
55319 disabledDays : this.disabledDays,
55320 disabledDaysText : this.disabledDaysText,
55321 format : this.format,
55322 showToday : this.showToday,
55323 minText : String.format(this.minText, this.formatDate(this.minValue)),
55324 maxText : String.format(this.maxText, this.formatDate(this.maxValue))
55326 this.menu.picker.setValue(this.getValue() || new Date());
55327 this.menu.show(this.el, "tl-bl?");
55328 this.menuEvents('on');
55332 menuEvents: function(method){
55333 this.menu[method]('select', this.onSelect, this);
55334 this.menu[method]('hide', this.onMenuHide, this);
55335 this.menu[method]('show', this.onFocus, this);
55338 onSelect: function(m, d){
55340 this.fireEvent('select', this, d);
55344 onMenuHide: function(){
55345 this.focus(false, 60);
55346 this.menuEvents('un');
55350 beforeBlur : function(){
55351 var v = this.parseDate(this.getRawValue());
55358 * @cfg {Boolean} grow @hide
55361 * @cfg {Number} growMin @hide
55364 * @cfg {Number} growMax @hide
55371 Ext.reg('datefield', Ext.form.DateField);/**
\r
55372 * @class Ext.form.DisplayField
\r
55373 * @extends Ext.form.Field
\r
55374 * A display-only text field which is not validated and not submitted.
\r
55376 * Creates a new DisplayField.
\r
55377 * @param {Object} config Configuration options
\r
55378 * @xtype displayfield
\r
55380 Ext.form.DisplayField = Ext.extend(Ext.form.Field, {
\r
55381 validationEvent : false,
\r
55382 validateOnBlur : false,
\r
55383 defaultAutoCreate : {tag: "div"},
\r
55385 * @cfg {String} fieldClass The default CSS class for the field (defaults to <tt>"x-form-display-field"</tt>)
\r
55387 fieldClass : "x-form-display-field",
\r
55389 * @cfg {Boolean} htmlEncode <tt>false</tt> to skip HTML-encoding the text when rendering it (defaults to
\r
55390 * <tt>false</tt>). This might be useful if you want to include tags in the field's innerHTML rather than
\r
55391 * rendering them as string literals per the default logic.
\r
55393 htmlEncode: false,
\r
55396 initEvents : Ext.emptyFn,
\r
55398 isValid : function(){
\r
55402 validate : function(){
\r
55406 getRawValue : function(){
\r
55407 var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, '');
\r
55408 if(v === this.emptyText){
\r
55411 if(this.htmlEncode){
\r
55412 v = Ext.util.Format.htmlDecode(v);
\r
55417 getValue : function(){
\r
55418 return this.getRawValue();
\r
55421 getName: function() {
\r
55422 return this.name;
\r
55425 setRawValue : function(v){
\r
55426 if(this.htmlEncode){
\r
55427 v = Ext.util.Format.htmlEncode(v);
\r
55429 return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
\r
55432 setValue : function(v){
\r
55433 this.setRawValue(v);
\r
55437 * @cfg {String} inputType
\r
55441 * @cfg {Boolean} disabled
\r
55445 * @cfg {Boolean} readOnly
\r
55449 * @cfg {Boolean} validateOnBlur
\r
55453 * @cfg {Number} validationDelay
\r
55457 * @cfg {String/Boolean} validationEvent
\r
55462 Ext.reg('displayfield', Ext.form.DisplayField);
\r
55464 * @class Ext.form.ComboBox
\r
55465 * @extends Ext.form.TriggerField
\r
55466 * <p>A combobox control with support for autocomplete, remote-loading, paging and many other features.</p>
\r
55467 * <p>A ComboBox works in a similar manner to a traditional HTML <select> field. The difference is
\r
55468 * that to submit the {@link #valueField}, you must specify a {@link #hiddenName} to create a hidden input
\r
55469 * field to hold the value of the valueField. The <i>{@link #displayField}</i> is shown in the text field
\r
55470 * which is named according to the {@link #name}.</p>
\r
55471 * <p><b><u>Events</u></b></p>
\r
55472 * <p>To do something when something in ComboBox is selected, configure the select event:<pre><code>
\r
55473 var cb = new Ext.form.ComboBox({
\r
55474 // all of your config options
\r
55476 scope: yourScope,
\r
55477 'select': yourFunction
\r
55481 // Alternatively, you can assign events after the object is created:
\r
55482 var cb = new Ext.form.ComboBox(yourOptions);
\r
55483 cb.on('select', yourFunction, yourScope);
\r
55484 * </code></pre></p>
\r
55486 * <p><b><u>ComboBox in Grid</u></b></p>
\r
55487 * <p>If using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a {@link Ext.grid.Column#renderer renderer}
\r
55488 * will be needed to show the displayField when the editor is not active. Set up the renderer manually, or implement
\r
55489 * a reusable render, for example:<pre><code>
\r
55490 // create reusable renderer
\r
55491 Ext.util.Format.comboRenderer = function(combo){
\r
55492 return function(value){
\r
55493 var record = combo.findRecord(combo.{@link #valueField}, value);
\r
55494 return record ? record.get(combo.{@link #displayField}) : combo.{@link #valueNotFoundText};
\r
55498 // create the combo instance
\r
55499 var combo = new Ext.form.ComboBox({
\r
55500 {@link #typeAhead}: true,
\r
55501 {@link #triggerAction}: 'all',
\r
55502 {@link #lazyRender}:true,
\r
55503 {@link #mode}: 'local',
\r
55504 {@link #store}: new Ext.data.ArrayStore({
\r
55510 data: [[1, 'item1'], [2, 'item2']]
\r
55512 {@link #valueField}: 'myId',
\r
55513 {@link #displayField}: 'displayText'
\r
55516 // snippet of column model used within grid
\r
55517 var cm = new Ext.grid.ColumnModel([{
\r
55520 header: "Some Header",
\r
55521 dataIndex: 'whatever',
\r
55523 editor: combo, // specify reference to combo instance
\r
55524 renderer: Ext.util.Format.comboRenderer(combo) // pass combo instance to reusable renderer
\r
55528 * </code></pre></p>
\r
55530 * <p><b><u>Filtering</u></b></p>
\r
55531 * <p>A ComboBox {@link #doQuery uses filtering itself}, for information about filtering the ComboBox
\r
55532 * store manually see <tt>{@link #lastQuery}</tt>.</p>
\r
55534 * Create a new ComboBox.
\r
55535 * @param {Object} config Configuration options
\r
55538 Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
\r
55540 * @cfg {Mixed} transform The id, DOM node or element of an existing HTML SELECT to convert to a ComboBox.
\r
55541 * Note that if you specify this and the combo is going to be in an {@link Ext.form.BasicForm} or
\r
55542 * {@link Ext.form.FormPanel}, you must also set <tt>{@link #lazyRender} = true</tt>.
\r
55545 * @cfg {Boolean} lazyRender <tt>true</tt> to prevent the ComboBox from rendering until requested
\r
55546 * (should always be used when rendering into an {@link Ext.Editor} (e.g. {@link Ext.grid.EditorGridPanel Grids}),
\r
55547 * defaults to <tt>false</tt>).
\r
55550 * @cfg {String/Object} autoCreate <p>A {@link Ext.DomHelper DomHelper} element spec, or <tt>true</tt> for a default
\r
55551 * element spec. Used to create the {@link Ext.Component#getEl Element} which will encapsulate this Component.
\r
55552 * See <tt>{@link Ext.Component#autoEl autoEl}</tt> for details. Defaults to:</p>
\r
55553 * <pre><code>{tag: "input", type: "text", size: "24", autocomplete: "off"}</code></pre>
\r
55556 * @cfg {Ext.data.Store/Array} store The data source to which this combo is bound (defaults to <tt>undefined</tt>).
\r
55557 * Acceptable values for this property are:
\r
55558 * <div class="mdetail-params"><ul>
\r
55559 * <li><b>any {@link Ext.data.Store Store} subclass</b></li>
\r
55560 * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally,
\r
55561 * automatically generating {@link Ext.data.Field#name field names} to work with all data components.
\r
55562 * <div class="mdetail-params"><ul>
\r
55563 * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">
\r
55564 * A 1-dimensional array will automatically be expanded (each array item will be used for both the combo
\r
55565 * {@link #valueField} and {@link #displayField})</div></li>
\r
55566 * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">
\r
55567 * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo
\r
55568 * {@link #valueField}, while the value at index 1 is assumed to be the combo {@link #displayField}.
\r
55569 * </div></li></ul></div></li></ul></div>
\r
55570 * <p>See also <tt>{@link #mode}</tt>.</p>
\r
55573 * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
\r
55574 * the dropdown list (defaults to undefined, with no header element)
\r
55578 defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
\r
55580 * @cfg {Number} listWidth The width (used as a parameter to {@link Ext.Element#setWidth}) of the dropdown
\r
55581 * list (defaults to the width of the ComboBox field). See also <tt>{@link #minListWidth}
\r
55584 * @cfg {String} displayField The underlying {@link Ext.data.Field#name data field name} to bind to this
\r
55585 * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'field1'</tt> if
\r
55586 * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on
\r
55587 * the store configuration}).
\r
55588 * <p>See also <tt>{@link #valueField}</tt>.</p>
\r
55589 * <p><b>Note</b>: if using a ComboBox in an {@link Ext.grid.EditorGridPanel Editor Grid} a
\r
55590 * {@link Ext.grid.Column#renderer renderer} will be needed to show the displayField when the editor is not
\r
55594 * @cfg {String} valueField The underlying {@link Ext.data.Field#name data value name} to bind to this
\r
55595 * ComboBox (defaults to undefined if <tt>{@link #mode} = 'remote'</tt> or <tt>'field2'</tt> if
\r
55596 * {@link #transform transforming a select} or if the {@link #store field name is autogenerated based on
\r
55597 * the store configuration}).
\r
55598 * <p><b>Note</b>: use of a <tt>valueField</tt> requires the user to make a selection in order for a value to be
\r
55599 * mapped. See also <tt>{@link #hiddenName}</tt>, <tt>{@link #hiddenValue}</tt>, and <tt>{@link #displayField}</tt>.</p>
\r
55602 * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
\r
55603 * field's data value (defaults to the underlying DOM element's name). Required for the combo's value to automatically
\r
55604 * post during a form submission. See also {@link #valueField}.
\r
55605 * <p><b>Note</b>: the hidden field's id will also default to this name if {@link #hiddenId} is not specified.
\r
55606 * The ComboBox {@link Ext.Component#id id} and the <tt>{@link #hiddenId}</tt> <b>should be different</b>, since
\r
55607 * no two DOM nodes should share the same id. So, if the ComboBox <tt>{@link Ext.form.Field#name name}</tt> and
\r
55608 * <tt>hiddenName</tt> are the same, you should specify a unique <tt>{@link #hiddenId}</tt>.</p>
\r
55611 * @cfg {String} hiddenId If <tt>{@link #hiddenName}</tt> is specified, <tt>hiddenId</tt> can also be provided
\r
55612 * to give the hidden field a unique id (defaults to the <tt>{@link #hiddenName}</tt>). The <tt>hiddenId</tt>
\r
55613 * and combo {@link Ext.Component#id id} should be different, since no two DOM
\r
55614 * nodes should share the same id.
\r
55617 * @cfg {String} hiddenValue Sets the initial value of the hidden field if {@link #hiddenName} is
\r
55618 * specified to contain the selected {@link #valueField}, from the Store. Defaults to the configured
\r
55619 * <tt>{@link Ext.form.Field#value value}</tt>.
\r
55622 * @cfg {String} listClass The CSS class to add to the predefined <tt>'x-combo-list'</tt> class
\r
55623 * applied the dropdown list element (defaults to '').
\r
55627 * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list
\r
55628 * (defaults to <tt>'x-combo-selected'</tt>)
\r
55630 selectedClass : 'x-combo-selected',
\r
55632 * @cfg {String} listEmptyText The empty text to display in the data view if no items are found.
\r
55633 * (defaults to '')
\r
55635 listEmptyText: '',
\r
55637 * @cfg {String} triggerClass An additional CSS class used to style the trigger button. The trigger will always
\r
55638 * get the class <tt>'x-form-trigger'</tt> and <tt>triggerClass</tt> will be <b>appended</b> if specified
\r
55639 * (defaults to <tt>'x-form-arrow-trigger'</tt> which displays a downward arrow icon).
\r
55641 triggerClass : 'x-form-arrow-trigger',
\r
55643 * @cfg {Boolean/String} shadow <tt>true</tt> or <tt>"sides"</tt> for the default effect, <tt>"frame"</tt> for
\r
55644 * 4-way shadow, and <tt>"drop"</tt> for bottom-right
\r
55646 shadow : 'sides',
\r
55648 * @cfg {String} listAlign A valid anchor position value. See <tt>{@link Ext.Element#alignTo}</tt> for details
\r
55649 * on supported anchor positions (defaults to <tt>'tl-bl?'</tt>)
\r
55651 listAlign : 'tl-bl?',
\r
55653 * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown
\r
55654 * (defaults to <tt>300</tt>)
\r
55658 * @cfg {Number} minHeight The minimum height in pixels of the dropdown list when the list is constrained by its
\r
55659 * distance to the viewport edges (defaults to <tt>90</tt>)
\r
55663 * @cfg {String} triggerAction The action to execute when the trigger is clicked.
\r
55664 * <div class="mdetail-params"><ul>
\r
55665 * <li><b><tt>'query'</tt></b> : <b>Default</b>
\r
55666 * <p class="sub-desc">{@link #doQuery run the query} using the {@link Ext.form.Field#getRawValue raw value}.</p></li>
\r
55667 * <li><b><tt>'all'</tt></b> :
\r
55668 * <p class="sub-desc">{@link #doQuery run the query} specified by the <tt>{@link #allQuery}</tt> config option</p></li>
\r
55670 * <p>See also <code>{@link #queryParam}</code>.</p>
\r
55672 triggerAction : 'query',
\r
55674 * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and
\r
55675 * {@link #typeAhead} activate (defaults to <tt>4</tt> if <tt>{@link #mode} = 'remote'</tt> or <tt>0</tt> if
\r
55676 * <tt>{@link #mode} = 'local'</tt>, does not apply if
\r
55677 * <tt>{@link Ext.form.TriggerField#editable editable} = false</tt>).
\r
55681 * @cfg {Boolean} typeAhead <tt>true</tt> to populate and autoselect the remainder of the text being
\r
55682 * typed after a configurable delay ({@link #typeAheadDelay}) if it matches a known value (defaults
\r
55683 * to <tt>false</tt>)
\r
55685 typeAhead : false,
\r
55687 * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and
\r
55688 * sending the query to filter the dropdown list (defaults to <tt>500</tt> if <tt>{@link #mode} = 'remote'</tt>
\r
55689 * or <tt>10</tt> if <tt>{@link #mode} = 'local'</tt>)
\r
55691 queryDelay : 500,
\r
55693 * @cfg {Number} pageSize If greater than <tt>0</tt>, a {@link Ext.PagingToolbar} is displayed in the
\r
55694 * footer of the dropdown list and the {@link #doQuery filter queries} will execute with page start and
\r
55695 * {@link Ext.PagingToolbar#pageSize limit} parameters. Only applies when <tt>{@link #mode} = 'remote'</tt>
\r
55696 * (defaults to <tt>0</tt>).
\r
55700 * @cfg {Boolean} selectOnFocus <tt>true</tt> to select any existing text in the field immediately on focus.
\r
55701 * Only applies when <tt>{@link Ext.form.TriggerField#editable editable} = true</tt> (defaults to
\r
55702 * <tt>false</tt>).
\r
55704 selectOnFocus : false,
\r
55706 * @cfg {String} queryParam Name of the query ({@link Ext.data.Store#baseParam baseParam} name for the store)
\r
55707 * as it will be passed on the querystring (defaults to <tt>'query'</tt>)
\r
55709 queryParam : 'query',
\r
55711 * @cfg {String} loadingText The text to display in the dropdown list while data is loading. Only applies
\r
55712 * when <tt>{@link #mode} = 'remote'</tt> (defaults to <tt>'Loading...'</tt>)
\r
55714 loadingText : 'Loading...',
\r
55716 * @cfg {Boolean} resizable <tt>true</tt> to add a resize handle to the bottom of the dropdown list
\r
55717 * (creates an {@link Ext.Resizable} with 'se' {@link Ext.Resizable#pinned pinned} handles).
\r
55718 * Defaults to <tt>false</tt>.
\r
55720 resizable : false,
\r
55722 * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if
\r
55723 * <tt>{@link #resizable} = true</tt> (defaults to <tt>8</tt>)
\r
55725 handleHeight : 8,
\r
55727 * @cfg {String} allQuery The text query to send to the server to return all records for the list
\r
55728 * with no filtering (defaults to '')
\r
55732 * @cfg {String} mode Acceptable values are:
\r
55733 * <div class="mdetail-params"><ul>
\r
55734 * <li><b><tt>'remote'</tt></b> : <b>Default</b>
\r
55735 * <p class="sub-desc">Automatically loads the <tt>{@link #store}</tt> the <b>first</b> time the trigger
\r
55736 * is clicked. If you do not want the store to be automatically loaded the first time the trigger is
\r
55737 * clicked, set to <tt>'local'</tt> and manually load the store. To force a requery of the store
\r
55738 * <b>every</b> time the trigger is clicked see <tt>{@link #lastQuery}</tt>.</p></li>
\r
55739 * <li><b><tt>'local'</tt></b> :
\r
55740 * <p class="sub-desc">ComboBox loads local data</p>
\r
55742 var combo = new Ext.form.ComboBox({
\r
55743 renderTo: document.body,
\r
55745 store: new Ext.data.ArrayStore({
\r
55748 'myId', // numeric value is the key
\r
55751 data: [[1, 'item1'], [2, 'item2']] // data is local
\r
55753 valueField: 'myId',
\r
55754 displayField: 'displayText',
\r
55755 triggerAction: 'all'
\r
55757 * </code></pre></li>
\r
55762 * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to <tt>70</tt>, will
\r
55763 * be ignored if <tt>{@link #listWidth}</tt> has a higher value)
\r
55765 minListWidth : 70,
\r
55767 * @cfg {Boolean} forceSelection <tt>true</tt> to restrict the selected value to one of the values in the list,
\r
55768 * <tt>false</tt> to allow the user to set arbitrary text into the field (defaults to <tt>false</tt>)
\r
55770 forceSelection : false,
\r
55772 * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
\r
55773 * if <tt>{@link #typeAhead} = true</tt> (defaults to <tt>250</tt>)
\r
55775 typeAheadDelay : 250,
\r
55777 * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
\r
55778 * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined). If this
\r
55779 * default text is used, it means there is no value set and no validation will occur on this field.
\r
55783 * @cfg {Boolean} lazyInit <tt>true</tt> to not initialize the list for this combo until the field is focused
\r
55784 * (defaults to <tt>true</tt>)
\r
55789 * The value of the match string used to filter the store. Delete this property to force a requery.
\r
55792 var combo = new Ext.form.ComboBox({
\r
55797 // delete the previous query in the beforequery event or set
\r
55798 // combo.lastQuery = null (this will reload the store the next time it expands)
\r
55799 beforequery: function(qe){
\r
55800 delete qe.combo.lastQuery;
\r
55805 * To make sure the filter in the store is not cleared the first time the ComboBox trigger is used
\r
55806 * configure the combo with <tt>lastQuery=''</tt>. Example use:
\r
55808 var combo = new Ext.form.ComboBox({
\r
55811 triggerAction: 'all',
\r
55815 * @property lastQuery
\r
55820 initComponent : function(){
\r
55821 Ext.form.ComboBox.superclass.initComponent.call(this);
\r
55825 * Fires when the dropdown list is expanded
\r
55826 * @param {Ext.form.ComboBox} combo This combo box
\r
55830 * @event collapse
\r
55831 * Fires when the dropdown list is collapsed
\r
55832 * @param {Ext.form.ComboBox} combo This combo box
\r
55836 * @event beforeselect
\r
55837 * Fires before a list item is selected. Return false to cancel the selection.
\r
55838 * @param {Ext.form.ComboBox} combo This combo box
\r
55839 * @param {Ext.data.Record} record The data record returned from the underlying store
\r
55840 * @param {Number} index The index of the selected item in the dropdown list
\r
55845 * Fires when a list item is selected
\r
55846 * @param {Ext.form.ComboBox} combo This combo box
\r
55847 * @param {Ext.data.Record} record The data record returned from the underlying store
\r
55848 * @param {Number} index The index of the selected item in the dropdown list
\r
55852 * @event beforequery
\r
55853 * Fires before all queries are processed. Return false to cancel the query or set the queryEvent's
\r
55854 * cancel property to true.
\r
55855 * @param {Object} queryEvent An object that has these properties:<ul>
\r
55856 * <li><code>combo</code> : Ext.form.ComboBox <div class="sub-desc">This combo box</div></li>
\r
55857 * <li><code>query</code> : String <div class="sub-desc">The query</div></li>
\r
55858 * <li><code>forceAll</code> : Boolean <div class="sub-desc">True to force "all" query</div></li>
\r
55859 * <li><code>cancel</code> : Boolean <div class="sub-desc">Set to true to cancel the query</div></li>
\r
55864 if(this.transform){
\r
55865 var s = Ext.getDom(this.transform);
\r
55866 if(!this.hiddenName){
\r
55867 this.hiddenName = s.name;
\r
55870 this.mode = 'local';
\r
55871 var d = [], opts = s.options;
\r
55872 for(var i = 0, len = opts.length;i < len; i++){
\r
55874 value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text;
\r
55875 if(o.selected && Ext.isEmpty(this.value, true)) {
\r
55876 this.value = value;
\r
55878 d.push([value, o.text]);
\r
55880 this.store = new Ext.data.ArrayStore({
\r
55882 fields: ['value', 'text'],
\r
55884 autoDestroy: true
\r
55886 this.valueField = 'value';
\r
55887 this.displayField = 'text';
\r
55889 s.name = Ext.id(); // wipe out the name in case somewhere else they have a reference
\r
55890 if(!this.lazyRender){
\r
55891 this.target = true;
\r
55892 this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
\r
55893 this.render(this.el.parentNode, s);
\r
55894 Ext.removeNode(s); // remove it
\r
55896 Ext.removeNode(s); // remove it
\r
55899 //auto-configure store from local array data
\r
55900 else if(this.store){
\r
55901 this.store = Ext.StoreMgr.lookup(this.store);
\r
55902 if(this.store.autoCreated){
\r
55903 this.displayField = this.valueField = 'field1';
\r
55904 if(!this.store.expandData){
\r
55905 this.displayField = 'field2';
\r
55907 this.mode = 'local';
\r
55911 this.selectedIndex = -1;
\r
55912 if(this.mode == 'local'){
\r
55913 if(!Ext.isDefined(this.initialConfig.queryDelay)){
\r
55914 this.queryDelay = 10;
\r
55916 if(!Ext.isDefined(this.initialConfig.minChars)){
\r
55917 this.minChars = 0;
\r
55923 onRender : function(ct, position){
\r
55924 Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
\r
55925 if(this.hiddenName){
\r
55926 this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
\r
55927 id: (this.hiddenId||this.hiddenName)}, 'before', true);
\r
55929 // prevent input submission
\r
55930 this.el.dom.removeAttribute('name');
\r
55933 this.el.dom.setAttribute('autocomplete', 'off');
\r
55936 if(!this.lazyInit){
\r
55939 this.on('focus', this.initList, this, {single: true});
\r
55944 initValue : function(){
\r
55945 Ext.form.ComboBox.superclass.initValue.call(this);
\r
55946 if(this.hiddenField){
\r
55947 this.hiddenField.value =
\r
55948 Ext.isDefined(this.hiddenValue) ? this.hiddenValue :
\r
55949 Ext.isDefined(this.value) ? this.value : '';
\r
55954 initList : function(){
\r
55956 var cls = 'x-combo-list';
\r
55958 this.list = new Ext.Layer({
\r
55959 parentEl: this.getListParent(),
\r
55960 shadow: this.shadow,
\r
55961 cls: [cls, this.listClass].join(' '),
\r
55965 var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
\r
55966 this.list.setSize(lw, 0);
\r
55967 this.list.swallowEvent('mousewheel');
\r
55968 this.assetHeight = 0;
\r
55969 if(this.syncFont !== false){
\r
55970 this.list.setStyle('font-size', this.el.getStyle('font-size'));
\r
55973 this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
\r
55974 this.assetHeight += this.header.getHeight();
\r
55977 this.innerList = this.list.createChild({cls:cls+'-inner'});
\r
55978 this.mon(this.innerList, 'mouseover', this.onViewOver, this);
\r
55979 this.mon(this.innerList, 'mousemove', this.onViewMove, this);
\r
55980 this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
\r
55982 if(this.pageSize){
\r
55983 this.footer = this.list.createChild({cls:cls+'-ft'});
\r
55984 this.pageTb = new Ext.PagingToolbar({
\r
55985 store: this.store,
\r
55986 pageSize: this.pageSize,
\r
55987 renderTo:this.footer
\r
55989 this.assetHeight += this.footer.getHeight();
\r
55994 * @cfg {String/Ext.XTemplate} tpl <p>The template string, or {@link Ext.XTemplate} instance to
\r
55995 * use to display each item in the dropdown list. The dropdown list is displayed in a
\r
55996 * DataView. See {@link #view}.</p>
\r
55997 * <p>The default template string is:</p><pre><code>
\r
55998 '<tpl for="."><div class="x-combo-list-item">{' + this.displayField + '}</div></tpl>'
\r
56000 * <p>Override the default value to create custom UI layouts for items in the list.
\r
56001 * For example:</p><pre><code>
\r
56002 '<tpl for="."><div ext:qtip="{state}. {nick}" class="x-combo-list-item">{state}</div></tpl>'
\r
56004 * <p>The template <b>must</b> contain one or more substitution parameters using field
\r
56005 * names from the Combo's</b> {@link #store Store}. In the example above an
\r
56006 * <pre>ext:qtip</pre> attribute is added to display other fields from the Store.</p>
\r
56007 * <p>To preserve the default visual look of list items, add the CSS class name
\r
56008 * <pre>x-combo-list-item</pre> to the template's container element.</p>
\r
56009 * <p>Also see {@link #itemSelector} for additional details.</p>
\r
56011 this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
\r
56013 * @cfg {String} itemSelector
\r
56014 * <p>A simple CSS selector (e.g. div.some-class or span:first-child) that will be
\r
56015 * used to determine what nodes the {@link #view Ext.DataView} which handles the dropdown
\r
56016 * display will be working with.</p>
\r
56017 * <p><b>Note</b>: this setting is <b>required</b> if a custom XTemplate has been
\r
56018 * specified in {@link #tpl} which assigns a class other than <pre>'x-combo-list-item'</pre>
\r
56019 * to dropdown list items</b>
\r
56024 * The {@link Ext.DataView DataView} used to display the ComboBox's options.
\r
56025 * @type Ext.DataView
\r
56027 this.view = new Ext.DataView({
\r
56028 applyTo: this.innerList,
\r
56030 singleSelect: true,
\r
56031 selectedClass: this.selectedClass,
\r
56032 itemSelector: this.itemSelector || '.' + cls + '-item',
\r
56033 emptyText: this.listEmptyText
\r
56036 this.mon(this.view, 'click', this.onViewClick, this);
\r
56038 this.bindStore(this.store, true);
\r
56040 if(this.resizable){
\r
56041 this.resizer = new Ext.Resizable(this.list, {
\r
56042 pinned:true, handles:'se'
\r
56044 this.mon(this.resizer, 'resize', function(r, w, h){
\r
56045 this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
\r
56046 this.listWidth = w;
\r
56047 this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
\r
56048 this.restrictHeight();
\r
56051 this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
\r
56057 * <p>Returns the element used to house this ComboBox's pop-up list. Defaults to the document body.</p>
\r
56058 * A custom implementation may be provided as a configuration option if the floating list needs to be rendered
\r
56059 * to a different Element. An example might be rendering the list inside a Menu so that clicking
\r
56060 * the list does not hide the Menu:<pre><code>
\r
56061 var store = new Ext.data.ArrayStore({
\r
56062 autoDestroy: true,
\r
56063 fields: ['initials', 'fullname'],
\r
56065 ['FF', 'Fred Flintstone'],
\r
56066 ['BR', 'Barney Rubble']
\r
56070 var combo = new Ext.form.ComboBox({
\r
56072 displayField: 'fullname',
\r
56073 emptyText: 'Select a name...',
\r
56074 forceSelection: true,
\r
56075 getListParent: function() {
\r
56076 return this.el.up('.x-menu');
\r
56078 iconCls: 'no-icon', //use iconCls if placing within menu to shift to right side of menu
\r
56080 selectOnFocus: true,
\r
56081 triggerAction: 'all',
\r
56086 var menu = new Ext.menu.Menu({
\r
56089 combo // A Field in a Menu
\r
56094 getListParent : function() {
\r
56095 return document.body;
\r
56099 * Returns the store associated with this combo.
\r
56100 * @return {Ext.data.Store} The store
\r
56102 getStore : function(){
\r
56103 return this.store;
\r
56107 bindStore : function(store, initial){
\r
56108 if(this.store && !initial){
\r
56109 if(this.store !== store && this.store.autoDestroy){
\r
56110 this.store.destroy();
\r
56112 this.store.un('beforeload', this.onBeforeLoad, this);
\r
56113 this.store.un('load', this.onLoad, this);
\r
56114 this.store.un('exception', this.collapse, this);
\r
56117 this.store = null;
\r
56119 this.view.bindStore(null);
\r
56122 this.pageTb.bindStore(null);
\r
56128 this.lastQuery = null;
\r
56129 if(this.pageTb) {
\r
56130 this.pageTb.bindStore(store);
\r
56134 this.store = Ext.StoreMgr.lookup(store);
\r
56137 beforeload: this.onBeforeLoad,
\r
56138 load: this.onLoad,
\r
56139 exception: this.collapse
\r
56143 this.view.bindStore(store);
\r
56149 initEvents : function(){
\r
56150 Ext.form.ComboBox.superclass.initEvents.call(this);
\r
56152 this.keyNav = new Ext.KeyNav(this.el, {
\r
56153 "up" : function(e){
\r
56154 this.inKeyMode = true;
\r
56155 this.selectPrev();
\r
56158 "down" : function(e){
\r
56159 if(!this.isExpanded()){
\r
56160 this.onTriggerClick();
\r
56162 this.inKeyMode = true;
\r
56163 this.selectNext();
\r
56167 "enter" : function(e){
\r
56168 this.onViewClick();
\r
56171 "esc" : function(e){
\r
56175 "tab" : function(e){
\r
56176 this.onViewClick(false);
\r
56182 doRelay : function(e, h, hname){
\r
56183 if(hname == 'down' || this.scope.isExpanded()){
\r
56184 // this MUST be called before ComboBox#fireKey()
\r
56185 var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
\r
56186 if(!Ext.isIE && Ext.EventManager.useKeydown){
\r
56187 // call Combo#fireKey() for browsers which use keydown event (except IE)
\r
56188 this.scope.fireKey(e);
\r
56195 forceKeyDown : true,
\r
56196 defaultEventAction: 'stopEvent'
\r
56198 this.queryDelay = Math.max(this.queryDelay || 10,
\r
56199 this.mode == 'local' ? 10 : 250);
\r
56200 this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
\r
56201 if(this.typeAhead){
\r
56202 this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
\r
56204 if(this.editable !== false && !this.enableKeyEvents){
\r
56205 this.mon(this.el, 'keyup', this.onKeyUp, this);
\r
56210 onDestroy : function(){
\r
56211 if (this.dqTask){
\r
56212 this.dqTask.cancel();
\r
56213 this.dqTask = null;
\r
56215 this.bindStore(null);
\r
56222 Ext.form.ComboBox.superclass.onDestroy.call(this);
\r
56226 fireKey : function(e){
\r
56227 if (!this.isExpanded()) {
\r
56228 Ext.form.ComboBox.superclass.fireKey.call(this, e);
\r
56233 onResize : function(w, h){
\r
56234 Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
\r
56235 if(this.isVisible() && this.list){
\r
56236 this.doResize(w);
\r
56238 this.bufferSize = w;
\r
56242 doResize: function(w){
\r
56243 if(!Ext.isDefined(this.listWidth)){
\r
56244 var lw = Math.max(w, this.minListWidth);
\r
56245 this.list.setWidth(lw);
\r
56246 this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
\r
56251 onEnable : function(){
\r
56252 Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
\r
56253 if(this.hiddenField){
\r
56254 this.hiddenField.disabled = false;
\r
56259 onDisable : function(){
\r
56260 Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
\r
56261 if(this.hiddenField){
\r
56262 this.hiddenField.disabled = true;
\r
56267 onBeforeLoad : function(){
\r
56268 if(!this.hasFocus){
\r
56271 this.innerList.update(this.loadingText ?
\r
56272 '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
\r
56273 this.restrictHeight();
\r
56274 this.selectedIndex = -1;
\r
56278 onLoad : function(){
\r
56279 if(!this.hasFocus){
\r
56282 if(this.store.getCount() > 0 || this.listEmptyText){
\r
56284 this.restrictHeight();
\r
56285 if(this.lastQuery == this.allQuery){
\r
56286 if(this.editable){
\r
56287 this.el.dom.select();
\r
56289 if(!this.selectByValue(this.value, true)){
\r
56290 this.select(0, true);
\r
56293 this.selectNext();
\r
56294 if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
\r
56295 this.taTask.delay(this.typeAheadDelay);
\r
56299 this.onEmptyResults();
\r
56301 //this.el.focus();
\r
56305 onTypeAhead : function(){
\r
56306 if(this.store.getCount() > 0){
\r
56307 var r = this.store.getAt(0);
\r
56308 var newValue = r.data[this.displayField];
\r
56309 var len = newValue.length;
\r
56310 var selStart = this.getRawValue().length;
\r
56311 if(selStart != len){
\r
56312 this.setRawValue(newValue);
\r
56313 this.selectText(selStart, newValue.length);
\r
56319 onSelect : function(record, index){
\r
56320 if(this.fireEvent('beforeselect', this, record, index) !== false){
\r
56321 this.setValue(record.data[this.valueField || this.displayField]);
\r
56323 this.fireEvent('select', this, record, index);
\r
56328 getName: function(){
\r
56329 var hf = this.hiddenField;
\r
56330 return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this);
\r
56334 * Returns the currently selected field value or empty string if no value is set.
\r
56335 * @return {String} value The selected value
\r
56337 getValue : function(){
\r
56338 if(this.valueField){
\r
56339 return Ext.isDefined(this.value) ? this.value : '';
\r
56341 return Ext.form.ComboBox.superclass.getValue.call(this);
\r
56346 * Clears any text/value currently set in the field
\r
56348 clearValue : function(){
\r
56349 if(this.hiddenField){
\r
56350 this.hiddenField.value = '';
\r
56352 this.setRawValue('');
\r
56353 this.lastSelectionText = '';
\r
56354 this.applyEmptyText();
\r
56359 * Sets the specified value into the field. If the value finds a match, the corresponding record text
\r
56360 * will be displayed in the field. If the value does not match the data value of an existing item,
\r
56361 * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
\r
56362 * Otherwise the field will be blank (although the value will still be set).
\r
56363 * @param {String} value The value to match
\r
56364 * @return {Ext.form.Field} this
\r
56366 setValue : function(v){
\r
56368 if(this.valueField){
\r
56369 var r = this.findRecord(this.valueField, v);
\r
56371 text = r.data[this.displayField];
\r
56372 }else if(Ext.isDefined(this.valueNotFoundText)){
\r
56373 text = this.valueNotFoundText;
\r
56376 this.lastSelectionText = text;
\r
56377 if(this.hiddenField){
\r
56378 this.hiddenField.value = v;
\r
56380 Ext.form.ComboBox.superclass.setValue.call(this, text);
\r
56386 findRecord : function(prop, value){
\r
56388 if(this.store.getCount() > 0){
\r
56389 this.store.each(function(r){
\r
56390 if(r.data[prop] == value){
\r
56400 onViewMove : function(e, t){
\r
56401 this.inKeyMode = false;
\r
56405 onViewOver : function(e, t){
\r
56406 if(this.inKeyMode){ // prevent key nav and mouse over conflicts
\r
56409 var item = this.view.findItemFromChild(t);
\r
56411 var index = this.view.indexOf(item);
\r
56412 this.select(index, false);
\r
56417 onViewClick : function(doFocus){
\r
56418 var index = this.view.getSelectedIndexes()[0],
\r
56420 r = s.getAt(index);
\r
56422 this.onSelect(r, index);
\r
56423 }else if(s.getCount() === 0){
\r
56424 this.onEmptyResults();
\r
56426 if(doFocus !== false){
\r
56432 restrictHeight : function(){
\r
56433 this.innerList.dom.style.height = '';
\r
56434 var inner = this.innerList.dom,
\r
56435 pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight,
\r
56436 h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
\r
56437 ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
\r
56438 hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
\r
56439 space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
\r
56441 h = Math.min(h, space, this.maxHeight);
\r
56443 this.innerList.setHeight(h);
\r
56444 this.list.beginUpdate();
\r
56445 this.list.setHeight(h+pad);
\r
56446 this.list.alignTo(this.wrap, this.listAlign);
\r
56447 this.list.endUpdate();
\r
56451 onEmptyResults : function(){
\r
56456 * Returns true if the dropdown list is expanded, else false.
\r
56458 isExpanded : function(){
\r
56459 return this.list && this.list.isVisible();
\r
56463 * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
\r
56464 * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
\r
56465 * @param {String} value The data value of the item to select
\r
56466 * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
\r
56467 * selected item if it is not currently in view (defaults to true)
\r
56468 * @return {Boolean} True if the value matched an item in the list, else false
\r
56470 selectByValue : function(v, scrollIntoView){
\r
56471 if(!Ext.isEmpty(v, true)){
\r
56472 var r = this.findRecord(this.valueField || this.displayField, v);
\r
56474 this.select(this.store.indexOf(r), scrollIntoView);
\r
56482 * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
\r
56483 * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
\r
56484 * @param {Number} index The zero-based index of the list item to select
\r
56485 * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
\r
56486 * selected item if it is not currently in view (defaults to true)
\r
56488 select : function(index, scrollIntoView){
\r
56489 this.selectedIndex = index;
\r
56490 this.view.select(index);
\r
56491 if(scrollIntoView !== false){
\r
56492 var el = this.view.getNode(index);
\r
56494 this.innerList.scrollChildIntoView(el, false);
\r
56500 selectNext : function(){
\r
56501 var ct = this.store.getCount();
\r
56503 if(this.selectedIndex == -1){
\r
56505 }else if(this.selectedIndex < ct-1){
\r
56506 this.select(this.selectedIndex+1);
\r
56512 selectPrev : function(){
\r
56513 var ct = this.store.getCount();
\r
56515 if(this.selectedIndex == -1){
\r
56517 }else if(this.selectedIndex !== 0){
\r
56518 this.select(this.selectedIndex-1);
\r
56524 onKeyUp : function(e){
\r
56525 var k = e.getKey();
\r
56526 if(this.editable !== false && (k == e.BACKSPACE || !e.isSpecialKey())){
\r
56527 this.lastKey = k;
\r
56528 this.dqTask.delay(this.queryDelay);
\r
56530 Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
\r
56534 validateBlur : function(){
\r
56535 return !this.list || !this.list.isVisible();
\r
56539 initQuery : function(){
\r
56540 this.doQuery(this.getRawValue());
\r
56544 beforeBlur : function(){
\r
56545 var val = this.getRawValue(),
\r
56546 rec = this.findRecord(this.displayField, val);
\r
56547 if(!rec && this.forceSelection){
\r
56548 if(val.length > 0 && val != this.emptyText){
\r
56549 this.el.dom.value = Ext.isDefined(this.lastSelectionText) ? this.lastSelectionText : '';
\r
56550 this.applyEmptyText();
\r
56552 this.clearValue();
\r
56556 val = rec.get(this.valueField || this.displayField);
\r
56558 this.setValue(val);
\r
56563 * Execute a query to filter the dropdown list. Fires the {@link #beforequery} event prior to performing the
\r
56564 * query allowing the query action to be canceled if needed.
\r
56565 * @param {String} query The SQL query to execute
\r
56566 * @param {Boolean} forceAll <tt>true</tt> to force the query to execute even if there are currently fewer
\r
56567 * characters in the field than the minimum specified by the <tt>{@link #minChars}</tt> config option. It
\r
56568 * also clears any filter previously saved in the current store (defaults to <tt>false</tt>)
\r
56570 doQuery : function(q, forceAll){
\r
56571 q = Ext.isEmpty(q) ? '' : q;
\r
56574 forceAll: forceAll,
\r
56578 if(this.fireEvent('beforequery', qe)===false || qe.cancel){
\r
56582 forceAll = qe.forceAll;
\r
56583 if(forceAll === true || (q.length >= this.minChars)){
\r
56584 if(this.lastQuery !== q){
\r
56585 this.lastQuery = q;
\r
56586 if(this.mode == 'local'){
\r
56587 this.selectedIndex = -1;
\r
56589 this.store.clearFilter();
\r
56591 this.store.filter(this.displayField, q);
\r
56595 this.store.baseParams[this.queryParam] = q;
\r
56596 this.store.load({
\r
56597 params: this.getParams(q)
\r
56602 this.selectedIndex = -1;
\r
56609 getParams : function(q){
\r
56611 //p[this.queryParam] = q;
\r
56612 if(this.pageSize){
\r
56614 p.limit = this.pageSize;
\r
56620 * Hides the dropdown list if it is currently expanded. Fires the {@link #collapse} event on completion.
\r
56622 collapse : function(){
\r
56623 if(!this.isExpanded()){
\r
56626 this.list.hide();
\r
56627 Ext.getDoc().un('mousewheel', this.collapseIf, this);
\r
56628 Ext.getDoc().un('mousedown', this.collapseIf, this);
\r
56629 this.fireEvent('collapse', this);
\r
56633 collapseIf : function(e){
\r
56634 if(!e.within(this.wrap) && !e.within(this.list)){
\r
56640 * Expands the dropdown list if it is currently hidden. Fires the {@link #expand} event on completion.
\r
56642 expand : function(){
\r
56643 if(this.isExpanded() || !this.hasFocus){
\r
56646 if(this.bufferSize){
\r
56647 this.doResize(this.bufferSize);
\r
56648 delete this.bufferSize;
\r
56650 this.list.alignTo(this.wrap, this.listAlign);
\r
56651 this.list.show();
\r
56652 if(Ext.isGecko2){
\r
56653 this.innerList.setOverflow('auto'); // necessary for FF 2.0/Mac
\r
56655 Ext.getDoc().on({
\r
56657 mousewheel: this.collapseIf,
\r
56658 mousedown: this.collapseIf
\r
56660 this.fireEvent('expand', this);
\r
56664 * @method onTriggerClick
\r
56668 // Implements the default empty TriggerField.onTriggerClick function
\r
56669 onTriggerClick : function(){
\r
56670 if(this.disabled){
\r
56673 if(this.isExpanded()){
\r
56677 this.onFocus({});
\r
56678 if(this.triggerAction == 'all') {
\r
56679 this.doQuery(this.allQuery, true);
\r
56681 this.doQuery(this.getRawValue());
\r
56689 * @method autoSize
\r
56692 * @cfg {Boolean} grow @hide
\r
56695 * @cfg {Number} growMin @hide
\r
56698 * @cfg {Number} growMax @hide
\r
56702 Ext.reg('combo', Ext.form.ComboBox);/**
56703 * @class Ext.form.Checkbox
56704 * @extends Ext.form.Field
56705 * Single checkbox field. Can be used as a direct replacement for traditional checkbox fields.
56707 * Creates a new Checkbox
56708 * @param {Object} config Configuration options
56711 Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
56713 * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
56715 focusClass : undefined,
56717 * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to 'x-form-field')
56719 fieldClass : 'x-form-field',
56721 * @cfg {Boolean} checked <tt>true</tt> if the checkbox should render initially checked (defaults to <tt>false</tt>)
56725 * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
56726 * {tag: 'input', type: 'checkbox', autocomplete: 'off'})
56728 defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
56730 * @cfg {String} boxLabel The text that appears beside the checkbox
56733 * @cfg {String} inputValue The value that should go into the generated input element's value attribute
56736 * @cfg {Function} handler A function called when the {@link #checked} value changes (can be used instead of
56737 * handling the check event). The handler is passed the following parameters:
56738 * <div class="mdetail-params"><ul>
56739 * <li><b>checkbox</b> : Ext.form.Checkbox<div class="sub-desc">The Checkbox being toggled.</div></li>
56740 * <li><b>checked</b> : Boolean<div class="sub-desc">The new checked state of the checkbox.</div></li>
56744 * @cfg {Object} scope An object to use as the scope ('this' reference) of the {@link #handler} function
56745 * (defaults to this Checkbox).
56749 actionMode : 'wrap',
56752 initComponent : function(){
56753 Ext.form.Checkbox.superclass.initComponent.call(this);
56757 * Fires when the checkbox is checked or unchecked.
56758 * @param {Ext.form.Checkbox} this This checkbox
56759 * @param {Boolean} checked The new checked value
56766 onResize : function(){
56767 Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
56768 if(!this.boxLabel && !this.fieldLabel){
56769 this.el.alignTo(this.wrap, 'c-c');
56774 initEvents : function(){
56775 Ext.form.Checkbox.superclass.initEvents.call(this);
56776 this.mon(this.el, {
56778 click: this.onClick,
56779 change: this.onClick
56785 * Overridden and disabled. The editor element does not support standard valid/invalid marking.
56788 markInvalid : Ext.emptyFn,
56791 * Overridden and disabled. The editor element does not support standard valid/invalid marking.
56794 clearInvalid : Ext.emptyFn,
56797 onRender : function(ct, position){
56798 Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
56799 if(this.inputValue !== undefined){
56800 this.el.dom.value = this.inputValue;
56802 this.wrap = this.el.wrap({cls: 'x-form-check-wrap'});
56804 this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
56807 this.setValue(true);
56809 this.checked = this.el.dom.checked;
56811 // Need to repaint for IE, otherwise positioning is broken
56813 this.wrap.repaint();
56815 this.resizeEl = this.positionEl = this.wrap;
56819 onDestroy : function(){
56820 Ext.destroy(this.wrap);
56821 Ext.form.Checkbox.superclass.onDestroy.call(this);
56825 initValue : function() {
56826 this.originalValue = this.getValue();
56830 * Returns the checked state of the checkbox.
56831 * @return {Boolean} True if checked, else false
56833 getValue : function(){
56835 return this.el.dom.checked;
56837 return this.checked;
56841 onClick : function(){
56842 if(this.el.dom.checked != this.checked){
56843 this.setValue(this.el.dom.checked);
56848 * Sets the checked state of the checkbox, fires the 'check' event, and calls a
56849 * <code>{@link #handler}</code> (if configured).
56850 * @param {Boolean/String} checked The following values will check the checkbox:
56851 * <code>true, 'true', '1', or 'on'</code>. Any other value will uncheck the checkbox.
56852 * @return {Ext.form.Field} this
56854 setValue : function(v){
56855 var checked = this.checked ;
56856 this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
56858 this.el.dom.checked = this.checked;
56859 this.el.dom.defaultChecked = this.checked;
56861 if(checked != this.checked){
56862 this.fireEvent('check', this, this.checked);
56864 this.handler.call(this.scope || this, this, this.checked);
56870 Ext.reg('checkbox', Ext.form.Checkbox);
56872 * @class Ext.form.CheckboxGroup
\r
56873 * @extends Ext.form.Field
\r
56874 * <p>A grouping container for {@link Ext.form.Checkbox} controls.</p>
\r
56875 * <p>Sample usage:</p>
\r
56877 var myCheckboxGroup = new Ext.form.CheckboxGroup({
\r
56879 xtype: 'checkboxgroup',
\r
56880 fieldLabel: 'Single Column',
\r
56881 itemCls: 'x-check-group-alt',
\r
56882 // Put all controls in a single column with width 100%
\r
56885 {boxLabel: 'Item 1', name: 'cb-col-1'},
\r
56886 {boxLabel: 'Item 2', name: 'cb-col-2', checked: true},
\r
56887 {boxLabel: 'Item 3', name: 'cb-col-3'}
\r
56892 * Creates a new CheckboxGroup
\r
56893 * @param {Object} config Configuration options
\r
56894 * @xtype checkboxgroup
\r
56896 Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
\r
56898 * @cfg {Array} items An Array of {@link Ext.form.Checkbox Checkbox}es or Checkbox config objects
\r
56899 * to arrange in the group.
\r
56902 * @cfg {String/Number/Array} columns Specifies the number of columns to use when displaying grouped
\r
56903 * checkbox/radio controls using automatic layout. This config can take several types of values:
\r
56904 * <ul><li><b>'auto'</b> : <p class="sub-desc">The controls will be rendered one per column on one row and the width
\r
56905 * of each column will be evenly distributed based on the width of the overall field container. This is the default.</p></li>
\r
56906 * <li><b>Number</b> : <p class="sub-desc">If you specific a number (e.g., 3) that number of columns will be
\r
56907 * created and the contained controls will be automatically distributed based on the value of {@link #vertical}.</p></li>
\r
56908 * <li><b>Array</b> : Object<p class="sub-desc">You can also specify an array of column widths, mixing integer
\r
56909 * (fixed width) and float (percentage width) values as needed (e.g., [100, .25, .75]). Any integer values will
\r
56910 * be rendered first, then any float values will be calculated as a percentage of the remaining space. Float
\r
56911 * values do not have to add up to 1 (100%) although if you want the controls to take up the entire field
\r
56912 * container you should do so.</p></li></ul>
\r
56914 columns : 'auto',
\r
56916 * @cfg {Boolean} vertical True to distribute contained controls across columns, completely filling each column
\r
56917 * top to bottom before starting on the next column. The number of controls in each column will be automatically
\r
56918 * calculated to keep columns as even as possible. The default value is false, so that controls will be added
\r
56919 * to columns one at a time, completely filling each row left to right before starting on the next row.
\r
56921 vertical : false,
\r
56923 * @cfg {Boolean} allowBlank False to validate that at least one item in the group is checked (defaults to true).
\r
56924 * If no items are selected at validation time, {@link @blankText} will be used as the error text.
\r
56926 allowBlank : true,
\r
56928 * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails (defaults to "You must
\r
56929 * select at least one item in this group")
\r
56931 blankText : "You must select at least one item in this group",
\r
56934 defaultType : 'checkbox',
\r
56937 groupCls : 'x-form-check-group',
\r
56940 initComponent: function(){
\r
56944 * Fires when the state of a child checkbox changes.
\r
56945 * @param {Ext.form.CheckboxGroup} this
\r
56946 * @param {Array} checked An array containing the checked boxes.
\r
56950 Ext.form.CheckboxGroup.superclass.initComponent.call(this);
\r
56954 onRender : function(ct, position){
\r
56958 cls: this.groupCls,
\r
56959 layout: 'column',
\r
56962 bufferResize: false // Default this to false, since it doesn't really have a proper ownerCt.
\r
56965 defaultType: this.defaultType,
\r
56974 if(this.items[0].items){
\r
56976 // The container has standard ColumnLayout configs, so pass them in directly
\r
56978 Ext.apply(panelCfg, {
\r
56979 layoutConfig: {columns: this.items.length},
\r
56980 defaults: this.defaults,
\r
56981 items: this.items
\r
56983 for(var i=0, len=this.items.length; i<len; i++){
\r
56984 Ext.applyIf(this.items[i], colCfg);
\r
56989 // The container has field item configs, so we have to generate the column
\r
56990 // panels first then move the items into the columns as needed.
\r
56992 var numCols, cols = [];
\r
56994 if(typeof this.columns == 'string'){ // 'auto' so create a col per item
\r
56995 this.columns = this.items.length;
\r
56997 if(!Ext.isArray(this.columns)){
\r
56999 for(var i=0; i<this.columns; i++){
\r
57000 cs.push((100/this.columns)*.01); // distribute by even %
\r
57002 this.columns = cs;
\r
57005 numCols = this.columns.length;
\r
57007 // Generate the column configs with the correct width setting
\r
57008 for(var i=0; i<numCols; i++){
\r
57009 var cc = Ext.apply({items:[]}, colCfg);
\r
57010 cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
\r
57011 if(this.defaults){
\r
57012 cc.defaults = Ext.apply(cc.defaults || {}, this.defaults)
\r
57017 // Distribute the original items into the columns
\r
57018 if(this.vertical){
\r
57019 var rows = Math.ceil(this.items.length / numCols), ri = 0;
\r
57020 for(var i=0, len=this.items.length; i<len; i++){
\r
57021 if(i>0 && i%rows==0){
\r
57024 if(this.items[i].fieldLabel){
\r
57025 this.items[i].hideLabel = false;
\r
57027 cols[ri].items.push(this.items[i]);
\r
57030 for(var i=0, len=this.items.length; i<len; i++){
\r
57031 var ci = i % numCols;
\r
57032 if(this.items[i].fieldLabel){
\r
57033 this.items[i].hideLabel = false;
\r
57035 cols[ci].items.push(this.items[i]);
\r
57039 Ext.apply(panelCfg, {
\r
57040 layoutConfig: {columns: numCols},
\r
57045 this.panel = new Ext.Panel(panelCfg);
\r
57046 this.panel.ownerCt = this;
\r
57047 this.el = this.panel.getEl();
\r
57049 if(this.forId && this.itemCls){
\r
57050 var l = this.el.up(this.itemCls).child('label', true);
\r
57052 l.setAttribute('htmlFor', this.forId);
\r
57056 var fields = this.panel.findBy(function(c){
\r
57057 return c.isFormField;
\r
57060 this.items = new Ext.util.MixedCollection();
\r
57061 this.items.addAll(fields);
\r
57063 Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
\r
57066 initValue : function(){
\r
57068 this.setValue.apply(this, this.buffered ? this.value : [this.value]);
\r
57069 delete this.buffered;
\r
57070 delete this.value;
\r
57074 afterRender : function(){
\r
57075 Ext.form.CheckboxGroup.superclass.afterRender.call(this);
\r
57076 this.eachItem(function(item){
\r
57077 item.on('check', this.fireChecked, this);
\r
57078 item.inGroup = true;
\r
57083 doLayout: function(){
\r
57084 //ugly method required to layout hidden items
\r
57085 if(this.rendered){
\r
57086 this.panel.forceLayout = this.ownerCt.forceLayout;
\r
57087 this.panel.doLayout();
\r
57092 fireChecked: function(){
\r
57094 this.eachItem(function(item){
\r
57095 if(item.checked){
\r
57099 this.fireEvent('change', this, arr);
\r
57103 validateValue : function(value){
\r
57104 if(!this.allowBlank){
\r
57105 var blank = true;
\r
57106 this.eachItem(function(f){
\r
57108 return (blank = false);
\r
57112 this.markInvalid(this.blankText);
\r
57120 isDirty: function(){
\r
57121 //override the behaviour to check sub items.
\r
57122 if (this.disabled || !this.rendered) {
\r
57126 var dirty = false;
\r
57127 this.eachItem(function(item){
\r
57128 if(item.isDirty()){
\r
57137 onDisable : function(){
\r
57138 this.eachItem(function(item){
\r
57144 onEnable : function(){
\r
57145 this.eachItem(function(item){
\r
57151 doLayout: function(){
\r
57152 if(this.rendered){
\r
57153 this.panel.forceLayout = this.ownerCt.forceLayout;
\r
57154 this.panel.doLayout();
\r
57159 onResize : function(w, h){
\r
57160 this.panel.setSize(w, h);
\r
57161 this.panel.doLayout();
\r
57164 // inherit docs from Field
\r
57165 reset : function(){
\r
57166 Ext.form.CheckboxGroup.superclass.reset.call(this);
\r
57167 this.eachItem(function(c){
\r
57175 * {@link Ext.form.Checkbox#setValue Set the value(s)} of an item or items
\r
57176 * in the group. Examples illustrating how this method may be called:
\r
57178 // call with name and value
\r
57179 myCheckboxGroup.setValue('cb-col-1', true);
\r
57180 // call with an array of boolean values
\r
57181 myCheckboxGroup.setValue([true, false, false]);
\r
57182 // call with an object literal specifying item:value pairs
\r
57183 myCheckboxGroup.setValue({
\r
57184 'cb-col-2': false,
\r
57187 // use comma separated string to set items with name to true (checked)
\r
57188 myCheckboxGroup.setValue('cb-col-1,cb-col-3');
\r
57190 * See {@link Ext.form.Checkbox#setValue} for additional information.
\r
57191 * @param {Mixed} id The checkbox to check, or as described by example shown.
\r
57192 * @param {Boolean} value (optional) The value to set the item.
\r
57193 * @return {Ext.form.CheckboxGroup} this
\r
57195 setValue: function(){
\r
57196 if(this.rendered){
\r
57197 this.onSetValue.apply(this, arguments);
\r
57199 this.buffered = true;
\r
57200 this.value = arguments;
\r
57205 onSetValue: function(id, value){
\r
57206 if(arguments.length == 1){
\r
57207 if(Ext.isArray(id)){
\r
57208 // an array of boolean values
\r
57209 Ext.each(id, function(val, idx){
\r
57210 var item = this.items.itemAt(idx);
\r
57212 item.setValue(val);
\r
57215 }else if(Ext.isObject(id)){
\r
57216 // set of name/value pairs
\r
57217 for(var i in id){
\r
57218 var f = this.getBox(i);
\r
57220 f.setValue(id[i]);
\r
57224 this.setValueForItem(id);
\r
57227 var f = this.getBox(id);
\r
57229 f.setValue(value);
\r
57235 onDestroy: function(){
\r
57236 Ext.destroy(this.panel);
\r
57237 Ext.form.CheckboxGroup.superclass.onDestroy.call(this);
\r
57241 setValueForItem : function(val){
\r
57242 val = String(val).split(',');
\r
57243 this.eachItem(function(item){
\r
57244 if(val.indexOf(item.inputValue)> -1){
\r
57245 item.setValue(true);
\r
57251 getBox : function(id){
\r
57253 this.eachItem(function(f){
\r
57254 if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){
\r
57263 * Gets an array of the selected {@link Ext.form.Checkbox} in the group.
\r
57264 * @return {Array} An array of the selected checkboxes.
\r
57266 getValue : function(){
\r
57268 this.eachItem(function(item){
\r
57269 if(item.checked){
\r
57277 eachItem: function(fn){
\r
57278 if(this.items && this.items.each){
\r
57279 this.items.each(fn, this);
\r
57284 * @cfg {String} name
\r
57289 * @method getRawValue
\r
57292 getRawValue : Ext.emptyFn,
\r
57295 * @method setRawValue
\r
57298 setRawValue : Ext.emptyFn
\r
57302 Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
\r
57304 * @class Ext.form.Radio
57305 * @extends Ext.form.Checkbox
57306 * Single radio field. Same as Checkbox, but provided as a convenience for automatically setting the input type.
57307 * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
57309 * Creates a new Radio
57310 * @param {Object} config Configuration options
57313 Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
57314 inputType: 'radio',
57317 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
57320 markInvalid : Ext.emptyFn,
57322 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
57325 clearInvalid : Ext.emptyFn,
57328 * If this radio is part of a group, it will return the selected value
57331 getGroupValue : function(){
57332 var p = this.el.up('form') || Ext.getBody();
57333 var c = p.child('input[name='+this.el.dom.name+']:checked', true);
57334 return c ? c.value : null;
57338 onClick : function(){
57339 if(this.el.dom.checked != this.checked){
57340 var els = this.getCheckEl().select('input[name=' + this.el.dom.name + ']');
57341 els.each(function(el){
57342 if(el.dom.id == this.id){
57343 this.setValue(true);
57345 Ext.getCmp(el.dom.id).setValue(false);
57352 * Sets either the checked/unchecked status of this Radio, or, if a string value
57353 * is passed, checks a sibling Radio of the same name whose value is the value specified.
57354 * @param value {String/Boolean} Checked value, or the value of the sibling radio button to check.
57355 * @return {Ext.form.Field} this
57357 setValue : function(v){
57358 if (typeof v == 'boolean') {
57359 Ext.form.Radio.superclass.setValue.call(this, v);
57361 var r = this.getCheckEl().child('input[name=' + this.el.dom.name + '][value=' + v + ']', true);
57363 Ext.getCmp(r.id).setValue(true);
57370 getCheckEl: function(){
57372 return this.el.up('.x-form-radio-group')
57374 return this.el.up('form') || Ext.getBody();
57377 Ext.reg('radio', Ext.form.Radio);
57379 * @class Ext.form.RadioGroup
\r
57380 * @extends Ext.form.CheckboxGroup
\r
57381 * A grouping container for {@link Ext.form.Radio} controls.
\r
57383 * Creates a new RadioGroup
\r
57384 * @param {Object} config Configuration options
\r
57385 * @xtype radiogroup
\r
57387 Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
\r
57389 * @cfg {Boolean} allowBlank True to allow every item in the group to be blank (defaults to true).
\r
57390 * If allowBlank = false and no items are selected at validation time, {@link @blankText} will
\r
57391 * be used as the error text.
\r
57393 allowBlank : true,
\r
57395 * @cfg {String} blankText Error text to display if the {@link #allowBlank} validation fails
\r
57396 * (defaults to 'You must select one item in this group')
\r
57398 blankText : 'You must select one item in this group',
\r
57401 defaultType : 'radio',
\r
57404 groupCls : 'x-form-radio-group',
\r
57408 * Fires when the state of a child radio changes.
\r
57409 * @param {Ext.form.RadioGroup} this
\r
57410 * @param {Ext.form.Radio} checked The checked radio
\r
57414 * Gets the selected {@link Ext.form.Radio} in the group, if it exists.
\r
57415 * @return {Ext.form.Radio} The selected radio.
\r
57417 getValue : function(){
\r
57419 this.eachItem(function(item){
\r
57420 if(item.checked){
\r
57429 * Sets the checked radio in the group.
\r
57430 * @param {String/Ext.form.Radio} id The radio to check.
\r
57431 * @param {Boolean} value The value to set the radio.
\r
57432 * @return {Ext.form.RadioGroup} this
\r
57434 onSetValue : function(id, value){
\r
57435 if(arguments.length > 1){
\r
57436 var f = this.getBox(id);
\r
57438 f.setValue(value);
\r
57440 this.eachItem(function(item){
\r
57442 item.setValue(false);
\r
57448 this.setValueForItem(id);
\r
57452 setValueForItem : function(val){
\r
57453 val = String(val).split(',')[0];
\r
57454 this.eachItem(function(item){
\r
57455 item.setValue(val == item.inputValue);
\r
57460 fireChecked : function(){
\r
57461 if(!this.checkTask){
\r
57462 this.checkTask = new Ext.util.DelayedTask(this.bufferChecked, this);
\r
57464 this.checkTask.delay(10);
\r
57468 bufferChecked : function(){
\r
57470 this.eachItem(function(item){
\r
57471 if(item.checked){
\r
57476 this.fireEvent('change', this, out);
\r
57479 onDestroy : function(){
\r
57480 if(this.checkTask){
\r
57481 this.checkTask.cancel();
\r
57482 this.checkTask = null;
\r
57484 Ext.form.RadioGroup.superclass.onDestroy.call(this);
\r
57489 Ext.reg('radiogroup', Ext.form.RadioGroup);
\r
57491 * @class Ext.form.Hidden
\r
57492 * @extends Ext.form.Field
\r
57493 * A basic hidden field for storing hidden values in forms that need to be passed in the form submit.
\r
57495 * Create a new Hidden field.
\r
57496 * @param {Object} config Configuration options
\r
57499 Ext.form.Hidden = Ext.extend(Ext.form.Field, {
\r
57501 inputType : 'hidden',
\r
57504 onRender : function(){
\r
57505 Ext.form.Hidden.superclass.onRender.apply(this, arguments);
\r
57509 initEvents : function(){
\r
57510 this.originalValue = this.getValue();
\r
57513 // These are all private overrides
\r
57514 setSize : Ext.emptyFn,
\r
57515 setWidth : Ext.emptyFn,
\r
57516 setHeight : Ext.emptyFn,
\r
57517 setPosition : Ext.emptyFn,
\r
57518 setPagePosition : Ext.emptyFn,
\r
57519 markInvalid : Ext.emptyFn,
\r
57520 clearInvalid : Ext.emptyFn
\r
57522 Ext.reg('hidden', Ext.form.Hidden);/**
57523 * @class Ext.form.BasicForm
57524 * @extends Ext.util.Observable
57525 * <p>Encapsulates the DOM <form> element at the heart of the {@link Ext.form.FormPanel FormPanel} class, and provides
57526 * input field management, validation, submission, and form loading services.</p>
57527 * <p>By default, Ext Forms are submitted through Ajax, using an instance of {@link Ext.form.Action.Submit}.
57528 * To enable normal browser submission of an Ext Form, use the {@link #standardSubmit} config option.</p>
57529 * <p><b><u>File Uploads</u></b></p>
57530 * <p>{@link #fileUpload File uploads} are not performed using Ajax submission, that
57531 * is they are <b>not</b> performed using XMLHttpRequests. Instead the form is submitted in the standard
57532 * manner with the DOM <tt><form></tt> element temporarily modified to have its
57533 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
57534 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
57535 * but removed after the return data has been gathered.</p>
57536 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
57537 * server is using JSON to send the return object, then the
57538 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
57539 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
57540 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
57541 * "<" as "&lt;", "&" as "&amp;" etc.</p>
57542 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
57543 * is created containing a <tt>responseText</tt> property in order to conform to the
57544 * requirements of event handlers and callbacks.</p>
57545 * <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>
57546 * and some server technologies (notably JEE) may require some custom processing in order to
57547 * retrieve parameter names and parameter values from the packet content.</p>
57549 * @param {Mixed} el The form element or its id
57550 * @param {Object} config Configuration options
57552 Ext.form.BasicForm = function(el, config){
57553 Ext.apply(this, config);
57554 if(Ext.isString(this.paramOrder)){
57555 this.paramOrder = this.paramOrder.split(/[\s,|]/);
57559 * A {@link Ext.util.MixedCollection MixedCollection) containing all the Ext.form.Fields in this form.
57560 * @type MixedCollection
57562 this.items = new Ext.util.MixedCollection(false, function(o){
57563 return o.getItemId();
57567 * @event beforeaction
57568 * Fires before any action is performed. Return false to cancel the action.
57569 * @param {Form} this
57570 * @param {Action} action The {@link Ext.form.Action} to be performed
57574 * @event actionfailed
57575 * Fires when an action fails.
57576 * @param {Form} this
57577 * @param {Action} action The {@link Ext.form.Action} that failed
57581 * @event actioncomplete
57582 * Fires when an action is completed.
57583 * @param {Form} this
57584 * @param {Action} action The {@link Ext.form.Action} that completed
57592 Ext.form.BasicForm.superclass.constructor.call(this);
57595 Ext.extend(Ext.form.BasicForm, Ext.util.Observable, {
57597 * @cfg {String} method
57598 * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
57601 * @cfg {DataReader} reader
57602 * An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to read
57603 * data when executing 'load' actions. This is optional as there is built-in
57604 * support for processing JSON. For additional information on using an XMLReader
57605 * see the example provided in examples/form/xml-form.html.
57608 * @cfg {DataReader} errorReader
57609 * <p>An Ext.data.DataReader (e.g. {@link Ext.data.XmlReader}) to be used to
57610 * read field error messages returned from 'submit' actions. This is optional
57611 * as there is built-in support for processing JSON.</p>
57612 * <p>The Records which provide messages for the invalid Fields must use the
57613 * Field name (or id) as the Record ID, and must contain a field called 'msg'
57614 * which contains the error message.</p>
57615 * <p>The errorReader does not have to be a full-blown implementation of a
57616 * DataReader. It simply needs to implement a <tt>read(xhr)</tt> function
57617 * which returns an Array of Records in an object with the following
57618 * structure:</p><pre><code>
57620 records: recordArray
57625 * @cfg {String} url
57626 * The URL to use for form actions if one isn't supplied in the
57627 * <code>{@link #doAction doAction} options</code>.
57630 * @cfg {Boolean} fileUpload
57631 * Set to true if this form is a file upload.
57632 * <p>File uploads are not performed using normal 'Ajax' techniques, that is they are <b>not</b>
57633 * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
57634 * DOM <tt><form></tt> element temporarily modified to have its
57635 * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
57636 * to a dynamically generated, hidden <tt><iframe></tt> which is inserted into the document
57637 * but removed after the return data has been gathered.</p>
57638 * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
57639 * server is using JSON to send the return object, then the
57640 * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
57641 * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
57642 * <p>Characters which are significant to an HTML parser must be sent as HTML entities, so encode
57643 * "<" as "&lt;", "&" as "&amp;" etc.</p>
57644 * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
57645 * is created containing a <tt>responseText</tt> property in order to conform to the
57646 * requirements of event handlers and callbacks.</p>
57647 * <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>
57648 * and some server technologies (notably JEE) may require some custom processing in order to
57649 * retrieve parameter names and parameter values from the packet content.</p>
57652 * @cfg {Object} baseParams
57653 * <p>Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.</p>
57654 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
57657 * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
57662 * @cfg {Object} api (Optional) If specified load and submit actions will be handled
57663 * with {@link Ext.form.Action.DirectLoad} and {@link Ext.form.Action.DirectSubmit}.
57664 * Methods which have been imported by Ext.Direct can be specified here to load and submit
57666 * Such as the following:<pre><code>
57668 load: App.ss.MyProfile.load,
57669 submit: App.ss.MyProfile.submit
57672 * <p>Load actions can use <code>{@link #paramOrder}</code> or <code>{@link #paramsAsHash}</code>
57673 * to customize how the load method is invoked.
57674 * Submit actions will always use a standard form submit. The formHandler configuration must
57675 * be set on the associated server-side method which has been imported by Ext.Direct</p>
57679 * @cfg {Array/String} paramOrder <p>A list of params to be executed server side.
57680 * Defaults to <tt>undefined</tt>. Only used for the <code>{@link #api}</code>
57681 * <code>load</code> configuration.</p>
57682 * <br><p>Specify the params in the order in which they must be executed on the
57683 * server-side as either (1) an Array of String values, or (2) a String of params
57684 * delimited by either whitespace, comma, or pipe. For example,
57685 * any of the following would be acceptable:</p><pre><code>
57686 paramOrder: ['param1','param2','param3']
57687 paramOrder: 'param1 param2 param3'
57688 paramOrder: 'param1,param2,param3'
57689 paramOrder: 'param1|param2|param'
57692 paramOrder: undefined,
57695 * @cfg {Boolean} paramsAsHash Only used for the <code>{@link #api}</code>
57696 * <code>load</code> configuration. Send parameters as a collection of named
57697 * arguments (defaults to <tt>false</tt>). Providing a
57698 * <tt>{@link #paramOrder}</tt> nullifies this configuration.
57700 paramsAsHash: false,
57703 * @cfg {String} waitTitle
57704 * The default title to show for the waiting message box (defaults to <tt>'Please Wait...'</tt>)
57706 waitTitle: 'Please Wait...',
57709 activeAction : null,
57712 * @cfg {Boolean} trackResetOnLoad If set to <tt>true</tt>, {@link #reset}() resets to the last loaded
57713 * or {@link #setValues}() data instead of when the form was first created. Defaults to <tt>false</tt>.
57715 trackResetOnLoad : false,
57718 * @cfg {Boolean} standardSubmit
57719 * <p>If set to <tt>true</tt>, standard HTML form submits are used instead
57720 * of XHR (Ajax) style form submissions. Defaults to <tt>false</tt>.</p>
57721 * <br><p><b>Note:</b> When using <code>standardSubmit</code>, the
57722 * <code>options</code> to <code>{@link #submit}</code> are ignored because
57723 * Ext's Ajax infrastracture is bypassed. To pass extra parameters (e.g.
57724 * <code>baseParams</code> and <code>params</code>), utilize hidden fields
57725 * to submit extra data, for example:</p>
57727 new Ext.FormPanel({
57728 standardSubmit: true,
57732 {@link url}: 'myProcess.php',
57734 xtype: 'textfield',
57739 handler: function(){
57740 var fp = this.ownerCt.ownerCt,
57741 form = fp.getForm();
57742 if (form.isValid()) {
57743 // check if there are baseParams and if
57744 // hiddent items have been added already
57745 if (fp.baseParams && !fp.paramsAdded) {
57746 // add hidden items for all baseParams
57747 for (i in fp.baseParams) {
57751 value: fp.baseParams[i]
57755 // set a custom flag to prevent re-adding
57756 fp.paramsAdded = true;
57758 form.{@link #submit}();
57766 * By default wait messages are displayed with Ext.MessageBox.wait. You can target a specific
57767 * element by passing it or its id or mask the form itself by passing in true.
57769 * @property waitMsgTarget
57773 initEl : function(el){
57774 this.el = Ext.get(el);
57775 this.id = this.el.id || Ext.id();
57776 if(!this.standardSubmit){
57777 this.el.on('submit', this.onSubmit, this);
57779 this.el.addClass('x-form');
57783 * Get the HTML form Element
57784 * @return Ext.Element
57791 onSubmit : function(e){
57796 destroy: function() {
57797 this.items.each(function(f){
57801 this.el.removeAllListeners();
57804 this.purgeListeners();
57808 * Returns true if client-side validation on the form is successful.
57811 isValid : function(){
57813 this.items.each(function(f){
57822 * <p>Returns true if any fields in this form have changed from their original values.</p>
57823 * <p>Note that if this BasicForm was configured with {@link #trackResetOnLoad} then the
57824 * Fields' <i>original values</i> are updated when the values are loaded by {@link #setValues}
57825 * or {@link #loadRecord}.</p>
57828 isDirty : function(){
57830 this.items.each(function(f){
57840 * Performs a predefined action ({@link Ext.form.Action.Submit} or
57841 * {@link Ext.form.Action.Load}) or a custom extension of {@link Ext.form.Action}
57842 * to perform application-specific processing.
57843 * @param {String/Object} actionName The name of the predefined action type,
57844 * or instance of {@link Ext.form.Action} to perform.
57845 * @param {Object} options (optional) The options to pass to the {@link Ext.form.Action}.
57846 * All of the config options listed below are supported by both the
57847 * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
57848 * actions unless otherwise noted (custom actions could also accept
57849 * other config options):<ul>
57851 * <li><b>url</b> : String<div class="sub-desc">The url for the action (defaults
57852 * to the form's {@link #url}.)</div></li>
57854 * <li><b>method</b> : String<div class="sub-desc">The form method to use (defaults
57855 * to the form's method, or POST if not defined)</div></li>
57857 * <li><b>params</b> : String/Object<div class="sub-desc"><p>The params to pass
57858 * (defaults to the form's baseParams, or none if not defined)</p>
57859 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p></div></li>
57861 * <li><b>headers</b> : Object<div class="sub-desc">Request headers to set for the action
57862 * (defaults to the form's default headers)</div></li>
57864 * <li><b>success</b> : Function<div class="sub-desc">The callback that will
57865 * be invoked after a successful response (see top of
57866 * {@link Ext.form.Action.Submit submit} and {@link Ext.form.Action.Load load}
57867 * for a description of what constitutes a successful response).
57868 * The function is passed the following parameters:<ul>
57869 * <li><tt>form</tt> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
57870 * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
57871 * <div class="sub-desc">The action object contains these properties of interest:<ul>
57872 * <li><tt>{@link Ext.form.Action#response response}</tt></li>
57873 * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
57874 * <li><tt>{@link Ext.form.Action#type type}</tt></li>
57875 * </ul></div></li></ul></div></li>
57877 * <li><b>failure</b> : Function<div class="sub-desc">The callback that will be invoked after a
57878 * failed transaction attempt. The function is passed the following parameters:<ul>
57879 * <li><tt>form</tt> : The {@link Ext.form.BasicForm} that requested the action.</li>
57880 * <li><tt>action</tt> : The {@link Ext.form.Action Action} object which performed the operation.
57881 * <div class="sub-desc">The action object contains these properties of interest:<ul>
57882 * <li><tt>{@link Ext.form.Action#failureType failureType}</tt></li>
57883 * <li><tt>{@link Ext.form.Action#response response}</tt></li>
57884 * <li><tt>{@link Ext.form.Action#result result}</tt> : interrogate for custom postprocessing</li>
57885 * <li><tt>{@link Ext.form.Action#type type}</tt></li>
57886 * </ul></div></li></ul></div></li>
57888 * <li><b>scope</b> : Object<div class="sub-desc">The scope in which to call the
57889 * callback functions (The <tt>this</tt> reference for the callback functions).</div></li>
57891 * <li><b>clientValidation</b> : Boolean<div class="sub-desc">Submit Action only.
57892 * Determines whether a Form's fields are validated in a final call to
57893 * {@link Ext.form.BasicForm#isValid isValid} prior to submission. Set to <tt>false</tt>
57894 * to prevent this. If undefined, pre-submission field validation is performed.</div></li></ul>
57896 * @return {BasicForm} this
57898 doAction : function(action, options){
57899 if(Ext.isString(action)){
57900 action = new Ext.form.Action.ACTION_TYPES[action](this, options);
57902 if(this.fireEvent('beforeaction', this, action) !== false){
57903 this.beforeAction(action);
57904 action.run.defer(100, action);
57910 * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Submit submit action}.
57911 * @param {Object} options The options to pass to the action (see {@link #doAction} for details).<br>
57912 * <p><b>Note:</b> this is ignored when using the {@link #standardSubmit} option.</p>
57913 * <p>The following code:</p><pre><code>
57914 myFormPanel.getForm().submit({
57915 clientValidation: true,
57916 url: 'updateConsignment.php',
57918 newStatus: 'delivered'
57920 success: function(form, action) {
57921 Ext.Msg.alert('Success', action.result.msg);
57923 failure: function(form, action) {
57924 switch (action.failureType) {
57925 case Ext.form.Action.CLIENT_INVALID:
57926 Ext.Msg.alert('Failure', 'Form fields may not be submitted with invalid values');
57928 case Ext.form.Action.CONNECT_FAILURE:
57929 Ext.Msg.alert('Failure', 'Ajax communication failed');
57931 case Ext.form.Action.SERVER_INVALID:
57932 Ext.Msg.alert('Failure', action.result.msg);
57937 * would process the following server response for a successful submission:<pre><code>
57939 "success":true, // note this is Boolean, not string
57940 "msg":"Consignment updated"
57943 * and the following server response for a failed submission:<pre><code>
57945 "success":false, // note this is Boolean, not string
57946 "msg":"You do not have permission to perform this operation"
57949 * @return {BasicForm} this
57951 submit : function(options){
57952 if(this.standardSubmit){
57953 var v = this.isValid();
57955 var el = this.el.dom;
57956 if(this.url && Ext.isEmpty(el.action)){
57957 el.action = this.url;
57963 var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
57964 this.doAction(submitAction, options);
57969 * Shortcut to {@link #doAction do} a {@link Ext.form.Action.Load load action}.
57970 * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
57971 * @return {BasicForm} this
57973 load : function(options){
57974 var loadAction = String.format('{0}load', this.api ? 'direct' : '');
57975 this.doAction(loadAction, options);
57980 * Persists the values in this form into the passed {@link Ext.data.Record} object in a beginEdit/endEdit block.
57981 * @param {Record} record The record to edit
57982 * @return {BasicForm} this
57984 updateRecord : function(record){
57985 record.beginEdit();
57986 var fs = record.fields;
57987 fs.each(function(f){
57988 var field = this.findField(f.name);
57990 record.set(f.name, field.getValue());
57998 * Loads an {@link Ext.data.Record} into this form by calling {@link #setValues} with the
57999 * {@link Ext.data.Record#data record data}.
58000 * See also {@link #trackResetOnLoad}.
58001 * @param {Record} record The record to load
58002 * @return {BasicForm} this
58004 loadRecord : function(record){
58005 this.setValues(record.data);
58010 beforeAction : function(action){
58011 var o = action.options;
58013 if(this.waitMsgTarget === true){
58014 this.el.mask(o.waitMsg, 'x-mask-loading');
58015 }else if(this.waitMsgTarget){
58016 this.waitMsgTarget = Ext.get(this.waitMsgTarget);
58017 this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
58019 Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle);
58025 afterAction : function(action, success){
58026 this.activeAction = null;
58027 var o = action.options;
58029 if(this.waitMsgTarget === true){
58031 }else if(this.waitMsgTarget){
58032 this.waitMsgTarget.unmask();
58034 Ext.MessageBox.updateProgress(1);
58035 Ext.MessageBox.hide();
58042 Ext.callback(o.success, o.scope, [this, action]);
58043 this.fireEvent('actioncomplete', this, action);
58045 Ext.callback(o.failure, o.scope, [this, action]);
58046 this.fireEvent('actionfailed', this, action);
58051 * Find a {@link Ext.form.Field} in this form.
58052 * @param {String} id The value to search for (specify either a {@link Ext.Component#id id},
58053 * {@link Ext.grid.Column#dataIndex dataIndex}, {@link Ext.form.Field#getName name or hiddenName}).
58056 findField : function(id){
58057 var field = this.items.get(id);
58058 if(!Ext.isObject(field)){
58059 this.items.each(function(f){
58060 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
58066 return field || null;
58071 * Mark fields in this form invalid in bulk.
58072 * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
58073 * @return {BasicForm} this
58075 markInvalid : function(errors){
58076 if(Ext.isArray(errors)){
58077 for(var i = 0, len = errors.length; i < len; i++){
58078 var fieldError = errors[i];
58079 var f = this.findField(fieldError.id);
58081 f.markInvalid(fieldError.msg);
58087 if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
58088 field.markInvalid(errors[id]);
58096 * Set values for fields in this form in bulk.
58097 * @param {Array/Object} values Either an array in the form:<pre><code>
58098 [{id:'clientName', value:'Fred. Olsen Lines'},
58099 {id:'portOfLoading', value:'FXT'},
58100 {id:'portOfDischarge', value:'OSL'} ]</code></pre>
58101 * or an object hash of the form:<pre><code>
58103 clientName: 'Fred. Olsen Lines',
58104 portOfLoading: 'FXT',
58105 portOfDischarge: 'OSL'
58107 * @return {BasicForm} this
58109 setValues : function(values){
58110 if(Ext.isArray(values)){ // array of objects
58111 for(var i = 0, len = values.length; i < len; i++){
58113 var f = this.findField(v.id);
58115 f.setValue(v.value);
58116 if(this.trackResetOnLoad){
58117 f.originalValue = f.getValue();
58121 }else{ // object hash
58124 if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
58125 field.setValue(values[id]);
58126 if(this.trackResetOnLoad){
58127 field.originalValue = field.getValue();
58136 * <p>Returns the fields in this form as an object with key/value pairs as they would be submitted using a standard form submit.
58137 * If multiple fields exist with the same name they are returned as an array.</p>
58138 * <p><b>Note:</b> The values are collected from all enabled HTML input elements within the form, <u>not</u> from
58139 * the Ext Field objects. This means that all returned values are Strings (or Arrays of Strings) and that the
58140 * value can potentially be the emptyText of a field.</p>
58141 * @param {Boolean} asString (optional) Pass true to return the values as a string. (defaults to false, returning an Object)
58142 * @return {String/Object}
58144 getValues : function(asString){
58145 var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
58146 if(asString === true){
58149 return Ext.urlDecode(fs);
58152 getFieldValues : function(){
58154 this.items.each(function(f){
58155 o[f.getName()] = f.getValue();
58161 * Clears all invalid messages in this form.
58162 * @return {BasicForm} this
58164 clearInvalid : function(){
58165 this.items.each(function(f){
58172 * Resets this form.
58173 * @return {BasicForm} this
58175 reset : function(){
58176 this.items.each(function(f){
58183 * Add Ext.form Components to this form's Collection. This does not result in rendering of
58184 * the passed Component, it just enables the form to validate Fields, and distribute values to
58186 * <p><b>You will not usually call this function. In order to be rendered, a Field must be added
58187 * to a {@link Ext.Container Container}, usually an {@link Ext.form.FormPanel FormPanel}.
58188 * The FormPanel to which the field is added takes care of adding the Field to the BasicForm's
58189 * collection.</b></p>
58190 * @param {Field} field1
58191 * @param {Field} field2 (optional)
58192 * @param {Field} etc (optional)
58193 * @return {BasicForm} this
58196 this.items.addAll(Array.prototype.slice.call(arguments, 0));
58202 * Removes a field from the items collection (does NOT remove its markup).
58203 * @param {Field} field
58204 * @return {BasicForm} this
58206 remove : function(field){
58207 this.items.remove(field);
58212 * Iterates through the {@link Ext.form.Field Field}s which have been {@link #add add}ed to this BasicForm,
58213 * checks them for an id attribute, and calls {@link Ext.form.Field#applyToMarkup} on the existing dom element with that id.
58214 * @return {BasicForm} this
58216 render : function(){
58217 this.items.each(function(f){
58218 if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
58219 f.applyToMarkup(f.id);
58226 * Calls {@link Ext#apply} for all fields in this form with the passed object.
58227 * @param {Object} values
58228 * @return {BasicForm} this
58230 applyToFields : function(o){
58231 this.items.each(function(f){
58238 * Calls {@link Ext#applyIf} for all field in this form with the passed object.
58239 * @param {Object} values
58240 * @return {BasicForm} this
58242 applyIfToFields : function(o){
58243 this.items.each(function(f){
58249 callFieldMethod : function(fnName, args){
58251 this.items.each(function(f){
58252 if(Ext.isFunction(f[fnName])){
58253 f[fnName].apply(f, args);
58261 Ext.BasicForm = Ext.form.BasicForm;/**
58262 * @class Ext.form.FormPanel
58263 * @extends Ext.Panel
58264 * <p>Standard form container.</p>
58266 * <p><b><u>Layout</u></b></p>
58267 * <p>By default, FormPanel is configured with <tt>layout:'form'</tt> to use an {@link Ext.layout.FormLayout}
58268 * layout manager, which styles and renders fields and labels correctly. When nesting additional Containers
58269 * within a FormPanel, you should ensure that any descendant Containers which host input Fields use the
58270 * {@link Ext.layout.FormLayout} layout manager.</p>
58272 * <p><b><u>BasicForm</u></b></p>
58273 * <p>Although <b>not listed</b> as configuration options of FormPanel, the FormPanel class accepts all
58274 * of the config options required to configure its internal {@link Ext.form.BasicForm} for:
58275 * <div class="mdetail-params"><ul>
58276 * <li>{@link Ext.form.BasicForm#fileUpload file uploads}</li>
58277 * <li>functionality for {@link Ext.form.BasicForm#doAction loading, validating and submitting} the form</li>
58280 * <p><b>Note</b>: If subclassing FormPanel, any configuration options for the BasicForm must be applied to
58281 * the <tt><b>initialConfig</b></tt> property of the FormPanel. Applying {@link Ext.form.BasicForm BasicForm}
58282 * configuration settings to <b><tt>this</tt></b> will <b>not</b> affect the BasicForm's configuration.</p>
58284 * <p><b><u>Form Validation</u></b></p>
58285 * <p>For information on form validation see the following:</p>
58286 * <div class="mdetail-params"><ul>
58287 * <li>{@link Ext.form.TextField}</li>
58288 * <li>{@link Ext.form.VTypes}</li>
58289 * <li>{@link Ext.form.BasicForm#doAction BasicForm.doAction <b>clientValidation</b> notes}</li>
58290 * <li><tt>{@link Ext.form.FormPanel#monitorValid monitorValid}</tt></li>
58293 * <p><b><u>Form Submission</u></b></p>
58294 * <p>By default, Ext Forms are submitted through Ajax, using {@link Ext.form.Action}. To enable normal browser
58295 * submission of the {@link Ext.form.BasicForm BasicForm} contained in this FormPanel, see the
58296 * <tt><b>{@link Ext.form.BasicForm#standardSubmit standardSubmit}</b></tt> option.</p>
58299 * @param {Object} config Configuration options
58302 Ext.FormPanel = Ext.extend(Ext.Panel, {
58304 * @cfg {String} formId (optional) The id of the FORM tag (defaults to an auto-generated id).
58307 * @cfg {Boolean} hideLabels
58308 * <p><tt>true</tt> to hide field labels by default (sets <tt>display:none</tt>). Defaults to
58309 * <tt>false</tt>.</p>
58310 * <p>Also see {@link Ext.Component}.<tt>{@link Ext.Component#hideLabel hideLabel}</tt>.
58313 * @cfg {Number} labelPad
58314 * The default padding in pixels for field labels (defaults to <tt>5</tt>). <tt>labelPad</tt> only
58315 * applies if <tt>{@link #labelWidth}</tt> is also specified, otherwise it will be ignored.
58318 * @cfg {String} labelSeparator
58319 * See {@link Ext.Component}.<tt>{@link Ext.Component#labelSeparator labelSeparator}</tt>
58322 * @cfg {Number} labelWidth The width of labels in pixels. This property cascades to child containers
58323 * and can be overridden on any child container (e.g., a fieldset can specify a different <tt>labelWidth</tt>
58324 * for its fields) (defaults to <tt>100</tt>).
58327 * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
58330 * @cfg {Array} buttons
58331 * An array of {@link Ext.Button}s or {@link Ext.Button} configs used to add buttons to the footer of this FormPanel.<br>
58332 * <p>Buttons in the footer of a FormPanel may be configured with the option <tt>formBind: true</tt>. This causes
58333 * the form's {@link #monitorValid valid state monitor task} to enable/disable those Buttons depending on
58334 * the form's valid/invalid state.</p>
58339 * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to <tt>75</tt>).
58341 minButtonWidth : 75,
58344 * @cfg {String} labelAlign The label alignment value used for the <tt>text-align</tt> specification
58345 * for the <b>container</b>. Valid values are <tt>"left</tt>", <tt>"top"</tt> or <tt>"right"</tt>
58346 * (defaults to <tt>"left"</tt>). This property cascades to child <b>containers</b> and can be
58347 * overridden on any child <b>container</b> (e.g., a fieldset can specify a different <tt>labelAlign</tt>
58350 labelAlign : 'left',
58353 * @cfg {Boolean} monitorValid If <tt>true</tt>, the form monitors its valid state <b>client-side</b> and
58354 * regularly fires the {@link #clientvalidation} event passing that state.<br>
58355 * <p>When monitoring valid state, the FormPanel enables/disables any of its configured
58356 * {@link #buttons} which have been configured with <code>formBind: true</code> depending
58357 * on whether the {@link Ext.form.BasicForm#isValid form is valid} or not. Defaults to <tt>false</tt></p>
58359 monitorValid : false,
58362 * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
58367 * @cfg {String} layout Defaults to <tt>'form'</tt>. Normally this configuration property should not be altered.
58368 * For additional details see {@link Ext.layout.FormLayout} and {@link Ext.Container#layout Ext.Container.layout}.
58373 initComponent : function(){
58374 this.form = this.createForm();
58375 Ext.FormPanel.superclass.initComponent.call(this);
58379 cls: this.baseCls + '-body',
58380 method : this.method || 'POST',
58381 id : this.formId || Ext.id()
58383 if(this.fileUpload) {
58384 this.bodyCfg.enctype = 'multipart/form-data';
58390 * @event clientvalidation
58391 * If the monitorValid config option is true, this event fires repetitively to notify of valid state
58392 * @param {Ext.form.FormPanel} this
58393 * @param {Boolean} valid true if the form has passed client-side validation
58398 this.relayEvents(this.form, ['beforeaction', 'actionfailed', 'actioncomplete']);
58402 createForm : function(){
58403 var config = Ext.applyIf({listeners: {}}, this.initialConfig);
58404 return new Ext.form.BasicForm(null, config);
58408 initFields : function(){
58410 var formPanel = this;
58411 var fn = function(c){
58412 if(formPanel.isField(c)){
58414 }else if(c.findBy && c != formPanel){
58415 formPanel.applySettings(c);
58416 //each check required for check/radio groups.
58417 if(c.items && c.items.each){
58418 c.items.each(fn, this);
58422 this.items.each(fn, this);
58426 applySettings: function(c){
58427 var ct = c.ownerCt;
58429 labelAlign: ct.labelAlign,
58430 labelWidth: ct.labelWidth,
58431 itemCls: ct.itemCls
58436 getLayoutTarget : function(){
58437 return this.form.el;
58441 * Provides access to the {@link Ext.form.BasicForm Form} which this Panel contains.
58442 * @return {Ext.form.BasicForm} The {@link Ext.form.BasicForm Form} which this Panel contains.
58444 getForm : function(){
58449 onRender : function(ct, position){
58451 Ext.FormPanel.superclass.onRender.call(this, ct, position);
58452 this.form.initEl(this.body);
58456 beforeDestroy : function(){
58457 this.stopMonitoring();
58458 Ext.FormPanel.superclass.beforeDestroy.call(this);
58460 * Clear the items here to prevent them being destroyed again.
58461 * Don't move this behaviour to BasicForm because it can be used
58464 this.form.items.clear();
58465 Ext.destroy(this.form);
58468 // Determine if a Component is usable as a form Field.
58469 isField : function(c) {
58470 return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid;
58474 initEvents : function(){
58475 Ext.FormPanel.superclass.initEvents.call(this);
58476 // Listeners are required here to catch bubbling events from children.
58479 add: this.onAddEvent,
58480 remove: this.onRemoveEvent
58482 if(this.monitorValid){ // initialize after render
58483 this.startMonitoring();
58488 onAdd: function(c){
58489 Ext.FormPanel.superclass.onAdd.call(this, c);
58490 this.processAdd(c);
58494 onAddEvent: function(ct, c){
58496 this.processAdd(c);
58501 processAdd : function(c){
58502 // If a single form Field, add it
58503 if(this.isField(c)){
58505 // If a Container, add any Fields it might contain
58506 }else if(c.findBy){
58507 this.applySettings(c);
58508 this.form.add.apply(this.form, c.findBy(this.isField));
58513 onRemove: function(c){
58514 Ext.FormPanel.superclass.onRemove.call(this, c);
58515 this.processRemove(c);
58518 onRemoveEvent: function(ct, c){
58520 this.processRemove(c);
58525 processRemove : function(c){
58526 // If a single form Field, remove it
58527 if(this.isField(c)){
58528 this.form.remove(c);
58529 // If a Container, remove any Fields it might contain
58530 }else if(c.findBy){
58531 Ext.each(c.findBy(this.isField), this.form.remove, this.form);
58536 * Starts monitoring of the valid state of this form. Usually this is done by passing the config
58537 * option "monitorValid"
58539 startMonitoring : function(){
58540 if(!this.validTask){
58541 this.validTask = new Ext.util.TaskRunner();
58542 this.validTask.start({
58543 run : this.bindHandler,
58544 interval : this.monitorPoll || 200,
58551 * Stops monitoring of the valid state of this form
58553 stopMonitoring : function(){
58554 if(this.validTask){
58555 this.validTask.stopAll();
58556 this.validTask = null;
58561 * This is a proxy for the underlying BasicForm's {@link Ext.form.BasicForm#load} call.
58562 * @param {Object} options The options to pass to the action (see {@link Ext.form.BasicForm#doAction} for details)
58565 this.form.load.apply(this.form, arguments);
58569 onDisable : function(){
58570 Ext.FormPanel.superclass.onDisable.call(this);
58572 this.form.items.each(function(){
58579 onEnable : function(){
58580 Ext.FormPanel.superclass.onEnable.call(this);
58582 this.form.items.each(function(){
58589 bindHandler : function(){
58591 this.form.items.each(function(f){
58592 if(!f.isValid(true)){
58598 var fitems = this.fbar.items.items;
58599 for(var i = 0, len = fitems.length; i < len; i++){
58600 var btn = fitems[i];
58601 if(btn.formBind === true && btn.disabled === valid){
58602 btn.setDisabled(!valid);
58606 this.fireEvent('clientvalidation', this, valid);
58609 Ext.reg('form', Ext.FormPanel);
58611 Ext.form.FormPanel = Ext.FormPanel;
58614 * @class Ext.form.FieldSet
\r
58615 * @extends Ext.Panel
\r
58616 * Standard container used for grouping items within a {@link Ext.form.FormPanel form}.
\r
58618 var form = new Ext.FormPanel({
\r
58619 title: 'Simple Form with FieldSets',
\r
58620 labelWidth: 75, // label settings here cascade unless overridden
\r
58621 url: 'save-form.php',
\r
58623 bodyStyle:'padding:5px 5px 0',
\r
58625 renderTo: document.body,
\r
58626 layout:'column', // arrange items in columns
\r
58627 defaults: { // defaults applied to items
\r
58630 bodyStyle: 'padding:4px'
\r
58633 // Fieldset in Column 1
\r
58634 xtype:'fieldset',
\r
58635 columnWidth: 0.5,
\r
58636 title: 'Fieldset 1',
\r
58637 collapsible: true,
\r
58640 anchor: '-20' // leave room for error icon
\r
58642 defaultType: 'textfield',
\r
58644 fieldLabel: 'Field 1'
\r
58646 fieldLabel: 'Field 2'
\r
58648 fieldLabel: 'Field 3'
\r
58652 // Fieldset in Column 2 - Panel inside
\r
58653 xtype:'fieldset',
\r
58654 title: 'Show Panel', // title, header, or checkboxToggle creates fieldset header
\r
58656 columnWidth: 0.5,
\r
58657 checkboxToggle: true,
\r
58658 collapsed: true, // fieldset initially collapsed
\r
58663 title: 'Panel inside a fieldset',
\r
58671 * @param {Object} config Configuration options
\r
58672 * @xtype fieldset
\r
58674 Ext.form.FieldSet = Ext.extend(Ext.Panel, {
\r
58676 * @cfg {Mixed} checkboxToggle <tt>true</tt> to render a checkbox into the fieldset frame just
\r
58677 * in front of the legend to expand/collapse the fieldset when the checkbox is toggled. (defaults
\r
58678 * to <tt>false</tt>).
\r
58679 * <p>A {@link Ext.DomHelper DomHelper} element spec may also be specified to create the checkbox.
\r
58680 * If <tt>true</tt> is specified, the default DomHelper config object used to create the element
\r
58681 * is:</p><pre><code>
\r
58682 * {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'}
\r
58686 * @cfg {String} checkboxName The name to assign to the fieldset's checkbox if <tt>{@link #checkboxToggle} = true</tt>
\r
58687 * (defaults to <tt>'[checkbox id]-checkbox'</tt>).
\r
58690 * @cfg {Boolean} collapsible
\r
58691 * <tt>true</tt> to make the fieldset collapsible and have the expand/collapse toggle button automatically
\r
58692 * rendered into the legend element, <tt>false</tt> to keep the fieldset statically sized with no collapse
\r
58693 * button (defaults to <tt>false</tt>). Another option is to configure <tt>{@link #checkboxToggle}</tt>.
\r
58696 * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
\r
58699 * @cfg {String} itemCls A css class to apply to the <tt>x-form-item</tt> of fields (see
\r
58700 * {@link Ext.layout.FormLayout}.{@link Ext.layout.FormLayout#fieldTpl fieldTpl} for details).
\r
58701 * This property cascades to child containers.
\r
58704 * @cfg {String} baseCls The base CSS class applied to the fieldset (defaults to <tt>'x-fieldset'</tt>).
\r
58706 baseCls : 'x-fieldset',
\r
58708 * @cfg {String} layout The {@link Ext.Container#layout} to use inside the fieldset (defaults to <tt>'form'</tt>).
\r
58712 * @cfg {Boolean} animCollapse
\r
58713 * <tt>true</tt> to animate the transition when the panel is collapsed, <tt>false</tt> to skip the
\r
58714 * animation (defaults to <tt>false</tt>).
\r
58716 animCollapse : false,
\r
58719 onRender : function(ct, position){
\r
58721 this.el = document.createElement('fieldset');
\r
58722 this.el.id = this.id;
\r
58723 if (this.title || this.header || this.checkboxToggle) {
\r
58724 this.el.appendChild(document.createElement('legend')).className = 'x-fieldset-header';
\r
58728 Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
\r
58730 if(this.checkboxToggle){
\r
58731 var o = typeof this.checkboxToggle == 'object' ?
\r
58732 this.checkboxToggle :
\r
58733 {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
\r
58734 this.checkbox = this.header.insertFirst(o);
\r
58735 this.checkbox.dom.checked = !this.collapsed;
\r
58736 this.mon(this.checkbox, 'click', this.onCheckClick, this);
\r
58741 onCollapse : function(doAnim, animArg){
\r
58742 if(this.checkbox){
\r
58743 this.checkbox.dom.checked = false;
\r
58745 Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
\r
58750 onExpand : function(doAnim, animArg){
\r
58751 if(this.checkbox){
\r
58752 this.checkbox.dom.checked = true;
\r
58754 Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
\r
58758 * This function is called by the fieldset's checkbox when it is toggled (only applies when
\r
58759 * checkboxToggle = true). This method should never be called externally, but can be
\r
58760 * overridden to provide custom behavior when the checkbox is toggled if needed.
\r
58762 onCheckClick : function(){
\r
58763 this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
\r
58767 * @cfg {String/Number} activeItem
\r
58771 * @cfg {Mixed} applyTo
\r
58775 * @cfg {Boolean} bodyBorder
\r
58779 * @cfg {Boolean} border
\r
58783 * @cfg {Boolean/Number} bufferResize
\r
58787 * @cfg {Boolean} collapseFirst
\r
58791 * @cfg {String} defaultType
\r
58795 * @cfg {String} disabledClass
\r
58799 * @cfg {String} elements
\r
58803 * @cfg {Boolean} floating
\r
58807 * @cfg {Boolean} footer
\r
58811 * @cfg {Boolean} frame
\r
58815 * @cfg {Boolean} header
\r
58819 * @cfg {Boolean} headerAsText
\r
58823 * @cfg {Boolean} hideCollapseTool
\r
58827 * @cfg {String} iconCls
\r
58831 * @cfg {Boolean/String} shadow
\r
58835 * @cfg {Number} shadowOffset
\r
58839 * @cfg {Boolean} shim
\r
58843 * @cfg {Object/Array} tbar
\r
58847 * @cfg {Array} tools
\r
58851 * @cfg {Ext.Template/Ext.XTemplate} toolTemplate
\r
58855 * @cfg {String} xtype
\r
58859 * @property header
\r
58863 * @property footer
\r
58871 * @method getBottomToolbar
\r
58875 * @method getTopToolbar
\r
58879 * @method setIconClass
\r
58883 * @event activate
\r
58887 * @event beforeclose
\r
58891 * @event bodyresize
\r
58899 * @event deactivate
\r
58903 Ext.reg('fieldset', Ext.form.FieldSet);
\r
58905 * @class Ext.form.HtmlEditor
\r
58906 * @extends Ext.form.Field
\r
58907 * Provides a lightweight HTML Editor component. Some toolbar features are not supported by Safari and will be
\r
58908 * automatically hidden when needed. These are noted in the config options where appropriate.
\r
58909 * <br><br>The editor's toolbar buttons have tooltips defined in the {@link #buttonTips} property, but they are not
\r
58910 * enabled by default unless the global {@link Ext.QuickTips} singleton is {@link Ext.QuickTips#init initialized}.
\r
58911 * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
\r
58912 * supported by this editor.</b>
\r
58913 * <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
58914 * any element that has display set to 'none' can cause problems in Safari and Firefox due to their default iframe reloading bugs.
\r
58915 * <br><br>Example usage:
\r
58917 // Simple example rendered with default options:
\r
58918 Ext.QuickTips.init(); // enable tooltips
\r
58919 new Ext.form.HtmlEditor({
\r
58920 renderTo: Ext.getBody(),
\r
58925 // Passed via xtype into a container and with custom options:
\r
58926 Ext.QuickTips.init(); // enable tooltips
\r
58928 title: 'HTML Editor',
\r
58929 renderTo: Ext.getBody(),
\r
58935 xtype: 'htmleditor',
\r
58936 enableColors: false,
\r
58937 enableAlignments: false
\r
58942 * Create a new HtmlEditor
\r
58943 * @param {Object} config
\r
58944 * @xtype htmleditor
\r
58947 Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
\r
58949 * @cfg {Boolean} enableFormat Enable the bold, italic and underline buttons (defaults to true)
\r
58951 enableFormat : true,
\r
58953 * @cfg {Boolean} enableFontSize Enable the increase/decrease font size buttons (defaults to true)
\r
58955 enableFontSize : true,
\r
58957 * @cfg {Boolean} enableColors Enable the fore/highlight color buttons (defaults to true)
\r
58959 enableColors : true,
\r
58961 * @cfg {Boolean} enableAlignments Enable the left, center, right alignment buttons (defaults to true)
\r
58963 enableAlignments : true,
\r
58965 * @cfg {Boolean} enableLists Enable the bullet and numbered list buttons. Not available in Safari. (defaults to true)
\r
58967 enableLists : true,
\r
58969 * @cfg {Boolean} enableSourceEdit Enable the switch to source edit button. Not available in Safari. (defaults to true)
\r
58971 enableSourceEdit : true,
\r
58973 * @cfg {Boolean} enableLinks Enable the create link button. Not available in Safari. (defaults to true)
\r
58975 enableLinks : true,
\r
58977 * @cfg {Boolean} enableFont Enable font selection. Not available in Safari. (defaults to true)
\r
58979 enableFont : true,
\r
58981 * @cfg {String} createLinkText The default text for the create link prompt
\r
58983 createLinkText : 'Please enter the URL for the link:',
\r
58985 * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
\r
58987 defaultLinkValue : 'http:/'+'/',
\r
58989 * @cfg {Array} fontFamilies An array of available font families
\r
58995 'Times New Roman',
\r
58998 defaultFont: 'tahoma',
\r
59000 * @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
59002 defaultValue: (Ext.isOpera || Ext.isIE6) ? ' ' : '​',
\r
59004 // private properties
\r
59005 actionMode: 'wrap',
\r
59006 validationEvent : false,
\r
59007 deferHeight: true,
\r
59008 initialized : false,
\r
59009 activated : false,
\r
59010 sourceEditMode : false,
\r
59011 onFocus : Ext.emptyFn,
\r
59013 hideMode:'offsets',
\r
59014 defaultAutoCreate : {
\r
59016 style:"width:500px;height:300px;",
\r
59017 autocomplete: "off"
\r
59021 initComponent : function(){
\r
59024 * @event initialize
\r
59025 * Fires when the editor is fully initialized (including the iframe)
\r
59026 * @param {HtmlEditor} this
\r
59030 * @event activate
\r
59031 * Fires when the editor is first receives the focus. Any insertion must wait
\r
59032 * until after this event.
\r
59033 * @param {HtmlEditor} this
\r
59037 * @event beforesync
\r
59038 * Fires before the textarea is updated with content from the editor iframe. Return false
\r
59039 * to cancel the sync.
\r
59040 * @param {HtmlEditor} this
\r
59041 * @param {String} html
\r
59045 * @event beforepush
\r
59046 * Fires before the iframe editor is updated with content from the textarea. Return false
\r
59047 * to cancel the push.
\r
59048 * @param {HtmlEditor} this
\r
59049 * @param {String} html
\r
59054 * Fires when the textarea is updated with content from the editor iframe.
\r
59055 * @param {HtmlEditor} this
\r
59056 * @param {String} html
\r
59061 * Fires when the iframe editor is updated with content from the textarea.
\r
59062 * @param {HtmlEditor} this
\r
59063 * @param {String} html
\r
59067 * @event editmodechange
\r
59068 * Fires when the editor switches edit modes
\r
59069 * @param {HtmlEditor} this
\r
59070 * @param {Boolean} sourceEdit True if source edit, false if standard editing.
\r
59077 createFontOptions : function(){
\r
59078 var buf = [], fs = this.fontFamilies, ff, lc;
\r
59079 for(var i = 0, len = fs.length; i< len; i++){
\r
59081 lc = ff.toLowerCase();
\r
59083 '<option value="',lc,'" style="font-family:',ff,';"',
\r
59084 (this.defaultFont == lc ? ' selected="true">' : '>'),
\r
59089 return buf.join('');
\r
59093 * Protected method that will not generally be called directly. It
\r
59094 * is called when the editor creates its toolbar. Override this method if you need to
\r
59095 * add custom toolbar buttons.
\r
59096 * @param {HtmlEditor} editor
\r
59098 createToolbar : function(editor){
\r
59100 var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
\r
59102 function btn(id, toggle, handler){
\r
59105 cls : 'x-btn-icon',
\r
59106 iconCls: 'x-edit-'+id,
\r
59107 enableToggle:toggle !== false,
\r
59109 handler:handler||editor.relayBtnCmd,
\r
59110 clickEvent:'mousedown',
\r
59111 tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
\r
59112 overflowText: editor.buttonTips[id].title || undefined,
\r
59117 // build the toolbar
\r
59118 var tb = new Ext.Toolbar({
\r
59119 renderTo:this.wrap.dom.firstChild
\r
59122 // stop form submits
\r
59123 this.mon(tb.el, 'click', function(e){
\r
59124 e.preventDefault();
\r
59127 if(this.enableFont && !Ext.isSafari2){
\r
59128 this.fontSelect = tb.el.createChild({
\r
59130 cls:'x-font-select',
\r
59131 html: this.createFontOptions()
\r
59133 this.mon(this.fontSelect, 'change', function(){
\r
59134 var font = this.fontSelect.dom.value;
\r
59135 this.relayCmd('fontname', font);
\r
59136 this.deferFocus();
\r
59140 this.fontSelect.dom,
\r
59145 if(this.enableFormat){
\r
59153 if(this.enableFontSize){
\r
59156 btn('increasefontsize', false, this.adjustFont),
\r
59157 btn('decreasefontsize', false, this.adjustFont)
\r
59161 if(this.enableColors){
\r
59164 itemId:'forecolor',
\r
59165 cls:'x-btn-icon',
\r
59166 iconCls: 'x-edit-forecolor',
\r
59167 clickEvent:'mousedown',
\r
59168 tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
\r
59170 menu : new Ext.menu.ColorMenu({
\r
59171 allowReselect: true,
\r
59172 focus: Ext.emptyFn,
\r
59177 select: function(cp, color){
\r
59178 this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
\r
59179 this.deferFocus();
\r
59182 clickEvent:'mousedown'
\r
59185 itemId:'backcolor',
\r
59186 cls:'x-btn-icon',
\r
59187 iconCls: 'x-edit-backcolor',
\r
59188 clickEvent:'mousedown',
\r
59189 tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
\r
59191 menu : new Ext.menu.ColorMenu({
\r
59192 focus: Ext.emptyFn,
\r
59195 allowReselect: true,
\r
59198 select: function(cp, color){
\r
59200 this.execCmd('useCSS', false);
\r
59201 this.execCmd('hilitecolor', color);
\r
59202 this.execCmd('useCSS', true);
\r
59203 this.deferFocus();
\r
59205 this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
\r
59206 this.deferFocus();
\r
59210 clickEvent:'mousedown'
\r
59216 if(this.enableAlignments){
\r
59219 btn('justifyleft'),
\r
59220 btn('justifycenter'),
\r
59221 btn('justifyright')
\r
59225 if(!Ext.isSafari2){
\r
59226 if(this.enableLinks){
\r
59229 btn('createlink', false, this.createLink)
\r
59233 if(this.enableLists){
\r
59236 btn('insertorderedlist'),
\r
59237 btn('insertunorderedlist')
\r
59240 if(this.enableSourceEdit){
\r
59243 btn('sourceedit', true, function(btn){
\r
59244 this.toggleSourceEdit(!this.sourceEditMode);
\r
59254 * Protected method that will not generally be called directly. It
\r
59255 * is called when the editor initializes the iframe with HTML contents. Override this method if you
\r
59256 * want to change the initialization markup of the iframe (e.g. to add stylesheets).
\r
59258 getDocMarkup : function(){
\r
59259 return '<html><head><style type="text/css">body{border:0;margin:0;padding:3px;height:98%;cursor:text;}</style></head><body></body></html>';
\r
59263 getEditorBody : function(){
\r
59264 return this.doc.body || this.doc.documentElement;
\r
59268 getDoc : function(){
\r
59269 return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
\r
59273 getWin : function(){
\r
59274 return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
\r
59278 onRender : function(ct, position){
\r
59279 Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
\r
59280 this.el.dom.style.border = '0 none';
\r
59281 this.el.dom.setAttribute('tabIndex', -1);
\r
59282 this.el.addClass('x-hidden');
\r
59283 if(Ext.isIE){ // fix IE 1px bogus margin
\r
59284 this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
\r
59286 this.wrap = this.el.wrap({
\r
59287 cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
\r
59290 this.createToolbar(this);
\r
59292 this.disableItems(true);
\r
59293 // is this needed?
\r
59294 // this.tb.doLayout();
\r
59296 this.createIFrame();
\r
59299 var sz = this.el.getSize();
\r
59300 this.setSize(sz.width, this.height || sz.height);
\r
59302 this.resizeEl = this.positionEl = this.wrap;
\r
59305 createIFrame: function(){
\r
59306 var iframe = document.createElement('iframe');
\r
59307 iframe.name = Ext.id();
\r
59308 iframe.frameBorder = '0';
\r
59309 iframe.src = Ext.SSL_SECURE_URL;
\r
59310 this.wrap.dom.appendChild(iframe);
\r
59312 this.iframe = iframe;
\r
59314 this.monitorTask = Ext.TaskMgr.start({
\r
59315 run: this.checkDesignMode,
\r
59321 initFrame : function(){
\r
59322 Ext.TaskMgr.stop(this.monitorTask);
\r
59323 this.doc = this.getDoc();
\r
59324 this.win = this.getWin();
\r
59327 this.doc.write(this.getDocMarkup());
\r
59328 this.doc.close();
\r
59330 var task = { // must defer to wait for browser to be ready
\r
59331 run : function(){
\r
59332 if(this.doc.body || this.doc.readyState == 'complete'){
\r
59333 Ext.TaskMgr.stop(task);
\r
59334 this.doc.designMode="on";
\r
59335 this.initEditor.defer(10, this);
\r
59342 Ext.TaskMgr.start(task);
\r
59346 checkDesignMode : function(){
\r
59347 if(this.wrap && this.wrap.dom.offsetWidth){
\r
59348 var doc = this.getDoc();
\r
59352 if(!doc.editorInitialized || String(doc.designMode).toLowerCase() != 'on'){
\r
59353 this.initFrame();
\r
59358 disableItems: function(disabled){
\r
59359 if(this.fontSelect){
\r
59360 this.fontSelect.dom.disabled = disabled;
\r
59362 this.tb.items.each(function(item){
\r
59363 if(item.getItemId() != 'sourceedit'){
\r
59364 item.setDisabled(disabled);
\r
59370 onResize : function(w, h){
\r
59371 Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
\r
59372 if(this.el && this.iframe){
\r
59373 if(Ext.isNumber(w)){
\r
59374 var aw = w - this.wrap.getFrameWidth('lr');
\r
59375 this.el.setWidth(aw);
\r
59376 this.tb.setWidth(aw);
\r
59377 this.iframe.style.width = Math.max(aw, 0) + 'px';
\r
59379 if(Ext.isNumber(h)){
\r
59380 var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
\r
59381 this.el.setHeight(ah);
\r
59382 this.iframe.style.height = Math.max(ah, 0) + 'px';
\r
59384 this.getEditorBody().style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
\r
59391 * Toggles the editor between standard and source edit mode.
\r
59392 * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
\r
59394 toggleSourceEdit : function(sourceEditMode){
\r
59395 if(sourceEditMode === undefined){
\r
59396 sourceEditMode = !this.sourceEditMode;
\r
59398 this.sourceEditMode = sourceEditMode === true;
\r
59399 var btn = this.tb.items.get('sourceedit');
\r
59400 if(btn.pressed !== this.sourceEditMode){
\r
59401 btn.toggle(this.sourceEditMode);
\r
59402 if(!btn.xtbHidden){
\r
59406 if(this.sourceEditMode){
\r
59407 this.disableItems(true);
\r
59408 this.syncValue();
\r
59409 this.iframe.className = 'x-hidden';
\r
59410 this.el.removeClass('x-hidden');
\r
59411 this.el.dom.removeAttribute('tabIndex');
\r
59414 if(this.initialized){
\r
59415 this.disableItems(false);
\r
59417 this.pushValue();
\r
59418 this.iframe.className = '';
\r
59419 this.el.addClass('x-hidden');
\r
59420 this.el.dom.setAttribute('tabIndex', -1);
\r
59421 this.deferFocus();
\r
59423 var lastSize = this.lastSize;
\r
59425 delete this.lastSize;
\r
59426 this.setSize(lastSize);
\r
59428 this.fireEvent('editmodechange', this, this.sourceEditMode);
\r
59431 // private used internally
\r
59432 createLink : function(){
\r
59433 var url = prompt(this.createLinkText, this.defaultLinkValue);
\r
59434 if(url && url != 'http:/'+'/'){
\r
59435 this.relayCmd('createlink', url);
\r
59440 initEvents : function(){
\r
59441 this.originalValue = this.getValue();
\r
59445 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
\r
59448 markInvalid : Ext.emptyFn,
\r
59451 * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
\r
59454 clearInvalid : Ext.emptyFn,
\r
59456 // docs inherit from Field
\r
59457 setValue : function(v){
\r
59458 Ext.form.HtmlEditor.superclass.setValue.call(this, v);
\r
59459 this.pushValue();
\r
59464 * Protected method that will not generally be called directly. If you need/want
\r
59465 * custom HTML cleanup, this is the method you should override.
\r
59466 * @param {String} html The HTML to be cleaned
\r
59467 * @return {String} The cleaned HTML
\r
59469 cleanHtml: function(html) {
\r
59470 html = String(html);
\r
59471 if(Ext.isWebKit){ // strip safari nonsense
\r
59472 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
\r
59476 * Neat little hack. Strips out all the non-digit characters from the default
\r
59477 * value and compares it to the character code of the first character in the string
\r
59478 * because it can cause encoding issues when posted to the server.
\r
59480 if(html.charCodeAt(0) == this.defaultValue.replace(/\D/g, '')){
\r
59481 html = html.substring(1);
\r
59487 * Protected method that will not generally be called directly. Syncs the contents
\r
59488 * of the editor iframe with the textarea.
\r
59490 syncValue : function(){
\r
59491 if(this.initialized){
\r
59492 var bd = this.getEditorBody();
\r
59493 var html = bd.innerHTML;
\r
59494 if(Ext.isWebKit){
\r
59495 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
\r
59496 var m = bs.match(/text-align:(.*?);/i);
\r
59498 html = '<div style="'+m[0]+'">' + html + '</div>';
\r
59501 html = this.cleanHtml(html);
\r
59502 if(this.fireEvent('beforesync', this, html) !== false){
\r
59503 this.el.dom.value = html;
\r
59504 this.fireEvent('sync', this, html);
\r
59509 //docs inherit from Field
\r
59510 getValue : function() {
\r
59511 this[this.sourceEditMode ? 'pushValue' : 'syncValue']();
\r
59512 return Ext.form.HtmlEditor.superclass.getValue.call(this);
\r
59516 * Protected method that will not generally be called directly. Pushes the value of the textarea
\r
59517 * into the iframe editor.
\r
59519 pushValue : function(){
\r
59520 if(this.initialized){
\r
59521 var v = this.el.dom.value;
\r
59522 if(!this.activated && v.length < 1){
\r
59523 v = this.defaultValue;
\r
59525 if(this.fireEvent('beforepush', this, v) !== false){
\r
59526 this.getEditorBody().innerHTML = v;
\r
59528 // Gecko hack, see: https://bugzilla.mozilla.org/show_bug.cgi?id=232791#c8
\r
59529 var d = this.doc,
\r
59530 mode = d.designMode.toLowerCase();
\r
59532 d.designMode = mode.toggle('on', 'off');
\r
59533 d.designMode = mode;
\r
59535 this.fireEvent('push', this, v);
\r
59541 deferFocus : function(){
\r
59542 this.focus.defer(10, this);
\r
59545 // docs inherit from Field
\r
59546 focus : function(){
\r
59547 if(this.win && !this.sourceEditMode){
\r
59548 this.win.focus();
\r
59555 initEditor : function(){
\r
59556 //Destroying the component during/before initEditor can cause issues.
\r
59558 var dbody = this.getEditorBody();
\r
59559 var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
\r
59560 ss['background-attachment'] = 'fixed'; // w3c
\r
59561 dbody.bgProperties = 'fixed'; // ie
\r
59563 Ext.DomHelper.applyStyles(dbody, ss);
\r
59567 Ext.EventManager.removeAll(this.doc);
\r
59571 this.doc = this.getDoc();
\r
59573 Ext.EventManager.on(this.doc, {
\r
59574 'mousedown': this.onEditorEvent,
\r
59575 'dblclick': this.onEditorEvent,
\r
59576 'click': this.onEditorEvent,
\r
59577 'keyup': this.onEditorEvent,
\r
59583 Ext.EventManager.on(this.doc, 'keypress', this.applyCommand, this);
\r
59585 if(Ext.isIE || Ext.isWebKit || Ext.isOpera){
\r
59586 Ext.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
\r
59588 this.initialized = true;
\r
59589 this.fireEvent('initialize', this);
\r
59590 this.doc.editorInitialized = true;
\r
59591 this.pushValue();
\r
59596 onDestroy : function(){
\r
59597 if(this.monitorTask){
\r
59598 Ext.TaskMgr.stop(this.monitorTask);
\r
59600 if(this.rendered){
\r
59601 Ext.destroy(this.tb);
\r
59603 this.wrap.dom.innerHTML = '';
\r
59604 this.wrap.remove();
\r
59608 this.el.removeAllListeners();
\r
59609 this.el.remove();
\r
59614 Ext.EventManager.removeAll(this.doc);
\r
59615 for (var prop in this.doc){
\r
59616 delete this.doc[prop];
\r
59620 this.purgeListeners();
\r
59624 onFirstFocus : function(){
\r
59625 this.activated = true;
\r
59626 this.disableItems(false);
\r
59627 if(Ext.isGecko){ // prevent silly gecko errors
\r
59628 this.win.focus();
\r
59629 var s = this.win.getSelection();
\r
59630 if(!s.focusNode || s.focusNode.nodeType != 3){
\r
59631 var r = s.getRangeAt(0);
\r
59632 r.selectNodeContents(this.getEditorBody());
\r
59633 r.collapse(true);
\r
59634 this.deferFocus();
\r
59637 this.execCmd('useCSS', true);
\r
59638 this.execCmd('styleWithCSS', false);
\r
59641 this.fireEvent('activate', this);
\r
59645 adjustFont: function(btn){
\r
59646 var adjust = btn.getItemId() == 'increasefontsize' ? 1 : -1;
\r
59648 var v = parseInt(this.doc.queryCommandValue('FontSize') || 2, 10);
\r
59649 if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){
\r
59650 // Safari 3 values
\r
59651 // 1 = 10px, 2 = 13px, 3 = 16px, 4 = 18px, 5 = 24px, 6 = 32px
\r
59654 }else if(v <= 13){
\r
59656 }else if(v <= 16){
\r
59658 }else if(v <= 18){
\r
59660 }else if(v <= 24){
\r
59665 v = v.constrain(1, 6);
\r
59667 if(Ext.isSafari){ // safari
\r
59670 v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);
\r
59672 this.execCmd('FontSize', v);
\r
59676 onEditorEvent : function(e){
\r
59677 this.updateToolbar();
\r
59682 * Protected method that will not generally be called directly. It triggers
\r
59683 * a toolbar update by reading the markup state of the current selection in the editor.
\r
59685 updateToolbar: function(){
\r
59687 if(!this.activated){
\r
59688 this.onFirstFocus();
\r
59692 var btns = this.tb.items.map, doc = this.doc;
\r
59694 if(this.enableFont && !Ext.isSafari2){
\r
59695 var name = (this.doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();
\r
59696 if(name != this.fontSelect.dom.value){
\r
59697 this.fontSelect.dom.value = name;
\r
59700 if(this.enableFormat){
\r
59701 btns.bold.toggle(doc.queryCommandState('bold'));
\r
59702 btns.italic.toggle(doc.queryCommandState('italic'));
\r
59703 btns.underline.toggle(doc.queryCommandState('underline'));
\r
59705 if(this.enableAlignments){
\r
59706 btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
\r
59707 btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
\r
59708 btns.justifyright.toggle(doc.queryCommandState('justifyright'));
\r
59710 if(!Ext.isSafari2 && this.enableLists){
\r
59711 btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));
\r
59712 btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));
\r
59715 Ext.menu.MenuMgr.hideAll();
\r
59717 this.syncValue();
\r
59721 relayBtnCmd : function(btn){
\r
59722 this.relayCmd(btn.getItemId());
\r
59726 * Executes a Midas editor command on the editor document and performs necessary focus and
\r
59727 * toolbar updates. <b>This should only be called after the editor is initialized.</b>
\r
59728 * @param {String} cmd The Midas command
\r
59729 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
\r
59731 relayCmd : function(cmd, value){
\r
59734 this.execCmd(cmd, value);
\r
59735 this.updateToolbar();
\r
59736 }).defer(10, this);
\r
59740 * Executes a Midas editor command directly on the editor document.
\r
59741 * For visual commands, you should use {@link #relayCmd} instead.
\r
59742 * <b>This should only be called after the editor is initialized.</b>
\r
59743 * @param {String} cmd The Midas command
\r
59744 * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
\r
59746 execCmd : function(cmd, value){
\r
59747 this.doc.execCommand(cmd, false, value === undefined ? null : value);
\r
59748 this.syncValue();
\r
59752 applyCommand : function(e){
\r
59754 var c = e.getCharCode(), cmd;
\r
59756 c = String.fromCharCode(c);
\r
59765 cmd = 'underline';
\r
59769 this.win.focus();
\r
59770 this.execCmd(cmd);
\r
59771 this.deferFocus();
\r
59772 e.preventDefault();
\r
59779 * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
\r
59780 * to insert text.
\r
59781 * @param {String} text
\r
59783 insertAtCursor : function(text){
\r
59784 if(!this.activated){
\r
59788 this.win.focus();
\r
59789 var r = this.doc.selection.createRange();
\r
59791 r.collapse(true);
\r
59792 r.pasteHTML(text);
\r
59793 this.syncValue();
\r
59794 this.deferFocus();
\r
59797 this.win.focus();
\r
59798 this.execCmd('InsertHTML', text);
\r
59799 this.deferFocus();
\r
59804 fixKeys : function(){ // load time branching for fastest keydown performance
\r
59806 return function(e){
\r
59807 var k = e.getKey(), r;
\r
59810 r = this.doc.selection.createRange();
\r
59812 r.collapse(true);
\r
59813 r.pasteHTML(' ');
\r
59814 this.deferFocus();
\r
59816 }else if(k == e.ENTER){
\r
59817 r = this.doc.selection.createRange();
\r
59819 var target = r.parentElement();
\r
59820 if(!target || target.tagName.toLowerCase() != 'li'){
\r
59822 r.pasteHTML('<br />');
\r
59823 r.collapse(false);
\r
59829 }else if(Ext.isOpera){
\r
59830 return function(e){
\r
59831 var k = e.getKey();
\r
59834 this.win.focus();
\r
59835 this.execCmd('InsertHTML',' ');
\r
59836 this.deferFocus();
\r
59839 }else if(Ext.isWebKit){
\r
59840 return function(e){
\r
59841 var k = e.getKey();
\r
59844 this.execCmd('InsertText','\t');
\r
59845 this.deferFocus();
\r
59846 }else if(k == e.ENTER){
\r
59848 this.execCmd('InsertHtml','<br /><br />');
\r
59849 this.deferFocus();
\r
59856 * Returns the editor's toolbar. <b>This is only available after the editor has been rendered.</b>
\r
59857 * @return {Ext.Toolbar}
\r
59859 getToolbar : function(){
\r
59864 * Object collection of toolbar tooltips for the buttons in the editor. The key
\r
59865 * is the command id associated with that button and the value is a valid QuickTips object.
\r
59870 title: 'Bold (Ctrl+B)',
\r
59871 text: 'Make the selected text bold.',
\r
59872 cls: 'x-html-editor-tip'
\r
59875 title: 'Italic (Ctrl+I)',
\r
59876 text: 'Make the selected text italic.',
\r
59877 cls: 'x-html-editor-tip'
\r
59885 title: 'Bold (Ctrl+B)',
\r
59886 text: 'Make the selected text bold.',
\r
59887 cls: 'x-html-editor-tip'
\r
59890 title: 'Italic (Ctrl+I)',
\r
59891 text: 'Make the selected text italic.',
\r
59892 cls: 'x-html-editor-tip'
\r
59895 title: 'Underline (Ctrl+U)',
\r
59896 text: 'Underline the selected text.',
\r
59897 cls: 'x-html-editor-tip'
\r
59899 increasefontsize : {
\r
59900 title: 'Grow Text',
\r
59901 text: 'Increase the font size.',
\r
59902 cls: 'x-html-editor-tip'
\r
59904 decreasefontsize : {
\r
59905 title: 'Shrink Text',
\r
59906 text: 'Decrease the font size.',
\r
59907 cls: 'x-html-editor-tip'
\r
59910 title: 'Text Highlight Color',
\r
59911 text: 'Change the background color of the selected text.',
\r
59912 cls: 'x-html-editor-tip'
\r
59915 title: 'Font Color',
\r
59916 text: 'Change the color of the selected text.',
\r
59917 cls: 'x-html-editor-tip'
\r
59920 title: 'Align Text Left',
\r
59921 text: 'Align text to the left.',
\r
59922 cls: 'x-html-editor-tip'
\r
59924 justifycenter : {
\r
59925 title: 'Center Text',
\r
59926 text: 'Center text in the editor.',
\r
59927 cls: 'x-html-editor-tip'
\r
59930 title: 'Align Text Right',
\r
59931 text: 'Align text to the right.',
\r
59932 cls: 'x-html-editor-tip'
\r
59934 insertunorderedlist : {
\r
59935 title: 'Bullet List',
\r
59936 text: 'Start a bulleted list.',
\r
59937 cls: 'x-html-editor-tip'
\r
59939 insertorderedlist : {
\r
59940 title: 'Numbered List',
\r
59941 text: 'Start a numbered list.',
\r
59942 cls: 'x-html-editor-tip'
\r
59945 title: 'Hyperlink',
\r
59946 text: 'Make the selected text a hyperlink.',
\r
59947 cls: 'x-html-editor-tip'
\r
59950 title: 'Source Edit',
\r
59951 text: 'Switch to source editing mode.',
\r
59952 cls: 'x-html-editor-tip'
\r
59956 // hide stuff that is not compatible
\r
59970 * @event specialkey
\r
59974 * @cfg {String} fieldClass @hide
\r
59977 * @cfg {String} focusClass @hide
\r
59980 * @cfg {String} autoCreate @hide
\r
59983 * @cfg {String} inputType @hide
\r
59986 * @cfg {String} invalidClass @hide
\r
59989 * @cfg {String} invalidText @hide
\r
59992 * @cfg {String} msgFx @hide
\r
59995 * @cfg {String} validateOnBlur @hide
\r
59998 * @cfg {Boolean} allowDomMove @hide
\r
60001 * @cfg {String} applyTo @hide
\r
60004 * @cfg {String} autoHeight @hide
\r
60007 * @cfg {String} autoWidth @hide
\r
60010 * @cfg {String} cls @hide
\r
60013 * @cfg {String} disabled @hide
\r
60016 * @cfg {String} disabledClass @hide
\r
60019 * @cfg {String} msgTarget @hide
\r
60022 * @cfg {String} readOnly @hide
\r
60025 * @cfg {String} style @hide
\r
60028 * @cfg {String} validationDelay @hide
\r
60031 * @cfg {String} validationEvent @hide
\r
60034 * @cfg {String} tabIndex @hide
\r
60037 * @property disabled
\r
60041 * @method applyToMarkup
\r
60045 * @method disable
\r
60053 * @method validate
\r
60061 * @method setDisabled
\r
60069 Ext.reg('htmleditor', Ext.form.HtmlEditor);/**
\r
60070 * @class Ext.form.TimeField
\r
60071 * @extends Ext.form.ComboBox
\r
60072 * Provides a time input field with a time dropdown and automatic time validation. Example usage:
\r
60074 new Ext.form.TimeField({
\r
60075 minValue: '9:00 AM',
\r
60076 maxValue: '6:00 PM',
\r
60081 * Create a new TimeField
\r
60082 * @param {Object} config
\r
60083 * @xtype timefield
\r
60085 Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
\r
60087 * @cfg {Date/String} minValue
\r
60088 * The minimum allowed time. Can be either a Javascript date object with a valid time value or a string
\r
60089 * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
\r
60093 * @cfg {Date/String} maxValue
\r
60094 * The maximum allowed time. Can be either a Javascript date object with a valid time value or a string
\r
60095 * time in a valid format -- see {@link #format} and {@link #altFormats} (defaults to null).
\r
60099 * @cfg {String} minText
\r
60100 * The error text to display when the date in the cell is before minValue (defaults to
\r
60101 * 'The time in this field must be equal to or after {0}').
\r
60103 minText : "The time in this field must be equal to or after {0}",
\r
60105 * @cfg {String} maxText
\r
60106 * The error text to display when the time is after maxValue (defaults to
\r
60107 * 'The time in this field must be equal to or before {0}').
\r
60109 maxText : "The time in this field must be equal to or before {0}",
\r
60111 * @cfg {String} invalidText
\r
60112 * The error text to display when the time in the field is invalid (defaults to
\r
60113 * '{value} is not a valid time').
\r
60115 invalidText : "{0} is not a valid time",
\r
60117 * @cfg {String} format
\r
60118 * The default time format string which can be overriden for localization support. The format must be
\r
60119 * valid according to {@link Date#parseDate} (defaults to 'g:i A', e.g., '3:15 PM'). For 24-hour time
\r
60120 * format try 'H:i' instead.
\r
60122 format : "g:i A",
\r
60124 * @cfg {String} altFormats
\r
60125 * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
\r
60126 * 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
60128 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
60130 * @cfg {Number} increment
\r
60131 * The number of minutes between each time value in the list (defaults to 15).
\r
60135 // private override
\r
60137 // private override
\r
60138 triggerAction: 'all',
\r
60139 // private override
\r
60140 typeAhead: false,
\r
60142 // private - This is the date to use when generating time values in the absence of either minValue
\r
60143 // or maxValue. Using the current date causes DST issues on DST boundary dates, so this is an
\r
60144 // arbitrary "safe" date that can be any date aside from DST boundary dates.
\r
60145 initDate: '1/1/2008',
\r
60148 initComponent : function(){
\r
60149 if(typeof this.minValue == "string"){
\r
60150 this.minValue = this.parseDate(this.minValue);
\r
60152 if(typeof this.maxValue == "string"){
\r
60153 this.maxValue = this.parseDate(this.maxValue);
\r
60157 var min = this.parseDate(this.minValue) || new Date(this.initDate).clearTime();
\r
60158 var max = this.parseDate(this.maxValue) || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1);
\r
60160 while(min <= max){
\r
60161 times.push(min.dateFormat(this.format));
\r
60162 min = min.add('mi', this.increment);
\r
60164 this.store = times;
\r
60166 Ext.form.TimeField.superclass.initComponent.call(this);
\r
60169 // inherited docs
\r
60170 getValue : function(){
\r
60171 var v = Ext.form.TimeField.superclass.getValue.call(this);
\r
60172 return this.formatDate(this.parseDate(v)) || '';
\r
60175 // inherited docs
\r
60176 setValue : function(value){
\r
60177 return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
\r
60180 // private overrides
\r
60181 validateValue : Ext.form.DateField.prototype.validateValue,
\r
60182 parseDate : Ext.form.DateField.prototype.parseDate,
\r
60183 formatDate : Ext.form.DateField.prototype.formatDate,
\r
60186 beforeBlur : function(){
\r
60187 var v = this.parseDate(this.getRawValue());
\r
60189 this.setValue(v.dateFormat(this.format));
\r
60191 Ext.form.TimeField.superclass.beforeBlur.call(this);
\r
60195 * @cfg {Boolean} grow @hide
\r
60198 * @cfg {Number} growMin @hide
\r
60201 * @cfg {Number} growMax @hide
\r
60205 * @method autoSize
\r
60208 Ext.reg('timefield', Ext.form.TimeField);/**
60209 * @class Ext.form.Label
60210 * @extends Ext.BoxComponent
60211 * Basic Label field.
60213 * Creates a new Label
60214 * @param {Ext.Element/String/Object} config The configuration options. If an element is passed, it is set as the internal
60215 * 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
60216 * and is used as the component id. Otherwise, it is assumed to be a standard config object and is applied to the component.
60219 Ext.form.Label = Ext.extend(Ext.BoxComponent, {
60221 * @cfg {String} text The plain text to display within the label (defaults to ''). If you need to include HTML
60222 * tags within the label's innerHTML, use the {@link #html} config instead.
60225 * @cfg {String} forId The id of the input element to which this label will be bound via the standard HTML 'for'
60226 * attribute. If not specified, the attribute will not be added to the label.
60229 * @cfg {String} html An HTML fragment that will be used as the label's innerHTML (defaults to '').
60230 * Note that if {@link #text} is specified it will take precedence and this value will be ignored.
60234 onRender : function(ct, position){
60236 this.el = document.createElement('label');
60237 this.el.id = this.getId();
60238 this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
60240 this.el.setAttribute('for', this.forId);
60243 Ext.form.Label.superclass.onRender.call(this, ct, position);
60247 * Updates the label's innerHTML with the specified string.
60248 * @param {String} text The new label text
60249 * @param {Boolean} encode (optional) False to skip HTML-encoding the text when rendering it
60250 * to the label (defaults to true which encodes the value). This might be useful if you want to include
60251 * tags in the label's innerHTML rather than rendering them as string literals per the default logic.
60252 * @return {Label} this
60254 setText : function(t, encode){
60255 var e = encode === false;
60256 this[!e ? 'text' : 'html'] = t;
60257 delete this[e ? 'text' : 'html'];
60259 this.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(t) : t;
60265 Ext.reg('label', Ext.form.Label);/**
\r
60266 * @class Ext.form.Action
\r
60267 * <p>The subclasses of this class provide actions to perform upon {@link Ext.form.BasicForm Form}s.</p>
\r
60268 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
\r
60269 * the Form needs to perform an action such as submit or load. The Configuration options
\r
60270 * listed for this class are set through the Form's action methods: {@link Ext.form.BasicForm#submit submit},
\r
60271 * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}</p>
\r
60272 * <p>The instance of Action which performed the action is passed to the success
\r
60273 * and failure callbacks of the Form's action methods ({@link Ext.form.BasicForm#submit submit},
\r
60274 * {@link Ext.form.BasicForm#load load} and {@link Ext.form.BasicForm#doAction doAction}),
\r
60275 * and to the {@link Ext.form.BasicForm#actioncomplete actioncomplete} and
\r
60276 * {@link Ext.form.BasicForm#actionfailed actionfailed} event handlers.</p>
\r
60278 Ext.form.Action = function(form, options){
\r
60279 this.form = form;
\r
60280 this.options = options || {};
\r
60284 * Failure type returned when client side validation of the Form fails
\r
60285 * thus aborting a submit action. Client side validation is performed unless
\r
60286 * {@link #clientValidation} is explicitly set to <tt>false</tt>.
\r
60290 Ext.form.Action.CLIENT_INVALID = 'client';
\r
60292 * <p>Failure type returned when server side processing fails and the {@link #result}'s
\r
60293 * <tt style="font-weight:bold">success</tt> property is set to <tt>false</tt>.</p>
\r
60294 * <p>In the case of a form submission, field-specific error messages may be returned in the
\r
60295 * {@link #result}'s <tt style="font-weight:bold">errors</tt> property.</p>
\r
60299 Ext.form.Action.SERVER_INVALID = 'server';
\r
60301 * Failure type returned when a communication error happens when attempting
\r
60302 * to send a request to the remote server. The {@link #response} may be examined to
\r
60303 * provide further information.
\r
60307 Ext.form.Action.CONNECT_FAILURE = 'connect';
\r
60309 * Failure type returned when the response's <tt style="font-weight:bold">success</tt>
\r
60310 * property is set to <tt>false</tt>, or no field values are returned in the response's
\r
60311 * <tt style="font-weight:bold">data</tt> property.
\r
60315 Ext.form.Action.LOAD_FAILURE = 'load';
\r
60317 Ext.form.Action.prototype = {
\r
60319 * @cfg {String} url The URL that the Action is to invoke.
\r
60322 * @cfg {Boolean} reset When set to <tt><b>true</b></tt>, causes the Form to be
\r
60323 * {@link Ext.form.BasicForm.reset reset} on Action success. If specified, this happens
\r
60324 * <b>before</b> the {@link #success} callback is called and before the Form's
\r
60325 * {@link Ext.form.BasicForm.actioncomplete actioncomplete} event fires.
\r
60328 * @cfg {String} method The HTTP method to use to access the requested URL. Defaults to the
\r
60329 * {@link Ext.form.BasicForm}'s method, or if that is not specified, the underlying DOM form's method.
\r
60332 * @cfg {Mixed} params <p>Extra parameter values to pass. These are added to the Form's
\r
60333 * {@link Ext.form.BasicForm#baseParams} and passed to the specified URL along with the Form's
\r
60334 * input fields.</p>
\r
60335 * <p>Parameters are encoded as standard HTTP parameters using {@link Ext#urlEncode}.</p>
\r
60338 * @cfg {Number} timeout The number of seconds to wait for a server response before
\r
60339 * failing with the {@link #failureType} as {@link #Action.CONNECT_FAILURE}. If not specified,
\r
60340 * defaults to the configured <tt>{@link Ext.form.BasicForm#timeout timeout}</tt> of the
\r
60341 * {@link Ext.form.BasicForm form}.
\r
60344 * @cfg {Function} success The function to call when a valid success return packet is recieved.
\r
60345 * The function is passed the following parameters:<ul class="mdetail-params">
\r
60346 * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
\r
60347 * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. The {@link #result}
\r
60348 * property of this object may be examined to perform custom postprocessing.</div></li>
\r
60352 * @cfg {Function} failure The function to call when a failure packet was recieved, or when an
\r
60353 * error ocurred in the Ajax communication.
\r
60354 * The function is passed the following parameters:<ul class="mdetail-params">
\r
60355 * <li><b>form</b> : Ext.form.BasicForm<div class="sub-desc">The form that requested the action</div></li>
\r
60356 * <li><b>action</b> : Ext.form.Action<div class="sub-desc">The Action class. If an Ajax
\r
60357 * error ocurred, the failure type will be in {@link #failureType}. The {@link #result}
\r
60358 * property of this object may be examined to perform custom postprocessing.</div></li>
\r
60362 * @cfg {Object} scope The scope in which to call the callback functions (The <tt>this</tt> reference
\r
60363 * for the callback functions).
\r
60366 * @cfg {String} waitMsg The message to be displayed by a call to {@link Ext.MessageBox#wait}
\r
60367 * during the time the action is being processed.
\r
60370 * @cfg {String} waitTitle The title to be displayed by a call to {@link Ext.MessageBox#wait}
\r
60371 * during the time the action is being processed.
\r
60375 * The type of action this Action instance performs.
\r
60376 * Currently only "submit" and "load" are supported.
\r
60379 type : 'default',
\r
60381 * The type of failure detected will be one of these: {@link #CLIENT_INVALID},
\r
60382 * {@link #SERVER_INVALID}, {@link #CONNECT_FAILURE}, or {@link #LOAD_FAILURE}. Usage:
\r
60384 var fp = new Ext.form.FormPanel({
\r
60389 handler: function(){
\r
60390 if(fp.getForm().isValid()){
\r
60391 fp.getForm().submit({
\r
60392 url: 'form-submit.php',
\r
60393 waitMsg: 'Submitting your data...',
\r
60394 success: function(form, action){
\r
60395 // server responded with success = true
\r
60396 var result = action.{@link #result};
\r
60398 failure: function(form, action){
\r
60399 if (action.{@link #failureType} === Ext.form.Action.{@link #CONNECT_FAILURE}) {
\r
60400 Ext.Msg.alert('Error',
\r
60401 'Status:'+action.{@link #response}.status+': '+
\r
60402 action.{@link #response}.statusText);
\r
60404 if (action.failureType === Ext.form.Action.{@link #SERVER_INVALID}){
\r
60405 // server responded with success = false
\r
60406 Ext.Msg.alert('Invalid', action.{@link #result}.errormsg);
\r
60414 handler: function(){
\r
60415 fp.getForm().reset();
\r
60419 * @property failureType
\r
60423 * The XMLHttpRequest object used to perform the action.
\r
60424 * @property response
\r
60428 * The decoded response object containing a boolean <tt style="font-weight:bold">success</tt> property and
\r
60429 * other, action-specific properties.
\r
60430 * @property result
\r
60434 // interface method
\r
60435 run : function(options){
\r
60439 // interface method
\r
60440 success : function(response){
\r
60444 // interface method
\r
60445 handleResponse : function(response){
\r
60449 // default connection failure
\r
60450 failure : function(response){
\r
60451 this.response = response;
\r
60452 this.failureType = Ext.form.Action.CONNECT_FAILURE;
\r
60453 this.form.afterAction(this, false);
\r
60457 // shared code among all Actions to validate that there was a response
\r
60458 // with either responseText or responseXml
\r
60459 processResponse : function(response){
\r
60460 this.response = response;
\r
60461 if(!response.responseText && !response.responseXML){
\r
60464 this.result = this.handleResponse(response);
\r
60465 return this.result;
\r
60468 // utility functions used internally
\r
60469 getUrl : function(appendParams){
\r
60470 var url = this.options.url || this.form.url || this.form.el.dom.action;
\r
60471 if(appendParams){
\r
60472 var p = this.getParams();
\r
60474 url = Ext.urlAppend(url, p);
\r
60481 getMethod : function(){
\r
60482 return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
\r
60486 getParams : function(){
\r
60487 var bp = this.form.baseParams;
\r
60488 var p = this.options.params;
\r
60490 if(typeof p == "object"){
\r
60491 p = Ext.urlEncode(Ext.applyIf(p, bp));
\r
60492 }else if(typeof p == 'string' && bp){
\r
60493 p += '&' + Ext.urlEncode(bp);
\r
60496 p = Ext.urlEncode(bp);
\r
60502 createCallback : function(opts){
\r
60503 var opts = opts || {};
\r
60505 success: this.success,
\r
60506 failure: this.failure,
\r
60508 timeout: (opts.timeout*1000) || (this.form.timeout*1000),
\r
60509 upload: this.form.fileUpload ? this.success : undefined
\r
60515 * @class Ext.form.Action.Submit
\r
60516 * @extends Ext.form.Action
\r
60517 * <p>A class which handles submission of data from {@link Ext.form.BasicForm Form}s
\r
60518 * and processes the returned response.</p>
\r
60519 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
\r
60520 * {@link Ext.form.BasicForm#submit submit}ting.</p>
\r
60521 * <p><u><b>Response Packet Criteria</b></u></p>
\r
60522 * <p>A response packet may contain:
\r
60523 * <div class="mdetail-params"><ul>
\r
60524 * <li><b><code>success</code></b> property : Boolean
\r
60525 * <div class="sub-desc">The <code>success</code> property is required.</div></li>
\r
60526 * <li><b><code>errors</code></b> property : Object
\r
60527 * <div class="sub-desc"><div class="sub-desc">The <code>errors</code> property,
\r
60528 * which is optional, contains error messages for invalid fields.</div></li>
\r
60530 * <p><u><b>JSON Packets</b></u></p>
\r
60531 * <p>By default, response packets are assumed to be JSON, so a typical response
\r
60532 * packet may look like this:</p><pre><code>
\r
60536 clientCode: "Client not found",
\r
60537 portOfLoading: "This field must not be null"
\r
60540 * <p>Other data may be placed into the response for processing by the {@link Ext.form.BasicForm}'s callback
\r
60541 * or event handler methods. The object decoded from this JSON is available in the
\r
60542 * {@link Ext.form.Action#result result} property.</p>
\r
60543 * <p>Alternatively, if an {@link #errorReader} is specified as an {@link Ext.data.XmlReader XmlReader}:</p><pre><code>
\r
60544 errorReader: new Ext.data.XmlReader({
\r
60545 record : 'field',
\r
60546 success: '@success'
\r
60552 * <p>then the results may be sent back in XML format:</p><pre><code>
\r
60553 <?xml version="1.0" encoding="UTF-8"?>
\r
60554 <message success="false">
\r
60557 <id>clientCode</id>
\r
60558 <msg><![CDATA[Code not found. <br /><i>This is a test validation message from the server </i>]]></msg>
\r
60561 <id>portOfLoading</id>
\r
60562 <msg><![CDATA[Port not found. <br /><i>This is a test validation message from the server </i>]]></msg>
\r
60567 * <p>Other elements may be placed into the response XML for processing by the {@link Ext.form.BasicForm}'s callback
\r
60568 * or event handler methods. The XML document is available in the {@link #errorReader}'s {@link Ext.data.XmlReader#xmlData xmlData} property.</p>
\r
60570 Ext.form.Action.Submit = function(form, options){
\r
60571 Ext.form.Action.Submit.superclass.constructor.call(this, form, options);
\r
60574 Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
\r
60576 * @cfg {Ext.data.DataReader} errorReader <p><b>Optional. JSON is interpreted with
\r
60577 * no need for an errorReader.</b></p>
\r
60578 * <p>A Reader which reads a single record from the returned data. The DataReader's
\r
60579 * <b>success</b> property specifies how submission success is determined. The Record's
\r
60580 * data provides the error messages to apply to any invalid form Fields.</p>
\r
60583 * @cfg {boolean} clientValidation Determines whether a Form's fields are validated
\r
60584 * in a final call to {@link Ext.form.BasicForm#isValid isValid} prior to submission.
\r
60585 * Pass <tt>false</tt> in the Form's submit options to prevent this. If not defined, pre-submission field validation
\r
60591 run : function(){
\r
60592 var o = this.options;
\r
60593 var method = this.getMethod();
\r
60594 var isGet = method == 'GET';
\r
60595 if(o.clientValidation === false || this.form.isValid()){
\r
60596 Ext.Ajax.request(Ext.apply(this.createCallback(o), {
\r
60597 form:this.form.el.dom,
\r
60598 url:this.getUrl(isGet),
\r
60600 headers: o.headers,
\r
60601 params:!isGet ? this.getParams() : null,
\r
60602 isUpload: this.form.fileUpload
\r
60604 }else if (o.clientValidation !== false){ // client validation failed
\r
60605 this.failureType = Ext.form.Action.CLIENT_INVALID;
\r
60606 this.form.afterAction(this, false);
\r
60611 success : function(response){
\r
60612 var result = this.processResponse(response);
\r
60613 if(result === true || result.success){
\r
60614 this.form.afterAction(this, true);
\r
60617 if(result.errors){
\r
60618 this.form.markInvalid(result.errors);
\r
60620 this.failureType = Ext.form.Action.SERVER_INVALID;
\r
60621 this.form.afterAction(this, false);
\r
60625 handleResponse : function(response){
\r
60626 if(this.form.errorReader){
\r
60627 var rs = this.form.errorReader.read(response);
\r
60630 for(var i = 0, len = rs.records.length; i < len; i++) {
\r
60631 var r = rs.records[i];
\r
60632 errors[i] = r.data;
\r
60635 if(errors.length < 1){
\r
60639 success : rs.success,
\r
60643 return Ext.decode(response.responseText);
\r
60649 * @class Ext.form.Action.Load
\r
60650 * @extends Ext.form.Action
\r
60651 * <p>A class which handles loading of data from a server into the Fields of an {@link Ext.form.BasicForm}.</p>
\r
60652 * <p>Instances of this class are only created by a {@link Ext.form.BasicForm Form} when
\r
60653 * {@link Ext.form.BasicForm#load load}ing.</p>
\r
60654 * <p><u><b>Response Packet Criteria</b></u></p>
\r
60655 * <p>A response packet <b>must</b> contain:
\r
60656 * <div class="mdetail-params"><ul>
\r
60657 * <li><b><code>success</code></b> property : Boolean</li>
\r
60658 * <li><b><code>data</code></b> property : Object</li>
\r
60659 * <div class="sub-desc">The <code>data</code> property contains the values of Fields to load.
\r
60660 * The individual value object for each Field is passed to the Field's
\r
60661 * {@link Ext.form.Field#setValue setValue} method.</div></li>
\r
60663 * <p><u><b>JSON Packets</b></u></p>
\r
60664 * <p>By default, response packets are assumed to be JSON, so for the following form load call:<pre><code>
\r
60665 var myFormPanel = new Ext.form.FormPanel({
\r
60666 title: 'Client and routing info',
\r
60668 fieldLabel: 'Client',
\r
60669 name: 'clientName'
\r
60671 fieldLabel: 'Port of loading',
\r
60672 name: 'portOfLoading'
\r
60674 fieldLabel: 'Port of discharge',
\r
60675 name: 'portOfDischarge'
\r
60678 myFormPanel.{@link Ext.form.FormPanel#getForm getForm}().{@link Ext.form.BasicForm#load load}({
\r
60679 url: '/getRoutingInfo.php',
\r
60681 consignmentRef: myConsignmentRef
\r
60683 failure: function(form, action) {
\r
60684 Ext.Msg.alert("Load failed", action.result.errorMessage);
\r
60688 * a <b>success response</b> packet may look like this:</p><pre><code>
\r
60692 clientName: "Fred. Olsen Lines",
\r
60693 portOfLoading: "FXT",
\r
60694 portOfDischarge: "OSL"
\r
60697 * while a <b>failure response</b> packet may look like this:</p><pre><code>
\r
60700 errorMessage: "Consignment reference not found"
\r
60702 * <p>Other data may be placed into the response for processing the {@link Ext.form.BasicForm Form}'s
\r
60703 * callback or event handler methods. The object decoded from this JSON is available in the
\r
60704 * {@link Ext.form.Action#result result} property.</p>
\r
60706 Ext.form.Action.Load = function(form, options){
\r
60707 Ext.form.Action.Load.superclass.constructor.call(this, form, options);
\r
60708 this.reader = this.form.reader;
\r
60711 Ext.extend(Ext.form.Action.Load, Ext.form.Action, {
\r
60716 run : function(){
\r
60717 Ext.Ajax.request(Ext.apply(
\r
60718 this.createCallback(this.options), {
\r
60719 method:this.getMethod(),
\r
60720 url:this.getUrl(false),
\r
60721 headers: this.options.headers,
\r
60722 params:this.getParams()
\r
60727 success : function(response){
\r
60728 var result = this.processResponse(response);
\r
60729 if(result === true || !result.success || !result.data){
\r
60730 this.failureType = Ext.form.Action.LOAD_FAILURE;
\r
60731 this.form.afterAction(this, false);
\r
60734 this.form.clearInvalid();
\r
60735 this.form.setValues(result.data);
\r
60736 this.form.afterAction(this, true);
\r
60740 handleResponse : function(response){
\r
60741 if(this.form.reader){
\r
60742 var rs = this.form.reader.read(response);
\r
60743 var data = rs.records && rs.records[0] ? rs.records[0].data : null;
\r
60745 success : rs.success,
\r
60749 return Ext.decode(response.responseText);
\r
60756 * @class Ext.form.Action.DirectLoad
\r
60757 * @extends Ext.form.Action.Load
\r
60758 * <p>Provides Ext.direct support for loading form data.</p>
\r
60759 * <p>This example illustrates usage of Ext.Direct to <b>load</b> a form through Ext.Direct.</p>
\r
60761 var myFormPanel = new Ext.form.FormPanel({
\r
60762 // configs for FormPanel
\r
60763 title: 'Basic Information',
\r
60764 renderTo: document.body,
\r
60765 width: 300, height: 160,
\r
60768 // configs apply to child items
\r
60769 defaults: {anchor: '100%'},
\r
60770 defaultType: 'textfield',
\r
60772 fieldLabel: 'Name',
\r
60775 fieldLabel: 'Email',
\r
60778 fieldLabel: 'Company',
\r
60782 // configs for BasicForm
\r
60784 // The server-side method to call for load() requests
\r
60785 load: Profile.getBasicInfo,
\r
60786 // The server-side must mark the submit handler as a 'formHandler'
\r
60787 submit: Profile.updateBasicInfo
\r
60789 // specify the order for the passed params
\r
60790 paramOrder: ['uid', 'foo']
\r
60794 myFormPanel.getForm().load({
\r
60795 // pass 2 arguments to server side getBasicInfo method (len=2)
\r
60802 * The data packet sent to the server will resemble something like:
\r
60806 "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
\r
60807 "data":[34,"bar"] // note the order of the params
\r
60811 * The form will process a data packet returned by the server that is similar
\r
60812 * to the following format:
\r
60816 "action":"Profile","method":"getBasicInfo","type":"rpc","tid":2,
\r
60820 "name":"Fred Flintstone",
\r
60821 "company":"Slate Rock and Gravel",
\r
60822 "email":"fred.flintstone@slaterg.com"
\r
60829 Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
\r
60830 constructor: function(form, opts) {
\r
60831 Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts);
\r
60833 type : 'directload',
\r
60835 run : function(){
\r
60836 var args = this.getParams();
\r
60837 args.push(this.success, this);
\r
60838 this.form.api.load.apply(window, args);
\r
60841 getParams : function() {
\r
60842 var buf = [], o = {};
\r
60843 var bp = this.form.baseParams;
\r
60844 var p = this.options.params;
\r
60845 Ext.apply(o, p, bp);
\r
60846 var paramOrder = this.form.paramOrder;
\r
60848 for(var i = 0, len = paramOrder.length; i < len; i++){
\r
60849 buf.push(o[paramOrder[i]]);
\r
60851 }else if(this.form.paramsAsHash){
\r
60856 // Direct actions have already been processed and therefore
\r
60857 // we can directly set the result; Direct Actions do not have
\r
60858 // a this.response property.
\r
60859 processResponse : function(result) {
\r
60860 this.result = result;
\r
60864 success : function(response, trans){
\r
60865 if(trans.type == Ext.Direct.exceptions.SERVER){
\r
60868 Ext.form.Action.DirectLoad.superclass.success.call(this, response);
\r
60873 * @class Ext.form.Action.DirectSubmit
\r
60874 * @extends Ext.form.Action.Submit
\r
60875 * <p>Provides Ext.direct support for submitting form data.</p>
\r
60876 * <p>This example illustrates usage of Ext.Direct to <b>submit</b> a form through Ext.Direct.</p>
\r
60878 var myFormPanel = new Ext.form.FormPanel({
\r
60879 // configs for FormPanel
\r
60880 title: 'Basic Information',
\r
60881 renderTo: document.body,
\r
60882 width: 300, height: 160,
\r
60886 handler: function(){
\r
60887 myFormPanel.getForm().submit({
\r
60896 // configs apply to child items
\r
60897 defaults: {anchor: '100%'},
\r
60898 defaultType: 'textfield',
\r
60900 fieldLabel: 'Name',
\r
60903 fieldLabel: 'Email',
\r
60906 fieldLabel: 'Company',
\r
60910 // configs for BasicForm
\r
60912 // The server-side method to call for load() requests
\r
60913 load: Profile.getBasicInfo,
\r
60914 // The server-side must mark the submit handler as a 'formHandler'
\r
60915 submit: Profile.updateBasicInfo
\r
60917 // specify the order for the passed params
\r
60918 paramOrder: ['uid', 'foo']
\r
60921 * The data packet sent to the server will resemble something like:
\r
60924 "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
\r
60928 "extAction":"Profile","extMethod":"updateBasicInfo",
\r
60929 "extType":"rpc","extTID":"6","extUpload":"false",
\r
60930 "name":"Aaron Conran","email":"aaron@extjs.com","company":"Ext JS, LLC"
\r
60935 * The form will process a data packet returned by the server that is similar
\r
60936 * to the following:
\r
60938 // sample success packet (batched requests)
\r
60941 "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":3,
\r
60948 // sample failure packet (one request)
\r
60950 "action":"Profile","method":"updateBasicInfo","type":"rpc","tid":"6",
\r
60953 "email":"already taken"
\r
60960 * Also see the discussion in {@link Ext.form.Action.DirectLoad}.
\r
60962 Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
\r
60963 constructor : function(form, opts) {
\r
60964 Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts);
\r
60966 type : 'directsubmit',
\r
60967 // override of Submit
\r
60968 run : function(){
\r
60969 var o = this.options;
\r
60970 if(o.clientValidation === false || this.form.isValid()){
\r
60971 // tag on any additional params to be posted in the
\r
60973 this.success.params = this.getParams();
\r
60974 this.form.api.submit(this.form.el.dom, this.success, this);
\r
60975 }else if (o.clientValidation !== false){ // client validation failed
\r
60976 this.failureType = Ext.form.Action.CLIENT_INVALID;
\r
60977 this.form.afterAction(this, false);
\r
60981 getParams : function() {
\r
60983 var bp = this.form.baseParams;
\r
60984 var p = this.options.params;
\r
60985 Ext.apply(o, p, bp);
\r
60988 // Direct actions have already been processed and therefore
\r
60989 // we can directly set the result; Direct Actions do not have
\r
60990 // a this.response property.
\r
60991 processResponse : function(result) {
\r
60992 this.result = result;
\r
60996 success : function(response, trans){
\r
60997 if(trans.type == Ext.Direct.exceptions.SERVER){
\r
61000 Ext.form.Action.DirectSubmit.superclass.success.call(this, response);
\r
61004 Ext.form.Action.ACTION_TYPES = {
\r
61005 'load' : Ext.form.Action.Load,
\r
61006 'submit' : Ext.form.Action.Submit,
\r
61007 'directload' : Ext.form.Action.DirectLoad,
\r
61008 'directsubmit' : Ext.form.Action.DirectSubmit
\r
61011 * @class Ext.form.VTypes
61012 * <p>This is a singleton object which contains a set of commonly used field validation functions.
61013 * The validations provided are basic and intended to be easily customizable and extended.</p>
61014 * <p>To add custom VTypes specify the <code>{@link Ext.form.TextField#vtype vtype}</code> validation
61015 * test function, and optionally specify any corresponding error text to display and any keystroke
61016 * filtering mask to apply. For example:</p>
61018 // custom Vtype for vtype:'time'
61019 var timeTest = /^([1-9]|1[0-9]):([0-5][0-9])(\s[a|p]m)$/i;
61020 Ext.apply(Ext.form.VTypes, {
61021 // vtype validation function
61022 time: function(val, field) {
61023 return timeTest.test(val);
61025 // vtype Text property: The error text to display when the validation function returns false
61026 timeText: 'Not a valid time. Must be in the format "12:34 PM".',
61027 // vtype Mask property: The keystroke filter mask
61028 timeMask: /[\d\s:amp]/i
61033 // custom Vtype for vtype:'IPAddress'
61034 Ext.apply(Ext.form.VTypes, {
61035 IPAddress: function(v) {
61036 return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
61038 IPAddressText: 'Must be a numeric IP address',
61039 IPAddressMask: /[\d\.]/i
61044 Ext.form.VTypes = function(){
61045 // closure these in so they are only created once.
61046 var alpha = /^[a-zA-Z_]+$/,
61047 alphanum = /^[a-zA-Z0-9_]+$/,
61048 email = /^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,4}$/,
61049 url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
61051 // All these messages and functions are configurable
61054 * The function used to validate email addresses. Note that this is a very basic validation -- complete
61055 * validation per the email RFC specifications is very complex and beyond the scope of this class, although
61056 * this function can be overridden if a more comprehensive validation scheme is desired. See the validation
61057 * section of the <a href="http://en.wikipedia.org/wiki/E-mail_address">Wikipedia article on email addresses</a>
61058 * for additional information. This implementation is intended to validate the following emails:<tt>
61059 * 'barney@example.de', 'barney.rubble@example.com', 'barney-rubble@example.coop', 'barney+rubble@example.com'
61061 * @param {String} value The email address
61062 * @return {Boolean} true if the RegExp test passed, and false if not.
61064 'email' : function(v){
61065 return email.test(v);
61068 * The error text to display when the email validation function returns false. Defaults to:
61069 * <tt>'This field should be an e-mail address in the format "user@example.com"'</tt>
61072 'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
61074 * The keystroke filter mask to be applied on email input. See the {@link #email} method for
61075 * information about more complex email validation. Defaults to:
61076 * <tt>/[a-z0-9_\.\-@]/i</tt>
61079 'emailMask' : /[a-z0-9_\.\-@]/i,
61082 * The function used to validate URLs
61083 * @param {String} value The URL
61084 * @return {Boolean} true if the RegExp test passed, and false if not.
61086 'url' : function(v){
61087 return url.test(v);
61090 * The error text to display when the url validation function returns false. Defaults to:
61091 * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
61094 'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
61097 * The function used to validate alpha values
61098 * @param {String} value The value
61099 * @return {Boolean} true if the RegExp test passed, and false if not.
61101 'alpha' : function(v){
61102 return alpha.test(v);
61105 * The error text to display when the alpha validation function returns false. Defaults to:
61106 * <tt>'This field should only contain letters and _'</tt>
61109 'alphaText' : 'This field should only contain letters and _',
61111 * The keystroke filter mask to be applied on alpha input. Defaults to:
61112 * <tt>/[a-z_]/i</tt>
61115 'alphaMask' : /[a-z_]/i,
61118 * The function used to validate alphanumeric values
61119 * @param {String} value The value
61120 * @return {Boolean} true if the RegExp test passed, and false if not.
61122 'alphanum' : function(v){
61123 return alphanum.test(v);
61126 * The error text to display when the alphanumeric validation function returns false. Defaults to:
61127 * <tt>'This field should only contain letters, numbers and _'</tt>
61130 'alphanumText' : 'This field should only contain letters, numbers and _',
61132 * The keystroke filter mask to be applied on alphanumeric input. Defaults to:
61133 * <tt>/[a-z0-9_]/i</tt>
61136 'alphanumMask' : /[a-z0-9_]/i
61139 * @class Ext.grid.GridPanel
\r
61140 * @extends Ext.Panel
\r
61141 * <p>This class represents the primary interface of a component based grid control to represent data
\r
61142 * in a tabular format of rows and columns. The GridPanel is composed of the following:</p>
\r
61143 * <div class="mdetail-params"><ul>
\r
61144 * <li><b>{@link Ext.data.Store Store}</b> : The Model holding the data records (rows)
\r
61145 * <div class="sub-desc"></div></li>
\r
61146 * <li><b>{@link Ext.grid.ColumnModel Column model}</b> : Column makeup
\r
61147 * <div class="sub-desc"></div></li>
\r
61148 * <li><b>{@link Ext.grid.GridView View}</b> : Encapsulates the user interface
\r
61149 * <div class="sub-desc"></div></li>
\r
61150 * <li><b>{@link Ext.grid.AbstractSelectionModel selection model}</b> : Selection behavior
\r
61151 * <div class="sub-desc"></div></li>
\r
61153 * <p>Example usage:</p>
\r
61155 var grid = new Ext.grid.GridPanel({
\r
61156 {@link #store}: new {@link Ext.data.Store}({
\r
61157 {@link Ext.data.Store#autoDestroy autoDestroy}: true,
\r
61158 {@link Ext.data.Store#reader reader}: reader,
\r
61159 {@link Ext.data.Store#data data}: xg.dummyData
\r
61161 {@link #colModel}: new {@link Ext.grid.ColumnModel}({
\r
61162 {@link Ext.grid.ColumnModel#defaults defaults}: {
\r
61166 {@link Ext.grid.ColumnModel#columns columns}: [
\r
61167 {id: 'company', header: 'Company', width: 200, sortable: true, dataIndex: 'company'},
\r
61168 {header: 'Price', renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
\r
61169 {header: 'Change', dataIndex: 'change'},
\r
61170 {header: '% Change', dataIndex: 'pctChange'},
\r
61171 // instead of specifying renderer: Ext.util.Format.dateRenderer('m/d/Y') use xtype
\r
61173 header: 'Last Updated', width: 135, dataIndex: 'lastChange',
\r
61174 xtype: 'datecolumn', format: 'M d, Y'
\r
61178 {@link #viewConfig}: {
\r
61179 {@link Ext.grid.GridView#forceFit forceFit}: true,
\r
61181 // Return CSS class to apply to rows depending upon data values
\r
61182 {@link Ext.grid.GridView#getRowClass getRowClass}: function(record, index) {
\r
61183 var c = record.{@link Ext.data.Record#get get}('change');
\r
61185 return 'price-fall';
\r
61186 } else if (c > 0) {
\r
61187 return 'price-rise';
\r
61191 {@link #sm}: new Ext.grid.RowSelectionModel({singleSelect:true}),
\r
61195 title: 'Framed with Row Selection and Horizontal Scrolling',
\r
61196 iconCls: 'icon-grid'
\r
61199 * <p><b><u>Notes:</u></b></p>
\r
61200 * <div class="mdetail-params"><ul>
\r
61201 * <li>Although this class inherits many configuration options from base classes, some of them
\r
61202 * (such as autoScroll, autoWidth, layout, items, etc) are not used by this class, and will
\r
61203 * have no effect.</li>
\r
61204 * <li>A grid <b>requires</b> a width in which to scroll its columns, and a height in which to
\r
61205 * scroll its rows. These dimensions can either be set explicitly through the
\r
61206 * <tt>{@link Ext.BoxComponent#height height}</tt> and <tt>{@link Ext.BoxComponent#width width}</tt>
\r
61207 * configuration options or implicitly set by using the grid as a child item of a
\r
61208 * {@link Ext.Container Container} which will have a {@link Ext.Container#layout layout manager}
\r
61209 * provide the sizing of its child items (for example the Container of the Grid may specify
\r
61210 * <tt>{@link Ext.Container#layout layout}:'fit'</tt>).</li>
\r
61211 * <li>To access the data in a Grid, it is necessary to use the data model encapsulated
\r
61212 * by the {@link #store Store}. See the {@link #cellclick} event for more details.</li>
\r
61215 * @param {Object} config The config object
\r
61218 Ext.grid.GridPanel = Ext.extend(Ext.Panel, {
\r
61220 * @cfg {String} autoExpandColumn
\r
61221 * <p>The <tt>{@link Ext.grid.Column#id id}</tt> of a {@link Ext.grid.Column column} in
\r
61222 * this grid that should expand to fill unused space. This value specified here can not
\r
61223 * be <tt>0</tt>.</p>
\r
61224 * <br><p><b>Note</b>: If the Grid's {@link Ext.grid.GridView view} is configured with
\r
61225 * <tt>{@link Ext.grid.GridView#forceFit forceFit}=true</tt> the <tt>autoExpandColumn</tt>
\r
61226 * is ignored. See {@link Ext.grid.Column}.<tt>{@link Ext.grid.Column#width width}</tt>
\r
61227 * for additional details.</p>
\r
61228 * <p>See <tt>{@link #autoExpandMax}</tt> and <tt>{@link #autoExpandMin}</tt> also.</p>
\r
61230 autoExpandColumn : false,
\r
61232 * @cfg {Number} autoExpandMax The maximum width the <tt>{@link #autoExpandColumn}</tt>
\r
61233 * can have (if enabled). Defaults to <tt>1000</tt>.
\r
61235 autoExpandMax : 1000,
\r
61237 * @cfg {Number} autoExpandMin The minimum width the <tt>{@link #autoExpandColumn}</tt>
\r
61238 * can have (if enabled). Defaults to <tt>50</tt>.
\r
61240 autoExpandMin : 50,
\r
61242 * @cfg {Boolean} columnLines <tt>true</tt> to add css for column separation lines.
\r
61243 * Default is <tt>false</tt>.
\r
61245 columnLines : false,
\r
61247 * @cfg {Object} cm Shorthand for <tt>{@link #colModel}</tt>.
\r
61250 * @cfg {Object} colModel The {@link Ext.grid.ColumnModel} to use when rendering the grid (required).
\r
61253 * @cfg {Array} columns An array of {@link Ext.grid.Column columns} to auto create a
\r
61254 * {@link Ext.grid.ColumnModel}. The ColumnModel may be explicitly created via the
\r
61255 * <tt>{@link #colModel}</tt> configuration property.
\r
61258 * @cfg {String} ddGroup The DD group this GridPanel belongs to. Defaults to <tt>'GridDD'</tt> if not specified.
\r
61261 * @cfg {String} ddText
\r
61262 * Configures the text in the drag proxy. Defaults to:
\r
61264 * ddText : '{0} selected row{1}'
\r
61266 * <tt>{0}</tt> is replaced with the number of selected rows.
\r
61268 ddText : '{0} selected row{1}',
\r
61270 * @cfg {Boolean} deferRowRender <P>Defaults to <tt>true</tt> to enable deferred row rendering.</p>
\r
61271 * <p>This allows the GridPanel to be initially rendered empty, with the expensive update of the row
\r
61272 * structure deferred so that layouts with GridPanels appear more quickly.</p>
\r
61274 deferRowRender : true,
\r
61276 * @cfg {Boolean} disableSelection <p><tt>true</tt> to disable selections in the grid. Defaults to <tt>false</tt>.</p>
\r
61277 * <p>Ignored if a {@link #selModel SelectionModel} is specified.</p>
\r
61280 * @cfg {Boolean} enableColumnResize <tt>false</tt> to turn off column resizing for the whole grid. Defaults to <tt>true</tt>.
\r
61283 * @cfg {Boolean} enableColumnHide
\r
61284 * Defaults to <tt>true</tt> to enable {@link Ext.grid.Column#hidden hiding of columns}
\r
61285 * with the {@link #enableHdMenu header menu}.
\r
61287 enableColumnHide : true,
\r
61289 * @cfg {Boolean} enableColumnMove Defaults to <tt>true</tt> to enable drag and drop reorder of columns. <tt>false</tt>
\r
61290 * to turn off column reordering via drag drop.
\r
61292 enableColumnMove : true,
\r
61294 * @cfg {Boolean} enableDragDrop <p>Enables dragging of the selected rows of the GridPanel. Defaults to <tt>false</tt>.</p>
\r
61295 * <p>Setting this to <b><tt>true</tt></b> causes this GridPanel's {@link #getView GridView} to
\r
61296 * create an instance of {@link Ext.grid.GridDragZone}. <b>Note</b>: this is available only <b>after</b>
\r
61297 * the Grid has been rendered as the GridView's <tt>{@link Ext.grid.GridView#dragZone dragZone}</tt>
\r
61299 * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's implementations of
\r
61300 * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
\r
61301 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} are able
\r
61302 * to process the {@link Ext.grid.GridDragZone#getDragData data} which is provided.</p>
\r
61304 enableDragDrop : false,
\r
61306 * @cfg {Boolean} enableHdMenu Defaults to <tt>true</tt> to enable the drop down button for menu in the headers.
\r
61308 enableHdMenu : true,
\r
61310 * @cfg {Boolean} hideHeaders True to hide the grid's header. Defaults to <code>false</code>.
\r
61313 * @cfg {Object} loadMask An {@link Ext.LoadMask} config or true to mask the grid while
\r
61314 * loading. Defaults to <code>false</code>.
\r
61316 loadMask : false,
\r
61318 * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if <tt>autoHeight</tt> is not on.
\r
61321 * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Defaults to <tt>25</tt>.
\r
61323 minColumnWidth : 25,
\r
61325 * @cfg {Object} sm Shorthand for <tt>{@link #selModel}</tt>.
\r
61328 * @cfg {Object} selModel Any subclass of {@link Ext.grid.AbstractSelectionModel} that will provide
\r
61329 * the selection model for the grid (defaults to {@link Ext.grid.RowSelectionModel} if not specified).
\r
61332 * @cfg {Ext.data.Store} store The {@link Ext.data.Store} the grid should use as its data source (required).
\r
61335 * @cfg {Boolean} stripeRows <tt>true</tt> to stripe the rows. Default is <tt>false</tt>.
\r
61336 * <p>This causes the CSS class <tt><b>x-grid3-row-alt</b></tt> to be added to alternate rows of
\r
61337 * the grid. A default CSS rule is provided which sets a background colour, but you can override this
\r
61338 * with a rule which either overrides the <b>background-color</b> style using the '!important'
\r
61339 * modifier, or which uses a CSS selector of higher specificity.</p>
\r
61341 stripeRows : false,
\r
61343 * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is <tt>true</tt>
\r
61344 * for GridPanel, but <tt>false</tt> for EditorGridPanel.
\r
61346 trackMouseOver : true,
\r
61348 * @cfg {Array} stateEvents
\r
61349 * An array of events that, when fired, should trigger this component to save its state.
\r
61350 * Defaults to:<pre><code>
\r
61351 * stateEvents: ['columnmove', 'columnresize', 'sortchange']
\r
61353 * <p>These can be any types of events supported by this component, including browser or
\r
61354 * custom events (e.g., <tt>['click', 'customerchange']</tt>).</p>
\r
61355 * <p>See {@link Ext.Component#stateful} for an explanation of saving and restoring
\r
61356 * Component state.</p>
\r
61358 stateEvents : ['columnmove', 'columnresize', 'sortchange'],
\r
61360 * @cfg {Object} view The {@link Ext.grid.GridView} used by the grid. This can be set
\r
61361 * before a call to {@link Ext.Component#render render()}.
\r
61366 * @cfg {Array} bubbleEvents
\r
61367 * <p>An array of events that, when fired, should be bubbled to any parent container.
\r
61368 * Defaults to <tt>[]</tt>.
\r
61370 bubbleEvents: [],
\r
61373 * @cfg {Object} viewConfig A config object that will be applied to the grid's UI view. Any of
\r
61374 * the config options available for {@link Ext.grid.GridView} can be specified here. This option
\r
61375 * is ignored if <tt>{@link #view}</tt> is specified.
\r
61379 rendered : false,
\r
61381 viewReady : false,
\r
61384 initComponent : function(){
\r
61385 Ext.grid.GridPanel.superclass.initComponent.call(this);
\r
61387 if(this.columnLines){
\r
61388 this.cls = (this.cls || '') + ' x-grid-with-col-lines';
\r
61390 // override any provided value since it isn't valid
\r
61391 // and is causing too many bug reports ;)
\r
61392 this.autoScroll = false;
\r
61393 this.autoWidth = false;
\r
61395 if(Ext.isArray(this.columns)){
\r
61396 this.colModel = new Ext.grid.ColumnModel(this.columns);
\r
61397 delete this.columns;
\r
61400 // check and correct shorthanded configs
\r
61402 this.store = this.ds;
\r
61406 this.colModel = this.cm;
\r
61410 this.selModel = this.sm;
\r
61413 this.store = Ext.StoreMgr.lookup(this.store);
\r
61419 * The raw click event for the entire grid.
\r
61420 * @param {Ext.EventObject} e
\r
61424 * @event dblclick
\r
61425 * The raw dblclick event for the entire grid.
\r
61426 * @param {Ext.EventObject} e
\r
61430 * @event contextmenu
\r
61431 * The raw contextmenu event for the entire grid.
\r
61432 * @param {Ext.EventObject} e
\r
61436 * @event mousedown
\r
61437 * The raw mousedown event for the entire grid.
\r
61438 * @param {Ext.EventObject} e
\r
61443 * The raw mouseup event for the entire grid.
\r
61444 * @param {Ext.EventObject} e
\r
61448 * @event mouseover
\r
61449 * The raw mouseover event for the entire grid.
\r
61450 * @param {Ext.EventObject} e
\r
61454 * @event mouseout
\r
61455 * The raw mouseout event for the entire grid.
\r
61456 * @param {Ext.EventObject} e
\r
61460 * @event keypress
\r
61461 * The raw keypress event for the entire grid.
\r
61462 * @param {Ext.EventObject} e
\r
61467 * The raw keydown event for the entire grid.
\r
61468 * @param {Ext.EventObject} e
\r
61474 * @event cellmousedown
\r
61475 * Fires before a cell is clicked
\r
61476 * @param {Grid} this
\r
61477 * @param {Number} rowIndex
\r
61478 * @param {Number} columnIndex
\r
61479 * @param {Ext.EventObject} e
\r
61483 * @event rowmousedown
\r
61484 * Fires before a row is clicked
\r
61485 * @param {Grid} this
\r
61486 * @param {Number} rowIndex
\r
61487 * @param {Ext.EventObject} e
\r
61491 * @event headermousedown
\r
61492 * Fires before a header is clicked
\r
61493 * @param {Grid} this
\r
61494 * @param {Number} columnIndex
\r
61495 * @param {Ext.EventObject} e
\r
61497 'headermousedown',
\r
61500 * @event cellclick
\r
61501 * Fires when a cell is clicked.
\r
61502 * The data for the cell is drawn from the {@link Ext.data.Record Record}
\r
61503 * for this row. To access the data in the listener function use the
\r
61504 * following technique:
\r
61506 function(grid, rowIndex, columnIndex, e) {
\r
61507 var record = grid.getStore().getAt(rowIndex); // Get the Record
\r
61508 var fieldName = grid.getColumnModel().getDataIndex(columnIndex); // Get field name
\r
61509 var data = record.get(fieldName);
\r
61512 * @param {Grid} this
\r
61513 * @param {Number} rowIndex
\r
61514 * @param {Number} columnIndex
\r
61515 * @param {Ext.EventObject} e
\r
61519 * @event celldblclick
\r
61520 * Fires when a cell is double clicked
\r
61521 * @param {Grid} this
\r
61522 * @param {Number} rowIndex
\r
61523 * @param {Number} columnIndex
\r
61524 * @param {Ext.EventObject} e
\r
61528 * @event rowclick
\r
61529 * Fires when a row is clicked
\r
61530 * @param {Grid} this
\r
61531 * @param {Number} rowIndex
\r
61532 * @param {Ext.EventObject} e
\r
61536 * @event rowdblclick
\r
61537 * Fires when a row is double clicked
\r
61538 * @param {Grid} this
\r
61539 * @param {Number} rowIndex
\r
61540 * @param {Ext.EventObject} e
\r
61544 * @event headerclick
\r
61545 * Fires when a header is clicked
\r
61546 * @param {Grid} this
\r
61547 * @param {Number} columnIndex
\r
61548 * @param {Ext.EventObject} e
\r
61552 * @event headerdblclick
\r
61553 * Fires when a header cell is double clicked
\r
61554 * @param {Grid} this
\r
61555 * @param {Number} columnIndex
\r
61556 * @param {Ext.EventObject} e
\r
61558 'headerdblclick',
\r
61560 * @event rowcontextmenu
\r
61561 * Fires when a row is right clicked
\r
61562 * @param {Grid} this
\r
61563 * @param {Number} rowIndex
\r
61564 * @param {Ext.EventObject} e
\r
61566 'rowcontextmenu',
\r
61568 * @event cellcontextmenu
\r
61569 * Fires when a cell is right clicked
\r
61570 * @param {Grid} this
\r
61571 * @param {Number} rowIndex
\r
61572 * @param {Number} cellIndex
\r
61573 * @param {Ext.EventObject} e
\r
61575 'cellcontextmenu',
\r
61577 * @event headercontextmenu
\r
61578 * Fires when a header is right clicked
\r
61579 * @param {Grid} this
\r
61580 * @param {Number} columnIndex
\r
61581 * @param {Ext.EventObject} e
\r
61583 'headercontextmenu',
\r
61585 * @event bodyscroll
\r
61586 * Fires when the body element is scrolled
\r
61587 * @param {Number} scrollLeft
\r
61588 * @param {Number} scrollTop
\r
61592 * @event columnresize
\r
61593 * Fires when the user resizes a column
\r
61594 * @param {Number} columnIndex
\r
61595 * @param {Number} newSize
\r
61599 * @event columnmove
\r
61600 * Fires when the user moves a column
\r
61601 * @param {Number} oldIndex
\r
61602 * @param {Number} newIndex
\r
61606 * @event sortchange
\r
61607 * Fires when the grid's store sort changes
\r
61608 * @param {Grid} this
\r
61609 * @param {Object} sortInfo An object with the keys field and direction
\r
61613 * @event reconfigure
\r
61614 * Fires when the grid is reconfigured with a new store and/or column model.
\r
61615 * @param {Grid} this
\r
61616 * @param {Ext.data.Store} store The new store
\r
61617 * @param {Ext.grid.ColumnModel} colModel The new column model
\r
61624 onRender : function(ct, position){
\r
61625 Ext.grid.GridPanel.superclass.onRender.apply(this, arguments);
\r
61627 var c = this.getGridEl();
\r
61629 this.el.addClass('x-grid-panel');
\r
61633 mousedown: this.onMouseDown,
\r
61634 click: this.onClick,
\r
61635 dblclick: this.onDblClick,
\r
61636 contextmenu: this.onContextMenu
\r
61639 this.relayEvents(c, ['mousedown','mouseup','mouseover','mouseout','keypress', 'keydown']);
\r
61641 var view = this.getView();
\r
61644 this.getSelectionModel().init(this);
\r
61648 initEvents : function(){
\r
61649 Ext.grid.GridPanel.superclass.initEvents.call(this);
\r
61651 if(this.loadMask){
\r
61652 this.loadMask = new Ext.LoadMask(this.bwrap,
\r
61653 Ext.apply({store:this.store}, this.loadMask));
\r
61657 initStateEvents : function(){
\r
61658 Ext.grid.GridPanel.superclass.initStateEvents.call(this);
\r
61659 this.mon(this.colModel, 'hiddenchange', this.saveState, this, {delay: 100});
\r
61662 applyState : function(state){
\r
61663 var cm = this.colModel,
\r
61664 cs = state.columns;
\r
61666 for(var i = 0, len = cs.length; i < len; i++){
\r
61668 var c = cm.getColumnById(s.id);
\r
61670 c.hidden = s.hidden;
\r
61671 c.width = s.width;
\r
61672 var oldIndex = cm.getIndexById(s.id);
\r
61673 if(oldIndex != i){
\r
61674 cm.moveColumn(oldIndex, i);
\r
61679 if(state.sort && this.store){
\r
61680 this.store[this.store.remoteSort ? 'setDefaultSort' : 'sort'](state.sort.field, state.sort.direction);
\r
61682 var o = Ext.apply({}, state);
\r
61683 delete o.columns;
\r
61685 Ext.grid.GridPanel.superclass.applyState.call(this, o);
\r
61688 getState : function(){
\r
61689 var o = {columns: []};
\r
61690 for(var i = 0, c; (c = this.colModel.config[i]); i++){
\r
61696 o.columns[i].hidden = true;
\r
61700 var ss = this.store.getSortState();
\r
61709 afterRender : function(){
\r
61710 Ext.grid.GridPanel.superclass.afterRender.call(this);
\r
61711 var v = this.view;
\r
61712 this.on('bodyresize', v.layout, v);
\r
61714 if(this.deferRowRender){
\r
61715 v.afterRender.defer(10, this.view);
\r
61719 this.viewReady = true;
\r
61723 * <p>Reconfigures the grid to use a different Store and Column Model
\r
61724 * and fires the 'reconfigure' event. The View will be bound to the new
\r
61725 * objects and refreshed.</p>
\r
61726 * <p>Be aware that upon reconfiguring a GridPanel, certain existing settings <i>may</i> become
\r
61727 * invalidated. For example the configured {@link #autoExpandColumn} may no longer exist in the
\r
61728 * new ColumnModel. Also, an existing {@link Ext.PagingToolbar PagingToolbar} will still be bound
\r
61729 * to the old Store, and will need rebinding. Any {@link #plugins} might also need reconfiguring
\r
61730 * with the new data.</p>
\r
61731 * @param {Ext.data.Store} store The new {@link Ext.data.Store} object
\r
61732 * @param {Ext.grid.ColumnModel} colModel The new {@link Ext.grid.ColumnModel} object
\r
61734 reconfigure : function(store, colModel){
\r
61735 if(this.loadMask){
\r
61736 this.loadMask.destroy();
\r
61737 this.loadMask = new Ext.LoadMask(this.bwrap,
\r
61738 Ext.apply({}, {store:store}, this.initialConfig.loadMask));
\r
61740 this.view.initData(store, colModel);
\r
61741 this.store = store;
\r
61742 this.colModel = colModel;
\r
61743 if(this.rendered){
\r
61744 this.view.refresh(true);
\r
61746 this.fireEvent('reconfigure', this, store, colModel);
\r
61750 onDestroy : function(){
\r
61751 if(this.rendered){
\r
61752 var c = this.body;
\r
61753 c.removeAllListeners();
\r
61755 Ext.destroy(this.view, this.loadMask);
\r
61756 }else if(this.store && this.store.autoDestroy){
\r
61757 this.store.destroy();
\r
61759 Ext.destroy(this.colModel, this.selModel);
\r
61760 this.store = this.selModel = this.colModel = this.view = this.loadMask = null;
\r
61761 Ext.grid.GridPanel.superclass.onDestroy.call(this);
\r
61765 processEvent : function(name, e){
\r
61766 this.fireEvent(name, e);
\r
61767 var t = e.getTarget();
\r
61768 var v = this.view;
\r
61769 var header = v.findHeaderIndex(t);
\r
61770 if(header !== false){
\r
61771 this.fireEvent('header' + name, this, header, e);
\r
61773 var row = v.findRowIndex(t);
\r
61774 var cell = v.findCellIndex(t);
\r
61775 if(row !== false){
\r
61776 this.fireEvent('row' + name, this, row, e);
\r
61777 if(cell !== false){
\r
61778 this.fireEvent('cell' + name, this, row, cell, e);
\r
61785 onClick : function(e){
\r
61786 this.processEvent('click', e);
\r
61790 onMouseDown : function(e){
\r
61791 this.processEvent('mousedown', e);
\r
61795 onContextMenu : function(e, t){
\r
61796 this.processEvent('contextmenu', e);
\r
61800 onDblClick : function(e){
\r
61801 this.processEvent('dblclick', e);
\r
61805 walkCells : function(row, col, step, fn, scope){
\r
61806 var cm = this.colModel, clen = cm.getColumnCount();
\r
61807 var ds = this.store, rlen = ds.getCount(), first = true;
\r
61819 if(fn.call(scope || this, row, col, cm) === true){
\r
61820 return [row, col];
\r
61831 while(row < rlen){
\r
61836 while(col < clen){
\r
61837 if(fn.call(scope || this, row, col, cm) === true){
\r
61838 return [row, col];
\r
61849 onResize : function(){
\r
61850 Ext.grid.GridPanel.superclass.onResize.apply(this, arguments);
\r
61851 if(this.viewReady){
\r
61852 this.view.layout();
\r
61857 * Returns the grid's underlying element.
\r
61858 * @return {Element} The element
\r
61860 getGridEl : function(){
\r
61861 return this.body;
\r
61864 // private for compatibility, overridden by editor grid
\r
61865 stopEditing : Ext.emptyFn,
\r
61868 * Returns the grid's selection model configured by the <code>{@link #selModel}</code>
\r
61869 * configuration option. If no selection model was configured, this will create
\r
61870 * and return a {@link Ext.grid.RowSelectionModel RowSelectionModel}.
\r
61871 * @return {SelectionModel}
\r
61873 getSelectionModel : function(){
\r
61874 if(!this.selModel){
\r
61875 this.selModel = new Ext.grid.RowSelectionModel(
\r
61876 this.disableSelection ? {selectRow: Ext.emptyFn} : null);
\r
61878 return this.selModel;
\r
61882 * Returns the grid's data store.
\r
61883 * @return {Ext.data.Store} The store
\r
61885 getStore : function(){
\r
61886 return this.store;
\r
61890 * Returns the grid's ColumnModel.
\r
61891 * @return {Ext.grid.ColumnModel} The column model
\r
61893 getColumnModel : function(){
\r
61894 return this.colModel;
\r
61898 * Returns the grid's GridView object.
\r
61899 * @return {Ext.grid.GridView} The grid view
\r
61901 getView : function(){
\r
61903 this.view = new Ext.grid.GridView(this.viewConfig);
\r
61905 return this.view;
\r
61908 * Called to get grid's drag proxy text, by default returns this.ddText.
\r
61909 * @return {String} The text
\r
61911 getDragDropText : function(){
\r
61912 var count = this.selModel.getCount();
\r
61913 return String.format(this.ddText, count, count == 1 ? '' : 's');
\r
61917 * @cfg {String/Number} activeItem
\r
61921 * @cfg {Boolean} autoDestroy
\r
61925 * @cfg {Object/String/Function} autoLoad
\r
61929 * @cfg {Boolean} autoWidth
\r
61933 * @cfg {Boolean/Number} bufferResize
\r
61937 * @cfg {String} defaultType
\r
61941 * @cfg {Object} defaults
\r
61945 * @cfg {Boolean} hideBorders
\r
61949 * @cfg {Mixed} items
\r
61953 * @cfg {String} layout
\r
61957 * @cfg {Object} layoutConfig
\r
61961 * @cfg {Boolean} monitorResize
\r
61965 * @property items
\r
61973 * @method cascade
\r
61977 * @method doLayout
\r
61985 * @method findBy
\r
61989 * @method findById
\r
61993 * @method findByType
\r
61997 * @method getComponent
\r
62001 * @method getLayout
\r
62005 * @method getUpdater
\r
62009 * @method insert
\r
62017 * @method remove
\r
62025 * @event afterLayout
\r
62029 * @event beforeadd
\r
62033 * @event beforeremove
\r
62044 * @cfg {String} allowDomMove @hide
\r
62047 * @cfg {String} autoEl @hide
\r
62050 * @cfg {String} applyTo @hide
\r
62053 * @cfg {String} autoScroll @hide
\r
62056 * @cfg {String} bodyBorder @hide
\r
62059 * @cfg {String} bodyStyle @hide
\r
62062 * @cfg {String} contentEl @hide
\r
62065 * @cfg {String} disabledClass @hide
\r
62068 * @cfg {String} elements @hide
\r
62071 * @cfg {String} html @hide
\r
62074 * @cfg {Boolean} preventBodyReset
\r
62078 * @property disabled
\r
62082 * @method applyToMarkup
\r
62090 * @method disable
\r
62094 * @method setDisabled
\r
62098 Ext.reg('grid', Ext.grid.GridPanel);/**
62099 * @class Ext.grid.GridView
62100 * @extends Ext.util.Observable
62101 * <p>This class encapsulates the user interface of an {@link Ext.grid.GridPanel}.
62102 * Methods of this class may be used to access user interface elements to enable
62103 * special display effects. Do not change the DOM structure of the user interface.</p>
62104 * <p>This class does not provide ways to manipulate the underlying data. The data
62105 * model of a Grid is held in an {@link Ext.data.Store}.</p>
62107 * @param {Object} config
62109 Ext.grid.GridView = function(config){
62110 Ext.apply(this, config);
62111 // These events are only used internally by the grid components
62114 * @event beforerowremoved
62115 * Internal UI Event. Fired before a row is removed.
62116 * @param {Ext.grid.GridView} view
62117 * @param {Number} rowIndex The index of the row to be removed.
62118 * @param {Ext.data.Record} record The Record to be removed
62120 'beforerowremoved',
62122 * @event beforerowsinserted
62123 * Internal UI Event. Fired before rows are inserted.
62124 * @param {Ext.grid.GridView} view
62125 * @param {Number} firstRow The index of the first row to be inserted.
62126 * @param {Number} lastRow The index of the last row to be inserted.
62128 'beforerowsinserted',
62130 * @event beforerefresh
62131 * Internal UI Event. Fired before the view is refreshed.
62132 * @param {Ext.grid.GridView} view
62136 * @event rowremoved
62137 * Internal UI Event. Fired after a row is removed.
62138 * @param {Ext.grid.GridView} view
62139 * @param {Number} rowIndex The index of the row that was removed.
62140 * @param {Ext.data.Record} record The Record that was removed
62144 * @event rowsinserted
62145 * Internal UI Event. Fired after rows are inserted.
62146 * @param {Ext.grid.GridView} view
62147 * @param {Number} firstRow The index of the first inserted.
62148 * @param {Number} lastRow The index of the last row inserted.
62152 * @event rowupdated
62153 * Internal UI Event. Fired after a row has been updated.
62154 * @param {Ext.grid.GridView} view
62155 * @param {Number} firstRow The index of the row updated.
62156 * @param {Ext.data.record} record The Record backing the row updated.
62161 * Internal UI Event. Fired after the GridView's body has been refreshed.
62162 * @param {Ext.grid.GridView} view
62166 Ext.grid.GridView.superclass.constructor.call(this);
62169 Ext.extend(Ext.grid.GridView, Ext.util.Observable, {
62171 * Override this function to apply custom CSS classes to rows during rendering. You can also supply custom
62172 * parameters to the row template for the current row to customize how it is rendered using the <b>rowParams</b>
62173 * parameter. This function should return the CSS class name (or empty string '' for none) that will be added
62174 * to the row's wrapping div. To apply multiple class names, simply return them space-delimited within the string
62175 * (e.g., 'my-class another-class'). Example usage:
62179 showPreview: true, // custom property
62180 enableRowBody: true, // required to create a second, full-width row to show expanded Record data
62181 getRowClass: function(record, rowIndex, rp, ds){ // rp = rowParams
62182 if(this.showPreview){
62183 rp.body = '<p>'+record.data.excerpt+'</p>';
62184 return 'x-grid3-row-expanded';
62186 return 'x-grid3-row-collapsed';
62190 * @param {Record} record The {@link Ext.data.Record} corresponding to the current row.
62191 * @param {Number} index The row index.
62192 * @param {Object} rowParams A config object that is passed to the row template during rendering that allows
62193 * customization of various aspects of a grid row.
62194 * <p>If {@link #enableRowBody} is configured <b><tt></tt>true</b>, then the following properties may be set
62195 * by this function, and will be used to render a full-width expansion row below each grid row:</p>
62197 * <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>
62198 * <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>
62200 * The following property will be passed in, and may be appended to:
62202 * <li><code>tstyle</code> : String <div class="sub-desc">A CSS style specification that willl be applied to the <table> element which encapsulates
62203 * both the standard grid row, and any expansion row.</div></li>
62205 * @param {Store} store The {@link Ext.data.Store} this grid is bound to
62206 * @method getRowClass
62207 * @return {String} a CSS class name to add to the row.
62210 * @cfg {Boolean} enableRowBody True to add a second TR element per row that can be used to provide a row body
62211 * that spans beneath the data row. Use the {@link #getRowClass} method's rowParams config to customize the row body.
62214 * @cfg {String} emptyText Default text (html tags are accepted) to display in the grid body when no rows
62215 * are available (defaults to ''). This value will be used to update the <tt>{@link #mainBody}</tt>:
62217 this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
62221 * @cfg {Boolean} headersDisabled True to disable the grid column headers (defaults to <tt>false</tt>).
62222 * Use the {@link Ext.grid.ColumnModel ColumnModel} <tt>{@link Ext.grid.ColumnModel#menuDisabled menuDisabled}</tt>
62223 * config to disable the <i>menu</i> for individual columns. While this config is true the
62224 * following will be disabled:<div class="mdetail-params"><ul>
62225 * <li>clicking on header to sort</li>
62226 * <li>the trigger to reveal the menu.</li>
62230 * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations
62231 * of the template methods of DragZone to enable dragging of the selected rows of a GridPanel.
62232 * See {@link Ext.grid.GridDragZone} for details.</p>
62233 * <p>This will <b>only</b> be present:<div class="mdetail-params"><ul>
62234 * <li><i>if</i> the owning GridPanel was configured with {@link Ext.grid.GridPanel#enableDragDrop enableDragDrop}: <tt>true</tt>.</li>
62235 * <li><i>after</i> the owning GridPanel has been rendered.</li>
62237 * @property dragZone
62238 * @type {Ext.grid.GridDragZone}
62241 * @cfg {Boolean} deferEmptyText True to defer <tt>{@link #emptyText}</tt> being applied until the store's
62242 * first load (defaults to <tt>true</tt>).
62244 deferEmptyText : true,
62246 * @cfg {Number} scrollOffset The amount of space to reserve for the vertical scrollbar
62247 * (defaults to <tt>undefined</tt>). If an explicit value isn't specified, this will be automatically
62250 scrollOffset : undefined,
62252 * @cfg {Boolean} autoFill
62253 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
62254 * when the grid is <b>initially rendered</b>. The
62255 * {@link Ext.grid.Column#width initially configured width}</tt> of each column will be adjusted
62256 * to fit the grid width and prevent horizontal scrolling. If columns are later resized (manually
62257 * or programmatically), the other columns in the grid will <b>not</b> be resized to fit the grid width.
62258 * See <tt>{@link #forceFit}</tt> also.
62262 * @cfg {Boolean} forceFit
62263 * Defaults to <tt>false</tt>. Specify <tt>true</tt> to have the column widths re-proportioned
62264 * at <b>all times</b>. The {@link Ext.grid.Column#width initially configured width}</tt> of each
62265 * column will be adjusted to fit the grid width and prevent horizontal scrolling. If columns are
62266 * later resized (manually or programmatically), the other columns in the grid <b>will</b> be resized
62267 * to fit the grid width. See <tt>{@link #autoFill}</tt> also.
62271 * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
62273 sortClasses : ['sort-asc', 'sort-desc'],
62275 * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
62277 sortAscText : 'Sort Ascending',
62279 * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
62281 sortDescText : 'Sort Descending',
62283 * @cfg {String} columnsText The text displayed in the 'Columns' menu item (defaults to <tt>'Columns'</tt>)
62285 columnsText : 'Columns',
62288 * @cfg {String} selectedRowClass The CSS class applied to a selected row (defaults to <tt>'x-grid3-row-selected'</tt>). An
62289 * example overriding the default styling:
62291 .x-grid3-row-selected {background-color: yellow;}
62293 * Note that this only controls the row, and will not do anything for the text inside it. To style inner
62294 * facets (like text) use something like:
62296 .x-grid3-row-selected .x-grid3-cell-inner {
62302 selectedRowClass : 'x-grid3-row-selected',
62306 tdClass : 'x-grid3-cell',
62307 hdCls : 'x-grid3-hd',
62311 * @cfg {Number} cellSelectorDepth The number of levels to search for cells in event delegation (defaults to <tt>4</tt>)
62313 cellSelectorDepth : 4,
62315 * @cfg {Number} rowSelectorDepth The number of levels to search for rows in event delegation (defaults to <tt>10</tt>)
62317 rowSelectorDepth : 10,
62320 * @cfg {String} cellSelector The selector used to find cells internally (defaults to <tt>'td.x-grid3-cell'</tt>)
62322 cellSelector : 'td.x-grid3-cell',
62324 * @cfg {String} rowSelector The selector used to find rows internally (defaults to <tt>'div.x-grid3-row'</tt>)
62326 rowSelector : 'div.x-grid3-row',
62329 firstRowCls: 'x-grid3-row-first',
62330 lastRowCls: 'x-grid3-row-last',
62331 rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
62333 /* -------------------------------- UI Specific ----------------------------- */
62336 initTemplates : function(){
62337 var ts = this.templates || {};
62339 ts.master = new Ext.Template(
62340 '<div class="x-grid3" hidefocus="true">',
62341 '<div class="x-grid3-viewport">',
62342 '<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>',
62343 '<div class="x-grid3-scroller"><div class="x-grid3-body" style="{bstyle}">{body}</div><a href="#" class="x-grid3-focus" tabIndex="-1"></a></div>',
62345 '<div class="x-grid3-resize-marker"> </div>',
62346 '<div class="x-grid3-resize-proxy"> </div>',
62352 ts.header = new Ext.Template(
62353 '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
62354 '<thead><tr class="x-grid3-hd-row">{cells}</tr></thead>',
62360 ts.hcell = new Ext.Template(
62361 '<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>' : '',
62362 '{value}<img class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
62368 ts.body = new Ext.Template('{rows}');
62372 ts.row = new Ext.Template(
62373 '<div class="x-grid3-row {alt}" style="{tstyle}"><table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
62374 '<tbody><tr>{cells}</tr>',
62375 (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>' : ''),
62376 '</tbody></table></div>'
62381 ts.cell = new Ext.Template(
62382 '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
62383 '<div class="x-grid3-cell-inner x-grid3-col-{id}" unselectable="on" {attr}>{value}</div>',
62390 if(t && typeof t.compile == 'function' && !t.compiled){
62391 t.disableFormats = true;
62396 this.templates = ts;
62397 this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
62401 fly : function(el){
62402 if(!this._flyweight){
62403 this._flyweight = new Ext.Element.Flyweight(document.body);
62405 this._flyweight.dom = el;
62406 return this._flyweight;
62410 getEditorParent : function(){
62411 return this.scroller.dom;
62415 initElements : function(){
62416 var E = Ext.Element;
62418 var el = this.grid.getGridEl().dom.firstChild;
62419 var cs = el.childNodes;
62421 this.el = new E(el);
62423 this.mainWrap = new E(cs[0]);
62424 this.mainHd = new E(this.mainWrap.dom.firstChild);
62426 if(this.grid.hideHeaders){
62427 this.mainHd.setDisplayed(false);
62430 this.innerHd = this.mainHd.dom.firstChild;
62431 this.scroller = new E(this.mainWrap.dom.childNodes[1]);
62433 this.scroller.setStyle('overflow-x', 'hidden');
62436 * <i>Read-only</i>. The GridView's body Element which encapsulates all rows in the Grid.
62437 * This {@link Ext.Element Element} is only available after the GridPanel has been rendered.
62438 * @type Ext.Element
62439 * @property mainBody
62441 this.mainBody = new E(this.scroller.dom.firstChild);
62443 this.focusEl = new E(this.scroller.dom.childNodes[1]);
62444 this.focusEl.swallowEvent('click', true);
62446 this.resizeMarker = new E(cs[1]);
62447 this.resizeProxy = new E(cs[2]);
62451 getRows : function(){
62452 return this.hasRows() ? this.mainBody.dom.childNodes : [];
62455 // finder methods, used with delegation
62458 findCell : function(el){
62462 return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
62466 * <p>Return the index of the grid column which contains the passed HTMLElement.</p>
62467 * See also {@link #findRowIndex}
62468 * @param {HTMLElement} el The target element
62469 * @return {Number} The column index, or <b>false</b> if the target element is not within a row of this GridView.
62471 findCellIndex : function(el, requiredCls){
62472 var cell = this.findCell(el);
62473 if(cell && (!requiredCls || this.fly(cell).hasClass(requiredCls))){
62474 return this.getCellIndex(cell);
62480 getCellIndex : function(el){
62482 var m = el.className.match(this.colRe);
62484 return this.cm.getIndexById(m[1]);
62491 findHeaderCell : function(el){
62492 var cell = this.findCell(el);
62493 return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
62497 findHeaderIndex : function(el){
62498 return this.findCellIndex(el, this.hdCls);
62502 * Return the HtmlElement representing the grid row which contains the passed element.
62503 * @param {HTMLElement} el The target HTMLElement
62504 * @return {HTMLElement} The row element, or null if the target element is not within a row of this GridView.
62506 findRow : function(el){
62510 return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
62514 * <p>Return the index of the grid row which contains the passed HTMLElement.</p>
62515 * See also {@link #findCellIndex}
62516 * @param {HTMLElement} el The target HTMLElement
62517 * @return {Number} The row index, or <b>false</b> if the target element is not within a row of this GridView.
62519 findRowIndex : function(el){
62520 var r = this.findRow(el);
62521 return r ? r.rowIndex : false;
62524 // getter methods for fetching elements dynamically in the grid
62527 * Return the <tt><div></tt> HtmlElement which represents a Grid row for the specified index.
62528 * @param {Number} index The row index
62529 * @return {HtmlElement} The div element.
62531 getRow : function(row){
62532 return this.getRows()[row];
62536 * Returns the grid's <tt><td></tt> HtmlElement at the specified coordinates.
62537 * @param {Number} row The row index in which to find the cell.
62538 * @param {Number} col The column index of the cell.
62539 * @return {HtmlElement} The td at the specified coordinates.
62541 getCell : function(row, col){
62542 return this.getRow(row).getElementsByTagName('td')[col];
62546 * Return the <tt><td></tt> HtmlElement which represents the Grid's header cell for the specified column index.
62547 * @param {Number} index The column index
62548 * @return {HtmlElement} The td element.
62550 getHeaderCell : function(index){
62551 return this.mainHd.dom.getElementsByTagName('td')[index];
62554 // manipulating elements
62556 // private - use getRowClass to apply custom row classes
62557 addRowClass : function(row, cls){
62558 var r = this.getRow(row);
62560 this.fly(r).addClass(cls);
62565 removeRowClass : function(row, cls){
62566 var r = this.getRow(row);
62568 this.fly(r).removeClass(cls);
62573 removeRow : function(row){
62574 Ext.removeNode(this.getRow(row));
62575 this.syncFocusEl(row);
62579 removeRows : function(firstRow, lastRow){
62580 var bd = this.mainBody.dom;
62581 for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
62582 Ext.removeNode(bd.childNodes[firstRow]);
62584 this.syncFocusEl(firstRow);
62590 getScrollState : function(){
62591 var sb = this.scroller.dom;
62592 return {left: sb.scrollLeft, top: sb.scrollTop};
62596 restoreScroll : function(state){
62597 var sb = this.scroller.dom;
62598 sb.scrollLeft = state.left;
62599 sb.scrollTop = state.top;
62603 * Scrolls the grid to the top
62605 scrollToTop : function(){
62606 this.scroller.dom.scrollTop = 0;
62607 this.scroller.dom.scrollLeft = 0;
62611 syncScroll : function(){
62612 this.syncHeaderScroll();
62613 var mb = this.scroller.dom;
62614 this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
62618 syncHeaderScroll : function(){
62619 var mb = this.scroller.dom;
62620 this.innerHd.scrollLeft = mb.scrollLeft;
62621 this.innerHd.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
62625 updateSortIcon : function(col, dir){
62626 var sc = this.sortClasses;
62627 var hds = this.mainHd.select('td').removeClass(sc);
62628 hds.item(col).addClass(sc[dir == 'DESC' ? 1 : 0]);
62632 updateAllColumnWidths : function(){
62633 var tw = this.getTotalWidth(),
62634 clen = this.cm.getColumnCount(),
62638 for(i = 0; i < clen; i++){
62639 ws[i] = this.getColumnWidth(i);
62641 this.innerHd.firstChild.style.width = this.getOffsetWidth();
62642 this.innerHd.firstChild.firstChild.style.width = tw;
62643 this.mainBody.dom.style.width = tw;
62644 for(i = 0; i < clen; i++){
62645 var hd = this.getHeaderCell(i);
62646 hd.style.width = ws[i];
62649 var ns = this.getRows(), row, trow;
62650 for(i = 0, len = ns.length; i < len; i++){
62652 row.style.width = tw;
62653 if(row.firstChild){
62654 row.firstChild.style.width = tw;
62655 trow = row.firstChild.rows[0];
62656 for (var j = 0; j < clen; j++) {
62657 trow.childNodes[j].style.width = ws[j];
62662 this.onAllColumnWidthsUpdated(ws, tw);
62666 updateColumnWidth : function(col, width){
62667 var w = this.getColumnWidth(col);
62668 var tw = this.getTotalWidth();
62669 this.innerHd.firstChild.style.width = this.getOffsetWidth();
62670 this.innerHd.firstChild.firstChild.style.width = tw;
62671 this.mainBody.dom.style.width = tw;
62672 var hd = this.getHeaderCell(col);
62673 hd.style.width = w;
62675 var ns = this.getRows(), row;
62676 for(var i = 0, len = ns.length; i < len; i++){
62678 row.style.width = tw;
62679 if(row.firstChild){
62680 row.firstChild.style.width = tw;
62681 row.firstChild.rows[0].childNodes[col].style.width = w;
62685 this.onColumnWidthUpdated(col, w, tw);
62689 updateColumnHidden : function(col, hidden){
62690 var tw = this.getTotalWidth();
62691 this.innerHd.firstChild.style.width = this.getOffsetWidth();
62692 this.innerHd.firstChild.firstChild.style.width = tw;
62693 this.mainBody.dom.style.width = tw;
62694 var display = hidden ? 'none' : '';
62696 var hd = this.getHeaderCell(col);
62697 hd.style.display = display;
62699 var ns = this.getRows(), row;
62700 for(var i = 0, len = ns.length; i < len; i++){
62702 row.style.width = tw;
62703 if(row.firstChild){
62704 row.firstChild.style.width = tw;
62705 row.firstChild.rows[0].childNodes[col].style.display = display;
62709 this.onColumnHiddenUpdated(col, hidden, tw);
62710 delete this.lastViewWidth; // force recalc
62715 doRender : function(cs, rs, ds, startRow, colCount, stripe){
62716 var ts = this.templates, ct = ts.cell, rt = ts.row, last = colCount-1;
62717 var tstyle = 'width:'+this.getTotalWidth()+';';
62719 var buf = [], cb, c, p = {}, rp = {tstyle: tstyle}, r;
62720 for(var j = 0, len = rs.length; j < len; j++){
62721 r = rs[j]; cb = [];
62722 var rowIndex = (j+startRow);
62723 for(var i = 0; i < colCount; i++){
62726 p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
62727 p.attr = p.cellAttr = '';
62728 p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
62730 if(Ext.isEmpty(p.value)){
62731 p.value = ' ';
62733 if(this.markDirty && r.dirty && typeof r.modified[c.name] !== 'undefined'){
62734 p.css += ' x-grid3-dirty-cell';
62736 cb[cb.length] = ct.apply(p);
62739 if(stripe && ((rowIndex+1) % 2 === 0)){
62740 alt[0] = 'x-grid3-row-alt';
62743 alt[1] = ' x-grid3-dirty-row';
62745 rp.cols = colCount;
62746 if(this.getRowClass){
62747 alt[2] = this.getRowClass(r, rowIndex, rp, ds);
62749 rp.alt = alt.join(' ');
62750 rp.cells = cb.join('');
62751 buf[buf.length] = rt.apply(rp);
62753 return buf.join('');
62757 processRows : function(startRow, skipStripe){
62758 if(!this.ds || this.ds.getCount() < 1){
62761 var rows = this.getRows();
62762 skipStripe = skipStripe || !this.grid.stripeRows;
62763 startRow = startRow || 0;
62764 Ext.each(rows, function(row, idx){
62765 row.rowIndex = idx;
62767 row.className = row.className.replace(this.rowClsRe, ' ');
62768 if ((idx + 1) % 2 === 0){
62769 row.className += ' x-grid3-row-alt';
62773 // add first/last-row classes
62774 if(startRow === 0){
62775 Ext.fly(rows[0]).addClass(this.firstRowCls);
62777 Ext.fly(rows[rows.length - 1]).addClass(this.lastRowCls);
62780 afterRender : function(){
62781 if(!this.ds || !this.cm){
62784 this.mainBody.dom.innerHTML = this.renderRows() || ' ';
62785 this.processRows(0, true);
62787 if(this.deferEmptyText !== true){
62788 this.applyEmptyText();
62793 renderUI : function(){
62795 var header = this.renderHeaders();
62796 var body = this.templates.body.apply({rows:' '});
62799 var html = this.templates.master.apply({
62802 ostyle: 'width:'+this.getOffsetWidth()+';',
62803 bstyle: 'width:'+this.getTotalWidth()+';'
62808 g.getGridEl().dom.innerHTML = html;
62810 this.initElements();
62812 // get mousedowns early
62813 Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
62816 mouseover: this.handleHdOver,
62817 mouseout: this.handleHdOut,
62818 mousemove: this.handleHdMove
62821 this.scroller.on('scroll', this.syncScroll, this);
62822 if(g.enableColumnResize !== false){
62823 this.splitZone = new Ext.grid.GridView.SplitDragZone(g, this.mainHd.dom);
62826 if(g.enableColumnMove){
62827 this.columnDrag = new Ext.grid.GridView.ColumnDragZone(g, this.innerHd);
62828 this.columnDrop = new Ext.grid.HeaderDropZone(g, this.mainHd.dom);
62831 if(g.enableHdMenu !== false){
62832 this.hmenu = new Ext.menu.Menu({id: g.id + '-hctx'});
62834 {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
62835 {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
62837 if(g.enableColumnHide !== false){
62838 this.colMenu = new Ext.menu.Menu({id:g.id + '-hcols-menu'});
62841 beforeshow: this.beforeColMenuShow,
62842 itemclick: this.handleHdMenuClick
62844 this.hmenu.add('-', {
62846 hideOnClick: false,
62847 text: this.columnsText,
62848 menu: this.colMenu,
62849 iconCls: 'x-cols-icon'
62852 this.hmenu.on('itemclick', this.handleHdMenuClick, this);
62855 if(g.trackMouseOver){
62858 mouseover: this.onRowOver,
62859 mouseout: this.onRowOut
62863 if(g.enableDragDrop || g.enableDrag){
62864 this.dragZone = new Ext.grid.GridDragZone(g, {
62865 ddGroup : g.ddGroup || 'GridDD'
62869 this.updateHeaderSortState();
62874 layout : function(){
62875 if(!this.mainBody){
62876 return; // not rendered
62879 var c = g.getGridEl();
62880 var csize = c.getSize(true);
62881 var vw = csize.width;
62883 if(!g.hideHeaders && (vw < 20 || csize.height < 20)){ // display: none?
62888 this.scroller.dom.style.overflow = 'visible';
62890 this.scroller.dom.style.position = 'static';
62893 this.el.setSize(csize.width, csize.height);
62895 var hdHeight = this.mainHd.getHeight();
62896 var vh = csize.height - (hdHeight);
62898 this.scroller.setSize(vw, vh);
62900 this.innerHd.style.width = (vw)+'px';
62904 if(this.lastViewWidth != vw){
62905 this.fitColumns(false, false);
62906 this.lastViewWidth = vw;
62910 this.syncHeaderScroll();
62912 this.onLayout(vw, vh);
62915 // template functions for subclasses and plugins
62916 // these functions include precalculated values
62917 onLayout : function(vw, vh){
62921 onColumnWidthUpdated : function(col, w, tw){
62925 onAllColumnWidthsUpdated : function(ws, tw){
62929 onColumnHiddenUpdated : function(col, hidden, tw){
62933 updateColumnText : function(col, text){
62937 afterMove : function(colIndex){
62941 /* ----------------------------------- Core Specific -------------------------------------------*/
62943 init : function(grid){
62946 this.initTemplates();
62947 this.initData(grid.store, grid.colModel);
62952 getColumnId : function(index){
62953 return this.cm.getColumnId(index);
62957 getOffsetWidth : function() {
62958 return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
62961 getScrollOffset: function(){
62962 return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
62966 renderHeaders : function(){
62968 ts = this.templates,
62972 len = cm.getColumnCount(),
62975 for(var i = 0; i < len; i++){
62976 p.id = cm.getColumnId(i);
62977 p.value = cm.getColumnHeader(i) || '';
62978 p.style = this.getColumnStyle(i, true);
62979 p.tooltip = this.getColumnTooltip(i);
62980 p.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
62981 if(cm.config[i].align == 'right'){
62982 p.istyle = 'padding-right:16px';
62986 cb[cb.length] = ct.apply(p);
62988 return ts.header.apply({cells: cb.join(''), tstyle:'width:'+this.getTotalWidth()+';'});
62992 getColumnTooltip : function(i){
62993 var tt = this.cm.getColumnTooltip(i);
62995 if(Ext.QuickTips.isEnabled()){
62996 return 'ext:qtip="'+tt+'"';
62998 return 'title="'+tt+'"';
63005 beforeUpdate : function(){
63006 this.grid.stopEditing(true);
63010 updateHeaders : function(){
63011 this.innerHd.firstChild.innerHTML = this.renderHeaders();
63012 this.innerHd.firstChild.style.width = this.getOffsetWidth();
63013 this.innerHd.firstChild.firstChild.style.width = this.getTotalWidth();
63017 * Focuses the specified row.
63018 * @param {Number} row The row index
63020 focusRow : function(row){
63021 this.focusCell(row, 0, false);
63025 * Focuses the specified cell.
63026 * @param {Number} row The row index
63027 * @param {Number} col The column index
63029 focusCell : function(row, col, hscroll){
63030 this.syncFocusEl(this.ensureVisible(row, col, hscroll));
63032 this.focusEl.focus();
63034 this.focusEl.focus.defer(1, this.focusEl);
63038 resolveCell : function(row, col, hscroll){
63039 if(typeof row != "number"){
63040 row = row.rowIndex;
63045 if(row < 0 || row >= this.ds.getCount()){
63048 col = (col !== undefined ? col : 0);
63050 var rowEl = this.getRow(row),
63052 colCount = cm.getColumnCount(),
63054 if(!(hscroll === false && col === 0)){
63055 while(col < colCount && cm.isHidden(col)){
63058 cellEl = this.getCell(row, col);
63061 return {row: rowEl, cell: cellEl};
63064 getResolvedXY : function(resolved){
63068 var s = this.scroller.dom, c = resolved.cell, r = resolved.row;
63069 return c ? Ext.fly(c).getXY() : [this.el.getX(), Ext.fly(r).getY()];
63072 syncFocusEl : function(row, col, hscroll){
63074 if(!Ext.isArray(xy)){
63075 row = Math.min(row, Math.max(0, this.getRows().length-1));
63076 xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
63078 this.focusEl.setXY(xy||this.scroller.getXY());
63081 ensureVisible : function(row, col, hscroll){
63082 var resolved = this.resolveCell(row, col, hscroll);
63083 if(!resolved || !resolved.row){
63087 var rowEl = resolved.row,
63088 cellEl = resolved.cell,
63089 c = this.scroller.dom,
63092 stop = this.el.dom;
63094 while(p && p != stop){
63095 ctop += p.offsetTop;
63096 p = p.offsetParent;
63099 ctop -= this.mainHd.dom.offsetHeight;
63100 stop = parseInt(c.scrollTop, 10);
63102 var cbot = ctop + rowEl.offsetHeight,
63103 ch = c.clientHeight,
63108 c.scrollTop = ctop;
63109 }else if(cbot > sbot){
63110 c.scrollTop = cbot-ch;
63113 if(hscroll !== false){
63114 var cleft = parseInt(cellEl.offsetLeft, 10);
63115 var cright = cleft + cellEl.offsetWidth;
63117 var sleft = parseInt(c.scrollLeft, 10);
63118 var sright = sleft + c.clientWidth;
63120 c.scrollLeft = cleft;
63121 }else if(cright > sright){
63122 c.scrollLeft = cright-c.clientWidth;
63125 return this.getResolvedXY(resolved);
63129 insertRows : function(dm, firstRow, lastRow, isUpdate){
63130 var last = dm.getCount() - 1;
63131 if(!isUpdate && firstRow === 0 && lastRow >= last){
63135 this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
63137 var html = this.renderRows(firstRow, lastRow),
63138 before = this.getRow(firstRow);
63140 if(firstRow === 0){
63141 Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
63143 Ext.DomHelper.insertHtml('beforeBegin', before, html);
63145 var r = this.getRow(last - 1);
63147 Ext.fly(r).removeClass(this.lastRowCls);
63149 Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
63152 this.fireEvent('rowsinserted', this, firstRow, lastRow);
63153 this.processRows(firstRow);
63154 }else if(firstRow === 0 || firstRow >= last){
63155 //ensure first/last row is kept after an update.
63156 Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
63159 this.syncFocusEl(firstRow);
63163 deleteRows : function(dm, firstRow, lastRow){
63164 if(dm.getRowCount()<1){
63167 this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
63169 this.removeRows(firstRow, lastRow);
63171 this.processRows(firstRow);
63172 this.fireEvent('rowsdeleted', this, firstRow, lastRow);
63177 getColumnStyle : function(col, isHeader){
63178 var style = !isHeader ? (this.cm.config[col].css || '') : '';
63179 style += 'width:'+this.getColumnWidth(col)+';';
63180 if(this.cm.isHidden(col)){
63181 style += 'display:none;';
63183 var align = this.cm.config[col].align;
63185 style += 'text-align:'+align+';';
63191 getColumnWidth : function(col){
63192 var w = this.cm.getColumnWidth(col);
63193 if(typeof w == 'number'){
63194 return (Ext.isBorderBox || (Ext.isWebKit && !Ext.isSafari2) ? w : (w - this.borderWidth > 0 ? w - this.borderWidth : 0)) + 'px';
63200 getTotalWidth : function(){
63201 return this.cm.getTotalWidth()+'px';
63205 fitColumns : function(preventRefresh, onlyExpand, omitColumn){
63206 var cm = this.cm, i;
63207 var tw = cm.getTotalWidth(false);
63208 var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
63210 if(aw < 20){ // not initialized, so don't screw up the default widths
63213 var extra = aw - tw;
63219 var vc = cm.getColumnCount(true);
63220 var ac = vc-(typeof omitColumn == 'number' ? 1 : 0);
63223 omitColumn = undefined;
63225 var colCount = cm.getColumnCount();
63230 for (i = 0; i < colCount; i++){
63231 if(!cm.isHidden(i) && !cm.isFixed(i) && i !== omitColumn){
63232 w = cm.getColumnWidth(i);
63239 var frac = (aw - cm.getTotalWidth())/width;
63240 while (cols.length){
63243 cm.setColumnWidth(i, Math.max(this.grid.minColumnWidth, Math.floor(w + w*frac)), true);
63246 if((tw = cm.getTotalWidth(false)) > aw){
63247 var adjustCol = ac != vc ? omitColumn : extraCol;
63248 cm.setColumnWidth(adjustCol, Math.max(1,
63249 cm.getColumnWidth(adjustCol)- (tw-aw)), true);
63252 if(preventRefresh !== true){
63253 this.updateAllColumnWidths();
63261 autoExpand : function(preventUpdate){
63262 var g = this.grid, cm = this.cm;
63263 if(!this.userResized && g.autoExpandColumn){
63264 var tw = cm.getTotalWidth(false);
63265 var aw = this.grid.getGridEl().getWidth(true)-this.getScrollOffset();
63267 var ci = cm.getIndexById(g.autoExpandColumn);
63268 var currentWidth = cm.getColumnWidth(ci);
63269 var cw = Math.min(Math.max(((aw-tw)+currentWidth), g.autoExpandMin), g.autoExpandMax);
63270 if(cw != currentWidth){
63271 cm.setColumnWidth(ci, cw, true);
63272 if(preventUpdate !== true){
63273 this.updateColumnWidth(ci, cw);
63281 getColumnData : function(){
63282 // build a map for all the columns
63283 var cs = [], cm = this.cm, colCount = cm.getColumnCount();
63284 for(var i = 0; i < colCount; i++){
63285 var name = cm.getDataIndex(i);
63287 name : (typeof name == 'undefined' ? this.ds.fields.get(i).name : name),
63288 renderer : cm.getRenderer(i),
63289 id : cm.getColumnId(i),
63290 style : this.getColumnStyle(i)
63297 renderRows : function(startRow, endRow){
63298 // pull in all the crap needed to render rows
63299 var g = this.grid, cm = g.colModel, ds = g.store, stripe = g.stripeRows;
63300 var colCount = cm.getColumnCount();
63302 if(ds.getCount() < 1){
63306 var cs = this.getColumnData();
63308 startRow = startRow || 0;
63309 endRow = typeof endRow == "undefined"? ds.getCount()-1 : endRow;
63311 // records to render
63312 var rs = ds.getRange(startRow, endRow);
63314 return this.doRender(cs, rs, ds, startRow, colCount, stripe);
63318 renderBody : function(){
63319 var markup = this.renderRows() || ' ';
63320 return this.templates.body.apply({rows: markup});
63324 refreshRow : function(record){
63325 var ds = this.ds, index;
63326 if(typeof record == 'number'){
63328 record = ds.getAt(index);
63333 index = ds.indexOf(record);
63338 this.insertRows(ds, index, index, true);
63339 this.getRow(index).rowIndex = index;
63340 this.onRemove(ds, record, index+1, true);
63341 this.fireEvent('rowupdated', this, index, record);
63345 * Refreshs the grid UI
63346 * @param {Boolean} headersToo (optional) True to also refresh the headers
63348 refresh : function(headersToo){
63349 this.fireEvent('beforerefresh', this);
63350 this.grid.stopEditing(true);
63352 var result = this.renderBody();
63353 this.mainBody.update(result).setWidth(this.getTotalWidth());
63354 if(headersToo === true){
63355 this.updateHeaders();
63356 this.updateHeaderSortState();
63358 this.processRows(0, true);
63360 this.applyEmptyText();
63361 this.fireEvent('refresh', this);
63365 applyEmptyText : function(){
63366 if(this.emptyText && !this.hasRows()){
63367 this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
63372 updateHeaderSortState : function(){
63373 var state = this.ds.getSortState();
63377 if(!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)){
63378 this.grid.fireEvent('sortchange', this.grid, state);
63380 this.sortState = state;
63381 var sortColumn = this.cm.findColumnIndex(state.field);
63382 if(sortColumn != -1){
63383 var sortDir = state.direction;
63384 this.updateSortIcon(sortColumn, sortDir);
63389 destroy : function(){
63391 Ext.menu.MenuMgr.unregister(this.colMenu);
63392 this.colMenu.destroy();
63393 delete this.colMenu;
63396 Ext.menu.MenuMgr.unregister(this.hmenu);
63397 this.hmenu.destroy();
63401 this.initData(null, null);
63402 this.purgeListeners();
63403 Ext.fly(this.innerHd).un("click", this.handleHdDown, this);
63405 if(this.grid.enableColumnMove){
63407 this.columnDrag.el,
63408 this.columnDrag.proxy.ghost,
63409 this.columnDrag.proxy.el,
63410 this.columnDrop.el,
63411 this.columnDrop.proxyTop,
63412 this.columnDrop.proxyBottom,
63413 this.columnDrag.dragData.ddel,
63414 this.columnDrag.dragData.header
63416 if (this.columnDrag.proxy.anim) {
63417 Ext.destroy(this.columnDrag.proxy.anim);
63419 delete this.columnDrag.proxy.ghost;
63420 delete this.columnDrag.dragData.ddel;
63421 delete this.columnDrag.dragData.header;
63422 this.columnDrag.destroy();
63423 delete Ext.dd.DDM.locationCache[this.columnDrag.id];
63424 delete this.columnDrag._domRef;
63426 delete this.columnDrop.proxyTop;
63427 delete this.columnDrop.proxyBottom;
63428 this.columnDrop.destroy();
63429 delete Ext.dd.DDM.locationCache["gridHeader" + this.grid.getGridEl().id];
63430 delete this.columnDrop._domRef;
63431 delete Ext.dd.DDM.ids[this.columnDrop.ddGroup];
63434 if (this.splitone){ // enableColumnResize
63435 this.splitone.destroy();
63436 delete this.splitone._domRef;
63437 delete Ext.dd.DDM.ids["gridSplitters" + this.grid.getGridEl().id];
63440 Ext.fly(this.innerHd).removeAllListeners();
63441 Ext.removeNode(this.innerHd);
63442 delete this.innerHd;
63459 delete this.grid.container;
63462 this.dragZone.destroy();
63465 Ext.dd.DDM.currentTarget = null;
63466 delete Ext.dd.DDM.locationCache[this.grid.getGridEl().id];
63468 Ext.EventManager.removeResizeListener(this.onWindowResize, this);
63472 onDenyColumnHide : function(){
63477 render : function(){
63479 var ct = this.grid.ownerCt;
63480 if (ct && ct.getLayout()){
63481 ct.on('afterlayout', function(){
63482 this.fitColumns(true, true);
63483 this.updateHeaders();
63484 }, this, {single: true});
63486 this.fitColumns(true, true);
63488 }else if(this.forceFit){
63489 this.fitColumns(true, false);
63490 }else if(this.grid.autoExpandColumn){
63491 this.autoExpand(true);
63497 /* --------------------------------- Model Events and Handlers --------------------------------*/
63499 initData : function(ds, cm){
63501 this.ds.un('load', this.onLoad, this);
63502 this.ds.un('datachanged', this.onDataChange, this);
63503 this.ds.un('add', this.onAdd, this);
63504 this.ds.un('remove', this.onRemove, this);
63505 this.ds.un('update', this.onUpdate, this);
63506 this.ds.un('clear', this.onClear, this);
63507 if(this.ds !== ds && this.ds.autoDestroy){
63515 datachanged: this.onDataChange,
63517 remove: this.onRemove,
63518 update: this.onUpdate,
63519 clear: this.onClear
63525 this.cm.un('configchange', this.onColConfigChange, this);
63526 this.cm.un('widthchange', this.onColWidthChange, this);
63527 this.cm.un('headerchange', this.onHeaderChange, this);
63528 this.cm.un('hiddenchange', this.onHiddenChange, this);
63529 this.cm.un('columnmoved', this.onColumnMove, this);
63532 delete this.lastViewWidth;
63535 configchange: this.onColConfigChange,
63536 widthchange: this.onColWidthChange,
63537 headerchange: this.onHeaderChange,
63538 hiddenchange: this.onHiddenChange,
63539 columnmoved: this.onColumnMove
63546 onDataChange : function(){
63548 this.updateHeaderSortState();
63549 this.syncFocusEl(0);
63553 onClear : function(){
63555 this.syncFocusEl(0);
63559 onUpdate : function(ds, record){
63560 this.refreshRow(record);
63564 onAdd : function(ds, records, index){
63565 this.insertRows(ds, index, index + (records.length-1));
63569 onRemove : function(ds, record, index, isUpdate){
63570 if(isUpdate !== true){
63571 this.fireEvent('beforerowremoved', this, index, record);
63573 this.removeRow(index);
63574 if(isUpdate !== true){
63575 this.processRows(index);
63576 this.applyEmptyText();
63577 this.fireEvent('rowremoved', this, index, record);
63582 onLoad : function(){
63583 this.scrollToTop.defer(Ext.isGecko ? 1 : 0, this);
63587 onColWidthChange : function(cm, col, width){
63588 this.updateColumnWidth(col, width);
63592 onHeaderChange : function(cm, col, text){
63593 this.updateHeaders();
63597 onHiddenChange : function(cm, col, hidden){
63598 this.updateColumnHidden(col, hidden);
63602 onColumnMove : function(cm, oldIndex, newIndex){
63603 this.indexMap = null;
63604 var s = this.getScrollState();
63605 this.refresh(true);
63606 this.restoreScroll(s);
63607 this.afterMove(newIndex);
63608 this.grid.fireEvent('columnmove', oldIndex, newIndex);
63612 onColConfigChange : function(){
63613 delete this.lastViewWidth;
63614 this.indexMap = null;
63615 this.refresh(true);
63618 /* -------------------- UI Events and Handlers ------------------------------ */
63620 initUI : function(grid){
63621 grid.on('headerclick', this.onHeaderClick, this);
63625 initEvents : function(){
63629 onHeaderClick : function(g, index){
63630 if(this.headersDisabled || !this.cm.isSortable(index)){
63633 g.stopEditing(true);
63634 g.store.sort(this.cm.getDataIndex(index));
63638 onRowOver : function(e, t){
63640 if((row = this.findRowIndex(t)) !== false){
63641 this.addRowClass(row, 'x-grid3-row-over');
63646 onRowOut : function(e, t){
63648 if((row = this.findRowIndex(t)) !== false && !e.within(this.getRow(row), true)){
63649 this.removeRowClass(row, 'x-grid3-row-over');
63654 handleWheel : function(e){
63655 e.stopPropagation();
63659 onRowSelect : function(row){
63660 this.addRowClass(row, this.selectedRowClass);
63664 onRowDeselect : function(row){
63665 this.removeRowClass(row, this.selectedRowClass);
63669 onCellSelect : function(row, col){
63670 var cell = this.getCell(row, col);
63672 this.fly(cell).addClass('x-grid3-cell-selected');
63677 onCellDeselect : function(row, col){
63678 var cell = this.getCell(row, col);
63680 this.fly(cell).removeClass('x-grid3-cell-selected');
63685 onColumnSplitterMoved : function(i, w){
63686 this.userResized = true;
63687 var cm = this.grid.colModel;
63688 cm.setColumnWidth(i, w, true);
63691 this.fitColumns(true, false, i);
63692 this.updateAllColumnWidths();
63694 this.updateColumnWidth(i, w);
63695 this.syncHeaderScroll();
63698 this.grid.fireEvent('columnresize', i, w);
63702 handleHdMenuClick : function(item){
63703 var index = this.hdCtxIndex,
63706 id = item.getItemId();
63709 ds.sort(cm.getDataIndex(index), 'ASC');
63712 ds.sort(cm.getDataIndex(index), 'DESC');
63715 index = cm.getIndexById(id.substr(4));
63717 if(item.checked && cm.getColumnsBy(this.isHideableColumn, this).length <= 1){
63718 this.onDenyColumnHide();
63721 cm.setHidden(index, item.checked);
63728 isHideableColumn : function(c){
63729 return !c.hidden && !c.fixed;
63733 beforeColMenuShow : function(){
63734 var cm = this.cm, colCount = cm.getColumnCount();
63735 this.colMenu.removeAll();
63736 for(var i = 0; i < colCount; i++){
63737 if(cm.config[i].fixed !== true && cm.config[i].hideable !== false){
63738 this.colMenu.add(new Ext.menu.CheckItem({
63739 itemId: 'col-'+cm.getColumnId(i),
63740 text: cm.getColumnHeader(i),
63741 checked: !cm.isHidden(i),
63743 disabled: cm.config[i].hideable === false
63750 handleHdDown : function(e, t){
63751 if(Ext.fly(t).hasClass('x-grid3-hd-btn')){
63753 var hd = this.findHeaderCell(t);
63754 Ext.fly(hd).addClass('x-grid3-hd-menu-open');
63755 var index = this.getCellIndex(hd);
63756 this.hdCtxIndex = index;
63757 var ms = this.hmenu.items, cm = this.cm;
63758 ms.get('asc').setDisabled(!cm.isSortable(index));
63759 ms.get('desc').setDisabled(!cm.isSortable(index));
63760 this.hmenu.on('hide', function(){
63761 Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
63762 }, this, {single:true});
63763 this.hmenu.show(t, 'tl-bl?');
63768 handleHdOver : function(e, t){
63769 var hd = this.findHeaderCell(t);
63770 if(hd && !this.headersDisabled){
63771 this.activeHdRef = t;
63772 this.activeHdIndex = this.getCellIndex(hd);
63773 var fly = this.fly(hd);
63774 this.activeHdRegion = fly.getRegion();
63775 if(!this.cm.isMenuDisabled(this.activeHdIndex)){
63776 fly.addClass('x-grid3-hd-over');
63777 this.activeHdBtn = fly.child('.x-grid3-hd-btn');
63778 if(this.activeHdBtn){
63779 this.activeHdBtn.dom.style.height = (hd.firstChild.offsetHeight-1)+'px';
63786 handleHdMove : function(e, t){
63787 var hd = this.findHeaderCell(this.activeHdRef);
63788 if(hd && !this.headersDisabled){
63789 var hw = this.splitHandleWidth || 5,
63790 r = this.activeHdRegion,
63794 if(this.grid.enableColumnResize !== false){
63795 if(x - r.left <= hw && this.cm.isResizable(this.activeHdIndex-1)){
63796 cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize'; // col-resize not always supported
63797 }else if(r.right - x <= (!this.activeHdBtn ? hw : 2) && this.cm.isResizable(this.activeHdIndex)){
63798 cur = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
63806 handleHdOut : function(e, t){
63807 var hd = this.findHeaderCell(t);
63808 if(hd && (!Ext.isIE || !e.within(hd, true))){
63809 this.activeHdRef = null;
63810 this.fly(hd).removeClass('x-grid3-hd-over');
63811 hd.style.cursor = '';
63816 hasRows : function(){
63817 var fc = this.mainBody.dom.firstChild;
63818 return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
63822 bind : function(d, c){
63823 this.initData(d, c);
63829 // This is a support class used internally by the Grid components
63830 Ext.grid.GridView.SplitDragZone = function(grid, hd){
63832 this.view = grid.getView();
63833 this.marker = this.view.resizeMarker;
63834 this.proxy = this.view.resizeProxy;
63835 Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
63836 'gridSplitters' + this.grid.getGridEl().id, {
63837 dragElId : Ext.id(this.proxy.dom), resizeFrame:false
63839 this.scroll = false;
63840 this.hw = this.view.splitHandleWidth || 5;
63842 Ext.extend(Ext.grid.GridView.SplitDragZone, Ext.dd.DDProxy, {
63844 b4StartDrag : function(x, y){
63845 this.view.headersDisabled = true;
63846 var h = this.view.mainWrap.getHeight();
63847 this.marker.setHeight(h);
63848 this.marker.show();
63849 this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
63850 this.proxy.setHeight(h);
63851 var w = this.cm.getColumnWidth(this.cellIndex);
63852 var minw = Math.max(w-this.grid.minColumnWidth, 0);
63853 this.resetConstraints();
63854 this.setXConstraint(minw, 1000);
63855 this.setYConstraint(0, 0);
63856 this.minX = x - minw;
63857 this.maxX = x + 1000;
63859 Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
63863 handleMouseDown : function(e){
63864 var t = this.view.findHeaderCell(e.getTarget());
63866 var xy = this.view.fly(t).getXY(), x = xy[0], y = xy[1];
63867 var exy = e.getXY(), ex = exy[0];
63868 var w = t.offsetWidth, adjust = false;
63869 if((ex - x) <= this.hw){
63871 }else if((x+w) - ex <= this.hw){
63874 if(adjust !== false){
63875 this.cm = this.grid.colModel;
63876 var ci = this.view.getCellIndex(t);
63878 if (ci + adjust < 0) {
63881 while(this.cm.isHidden(ci+adjust)){
63888 this.cellIndex = ci+adjust;
63889 this.split = t.dom;
63890 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
63891 Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
63893 }else if(this.view.columnDrag){
63894 this.view.columnDrag.callHandleMouseDown(e);
63899 endDrag : function(e){
63900 this.marker.hide();
63902 var endX = Math.max(this.minX, e.getPageX());
63903 var diff = endX - this.startPos;
63904 v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
63905 setTimeout(function(){
63906 v.headersDisabled = false;
63910 autoOffset : function(){
63911 this.setDelta(0,0);
63915 // This is a support class used internally by the Grid components
\r
63916 Ext.grid.HeaderDragZone = function(grid, hd, hd2){
\r
63917 this.grid = grid;
\r
63918 this.view = grid.getView();
\r
63919 this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
\r
63920 Ext.grid.HeaderDragZone.superclass.constructor.call(this, hd);
\r
63922 this.setHandleElId(Ext.id(hd));
\r
63923 this.setOuterHandleElId(Ext.id(hd2));
\r
63925 this.scroll = false;
\r
63927 Ext.extend(Ext.grid.HeaderDragZone, Ext.dd.DragZone, {
\r
63928 maxDragWidth: 120,
\r
63929 getDragData : function(e){
\r
63930 var t = Ext.lib.Event.getTarget(e);
\r
63931 var h = this.view.findHeaderCell(t);
\r
63933 return {ddel: h.firstChild, header:h};
\r
63938 onInitDrag : function(e){
\r
63939 this.view.headersDisabled = true;
\r
63940 var clone = this.dragData.ddel.cloneNode(true);
\r
63941 clone.id = Ext.id();
\r
63942 clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
\r
63943 this.proxy.update(clone);
\r
63947 afterValidDrop : function(){
\r
63948 var v = this.view;
\r
63949 setTimeout(function(){
\r
63950 v.headersDisabled = false;
\r
63954 afterInvalidDrop : function(){
\r
63955 var v = this.view;
\r
63956 setTimeout(function(){
\r
63957 v.headersDisabled = false;
\r
63963 // This is a support class used internally by the Grid components
\r
63964 Ext.grid.HeaderDropZone = function(grid, hd, hd2){
\r
63965 this.grid = grid;
\r
63966 this.view = grid.getView();
\r
63967 // split the proxies so they don't interfere with mouse events
\r
63968 this.proxyTop = Ext.DomHelper.append(document.body, {
\r
63969 cls:"col-move-top", html:" "
\r
63971 this.proxyBottom = Ext.DomHelper.append(document.body, {
\r
63972 cls:"col-move-bottom", html:" "
\r
63974 this.proxyTop.hide = this.proxyBottom.hide = function(){
\r
63975 this.setLeftTop(-100,-100);
\r
63976 this.setStyle("visibility", "hidden");
\r
63978 this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
\r
63979 // temporarily disabled
\r
63980 //Ext.dd.ScrollManager.register(this.view.scroller.dom);
\r
63981 Ext.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
\r
63983 Ext.extend(Ext.grid.HeaderDropZone, Ext.dd.DropZone, {
\r
63984 proxyOffsets : [-4, -9],
\r
63985 fly: Ext.Element.fly,
\r
63987 getTargetFromEvent : function(e){
\r
63988 var t = Ext.lib.Event.getTarget(e);
\r
63989 var cindex = this.view.findCellIndex(t);
\r
63990 if(cindex !== false){
\r
63991 return this.view.getHeaderCell(cindex);
\r
63995 nextVisible : function(h){
\r
63996 var v = this.view, cm = this.grid.colModel;
\r
63997 h = h.nextSibling;
\r
63999 if(!cm.isHidden(v.getCellIndex(h))){
\r
64002 h = h.nextSibling;
\r
64007 prevVisible : function(h){
\r
64008 var v = this.view, cm = this.grid.colModel;
\r
64009 h = h.prevSibling;
\r
64011 if(!cm.isHidden(v.getCellIndex(h))){
\r
64014 h = h.prevSibling;
\r
64019 positionIndicator : function(h, n, e){
\r
64020 var x = Ext.lib.Event.getPageX(e);
\r
64021 var r = Ext.lib.Dom.getRegion(n.firstChild);
\r
64022 var px, pt, py = r.top + this.proxyOffsets[1];
\r
64023 if((r.right - x) <= (r.right-r.left)/2){
\r
64024 px = r.right+this.view.borderWidth;
\r
64031 if(this.grid.colModel.isFixed(this.view.getCellIndex(n))){
\r
64035 px += this.proxyOffsets[0];
\r
64036 this.proxyTop.setLeftTop(px, py);
\r
64037 this.proxyTop.show();
\r
64038 if(!this.bottomOffset){
\r
64039 this.bottomOffset = this.view.mainHd.getHeight();
\r
64041 this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
\r
64042 this.proxyBottom.show();
\r
64046 onNodeEnter : function(n, dd, e, data){
\r
64047 if(data.header != n){
\r
64048 this.positionIndicator(data.header, n, e);
\r
64052 onNodeOver : function(n, dd, e, data){
\r
64053 var result = false;
\r
64054 if(data.header != n){
\r
64055 result = this.positionIndicator(data.header, n, e);
\r
64058 this.proxyTop.hide();
\r
64059 this.proxyBottom.hide();
\r
64061 return result ? this.dropAllowed : this.dropNotAllowed;
\r
64064 onNodeOut : function(n, dd, e, data){
\r
64065 this.proxyTop.hide();
\r
64066 this.proxyBottom.hide();
\r
64069 onNodeDrop : function(n, dd, e, data){
\r
64070 var h = data.header;
\r
64072 var cm = this.grid.colModel;
\r
64073 var x = Ext.lib.Event.getPageX(e);
\r
64074 var r = Ext.lib.Dom.getRegion(n.firstChild);
\r
64075 var pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
\r
64076 var oldIndex = this.view.getCellIndex(h);
\r
64077 var newIndex = this.view.getCellIndex(n);
\r
64078 if(pt == "after"){
\r
64081 if(oldIndex < newIndex){
\r
64084 cm.moveColumn(oldIndex, newIndex);
\r
64092 Ext.grid.GridView.ColumnDragZone = function(grid, hd){
\r
64093 Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
\r
64094 this.proxy.el.addClass('x-grid3-col-dd');
\r
64097 Ext.extend(Ext.grid.GridView.ColumnDragZone, Ext.grid.HeaderDragZone, {
\r
64098 handleMouseDown : function(e){
\r
64102 callHandleMouseDown : function(e){
\r
64103 Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
\r
64106 // This is a support class used internally by the Grid components
64107 Ext.grid.SplitDragZone = function(grid, hd, hd2){
64109 this.view = grid.getView();
64110 this.proxy = this.view.resizeProxy;
64111 Ext.grid.SplitDragZone.superclass.constructor.call(this, hd,
64112 "gridSplitters" + this.grid.getGridEl().id, {
64113 dragElId : Ext.id(this.proxy.dom), resizeFrame:false
64115 this.setHandleElId(Ext.id(hd));
64116 this.setOuterHandleElId(Ext.id(hd2));
64117 this.scroll = false;
64119 Ext.extend(Ext.grid.SplitDragZone, Ext.dd.DDProxy, {
64120 fly: Ext.Element.fly,
64122 b4StartDrag : function(x, y){
64123 this.view.headersDisabled = true;
64124 this.proxy.setHeight(this.view.mainWrap.getHeight());
64125 var w = this.cm.getColumnWidth(this.cellIndex);
64126 var minw = Math.max(w-this.grid.minColumnWidth, 0);
64127 this.resetConstraints();
64128 this.setXConstraint(minw, 1000);
64129 this.setYConstraint(0, 0);
64130 this.minX = x - minw;
64131 this.maxX = x + 1000;
64133 Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
64137 handleMouseDown : function(e){
64138 var ev = Ext.EventObject.setEvent(e);
64139 var t = this.fly(ev.getTarget());
64140 if(t.hasClass("x-grid-split")){
64141 this.cellIndex = this.view.getCellIndex(t.dom);
64142 this.split = t.dom;
64143 this.cm = this.grid.colModel;
64144 if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
64145 Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
64150 endDrag : function(e){
64151 this.view.headersDisabled = false;
64152 var endX = Math.max(this.minX, Ext.lib.Event.getPageX(e));
64153 var diff = endX - this.startPos;
64154 this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
64157 autoOffset : function(){
64158 this.setDelta(0,0);
64161 * @class Ext.grid.GridDragZone
64162 * @extends Ext.dd.DragZone
64163 * <p>A customized implementation of a {@link Ext.dd.DragZone DragZone} which provides default implementations of two of the
64164 * template methods of DragZone to enable dragging of the selected rows of a GridPanel.</p>
64165 * <p>A cooperating {@link Ext.dd.DropZone DropZone} must be created who's template method implementations of
64166 * {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
64167 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop}</p> are able
64168 * to process the {@link #getDragData data} which is provided.
64170 Ext.grid.GridDragZone = function(grid, config){
64171 this.view = grid.getView();
64172 Ext.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
64173 this.scroll = false;
64175 this.ddel = document.createElement('div');
64176 this.ddel.className = 'x-grid-dd-wrap';
64179 Ext.extend(Ext.grid.GridDragZone, Ext.dd.DragZone, {
64180 ddGroup : "GridDD",
64183 * <p>The provided implementation of the getDragData method which collects the data to be dragged from the GridPanel on mousedown.</p>
64184 * <p>This data is available for processing in the {@link Ext.dd.DropZone#onNodeEnter onNodeEnter}, {@link Ext.dd.DropZone#onNodeOver onNodeOver},
64185 * {@link Ext.dd.DropZone#onNodeOut onNodeOut} and {@link Ext.dd.DropZone#onNodeDrop onNodeDrop} methods of a cooperating {@link Ext.dd.DropZone DropZone}.</p>
64186 * <p>The data object contains the following properties:<ul>
64187 * <li><b>grid</b> : Ext.Grid.GridPanel<div class="sub-desc">The GridPanel from which the data is being dragged.</div></li>
64188 * <li><b>ddel</b> : htmlElement<div class="sub-desc">An htmlElement which provides the "picture" of the data being dragged.</div></li>
64189 * <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>
64190 * <li><b>selections</b> : Array<div class="sub-desc">An Array of the selected Records which are being dragged from the GridPanel.</div></li>
64193 getDragData : function(e){
64194 var t = Ext.lib.Event.getTarget(e);
64195 var rowIndex = this.view.findRowIndex(t);
64196 if(rowIndex !== false){
64197 var sm = this.grid.selModel;
64198 if(!sm.isSelected(rowIndex) || e.hasModifier()){
64199 sm.handleMouseDown(this.grid, rowIndex, e);
64201 return {grid: this.grid, ddel: this.ddel, rowIndex: rowIndex, selections:sm.getSelections()};
64207 * <p>The provided implementation of the onInitDrag method. Sets the <tt>innerHTML</tt> of the drag proxy which provides the "picture"
64208 * of the data being dragged.</p>
64209 * <p>The <tt>innerHTML</tt> data is found by calling the owning GridPanel's {@link Ext.grid.GridPanel#getDragDropText getDragDropText}.</p>
64211 onInitDrag : function(e){
64212 var data = this.dragData;
64213 this.ddel.innerHTML = this.grid.getDragDropText();
64214 this.proxy.update(this.ddel);
64215 // fire start drag?
64219 * An empty immplementation. Implement this to provide behaviour after a repair of an invalid drop. An implementation might highlight
64220 * the selected rows to show that they have not been dragged.
64222 afterRepair : function(){
64223 this.dragging = false;
64227 * <p>An empty implementation. Implement this to provide coordinates for the drag proxy to slide back to after an invalid drop.</p>
64228 * <p>Called before a repair of an invalid drop to get the XY to animate to.</p>
64229 * @param {EventObject} e The mouse up event
64230 * @return {Array} The xy location (e.g. [100, 200])
64232 getRepairXY : function(e, data){
64236 onEndDrag : function(data, e){
64240 onValidDrop : function(dd, e, id){
64245 beforeInvalidDrop : function(e, id){
64250 * @class Ext.grid.ColumnModel
64251 * @extends Ext.util.Observable
64252 * <p>After the data has been read into the client side cache (<b>{@link Ext.data.Store Store}</b>),
64253 * the ColumnModel is used to configure how and what parts of that data will be displayed in the
64254 * vertical slices (columns) of the grid. The Ext.grid.ColumnModel Class is the default implementation
64255 * of a ColumnModel used by implentations of {@link Ext.grid.GridPanel GridPanel}.</p>
64256 * <p>Data is mapped into the store's records and then indexed into the ColumnModel using the
64257 * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt>:</p>
64259 {data source} == mapping ==> {data store} == <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> ==> {ColumnModel}
64261 * <p>Each {@link Ext.grid.Column Column} in the grid's ColumnModel is configured with a
64262 * <tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt> to specify how the data within
64263 * each record in the store is indexed into the ColumnModel.</p>
64264 * <p>There are two ways to initialize the ColumnModel class:</p>
64265 * <p><u>Initialization Method 1: an Array</u></p>
64267 var colModel = new Ext.grid.ColumnModel([
64268 { header: "Ticker", width: 60, sortable: true},
64269 { header: "Company Name", width: 150, sortable: true, id: 'company'},
64270 { header: "Market Cap.", width: 100, sortable: true},
64271 { header: "$ Sales", width: 100, sortable: true, renderer: money},
64272 { header: "Employees", width: 100, sortable: true, resizable: false}
64275 * <p>The ColumnModel may be initialized with an Array of {@link Ext.grid.Column} column configuration
64276 * objects to define the initial layout / display of the columns in the Grid. The order of each
64277 * {@link Ext.grid.Column} column configuration object within the specified Array defines the initial
64278 * order of the column display. A Column's display may be initially hidden using the
64279 * <tt>{@link Ext.grid.Column#hidden hidden}</tt></b> config property (and then shown using the column
64280 * header menu). Fields that are not included in the ColumnModel will not be displayable at all.</p>
64281 * <p>How each column in the grid correlates (maps) to the {@link Ext.data.Record} field in the
64282 * {@link Ext.data.Store Store} the column draws its data from is configured through the
64283 * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b>. If the
64284 * <b><tt>{@link Ext.grid.Column#dataIndex dataIndex}</tt></b> is not explicitly defined (as shown in the
64285 * example above) it will use the column configuration's index in the Array as the index.</p>
64286 * <p>See <b><tt>{@link Ext.grid.Column}</tt></b> for additional configuration options for each column.</p>
64287 * <p><u>Initialization Method 2: an Object</u></p>
64288 * <p>In order to use configuration options from <tt>Ext.grid.ColumnModel</tt>, an Object may be used to
64289 * initialize the ColumnModel. The column configuration Array will be specified in the <tt><b>{@link #columns}</b></tt>
64290 * config property. The <tt><b>{@link #defaults}</b></tt> config property can be used to apply defaults
64291 * for all columns, e.g.:</p><pre><code>
64292 var colModel = new Ext.grid.ColumnModel({
64294 { header: "Ticker", width: 60, menuDisabled: false},
64295 { header: "Company Name", width: 150, id: 'company'},
64296 { header: "Market Cap."},
64297 { header: "$ Sales", renderer: money},
64298 { header: "Employees", resizable: false}
64302 menuDisabled: true,
64306 {@link #hiddenchange}: function(cm, colIndex, hidden) {
64307 saveConfig(colIndex, hidden);
64312 * <p>In both examples above, the ability to apply a CSS class to all cells in a column (including the
64313 * header) is demonstrated through the use of the <b><tt>{@link Ext.grid.Column#id id}</tt></b> config
64314 * option. This column could be styled by including the following css:</p><pre><code>
64315 //add this css *after* the core css is loaded
64316 .x-grid3-td-company {
64317 color: red; // entire column will have red font
64319 // modify the header row only, adding an icon to the column header
64320 .x-grid3-hd-company {
64321 background: transparent
64322 url(../../resources/images/icons/silk/building.png)
64323 no-repeat 3px 3px ! important;
64327 * Note that the "Company Name" column could be specified as the
64328 * <b><tt>{@link Ext.grid.GridPanel}.{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></b>.
64330 * @param {Mixed} config Specify either an Array of {@link Ext.grid.Column} configuration objects or specify
64331 * a configuration Object (see introductory section discussion utilizing Initialization Method 2 above).
64333 Ext.grid.ColumnModel = function(config){
64335 * An Array of {@link Ext.grid.Column Column definition} objects representing the configuration
64336 * of this ColumnModel. See {@link Ext.grid.Column} for the configuration properties that may
64341 if(config.columns){
64342 Ext.apply(this, config);
64343 this.setConfig(config.columns, true);
64345 this.setConfig(config, true);
64349 * @event widthchange
64350 * Fires when the width of a column is programmaticially changed using
64351 * <code>{@link #setColumnWidth}</code>.
64352 * Note internal resizing suppresses the event from firing. See also
64353 * {@link Ext.grid.GridPanel}.<code>{@link #columnresize}</code>.
64354 * @param {ColumnModel} this
64355 * @param {Number} columnIndex The column index
64356 * @param {Number} newWidth The new width
64360 * @event headerchange
64361 * Fires when the text of a header changes.
64362 * @param {ColumnModel} this
64363 * @param {Number} columnIndex The column index
64364 * @param {String} newText The new header text
64368 * @event hiddenchange
64369 * Fires when a column is hidden or "unhidden".
64370 * @param {ColumnModel} this
64371 * @param {Number} columnIndex The column index
64372 * @param {Boolean} hidden true if hidden, false otherwise
64376 * @event columnmoved
64377 * Fires when a column is moved.
64378 * @param {ColumnModel} this
64379 * @param {Number} oldIndex
64380 * @param {Number} newIndex
64384 * @event configchange
64385 * Fires when the configuration is changed
64386 * @param {ColumnModel} this
64390 Ext.grid.ColumnModel.superclass.constructor.call(this);
64392 Ext.extend(Ext.grid.ColumnModel, Ext.util.Observable, {
64394 * @cfg {Number} defaultWidth (optional) The width of columns which have no <tt>{@link #width}</tt>
64395 * specified (defaults to <tt>100</tt>). This property shall preferably be configured through the
64396 * <tt><b>{@link #defaults}</b></tt> config property.
64400 * @cfg {Boolean} defaultSortable (optional) Default sortable of columns which have no
64401 * sortable specified (defaults to <tt>false</tt>). This property shall preferably be configured
64402 * through the <tt><b>{@link #defaults}</b></tt> config property.
64404 defaultSortable: false,
64406 * @cfg {Array} columns An Array of object literals. The config options defined by
64407 * <b>{@link Ext.grid.Column}</b> are the options which may appear in the object literal for each
64408 * individual column definition.
64411 * @cfg {Object} defaults Object literal which will be used to apply {@link Ext.grid.Column}
64412 * configuration options to all <tt><b>{@link #columns}</b></tt>. Configuration options specified with
64413 * individual {@link Ext.grid.Column column} configs will supersede these <tt><b>{@link #defaults}</b></tt>.
64417 * Returns the id of the column at the specified index.
64418 * @param {Number} index The column index
64419 * @return {String} the id
64421 getColumnId : function(index){
64422 return this.config[index].id;
64425 getColumnAt : function(index){
64426 return this.config[index];
64430 * <p>Reconfigures this column model according to the passed Array of column definition objects.
64431 * For a description of the individual properties of a column definition object, see the
64432 * <a href="#Ext.grid.ColumnModel-configs">Config Options</a>.</p>
64433 * <p>Causes the {@link #configchange} event to be fired. A {@link Ext.grid.GridPanel GridPanel}
64434 * using this ColumnModel will listen for this event and refresh its UI automatically.</p>
64435 * @param {Array} config Array of Column definition objects.
64436 * @param {Boolean} initial Specify <tt>true</tt> to bypass cleanup which deletes the <tt>totalWidth</tt>
64437 * and destroys existing editors.
64439 setConfig : function(config, initial){
64441 if(!initial){ // cleanup
64442 delete this.totalWidth;
64443 for(i = 0, len = this.config.length; i < len; i++){
64444 c = this.config[i];
64446 c.editor.destroy();
64451 // backward compatibility
64452 this.defaults = Ext.apply({
64453 width: this.defaultWidth,
64454 sortable: this.defaultSortable
64457 this.config = config;
64460 for(i = 0, len = config.length; i < len; i++){
64461 c = Ext.applyIf(config[i], this.defaults);
64462 // if no id, create one using column's ordinal position
64463 if(typeof c.id == 'undefined'){
64467 var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
64471 this.lookup[c.id] = c;
64474 this.fireEvent('configchange', this);
64479 * Returns the column for a specified id.
64480 * @param {String} id The column id
64481 * @return {Object} the column
64483 getColumnById : function(id){
64484 return this.lookup[id];
64488 * Returns the index for a specified column id.
64489 * @param {String} id The column id
64490 * @return {Number} the index, or -1 if not found
64492 getIndexById : function(id){
64493 for(var i = 0, len = this.config.length; i < len; i++){
64494 if(this.config[i].id == id){
64502 * Moves a column from one position to another.
64503 * @param {Number} oldIndex The index of the column to move.
64504 * @param {Number} newIndex The position at which to reinsert the coolumn.
64506 moveColumn : function(oldIndex, newIndex){
64507 var c = this.config[oldIndex];
64508 this.config.splice(oldIndex, 1);
64509 this.config.splice(newIndex, 0, c);
64510 this.dataMap = null;
64511 this.fireEvent("columnmoved", this, oldIndex, newIndex);
64515 * Returns the number of columns.
64516 * @param {Boolean} visibleOnly Optional. Pass as true to only include visible columns.
64519 getColumnCount : function(visibleOnly){
64520 if(visibleOnly === true){
64522 for(var i = 0, len = this.config.length; i < len; i++){
64523 if(!this.isHidden(i)){
64529 return this.config.length;
64533 * Returns the column configs that return true by the passed function that is called
64534 * with (columnConfig, index)
64536 // returns an array of column config objects for all hidden columns
64537 var columns = grid.getColumnModel().getColumnsBy(function(c){
64541 * @param {Function} fn
64542 * @param {Object} scope (optional)
64543 * @return {Array} result
64545 getColumnsBy : function(fn, scope){
64547 for(var i = 0, len = this.config.length; i < len; i++){
64548 var c = this.config[i];
64549 if(fn.call(scope||this, c, i) === true){
64557 * Returns true if the specified column is sortable.
64558 * @param {Number} col The column index
64559 * @return {Boolean}
64561 isSortable : function(col){
64562 return !!this.config[col].sortable;
64566 * Returns true if the specified column menu is disabled.
64567 * @param {Number} col The column index
64568 * @return {Boolean}
64570 isMenuDisabled : function(col){
64571 return !!this.config[col].menuDisabled;
64575 * Returns the rendering (formatting) function defined for the column.
64576 * @param {Number} col The column index.
64577 * @return {Function} The function used to render the cell. See {@link #setRenderer}.
64579 getRenderer : function(col){
64580 if(!this.config[col].renderer){
64581 return Ext.grid.ColumnModel.defaultRenderer;
64583 return this.config[col].renderer;
64587 * Sets the rendering (formatting) function for a column. See {@link Ext.util.Format} for some
64588 * default formatting functions.
64589 * @param {Number} col The column index
64590 * @param {Function} fn The function to use to process the cell's raw data
64591 * to return HTML markup for the grid view. The render function is called with
64592 * the following parameters:<ul>
64593 * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
64594 * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
64595 * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
64596 * <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
64597 * (e.g. 'style="color:red;"').</p></li></ul></p></li>
64598 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was extracted.</p></li>
64599 * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
64600 * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
64601 * <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>
64603 setRenderer : function(col, fn){
64604 this.config[col].renderer = fn;
64608 * Returns the width for the specified column.
64609 * @param {Number} col The column index
64612 getColumnWidth : function(col){
64613 return this.config[col].width;
64617 * Sets the width for a column.
64618 * @param {Number} col The column index
64619 * @param {Number} width The new width
64620 * @param {Boolean} suppressEvent True to suppress firing the <code>{@link #widthchange}</code>
64621 * event. Defaults to false.
64623 setColumnWidth : function(col, width, suppressEvent){
64624 this.config[col].width = width;
64625 this.totalWidth = null;
64626 if(!suppressEvent){
64627 this.fireEvent("widthchange", this, col, width);
64632 * Returns the total width of all columns.
64633 * @param {Boolean} includeHidden True to include hidden column widths
64636 getTotalWidth : function(includeHidden){
64637 if(!this.totalWidth){
64638 this.totalWidth = 0;
64639 for(var i = 0, len = this.config.length; i < len; i++){
64640 if(includeHidden || !this.isHidden(i)){
64641 this.totalWidth += this.getColumnWidth(i);
64645 return this.totalWidth;
64649 * Returns the header for the specified column.
64650 * @param {Number} col The column index
64653 getColumnHeader : function(col){
64654 return this.config[col].header;
64658 * Sets the header for a column.
64659 * @param {Number} col The column index
64660 * @param {String} header The new header
64662 setColumnHeader : function(col, header){
64663 this.config[col].header = header;
64664 this.fireEvent("headerchange", this, col, header);
64668 * Returns the tooltip for the specified column.
64669 * @param {Number} col The column index
64672 getColumnTooltip : function(col){
64673 return this.config[col].tooltip;
64676 * Sets the tooltip for a column.
64677 * @param {Number} col The column index
64678 * @param {String} tooltip The new tooltip
64680 setColumnTooltip : function(col, tooltip){
64681 this.config[col].tooltip = tooltip;
64685 * Returns the dataIndex for the specified column.
64687 // Get field name for the column
64688 var fieldName = grid.getColumnModel().getDataIndex(columnIndex);
64690 * @param {Number} col The column index
64691 * @return {String} The column's dataIndex
64693 getDataIndex : function(col){
64694 return this.config[col].dataIndex;
64698 * Sets the dataIndex for a column.
64699 * @param {Number} col The column index
64700 * @param {String} dataIndex The new dataIndex
64702 setDataIndex : function(col, dataIndex){
64703 this.config[col].dataIndex = dataIndex;
64707 * Finds the index of the first matching column for the given dataIndex.
64708 * @param {String} col The dataIndex to find
64709 * @return {Number} The column index, or -1 if no match was found
64711 findColumnIndex : function(dataIndex){
64712 var c = this.config;
64713 for(var i = 0, len = c.length; i < len; i++){
64714 if(c[i].dataIndex == dataIndex){
64722 * Returns true if the cell is editable.
64724 var store = new Ext.data.Store({...});
64725 var colModel = new Ext.grid.ColumnModel({
64727 isCellEditable: function(col, row) {
64728 var record = store.getAt(row);
64729 if (record.get('readonly')) { // replace with your condition
64732 return Ext.grid.ColumnModel.prototype.isCellEditable.call(this, col, row);
64735 var grid = new Ext.grid.GridPanel({
64737 colModel: colModel,
64741 * @param {Number} colIndex The column index
64742 * @param {Number} rowIndex The row index
64743 * @return {Boolean}
64745 isCellEditable : function(colIndex, rowIndex){
64746 return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
64750 * Returns the editor defined for the cell/column.
64751 * @param {Number} colIndex The column index
64752 * @param {Number} rowIndex The row index
64753 * @return {Ext.Editor} The {@link Ext.Editor Editor} that was created to wrap
64754 * the {@link Ext.form.Field Field} used to edit the cell.
64756 getCellEditor : function(colIndex, rowIndex){
64757 return this.config[colIndex].getCellEditor(rowIndex);
64761 * Sets if a column is editable.
64762 * @param {Number} col The column index
64763 * @param {Boolean} editable True if the column is editable
64765 setEditable : function(col, editable){
64766 this.config[col].editable = editable;
64770 * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#hidden hidden}</code>,
64771 * <tt>false</tt> otherwise.
64772 * @param {Number} colIndex The column index
64773 * @return {Boolean}
64775 isHidden : function(colIndex){
64776 return !!this.config[colIndex].hidden; // ensure returns boolean
64780 * Returns <tt>true</tt> if the column is <code>{@link Ext.grid.Column#fixed fixed}</code>,
64781 * <tt>false</tt> otherwise.
64782 * @param {Number} colIndex The column index
64783 * @return {Boolean}
64785 isFixed : function(colIndex){
64786 return !!this.config[colIndex].fixed;
64790 * Returns true if the column can be resized
64791 * @return {Boolean}
64793 isResizable : function(colIndex){
64794 return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
64797 * Sets if a column is hidden.
64799 myGrid.getColumnModel().setHidden(0, true); // hide column 0 (0 = the first column).
64801 * @param {Number} colIndex The column index
64802 * @param {Boolean} hidden True if the column is hidden
64804 setHidden : function(colIndex, hidden){
64805 var c = this.config[colIndex];
64806 if(c.hidden !== hidden){
64808 this.totalWidth = null;
64809 this.fireEvent("hiddenchange", this, colIndex, hidden);
64814 * Sets the editor for a column and destroys the prior editor.
64815 * @param {Number} col The column index
64816 * @param {Object} editor The editor object
64818 setEditor : function(col, editor){
64819 Ext.destroy(this.config[col].editor);
64820 this.config[col].editor = editor;
64824 * Destroys this column model by purging any event listeners, and removing any editors.
64826 destroy : function(){
64827 for(var i = 0, c = this.config, len = c.length; i < len; i++){
64828 Ext.destroy(c[i].editor);
64830 this.purgeListeners();
64835 Ext.grid.ColumnModel.defaultRenderer = function(value){
64836 if(typeof value == "string" && value.length < 1){
64841 * @class Ext.grid.AbstractSelectionModel
\r
64842 * @extends Ext.util.Observable
\r
64843 * Abstract base class for grid SelectionModels. It provides the interface that should be
\r
64844 * implemented by descendant classes. This class should not be directly instantiated.
\r
64847 Ext.grid.AbstractSelectionModel = function(){
\r
64848 this.locked = false;
\r
64849 Ext.grid.AbstractSelectionModel.superclass.constructor.call(this);
\r
64852 Ext.extend(Ext.grid.AbstractSelectionModel, Ext.util.Observable, {
\r
64854 * The GridPanel for which this SelectionModel is handling selection. Read-only.
\r
64859 /** @ignore Called by the grid automatically. Do not call directly. */
\r
64860 init : function(grid){
\r
64861 this.grid = grid;
\r
64862 this.initEvents();
\r
64866 * Locks the selections.
\r
64868 lock : function(){
\r
64869 this.locked = true;
\r
64873 * Unlocks the selections.
\r
64875 unlock : function(){
\r
64876 this.locked = false;
\r
64880 * Returns true if the selections are locked.
\r
64881 * @return {Boolean}
\r
64883 isLocked : function(){
\r
64884 return this.locked;
\r
64887 destroy: function(){
\r
64888 this.purgeListeners();
\r
64891 * @class Ext.grid.RowSelectionModel
64892 * @extends Ext.grid.AbstractSelectionModel
64893 * The default SelectionModel used by {@link Ext.grid.GridPanel}.
64894 * It supports multiple selections and keyboard selection/navigation. The objects stored
64895 * as selections and returned by {@link #getSelected}, and {@link #getSelections} are
64896 * the {@link Ext.data.Record Record}s which provide the data for the selected rows.
64898 * @param {Object} config
64900 Ext.grid.RowSelectionModel = function(config){
64901 Ext.apply(this, config);
64902 this.selections = new Ext.util.MixedCollection(false, function(o){
64907 this.lastActive = false;
64911 * @event selectionchange
64912 * Fires when the selection changes
64913 * @param {SelectionModel} this
64917 * @event beforerowselect
64918 * Fires before a row is selected, return false to cancel the selection.
64919 * @param {SelectionModel} this
64920 * @param {Number} rowIndex The index to be selected
64921 * @param {Boolean} keepExisting False if other selections will be cleared
64922 * @param {Record} record The record to be selected
64927 * Fires when a row is selected.
64928 * @param {SelectionModel} this
64929 * @param {Number} rowIndex The selected index
64930 * @param {Ext.data.Record} r The selected record
64934 * @event rowdeselect
64935 * Fires when a row is deselected. To prevent deselection
64936 * {@link Ext.grid.AbstractSelectionModel#lock lock the selections}.
64937 * @param {SelectionModel} this
64938 * @param {Number} rowIndex
64939 * @param {Record} record
64944 Ext.grid.RowSelectionModel.superclass.constructor.call(this);
64947 Ext.extend(Ext.grid.RowSelectionModel, Ext.grid.AbstractSelectionModel, {
64949 * @cfg {Boolean} singleSelect
64950 * <tt>true</tt> to allow selection of only one row at a time (defaults to <tt>false</tt>
64951 * allowing multiple selections)
64953 singleSelect : false,
64956 * @cfg {Boolean} moveEditorOnEnter
64957 * <tt>false</tt> to turn off moving the editor to the next row down when the enter key is pressed
64958 * or the next row up when shift + enter keys are pressed.
64961 initEvents : function(){
64963 if(!this.grid.enableDragDrop && !this.grid.enableDrag){
64964 this.grid.on('rowmousedown', this.handleMouseDown, this);
64967 this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
64968 'up' : function(e){
64969 if(!e.shiftKey || this.singleSelect){
64970 this.selectPrevious(false);
64971 }else if(this.last !== false && this.lastActive !== false){
64972 var last = this.last;
64973 this.selectRange(this.last, this.lastActive-1);
64974 this.grid.getView().focusRow(this.lastActive);
64975 if(last !== false){
64979 this.selectFirstRow();
64982 'down' : function(e){
64983 if(!e.shiftKey || this.singleSelect){
64984 this.selectNext(false);
64985 }else if(this.last !== false && this.lastActive !== false){
64986 var last = this.last;
64987 this.selectRange(this.last, this.lastActive+1);
64988 this.grid.getView().focusRow(this.lastActive);
64989 if(last !== false){
64993 this.selectFirstRow();
64999 this.grid.getView().on({
65001 refresh: this.onRefresh,
65002 rowupdated: this.onRowUpdated,
65003 rowremoved: this.onRemove
65008 onRefresh : function(){
65009 var ds = this.grid.store, index;
65010 var s = this.getSelections();
65011 this.clearSelections(true);
65012 for(var i = 0, len = s.length; i < len; i++){
65014 if((index = ds.indexOfId(r.id)) != -1){
65015 this.selectRow(index, true);
65018 if(s.length != this.selections.getCount()){
65019 this.fireEvent('selectionchange', this);
65024 onRemove : function(v, index, r){
65025 if(this.selections.remove(r) !== false){
65026 this.fireEvent('selectionchange', this);
65031 onRowUpdated : function(v, index, r){
65032 if(this.isSelected(r)){
65033 v.onRowSelect(index);
65039 * @param {Array} records The records to select
65040 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
65042 selectRecords : function(records, keepExisting){
65044 this.clearSelections();
65046 var ds = this.grid.store;
65047 for(var i = 0, len = records.length; i < len; i++){
65048 this.selectRow(ds.indexOf(records[i]), true);
65053 * Gets the number of selected rows.
65056 getCount : function(){
65057 return this.selections.length;
65061 * Selects the first row in the grid.
65063 selectFirstRow : function(){
65068 * Select the last row.
65069 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
65071 selectLastRow : function(keepExisting){
65072 this.selectRow(this.grid.store.getCount() - 1, keepExisting);
65076 * Selects the row immediately following the last selected row.
65077 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
65078 * @return {Boolean} <tt>true</tt> if there is a next row, else <tt>false</tt>
65080 selectNext : function(keepExisting){
65081 if(this.hasNext()){
65082 this.selectRow(this.last+1, keepExisting);
65083 this.grid.getView().focusRow(this.last);
65090 * Selects the row that precedes the last selected row.
65091 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
65092 * @return {Boolean} <tt>true</tt> if there is a previous row, else <tt>false</tt>
65094 selectPrevious : function(keepExisting){
65095 if(this.hasPrevious()){
65096 this.selectRow(this.last-1, keepExisting);
65097 this.grid.getView().focusRow(this.last);
65104 * Returns true if there is a next record to select
65105 * @return {Boolean}
65107 hasNext : function(){
65108 return this.last !== false && (this.last+1) < this.grid.store.getCount();
65112 * Returns true if there is a previous record to select
65113 * @return {Boolean}
65115 hasPrevious : function(){
65116 return !!this.last;
65121 * Returns the selected records
65122 * @return {Array} Array of selected records
65124 getSelections : function(){
65125 return [].concat(this.selections.items);
65129 * Returns the first selected record.
65132 getSelected : function(){
65133 return this.selections.itemAt(0);
65137 * Calls the passed function with each selection. If the function returns
65138 * <tt>false</tt>, iteration is stopped and this function returns
65139 * <tt>false</tt>. Otherwise it returns <tt>true</tt>.
65140 * @param {Function} fn
65141 * @param {Object} scope (optional)
65142 * @return {Boolean} true if all selections were iterated
65144 each : function(fn, scope){
65145 var s = this.getSelections();
65146 for(var i = 0, len = s.length; i < len; i++){
65147 if(fn.call(scope || this, s[i], i) === false){
65155 * Clears all selections if the selection model
65156 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
65157 * @param {Boolean} fast (optional) <tt>true</tt> to bypass the
65158 * conditional checks and events described in {@link #deselectRow}.
65160 clearSelections : function(fast){
65161 if(this.isLocked()){
65165 var ds = this.grid.store;
65166 var s = this.selections;
65167 s.each(function(r){
65168 this.deselectRow(ds.indexOfId(r.id));
65172 this.selections.clear();
65179 * Selects all rows if the selection model
65180 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
65182 selectAll : function(){
65183 if(this.isLocked()){
65186 this.selections.clear();
65187 for(var i = 0, len = this.grid.store.getCount(); i < len; i++){
65188 this.selectRow(i, true);
65193 * Returns <tt>true</tt> if there is a selection.
65194 * @return {Boolean}
65196 hasSelection : function(){
65197 return this.selections.length > 0;
65201 * Returns <tt>true</tt> if the specified row is selected.
65202 * @param {Number/Record} index The record or index of the record to check
65203 * @return {Boolean}
65205 isSelected : function(index){
65206 var r = Ext.isNumber(index) ? this.grid.store.getAt(index) : index;
65207 return (r && this.selections.key(r.id) ? true : false);
65211 * Returns <tt>true</tt> if the specified record id is selected.
65212 * @param {String} id The id of record to check
65213 * @return {Boolean}
65215 isIdSelected : function(id){
65216 return (this.selections.key(id) ? true : false);
65220 handleMouseDown : function(g, rowIndex, e){
65221 if(e.button !== 0 || this.isLocked()){
65224 var view = this.grid.getView();
65225 if(e.shiftKey && !this.singleSelect && this.last !== false){
65226 var last = this.last;
65227 this.selectRange(last, rowIndex, e.ctrlKey);
65228 this.last = last; // reset the last
65229 view.focusRow(rowIndex);
65231 var isSelected = this.isSelected(rowIndex);
65232 if(e.ctrlKey && isSelected){
65233 this.deselectRow(rowIndex);
65234 }else if(!isSelected || this.getCount() > 1){
65235 this.selectRow(rowIndex, e.ctrlKey || e.shiftKey);
65236 view.focusRow(rowIndex);
65242 * Selects multiple rows.
65243 * @param {Array} rows Array of the indexes of the row to select
65244 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep
65245 * existing selections (defaults to <tt>false</tt>)
65247 selectRows : function(rows, keepExisting){
65249 this.clearSelections();
65251 for(var i = 0, len = rows.length; i < len; i++){
65252 this.selectRow(rows[i], true);
65257 * Selects a range of rows if the selection model
65258 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
65259 * All rows in between startRow and endRow are also selected.
65260 * @param {Number} startRow The index of the first row in the range
65261 * @param {Number} endRow The index of the last row in the range
65262 * @param {Boolean} keepExisting (optional) True to retain existing selections
65264 selectRange : function(startRow, endRow, keepExisting){
65266 if(this.isLocked()){
65270 this.clearSelections();
65272 if(startRow <= endRow){
65273 for(i = startRow; i <= endRow; i++){
65274 this.selectRow(i, true);
65277 for(i = startRow; i >= endRow; i--){
65278 this.selectRow(i, true);
65284 * Deselects a range of rows if the selection model
65285 * {@link Ext.grid.AbstractSelectionModel#isLocked is not locked}.
65286 * All rows in between startRow and endRow are also deselected.
65287 * @param {Number} startRow The index of the first row in the range
65288 * @param {Number} endRow The index of the last row in the range
65290 deselectRange : function(startRow, endRow, preventViewNotify){
65291 if(this.isLocked()){
65294 for(var i = startRow; i <= endRow; i++){
65295 this.deselectRow(i, preventViewNotify);
65300 * Selects a row. Before selecting a row, checks if the selection model
65301 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked} and fires the
65302 * {@link #beforerowselect} event. If these checks are satisfied the row
65303 * will be selected and followed up by firing the {@link #rowselect} and
65304 * {@link #selectionchange} events.
65305 * @param {Number} row The index of the row to select
65306 * @param {Boolean} keepExisting (optional) <tt>true</tt> to keep existing selections
65307 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
65308 * prevent notifying the view (disables updating the selected appearance)
65310 selectRow : function(index, keepExisting, preventViewNotify){
65311 if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){
65314 var r = this.grid.store.getAt(index);
65315 if(r && this.fireEvent('beforerowselect', this, index, keepExisting, r) !== false){
65316 if(!keepExisting || this.singleSelect){
65317 this.clearSelections();
65319 this.selections.add(r);
65320 this.last = this.lastActive = index;
65321 if(!preventViewNotify){
65322 this.grid.getView().onRowSelect(index);
65324 this.fireEvent('rowselect', this, index, r);
65325 this.fireEvent('selectionchange', this);
65330 * Deselects a row. Before deselecting a row, checks if the selection model
65331 * {@link Ext.grid.AbstractSelectionModel#isLocked is locked}.
65332 * If this check is satisfied the row will be deselected and followed up by
65333 * firing the {@link #rowdeselect} and {@link #selectionchange} events.
65334 * @param {Number} row The index of the row to deselect
65335 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
65336 * prevent notifying the view (disables updating the selected appearance)
65338 deselectRow : function(index, preventViewNotify){
65339 if(this.isLocked()){
65342 if(this.last == index){
65345 if(this.lastActive == index){
65346 this.lastActive = false;
65348 var r = this.grid.store.getAt(index);
65350 this.selections.remove(r);
65351 if(!preventViewNotify){
65352 this.grid.getView().onRowDeselect(index);
65354 this.fireEvent('rowdeselect', this, index, r);
65355 this.fireEvent('selectionchange', this);
65360 restoreLast : function(){
65362 this.last = this._last;
65367 acceptsNav : function(row, col, cm){
65368 return !cm.isHidden(col) && cm.isCellEditable(col, row);
65372 onEditorKey : function(field, e){
65373 var k = e.getKey(),
65377 ed = g.activeEditor,
65379 var shift = e.shiftKey;
65384 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
65386 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
65388 }else if(k == e.ENTER){
65389 if(this.moveEditorOnEnter !== false){
65391 newCell = g.walkCells(last.row - 1, last.col, -1, this.acceptsNav, this);
65393 newCell = g.walkCells(last.row + 1, last.col, 1, this.acceptsNav, this);
65402 this.selectRow(r); // *** highlight newly-selected cell and update selection
65405 if(g.isEditor && g.editing){ // *** handle tabbing while editorgrid is in edit mode
65406 ae = g.activeEditor;
65407 if(ae && ae.field.triggerBlur){
65408 // *** if activeEditor is a TriggerField, explicitly call its triggerBlur() method
65409 ae.field.triggerBlur();
65412 g.startEditing(r, c);
65416 destroy : function(){
65418 this.rowNav.disable();
65419 this.rowNav = null;
65421 Ext.grid.RowSelectionModel.superclass.destroy.call(this);
65424 * @class Ext.grid.Column
\r
65425 * <p>This class encapsulates column configuration data to be used in the initialization of a
\r
65426 * {@link Ext.grid.ColumnModel ColumnModel}.</p>
\r
65427 * <p>While subclasses are provided to render data in different ways, this class renders a passed
\r
65428 * data field unchanged and is usually used for textual columns.</p>
\r
65430 Ext.grid.Column = function(config){
\r
65431 Ext.apply(this, config);
\r
65433 if(Ext.isString(this.renderer)){
\r
65434 this.renderer = Ext.util.Format[this.renderer];
\r
65435 } else if(Ext.isObject(this.renderer)){
\r
65436 this.scope = this.renderer.scope;
\r
65437 this.renderer = this.renderer.fn;
\r
65439 this.renderer = this.renderer.createDelegate(this.scope || config);
\r
65442 this.editor = Ext.create(this.editor, 'textfield');
\r
65446 Ext.grid.Column.prototype = {
\r
65448 * @cfg {Boolean} editable Optional. Defaults to <tt>true</tt>, enabling the configured
\r
65449 * <tt>{@link #editor}</tt>. Set to <tt>false</tt> to initially disable editing on this column.
\r
65450 * The initial configuration may be dynamically altered using
\r
65451 * {@link Ext.grid.ColumnModel}.{@link Ext.grid.ColumnModel#setEditable setEditable()}.
\r
65454 * @cfg {String} id Optional. A name which identifies this column (defaults to the column's initial
\r
65455 * ordinal position.) The <tt>id</tt> is used to create a CSS <b>class</b> name which is applied to all
\r
65456 * table cells (including headers) in that column (in this context the <tt>id</tt> does not need to be
\r
65457 * unique). The class name takes the form of <pre>x-grid3-td-<b>id</b></pre>
\r
65458 * Header cells will also receive this class name, but will also have the class <pre>x-grid3-hd</pre>
\r
65459 * So, to target header cells, use CSS selectors such as:<pre>.x-grid3-hd-row .x-grid3-td-<b>id</b></pre>
\r
65460 * The {@link Ext.grid.GridPanel#autoExpandColumn} grid config option references the column via this
\r
65461 * unique identifier.
\r
65464 * @cfg {String} header Optional. The header text to be used as innerHTML
\r
65465 * (html tags are accepted) to display in the Grid view. <b>Note</b>: to
\r
65466 * have a clickable header with no text displayed use <tt>' '</tt>.
\r
65469 * @cfg {Boolean} groupable Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
65470 * may be used to disable the header menu item to group by the column selected. Defaults to <tt>true</tt>,
\r
65471 * which enables the header menu group option. Set to <tt>false</tt> to disable (but still show) the
\r
65472 * group option in the header menu for the column. See also <code>{@link #groupName}</code>.
\r
65475 * @cfg {String} groupName Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
65476 * may be used to specify the text with which to prefix the group field value in the group header line.
\r
65477 * See also {@link #groupRenderer} and
\r
65478 * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#showGroupName showGroupName}.
\r
65481 * @cfg {Function} groupRenderer <p>Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
65482 * may be used to specify the function used to format the grouping field value for display in the group
\r
65483 * {@link #groupName header}. If a <tt><b>groupRenderer</b></tt> is not specified, the configured
\r
65484 * <tt><b>{@link #renderer}</b></tt> will be called; if a <tt><b>{@link #renderer}</b></tt> is also not specified
\r
65485 * the new value of the group field will be used.</p>
\r
65486 * <p>The called function (either the <tt><b>groupRenderer</b></tt> or <tt><b>{@link #renderer}</b></tt>) will be
\r
65487 * passed the following parameters:
\r
65488 * <div class="mdetail-params"><ul>
\r
65489 * <li><b>v</b> : Object<p class="sub-desc">The new value of the group field.</p></li>
\r
65490 * <li><b>unused</b> : undefined<p class="sub-desc">Unused parameter.</p></li>
\r
65491 * <li><b>r</b> : Ext.data.Record<p class="sub-desc">The Record providing the data
\r
65492 * for the row which caused group change.</p></li>
\r
65493 * <li><b>rowIndex</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
65494 * <li><b>colIndex</b> : Number<p class="sub-desc">The column index of the group field.</p></li>
\r
65495 * <li><b>ds</b> : Ext.data.Store<p class="sub-desc">The Store which is providing the data Model.</p></li>
\r
65496 * </ul></div></p>
\r
65497 * <p>The function should return a string value.</p>
\r
65500 * @cfg {String} emptyGroupText Optional. If the grid is being rendered by an {@link Ext.grid.GroupingView}, this option
\r
65501 * may be used to specify the text to display when there is an empty group value. Defaults to the
\r
65502 * {@link Ext.grid.GroupingView}.{@link Ext.grid.GroupingView#emptyGroupText emptyGroupText}.
\r
65505 * @cfg {String} dataIndex <p><b>Required</b>. The name of the field in the
\r
65506 * grid's {@link Ext.data.Store}'s {@link Ext.data.Record} definition from
\r
65507 * which to draw the column's value.</p>
\r
65510 * @cfg {Number} width
\r
65511 * Optional. The initial width in pixels of the column.
\r
65512 * The width of each column can also be affected if any of the following are configured:
\r
65513 * <div class="mdetail-params"><ul>
\r
65514 * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#autoExpandColumn autoExpandColumn}</tt></li>
\r
65515 * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#forceFit forceFit}</tt>
\r
65516 * <div class="sub-desc">
\r
65517 * <p>By specifying <tt>forceFit:true</tt>, {@link #fixed non-fixed width} columns will be
\r
65518 * re-proportioned (based on the relative initial widths) to fill the width of the grid so
\r
65519 * that no horizontal scrollbar is shown.</p>
\r
65521 * <li>{@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#autoFill autoFill}</tt></li>
\r
65522 * <li>{@link Ext.grid.GridPanel}.<tt>{@link Ext.grid.GridPanel#minColumnWidth minColumnWidth}</tt></li>
\r
65523 * <br><p><b>Note</b>: when the width of each column is determined, a space on the right side
\r
65524 * is reserved for the vertical scrollbar. The
\r
65525 * {@link Ext.grid.GridView}.<tt>{@link Ext.grid.GridView#scrollOffset scrollOffset}</tt>
\r
65526 * can be modified to reduce or eliminate the reserved offset.</p>
\r
65529 * @cfg {Boolean} sortable Optional. <tt>true</tt> if sorting is to be allowed on this column.
\r
65530 * Defaults to the value of the <code>{@link Ext.grid.ColumnModel#defaultSortable}</code> property.
\r
65531 * Whether local/remote sorting is used is specified in <code>{@link Ext.data.Store#remoteSort}</code>.
\r
65534 * @cfg {Boolean} fixed Optional. <tt>true</tt> if the column width cannot be changed. Defaults to <tt>false</tt>.
\r
65537 * @cfg {Boolean} resizable Optional. <tt>false</tt> to disable column resizing. Defaults to <tt>true</tt>.
\r
65540 * @cfg {Boolean} menuDisabled Optional. <tt>true</tt> to disable the column menu. Defaults to <tt>false</tt>.
\r
65543 * @cfg {Boolean} hidden
\r
65544 * Optional. <tt>true</tt> to initially hide this column. Defaults to <tt>false</tt>.
\r
65545 * A hidden column {@link Ext.grid.GridPanel#enableColumnHide may be shown via the header row menu}.
\r
65546 * If a column is never to be shown, simply do not include this column in the Column Model at all.
\r
65549 * @cfg {String} tooltip Optional. A text string to use as the column header's tooltip. If Quicktips
\r
65550 * are enabled, this value will be used as the text of the quick tip, otherwise it will be set as the
\r
65551 * header's HTML title attribute. Defaults to ''.
\r
65554 * @cfg {Mixed} renderer
\r
65555 * <p>For an alternative to specifying a renderer see <code>{@link #xtype}</code></p>
\r
65556 * <p>Optional. A renderer is an 'interceptor' method which can be used transform data (value,
\r
65557 * appearance, etc.) before it is rendered). This may be specified in either of three ways:
\r
65558 * <div class="mdetail-params"><ul>
\r
65559 * <li>A renderer function used to return HTML markup for a cell given the cell's data value.</li>
\r
65560 * <li>A string which references a property name of the {@link Ext.util.Format} class which
\r
65561 * provides a renderer function.</li>
\r
65562 * <li>An object specifying both the renderer function, and its execution scope (<tt><b>this</b></tt>
\r
65563 * reference) e.g.:<pre style="margin-left:1.2em"><code>
\r
65565 fn: this.gridRenderer,
\r
65568 </code></pre></li></ul></div>
\r
65569 * If not specified, the default renderer uses the raw data value.</p>
\r
65570 * <p>For information about the renderer function (passed parameters, etc.), see
\r
65571 * {@link Ext.grid.ColumnModel#setRenderer}. An example of specifying renderer function inline:</p><pre><code>
\r
65572 var companyColumn = {
\r
65573 header: 'Company Name',
\r
65574 dataIndex: 'company',
\r
65575 renderer: function(value, metaData, record, rowIndex, colIndex, store) {
\r
65576 // provide the logic depending on business rules
\r
65577 // name of your own choosing to manipulate the cell depending upon
\r
65578 // the data in the underlying Record object.
\r
65579 if (value == 'whatever') {
\r
65580 //metaData.css : String : A CSS class name to add to the TD element of the cell.
\r
65581 //metaData.attr : String : An html attribute definition string to apply to
\r
65582 // the data container element within the table
\r
65583 // cell (e.g. 'style="color:red;"').
\r
65584 metaData.css = 'name-of-css-class-you-will-define';
\r
65590 * See also {@link #scope}.
\r
65593 * @cfg {String} xtype Optional. A String which references a predefined {@link Ext.grid.Column} subclass
\r
65594 * type which is preconfigured with an appropriate <code>{@link #renderer}</code> to be easily
\r
65595 * configured into a ColumnModel. The predefined {@link Ext.grid.Column} subclass types are:
\r
65596 * <div class="mdetail-params"><ul>
\r
65597 * <li><b><tt>gridcolumn</tt></b> : {@link Ext.grid.Column} (<b>Default</b>)<p class="sub-desc"></p></li>
\r
65598 * <li><b><tt>booleancolumn</tt></b> : {@link Ext.grid.BooleanColumn}<p class="sub-desc"></p></li>
\r
65599 * <li><b><tt>numbercolumn</tt></b> : {@link Ext.grid.NumberColumn}<p class="sub-desc"></p></li>
\r
65600 * <li><b><tt>datecolumn</tt></b> : {@link Ext.grid.DateColumn}<p class="sub-desc"></p></li>
\r
65601 * <li><b><tt>templatecolumn</tt></b> : {@link Ext.grid.TemplateColumn}<p class="sub-desc"></p></li>
\r
65603 * <p>Configuration properties for the specified <code>xtype</code> may be specified with
\r
65604 * the Column configuration properties, for example:</p>
\r
65606 var grid = new Ext.grid.GridPanel({
\r
65609 header: 'Last Updated',
\r
65610 dataIndex: 'lastChange',
\r
65613 //renderer: Ext.util.Format.dateRenderer('m/d/Y'),
\r
65614 xtype: 'datecolumn', // use xtype instead of renderer
\r
65615 format: 'M/d/Y' // configuration property for {@link Ext.grid.DateColumn}
\r
65623 * @cfg {Object} scope Optional. The scope (<tt><b>this</b></tt> reference) in which to execute the
\r
65624 * renderer. Defaults to the Column configuration object.
\r
65627 * @cfg {String} align Optional. Set the CSS text-align property of the column. Defaults to undefined.
\r
65630 * @cfg {String} css Optional. An inline style definition string which is applied to all table cells in the column
\r
65631 * (excluding headers). Defaults to undefined.
\r
65634 * @cfg {Boolean} hideable Optional. Specify as <tt>false</tt> to prevent the user from hiding this column
\r
65635 * (defaults to true). To disallow column hiding globally for all columns in the grid, use
\r
65636 * {@link Ext.grid.GridPanel#enableColumnHide} instead.
\r
65639 * @cfg {Ext.form.Field} editor Optional. The {@link Ext.form.Field} to use when editing values in this column
\r
65640 * if editing is supported by the grid. See <tt>{@link #editable}</tt> also.
\r
65645 * @cfg {Boolean} isColumn
\r
65646 * Used by ColumnModel setConfig method to avoid reprocessing a Column
\r
65647 * if <code>isColumn</code> is not set ColumnModel will recreate a new Ext.grid.Column
\r
65648 * Defaults to true.
\r
65653 * Optional. A function which returns displayable data when passed the following parameters:
\r
65654 * <div class="mdetail-params"><ul>
\r
65655 * <li><b>value</b> : Object<p class="sub-desc">The data value for the cell.</p></li>
\r
65656 * <li><b>metadata</b> : Object<p class="sub-desc">An object in which you may set the following attributes:<ul>
\r
65657 * <li><b>css</b> : String<p class="sub-desc">A CSS class name to add to the cell's TD element.</p></li>
\r
65658 * <li><b>attr</b> : String<p class="sub-desc">An HTML attribute definition string to apply to the data container
\r
65659 * element <i>within</i> the table cell (e.g. 'style="color:red;"').</p></li></ul></p></li>
\r
65660 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record} from which the data was
\r
65661 * extracted.</p></li>
\r
65662 * <li><b>rowIndex</b> : Number<p class="sub-desc">Row index</p></li>
\r
65663 * <li><b>colIndex</b> : Number<p class="sub-desc">Column index</p></li>
\r
65664 * <li><b>store</b> : Ext.data.Store<p class="sub-desc">The {@link Ext.data.Store} object from which the Record
\r
65665 * was extracted.</p></li>
\r
65667 * @property renderer
\r
65670 renderer : function(value){
\r
65671 if(Ext.isString(value) && value.length < 1){
\r
65678 getEditor: function(rowIndex){
\r
65679 return this.editable !== false ? this.editor : null;
\r
65683 * Returns the {@link Ext.Editor editor} defined for this column that was created to wrap the {@link Ext.form.Field Field}
\r
65684 * used to edit the cell.
\r
65685 * @param {Number} rowIndex The row index
\r
65686 * @return {Ext.Editor}
\r
65688 getCellEditor: function(rowIndex){
\r
65689 var editor = this.getEditor(rowIndex);
\r
65691 if(!editor.startEdit){
\r
65692 if(!editor.gridEditor){
\r
65693 editor.gridEditor = new Ext.grid.GridEditor(editor);
\r
65695 return editor.gridEditor;
\r
65696 }else if(editor.startEdit){
\r
65705 * @class Ext.grid.BooleanColumn
\r
65706 * @extends Ext.grid.Column
\r
65707 * <p>A Column definition class which renders boolean data fields. See the {@link Ext.grid.Column#xtype xtype}
\r
65708 * config option of {@link Ext.grid.Column} for more details.</p>
\r
65710 Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
\r
65712 * @cfg {String} trueText
\r
65713 * The string returned by the renderer when the column value is not falsey (defaults to <tt>'true'</tt>).
\r
65715 trueText: 'true',
\r
65717 * @cfg {String} falseText
\r
65718 * The string returned by the renderer when the column value is falsey (but not undefined) (defaults to
\r
65719 * <tt>'false'</tt>).
\r
65721 falseText: 'false',
\r
65723 * @cfg {String} undefinedText
\r
65724 * The string returned by the renderer when the column value is undefined (defaults to <tt>' '</tt>).
\r
65726 undefinedText: ' ',
\r
65728 constructor: function(cfg){
\r
65729 Ext.grid.BooleanColumn.superclass.constructor.call(this, cfg);
\r
65730 var t = this.trueText, f = this.falseText, u = this.undefinedText;
\r
65731 this.renderer = function(v){
\r
65732 if(v === undefined){
\r
65735 if(!v || v === 'false'){
\r
65744 * @class Ext.grid.NumberColumn
\r
65745 * @extends Ext.grid.Column
\r
65746 * <p>A Column definition class which renders a numeric data field according to a {@link #format} string. See the
\r
65747 * {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more details.</p>
\r
65749 Ext.grid.NumberColumn = Ext.extend(Ext.grid.Column, {
\r
65751 * @cfg {String} format
\r
65752 * A formatting string as used by {@link Ext.util.Format#number} to format a numeric value for this Column
\r
65753 * (defaults to <tt>'0,000.00'</tt>).
\r
65755 format : '0,000.00',
\r
65756 constructor: function(cfg){
\r
65757 Ext.grid.NumberColumn.superclass.constructor.call(this, cfg);
\r
65758 this.renderer = Ext.util.Format.numberRenderer(this.format);
\r
65763 * @class Ext.grid.DateColumn
\r
65764 * @extends Ext.grid.Column
\r
65765 * <p>A Column definition class which renders a passed date according to the default locale, or a configured
\r
65766 * {@link #format}. See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column}
\r
65767 * for more details.</p>
\r
65769 Ext.grid.DateColumn = Ext.extend(Ext.grid.Column, {
\r
65771 * @cfg {String} format
\r
65772 * A formatting string as used by {@link Date#format} to format a Date for this Column
\r
65773 * (defaults to <tt>'m/d/Y'</tt>).
\r
65775 format : 'm/d/Y',
\r
65776 constructor: function(cfg){
\r
65777 Ext.grid.DateColumn.superclass.constructor.call(this, cfg);
\r
65778 this.renderer = Ext.util.Format.dateRenderer(this.format);
\r
65783 * @class Ext.grid.TemplateColumn
\r
65784 * @extends Ext.grid.Column
\r
65785 * <p>A Column definition class which renders a value by processing a {@link Ext.data.Record Record}'s
\r
65786 * {@link Ext.data.Record#data data} using a {@link #tpl configured} {@link Ext.XTemplate XTemplate}.
\r
65787 * See the {@link Ext.grid.Column#xtype xtype} config option of {@link Ext.grid.Column} for more
\r
65790 Ext.grid.TemplateColumn = Ext.extend(Ext.grid.Column, {
\r
65792 * @cfg {String/XTemplate} tpl
\r
65793 * An {@link Ext.XTemplate XTemplate}, or an XTemplate <i>definition string</i> to use to process a
\r
65794 * {@link Ext.data.Record Record}'s {@link Ext.data.Record#data data} to produce a column's rendered value.
\r
65796 constructor: function(cfg){
\r
65797 Ext.grid.TemplateColumn.superclass.constructor.call(this, cfg);
\r
65798 var tpl = Ext.isObject(this.tpl) ? this.tpl : new Ext.XTemplate(this.tpl);
\r
65799 this.renderer = function(value, p, r){
\r
65800 return tpl.apply(r.data);
\r
65807 * @property types
\r
65809 * @member Ext.grid.Column
\r
65811 * <p>An object containing predefined Column classes keyed by a mnemonic code which may be referenced
\r
65812 * by the {@link Ext.grid.ColumnModel#xtype xtype} config option of ColumnModel.</p>
\r
65813 * <p>This contains the following properties</p><div class="mdesc-details"><ul>
\r
65814 * <li>gridcolumn : <b>{@link Ext.grid.Column Column constructor}</b></li>
\r
65815 * <li>booleancolumn : <b>{@link Ext.grid.BooleanColumn BooleanColumn constructor}</b></li>
\r
65816 * <li>numbercolumn : <b>{@link Ext.grid.NumberColumn NumberColumn constructor}</b></li>
\r
65817 * <li>datecolumn : <b>{@link Ext.grid.DateColumn DateColumn constructor}</b></li>
\r
65818 * <li>templatecolumn : <b>{@link Ext.grid.TemplateColumn TemplateColumn constructor}</b></li>
\r
65821 Ext.grid.Column.types = {
\r
65822 gridcolumn : Ext.grid.Column,
\r
65823 booleancolumn: Ext.grid.BooleanColumn,
\r
65824 numbercolumn: Ext.grid.NumberColumn,
\r
65825 datecolumn: Ext.grid.DateColumn,
\r
65826 templatecolumn: Ext.grid.TemplateColumn
\r
65828 * @class Ext.grid.RowNumberer
65829 * This is a utility class that can be passed into a {@link Ext.grid.ColumnModel} as a column config that provides
65830 * an automatic row numbering column.
65833 // This is a typical column config with the first column providing row numbers
65834 var colModel = new Ext.grid.ColumnModel([
65835 new Ext.grid.RowNumberer(),
65836 {header: "Name", width: 80, sortable: true},
65837 {header: "Code", width: 50, sortable: true},
65838 {header: "Description", width: 200, sortable: true}
65842 * @param {Object} config The configuration options
65844 Ext.grid.RowNumberer = function(config){
65845 Ext.apply(this, config);
65847 this.renderer = this.renderer.createDelegate(this);
65851 Ext.grid.RowNumberer.prototype = {
65853 * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the row
65854 * number column (defaults to '').
65858 * @cfg {Number} width The default width in pixels of the row number column (defaults to 23).
65862 * @cfg {Boolean} sortable True if the row number column is sortable (defaults to false).
65872 rowspan: undefined,
65875 renderer : function(v, p, record, rowIndex){
65877 p.cellAttr = 'rowspan="'+this.rowspan+'"';
65882 * @class Ext.grid.CheckboxSelectionModel
\r
65883 * @extends Ext.grid.RowSelectionModel
\r
65884 * A custom selection model that renders a column of checkboxes that can be toggled to select or deselect rows.
\r
65886 * @param {Object} config The configuration options
\r
65888 Ext.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
\r
65891 * @cfg {Boolean} checkOnly <tt>true</tt> if rows can only be selected by clicking on the
\r
65892 * checkbox column (defaults to <tt>false</tt>).
\r
65895 * @cfg {String} header Any valid text or HTML fragment to display in the header cell for the
\r
65896 * checkbox column. Defaults to:<pre><code>
\r
65897 * '<div class="x-grid3-hd-checker">&#160;</div>'</tt>
\r
65899 * The default CSS class of <tt>'x-grid3-hd-checker'</tt> displays a checkbox in the header
\r
65900 * and provides support for automatic check all/none behavior on header click. This string
\r
65901 * can be replaced by any valid HTML fragment, including a simple text string (e.g.,
\r
65902 * <tt>'Select Rows'</tt>), but the automatic check all/none behavior will only work if the
\r
65903 * <tt>'x-grid3-hd-checker'</tt> class is supplied.
\r
65905 header : '<div class="x-grid3-hd-checker"> </div>',
\r
65907 * @cfg {Number} width The default width in pixels of the checkbox column (defaults to <tt>20</tt>).
\r
65911 * @cfg {Boolean} sortable <tt>true</tt> if the checkbox column is sortable (defaults to
\r
65912 * <tt>false</tt>).
\r
65914 sortable : false,
\r
65917 menuDisabled : true,
\r
65922 constructor : function(){
\r
65923 Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
\r
65925 if(this.checkOnly){
\r
65926 this.handleMouseDown = Ext.emptyFn;
\r
65931 initEvents : function(){
\r
65932 Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
\r
65933 this.grid.on('render', function(){
\r
65934 var view = this.grid.getView();
\r
65935 view.mainBody.on('mousedown', this.onMouseDown, this);
\r
65936 Ext.fly(view.innerHd).on('mousedown', this.onHdMouseDown, this);
\r
65942 onMouseDown : function(e, t){
\r
65943 if(e.button === 0 && t.className == 'x-grid3-row-checker'){ // Only fire if left-click
\r
65945 var row = e.getTarget('.x-grid3-row');
\r
65947 var index = row.rowIndex;
\r
65948 if(this.isSelected(index)){
\r
65949 this.deselectRow(index);
\r
65951 this.selectRow(index, true);
\r
65958 onHdMouseDown : function(e, t){
\r
65959 if(t.className == 'x-grid3-hd-checker'){
\r
65961 var hd = Ext.fly(t.parentNode);
\r
65962 var isChecked = hd.hasClass('x-grid3-hd-checker-on');
\r
65964 hd.removeClass('x-grid3-hd-checker-on');
\r
65965 this.clearSelections();
\r
65967 hd.addClass('x-grid3-hd-checker-on');
\r
65968 this.selectAll();
\r
65974 renderer : function(v, p, record){
\r
65975 return '<div class="x-grid3-row-checker"> </div>';
\r
65978 * @class Ext.grid.CellSelectionModel
65979 * @extends Ext.grid.AbstractSelectionModel
65980 * This class provides the basic implementation for <i>single</i> <b>cell</b> selection in a grid.
65981 * The object stored as the selection contains the following properties:
65982 * <div class="mdetail-params"><ul>
65983 * <li><b>cell</b> : see {@link #getSelectedCell}
65984 * <li><b>record</b> : Ext.data.record The {@link Ext.data.Record Record}
65985 * which provides the data for the row containing the selection</li>
65988 * @param {Object} config The object containing the configuration of this model.
65990 Ext.grid.CellSelectionModel = function(config){
65991 Ext.apply(this, config);
65993 this.selection = null;
65997 * @event beforecellselect
65998 * Fires before a cell is selected, return false to cancel the selection.
65999 * @param {SelectionModel} this
66000 * @param {Number} rowIndex The selected row index
66001 * @param {Number} colIndex The selected cell index
66003 "beforecellselect",
66005 * @event cellselect
66006 * Fires when a cell is selected.
66007 * @param {SelectionModel} this
66008 * @param {Number} rowIndex The selected row index
66009 * @param {Number} colIndex The selected cell index
66013 * @event selectionchange
66014 * Fires when the active selection changes.
66015 * @param {SelectionModel} this
66016 * @param {Object} selection null for no selection or an object with two properties
66017 * <div class="mdetail-params"><ul>
66018 * <li><b>cell</b> : see {@link #getSelectedCell}
66019 * <li><b>record</b> : Ext.data.record<p class="sub-desc">The {@link Ext.data.Record Record}
66020 * which provides the data for the row containing the selection</p></li>
66026 Ext.grid.CellSelectionModel.superclass.constructor.call(this);
66029 Ext.extend(Ext.grid.CellSelectionModel, Ext.grid.AbstractSelectionModel, {
66032 initEvents : function(){
66033 this.grid.on('cellmousedown', this.handleMouseDown, this);
66034 this.grid.on(Ext.EventManager.useKeydown ? 'keydown' : 'keypress', this.handleKeyDown, this);
66035 this.grid.getView().on({
66037 refresh: this.onViewChange,
66038 rowupdated: this.onRowUpdated,
66039 beforerowremoved: this.clearSelections,
66040 beforerowsinserted: this.clearSelections
66042 if(this.grid.isEditor){
66043 this.grid.on('beforeedit', this.beforeEdit, this);
66048 beforeEdit : function(e){
66049 this.select(e.row, e.column, false, true, e.record);
66053 onRowUpdated : function(v, index, r){
66054 if(this.selection && this.selection.record == r){
66055 v.onCellSelect(index, this.selection.cell[1]);
66060 onViewChange : function(){
66061 this.clearSelections(true);
66065 * Returns an array containing the row and column indexes of the currently selected cell
66066 * (e.g., [0, 0]), or null if none selected. The array has elements:
66067 * <div class="mdetail-params"><ul>
66068 * <li><b>rowIndex</b> : Number<p class="sub-desc">The index of the selected row</p></li>
66069 * <li><b>cellIndex</b> : Number<p class="sub-desc">The index of the selected cell.
66070 * Due to possible column reordering, the cellIndex should <b>not</b> be used as an
66071 * index into the Record's data. Instead, use the cellIndex to determine the <i>name</i>
66072 * of the selected cell and use the field name to retrieve the data value from the record:<pre><code>
66074 var fieldName = grid.getColumnModel().getDataIndex(cellIndex);
66075 // get data value based on name
66076 var data = record.get(fieldName);
66077 * </code></pre></p></li>
66079 * @return {Array} An array containing the row and column indexes of the selected cell, or null if none selected.
66081 getSelectedCell : function(){
66082 return this.selection ? this.selection.cell : null;
66086 * If anything is selected, clears all selections and fires the selectionchange event.
66087 * @param {Boolean} preventNotify <tt>true</tt> to prevent the gridview from
66088 * being notified about the change.
66090 clearSelections : function(preventNotify){
66091 var s = this.selection;
66093 if(preventNotify !== true){
66094 this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
66096 this.selection = null;
66097 this.fireEvent("selectionchange", this, null);
66102 * Returns <tt>true</tt> if there is a selection.
66103 * @return {Boolean}
66105 hasSelection : function(){
66106 return this.selection ? true : false;
66110 handleMouseDown : function(g, row, cell, e){
66111 if(e.button !== 0 || this.isLocked()){
66114 this.select(row, cell);
66118 * Selects a cell. Before selecting a cell, fires the
66119 * {@link #beforecellselect} event. If this check is satisfied the cell
66120 * will be selected and followed up by firing the {@link #cellselect} and
66121 * {@link #selectionchange} events.
66122 * @param {Number} rowIndex The index of the row to select
66123 * @param {Number} colIndex The index of the column to select
66124 * @param {Boolean} preventViewNotify (optional) Specify <tt>true</tt> to
66125 * prevent notifying the view (disables updating the selected appearance)
66126 * @param {Boolean} preventFocus (optional) Whether to prevent the cell at
66127 * the specified rowIndex / colIndex from being focused.
66128 * @param {Ext.data.Record} r (optional) The record to select
66130 select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){
66131 if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
66132 this.clearSelections();
66133 r = r || this.grid.store.getAt(rowIndex);
66136 cell : [rowIndex, colIndex]
66138 if(!preventViewNotify){
66139 var v = this.grid.getView();
66140 v.onCellSelect(rowIndex, colIndex);
66141 if(preventFocus !== true){
66142 v.focusCell(rowIndex, colIndex);
66145 this.fireEvent("cellselect", this, rowIndex, colIndex);
66146 this.fireEvent("selectionchange", this, this.selection);
66151 isSelectable : function(rowIndex, colIndex, cm){
66152 return !cm.isHidden(colIndex);
66156 onEditorKey: function(field, e){
66157 if(e.getKey() == e.TAB){
66158 this.handleKeyDown(e);
66163 handleKeyDown : function(e){
66164 if(!e.isNavKeyPress()){
66168 var k = e.getKey(),
66170 s = this.selection,
66172 walk = function(row, col, step){
66173 return g.walkCells(
66177 g.isEditor && g.editing ? sm.acceptsNav : sm.isSelectable, // *** handle tabbing while editorgrid is in edit mode
66181 cell, newCell, r, c, ae;
66190 // *** call e.stopEvent() only for non ESC, PAGE UP/DOWN KEYS
66196 cell = walk(0, 0, 1); // *** use private walk() function defined above
66198 this.select(cell[0], cell[1]);
66203 cell = s.cell; // currently selected cell
66204 r = cell[0]; // current row
66205 c = cell[1]; // current column
66210 newCell = walk(r, c - 1, -1);
66212 newCell = walk(r, c + 1, 1);
66216 newCell = walk(r + 1, c, 1);
66219 newCell = walk(r - 1, c, -1);
66222 newCell = walk(r, c + 1, 1);
66225 newCell = walk(r, c - 1, -1);
66228 if (g.isEditor && !g.editing) {
66229 g.startEditing(r, c);
66236 // *** reassign r & c variables to newly-selected cell's row and column
66240 this.select(r, c); // *** highlight newly-selected cell and update selection
66242 if(g.isEditor && g.editing){ // *** handle tabbing while editorgrid is in edit mode
66243 ae = g.activeEditor;
66244 if(ae && ae.field.triggerBlur){
66245 // *** if activeEditor is a TriggerField, explicitly call its triggerBlur() method
66246 ae.field.triggerBlur();
66248 g.startEditing(r, c);
66253 acceptsNav : function(row, col, cm){
66254 return !cm.isHidden(col) && cm.isCellEditable(col, row);
66257 * @class Ext.grid.EditorGridPanel
66258 * @extends Ext.grid.GridPanel
66259 * <p>This class extends the {@link Ext.grid.GridPanel GridPanel Class} to provide cell editing
66260 * on selected {@link Ext.grid.Column columns}. The editable columns are specified by providing
66261 * an {@link Ext.grid.ColumnModel#editor editor} in the {@link Ext.grid.Column column configuration}.</p>
66262 * <p>Editability of columns may be controlled programatically by inserting an implementation
66263 * of {@link Ext.grid.ColumnModel#isCellEditable isCellEditable} into the
66264 * {@link Ext.grid.ColumnModel ColumnModel}.</p>
66265 * <p>Editing is performed on the value of the <i>field</i> specified by the column's
66266 * <tt>{@link Ext.grid.ColumnModel#dataIndex dataIndex}</tt> in the backing {@link Ext.data.Store Store}
66267 * (so if you are using a {@link Ext.grid.ColumnModel#setRenderer renderer} in order to display
66268 * transformed data, this must be accounted for).</p>
66269 * <p>If a value-to-description mapping is used to render a column, then a {@link Ext.form.Field#ComboBox ComboBox}
66270 * which uses the same {@link Ext.form.Field#valueField value}-to-{@link Ext.form.Field#displayFieldField description}
66271 * mapping would be an appropriate editor.</p>
66272 * If there is a more complex mismatch between the visible data in the grid, and the editable data in
66273 * the {@link Edt.data.Store Store}, then code to transform the data both before and after editing can be
66274 * injected using the {@link #beforeedit} and {@link #afteredit} events.
66276 * @param {Object} config The config object
66277 * @xtype editorgrid
66279 Ext.grid.EditorGridPanel = Ext.extend(Ext.grid.GridPanel, {
66281 * @cfg {Number} clicksToEdit
66282 * <p>The number of clicks on a cell required to display the cell's editor (defaults to 2).</p>
66283 * <p>Setting this option to 'auto' means that mousedown <i>on the selected cell</i> starts
66284 * editing that cell.</p>
66289 * @cfg {Boolean} forceValidation
66290 * True to force validation even if the value is unmodified (defaults to false)
66292 forceValidation: false,
66300 * @cfg {Boolean} autoEncode
66301 * True to automatically HTML encode and decode values pre and post edit (defaults to false)
66303 autoEncode : false,
66306 * @cfg {Boolean} trackMouseOver @hide
66309 trackMouseOver: false, // causes very odd FF errors
66312 initComponent : function(){
66313 Ext.grid.EditorGridPanel.superclass.initComponent.call(this);
66315 if(!this.selModel){
66317 * @cfg {Object} selModel Any subclass of AbstractSelectionModel that will provide the selection model for
66318 * the grid (defaults to {@link Ext.grid.CellSelectionModel} if not specified).
66320 this.selModel = new Ext.grid.CellSelectionModel();
66323 this.activeEditor = null;
66327 * @event beforeedit
66328 * Fires before cell editing is triggered. The edit event object has the following properties <br />
66329 * <ul style="padding:5px;padding-left:16px;">
66330 * <li>grid - This grid</li>
66331 * <li>record - The record being edited</li>
66332 * <li>field - The field name being edited</li>
66333 * <li>value - The value for the field being edited.</li>
66334 * <li>row - The grid row index</li>
66335 * <li>column - The grid column index</li>
66336 * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
66338 * @param {Object} e An edit event (see above for description)
66343 * Fires after a cell is edited. The edit event object has the following properties <br />
66344 * <ul style="padding:5px;padding-left:16px;">
66345 * <li>grid - This grid</li>
66346 * <li>record - The record being edited</li>
66347 * <li>field - The field name being edited</li>
66348 * <li>value - The value being set</li>
66349 * <li>originalValue - The original value for the field, before the edit.</li>
66350 * <li>row - The grid row index</li>
66351 * <li>column - The grid column index</li>
66355 grid.on('afteredit', afterEdit, this );
66357 function afterEdit(e) {
66358 // execute an XHR to send/commit data to the server, in callback do (if successful):
66362 * @param {Object} e An edit event (see above for description)
66366 * @event validateedit
66367 * Fires after a cell is edited, but before the value is set in the record. Return false
66368 * to cancel the change. The edit event object has the following properties <br />
66369 * <ul style="padding:5px;padding-left:16px;">
66370 * <li>grid - This grid</li>
66371 * <li>record - The record being edited</li>
66372 * <li>field - The field name being edited</li>
66373 * <li>value - The value being set</li>
66374 * <li>originalValue - The original value for the field, before the edit.</li>
66375 * <li>row - The grid row index</li>
66376 * <li>column - The grid column index</li>
66377 * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
66379 * Usage example showing how to remove the red triangle (dirty record indicator) from some
66380 * records (not all). By observing the grid's validateedit event, it can be cancelled if
66381 * the edit occurs on a targeted row (for example) and then setting the field's new value
66382 * in the Record directly:
66384 grid.on('validateedit', function(e) {
66385 var myTargetRow = 6;
66387 if (e.row == myTargetRow) {
66389 e.record.data[e.field] = e.value;
66393 * @param {Object} e An edit event (see above for description)
66400 initEvents : function(){
66401 Ext.grid.EditorGridPanel.superclass.initEvents.call(this);
66403 this.getGridEl().on('mousewheel', this.stopEditing.createDelegate(this, [true]), this);
66404 this.on('columnresize', this.stopEditing, this, [true]);
66406 if(this.clicksToEdit == 1){
66407 this.on("cellclick", this.onCellDblClick, this);
66409 var view = this.getView();
66410 if(this.clicksToEdit == 'auto' && view.mainBody){
66411 view.mainBody.on('mousedown', this.onAutoEditClick, this);
66413 this.on('celldblclick', this.onCellDblClick, this);
66418 onCellDblClick : function(g, row, col){
66419 this.startEditing(row, col);
66423 onAutoEditClick : function(e, t){
66424 if(e.button !== 0){
66427 var row = this.view.findRowIndex(t);
66428 var col = this.view.findCellIndex(t);
66429 if(row !== false && col !== false){
66430 this.stopEditing();
66431 if(this.selModel.getSelectedCell){ // cell sm
66432 var sc = this.selModel.getSelectedCell();
66433 if(sc && sc[0] === row && sc[1] === col){
66434 this.startEditing(row, col);
66437 if(this.selModel.isSelected(row)){
66438 this.startEditing(row, col);
66445 onEditComplete : function(ed, value, startValue){
66446 this.editing = false;
66447 this.activeEditor = null;
66450 var field = this.colModel.getDataIndex(ed.col);
66451 value = this.postEditValue(value, startValue, r, field);
66452 if(this.forceValidation === true || String(value) !== String(startValue)){
66457 originalValue: startValue,
66463 if(this.fireEvent("validateedit", e) !== false && !e.cancel && String(value) !== String(startValue)){
66464 r.set(field, e.value);
66466 this.fireEvent("afteredit", e);
66469 this.view.focusCell(ed.row, ed.col);
66473 * Starts editing the specified for the specified row/column
66474 * @param {Number} rowIndex
66475 * @param {Number} colIndex
66477 startEditing : function(row, col){
66478 this.stopEditing();
66479 if(this.colModel.isCellEditable(col, row)){
66480 this.view.ensureVisible(row, col, true);
66481 var r = this.store.getAt(row);
66482 var field = this.colModel.getDataIndex(col);
66487 value: r.data[field],
66492 if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
66493 this.editing = true;
66494 var ed = this.colModel.getCellEditor(col, row);
66499 ed.parentEl = this.view.getEditorParent(ed);
66504 c.field.focus(false, true);
66509 specialkey: function(field, e){
66510 this.getSelectionModel().onEditorKey(field, e);
66512 complete: this.onEditComplete,
66513 canceledit: this.stopEditing.createDelegate(this, [true])
66525 this.activeEditor = ed;
66526 var v = this.preEditValue(r, field);
66527 ed.startEdit(this.view.getCell(row, col).firstChild, Ext.isDefined(v) ? v : '');
66533 preEditValue : function(r, field){
66534 var value = r.data[field];
66535 return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlDecode(value) : value;
66539 postEditValue : function(value, originalValue, r, field){
66540 return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlEncode(value) : value;
66544 * Stops any active editing
66545 * @param {Boolean} cancel (optional) True to cancel any changes
66547 stopEditing : function(cancel){
66549 var ae = this.activeEditor;
66551 ae[cancel === true ? 'cancelEdit' : 'completeEdit']();
66552 this.view.focusCell(ae.row, ae.col);
66554 this.activeEditor = null;
66556 this.editing = false;
66559 Ext.reg('editorgrid', Ext.grid.EditorGridPanel);// private
66560 // This is a support class used internally by the Grid components
66561 Ext.grid.GridEditor = function(field, config){
66562 Ext.grid.GridEditor.superclass.constructor.call(this, field, config);
66563 field.monitorTab = false;
66566 Ext.extend(Ext.grid.GridEditor, Ext.Editor, {
66567 alignment: "tl-tl",
66570 cls: "x-small-editor x-grid-editor",
66574 * @class Ext.grid.PropertyRecord
66575 * A specific {@link Ext.data.Record} type that represents a name/value pair and is made to work with the
66576 * {@link Ext.grid.PropertyGrid}. Typically, PropertyRecords do not need to be created directly as they can be
66577 * created implicitly by simply using the appropriate data configs either via the {@link Ext.grid.PropertyGrid#source}
66578 * config property or by calling {@link Ext.grid.PropertyGrid#setSource}. However, if the need arises, these records
66579 * can also be created explicitly as shwon below. Example usage:
66581 var rec = new Ext.grid.PropertyRecord({
66583 value: new Date(Date.parse('05/26/1972'))
66585 // Add record to an already populated grid
66586 grid.store.addSorted(rec);
66589 * @param {Object} config A data object in the format: {name: [name], value: [value]}. The specified value's type
66590 * will be read automatically by the grid to determine the type of editor to use when displaying it.
66592 Ext.grid.PropertyRecord = Ext.data.Record.create([
66593 {name:'name',type:'string'}, 'value'
66597 * @class Ext.grid.PropertyStore
66598 * @extends Ext.util.Observable
66599 * A custom wrapper for the {@link Ext.grid.PropertyGrid}'s {@link Ext.data.Store}. This class handles the mapping
66600 * between the custom data source objects supported by the grid and the {@link Ext.grid.PropertyRecord} format
66601 * required for compatibility with the underlying store. Generally this class should not need to be used directly --
66602 * the grid's data should be accessed from the underlying store via the {@link #store} property.
66604 * @param {Ext.grid.Grid} grid The grid this store will be bound to
66605 * @param {Object} source The source data config object
66607 Ext.grid.PropertyStore = function(grid, source){
66609 this.store = new Ext.data.Store({
66610 recordType : Ext.grid.PropertyRecord
66612 this.store.on('update', this.onUpdate, this);
66614 this.setSource(source);
66616 Ext.grid.PropertyStore.superclass.constructor.call(this);
66618 Ext.extend(Ext.grid.PropertyStore, Ext.util.Observable, {
66619 // protected - should only be called by the grid. Use grid.setSource instead.
66620 setSource : function(o){
66622 this.store.removeAll();
66625 if(this.isEditableValue(o[k])){
66626 data.push(new Ext.grid.PropertyRecord({name: k, value: o[k]}, k));
66629 this.store.loadRecords({records: data}, {}, true);
66633 onUpdate : function(ds, record, type){
66634 if(type == Ext.data.Record.EDIT){
66635 var v = record.data.value;
66636 var oldValue = record.modified.value;
66637 if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
66638 this.source[record.id] = v;
66640 this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
66648 getProperty : function(row){
66649 return this.store.getAt(row);
66653 isEditableValue: function(val){
66654 if(Ext.isDate(val)){
66657 return !(Ext.isObject(val) || Ext.isFunction(val));
66661 setValue : function(prop, value){
66662 this.source[prop] = value;
66663 this.store.getById(prop).set('value', value);
66666 // protected - should only be called by the grid. Use grid.getSource instead.
66667 getSource : function(){
66668 return this.source;
66673 * @class Ext.grid.PropertyColumnModel
66674 * @extends Ext.grid.ColumnModel
66675 * A custom column model for the {@link Ext.grid.PropertyGrid}. Generally it should not need to be used directly.
66677 * @param {Ext.grid.Grid} grid The grid this store will be bound to
66678 * @param {Object} source The source data config object
66680 Ext.grid.PropertyColumnModel = function(grid, store){
66685 g.PropertyColumnModel.superclass.constructor.call(this, [
66686 {header: this.nameText, width:50, sortable: true, dataIndex:'name', id: 'name', menuDisabled:true},
66687 {header: this.valueText, width:50, resizable:false, dataIndex: 'value', id: 'value', menuDisabled:true}
66689 this.store = store;
66691 var bfield = new f.Field({
66692 autoCreate: {tag: 'select', children: [
66693 {tag: 'option', value: 'true', html: 'true'},
66694 {tag: 'option', value: 'false', html: 'false'}
66696 getValue : function(){
66697 return this.el.dom.value == 'true';
66701 'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
66702 'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
66703 'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
66704 'boolean' : new g.GridEditor(bfield, {
66708 this.renderCellDelegate = this.renderCell.createDelegate(this);
66709 this.renderPropDelegate = this.renderProp.createDelegate(this);
66712 Ext.extend(Ext.grid.PropertyColumnModel, Ext.grid.ColumnModel, {
66713 // private - strings used for locale support
66715 valueText : 'Value',
66716 dateFormat : 'm/j/Y',
66719 renderDate : function(dateVal){
66720 return dateVal.dateFormat(this.dateFormat);
66724 renderBool : function(bVal){
66725 return bVal ? 'true' : 'false';
66729 isCellEditable : function(colIndex, rowIndex){
66730 return colIndex == 1;
66734 getRenderer : function(col){
66736 this.renderCellDelegate : this.renderPropDelegate;
66740 renderProp : function(v){
66741 return this.getPropertyName(v);
66745 renderCell : function(val){
66747 if(Ext.isDate(val)){
66748 rv = this.renderDate(val);
66749 }else if(typeof val == 'boolean'){
66750 rv = this.renderBool(val);
66752 return Ext.util.Format.htmlEncode(rv);
66756 getPropertyName : function(name){
66757 var pn = this.grid.propertyNames;
66758 return pn && pn[name] ? pn[name] : name;
66762 getCellEditor : function(colIndex, rowIndex){
66763 var p = this.store.getProperty(rowIndex),
66765 val = p.data.value;
66766 if(this.grid.customEditors[n]){
66767 return this.grid.customEditors[n];
66769 if(Ext.isDate(val)){
66770 return this.editors.date;
66771 }else if(typeof val == 'number'){
66772 return this.editors.number;
66773 }else if(typeof val == 'boolean'){
66774 return this.editors['boolean'];
66776 return this.editors.string;
66781 destroy : function(){
66782 Ext.grid.PropertyColumnModel.superclass.destroy.call(this);
66783 for(var ed in this.editors){
66790 * @class Ext.grid.PropertyGrid
66791 * @extends Ext.grid.EditorGridPanel
66792 * A specialized grid implementation intended to mimic the traditional property grid as typically seen in
66793 * development IDEs. Each row in the grid represents a property of some object, and the data is stored
66794 * as a set of name/value pairs in {@link Ext.grid.PropertyRecord}s. Example usage:
66796 var grid = new Ext.grid.PropertyGrid({
66797 title: 'Properties Grid',
66800 renderTo: 'grid-ct',
66802 "(name)": "My Object",
66803 "Created": new Date(Date.parse('10/15/2006')),
66804 "Available": false,
66806 "Description": "A test object"
66811 * @param {Object} config The grid config object
66813 Ext.grid.PropertyGrid = Ext.extend(Ext.grid.EditorGridPanel, {
66815 * @cfg {Object} propertyNames An object containing property name/display name pairs.
66816 * If specified, the display name will be shown in the name column instead of the property name.
66819 * @cfg {Object} source A data object to use as the data source of the grid (see {@link #setSource} for details).
66822 * @cfg {Object} customEditors An object containing name/value pairs of custom editor type definitions that allow
66823 * the grid to support additional types of editable fields. By default, the grid supports strongly-typed editing
66824 * of strings, dates, numbers and booleans using built-in form editors, but any custom type can be supported and
66825 * associated with a custom input control by specifying a custom editor. The name of the editor
66826 * type should correspond with the name of the property that will use the editor. Example usage:
66828 var grid = new Ext.grid.PropertyGrid({
66831 'Start Time': new Ext.grid.GridEditor(new Ext.form.TimeField({selectOnFocus:true}))
66834 'Start Time': '10:00 AM'
66840 // private config overrides
66841 enableColumnMove:false,
66843 trackMouseOver: false,
66845 enableHdMenu : false,
66851 initComponent : function(){
66852 this.customEditors = this.customEditors || {};
66853 this.lastEditRow = null;
66854 var store = new Ext.grid.PropertyStore(this);
66855 this.propStore = store;
66856 var cm = new Ext.grid.PropertyColumnModel(this, store);
66857 store.store.sort('name', 'ASC');
66860 * @event beforepropertychange
66861 * Fires before a property value changes. Handlers can return false to cancel the property change
66862 * (this will internally call {@link Ext.data.Record#reject} on the property's record).
66863 * @param {Object} source The source data object for the grid (corresponds to the same object passed in
66864 * as the {@link #source} config property).
66865 * @param {String} recordId The record's id in the data store
66866 * @param {Mixed} value The current edited property value
66867 * @param {Mixed} oldValue The original property value prior to editing
66869 'beforepropertychange',
66871 * @event propertychange
66872 * Fires after a property value has changed.
66873 * @param {Object} source The source data object for the grid (corresponds to the same object passed in
66874 * as the {@link #source} config property).
66875 * @param {String} recordId The record's id in the data store
66876 * @param {Mixed} value The current edited property value
66877 * @param {Mixed} oldValue The original property value prior to editing
66882 this.ds = store.store;
66883 Ext.grid.PropertyGrid.superclass.initComponent.call(this);
66885 this.mon(this.selModel, 'beforecellselect', function(sm, rowIndex, colIndex){
66886 if(colIndex === 0){
66887 this.startEditing.defer(200, this, [rowIndex, 1]);
66894 onRender : function(){
66895 Ext.grid.PropertyGrid.superclass.onRender.apply(this, arguments);
66897 this.getGridEl().addClass('x-props-grid');
66901 afterRender: function(){
66902 Ext.grid.PropertyGrid.superclass.afterRender.apply(this, arguments);
66904 this.setSource(this.source);
66909 * Sets the source data object containing the property data. The data object can contain one or more name/value
66910 * pairs representing all of the properties of an object to display in the grid, and this data will automatically
66911 * be loaded into the grid's {@link #store}. The values should be supplied in the proper data type if needed,
66912 * otherwise string type will be assumed. If the grid already contains data, this method will replace any
66913 * existing data. See also the {@link #source} config value. Example usage:
66916 "(name)": "My Object",
66917 "Created": new Date(Date.parse('10/15/2006')), // date type
66918 "Available": false, // boolean type
66919 "Version": .01, // decimal type
66920 "Description": "A test object"
66923 * @param {Object} source The data object
66925 setSource : function(source){
66926 this.propStore.setSource(source);
66930 * Gets the source data object containing the property data. See {@link #setSource} for details regarding the
66931 * format of the data object.
66932 * @return {Object} The data object
66934 getSource : function(){
66935 return this.propStore.getSource();
66938 Ext.reg("propertygrid", Ext.grid.PropertyGrid);
66940 * @class Ext.grid.GroupingView
\r
66941 * @extends Ext.grid.GridView
\r
66942 * Adds the ability for single level grouping to the grid. A {@link Ext.data.GroupingStore GroupingStore}
\r
66943 * must be used to enable grouping. Some grouping characteristics may also be configured at the
\r
66944 * {@link Ext.grid.Column Column level}<div class="mdetail-params"><ul>
\r
66945 * <li><code>{@link Ext.grid.Column#emptyGroupText emptyGroupText}</li>
\r
66946 * <li><code>{@link Ext.grid.Column#groupable groupable}</li>
\r
66947 * <li><code>{@link Ext.grid.Column#groupName groupName}</li>
\r
66948 * <li><code>{@link Ext.grid.Column#groupRender groupRender}</li>
\r
66950 * <p>Sample usage:</p>
\r
66952 var grid = new Ext.grid.GridPanel({
\r
66953 // A groupingStore is required for a GroupingView
\r
66954 store: new {@link Ext.data.GroupingStore}({
\r
66955 autoDestroy: true,
\r
66957 data: xg.dummyData,
\r
66958 sortInfo: {field: 'company', direction: 'ASC'},
\r
66959 {@link Ext.data.GroupingStore#groupOnSort groupOnSort}: true,
\r
66960 {@link Ext.data.GroupingStore#remoteGroup remoteGroup}: true,
\r
66961 {@link Ext.data.GroupingStore#groupField groupField}: 'industry'
\r
66963 colModel: new {@link Ext.grid.ColumnModel}({
\r
66965 {id:'company',header: 'Company', width: 60, dataIndex: 'company'},
\r
66966 // {@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
66967 {header: 'Price', renderer: Ext.util.Format.usMoney, dataIndex: 'price', {@link Ext.grid.Column#groupable groupable}: false},
\r
66968 {header: 'Change', dataIndex: 'change', renderer: Ext.util.Format.usMoney},
\r
66969 {header: 'Industry', dataIndex: 'industry'},
\r
66970 {header: 'Last Updated', renderer: Ext.util.Format.dateRenderer('m/d/Y'), dataIndex: 'lastChange'}
\r
66974 menuDisabled: false,
\r
66979 view: new Ext.grid.GroupingView({
\r
66980 {@link Ext.grid.GridView#forceFit forceFit}: true,
\r
66981 // custom grouping text template to display the number of items per group
\r
66982 {@link #groupTextTpl}: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
\r
66988 collapsible: true,
\r
66989 animCollapse: false,
\r
66990 title: 'Grouping Example',
\r
66991 iconCls: 'icon-grid',
\r
66992 renderTo: document.body
\r
66996 * @param {Object} config
\r
66998 Ext.grid.GroupingView = Ext.extend(Ext.grid.GridView, {
\r
67001 * @cfg {String} groupByText Text displayed in the grid header menu for grouping by a column
\r
67002 * (defaults to 'Group By This Field').
\r
67004 groupByText : 'Group By This Field',
\r
67006 * @cfg {String} showGroupsText Text displayed in the grid header for enabling/disabling grouping
\r
67007 * (defaults to 'Show in Groups').
\r
67009 showGroupsText : 'Show in Groups',
\r
67011 * @cfg {Boolean} hideGroupedColumn <tt>true</tt> to hide the column that is currently grouped (defaults to <tt>false</tt>)
\r
67013 hideGroupedColumn : false,
\r
67015 * @cfg {Boolean} showGroupName If <tt>true</tt> will display a prefix plus a ': ' before the group field value
\r
67016 * in the group header line. The prefix will consist of the <tt><b>{@link Ext.grid.Column#groupName groupName}</b></tt>
\r
67017 * (or the configured <tt><b>{@link Ext.grid.Column#header header}</b></tt> if not provided) configured in the
\r
67018 * {@link Ext.grid.Column} for each set of grouped rows (defaults to <tt>true</tt>).
\r
67020 showGroupName : true,
\r
67022 * @cfg {Boolean} startCollapsed <tt>true</tt> to start all groups collapsed (defaults to <tt>false</tt>)
\r
67024 startCollapsed : false,
\r
67026 * @cfg {Boolean} enableGrouping <tt>false</tt> to disable grouping functionality (defaults to <tt>true</tt>)
\r
67028 enableGrouping : true,
\r
67030 * @cfg {Boolean} enableGroupingMenu <tt>true</tt> to enable the grouping control in the column menu (defaults to <tt>true</tt>)
\r
67032 enableGroupingMenu : true,
\r
67034 * @cfg {Boolean} enableNoGroups <tt>true</tt> to allow the user to turn off grouping (defaults to <tt>true</tt>)
\r
67036 enableNoGroups : true,
\r
67038 * @cfg {String} emptyGroupText The text to display when there is an empty group value (defaults to <tt>'(None)'</tt>).
\r
67039 * May also be specified per column, see {@link Ext.grid.Column}.{@link Ext.grid.Column#emptyGroupText emptyGroupText}.
\r
67041 emptyGroupText : '(None)',
\r
67043 * @cfg {Boolean} ignoreAdd <tt>true</tt> to skip refreshing the view when new rows are added (defaults to <tt>false</tt>)
\r
67045 ignoreAdd : false,
\r
67047 * @cfg {String} groupTextTpl The template used to render the group header (defaults to <tt>'{text}'</tt>).
\r
67048 * This is used to format an object which contains the following properties:
\r
67049 * <div class="mdetail-params"><ul>
\r
67050 * <li><b>group</b> : String<p class="sub-desc">The <i>rendered</i> value of the group field.
\r
67051 * By default this is the unchanged value of the group field. If a <tt><b>{@link Ext.grid.Column#groupRenderer groupRenderer}</b></tt>
\r
67052 * is specified, it is the result of a call to that function.</p></li>
\r
67053 * <li><b>gvalue</b> : Object<p class="sub-desc">The <i>raw</i> value of the group field.</p></li>
\r
67054 * <li><b>text</b> : String<p class="sub-desc">The configured header (as described in <tt>{@link #showGroupName})</tt>
\r
67055 * if <tt>{@link #showGroupName}</tt> is <tt>true</tt>) plus the <i>rendered</i> group field value.</p></li>
\r
67056 * <li><b>groupId</b> : String<p class="sub-desc">A unique, generated ID which is applied to the
\r
67057 * View Element which contains the group.</p></li>
\r
67058 * <li><b>startRow</b> : Number<p class="sub-desc">The row index of the Record which caused group change.</p></li>
\r
67059 * <li><b>rs</b> : Array<p class="sub-desc">Contains a single element: The Record providing the data
\r
67060 * for the row which caused group change.</p></li>
\r
67061 * <li><b>cls</b> : String<p class="sub-desc">The generated class name string to apply to the group header Element.</p></li>
\r
67062 * <li><b>style</b> : String<p class="sub-desc">The inline style rules to apply to the group header Element.</p></li>
\r
67063 * </ul></div></p>
\r
67064 * See {@link Ext.XTemplate} for information on how to format data using a template. Possible usage:<pre><code>
\r
67065 var grid = new Ext.grid.GridPanel({
\r
67067 view: new Ext.grid.GroupingView({
\r
67068 groupTextTpl: '{text} ({[values.rs.length]} {[values.rs.length > 1 ? "Items" : "Item"]})'
\r
67073 groupTextTpl : '{text}',
\r
67076 * @cfg {String} groupMode Indicates how to construct the group identifier. <tt>'value'</tt> constructs the id using
\r
67077 * raw value, <tt>'display'</tt> constructs the id using the rendered value. Defaults to <tt>'value'</tt>.
\r
67079 groupMode: 'value',
\r
67082 * @cfg {Function} groupRenderer This property must be configured in the {@link Ext.grid.Column} for
\r
67090 initTemplates : function(){
\r
67091 Ext.grid.GroupingView.superclass.initTemplates.call(this);
\r
67094 var sm = this.grid.getSelectionModel();
\r
67095 sm.on(sm.selectRow ? 'beforerowselect' : 'beforecellselect',
\r
67096 this.onBeforeRowSelect, this);
\r
67098 if(!this.startGroup){
\r
67099 this.startGroup = new Ext.XTemplate(
\r
67100 '<div id="{groupId}" class="x-grid-group {cls}">',
\r
67101 '<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div class="x-grid-group-title">', this.groupTextTpl ,'</div></div>',
\r
67102 '<div id="{groupId}-bd" class="x-grid-group-body">'
\r
67105 this.startGroup.compile();
\r
67106 this.endGroup = '</div></div>';
\r
67110 findGroup : function(el){
\r
67111 return Ext.fly(el).up('.x-grid-group', this.mainBody.dom);
\r
67115 getGroups : function(){
\r
67116 return this.hasRows() ? this.mainBody.dom.childNodes : [];
\r
67120 onAdd : function(){
\r
67121 if(this.enableGrouping && !this.ignoreAdd){
\r
67122 var ss = this.getScrollState();
\r
67124 this.restoreScroll(ss);
\r
67125 }else if(!this.enableGrouping){
\r
67126 Ext.grid.GroupingView.superclass.onAdd.apply(this, arguments);
\r
67131 onRemove : function(ds, record, index, isUpdate){
\r
67132 Ext.grid.GroupingView.superclass.onRemove.apply(this, arguments);
\r
67133 var g = document.getElementById(record._groupId);
\r
67134 if(g && g.childNodes[1].childNodes.length < 1){
\r
67135 Ext.removeNode(g);
\r
67137 this.applyEmptyText();
\r
67141 refreshRow : function(record){
\r
67142 if(this.ds.getCount()==1){
\r
67145 this.isUpdating = true;
\r
67146 Ext.grid.GroupingView.superclass.refreshRow.apply(this, arguments);
\r
67147 this.isUpdating = false;
\r
67152 beforeMenuShow : function(){
\r
67153 var item, items = this.hmenu.items, disabled = this.cm.config[this.hdCtxIndex].groupable === false;
\r
67154 if((item = items.get('groupBy'))){
\r
67155 item.setDisabled(disabled);
\r
67157 if((item = items.get('showGroups'))){
\r
67158 item.setDisabled(disabled);
\r
67159 item.setChecked(!!this.getGroupField(), true);
\r
67164 renderUI : function(){
\r
67165 Ext.grid.GroupingView.superclass.renderUI.call(this);
\r
67166 this.mainBody.on('mousedown', this.interceptMouse, this);
\r
67168 if(this.enableGroupingMenu && this.hmenu){
\r
67169 this.hmenu.add('-',{
\r
67170 itemId:'groupBy',
\r
67171 text: this.groupByText,
\r
67172 handler: this.onGroupByClick,
\r
67174 iconCls:'x-group-by-icon'
\r
67176 if(this.enableNoGroups){
\r
67178 itemId:'showGroups',
\r
67179 text: this.showGroupsText,
\r
67181 checkHandler: this.onShowGroupsClick,
\r
67185 this.hmenu.on('beforeshow', this.beforeMenuShow, this);
\r
67190 onGroupByClick : function(){
\r
67191 this.grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
\r
67192 this.beforeMenuShow(); // Make sure the checkboxes get properly set when changing groups
\r
67196 onShowGroupsClick : function(mi, checked){
\r
67198 this.onGroupByClick();
\r
67200 this.grid.store.clearGrouping();
\r
67205 * Toggles the specified group if no value is passed, otherwise sets the expanded state of the group to the value passed.
\r
67206 * @param {String} groupId The groupId assigned to the group (see getGroupId)
\r
67207 * @param {Boolean} expanded (optional)
\r
67209 toggleGroup : function(group, expanded){
\r
67210 this.grid.stopEditing(true);
\r
67211 group = Ext.getDom(group);
\r
67212 var gel = Ext.fly(group);
\r
67213 expanded = expanded !== undefined ?
\r
67214 expanded : gel.hasClass('x-grid-group-collapsed');
\r
67216 this.state[gel.dom.id] = expanded;
\r
67217 gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed');
\r
67221 * Toggles all groups if no value is passed, otherwise sets the expanded state of all groups to the value passed.
\r
67222 * @param {Boolean} expanded (optional)
\r
67224 toggleAllGroups : function(expanded){
\r
67225 var groups = this.getGroups();
\r
67226 for(var i = 0, len = groups.length; i < len; i++){
\r
67227 this.toggleGroup(groups[i], expanded);
\r
67232 * Expands all grouped rows.
\r
67234 expandAllGroups : function(){
\r
67235 this.toggleAllGroups(true);
\r
67239 * Collapses all grouped rows.
\r
67241 collapseAllGroups : function(){
\r
67242 this.toggleAllGroups(false);
\r
67246 interceptMouse : function(e){
\r
67247 var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
\r
67250 this.toggleGroup(hd.parentNode);
\r
67255 getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){
\r
67256 var g = groupRenderer ? groupRenderer(v, {}, r, rowIndex, colIndex, ds) : String(v);
\r
67257 if(g === '' || g === ' '){
\r
67258 g = this.cm.config[colIndex].emptyGroupText || this.emptyGroupText;
\r
67264 getGroupField : function(){
\r
67265 return this.grid.store.getGroupState();
\r
67269 afterRender : function(){
\r
67270 Ext.grid.GroupingView.superclass.afterRender.call(this);
\r
67271 if(this.grid.deferRowRender){
\r
67272 this.updateGroupWidths();
\r
67277 renderRows : function(){
\r
67278 var groupField = this.getGroupField();
\r
67279 var eg = !!groupField;
\r
67280 // if they turned off grouping and the last grouped field is hidden
\r
67281 if(this.hideGroupedColumn) {
\r
67282 var colIndex = this.cm.findColumnIndex(groupField);
\r
67283 if(!eg && this.lastGroupField !== undefined) {
\r
67284 this.mainBody.update('');
\r
67285 this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField), false);
\r
67286 delete this.lastGroupField;
\r
67287 }else if (eg && this.lastGroupField === undefined) {
\r
67288 this.lastGroupField = groupField;
\r
67289 this.cm.setHidden(colIndex, true);
\r
67290 }else if (eg && this.lastGroupField !== undefined && groupField !== this.lastGroupField) {
\r
67291 this.mainBody.update('');
\r
67292 var oldIndex = this.cm.findColumnIndex(this.lastGroupField);
\r
67293 this.cm.setHidden(oldIndex, false);
\r
67294 this.lastGroupField = groupField;
\r
67295 this.cm.setHidden(colIndex, true);
\r
67298 return Ext.grid.GroupingView.superclass.renderRows.apply(
\r
67299 this, arguments);
\r
67303 doRender : function(cs, rs, ds, startRow, colCount, stripe){
\r
67304 if(rs.length < 1){
\r
67307 var groupField = this.getGroupField(),
\r
67308 colIndex = this.cm.findColumnIndex(groupField),
\r
67311 this.enableGrouping = !!groupField;
\r
67313 if(!this.enableGrouping || this.isUpdating){
\r
67314 return Ext.grid.GroupingView.superclass.doRender.apply(
\r
67315 this, arguments);
\r
67317 var gstyle = 'width:' + this.getTotalWidth() + ';',
\r
67318 gidPrefix = this.grid.getGridEl().id,
\r
67319 cfg = this.cm.config[colIndex],
\r
67320 groupRenderer = cfg.groupRenderer || cfg.renderer,
\r
67321 prefix = this.showGroupName ? (cfg.groupName || cfg.header)+': ' : '',
\r
67323 curGroup, i, len, gid;
\r
67325 for(i = 0, len = rs.length; i < len; i++){
\r
67326 var rowIndex = startRow + i,
\r
67328 gvalue = r.data[groupField];
\r
67330 g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
\r
67331 if(!curGroup || curGroup.group != g){
\r
67332 gid = this.constructId(gvalue, gidPrefix, groupField, colIndex);
\r
67333 // if state is defined use it, however state is in terms of expanded
\r
67334 // so negate it, otherwise use the default.
\r
67335 var isCollapsed = typeof this.state[gid] !== 'undefined' ? !this.state[gid] : this.startCollapsed;
\r
67336 var gcls = isCollapsed ? 'x-grid-group-collapsed' : '';
\r
67340 text: prefix + g,
\r
67342 startRow: rowIndex,
\r
67347 groups.push(curGroup);
\r
67349 curGroup.rs.push(r);
\r
67351 r._groupId = gid;
\r
67355 for(i = 0, len = groups.length; i < len; i++){
\r
67357 this.doGroupStart(buf, g, cs, ds, colCount);
\r
67358 buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call(
\r
67359 this, cs, g.rs, ds, g.startRow, colCount, stripe);
\r
67361 this.doGroupEnd(buf, g, cs, ds, colCount);
\r
67363 return buf.join('');
\r
67367 * Dynamically tries to determine the groupId of a specific value
\r
67368 * @param {String} value
\r
67369 * @return {String} The group id
\r
67371 getGroupId : function(value){
\r
67372 var field = this.getGroupField();
\r
67373 return this.constructId(value, this.grid.getGridEl().id, field, this.cm.findColumnIndex(field));
\r
67377 constructId : function(value, prefix, field, idx){
\r
67378 var cfg = this.cm.config[idx],
\r
67379 groupRenderer = cfg.groupRenderer || cfg.renderer,
\r
67380 val = (this.groupMode == 'value') ? value : this.getGroup(value, {data:{}}, groupRenderer, 0, idx, this.ds);
\r
67382 return prefix + '-gp-' + field + '-' + Ext.util.Format.htmlEncode(val);
\r
67386 doGroupStart : function(buf, g, cs, ds, colCount){
\r
67387 buf[buf.length] = this.startGroup.apply(g);
\r
67391 doGroupEnd : function(buf, g, cs, ds, colCount){
\r
67392 buf[buf.length] = this.endGroup;
\r
67396 getRows : function(){
\r
67397 if(!this.enableGrouping){
\r
67398 return Ext.grid.GroupingView.superclass.getRows.call(this);
\r
67401 var g, gs = this.getGroups();
\r
67402 for(var i = 0, len = gs.length; i < len; i++){
\r
67403 g = gs[i].childNodes[1].childNodes;
\r
67404 for(var j = 0, jlen = g.length; j < jlen; j++){
\r
67405 r[r.length] = g[j];
\r
67412 updateGroupWidths : function(){
\r
67413 if(!this.enableGrouping || !this.hasRows()){
\r
67416 var tw = Math.max(this.cm.getTotalWidth(), this.el.dom.offsetWidth-this.getScrollOffset()) +'px';
\r
67417 var gs = this.getGroups();
\r
67418 for(var i = 0, len = gs.length; i < len; i++){
\r
67419 gs[i].firstChild.style.width = tw;
\r
67424 onColumnWidthUpdated : function(col, w, tw){
\r
67425 Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this, col, w, tw);
\r
67426 this.updateGroupWidths();
\r
67430 onAllColumnWidthsUpdated : function(ws, tw){
\r
67431 Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this, ws, tw);
\r
67432 this.updateGroupWidths();
\r
67436 onColumnHiddenUpdated : function(col, hidden, tw){
\r
67437 Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this, col, hidden, tw);
\r
67438 this.updateGroupWidths();
\r
67442 onLayout : function(){
\r
67443 this.updateGroupWidths();
\r
67447 onBeforeRowSelect : function(sm, rowIndex){
\r
67448 if(!this.enableGrouping){
\r
67451 var row = this.getRow(rowIndex);
\r
67452 if(row && !row.offsetParent){
\r
67453 var g = this.findGroup(row);
\r
67454 this.toggleGroup(g, true);
\r
67459 Ext.grid.GroupingView.GROUP_ID = 1000;