roojs-ui.js
[roojs1] / roojs-ui-debug.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11
12
13 /**
14  * @class Roo.data.SortTypes
15  * @static
16  * Defines the default sorting (casting?) comparison functions used when sorting data.
17  */
18 Roo.data.SortTypes = {
19     /**
20      * Default sort that does nothing
21      * @param {Mixed} s The value being converted
22      * @return {Mixed} The comparison value
23      */
24     none : function(s){
25         return s;
26     },
27     
28     /**
29      * The regular expression used to strip tags
30      * @type {RegExp}
31      * @property
32      */
33     stripTagsRE : /<\/?[^>]+>/gi,
34     
35     /**
36      * Strips all HTML tags to sort on text only
37      * @param {Mixed} s The value being converted
38      * @return {String} The comparison value
39      */
40     asText : function(s){
41         return String(s).replace(this.stripTagsRE, "");
42     },
43     
44     /**
45      * Strips all HTML tags to sort on text only - Case insensitive
46      * @param {Mixed} s The value being converted
47      * @return {String} The comparison value
48      */
49     asUCText : function(s){
50         return String(s).toUpperCase().replace(this.stripTagsRE, "");
51     },
52     
53     /**
54      * Case insensitive string
55      * @param {Mixed} s The value being converted
56      * @return {String} The comparison value
57      */
58     asUCString : function(s) {
59         return String(s).toUpperCase();
60     },
61     
62     /**
63      * Date sorting
64      * @param {Mixed} s The value being converted
65      * @return {Number} The comparison value
66      */
67     asDate : function(s) {
68         if(!s){
69             return 0;
70         }
71         if(s instanceof Date){
72             return s.getTime();
73         }
74         return Date.parse(String(s));
75     },
76     
77     /**
78      * Float sorting
79      * @param {Mixed} s The value being converted
80      * @return {Float} The comparison value
81      */
82     asFloat : function(s) {
83         var val = parseFloat(String(s).replace(/,/g, ""));
84         if(isNaN(val)) {
85             val = 0;
86         }
87         return val;
88     },
89     
90     /**
91      * Integer sorting
92      * @param {Mixed} s The value being converted
93      * @return {Number} The comparison value
94      */
95     asInt : function(s) {
96         var val = parseInt(String(s).replace(/,/g, ""));
97         if(isNaN(val)) {
98             val = 0;
99         }
100         return val;
101     }
102 };/*
103  * Based on:
104  * Ext JS Library 1.1.1
105  * Copyright(c) 2006-2007, Ext JS, LLC.
106  *
107  * Originally Released Under LGPL - original licence link has changed is not relivant.
108  *
109  * Fork - LGPL
110  * <script type="text/javascript">
111  */
112
113 /**
114 * @class Roo.data.Record
115  * Instances of this class encapsulate both record <em>definition</em> information, and record
116  * <em>value</em> information for use in {@link Roo.data.Store} objects, or any code which needs
117  * to access Records cached in an {@link Roo.data.Store} object.<br>
118  * <p>
119  * Constructors for this class are generated by passing an Array of field definition objects to {@link #create}.
120  * Instances are usually only created by {@link Roo.data.Reader} implementations when processing unformatted data
121  * objects.<br>
122  * <p>
123  * Record objects generated by this constructor inherit all the methods of Roo.data.Record listed below.
124  * @constructor
125  * This constructor should not be used to create Record objects. Instead, use the constructor generated by
126  * {@link #create}. The parameters are the same.
127  * @param {Array} data An associative Array of data values keyed by the field name.
128  * @param {Object} id (Optional) The id of the record. This id should be unique, and is used by the
129  * {@link Roo.data.Store} object which owns the Record to index its collection of Records. If
130  * not specified an integer id is generated.
131  */
132 Roo.data.Record = function(data, id){
133     this.id = (id || id === 0) ? id : ++Roo.data.Record.AUTO_ID;
134     this.data = data;
135 };
136
137 /**
138  * Generate a constructor for a specific record layout.
139  * @param {Array} o An Array of field definition objects which specify field names, and optionally,
140  * data types, and a mapping for an {@link Roo.data.Reader} to extract the field's value from a data object.
141  * Each field definition object may contain the following properties: <ul>
142  * <li><b>name</b> : String<p style="margin-left:1em">The name by which the field is referenced within the Record. This is referenced by,
143  * for example the <em>dataIndex</em> property in column definition objects passed to {@link Roo.grid.ColumnModel}</p></li>
144  * <li><b>mapping</b> : String<p style="margin-left:1em">(Optional) A path specification for use by the {@link Roo.data.Reader} implementation
145  * that is creating the Record to access the data value from the data object. If an {@link Roo.data.JsonReader}
146  * is being used, then this is a string containing the javascript expression to reference the data relative to 
147  * the record item's root. If an {@link Roo.data.XmlReader} is being used, this is an {@link Roo.DomQuery} path
148  * to the data item relative to the record element. If the mapping expression is the same as the field name,
149  * this may be omitted.</p></li>
150  * <li><b>type</b> : String<p style="margin-left:1em">(Optional) The data type for conversion to displayable value. Possible values are
151  * <ul><li>auto (Default, implies no conversion)</li>
152  * <li>string</li>
153  * <li>int</li>
154  * <li>float</li>
155  * <li>boolean</li>
156  * <li>date</li></ul></p></li>
157  * <li><b>sortType</b> : Mixed<p style="margin-left:1em">(Optional) A member of {@link Roo.data.SortTypes}.</p></li>
158  * <li><b>sortDir</b> : String<p style="margin-left:1em">(Optional) Initial direction to sort. "ASC" or "DESC"</p></li>
159  * <li><b>convert</b> : Function<p style="margin-left:1em">(Optional) A function which converts the value provided
160  * by the Reader into an object that will be stored in the Record. It is passed the
161  * following parameters:<ul>
162  * <li><b>v</b> : Mixed<p style="margin-left:1em">The data value as read by the Reader.</p></li>
163  * </ul></p></li>
164  * <li><b>dateFormat</b> : String<p style="margin-left:1em">(Optional) A format String for the Date.parseDate function.</p></li>
165  * </ul>
166  * <br>usage:<br><pre><code>
167 var TopicRecord = Roo.data.Record.create(
168     {name: 'title', mapping: 'topic_title'},
169     {name: 'author', mapping: 'username'},
170     {name: 'totalPosts', mapping: 'topic_replies', type: 'int'},
171     {name: 'lastPost', mapping: 'post_time', type: 'date'},
172     {name: 'lastPoster', mapping: 'user2'},
173     {name: 'excerpt', mapping: 'post_text'}
174 );
175
176 var myNewRecord = new TopicRecord({
177     title: 'Do my job please',
178     author: 'noobie',
179     totalPosts: 1,
180     lastPost: new Date(),
181     lastPoster: 'Animal',
182     excerpt: 'No way dude!'
183 });
184 myStore.add(myNewRecord);
185 </code></pre>
186  * @method create
187  * @static
188  */
189 Roo.data.Record.create = function(o){
190     var f = function(){
191         f.superclass.constructor.apply(this, arguments);
192     };
193     Roo.extend(f, Roo.data.Record);
194     var p = f.prototype;
195     p.fields = new Roo.util.MixedCollection(false, function(field){
196         return field.name;
197     });
198     for(var i = 0, len = o.length; i < len; i++){
199         p.fields.add(new Roo.data.Field(o[i]));
200     }
201     f.getField = function(name){
202         return p.fields.get(name);  
203     };
204     return f;
205 };
206
207 Roo.data.Record.AUTO_ID = 1000;
208 Roo.data.Record.EDIT = 'edit';
209 Roo.data.Record.REJECT = 'reject';
210 Roo.data.Record.COMMIT = 'commit';
211
212 Roo.data.Record.prototype = {
213     /**
214      * Readonly flag - true if this record has been modified.
215      * @type Boolean
216      */
217     dirty : false,
218     editing : false,
219     error: null,
220     modified: null,
221
222     // private
223     join : function(store){
224         this.store = store;
225     },
226
227     /**
228      * Set the named field to the specified value.
229      * @param {String} name The name of the field to set.
230      * @param {Object} value The value to set the field to.
231      */
232     set : function(name, value){
233         if(this.data[name] == value){
234             return;
235         }
236         this.dirty = true;
237         if(!this.modified){
238             this.modified = {};
239         }
240         if(typeof this.modified[name] == 'undefined'){
241             this.modified[name] = this.data[name];
242         }
243         this.data[name] = value;
244         if(!this.editing && this.store){
245             this.store.afterEdit(this);
246         }       
247     },
248
249     /**
250      * Get the value of the named field.
251      * @param {String} name The name of the field to get the value of.
252      * @return {Object} The value of the field.
253      */
254     get : function(name){
255         return this.data[name]; 
256     },
257
258     // private
259     beginEdit : function(){
260         this.editing = true;
261         this.modified = {}; 
262     },
263
264     // private
265     cancelEdit : function(){
266         this.editing = false;
267         delete this.modified;
268     },
269
270     // private
271     endEdit : function(){
272         this.editing = false;
273         if(this.dirty && this.store){
274             this.store.afterEdit(this);
275         }
276     },
277
278     /**
279      * Usually called by the {@link Roo.data.Store} which owns the Record.
280      * Rejects all changes made to the Record since either creation, or the last commit operation.
281      * Modified fields are reverted to their original values.
282      * <p>
283      * Developers should subscribe to the {@link Roo.data.Store#update} event to have their code notified
284      * of reject operations.
285      */
286     reject : function(){
287         var m = this.modified;
288         for(var n in m){
289             if(typeof m[n] != "function"){
290                 this.data[n] = m[n];
291             }
292         }
293         this.dirty = false;
294         delete this.modified;
295         this.editing = false;
296         if(this.store){
297             this.store.afterReject(this);
298         }
299     },
300
301     /**
302      * Usually called by the {@link Roo.data.Store} which owns the Record.
303      * Commits all changes made to the Record since either creation, or the last commit operation.
304      * <p>
305      * Developers should subscribe to the {@link Roo.data.Store#update} event to have their code notified
306      * of commit operations.
307      */
308     commit : function(){
309         this.dirty = false;
310         delete this.modified;
311         this.editing = false;
312         if(this.store){
313             this.store.afterCommit(this);
314         }
315     },
316
317     // private
318     hasError : function(){
319         return this.error != null;
320     },
321
322     // private
323     clearError : function(){
324         this.error = null;
325     },
326
327     /**
328      * Creates a copy of this record.
329      * @param {String} id (optional) A new record id if you don't want to use this record's id
330      * @return {Record}
331      */
332     copy : function(newId) {
333         return new this.constructor(Roo.apply({}, this.data), newId || this.id);
334     }
335 };/*
336  * Based on:
337  * Ext JS Library 1.1.1
338  * Copyright(c) 2006-2007, Ext JS, LLC.
339  *
340  * Originally Released Under LGPL - original licence link has changed is not relivant.
341  *
342  * Fork - LGPL
343  * <script type="text/javascript">
344  */
345
346
347
348 /**
349  * @class Roo.data.Store
350  * @extends Roo.util.Observable
351  * The Store class encapsulates a client side cache of {@link Roo.data.Record} objects which provide input data
352  * for widgets such as the Roo.grid.Grid, or the Roo.form.ComboBox.<br>
353  * <p>
354  * A Store object uses an implementation of {@link Roo.data.DataProxy} to access a data object unless you call loadData() directly and pass in your data. The Store object
355  * has no knowledge of the format of the data returned by the Proxy.<br>
356  * <p>
357  * A Store object uses its configured implementation of {@link Roo.data.DataReader} to create {@link Roo.data.Record}
358  * instances from the data object. These records are cached and made available through accessor functions.
359  * @constructor
360  * Creates a new Store.
361  * @param {Object} config A config object containing the objects needed for the Store to access data,
362  * and read the data into Records.
363  */
364 Roo.data.Store = function(config){
365     this.data = new Roo.util.MixedCollection(false);
366     this.data.getKey = function(o){
367         return o.id;
368     };
369     this.baseParams = {};
370     // private
371     this.paramNames = {
372         "start" : "start",
373         "limit" : "limit",
374         "sort" : "sort",
375         "dir" : "dir",
376         "multisort" : "_multisort"
377     };
378
379     if(config && config.data){
380         this.inlineData = config.data;
381         delete config.data;
382     }
383
384     Roo.apply(this, config);
385     
386     if(this.reader){ // reader passed
387         this.reader = Roo.factory(this.reader, Roo.data);
388         this.reader.xmodule = this.xmodule || false;
389         if(!this.recordType){
390             this.recordType = this.reader.recordType;
391         }
392         if(this.reader.onMetaChange){
393             this.reader.onMetaChange = this.onMetaChange.createDelegate(this);
394         }
395     }
396
397     if(this.recordType){
398         this.fields = this.recordType.prototype.fields;
399     }
400     this.modified = [];
401
402     this.addEvents({
403         /**
404          * @event datachanged
405          * Fires when the data cache has changed, and a widget which is using this Store
406          * as a Record cache should refresh its view.
407          * @param {Store} this
408          */
409         datachanged : true,
410         /**
411          * @event metachange
412          * Fires when this store's reader provides new metadata (fields). This is currently only support for JsonReaders.
413          * @param {Store} this
414          * @param {Object} meta The JSON metadata
415          */
416         metachange : true,
417         /**
418          * @event add
419          * Fires when Records have been added to the Store
420          * @param {Store} this
421          * @param {Roo.data.Record[]} records The array of Records added
422          * @param {Number} index The index at which the record(s) were added
423          */
424         add : true,
425         /**
426          * @event remove
427          * Fires when a Record has been removed from the Store
428          * @param {Store} this
429          * @param {Roo.data.Record} record The Record that was removed
430          * @param {Number} index The index at which the record was removed
431          */
432         remove : true,
433         /**
434          * @event update
435          * Fires when a Record has been updated
436          * @param {Store} this
437          * @param {Roo.data.Record} record The Record that was updated
438          * @param {String} operation The update operation being performed.  Value may be one of:
439          * <pre><code>
440  Roo.data.Record.EDIT
441  Roo.data.Record.REJECT
442  Roo.data.Record.COMMIT
443          * </code></pre>
444          */
445         update : true,
446         /**
447          * @event clear
448          * Fires when the data cache has been cleared.
449          * @param {Store} this
450          */
451         clear : true,
452         /**
453          * @event beforeload
454          * Fires before a request is made for a new data object.  If the beforeload handler returns false
455          * the load action will be canceled.
456          * @param {Store} this
457          * @param {Object} options The loading options that were specified (see {@link #load} for details)
458          */
459         beforeload : true,
460         /**
461          * @event beforeloadadd
462          * Fires after a new set of Records has been loaded.
463          * @param {Store} this
464          * @param {Roo.data.Record[]} records The Records that were loaded
465          * @param {Object} options The loading options that were specified (see {@link #load} for details)
466          */
467         beforeloadadd : true,
468         /**
469          * @event load
470          * Fires after a new set of Records has been loaded, before they are added to the store.
471          * @param {Store} this
472          * @param {Roo.data.Record[]} records The Records that were loaded
473          * @param {Object} options The loading options that were specified (see {@link #load} for details)
474          * @params {Object} return from reader
475          */
476         load : true,
477         /**
478          * @event loadexception
479          * Fires if an exception occurs in the Proxy during loading.
480          * Called with the signature of the Proxy's "loadexception" event.
481          * If you return Json { data: [] , success: false, .... } then this will be thrown with the following args
482          * 
483          * @param {Proxy} 
484          * @param {Object} return from JsonData.reader() - success, totalRecords, records
485          * @param {Object} load options 
486          * @param {Object} jsonData from your request (normally this contains the Exception)
487          */
488         loadexception : true
489     });
490     
491     if(this.proxy){
492         this.proxy = Roo.factory(this.proxy, Roo.data);
493         this.proxy.xmodule = this.xmodule || false;
494         this.relayEvents(this.proxy,  ["loadexception"]);
495     }
496     this.sortToggle = {};
497     this.sortOrder = []; // array of order of sorting - updated by grid if multisort is enabled.
498
499     Roo.data.Store.superclass.constructor.call(this);
500
501     if(this.inlineData){
502         this.loadData(this.inlineData);
503         delete this.inlineData;
504     }
505 };
506
507 Roo.extend(Roo.data.Store, Roo.util.Observable, {
508      /**
509     * @cfg {boolean} isLocal   flag if data is locally available (and can be always looked up
510     * without a remote query - used by combo/forms at present.
511     */
512     
513     /**
514     * @cfg {Roo.data.DataProxy} proxy [required] The Proxy object which provides access to a data object.
515     */
516     /**
517     * @cfg {Array} data Inline data to be loaded when the store is initialized.
518     */
519     /**
520     * @cfg {Roo.data.DataReader} reader [required]  The Reader object which processes the data object and returns
521     * an Array of Roo.data.record objects which are cached keyed by their <em>id</em> property.
522     */
523     /**
524     * @cfg {Object} baseParams An object containing properties which are to be sent as parameters
525     * on any HTTP request
526     */
527     /**
528     * @cfg {Object} sortInfo A config object in the format: {field: "fieldName", direction: "ASC|DESC"}
529     */
530     /**
531     * @cfg {Boolean} multiSort enable multi column sorting (sort is based on the order of columns, remote only at present)
532     */
533     multiSort: false,
534     /**
535     * @cfg {boolean} remoteSort True if sorting is to be handled by requesting the Proxy to provide a refreshed
536     * version of the data object in sorted order, as opposed to sorting the Record cache in place (defaults to false).
537     */
538     remoteSort : false,
539
540     /**
541     * @cfg {boolean} pruneModifiedRecords True to clear all modified record information each time the store is
542      * loaded or when a record is removed. (defaults to false).
543     */
544     pruneModifiedRecords : false,
545
546     // private
547     lastOptions : null,
548
549     /**
550      * Add Records to the Store and fires the add event.
551      * @param {Roo.data.Record[]} records An Array of Roo.data.Record objects to add to the cache.
552      */
553     add : function(records){
554         records = [].concat(records);
555         for(var i = 0, len = records.length; i < len; i++){
556             records[i].join(this);
557         }
558         var index = this.data.length;
559         this.data.addAll(records);
560         this.fireEvent("add", this, records, index);
561     },
562
563     /**
564      * Remove a Record from the Store and fires the remove event.
565      * @param {Ext.data.Record} record The Roo.data.Record object to remove from the cache.
566      */
567     remove : function(record){
568         var index = this.data.indexOf(record);
569         this.data.removeAt(index);
570  
571         if(this.pruneModifiedRecords){
572             this.modified.remove(record);
573         }
574         this.fireEvent("remove", this, record, index);
575     },
576
577     /**
578      * Remove all Records from the Store and fires the clear event.
579      */
580     removeAll : function(){
581         this.data.clear();
582         if(this.pruneModifiedRecords){
583             this.modified = [];
584         }
585         this.fireEvent("clear", this);
586     },
587
588     /**
589      * Inserts Records to the Store at the given index and fires the add event.
590      * @param {Number} index The start index at which to insert the passed Records.
591      * @param {Roo.data.Record[]} records An Array of Roo.data.Record objects to add to the cache.
592      */
593     insert : function(index, records){
594         records = [].concat(records);
595         for(var i = 0, len = records.length; i < len; i++){
596             this.data.insert(index, records[i]);
597             records[i].join(this);
598         }
599         this.fireEvent("add", this, records, index);
600     },
601
602     /**
603      * Get the index within the cache of the passed Record.
604      * @param {Roo.data.Record} record The Roo.data.Record object to to find.
605      * @return {Number} The index of the passed Record. Returns -1 if not found.
606      */
607     indexOf : function(record){
608         return this.data.indexOf(record);
609     },
610
611     /**
612      * Get the index within the cache of the Record with the passed id.
613      * @param {String} id The id of the Record to find.
614      * @return {Number} The index of the Record. Returns -1 if not found.
615      */
616     indexOfId : function(id){
617         return this.data.indexOfKey(id);
618     },
619
620     /**
621      * Get the Record with the specified id.
622      * @param {String} id The id of the Record to find.
623      * @return {Roo.data.Record} The Record with the passed id. Returns undefined if not found.
624      */
625     getById : function(id){
626         return this.data.key(id);
627     },
628
629     /**
630      * Get the Record at the specified index.
631      * @param {Number} index The index of the Record to find.
632      * @return {Roo.data.Record} The Record at the passed index. Returns undefined if not found.
633      */
634     getAt : function(index){
635         return this.data.itemAt(index);
636     },
637
638     /**
639      * Returns a range of Records between specified indices.
640      * @param {Number} startIndex (optional) The starting index (defaults to 0)
641      * @param {Number} endIndex (optional) The ending index (defaults to the last Record in the Store)
642      * @return {Roo.data.Record[]} An array of Records
643      */
644     getRange : function(start, end){
645         return this.data.getRange(start, end);
646     },
647
648     // private
649     storeOptions : function(o){
650         o = Roo.apply({}, o);
651         delete o.callback;
652         delete o.scope;
653         this.lastOptions = o;
654     },
655
656     /**
657      * Loads the Record cache from the configured Proxy using the configured Reader.
658      * <p>
659      * If using remote paging, then the first load call must specify the <em>start</em>
660      * and <em>limit</em> properties in the options.params property to establish the initial
661      * position within the dataset, and the number of Records to cache on each read from the Proxy.
662      * <p>
663      * <strong>It is important to note that for remote data sources, loading is asynchronous,
664      * and this call will return before the new data has been loaded. Perform any post-processing
665      * in a callback function, or in a "load" event handler.</strong>
666      * <p>
667      * @param {Object} options An object containing properties which control loading options:<ul>
668      * <li>params {Object} An object containing properties to pass as HTTP parameters to a remote data source.</li>
669      * <li>params.data {Object} if you are using a MemoryProxy / JsonReader, use this as the data to load stuff..
670      * <pre>
671                 {
672                     data : data,  // array of key=>value data like JsonReader
673                     total : data.length,
674                     success : true
675                     
676                 }
677         </pre>
678             }.</li>
679      * <li>callback {Function} A function to be called after the Records have been loaded. The callback is
680      * passed the following arguments:<ul>
681      * <li>r : Roo.data.Record[]</li>
682      * <li>options: Options object from the load call</li>
683      * <li>success: Boolean success indicator</li></ul></li>
684      * <li>scope {Object} Scope with which to call the callback (defaults to the Store object)</li>
685      * <li>add {Boolean} indicator to append loaded records rather than replace the current cache.</li>
686      * </ul>
687      */
688     load : function(options){
689         options = options || {};
690         if(this.fireEvent("beforeload", this, options) !== false){
691             this.storeOptions(options);
692             var p = Roo.apply(options.params || {}, this.baseParams);
693             // if meta was not loaded from remote source.. try requesting it.
694             if (!this.reader.metaFromRemote) {
695                 p._requestMeta = 1;
696             }
697             if(this.sortInfo && this.remoteSort){
698                 var pn = this.paramNames;
699                 p[pn["sort"]] = this.sortInfo.field;
700                 p[pn["dir"]] = this.sortInfo.direction;
701             }
702             if (this.multiSort) {
703                 var pn = this.paramNames;
704                 p[pn["multisort"]] = Roo.encode( { sort : this.sortToggle, order: this.sortOrder });
705             }
706             
707             this.proxy.load(p, this.reader, this.loadRecords, this, options);
708         }
709     },
710
711     /**
712      * Reloads the Record cache from the configured Proxy using the configured Reader and
713      * the options from the last load operation performed.
714      * @param {Object} options (optional) An object containing properties which may override the options
715      * used in the last load operation. See {@link #load} for details (defaults to null, in which case
716      * the most recently used options are reused).
717      */
718     reload : function(options){
719         this.load(Roo.applyIf(options||{}, this.lastOptions));
720     },
721
722     // private
723     // Called as a callback by the Reader during a load operation.
724     loadRecords : function(o, options, success){
725          
726         if(!o){
727             if(success !== false){
728                 this.fireEvent("load", this, [], options, o);
729             }
730             if(options.callback){
731                 options.callback.call(options.scope || this, [], options, false);
732             }
733             return;
734         }
735         // if data returned failure - throw an exception.
736         if (o.success === false) {
737             // show a message if no listener is registered.
738             if (!this.hasListener('loadexception') && typeof(o.raw.errorMsg) != 'undefined') {
739                     Roo.MessageBox.alert("Error loading",o.raw.errorMsg);
740             }
741             // loadmask wil be hooked into this..
742             this.fireEvent("loadexception", this, o, options, o.raw.errorMsg);
743             return;
744         }
745         var r = o.records, t = o.totalRecords || r.length;
746         
747         this.fireEvent("beforeloadadd", this, r, options, o);
748         
749         if(!options || options.add !== true){
750             if(this.pruneModifiedRecords){
751                 this.modified = [];
752             }
753             for(var i = 0, len = r.length; i < len; i++){
754                 r[i].join(this);
755             }
756             if(this.snapshot){
757                 this.data = this.snapshot;
758                 delete this.snapshot;
759             }
760             this.data.clear();
761             this.data.addAll(r);
762             this.totalLength = t;
763             this.applySort();
764             this.fireEvent("datachanged", this);
765         }else{
766             this.totalLength = Math.max(t, this.data.length+r.length);
767             this.add(r);
768         }
769         
770         if(this.parent && !Roo.isIOS && !this.useNativeIOS && this.parent.emptyTitle.length) {
771                 
772             var e = new Roo.data.Record({});
773
774             e.set(this.parent.displayField, this.parent.emptyTitle);
775             e.set(this.parent.valueField, '');
776
777             this.insert(0, e);
778         }
779             
780         this.fireEvent("load", this, r, options, o);
781         if(options.callback){
782             options.callback.call(options.scope || this, r, options, true);
783         }
784     },
785
786
787     /**
788      * Loads data from a passed data block. A Reader which understands the format of the data
789      * must have been configured in the constructor.
790      * @param {Object} data The data block from which to read the Records.  The format of the data expected
791      * is dependent on the type of Reader that is configured and should correspond to that Reader's readRecords parameter.
792      * @param {Boolean} append (Optional) True to append the new Records rather than replace the existing cache.
793      */
794     loadData : function(o, append){
795         var r = this.reader.readRecords(o);
796         this.loadRecords(r, {add: append}, true);
797     },
798     
799      /**
800      * using 'cn' the nested child reader read the child array into it's child stores.
801      * @param {Object} rec The record with a 'children array
802      */
803     loadDataFromChildren : function(rec)
804     {
805         this.loadData(this.reader.toLoadData(rec));
806     },
807     
808
809     /**
810      * Gets the number of cached records.
811      * <p>
812      * <em>If using paging, this may not be the total size of the dataset. If the data object
813      * used by the Reader contains the dataset size, then the getTotalCount() function returns
814      * the data set size</em>
815      */
816     getCount : function(){
817         return this.data.length || 0;
818     },
819
820     /**
821      * Gets the total number of records in the dataset as returned by the server.
822      * <p>
823      * <em>If using paging, for this to be accurate, the data object used by the Reader must contain
824      * the dataset size</em>
825      */
826     getTotalCount : function(){
827         return this.totalLength || 0;
828     },
829
830     /**
831      * Returns the sort state of the Store as an object with two properties:
832      * <pre><code>
833  field {String} The name of the field by which the Records are sorted
834  direction {String} The sort order, "ASC" or "DESC"
835      * </code></pre>
836      */
837     getSortState : function(){
838         return this.sortInfo;
839     },
840
841     // private
842     applySort : function(){
843         if(this.sortInfo && !this.remoteSort){
844             var s = this.sortInfo, f = s.field;
845             var st = this.fields.get(f).sortType;
846             var fn = function(r1, r2){
847                 var v1 = st(r1.data[f]), v2 = st(r2.data[f]);
848                 return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
849             };
850             this.data.sort(s.direction, fn);
851             if(this.snapshot && this.snapshot != this.data){
852                 this.snapshot.sort(s.direction, fn);
853             }
854         }
855     },
856
857     /**
858      * Sets the default sort column and order to be used by the next load operation.
859      * @param {String} fieldName The name of the field to sort by.
860      * @param {String} dir (optional) The sort order, "ASC" or "DESC" (defaults to "ASC")
861      */
862     setDefaultSort : function(field, dir){
863         this.sortInfo = {field: field, direction: dir ? dir.toUpperCase() : "ASC"};
864     },
865
866     /**
867      * Sort the Records.
868      * If remote sorting is used, the sort is performed on the server, and the cache is
869      * reloaded. If local sorting is used, the cache is sorted internally.
870      * @param {String} fieldName The name of the field to sort by.
871      * @param {String} dir (optional) The sort order, "ASC" or "DESC" (defaults to "ASC")
872      */
873     sort : function(fieldName, dir){
874         var f = this.fields.get(fieldName);
875         if(!dir){
876             this.sortToggle[f.name] = this.sortToggle[f.name] || f.sortDir;
877             
878             if(this.multiSort || (this.sortInfo && this.sortInfo.field == f.name) ){ // toggle sort dir
879                 dir = (this.sortToggle[f.name] || "ASC").toggle("ASC", "DESC");
880             }else{
881                 dir = f.sortDir;
882             }
883         }
884         this.sortToggle[f.name] = dir;
885         this.sortInfo = {field: f.name, direction: dir};
886         if(!this.remoteSort){
887             this.applySort();
888             this.fireEvent("datachanged", this);
889         }else{
890             this.load(this.lastOptions);
891         }
892     },
893
894     /**
895      * Calls the specified function for each of the Records in the cache.
896      * @param {Function} fn The function to call. The Record is passed as the first parameter.
897      * Returning <em>false</em> aborts and exits the iteration.
898      * @param {Object} scope (optional) The scope in which to call the function (defaults to the Record).
899      */
900     each : function(fn, scope){
901         this.data.each(fn, scope);
902     },
903
904     /**
905      * Gets all records modified since the last commit.  Modified records are persisted across load operations
906      * (e.g., during paging).
907      * @return {Roo.data.Record[]} An array of Records containing outstanding modifications.
908      */
909     getModifiedRecords : function(){
910         return this.modified;
911     },
912
913     // private
914     createFilterFn : function(property, value, anyMatch){
915         if(!value.exec){ // not a regex
916             value = String(value);
917             if(value.length == 0){
918                 return false;
919             }
920             value = new RegExp((anyMatch === true ? '' : '^') + Roo.escapeRe(value), "i");
921         }
922         return function(r){
923             return value.test(r.data[property]);
924         };
925     },
926
927     /**
928      * Sums the value of <i>property</i> for each record between start and end and returns the result.
929      * @param {String} property A field on your records
930      * @param {Number} start The record index to start at (defaults to 0)
931      * @param {Number} end The last record index to include (defaults to length - 1)
932      * @return {Number} The sum
933      */
934     sum : function(property, start, end){
935         var rs = this.data.items, v = 0;
936         start = start || 0;
937         end = (end || end === 0) ? end : rs.length-1;
938
939         for(var i = start; i <= end; i++){
940             v += (rs[i].data[property] || 0);
941         }
942         return v;
943     },
944
945     /**
946      * Filter the records by a specified property.
947      * @param {String} field A field on your records
948      * @param {String/RegExp} value Either a string that the field
949      * should start with or a RegExp to test against the field
950      * @param {Boolean} anyMatch True to match any part not just the beginning
951      */
952     filter : function(property, value, anyMatch){
953         var fn = this.createFilterFn(property, value, anyMatch);
954         return fn ? this.filterBy(fn) : this.clearFilter();
955     },
956
957     /**
958      * Filter by a function. The specified function will be called with each
959      * record in this data source. If the function returns true the record is included,
960      * otherwise it is filtered.
961      * @param {Function} fn The function to be called, it will receive 2 args (record, id)
962      * @param {Object} scope (optional) The scope of the function (defaults to this)
963      */
964     filterBy : function(fn, scope){
965         this.snapshot = this.snapshot || this.data;
966         this.data = this.queryBy(fn, scope||this);
967         this.fireEvent("datachanged", this);
968     },
969
970     /**
971      * Query the records by a specified property.
972      * @param {String} field A field on your records
973      * @param {String/RegExp} value Either a string that the field
974      * should start with or a RegExp to test against the field
975      * @param {Boolean} anyMatch True to match any part not just the beginning
976      * @return {MixedCollection} Returns an Roo.util.MixedCollection of the matched records
977      */
978     query : function(property, value, anyMatch){
979         var fn = this.createFilterFn(property, value, anyMatch);
980         return fn ? this.queryBy(fn) : this.data.clone();
981     },
982
983     /**
984      * Query by a function. The specified function will be called with each
985      * record in this data source. If the function returns true the record is included
986      * in the results.
987      * @param {Function} fn The function to be called, it will receive 2 args (record, id)
988      * @param {Object} scope (optional) The scope of the function (defaults to this)
989       @return {MixedCollection} Returns an Roo.util.MixedCollection of the matched records
990      **/
991     queryBy : function(fn, scope){
992         var data = this.snapshot || this.data;
993         return data.filterBy(fn, scope||this);
994     },
995
996     /**
997      * Collects unique values for a particular dataIndex from this store.
998      * @param {String} dataIndex The property to collect
999      * @param {Boolean} allowNull (optional) Pass true to allow null, undefined or empty string values
1000      * @param {Boolean} bypassFilter (optional) Pass true to collect from all records, even ones which are filtered
1001      * @return {Array} An array of the unique values
1002      **/
1003     collect : function(dataIndex, allowNull, bypassFilter){
1004         var d = (bypassFilter === true && this.snapshot) ?
1005                 this.snapshot.items : this.data.items;
1006         var v, sv, r = [], l = {};
1007         for(var i = 0, len = d.length; i < len; i++){
1008             v = d[i].data[dataIndex];
1009             sv = String(v);
1010             if((allowNull || !Roo.isEmpty(v)) && !l[sv]){
1011                 l[sv] = true;
1012                 r[r.length] = v;
1013             }
1014         }
1015         return r;
1016     },
1017
1018     /**
1019      * Revert to a view of the Record cache with no filtering applied.
1020      * @param {Boolean} suppressEvent If true the filter is cleared silently without notifying listeners
1021      */
1022     clearFilter : function(suppressEvent){
1023         if(this.snapshot && this.snapshot != this.data){
1024             this.data = this.snapshot;
1025             delete this.snapshot;
1026             if(suppressEvent !== true){
1027                 this.fireEvent("datachanged", this);
1028             }
1029         }
1030     },
1031
1032     // private
1033     afterEdit : function(record){
1034         if(this.modified.indexOf(record) == -1){
1035             this.modified.push(record);
1036         }
1037         this.fireEvent("update", this, record, Roo.data.Record.EDIT);
1038     },
1039     
1040     // private
1041     afterReject : function(record){
1042         this.modified.remove(record);
1043         this.fireEvent("update", this, record, Roo.data.Record.REJECT);
1044     },
1045
1046     // private
1047     afterCommit : function(record){
1048         this.modified.remove(record);
1049         this.fireEvent("update", this, record, Roo.data.Record.COMMIT);
1050     },
1051
1052     /**
1053      * Commit all Records with outstanding changes. To handle updates for changes, subscribe to the
1054      * Store's "update" event, and perform updating when the third parameter is Roo.data.Record.COMMIT.
1055      */
1056     commitChanges : function(){
1057         var m = this.modified.slice(0);
1058         this.modified = [];
1059         for(var i = 0, len = m.length; i < len; i++){
1060             m[i].commit();
1061         }
1062     },
1063
1064     /**
1065      * Cancel outstanding changes on all changed records.
1066      */
1067     rejectChanges : function(){
1068         var m = this.modified.slice(0);
1069         this.modified = [];
1070         for(var i = 0, len = m.length; i < len; i++){
1071             m[i].reject();
1072         }
1073     },
1074
1075     onMetaChange : function(meta, rtype, o){
1076         this.recordType = rtype;
1077         this.fields = rtype.prototype.fields;
1078         delete this.snapshot;
1079         this.sortInfo = meta.sortInfo || this.sortInfo;
1080         this.modified = [];
1081         this.fireEvent('metachange', this, this.reader.meta);
1082     },
1083     
1084     moveIndex : function(data, type)
1085     {
1086         var index = this.indexOf(data);
1087         
1088         var newIndex = index + type;
1089         
1090         this.remove(data);
1091         
1092         this.insert(newIndex, data);
1093         
1094     }
1095 });/*
1096  * Based on:
1097  * Ext JS Library 1.1.1
1098  * Copyright(c) 2006-2007, Ext JS, LLC.
1099  *
1100  * Originally Released Under LGPL - original licence link has changed is not relivant.
1101  *
1102  * Fork - LGPL
1103  * <script type="text/javascript">
1104  */
1105
1106 /**
1107  * @class Roo.data.SimpleStore
1108  * @extends Roo.data.Store
1109  * Small helper class to make creating Stores from Array data easier.
1110  * @cfg {Number} id The array index of the record id. Leave blank to auto generate ids.
1111  * @cfg {Array} fields An array of field definition objects, or field name strings.
1112  * @cfg {Object} an existing reader (eg. copied from another store)
1113  * @cfg {Array} data The multi-dimensional array of data
1114  * @cfg {Roo.data.DataProxy} proxy [not-required]  
1115  * @cfg {Roo.data.Reader} reader  [not-required] 
1116  * @constructor
1117  * @param {Object} config
1118  */
1119 Roo.data.SimpleStore = function(config)
1120 {
1121     Roo.data.SimpleStore.superclass.constructor.call(this, {
1122         isLocal : true,
1123         reader: typeof(config.reader) != 'undefined' ? config.reader : new Roo.data.ArrayReader({
1124                 id: config.id
1125             },
1126             Roo.data.Record.create(config.fields)
1127         ),
1128         proxy : new Roo.data.MemoryProxy(config.data)
1129     });
1130     this.load();
1131 };
1132 Roo.extend(Roo.data.SimpleStore, Roo.data.Store);/*
1133  * Based on:
1134  * Ext JS Library 1.1.1
1135  * Copyright(c) 2006-2007, Ext JS, LLC.
1136  *
1137  * Originally Released Under LGPL - original licence link has changed is not relivant.
1138  *
1139  * Fork - LGPL
1140  * <script type="text/javascript">
1141  */
1142
1143 /**
1144 /**
1145  * @extends Roo.data.Store
1146  * @class Roo.data.JsonStore
1147  * Small helper class to make creating Stores for JSON data easier. <br/>
1148 <pre><code>
1149 var store = new Roo.data.JsonStore({
1150     url: 'get-images.php',
1151     root: 'images',
1152     fields: ['name', 'url', {name:'size', type: 'float'}, {name:'lastmod', type:'date'}]
1153 });
1154 </code></pre>
1155  * <b>Note: Although they are not listed, this class inherits all of the config options of Store,
1156  * JsonReader and HttpProxy (unless inline data is provided).</b>
1157  * @cfg {Array} fields An array of field definition objects, or field name strings.
1158  * @constructor
1159  * @param {Object} config
1160  */
1161 Roo.data.JsonStore = function(c){
1162     Roo.data.JsonStore.superclass.constructor.call(this, Roo.apply(c, {
1163         proxy: !c.data ? new Roo.data.HttpProxy({url: c.url}) : undefined,
1164         reader: new Roo.data.JsonReader(c, c.fields)
1165     }));
1166 };
1167 Roo.extend(Roo.data.JsonStore, Roo.data.Store);/*
1168  * Based on:
1169  * Ext JS Library 1.1.1
1170  * Copyright(c) 2006-2007, Ext JS, LLC.
1171  *
1172  * Originally Released Under LGPL - original licence link has changed is not relivant.
1173  *
1174  * Fork - LGPL
1175  * <script type="text/javascript">
1176  */
1177
1178  
1179 Roo.data.Field = function(config){
1180     if(typeof config == "string"){
1181         config = {name: config};
1182     }
1183     Roo.apply(this, config);
1184     
1185     if(!this.type){
1186         this.type = "auto";
1187     }
1188     
1189     var st = Roo.data.SortTypes;
1190     // named sortTypes are supported, here we look them up
1191     if(typeof this.sortType == "string"){
1192         this.sortType = st[this.sortType];
1193     }
1194     
1195     // set default sortType for strings and dates
1196     if(!this.sortType){
1197         switch(this.type){
1198             case "string":
1199                 this.sortType = st.asUCString;
1200                 break;
1201             case "date":
1202                 this.sortType = st.asDate;
1203                 break;
1204             default:
1205                 this.sortType = st.none;
1206         }
1207     }
1208
1209     // define once
1210     var stripRe = /[\$,%]/g;
1211
1212     // prebuilt conversion function for this field, instead of
1213     // switching every time we're reading a value
1214     if(!this.convert){
1215         var cv, dateFormat = this.dateFormat;
1216         switch(this.type){
1217             case "":
1218             case "auto":
1219             case undefined:
1220                 cv = function(v){ return v; };
1221                 break;
1222             case "string":
1223                 cv = function(v){ return (v === undefined || v === null) ? '' : String(v); };
1224                 break;
1225             case "int":
1226                 cv = function(v){
1227                     return v !== undefined && v !== null && v !== '' ?
1228                            parseInt(String(v).replace(stripRe, ""), 10) : '';
1229                     };
1230                 break;
1231             case "float":
1232                 cv = function(v){
1233                     return v !== undefined && v !== null && v !== '' ?
1234                            parseFloat(String(v).replace(stripRe, ""), 10) : ''; 
1235                     };
1236                 break;
1237             case "bool":
1238             case "boolean":
1239                 cv = function(v){ return v === true || v === "true" || v == 1; };
1240                 break;
1241             case "date":
1242                 cv = function(v){
1243                     if(!v){
1244                         return '';
1245                     }
1246                     if(v instanceof Date){
1247                         return v;
1248                     }
1249                     if(dateFormat){
1250                         if(dateFormat == "timestamp"){
1251                             return new Date(v*1000);
1252                         }
1253                         return Date.parseDate(v, dateFormat);
1254                     }
1255                     var parsed = Date.parse(v);
1256                     return parsed ? new Date(parsed) : null;
1257                 };
1258              break;
1259             
1260         }
1261         this.convert = cv;
1262     }
1263 };
1264
1265 Roo.data.Field.prototype = {
1266     dateFormat: null,
1267     defaultValue: "",
1268     mapping: null,
1269     sortType : null,
1270     sortDir : "ASC"
1271 };/*
1272  * Based on:
1273  * Ext JS Library 1.1.1
1274  * Copyright(c) 2006-2007, Ext JS, LLC.
1275  *
1276  * Originally Released Under LGPL - original licence link has changed is not relivant.
1277  *
1278  * Fork - LGPL
1279  * <script type="text/javascript">
1280  */
1281  
1282 // Base class for reading structured data from a data source.  This class is intended to be
1283 // extended (see ArrayReader, JsonReader and XmlReader) and should not be created directly.
1284
1285 /**
1286  * @class Roo.data.DataReader
1287  * @abstract
1288  * Base class for reading structured data from a data source.  This class is intended to be
1289  * extended (see {Roo.data.ArrayReader}, {Roo.data.JsonReader} and {Roo.data.XmlReader}) and should not be created directly.
1290  */
1291
1292 Roo.data.DataReader = function(meta, recordType){
1293     
1294     this.meta = meta;
1295     
1296     this.recordType = recordType instanceof Array ? 
1297         Roo.data.Record.create(recordType) : recordType;
1298 };
1299
1300 Roo.data.DataReader.prototype = {
1301     
1302     
1303     readerType : 'Data',
1304      /**
1305      * Create an empty record
1306      * @param {Object} data (optional) - overlay some values
1307      * @return {Roo.data.Record} record created.
1308      */
1309     newRow :  function(d) {
1310         var da =  {};
1311         this.recordType.prototype.fields.each(function(c) {
1312             switch( c.type) {
1313                 case 'int' : da[c.name] = 0; break;
1314                 case 'date' : da[c.name] = new Date(); break;
1315                 case 'float' : da[c.name] = 0.0; break;
1316                 case 'boolean' : da[c.name] = false; break;
1317                 default : da[c.name] = ""; break;
1318             }
1319             
1320         });
1321         return new this.recordType(Roo.apply(da, d));
1322     }
1323     
1324     
1325 };/*
1326  * Based on:
1327  * Ext JS Library 1.1.1
1328  * Copyright(c) 2006-2007, Ext JS, LLC.
1329  *
1330  * Originally Released Under LGPL - original licence link has changed is not relivant.
1331  *
1332  * Fork - LGPL
1333  * <script type="text/javascript">
1334  */
1335
1336 /**
1337  * @class Roo.data.DataProxy
1338  * @extends Roo.util.Observable
1339  * @abstract
1340  * This class is an abstract base class for implementations which provide retrieval of
1341  * unformatted data objects.<br>
1342  * <p>
1343  * DataProxy implementations are usually used in conjunction with an implementation of Roo.data.DataReader
1344  * (of the appropriate type which knows how to parse the data object) to provide a block of
1345  * {@link Roo.data.Records} to an {@link Roo.data.Store}.<br>
1346  * <p>
1347  * Custom implementations must implement the load method as described in
1348  * {@link Roo.data.HttpProxy#load}.
1349  */
1350 Roo.data.DataProxy = function(){
1351     this.addEvents({
1352         /**
1353          * @event beforeload
1354          * Fires before a network request is made to retrieve a data object.
1355          * @param {Object} This DataProxy object.
1356          * @param {Object} params The params parameter to the load function.
1357          */
1358         beforeload : true,
1359         /**
1360          * @event load
1361          * Fires before the load method's callback is called.
1362          * @param {Object} This DataProxy object.
1363          * @param {Object} o The data object.
1364          * @param {Object} arg The callback argument object passed to the load function.
1365          */
1366         load : true,
1367         /**
1368          * @event loadexception
1369          * Fires if an Exception occurs during data retrieval.
1370          * @param {Object} This DataProxy object.
1371          * @param {Object} o The data object.
1372          * @param {Object} arg The callback argument object passed to the load function.
1373          * @param {Object} e The Exception.
1374          */
1375         loadexception : true
1376     });
1377     Roo.data.DataProxy.superclass.constructor.call(this);
1378 };
1379
1380 Roo.extend(Roo.data.DataProxy, Roo.util.Observable);
1381
1382     /**
1383      * @cfg {void} listeners (Not available) Constructor blocks listeners from being set
1384      */
1385 /*
1386  * Based on:
1387  * Ext JS Library 1.1.1
1388  * Copyright(c) 2006-2007, Ext JS, LLC.
1389  *
1390  * Originally Released Under LGPL - original licence link has changed is not relivant.
1391  *
1392  * Fork - LGPL
1393  * <script type="text/javascript">
1394  */
1395 /**
1396  * @class Roo.data.MemoryProxy
1397  * @extends Roo.data.DataProxy
1398  * An implementation of Roo.data.DataProxy that simply passes the data specified in its constructor
1399  * to the Reader when its load method is called.
1400  * @constructor
1401  * @param {Object} config  A config object containing the objects needed for the Store to access data,
1402  */
1403 Roo.data.MemoryProxy = function(config){
1404     var data = config;
1405     if (typeof(config) != 'undefined' && typeof(config.data) != 'undefined') {
1406         data = config.data;
1407     }
1408     Roo.data.MemoryProxy.superclass.constructor.call(this);
1409     this.data = data;
1410 };
1411
1412 Roo.extend(Roo.data.MemoryProxy, Roo.data.DataProxy, {
1413     
1414     /**
1415      *  @cfg {Object} data The data object which the Reader uses to construct a block of Roo.data.Records.
1416      */
1417     /**
1418      * Load data from the requested source (in this case an in-memory
1419      * data object passed to the constructor), read the data object into
1420      * a block of Roo.data.Records using the passed Roo.data.DataReader implementation, and
1421      * process that block using the passed callback.
1422      * @param {Object} params This parameter is not used by the MemoryProxy class.
1423      * @param {Roo.data.DataReader} reader The Reader object which converts the data
1424      * object into a block of Roo.data.Records.
1425      * @param {Function} callback The function into which to pass the block of Roo.data.records.
1426      * The function must be passed <ul>
1427      * <li>The Record block object</li>
1428      * <li>The "arg" argument from the load function</li>
1429      * <li>A boolean success indicator</li>
1430      * </ul>
1431      * @param {Object} scope The scope in which to call the callback
1432      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
1433      */
1434     load : function(params, reader, callback, scope, arg){
1435         params = params || {};
1436         var result;
1437         try {
1438             result = reader.readRecords(params.data ? params.data :this.data);
1439         }catch(e){
1440             this.fireEvent("loadexception", this, arg, null, e);
1441             callback.call(scope, null, arg, false);
1442             return;
1443         }
1444         callback.call(scope, result, arg, true);
1445     },
1446     
1447     // private
1448     update : function(params, records){
1449         
1450     }
1451 });/*
1452  * Based on:
1453  * Ext JS Library 1.1.1
1454  * Copyright(c) 2006-2007, Ext JS, LLC.
1455  *
1456  * Originally Released Under LGPL - original licence link has changed is not relivant.
1457  *
1458  * Fork - LGPL
1459  * <script type="text/javascript">
1460  */
1461 /**
1462  * @class Roo.data.HttpProxy
1463  * @extends Roo.data.DataProxy
1464  * An implementation of {@link Roo.data.DataProxy} that reads a data object from an {@link Roo.data.Connection} object
1465  * configured to reference a certain URL.<br><br>
1466  * <p>
1467  * <em>Note that this class cannot be used to retrieve data from a domain other than the domain
1468  * from which the running page was served.<br><br>
1469  * <p>
1470  * For cross-domain access to remote data, use an {@link Roo.data.ScriptTagProxy}.</em><br><br>
1471  * <p>
1472  * Be aware that to enable the browser to parse an XML document, the server must set
1473  * the Content-Type header in the HTTP response to "text/xml".
1474  * @constructor
1475  * @param {Object} conn Connection config options to add to each request (e.g. {url: 'foo.php'} or
1476  * an {@link Roo.data.Connection} object.  If a Connection config is passed, the singleton {@link Roo.Ajax} object
1477  * will be used to make the request.
1478  */
1479 Roo.data.HttpProxy = function(conn){
1480     Roo.data.HttpProxy.superclass.constructor.call(this);
1481     // is conn a conn config or a real conn?
1482     this.conn = conn;
1483     this.useAjax = !conn || !conn.events;
1484   
1485 };
1486
1487 Roo.extend(Roo.data.HttpProxy, Roo.data.DataProxy, {
1488     // thse are take from connection...
1489     
1490     /**
1491      * @cfg {String} url (Optional) The default URL to be used for requests to the server. (defaults to undefined)
1492      */
1493     /**
1494      * @cfg {Object} extraParams (Optional) An object containing properties which are used as
1495      * extra parameters to each request made by this object. (defaults to undefined)
1496      */
1497     /**
1498      * @cfg {Object} defaultHeaders (Optional) An object containing request headers which are added
1499      *  to each request made by this object. (defaults to undefined)
1500      */
1501     /**
1502      * @cfg {String} method (Optional) The default HTTP method to be used for requests. (defaults to undefined; if not set but parms are present will use POST, otherwise GET)
1503      */
1504     /**
1505      * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
1506      */
1507      /**
1508      * @cfg {Boolean} autoAbort (Optional) Whether this request should abort any pending requests. (defaults to false)
1509      * @type Boolean
1510      */
1511   
1512
1513     /**
1514      * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
1515      * @type Boolean
1516      */
1517     /**
1518      * Return the {@link Roo.data.Connection} object being used by this Proxy.
1519      * @return {Connection} The Connection object. This object may be used to subscribe to events on
1520      * a finer-grained basis than the DataProxy events.
1521      */
1522     getConnection : function(){
1523         return this.useAjax ? Roo.Ajax : this.conn;
1524     },
1525
1526     /**
1527      * Load data from the configured {@link Roo.data.Connection}, read the data object into
1528      * a block of Roo.data.Records using the passed {@link Roo.data.DataReader} implementation, and
1529      * process that block using the passed callback.
1530      * @param {Object} params An object containing properties which are to be used as HTTP parameters
1531      * for the request to the remote server.
1532      * @param {Roo.data.DataReader} reader The Reader object which converts the data
1533      * object into a block of Roo.data.Records.
1534      * @param {Function} callback The function into which to pass the block of Roo.data.Records.
1535      * The function must be passed <ul>
1536      * <li>The Record block object</li>
1537      * <li>The "arg" argument from the load function</li>
1538      * <li>A boolean success indicator</li>
1539      * </ul>
1540      * @param {Object} scope The scope in which to call the callback
1541      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
1542      */
1543     load : function(params, reader, callback, scope, arg){
1544         if(this.fireEvent("beforeload", this, params) !== false){
1545             var  o = {
1546                 params : params || {},
1547                 request: {
1548                     callback : callback,
1549                     scope : scope,
1550                     arg : arg
1551                 },
1552                 reader: reader,
1553                 callback : this.loadResponse,
1554                 scope: this
1555             };
1556             if(this.useAjax){
1557                 Roo.applyIf(o, this.conn);
1558                 if(this.activeRequest){
1559                     Roo.Ajax.abort(this.activeRequest);
1560                 }
1561                 this.activeRequest = Roo.Ajax.request(o);
1562             }else{
1563                 this.conn.request(o);
1564             }
1565         }else{
1566             callback.call(scope||this, null, arg, false);
1567         }
1568     },
1569
1570     // private
1571     loadResponse : function(o, success, response){
1572         delete this.activeRequest;
1573         if(!success){
1574             this.fireEvent("loadexception", this, o, response);
1575             o.request.callback.call(o.request.scope, null, o.request.arg, false);
1576             return;
1577         }
1578         var result;
1579         try {
1580             result = o.reader.read(response);
1581         }catch(e){
1582             o.success = false;
1583             o.raw = { errorMsg : response.responseText };
1584             this.fireEvent("loadexception", this, o, response, e);
1585             o.request.callback.call(o.request.scope, o, o.request.arg, false);
1586             return;
1587         }
1588         
1589         this.fireEvent("load", this, o, o.request.arg);
1590         o.request.callback.call(o.request.scope, result, o.request.arg, true);
1591     },
1592
1593     // private
1594     update : function(dataSet){
1595
1596     },
1597
1598     // private
1599     updateResponse : function(dataSet){
1600
1601     }
1602 });/*
1603  * Based on:
1604  * Ext JS Library 1.1.1
1605  * Copyright(c) 2006-2007, Ext JS, LLC.
1606  *
1607  * Originally Released Under LGPL - original licence link has changed is not relivant.
1608  *
1609  * Fork - LGPL
1610  * <script type="text/javascript">
1611  */
1612
1613 /**
1614  * @class Roo.data.ScriptTagProxy
1615  * An implementation of Roo.data.DataProxy that reads a data object from a URL which may be in a domain
1616  * other than the originating domain of the running page.<br><br>
1617  * <p>
1618  * <em>Note that if you are retrieving data from a page that is in a domain that is NOT the same as the originating domain
1619  * of the running page, you must use this class, rather than DataProxy.</em><br><br>
1620  * <p>
1621  * The content passed back from a server resource requested by a ScriptTagProxy is executable JavaScript
1622  * source code that is used as the source inside a &lt;script> tag.<br><br>
1623  * <p>
1624  * In order for the browser to process the returned data, the server must wrap the data object
1625  * with a call to a callback function, the name of which is passed as a parameter by the ScriptTagProxy.
1626  * Below is a Java example for a servlet which returns data for either a ScriptTagProxy, or an HttpProxy
1627  * depending on whether the callback name was passed:
1628  * <p>
1629  * <pre><code>
1630 boolean scriptTag = false;
1631 String cb = request.getParameter("callback");
1632 if (cb != null) {
1633     scriptTag = true;
1634     response.setContentType("text/javascript");
1635 } else {
1636     response.setContentType("application/x-json");
1637 }
1638 Writer out = response.getWriter();
1639 if (scriptTag) {
1640     out.write(cb + "(");
1641 }
1642 out.print(dataBlock.toJsonString());
1643 if (scriptTag) {
1644     out.write(");");
1645 }
1646 </pre></code>
1647  *
1648  * @constructor
1649  * @param {Object} config A configuration object.
1650  */
1651 Roo.data.ScriptTagProxy = function(config){
1652     Roo.data.ScriptTagProxy.superclass.constructor.call(this);
1653     Roo.apply(this, config);
1654     this.head = document.getElementsByTagName("head")[0];
1655 };
1656
1657 Roo.data.ScriptTagProxy.TRANS_ID = 1000;
1658
1659 Roo.extend(Roo.data.ScriptTagProxy, Roo.data.DataProxy, {
1660     /**
1661      * @cfg {String} url The URL from which to request the data object.
1662      */
1663     /**
1664      * @cfg {Number} timeout (Optional) The number of milliseconds to wait for a response. Defaults to 30 seconds.
1665      */
1666     timeout : 30000,
1667     /**
1668      * @cfg {String} callbackParam (Optional) The name of the parameter to pass to the server which tells
1669      * the server the name of the callback function set up by the load call to process the returned data object.
1670      * Defaults to "callback".<p>The server-side processing must read this parameter value, and generate
1671      * javascript output which calls this named function passing the data object as its only parameter.
1672      */
1673     callbackParam : "callback",
1674     /**
1675      *  @cfg {Boolean} nocache (Optional) Defaults to true. Disable cacheing by adding a unique parameter
1676      * name to the request.
1677      */
1678     nocache : true,
1679
1680     /**
1681      * Load data from the configured URL, read the data object into
1682      * a block of Roo.data.Records using the passed Roo.data.DataReader implementation, and
1683      * process that block using the passed callback.
1684      * @param {Object} params An object containing properties which are to be used as HTTP parameters
1685      * for the request to the remote server.
1686      * @param {Roo.data.DataReader} reader The Reader object which converts the data
1687      * object into a block of Roo.data.Records.
1688      * @param {Function} callback The function into which to pass the block of Roo.data.Records.
1689      * The function must be passed <ul>
1690      * <li>The Record block object</li>
1691      * <li>The "arg" argument from the load function</li>
1692      * <li>A boolean success indicator</li>
1693      * </ul>
1694      * @param {Object} scope The scope in which to call the callback
1695      * @param {Object} arg An optional argument which is passed to the callback as its second parameter.
1696      */
1697     load : function(params, reader, callback, scope, arg){
1698         if(this.fireEvent("beforeload", this, params) !== false){
1699
1700             var p = Roo.urlEncode(Roo.apply(params, this.extraParams));
1701
1702             var url = this.url;
1703             url += (url.indexOf("?") != -1 ? "&" : "?") + p;
1704             if(this.nocache){
1705                 url += "&_dc=" + (new Date().getTime());
1706             }
1707             var transId = ++Roo.data.ScriptTagProxy.TRANS_ID;
1708             var trans = {
1709                 id : transId,
1710                 cb : "stcCallback"+transId,
1711                 scriptId : "stcScript"+transId,
1712                 params : params,
1713                 arg : arg,
1714                 url : url,
1715                 callback : callback,
1716                 scope : scope,
1717                 reader : reader
1718             };
1719             var conn = this;
1720
1721             window[trans.cb] = function(o){
1722                 conn.handleResponse(o, trans);
1723             };
1724
1725             url += String.format("&{0}={1}", this.callbackParam, trans.cb);
1726
1727             if(this.autoAbort !== false){
1728                 this.abort();
1729             }
1730
1731             trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
1732
1733             var script = document.createElement("script");
1734             script.setAttribute("src", url);
1735             script.setAttribute("type", "text/javascript");
1736             script.setAttribute("id", trans.scriptId);
1737             this.head.appendChild(script);
1738
1739             this.trans = trans;
1740         }else{
1741             callback.call(scope||this, null, arg, false);
1742         }
1743     },
1744
1745     // private
1746     isLoading : function(){
1747         return this.trans ? true : false;
1748     },
1749
1750     /**
1751      * Abort the current server request.
1752      */
1753     abort : function(){
1754         if(this.isLoading()){
1755             this.destroyTrans(this.trans);
1756         }
1757     },
1758
1759     // private
1760     destroyTrans : function(trans, isLoaded){
1761         this.head.removeChild(document.getElementById(trans.scriptId));
1762         clearTimeout(trans.timeoutId);
1763         if(isLoaded){
1764             window[trans.cb] = undefined;
1765             try{
1766                 delete window[trans.cb];
1767             }catch(e){}
1768         }else{
1769             // if hasn't been loaded, wait for load to remove it to prevent script error
1770             window[trans.cb] = function(){
1771                 window[trans.cb] = undefined;
1772                 try{
1773                     delete window[trans.cb];
1774                 }catch(e){}
1775             };
1776         }
1777     },
1778
1779     // private
1780     handleResponse : function(o, trans){
1781         this.trans = false;
1782         this.destroyTrans(trans, true);
1783         var result;
1784         try {
1785             result = trans.reader.readRecords(o);
1786         }catch(e){
1787             this.fireEvent("loadexception", this, o, trans.arg, e);
1788             trans.callback.call(trans.scope||window, null, trans.arg, false);
1789             return;
1790         }
1791         this.fireEvent("load", this, o, trans.arg);
1792         trans.callback.call(trans.scope||window, result, trans.arg, true);
1793     },
1794
1795     // private
1796     handleFailure : function(trans){
1797         this.trans = false;
1798         this.destroyTrans(trans, false);
1799         this.fireEvent("loadexception", this, null, trans.arg);
1800         trans.callback.call(trans.scope||window, null, trans.arg, false);
1801     }
1802 });/*
1803  * Based on:
1804  * Ext JS Library 1.1.1
1805  * Copyright(c) 2006-2007, Ext JS, LLC.
1806  *
1807  * Originally Released Under LGPL - original licence link has changed is not relivant.
1808  *
1809  * Fork - LGPL
1810  * <script type="text/javascript">
1811  */
1812
1813 /**
1814  * @class Roo.data.JsonReader
1815  * @extends Roo.data.DataReader
1816  * Data reader class to create an Array of Roo.data.Record objects from a JSON response
1817  * based on mappings in a provided Roo.data.Record constructor.
1818  * 
1819  * The default behaviour of a store is to send ?_requestMeta=1, unless the class has recieved 'metaData' property
1820  * in the reply previously. 
1821  * 
1822  * <p>
1823  * Example code:
1824  * <pre><code>
1825 var RecordDef = Roo.data.Record.create([
1826     {name: 'name', mapping: 'name'},     // "mapping" property not needed if it's the same as "name"
1827     {name: 'occupation'}                 // This field will use "occupation" as the mapping.
1828 ]);
1829 var myReader = new Roo.data.JsonReader({
1830     totalProperty: "results",    // The property which contains the total dataset size (optional)
1831     root: "rows",                // The property which contains an Array of row objects
1832     id: "id"                     // The property within each row object that provides an ID for the record (optional)
1833 }, RecordDef);
1834 </code></pre>
1835  * <p>
1836  * This would consume a JSON file like this:
1837  * <pre><code>
1838 { 'results': 2, 'rows': [
1839     { 'id': 1, 'name': 'Bill', occupation: 'Gardener' },
1840     { 'id': 2, 'name': 'Ben', occupation: 'Horticulturalist' } ]
1841 }
1842 </code></pre>
1843  * @cfg {String} totalProperty Name of the property from which to retrieve the total number of records
1844  * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
1845  * paged from the remote server.
1846  * @cfg {String} successProperty Name of the property from which to retrieve the success attribute used by forms.
1847  * @cfg {String} root name of the property which contains the Array of row objects.
1848  * @cfg {String} id Name of the property within a row object that contains a record identifier value.
1849  * @cfg {Array} fields Array of field definition objects
1850  * @constructor
1851  * Create a new JsonReader
1852  * @param {Object} meta Metadata configuration options
1853  * @param {Object} recordType Either an Array of field definition objects,
1854  * or an {@link Roo.data.Record} object created using {@link Roo.data.Record#create}.
1855  */
1856 Roo.data.JsonReader = function(meta, recordType){
1857     
1858     meta = meta || {};
1859     // set some defaults:
1860     Roo.applyIf(meta, {
1861         totalProperty: 'total',
1862         successProperty : 'success',
1863         root : 'data',
1864         id : 'id'
1865     });
1866     
1867     Roo.data.JsonReader.superclass.constructor.call(this, meta, recordType||meta.fields);
1868 };
1869 Roo.extend(Roo.data.JsonReader, Roo.data.DataReader, {
1870     
1871     readerType : 'Json',
1872     
1873     /**
1874      * @prop {Boolean} metaFromRemote  - if the meta data was loaded from the remote source.
1875      * Used by Store query builder to append _requestMeta to params.
1876      * 
1877      */
1878     metaFromRemote : false,
1879     /**
1880      * This method is only used by a DataProxy which has retrieved data from a remote server.
1881      * @param {Object} response The XHR object which contains the JSON data in its responseText.
1882      * @return {Object} data A data block which is used by an Roo.data.Store object as
1883      * a cache of Roo.data.Records.
1884      */
1885     read : function(response){
1886         var json = response.responseText;
1887        
1888         var o = /* eval:var:o */ eval("("+json+")");
1889         if(!o) {
1890             throw {message: "JsonReader.read: Json object not found"};
1891         }
1892         
1893         if(o.metaData){
1894             
1895             delete this.ef;
1896             this.metaFromRemote = true;
1897             this.meta = o.metaData;
1898             this.recordType = Roo.data.Record.create(o.metaData.fields);
1899             this.onMetaChange(this.meta, this.recordType, o);
1900         }
1901         return this.readRecords(o);
1902     },
1903
1904     // private function a store will implement
1905     onMetaChange : function(meta, recordType, o){
1906
1907     },
1908
1909     /**
1910          * @ignore
1911          */
1912     simpleAccess: function(obj, subsc) {
1913         return obj[subsc];
1914     },
1915
1916         /**
1917          * @ignore
1918          */
1919     getJsonAccessor: function(){
1920         var re = /[\[\.]/;
1921         return function(expr) {
1922             try {
1923                 return(re.test(expr))
1924                     ? new Function("obj", "return obj." + expr)
1925                     : function(obj){
1926                         return obj[expr];
1927                     };
1928             } catch(e){}
1929             return Roo.emptyFn;
1930         };
1931     }(),
1932
1933     /**
1934      * Create a data block containing Roo.data.Records from an XML document.
1935      * @param {Object} o An object which contains an Array of row objects in the property specified
1936      * in the config as 'root, and optionally a property, specified in the config as 'totalProperty'
1937      * which contains the total size of the dataset.
1938      * @return {Object} data A data block which is used by an Roo.data.Store object as
1939      * a cache of Roo.data.Records.
1940      */
1941     readRecords : function(o){
1942         /**
1943          * After any data loads, the raw JSON data is available for further custom processing.
1944          * @type Object
1945          */
1946         this.o = o;
1947         var s = this.meta, Record = this.recordType,
1948             f = Record ? Record.prototype.fields : null, fi = f ? f.items : [], fl = f ? f.length : 0;
1949
1950 //      Generate extraction functions for the totalProperty, the root, the id, and for each field
1951         if (!this.ef) {
1952             if(s.totalProperty) {
1953                     this.getTotal = this.getJsonAccessor(s.totalProperty);
1954                 }
1955                 if(s.successProperty) {
1956                     this.getSuccess = this.getJsonAccessor(s.successProperty);
1957                 }
1958                 this.getRoot = s.root ? this.getJsonAccessor(s.root) : function(p){return p;};
1959                 if (s.id) {
1960                         var g = this.getJsonAccessor(s.id);
1961                         this.getId = function(rec) {
1962                                 var r = g(rec);  
1963                                 return (r === undefined || r === "") ? null : r;
1964                         };
1965                 } else {
1966                         this.getId = function(){return null;};
1967                 }
1968             this.ef = [];
1969             for(var jj = 0; jj < fl; jj++){
1970                 f = fi[jj];
1971                 var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
1972                 this.ef[jj] = this.getJsonAccessor(map);
1973             }
1974         }
1975
1976         var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
1977         if(s.totalProperty){
1978             var vt = parseInt(this.getTotal(o), 10);
1979             if(!isNaN(vt)){
1980                 totalRecords = vt;
1981             }
1982         }
1983         if(s.successProperty){
1984             var vs = this.getSuccess(o);
1985             if(vs === false || vs === 'false'){
1986                 success = false;
1987             }
1988         }
1989         var records = [];
1990         for(var i = 0; i < c; i++){
1991             var n = root[i];
1992             var values = {};
1993             var id = this.getId(n);
1994             for(var j = 0; j < fl; j++){
1995                 f = fi[j];
1996                                 var v = this.ef[j](n);
1997                                 if (!f.convert) {
1998                                         Roo.log('missing convert for ' + f.name);
1999                                         Roo.log(f);
2000                                         continue;
2001                                 }
2002                                 values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue);
2003             }
2004                         if (!Record) {
2005                                 return {
2006                                         raw : { errorMsg : "JSON Reader Error: fields or metadata not available to create Record" },
2007                                         success : false,
2008                                         records : [],
2009                                         totalRecords : 0
2010                                 };
2011                         }
2012             var record = new Record(values, id);
2013             record.json = n;
2014             records[i] = record;
2015         }
2016         return {
2017             raw : o,
2018             success : success,
2019             records : records,
2020             totalRecords : totalRecords
2021         };
2022     },
2023     // used when loading children.. @see loadDataFromChildren
2024     toLoadData: function(rec)
2025     {
2026         // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
2027         var data = typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
2028         return { data : data, total : data.length };
2029         
2030     }
2031 });/*
2032  * Based on:
2033  * Ext JS Library 1.1.1
2034  * Copyright(c) 2006-2007, Ext JS, LLC.
2035  *
2036  * Originally Released Under LGPL - original licence link has changed is not relivant.
2037  *
2038  * Fork - LGPL
2039  * <script type="text/javascript">
2040  */
2041
2042 /**
2043  * @class Roo.data.XmlReader
2044  * @extends Roo.data.DataReader
2045  * Data reader class to create an Array of {@link Roo.data.Record} objects from an XML document
2046  * based on mappings in a provided Roo.data.Record constructor.<br><br>
2047  * <p>
2048  * <em>Note that in order for the browser to parse a returned XML document, the Content-Type
2049  * header in the HTTP response must be set to "text/xml".</em>
2050  * <p>
2051  * Example code:
2052  * <pre><code>
2053 var RecordDef = Roo.data.Record.create([
2054    {name: 'name', mapping: 'name'},     // "mapping" property not needed if it's the same as "name"
2055    {name: 'occupation'}                 // This field will use "occupation" as the mapping.
2056 ]);
2057 var myReader = new Roo.data.XmlReader({
2058    totalRecords: "results", // The element which contains the total dataset size (optional)
2059    record: "row",           // The repeated element which contains row information
2060    id: "id"                 // The element within the row that provides an ID for the record (optional)
2061 }, RecordDef);
2062 </code></pre>
2063  * <p>
2064  * This would consume an XML file like this:
2065  * <pre><code>
2066 &lt;?xml?>
2067 &lt;dataset>
2068  &lt;results>2&lt;/results>
2069  &lt;row>
2070    &lt;id>1&lt;/id>
2071    &lt;name>Bill&lt;/name>
2072    &lt;occupation>Gardener&lt;/occupation>
2073  &lt;/row>
2074  &lt;row>
2075    &lt;id>2&lt;/id>
2076    &lt;name>Ben&lt;/name>
2077    &lt;occupation>Horticulturalist&lt;/occupation>
2078  &lt;/row>
2079 &lt;/dataset>
2080 </code></pre>
2081  * @cfg {String} totalRecords The DomQuery path from which to retrieve the total number of records
2082  * in the dataset. This is only needed if the whole dataset is not passed in one go, but is being
2083  * paged from the remote server.
2084  * @cfg {String} record The DomQuery path to the repeated element which contains record information.
2085  * @cfg {String} success The DomQuery path to the success attribute used by forms.
2086  * @cfg {String} id The DomQuery path relative from the record element to the element that contains
2087  * a record identifier value.
2088  * @constructor
2089  * Create a new XmlReader
2090  * @param {Object} meta Metadata configuration options
2091  * @param {Mixed} recordType The definition of the data record type to produce.  This can be either a valid
2092  * Record subclass created with {@link Roo.data.Record#create}, or an array of objects with which to call
2093  * Roo.data.Record.create.  See the {@link Roo.data.Record} class for more details.
2094  */
2095 Roo.data.XmlReader = function(meta, recordType){
2096     meta = meta || {};
2097     Roo.data.XmlReader.superclass.constructor.call(this, meta, recordType||meta.fields);
2098 };
2099 Roo.extend(Roo.data.XmlReader, Roo.data.DataReader, {
2100     
2101     readerType : 'Xml',
2102     
2103     /**
2104      * This method is only used by a DataProxy which has retrieved data from a remote server.
2105          * @param {Object} response The XHR object which contains the parsed XML document.  The response is expected
2106          * to contain a method called 'responseXML' that returns an XML document object.
2107      * @return {Object} records A data block which is used by an {@link Roo.data.Store} as
2108      * a cache of Roo.data.Records.
2109      */
2110     read : function(response){
2111         var doc = response.responseXML;
2112         if(!doc) {
2113             throw {message: "XmlReader.read: XML Document not available"};
2114         }
2115         return this.readRecords(doc);
2116     },
2117
2118     /**
2119      * Create a data block containing Roo.data.Records from an XML document.
2120          * @param {Object} doc A parsed XML document.
2121      * @return {Object} records A data block which is used by an {@link Roo.data.Store} as
2122      * a cache of Roo.data.Records.
2123      */
2124     readRecords : function(doc){
2125         /**
2126          * After any data loads/reads, the raw XML Document is available for further custom processing.
2127          * @type XMLDocument
2128          */
2129         this.xmlData = doc;
2130         var root = doc.documentElement || doc;
2131         var q = Roo.DomQuery;
2132         var recordType = this.recordType, fields = recordType.prototype.fields;
2133         var sid = this.meta.id;
2134         var totalRecords = 0, success = true;
2135         if(this.meta.totalRecords){
2136             totalRecords = q.selectNumber(this.meta.totalRecords, root, 0);
2137         }
2138         
2139         if(this.meta.success){
2140             var sv = q.selectValue(this.meta.success, root, true);
2141             success = sv !== false && sv !== 'false';
2142         }
2143         var records = [];
2144         var ns = q.select(this.meta.record, root);
2145         for(var i = 0, len = ns.length; i < len; i++) {
2146                 var n = ns[i];
2147                 var values = {};
2148                 var id = sid ? q.selectValue(sid, n) : undefined;
2149                 for(var j = 0, jlen = fields.length; j < jlen; j++){
2150                     var f = fields.items[j];
2151                 var v = q.selectValue(f.mapping || f.name, n, f.defaultValue);
2152                     v = f.convert(v);
2153                     values[f.name] = v;
2154                 }
2155                 var record = new recordType(values, id);
2156                 record.node = n;
2157                 records[records.length] = record;
2158             }
2159
2160             return {
2161                 success : success,
2162                 records : records,
2163                 totalRecords : totalRecords || records.length
2164             };
2165     }
2166 });/*
2167  * Based on:
2168  * Ext JS Library 1.1.1
2169  * Copyright(c) 2006-2007, Ext JS, LLC.
2170  *
2171  * Originally Released Under LGPL - original licence link has changed is not relivant.
2172  *
2173  * Fork - LGPL
2174  * <script type="text/javascript">
2175  */
2176
2177 /**
2178  * @class Roo.data.ArrayReader
2179  * @extends Roo.data.DataReader
2180  * Data reader class to create an Array of Roo.data.Record objects from an Array.
2181  * Each element of that Array represents a row of data fields. The
2182  * fields are pulled into a Record object using as a subscript, the <em>mapping</em> property
2183  * of the field definition if it exists, or the field's ordinal position in the definition.<br>
2184  * <p>
2185  * Example code:.
2186  * <pre><code>
2187 var RecordDef = Roo.data.Record.create([
2188     {name: 'name', mapping: 1},         // "mapping" only needed if an "id" field is present which
2189     {name: 'occupation', mapping: 2}    // precludes using the ordinal position as the index.
2190 ]);
2191 var myReader = new Roo.data.ArrayReader({
2192     id: 0                     // The subscript within row Array that provides an ID for the Record (optional)
2193 }, RecordDef);
2194 </code></pre>
2195  * <p>
2196  * This would consume an Array like this:
2197  * <pre><code>
2198 [ [1, 'Bill', 'Gardener'], [2, 'Ben', 'Horticulturalist'] ]
2199   </code></pre>
2200  
2201  * @constructor
2202  * Create a new JsonReader
2203  * @param {Object} meta Metadata configuration options.
2204  * @param {Object|Array} recordType Either an Array of field definition objects
2205  * 
2206  * @cfg {Array} fields Array of field definition objects
2207  * @cfg {String} id Name of the property within a row object that contains a record identifier value.
2208  * as specified to {@link Roo.data.Record#create},
2209  * or an {@link Roo.data.Record} object
2210  *
2211  * 
2212  * created using {@link Roo.data.Record#create}.
2213  */
2214 Roo.data.ArrayReader = function(meta, recordType)
2215 {    
2216     Roo.data.ArrayReader.superclass.constructor.call(this, meta, recordType||meta.fields);
2217 };
2218
2219 Roo.extend(Roo.data.ArrayReader, Roo.data.JsonReader, {
2220     
2221       /**
2222      * Create a data block containing Roo.data.Records from an XML document.
2223      * @param {Object} o An Array of row objects which represents the dataset.
2224      * @return {Object} A data block which is used by an {@link Roo.data.Store} object as
2225      * a cache of Roo.data.Records.
2226      */
2227     readRecords : function(o)
2228     {
2229         var sid = this.meta ? this.meta.id : null;
2230         var recordType = this.recordType, fields = recordType.prototype.fields;
2231         var records = [];
2232         var root = o;
2233         for(var i = 0; i < root.length; i++){
2234             var n = root[i];
2235             var values = {};
2236             var id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
2237             for(var j = 0, jlen = fields.length; j < jlen; j++){
2238                 var f = fields.items[j];
2239                 var k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
2240                 var v = n[k] !== undefined ? n[k] : f.defaultValue;
2241                 v = f.convert(v);
2242                 values[f.name] = v;
2243             }
2244             var record = new recordType(values, id);
2245             record.json = n;
2246             records[records.length] = record;
2247         }
2248         return {
2249             records : records,
2250             totalRecords : records.length
2251         };
2252     },
2253     // used when loading children.. @see loadDataFromChildren
2254     toLoadData: function(rec)
2255     {
2256         // expect rec just to be an array.. eg [a,b,c, [...] << cn ]
2257         return typeof(rec.data.cn) == 'undefined' ? [] : rec.data.cn;
2258         
2259     }
2260     
2261     
2262 });/*
2263  * Based on:
2264  * Ext JS Library 1.1.1
2265  * Copyright(c) 2006-2007, Ext JS, LLC.
2266  *
2267  * Originally Released Under LGPL - original licence link has changed is not relivant.
2268  *
2269  * Fork - LGPL
2270  * <script type="text/javascript">
2271  */
2272
2273
2274 /**
2275  * @class Roo.data.Tree
2276  * @extends Roo.util.Observable
2277  * Represents a tree data structure and bubbles all the events for its nodes. The nodes
2278  * in the tree have most standard DOM functionality.
2279  * @constructor
2280  * @param {Node} root (optional) The root node
2281  */
2282 Roo.data.Tree = function(root){
2283    this.nodeHash = {};
2284    /**
2285     * The root node for this tree
2286     * @type Node
2287     */
2288    this.root = null;
2289    if(root){
2290        this.setRootNode(root);
2291    }
2292    this.addEvents({
2293        /**
2294         * @event append
2295         * Fires when a new child node is appended to a node in this tree.
2296         * @param {Tree} tree The owner tree
2297         * @param {Node} parent The parent node
2298         * @param {Node} node The newly appended node
2299         * @param {Number} index The index of the newly appended node
2300         */
2301        "append" : true,
2302        /**
2303         * @event remove
2304         * Fires when a child node is removed from a node in this tree.
2305         * @param {Tree} tree The owner tree
2306         * @param {Node} parent The parent node
2307         * @param {Node} node The child node removed
2308         */
2309        "remove" : true,
2310        /**
2311         * @event move
2312         * Fires when a node is moved to a new location in the tree
2313         * @param {Tree} tree The owner tree
2314         * @param {Node} node The node moved
2315         * @param {Node} oldParent The old parent of this node
2316         * @param {Node} newParent The new parent of this node
2317         * @param {Number} index The index it was moved to
2318         */
2319        "move" : true,
2320        /**
2321         * @event insert
2322         * Fires when a new child node is inserted in a node in this tree.
2323         * @param {Tree} tree The owner tree
2324         * @param {Node} parent The parent node
2325         * @param {Node} node The child node inserted
2326         * @param {Node} refNode The child node the node was inserted before
2327         */
2328        "insert" : true,
2329        /**
2330         * @event beforeappend
2331         * Fires before a new child is appended to a node in this tree, return false to cancel the append.
2332         * @param {Tree} tree The owner tree
2333         * @param {Node} parent The parent node
2334         * @param {Node} node The child node to be appended
2335         */
2336        "beforeappend" : true,
2337        /**
2338         * @event beforeremove
2339         * Fires before a child is removed from a node in this tree, return false to cancel the remove.
2340         * @param {Tree} tree The owner tree
2341         * @param {Node} parent The parent node
2342         * @param {Node} node The child node to be removed
2343         */
2344        "beforeremove" : true,
2345        /**
2346         * @event beforemove
2347         * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
2348         * @param {Tree} tree The owner tree
2349         * @param {Node} node The node being moved
2350         * @param {Node} oldParent The parent of the node
2351         * @param {Node} newParent The new parent the node is moving to
2352         * @param {Number} index The index it is being moved to
2353         */
2354        "beforemove" : true,
2355        /**
2356         * @event beforeinsert
2357         * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
2358         * @param {Tree} tree The owner tree
2359         * @param {Node} parent The parent node
2360         * @param {Node} node The child node to be inserted
2361         * @param {Node} refNode The child node the node is being inserted before
2362         */
2363        "beforeinsert" : true
2364    });
2365
2366     Roo.data.Tree.superclass.constructor.call(this);
2367 };
2368
2369 Roo.extend(Roo.data.Tree, Roo.util.Observable, {
2370     pathSeparator: "/",
2371
2372     proxyNodeEvent : function(){
2373         return this.fireEvent.apply(this, arguments);
2374     },
2375
2376     /**
2377      * Returns the root node for this tree.
2378      * @return {Node}
2379      */
2380     getRootNode : function(){
2381         return this.root;
2382     },
2383
2384     /**
2385      * Sets the root node for this tree.
2386      * @param {Node} node
2387      * @return {Node}
2388      */
2389     setRootNode : function(node){
2390         this.root = node;
2391         node.ownerTree = this;
2392         node.isRoot = true;
2393         this.registerNode(node);
2394         return node;
2395     },
2396
2397     /**
2398      * Gets a node in this tree by its id.
2399      * @param {String} id
2400      * @return {Node}
2401      */
2402     getNodeById : function(id){
2403         return this.nodeHash[id];
2404     },
2405
2406     registerNode : function(node){
2407         this.nodeHash[node.id] = node;
2408     },
2409
2410     unregisterNode : function(node){
2411         delete this.nodeHash[node.id];
2412     },
2413
2414     toString : function(){
2415         return "[Tree"+(this.id?" "+this.id:"")+"]";
2416     }
2417 });
2418
2419 /**
2420  * @class Roo.data.Node
2421  * @extends Roo.util.Observable
2422  * @cfg {Boolean} leaf true if this node is a leaf and does not have children
2423  * @cfg {String} id The id for this node. If one is not specified, one is generated.
2424  * @constructor
2425  * @param {Object} attributes The attributes/config for the node
2426  */
2427 Roo.data.Node = function(attributes){
2428     /**
2429      * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
2430      * @type {Object}
2431      */
2432     this.attributes = attributes || {};
2433     this.leaf = this.attributes.leaf;
2434     /**
2435      * The node id. @type String
2436      */
2437     this.id = this.attributes.id;
2438     if(!this.id){
2439         this.id = Roo.id(null, "ynode-");
2440         this.attributes.id = this.id;
2441     }
2442      
2443     
2444     /**
2445      * All child nodes of this node. @type Array
2446      */
2447     this.childNodes = [];
2448     if(!this.childNodes.indexOf){ // indexOf is a must
2449         this.childNodes.indexOf = function(o){
2450             for(var i = 0, len = this.length; i < len; i++){
2451                 if(this[i] == o) {
2452                     return i;
2453                 }
2454             }
2455             return -1;
2456         };
2457     }
2458     /**
2459      * The parent node for this node. @type Node
2460      */
2461     this.parentNode = null;
2462     /**
2463      * The first direct child node of this node, or null if this node has no child nodes. @type Node
2464      */
2465     this.firstChild = null;
2466     /**
2467      * The last direct child node of this node, or null if this node has no child nodes. @type Node
2468      */
2469     this.lastChild = null;
2470     /**
2471      * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
2472      */
2473     this.previousSibling = null;
2474     /**
2475      * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
2476      */
2477     this.nextSibling = null;
2478
2479     this.addEvents({
2480        /**
2481         * @event append
2482         * Fires when a new child node is appended
2483         * @param {Tree} tree The owner tree
2484         * @param {Node} this This node
2485         * @param {Node} node The newly appended node
2486         * @param {Number} index The index of the newly appended node
2487         */
2488        "append" : true,
2489        /**
2490         * @event remove
2491         * Fires when a child node is removed
2492         * @param {Tree} tree The owner tree
2493         * @param {Node} this This node
2494         * @param {Node} node The removed node
2495         */
2496        "remove" : true,
2497        /**
2498         * @event move
2499         * Fires when this node is moved to a new location in the tree
2500         * @param {Tree} tree The owner tree
2501         * @param {Node} this This node
2502         * @param {Node} oldParent The old parent of this node
2503         * @param {Node} newParent The new parent of this node
2504         * @param {Number} index The index it was moved to
2505         */
2506        "move" : true,
2507        /**
2508         * @event insert
2509         * Fires when a new child node is inserted.
2510         * @param {Tree} tree The owner tree
2511         * @param {Node} this This node
2512         * @param {Node} node The child node inserted
2513         * @param {Node} refNode The child node the node was inserted before
2514         */
2515        "insert" : true,
2516        /**
2517         * @event beforeappend
2518         * Fires before a new child is appended, return false to cancel the append.
2519         * @param {Tree} tree The owner tree
2520         * @param {Node} this This node
2521         * @param {Node} node The child node to be appended
2522         */
2523        "beforeappend" : true,
2524        /**
2525         * @event beforeremove
2526         * Fires before a child is removed, return false to cancel the remove.
2527         * @param {Tree} tree The owner tree
2528         * @param {Node} this This node
2529         * @param {Node} node The child node to be removed
2530         */
2531        "beforeremove" : true,
2532        /**
2533         * @event beforemove
2534         * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
2535         * @param {Tree} tree The owner tree
2536         * @param {Node} this This node
2537         * @param {Node} oldParent The parent of this node
2538         * @param {Node} newParent The new parent this node is moving to
2539         * @param {Number} index The index it is being moved to
2540         */
2541        "beforemove" : true,
2542        /**
2543         * @event beforeinsert
2544         * Fires before a new child is inserted, return false to cancel the insert.
2545         * @param {Tree} tree The owner tree
2546         * @param {Node} this This node
2547         * @param {Node} node The child node to be inserted
2548         * @param {Node} refNode The child node the node is being inserted before
2549         */
2550        "beforeinsert" : true
2551    });
2552     this.listeners = this.attributes.listeners;
2553     Roo.data.Node.superclass.constructor.call(this);
2554 };
2555
2556 Roo.extend(Roo.data.Node, Roo.util.Observable, {
2557     fireEvent : function(evtName){
2558         // first do standard event for this node
2559         if(Roo.data.Node.superclass.fireEvent.apply(this, arguments) === false){
2560             return false;
2561         }
2562         // then bubble it up to the tree if the event wasn't cancelled
2563         var ot = this.getOwnerTree();
2564         if(ot){
2565             if(ot.proxyNodeEvent.apply(ot, arguments) === false){
2566                 return false;
2567             }
2568         }
2569         return true;
2570     },
2571
2572     /**
2573      * Returns true if this node is a leaf
2574      * @return {Boolean}
2575      */
2576     isLeaf : function(){
2577         return this.leaf === true;
2578     },
2579
2580     // private
2581     setFirstChild : function(node){
2582         this.firstChild = node;
2583     },
2584
2585     //private
2586     setLastChild : function(node){
2587         this.lastChild = node;
2588     },
2589
2590
2591     /**
2592      * Returns true if this node is the last child of its parent
2593      * @return {Boolean}
2594      */
2595     isLast : function(){
2596        return (!this.parentNode ? true : this.parentNode.lastChild == this);
2597     },
2598
2599     /**
2600      * Returns true if this node is the first child of its parent
2601      * @return {Boolean}
2602      */
2603     isFirst : function(){
2604        return (!this.parentNode ? true : this.parentNode.firstChild == this);
2605     },
2606
2607     hasChildNodes : function(){
2608         return !this.isLeaf() && this.childNodes.length > 0;
2609     },
2610
2611     /**
2612      * Insert node(s) as the last child node of this node.
2613      * @param {Node/Array} node The node or Array of nodes to append
2614      * @return {Node} The appended node if single append, or null if an array was passed
2615      */
2616     appendChild : function(node){
2617         var multi = false;
2618         if(node instanceof Array){
2619             multi = node;
2620         }else if(arguments.length > 1){
2621             multi = arguments;
2622         }
2623         
2624         // if passed an array or multiple args do them one by one
2625         if(multi){
2626             for(var i = 0, len = multi.length; i < len; i++) {
2627                 this.appendChild(multi[i]);
2628             }
2629         }else{
2630             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
2631                 return false;
2632             }
2633             var index = this.childNodes.length;
2634             var oldParent = node.parentNode;
2635             // it's a move, make sure we move it cleanly
2636             if(oldParent){
2637                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
2638                     return false;
2639                 }
2640                 oldParent.removeChild(node);
2641             }
2642             
2643             index = this.childNodes.length;
2644             if(index == 0){
2645                 this.setFirstChild(node);
2646             }
2647             this.childNodes.push(node);
2648             node.parentNode = this;
2649             var ps = this.childNodes[index-1];
2650             if(ps){
2651                 node.previousSibling = ps;
2652                 ps.nextSibling = node;
2653             }else{
2654                 node.previousSibling = null;
2655             }
2656             node.nextSibling = null;
2657             this.setLastChild(node);
2658             node.setOwnerTree(this.getOwnerTree());
2659             this.fireEvent("append", this.ownerTree, this, node, index);
2660             if(this.ownerTree) {
2661                 this.ownerTree.fireEvent("appendnode", this, node, index);
2662             }
2663             if(oldParent){
2664                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
2665             }
2666             return node;
2667         }
2668     },
2669
2670     /**
2671      * Removes a child node from this node.
2672      * @param {Node} node The node to remove
2673      * @return {Node} The removed node
2674      */
2675     removeChild : function(node){
2676         var index = this.childNodes.indexOf(node);
2677         if(index == -1){
2678             return false;
2679         }
2680         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
2681             return false;
2682         }
2683
2684         // remove it from childNodes collection
2685         this.childNodes.splice(index, 1);
2686
2687         // update siblings
2688         if(node.previousSibling){
2689             node.previousSibling.nextSibling = node.nextSibling;
2690         }
2691         if(node.nextSibling){
2692             node.nextSibling.previousSibling = node.previousSibling;
2693         }
2694
2695         // update child refs
2696         if(this.firstChild == node){
2697             this.setFirstChild(node.nextSibling);
2698         }
2699         if(this.lastChild == node){
2700             this.setLastChild(node.previousSibling);
2701         }
2702
2703         node.setOwnerTree(null);
2704         // clear any references from the node
2705         node.parentNode = null;
2706         node.previousSibling = null;
2707         node.nextSibling = null;
2708         this.fireEvent("remove", this.ownerTree, this, node);
2709         return node;
2710     },
2711
2712     /**
2713      * Inserts the first node before the second node in this nodes childNodes collection.
2714      * @param {Node} node The node to insert
2715      * @param {Node} refNode The node to insert before (if null the node is appended)
2716      * @return {Node} The inserted node
2717      */
2718     insertBefore : function(node, refNode){
2719         if(!refNode){ // like standard Dom, refNode can be null for append
2720             return this.appendChild(node);
2721         }
2722         // nothing to do
2723         if(node == refNode){
2724             return false;
2725         }
2726
2727         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
2728             return false;
2729         }
2730         var index = this.childNodes.indexOf(refNode);
2731         var oldParent = node.parentNode;
2732         var refIndex = index;
2733
2734         // when moving internally, indexes will change after remove
2735         if(oldParent == this && this.childNodes.indexOf(node) < index){
2736             refIndex--;
2737         }
2738
2739         // it's a move, make sure we move it cleanly
2740         if(oldParent){
2741             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
2742                 return false;
2743             }
2744             oldParent.removeChild(node);
2745         }
2746         if(refIndex == 0){
2747             this.setFirstChild(node);
2748         }
2749         this.childNodes.splice(refIndex, 0, node);
2750         node.parentNode = this;
2751         var ps = this.childNodes[refIndex-1];
2752         if(ps){
2753             node.previousSibling = ps;
2754             ps.nextSibling = node;
2755         }else{
2756             node.previousSibling = null;
2757         }
2758         node.nextSibling = refNode;
2759         refNode.previousSibling = node;
2760         node.setOwnerTree(this.getOwnerTree());
2761         this.fireEvent("insert", this.ownerTree, this, node, refNode);
2762         if(oldParent){
2763             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
2764         }
2765         return node;
2766     },
2767
2768     /**
2769      * Returns the child node at the specified index.
2770      * @param {Number} index
2771      * @return {Node}
2772      */
2773     item : function(index){
2774         return this.childNodes[index];
2775     },
2776
2777     /**
2778      * Replaces one child node in this node with another.
2779      * @param {Node} newChild The replacement node
2780      * @param {Node} oldChild The node to replace
2781      * @return {Node} The replaced node
2782      */
2783     replaceChild : function(newChild, oldChild){
2784         this.insertBefore(newChild, oldChild);
2785         this.removeChild(oldChild);
2786         return oldChild;
2787     },
2788
2789     /**
2790      * Returns the index of a child node
2791      * @param {Node} node
2792      * @return {Number} The index of the node or -1 if it was not found
2793      */
2794     indexOf : function(child){
2795         return this.childNodes.indexOf(child);
2796     },
2797
2798     /**
2799      * Returns the tree this node is in.
2800      * @return {Tree}
2801      */
2802     getOwnerTree : function(){
2803         // if it doesn't have one, look for one
2804         if(!this.ownerTree){
2805             var p = this;
2806             while(p){
2807                 if(p.ownerTree){
2808                     this.ownerTree = p.ownerTree;
2809                     break;
2810                 }
2811                 p = p.parentNode;
2812             }
2813         }
2814         return this.ownerTree;
2815     },
2816
2817     /**
2818      * Returns depth of this node (the root node has a depth of 0)
2819      * @return {Number}
2820      */
2821     getDepth : function(){
2822         var depth = 0;
2823         var p = this;
2824         while(p.parentNode){
2825             ++depth;
2826             p = p.parentNode;
2827         }
2828         return depth;
2829     },
2830
2831     // private
2832     setOwnerTree : function(tree){
2833         // if it's move, we need to update everyone
2834         if(tree != this.ownerTree){
2835             if(this.ownerTree){
2836                 this.ownerTree.unregisterNode(this);
2837             }
2838             this.ownerTree = tree;
2839             var cs = this.childNodes;
2840             for(var i = 0, len = cs.length; i < len; i++) {
2841                 cs[i].setOwnerTree(tree);
2842             }
2843             if(tree){
2844                 tree.registerNode(this);
2845             }
2846         }
2847     },
2848
2849     /**
2850      * Returns the path for this node. The path can be used to expand or select this node programmatically.
2851      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
2852      * @return {String} The path
2853      */
2854     getPath : function(attr){
2855         attr = attr || "id";
2856         var p = this.parentNode;
2857         var b = [this.attributes[attr]];
2858         while(p){
2859             b.unshift(p.attributes[attr]);
2860             p = p.parentNode;
2861         }
2862         var sep = this.getOwnerTree().pathSeparator;
2863         return sep + b.join(sep);
2864     },
2865
2866     /**
2867      * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
2868      * function call will be the scope provided or the current node. The arguments to the function
2869      * will be the args provided or the current node. If the function returns false at any point,
2870      * the bubble is stopped.
2871      * @param {Function} fn The function to call
2872      * @param {Object} scope (optional) The scope of the function (defaults to current node)
2873      * @param {Array} args (optional) The args to call the function with (default to passing the current node)
2874      */
2875     bubble : function(fn, scope, args){
2876         var p = this;
2877         while(p){
2878             if(fn.call(scope || p, args || p) === false){
2879                 break;
2880             }
2881             p = p.parentNode;
2882         }
2883     },
2884
2885     /**
2886      * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of
2887      * function call will be the scope provided or the current node. The arguments to the function
2888      * will be the args provided or the current node. If the function returns false at any point,
2889      * the cascade is stopped on that branch.
2890      * @param {Function} fn The function to call
2891      * @param {Object} scope (optional) The scope of the function (defaults to current node)
2892      * @param {Array} args (optional) The args to call the function with (default to passing the current node)
2893      */
2894     cascade : function(fn, scope, args){
2895         if(fn.call(scope || this, args || this) !== false){
2896             var cs = this.childNodes;
2897             for(var i = 0, len = cs.length; i < len; i++) {
2898                 cs[i].cascade(fn, scope, args);
2899             }
2900         }
2901     },
2902
2903     /**
2904      * Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of
2905      * function call will be the scope provided or the current node. The arguments to the function
2906      * will be the args provided or the current node. If the function returns false at any point,
2907      * the iteration stops.
2908      * @param {Function} fn The function to call
2909      * @param {Object} scope (optional) The scope of the function (defaults to current node)
2910      * @param {Array} args (optional) The args to call the function with (default to passing the current node)
2911      */
2912     eachChild : function(fn, scope, args){
2913         var cs = this.childNodes;
2914         for(var i = 0, len = cs.length; i < len; i++) {
2915                 if(fn.call(scope || this, args || cs[i]) === false){
2916                     break;
2917                 }
2918         }
2919     },
2920
2921     /**
2922      * Finds the first child that has the attribute with the specified value.
2923      * @param {String} attribute The attribute name
2924      * @param {Mixed} value The value to search for
2925      * @return {Node} The found child or null if none was found
2926      */
2927     findChild : function(attribute, value){
2928         var cs = this.childNodes;
2929         for(var i = 0, len = cs.length; i < len; i++) {
2930                 if(cs[i].attributes[attribute] == value){
2931                     return cs[i];
2932                 }
2933         }
2934         return null;
2935     },
2936
2937     /**
2938      * Finds the first child by a custom function. The child matches if the function passed
2939      * returns true.
2940      * @param {Function} fn
2941      * @param {Object} scope (optional)
2942      * @return {Node} The found child or null if none was found
2943      */
2944     findChildBy : function(fn, scope){
2945         var cs = this.childNodes;
2946         for(var i = 0, len = cs.length; i < len; i++) {
2947                 if(fn.call(scope||cs[i], cs[i]) === true){
2948                     return cs[i];
2949                 }
2950         }
2951         return null;
2952     },
2953
2954     /**
2955      * Sorts this nodes children using the supplied sort function
2956      * @param {Function} fn
2957      * @param {Object} scope (optional)
2958      */
2959     sort : function(fn, scope){
2960         var cs = this.childNodes;
2961         var len = cs.length;
2962         if(len > 0){
2963             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
2964             cs.sort(sortFn);
2965             for(var i = 0; i < len; i++){
2966                 var n = cs[i];
2967                 n.previousSibling = cs[i-1];
2968                 n.nextSibling = cs[i+1];
2969                 if(i == 0){
2970                     this.setFirstChild(n);
2971                 }
2972                 if(i == len-1){
2973                     this.setLastChild(n);
2974                 }
2975             }
2976         }
2977     },
2978
2979     /**
2980      * Returns true if this node is an ancestor (at any point) of the passed node.
2981      * @param {Node} node
2982      * @return {Boolean}
2983      */
2984     contains : function(node){
2985         return node.isAncestor(this);
2986     },
2987
2988     /**
2989      * Returns true if the passed node is an ancestor (at any point) of this node.
2990      * @param {Node} node
2991      * @return {Boolean}
2992      */
2993     isAncestor : function(node){
2994         var p = this.parentNode;
2995         while(p){
2996             if(p == node){
2997                 return true;
2998             }
2999             p = p.parentNode;
3000         }
3001         return false;
3002     },
3003
3004     toString : function(){
3005         return "[Node"+(this.id?" "+this.id:"")+"]";
3006     }
3007 });/*
3008  * Based on:
3009  * Ext JS Library 1.1.1
3010  * Copyright(c) 2006-2007, Ext JS, LLC.
3011  *
3012  * Originally Released Under LGPL - original licence link has changed is not relivant.
3013  *
3014  * Fork - LGPL
3015  * <script type="text/javascript">
3016  */
3017
3018
3019 /**
3020  * @class Roo.Shadow
3021  * Simple class that can provide a shadow effect for any element.  Note that the element MUST be absolutely positioned,
3022  * and the shadow does not provide any shimming.  This should be used only in simple cases -- for more advanced
3023  * functionality that can also provide the same shadow effect, see the {@link Roo.Layer} class.
3024  * @constructor
3025  * Create a new Shadow
3026  * @param {Object} config The config object
3027  */
3028 Roo.Shadow = function(config){
3029     Roo.apply(this, config);
3030     if(typeof this.mode != "string"){
3031         this.mode = this.defaultMode;
3032     }
3033     var o = this.offset, a = {h: 0};
3034     var rad = Math.floor(this.offset/2);
3035     switch(this.mode.toLowerCase()){ // all this hideous nonsense calculates the various offsets for shadows
3036         case "drop":
3037             a.w = 0;
3038             a.l = a.t = o;
3039             a.t -= 1;
3040             if(Roo.isIE){
3041                 a.l -= this.offset + rad;
3042                 a.t -= this.offset + rad;
3043                 a.w -= rad;
3044                 a.h -= rad;
3045                 a.t += 1;
3046             }
3047         break;
3048         case "sides":
3049             a.w = (o*2);
3050             a.l = -o;
3051             a.t = o-1;
3052             if(Roo.isIE){
3053                 a.l -= (this.offset - rad);
3054                 a.t -= this.offset + rad;
3055                 a.l += 1;
3056                 a.w -= (this.offset - rad)*2;
3057                 a.w -= rad + 1;
3058                 a.h -= 1;
3059             }
3060         break;
3061         case "frame":
3062             a.w = a.h = (o*2);
3063             a.l = a.t = -o;
3064             a.t += 1;
3065             a.h -= 2;
3066             if(Roo.isIE){
3067                 a.l -= (this.offset - rad);
3068                 a.t -= (this.offset - rad);
3069                 a.l += 1;
3070                 a.w -= (this.offset + rad + 1);
3071                 a.h -= (this.offset + rad);
3072                 a.h += 1;
3073             }
3074         break;
3075     };
3076
3077     this.adjusts = a;
3078 };
3079
3080 Roo.Shadow.prototype = {
3081     /**
3082      * @cfg {String} mode
3083      * The shadow display mode.  Supports the following options:<br />
3084      * sides: Shadow displays on both sides and bottom only<br />
3085      * frame: Shadow displays equally on all four sides<br />
3086      * drop: Traditional bottom-right drop shadow (default)
3087      */
3088     mode: false,
3089     /**
3090      * @cfg {String} offset
3091      * The number of pixels to offset the shadow from the element (defaults to 4)
3092      */
3093     offset: 4,
3094
3095     // private
3096     defaultMode: "drop",
3097
3098     /**
3099      * Displays the shadow under the target element
3100      * @param {String/HTMLElement/Element} targetEl The id or element under which the shadow should display
3101      */
3102     show : function(target){
3103         target = Roo.get(target);
3104         if(!this.el){
3105             this.el = Roo.Shadow.Pool.pull();
3106             if(this.el.dom.nextSibling != target.dom){
3107                 this.el.insertBefore(target);
3108             }
3109         }
3110         this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10)-1);
3111         if(Roo.isIE){
3112             this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")";
3113         }
3114         this.realign(
3115             target.getLeft(true),
3116             target.getTop(true),
3117             target.getWidth(),
3118             target.getHeight()
3119         );
3120         this.el.dom.style.display = "block";
3121     },
3122
3123     /**
3124      * Returns true if the shadow is visible, else false
3125      */
3126     isVisible : function(){
3127         return this.el ? true : false;  
3128     },
3129
3130     /**
3131      * Direct alignment when values are already available. Show must be called at least once before
3132      * calling this method to ensure it is initialized.
3133      * @param {Number} left The target element left position
3134      * @param {Number} top The target element top position
3135      * @param {Number} width The target element width
3136      * @param {Number} height The target element height
3137      */
3138     realign : function(l, t, w, h){
3139         if(!this.el){
3140             return;
3141         }
3142         var a = this.adjusts, d = this.el.dom, s = d.style;
3143         var iea = 0;
3144         s.left = (l+a.l)+"px";
3145         s.top = (t+a.t)+"px";
3146         var sw = (w+a.w), sh = (h+a.h), sws = sw +"px", shs = sh + "px";
3147  
3148         if(s.width != sws || s.height != shs){
3149             s.width = sws;
3150             s.height = shs;
3151             if(!Roo.isIE){
3152                 var cn = d.childNodes;
3153                 var sww = Math.max(0, (sw-12))+"px";
3154                 cn[0].childNodes[1].style.width = sww;
3155                 cn[1].childNodes[1].style.width = sww;
3156                 cn[2].childNodes[1].style.width = sww;
3157                 cn[1].style.height = Math.max(0, (sh-12))+"px";
3158             }
3159         }
3160     },
3161
3162     /**
3163      * Hides this shadow
3164      */
3165     hide : function(){
3166         if(this.el){
3167             this.el.dom.style.display = "none";
3168             Roo.Shadow.Pool.push(this.el);
3169             delete this.el;
3170         }
3171     },
3172
3173     /**
3174      * Adjust the z-index of this shadow
3175      * @param {Number} zindex The new z-index
3176      */
3177     setZIndex : function(z){
3178         this.zIndex = z;
3179         if(this.el){
3180             this.el.setStyle("z-index", z);
3181         }
3182     }
3183 };
3184
3185 // Private utility class that manages the internal Shadow cache
3186 Roo.Shadow.Pool = function(){
3187     var p = [];
3188     var markup = Roo.isIE ?
3189                  '<div class="x-ie-shadow"></div>' :
3190                  '<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>';
3191     return {
3192         pull : function(){
3193             var sh = p.shift();
3194             if(!sh){
3195                 sh = Roo.get(Roo.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
3196                 sh.autoBoxAdjust = false;
3197             }
3198             return sh;
3199         },
3200
3201         push : function(sh){
3202             p.push(sh);
3203         }
3204     };
3205 }();/*
3206  * Based on:
3207  * Ext JS Library 1.1.1
3208  * Copyright(c) 2006-2007, Ext JS, LLC.
3209  *
3210  * Originally Released Under LGPL - original licence link has changed is not relivant.
3211  *
3212  * Fork - LGPL
3213  * <script type="text/javascript">
3214  */
3215
3216
3217 /**
3218  * @class Roo.SplitBar
3219  * @extends Roo.util.Observable
3220  * Creates draggable splitter bar functionality from two elements (element to be dragged and element to be resized).
3221  * <br><br>
3222  * Usage:
3223  * <pre><code>
3224 var split = new Roo.SplitBar("elementToDrag", "elementToSize",
3225                    Roo.SplitBar.HORIZONTAL, Roo.SplitBar.LEFT);
3226 split.setAdapter(new Roo.SplitBar.AbsoluteLayoutAdapter("container"));
3227 split.minSize = 100;
3228 split.maxSize = 600;
3229 split.animate = true;
3230 split.on('moved', splitterMoved);
3231 </code></pre>
3232  * @constructor
3233  * Create a new SplitBar
3234  * @param {String/HTMLElement/Roo.Element} dragElement The element to be dragged and act as the SplitBar. 
3235  * @param {String/HTMLElement/Roo.Element} resizingElement The element to be resized based on where the SplitBar element is dragged 
3236  * @param {Number} orientation (optional) Either Roo.SplitBar.HORIZONTAL or Roo.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
3237  * @param {Number} placement (optional) Either Roo.SplitBar.LEFT or Roo.SplitBar.RIGHT for horizontal or  
3238                         Roo.SplitBar.TOP or Roo.SplitBar.BOTTOM for vertical. (By default, this is determined automatically by the initial
3239                         position of the SplitBar).
3240  */
3241 Roo.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
3242     
3243     /** @private */
3244     this.el = Roo.get(dragElement, true);
3245     this.el.dom.unselectable = "on";
3246     /** @private */
3247     this.resizingEl = Roo.get(resizingElement, true);
3248
3249     /**
3250      * @private
3251      * The orientation of the split. Either Roo.SplitBar.HORIZONTAL or Roo.SplitBar.VERTICAL. (Defaults to HORIZONTAL)
3252      * Note: If this is changed after creating the SplitBar, the placement property must be manually updated
3253      * @type Number
3254      */
3255     this.orientation = orientation || Roo.SplitBar.HORIZONTAL;
3256     
3257     /**
3258      * The minimum size of the resizing element. (Defaults to 0)
3259      * @type Number
3260      */
3261     this.minSize = 0;
3262     
3263     /**
3264      * The maximum size of the resizing element. (Defaults to 2000)
3265      * @type Number
3266      */
3267     this.maxSize = 2000;
3268     
3269     /**
3270      * Whether to animate the transition to the new size
3271      * @type Boolean
3272      */
3273     this.animate = false;
3274     
3275     /**
3276      * Whether to create a transparent shim that overlays the page when dragging, enables dragging across iframes.
3277      * @type Boolean
3278      */
3279     this.useShim = false;
3280     
3281     /** @private */
3282     this.shim = null;
3283     
3284     if(!existingProxy){
3285         /** @private */
3286         this.proxy = Roo.SplitBar.createProxy(this.orientation);
3287     }else{
3288         this.proxy = Roo.get(existingProxy).dom;
3289     }
3290     /** @private */
3291     this.dd = new Roo.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
3292     
3293     /** @private */
3294     this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
3295     
3296     /** @private */
3297     this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
3298     
3299     /** @private */
3300     this.dragSpecs = {};
3301     
3302     /**
3303      * @private The adapter to use to positon and resize elements
3304      */
3305     this.adapter = new Roo.SplitBar.BasicLayoutAdapter();
3306     this.adapter.init(this);
3307     
3308     if(this.orientation == Roo.SplitBar.HORIZONTAL){
3309         /** @private */
3310         this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Roo.SplitBar.LEFT : Roo.SplitBar.RIGHT);
3311         this.el.addClass("x-splitbar-h");
3312     }else{
3313         /** @private */
3314         this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Roo.SplitBar.TOP : Roo.SplitBar.BOTTOM);
3315         this.el.addClass("x-splitbar-v");
3316     }
3317     
3318     this.addEvents({
3319         /**
3320          * @event resize
3321          * Fires when the splitter is moved (alias for {@link #event-moved})
3322          * @param {Roo.SplitBar} this
3323          * @param {Number} newSize the new width or height
3324          */
3325         "resize" : true,
3326         /**
3327          * @event moved
3328          * Fires when the splitter is moved
3329          * @param {Roo.SplitBar} this
3330          * @param {Number} newSize the new width or height
3331          */
3332         "moved" : true,
3333         /**
3334          * @event beforeresize
3335          * Fires before the splitter is dragged
3336          * @param {Roo.SplitBar} this
3337          */
3338         "beforeresize" : true,
3339
3340         "beforeapply" : true
3341     });
3342
3343     Roo.util.Observable.call(this);
3344 };
3345
3346 Roo.extend(Roo.SplitBar, Roo.util.Observable, {
3347     onStartProxyDrag : function(x, y){
3348         this.fireEvent("beforeresize", this);
3349         if(!this.overlay){
3350             var o = Roo.DomHelper.insertFirst(document.body,  {cls: "x-drag-overlay", html: "&#160;"}, true);
3351             o.unselectable();
3352             o.enableDisplayMode("block");
3353             // all splitbars share the same overlay
3354             Roo.SplitBar.prototype.overlay = o;
3355         }
3356         this.overlay.setSize(Roo.lib.Dom.getViewWidth(true), Roo.lib.Dom.getViewHeight(true));
3357         this.overlay.show();
3358         Roo.get(this.proxy).setDisplayed("block");
3359         var size = this.adapter.getElementSize(this);
3360         this.activeMinSize = this.getMinimumSize();;
3361         this.activeMaxSize = this.getMaximumSize();;
3362         var c1 = size - this.activeMinSize;
3363         var c2 = Math.max(this.activeMaxSize - size, 0);
3364         if(this.orientation == Roo.SplitBar.HORIZONTAL){
3365             this.dd.resetConstraints();
3366             this.dd.setXConstraint(
3367                 this.placement == Roo.SplitBar.LEFT ? c1 : c2, 
3368                 this.placement == Roo.SplitBar.LEFT ? c2 : c1
3369             );
3370             this.dd.setYConstraint(0, 0);
3371         }else{
3372             this.dd.resetConstraints();
3373             this.dd.setXConstraint(0, 0);
3374             this.dd.setYConstraint(
3375                 this.placement == Roo.SplitBar.TOP ? c1 : c2, 
3376                 this.placement == Roo.SplitBar.TOP ? c2 : c1
3377             );
3378          }
3379         this.dragSpecs.startSize = size;
3380         this.dragSpecs.startPoint = [x, y];
3381         Roo.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
3382     },
3383     
3384     /** 
3385      * @private Called after the drag operation by the DDProxy
3386      */
3387     onEndProxyDrag : function(e){
3388         Roo.get(this.proxy).setDisplayed(false);
3389         var endPoint = Roo.lib.Event.getXY(e);
3390         if(this.overlay){
3391             this.overlay.hide();
3392         }
3393         var newSize;
3394         if(this.orientation == Roo.SplitBar.HORIZONTAL){
3395             newSize = this.dragSpecs.startSize + 
3396                 (this.placement == Roo.SplitBar.LEFT ?
3397                     endPoint[0] - this.dragSpecs.startPoint[0] :
3398                     this.dragSpecs.startPoint[0] - endPoint[0]
3399                 );
3400         }else{
3401             newSize = this.dragSpecs.startSize + 
3402                 (this.placement == Roo.SplitBar.TOP ?
3403                     endPoint[1] - this.dragSpecs.startPoint[1] :
3404                     this.dragSpecs.startPoint[1] - endPoint[1]
3405                 );
3406         }
3407         newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
3408         if(newSize != this.dragSpecs.startSize){
3409             if(this.fireEvent('beforeapply', this, newSize) !== false){
3410                 this.adapter.setElementSize(this, newSize);
3411                 this.fireEvent("moved", this, newSize);
3412                 this.fireEvent("resize", this, newSize);
3413             }
3414         }
3415     },
3416     
3417     /**
3418      * Get the adapter this SplitBar uses
3419      * @return The adapter object
3420      */
3421     getAdapter : function(){
3422         return this.adapter;
3423     },
3424     
3425     /**
3426      * Set the adapter this SplitBar uses
3427      * @param {Object} adapter A SplitBar adapter object
3428      */
3429     setAdapter : function(adapter){
3430         this.adapter = adapter;
3431         this.adapter.init(this);
3432     },
3433     
3434     /**
3435      * Gets the minimum size for the resizing element
3436      * @return {Number} The minimum size
3437      */
3438     getMinimumSize : function(){
3439         return this.minSize;
3440     },
3441     
3442     /**
3443      * Sets the minimum size for the resizing element
3444      * @param {Number} minSize The minimum size
3445      */
3446     setMinimumSize : function(minSize){
3447         this.minSize = minSize;
3448     },
3449     
3450     /**
3451      * Gets the maximum size for the resizing element
3452      * @return {Number} The maximum size
3453      */
3454     getMaximumSize : function(){
3455         return this.maxSize;
3456     },
3457     
3458     /**
3459      * Sets the maximum size for the resizing element
3460      * @param {Number} maxSize The maximum size
3461      */
3462     setMaximumSize : function(maxSize){
3463         this.maxSize = maxSize;
3464     },
3465     
3466     /**
3467      * Sets the initialize size for the resizing element
3468      * @param {Number} size The initial size
3469      */
3470     setCurrentSize : function(size){
3471         var oldAnimate = this.animate;
3472         this.animate = false;
3473         this.adapter.setElementSize(this, size);
3474         this.animate = oldAnimate;
3475     },
3476     
3477     /**
3478      * Destroy this splitbar. 
3479      * @param {Boolean} removeEl True to remove the element
3480      */
3481     destroy : function(removeEl){
3482         if(this.shim){
3483             this.shim.remove();
3484         }
3485         this.dd.unreg();
3486         this.proxy.parentNode.removeChild(this.proxy);
3487         if(removeEl){
3488             this.el.remove();
3489         }
3490     }
3491 });
3492
3493 /**
3494  * @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.
3495  */
3496 Roo.SplitBar.createProxy = function(dir){
3497     var proxy = new Roo.Element(document.createElement("div"));
3498     proxy.unselectable();
3499     var cls = 'x-splitbar-proxy';
3500     proxy.addClass(cls + ' ' + (dir == Roo.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
3501     document.body.appendChild(proxy.dom);
3502     return proxy.dom;
3503 };
3504
3505 /** 
3506  * @class Roo.SplitBar.BasicLayoutAdapter
3507  * Default Adapter. It assumes the splitter and resizing element are not positioned
3508  * elements and only gets/sets the width of the element. Generally used for table based layouts.
3509  */
3510 Roo.SplitBar.BasicLayoutAdapter = function(){
3511 };
3512
3513 Roo.SplitBar.BasicLayoutAdapter.prototype = {
3514     // do nothing for now
3515     init : function(s){
3516     
3517     },
3518     /**
3519      * Called before drag operations to get the current size of the resizing element. 
3520      * @param {Roo.SplitBar} s The SplitBar using this adapter
3521      */
3522      getElementSize : function(s){
3523         if(s.orientation == Roo.SplitBar.HORIZONTAL){
3524             return s.resizingEl.getWidth();
3525         }else{
3526             return s.resizingEl.getHeight();
3527         }
3528     },
3529     
3530     /**
3531      * Called after drag operations to set the size of the resizing element.
3532      * @param {Roo.SplitBar} s The SplitBar using this adapter
3533      * @param {Number} newSize The new size to set
3534      * @param {Function} onComplete A function to be invoked when resizing is complete
3535      */
3536     setElementSize : function(s, newSize, onComplete){
3537         if(s.orientation == Roo.SplitBar.HORIZONTAL){
3538             if(!s.animate){
3539                 s.resizingEl.setWidth(newSize);
3540                 if(onComplete){
3541                     onComplete(s, newSize);
3542                 }
3543             }else{
3544                 s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');
3545             }
3546         }else{
3547             
3548             if(!s.animate){
3549                 s.resizingEl.setHeight(newSize);
3550                 if(onComplete){
3551                     onComplete(s, newSize);
3552                 }
3553             }else{
3554                 s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');
3555             }
3556         }
3557     }
3558 };
3559
3560 /** 
3561  *@class Roo.SplitBar.AbsoluteLayoutAdapter
3562  * @extends Roo.SplitBar.BasicLayoutAdapter
3563  * Adapter that  moves the splitter element to align with the resized sizing element. 
3564  * Used with an absolute positioned SplitBar.
3565  * @param {String/HTMLElement/Roo.Element} container The container that wraps around the absolute positioned content. If it's
3566  * document.body, make sure you assign an id to the body element.
3567  */
3568 Roo.SplitBar.AbsoluteLayoutAdapter = function(container){
3569     this.basic = new Roo.SplitBar.BasicLayoutAdapter();
3570     this.container = Roo.get(container);
3571 };
3572
3573 Roo.SplitBar.AbsoluteLayoutAdapter.prototype = {
3574     init : function(s){
3575         this.basic.init(s);
3576     },
3577     
3578     getElementSize : function(s){
3579         return this.basic.getElementSize(s);
3580     },
3581     
3582     setElementSize : function(s, newSize, onComplete){
3583         this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
3584     },
3585     
3586     moveSplitter : function(s){
3587         var yes = Roo.SplitBar;
3588         switch(s.placement){
3589             case yes.LEFT:
3590                 s.el.setX(s.resizingEl.getRight());
3591                 break;
3592             case yes.RIGHT:
3593                 s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");
3594                 break;
3595             case yes.TOP:
3596                 s.el.setY(s.resizingEl.getBottom());
3597                 break;
3598             case yes.BOTTOM:
3599                 s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
3600                 break;
3601         }
3602     }
3603 };
3604
3605 /**
3606  * Orientation constant - Create a vertical SplitBar
3607  * @static
3608  * @type Number
3609  */
3610 Roo.SplitBar.VERTICAL = 1;
3611
3612 /**
3613  * Orientation constant - Create a horizontal SplitBar
3614  * @static
3615  * @type Number
3616  */
3617 Roo.SplitBar.HORIZONTAL = 2;
3618
3619 /**
3620  * Placement constant - The resizing element is to the left of the splitter element
3621  * @static
3622  * @type Number
3623  */
3624 Roo.SplitBar.LEFT = 1;
3625
3626 /**
3627  * Placement constant - The resizing element is to the right of the splitter element
3628  * @static
3629  * @type Number
3630  */
3631 Roo.SplitBar.RIGHT = 2;
3632
3633 /**
3634  * Placement constant - The resizing element is positioned above the splitter element
3635  * @static
3636  * @type Number
3637  */
3638 Roo.SplitBar.TOP = 3;
3639
3640 /**
3641  * Placement constant - The resizing element is positioned under splitter element
3642  * @static
3643  * @type Number
3644  */
3645 Roo.SplitBar.BOTTOM = 4;
3646 /*
3647  * Based on:
3648  * Ext JS Library 1.1.1
3649  * Copyright(c) 2006-2007, Ext JS, LLC.
3650  *
3651  * Originally Released Under LGPL - original licence link has changed is not relivant.
3652  *
3653  * Fork - LGPL
3654  * <script type="text/javascript">
3655  */
3656
3657 /**
3658  * @class Roo.View
3659  * @extends Roo.util.Observable
3660  * Create a "View" for an element based on a data model or UpdateManager and the supplied DomHelper template. 
3661  * This class also supports single and multi selection modes. <br>
3662  * Create a data model bound view:
3663  <pre><code>
3664  var store = new Roo.data.Store(...);
3665
3666  var view = new Roo.View({
3667     el : "my-element",
3668     tpl : '&lt;div id="{0}"&gt;{2} - {1}&lt;/div&gt;', // auto create template
3669  
3670     singleSelect: true,
3671     selectedClass: "ydataview-selected",
3672     store: store
3673  });
3674
3675  // listen for node click?
3676  view.on("click", function(vw, index, node, e){
3677  alert('Node "' + node.id + '" at index: ' + index + " was clicked.");
3678  });
3679
3680  // load XML data
3681  dataModel.load("foobar.xml");
3682  </code></pre>
3683  For an example of creating a JSON/UpdateManager view, see {@link Roo.JsonView}.
3684  * <br><br>
3685  * <b>Note: The root of your template must be a single node. Table/row implementations may work but are not supported due to
3686  * IE"s limited insertion support with tables and Opera"s faulty event bubbling.</b>
3687  * 
3688  * Note: old style constructor is still suported (container, template, config)
3689  * 
3690  * @constructor
3691  * Create a new View
3692  * @param {Object} config The config object
3693  * 
3694  */
3695 Roo.View = function(config, depreciated_tpl, depreciated_config){
3696     
3697     this.parent = false;
3698     
3699     if (typeof(depreciated_tpl) == 'undefined') {
3700         // new way.. - universal constructor.
3701         Roo.apply(this, config);
3702         this.el  = Roo.get(this.el);
3703     } else {
3704         // old format..
3705         this.el  = Roo.get(config);
3706         this.tpl = depreciated_tpl;
3707         Roo.apply(this, depreciated_config);
3708     }
3709     this.wrapEl  = this.el.wrap().wrap();
3710     ///this.el = this.wrapEla.appendChild(document.createElement("div"));
3711     
3712     
3713     if(typeof(this.tpl) == "string"){
3714         this.tpl = new Roo.Template(this.tpl);
3715     } else {
3716         // support xtype ctors..
3717         this.tpl = new Roo.factory(this.tpl, Roo);
3718     }
3719     
3720     
3721     this.tpl.compile();
3722     
3723     /** @private */
3724     this.addEvents({
3725         /**
3726          * @event beforeclick
3727          * Fires before a click is processed. Returns false to cancel the default action.
3728          * @param {Roo.View} this
3729          * @param {Number} index The index of the target node
3730          * @param {HTMLElement} node The target node
3731          * @param {Roo.EventObject} e The raw event object
3732          */
3733             "beforeclick" : true,
3734         /**
3735          * @event click
3736          * Fires when a template node is clicked.
3737          * @param {Roo.View} this
3738          * @param {Number} index The index of the target node
3739          * @param {HTMLElement} node The target node
3740          * @param {Roo.EventObject} e The raw event object
3741          */
3742             "click" : true,
3743         /**
3744          * @event dblclick
3745          * Fires when a template node is double clicked.
3746          * @param {Roo.View} this
3747          * @param {Number} index The index of the target node
3748          * @param {HTMLElement} node The target node
3749          * @param {Roo.EventObject} e The raw event object
3750          */
3751             "dblclick" : true,
3752         /**
3753          * @event contextmenu
3754          * Fires when a template node is right clicked.
3755          * @param {Roo.View} this
3756          * @param {Number} index The index of the target node
3757          * @param {HTMLElement} node The target node
3758          * @param {Roo.EventObject} e The raw event object
3759          */
3760             "contextmenu" : true,
3761         /**
3762          * @event selectionchange
3763          * Fires when the selected nodes change.
3764          * @param {Roo.View} this
3765          * @param {Array} selections Array of the selected nodes
3766          */
3767             "selectionchange" : true,
3768     
3769         /**
3770          * @event beforeselect
3771          * Fires before a selection is made. If any handlers return false, the selection is cancelled.
3772          * @param {Roo.View} this
3773          * @param {HTMLElement} node The node to be selected
3774          * @param {Array} selections Array of currently selected nodes
3775          */
3776             "beforeselect" : true,
3777         /**
3778          * @event preparedata
3779          * Fires on every row to render, to allow you to change the data.
3780          * @param {Roo.View} this
3781          * @param {Object} data to be rendered (change this)
3782          */
3783           "preparedata" : true
3784           
3785           
3786         });
3787
3788
3789
3790     this.el.on({
3791         "click": this.onClick,
3792         "dblclick": this.onDblClick,
3793         "contextmenu": this.onContextMenu,
3794         scope:this
3795     });
3796
3797     this.selections = [];
3798     this.nodes = [];
3799     this.cmp = new Roo.CompositeElementLite([]);
3800     if(this.store){
3801         this.store = Roo.factory(this.store, Roo.data);
3802         this.setStore(this.store, true);
3803     }
3804     
3805     if ( this.footer && this.footer.xtype) {
3806            
3807          var fctr = this.wrapEl.appendChild(document.createElement("div"));
3808         
3809         this.footer.dataSource = this.store;
3810         this.footer.container = fctr;
3811         this.footer = Roo.factory(this.footer, Roo);
3812         fctr.insertFirst(this.el);
3813         
3814         // this is a bit insane - as the paging toolbar seems to detach the el..
3815 //        dom.parentNode.parentNode.parentNode
3816          // they get detached?
3817     }
3818     
3819     
3820     Roo.View.superclass.constructor.call(this);
3821     
3822     
3823 };
3824
3825 Roo.extend(Roo.View, Roo.util.Observable, {
3826     
3827      /**
3828      * @cfg {Roo.data.Store} store Data store to load data from.
3829      */
3830     store : false,
3831     
3832     /**
3833      * @cfg {String|Roo.Element} el The container element.
3834      */
3835     el : '',
3836     
3837     /**
3838      * @cfg {String|Roo.Template} tpl The template used by this View 
3839      */
3840     tpl : false,
3841     /**
3842      * @cfg {String} dataName the named area of the template to use as the data area
3843      *                          Works with domtemplates roo-name="name"
3844      */
3845     dataName: false,
3846     /**
3847      * @cfg {String} selectedClass The css class to add to selected nodes
3848      */
3849     selectedClass : "x-view-selected",
3850      /**
3851      * @cfg {String} emptyText The empty text to show when nothing is loaded.
3852      */
3853     emptyText : "",
3854     
3855     /**
3856      * @cfg {String} text to display on mask (default Loading)
3857      */
3858     mask : false,
3859     /**
3860      * @cfg {Boolean} multiSelect Allow multiple selection
3861      */
3862     multiSelect : false,
3863     /**
3864      * @cfg {Boolean} singleSelect Allow single selection
3865      */
3866     singleSelect:  false,
3867     
3868     /**
3869      * @cfg {Boolean} toggleSelect - selecting 
3870      */
3871     toggleSelect : false,
3872     
3873     /**
3874      * @cfg {Boolean} tickable - selecting 
3875      */
3876     tickable : false,
3877     
3878     /**
3879      * Returns the element this view is bound to.
3880      * @return {Roo.Element}
3881      */
3882     getEl : function(){
3883         return this.wrapEl;
3884     },
3885     
3886     
3887
3888     /**
3889      * Refreshes the view. - called by datachanged on the store. - do not call directly.
3890      */
3891     refresh : function(){
3892         //Roo.log('refresh');
3893         var t = this.tpl;
3894         
3895         // if we are using something like 'domtemplate', then
3896         // the what gets used is:
3897         // t.applySubtemplate(NAME, data, wrapping data..)
3898         // the outer template then get' applied with
3899         //     the store 'extra data'
3900         // and the body get's added to the
3901         //      roo-name="data" node?
3902         //      <span class='roo-tpl-{name}'></span> ?????
3903         
3904         
3905         
3906         this.clearSelections();
3907         this.el.update("");
3908         var html = [];
3909         var records = this.store.getRange();
3910         if(records.length < 1) {
3911             
3912             // is this valid??  = should it render a template??
3913             
3914             this.el.update(this.emptyText);
3915             return;
3916         }
3917         var el = this.el;
3918         if (this.dataName) {
3919             this.el.update(t.apply(this.store.meta)); //????
3920             el = this.el.child('.roo-tpl-' + this.dataName);
3921         }
3922         
3923         for(var i = 0, len = records.length; i < len; i++){
3924             var data = this.prepareData(records[i].data, i, records[i]);
3925             this.fireEvent("preparedata", this, data, i, records[i]);
3926             
3927             var d = Roo.apply({}, data);
3928             
3929             if(this.tickable){
3930                 Roo.apply(d, {'roo-id' : Roo.id()});
3931                 
3932                 var _this = this;
3933             
3934                 Roo.each(this.parent.item, function(item){
3935                     if(item[_this.parent.valueField] != data[_this.parent.valueField]){
3936                         return;
3937                     }
3938                     Roo.apply(d, {'roo-data-checked' : 'checked'});
3939                 });
3940             }
3941             
3942             html[html.length] = Roo.util.Format.trim(
3943                 this.dataName ?
3944                     t.applySubtemplate(this.dataName, d, this.store.meta) :
3945                     t.apply(d)
3946             );
3947         }
3948         
3949         
3950         
3951         el.update(html.join(""));
3952         this.nodes = el.dom.childNodes;
3953         this.updateIndexes(0);
3954     },
3955     
3956
3957     /**
3958      * Function to override to reformat the data that is sent to
3959      * the template for each node.
3960      * DEPRICATED - use the preparedata event handler.
3961      * @param {Array/Object} data The raw data (array of colData for a data model bound view or
3962      * a JSON object for an UpdateManager bound view).
3963      */
3964     prepareData : function(data, index, record)
3965     {
3966         this.fireEvent("preparedata", this, data, index, record);
3967         return data;
3968     },
3969
3970     onUpdate : function(ds, record){
3971         // Roo.log('on update');   
3972         this.clearSelections();
3973         var index = this.store.indexOf(record);
3974         var n = this.nodes[index];
3975         this.tpl.insertBefore(n, this.prepareData(record.data, index, record));
3976         n.parentNode.removeChild(n);
3977         this.updateIndexes(index, index);
3978     },
3979
3980     
3981     
3982 // --------- FIXME     
3983     onAdd : function(ds, records, index)
3984     {
3985         //Roo.log(['on Add', ds, records, index] );        
3986         this.clearSelections();
3987         if(this.nodes.length == 0){
3988             this.refresh();
3989             return;
3990         }
3991         var n = this.nodes[index];
3992         for(var i = 0, len = records.length; i < len; i++){
3993             var d = this.prepareData(records[i].data, i, records[i]);
3994             if(n){
3995                 this.tpl.insertBefore(n, d);
3996             }else{
3997                 
3998                 this.tpl.append(this.el, d);
3999             }
4000         }
4001         this.updateIndexes(index);
4002     },
4003
4004     onRemove : function(ds, record, index){
4005        // Roo.log('onRemove');
4006         this.clearSelections();
4007         var el = this.dataName  ?
4008             this.el.child('.roo-tpl-' + this.dataName) :
4009             this.el; 
4010         
4011         el.dom.removeChild(this.nodes[index]);
4012         this.updateIndexes(index);
4013     },
4014
4015     /**
4016      * Refresh an individual node.
4017      * @param {Number} index
4018      */
4019     refreshNode : function(index){
4020         this.onUpdate(this.store, this.store.getAt(index));
4021     },
4022
4023     updateIndexes : function(startIndex, endIndex){
4024         var ns = this.nodes;
4025         startIndex = startIndex || 0;
4026         endIndex = endIndex || ns.length - 1;
4027         for(var i = startIndex; i <= endIndex; i++){
4028             ns[i].nodeIndex = i;
4029         }
4030     },
4031
4032     /**
4033      * Changes the data store this view uses and refresh the view.
4034      * @param {Store} store
4035      */
4036     setStore : function(store, initial){
4037         if(!initial && this.store){
4038             this.store.un("datachanged", this.refresh);
4039             this.store.un("add", this.onAdd);
4040             this.store.un("remove", this.onRemove);
4041             this.store.un("update", this.onUpdate);
4042             this.store.un("clear", this.refresh);
4043             this.store.un("beforeload", this.onBeforeLoad);
4044             this.store.un("load", this.onLoad);
4045             this.store.un("loadexception", this.onLoad);
4046         }
4047         if(store){
4048           
4049             store.on("datachanged", this.refresh, this);
4050             store.on("add", this.onAdd, this);
4051             store.on("remove", this.onRemove, this);
4052             store.on("update", this.onUpdate, this);
4053             store.on("clear", this.refresh, this);
4054             store.on("beforeload", this.onBeforeLoad, this);
4055             store.on("load", this.onLoad, this);
4056             store.on("loadexception", this.onLoad, this);
4057         }
4058         
4059         if(store){
4060             this.refresh();
4061         }
4062     },
4063     /**
4064      * onbeforeLoad - masks the loading area.
4065      *
4066      */
4067     onBeforeLoad : function(store,opts)
4068     {
4069          //Roo.log('onBeforeLoad');   
4070         if (!opts.add) {
4071             this.el.update("");
4072         }
4073         this.el.mask(this.mask ? this.mask : "Loading" ); 
4074     },
4075     onLoad : function ()
4076     {
4077         this.el.unmask();
4078     },
4079     
4080
4081     /**
4082      * Returns the template node the passed child belongs to or null if it doesn't belong to one.
4083      * @param {HTMLElement} node
4084      * @return {HTMLElement} The template node
4085      */
4086     findItemFromChild : function(node){
4087         var el = this.dataName  ?
4088             this.el.child('.roo-tpl-' + this.dataName,true) :
4089             this.el.dom; 
4090         
4091         if(!node || node.parentNode == el){
4092                     return node;
4093             }
4094             var p = node.parentNode;
4095             while(p && p != el){
4096             if(p.parentNode == el){
4097                 return p;
4098             }
4099             p = p.parentNode;
4100         }
4101             return null;
4102     },
4103
4104     /** @ignore */
4105     onClick : function(e){
4106         var item = this.findItemFromChild(e.getTarget());
4107         if(item){
4108             var index = this.indexOf(item);
4109             if(this.onItemClick(item, index, e) !== false){
4110                 this.fireEvent("click", this, index, item, e);
4111             }
4112         }else{
4113             this.clearSelections();
4114         }
4115     },
4116
4117     /** @ignore */
4118     onContextMenu : function(e){
4119         var item = this.findItemFromChild(e.getTarget());
4120         if(item){
4121             this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
4122         }
4123     },
4124
4125     /** @ignore */
4126     onDblClick : function(e){
4127         var item = this.findItemFromChild(e.getTarget());
4128         if(item){
4129             this.fireEvent("dblclick", this, this.indexOf(item), item, e);
4130         }
4131     },
4132
4133     onItemClick : function(item, index, e)
4134     {
4135         if(this.fireEvent("beforeclick", this, index, item, e) === false){
4136             return false;
4137         }
4138         if (this.toggleSelect) {
4139             var m = this.isSelected(item) ? 'unselect' : 'select';
4140             //Roo.log(m);
4141             var _t = this;
4142             _t[m](item, true, false);
4143             return true;
4144         }
4145         if(this.multiSelect || this.singleSelect){
4146             if(this.multiSelect && e.shiftKey && this.lastSelection){
4147                 this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
4148             }else{
4149                 this.select(item, this.multiSelect && e.ctrlKey);
4150                 this.lastSelection = item;
4151             }
4152             
4153             if(!this.tickable){
4154                 e.preventDefault();
4155             }
4156             
4157         }
4158         return true;
4159     },
4160
4161     /**
4162      * Get the number of selected nodes.
4163      * @return {Number}
4164      */
4165     getSelectionCount : function(){
4166         return this.selections.length;
4167     },
4168
4169     /**
4170      * Get the currently selected nodes.
4171      * @return {Array} An array of HTMLElements
4172      */
4173     getSelectedNodes : function(){
4174         return this.selections;
4175     },
4176
4177     /**
4178      * Get the indexes of the selected nodes.
4179      * @return {Array}
4180      */
4181     getSelectedIndexes : function(){
4182         var indexes = [], s = this.selections;
4183         for(var i = 0, len = s.length; i < len; i++){
4184             indexes.push(s[i].nodeIndex);
4185         }
4186         return indexes;
4187     },
4188
4189     /**
4190      * Clear all selections
4191      * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange event
4192      */
4193     clearSelections : function(suppressEvent){
4194         if(this.nodes && (this.multiSelect || this.singleSelect) && this.selections.length > 0){
4195             this.cmp.elements = this.selections;
4196             this.cmp.removeClass(this.selectedClass);
4197             this.selections = [];
4198             if(!suppressEvent){
4199                 this.fireEvent("selectionchange", this, this.selections);
4200             }
4201         }
4202     },
4203
4204     /**
4205      * Returns true if the passed node is selected
4206      * @param {HTMLElement/Number} node The node or node index
4207      * @return {Boolean}
4208      */
4209     isSelected : function(node){
4210         var s = this.selections;
4211         if(s.length < 1){
4212             return false;
4213         }
4214         node = this.getNode(node);
4215         return s.indexOf(node) !== -1;
4216     },
4217
4218     /**
4219      * Selects nodes.
4220      * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
4221      * @param {Boolean} keepExisting (optional) true to keep existing selections
4222      * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
4223      */
4224     select : function(nodeInfo, keepExisting, suppressEvent){
4225         if(nodeInfo instanceof Array){
4226             if(!keepExisting){
4227                 this.clearSelections(true);
4228             }
4229             for(var i = 0, len = nodeInfo.length; i < len; i++){
4230                 this.select(nodeInfo[i], true, true);
4231             }
4232             return;
4233         } 
4234         var node = this.getNode(nodeInfo);
4235         if(!node || this.isSelected(node)){
4236             return; // already selected.
4237         }
4238         if(!keepExisting){
4239             this.clearSelections(true);
4240         }
4241         
4242         if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
4243             Roo.fly(node).addClass(this.selectedClass);
4244             this.selections.push(node);
4245             if(!suppressEvent){
4246                 this.fireEvent("selectionchange", this, this.selections);
4247             }
4248         }
4249         
4250         
4251     },
4252       /**
4253      * Unselects nodes.
4254      * @param {Array/HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node, id of a template node or an array of any of those to select
4255      * @param {Boolean} keepExisting (optional) true IGNORED (for campatibility with select)
4256      * @param {Boolean} suppressEvent (optional) true to skip firing of the selectionchange vent
4257      */
4258     unselect : function(nodeInfo, keepExisting, suppressEvent)
4259     {
4260         if(nodeInfo instanceof Array){
4261             Roo.each(this.selections, function(s) {
4262                 this.unselect(s, nodeInfo);
4263             }, this);
4264             return;
4265         }
4266         var node = this.getNode(nodeInfo);
4267         if(!node || !this.isSelected(node)){
4268             //Roo.log("not selected");
4269             return; // not selected.
4270         }
4271         // fireevent???
4272         var ns = [];
4273         Roo.each(this.selections, function(s) {
4274             if (s == node ) {
4275                 Roo.fly(node).removeClass(this.selectedClass);
4276
4277                 return;
4278             }
4279             ns.push(s);
4280         },this);
4281         
4282         this.selections= ns;
4283         this.fireEvent("selectionchange", this, this.selections);
4284     },
4285
4286     /**
4287      * Gets a template node.
4288      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
4289      * @return {HTMLElement} The node or null if it wasn't found
4290      */
4291     getNode : function(nodeInfo){
4292         if(typeof nodeInfo == "string"){
4293             return document.getElementById(nodeInfo);
4294         }else if(typeof nodeInfo == "number"){
4295             return this.nodes[nodeInfo];
4296         }
4297         return nodeInfo;
4298     },
4299
4300     /**
4301      * Gets a range template nodes.
4302      * @param {Number} startIndex
4303      * @param {Number} endIndex
4304      * @return {Array} An array of nodes
4305      */
4306     getNodes : function(start, end){
4307         var ns = this.nodes;
4308         start = start || 0;
4309         end = typeof end == "undefined" ? ns.length - 1 : end;
4310         var nodes = [];
4311         if(start <= end){
4312             for(var i = start; i <= end; i++){
4313                 nodes.push(ns[i]);
4314             }
4315         } else{
4316             for(var i = start; i >= end; i--){
4317                 nodes.push(ns[i]);
4318             }
4319         }
4320         return nodes;
4321     },
4322
4323     /**
4324      * Finds the index of the passed node
4325      * @param {HTMLElement/String/Number} nodeInfo An HTMLElement template node, index of a template node or the id of a template node
4326      * @return {Number} The index of the node or -1
4327      */
4328     indexOf : function(node){
4329         node = this.getNode(node);
4330         if(typeof node.nodeIndex == "number"){
4331             return node.nodeIndex;
4332         }
4333         var ns = this.nodes;
4334         for(var i = 0, len = ns.length; i < len; i++){
4335             if(ns[i] == node){
4336                 return i;
4337             }
4338         }
4339         return -1;
4340     }
4341 });
4342 /*
4343  * Based on:
4344  * Ext JS Library 1.1.1
4345  * Copyright(c) 2006-2007, Ext JS, LLC.
4346  *
4347  * Originally Released Under LGPL - original licence link has changed is not relivant.
4348  *
4349  * Fork - LGPL
4350  * <script type="text/javascript">
4351  */
4352
4353 /**
4354  * @class Roo.JsonView
4355  * @extends Roo.View
4356  * Shortcut class to create a JSON + {@link Roo.UpdateManager} template view. Usage:
4357 <pre><code>
4358 var view = new Roo.JsonView({
4359     container: "my-element",
4360     tpl: '&lt;div id="{id}"&gt;{foo} - {bar}&lt;/div&gt;', // auto create template
4361     multiSelect: true, 
4362     jsonRoot: "data" 
4363 });
4364
4365 // listen for node click?
4366 view.on("click", function(vw, index, node, e){
4367     alert('Node "' + node.id + '" at index: ' + index + " was clicked.");
4368 });
4369
4370 // direct load of JSON data
4371 view.load("foobar.php");
4372
4373 // Example from my blog list
4374 var tpl = new Roo.Template(
4375     '&lt;div class="entry"&gt;' +
4376     '&lt;a class="entry-title" href="{link}"&gt;{title}&lt;/a&gt;' +
4377     "&lt;h4&gt;{date} by {author} | {comments} Comments&lt;/h4&gt;{description}" +
4378     "&lt;/div&gt;&lt;hr /&gt;"
4379 );
4380
4381 var moreView = new Roo.JsonView({
4382     container :  "entry-list", 
4383     template : tpl,
4384     jsonRoot: "posts"
4385 });
4386 moreView.on("beforerender", this.sortEntries, this);
4387 moreView.load({
4388     url: "/blog/get-posts.php",
4389     params: "allposts=true",
4390     text: "Loading Blog Entries..."
4391 });
4392 </code></pre>
4393
4394 * Note: old code is supported with arguments : (container, template, config)
4395
4396
4397  * @constructor
4398  * Create a new JsonView
4399  * 
4400  * @param {Object} config The config object
4401  * 
4402  */
4403 Roo.JsonView = function(config, depreciated_tpl, depreciated_config){
4404     
4405     
4406     Roo.JsonView.superclass.constructor.call(this, config, depreciated_tpl, depreciated_config);
4407
4408     var um = this.el.getUpdateManager();
4409     um.setRenderer(this);
4410     um.on("update", this.onLoad, this);
4411     um.on("failure", this.onLoadException, this);
4412
4413     /**
4414      * @event beforerender
4415      * Fires before rendering of the downloaded JSON data.
4416      * @param {Roo.JsonView} this
4417      * @param {Object} data The JSON data loaded
4418      */
4419     /**
4420      * @event load
4421      * Fires when data is loaded.
4422      * @param {Roo.JsonView} this
4423      * @param {Object} data The JSON data loaded
4424      * @param {Object} response The raw Connect response object
4425      */
4426     /**
4427      * @event loadexception
4428      * Fires when loading fails.
4429      * @param {Roo.JsonView} this
4430      * @param {Object} response The raw Connect response object
4431      */
4432     this.addEvents({
4433         'beforerender' : true,
4434         'load' : true,
4435         'loadexception' : true
4436     });
4437 };
4438 Roo.extend(Roo.JsonView, Roo.View, {
4439     /**
4440      * @type {String} The root property in the loaded JSON object that contains the data
4441      */
4442     jsonRoot : "",
4443
4444     /**
4445      * Refreshes the view.
4446      */
4447     refresh : function(){
4448         this.clearSelections();
4449         this.el.update("");
4450         var html = [];
4451         var o = this.jsonData;
4452         if(o && o.length > 0){
4453             for(var i = 0, len = o.length; i < len; i++){
4454                 var data = this.prepareData(o[i], i, o);
4455                 html[html.length] = this.tpl.apply(data);
4456             }
4457         }else{
4458             html.push(this.emptyText);
4459         }
4460         this.el.update(html.join(""));
4461         this.nodes = this.el.dom.childNodes;
4462         this.updateIndexes(0);
4463     },
4464
4465     /**
4466      * Performs an async HTTP request, and loads the JSON from the response. If <i>params</i> are specified it uses POST, otherwise it uses GET.
4467      * @param {Object/String/Function} url The URL for this request, or a function to call to get the URL, or a config object containing any of the following options:
4468      <pre><code>
4469      view.load({
4470          url: "your-url.php",
4471          params: {param1: "foo", param2: "bar"}, // or a URL encoded string
4472          callback: yourFunction,
4473          scope: yourObject, //(optional scope)
4474          discardUrl: false,
4475          nocache: false,
4476          text: "Loading...",
4477          timeout: 30,
4478          scripts: false
4479      });
4480      </code></pre>
4481      * The only required property is <i>url</i>. The optional properties <i>nocache</i>, <i>text</i> and <i>scripts</i>
4482      * are respectively shorthand for <i>disableCaching</i>, <i>indicatorText</i>, and <i>loadScripts</i> and are used to set their associated property on this UpdateManager instance.
4483      * @param {String/Object} params (optional) The parameters to pass, as either a URL encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
4484      * @param {Function} callback (optional) Callback when transaction is complete - called with signature (oElement, bSuccess)
4485      * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used URL. If true, it will not store the URL.
4486      */
4487     load : function(){
4488         var um = this.el.getUpdateManager();
4489         um.update.apply(um, arguments);
4490     },
4491
4492     // note - render is a standard framework call...
4493     // using it for the response is really flaky... - it's called by UpdateManager normally, except when called by the XComponent/addXtype.
4494     render : function(el, response){
4495         
4496         this.clearSelections();
4497         this.el.update("");
4498         var o;
4499         try{
4500             if (response != '') {
4501                 o = Roo.util.JSON.decode(response.responseText);
4502                 if(this.jsonRoot){
4503                     
4504                     o = o[this.jsonRoot];
4505                 }
4506             }
4507         } catch(e){
4508         }
4509         /**
4510          * The current JSON data or null
4511          */
4512         this.jsonData = o;
4513         this.beforeRender();
4514         this.refresh();
4515     },
4516
4517 /**
4518  * Get the number of records in the current JSON dataset
4519  * @return {Number}
4520  */
4521     getCount : function(){
4522         return this.jsonData ? this.jsonData.length : 0;
4523     },
4524
4525 /**
4526  * Returns the JSON object for the specified node(s)
4527  * @param {HTMLElement/Array} node The node or an array of nodes
4528  * @return {Object/Array} If you pass in an array, you get an array back, otherwise
4529  * you get the JSON object for the node
4530  */
4531     getNodeData : function(node){
4532         if(node instanceof Array){
4533             var data = [];
4534             for(var i = 0, len = node.length; i < len; i++){
4535                 data.push(this.getNodeData(node[i]));
4536             }
4537             return data;
4538         }
4539         return this.jsonData[this.indexOf(node)] || null;
4540     },
4541
4542     beforeRender : function(){
4543         this.snapshot = this.jsonData;
4544         if(this.sortInfo){
4545             this.sort.apply(this, this.sortInfo);
4546         }
4547         this.fireEvent("beforerender", this, this.jsonData);
4548     },
4549
4550     onLoad : function(el, o){
4551         this.fireEvent("load", this, this.jsonData, o);
4552     },
4553
4554     onLoadException : function(el, o){
4555         this.fireEvent("loadexception", this, o);
4556     },
4557
4558 /**
4559  * Filter the data by a specific property.
4560  * @param {String} property A property on your JSON objects
4561  * @param {String/RegExp} value Either string that the property values
4562  * should start with, or a RegExp to test against the property
4563  */
4564     filter : function(property, value){
4565         if(this.jsonData){
4566             var data = [];
4567             var ss = this.snapshot;
4568             if(typeof value == "string"){
4569                 var vlen = value.length;
4570                 if(vlen == 0){
4571                     this.clearFilter();
4572                     return;
4573                 }
4574                 value = value.toLowerCase();
4575                 for(var i = 0, len = ss.length; i < len; i++){
4576                     var o = ss[i];
4577                     if(o[property].substr(0, vlen).toLowerCase() == value){
4578                         data.push(o);
4579                     }
4580                 }
4581             } else if(value.exec){ // regex?
4582                 for(var i = 0, len = ss.length; i < len; i++){
4583                     var o = ss[i];
4584                     if(value.test(o[property])){
4585                         data.push(o);
4586                     }
4587                 }
4588             } else{
4589                 return;
4590             }
4591             this.jsonData = data;
4592             this.refresh();
4593         }
4594     },
4595
4596 /**
4597  * Filter by a function. The passed function will be called with each
4598  * object in the current dataset. If the function returns true the value is kept,
4599  * otherwise it is filtered.
4600  * @param {Function} fn
4601  * @param {Object} scope (optional) The scope of the function (defaults to this JsonView)
4602  */
4603     filterBy : function(fn, scope){
4604         if(this.jsonData){
4605             var data = [];
4606             var ss = this.snapshot;
4607             for(var i = 0, len = ss.length; i < len; i++){
4608                 var o = ss[i];
4609                 if(fn.call(scope || this, o)){
4610                     data.push(o);
4611                 }
4612             }
4613             this.jsonData = data;
4614             this.refresh();
4615         }
4616     },
4617
4618 /**
4619  * Clears the current filter.
4620  */
4621     clearFilter : function(){
4622         if(this.snapshot && this.jsonData != this.snapshot){
4623             this.jsonData = this.snapshot;
4624             this.refresh();
4625         }
4626     },
4627
4628
4629 /**
4630  * Sorts the data for this view and refreshes it.
4631  * @param {String} property A property on your JSON objects to sort on
4632  * @param {String} direction (optional) "desc" or "asc" (defaults to "asc")
4633  * @param {Function} sortType (optional) A function to call to convert the data to a sortable value.
4634  */
4635     sort : function(property, dir, sortType){
4636         this.sortInfo = Array.prototype.slice.call(arguments, 0);
4637         if(this.jsonData){
4638             var p = property;
4639             var dsc = dir && dir.toLowerCase() == "desc";
4640             var f = function(o1, o2){
4641                 var v1 = sortType ? sortType(o1[p]) : o1[p];
4642                 var v2 = sortType ? sortType(o2[p]) : o2[p];
4643                 ;
4644                 if(v1 < v2){
4645                     return dsc ? +1 : -1;
4646                 } else if(v1 > v2){
4647                     return dsc ? -1 : +1;
4648                 } else{
4649                     return 0;
4650                 }
4651             };
4652             this.jsonData.sort(f);
4653             this.refresh();
4654             if(this.jsonData != this.snapshot){
4655                 this.snapshot.sort(f);
4656             }
4657         }
4658     }
4659 });/*
4660  * Based on:
4661  * Ext JS Library 1.1.1
4662  * Copyright(c) 2006-2007, Ext JS, LLC.
4663  *
4664  * Originally Released Under LGPL - original licence link has changed is not relivant.
4665  *
4666  * Fork - LGPL
4667  * <script type="text/javascript">
4668  */
4669  
4670
4671 /**
4672  * @class Roo.ColorPalette
4673  * @extends Roo.Component
4674  * Simple color palette class for choosing colors.  The palette can be rendered to any container.<br />
4675  * Here's an example of typical usage:
4676  * <pre><code>
4677 var cp = new Roo.ColorPalette({value:'993300'});  // initial selected color
4678 cp.render('my-div');
4679
4680 cp.on('select', function(palette, selColor){
4681     // do something with selColor
4682 });
4683 </code></pre>
4684  * @constructor
4685  * Create a new ColorPalette
4686  * @param {Object} config The config object
4687  */
4688 Roo.ColorPalette = function(config){
4689     Roo.ColorPalette.superclass.constructor.call(this, config);
4690     this.addEvents({
4691         /**
4692              * @event select
4693              * Fires when a color is selected
4694              * @param {ColorPalette} this
4695              * @param {String} color The 6-digit color hex code (without the # symbol)
4696              */
4697         select: true
4698     });
4699
4700     if(this.handler){
4701         this.on("select", this.handler, this.scope, true);
4702     }
4703 };
4704 Roo.extend(Roo.ColorPalette, Roo.Component, {
4705     /**
4706      * @cfg {String} itemCls
4707      * The CSS class to apply to the containing element (defaults to "x-color-palette")
4708      */
4709     itemCls : "x-color-palette",
4710     /**
4711      * @cfg {String} value
4712      * The initial color to highlight (should be a valid 6-digit color hex code without the # symbol).  Note that
4713      * the hex codes are case-sensitive.
4714      */
4715     value : null,
4716     clickEvent:'click',
4717     // private
4718     ctype: "Roo.ColorPalette",
4719
4720     /**
4721      * @cfg {Boolean} allowReselect If set to true then reselecting a color that is already selected fires the selection event
4722      */
4723     allowReselect : false,
4724
4725     /**
4726      * <p>An array of 6-digit color hex code strings (without the # symbol).  This array can contain any number
4727      * of colors, and each hex code should be unique.  The width of the palette is controlled via CSS by adjusting
4728      * the width property of the 'x-color-palette' class (or assigning a custom class), so you can balance the number
4729      * of colors with the width setting until the box is symmetrical.</p>
4730      * <p>You can override individual colors if needed:</p>
4731      * <pre><code>
4732 var cp = new Roo.ColorPalette();
4733 cp.colors[0] = "FF0000";  // change the first box to red
4734 </code></pre>
4735
4736 Or you can provide a custom array of your own for complete control:
4737 <pre><code>
4738 var cp = new Roo.ColorPalette();
4739 cp.colors = ["000000", "993300", "333300"];
4740 </code></pre>
4741      * @type Array
4742      */
4743     colors : [
4744         "000000", "993300", "333300", "003300", "003366", "000080", "333399", "333333",
4745         "800000", "FF6600", "808000", "008000", "008080", "0000FF", "666699", "808080",
4746         "FF0000", "FF9900", "99CC00", "339966", "33CCCC", "3366FF", "800080", "969696",
4747         "FF00FF", "FFCC00", "FFFF00", "00FF00", "00FFFF", "00CCFF", "993366", "C0C0C0",
4748         "FF99CC", "FFCC99", "FFFF99", "CCFFCC", "CCFFFF", "99CCFF", "CC99FF", "FFFFFF"
4749     ],
4750
4751     // private
4752     onRender : function(container, position){
4753         var t = new Roo.MasterTemplate(
4754             '<tpl><a href="#" class="color-{0}" hidefocus="on"><em><span style="background:#{0}" unselectable="on">&#160;</span></em></a></tpl>'
4755         );
4756         var c = this.colors;
4757         for(var i = 0, len = c.length; i < len; i++){
4758             t.add([c[i]]);
4759         }
4760         var el = document.createElement("div");
4761         el.className = this.itemCls;
4762         t.overwrite(el);
4763         container.dom.insertBefore(el, position);
4764         this.el = Roo.get(el);
4765         this.el.on(this.clickEvent, this.handleClick,  this, {delegate: "a"});
4766         if(this.clickEvent != 'click'){
4767             this.el.on('click', Roo.emptyFn,  this, {delegate: "a", preventDefault:true});
4768         }
4769     },
4770
4771     // private
4772     afterRender : function(){
4773         Roo.ColorPalette.superclass.afterRender.call(this);
4774         if(this.value){
4775             var s = this.value;
4776             this.value = null;
4777             this.select(s);
4778         }
4779     },
4780
4781     // private
4782     handleClick : function(e, t){
4783         e.preventDefault();
4784         if(!this.disabled){
4785             var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
4786             this.select(c.toUpperCase());
4787         }
4788     },
4789
4790     /**
4791      * Selects the specified color in the palette (fires the select event)
4792      * @param {String} color A valid 6-digit color hex code (# will be stripped if included)
4793      */
4794     select : function(color){
4795         color = color.replace("#", "");
4796         if(color != this.value || this.allowReselect){
4797             var el = this.el;
4798             if(this.value){
4799                 el.child("a.color-"+this.value).removeClass("x-color-palette-sel");
4800             }
4801             el.child("a.color-"+color).addClass("x-color-palette-sel");
4802             this.value = color;
4803             this.fireEvent("select", this, color);
4804         }
4805     }
4806 });/*
4807  * Based on:
4808  * Ext JS Library 1.1.1
4809  * Copyright(c) 2006-2007, Ext JS, LLC.
4810  *
4811  * Originally Released Under LGPL - original licence link has changed is not relivant.
4812  *
4813  * Fork - LGPL
4814  * <script type="text/javascript">
4815  */
4816  
4817 /**
4818  * @class Roo.DatePicker
4819  * @extends Roo.Component
4820  * Simple date picker class.
4821  * @constructor
4822  * Create a new DatePicker
4823  * @param {Object} config The config object
4824  */
4825 Roo.DatePicker = function(config){
4826     Roo.DatePicker.superclass.constructor.call(this, config);
4827
4828     this.value = config && config.value ?
4829                  config.value.clearTime() : new Date().clearTime();
4830
4831     this.addEvents({
4832         /**
4833              * @event select
4834              * Fires when a date is selected
4835              * @param {DatePicker} this
4836              * @param {Date} date The selected date
4837              */
4838         'select': true,
4839         /**
4840              * @event monthchange
4841              * Fires when the displayed month changes 
4842              * @param {DatePicker} this
4843              * @param {Date} date The selected month
4844              */
4845         'monthchange': true
4846     });
4847
4848     if(this.handler){
4849         this.on("select", this.handler,  this.scope || this);
4850     }
4851     // build the disabledDatesRE
4852     if(!this.disabledDatesRE && this.disabledDates){
4853         var dd = this.disabledDates;
4854         var re = "(?:";
4855         for(var i = 0; i < dd.length; i++){
4856             re += dd[i];
4857             if(i != dd.length-1) {
4858                 re += "|";
4859             }
4860         }
4861         this.disabledDatesRE = new RegExp(re + ")");
4862     }
4863 };
4864
4865 Roo.extend(Roo.DatePicker, Roo.Component, {
4866     /**
4867      * @cfg {String} todayText
4868      * The text to display on the button that selects the current date (defaults to "Today")
4869      */
4870     todayText : "Today",
4871     /**
4872      * @cfg {String} okText
4873      * The text to display on the ok button
4874      */
4875     okText : "&#160;OK&#160;", // &#160; to give the user extra clicking room
4876     /**
4877      * @cfg {String} cancelText
4878      * The text to display on the cancel button
4879      */
4880     cancelText : "Cancel",
4881     /**
4882      * @cfg {String} todayTip
4883      * The tooltip to display for the button that selects the current date (defaults to "{current date} (Spacebar)")
4884      */
4885     todayTip : "{0} (Spacebar)",
4886     /**
4887      * @cfg {Date} minDate
4888      * Minimum allowable date (JavaScript date object, defaults to null)
4889      */
4890     minDate : null,
4891     /**
4892      * @cfg {Date} maxDate
4893      * Maximum allowable date (JavaScript date object, defaults to null)
4894      */
4895     maxDate : null,
4896     /**
4897      * @cfg {String} minText
4898      * The error text to display if the minDate validation fails (defaults to "This date is before the minimum date")
4899      */
4900     minText : "This date is before the minimum date",
4901     /**
4902      * @cfg {String} maxText
4903      * The error text to display if the maxDate validation fails (defaults to "This date is after the maximum date")
4904      */
4905     maxText : "This date is after the maximum date",
4906     /**
4907      * @cfg {String} format
4908      * The default date format string which can be overriden for localization support.  The format must be
4909      * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
4910      */
4911     format : "m/d/y",
4912     /**
4913      * @cfg {Array} disabledDays
4914      * An array of days to disable, 0-based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
4915      */
4916     disabledDays : null,
4917     /**
4918      * @cfg {String} disabledDaysText
4919      * The tooltip to display when the date falls on a disabled day (defaults to "")
4920      */
4921     disabledDaysText : "",
4922     /**
4923      * @cfg {RegExp} disabledDatesRE
4924      * JavaScript regular expression used to disable a pattern of dates (defaults to null)
4925      */
4926     disabledDatesRE : null,
4927     /**
4928      * @cfg {String} disabledDatesText
4929      * The tooltip text to display when the date falls on a disabled date (defaults to "")
4930      */
4931     disabledDatesText : "",
4932     /**
4933      * @cfg {Boolean} constrainToViewport
4934      * True to constrain the date picker to the viewport (defaults to true)
4935      */
4936     constrainToViewport : true,
4937     /**
4938      * @cfg {Array} monthNames
4939      * An array of textual month names which can be overriden for localization support (defaults to Date.monthNames)
4940      */
4941     monthNames : Date.monthNames,
4942     /**
4943      * @cfg {Array} dayNames
4944      * An array of textual day names which can be overriden for localization support (defaults to Date.dayNames)
4945      */
4946     dayNames : Date.dayNames,
4947     /**
4948      * @cfg {String} nextText
4949      * The next month navigation button tooltip (defaults to 'Next Month (Control+Right)')
4950      */
4951     nextText: 'Next Month (Control+Right)',
4952     /**
4953      * @cfg {String} prevText
4954      * The previous month navigation button tooltip (defaults to 'Previous Month (Control+Left)')
4955      */
4956     prevText: 'Previous Month (Control+Left)',
4957     /**
4958      * @cfg {String} monthYearText
4959      * The header month selector tooltip (defaults to 'Choose a month (Control+Up/Down to move years)')
4960      */
4961     monthYearText: 'Choose a month (Control+Up/Down to move years)',
4962     /**
4963      * @cfg {Number} startDay
4964      * Day index at which the week should begin, 0-based (defaults to 0, which is Sunday)
4965      */
4966     startDay : 0,
4967     /**
4968      * @cfg {Bool} showClear
4969      * Show a clear button (usefull for date form elements that can be blank.)
4970      */
4971     
4972     showClear: false,
4973     
4974     /**
4975      * Sets the value of the date field
4976      * @param {Date} value The date to set
4977      */
4978     setValue : function(value){
4979         var old = this.value;
4980         
4981         if (typeof(value) == 'string') {
4982          
4983             value = Date.parseDate(value, this.format);
4984         }
4985         if (!value) {
4986             value = new Date();
4987         }
4988         
4989         this.value = value.clearTime(true);
4990         if(this.el){
4991             this.update(this.value);
4992         }
4993     },
4994
4995     /**
4996      * Gets the current selected value of the date field
4997      * @return {Date} The selected date
4998      */
4999     getValue : function(){
5000         return this.value;
5001     },
5002
5003     // private
5004     focus : function(){
5005         if(this.el){
5006             this.update(this.activeDate);
5007         }
5008     },
5009
5010     // privateval
5011     onRender : function(container, position){
5012         
5013         var m = [
5014              '<table cellspacing="0">',
5015                 '<tr><td class="x-date-left"><a href="#" title="', this.prevText ,'">&#160;</a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="', this.nextText ,'">&#160;</a></td></tr>',
5016                 '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'];
5017         var dn = this.dayNames;
5018         for(var i = 0; i < 7; i++){
5019             var d = this.startDay+i;
5020             if(d > 6){
5021                 d = d-7;
5022             }
5023             m.push("<th><span>", dn[d].substr(0,1), "</span></th>");
5024         }
5025         m[m.length] = "</tr></thead><tbody><tr>";
5026         for(var i = 0; i < 42; i++) {
5027             if(i % 7 == 0 && i != 0){
5028                 m[m.length] = "</tr><tr>";
5029             }
5030             m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';
5031         }
5032         m[m.length] = '</tr></tbody></table></td></tr><tr>'+
5033             '<td colspan="3" class="x-date-bottom" align="center"></td></tr></table><div class="x-date-mp"></div>';
5034
5035         var el = document.createElement("div");
5036         el.className = "x-date-picker";
5037         el.innerHTML = m.join("");
5038
5039         container.dom.insertBefore(el, position);
5040
5041         this.el = Roo.get(el);
5042         this.eventEl = Roo.get(el.firstChild);
5043
5044         new Roo.util.ClickRepeater(this.el.child("td.x-date-left a"), {
5045             handler: this.showPrevMonth,
5046             scope: this,
5047             preventDefault:true,
5048             stopDefault:true
5049         });
5050
5051         new Roo.util.ClickRepeater(this.el.child("td.x-date-right a"), {
5052             handler: this.showNextMonth,
5053             scope: this,
5054             preventDefault:true,
5055             stopDefault:true
5056         });
5057
5058         this.eventEl.on("mousewheel", this.handleMouseWheel,  this);
5059
5060         this.monthPicker = this.el.down('div.x-date-mp');
5061         this.monthPicker.enableDisplayMode('block');
5062         
5063         var kn = new Roo.KeyNav(this.eventEl, {
5064             "left" : function(e){
5065                 e.ctrlKey ?
5066                     this.showPrevMonth() :
5067                     this.update(this.activeDate.add("d", -1));
5068             },
5069
5070             "right" : function(e){
5071                 e.ctrlKey ?
5072                     this.showNextMonth() :
5073                     this.update(this.activeDate.add("d", 1));
5074             },
5075
5076             "up" : function(e){
5077                 e.ctrlKey ?
5078                     this.showNextYear() :
5079                     this.update(this.activeDate.add("d", -7));
5080             },
5081
5082             "down" : function(e){
5083                 e.ctrlKey ?
5084                     this.showPrevYear() :
5085                     this.update(this.activeDate.add("d", 7));
5086             },
5087
5088             "pageUp" : function(e){
5089                 this.showNextMonth();
5090             },
5091
5092             "pageDown" : function(e){
5093                 this.showPrevMonth();
5094             },
5095
5096             "enter" : function(e){
5097                 e.stopPropagation();
5098                 return true;
5099             },
5100
5101             scope : this
5102         });
5103
5104         this.eventEl.on("click", this.handleDateClick,  this, {delegate: "a.x-date-date"});
5105
5106         this.eventEl.addKeyListener(Roo.EventObject.SPACE, this.selectToday,  this);
5107
5108         this.el.unselectable();
5109         
5110         this.cells = this.el.select("table.x-date-inner tbody td");
5111         this.textNodes = this.el.query("table.x-date-inner tbody span");
5112
5113         this.mbtn = new Roo.Button(this.el.child("td.x-date-middle", true), {
5114             text: "&#160;",
5115             tooltip: this.monthYearText
5116         });
5117
5118         this.mbtn.on('click', this.showMonthPicker, this);
5119         this.mbtn.el.child(this.mbtn.menuClassTarget).addClass("x-btn-with-menu");
5120
5121
5122         var today = (new Date()).dateFormat(this.format);
5123         
5124         var baseTb = new Roo.Toolbar(this.el.child("td.x-date-bottom", true));
5125         if (this.showClear) {
5126             baseTb.add( new Roo.Toolbar.Fill());
5127         }
5128         baseTb.add({
5129             text: String.format(this.todayText, today),
5130             tooltip: String.format(this.todayTip, today),
5131             handler: this.selectToday,
5132             scope: this
5133         });
5134         
5135         //var todayBtn = new Roo.Button(this.el.child("td.x-date-bottom", true), {
5136             
5137         //});
5138         if (this.showClear) {
5139             
5140             baseTb.add( new Roo.Toolbar.Fill());
5141             baseTb.add({
5142                 text: '&#160;',
5143                 cls: 'x-btn-icon x-btn-clear',
5144                 handler: function() {
5145                     //this.value = '';
5146                     this.fireEvent("select", this, '');
5147                 },
5148                 scope: this
5149             });
5150         }
5151         
5152         
5153         if(Roo.isIE){
5154             this.el.repaint();
5155         }
5156         this.update(this.value);
5157     },
5158
5159     createMonthPicker : function(){
5160         if(!this.monthPicker.dom.firstChild){
5161             var buf = ['<table border="0" cellspacing="0">'];
5162             for(var i = 0; i < 6; i++){
5163                 buf.push(
5164                     '<tr><td class="x-date-mp-month"><a href="#">', this.monthNames[i].substr(0, 3), '</a></td>',
5165                     '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', this.monthNames[i+6].substr(0, 3), '</a></td>',
5166                     i == 0 ?
5167                     '<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>' :
5168                     '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
5169                 );
5170             }
5171             buf.push(
5172                 '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
5173                     this.okText,
5174                     '</button><button type="button" class="x-date-mp-cancel">',
5175                     this.cancelText,
5176                     '</button></td></tr>',
5177                 '</table>'
5178             );
5179             this.monthPicker.update(buf.join(''));
5180             this.monthPicker.on('click', this.onMonthClick, this);
5181             this.monthPicker.on('dblclick', this.onMonthDblClick, this);
5182
5183             this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
5184             this.mpYears = this.monthPicker.select('td.x-date-mp-year');
5185
5186             this.mpMonths.each(function(m, a, i){
5187                 i += 1;
5188                 if((i%2) == 0){
5189                     m.dom.xmonth = 5 + Math.round(i * .5);
5190                 }else{
5191                     m.dom.xmonth = Math.round((i-1) * .5);
5192                 }
5193             });
5194         }
5195     },
5196
5197     showMonthPicker : function(){
5198         this.createMonthPicker();
5199         var size = this.el.getSize();
5200         this.monthPicker.setSize(size);
5201         this.monthPicker.child('table').setSize(size);
5202
5203         this.mpSelMonth = (this.activeDate || this.value).getMonth();
5204         this.updateMPMonth(this.mpSelMonth);
5205         this.mpSelYear = (this.activeDate || this.value).getFullYear();
5206         this.updateMPYear(this.mpSelYear);
5207
5208         this.monthPicker.slideIn('t', {duration:.2});
5209     },
5210
5211     updateMPYear : function(y){
5212         this.mpyear = y;
5213         var ys = this.mpYears.elements;
5214         for(var i = 1; i <= 10; i++){
5215             var td = ys[i-1], y2;
5216             if((i%2) == 0){
5217                 y2 = y + Math.round(i * .5);
5218                 td.firstChild.innerHTML = y2;
5219                 td.xyear = y2;
5220             }else{
5221                 y2 = y - (5-Math.round(i * .5));
5222                 td.firstChild.innerHTML = y2;
5223                 td.xyear = y2;
5224             }
5225             this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
5226         }
5227     },
5228
5229     updateMPMonth : function(sm){
5230         this.mpMonths.each(function(m, a, i){
5231             m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
5232         });
5233     },
5234
5235     selectMPMonth: function(m){
5236         
5237     },
5238
5239     onMonthClick : function(e, t){
5240         e.stopEvent();
5241         var el = new Roo.Element(t), pn;
5242         if(el.is('button.x-date-mp-cancel')){
5243             this.hideMonthPicker();
5244         }
5245         else if(el.is('button.x-date-mp-ok')){
5246             this.update(new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
5247             this.hideMonthPicker();
5248         }
5249         else if(pn = el.up('td.x-date-mp-month', 2)){
5250             this.mpMonths.removeClass('x-date-mp-sel');
5251             pn.addClass('x-date-mp-sel');
5252             this.mpSelMonth = pn.dom.xmonth;
5253         }
5254         else if(pn = el.up('td.x-date-mp-year', 2)){
5255             this.mpYears.removeClass('x-date-mp-sel');
5256             pn.addClass('x-date-mp-sel');
5257             this.mpSelYear = pn.dom.xyear;
5258         }
5259         else if(el.is('a.x-date-mp-prev')){
5260             this.updateMPYear(this.mpyear-10);
5261         }
5262         else if(el.is('a.x-date-mp-next')){
5263             this.updateMPYear(this.mpyear+10);
5264         }
5265     },
5266
5267     onMonthDblClick : function(e, t){
5268         e.stopEvent();
5269         var el = new Roo.Element(t), pn;
5270         if(pn = el.up('td.x-date-mp-month', 2)){
5271             this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
5272             this.hideMonthPicker();
5273         }
5274         else if(pn = el.up('td.x-date-mp-year', 2)){
5275             this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
5276             this.hideMonthPicker();
5277         }
5278     },
5279
5280     hideMonthPicker : function(disableAnim){
5281         if(this.monthPicker){
5282             if(disableAnim === true){
5283                 this.monthPicker.hide();
5284             }else{
5285                 this.monthPicker.slideOut('t', {duration:.2});
5286             }
5287         }
5288     },
5289
5290     // private
5291     showPrevMonth : function(e){
5292         this.update(this.activeDate.add("mo", -1));
5293     },
5294
5295     // private
5296     showNextMonth : function(e){
5297         this.update(this.activeDate.add("mo", 1));
5298     },
5299
5300     // private
5301     showPrevYear : function(){
5302         this.update(this.activeDate.add("y", -1));
5303     },
5304
5305     // private
5306     showNextYear : function(){
5307         this.update(this.activeDate.add("y", 1));
5308     },
5309
5310     // private
5311     handleMouseWheel : function(e){
5312         var delta = e.getWheelDelta();
5313         if(delta > 0){
5314             this.showPrevMonth();
5315             e.stopEvent();
5316         } else if(delta < 0){
5317             this.showNextMonth();
5318             e.stopEvent();
5319         }
5320     },
5321
5322     // private
5323     handleDateClick : function(e, t){
5324         e.stopEvent();
5325         if(t.dateValue && !Roo.fly(t.parentNode).hasClass("x-date-disabled")){
5326             this.setValue(new Date(t.dateValue));
5327             this.fireEvent("select", this, this.value);
5328         }
5329     },
5330
5331     // private
5332     selectToday : function(){
5333         this.setValue(new Date().clearTime());
5334         this.fireEvent("select", this, this.value);
5335     },
5336
5337     // private
5338     update : function(date)
5339     {
5340         var vd = this.activeDate;
5341         this.activeDate = date;
5342         if(vd && this.el){
5343             var t = date.getTime();
5344             if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
5345                 this.cells.removeClass("x-date-selected");
5346                 this.cells.each(function(c){
5347                    if(c.dom.firstChild.dateValue == t){
5348                        c.addClass("x-date-selected");
5349                        setTimeout(function(){
5350                             try{c.dom.firstChild.focus();}catch(e){}
5351                        }, 50);
5352                        return false;
5353                    }
5354                 });
5355                 return;
5356             }
5357         }
5358         
5359         var days = date.getDaysInMonth();
5360         var firstOfMonth = date.getFirstDateOfMonth();
5361         var startingPos = firstOfMonth.getDay()-this.startDay;
5362
5363         if(startingPos <= this.startDay){
5364             startingPos += 7;
5365         }
5366
5367         var pm = date.add("mo", -1);
5368         var prevStart = pm.getDaysInMonth()-startingPos;
5369
5370         var cells = this.cells.elements;
5371         var textEls = this.textNodes;
5372         days += startingPos;
5373
5374         // convert everything to numbers so it's fast
5375         var day = 86400000;
5376         var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
5377         var today = new Date().clearTime().getTime();
5378         var sel = date.clearTime().getTime();
5379         var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
5380         var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
5381         var ddMatch = this.disabledDatesRE;
5382         var ddText = this.disabledDatesText;
5383         var ddays = this.disabledDays ? this.disabledDays.join("") : false;
5384         var ddaysText = this.disabledDaysText;
5385         var format = this.format;
5386
5387         var setCellClass = function(cal, cell){
5388             cell.title = "";
5389             var t = d.getTime();
5390             cell.firstChild.dateValue = t;
5391             if(t == today){
5392                 cell.className += " x-date-today";
5393                 cell.title = cal.todayText;
5394             }
5395             if(t == sel){
5396                 cell.className += " x-date-selected";
5397                 setTimeout(function(){
5398                     try{cell.firstChild.focus();}catch(e){}
5399                 }, 50);
5400             }
5401             // disabling
5402             if(t < min) {
5403                 cell.className = " x-date-disabled";
5404                 cell.title = cal.minText;
5405                 return;
5406             }
5407             if(t > max) {
5408                 cell.className = " x-date-disabled";
5409                 cell.title = cal.maxText;
5410                 return;
5411             }
5412             if(ddays){
5413                 if(ddays.indexOf(d.getDay()) != -1){
5414                     cell.title = ddaysText;
5415                     cell.className = " x-date-disabled";
5416                 }
5417             }
5418             if(ddMatch && format){
5419                 var fvalue = d.dateFormat(format);
5420                 if(ddMatch.test(fvalue)){
5421                     cell.title = ddText.replace("%0", fvalue);
5422                     cell.className = " x-date-disabled";
5423                 }
5424             }
5425         };
5426
5427         var i = 0;
5428         for(; i < startingPos; i++) {
5429             textEls[i].innerHTML = (++prevStart);
5430             d.setDate(d.getDate()+1);
5431             cells[i].className = "x-date-prevday";
5432             setCellClass(this, cells[i]);
5433         }
5434         for(; i < days; i++){
5435             intDay = i - startingPos + 1;
5436             textEls[i].innerHTML = (intDay);
5437             d.setDate(d.getDate()+1);
5438             cells[i].className = "x-date-active";
5439             setCellClass(this, cells[i]);
5440         }
5441         var extraDays = 0;
5442         for(; i < 42; i++) {
5443              textEls[i].innerHTML = (++extraDays);
5444              d.setDate(d.getDate()+1);
5445              cells[i].className = "x-date-nextday";
5446              setCellClass(this, cells[i]);
5447         }
5448
5449         this.mbtn.setText(this.monthNames[date.getMonth()] + " " + date.getFullYear());
5450         this.fireEvent('monthchange', this, date);
5451         
5452         if(!this.internalRender){
5453             var main = this.el.dom.firstChild;
5454             var w = main.offsetWidth;
5455             this.el.setWidth(w + this.el.getBorderWidth("lr"));
5456             Roo.fly(main).setWidth(w);
5457             this.internalRender = true;
5458             // opera does not respect the auto grow header center column
5459             // then, after it gets a width opera refuses to recalculate
5460             // without a second pass
5461             if(Roo.isOpera && !this.secondPass){
5462                 main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + "px";
5463                 this.secondPass = true;
5464                 this.update.defer(10, this, [date]);
5465             }
5466         }
5467         
5468         
5469     }
5470 });        /*
5471  * Based on:
5472  * Ext JS Library 1.1.1
5473  * Copyright(c) 2006-2007, Ext JS, LLC.
5474  *
5475  * Originally Released Under LGPL - original licence link has changed is not relivant.
5476  *
5477  * Fork - LGPL
5478  * <script type="text/javascript">
5479  */
5480 /**
5481  * @class Roo.TabPanel
5482  * @extends Roo.util.Observable
5483  * A lightweight tab container.
5484  * <br><br>
5485  * Usage:
5486  * <pre><code>
5487 // basic tabs 1, built from existing content
5488 var tabs = new Roo.TabPanel("tabs1");
5489 tabs.addTab("script", "View Script");
5490 tabs.addTab("markup", "View Markup");
5491 tabs.activate("script");
5492
5493 // more advanced tabs, built from javascript
5494 var jtabs = new Roo.TabPanel("jtabs");
5495 jtabs.addTab("jtabs-1", "Normal Tab", "My content was added during construction.");
5496
5497 // set up the UpdateManager
5498 var tab2 = jtabs.addTab("jtabs-2", "Ajax Tab 1");
5499 var updater = tab2.getUpdateManager();
5500 updater.setDefaultUrl("ajax1.htm");
5501 tab2.on('activate', updater.refresh, updater, true);
5502
5503 // Use setUrl for Ajax loading
5504 var tab3 = jtabs.addTab("jtabs-3", "Ajax Tab 2");
5505 tab3.setUrl("ajax2.htm", null, true);
5506
5507 // Disabled tab
5508 var tab4 = jtabs.addTab("tabs1-5", "Disabled Tab", "Can't see me cause I'm disabled");
5509 tab4.disable();
5510
5511 jtabs.activate("jtabs-1");
5512  * </code></pre>
5513  * @constructor
5514  * Create a new TabPanel.
5515  * @param {String/HTMLElement/Roo.Element} container The id, DOM element or Roo.Element container where this TabPanel is to be rendered.
5516  * @param {Object/Boolean} config Config object to set any properties for this TabPanel, or true to render the tabs on the bottom.
5517  */
5518 Roo.TabPanel = function(container, config){
5519     /**
5520     * The container element for this TabPanel.
5521     * @type Roo.Element
5522     */
5523     this.el = Roo.get(container, true);
5524     if(config){
5525         if(typeof config == "boolean"){
5526             this.tabPosition = config ? "bottom" : "top";
5527         }else{
5528             Roo.apply(this, config);
5529         }
5530     }
5531     if(this.tabPosition == "bottom"){
5532         this.bodyEl = Roo.get(this.createBody(this.el.dom));
5533         this.el.addClass("x-tabs-bottom");
5534     }
5535     this.stripWrap = Roo.get(this.createStrip(this.el.dom), true);
5536     this.stripEl = Roo.get(this.createStripList(this.stripWrap.dom), true);
5537     this.stripBody = Roo.get(this.stripWrap.dom.firstChild.firstChild, true);
5538     if(Roo.isIE){
5539         Roo.fly(this.stripWrap.dom.firstChild).setStyle("overflow-x", "hidden");
5540     }
5541     if(this.tabPosition != "bottom"){
5542         /** The body element that contains {@link Roo.TabPanelItem} bodies. +
5543          * @type Roo.Element
5544          */
5545         this.bodyEl = Roo.get(this.createBody(this.el.dom));
5546         this.el.addClass("x-tabs-top");
5547     }
5548     this.items = [];
5549
5550     this.bodyEl.setStyle("position", "relative");
5551
5552     this.active = null;
5553     this.activateDelegate = this.activate.createDelegate(this);
5554
5555     this.addEvents({
5556         /**
5557          * @event tabchange
5558          * Fires when the active tab changes
5559          * @param {Roo.TabPanel} this
5560          * @param {Roo.TabPanelItem} activePanel The new active tab
5561          */
5562         "tabchange": true,
5563         /**
5564          * @event beforetabchange
5565          * Fires before the active tab changes, set cancel to true on the "e" parameter to cancel the change
5566          * @param {Roo.TabPanel} this
5567          * @param {Object} e Set cancel to true on this object to cancel the tab change
5568          * @param {Roo.TabPanelItem} tab The tab being changed to
5569          */
5570         "beforetabchange" : true
5571     });
5572
5573     Roo.EventManager.onWindowResize(this.onResize, this);
5574     this.cpad = this.el.getPadding("lr");
5575     this.hiddenCount = 0;
5576
5577
5578     // toolbar on the tabbar support...
5579     if (this.toolbar) {
5580         var tcfg = this.toolbar;
5581         tcfg.container = this.stripEl.child('td.x-tab-strip-toolbar');  
5582         this.toolbar = new Roo.Toolbar(tcfg);
5583         if (Roo.isSafari) {
5584             var tbl = tcfg.container.child('table', true);
5585             tbl.setAttribute('width', '100%');
5586         }
5587         
5588     }
5589    
5590
5591
5592     Roo.TabPanel.superclass.constructor.call(this);
5593 };
5594
5595 Roo.extend(Roo.TabPanel, Roo.util.Observable, {
5596     /*
5597      *@cfg {String} tabPosition "top" or "bottom" (defaults to "top")
5598      */
5599     tabPosition : "top",
5600     /*
5601      *@cfg {Number} currentTabWidth The width of the current tab (defaults to 0)
5602      */
5603     currentTabWidth : 0,
5604     /*
5605      *@cfg {Number} minTabWidth The minimum width of a tab (defaults to 40) (ignored if {@link #resizeTabs} is not true)
5606      */
5607     minTabWidth : 40,
5608     /*
5609      *@cfg {Number} maxTabWidth The maximum width of a tab (defaults to 250) (ignored if {@link #resizeTabs} is not true)
5610      */
5611     maxTabWidth : 250,
5612     /*
5613      *@cfg {Number} preferredTabWidth The preferred (default) width of a tab (defaults to 175) (ignored if {@link #resizeTabs} is not true)
5614      */
5615     preferredTabWidth : 175,
5616     /*
5617      *@cfg {Boolean} resizeTabs True to enable dynamic tab resizing (defaults to false)
5618      */
5619     resizeTabs : false,
5620     /*
5621      *@cfg {Boolean} monitorResize Set this to true to turn on window resize monitoring (ignored if {@link #resizeTabs} is not true) (defaults to true)
5622      */
5623     monitorResize : true,
5624     /*
5625      *@cfg {Object} toolbar xtype description of toolbar to show at the right of the tab bar. 
5626      */
5627     toolbar : false,
5628
5629     /**
5630      * Creates a new {@link Roo.TabPanelItem} by looking for an existing element with the provided id -- if it's not found it creates one.
5631      * @param {String} id The id of the div to use <b>or create</b>
5632      * @param {String} text The text for the tab
5633      * @param {String} content (optional) Content to put in the TabPanelItem body
5634      * @param {Boolean} closable (optional) True to create a close icon on the tab
5635      * @return {Roo.TabPanelItem} The created TabPanelItem
5636      */
5637     addTab : function(id, text, content, closable){
5638         var item = new Roo.TabPanelItem(this, id, text, closable);
5639         this.addTabItem(item);
5640         if(content){
5641             item.setContent(content);
5642         }
5643         return item;
5644     },
5645
5646     /**
5647      * Returns the {@link Roo.TabPanelItem} with the specified id/index
5648      * @param {String/Number} id The id or index of the TabPanelItem to fetch.
5649      * @return {Roo.TabPanelItem}
5650      */
5651     getTab : function(id){
5652         return this.items[id];
5653     },
5654
5655     /**
5656      * Hides the {@link Roo.TabPanelItem} with the specified id/index
5657      * @param {String/Number} id The id or index of the TabPanelItem to hide.
5658      */
5659     hideTab : function(id){
5660         var t = this.items[id];
5661         if(!t.isHidden()){
5662            t.setHidden(true);
5663            this.hiddenCount++;
5664            this.autoSizeTabs();
5665         }
5666     },
5667
5668     /**
5669      * "Unhides" the {@link Roo.TabPanelItem} with the specified id/index.
5670      * @param {String/Number} id The id or index of the TabPanelItem to unhide.
5671      */
5672     unhideTab : function(id){
5673         var t = this.items[id];
5674         if(t.isHidden()){
5675            t.setHidden(false);
5676            this.hiddenCount--;
5677            this.autoSizeTabs();
5678         }
5679     },
5680
5681     /**
5682      * Adds an existing {@link Roo.TabPanelItem}.
5683      * @param {Roo.TabPanelItem} item The TabPanelItem to add
5684      */
5685     addTabItem : function(item){
5686         this.items[item.id] = item;
5687         this.items.push(item);
5688         if(this.resizeTabs){
5689            item.setWidth(this.currentTabWidth || this.preferredTabWidth);
5690            this.autoSizeTabs();
5691         }else{
5692             item.autoSize();
5693         }
5694     },
5695
5696     /**
5697      * Removes a {@link Roo.TabPanelItem}.
5698      * @param {String/Number} id The id or index of the TabPanelItem to remove.
5699      */
5700     removeTab : function(id){
5701         var items = this.items;
5702         var tab = items[id];
5703         if(!tab) { return; }
5704         var index = items.indexOf(tab);
5705         if(this.active == tab && items.length > 1){
5706             var newTab = this.getNextAvailable(index);
5707             if(newTab) {
5708                 newTab.activate();
5709             }
5710         }
5711         this.stripEl.dom.removeChild(tab.pnode.dom);
5712         if(tab.bodyEl.dom.parentNode == this.bodyEl.dom){ // if it was moved already prevent error
5713             this.bodyEl.dom.removeChild(tab.bodyEl.dom);
5714         }
5715         items.splice(index, 1);
5716         delete this.items[tab.id];
5717         tab.fireEvent("close", tab);
5718         tab.purgeListeners();
5719         this.autoSizeTabs();
5720     },
5721
5722     getNextAvailable : function(start){
5723         var items = this.items;
5724         var index = start;
5725         // look for a next tab that will slide over to
5726         // replace the one being removed
5727         while(index < items.length){
5728             var item = items[++index];
5729             if(item && !item.isHidden()){
5730                 return item;
5731             }
5732         }
5733         // if one isn't found select the previous tab (on the left)
5734         index = start;
5735         while(index >= 0){
5736             var item = items[--index];
5737             if(item && !item.isHidden()){
5738                 return item;
5739             }
5740         }
5741         return null;
5742     },
5743
5744     /**
5745      * Disables a {@link Roo.TabPanelItem}. It cannot be the active tab, if it is this call is ignored.
5746      * @param {String/Number} id The id or index of the TabPanelItem to disable.
5747      */
5748     disableTab : function(id){
5749         var tab = this.items[id];
5750         if(tab && this.active != tab){
5751             tab.disable();
5752         }
5753     },
5754
5755     /**
5756      * Enables a {@link Roo.TabPanelItem} that is disabled.
5757      * @param {String/Number} id The id or index of the TabPanelItem to enable.
5758      */
5759     enableTab : function(id){
5760         var tab = this.items[id];
5761         tab.enable();
5762     },
5763
5764     /**
5765      * Activates a {@link Roo.TabPanelItem}. The currently active one will be deactivated.
5766      * @param {String/Number} id The id or index of the TabPanelItem to activate.
5767      * @return {Roo.TabPanelItem} The TabPanelItem.
5768      */
5769     activate : function(id){
5770         var tab = this.items[id];
5771         if(!tab){
5772             return null;
5773         }
5774         if(tab == this.active || tab.disabled){
5775             return tab;
5776         }
5777         var e = {};
5778         this.fireEvent("beforetabchange", this, e, tab);
5779         if(e.cancel !== true && !tab.disabled){
5780             if(this.active){
5781                 this.active.hide();
5782             }
5783             this.active = this.items[id];
5784             this.active.show();
5785             this.fireEvent("tabchange", this, this.active);
5786         }
5787         return tab;
5788     },
5789
5790     /**
5791      * Gets the active {@link Roo.TabPanelItem}.
5792      * @return {Roo.TabPanelItem} The active TabPanelItem or null if none are active.
5793      */
5794     getActiveTab : function(){
5795         return this.active;
5796     },
5797
5798     /**
5799      * Updates the tab body element to fit the height of the container element
5800      * for overflow scrolling
5801      * @param {Number} targetHeight (optional) Override the starting height from the elements height
5802      */
5803     syncHeight : function(targetHeight){
5804         var height = (targetHeight || this.el.getHeight())-this.el.getBorderWidth("tb")-this.el.getPadding("tb");
5805         var bm = this.bodyEl.getMargins();
5806         var newHeight = height-(this.stripWrap.getHeight()||0)-(bm.top+bm.bottom);
5807         this.bodyEl.setHeight(newHeight);
5808         return newHeight;
5809     },
5810
5811     onResize : function(){
5812         if(this.monitorResize){
5813             this.autoSizeTabs();
5814         }
5815     },
5816
5817     /**
5818      * Disables tab resizing while tabs are being added (if {@link #resizeTabs} is false this does nothing)
5819      */
5820     beginUpdate : function(){
5821         this.updating = true;
5822     },
5823
5824     /**
5825      * Stops an update and resizes the tabs (if {@link #resizeTabs} is false this does nothing)
5826      */
5827     endUpdate : function(){
5828         this.updating = false;
5829         this.autoSizeTabs();
5830     },
5831
5832     /**
5833      * Manual call to resize the tabs (if {@link #resizeTabs} is false this does nothing)
5834      */
5835     autoSizeTabs : function(){
5836         var count = this.items.length;
5837         var vcount = count - this.hiddenCount;
5838         if(!this.resizeTabs || count < 1 || vcount < 1 || this.updating) {
5839             return;
5840         }
5841         var w = Math.max(this.el.getWidth() - this.cpad, 10);
5842         var availWidth = Math.floor(w / vcount);
5843         var b = this.stripBody;
5844         if(b.getWidth() > w){
5845             var tabs = this.items;
5846             this.setTabWidth(Math.max(availWidth, this.minTabWidth)-2);
5847             if(availWidth < this.minTabWidth){
5848                 /*if(!this.sleft){    // incomplete scrolling code
5849                     this.createScrollButtons();
5850                 }
5851                 this.showScroll();
5852                 this.stripClip.setWidth(w - (this.sleft.getWidth()+this.sright.getWidth()));*/
5853             }
5854         }else{
5855             if(this.currentTabWidth < this.preferredTabWidth){
5856                 this.setTabWidth(Math.min(availWidth, this.preferredTabWidth)-2);
5857             }
5858         }
5859     },
5860
5861     /**
5862      * Returns the number of tabs in this TabPanel.
5863      * @return {Number}
5864      */
5865      getCount : function(){
5866          return this.items.length;
5867      },
5868
5869     /**
5870      * Resizes all the tabs to the passed width
5871      * @param {Number} The new width
5872      */
5873     setTabWidth : function(width){
5874         this.currentTabWidth = width;
5875         for(var i = 0, len = this.items.length; i < len; i++) {
5876                 if(!this.items[i].isHidden()) {
5877                 this.items[i].setWidth(width);
5878             }
5879         }
5880     },
5881
5882     /**
5883      * Destroys this TabPanel
5884      * @param {Boolean} removeEl (optional) True to remove the element from the DOM as well (defaults to undefined)
5885      */
5886     destroy : function(removeEl){
5887         Roo.EventManager.removeResizeListener(this.onResize, this);
5888         for(var i = 0, len = this.items.length; i < len; i++){
5889             this.items[i].purgeListeners();
5890         }
5891         if(removeEl === true){
5892             this.el.update("");
5893             this.el.remove();
5894         }
5895     }
5896 });
5897
5898 /**
5899  * @class Roo.TabPanelItem
5900  * @extends Roo.util.Observable
5901  * Represents an individual item (tab plus body) in a TabPanel.
5902  * @param {Roo.TabPanel} tabPanel The {@link Roo.TabPanel} this TabPanelItem belongs to
5903  * @param {String} id The id of this TabPanelItem
5904  * @param {String} text The text for the tab of this TabPanelItem
5905  * @param {Boolean} closable True to allow this TabPanelItem to be closable (defaults to false)
5906  */
5907 Roo.TabPanelItem = function(tabPanel, id, text, closable){
5908     /**
5909      * The {@link Roo.TabPanel} this TabPanelItem belongs to
5910      * @type Roo.TabPanel
5911      */
5912     this.tabPanel = tabPanel;
5913     /**
5914      * The id for this TabPanelItem
5915      * @type String
5916      */
5917     this.id = id;
5918     /** @private */
5919     this.disabled = false;
5920     /** @private */
5921     this.text = text;
5922     /** @private */
5923     this.loaded = false;
5924     this.closable = closable;
5925
5926     /**
5927      * The body element for this TabPanelItem.
5928      * @type Roo.Element
5929      */
5930     this.bodyEl = Roo.get(tabPanel.createItemBody(tabPanel.bodyEl.dom, id));
5931     this.bodyEl.setVisibilityMode(Roo.Element.VISIBILITY);
5932     this.bodyEl.setStyle("display", "block");
5933     this.bodyEl.setStyle("zoom", "1");
5934     this.hideAction();
5935
5936     var els = tabPanel.createStripElements(tabPanel.stripEl.dom, text, closable);
5937     /** @private */
5938     this.el = Roo.get(els.el, true);
5939     this.inner = Roo.get(els.inner, true);
5940     this.textEl = Roo.get(this.el.dom.firstChild.firstChild.firstChild, true);
5941     this.pnode = Roo.get(els.el.parentNode, true);
5942     this.el.on("mousedown", this.onTabMouseDown, this);
5943     this.el.on("click", this.onTabClick, this);
5944     /** @private */
5945     if(closable){
5946         var c = Roo.get(els.close, true);
5947         c.dom.title = this.closeText;
5948         c.addClassOnOver("close-over");
5949         c.on("click", this.closeClick, this);
5950      }
5951
5952     this.addEvents({
5953          /**
5954          * @event activate
5955          * Fires when this tab becomes the active tab.
5956          * @param {Roo.TabPanel} tabPanel The parent TabPanel
5957          * @param {Roo.TabPanelItem} this
5958          */
5959         "activate": true,
5960         /**
5961          * @event beforeclose
5962          * Fires before this tab is closed. To cancel the close, set cancel to true on e (e.cancel = true).
5963          * @param {Roo.TabPanelItem} this
5964          * @param {Object} e Set cancel to true on this object to cancel the close.
5965          */
5966         "beforeclose": true,
5967         /**
5968          * @event close
5969          * Fires when this tab is closed.
5970          * @param {Roo.TabPanelItem} this
5971          */
5972          "close": true,
5973         /**
5974          * @event deactivate
5975          * Fires when this tab is no longer the active tab.
5976          * @param {Roo.TabPanel} tabPanel The parent TabPanel
5977          * @param {Roo.TabPanelItem} this
5978          */
5979          "deactivate" : true
5980     });
5981     this.hidden = false;
5982
5983     Roo.TabPanelItem.superclass.constructor.call(this);
5984 };
5985
5986 Roo.extend(Roo.TabPanelItem, Roo.util.Observable, {
5987     purgeListeners : function(){
5988        Roo.util.Observable.prototype.purgeListeners.call(this);
5989        this.el.removeAllListeners();
5990     },
5991     /**
5992      * Shows this TabPanelItem -- this <b>does not</b> deactivate the currently active TabPanelItem.
5993      */
5994     show : function(){
5995         this.pnode.addClass("on");
5996         this.showAction();
5997         if(Roo.isOpera){
5998             this.tabPanel.stripWrap.repaint();
5999         }
6000         this.fireEvent("activate", this.tabPanel, this);
6001     },
6002
6003     /**
6004      * Returns true if this tab is the active tab.
6005      * @return {Boolean}
6006      */
6007     isActive : function(){
6008         return this.tabPanel.getActiveTab() == this;
6009     },
6010
6011     /**
6012      * Hides this TabPanelItem -- if you don't activate another TabPanelItem this could look odd.
6013      */
6014     hide : function(){
6015         this.pnode.removeClass("on");
6016         this.hideAction();
6017         this.fireEvent("deactivate", this.tabPanel, this);
6018     },
6019
6020     hideAction : function(){
6021         this.bodyEl.hide();
6022         this.bodyEl.setStyle("position", "absolute");
6023         this.bodyEl.setLeft("-20000px");
6024         this.bodyEl.setTop("-20000px");
6025     },
6026
6027     showAction : function(){
6028         this.bodyEl.setStyle("position", "relative");
6029         this.bodyEl.setTop("");
6030         this.bodyEl.setLeft("");
6031         this.bodyEl.show();
6032     },
6033
6034     /**
6035      * Set the tooltip for the tab.
6036      * @param {String} tooltip The tab's tooltip
6037      */
6038     setTooltip : function(text){
6039         if(Roo.QuickTips && Roo.QuickTips.isEnabled()){
6040             this.textEl.dom.qtip = text;
6041             this.textEl.dom.removeAttribute('title');
6042         }else{
6043             this.textEl.dom.title = text;
6044         }
6045     },
6046
6047     onTabClick : function(e){
6048         e.preventDefault();
6049         this.tabPanel.activate(this.id);
6050     },
6051
6052     onTabMouseDown : function(e){
6053         e.preventDefault();
6054         this.tabPanel.activate(this.id);
6055     },
6056
6057     getWidth : function(){
6058         return this.inner.getWidth();
6059     },
6060
6061     setWidth : function(width){
6062         var iwidth = width - this.pnode.getPadding("lr");
6063         this.inner.setWidth(iwidth);
6064         this.textEl.setWidth(iwidth-this.inner.getPadding("lr"));
6065         this.pnode.setWidth(width);
6066     },
6067
6068     /**
6069      * Show or hide the tab
6070      * @param {Boolean} hidden True to hide or false to show.
6071      */
6072     setHidden : function(hidden){
6073         this.hidden = hidden;
6074         this.pnode.setStyle("display", hidden ? "none" : "");
6075     },
6076
6077     /**
6078      * Returns true if this tab is "hidden"
6079      * @return {Boolean}
6080      */
6081     isHidden : function(){
6082         return this.hidden;
6083     },
6084
6085     /**
6086      * Returns the text for this tab
6087      * @return {String}
6088      */
6089     getText : function(){
6090         return this.text;
6091     },
6092
6093     autoSize : function(){
6094         //this.el.beginMeasure();
6095         this.textEl.setWidth(1);
6096         /*
6097          *  #2804 [new] Tabs in Roojs
6098          *  increase the width by 2-4 pixels to prevent the ellipssis showing in chrome
6099          */
6100         this.setWidth(this.textEl.dom.scrollWidth+this.pnode.getPadding("lr")+this.inner.getPadding("lr") + 2);
6101         //this.el.endMeasure();
6102     },
6103
6104     /**
6105      * Sets the text for the tab (Note: this also sets the tooltip text)
6106      * @param {String} text The tab's text and tooltip
6107      */
6108     setText : function(text){
6109         this.text = text;
6110         this.textEl.update(text);
6111         this.setTooltip(text);
6112         if(!this.tabPanel.resizeTabs){
6113             this.autoSize();
6114         }
6115     },
6116     /**
6117      * Activates this TabPanelItem -- this <b>does</b> deactivate the currently active TabPanelItem.
6118      */
6119     activate : function(){
6120         this.tabPanel.activate(this.id);
6121     },
6122
6123     /**
6124      * Disables this TabPanelItem -- this does nothing if this is the active TabPanelItem.
6125      */
6126     disable : function(){
6127         if(this.tabPanel.active != this){
6128             this.disabled = true;
6129             this.pnode.addClass("disabled");
6130         }
6131     },
6132
6133     /**
6134      * Enables this TabPanelItem if it was previously disabled.
6135      */
6136     enable : function(){
6137         this.disabled = false;
6138         this.pnode.removeClass("disabled");
6139     },
6140
6141     /**
6142      * Sets the content for this TabPanelItem.
6143      * @param {String} content The content
6144      * @param {Boolean} loadScripts true to look for and load scripts
6145      */
6146     setContent : function(content, loadScripts){
6147         this.bodyEl.update(content, loadScripts);
6148     },
6149
6150     /**
6151      * Gets the {@link Roo.UpdateManager} for the body of this TabPanelItem. Enables you to perform Ajax updates.
6152      * @return {Roo.UpdateManager} The UpdateManager
6153      */
6154     getUpdateManager : function(){
6155         return this.bodyEl.getUpdateManager();
6156     },
6157
6158     /**
6159      * Set a URL to be used to load the content for this TabPanelItem.
6160      * @param {String/Function} url The URL to load the content from, or a function to call to get the URL
6161      * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Roo.UpdateManager#update} for more details. (Defaults to null)
6162      * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this TabPanelItem is activated. (Defaults to false)
6163      * @return {Roo.UpdateManager} The UpdateManager
6164      */
6165     setUrl : function(url, params, loadOnce){
6166         if(this.refreshDelegate){
6167             this.un('activate', this.refreshDelegate);
6168         }
6169         this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
6170         this.on("activate", this.refreshDelegate);
6171         return this.bodyEl.getUpdateManager();
6172     },
6173
6174     /** @private */
6175     _handleRefresh : function(url, params, loadOnce){
6176         if(!loadOnce || !this.loaded){
6177             var updater = this.bodyEl.getUpdateManager();
6178             updater.update(url, params, this._setLoaded.createDelegate(this));
6179         }
6180     },
6181
6182     /**
6183      *   Forces a content refresh from the URL specified in the {@link #setUrl} method.
6184      *   Will fail silently if the setUrl method has not been called.
6185      *   This does not activate the panel, just updates its content.
6186      */
6187     refresh : function(){
6188         if(this.refreshDelegate){
6189            this.loaded = false;
6190            this.refreshDelegate();
6191         }
6192     },
6193
6194     /** @private */
6195     _setLoaded : function(){
6196         this.loaded = true;
6197     },
6198
6199     /** @private */
6200     closeClick : function(e){
6201         var o = {};
6202         e.stopEvent();
6203         this.fireEvent("beforeclose", this, o);
6204         if(o.cancel !== true){
6205             this.tabPanel.removeTab(this.id);
6206         }
6207     },
6208     /**
6209      * The text displayed in the tooltip for the close icon.
6210      * @type String
6211      */
6212     closeText : "Close this tab"
6213 });
6214
6215 /** @private */
6216 Roo.TabPanel.prototype.createStrip = function(container){
6217     var strip = document.createElement("div");
6218     strip.className = "x-tabs-wrap";
6219     container.appendChild(strip);
6220     return strip;
6221 };
6222 /** @private */
6223 Roo.TabPanel.prototype.createStripList = function(strip){
6224     // div wrapper for retard IE
6225     // returns the "tr" element.
6226     strip.innerHTML = '<div class="x-tabs-strip-wrap">'+
6227         '<table class="x-tabs-strip" cellspacing="0" cellpadding="0" border="0"><tbody><tr>'+
6228         '<td class="x-tab-strip-toolbar"></td></tr></tbody></table></div>';
6229     return strip.firstChild.firstChild.firstChild.firstChild;
6230 };
6231 /** @private */
6232 Roo.TabPanel.prototype.createBody = function(container){
6233     var body = document.createElement("div");
6234     Roo.id(body, "tab-body");
6235     Roo.fly(body).addClass("x-tabs-body");
6236     container.appendChild(body);
6237     return body;
6238 };
6239 /** @private */
6240 Roo.TabPanel.prototype.createItemBody = function(bodyEl, id){
6241     var body = Roo.getDom(id);
6242     if(!body){
6243         body = document.createElement("div");
6244         body.id = id;
6245     }
6246     Roo.fly(body).addClass("x-tabs-item-body");
6247     bodyEl.insertBefore(body, bodyEl.firstChild);
6248     return body;
6249 };
6250 /** @private */
6251 Roo.TabPanel.prototype.createStripElements = function(stripEl, text, closable){
6252     var td = document.createElement("td");
6253     stripEl.insertBefore(td, stripEl.childNodes[stripEl.childNodes.length-1]);
6254     //stripEl.appendChild(td);
6255     if(closable){
6256         td.className = "x-tabs-closable";
6257         if(!this.closeTpl){
6258             this.closeTpl = new Roo.Template(
6259                '<a href="#" class="x-tabs-right"><span class="x-tabs-left"><em class="x-tabs-inner">' +
6260                '<span unselectable="on"' + (this.disableTooltips ? '' : ' title="{text}"') +' class="x-tabs-text">{text}</span>' +
6261                '<div unselectable="on" class="close-icon">&#160;</div></em></span></a>'
6262             );
6263         }
6264         var el = this.closeTpl.overwrite(td, {"text": text});
6265         var close = el.getElementsByTagName("div")[0];
6266         var inner = el.getElementsByTagName("em")[0];
6267         return {"el": el, "close": close, "inner": inner};
6268     } else {
6269         if(!this.tabTpl){
6270             this.tabTpl = new Roo.Template(
6271                '<a href="#" class="x-tabs-right"><span class="x-tabs-left"><em class="x-tabs-inner">' +
6272                '<span unselectable="on"' + (this.disableTooltips ? '' : ' title="{text}"') +' class="x-tabs-text">{text}</span></em></span></a>'
6273             );
6274         }
6275         var el = this.tabTpl.overwrite(td, {"text": text});
6276         var inner = el.getElementsByTagName("em")[0];
6277         return {"el": el, "inner": inner};
6278     }
6279 };/*
6280  * Based on:
6281  * Ext JS Library 1.1.1
6282  * Copyright(c) 2006-2007, Ext JS, LLC.
6283  *
6284  * Originally Released Under LGPL - original licence link has changed is not relivant.
6285  *
6286  * Fork - LGPL
6287  * <script type="text/javascript">
6288  */
6289
6290 /**
6291  * @class Roo.Button
6292  * @extends Roo.util.Observable
6293  * Simple Button class
6294  * @cfg {String} text The button text
6295  * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image
6296  * CSS property of the button by default, so if you want a mixed icon/text button, set cls:"x-btn-text-icon")
6297  * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event)
6298  * @cfg {Object} scope The scope of the handler
6299  * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width)
6300  * @cfg {String/Object} tooltip The tooltip for the button - can be a string or QuickTips config object
6301  * @cfg {Boolean} hidden True to start hidden (defaults to false)
6302  * @cfg {Boolean} disabled True to start disabled (defaults to false)
6303  * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
6304  * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed, only
6305    applies if enableToggle = true)
6306  * @cfg {String/HTMLElement/Element} renderTo The element to append the button to
6307  * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be
6308   an {@link Roo.util.ClickRepeater} config object (defaults to false).
6309  * @constructor
6310  * Create a new button
6311  * @param {Object} config The config object
6312  */
6313 Roo.Button = function(renderTo, config)
6314 {
6315     if (!config) {
6316         config = renderTo;
6317         renderTo = config.renderTo || false;
6318     }
6319     
6320     Roo.apply(this, config);
6321     this.addEvents({
6322         /**
6323              * @event click
6324              * Fires when this button is clicked
6325              * @param {Button} this
6326              * @param {EventObject} e The click event
6327              */
6328             "click" : true,
6329         /**
6330              * @event toggle
6331              * Fires when the "pressed" state of this button changes (only if enableToggle = true)
6332              * @param {Button} this
6333              * @param {Boolean} pressed
6334              */
6335             "toggle" : true,
6336         /**
6337              * @event mouseover
6338              * Fires when the mouse hovers over the button
6339              * @param {Button} this
6340              * @param {Event} e The event object
6341              */
6342         'mouseover' : true,
6343         /**
6344              * @event mouseout
6345              * Fires when the mouse exits the button
6346              * @param {Button} this
6347              * @param {Event} e The event object
6348              */
6349         'mouseout': true,
6350          /**
6351              * @event render
6352              * Fires when the button is rendered
6353              * @param {Button} this
6354              */
6355         'render': true
6356     });
6357     if(this.menu){
6358         this.menu = Roo.menu.MenuMgr.get(this.menu);
6359     }
6360     // register listeners first!!  - so render can be captured..
6361     Roo.util.Observable.call(this);
6362     if(renderTo){
6363         this.render(renderTo);
6364     }
6365     
6366   
6367 };
6368
6369 Roo.extend(Roo.Button, Roo.util.Observable, {
6370     /**
6371      * 
6372      */
6373     
6374     /**
6375      * Read-only. True if this button is hidden
6376      * @type Boolean
6377      */
6378     hidden : false,
6379     /**
6380      * Read-only. True if this button is disabled
6381      * @type Boolean
6382      */
6383     disabled : false,
6384     /**
6385      * Read-only. True if this button is pressed (only if enableToggle = true)
6386      * @type Boolean
6387      */
6388     pressed : false,
6389
6390     /**
6391      * @cfg {Number} tabIndex 
6392      * The DOM tabIndex for this button (defaults to undefined)
6393      */
6394     tabIndex : undefined,
6395
6396     /**
6397      * @cfg {Boolean} enableToggle
6398      * True to enable pressed/not pressed toggling (defaults to false)
6399      */
6400     enableToggle: false,
6401     /**
6402      * @cfg {Roo.menu.Menu} menu
6403      * Standard menu attribute consisting of a reference to a menu object, a menu id or a menu config blob (defaults to undefined).
6404      */
6405     menu : undefined,
6406     /**
6407      * @cfg {String} menuAlign
6408      * The position to align the menu to (see {@link Roo.Element#alignTo} for more details, defaults to 'tl-bl?').
6409      */
6410     menuAlign : "tl-bl?",
6411
6412     /**
6413      * @cfg {String} iconCls
6414      * A css class which sets a background image to be used as the icon for this button (defaults to undefined).
6415      */
6416     iconCls : undefined,
6417     /**
6418      * @cfg {String} type
6419      * The button's type, corresponding to the DOM input element type attribute.  Either "submit," "reset" or "button" (default).
6420      */
6421     type : 'button',
6422
6423     // private
6424     menuClassTarget: 'tr',
6425
6426     /**
6427      * @cfg {String} clickEvent
6428      * The type of event to map to the button's event handler (defaults to 'click')
6429      */
6430     clickEvent : 'click',
6431
6432     /**
6433      * @cfg {Boolean} handleMouseEvents
6434      * False to disable visual cues on mouseover, mouseout and mousedown (defaults to true)
6435      */
6436     handleMouseEvents : true,
6437
6438     /**
6439      * @cfg {String} tooltipType
6440      * The type of tooltip to use. Either "qtip" (default) for QuickTips or "title" for title attribute.
6441      */
6442     tooltipType : 'qtip',
6443
6444     /**
6445      * @cfg {String} cls
6446      * A CSS class to apply to the button's main element.
6447      */
6448     
6449     /**
6450      * @cfg {Roo.Template} template (Optional)
6451      * An {@link Roo.Template} with which to create the Button's main element. This Template must
6452      * contain numeric substitution parameter 0 if it is to display the tRoo property. Changing the template could
6453      * require code modifications if required elements (e.g. a button) aren't present.
6454      */
6455
6456     // private
6457     render : function(renderTo){
6458         var btn;
6459         if(this.hideParent){
6460             this.parentEl = Roo.get(renderTo);
6461         }
6462         if(!this.dhconfig){
6463             if(!this.template){
6464                 if(!Roo.Button.buttonTemplate){
6465                     // hideous table template
6466                     Roo.Button.buttonTemplate = new Roo.Template(
6467                         '<table border="0" cellpadding="0" cellspacing="0" class="x-btn-wrap"><tbody><tr>',
6468                         '<td class="x-btn-left"><i>&#160;</i></td><td class="x-btn-center"><em unselectable="on"><button class="x-btn-text" type="{1}">{0}</button></em></td><td class="x-btn-right"><i>&#160;</i></td>',
6469                         "</tr></tbody></table>");
6470                 }
6471                 this.template = Roo.Button.buttonTemplate;
6472             }
6473             btn = this.template.append(renderTo, [this.text || '&#160;', this.type], true);
6474             var btnEl = btn.child("button:first");
6475             btnEl.on('focus', this.onFocus, this);
6476             btnEl.on('blur', this.onBlur, this);
6477             if(this.cls){
6478                 btn.addClass(this.cls);
6479             }
6480             if(this.icon){
6481                 btnEl.setStyle('background-image', 'url(' +this.icon +')');
6482             }
6483             if(this.iconCls){
6484                 btnEl.addClass(this.iconCls);
6485                 if(!this.cls){
6486                     btn.addClass(this.text ? 'x-btn-text-icon' : 'x-btn-icon');
6487                 }
6488             }
6489             if(this.tabIndex !== undefined){
6490                 btnEl.dom.tabIndex = this.tabIndex;
6491             }
6492             if(this.tooltip){
6493                 if(typeof this.tooltip == 'object'){
6494                     Roo.QuickTips.tips(Roo.apply({
6495                           target: btnEl.id
6496                     }, this.tooltip));
6497                 } else {
6498                     btnEl.dom[this.tooltipType] = this.tooltip;
6499                 }
6500             }
6501         }else{
6502             btn = Roo.DomHelper.append(Roo.get(renderTo).dom, this.dhconfig, true);
6503         }
6504         this.el = btn;
6505         if(this.id){
6506             this.el.dom.id = this.el.id = this.id;
6507         }
6508         if(this.menu){
6509             this.el.child(this.menuClassTarget).addClass("x-btn-with-menu");
6510             this.menu.on("show", this.onMenuShow, this);
6511             this.menu.on("hide", this.onMenuHide, this);
6512         }
6513         btn.addClass("x-btn");
6514         if(Roo.isIE && !Roo.isIE7){
6515             this.autoWidth.defer(1, this);
6516         }else{
6517             this.autoWidth();
6518         }
6519         if(this.handleMouseEvents){
6520             btn.on("mouseover", this.onMouseOver, this);
6521             btn.on("mouseout", this.onMouseOut, this);
6522             btn.on("mousedown", this.onMouseDown, this);
6523         }
6524         btn.on(this.clickEvent, this.onClick, this);
6525         //btn.on("mouseup", this.onMouseUp, this);
6526         if(this.hidden){
6527             this.hide();
6528         }
6529         if(this.disabled){
6530             this.disable();
6531         }
6532         Roo.ButtonToggleMgr.register(this);
6533         if(this.pressed){
6534             this.el.addClass("x-btn-pressed");
6535         }
6536         if(this.repeat){
6537             var repeater = new Roo.util.ClickRepeater(btn,
6538                 typeof this.repeat == "object" ? this.repeat : {}
6539             );
6540             repeater.on("click", this.onClick,  this);
6541         }
6542         
6543         this.fireEvent('render', this);
6544         
6545     },
6546     /**
6547      * Returns the button's underlying element
6548      * @return {Roo.Element} The element
6549      */
6550     getEl : function(){
6551         return this.el;  
6552     },
6553     
6554     /**
6555      * Destroys this Button and removes any listeners.
6556      */
6557     destroy : function(){
6558         Roo.ButtonToggleMgr.unregister(this);
6559         this.el.removeAllListeners();
6560         this.purgeListeners();
6561         this.el.remove();
6562     },
6563
6564     // private
6565     autoWidth : function(){
6566         if(this.el){
6567             this.el.setWidth("auto");
6568             if(Roo.isIE7 && Roo.isStrict){
6569                 var ib = this.el.child('button');
6570                 if(ib && ib.getWidth() > 20){
6571                     ib.clip();
6572                     ib.setWidth(Roo.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));
6573                 }
6574             }
6575             if(this.minWidth){
6576                 if(this.hidden){
6577                     this.el.beginMeasure();
6578                 }
6579                 if(this.el.getWidth() < this.minWidth){
6580                     this.el.setWidth(this.minWidth);
6581                 }
6582                 if(this.hidden){
6583                     this.el.endMeasure();
6584                 }
6585             }
6586         }
6587     },
6588
6589     /**
6590      * Assigns this button's click handler
6591      * @param {Function} handler The function to call when the button is clicked
6592      * @param {Object} scope (optional) Scope for the function passed in
6593      */
6594     setHandler : function(handler, scope){
6595         this.handler = handler;
6596         this.scope = scope;  
6597     },
6598     
6599     /**
6600      * Sets this button's text
6601      * @param {String} text The button text
6602      */
6603     setText : function(text){
6604         this.text = text;
6605         if(this.el){
6606             this.el.child("td.x-btn-center button.x-btn-text").update(text);
6607         }
6608         this.autoWidth();
6609     },
6610     
6611     /**
6612      * Gets the text for this button
6613      * @return {String} The button text
6614      */
6615     getText : function(){
6616         return this.text;  
6617     },
6618     
6619     /**
6620      * Show this button
6621      */
6622     show: function(){
6623         this.hidden = false;
6624         if(this.el){
6625             this[this.hideParent? 'parentEl' : 'el'].setStyle("display", "");
6626         }
6627     },
6628     
6629     /**
6630      * Hide this button
6631      */
6632     hide: function(){
6633         this.hidden = true;
6634         if(this.el){
6635             this[this.hideParent? 'parentEl' : 'el'].setStyle("display", "none");
6636         }
6637     },
6638     
6639     /**
6640      * Convenience function for boolean show/hide
6641      * @param {Boolean} visible True to show, false to hide
6642      */
6643     setVisible: function(visible){
6644         if(visible) {
6645             this.show();
6646         }else{
6647             this.hide();
6648         }
6649     },
6650     /**
6651          * Similar to toggle, but does not trigger event.
6652          * @param {Boolean} state [required] Force a particular state
6653          */
6654         setPressed : function(state)
6655         {
6656             if(state != this.pressed){
6657             if(state){
6658                 this.el.addClass("x-btn-pressed");
6659                 this.pressed = true;
6660             }else{
6661                 this.el.removeClass("x-btn-pressed");
6662                 this.pressed = false;
6663             }
6664         }
6665         },
6666         
6667     /**
6668      * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
6669      * @param {Boolean} state (optional) Force a particular state
6670      */
6671     toggle : function(state){
6672         state = state === undefined ? !this.pressed : state;
6673         if(state != this.pressed){
6674             if(state){
6675                 this.el.addClass("x-btn-pressed");
6676                 this.pressed = true;
6677                 this.fireEvent("toggle", this, true);
6678             }else{
6679                 this.el.removeClass("x-btn-pressed");
6680                 this.pressed = false;
6681                 this.fireEvent("toggle", this, false);
6682             }
6683             if(this.toggleHandler){
6684                 this.toggleHandler.call(this.scope || this, this, state);
6685             }
6686         }
6687     },
6688     
6689         
6690         
6691     /**
6692      * Focus the button
6693      */
6694     focus : function(){
6695         this.el.child('button:first').focus();
6696     },
6697     
6698     /**
6699      * Disable this button
6700      */
6701     disable : function(){
6702         if(this.el){
6703             this.el.addClass("x-btn-disabled");
6704         }
6705         this.disabled = true;
6706     },
6707     
6708     /**
6709      * Enable this button
6710      */
6711     enable : function(){
6712         if(this.el){
6713             this.el.removeClass("x-btn-disabled");
6714         }
6715         this.disabled = false;
6716     },
6717
6718     /**
6719      * Convenience function for boolean enable/disable
6720      * @param {Boolean} enabled True to enable, false to disable
6721      */
6722     setDisabled : function(v){
6723         this[v !== true ? "enable" : "disable"]();
6724     },
6725
6726     // private
6727     onClick : function(e)
6728     {
6729         if(e){
6730             e.preventDefault();
6731         }
6732         if(e.button != 0){
6733             return;
6734         }
6735         if(!this.disabled){
6736             if(this.enableToggle){
6737                 this.toggle();
6738             }
6739             if(this.menu && !this.menu.isVisible()){
6740                 this.menu.show(this.el, this.menuAlign);
6741             }
6742             this.fireEvent("click", this, e);
6743             if(this.handler){
6744                 this.el.removeClass("x-btn-over");
6745                 this.handler.call(this.scope || this, this, e);
6746             }
6747         }
6748     },
6749     // private
6750     onMouseOver : function(e){
6751         if(!this.disabled){
6752             this.el.addClass("x-btn-over");
6753             this.fireEvent('mouseover', this, e);
6754         }
6755     },
6756     // private
6757     onMouseOut : function(e){
6758         if(!e.within(this.el,  true)){
6759             this.el.removeClass("x-btn-over");
6760             this.fireEvent('mouseout', this, e);
6761         }
6762     },
6763     // private
6764     onFocus : function(e){
6765         if(!this.disabled){
6766             this.el.addClass("x-btn-focus");
6767         }
6768     },
6769     // private
6770     onBlur : function(e){
6771         this.el.removeClass("x-btn-focus");
6772     },
6773     // private
6774     onMouseDown : function(e){
6775         if(!this.disabled && e.button == 0){
6776             this.el.addClass("x-btn-click");
6777             Roo.get(document).on('mouseup', this.onMouseUp, this);
6778         }
6779     },
6780     // private
6781     onMouseUp : function(e){
6782         if(e.button == 0){
6783             this.el.removeClass("x-btn-click");
6784             Roo.get(document).un('mouseup', this.onMouseUp, this);
6785         }
6786     },
6787     // private
6788     onMenuShow : function(e){
6789         this.el.addClass("x-btn-menu-active");
6790     },
6791     // private
6792     onMenuHide : function(e){
6793         this.el.removeClass("x-btn-menu-active");
6794     }   
6795 });
6796
6797 // Private utility class used by Button
6798 Roo.ButtonToggleMgr = function(){
6799    var groups = {};
6800    
6801    function toggleGroup(btn, state){
6802        if(state){
6803            var g = groups[btn.toggleGroup];
6804            for(var i = 0, l = g.length; i < l; i++){
6805                if(g[i] != btn){
6806                    g[i].toggle(false);
6807                }
6808            }
6809        }
6810    }
6811    
6812    return {
6813        register : function(btn){
6814            if(!btn.toggleGroup){
6815                return;
6816            }
6817            var g = groups[btn.toggleGroup];
6818            if(!g){
6819                g = groups[btn.toggleGroup] = [];
6820            }
6821            g.push(btn);
6822            btn.on("toggle", toggleGroup);
6823        },
6824        
6825        unregister : function(btn){
6826            if(!btn.toggleGroup){
6827                return;
6828            }
6829            var g = groups[btn.toggleGroup];
6830            if(g){
6831                g.remove(btn);
6832                btn.un("toggle", toggleGroup);
6833            }
6834        }
6835    };
6836 }();/*
6837  * Based on:
6838  * Ext JS Library 1.1.1
6839  * Copyright(c) 2006-2007, Ext JS, LLC.
6840  *
6841  * Originally Released Under LGPL - original licence link has changed is not relivant.
6842  *
6843  * Fork - LGPL
6844  * <script type="text/javascript">
6845  */
6846  
6847 /**
6848  * @class Roo.SplitButton
6849  * @extends Roo.Button
6850  * A split button that provides a built-in dropdown arrow that can fire an event separately from the default
6851  * click event of the button.  Typically this would be used to display a dropdown menu that provides additional
6852  * options to the primary button action, but any custom handler can provide the arrowclick implementation.
6853  * @cfg {Function} arrowHandler A function called when the arrow button is clicked (can be used instead of click event)
6854  * @cfg {String} arrowTooltip The title attribute of the arrow
6855  * @constructor
6856  * Create a new menu button
6857  * @param {String/HTMLElement/Element} renderTo The element to append the button to
6858  * @param {Object} config The config object
6859  */
6860 Roo.SplitButton = function(renderTo, config){
6861     Roo.SplitButton.superclass.constructor.call(this, renderTo, config);
6862     /**
6863      * @event arrowclick
6864      * Fires when this button's arrow is clicked
6865      * @param {SplitButton} this
6866      * @param {EventObject} e The click event
6867      */
6868     this.addEvents({"arrowclick":true});
6869 };
6870
6871 Roo.extend(Roo.SplitButton, Roo.Button, {
6872     render : function(renderTo){
6873         // this is one sweet looking template!
6874         var tpl = new Roo.Template(
6875             '<table cellspacing="0" class="x-btn-menu-wrap x-btn"><tr><td>',
6876             '<table cellspacing="0" class="x-btn-wrap x-btn-menu-text-wrap"><tbody>',
6877             '<tr><td class="x-btn-left"><i>&#160;</i></td><td class="x-btn-center"><button class="x-btn-text" type="{1}">{0}</button></td></tr>',
6878             "</tbody></table></td><td>",
6879             '<table cellspacing="0" class="x-btn-wrap x-btn-menu-arrow-wrap"><tbody>',
6880             '<tr><td class="x-btn-center"><button class="x-btn-menu-arrow-el" type="button">&#160;</button></td><td class="x-btn-right"><i>&#160;</i></td></tr>',
6881             "</tbody></table></td></tr></table>"
6882         );
6883         var btn = tpl.append(renderTo, [this.text, this.type], true);
6884         var btnEl = btn.child("button");
6885         if(this.cls){
6886             btn.addClass(this.cls);
6887         }
6888         if(this.icon){
6889             btnEl.setStyle('background-image', 'url(' +this.icon +')');
6890         }
6891         if(this.iconCls){
6892             btnEl.addClass(this.iconCls);
6893             if(!this.cls){
6894                 btn.addClass(this.text ? 'x-btn-text-icon' : 'x-btn-icon');
6895             }
6896         }
6897         this.el = btn;
6898         if(this.handleMouseEvents){
6899             btn.on("mouseover", this.onMouseOver, this);
6900             btn.on("mouseout", this.onMouseOut, this);
6901             btn.on("mousedown", this.onMouseDown, this);
6902             btn.on("mouseup", this.onMouseUp, this);
6903         }
6904         btn.on(this.clickEvent, this.onClick, this);
6905         if(this.tooltip){
6906             if(typeof this.tooltip == 'object'){
6907                 Roo.QuickTips.tips(Roo.apply({
6908                       target: btnEl.id
6909                 }, this.tooltip));
6910             } else {
6911                 btnEl.dom[this.tooltipType] = this.tooltip;
6912             }
6913         }
6914         if(this.arrowTooltip){
6915             btn.child("button:nth(2)").dom[this.tooltipType] = this.arrowTooltip;
6916         }
6917         if(this.hidden){
6918             this.hide();
6919         }
6920         if(this.disabled){
6921             this.disable();
6922         }
6923         if(this.pressed){
6924             this.el.addClass("x-btn-pressed");
6925         }
6926         if(Roo.isIE && !Roo.isIE7){
6927             this.autoWidth.defer(1, this);
6928         }else{
6929             this.autoWidth();
6930         }
6931         if(this.menu){
6932             this.menu.on("show", this.onMenuShow, this);
6933             this.menu.on("hide", this.onMenuHide, this);
6934         }
6935         this.fireEvent('render', this);
6936     },
6937
6938     // private
6939     autoWidth : function(){
6940         if(this.el){
6941             var tbl = this.el.child("table:first");
6942             var tbl2 = this.el.child("table:last");
6943             this.el.setWidth("auto");
6944             tbl.setWidth("auto");
6945             if(Roo.isIE7 && Roo.isStrict){
6946                 var ib = this.el.child('button:first');
6947                 if(ib && ib.getWidth() > 20){
6948                     ib.clip();
6949                     ib.setWidth(Roo.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));
6950                 }
6951             }
6952             if(this.minWidth){
6953                 if(this.hidden){
6954                     this.el.beginMeasure();
6955                 }
6956                 if((tbl.getWidth()+tbl2.getWidth()) < this.minWidth){
6957                     tbl.setWidth(this.minWidth-tbl2.getWidth());
6958                 }
6959                 if(this.hidden){
6960                     this.el.endMeasure();
6961                 }
6962             }
6963             this.el.setWidth(tbl.getWidth()+tbl2.getWidth());
6964         } 
6965     },
6966     /**
6967      * Sets this button's click handler
6968      * @param {Function} handler The function to call when the button is clicked
6969      * @param {Object} scope (optional) Scope for the function passed above
6970      */
6971     setHandler : function(handler, scope){
6972         this.handler = handler;
6973         this.scope = scope;  
6974     },
6975     
6976     /**
6977      * Sets this button's arrow click handler
6978      * @param {Function} handler The function to call when the arrow is clicked
6979      * @param {Object} scope (optional) Scope for the function passed above
6980      */
6981     setArrowHandler : function(handler, scope){
6982         this.arrowHandler = handler;
6983         this.scope = scope;  
6984     },
6985     
6986     /**
6987      * Focus the button
6988      */
6989     focus : function(){
6990         if(this.el){
6991             this.el.child("button:first").focus();
6992         }
6993     },
6994
6995     // private
6996     onClick : function(e){
6997         e.preventDefault();
6998         if(!this.disabled){
6999             if(e.getTarget(".x-btn-menu-arrow-wrap")){
7000                 if(this.menu && !this.menu.isVisible()){
7001                     this.menu.show(this.el, this.menuAlign);
7002                 }
7003                 this.fireEvent("arrowclick", this, e);
7004                 if(this.arrowHandler){
7005                     this.arrowHandler.call(this.scope || this, this, e);
7006                 }
7007             }else{
7008                 this.fireEvent("click", this, e);
7009                 if(this.handler){
7010                     this.handler.call(this.scope || this, this, e);
7011                 }
7012             }
7013         }
7014     },
7015     // private
7016     onMouseDown : function(e){
7017         if(!this.disabled){
7018             Roo.fly(e.getTarget("table")).addClass("x-btn-click");
7019         }
7020     },
7021     // private
7022     onMouseUp : function(e){
7023         Roo.fly(e.getTarget("table")).removeClass("x-btn-click");
7024     }   
7025 });
7026
7027
7028 // backwards compat
7029 Roo.MenuButton = Roo.SplitButton;/*
7030  * Based on:
7031  * Ext JS Library 1.1.1
7032  * Copyright(c) 2006-2007, Ext JS, LLC.
7033  *
7034  * Originally Released Under LGPL - original licence link has changed is not relivant.
7035  *
7036  * Fork - LGPL
7037  * <script type="text/javascript">
7038  */
7039
7040 /**
7041  * @class Roo.Toolbar
7042  * @children   Roo.Toolbar.Item Roo.Toolbar.Button Roo.Toolbar.SplitButton Roo.form.Field 
7043  * Basic Toolbar class.
7044  * @constructor
7045  * Creates a new Toolbar
7046  * @param {Object} container The config object
7047  */ 
7048 Roo.Toolbar = function(container, buttons, config)
7049 {
7050     /// old consturctor format still supported..
7051     if(container instanceof Array){ // omit the container for later rendering
7052         buttons = container;
7053         config = buttons;
7054         container = null;
7055     }
7056     if (typeof(container) == 'object' && container.xtype) {
7057         config = container;
7058         container = config.container;
7059         buttons = config.buttons || []; // not really - use items!!
7060     }
7061     var xitems = [];
7062     if (config && config.items) {
7063         xitems = config.items;
7064         delete config.items;
7065     }
7066     Roo.apply(this, config);
7067     this.buttons = buttons;
7068     
7069     if(container){
7070         this.render(container);
7071     }
7072     this.xitems = xitems;
7073     Roo.each(xitems, function(b) {
7074         this.add(b);
7075     }, this);
7076     
7077 };
7078
7079 Roo.Toolbar.prototype = {
7080     /**
7081      * @cfg {Array} items
7082      * array of button configs or elements to add (will be converted to a MixedCollection)
7083      */
7084     items: false,
7085     /**
7086      * @cfg {String/HTMLElement/Element} container
7087      * The id or element that will contain the toolbar
7088      */
7089     // private
7090     render : function(ct){
7091         this.el = Roo.get(ct);
7092         if(this.cls){
7093             this.el.addClass(this.cls);
7094         }
7095         // using a table allows for vertical alignment
7096         // 100% width is needed by Safari...
7097         this.el.update('<div class="x-toolbar x-small-editor"><table cellspacing="0"><tr></tr></table></div>');
7098         this.tr = this.el.child("tr", true);
7099         var autoId = 0;
7100         this.items = new Roo.util.MixedCollection(false, function(o){
7101             return o.id || ("item" + (++autoId));
7102         });
7103         if(this.buttons){
7104             this.add.apply(this, this.buttons);
7105             delete this.buttons;
7106         }
7107     },
7108
7109     /**
7110      * Adds element(s) to the toolbar -- this function takes a variable number of 
7111      * arguments of mixed type and adds them to the toolbar.
7112      * @param {Mixed} arg1 The following types of arguments are all valid:<br />
7113      * <ul>
7114      * <li>{@link Roo.Toolbar.Button} config: A valid button config object (equivalent to {@link #addButton})</li>
7115      * <li>HtmlElement: Any standard HTML element (equivalent to {@link #addElement})</li>
7116      * <li>Field: Any form field (equivalent to {@link #addField})</li>
7117      * <li>Item: Any subclass of {@link Roo.Toolbar.Item} (equivalent to {@link #addItem})</li>
7118      * <li>String: Any generic string (gets wrapped in a {@link Roo.Toolbar.TextItem}, equivalent to {@link #addText}).
7119      * Note that there are a few special strings that are treated differently as explained nRoo.</li>
7120      * <li>'separator' or '-': Creates a separator element (equivalent to {@link #addSeparator})</li>
7121      * <li>' ': Creates a spacer element (equivalent to {@link #addSpacer})</li>
7122      * <li>'->': Creates a fill element (equivalent to {@link #addFill})</li>
7123      * </ul>
7124      * @param {Mixed} arg2
7125      * @param {Mixed} etc.
7126      */
7127     add : function(){
7128         var a = arguments, l = a.length;
7129         for(var i = 0; i < l; i++){
7130             this._add(a[i]);
7131         }
7132     },
7133     // private..
7134     _add : function(el) {
7135         
7136         if (el.xtype) {
7137             el = Roo.factory(el, typeof(Roo.Toolbar[el.xtype]) == 'undefined' ? Roo.form : Roo.Toolbar);
7138         }
7139         
7140         if (el.applyTo){ // some kind of form field
7141             return this.addField(el);
7142         } 
7143         if (el.render){ // some kind of Toolbar.Item
7144             return this.addItem(el);
7145         }
7146         if (typeof el == "string"){ // string
7147             if(el == "separator" || el == "-"){
7148                 return this.addSeparator();
7149             }
7150             if (el == " "){
7151                 return this.addSpacer();
7152             }
7153             if(el == "->"){
7154                 return this.addFill();
7155             }
7156             return this.addText(el);
7157             
7158         }
7159         if(el.tagName){ // element
7160             return this.addElement(el);
7161         }
7162         if(typeof el == "object"){ // must be button config?
7163             return this.addButton(el);
7164         }
7165         // and now what?!?!
7166         return false;
7167         
7168     },
7169     
7170     /**
7171      * Add an Xtype element
7172      * @param {Object} xtype Xtype Object
7173      * @return {Object} created Object
7174      */
7175     addxtype : function(e){
7176         return this.add(e);  
7177     },
7178     
7179     /**
7180      * Returns the Element for this toolbar.
7181      * @return {Roo.Element}
7182      */
7183     getEl : function(){
7184         return this.el;  
7185     },
7186     
7187     /**
7188      * Adds a separator
7189      * @return {Roo.Toolbar.Item} The separator item
7190      */
7191     addSeparator : function(){
7192         return this.addItem(new Roo.Toolbar.Separator());
7193     },
7194
7195     /**
7196      * Adds a spacer element
7197      * @return {Roo.Toolbar.Spacer} The spacer item
7198      */
7199     addSpacer : function(){
7200         return this.addItem(new Roo.Toolbar.Spacer());
7201     },
7202
7203     /**
7204      * Adds a fill element that forces subsequent additions to the right side of the toolbar
7205      * @return {Roo.Toolbar.Fill} The fill item
7206      */
7207     addFill : function(){
7208         return this.addItem(new Roo.Toolbar.Fill());
7209     },
7210
7211     /**
7212      * Adds any standard HTML element to the toolbar
7213      * @param {String/HTMLElement/Element} el The element or id of the element to add
7214      * @return {Roo.Toolbar.Item} The element's item
7215      */
7216     addElement : function(el){
7217         return this.addItem(new Roo.Toolbar.Item(el));
7218     },
7219     /**
7220      * Collection of items on the toolbar.. (only Toolbar Items, so use fields to retrieve fields)
7221      * @type Roo.util.MixedCollection  
7222      */
7223     items : false,
7224      
7225     /**
7226      * Adds any Toolbar.Item or subclass
7227      * @param {Roo.Toolbar.Item} item
7228      * @return {Roo.Toolbar.Item} The item
7229      */
7230     addItem : function(item){
7231         var td = this.nextBlock();
7232         item.render(td);
7233         this.items.add(item);
7234         return item;
7235     },
7236     
7237     /**
7238      * Adds a button (or buttons). See {@link Roo.Toolbar.Button} for more info on the config.
7239      * @param {Object/Array} config A button config or array of configs
7240      * @return {Roo.Toolbar.Button/Array}
7241      */
7242     addButton : function(config){
7243         if(config instanceof Array){
7244             var buttons = [];
7245             for(var i = 0, len = config.length; i < len; i++) {
7246                 buttons.push(this.addButton(config[i]));
7247             }
7248             return buttons;
7249         }
7250         var b = config;
7251         if(!(config instanceof Roo.Toolbar.Button)){
7252             b = config.split ?
7253                 new Roo.Toolbar.SplitButton(config) :
7254                 new Roo.Toolbar.Button(config);
7255         }
7256         var td = this.nextBlock();
7257         b.render(td);
7258         this.items.add(b);
7259         return b;
7260     },
7261     
7262     /**
7263      * Adds text to the toolbar
7264      * @param {String} text The text to add
7265      * @return {Roo.Toolbar.Item} The element's item
7266      */
7267     addText : function(text){
7268         return this.addItem(new Roo.Toolbar.TextItem(text));
7269     },
7270     
7271     /**
7272      * Inserts any {@link Roo.Toolbar.Item}/{@link Roo.Toolbar.Button} at the specified index.
7273      * @param {Number} index The index where the item is to be inserted
7274      * @param {Object/Roo.Toolbar.Item/Roo.Toolbar.Button (may be Array)} item The button, or button config object to be inserted.
7275      * @return {Roo.Toolbar.Button/Item}
7276      */
7277     insertButton : function(index, item){
7278         if(item instanceof Array){
7279             var buttons = [];
7280             for(var i = 0, len = item.length; i < len; i++) {
7281                buttons.push(this.insertButton(index + i, item[i]));
7282             }
7283             return buttons;
7284         }
7285         if (!(item instanceof Roo.Toolbar.Button)){
7286            item = new Roo.Toolbar.Button(item);
7287         }
7288         var td = document.createElement("td");
7289         this.tr.insertBefore(td, this.tr.childNodes[index]);
7290         item.render(td);
7291         this.items.insert(index, item);
7292         return item;
7293     },
7294     
7295     /**
7296      * Adds a new element to the toolbar from the passed {@link Roo.DomHelper} config.
7297      * @param {Object} config
7298      * @return {Roo.Toolbar.Item} The element's item
7299      */
7300     addDom : function(config, returnEl){
7301         var td = this.nextBlock();
7302         Roo.DomHelper.overwrite(td, config);
7303         var ti = new Roo.Toolbar.Item(td.firstChild);
7304         ti.render(td);
7305         this.items.add(ti);
7306         return ti;
7307     },
7308
7309     /**
7310      * Collection of fields on the toolbar.. usefull for quering (value is false if there are no fields)
7311      * @type Roo.util.MixedCollection  
7312      */
7313     fields : false,
7314     
7315     /**
7316      * Adds a dynamically rendered Roo.form field (TextField, ComboBox, etc).
7317      * Note: the field should not have been rendered yet. For a field that has already been
7318      * rendered, use {@link #addElement}.
7319      * @param {Roo.form.Field} field
7320      * @return {Roo.ToolbarItem}
7321      */
7322      
7323       
7324     addField : function(field) {
7325         if (!this.fields) {
7326             var autoId = 0;
7327             this.fields = new Roo.util.MixedCollection(false, function(o){
7328                 return o.id || ("item" + (++autoId));
7329             });
7330
7331         }
7332         
7333         var td = this.nextBlock();
7334         field.render(td);
7335         var ti = new Roo.Toolbar.Item(td.firstChild);
7336         ti.render(td);
7337         this.items.add(ti);
7338         this.fields.add(field);
7339         return ti;
7340     },
7341     /**
7342      * Hide the toolbar
7343      * @method hide
7344      */
7345      
7346       
7347     hide : function()
7348     {
7349         this.el.child('div').setVisibilityMode(Roo.Element.DISPLAY);
7350         this.el.child('div').hide();
7351     },
7352     /**
7353      * Show the toolbar
7354      * @method show
7355      */
7356     show : function()
7357     {
7358         this.el.child('div').show();
7359     },
7360       
7361     // private
7362     nextBlock : function(){
7363         var td = document.createElement("td");
7364         this.tr.appendChild(td);
7365         return td;
7366     },
7367
7368     // private
7369     destroy : function(){
7370         if(this.items){ // rendered?
7371             Roo.destroy.apply(Roo, this.items.items);
7372         }
7373         if(this.fields){ // rendered?
7374             Roo.destroy.apply(Roo, this.fields.items);
7375         }
7376         Roo.Element.uncache(this.el, this.tr);
7377     }
7378 };
7379
7380 /**
7381  * @class Roo.Toolbar.Item
7382  * The base class that other classes should extend in order to get some basic common toolbar item functionality.
7383  * @constructor
7384  * Creates a new Item
7385  * @param {HTMLElement} el 
7386  */
7387 Roo.Toolbar.Item = function(el){
7388     var cfg = {};
7389     if (typeof (el.xtype) != 'undefined') {
7390         cfg = el;
7391         el = cfg.el;
7392     }
7393     
7394     this.el = Roo.getDom(el);
7395     this.id = Roo.id(this.el);
7396     this.hidden = false;
7397     
7398     this.addEvents({
7399          /**
7400              * @event render
7401              * Fires when the button is rendered
7402              * @param {Button} this
7403              */
7404         'render': true
7405     });
7406     Roo.Toolbar.Item.superclass.constructor.call(this,cfg);
7407 };
7408 Roo.extend(Roo.Toolbar.Item, Roo.util.Observable, {
7409 //Roo.Toolbar.Item.prototype = {
7410     
7411     /**
7412      * Get this item's HTML Element
7413      * @return {HTMLElement}
7414      */
7415     getEl : function(){
7416        return this.el;  
7417     },
7418
7419     // private
7420     render : function(td){
7421         
7422          this.td = td;
7423         td.appendChild(this.el);
7424         
7425         this.fireEvent('render', this);
7426     },
7427     
7428     /**
7429      * Removes and destroys this item.
7430      */
7431     destroy : function(){
7432         this.td.parentNode.removeChild(this.td);
7433     },
7434     
7435     /**
7436      * Shows this item.
7437      */
7438     show: function(){
7439         this.hidden = false;
7440         this.td.style.display = "";
7441     },
7442     
7443     /**
7444      * Hides this item.
7445      */
7446     hide: function(){
7447         this.hidden = true;
7448         this.td.style.display = "none";
7449     },
7450     
7451     /**
7452      * Convenience function for boolean show/hide.
7453      * @param {Boolean} visible true to show/false to hide
7454      */
7455     setVisible: function(visible){
7456         if(visible) {
7457             this.show();
7458         }else{
7459             this.hide();
7460         }
7461     },
7462     
7463     /**
7464      * Try to focus this item.
7465      */
7466     focus : function(){
7467         Roo.fly(this.el).focus();
7468     },
7469     
7470     /**
7471      * Disables this item.
7472      */
7473     disable : function(){
7474         Roo.fly(this.td).addClass("x-item-disabled");
7475         this.disabled = true;
7476         this.el.disabled = true;
7477     },
7478     
7479     /**
7480      * Enables this item.
7481      */
7482     enable : function(){
7483         Roo.fly(this.td).removeClass("x-item-disabled");
7484         this.disabled = false;
7485         this.el.disabled = false;
7486     }
7487 });
7488
7489
7490 /**
7491  * @class Roo.Toolbar.Separator
7492  * @extends Roo.Toolbar.Item
7493  * A simple toolbar separator class
7494  * @constructor
7495  * Creates a new Separator
7496  */
7497 Roo.Toolbar.Separator = function(cfg){
7498     
7499     var s = document.createElement("span");
7500     s.className = "ytb-sep";
7501     if (cfg) {
7502         cfg.el = s;
7503     }
7504     
7505     Roo.Toolbar.Separator.superclass.constructor.call(this, cfg || s);
7506 };
7507 Roo.extend(Roo.Toolbar.Separator, Roo.Toolbar.Item, {
7508     enable:Roo.emptyFn,
7509     disable:Roo.emptyFn,
7510     focus:Roo.emptyFn
7511 });
7512
7513 /**
7514  * @class Roo.Toolbar.Spacer
7515  * @extends Roo.Toolbar.Item
7516  * A simple element that adds extra horizontal space to a toolbar.
7517  * @constructor
7518  * Creates a new Spacer
7519  */
7520 Roo.Toolbar.Spacer = function(cfg){
7521     var s = document.createElement("div");
7522     s.className = "ytb-spacer";
7523     if (cfg) {
7524         cfg.el = s;
7525     }
7526     Roo.Toolbar.Spacer.superclass.constructor.call(this, cfg || s);
7527 };
7528 Roo.extend(Roo.Toolbar.Spacer, Roo.Toolbar.Item, {
7529     enable:Roo.emptyFn,
7530     disable:Roo.emptyFn,
7531     focus:Roo.emptyFn
7532 });
7533
7534 /**
7535  * @class Roo.Toolbar.Fill
7536  * @extends Roo.Toolbar.Spacer
7537  * A simple element that adds a greedy (100% width) horizontal space to a toolbar.
7538  * @constructor
7539  * Creates a new Spacer
7540  */
7541 Roo.Toolbar.Fill = Roo.extend(Roo.Toolbar.Spacer, {
7542     // private
7543     render : function(td){
7544         td.style.width = '100%';
7545         Roo.Toolbar.Fill.superclass.render.call(this, td);
7546     }
7547 });
7548
7549 /**
7550  * @class Roo.Toolbar.TextItem
7551  * @extends Roo.Toolbar.Item
7552  * A simple class that renders text directly into a toolbar.
7553  * @constructor
7554  * Creates a new TextItem
7555  * @cfg {string} text 
7556  */
7557 Roo.Toolbar.TextItem = function(cfg){
7558     var  text = cfg || "";
7559     if (typeof(cfg) == 'object') {
7560         text = cfg.text || "";
7561     }  else {
7562         cfg = null;
7563     }
7564     var s = document.createElement("span");
7565     s.className = "ytb-text";
7566     s.innerHTML = text;
7567     if (cfg) {
7568         cfg.el  = s;
7569     }
7570     
7571     Roo.Toolbar.TextItem.superclass.constructor.call(this, cfg ||  s);
7572 };
7573 Roo.extend(Roo.Toolbar.TextItem, Roo.Toolbar.Item, {
7574     
7575      
7576     enable:Roo.emptyFn,
7577     disable:Roo.emptyFn,
7578     focus:Roo.emptyFn,
7579      /**
7580      * Shows this button
7581      */
7582     show: function(){
7583         this.hidden = false;
7584         this.el.style.display = "";
7585     },
7586     
7587     /**
7588      * Hides this button
7589      */
7590     hide: function(){
7591         this.hidden = true;
7592         this.el.style.display = "none";
7593     }
7594     
7595 });
7596
7597 /**
7598  * @class Roo.Toolbar.Button
7599  * @extends Roo.Button
7600  * A button that renders into a toolbar.
7601  * @constructor
7602  * Creates a new Button
7603  * @param {Object} config A standard {@link Roo.Button} config object
7604  */
7605 Roo.Toolbar.Button = function(config){
7606     Roo.Toolbar.Button.superclass.constructor.call(this, null, config);
7607 };
7608 Roo.extend(Roo.Toolbar.Button, Roo.Button,
7609 {
7610     
7611     
7612     render : function(td){
7613         this.td = td;
7614         Roo.Toolbar.Button.superclass.render.call(this, td);
7615     },
7616     
7617     /**
7618      * Removes and destroys this button
7619      */
7620     destroy : function(){
7621         Roo.Toolbar.Button.superclass.destroy.call(this);
7622         this.td.parentNode.removeChild(this.td);
7623     },
7624     
7625     /**
7626      * Shows this button
7627      */
7628     show: function(){
7629         this.hidden = false;
7630         this.td.style.display = "";
7631     },
7632     
7633     /**
7634      * Hides this button
7635      */
7636     hide: function(){
7637         this.hidden = true;
7638         this.td.style.display = "none";
7639     },
7640
7641     /**
7642      * Disables this item
7643      */
7644     disable : function(){
7645         Roo.fly(this.td).addClass("x-item-disabled");
7646         this.disabled = true;
7647     },
7648
7649     /**
7650      * Enables this item
7651      */
7652     enable : function(){
7653         Roo.fly(this.td).removeClass("x-item-disabled");
7654         this.disabled = false;
7655     }
7656 });
7657 // backwards compat
7658 Roo.ToolbarButton = Roo.Toolbar.Button;
7659
7660 /**
7661  * @class Roo.Toolbar.SplitButton
7662  * @extends Roo.SplitButton
7663  * A menu button that renders into a toolbar.
7664  * @constructor
7665  * Creates a new SplitButton
7666  * @param {Object} config A standard {@link Roo.SplitButton} config object
7667  */
7668 Roo.Toolbar.SplitButton = function(config){
7669     Roo.Toolbar.SplitButton.superclass.constructor.call(this, null, config);
7670 };
7671 Roo.extend(Roo.Toolbar.SplitButton, Roo.SplitButton, {
7672     render : function(td){
7673         this.td = td;
7674         Roo.Toolbar.SplitButton.superclass.render.call(this, td);
7675     },
7676     
7677     /**
7678      * Removes and destroys this button
7679      */
7680     destroy : function(){
7681         Roo.Toolbar.SplitButton.superclass.destroy.call(this);
7682         this.td.parentNode.removeChild(this.td);
7683     },
7684     
7685     /**
7686      * Shows this button
7687      */
7688     show: function(){
7689         this.hidden = false;
7690         this.td.style.display = "";
7691     },
7692     
7693     /**
7694      * Hides this button
7695      */
7696     hide: function(){
7697         this.hidden = true;
7698         this.td.style.display = "none";
7699     }
7700 });
7701
7702 // backwards compat
7703 Roo.Toolbar.MenuButton = Roo.Toolbar.SplitButton;/*
7704  * Based on:
7705  * Ext JS Library 1.1.1
7706  * Copyright(c) 2006-2007, Ext JS, LLC.
7707  *
7708  * Originally Released Under LGPL - original licence link has changed is not relivant.
7709  *
7710  * Fork - LGPL
7711  * <script type="text/javascript">
7712  */
7713  
7714 /**
7715  * @class Roo.PagingToolbar
7716  * @extends Roo.Toolbar
7717  * @children   Roo.Toolbar.Item Roo.Toolbar.Button Roo.Toolbar.SplitButton Roo.form.Field
7718  * A specialized toolbar that is bound to a {@link Roo.data.Store} and provides automatic paging controls.
7719  * @constructor
7720  * Create a new PagingToolbar
7721  * @param {Object} config The config object
7722  */
7723 Roo.PagingToolbar = function(el, ds, config)
7724 {
7725     // old args format still supported... - xtype is prefered..
7726     if (typeof(el) == 'object' && el.xtype) {
7727         // created from xtype...
7728         config = el;
7729         ds = el.dataSource;
7730         el = config.container;
7731     }
7732     var items = [];
7733     if (config.items) {
7734         items = config.items;
7735         config.items = [];
7736     }
7737     
7738     Roo.PagingToolbar.superclass.constructor.call(this, el, null, config);
7739     this.ds = ds;
7740     this.cursor = 0;
7741     this.renderButtons(this.el);
7742     this.bind(ds);
7743     
7744     // supprot items array.
7745    
7746     Roo.each(items, function(e) {
7747         this.add(Roo.factory(e));
7748     },this);
7749     
7750 };
7751
7752 Roo.extend(Roo.PagingToolbar, Roo.Toolbar, {
7753    
7754     /**
7755      * @cfg {String/HTMLElement/Element} container
7756      * container The id or element that will contain the toolbar
7757      */
7758     /**
7759      * @cfg {Boolean} displayInfo
7760      * True to display the displayMsg (defaults to false)
7761      */
7762     
7763     
7764     /**
7765      * @cfg {Number} pageSize
7766      * The number of records to display per page (defaults to 20)
7767      */
7768     pageSize: 20,
7769     /**
7770      * @cfg {String} displayMsg
7771      * The paging status message to display (defaults to "Displaying {start} - {end} of {total}")
7772      */
7773     displayMsg : 'Displaying {0} - {1} of {2}',
7774     /**
7775      * @cfg {String} emptyMsg
7776      * The message to display when no records are found (defaults to "No data to display")
7777      */
7778     emptyMsg : 'No data to display',
7779     /**
7780      * Customizable piece of the default paging text (defaults to "Page")
7781      * @type String
7782      */
7783     beforePageText : "Page",
7784     /**
7785      * Customizable piece of the default paging text (defaults to "of %0")
7786      * @type String
7787      */
7788     afterPageText : "of {0}",
7789     /**
7790      * Customizable piece of the default paging text (defaults to "First Page")
7791      * @type String
7792      */
7793     firstText : "First Page",
7794     /**
7795      * Customizable piece of the default paging text (defaults to "Previous Page")
7796      * @type String
7797      */
7798     prevText : "Previous Page",
7799     /**
7800      * Customizable piece of the default paging text (defaults to "Next Page")
7801      * @type String
7802      */
7803     nextText : "Next Page",
7804     /**
7805      * Customizable piece of the default paging text (defaults to "Last Page")
7806      * @type String
7807      */
7808     lastText : "Last Page",
7809     /**
7810      * Customizable piece of the default paging text (defaults to "Refresh")
7811      * @type String
7812      */
7813     refreshText : "Refresh",
7814
7815     // private
7816     renderButtons : function(el){
7817         Roo.PagingToolbar.superclass.render.call(this, el);
7818         this.first = this.addButton({
7819             tooltip: this.firstText,
7820             cls: "x-btn-icon x-grid-page-first",
7821             disabled: true,
7822             handler: this.onClick.createDelegate(this, ["first"])
7823         });
7824         this.prev = this.addButton({
7825             tooltip: this.prevText,
7826             cls: "x-btn-icon x-grid-page-prev",
7827             disabled: true,
7828             handler: this.onClick.createDelegate(this, ["prev"])
7829         });
7830         //this.addSeparator();
7831         this.add(this.beforePageText);
7832         this.field = Roo.get(this.addDom({
7833            tag: "input",
7834            type: "text",
7835            size: "3",
7836            value: "1",
7837            cls: "x-grid-page-number"
7838         }).el);
7839         this.field.on("keydown", this.onPagingKeydown, this);
7840         this.field.on("focus", function(){this.dom.select();});
7841         this.afterTextEl = this.addText(String.format(this.afterPageText, 1));
7842         this.field.setHeight(18);
7843         //this.addSeparator();
7844         this.next = this.addButton({
7845             tooltip: this.nextText,
7846             cls: "x-btn-icon x-grid-page-next",
7847             disabled: true,
7848             handler: this.onClick.createDelegate(this, ["next"])
7849         });
7850         this.last = this.addButton({
7851             tooltip: this.lastText,
7852             cls: "x-btn-icon x-grid-page-last",
7853             disabled: true,
7854             handler: this.onClick.createDelegate(this, ["last"])
7855         });
7856         //this.addSeparator();
7857         this.loading = this.addButton({
7858             tooltip: this.refreshText,
7859             cls: "x-btn-icon x-grid-loading",
7860             handler: this.onClick.createDelegate(this, ["refresh"])
7861         });
7862
7863         if(this.displayInfo){
7864             this.displayEl = Roo.fly(this.el.dom.firstChild).createChild({cls:'x-paging-info'});
7865         }
7866     },
7867
7868     // private
7869     updateInfo : function(){
7870         if(this.displayEl){
7871             var count = this.ds.getCount();
7872             var msg = count == 0 ?
7873                 this.emptyMsg :
7874                 String.format(
7875                     this.displayMsg,
7876                     this.cursor+1, this.cursor+count, this.ds.getTotalCount()    
7877                 );
7878             this.displayEl.update(msg);
7879         }
7880     },
7881
7882     // private
7883     onLoad : function(ds, r, o){
7884        this.cursor = o.params ? o.params.start : 0;
7885        var d = this.getPageData(), ap = d.activePage, ps = d.pages;
7886
7887        this.afterTextEl.el.innerHTML = String.format(this.afterPageText, d.pages);
7888        this.field.dom.value = ap;
7889        this.first.setDisabled(ap == 1);
7890        this.prev.setDisabled(ap == 1);
7891        this.next.setDisabled(ap == ps);
7892        this.last.setDisabled(ap == ps);
7893        this.loading.enable();
7894        this.updateInfo();
7895     },
7896
7897     // private
7898     getPageData : function(){
7899         var total = this.ds.getTotalCount();
7900         return {
7901             total : total,
7902             activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize),
7903             pages :  total < this.pageSize ? 1 : Math.ceil(total/this.pageSize)
7904         };
7905     },
7906
7907     // private
7908     onLoadError : function(){
7909         this.loading.enable();
7910     },
7911
7912     // private
7913     onPagingKeydown : function(e){
7914         var k = e.getKey();
7915         var d = this.getPageData();
7916         if(k == e.RETURN){
7917             var v = this.field.dom.value, pageNum;
7918             if(!v || isNaN(pageNum = parseInt(v, 10))){
7919                 this.field.dom.value = d.activePage;
7920                 return;
7921             }
7922             pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1;
7923             this.ds.load({params:{start: pageNum * this.pageSize, limit: this.pageSize}});
7924             e.stopEvent();
7925         }
7926         else if(k == e.HOME || (k == e.UP && e.ctrlKey) || (k == e.PAGEUP && e.ctrlKey) || (k == e.RIGHT && e.ctrlKey) || k == e.END || (k == e.DOWN && e.ctrlKey) || (k == e.LEFT && e.ctrlKey) || (k == e.PAGEDOWN && e.ctrlKey))
7927         {
7928           var pageNum = (k == e.HOME || (k == e.DOWN && e.ctrlKey) || (k == e.LEFT && e.ctrlKey) || (k == e.PAGEDOWN && e.ctrlKey)) ? 1 : d.pages;
7929           this.field.dom.value = pageNum;
7930           this.ds.load({params:{start: (pageNum - 1) * this.pageSize, limit: this.pageSize}});
7931           e.stopEvent();
7932         }
7933         else if(k == e.UP || k == e.RIGHT || k == e.PAGEUP || k == e.DOWN || k == e.LEFT || k == e.PAGEDOWN)
7934         {
7935           var v = this.field.dom.value, pageNum; 
7936           var increment = (e.shiftKey) ? 10 : 1;
7937           if(k == e.DOWN || k == e.LEFT || k == e.PAGEDOWN) {
7938             increment *= -1;
7939           }
7940           if(!v || isNaN(pageNum = parseInt(v, 10))) {
7941             this.field.dom.value = d.activePage;
7942             return;
7943           }
7944           else if(parseInt(v, 10) + increment >= 1 & parseInt(v, 10) + increment <= d.pages)
7945           {
7946             this.field.dom.value = parseInt(v, 10) + increment;
7947             pageNum = Math.min(Math.max(1, pageNum + increment), d.pages) - 1;
7948             this.ds.load({params:{start: pageNum * this.pageSize, limit: this.pageSize}});
7949           }
7950           e.stopEvent();
7951         }
7952     },
7953
7954     // private
7955     beforeLoad : function(){
7956         if(this.loading){
7957             this.loading.disable();
7958         }
7959     },
7960     /**
7961      * event that occurs when you click on the navigation buttons - can be used to trigger load of a grid.
7962      * @param {String} which (first|prev|next|last|refresh)  which button to press.
7963      *
7964      */
7965     // private
7966     onClick : function(which){
7967         var ds = this.ds;
7968         switch(which){
7969             case "first":
7970                 ds.load({params:{start: 0, limit: this.pageSize}});
7971             break;
7972             case "prev":
7973                 ds.load({params:{start: Math.max(0, this.cursor-this.pageSize), limit: this.pageSize}});
7974             break;
7975             case "next":
7976                 ds.load({params:{start: this.cursor+this.pageSize, limit: this.pageSize}});
7977             break;
7978             case "last":
7979                 var total = ds.getTotalCount();
7980                 var extra = total % this.pageSize;
7981                 var lastStart = extra ? (total - extra) : total-this.pageSize;
7982                 ds.load({params:{start: lastStart, limit: this.pageSize}});
7983             break;
7984             case "refresh":
7985                 ds.load({params:{start: this.cursor, limit: this.pageSize}});
7986             break;
7987         }
7988     },
7989
7990     /**
7991      * Unbinds the paging toolbar from the specified {@link Roo.data.Store}
7992      * @param {Roo.data.Store} store The data store to unbind
7993      */
7994     unbind : function(ds){
7995         ds.un("beforeload", this.beforeLoad, this);
7996         ds.un("load", this.onLoad, this);
7997         ds.un("loadexception", this.onLoadError, this);
7998         ds.un("remove", this.updateInfo, this);
7999         ds.un("add", this.updateInfo, this);
8000         this.ds = undefined;
8001     },
8002
8003     /**
8004      * Binds the paging toolbar to the specified {@link Roo.data.Store}
8005      * @param {Roo.data.Store} store The data store to bind
8006      */
8007     bind : function(ds){
8008         ds.on("beforeload", this.beforeLoad, this);
8009         ds.on("load", this.onLoad, this);
8010         ds.on("loadexception", this.onLoadError, this);
8011         ds.on("remove", this.updateInfo, this);
8012         ds.on("add", this.updateInfo, this);
8013         this.ds = ds;
8014     }
8015 });/*
8016  * Based on:
8017  * Ext JS Library 1.1.1
8018  * Copyright(c) 2006-2007, Ext JS, LLC.
8019  *
8020  * Originally Released Under LGPL - original licence link has changed is not relivant.
8021  *
8022  * Fork - LGPL
8023  * <script type="text/javascript">
8024  */
8025
8026 /**
8027  * @class Roo.Resizable
8028  * @extends Roo.util.Observable
8029  * <p>Applies drag handles to an element to make it resizable. The drag handles are inserted into the element
8030  * and positioned absolute. Some elements, such as a textarea or image, don't support this. To overcome that, you can wrap
8031  * 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
8032  * the element will be wrapped for you automatically.</p>
8033  * <p>Here is the list of valid resize handles:</p>
8034  * <pre>
8035 Value   Description
8036 ------  -------------------
8037  'n'     north
8038  's'     south
8039  'e'     east
8040  'w'     west
8041  'nw'    northwest
8042  'sw'    southwest
8043  'se'    southeast
8044  'ne'    northeast
8045  'hd'    horizontal drag
8046  'all'   all
8047 </pre>
8048  * <p>Here's an example showing the creation of a typical Resizable:</p>
8049  * <pre><code>
8050 var resizer = new Roo.Resizable("element-id", {
8051     handles: 'all',
8052     minWidth: 200,
8053     minHeight: 100,
8054     maxWidth: 500,
8055     maxHeight: 400,
8056     pinned: true
8057 });
8058 resizer.on("resize", myHandler);
8059 </code></pre>
8060  * <p>To hide a particular handle, set its display to none in CSS, or through script:<br>
8061  * resizer.east.setDisplayed(false);</p>
8062  * @cfg {Boolean/String/Element} resizeChild True to resize the first child, or id/element to resize (defaults to false)
8063  * @cfg {Array/String} adjustments String "auto" or an array [width, height] with values to be <b>added</b> to the
8064  * resize operation's new size (defaults to [0, 0])
8065  * @cfg {Number} minWidth The minimum width for the element (defaults to 5)
8066  * @cfg {Number} minHeight The minimum height for the element (defaults to 5)
8067  * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
8068  * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
8069  * @cfg {Boolean} enabled False to disable resizing (defaults to true)
8070  * @cfg {Boolean} wrap True to wrap an element with a div if needed (required for textareas and images, defaults to false)
8071  * @cfg {Number} width The width of the element in pixels (defaults to null)
8072  * @cfg {Number} height The height of the element in pixels (defaults to null)
8073  * @cfg {Boolean} animate True to animate the resize (not compatible with dynamic sizing, defaults to false)
8074  * @cfg {Number} duration Animation duration if animate = true (defaults to .35)
8075  * @cfg {Boolean} dynamic True to resize the element while dragging instead of using a proxy (defaults to false)
8076  * @cfg {String} handles String consisting of the resize handles to display (defaults to undefined)
8077  * @cfg {Boolean} multiDirectional <b>Deprecated</b>.  The old style of adding multi-direction resize handles, deprecated
8078  * in favor of the handles config option (defaults to false)
8079  * @cfg {Boolean} disableTrackOver True to disable mouse tracking. This is only applied at config time. (defaults to false)
8080  * @cfg {String} easing Animation easing if animate = true (defaults to 'easingOutStrong')
8081  * @cfg {Number} widthIncrement The increment to snap the width resize in pixels (dynamic must be true, defaults to 0)
8082  * @cfg {Number} heightIncrement The increment to snap the height resize in pixels (dynamic must be true, defaults to 0)
8083  * @cfg {Boolean} pinned True to ensure that the resize handles are always visible, false to display them only when the
8084  * user mouses over the resizable borders. This is only applied at config time. (defaults to false)
8085  * @cfg {Boolean} preserveRatio True to preserve the original ratio between height and width during resize (defaults to false)
8086  * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
8087  * @cfg {Number} minX The minimum allowed page X for the element (only used for west resizing, defaults to 0)
8088  * @cfg {Number} minY The minimum allowed page Y for the element (only used for north resizing, defaults to 0)
8089  * @cfg {Boolean} draggable Convenience to initialize drag drop (defaults to false)
8090  * @constructor
8091  * Create a new resizable component
8092  * @param {String/HTMLElement/Roo.Element} el The id or element to resize
8093  * @param {Object} config configuration options
8094   */
8095 Roo.Resizable = function(el, config)
8096 {
8097     this.el = Roo.get(el);
8098
8099     if(config && config.wrap){
8100         config.resizeChild = this.el;
8101         this.el = this.el.wrap(typeof config.wrap == "object" ? config.wrap : {cls:"xresizable-wrap"});
8102         this.el.id = this.el.dom.id = config.resizeChild.id + "-rzwrap";
8103         this.el.setStyle("overflow", "hidden");
8104         this.el.setPositioning(config.resizeChild.getPositioning());
8105         config.resizeChild.clearPositioning();
8106         if(!config.width || !config.height){
8107             var csize = config.resizeChild.getSize();
8108             this.el.setSize(csize.width, csize.height);
8109         }
8110         if(config.pinned && !config.adjustments){
8111             config.adjustments = "auto";
8112         }
8113     }
8114
8115     this.proxy = this.el.createProxy({tag: "div", cls: "x-resizable-proxy", id: this.el.id + "-rzproxy"});
8116     this.proxy.unselectable();
8117     this.proxy.enableDisplayMode('block');
8118
8119     Roo.apply(this, config);
8120
8121     if(this.pinned){
8122         this.disableTrackOver = true;
8123         this.el.addClass("x-resizable-pinned");
8124     }
8125     // if the element isn't positioned, make it relative
8126     var position = this.el.getStyle("position");
8127     if(position != "absolute" && position != "fixed"){
8128         this.el.setStyle("position", "relative");
8129     }
8130     if(!this.handles){ // no handles passed, must be legacy style
8131         this.handles = 's,e,se';
8132         if(this.multiDirectional){
8133             this.handles += ',n,w';
8134         }
8135     }
8136     if(this.handles == "all"){
8137         this.handles = "n s e w ne nw se sw";
8138     }
8139     var hs = this.handles.split(/\s*?[,;]\s*?| /);
8140     var ps = Roo.Resizable.positions;
8141     for(var i = 0, len = hs.length; i < len; i++){
8142         if(hs[i] && ps[hs[i]]){
8143             var pos = ps[hs[i]];
8144             this[pos] = new Roo.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent);
8145         }
8146     }
8147     // legacy
8148     this.corner = this.southeast;
8149     
8150     // updateBox = the box can move..
8151     if(this.handles.indexOf("n") != -1 || this.handles.indexOf("w") != -1 || this.handles.indexOf("hd") != -1) {
8152         this.updateBox = true;
8153     }
8154
8155     this.activeHandle = null;
8156
8157     if(this.resizeChild){
8158         if(typeof this.resizeChild == "boolean"){
8159             this.resizeChild = Roo.get(this.el.dom.firstChild, true);
8160         }else{
8161             this.resizeChild = Roo.get(this.resizeChild, true);
8162         }
8163     }
8164     
8165     if(this.adjustments == "auto"){
8166         var rc = this.resizeChild;
8167         var hw = this.west, he = this.east, hn = this.north, hs = this.south;
8168         if(rc && (hw || hn)){
8169             rc.position("relative");
8170             rc.setLeft(hw ? hw.el.getWidth() : 0);
8171             rc.setTop(hn ? hn.el.getHeight() : 0);
8172         }
8173         this.adjustments = [
8174             (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
8175             (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
8176         ];
8177     }
8178
8179     if(this.draggable){
8180         this.dd = this.dynamic ?
8181             this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
8182         this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
8183     }
8184
8185     // public events
8186     this.addEvents({
8187         /**
8188          * @event beforeresize
8189          * Fired before resize is allowed. Set enabled to false to cancel resize.
8190          * @param {Roo.Resizable} this
8191          * @param {Roo.EventObject} e The mousedown event
8192          */
8193         "beforeresize" : true,
8194         /**
8195          * @event resizing
8196          * Fired a resizing.
8197          * @param {Roo.Resizable} this
8198          * @param {Number} x The new x position
8199          * @param {Number} y The new y position
8200          * @param {Number} w The new w width
8201          * @param {Number} h The new h hight
8202          * @param {Roo.EventObject} e The mouseup event
8203          */
8204         "resizing" : true,
8205         /**
8206          * @event resize
8207          * Fired after a resize.
8208          * @param {Roo.Resizable} this
8209          * @param {Number} width The new width
8210          * @param {Number} height The new height
8211          * @param {Roo.EventObject} e The mouseup event
8212          */
8213         "resize" : true
8214     });
8215
8216     if(this.width !== null && this.height !== null){
8217         this.resizeTo(this.width, this.height);
8218     }else{
8219         this.updateChildSize();
8220     }
8221     if(Roo.isIE){
8222         this.el.dom.style.zoom = 1;
8223     }
8224     Roo.Resizable.superclass.constructor.call(this);
8225 };
8226
8227 Roo.extend(Roo.Resizable, Roo.util.Observable, {
8228         resizeChild : false,
8229         adjustments : [0, 0],
8230         minWidth : 5,
8231         minHeight : 5,
8232         maxWidth : 10000,
8233         maxHeight : 10000,
8234         enabled : true,
8235         animate : false,
8236         duration : .35,
8237         dynamic : false,
8238         handles : false,
8239         multiDirectional : false,
8240         disableTrackOver : false,
8241         easing : 'easeOutStrong',
8242         widthIncrement : 0,
8243         heightIncrement : 0,
8244         pinned : false,
8245         width : null,
8246         height : null,
8247         preserveRatio : false,
8248         transparent: false,
8249         minX: 0,
8250         minY: 0,
8251         draggable: false,
8252
8253         /**
8254          * @cfg {String/HTMLElement/Element} constrainTo Constrain the resize to a particular element
8255          */
8256         constrainTo: undefined,
8257         /**
8258          * @cfg {Roo.lib.Region} resizeRegion Constrain the resize to a particular region
8259          */
8260         resizeRegion: undefined,
8261
8262
8263     /**
8264      * Perform a manual resize
8265      * @param {Number} width
8266      * @param {Number} height
8267      */
8268     resizeTo : function(width, height){
8269         this.el.setSize(width, height);
8270         this.updateChildSize();
8271         this.fireEvent("resize", this, width, height, null);
8272     },
8273
8274     // private
8275     startSizing : function(e, handle){
8276         this.fireEvent("beforeresize", this, e);
8277         if(this.enabled){ // 2nd enabled check in case disabled before beforeresize handler
8278
8279             if(!this.overlay){
8280                 this.overlay = this.el.createProxy({tag: "div", cls: "x-resizable-overlay", html: "&#160;"});
8281                 this.overlay.unselectable();
8282                 this.overlay.enableDisplayMode("block");
8283                 this.overlay.on("mousemove", this.onMouseMove, this);
8284                 this.overlay.on("mouseup", this.onMouseUp, this);
8285             }
8286             this.overlay.setStyle("cursor", handle.el.getStyle("cursor"));
8287
8288             this.resizing = true;
8289             this.startBox = this.el.getBox();
8290             this.startPoint = e.getXY();
8291             this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
8292                             (this.startBox.y + this.startBox.height) - this.startPoint[1]];
8293
8294             this.overlay.setSize(Roo.lib.Dom.getViewWidth(true), Roo.lib.Dom.getViewHeight(true));
8295             this.overlay.show();
8296
8297             if(this.constrainTo) {
8298                 var ct = Roo.get(this.constrainTo);
8299                 this.resizeRegion = ct.getRegion().adjust(
8300                     ct.getFrameWidth('t'),
8301                     ct.getFrameWidth('l'),
8302                     -ct.getFrameWidth('b'),
8303                     -ct.getFrameWidth('r')
8304                 );
8305             }
8306
8307             this.proxy.setStyle('visibility', 'hidden'); // workaround display none
8308             this.proxy.show();
8309             this.proxy.setBox(this.startBox);
8310             if(!this.dynamic){
8311                 this.proxy.setStyle('visibility', 'visible');
8312             }
8313         }
8314     },
8315
8316     // private
8317     onMouseDown : function(handle, e){
8318         if(this.enabled){
8319             e.stopEvent();
8320             this.activeHandle = handle;
8321             this.startSizing(e, handle);
8322         }
8323     },
8324
8325     // private
8326     onMouseUp : function(e){
8327         var size = this.resizeElement();
8328         this.resizing = false;
8329         this.handleOut();
8330         this.overlay.hide();
8331         this.proxy.hide();
8332         this.fireEvent("resize", this, size.width, size.height, e);
8333     },
8334
8335     // private
8336     updateChildSize : function(){
8337         
8338         if(this.resizeChild){
8339             var el = this.el;
8340             var child = this.resizeChild;
8341             var adj = this.adjustments;
8342             if(el.dom.offsetWidth){
8343                 var b = el.getSize(true);
8344                 child.setSize(b.width+adj[0], b.height+adj[1]);
8345             }
8346             // Second call here for IE
8347             // The first call enables instant resizing and
8348             // the second call corrects scroll bars if they
8349             // exist
8350             if(Roo.isIE){
8351                 setTimeout(function(){
8352                     if(el.dom.offsetWidth){
8353                         var b = el.getSize(true);
8354                         child.setSize(b.width+adj[0], b.height+adj[1]);
8355                     }
8356                 }, 10);
8357             }
8358         }
8359     },
8360
8361     // private
8362     snap : function(value, inc, min){
8363         if(!inc || !value) {
8364             return value;
8365         }
8366         var newValue = value;
8367         var m = value % inc;
8368         if(m > 0){
8369             if(m > (inc/2)){
8370                 newValue = value + (inc-m);
8371             }else{
8372                 newValue = value - m;
8373             }
8374         }
8375         return Math.max(min, newValue);
8376     },
8377
8378     // private
8379     resizeElement : function(){
8380         var box = this.proxy.getBox();
8381         if(this.updateBox){
8382             this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
8383         }else{
8384             this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
8385         }
8386         this.updateChildSize();
8387         if(!this.dynamic){
8388             this.proxy.hide();
8389         }
8390         return box;
8391     },
8392
8393     // private
8394     constrain : function(v, diff, m, mx){
8395         if(v - diff < m){
8396             diff = v - m;
8397         }else if(v - diff > mx){
8398             diff = mx - v;
8399         }
8400         return diff;
8401     },
8402
8403     // private
8404     onMouseMove : function(e){
8405         
8406         if(this.enabled){
8407             try{// try catch so if something goes wrong the user doesn't get hung
8408
8409             if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
8410                 return;
8411             }
8412
8413             //var curXY = this.startPoint;
8414             var curSize = this.curSize || this.startBox;
8415             var x = this.startBox.x, y = this.startBox.y;
8416             var ox = x, oy = y;
8417             var w = curSize.width, h = curSize.height;
8418             var ow = w, oh = h;
8419             var mw = this.minWidth, mh = this.minHeight;
8420             var mxw = this.maxWidth, mxh = this.maxHeight;
8421             var wi = this.widthIncrement;
8422             var hi = this.heightIncrement;
8423
8424             var eventXY = e.getXY();
8425             var diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0]));
8426             var diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1]));
8427
8428             var pos = this.activeHandle.position;
8429
8430             switch(pos){
8431                 case "east":
8432                     w += diffX;
8433                     w = Math.min(Math.max(mw, w), mxw);
8434                     break;
8435              
8436                 case "south":
8437                     h += diffY;
8438                     h = Math.min(Math.max(mh, h), mxh);
8439                     break;
8440                 case "southeast":
8441                     w += diffX;
8442                     h += diffY;
8443                     w = Math.min(Math.max(mw, w), mxw);
8444                     h = Math.min(Math.max(mh, h), mxh);
8445                     break;
8446                 case "north":
8447                     diffY = this.constrain(h, diffY, mh, mxh);
8448                     y += diffY;
8449                     h -= diffY;
8450                     break;
8451                 case "hdrag":
8452                     
8453                     if (wi) {
8454                         var adiffX = Math.abs(diffX);
8455                         var sub = (adiffX % wi); // how much 
8456                         if (sub > (wi/2)) { // far enough to snap
8457                             diffX = (diffX > 0) ? diffX-sub + wi : diffX+sub - wi;
8458                         } else {
8459                             // remove difference.. 
8460                             diffX = (diffX > 0) ? diffX-sub : diffX+sub;
8461                         }
8462                     }
8463                     x += diffX;
8464                     x = Math.max(this.minX, x);
8465                     break;
8466                 case "west":
8467                     diffX = this.constrain(w, diffX, mw, mxw);
8468                     x += diffX;
8469                     w -= diffX;
8470                     break;
8471                 case "northeast":
8472                     w += diffX;
8473                     w = Math.min(Math.max(mw, w), mxw);
8474                     diffY = this.constrain(h, diffY, mh, mxh);
8475                     y += diffY;
8476                     h -= diffY;
8477                     break;
8478                 case "northwest":
8479                     diffX = this.constrain(w, diffX, mw, mxw);
8480                     diffY = this.constrain(h, diffY, mh, mxh);
8481                     y += diffY;
8482                     h -= diffY;
8483                     x += diffX;
8484                     w -= diffX;
8485                     break;
8486                case "southwest":
8487                     diffX = this.constrain(w, diffX, mw, mxw);
8488                     h += diffY;
8489                     h = Math.min(Math.max(mh, h), mxh);
8490                     x += diffX;
8491                     w -= diffX;
8492                     break;
8493             }
8494
8495             var sw = this.snap(w, wi, mw);
8496             var sh = this.snap(h, hi, mh);
8497             if(sw != w || sh != h){
8498                 switch(pos){
8499                     case "northeast":
8500                         y -= sh - h;
8501                     break;
8502                     case "north":
8503                         y -= sh - h;
8504                         break;
8505                     case "southwest":
8506                         x -= sw - w;
8507                     break;
8508                     case "west":
8509                         x -= sw - w;
8510                         break;
8511                     case "northwest":
8512                         x -= sw - w;
8513                         y -= sh - h;
8514                     break;
8515                 }
8516                 w = sw;
8517                 h = sh;
8518             }
8519
8520             if(this.preserveRatio){
8521                 switch(pos){
8522                     case "southeast":
8523                     case "east":
8524                         h = oh * (w/ow);
8525                         h = Math.min(Math.max(mh, h), mxh);
8526                         w = ow * (h/oh);
8527                        break;
8528                     case "south":
8529                         w = ow * (h/oh);
8530                         w = Math.min(Math.max(mw, w), mxw);
8531                         h = oh * (w/ow);
8532                         break;
8533                     case "northeast":
8534                         w = ow * (h/oh);
8535                         w = Math.min(Math.max(mw, w), mxw);
8536                         h = oh * (w/ow);
8537                     break;
8538                     case "north":
8539                         var tw = w;
8540                         w = ow * (h/oh);
8541                         w = Math.min(Math.max(mw, w), mxw);
8542                         h = oh * (w/ow);
8543                         x += (tw - w) / 2;
8544                         break;
8545                     case "southwest":
8546                         h = oh * (w/ow);
8547                         h = Math.min(Math.max(mh, h), mxh);
8548                         var tw = w;
8549                         w = ow * (h/oh);
8550                         x += tw - w;
8551                         break;
8552                     case "west":
8553                         var th = h;
8554                         h = oh * (w/ow);
8555                         h = Math.min(Math.max(mh, h), mxh);
8556                         y += (th - h) / 2;
8557                         var tw = w;
8558                         w = ow * (h/oh);
8559                         x += tw - w;
8560                        break;
8561                     case "northwest":
8562                         var tw = w;
8563                         var th = h;
8564                         h = oh * (w/ow);
8565                         h = Math.min(Math.max(mh, h), mxh);
8566                         w = ow * (h/oh);
8567                         y += th - h;
8568                         x += tw - w;
8569                        break;
8570
8571                 }
8572             }
8573             if (pos == 'hdrag') {
8574                 w = ow;
8575             }
8576             this.proxy.setBounds(x, y, w, h);
8577             if(this.dynamic){
8578                 this.resizeElement();
8579             }
8580             }catch(e){}
8581         }
8582         this.fireEvent("resizing", this, x, y, w, h, e);
8583     },
8584
8585     // private
8586     handleOver : function(){
8587         if(this.enabled){
8588             this.el.addClass("x-resizable-over");
8589         }
8590     },
8591
8592     // private
8593     handleOut : function(){
8594         if(!this.resizing){
8595             this.el.removeClass("x-resizable-over");
8596         }
8597     },
8598
8599     /**
8600      * Returns the element this component is bound to.
8601      * @return {Roo.Element}
8602      */
8603     getEl : function(){
8604         return this.el;
8605     },
8606
8607     /**
8608      * Returns the resizeChild element (or null).
8609      * @return {Roo.Element}
8610      */
8611     getResizeChild : function(){
8612         return this.resizeChild;
8613     },
8614     groupHandler : function()
8615     {
8616         
8617     },
8618     /**
8619      * Destroys this resizable. If the element was wrapped and
8620      * removeEl is not true then the element remains.
8621      * @param {Boolean} removeEl (optional) true to remove the element from the DOM
8622      */
8623     destroy : function(removeEl){
8624         this.proxy.remove();
8625         if(this.overlay){
8626             this.overlay.removeAllListeners();
8627             this.overlay.remove();
8628         }
8629         var ps = Roo.Resizable.positions;
8630         for(var k in ps){
8631             if(typeof ps[k] != "function" && this[ps[k]]){
8632                 var h = this[ps[k]];
8633                 h.el.removeAllListeners();
8634                 h.el.remove();
8635             }
8636         }
8637         if(removeEl){
8638             this.el.update("");
8639             this.el.remove();
8640         }
8641     }
8642 });
8643
8644 // private
8645 // hash to map config positions to true positions
8646 Roo.Resizable.positions = {
8647     n: "north", s: "south", e: "east", w: "west", se: "southeast", sw: "southwest", nw: "northwest", ne: "northeast", 
8648     hd: "hdrag"
8649 };
8650
8651 // private
8652 Roo.Resizable.Handle = function(rz, pos, disableTrackOver, transparent){
8653     if(!this.tpl){
8654         // only initialize the template if resizable is used
8655         var tpl = Roo.DomHelper.createTemplate(
8656             {tag: "div", cls: "x-resizable-handle x-resizable-handle-{0}"}
8657         );
8658         tpl.compile();
8659         Roo.Resizable.Handle.prototype.tpl = tpl;
8660     }
8661     this.position = pos;
8662     this.rz = rz;
8663     // show north drag fro topdra
8664     var handlepos = pos == 'hdrag' ? 'north' : pos;
8665     
8666     this.el = this.tpl.append(rz.el.dom, [handlepos], true);
8667     if (pos == 'hdrag') {
8668         this.el.setStyle('cursor', 'pointer');
8669     }
8670     this.el.unselectable();
8671     if(transparent){
8672         this.el.setOpacity(0);
8673     }
8674     this.el.on("mousedown", this.onMouseDown, this);
8675     if(!disableTrackOver){
8676         this.el.on("mouseover", this.onMouseOver, this);
8677         this.el.on("mouseout", this.onMouseOut, this);
8678     }
8679 };
8680
8681 // private
8682 Roo.Resizable.Handle.prototype = {
8683     afterResize : function(rz){
8684         Roo.log('after?');
8685         // do nothing
8686     },
8687     // private
8688     onMouseDown : function(e){
8689         this.rz.onMouseDown(this, e);
8690     },
8691     // private
8692     onMouseOver : function(e){
8693         this.rz.handleOver(this, e);
8694     },
8695     // private
8696     onMouseOut : function(e){
8697         this.rz.handleOut(this, e);
8698     }
8699 };/*
8700  * Based on:
8701  * Ext JS Library 1.1.1
8702  * Copyright(c) 2006-2007, Ext JS, LLC.
8703  *
8704  * Originally Released Under LGPL - original licence link has changed is not relivant.
8705  *
8706  * Fork - LGPL
8707  * <script type="text/javascript">
8708  */
8709
8710 /**
8711  * @class Roo.Editor
8712  * @extends Roo.Component
8713  * A base editor field that handles displaying/hiding on demand and has some built-in sizing and event handling logic.
8714  * @constructor
8715  * Create a new Editor
8716  * @param {Roo.form.Field} field The Field object (or descendant)
8717  * @param {Object} config The config object
8718  */
8719 Roo.Editor = function(field, config){
8720     Roo.Editor.superclass.constructor.call(this, config);
8721     this.field = field;
8722     this.addEvents({
8723         /**
8724              * @event beforestartedit
8725              * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
8726              * false from the handler of this event.
8727              * @param {Editor} this
8728              * @param {Roo.Element} boundEl The underlying element bound to this editor
8729              * @param {Mixed} value The field value being set
8730              */
8731         "beforestartedit" : true,
8732         /**
8733              * @event startedit
8734              * Fires when this editor is displayed
8735              * @param {Roo.Element} boundEl The underlying element bound to this editor
8736              * @param {Mixed} value The starting field value
8737              */
8738         "startedit" : true,
8739         /**
8740              * @event beforecomplete
8741              * Fires after a change has been made to the field, but before the change is reflected in the underlying
8742              * field.  Saving the change to the field can be canceled by returning false from the handler of this event.
8743              * Note that if the value has not changed and ignoreNoChange = true, the editing will still end but this
8744              * event will not fire since no edit actually occurred.
8745              * @param {Editor} this
8746              * @param {Mixed} value The current field value
8747              * @param {Mixed} startValue The original field value
8748              */
8749         "beforecomplete" : true,
8750         /**
8751              * @event complete
8752              * Fires after editing is complete and any changed value has been written to the underlying field.
8753              * @param {Editor} this
8754              * @param {Mixed} value The current field value
8755              * @param {Mixed} startValue The original field value
8756              */
8757         "complete" : true,
8758         /**
8759          * @event specialkey
8760          * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
8761          * {@link Roo.EventObject#getKey} to determine which key was pressed.
8762          * @param {Roo.form.Field} this
8763          * @param {Roo.EventObject} e The event object
8764          */
8765         "specialkey" : true
8766     });
8767 };
8768
8769 Roo.extend(Roo.Editor, Roo.Component, {
8770     /**
8771      * @cfg {Boolean/String} autosize
8772      * True for the editor to automatically adopt the size of the underlying field, "width" to adopt the width only,
8773      * or "height" to adopt the height only (defaults to false)
8774      */
8775     /**
8776      * @cfg {Boolean} revertInvalid
8777      * True to automatically revert the field value and cancel the edit when the user completes an edit and the field
8778      * validation fails (defaults to true)
8779      */
8780     /**
8781      * @cfg {Boolean} ignoreNoChange
8782      * True to skip the the edit completion process (no save, no events fired) if the user completes an edit and
8783      * the value has not changed (defaults to false).  Applies only to string values - edits for other data types
8784      * will never be ignored.
8785      */
8786     /**
8787      * @cfg {Boolean} hideEl
8788      * False to keep the bound element visible while the editor is displayed (defaults to true)
8789      */
8790     /**
8791      * @cfg {Mixed} value
8792      * The data value of the underlying field (defaults to "")
8793      */
8794     value : "",
8795     /**
8796      * @cfg {String} alignment
8797      * The position to align to (see {@link Roo.Element#alignTo} for more details, defaults to "c-c?").
8798      */
8799     alignment: "c-c?",
8800     /**
8801      * @cfg {Boolean/String} shadow "sides" for sides/bottom only, "frame" for 4-way shadow, and "drop"
8802      * for bottom-right shadow (defaults to "frame")
8803      */
8804     shadow : "frame",
8805     /**
8806      * @cfg {Boolean} constrain True to constrain the editor to the viewport
8807      */
8808     constrain : false,
8809     /**
8810      * @cfg {Boolean} completeOnEnter True to complete the edit when the enter key is pressed (defaults to false)
8811      */
8812     completeOnEnter : false,
8813     /**
8814      * @cfg {Boolean} cancelOnEsc True to cancel the edit when the escape key is pressed (defaults to false)
8815      */
8816     cancelOnEsc : false,
8817     /**
8818      * @cfg {Boolean} updateEl True to update the innerHTML of the bound element when the update completes (defaults to false)
8819      */
8820     updateEl : false,
8821
8822     // private
8823     onRender : function(ct, position){
8824         this.el = new Roo.Layer({
8825             shadow: this.shadow,
8826             cls: "x-editor",
8827             parentEl : ct,
8828             shim : this.shim,
8829             shadowOffset:4,
8830             id: this.id,
8831             constrain: this.constrain
8832         });
8833         this.el.setStyle("overflow", Roo.isGecko ? "auto" : "hidden");
8834         if(this.field.msgTarget != 'title'){
8835             this.field.msgTarget = 'qtip';
8836         }
8837         this.field.render(this.el);
8838         if(Roo.isGecko){
8839             this.field.el.dom.setAttribute('autocomplete', 'off');
8840         }
8841         this.field.on("specialkey", this.onSpecialKey, this);
8842         if(this.swallowKeys){
8843             this.field.el.swallowEvent(['keydown','keypress']);
8844         }
8845         this.field.show();
8846         this.field.on("blur", this.onBlur, this);
8847         if(this.field.grow){
8848             this.field.on("autosize", this.el.sync,  this.el, {delay:1});
8849         }
8850     },
8851
8852     onSpecialKey : function(field, e)
8853     {
8854         //Roo.log('editor onSpecialKey');
8855         if(this.completeOnEnter && e.getKey() == e.ENTER){
8856             e.stopEvent();
8857             this.completeEdit();
8858             return;
8859         }
8860         // do not fire special key otherwise it might hide close the editor...
8861         if(e.getKey() == e.ENTER){    
8862             return;
8863         }
8864         if(this.cancelOnEsc && e.getKey() == e.ESC){
8865             this.cancelEdit();
8866             return;
8867         } 
8868         this.fireEvent('specialkey', field, e);
8869     
8870     },
8871
8872     /**
8873      * Starts the editing process and shows the editor.
8874      * @param {String/HTMLElement/Element} el The element to edit
8875      * @param {String} value (optional) A value to initialize the editor with. If a value is not provided, it defaults
8876       * to the innerHTML of el.
8877      */
8878     startEdit : function(el, value){
8879         if(this.editing){
8880             this.completeEdit();
8881         }
8882         this.boundEl = Roo.get(el);
8883         var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
8884         if(!this.rendered){
8885             this.render(this.parentEl || document.body);
8886         }
8887         if(this.fireEvent("beforestartedit", this, this.boundEl, v) === false){
8888             return;
8889         }
8890         this.startValue = v;
8891         this.field.setValue(v);
8892         if(this.autoSize){
8893             var sz = this.boundEl.getSize();
8894             switch(this.autoSize){
8895                 case "width":
8896                 this.setSize(sz.width,  "");
8897                 break;
8898                 case "height":
8899                 this.setSize("",  sz.height);
8900                 break;
8901                 default:
8902                 this.setSize(sz.width,  sz.height);
8903             }
8904         }
8905         this.el.alignTo(this.boundEl, this.alignment);
8906         this.editing = true;
8907         if(Roo.QuickTips){
8908             Roo.QuickTips.disable();
8909         }
8910         this.show();
8911     },
8912
8913     /**
8914      * Sets the height and width of this editor.
8915      * @param {Number} width The new width
8916      * @param {Number} height The new height
8917      */
8918     setSize : function(w, h){
8919         this.field.setSize(w, h);
8920         if(this.el){
8921             this.el.sync();
8922         }
8923     },
8924
8925     /**
8926      * Realigns the editor to the bound field based on the current alignment config value.
8927      */
8928     realign : function(){
8929         this.el.alignTo(this.boundEl, this.alignment);
8930     },
8931
8932     /**
8933      * Ends the editing process, persists the changed value to the underlying field, and hides the editor.
8934      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after edit (defaults to false)
8935      */
8936     completeEdit : function(remainVisible){
8937         if(!this.editing){
8938             return;
8939         }
8940         var v = this.getValue();
8941         if(this.revertInvalid !== false && !this.field.isValid()){
8942             v = this.startValue;
8943             this.cancelEdit(true);
8944         }
8945         if(String(v) === String(this.startValue) && this.ignoreNoChange){
8946             this.editing = false;
8947             this.hide();
8948             return;
8949         }
8950         if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
8951             this.editing = false;
8952             if(this.updateEl && this.boundEl){
8953                 this.boundEl.update(v);
8954             }
8955             if(remainVisible !== true){
8956                 this.hide();
8957             }
8958             this.fireEvent("complete", this, v, this.startValue);
8959         }
8960     },
8961
8962     // private
8963     onShow : function(){
8964         this.el.show();
8965         if(this.hideEl !== false){
8966             this.boundEl.hide();
8967         }
8968         this.field.show();
8969         if(Roo.isIE && !this.fixIEFocus){ // IE has problems with focusing the first time
8970             this.fixIEFocus = true;
8971             this.deferredFocus.defer(50, this);
8972         }else{
8973             this.field.focus();
8974         }
8975         this.fireEvent("startedit", this.boundEl, this.startValue);
8976     },
8977
8978     deferredFocus : function(){
8979         if(this.editing){
8980             this.field.focus();
8981         }
8982     },
8983
8984     /**
8985      * Cancels the editing process and hides the editor without persisting any changes.  The field value will be
8986      * reverted to the original starting value.
8987      * @param {Boolean} remainVisible Override the default behavior and keep the editor visible after
8988      * cancel (defaults to false)
8989      */
8990     cancelEdit : function(remainVisible){
8991         if(this.editing){
8992             this.setValue(this.startValue);
8993             if(remainVisible !== true){
8994                 this.hide();
8995             }
8996         }
8997     },
8998
8999     // private
9000     onBlur : function(){
9001         if(this.allowBlur !== true && this.editing){
9002             this.completeEdit();
9003         }
9004     },
9005
9006     // private
9007     onHide : function(){
9008         if(this.editing){
9009             this.completeEdit();
9010             return;
9011         }
9012         this.field.blur();
9013         if(this.field.collapse){
9014             this.field.collapse();
9015         }
9016         this.el.hide();
9017         if(this.hideEl !== false){
9018             this.boundEl.show();
9019         }
9020         if(Roo.QuickTips){
9021             Roo.QuickTips.enable();
9022         }
9023     },
9024
9025     /**
9026      * Sets the data value of the editor
9027      * @param {Mixed} value Any valid value supported by the underlying field
9028      */
9029     setValue : function(v){
9030         this.field.setValue(v);
9031     },
9032
9033     /**
9034      * Gets the data value of the editor
9035      * @return {Mixed} The data value
9036      */
9037     getValue : function(){
9038         return this.field.getValue();
9039     }
9040 });/*
9041  * Based on:
9042  * Ext JS Library 1.1.1
9043  * Copyright(c) 2006-2007, Ext JS, LLC.
9044  *
9045  * Originally Released Under LGPL - original licence link has changed is not relivant.
9046  *
9047  * Fork - LGPL
9048  * <script type="text/javascript">
9049  */
9050  
9051 /**
9052  * @class Roo.BasicDialog
9053  * @extends Roo.util.Observable
9054  * @parent none builder
9055  * Lightweight Dialog Class.  The code below shows the creation of a typical dialog using existing HTML markup:
9056  * <pre><code>
9057 var dlg = new Roo.BasicDialog("my-dlg", {
9058     height: 200,
9059     width: 300,
9060     minHeight: 100,
9061     minWidth: 150,
9062     modal: true,
9063     proxyDrag: true,
9064     shadow: true
9065 });
9066 dlg.addKeyListener(27, dlg.hide, dlg); // ESC can also close the dialog
9067 dlg.addButton('OK', dlg.hide, dlg);    // Could call a save function instead of hiding
9068 dlg.addButton('Cancel', dlg.hide, dlg);
9069 dlg.show();
9070 </code></pre>
9071   <b>A Dialog should always be a direct child of the body element.</b>
9072  * @cfg {Boolean/DomHelper} autoCreate True to auto create from scratch, or using a DomHelper Object (defaults to false)
9073  * @cfg {String} title Default text to display in the title bar (defaults to null)
9074  * @cfg {Number} width Width of the dialog in pixels (can also be set via CSS).  Determined by browser if unspecified.
9075  * @cfg {Number} height Height of the dialog in pixels (can also be set via CSS).  Determined by browser if unspecified.
9076  * @cfg {Number} x The default left page coordinate of the dialog (defaults to center screen)
9077  * @cfg {Number} y The default top page coordinate of the dialog (defaults to center screen)
9078  * @cfg {String/Element} animateTarget Id or element from which the dialog should animate while opening
9079  * (defaults to null with no animation)
9080  * @cfg {Boolean} resizable False to disable manual dialog resizing (defaults to true)
9081  * @cfg {String} resizeHandles Which resize handles to display - see the {@link Roo.Resizable} handles config
9082  * property for valid values (defaults to 'all')
9083  * @cfg {Number} minHeight The minimum allowable height for a resizable dialog (defaults to 80)
9084  * @cfg {Number} minWidth The minimum allowable width for a resizable dialog (defaults to 200)
9085  * @cfg {Boolean} modal True to show the dialog modally, preventing user interaction with the rest of the page (defaults to false)
9086  * @cfg {Boolean} autoScroll True to allow the dialog body contents to overflow and display scrollbars (defaults to false)
9087  * @cfg {Boolean} closable False to remove the built-in top-right corner close button (defaults to true)
9088  * @cfg {Boolean} collapsible False to remove the built-in top-right corner collapse button (defaults to true)
9089  * @cfg {Boolean} constraintoviewport True to keep the dialog constrained within the visible viewport boundaries (defaults to true)
9090  * @cfg {Boolean} syncHeightBeforeShow True to cause the dimensions to be recalculated before the dialog is shown (defaults to false)
9091  * @cfg {Boolean} draggable False to disable dragging of the dialog within the viewport (defaults to true)
9092  * @cfg {Boolean} autoTabs If true, all elements with class 'x-dlg-tab' will get automatically converted to tabs (defaults to false)
9093  * @cfg {String} tabTag The tag name of tab elements, used when autoTabs = true (defaults to 'div')
9094  * @cfg {Boolean} proxyDrag True to drag a lightweight proxy element rather than the dialog itself, used when
9095  * draggable = true (defaults to false)
9096  * @cfg {Boolean} fixedcenter True to ensure that anytime the dialog is shown or resized it gets centered (defaults to false)
9097  * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
9098  * shadow (defaults to false)
9099  * @cfg {Number} shadowOffset The number of pixels to offset the shadow if displayed (defaults to 5)
9100  * @cfg {String} buttonAlign Valid values are "left," "center" and "right" (defaults to "right")
9101  * @cfg {Number} minButtonWidth Minimum width of all dialog buttons (defaults to 75)
9102  * @cfg {Array} buttons Array of buttons
9103  * @cfg {Boolean} shim True to create an iframe shim that prevents selects from showing through (defaults to false)
9104  * @constructor
9105  * Create a new BasicDialog.
9106  * @param {String/HTMLElement/Roo.Element} el The container element or DOM node, or its id
9107  * @param {Object} config Configuration options
9108  */
9109 Roo.BasicDialog = function(el, config){
9110     this.el = Roo.get(el);
9111     var dh = Roo.DomHelper;
9112     if(!this.el && config && config.autoCreate){
9113         if(typeof config.autoCreate == "object"){
9114             if(!config.autoCreate.id){
9115                 config.autoCreate.id = el;
9116             }
9117             this.el = dh.append(document.body,
9118                         config.autoCreate, true);
9119         }else{
9120             this.el = dh.append(document.body,
9121                         {tag: "div", id: el, style:'visibility:hidden;'}, true);
9122         }
9123     }
9124     el = this.el;
9125     el.setDisplayed(true);
9126     el.hide = this.hideAction;
9127     this.id = el.id;
9128     el.addClass("x-dlg");
9129
9130     Roo.apply(this, config);
9131
9132     this.proxy = el.createProxy("x-dlg-proxy");
9133     this.proxy.hide = this.hideAction;
9134     this.proxy.setOpacity(.5);
9135     this.proxy.hide();
9136
9137     if(config.width){
9138         el.setWidth(config.width);
9139     }
9140     if(config.height){
9141         el.setHeight(config.height);
9142     }
9143     this.size = el.getSize();
9144     if(typeof config.x != "undefined" && typeof config.y != "undefined"){
9145         this.xy = [config.x,config.y];
9146     }else{
9147         this.xy = el.getCenterXY(true);
9148     }
9149     /** The header element @type Roo.Element */
9150     this.header = el.child("> .x-dlg-hd");
9151     /** The body element @type Roo.Element */
9152     this.body = el.child("> .x-dlg-bd");
9153     /** The footer element @type Roo.Element */
9154     this.footer = el.child("> .x-dlg-ft");
9155
9156     if(!this.header){
9157         this.header = el.createChild({tag: "div", cls:"x-dlg-hd", html: "&#160;"}, this.body ? this.body.dom : null);
9158     }
9159     if(!this.body){
9160         this.body = el.createChild({tag: "div", cls:"x-dlg-bd"});
9161     }
9162
9163     this.header.unselectable();
9164     if(this.title){
9165         this.header.update(this.title);
9166     }
9167     // this element allows the dialog to be focused for keyboard event
9168     this.focusEl = el.createChild({tag: "a", href:"#", cls:"x-dlg-focus", tabIndex:"-1"});
9169     this.focusEl.swallowEvent("click", true);
9170
9171     this.header.wrap({cls:"x-dlg-hd-right"}).wrap({cls:"x-dlg-hd-left"}, true);
9172
9173     // wrap the body and footer for special rendering
9174     this.bwrap = this.body.wrap({tag: "div", cls:"x-dlg-dlg-body"});
9175     if(this.footer){
9176         this.bwrap.dom.appendChild(this.footer.dom);
9177     }
9178
9179     this.bg = this.el.createChild({
9180         tag: "div", cls:"x-dlg-bg",
9181         html: '<div class="x-dlg-bg-left"><div class="x-dlg-bg-right"><div class="x-dlg-bg-center">&#160;</div></div></div>'
9182     });
9183     this.centerBg = this.bg.child("div.x-dlg-bg-center");
9184
9185
9186     if(this.autoScroll !== false && !this.autoTabs){
9187         this.body.setStyle("overflow", "auto");
9188     }
9189
9190     this.toolbox = this.el.createChild({cls: "x-dlg-toolbox"});
9191
9192     if(this.closable !== false){
9193         this.el.addClass("x-dlg-closable");
9194         this.close = this.toolbox.createChild({cls:"x-dlg-close"});
9195         this.close.on("click", this.closeClick, this);
9196         this.close.addClassOnOver("x-dlg-close-over");
9197     }
9198     if(this.collapsible !== false){
9199         this.collapseBtn = this.toolbox.createChild({cls:"x-dlg-collapse"});
9200         this.collapseBtn.on("click", this.collapseClick, this);
9201         this.collapseBtn.addClassOnOver("x-dlg-collapse-over");
9202         this.header.on("dblclick", this.collapseClick, this);
9203     }
9204     if(this.resizable !== false){
9205         this.el.addClass("x-dlg-resizable");
9206         this.resizer = new Roo.Resizable(el, {
9207             minWidth: this.minWidth || 80,
9208             minHeight:this.minHeight || 80,
9209             handles: this.resizeHandles || "all",
9210             pinned: true
9211         });
9212         this.resizer.on("beforeresize", this.beforeResize, this);
9213         this.resizer.on("resize", this.onResize, this);
9214     }
9215     if(this.draggable !== false){
9216         el.addClass("x-dlg-draggable");
9217         if (!this.proxyDrag) {
9218             var dd = new Roo.dd.DD(el.dom.id, "WindowDrag");
9219         }
9220         else {
9221             var dd = new Roo.dd.DDProxy(el.dom.id, "WindowDrag", {dragElId: this.proxy.id});
9222         }
9223         dd.setHandleElId(this.header.id);
9224         dd.endDrag = this.endMove.createDelegate(this);
9225         dd.startDrag = this.startMove.createDelegate(this);
9226         dd.onDrag = this.onDrag.createDelegate(this);
9227         dd.scroll = false;
9228         this.dd = dd;
9229     }
9230     if(this.modal){
9231         this.mask = dh.append(document.body, {tag: "div", cls:"x-dlg-mask"}, true);
9232         this.mask.enableDisplayMode("block");
9233         this.mask.hide();
9234         this.el.addClass("x-dlg-modal");
9235     }
9236     if(this.shadow){
9237         this.shadow = new Roo.Shadow({
9238             mode : typeof this.shadow == "string" ? this.shadow : "sides",
9239             offset : this.shadowOffset
9240         });
9241     }else{
9242         this.shadowOffset = 0;
9243     }
9244     if(Roo.useShims && this.shim !== false){
9245         this.shim = this.el.createShim();
9246         this.shim.hide = this.hideAction;
9247         this.shim.hide();
9248     }else{
9249         this.shim = false;
9250     }
9251     if(this.autoTabs){
9252         this.initTabs();
9253     }
9254     if (this.buttons) { 
9255         var bts= this.buttons;
9256         this.buttons = [];
9257         Roo.each(bts, function(b) {
9258             this.addButton(b);
9259         }, this);
9260     }
9261     
9262     
9263     this.addEvents({
9264         /**
9265          * @event keydown
9266          * Fires when a key is pressed
9267          * @param {Roo.BasicDialog} this
9268          * @param {Roo.EventObject} e
9269          */
9270         "keydown" : true,
9271         /**
9272          * @event move
9273          * Fires when this dialog is moved by the user.
9274          * @param {Roo.BasicDialog} this
9275          * @param {Number} x The new page X
9276          * @param {Number} y The new page Y
9277          */
9278         "move" : true,
9279         /**
9280          * @event resize
9281          * Fires when this dialog is resized by the user.
9282          * @param {Roo.BasicDialog} this
9283          * @param {Number} width The new width
9284          * @param {Number} height The new height
9285          */
9286         "resize" : true,
9287         /**
9288          * @event beforehide
9289          * Fires before this dialog is hidden.
9290          * @param {Roo.BasicDialog} this
9291          */
9292         "beforehide" : true,
9293         /**
9294          * @event hide
9295          * Fires when this dialog is hidden.
9296          * @param {Roo.BasicDialog} this
9297          */
9298         "hide" : true,
9299         /**
9300          * @event beforeshow
9301          * Fires before this dialog is shown.
9302          * @param {Roo.BasicDialog} this
9303          */
9304         "beforeshow" : true,
9305         /**
9306          * @event show
9307          * Fires when this dialog is shown.
9308          * @param {Roo.BasicDialog} this
9309          */
9310         "show" : true
9311     });
9312     el.on("keydown", this.onKeyDown, this);
9313     el.on("mousedown", this.toFront, this);
9314     Roo.EventManager.onWindowResize(this.adjustViewport, this, true);
9315     this.el.hide();
9316     Roo.DialogManager.register(this);
9317     Roo.BasicDialog.superclass.constructor.call(this);
9318 };
9319
9320 Roo.extend(Roo.BasicDialog, Roo.util.Observable, {
9321     shadowOffset: Roo.isIE ? 6 : 5,
9322     minHeight: 80,
9323     minWidth: 200,
9324     minButtonWidth: 75,
9325     defaultButton: null,
9326     buttonAlign: "right",
9327     tabTag: 'div',
9328     firstShow: true,
9329
9330     /**
9331      * Sets the dialog title text
9332      * @param {String} text The title text to display
9333      * @return {Roo.BasicDialog} this
9334      */
9335     setTitle : function(text){
9336         this.header.update(text);
9337         return this;
9338     },
9339
9340     // private
9341     closeClick : function(){
9342         this.hide();
9343     },
9344
9345     // private
9346     collapseClick : function(){
9347         this[this.collapsed ? "expand" : "collapse"]();
9348     },
9349
9350     /**
9351      * Collapses the dialog to its minimized state (only the title bar is visible).
9352      * Equivalent to the user clicking the collapse dialog button.
9353      */
9354     collapse : function(){
9355         if(!this.collapsed){
9356             this.collapsed = true;
9357             this.el.addClass("x-dlg-collapsed");
9358             this.restoreHeight = this.el.getHeight();
9359             this.resizeTo(this.el.getWidth(), this.header.getHeight());
9360         }
9361     },
9362
9363     /**
9364      * Expands a collapsed dialog back to its normal state.  Equivalent to the user
9365      * clicking the expand dialog button.
9366      */
9367     expand : function(){
9368         if(this.collapsed){
9369             this.collapsed = false;
9370             this.el.removeClass("x-dlg-collapsed");
9371             this.resizeTo(this.el.getWidth(), this.restoreHeight);
9372         }
9373     },
9374
9375     /**
9376      * Reinitializes the tabs component, clearing out old tabs and finding new ones.
9377      * @return {Roo.TabPanel} The tabs component
9378      */
9379     initTabs : function(){
9380         var tabs = this.getTabs();
9381         while(tabs.getTab(0)){
9382             tabs.removeTab(0);
9383         }
9384         this.el.select(this.tabTag+'.x-dlg-tab').each(function(el){
9385             var dom = el.dom;
9386             tabs.addTab(Roo.id(dom), dom.title);
9387             dom.title = "";
9388         });
9389         tabs.activate(0);
9390         return tabs;
9391     },
9392
9393     // private
9394     beforeResize : function(){
9395         this.resizer.minHeight = Math.max(this.minHeight, this.getHeaderFooterHeight(true)+40);
9396     },
9397
9398     // private
9399     onResize : function(){
9400         this.refreshSize();
9401         this.syncBodyHeight();
9402         this.adjustAssets();
9403         this.focus();
9404         this.fireEvent("resize", this, this.size.width, this.size.height);
9405     },
9406
9407     // private
9408     onKeyDown : function(e){
9409         if(this.isVisible()){
9410             this.fireEvent("keydown", this, e);
9411         }
9412     },
9413
9414     /**
9415      * Resizes the dialog.
9416      * @param {Number} width
9417      * @param {Number} height
9418      * @return {Roo.BasicDialog} this
9419      */
9420     resizeTo : function(width, height){
9421         this.el.setSize(width, height);
9422         this.size = {width: width, height: height};
9423         this.syncBodyHeight();
9424         if(this.fixedcenter){
9425             this.center();
9426         }
9427         if(this.isVisible()){
9428             this.constrainXY();
9429             this.adjustAssets();
9430         }
9431         this.fireEvent("resize", this, width, height);
9432         return this;
9433     },
9434
9435
9436     /**
9437      * Resizes the dialog to fit the specified content size.
9438      * @param {Number} width
9439      * @param {Number} height
9440      * @return {Roo.BasicDialog} this
9441      */
9442     setContentSize : function(w, h){
9443         h += this.getHeaderFooterHeight() + this.body.getMargins("tb");
9444         w += this.body.getMargins("lr") + this.bwrap.getMargins("lr") + this.centerBg.getPadding("lr");
9445         //if(!this.el.isBorderBox()){
9446             h +=  this.body.getPadding("tb") + this.bwrap.getBorderWidth("tb") + this.body.getBorderWidth("tb") + this.el.getBorderWidth("tb");
9447             w += this.body.getPadding("lr") + this.bwrap.getBorderWidth("lr") + this.body.getBorderWidth("lr") + this.bwrap.getPadding("lr") + this.el.getBorderWidth("lr");
9448         //}
9449         if(this.tabs){
9450             h += this.tabs.stripWrap.getHeight() + this.tabs.bodyEl.getMargins("tb") + this.tabs.bodyEl.getPadding("tb");
9451             w += this.tabs.bodyEl.getMargins("lr") + this.tabs.bodyEl.getPadding("lr");
9452         }
9453         this.resizeTo(w, h);
9454         return this;
9455     },
9456
9457     /**
9458      * Adds a key listener for when this dialog is displayed.  This allows you to hook in a function that will be
9459      * executed in response to a particular key being pressed while the dialog is active.
9460      * @param {Number/Array/Object} key Either the numeric key code, array of key codes or an object with the following options:
9461      *                                  {key: (number or array), shift: (true/false), ctrl: (true/false), alt: (true/false)}
9462      * @param {Function} fn The function to call
9463      * @param {Object} scope (optional) The scope of the function
9464      * @return {Roo.BasicDialog} this
9465      */
9466     addKeyListener : function(key, fn, scope){
9467         var keyCode, shift, ctrl, alt;
9468         if(typeof key == "object" && !(key instanceof Array)){
9469             keyCode = key["key"];
9470             shift = key["shift"];
9471             ctrl = key["ctrl"];
9472             alt = key["alt"];
9473         }else{
9474             keyCode = key;
9475         }
9476         var handler = function(dlg, e){
9477             if((!shift || e.shiftKey) && (!ctrl || e.ctrlKey) &&  (!alt || e.altKey)){
9478                 var k = e.getKey();
9479                 if(keyCode instanceof Array){
9480                     for(var i = 0, len = keyCode.length; i < len; i++){
9481                         if(keyCode[i] == k){
9482                           fn.call(scope || window, dlg, k, e);
9483                           return;
9484                         }
9485                     }
9486                 }else{
9487                     if(k == keyCode){
9488                         fn.call(scope || window, dlg, k, e);
9489                     }
9490                 }
9491             }
9492         };
9493         this.on("keydown", handler);
9494         return this;
9495     },
9496
9497     /**
9498      * Returns the TabPanel component (creates it if it doesn't exist).
9499      * Note: If you wish to simply check for the existence of tabs without creating them,
9500      * check for a null 'tabs' property.
9501      * @return {Roo.TabPanel} The tabs component
9502      */
9503     getTabs : function(){
9504         if(!this.tabs){
9505             this.el.addClass("x-dlg-auto-tabs");
9506             this.body.addClass(this.tabPosition == "bottom" ? "x-tabs-bottom" : "x-tabs-top");
9507             this.tabs = new Roo.TabPanel(this.body.dom, this.tabPosition == "bottom");
9508         }
9509         return this.tabs;
9510     },
9511
9512     /**
9513      * Adds a button to the footer section of the dialog.
9514      * @param {String/Object} config A string becomes the button text, an object can either be a Button config
9515      * object or a valid Roo.DomHelper element config
9516      * @param {Function} handler The function called when the button is clicked
9517      * @param {Object} scope (optional) The scope of the handler function (accepts position as a property)
9518      * @return {Roo.Button} The new button
9519      */
9520     addButton : function(config, handler, scope){
9521         var dh = Roo.DomHelper;
9522         if(!this.footer){
9523             this.footer = dh.append(this.bwrap, {tag: "div", cls:"x-dlg-ft"}, true);
9524         }
9525         if(!this.btnContainer){
9526             var tb = this.footer.createChild({
9527
9528                 cls:"x-dlg-btns x-dlg-btns-"+this.buttonAlign,
9529                 html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
9530             }, null, true);
9531             this.btnContainer = tb.firstChild.firstChild.firstChild;
9532         }
9533         var bconfig = {
9534             handler: handler,
9535             scope: scope,
9536             minWidth: this.minButtonWidth,
9537             hideParent:true
9538         };
9539         if(typeof config == "string"){
9540             bconfig.text = config;
9541         }else{
9542             if(config.tag){
9543                 bconfig.dhconfig = config;
9544             }else{
9545                 Roo.apply(bconfig, config);
9546             }
9547         }
9548         var fc = false;
9549         if ((typeof(bconfig.position) != 'undefined') && bconfig.position < this.btnContainer.childNodes.length-1) {
9550             bconfig.position = Math.max(0, bconfig.position);
9551             fc = this.btnContainer.childNodes[bconfig.position];
9552         }
9553          
9554         var btn = new Roo.Button(
9555             fc ? 
9556                 this.btnContainer.insertBefore(document.createElement("td"),fc)
9557                 : this.btnContainer.appendChild(document.createElement("td")),
9558             //Roo.get(this.btnContainer).createChild( { tag: 'td'},  fc ),
9559             bconfig
9560         );
9561         this.syncBodyHeight();
9562         if(!this.buttons){
9563             /**
9564              * Array of all the buttons that have been added to this dialog via addButton
9565              * @type Array
9566              */
9567             this.buttons = [];
9568         }
9569         this.buttons.push(btn);
9570         return btn;
9571     },
9572
9573     /**
9574      * Sets the default button to be focused when the dialog is displayed.
9575      * @param {Roo.BasicDialog.Button} btn The button object returned by {@link #addButton}
9576      * @return {Roo.BasicDialog} this
9577      */
9578     setDefaultButton : function(btn){
9579         this.defaultButton = btn;
9580         return this;
9581     },
9582
9583     // private
9584     getHeaderFooterHeight : function(safe){
9585         var height = 0;
9586         if(this.header){
9587            height += this.header.getHeight();
9588         }
9589         if(this.footer){
9590            var fm = this.footer.getMargins();
9591             height += (this.footer.getHeight()+fm.top+fm.bottom);
9592         }
9593         height += this.bwrap.getPadding("tb")+this.bwrap.getBorderWidth("tb");
9594         height += this.centerBg.getPadding("tb");
9595         return height;
9596     },
9597
9598     // private
9599     syncBodyHeight : function()
9600     {
9601         var bd = this.body, // the text
9602             cb = this.centerBg, // wrapper around bottom.. but does not seem to be used..
9603             bw = this.bwrap;
9604         var height = this.size.height - this.getHeaderFooterHeight(false);
9605         bd.setHeight(height-bd.getMargins("tb"));
9606         var hh = this.header.getHeight();
9607         var h = this.size.height-hh;
9608         cb.setHeight(h);
9609         
9610         bw.setLeftTop(cb.getPadding("l"), hh+cb.getPadding("t"));
9611         bw.setHeight(h-cb.getPadding("tb"));
9612         
9613         bw.setWidth(this.el.getWidth(true)-cb.getPadding("lr"));
9614         bd.setWidth(bw.getWidth(true));
9615         if(this.tabs){
9616             this.tabs.syncHeight();
9617             if(Roo.isIE){
9618                 this.tabs.el.repaint();
9619             }
9620         }
9621     },
9622
9623     /**
9624      * Restores the previous state of the dialog if Roo.state is configured.
9625      * @return {Roo.BasicDialog} this
9626      */
9627     restoreState : function(){
9628         var box = Roo.state.Manager.get(this.stateId || (this.el.id + "-state"));
9629         if(box && box.width){
9630             this.xy = [box.x, box.y];
9631             this.resizeTo(box.width, box.height);
9632         }
9633         return this;
9634     },
9635
9636     // private
9637     beforeShow : function(){
9638         this.expand();
9639         if(this.fixedcenter){
9640             this.xy = this.el.getCenterXY(true);
9641         }
9642         if(this.modal){
9643             Roo.get(document.body).addClass("x-body-masked");
9644             this.mask.setSize(Roo.lib.Dom.getViewWidth(true), Roo.lib.Dom.getViewHeight(true));
9645             this.mask.show();
9646         }
9647         this.constrainXY();
9648     },
9649
9650     // private
9651     animShow : function(){
9652         var b = Roo.get(this.animateTarget).getBox();
9653         this.proxy.setSize(b.width, b.height);
9654         this.proxy.setLocation(b.x, b.y);
9655         this.proxy.show();
9656         this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height,
9657                     true, .35, this.showEl.createDelegate(this));
9658     },
9659
9660     /**
9661      * Shows the dialog.
9662      * @param {String/HTMLElement/Roo.Element} animateTarget (optional) Reset the animation target
9663      * @return {Roo.BasicDialog} this
9664      */
9665     show : function(animateTarget){
9666         if (this.fireEvent("beforeshow", this) === false){
9667             return;
9668         }
9669         if(this.syncHeightBeforeShow){
9670             this.syncBodyHeight();
9671         }else if(this.firstShow){
9672             this.firstShow = false;
9673             this.syncBodyHeight(); // sync the height on the first show instead of in the constructor
9674         }
9675         this.animateTarget = animateTarget || this.animateTarget;
9676         if(!this.el.isVisible()){
9677             this.beforeShow();
9678             if(this.animateTarget && Roo.get(this.animateTarget)){
9679                 this.animShow();
9680             }else{
9681                 this.showEl();
9682             }
9683         }
9684         return this;
9685     },
9686
9687     // private
9688     showEl : function(){
9689         this.proxy.hide();
9690         this.el.setXY(this.xy);
9691         this.el.show();
9692         this.adjustAssets(true);
9693         this.toFront();
9694         this.focus();
9695         // IE peekaboo bug - fix found by Dave Fenwick
9696         if(Roo.isIE){
9697             this.el.repaint();
9698         }
9699         this.fireEvent("show", this);
9700     },
9701
9702     /**
9703      * Focuses the dialog.  If a defaultButton is set, it will receive focus, otherwise the
9704      * dialog itself will receive focus.
9705      */
9706     focus : function(){
9707         if(this.defaultButton){
9708             this.defaultButton.focus();
9709         }else{
9710             this.focusEl.focus();
9711         }
9712     },
9713
9714     // private
9715     constrainXY : function(){
9716         if(this.constraintoviewport !== false){
9717             if(!this.viewSize){
9718                 if(this.container){
9719                     var s = this.container.getSize();
9720                     this.viewSize = [s.width, s.height];
9721                 }else{
9722                     this.viewSize = [Roo.lib.Dom.getViewWidth(),Roo.lib.Dom.getViewHeight()];
9723                 }
9724             }
9725             var s = Roo.get(this.container||document).getScroll();
9726
9727             var x = this.xy[0], y = this.xy[1];
9728             var w = this.size.width, h = this.size.height;
9729             var vw = this.viewSize[0], vh = this.viewSize[1];
9730             // only move it if it needs it
9731             var moved = false;
9732             // first validate right/bottom
9733             if(x + w > vw+s.left){
9734                 x = vw - w;
9735                 moved = true;
9736             }
9737             if(y + h > vh+s.top){
9738                 y = vh - h;
9739                 moved = true;
9740             }
9741             // then make sure top/left isn't negative
9742             if(x < s.left){
9743                 x = s.left;
9744                 moved = true;
9745             }
9746             if(y < s.top){
9747                 y = s.top;
9748                 moved = true;
9749             }
9750             if(moved){
9751                 // cache xy
9752                 this.xy = [x, y];
9753                 if(this.isVisible()){
9754                     this.el.setLocation(x, y);
9755                     this.adjustAssets();
9756                 }
9757             }
9758         }
9759     },
9760
9761     // private
9762     onDrag : function(){
9763         if(!this.proxyDrag){
9764             this.xy = this.el.getXY();
9765             this.adjustAssets();
9766         }
9767     },
9768
9769     // private
9770     adjustAssets : function(doShow){
9771         var x = this.xy[0], y = this.xy[1];
9772         var w = this.size.width, h = this.size.height;
9773         if(doShow === true){
9774             if(this.shadow){
9775                 this.shadow.show(this.el);
9776             }
9777             if(this.shim){
9778                 this.shim.show();
9779             }
9780         }
9781         if(this.shadow && this.shadow.isVisible()){
9782             this.shadow.show(this.el);
9783         }
9784         if(this.shim && this.shim.isVisible()){
9785             this.shim.setBounds(x, y, w, h);
9786         }
9787     },
9788
9789     // private
9790     adjustViewport : function(w, h){
9791         if(!w || !h){
9792             w = Roo.lib.Dom.getViewWidth();
9793             h = Roo.lib.Dom.getViewHeight();
9794         }
9795         // cache the size
9796         this.viewSize = [w, h];
9797         if(this.modal && this.mask.isVisible()){
9798             this.mask.setSize(w, h); // first make sure the mask isn't causing overflow
9799             this.mask.setSize(Roo.lib.Dom.getViewWidth(true), Roo.lib.Dom.getViewHeight(true));
9800         }
9801         if(this.isVisible()){
9802             this.constrainXY();
9803         }
9804     },
9805
9806     /**
9807      * Destroys this dialog and all its supporting elements (including any tabs, shim,
9808      * shadow, proxy, mask, etc.)  Also removes all event listeners.
9809      * @param {Boolean} removeEl (optional) true to remove the element from the DOM
9810      */
9811     destroy : function(removeEl){
9812         if(this.isVisible()){
9813             this.animateTarget = null;
9814             this.hide();
9815         }
9816         Roo.EventManager.removeResizeListener(this.adjustViewport, this);
9817         if(this.tabs){
9818             this.tabs.destroy(removeEl);
9819         }
9820         Roo.destroy(
9821              this.shim,
9822              this.proxy,
9823              this.resizer,
9824              this.close,
9825              this.mask
9826         );
9827         if(this.dd){
9828             this.dd.unreg();
9829         }
9830         if(this.buttons){
9831            for(var i = 0, len = this.buttons.length; i < len; i++){
9832                this.buttons[i].destroy();
9833            }
9834         }
9835         this.el.removeAllListeners();
9836         if(removeEl === true){
9837             this.el.update("");
9838             this.el.remove();
9839         }
9840         Roo.DialogManager.unregister(this);
9841     },
9842
9843     // private
9844     startMove : function(){
9845         if(this.proxyDrag){
9846             this.proxy.show();
9847         }
9848         if(this.constraintoviewport !== false){
9849             this.dd.constrainTo(document.body, {right: this.shadowOffset, bottom: this.shadowOffset});
9850         }
9851     },
9852
9853     // private
9854     endMove : function(){
9855         if(!this.proxyDrag){
9856             Roo.dd.DD.prototype.endDrag.apply(this.dd, arguments);
9857         }else{
9858             Roo.dd.DDProxy.prototype.endDrag.apply(this.dd, arguments);
9859             this.proxy.hide();
9860         }
9861         this.refreshSize();
9862         this.adjustAssets();
9863         this.focus();
9864         this.fireEvent("move", this, this.xy[0], this.xy[1]);
9865     },
9866
9867     /**
9868      * Brings this dialog to the front of any other visible dialogs
9869      * @return {Roo.BasicDialog} this
9870      */
9871     toFront : function(){
9872         Roo.DialogManager.bringToFront(this);
9873         return this;
9874     },
9875
9876     /**
9877      * Sends this dialog to the back (under) of any other visible dialogs
9878      * @return {Roo.BasicDialog} this
9879      */
9880     toBack : function(){
9881         Roo.DialogManager.sendToBack(this);
9882         return this;
9883     },
9884
9885     /**
9886      * Centers this dialog in the viewport
9887      * @return {Roo.BasicDialog} this
9888      */
9889     center : function(){
9890         var xy = this.el.getCenterXY(true);
9891         this.moveTo(xy[0], xy[1]);
9892         return this;
9893     },
9894
9895     /**
9896      * Moves the dialog's top-left corner to the specified point
9897      * @param {Number} x
9898      * @param {Number} y
9899      * @return {Roo.BasicDialog} this
9900      */
9901     moveTo : function(x, y){
9902         this.xy = [x,y];
9903         if(this.isVisible()){
9904             this.el.setXY(this.xy);
9905             this.adjustAssets();
9906         }
9907         return this;
9908     },
9909
9910     /**
9911      * Aligns the dialog to the specified element
9912      * @param {String/HTMLElement/Roo.Element} element The element to align to.
9913      * @param {String} position The position to align to (see {@link Roo.Element#alignTo} for more details).
9914      * @param {Array} offsets (optional) Offset the positioning by [x, y]
9915      * @return {Roo.BasicDialog} this
9916      */
9917     alignTo : function(element, position, offsets){
9918         this.xy = this.el.getAlignToXY(element, position, offsets);
9919         if(this.isVisible()){
9920             this.el.setXY(this.xy);
9921             this.adjustAssets();
9922         }
9923         return this;
9924     },
9925
9926     /**
9927      * Anchors an element to another element and realigns it when the window is resized.
9928      * @param {String/HTMLElement/Roo.Element} element The element to align to.
9929      * @param {String} position The position to align to (see {@link Roo.Element#alignTo} for more details)
9930      * @param {Array} offsets (optional) Offset the positioning by [x, y]
9931      * @param {Boolean/Number} monitorScroll (optional) true to monitor body scroll and reposition. If this parameter
9932      * is a number, it is used as the buffer delay (defaults to 50ms).
9933      * @return {Roo.BasicDialog} this
9934      */
9935     anchorTo : function(el, alignment, offsets, monitorScroll){
9936         var action = function(){
9937             this.alignTo(el, alignment, offsets);
9938         };
9939         Roo.EventManager.onWindowResize(action, this);
9940         var tm = typeof monitorScroll;
9941         if(tm != 'undefined'){
9942             Roo.EventManager.on(window, 'scroll', action, this,
9943                 {buffer: tm == 'number' ? monitorScroll : 50});
9944         }
9945         action.call(this);
9946         return this;
9947     },
9948
9949     /**
9950      * Returns true if the dialog is visible
9951      * @return {Boolean}
9952      */
9953     isVisible : function(){
9954         return this.el.isVisible();
9955     },
9956
9957     // private
9958     animHide : function(callback){
9959         var b = Roo.get(this.animateTarget).getBox();
9960         this.proxy.show();
9961         this.proxy.setBounds(this.xy[0], this.xy[1], this.size.width, this.size.height);
9962         this.el.hide();
9963         this.proxy.setBounds(b.x, b.y, b.width, b.height, true, .35,
9964                     this.hideEl.createDelegate(this, [callback]));
9965     },
9966
9967     /**
9968      * Hides the dialog.
9969      * @param {Function} callback (optional) Function to call when the dialog is hidden
9970      * @return {Roo.BasicDialog} this
9971      */
9972     hide : function(callback){
9973         if (this.fireEvent("beforehide", this) === false){
9974             return;
9975         }
9976         if(this.shadow){
9977             this.shadow.hide();
9978         }
9979         if(this.shim) {
9980           this.shim.hide();
9981         }
9982         // sometimes animateTarget seems to get set.. causing problems...
9983         // this just double checks..
9984         if(this.animateTarget && Roo.get(this.animateTarget)) {
9985            this.animHide(callback);
9986         }else{
9987             this.el.hide();
9988             this.hideEl(callback);
9989         }
9990         return this;
9991     },
9992
9993     // private
9994     hideEl : function(callback){
9995         this.proxy.hide();
9996         if(this.modal){
9997             this.mask.hide();
9998             Roo.get(document.body).removeClass("x-body-masked");
9999         }
10000         this.fireEvent("hide", this);
10001         if(typeof callback == "function"){
10002             callback();
10003         }
10004     },
10005
10006     // private
10007     hideAction : function(){
10008         this.setLeft("-10000px");
10009         this.setTop("-10000px");
10010         this.setStyle("visibility", "hidden");
10011     },
10012
10013     // private
10014     refreshSize : function(){
10015         this.size = this.el.getSize();
10016         this.xy = this.el.getXY();
10017         Roo.state.Manager.set(this.stateId || this.el.id + "-state", this.el.getBox());
10018     },
10019
10020     // private
10021     // z-index is managed by the DialogManager and may be overwritten at any time
10022     setZIndex : function(index){
10023         if(this.modal){
10024             this.mask.setStyle("z-index", index);
10025         }
10026         if(this.shim){
10027             this.shim.setStyle("z-index", ++index);
10028         }
10029         if(this.shadow){
10030             this.shadow.setZIndex(++index);
10031         }
10032         this.el.setStyle("z-index", ++index);
10033         if(this.proxy){
10034             this.proxy.setStyle("z-index", ++index);
10035         }
10036         if(this.resizer){
10037             this.resizer.proxy.setStyle("z-index", ++index);
10038         }
10039
10040         this.lastZIndex = index;
10041     },
10042
10043     /**
10044      * Returns the element for this dialog
10045      * @return {Roo.Element} The underlying dialog Element
10046      */
10047     getEl : function(){
10048         return this.el;
10049     }
10050 });
10051
10052 /**
10053  * @class Roo.DialogManager
10054  * Provides global access to BasicDialogs that have been created and
10055  * support for z-indexing (layering) multiple open dialogs.
10056  */
10057 Roo.DialogManager = function(){
10058     var list = {};
10059     var accessList = [];
10060     var front = null;
10061
10062     // private
10063     var sortDialogs = function(d1, d2){
10064         return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
10065     };
10066
10067     // private
10068     var orderDialogs = function(){
10069         accessList.sort(sortDialogs);
10070         var seed = Roo.DialogManager.zseed;
10071         for(var i = 0, len = accessList.length; i < len; i++){
10072             var dlg = accessList[i];
10073             if(dlg){
10074                 dlg.setZIndex(seed + (i*10));
10075             }
10076         }
10077     };
10078
10079     return {
10080         /**
10081          * The starting z-index for BasicDialogs (defaults to 9000)
10082          * @type Number The z-index value
10083          */
10084         zseed : 9000,
10085
10086         // private
10087         register : function(dlg){
10088             list[dlg.id] = dlg;
10089             accessList.push(dlg);
10090         },
10091
10092         // private
10093         unregister : function(dlg){
10094             delete list[dlg.id];
10095             var i=0;
10096             var len=0;
10097             if(!accessList.indexOf){
10098                 for(  i = 0, len = accessList.length; i < len; i++){
10099                     if(accessList[i] == dlg){
10100                         accessList.splice(i, 1);
10101                         return;
10102                     }
10103                 }
10104             }else{
10105                  i = accessList.indexOf(dlg);
10106                 if(i != -1){
10107                     accessList.splice(i, 1);
10108                 }
10109             }
10110         },
10111
10112         /**
10113          * Gets a registered dialog by id
10114          * @param {String/Object} id The id of the dialog or a dialog
10115          * @return {Roo.BasicDialog} this
10116          */
10117         get : function(id){
10118             return typeof id == "object" ? id : list[id];
10119         },
10120
10121         /**
10122          * Brings the specified dialog to the front
10123          * @param {String/Object} dlg The id of the dialog or a dialog
10124          * @return {Roo.BasicDialog} this
10125          */
10126         bringToFront : function(dlg){
10127             dlg = this.get(dlg);
10128             if(dlg != front){
10129                 front = dlg;
10130                 dlg._lastAccess = new Date().getTime();
10131                 orderDialogs();
10132             }
10133             return dlg;
10134         },
10135
10136         /**
10137          * Sends the specified dialog to the back
10138          * @param {String/Object} dlg The id of the dialog or a dialog
10139          * @return {Roo.BasicDialog} this
10140          */
10141         sendToBack : function(dlg){
10142             dlg = this.get(dlg);
10143             dlg._lastAccess = -(new Date().getTime());
10144             orderDialogs();
10145             return dlg;
10146         },
10147
10148         /**
10149          * Hides all dialogs
10150          */
10151         hideAll : function(){
10152             for(var id in list){
10153                 if(list[id] && typeof list[id] != "function" && list[id].isVisible()){
10154                     list[id].hide();
10155                 }
10156             }
10157         }
10158     };
10159 }();
10160
10161 /**
10162  * @class Roo.LayoutDialog
10163  * @extends Roo.BasicDialog
10164  * @children Roo.ContentPanel
10165  * @parent builder none
10166  * Dialog which provides adjustments for working with a layout in a Dialog.
10167  * Add your necessary layout config options to the dialog's config.<br>
10168  * Example usage (including a nested layout):
10169  * <pre><code>
10170 if(!dialog){
10171     dialog = new Roo.LayoutDialog("download-dlg", {
10172         modal: true,
10173         width:600,
10174         height:450,
10175         shadow:true,
10176         minWidth:500,
10177         minHeight:350,
10178         autoTabs:true,
10179         proxyDrag:true,
10180         // layout config merges with the dialog config
10181         center:{
10182             tabPosition: "top",
10183             alwaysShowTabs: true
10184         }
10185     });
10186     dialog.addKeyListener(27, dialog.hide, dialog);
10187     dialog.setDefaultButton(dialog.addButton("Close", dialog.hide, dialog));
10188     dialog.addButton("Build It!", this.getDownload, this);
10189
10190     // we can even add nested layouts
10191     var innerLayout = new Roo.BorderLayout("dl-inner", {
10192         east: {
10193             initialSize: 200,
10194             autoScroll:true,
10195             split:true
10196         },
10197         center: {
10198             autoScroll:true
10199         }
10200     });
10201     innerLayout.beginUpdate();
10202     innerLayout.add("east", new Roo.ContentPanel("dl-details"));
10203     innerLayout.add("center", new Roo.ContentPanel("selection-panel"));
10204     innerLayout.endUpdate(true);
10205
10206     var layout = dialog.getLayout();
10207     layout.beginUpdate();
10208     layout.add("center", new Roo.ContentPanel("standard-panel",
10209                         {title: "Download the Source", fitToFrame:true}));
10210     layout.add("center", new Roo.NestedLayoutPanel(innerLayout,
10211                {title: "Build your own roo.js"}));
10212     layout.getRegion("center").showPanel(sp);
10213     layout.endUpdate();
10214 }
10215 </code></pre>
10216     * @constructor
10217     * @param {String/HTMLElement/Roo.Element} el The id of or container element, or config
10218     * @param {Object} config configuration options
10219   */
10220 Roo.LayoutDialog = function(el, cfg){
10221     
10222     var config=  cfg;
10223     if (typeof(cfg) == 'undefined') {
10224         config = Roo.apply({}, el);
10225         // not sure why we use documentElement here.. - it should always be body.
10226         // IE7 borks horribly if we use documentElement.
10227         // webkit also does not like documentElement - it creates a body element...
10228         el = Roo.get( document.body || document.documentElement ).createChild();
10229         //config.autoCreate = true;
10230     }
10231     
10232     
10233     config.autoTabs = false;
10234     Roo.LayoutDialog.superclass.constructor.call(this, el, config);
10235     this.body.setStyle({overflow:"hidden", position:"relative"});
10236     this.layout = new Roo.BorderLayout(this.body.dom, config);
10237     this.layout.monitorWindowResize = false;
10238     this.el.addClass("x-dlg-auto-layout");
10239     // fix case when center region overwrites center function
10240     this.center = Roo.BasicDialog.prototype.center;
10241     this.on("show", this.layout.layout, this.layout, true);
10242     if (config.items) {
10243         var xitems = config.items;
10244         delete config.items;
10245         Roo.each(xitems, this.addxtype, this);
10246     }
10247     
10248     
10249 };
10250 Roo.extend(Roo.LayoutDialog, Roo.BasicDialog, {
10251     
10252     
10253     /**
10254      * @cfg {Roo.LayoutRegion} east  
10255      */
10256     /**
10257      * @cfg {Roo.LayoutRegion} west
10258      */
10259     /**
10260      * @cfg {Roo.LayoutRegion} south
10261      */
10262     /**
10263      * @cfg {Roo.LayoutRegion} north
10264      */
10265     /**
10266      * @cfg {Roo.LayoutRegion} center
10267      */
10268     /**
10269      * @cfg {Roo.Button} buttons[]  Bottom buttons..
10270      */
10271     
10272     
10273     /**
10274      * Ends update of the layout <strike>and resets display to none</strike>. Use standard beginUpdate/endUpdate on the layout.
10275      * @deprecated
10276      */
10277     endUpdate : function(){
10278         this.layout.endUpdate();
10279     },
10280
10281     /**
10282      * Begins an update of the layout <strike>and sets display to block and visibility to hidden</strike>. Use standard beginUpdate/endUpdate on the layout.
10283      *  @deprecated
10284      */
10285     beginUpdate : function(){
10286         this.layout.beginUpdate();
10287     },
10288
10289     /**
10290      * Get the BorderLayout for this dialog
10291      * @return {Roo.BorderLayout}
10292      */
10293     getLayout : function(){
10294         return this.layout;
10295     },
10296
10297     showEl : function(){
10298         Roo.LayoutDialog.superclass.showEl.apply(this, arguments);
10299         if(Roo.isIE7){
10300             this.layout.layout();
10301         }
10302     },
10303
10304     // private
10305     // Use the syncHeightBeforeShow config option to control this automatically
10306     syncBodyHeight : function(){
10307         Roo.LayoutDialog.superclass.syncBodyHeight.call(this);
10308         if(this.layout){this.layout.layout();}
10309     },
10310     
10311       /**
10312      * Add an xtype element (actually adds to the layout.)
10313      * @return {Object} xdata xtype object data.
10314      */
10315     
10316     addxtype : function(c) {
10317         return this.layout.addxtype(c);
10318     }
10319 });/*
10320  * Based on:
10321  * Ext JS Library 1.1.1
10322  * Copyright(c) 2006-2007, Ext JS, LLC.
10323  *
10324  * Originally Released Under LGPL - original licence link has changed is not relivant.
10325  *
10326  * Fork - LGPL
10327  * <script type="text/javascript">
10328  */
10329  
10330 /**
10331  * @class Roo.MessageBox
10332  * @static
10333  * Utility class for generating different styles of message boxes.  The alias Roo.Msg can also be used.
10334  * Example usage:
10335  *<pre><code>
10336 // Basic alert:
10337 Roo.Msg.alert('Status', 'Changes saved successfully.');
10338
10339 // Prompt for user data:
10340 Roo.Msg.prompt('Name', 'Please enter your name:', function(btn, text){
10341     if (btn == 'ok'){
10342         // process text value...
10343     }
10344 });
10345
10346 // Show a dialog using config options:
10347 Roo.Msg.show({
10348    title:'Save Changes?',
10349    msg: 'Your are closing a tab that has unsaved changes. Would you like to save your changes?',
10350    buttons: Roo.Msg.YESNOCANCEL,
10351    fn: processResult,
10352    animEl: 'elId'
10353 });
10354 </code></pre>
10355  * @static
10356  */
10357 Roo.MessageBox = function(){
10358     var dlg, opt, mask, waitTimer;
10359     var bodyEl, msgEl, textboxEl, textareaEl, progressEl, pp;
10360     var buttons, activeTextEl, bwidth;
10361
10362     // private
10363     var handleButton = function(button){
10364         dlg.hide();
10365         Roo.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value], 1);
10366     };
10367
10368     // private
10369     var handleHide = function(){
10370         if(opt && opt.cls){
10371             dlg.el.removeClass(opt.cls);
10372         }
10373         if(waitTimer){
10374             Roo.TaskMgr.stop(waitTimer);
10375             waitTimer = null;
10376         }
10377     };
10378
10379     // private
10380     var updateButtons = function(b){
10381         var width = 0;
10382         if(!b){
10383             buttons["ok"].hide();
10384             buttons["cancel"].hide();
10385             buttons["yes"].hide();
10386             buttons["no"].hide();
10387             dlg.footer.dom.style.display = 'none';
10388             return width;
10389         }
10390         dlg.footer.dom.style.display = '';
10391         for(var k in buttons){
10392             if(typeof buttons[k] != "function"){
10393                 if(b[k]){
10394                     buttons[k].show();
10395                     buttons[k].setText(typeof b[k] == "string" ? b[k] : Roo.MessageBox.buttonText[k]);
10396                     width += buttons[k].el.getWidth()+15;
10397                 }else{
10398                     buttons[k].hide();
10399                 }
10400             }
10401         }
10402         return width;
10403     };
10404
10405     // private
10406     var handleEsc = function(d, k, e){
10407         if(opt && opt.closable !== false){
10408             dlg.hide();
10409         }
10410         if(e){
10411             e.stopEvent();
10412         }
10413     };
10414
10415     return {
10416         /**
10417          * Returns a reference to the underlying {@link Roo.BasicDialog} element
10418          * @return {Roo.BasicDialog} The BasicDialog element
10419          */
10420         getDialog : function(){
10421            if(!dlg){
10422                 dlg = new Roo.BasicDialog("x-msg-box", {
10423                     autoCreate : true,
10424                     shadow: true,
10425                     draggable: true,
10426                     resizable:false,
10427                     constraintoviewport:false,
10428                     fixedcenter:true,
10429                     collapsible : false,
10430                     shim:true,
10431                     modal: true,
10432                     width:400, height:100,
10433                     buttonAlign:"center",
10434                     closeClick : function(){
10435                         if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){
10436                             handleButton("no");
10437                         }else{
10438                             handleButton("cancel");
10439                         }
10440                     }
10441                 });
10442               
10443                 dlg.on("hide", handleHide);
10444                 mask = dlg.mask;
10445                 dlg.addKeyListener(27, handleEsc);
10446                 buttons = {};
10447                 var bt = this.buttonText;
10448                 buttons["ok"] = dlg.addButton(bt["ok"], handleButton.createCallback("ok"));
10449                 buttons["yes"] = dlg.addButton(bt["yes"], handleButton.createCallback("yes"));
10450                 buttons["no"] = dlg.addButton(bt["no"], handleButton.createCallback("no"));
10451                 buttons["cancel"] = dlg.addButton(bt["cancel"], handleButton.createCallback("cancel"));
10452                 bodyEl = dlg.body.createChild({
10453
10454                     html:'<span class="roo-mb-text"></span><br /><input type="text" class="roo-mb-input" /><textarea class="roo-mb-textarea"></textarea><div class="roo-mb-progress-wrap"><div class="roo-mb-progress"><div class="roo-mb-progress-bar">&#160;</div></div></div>'
10455                 });
10456                 msgEl = bodyEl.dom.firstChild;
10457                 textboxEl = Roo.get(bodyEl.dom.childNodes[2]);
10458                 textboxEl.enableDisplayMode();
10459                 textboxEl.addKeyListener([10,13], function(){
10460                     if(dlg.isVisible() && opt && opt.buttons){
10461                         if(opt.buttons.ok){
10462                             handleButton("ok");
10463                         }else if(opt.buttons.yes){
10464                             handleButton("yes");
10465                         }
10466                     }
10467                 });
10468                 textareaEl = Roo.get(bodyEl.dom.childNodes[3]);
10469                 textareaEl.enableDisplayMode();
10470                 progressEl = Roo.get(bodyEl.dom.childNodes[4]);
10471                 progressEl.enableDisplayMode();
10472                 var pf = progressEl.dom.firstChild;
10473                 if (pf) {
10474                     pp = Roo.get(pf.firstChild);
10475                     pp.setHeight(pf.offsetHeight);
10476                 }
10477                 
10478             }
10479             return dlg;
10480         },
10481
10482         /**
10483          * Updates the message box body text
10484          * @param {String} text (optional) Replaces the message box element's innerHTML with the specified string (defaults to
10485          * the XHTML-compliant non-breaking space character '&amp;#160;')
10486          * @return {Roo.MessageBox} This message box
10487          */
10488         updateText : function(text){
10489             if(!dlg.isVisible() && !opt.width){
10490                 dlg.resizeTo(this.maxWidth, 100); // resize first so content is never clipped from previous shows
10491             }
10492             msgEl.innerHTML = text || '&#160;';
10493       
10494             var cw =  Math.max(msgEl.offsetWidth, msgEl.parentNode.scrollWidth);
10495             //Roo.log("guesed size: " + JSON.stringify([cw,msgEl.offsetWidth, msgEl.parentNode.scrollWidth]));
10496             var w = Math.max(
10497                     Math.min(opt.width || cw , this.maxWidth), 
10498                     Math.max(opt.minWidth || this.minWidth, bwidth)
10499             );
10500             if(opt.prompt){
10501                 activeTextEl.setWidth(w);
10502             }
10503             if(dlg.isVisible()){
10504                 dlg.fixedcenter = false;
10505             }
10506             // to big, make it scroll. = But as usual stupid IE does not support
10507             // !important..
10508             
10509             if ( bodyEl.getHeight() > (Roo.lib.Dom.getViewHeight() - 100)) {
10510                 bodyEl.setHeight ( Roo.lib.Dom.getViewHeight() - 100 );
10511                 bodyEl.dom.style.overflowY = 'auto' + ( Roo.isIE ? '' : ' !important');
10512             } else {
10513                 bodyEl.dom.style.height = '';
10514                 bodyEl.dom.style.overflowY = '';
10515             }
10516             if (cw > w) {
10517                 bodyEl.dom.style.get = 'auto' + ( Roo.isIE ? '' : ' !important');
10518             } else {
10519                 bodyEl.dom.style.overflowX = '';
10520             }
10521             
10522             dlg.setContentSize(w, bodyEl.getHeight());
10523             if(dlg.isVisible()){
10524                 dlg.fixedcenter = true;
10525             }
10526             return this;
10527         },
10528
10529         /**
10530          * Updates a progress-style message box's text and progress bar.  Only relevant on message boxes
10531          * initiated via {@link Roo.MessageBox#progress} or by calling {@link Roo.MessageBox#show} with progress: true.
10532          * @param {Number} value Any number between 0 and 1 (e.g., .5)
10533          * @param {String} text (optional) If defined, the message box's body text is replaced with the specified string (defaults to undefined)
10534          * @return {Roo.MessageBox} This message box
10535          */
10536         updateProgress : function(value, text){
10537             if(text){
10538                 this.updateText(text);
10539             }
10540             if (pp) { // weird bug on my firefox - for some reason this is not defined
10541                 pp.setWidth(Math.floor(value*progressEl.dom.firstChild.offsetWidth));
10542             }
10543             return this;
10544         },        
10545
10546         /**
10547          * Returns true if the message box is currently displayed
10548          * @return {Boolean} True if the message box is visible, else false
10549          */
10550         isVisible : function(){
10551             return dlg && dlg.isVisible();  
10552         },
10553
10554         /**
10555          * Hides the message box if it is displayed
10556          */
10557         hide : function(){
10558             if(this.isVisible()){
10559                 dlg.hide();
10560             }  
10561         },
10562
10563         /**
10564          * Displays a new message box, or reinitializes an existing message box, based on the config options
10565          * passed in. All functions (e.g. prompt, alert, etc) on MessageBox call this function internally.
10566          * The following config object properties are supported:
10567          * <pre>
10568 Property    Type             Description
10569 ----------  ---------------  ------------------------------------------------------------------------------------
10570 animEl            String/Element   An id or Element from which the message box should animate as it opens and
10571                                    closes (defaults to undefined)
10572 buttons           Object/Boolean   A button config object (e.g., Roo.MessageBox.OKCANCEL or {ok:'Foo',
10573                                    cancel:'Bar'}), or false to not show any buttons (defaults to false)
10574 closable          Boolean          False to hide the top-right close button (defaults to true).  Note that
10575                                    progress and wait dialogs will ignore this property and always hide the
10576                                    close button as they can only be closed programmatically.
10577 cls               String           A custom CSS class to apply to the message box element
10578 defaultTextHeight Number           The default height in pixels of the message box's multiline textarea if
10579                                    displayed (defaults to 75)
10580 fn                Function         A callback function to execute after closing the dialog.  The arguments to the
10581                                    function will be btn (the name of the button that was clicked, if applicable,
10582                                    e.g. "ok"), and text (the value of the active text field, if applicable).
10583                                    Progress and wait dialogs will ignore this option since they do not respond to
10584                                    user actions and can only be closed programmatically, so any required function
10585                                    should be called by the same code after it closes the dialog.
10586 icon              String           A CSS class that provides a background image to be used as an icon for
10587                                    the dialog (e.g., Roo.MessageBox.WARNING or 'custom-class', defaults to '')
10588 maxWidth          Number           The maximum width in pixels of the message box (defaults to 600)
10589 minWidth          Number           The minimum width in pixels of the message box (defaults to 100)
10590 modal             Boolean          False to allow user interaction with the page while the message box is
10591                                    displayed (defaults to true)
10592 msg               String           A string that will replace the existing message box body text (defaults
10593                                    to the XHTML-compliant non-breaking space character '&#160;')
10594 multiline         Boolean          True to prompt the user to enter multi-line text (defaults to false)
10595 progress          Boolean          True to display a progress bar (defaults to false)
10596 progressText      String           The text to display inside the progress bar if progress = true (defaults to '')
10597 prompt            Boolean          True to prompt the user to enter single-line text (defaults to false)
10598 proxyDrag         Boolean          True to display a lightweight proxy while dragging (defaults to false)
10599 title             String           The title text
10600 value             String           The string value to set into the active textbox element if displayed
10601 wait              Boolean          True to display a progress bar (defaults to false)
10602 width             Number           The width of the dialog in pixels
10603 </pre>
10604          *
10605          * Example usage:
10606          * <pre><code>
10607 Roo.Msg.show({
10608    title: 'Address',
10609    msg: 'Please enter your address:',
10610    width: 300,
10611    buttons: Roo.MessageBox.OKCANCEL,
10612    multiline: true,
10613    fn: saveAddress,
10614    animEl: 'addAddressBtn'
10615 });
10616 </code></pre>
10617          * @param {Object} config Configuration options
10618          * @return {Roo.MessageBox} This message box
10619          */
10620         show : function(options)
10621         {
10622             
10623             // this causes nightmares if you show one dialog after another
10624             // especially on callbacks..
10625              
10626             if(this.isVisible()){
10627                 
10628                 this.hide();
10629                 Roo.log("[Roo.Messagebox] Show called while message displayed:" );
10630                 Roo.log("Old Dialog Message:" +  msgEl.innerHTML );
10631                 Roo.log("New Dialog Message:" +  options.msg )
10632                 //this.alert("ERROR", "Multiple dialogs where displayed at the same time");
10633                 //throw "Roo.MessageBox ERROR : Multiple dialogs where displayed at the same time";
10634                 
10635             }
10636             var d = this.getDialog();
10637             opt = options;
10638             d.setTitle(opt.title || "&#160;");
10639             d.close.setDisplayed(opt.closable !== false);
10640             activeTextEl = textboxEl;
10641             opt.prompt = opt.prompt || (opt.multiline ? true : false);
10642             if(opt.prompt){
10643                 if(opt.multiline){
10644                     textboxEl.hide();
10645                     textareaEl.show();
10646                     textareaEl.setHeight(typeof opt.multiline == "number" ?
10647                         opt.multiline : this.defaultTextHeight);
10648                     activeTextEl = textareaEl;
10649                 }else{
10650                     textboxEl.show();
10651                     textareaEl.hide();
10652                 }
10653             }else{
10654                 textboxEl.hide();
10655                 textareaEl.hide();
10656             }
10657             progressEl.setDisplayed(opt.progress === true);
10658             this.updateProgress(0);
10659             activeTextEl.dom.value = opt.value || "";
10660             if(opt.prompt){
10661                 dlg.setDefaultButton(activeTextEl);
10662             }else{
10663                 var bs = opt.buttons;
10664                 var db = null;
10665                 if(bs && bs.ok){
10666                     db = buttons["ok"];
10667                 }else if(bs && bs.yes){
10668                     db = buttons["yes"];
10669                 }
10670                 dlg.setDefaultButton(db);
10671             }
10672             bwidth = updateButtons(opt.buttons);
10673             this.updateText(opt.msg);
10674             if(opt.cls){
10675                 d.el.addClass(opt.cls);
10676             }
10677             d.proxyDrag = opt.proxyDrag === true;
10678             d.modal = opt.modal !== false;
10679             d.mask = opt.modal !== false ? mask : false;
10680             if(!d.isVisible()){
10681                 // force it to the end of the z-index stack so it gets a cursor in FF
10682                 document.body.appendChild(dlg.el.dom);
10683                 d.animateTarget = null;
10684                 d.show(options.animEl);
10685             }
10686             dlg.toFront();
10687             return this;
10688         },
10689
10690         /**
10691          * Displays a message box with a progress bar.  This message box has no buttons and is not closeable by
10692          * the user.  You are responsible for updating the progress bar as needed via {@link Roo.MessageBox#updateProgress}
10693          * and closing the message box when the process is complete.
10694          * @param {String} title The title bar text
10695          * @param {String} msg The message box body text
10696          * @return {Roo.MessageBox} This message box
10697          */
10698         progress : function(title, msg){
10699             this.show({
10700                 title : title,
10701                 msg : msg,
10702                 buttons: false,
10703                 progress:true,
10704                 closable:false,
10705                 minWidth: this.minProgressWidth,
10706                 modal : true
10707             });
10708             return this;
10709         },
10710
10711         /**
10712          * Displays a standard read-only message box with an OK button (comparable to the basic JavaScript Window.alert).
10713          * If a callback function is passed it will be called after the user clicks the button, and the
10714          * id of the button that was clicked will be passed as the only parameter to the callback
10715          * (could also be the top-right close button).
10716          * @param {String} title The title bar text
10717          * @param {String} msg The message box body text
10718          * @param {Function} fn (optional) The callback function invoked after the message box is closed
10719          * @param {Object} scope (optional) The scope of the callback function
10720          * @return {Roo.MessageBox} This message box
10721          */
10722         alert : function(title, msg, fn, scope){
10723             this.show({
10724                 title : title,
10725                 msg : msg,
10726                 buttons: this.OK,
10727                 fn: fn,
10728                 scope : scope,
10729                 modal : true
10730             });
10731             return this;
10732         },
10733
10734         /**
10735          * Displays a message box with an infinitely auto-updating progress bar.  This can be used to block user
10736          * interaction while waiting for a long-running process to complete that does not have defined intervals.
10737          * You are responsible for closing the message box when the process is complete.
10738          * @param {String} msg The message box body text
10739          * @param {String} title (optional) The title bar text
10740          * @return {Roo.MessageBox} This message box
10741          */
10742         wait : function(msg, title){
10743             this.show({
10744                 title : title,
10745                 msg : msg,
10746                 buttons: false,
10747                 closable:false,
10748                 progress:true,
10749                 modal:true,
10750                 width:300,
10751                 wait:true
10752             });
10753             waitTimer = Roo.TaskMgr.start({
10754                 run: function(i){
10755                     Roo.MessageBox.updateProgress(((((i+20)%20)+1)*5)*.01);
10756                 },
10757                 interval: 1000
10758             });
10759             return this;
10760         },
10761
10762         /**
10763          * Displays a confirmation message box with Yes and No buttons (comparable to JavaScript's Window.confirm).
10764          * If a callback function is passed it will be called after the user clicks either button, and the id of the
10765          * button that was clicked will be passed as the only parameter to the callback (could also be the top-right close button).
10766          * @param {String} title The title bar text
10767          * @param {String} msg The message box body text
10768          * @param {Function} fn (optional) The callback function invoked after the message box is closed
10769          * @param {Object} scope (optional) The scope of the callback function
10770          * @return {Roo.MessageBox} This message box
10771          */
10772         confirm : function(title, msg, fn, scope){
10773             this.show({
10774                 title : title,
10775                 msg : msg,
10776                 buttons: this.YESNO,
10777                 fn: fn,
10778                 scope : scope,
10779                 modal : true
10780             });
10781             return this;
10782         },
10783
10784         /**
10785          * Displays a message box with OK and Cancel buttons prompting the user to enter some text (comparable to
10786          * JavaScript's Window.prompt).  The prompt can be a single-line or multi-line textbox.  If a callback function
10787          * is passed it will be called after the user clicks either button, and the id of the button that was clicked
10788          * (could also be the top-right close button) and the text that was entered will be passed as the two
10789          * parameters to the callback.
10790          * @param {String} title The title bar text
10791          * @param {String} msg The message box body text
10792          * @param {Function} fn (optional) The callback function invoked after the message box is closed
10793          * @param {Object} scope (optional) The scope of the callback function
10794          * @param {Boolean/Number} multiline (optional) True to create a multiline textbox using the defaultTextHeight
10795          * property, or the height in pixels to create the textbox (defaults to false / single-line)
10796          * @return {Roo.MessageBox} This message box
10797          */
10798         prompt : function(title, msg, fn, scope, multiline){
10799             this.show({
10800                 title : title,
10801                 msg : msg,
10802                 buttons: this.OKCANCEL,
10803                 fn: fn,
10804                 minWidth:250,
10805                 scope : scope,
10806                 prompt:true,
10807                 multiline: multiline,
10808                 modal : true
10809             });
10810             return this;
10811         },
10812
10813         /**
10814          * Button config that displays a single OK button
10815          * @type Object
10816          */
10817         OK : {ok:true},
10818         /**
10819          * Button config that displays Yes and No buttons
10820          * @type Object
10821          */
10822         YESNO : {yes:true, no:true},
10823         /**
10824          * Button config that displays OK and Cancel buttons
10825          * @type Object
10826          */
10827         OKCANCEL : {ok:true, cancel:true},
10828         /**
10829          * Button config that displays Yes, No and Cancel buttons
10830          * @type Object
10831          */
10832         YESNOCANCEL : {yes:true, no:true, cancel:true},
10833
10834         /**
10835          * The default height in pixels of the message box's multiline textarea if displayed (defaults to 75)
10836          * @type Number
10837          */
10838         defaultTextHeight : 75,
10839         /**
10840          * The maximum width in pixels of the message box (defaults to 600)
10841          * @type Number
10842          */
10843         maxWidth : 600,
10844         /**
10845          * The minimum width in pixels of the message box (defaults to 100)
10846          * @type Number
10847          */
10848         minWidth : 100,
10849         /**
10850          * The minimum width in pixels of the message box if it is a progress-style dialog.  This is useful
10851          * for setting a different minimum width than text-only dialogs may need (defaults to 250)
10852          * @type Number
10853          */
10854         minProgressWidth : 250,
10855         /**
10856          * An object containing the default button text strings that can be overriden for localized language support.
10857          * Supported properties are: ok, cancel, yes and no.
10858          * Customize the default text like so: Roo.MessageBox.buttonText.yes = "S?";
10859          * @type Object
10860          */
10861         buttonText : {
10862             ok : "OK",
10863             cancel : "Cancel",
10864             yes : "Yes",
10865             no : "No"
10866         }
10867     };
10868 }();
10869
10870 /**
10871  * Shorthand for {@link Roo.MessageBox}
10872  */
10873 Roo.Msg = Roo.MessageBox;/*
10874  * Based on:
10875  * Ext JS Library 1.1.1
10876  * Copyright(c) 2006-2007, Ext JS, LLC.
10877  *
10878  * Originally Released Under LGPL - original licence link has changed is not relivant.
10879  *
10880  * Fork - LGPL
10881  * <script type="text/javascript">
10882  */
10883 /**
10884  * @class Roo.QuickTips
10885  * Provides attractive and customizable tooltips for any element.
10886  * @static
10887  */
10888 Roo.QuickTips = function(){
10889     var el, tipBody, tipBodyText, tipTitle, tm, cfg, close, tagEls = {}, esc, removeCls = null, bdLeft, bdRight;
10890     var ce, bd, xy, dd;
10891     var visible = false, disabled = true, inited = false;
10892     var showProc = 1, hideProc = 1, dismissProc = 1, locks = [];
10893     
10894     var onOver = function(e){
10895         if(disabled){
10896             return;
10897         }
10898         var t = e.getTarget();
10899         if(!t || t.nodeType !== 1 || t == document || t == document.body){
10900             return;
10901         }
10902         if(ce && t == ce.el){
10903             clearTimeout(hideProc);
10904             return;
10905         }
10906         if(t && tagEls[t.id]){
10907             tagEls[t.id].el = t;
10908             showProc = show.defer(tm.showDelay, tm, [tagEls[t.id]]);
10909             return;
10910         }
10911         var ttp, et = Roo.fly(t);
10912         var ns = cfg.namespace;
10913         if(tm.interceptTitles && t.title){
10914             ttp = t.title;
10915             t.qtip = ttp;
10916             t.removeAttribute("title");
10917             e.preventDefault();
10918         }else{
10919             ttp = t.qtip || et.getAttributeNS(ns, cfg.attribute) || et.getAttributeNS(cfg.alt_namespace, cfg.attribute) ;
10920         }
10921         if(ttp){
10922             showProc = show.defer(tm.showDelay, tm, [{
10923                 el: t, 
10924                 text: ttp.replace(/\\n/g,'<br/>'),
10925                 width: et.getAttributeNS(ns, cfg.width),
10926                 autoHide: et.getAttributeNS(ns, cfg.hide) != "user",
10927                 title: et.getAttributeNS(ns, cfg.title),
10928                     cls: et.getAttributeNS(ns, cfg.cls)
10929             }]);
10930         }
10931     };
10932     
10933     var onOut = function(e){
10934         clearTimeout(showProc);
10935         var t = e.getTarget();
10936         if(t && ce && ce.el == t && (tm.autoHide && ce.autoHide !== false)){
10937             hideProc = setTimeout(hide, tm.hideDelay);
10938         }
10939     };
10940     
10941     var onMove = function(e){
10942         if(disabled){
10943             return;
10944         }
10945         xy = e.getXY();
10946         xy[1] += 18;
10947         if(tm.trackMouse && ce){
10948             el.setXY(xy);
10949         }
10950     };
10951     
10952     var onDown = function(e){
10953         clearTimeout(showProc);
10954         clearTimeout(hideProc);
10955         if(!e.within(el)){
10956             if(tm.hideOnClick){
10957                 hide();
10958                 tm.disable();
10959                 tm.enable.defer(100, tm);
10960             }
10961         }
10962     };
10963     
10964     var getPad = function(){
10965         return 2;//bdLeft.getPadding('l')+bdRight.getPadding('r');
10966     };
10967
10968     var show = function(o){
10969         if(disabled){
10970             return;
10971         }
10972         clearTimeout(dismissProc);
10973         ce = o;
10974         if(removeCls){ // in case manually hidden
10975             el.removeClass(removeCls);
10976             removeCls = null;
10977         }
10978         if(ce.cls){
10979             el.addClass(ce.cls);
10980             removeCls = ce.cls;
10981         }
10982         if(ce.title){
10983             tipTitle.update(ce.title);
10984             tipTitle.show();
10985         }else{
10986             tipTitle.update('');
10987             tipTitle.hide();
10988         }
10989         el.dom.style.width  = tm.maxWidth+'px';
10990         //tipBody.dom.style.width = '';
10991         tipBodyText.update(o.text);
10992         var p = getPad(), w = ce.width;
10993         if(!w){
10994             var td = tipBodyText.dom;
10995             var aw = Math.max(td.offsetWidth, td.clientWidth, td.scrollWidth);
10996             if(aw > tm.maxWidth){
10997                 w = tm.maxWidth;
10998             }else if(aw < tm.minWidth){
10999                 w = tm.minWidth;
11000             }else{
11001                 w = aw;
11002             }
11003         }
11004         //tipBody.setWidth(w);
11005         el.setWidth(parseInt(w, 10) + p);
11006         if(ce.autoHide === false){
11007             close.setDisplayed(true);
11008             if(dd){
11009                 dd.unlock();
11010             }
11011         }else{
11012             close.setDisplayed(false);
11013             if(dd){
11014                 dd.lock();
11015             }
11016         }
11017         if(xy){
11018             el.avoidY = xy[1]-18;
11019             el.setXY(xy);
11020         }
11021         if(tm.animate){
11022             el.setOpacity(.1);
11023             el.setStyle("visibility", "visible");
11024             el.fadeIn({callback: afterShow});
11025         }else{
11026             afterShow();
11027         }
11028     };
11029     
11030     var afterShow = function(){
11031         if(ce){
11032             el.show();
11033             esc.enable();
11034             if(tm.autoDismiss && ce.autoHide !== false){
11035                 dismissProc = setTimeout(hide, tm.autoDismissDelay);
11036             }
11037         }
11038     };
11039     
11040     var hide = function(noanim){
11041         clearTimeout(dismissProc);
11042         clearTimeout(hideProc);
11043         ce = null;
11044         if(el.isVisible()){
11045             esc.disable();
11046             if(noanim !== true && tm.animate){
11047                 el.fadeOut({callback: afterHide});
11048             }else{
11049                 afterHide();
11050             } 
11051         }
11052     };
11053     
11054     var afterHide = function(){
11055         el.hide();
11056         if(removeCls){
11057             el.removeClass(removeCls);
11058             removeCls = null;
11059         }
11060     };
11061     
11062     return {
11063         /**
11064         * @cfg {Number} minWidth
11065         * The minimum width of the quick tip (defaults to 40)
11066         */
11067        minWidth : 40,
11068         /**
11069         * @cfg {Number} maxWidth
11070         * The maximum width of the quick tip (defaults to 300)
11071         */
11072        maxWidth : 300,
11073         /**
11074         * @cfg {Boolean} interceptTitles
11075         * True to automatically use the element's DOM title value if available (defaults to false)
11076         */
11077        interceptTitles : false,
11078         /**
11079         * @cfg {Boolean} trackMouse
11080         * True to have the quick tip follow the mouse as it moves over the target element (defaults to false)
11081         */
11082        trackMouse : false,
11083         /**
11084         * @cfg {Boolean} hideOnClick
11085         * True to hide the quick tip if the user clicks anywhere in the document (defaults to true)
11086         */
11087        hideOnClick : true,
11088         /**
11089         * @cfg {Number} showDelay
11090         * Delay in milliseconds before the quick tip displays after the mouse enters the target element (defaults to 500)
11091         */
11092        showDelay : 500,
11093         /**
11094         * @cfg {Number} hideDelay
11095         * Delay in milliseconds before the quick tip hides when autoHide = true (defaults to 200)
11096         */
11097        hideDelay : 200,
11098         /**
11099         * @cfg {Boolean} autoHide
11100         * True to automatically hide the quick tip after the mouse exits the target element (defaults to true).
11101         * Used in conjunction with hideDelay.
11102         */
11103        autoHide : true,
11104         /**
11105         * @cfg {Boolean}
11106         * True to automatically hide the quick tip after a set period of time, regardless of the user's actions
11107         * (defaults to true).  Used in conjunction with autoDismissDelay.
11108         */
11109        autoDismiss : true,
11110         /**
11111         * @cfg {Number}
11112         * Delay in milliseconds before the quick tip hides when autoDismiss = true (defaults to 5000)
11113         */
11114        autoDismissDelay : 5000,
11115        /**
11116         * @cfg {Boolean} animate
11117         * True to turn on fade animation. Defaults to false (ClearType/scrollbar flicker issues in IE7).
11118         */
11119        animate : false,
11120
11121        /**
11122         * @cfg {String} title
11123         * Title text to display (defaults to '').  This can be any valid HTML markup.
11124         */
11125         title: '',
11126        /**
11127         * @cfg {String} text
11128         * Body text to display (defaults to '').  This can be any valid HTML markup.
11129         */
11130         text : '',
11131        /**
11132         * @cfg {String} cls
11133         * A CSS class to apply to the base quick tip element (defaults to '').
11134         */
11135         cls : '',
11136        /**
11137         * @cfg {Number} width
11138         * Width in pixels of the quick tip (defaults to auto).  Width will be ignored if it exceeds the bounds of
11139         * minWidth or maxWidth.
11140         */
11141         width : null,
11142
11143     /**
11144      * Initialize and enable QuickTips for first use.  This should be called once before the first attempt to access
11145      * or display QuickTips in a page.
11146      */
11147        init : function(){
11148           tm = Roo.QuickTips;
11149           cfg = tm.tagConfig;
11150           if(!inited){
11151               if(!Roo.isReady){ // allow calling of init() before onReady
11152                   Roo.onReady(Roo.QuickTips.init, Roo.QuickTips);
11153                   return;
11154               }
11155               el = new Roo.Layer({cls:"x-tip", shadow:"drop", shim: true, constrain:true, shadowOffset:4});
11156               el.fxDefaults = {stopFx: true};
11157               // maximum custom styling
11158               //el.update('<div class="x-tip-top-left"><div class="x-tip-top-right"><div class="x-tip-top"></div></div></div><div class="x-tip-bd-left"><div class="x-tip-bd-right"><div class="x-tip-bd"><div class="x-tip-close"></div><h3></h3><div class="x-tip-bd-inner"></div><div class="x-clear"></div></div></div></div><div class="x-tip-ft-left"><div class="x-tip-ft-right"><div class="x-tip-ft"></div></div></div>');
11159               el.update('<div class="x-tip-bd"><div class="x-tip-close"></div><h3></h3><div class="x-tip-bd-inner"></div><div class="x-clear"></div></div>');              
11160               tipTitle = el.child('h3');
11161               tipTitle.enableDisplayMode("block");
11162               tipBody = el.child('div.x-tip-bd');
11163               tipBodyText = el.child('div.x-tip-bd-inner');
11164               //bdLeft = el.child('div.x-tip-bd-left');
11165               //bdRight = el.child('div.x-tip-bd-right');
11166               close = el.child('div.x-tip-close');
11167               close.enableDisplayMode("block");
11168               close.on("click", hide);
11169               var d = Roo.get(document);
11170               d.on("mousedown", onDown);
11171               d.on("mouseover", onOver);
11172               d.on("mouseout", onOut);
11173               d.on("mousemove", onMove);
11174               esc = d.addKeyListener(27, hide);
11175               esc.disable();
11176               if(Roo.dd.DD){
11177                   dd = el.initDD("default", null, {
11178                       onDrag : function(){
11179                           el.sync();  
11180                       }
11181                   });
11182                   dd.setHandleElId(tipTitle.id);
11183                   dd.lock();
11184               }
11185               inited = true;
11186           }
11187           this.enable(); 
11188        },
11189
11190     /**
11191      * Configures a new quick tip instance and assigns it to a target element.  The following config options
11192      * are supported:
11193      * <pre>
11194 Property    Type                   Description
11195 ----------  ---------------------  ------------------------------------------------------------------------
11196 target      Element/String/Array   An Element, id or array of ids that this quick tip should be tied to
11197      * </ul>
11198      * @param {Object} config The config object
11199      */
11200        register : function(config){
11201            var cs = config instanceof Array ? config : arguments;
11202            for(var i = 0, len = cs.length; i < len; i++) {
11203                var c = cs[i];
11204                var target = c.target;
11205                if(target){
11206                    if(target instanceof Array){
11207                        for(var j = 0, jlen = target.length; j < jlen; j++){
11208                            tagEls[target[j]] = c;
11209                        }
11210                    }else{
11211                        tagEls[typeof target == 'string' ? target : Roo.id(target)] = c;
11212                    }
11213                }
11214            }
11215        },
11216
11217     /**
11218      * Removes this quick tip from its element and destroys it.
11219      * @param {String/HTMLElement/Element} el The element from which the quick tip is to be removed.
11220      */
11221        unregister : function(el){
11222            delete tagEls[Roo.id(el)];
11223        },
11224
11225     /**
11226      * Enable this quick tip.
11227      */
11228        enable : function(){
11229            if(inited && disabled){
11230                locks.pop();
11231                if(locks.length < 1){
11232                    disabled = false;
11233                }
11234            }
11235        },
11236
11237     /**
11238      * Disable this quick tip.
11239      */
11240        disable : function(){
11241           disabled = true;
11242           clearTimeout(showProc);
11243           clearTimeout(hideProc);
11244           clearTimeout(dismissProc);
11245           if(ce){
11246               hide(true);
11247           }
11248           locks.push(1);
11249        },
11250
11251     /**
11252      * Returns true if the quick tip is enabled, else false.
11253      */
11254        isEnabled : function(){
11255             return !disabled;
11256        },
11257
11258         // private
11259        tagConfig : {
11260            namespace : "roo", // was ext?? this may break..
11261            alt_namespace : "ext",
11262            attribute : "qtip",
11263            width : "width",
11264            target : "target",
11265            title : "qtitle",
11266            hide : "hide",
11267            cls : "qclass"
11268        }
11269    };
11270 }();
11271
11272 // backwards compat
11273 Roo.QuickTips.tips = Roo.QuickTips.register;/*
11274  * Based on:
11275  * Ext JS Library 1.1.1
11276  * Copyright(c) 2006-2007, Ext JS, LLC.
11277  *
11278  * Originally Released Under LGPL - original licence link has changed is not relivant.
11279  *
11280  * Fork - LGPL
11281  * <script type="text/javascript">
11282  */
11283  
11284
11285 /**
11286  * @class Roo.tree.TreePanel
11287  * @extends Roo.data.Tree
11288  * @cfg {Roo.tree.TreeNode} root The root node
11289  * @cfg {Boolean} rootVisible false to hide the root node (defaults to true)
11290  * @cfg {Boolean} lines false to disable tree lines (defaults to true)
11291  * @cfg {Boolean} enableDD true to enable drag and drop
11292  * @cfg {Boolean} enableDrag true to enable just drag
11293  * @cfg {Boolean} enableDrop true to enable just drop
11294  * @cfg {Object} dragConfig Custom config to pass to the {@link Roo.tree.TreeDragZone} instance
11295  * @cfg {Object} dropConfig Custom config to pass to the {@link Roo.tree.TreeDropZone} instance
11296  * @cfg {String} ddGroup The DD group this TreePanel belongs to
11297  * @cfg {String} ddAppendOnly True if the tree should only allow append drops (use for trees which are sorted)
11298  * @cfg {Boolean} ddScroll true to enable YUI body scrolling
11299  * @cfg {Boolean} containerScroll true to register this container with ScrollManager
11300  * @cfg {Boolean} hlDrop false to disable node highlight on drop (defaults to the value of Roo.enableFx)
11301  * @cfg {String} hlColor The color of the node highlight (defaults to C3DAF9)
11302  * @cfg {Boolean} animate true to enable animated expand/collapse (defaults to the value of Roo.enableFx)
11303  * @cfg {Boolean} singleExpand true if only 1 node per branch may be expanded
11304  * @cfg {Boolean} selModel A tree selection model to use with this TreePanel (defaults to a {@link Roo.tree.DefaultSelectionModel})
11305  * @cfg {Roo.tree.TreeLoader} loader A TreeLoader for use with this TreePanel
11306  * @cfg {Roo.tree.TreeEditor} editor The TreeEditor to display when clicked.
11307  * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to '/')
11308  * @cfg {Function} renderer DEPRECATED - use TreeLoader:create event / Sets the rendering (formatting) function for the nodes. to return HTML markup for the tree view. The render function is called with  the following parameters:<ul><li>The {Object} The data for the node.</li></ul>
11309  * @cfg {Function} rendererTip DEPRECATED - use TreeLoader:create event / Sets the rendering (formatting) function for the nodes hovertip to return HTML markup for the tree view. The render function is called with  the following parameters:<ul><li>The {Object} The data for the node.</li></ul>
11310  * 
11311  * @constructor
11312  * @param {String/HTMLElement/Element} el The container element
11313  * @param {Object} config
11314  */
11315 Roo.tree.TreePanel = function(el, config){
11316     var root = false;
11317     var loader = false;
11318     if (config.root) {
11319         root = config.root;
11320         delete config.root;
11321     }
11322     if (config.loader) {
11323         loader = config.loader;
11324         delete config.loader;
11325     }
11326     
11327     Roo.apply(this, config);
11328     Roo.tree.TreePanel.superclass.constructor.call(this);
11329     this.el = Roo.get(el);
11330     this.el.addClass('x-tree');
11331     //console.log(root);
11332     if (root) {
11333         this.setRootNode( Roo.factory(root, Roo.tree));
11334     }
11335     if (loader) {
11336         this.loader = Roo.factory(loader, Roo.tree);
11337     }
11338    /**
11339     * Read-only. The id of the container element becomes this TreePanel's id.
11340     */
11341     this.id = this.el.id;
11342     this.addEvents({
11343         /**
11344         * @event beforeload
11345         * Fires before a node is loaded, return false to cancel
11346         * @param {Node} node The node being loaded
11347         */
11348         "beforeload" : true,
11349         /**
11350         * @event load
11351         * Fires when a node is loaded
11352         * @param {Node} node The node that was loaded
11353         */
11354         "load" : true,
11355         /**
11356         * @event textchange
11357         * Fires when the text for a node is changed
11358         * @param {Node} node The node
11359         * @param {String} text The new text
11360         * @param {String} oldText The old text
11361         */
11362         "textchange" : true,
11363         /**
11364         * @event beforeexpand
11365         * Fires before a node is expanded, return false to cancel.
11366         * @param {Node} node The node
11367         * @param {Boolean} deep
11368         * @param {Boolean} anim
11369         */
11370         "beforeexpand" : true,
11371         /**
11372         * @event beforecollapse
11373         * Fires before a node is collapsed, return false to cancel.
11374         * @param {Node} node The node
11375         * @param {Boolean} deep
11376         * @param {Boolean} anim
11377         */
11378         "beforecollapse" : true,
11379         /**
11380         * @event expand
11381         * Fires when a node is expanded
11382         * @param {Node} node The node
11383         */
11384         "expand" : true,
11385         /**
11386         * @event disabledchange
11387         * Fires when the disabled status of a node changes
11388         * @param {Node} node The node
11389         * @param {Boolean} disabled
11390         */
11391         "disabledchange" : true,
11392         /**
11393         * @event collapse
11394         * Fires when a node is collapsed
11395         * @param {Node} node The node
11396         */
11397         "collapse" : true,
11398         /**
11399         * @event beforeclick
11400         * Fires before click processing on a node. Return false to cancel the default action.
11401         * @param {Node} node The node
11402         * @param {Roo.EventObject} e The event object
11403         */
11404         "beforeclick":true,
11405         /**
11406         * @event checkchange
11407         * Fires when a node with a checkbox's checked property changes
11408         * @param {Node} this This node
11409         * @param {Boolean} checked
11410         */
11411         "checkchange":true,
11412         /**
11413         * @event click
11414         * Fires when a node is clicked
11415         * @param {Node} node The node
11416         * @param {Roo.EventObject} e The event object
11417         */
11418         "click":true,
11419         /**
11420         * @event dblclick
11421         * Fires when a node is double clicked
11422         * @param {Node} node The node
11423         * @param {Roo.EventObject} e The event object
11424         */
11425         "dblclick":true,
11426         /**
11427         * @event contextmenu
11428         * Fires when a node is right clicked
11429         * @param {Node} node The node
11430         * @param {Roo.EventObject} e The event object
11431         */
11432         "contextmenu":true,
11433         /**
11434         * @event beforechildrenrendered
11435         * Fires right before the child nodes for a node are rendered
11436         * @param {Node} node The node
11437         */
11438         "beforechildrenrendered":true,
11439         /**
11440         * @event startdrag
11441         * Fires when a node starts being dragged
11442         * @param {Roo.tree.TreePanel} this
11443         * @param {Roo.tree.TreeNode} node
11444         * @param {event} e The raw browser event
11445         */ 
11446        "startdrag" : true,
11447        /**
11448         * @event enddrag
11449         * Fires when a drag operation is complete
11450         * @param {Roo.tree.TreePanel} this
11451         * @param {Roo.tree.TreeNode} node
11452         * @param {event} e The raw browser event
11453         */
11454        "enddrag" : true,
11455        /**
11456         * @event dragdrop
11457         * Fires when a dragged node is dropped on a valid DD target
11458         * @param {Roo.tree.TreePanel} this
11459         * @param {Roo.tree.TreeNode} node
11460         * @param {DD} dd The dd it was dropped on
11461         * @param {event} e The raw browser event
11462         */
11463        "dragdrop" : true,
11464        /**
11465         * @event beforenodedrop
11466         * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent
11467         * passed to handlers has the following properties:<br />
11468         * <ul style="padding:5px;padding-left:16px;">
11469         * <li>tree - The TreePanel</li>
11470         * <li>target - The node being targeted for the drop</li>
11471         * <li>data - The drag data from the drag source</li>
11472         * <li>point - The point of the drop - append, above or below</li>
11473         * <li>source - The drag source</li>
11474         * <li>rawEvent - Raw mouse event</li>
11475         * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)
11476         * to be inserted by setting them on this object.</li>
11477         * <li>cancel - Set this to true to cancel the drop.</li>
11478         * </ul>
11479         * @param {Object} dropEvent
11480         */
11481        "beforenodedrop" : true,
11482        /**
11483         * @event nodedrop
11484         * Fires after a DD object is dropped on a node in this tree. The dropEvent
11485         * passed to handlers has the following properties:<br />
11486         * <ul style="padding:5px;padding-left:16px;">
11487         * <li>tree - The TreePanel</li>
11488         * <li>target - The node being targeted for the drop</li>
11489         * <li>data - The drag data from the drag source</li>
11490         * <li>point - The point of the drop - append, above or below</li>
11491         * <li>source - The drag source</li>
11492         * <li>rawEvent - Raw mouse event</li>
11493         * <li>dropNode - Dropped node(s).</li>
11494         * </ul>
11495         * @param {Object} dropEvent
11496         */
11497        "nodedrop" : true,
11498         /**
11499         * @event nodedragover
11500         * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent
11501         * passed to handlers has the following properties:<br />
11502         * <ul style="padding:5px;padding-left:16px;">
11503         * <li>tree - The TreePanel</li>
11504         * <li>target - The node being targeted for the drop</li>
11505         * <li>data - The drag data from the drag source</li>
11506         * <li>point - The point of the drop - append, above or below</li>
11507         * <li>source - The drag source</li>
11508         * <li>rawEvent - Raw mouse event</li>
11509         * <li>dropNode - Drop node(s) provided by the source.</li>
11510         * <li>cancel - Set this to true to signal drop not allowed.</li>
11511         * </ul>
11512         * @param {Object} dragOverEvent
11513         */
11514        "nodedragover" : true,
11515        /**
11516         * @event appendnode
11517         * Fires when append node to the tree
11518         * @param {Roo.tree.TreePanel} this
11519         * @param {Roo.tree.TreeNode} node
11520         * @param {Number} index The index of the newly appended node
11521         */
11522        "appendnode" : true
11523         
11524     });
11525     if(this.singleExpand){
11526        this.on("beforeexpand", this.restrictExpand, this);
11527     }
11528     if (this.editor) {
11529         this.editor.tree = this;
11530         this.editor = Roo.factory(this.editor, Roo.tree);
11531     }
11532     
11533     if (this.selModel) {
11534         this.selModel = Roo.factory(this.selModel, Roo.tree);
11535     }
11536    
11537 };
11538 Roo.extend(Roo.tree.TreePanel, Roo.data.Tree, {
11539     rootVisible : true,
11540     animate: Roo.enableFx,
11541     lines : true,
11542     enableDD : false,
11543     hlDrop : Roo.enableFx,
11544   
11545     renderer: false,
11546     
11547     rendererTip: false,
11548     // private
11549     restrictExpand : function(node){
11550         var p = node.parentNode;
11551         if(p){
11552             if(p.expandedChild && p.expandedChild.parentNode == p){
11553                 p.expandedChild.collapse();
11554             }
11555             p.expandedChild = node;
11556         }
11557     },
11558
11559     // private override
11560     setRootNode : function(node){
11561         Roo.tree.TreePanel.superclass.setRootNode.call(this, node);
11562         if(!this.rootVisible){
11563             node.ui = new Roo.tree.RootTreeNodeUI(node);
11564         }
11565         return node;
11566     },
11567
11568     /**
11569      * Returns the container element for this TreePanel
11570      */
11571     getEl : function(){
11572         return this.el;
11573     },
11574
11575     /**
11576      * Returns the default TreeLoader for this TreePanel
11577      */
11578     getLoader : function(){
11579         return this.loader;
11580     },
11581
11582     /**
11583      * Expand all nodes
11584      */
11585     expandAll : function(){
11586         this.root.expand(true);
11587     },
11588
11589     /**
11590      * Collapse all nodes
11591      */
11592     collapseAll : function(){
11593         this.root.collapse(true);
11594     },
11595
11596     /**
11597      * Returns the selection model used by this TreePanel
11598      */
11599     getSelectionModel : function(){
11600         if(!this.selModel){
11601             this.selModel = new Roo.tree.DefaultSelectionModel();
11602         }
11603         return this.selModel;
11604     },
11605
11606     /**
11607      * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. "id")
11608      * @param {String} attribute (optional) Defaults to null (return the actual nodes)
11609      * @param {TreeNode} startNode (optional) The node to start from, defaults to the root
11610      * @return {Array}
11611      */
11612     getChecked : function(a, startNode){
11613         startNode = startNode || this.root;
11614         var r = [];
11615         var f = function(){
11616             if(this.attributes.checked){
11617                 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
11618             }
11619         }
11620         startNode.cascade(f);
11621         return r;
11622     },
11623
11624     /**
11625      * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Roo.data.Node#getPath}
11626      * @param {String} path
11627      * @param {String} attr (optional) The attribute used in the path (see {@link Roo.data.Node#getPath} for more info)
11628      * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
11629      * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.
11630      */
11631     expandPath : function(path, attr, callback){
11632         attr = attr || "id";
11633         var keys = path.split(this.pathSeparator);
11634         var curNode = this.root;
11635         if(curNode.attributes[attr] != keys[1]){ // invalid root
11636             if(callback){
11637                 callback(false, null);
11638             }
11639             return;
11640         }
11641         var index = 1;
11642         var f = function(){
11643             if(++index == keys.length){
11644                 if(callback){
11645                     callback(true, curNode);
11646                 }
11647                 return;
11648             }
11649             var c = curNode.findChild(attr, keys[index]);
11650             if(!c){
11651                 if(callback){
11652                     callback(false, curNode);
11653                 }
11654                 return;
11655             }
11656             curNode = c;
11657             c.expand(false, false, f);
11658         };
11659         curNode.expand(false, false, f);
11660     },
11661
11662     /**
11663      * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Roo.data.Node#getPath}
11664      * @param {String} path
11665      * @param {String} attr (optional) The attribute used in the path (see {@link Roo.data.Node#getPath} for more info)
11666      * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with
11667      * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.
11668      */
11669     selectPath : function(path, attr, callback){
11670         attr = attr || "id";
11671         var keys = path.split(this.pathSeparator);
11672         var v = keys.pop();
11673         if(keys.length > 0){
11674             var f = function(success, node){
11675                 if(success && node){
11676                     var n = node.findChild(attr, v);
11677                     if(n){
11678                         n.select();
11679                         if(callback){
11680                             callback(true, n);
11681                         }
11682                     }else if(callback){
11683                         callback(false, n);
11684                     }
11685                 }else{
11686                     if(callback){
11687                         callback(false, n);
11688                     }
11689                 }
11690             };
11691             this.expandPath(keys.join(this.pathSeparator), attr, f);
11692         }else{
11693             this.root.select();
11694             if(callback){
11695                 callback(true, this.root);
11696             }
11697         }
11698     },
11699
11700     getTreeEl : function(){
11701         return this.el;
11702     },
11703
11704     /**
11705      * Trigger rendering of this TreePanel
11706      */
11707     render : function(){
11708         if (this.innerCt) {
11709             return this; // stop it rendering more than once!!
11710         }
11711         
11712         this.innerCt = this.el.createChild({tag:"ul",
11713                cls:"x-tree-root-ct " +
11714                (this.lines ? "x-tree-lines" : "x-tree-no-lines")});
11715
11716         if(this.containerScroll){
11717             Roo.dd.ScrollManager.register(this.el);
11718         }
11719         if((this.enableDD || this.enableDrop) && !this.dropZone){
11720            /**
11721             * The dropZone used by this tree if drop is enabled
11722             * @type Roo.tree.TreeDropZone
11723             */
11724              this.dropZone = new Roo.tree.TreeDropZone(this, this.dropConfig || {
11725                ddGroup: this.ddGroup || "TreeDD", appendOnly: this.ddAppendOnly === true
11726            });
11727         }
11728         if((this.enableDD || this.enableDrag) && !this.dragZone){
11729            /**
11730             * The dragZone used by this tree if drag is enabled
11731             * @type Roo.tree.TreeDragZone
11732             */
11733             this.dragZone = new Roo.tree.TreeDragZone(this, this.dragConfig || {
11734                ddGroup: this.ddGroup || "TreeDD",
11735                scroll: this.ddScroll
11736            });
11737         }
11738         this.getSelectionModel().init(this);
11739         if (!this.root) {
11740             Roo.log("ROOT not set in tree");
11741             return this;
11742         }
11743         this.root.render();
11744         if(!this.rootVisible){
11745             this.root.renderChildren();
11746         }
11747         return this;
11748     }
11749 });/*
11750  * Based on:
11751  * Ext JS Library 1.1.1
11752  * Copyright(c) 2006-2007, Ext JS, LLC.
11753  *
11754  * Originally Released Under LGPL - original licence link has changed is not relivant.
11755  *
11756  * Fork - LGPL
11757  * <script type="text/javascript">
11758  */
11759  
11760
11761 /**
11762  * @class Roo.tree.DefaultSelectionModel
11763  * @extends Roo.util.Observable
11764  * The default single selection for a TreePanel.
11765  * @param {Object} cfg Configuration
11766  */
11767 Roo.tree.DefaultSelectionModel = function(cfg){
11768    this.selNode = null;
11769    
11770    
11771    
11772    this.addEvents({
11773        /**
11774         * @event selectionchange
11775         * Fires when the selected node changes
11776         * @param {DefaultSelectionModel} this
11777         * @param {TreeNode} node the new selection
11778         */
11779        "selectionchange" : true,
11780
11781        /**
11782         * @event beforeselect
11783         * Fires before the selected node changes, return false to cancel the change
11784         * @param {DefaultSelectionModel} this
11785         * @param {TreeNode} node the new selection
11786         * @param {TreeNode} node the old selection
11787         */
11788        "beforeselect" : true
11789    });
11790    
11791     Roo.tree.DefaultSelectionModel.superclass.constructor.call(this,cfg);
11792 };
11793
11794 Roo.extend(Roo.tree.DefaultSelectionModel, Roo.util.Observable, {
11795     init : function(tree){
11796         this.tree = tree;
11797         tree.getTreeEl().on("keydown", this.onKeyDown, this);
11798         tree.on("click", this.onNodeClick, this);
11799     },
11800     
11801     onNodeClick : function(node, e){
11802         if (e.ctrlKey && this.selNode == node)  {
11803             this.unselect(node);
11804             return;
11805         }
11806         this.select(node);
11807     },
11808     
11809     /**
11810      * Select a node.
11811      * @param {TreeNode} node The node to select
11812      * @return {TreeNode} The selected node
11813      */
11814     select : function(node){
11815         var last = this.selNode;
11816         if(last != node && this.fireEvent('beforeselect', this, node, last) !== false){
11817             if(last){
11818                 last.ui.onSelectedChange(false);
11819             }
11820             this.selNode = node;
11821             node.ui.onSelectedChange(true);
11822             this.fireEvent("selectionchange", this, node, last);
11823         }
11824         return node;
11825     },
11826     
11827     /**
11828      * Deselect a node.
11829      * @param {TreeNode} node The node to unselect
11830      */
11831     unselect : function(node){
11832         if(this.selNode == node){
11833             this.clearSelections();
11834         }    
11835     },
11836     
11837     /**
11838      * Clear all selections
11839      */
11840     clearSelections : function(){
11841         var n = this.selNode;
11842         if(n){
11843             n.ui.onSelectedChange(false);
11844             this.selNode = null;
11845             this.fireEvent("selectionchange", this, null);
11846         }
11847         return n;
11848     },
11849     
11850     /**
11851      * Get the selected node
11852      * @return {TreeNode} The selected node
11853      */
11854     getSelectedNode : function(){
11855         return this.selNode;    
11856     },
11857     
11858     /**
11859      * Returns true if the node is selected
11860      * @param {TreeNode} node The node to check
11861      * @return {Boolean}
11862      */
11863     isSelected : function(node){
11864         return this.selNode == node;  
11865     },
11866
11867     /**
11868      * Selects the node above the selected node in the tree, intelligently walking the nodes
11869      * @return TreeNode The new selection
11870      */
11871     selectPrevious : function(){
11872         var s = this.selNode || this.lastSelNode;
11873         if(!s){
11874             return null;
11875         }
11876         var ps = s.previousSibling;
11877         if(ps){
11878             if(!ps.isExpanded() || ps.childNodes.length < 1){
11879                 return this.select(ps);
11880             } else{
11881                 var lc = ps.lastChild;
11882                 while(lc && lc.isExpanded() && lc.childNodes.length > 0){
11883                     lc = lc.lastChild;
11884                 }
11885                 return this.select(lc);
11886             }
11887         } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
11888             return this.select(s.parentNode);
11889         }
11890         return null;
11891     },
11892
11893     /**
11894      * Selects the node above the selected node in the tree, intelligently walking the nodes
11895      * @return TreeNode The new selection
11896      */
11897     selectNext : function(){
11898         var s = this.selNode || this.lastSelNode;
11899         if(!s){
11900             return null;
11901         }
11902         if(s.firstChild && s.isExpanded()){
11903              return this.select(s.firstChild);
11904          }else if(s.nextSibling){
11905              return this.select(s.nextSibling);
11906          }else if(s.parentNode){
11907             var newS = null;
11908             s.parentNode.bubble(function(){
11909                 if(this.nextSibling){
11910                     newS = this.getOwnerTree().selModel.select(this.nextSibling);
11911                     return false;
11912                 }
11913             });
11914             return newS;
11915          }
11916         return null;
11917     },
11918
11919     onKeyDown : function(e){
11920         var s = this.selNode || this.lastSelNode;
11921         // undesirable, but required
11922         var sm = this;
11923         if(!s){
11924             return;
11925         }
11926         var k = e.getKey();
11927         switch(k){
11928              case e.DOWN:
11929                  e.stopEvent();
11930                  this.selectNext();
11931              break;
11932              case e.UP:
11933                  e.stopEvent();
11934                  this.selectPrevious();
11935              break;
11936              case e.RIGHT:
11937                  e.preventDefault();
11938                  if(s.hasChildNodes()){
11939                      if(!s.isExpanded()){
11940                          s.expand();
11941                      }else if(s.firstChild){
11942                          this.select(s.firstChild, e);
11943                      }
11944                  }
11945              break;
11946              case e.LEFT:
11947                  e.preventDefault();
11948                  if(s.hasChildNodes() && s.isExpanded()){
11949                      s.collapse();
11950                  }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
11951                      this.select(s.parentNode, e);
11952                  }
11953              break;
11954         };
11955     }
11956 });
11957
11958 /**
11959  * @class Roo.tree.MultiSelectionModel
11960  * @extends Roo.util.Observable
11961  * Multi selection for a TreePanel.
11962  * @param {Object} cfg Configuration
11963  */
11964 Roo.tree.MultiSelectionModel = function(){
11965    this.selNodes = [];
11966    this.selMap = {};
11967    this.addEvents({
11968        /**
11969         * @event selectionchange
11970         * Fires when the selected nodes change
11971         * @param {MultiSelectionModel} this
11972         * @param {Array} nodes Array of the selected nodes
11973         */
11974        "selectionchange" : true
11975    });
11976    Roo.tree.MultiSelectionModel.superclass.constructor.call(this,cfg);
11977    
11978 };
11979
11980 Roo.extend(Roo.tree.MultiSelectionModel, Roo.util.Observable, {
11981     init : function(tree){
11982         this.tree = tree;
11983         tree.getTreeEl().on("keydown", this.onKeyDown, this);
11984         tree.on("click", this.onNodeClick, this);
11985     },
11986     
11987     onNodeClick : function(node, e){
11988         this.select(node, e, e.ctrlKey);
11989     },
11990     
11991     /**
11992      * Select a node.
11993      * @param {TreeNode} node The node to select
11994      * @param {EventObject} e (optional) An event associated with the selection
11995      * @param {Boolean} keepExisting True to retain existing selections
11996      * @return {TreeNode} The selected node
11997      */
11998     select : function(node, e, keepExisting){
11999         if(keepExisting !== true){
12000             this.clearSelections(true);
12001         }
12002         if(this.isSelected(node)){
12003             this.lastSelNode = node;
12004             return node;
12005         }
12006         this.selNodes.push(node);
12007         this.selMap[node.id] = node;
12008         this.lastSelNode = node;
12009         node.ui.onSelectedChange(true);
12010         this.fireEvent("selectionchange", this, this.selNodes);
12011         return node;
12012     },
12013     
12014     /**
12015      * Deselect a node.
12016      * @param {TreeNode} node The node to unselect
12017      */
12018     unselect : function(node){
12019         if(this.selMap[node.id]){
12020             node.ui.onSelectedChange(false);
12021             var sn = this.selNodes;
12022             var index = -1;
12023             if(sn.indexOf){
12024                 index = sn.indexOf(node);
12025             }else{
12026                 for(var i = 0, len = sn.length; i < len; i++){
12027                     if(sn[i] == node){
12028                         index = i;
12029                         break;
12030                     }
12031                 }
12032             }
12033             if(index != -1){
12034                 this.selNodes.splice(index, 1);
12035             }
12036             delete this.selMap[node.id];
12037             this.fireEvent("selectionchange", this, this.selNodes);
12038         }
12039     },
12040     
12041     /**
12042      * Clear all selections
12043      */
12044     clearSelections : function(suppressEvent){
12045         var sn = this.selNodes;
12046         if(sn.length > 0){
12047             for(var i = 0, len = sn.length; i < len; i++){
12048                 sn[i].ui.onSelectedChange(false);
12049             }
12050             this.selNodes = [];
12051             this.selMap = {};
12052             if(suppressEvent !== true){
12053                 this.fireEvent("selectionchange", this, this.selNodes);
12054             }
12055         }
12056     },
12057     
12058     /**
12059      * Returns true if the node is selected
12060      * @param {TreeNode} node The node to check
12061      * @return {Boolean}
12062      */
12063     isSelected : function(node){
12064         return this.selMap[node.id] ? true : false;  
12065     },
12066     
12067     /**
12068      * Returns an array of the selected nodes
12069      * @return {Array}
12070      */
12071     getSelectedNodes : function(){
12072         return this.selNodes;    
12073     },
12074
12075     onKeyDown : Roo.tree.DefaultSelectionModel.prototype.onKeyDown,
12076
12077     selectNext : Roo.tree.DefaultSelectionModel.prototype.selectNext,
12078
12079     selectPrevious : Roo.tree.DefaultSelectionModel.prototype.selectPrevious
12080 });/*
12081  * Based on:
12082  * Ext JS Library 1.1.1
12083  * Copyright(c) 2006-2007, Ext JS, LLC.
12084  *
12085  * Originally Released Under LGPL - original licence link has changed is not relivant.
12086  *
12087  * Fork - LGPL
12088  * <script type="text/javascript">
12089  */
12090  
12091 /**
12092  * @class Roo.tree.TreeNode
12093  * @extends Roo.data.Node
12094  * @cfg {String} text The text for this node
12095  * @cfg {Boolean} expanded true to start the node expanded
12096  * @cfg {Boolean} allowDrag false to make this node undraggable if DD is on (defaults to true)
12097  * @cfg {Boolean} allowDrop false if this node cannot be drop on
12098  * @cfg {Boolean} disabled true to start the node disabled
12099  * @cfg {String} icon The path to an icon for the node. The preferred way to do this
12100  *    is to use the cls or iconCls attributes and add the icon via a CSS background image.
12101  * @cfg {String} cls A css class to be added to the node
12102  * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
12103  * @cfg {String} href URL of the link used for the node (defaults to #)
12104  * @cfg {String} hrefTarget target frame for the link
12105  * @cfg {String} qtip An Ext QuickTip for the node
12106  * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
12107  * @cfg {Boolean} singleClickExpand True for single click expand on this node
12108  * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Roo.tree.TreeNodeUI)
12109  * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
12110  * (defaults to undefined with no checkbox rendered)
12111  * @constructor
12112  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
12113  */
12114 Roo.tree.TreeNode = function(attributes){
12115     attributes = attributes || {};
12116     if(typeof attributes == "string"){
12117         attributes = {text: attributes};
12118     }
12119     this.childrenRendered = false;
12120     this.rendered = false;
12121     Roo.tree.TreeNode.superclass.constructor.call(this, attributes);
12122     this.expanded = attributes.expanded === true;
12123     this.isTarget = attributes.isTarget !== false;
12124     this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
12125     this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
12126
12127     /**
12128      * Read-only. The text for this node. To change it use setText().
12129      * @type String
12130      */
12131     this.text = attributes.text;
12132     /**
12133      * True if this node is disabled.
12134      * @type Boolean
12135      */
12136     this.disabled = attributes.disabled === true;
12137
12138     this.addEvents({
12139         /**
12140         * @event textchange
12141         * Fires when the text for this node is changed
12142         * @param {Node} this This node
12143         * @param {String} text The new text
12144         * @param {String} oldText The old text
12145         */
12146         "textchange" : true,
12147         /**
12148         * @event beforeexpand
12149         * Fires before this node is expanded, return false to cancel.
12150         * @param {Node} this This node
12151         * @param {Boolean} deep
12152         * @param {Boolean} anim
12153         */
12154         "beforeexpand" : true,
12155         /**
12156         * @event beforecollapse
12157         * Fires before this node is collapsed, return false to cancel.
12158         * @param {Node} this This node
12159         * @param {Boolean} deep
12160         * @param {Boolean} anim
12161         */
12162         "beforecollapse" : true,
12163         /**
12164         * @event expand
12165         * Fires when this node is expanded
12166         * @param {Node} this This node
12167         */
12168         "expand" : true,
12169         /**
12170         * @event disabledchange
12171         * Fires when the disabled status of this node changes
12172         * @param {Node} this This node
12173         * @param {Boolean} disabled
12174         */
12175         "disabledchange" : true,
12176         /**
12177         * @event collapse
12178         * Fires when this node is collapsed
12179         * @param {Node} this This node
12180         */
12181         "collapse" : true,
12182         /**
12183         * @event beforeclick
12184         * Fires before click processing. Return false to cancel the default action.
12185         * @param {Node} this This node
12186         * @param {Roo.EventObject} e The event object
12187         */
12188         "beforeclick":true,
12189         /**
12190         * @event checkchange
12191         * Fires when a node with a checkbox's checked property changes
12192         * @param {Node} this This node
12193         * @param {Boolean} checked
12194         */
12195         "checkchange":true,
12196         /**
12197         * @event click
12198         * Fires when this node is clicked
12199         * @param {Node} this This node
12200         * @param {Roo.EventObject} e The event object
12201         */
12202         "click":true,
12203         /**
12204         * @event dblclick
12205         * Fires when this node is double clicked
12206         * @param {Node} this This node
12207         * @param {Roo.EventObject} e The event object
12208         */
12209         "dblclick":true,
12210         /**
12211         * @event contextmenu
12212         * Fires when this node is right clicked
12213         * @param {Node} this This node
12214         * @param {Roo.EventObject} e The event object
12215         */
12216         "contextmenu":true,
12217         /**
12218         * @event beforechildrenrendered
12219         * Fires right before the child nodes for this node are rendered
12220         * @param {Node} this This node
12221         */
12222         "beforechildrenrendered":true
12223     });
12224
12225     var uiClass = this.attributes.uiProvider || Roo.tree.TreeNodeUI;
12226
12227     /**
12228      * Read-only. The UI for this node
12229      * @type TreeNodeUI
12230      */
12231     this.ui = new uiClass(this);
12232     
12233     // finally support items[]
12234     if (typeof(this.attributes.items) == 'undefined' || !this.attributes.items) {
12235         return;
12236     }
12237     
12238     
12239     Roo.each(this.attributes.items, function(c) {
12240         this.appendChild(Roo.factory(c,Roo.Tree));
12241     }, this);
12242     delete this.attributes.items;
12243     
12244     
12245     
12246 };
12247 Roo.extend(Roo.tree.TreeNode, Roo.data.Node, {
12248     preventHScroll: true,
12249     /**
12250      * Returns true if this node is expanded
12251      * @return {Boolean}
12252      */
12253     isExpanded : function(){
12254         return this.expanded;
12255     },
12256
12257     /**
12258      * Returns the UI object for this node
12259      * @return {TreeNodeUI}
12260      */
12261     getUI : function(){
12262         return this.ui;
12263     },
12264
12265     // private override
12266     setFirstChild : function(node){
12267         var of = this.firstChild;
12268         Roo.tree.TreeNode.superclass.setFirstChild.call(this, node);
12269         if(this.childrenRendered && of && node != of){
12270             of.renderIndent(true, true);
12271         }
12272         if(this.rendered){
12273             this.renderIndent(true, true);
12274         }
12275     },
12276
12277     // private override
12278     setLastChild : function(node){
12279         var ol = this.lastChild;
12280         Roo.tree.TreeNode.superclass.setLastChild.call(this, node);
12281         if(this.childrenRendered && ol && node != ol){
12282             ol.renderIndent(true, true);
12283         }
12284         if(this.rendered){
12285             this.renderIndent(true, true);
12286         }
12287     },
12288
12289     // these methods are overridden to provide lazy rendering support
12290     // private override
12291     appendChild : function()
12292     {
12293         var node = Roo.tree.TreeNode.superclass.appendChild.apply(this, arguments);
12294         if(node && this.childrenRendered){
12295             node.render();
12296         }
12297         this.ui.updateExpandIcon();
12298         return node;
12299     },
12300
12301     // private override
12302     removeChild : function(node){
12303         this.ownerTree.getSelectionModel().unselect(node);
12304         Roo.tree.TreeNode.superclass.removeChild.apply(this, arguments);
12305         // if it's been rendered remove dom node
12306         if(this.childrenRendered){
12307             node.ui.remove();
12308         }
12309         if(this.childNodes.length < 1){
12310             this.collapse(false, false);
12311         }else{
12312             this.ui.updateExpandIcon();
12313         }
12314         if(!this.firstChild) {
12315             this.childrenRendered = false;
12316         }
12317         return node;
12318     },
12319
12320     // private override
12321     insertBefore : function(node, refNode){
12322         var newNode = Roo.tree.TreeNode.superclass.insertBefore.apply(this, arguments);
12323         if(newNode && refNode && this.childrenRendered){
12324             node.render();
12325         }
12326         this.ui.updateExpandIcon();
12327         return newNode;
12328     },
12329
12330     /**
12331      * Sets the text for this node
12332      * @param {String} text
12333      */
12334     setText : function(text){
12335         var oldText = this.text;
12336         this.text = text;
12337         this.attributes.text = text;
12338         if(this.rendered){ // event without subscribing
12339             this.ui.onTextChange(this, text, oldText);
12340         }
12341         this.fireEvent("textchange", this, text, oldText);
12342     },
12343
12344     /**
12345      * Triggers selection of this node
12346      */
12347     select : function(){
12348         this.getOwnerTree().getSelectionModel().select(this);
12349     },
12350
12351     /**
12352      * Triggers deselection of this node
12353      */
12354     unselect : function(){
12355         this.getOwnerTree().getSelectionModel().unselect(this);
12356     },
12357
12358     /**
12359      * Returns true if this node is selected
12360      * @return {Boolean}
12361      */
12362     isSelected : function(){
12363         return this.getOwnerTree().getSelectionModel().isSelected(this);
12364     },
12365
12366     /**
12367      * Expand this node.
12368      * @param {Boolean} deep (optional) True to expand all children as well
12369      * @param {Boolean} anim (optional) false to cancel the default animation
12370      * @param {Function} callback (optional) A callback to be called when
12371      * expanding this node completes (does not wait for deep expand to complete).
12372      * Called with 1 parameter, this node.
12373      */
12374     expand : function(deep, anim, callback){
12375         if(!this.expanded){
12376             if(this.fireEvent("beforeexpand", this, deep, anim) === false){
12377                 return;
12378             }
12379             if(!this.childrenRendered){
12380                 this.renderChildren();
12381             }
12382             this.expanded = true;
12383             
12384             if(!this.isHiddenRoot() && (this.getOwnerTree() && this.getOwnerTree().animate && anim !== false) || anim){
12385                 this.ui.animExpand(function(){
12386                     this.fireEvent("expand", this);
12387                     if(typeof callback == "function"){
12388                         callback(this);
12389                     }
12390                     if(deep === true){
12391                         this.expandChildNodes(true);
12392                     }
12393                 }.createDelegate(this));
12394                 return;
12395             }else{
12396                 this.ui.expand();
12397                 this.fireEvent("expand", this);
12398                 if(typeof callback == "function"){
12399                     callback(this);
12400                 }
12401             }
12402         }else{
12403            if(typeof callback == "function"){
12404                callback(this);
12405            }
12406         }
12407         if(deep === true){
12408             this.expandChildNodes(true);
12409         }
12410     },
12411
12412     isHiddenRoot : function(){
12413         return this.isRoot && !this.getOwnerTree().rootVisible;
12414     },
12415
12416     /**
12417      * Collapse this node.
12418      * @param {Boolean} deep (optional) True to collapse all children as well
12419      * @param {Boolean} anim (optional) false to cancel the default animation
12420      */
12421     collapse : function(deep, anim){
12422         if(this.expanded && !this.isHiddenRoot()){
12423             if(this.fireEvent("beforecollapse", this, deep, anim) === false){
12424                 return;
12425             }
12426             this.expanded = false;
12427             if((this.getOwnerTree().animate && anim !== false) || anim){
12428                 this.ui.animCollapse(function(){
12429                     this.fireEvent("collapse", this);
12430                     if(deep === true){
12431                         this.collapseChildNodes(true);
12432                     }
12433                 }.createDelegate(this));
12434                 return;
12435             }else{
12436                 this.ui.collapse();
12437                 this.fireEvent("collapse", this);
12438             }
12439         }
12440         if(deep === true){
12441             var cs = this.childNodes;
12442             for(var i = 0, len = cs.length; i < len; i++) {
12443                 cs[i].collapse(true, false);
12444             }
12445         }
12446     },
12447
12448     // private
12449     delayedExpand : function(delay){
12450         if(!this.expandProcId){
12451             this.expandProcId = this.expand.defer(delay, this);
12452         }
12453     },
12454
12455     // private
12456     cancelExpand : function(){
12457         if(this.expandProcId){
12458             clearTimeout(this.expandProcId);
12459         }
12460         this.expandProcId = false;
12461     },
12462
12463     /**
12464      * Toggles expanded/collapsed state of the node
12465      */
12466     toggle : function(){
12467         if(this.expanded){
12468             this.collapse();
12469         }else{
12470             this.expand();
12471         }
12472     },
12473
12474     /**
12475      * Ensures all parent nodes are expanded
12476      */
12477     ensureVisible : function(callback){
12478         var tree = this.getOwnerTree();
12479         tree.expandPath(this.parentNode.getPath(), false, function(){
12480             tree.getTreeEl().scrollChildIntoView(this.ui.anchor);
12481             Roo.callback(callback);
12482         }.createDelegate(this));
12483     },
12484
12485     /**
12486      * Expand all child nodes
12487      * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
12488      */
12489     expandChildNodes : function(deep){
12490         var cs = this.childNodes;
12491         for(var i = 0, len = cs.length; i < len; i++) {
12492                 cs[i].expand(deep);
12493         }
12494     },
12495
12496     /**
12497      * Collapse all child nodes
12498      * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
12499      */
12500     collapseChildNodes : function(deep){
12501         var cs = this.childNodes;
12502         for(var i = 0, len = cs.length; i < len; i++) {
12503                 cs[i].collapse(deep);
12504         }
12505     },
12506
12507     /**
12508      * Disables this node
12509      */
12510     disable : function(){
12511         this.disabled = true;
12512         this.unselect();
12513         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
12514             this.ui.onDisableChange(this, true);
12515         }
12516         this.fireEvent("disabledchange", this, true);
12517     },
12518
12519     /**
12520      * Enables this node
12521      */
12522     enable : function(){
12523         this.disabled = false;
12524         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
12525             this.ui.onDisableChange(this, false);
12526         }
12527         this.fireEvent("disabledchange", this, false);
12528     },
12529
12530     // private
12531     renderChildren : function(suppressEvent){
12532         if(suppressEvent !== false){
12533             this.fireEvent("beforechildrenrendered", this);
12534         }
12535         var cs = this.childNodes;
12536         for(var i = 0, len = cs.length; i < len; i++){
12537             cs[i].render(true);
12538         }
12539         this.childrenRendered = true;
12540     },
12541
12542     // private
12543     sort : function(fn, scope){
12544         Roo.tree.TreeNode.superclass.sort.apply(this, arguments);
12545         if(this.childrenRendered){
12546             var cs = this.childNodes;
12547             for(var i = 0, len = cs.length; i < len; i++){
12548                 cs[i].render(true);
12549             }
12550         }
12551     },
12552
12553     // private
12554     render : function(bulkRender){
12555         this.ui.render(bulkRender);
12556         if(!this.rendered){
12557             this.rendered = true;
12558             if(this.expanded){
12559                 this.expanded = false;
12560                 this.expand(false, false);
12561             }
12562         }
12563     },
12564
12565     // private
12566     renderIndent : function(deep, refresh){
12567         if(refresh){
12568             this.ui.childIndent = null;
12569         }
12570         this.ui.renderIndent();
12571         if(deep === true && this.childrenRendered){
12572             var cs = this.childNodes;
12573             for(var i = 0, len = cs.length; i < len; i++){
12574                 cs[i].renderIndent(true, refresh);
12575             }
12576         }
12577     }
12578 });/*
12579  * Based on:
12580  * Ext JS Library 1.1.1
12581  * Copyright(c) 2006-2007, Ext JS, LLC.
12582  *
12583  * Originally Released Under LGPL - original licence link has changed is not relivant.
12584  *
12585  * Fork - LGPL
12586  * <script type="text/javascript">
12587  */
12588  
12589 /**
12590  * @class Roo.tree.AsyncTreeNode
12591  * @extends Roo.tree.TreeNode
12592  * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
12593  * @constructor
12594  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node 
12595  */
12596  Roo.tree.AsyncTreeNode = function(config){
12597     this.loaded = false;
12598     this.loading = false;
12599     Roo.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
12600     /**
12601     * @event beforeload
12602     * Fires before this node is loaded, return false to cancel
12603     * @param {Node} this This node
12604     */
12605     this.addEvents({'beforeload':true, 'load': true});
12606     /**
12607     * @event load
12608     * Fires when this node is loaded
12609     * @param {Node} this This node
12610     */
12611     /**
12612      * The loader used by this node (defaults to using the tree's defined loader)
12613      * @type TreeLoader
12614      * @property loader
12615      */
12616 };
12617 Roo.extend(Roo.tree.AsyncTreeNode, Roo.tree.TreeNode, {
12618     expand : function(deep, anim, callback){
12619         if(this.loading){ // if an async load is already running, waiting til it's done
12620             var timer;
12621             var f = function(){
12622                 if(!this.loading){ // done loading
12623                     clearInterval(timer);
12624                     this.expand(deep, anim, callback);
12625                 }
12626             }.createDelegate(this);
12627             timer = setInterval(f, 200);
12628             return;
12629         }
12630         if(!this.loaded){
12631             if(this.fireEvent("beforeload", this) === false){
12632                 return;
12633             }
12634             this.loading = true;
12635             this.ui.beforeLoad(this);
12636             var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
12637             if(loader){
12638                 loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback]));
12639                 return;
12640             }
12641         }
12642         Roo.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback);
12643     },
12644     
12645     /**
12646      * Returns true if this node is currently loading
12647      * @return {Boolean}
12648      */
12649     isLoading : function(){
12650         return this.loading;  
12651     },
12652     
12653     loadComplete : function(deep, anim, callback){
12654         this.loading = false;
12655         this.loaded = true;
12656         this.ui.afterLoad(this);
12657         this.fireEvent("load", this);
12658         this.expand(deep, anim, callback);
12659     },
12660     
12661     /**
12662      * Returns true if this node has been loaded
12663      * @return {Boolean}
12664      */
12665     isLoaded : function(){
12666         return this.loaded;
12667     },
12668     
12669     hasChildNodes : function(){
12670         if(!this.isLeaf() && !this.loaded){
12671             return true;
12672         }else{
12673             return Roo.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
12674         }
12675     },
12676
12677     /**
12678      * Trigger a reload for this node
12679      * @param {Function} callback
12680      */
12681     reload : function(callback){
12682         this.collapse(false, false);
12683         while(this.firstChild){
12684             this.removeChild(this.firstChild);
12685         }
12686         this.childrenRendered = false;
12687         this.loaded = false;
12688         if(this.isHiddenRoot()){
12689             this.expanded = false;
12690         }
12691         this.expand(false, false, callback);
12692     }
12693 });/*
12694  * Based on:
12695  * Ext JS Library 1.1.1
12696  * Copyright(c) 2006-2007, Ext JS, LLC.
12697  *
12698  * Originally Released Under LGPL - original licence link has changed is not relivant.
12699  *
12700  * Fork - LGPL
12701  * <script type="text/javascript">
12702  */
12703  
12704 /**
12705  * @class Roo.tree.TreeNodeUI
12706  * @constructor
12707  * @param {Object} node The node to render
12708  * The TreeNode UI implementation is separate from the
12709  * tree implementation. Unless you are customizing the tree UI,
12710  * you should never have to use this directly.
12711  */
12712 Roo.tree.TreeNodeUI = function(node){
12713     this.node = node;
12714     this.rendered = false;
12715     this.animating = false;
12716     this.emptyIcon = Roo.BLANK_IMAGE_URL;
12717 };
12718
12719 Roo.tree.TreeNodeUI.prototype = {
12720     removeChild : function(node){
12721         if(this.rendered){
12722             this.ctNode.removeChild(node.ui.getEl());
12723         }
12724     },
12725
12726     beforeLoad : function(){
12727          this.addClass("x-tree-node-loading");
12728     },
12729
12730     afterLoad : function(){
12731          this.removeClass("x-tree-node-loading");
12732     },
12733
12734     onTextChange : function(node, text, oldText){
12735         if(this.rendered){
12736             this.textNode.innerHTML = text;
12737         }
12738     },
12739
12740     onDisableChange : function(node, state){
12741         this.disabled = state;
12742         if(state){
12743             this.addClass("x-tree-node-disabled");
12744         }else{
12745             this.removeClass("x-tree-node-disabled");
12746         }
12747     },
12748
12749     onSelectedChange : function(state){
12750         if(state){
12751             this.focus();
12752             this.addClass("x-tree-selected");
12753         }else{
12754             //this.blur();
12755             this.removeClass("x-tree-selected");
12756         }
12757     },
12758
12759     onMove : function(tree, node, oldParent, newParent, index, refNode){
12760         this.childIndent = null;
12761         if(this.rendered){
12762             var targetNode = newParent.ui.getContainer();
12763             if(!targetNode){//target not rendered
12764                 this.holder = document.createElement("div");
12765                 this.holder.appendChild(this.wrap);
12766                 return;
12767             }
12768             var insertBefore = refNode ? refNode.ui.getEl() : null;
12769             if(insertBefore){
12770                 targetNode.insertBefore(this.wrap, insertBefore);
12771             }else{
12772                 targetNode.appendChild(this.wrap);
12773             }
12774             this.node.renderIndent(true);
12775         }
12776     },
12777
12778     addClass : function(cls){
12779         if(this.elNode){
12780             Roo.fly(this.elNode).addClass(cls);
12781         }
12782     },
12783
12784     removeClass : function(cls){
12785         if(this.elNode){
12786             Roo.fly(this.elNode).removeClass(cls);
12787         }
12788     },
12789
12790     remove : function(){
12791         if(this.rendered){
12792             this.holder = document.createElement("div");
12793             this.holder.appendChild(this.wrap);
12794         }
12795     },
12796
12797     fireEvent : function(){
12798         return this.node.fireEvent.apply(this.node, arguments);
12799     },
12800
12801     initEvents : function(){
12802         this.node.on("move", this.onMove, this);
12803         var E = Roo.EventManager;
12804         var a = this.anchor;
12805
12806         var el = Roo.fly(a, '_treeui');
12807
12808         if(Roo.isOpera){ // opera render bug ignores the CSS
12809             el.setStyle("text-decoration", "none");
12810         }
12811
12812         el.on("click", this.onClick, this);
12813         el.on("dblclick", this.onDblClick, this);
12814
12815         if(this.checkbox){
12816             Roo.EventManager.on(this.checkbox,
12817                     Roo.isIE ? 'click' : 'change', this.onCheckChange, this);
12818         }
12819
12820         el.on("contextmenu", this.onContextMenu, this);
12821
12822         var icon = Roo.fly(this.iconNode);
12823         icon.on("click", this.onClick, this);
12824         icon.on("dblclick", this.onDblClick, this);
12825         icon.on("contextmenu", this.onContextMenu, this);
12826         E.on(this.ecNode, "click", this.ecClick, this, true);
12827
12828         if(this.node.disabled){
12829             this.addClass("x-tree-node-disabled");
12830         }
12831         if(this.node.hidden){
12832             this.addClass("x-tree-node-disabled");
12833         }
12834         var ot = this.node.getOwnerTree();
12835         var dd = ot ? (ot.enableDD || ot.enableDrag || ot.enableDrop) : false;
12836         if(dd && (!this.node.isRoot || ot.rootVisible)){
12837             Roo.dd.Registry.register(this.elNode, {
12838                 node: this.node,
12839                 handles: this.getDDHandles(),
12840                 isHandle: false
12841             });
12842         }
12843     },
12844
12845     getDDHandles : function(){
12846         return [this.iconNode, this.textNode];
12847     },
12848
12849     hide : function(){
12850         if(this.rendered){
12851             this.wrap.style.display = "none";
12852         }
12853     },
12854
12855     show : function(){
12856         if(this.rendered){
12857             this.wrap.style.display = "";
12858         }
12859     },
12860
12861     onContextMenu : function(e){
12862         if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
12863             e.preventDefault();
12864             this.focus();
12865             this.fireEvent("contextmenu", this.node, e);
12866         }
12867     },
12868
12869     onClick : function(e){
12870         if(this.dropping){
12871             e.stopEvent();
12872             return;
12873         }
12874         if(this.fireEvent("beforeclick", this.node, e) !== false){
12875             if(!this.disabled && this.node.attributes.href){
12876                 this.fireEvent("click", this.node, e);
12877                 return;
12878             }
12879             e.preventDefault();
12880             if(this.disabled){
12881                 return;
12882             }
12883
12884             if(this.node.attributes.singleClickExpand && !this.animating && this.node.hasChildNodes()){
12885                 this.node.toggle();
12886             }
12887
12888             this.fireEvent("click", this.node, e);
12889         }else{
12890             e.stopEvent();
12891         }
12892     },
12893
12894     onDblClick : function(e){
12895         e.preventDefault();
12896         if(this.disabled){
12897             return;
12898         }
12899         if(this.checkbox){
12900             this.toggleCheck();
12901         }
12902         if(!this.animating && this.node.hasChildNodes()){
12903             this.node.toggle();
12904         }
12905         this.fireEvent("dblclick", this.node, e);
12906     },
12907
12908     onCheckChange : function(){
12909         var checked = this.checkbox.checked;
12910         this.node.attributes.checked = checked;
12911         this.fireEvent('checkchange', this.node, checked);
12912     },
12913
12914     ecClick : function(e){
12915         if(!this.animating && this.node.hasChildNodes()){
12916             this.node.toggle();
12917         }
12918     },
12919
12920     startDrop : function(){
12921         this.dropping = true;
12922     },
12923
12924     // delayed drop so the click event doesn't get fired on a drop
12925     endDrop : function(){
12926        setTimeout(function(){
12927            this.dropping = false;
12928        }.createDelegate(this), 50);
12929     },
12930
12931     expand : function(){
12932         this.updateExpandIcon();
12933         this.ctNode.style.display = "";
12934     },
12935
12936     focus : function(){
12937         if(!this.node.preventHScroll){
12938             try{this.anchor.focus();
12939             }catch(e){}
12940         }else if(!Roo.isIE){
12941             try{
12942                 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
12943                 var l = noscroll.scrollLeft;
12944                 this.anchor.focus();
12945                 noscroll.scrollLeft = l;
12946             }catch(e){}
12947         }
12948     },
12949
12950     toggleCheck : function(value){
12951         var cb = this.checkbox;
12952         if(cb){
12953             cb.checked = (value === undefined ? !cb.checked : value);
12954         }
12955     },
12956
12957     blur : function(){
12958         try{
12959             this.anchor.blur();
12960         }catch(e){}
12961     },
12962
12963     animExpand : function(callback){
12964         var ct = Roo.get(this.ctNode);
12965         ct.stopFx();
12966         if(!this.node.hasChildNodes()){
12967             this.updateExpandIcon();
12968             this.ctNode.style.display = "";
12969             Roo.callback(callback);
12970             return;
12971         }
12972         this.animating = true;
12973         this.updateExpandIcon();
12974
12975         ct.slideIn('t', {
12976            callback : function(){
12977                this.animating = false;
12978                Roo.callback(callback);
12979             },
12980             scope: this,
12981             duration: this.node.ownerTree.duration || .25
12982         });
12983     },
12984
12985     highlight : function(){
12986         var tree = this.node.getOwnerTree();
12987         Roo.fly(this.wrap).highlight(
12988             tree.hlColor || "C3DAF9",
12989             {endColor: tree.hlBaseColor}
12990         );
12991     },
12992
12993     collapse : function(){
12994         this.updateExpandIcon();
12995         this.ctNode.style.display = "none";
12996     },
12997
12998     animCollapse : function(callback){
12999         var ct = Roo.get(this.ctNode);
13000         ct.enableDisplayMode('block');
13001         ct.stopFx();
13002
13003         this.animating = true;
13004         this.updateExpandIcon();
13005
13006         ct.slideOut('t', {
13007             callback : function(){
13008                this.animating = false;
13009                Roo.callback(callback);
13010             },
13011             scope: this,
13012             duration: this.node.ownerTree.duration || .25
13013         });
13014     },
13015
13016     getContainer : function(){
13017         return this.ctNode;
13018     },
13019
13020     getEl : function(){
13021         return this.wrap;
13022     },
13023
13024     appendDDGhost : function(ghostNode){
13025         ghostNode.appendChild(this.elNode.cloneNode(true));
13026     },
13027
13028     getDDRepairXY : function(){
13029         return Roo.lib.Dom.getXY(this.iconNode);
13030     },
13031
13032     onRender : function(){
13033         this.render();
13034     },
13035
13036     render : function(bulkRender){
13037         var n = this.node, a = n.attributes;
13038         var targetNode = n.parentNode ?
13039               n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
13040
13041         if(!this.rendered){
13042             this.rendered = true;
13043
13044             this.renderElements(n, a, targetNode, bulkRender);
13045
13046             if(a.qtip){
13047                if(this.textNode.setAttributeNS){
13048                    this.textNode.setAttributeNS("ext", "qtip", a.qtip);
13049                    if(a.qtipTitle){
13050                        this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
13051                    }
13052                }else{
13053                    this.textNode.setAttribute("ext:qtip", a.qtip);
13054                    if(a.qtipTitle){
13055                        this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
13056                    }
13057                }
13058             }else if(a.qtipCfg){
13059                 a.qtipCfg.target = Roo.id(this.textNode);
13060                 Roo.QuickTips.register(a.qtipCfg);
13061             }
13062             this.initEvents();
13063             if(!this.node.expanded){
13064                 this.updateExpandIcon();
13065             }
13066         }else{
13067             if(bulkRender === true) {
13068                 targetNode.appendChild(this.wrap);
13069             }
13070         }
13071     },
13072
13073     renderElements : function(n, a, targetNode, bulkRender)
13074     {
13075         // add some indent caching, this helps performance when rendering a large tree
13076         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
13077         var t = n.getOwnerTree();
13078         var txt = t && t.renderer ? t.renderer(n.attributes) : Roo.util.Format.htmlEncode(n.text);
13079         if (typeof(n.attributes.html) != 'undefined') {
13080             txt = n.attributes.html;
13081         }
13082         var tip = t && t.rendererTip ? t.rendererTip(n.attributes) : txt;
13083         var cb = typeof a.checked == 'boolean';
13084         var href = a.href ? a.href : Roo.isGecko ? "" : "#";
13085         var buf = ['<li class="x-tree-node"><div class="x-tree-node-el ', a.cls,'">',
13086             '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
13087             '<img src="', this.emptyIcon, '" class="x-tree-ec-icon" />',
13088             '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
13089             cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : ' />')) : '',
13090             '<a hidefocus="on" href="',href,'" tabIndex="1" ',
13091              a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", 
13092                 '><span unselectable="on" qtip="' , tip ,'">',txt,"</span></a></div>",
13093             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
13094             "</li>"];
13095
13096         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
13097             this.wrap = Roo.DomHelper.insertHtml("beforeBegin",
13098                                 n.nextSibling.ui.getEl(), buf.join(""));
13099         }else{
13100             this.wrap = Roo.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
13101         }
13102
13103         this.elNode = this.wrap.childNodes[0];
13104         this.ctNode = this.wrap.childNodes[1];
13105         var cs = this.elNode.childNodes;
13106         this.indentNode = cs[0];
13107         this.ecNode = cs[1];
13108         this.iconNode = cs[2];
13109         var index = 3;
13110         if(cb){
13111             this.checkbox = cs[3];
13112             index++;
13113         }
13114         this.anchor = cs[index];
13115         this.textNode = cs[index].firstChild;
13116     },
13117
13118     getAnchor : function(){
13119         return this.anchor;
13120     },
13121
13122     getTextEl : function(){
13123         return this.textNode;
13124     },
13125
13126     getIconEl : function(){
13127         return this.iconNode;
13128     },
13129
13130     isChecked : function(){
13131         return this.checkbox ? this.checkbox.checked : false;
13132     },
13133
13134     updateExpandIcon : function(){
13135         if(this.rendered){
13136             var n = this.node, c1, c2;
13137             var cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow";
13138             var hasChild = n.hasChildNodes();
13139             if(hasChild){
13140                 if(n.expanded){
13141                     cls += "-minus";
13142                     c1 = "x-tree-node-collapsed";
13143                     c2 = "x-tree-node-expanded";
13144                 }else{
13145                     cls += "-plus";
13146                     c1 = "x-tree-node-expanded";
13147                     c2 = "x-tree-node-collapsed";
13148                 }
13149                 if(this.wasLeaf){
13150                     this.removeClass("x-tree-node-leaf");
13151                     this.wasLeaf = false;
13152                 }
13153                 if(this.c1 != c1 || this.c2 != c2){
13154                     Roo.fly(this.elNode).replaceClass(c1, c2);
13155                     this.c1 = c1; this.c2 = c2;
13156                 }
13157             }else{
13158                 // this changes non-leafs into leafs if they have no children.
13159                 // it's not very rational behaviour..
13160                 
13161                 if(!this.wasLeaf && this.node.leaf){
13162                     Roo.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-leaf");
13163                     delete this.c1;
13164                     delete this.c2;
13165                     this.wasLeaf = true;
13166                 }
13167             }
13168             var ecc = "x-tree-ec-icon "+cls;
13169             if(this.ecc != ecc){
13170                 this.ecNode.className = ecc;
13171                 this.ecc = ecc;
13172             }
13173         }
13174     },
13175
13176     getChildIndent : function(){
13177         if(!this.childIndent){
13178             var buf = [];
13179             var p = this.node;
13180             while(p){
13181                 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
13182                     if(!p.isLast()) {
13183                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
13184                     } else {
13185                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
13186                     }
13187                 }
13188                 p = p.parentNode;
13189             }
13190             this.childIndent = buf.join("");
13191         }
13192         return this.childIndent;
13193     },
13194
13195     renderIndent : function(){
13196         if(this.rendered){
13197             var indent = "";
13198             var p = this.node.parentNode;
13199             if(p){
13200                 indent = p.ui.getChildIndent();
13201             }
13202             if(this.indentMarkup != indent){ // don't rerender if not required
13203                 this.indentNode.innerHTML = indent;
13204                 this.indentMarkup = indent;
13205             }
13206             this.updateExpandIcon();
13207         }
13208     }
13209 };
13210
13211 Roo.tree.RootTreeNodeUI = function(){
13212     Roo.tree.RootTreeNodeUI.superclass.constructor.apply(this, arguments);
13213 };
13214 Roo.extend(Roo.tree.RootTreeNodeUI, Roo.tree.TreeNodeUI, {
13215     render : function(){
13216         if(!this.rendered){
13217             var targetNode = this.node.ownerTree.innerCt.dom;
13218             this.node.expanded = true;
13219             targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
13220             this.wrap = this.ctNode = targetNode.firstChild;
13221         }
13222     },
13223     collapse : function(){
13224     },
13225     expand : function(){
13226     }
13227 });/*
13228  * Based on:
13229  * Ext JS Library 1.1.1
13230  * Copyright(c) 2006-2007, Ext JS, LLC.
13231  *
13232  * Originally Released Under LGPL - original licence link has changed is not relivant.
13233  *
13234  * Fork - LGPL
13235  * <script type="text/javascript">
13236  */
13237 /**
13238  * @class Roo.tree.TreeLoader
13239  * @extends Roo.util.Observable
13240  * A TreeLoader provides for lazy loading of an {@link Roo.tree.TreeNode}'s child
13241  * nodes from a specified URL. The response must be a javascript Array definition
13242  * who's elements are node definition objects. eg:
13243  * <pre><code>
13244 {  success : true,
13245    data :      [
13246    
13247     { 'id': 1, 'text': 'A folder Node', 'leaf': false },
13248     { 'id': 2, 'text': 'A leaf Node', 'leaf': true }
13249     ]
13250 }
13251
13252
13253 </code></pre>
13254  * <br><br>
13255  * The old style respose with just an array is still supported, but not recommended.
13256  * <br><br>
13257  *
13258  * A server request is sent, and child nodes are loaded only when a node is expanded.
13259  * The loading node's id is passed to the server under the parameter name "node" to
13260  * enable the server to produce the correct child nodes.
13261  * <br><br>
13262  * To pass extra parameters, an event handler may be attached to the "beforeload"
13263  * event, and the parameters specified in the TreeLoader's baseParams property:
13264  * <pre><code>
13265     myTreeLoader.on("beforeload", function(treeLoader, node) {
13266         this.baseParams.category = node.attributes.category;
13267     }, this);
13268     
13269 </code></pre>
13270  *
13271  * This would pass an HTTP parameter called "category" to the server containing
13272  * the value of the Node's "category" attribute.
13273  * @constructor
13274  * Creates a new Treeloader.
13275  * @param {Object} config A config object containing config properties.
13276  */
13277 Roo.tree.TreeLoader = function(config){
13278     this.baseParams = {};
13279     this.requestMethod = "POST";
13280     Roo.apply(this, config);
13281
13282     this.addEvents({
13283     
13284         /**
13285          * @event beforeload
13286          * Fires before a network request is made to retrieve the Json text which specifies a node's children.
13287          * @param {Object} This TreeLoader object.
13288          * @param {Object} node The {@link Roo.tree.TreeNode} object being loaded.
13289          * @param {Object} callback The callback function specified in the {@link #load} call.
13290          */
13291         beforeload : true,
13292         /**
13293          * @event load
13294          * Fires when the node has been successfuly loaded.
13295          * @param {Object} This TreeLoader object.
13296          * @param {Object} node The {@link Roo.tree.TreeNode} object being loaded.
13297          * @param {Object} response The response object containing the data from the server.
13298          */
13299         load : true,
13300         /**
13301          * @event loadexception
13302          * Fires if the network request failed.
13303          * @param {Object} This TreeLoader object.
13304          * @param {Object} node The {@link Roo.tree.TreeNode} object being loaded.
13305          * @param {Object} response The response object containing the data from the server.
13306          */
13307         loadexception : true,
13308         /**
13309          * @event create
13310          * Fires before a node is created, enabling you to return custom Node types 
13311          * @param {Object} This TreeLoader object.
13312          * @param {Object} attr - the data returned from the AJAX call (modify it to suit)
13313          */
13314         create : true
13315     });
13316
13317     Roo.tree.TreeLoader.superclass.constructor.call(this);
13318 };
13319
13320 Roo.extend(Roo.tree.TreeLoader, Roo.util.Observable, {
13321     /**
13322     * @cfg {String} dataUrl The URL from which to request a Json string which
13323     * specifies an array of node definition object representing the child nodes
13324     * to be loaded.
13325     */
13326     /**
13327     * @cfg {String} requestMethod either GET or POST
13328     * defaults to POST (due to BC)
13329     * to be loaded.
13330     */
13331     /**
13332     * @cfg {Object} baseParams (optional) An object containing properties which
13333     * specify HTTP parameters to be passed to each request for child nodes.
13334     */
13335     /**
13336     * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
13337     * created by this loader. If the attributes sent by the server have an attribute in this object,
13338     * they take priority.
13339     */
13340     /**
13341     * @cfg {Object} uiProviders (optional) An object containing properties which
13342     * 
13343     * DEPRECATED - use 'create' event handler to modify attributes - which affect creation.
13344     * specify custom {@link Roo.tree.TreeNodeUI} implementations. If the optional
13345     * <i>uiProvider</i> attribute of a returned child node is a string rather
13346     * than a reference to a TreeNodeUI implementation, this that string value
13347     * is used as a property name in the uiProviders object. You can define the provider named
13348     * 'default' , and this will be used for all nodes (if no uiProvider is delivered by the node data)
13349     */
13350     uiProviders : {},
13351
13352     /**
13353     * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
13354     * child nodes before loading.
13355     */
13356     clearOnLoad : true,
13357
13358     /**
13359     * @cfg {String} root (optional) Default to false. Use this to read data from an object 
13360     * property on loading, rather than expecting an array. (eg. more compatible to a standard
13361     * Grid query { data : [ .....] }
13362     */
13363     
13364     root : false,
13365      /**
13366     * @cfg {String} queryParam (optional) 
13367     * Name of the query as it will be passed on the querystring (defaults to 'node')
13368     * eg. the request will be ?node=[id]
13369     */
13370     
13371     
13372     queryParam: false,
13373     
13374     /**
13375      * Load an {@link Roo.tree.TreeNode} from the URL specified in the constructor.
13376      * This is called automatically when a node is expanded, but may be used to reload
13377      * a node (or append new children if the {@link #clearOnLoad} option is false.)
13378      * @param {Roo.tree.TreeNode} node
13379      * @param {Function} callback
13380      */
13381     load : function(node, callback){
13382         if(this.clearOnLoad){
13383             while(node.firstChild){
13384                 node.removeChild(node.firstChild);
13385             }
13386         }
13387         if(node.attributes.children){ // preloaded json children
13388             var cs = node.attributes.children;
13389             for(var i = 0, len = cs.length; i < len; i++){
13390                 node.appendChild(this.createNode(cs[i]));
13391             }
13392             if(typeof callback == "function"){
13393                 callback();
13394             }
13395         }else if(this.dataUrl){
13396             this.requestData(node, callback);
13397         }
13398     },
13399
13400     getParams: function(node){
13401         var buf = [], bp = this.baseParams;
13402         for(var key in bp){
13403             if(typeof bp[key] != "function"){
13404                 buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");
13405             }
13406         }
13407         var n = this.queryParam === false ? 'node' : this.queryParam;
13408         buf.push(n + "=", encodeURIComponent(node.id));
13409         return buf.join("");
13410     },
13411
13412     requestData : function(node, callback){
13413         if(this.fireEvent("beforeload", this, node, callback) !== false){
13414             this.transId = Roo.Ajax.request({
13415                 method:this.requestMethod,
13416                 url: this.dataUrl||this.url,
13417                 success: this.handleResponse,
13418                 failure: this.handleFailure,
13419                 scope: this,
13420                 argument: {callback: callback, node: node},
13421                 params: this.getParams(node)
13422             });
13423         }else{
13424             // if the load is cancelled, make sure we notify
13425             // the node that we are done
13426             if(typeof callback == "function"){
13427                 callback();
13428             }
13429         }
13430     },
13431
13432     isLoading : function(){
13433         return this.transId ? true : false;
13434     },
13435
13436     abort : function(){
13437         if(this.isLoading()){
13438             Roo.Ajax.abort(this.transId);
13439         }
13440     },
13441
13442     // private
13443     createNode : function(attr)
13444     {
13445         // apply baseAttrs, nice idea Corey!
13446         if(this.baseAttrs){
13447             Roo.applyIf(attr, this.baseAttrs);
13448         }
13449         if(this.applyLoader !== false){
13450             attr.loader = this;
13451         }
13452         // uiProvider = depreciated..
13453         
13454         if(typeof(attr.uiProvider) == 'string'){
13455            attr.uiProvider = this.uiProviders[attr.uiProvider] || 
13456                 /**  eval:var:attr */ eval(attr.uiProvider);
13457         }
13458         if(typeof(this.uiProviders['default']) != 'undefined') {
13459             attr.uiProvider = this.uiProviders['default'];
13460         }
13461         
13462         this.fireEvent('create', this, attr);
13463         
13464         attr.leaf  = typeof(attr.leaf) == 'string' ? attr.leaf * 1 : attr.leaf;
13465         return(attr.leaf ?
13466                         new Roo.tree.TreeNode(attr) :
13467                         new Roo.tree.AsyncTreeNode(attr));
13468     },
13469
13470     processResponse : function(response, node, callback)
13471     {
13472         var json = response.responseText;
13473         try {
13474             
13475             var o = Roo.decode(json);
13476             
13477             if (this.root === false && typeof(o.success) != undefined) {
13478                 this.root = 'data'; // the default behaviour for list like data..
13479                 }
13480                 
13481             if (this.root !== false &&  !o.success) {
13482                 // it's a failure condition.
13483                 var a = response.argument;
13484                 this.fireEvent("loadexception", this, a.node, response);
13485                 Roo.log("Load failed - should have a handler really");
13486                 return;
13487             }
13488             
13489             
13490             
13491             if (this.root !== false) {
13492                  o = o[this.root];
13493             }
13494             
13495             for(var i = 0, len = o.length; i < len; i++){
13496                 var n = this.createNode(o[i]);
13497                 if(n){
13498                     node.appendChild(n);
13499                 }
13500             }
13501             if(typeof callback == "function"){
13502                 callback(this, node);
13503             }
13504         }catch(e){
13505             this.handleFailure(response);
13506         }
13507     },
13508
13509     handleResponse : function(response){
13510         this.transId = false;
13511         var a = response.argument;
13512         this.processResponse(response, a.node, a.callback);
13513         this.fireEvent("load", this, a.node, response);
13514     },
13515
13516     handleFailure : function(response)
13517     {
13518         // should handle failure better..
13519         this.transId = false;
13520         var a = response.argument;
13521         this.fireEvent("loadexception", this, a.node, response);
13522         if(typeof a.callback == "function"){
13523             a.callback(this, a.node);
13524         }
13525     }
13526 });/*
13527  * Based on:
13528  * Ext JS Library 1.1.1
13529  * Copyright(c) 2006-2007, Ext JS, LLC.
13530  *
13531  * Originally Released Under LGPL - original licence link has changed is not relivant.
13532  *
13533  * Fork - LGPL
13534  * <script type="text/javascript">
13535  */
13536
13537 /**
13538 * @class Roo.tree.TreeFilter
13539 * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
13540 * @param {TreePanel} tree
13541 * @param {Object} config (optional)
13542  */
13543 Roo.tree.TreeFilter = function(tree, config){
13544     this.tree = tree;
13545     this.filtered = {};
13546     Roo.apply(this, config);
13547 };
13548
13549 Roo.tree.TreeFilter.prototype = {
13550     clearBlank:false,
13551     reverse:false,
13552     autoClear:false,
13553     remove:false,
13554
13555      /**
13556      * Filter the data by a specific attribute.
13557      * @param {String/RegExp} value Either string that the attribute value
13558      * should start with or a RegExp to test against the attribute
13559      * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
13560      * @param {TreeNode} startNode (optional) The node to start the filter at.
13561      */
13562     filter : function(value, attr, startNode){
13563         attr = attr || "text";
13564         var f;
13565         if(typeof value == "string"){
13566             var vlen = value.length;
13567             // auto clear empty filter
13568             if(vlen == 0 && this.clearBlank){
13569                 this.clear();
13570                 return;
13571             }
13572             value = value.toLowerCase();
13573             f = function(n){
13574                 return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
13575             };
13576         }else if(value.exec){ // regex?
13577             f = function(n){
13578                 return value.test(n.attributes[attr]);
13579             };
13580         }else{
13581             throw 'Illegal filter type, must be string or regex';
13582         }
13583         this.filterBy(f, null, startNode);
13584         },
13585
13586     /**
13587      * Filter by a function. The passed function will be called with each
13588      * node in the tree (or from the startNode). If the function returns true, the node is kept
13589      * otherwise it is filtered. If a node is filtered, its children are also filtered.
13590      * @param {Function} fn The filter function
13591      * @param {Object} scope (optional) The scope of the function (defaults to the current node)
13592      */
13593     filterBy : function(fn, scope, startNode){
13594         startNode = startNode || this.tree.root;
13595         if(this.autoClear){
13596             this.clear();
13597         }
13598         var af = this.filtered, rv = this.reverse;
13599         var f = function(n){
13600             if(n == startNode){
13601                 return true;
13602             }
13603             if(af[n.id]){
13604                 return false;
13605             }
13606             var m = fn.call(scope || n, n);
13607             if(!m || rv){
13608                 af[n.id] = n;
13609                 n.ui.hide();
13610                 return false;
13611             }
13612             return true;
13613         };
13614         startNode.cascade(f);
13615         if(this.remove){
13616            for(var id in af){
13617                if(typeof id != "function"){
13618                    var n = af[id];
13619                    if(n && n.parentNode){
13620                        n.parentNode.removeChild(n);
13621                    }
13622                }
13623            }
13624         }
13625     },
13626
13627     /**
13628      * Clears the current filter. Note: with the "remove" option
13629      * set a filter cannot be cleared.
13630      */
13631     clear : function(){
13632         var t = this.tree;
13633         var af = this.filtered;
13634         for(var id in af){
13635             if(typeof id != "function"){
13636                 var n = af[id];
13637                 if(n){
13638                     n.ui.show();
13639                 }
13640             }
13641         }
13642         this.filtered = {};
13643     }
13644 };
13645 /*
13646  * Based on:
13647  * Ext JS Library 1.1.1
13648  * Copyright(c) 2006-2007, Ext JS, LLC.
13649  *
13650  * Originally Released Under LGPL - original licence link has changed is not relivant.
13651  *
13652  * Fork - LGPL
13653  * <script type="text/javascript">
13654  */
13655  
13656
13657 /**
13658  * @class Roo.tree.TreeSorter
13659  * Provides sorting of nodes in a TreePanel
13660  * 
13661  * @cfg {Boolean} folderSort True to sort leaf nodes under non leaf nodes
13662  * @cfg {String} property The named attribute on the node to sort by (defaults to text)
13663  * @cfg {String} dir The direction to sort (asc or desc) (defaults to asc)
13664  * @cfg {String} leafAttr The attribute used to determine leaf nodes in folder sort (defaults to "leaf")
13665  * @cfg {Boolean} caseSensitive true for case sensitive sort (defaults to false)
13666  * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting
13667  * @constructor
13668  * @param {TreePanel} tree
13669  * @param {Object} config
13670  */
13671 Roo.tree.TreeSorter = function(tree, config){
13672     Roo.apply(this, config);
13673     tree.on("beforechildrenrendered", this.doSort, this);
13674     tree.on("append", this.updateSort, this);
13675     tree.on("insert", this.updateSort, this);
13676     
13677     var dsc = this.dir && this.dir.toLowerCase() == "desc";
13678     var p = this.property || "text";
13679     var sortType = this.sortType;
13680     var fs = this.folderSort;
13681     var cs = this.caseSensitive === true;
13682     var leafAttr = this.leafAttr || 'leaf';
13683
13684     this.sortFn = function(n1, n2){
13685         if(fs){
13686             if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
13687                 return 1;
13688             }
13689             if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
13690                 return -1;
13691             }
13692         }
13693         var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
13694         var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
13695         if(v1 < v2){
13696                         return dsc ? +1 : -1;
13697                 }else if(v1 > v2){
13698                         return dsc ? -1 : +1;
13699         }else{
13700                 return 0;
13701         }
13702     };
13703 };
13704
13705 Roo.tree.TreeSorter.prototype = {
13706     doSort : function(node){
13707         node.sort(this.sortFn);
13708     },
13709     
13710     compareNodes : function(n1, n2){
13711         return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
13712     },
13713     
13714     updateSort : function(tree, node){
13715         if(node.childrenRendered){
13716             this.doSort.defer(1, this, [node]);
13717         }
13718     }
13719 };/*
13720  * Based on:
13721  * Ext JS Library 1.1.1
13722  * Copyright(c) 2006-2007, Ext JS, LLC.
13723  *
13724  * Originally Released Under LGPL - original licence link has changed is not relivant.
13725  *
13726  * Fork - LGPL
13727  * <script type="text/javascript">
13728  */
13729
13730 if(Roo.dd.DropZone){
13731     
13732 Roo.tree.TreeDropZone = function(tree, config){
13733     this.allowParentInsert = false;
13734     this.allowContainerDrop = false;
13735     this.appendOnly = false;
13736     Roo.tree.TreeDropZone.superclass.constructor.call(this, tree.innerCt, config);
13737     this.tree = tree;
13738     this.lastInsertClass = "x-tree-no-status";
13739     this.dragOverData = {};
13740 };
13741
13742 Roo.extend(Roo.tree.TreeDropZone, Roo.dd.DropZone, {
13743     ddGroup : "TreeDD",
13744     scroll:  true,
13745     
13746     expandDelay : 1000,
13747     
13748     expandNode : function(node){
13749         if(node.hasChildNodes() && !node.isExpanded()){
13750             node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
13751         }
13752     },
13753     
13754     queueExpand : function(node){
13755         this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
13756     },
13757     
13758     cancelExpand : function(){
13759         if(this.expandProcId){
13760             clearTimeout(this.expandProcId);
13761             this.expandProcId = false;
13762         }
13763     },
13764     
13765     isValidDropPoint : function(n, pt, dd, e, data){
13766         if(!n || !data){ return false; }
13767         var targetNode = n.node;
13768         var dropNode = data.node;
13769         // default drop rules
13770         if(!(targetNode && targetNode.isTarget && pt)){
13771             return false;
13772         }
13773         if(pt == "append" && targetNode.allowChildren === false){
13774             return false;
13775         }
13776         if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
13777             return false;
13778         }
13779         if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
13780             return false;
13781         }
13782         // reuse the object
13783         var overEvent = this.dragOverData;
13784         overEvent.tree = this.tree;
13785         overEvent.target = targetNode;
13786         overEvent.data = data;
13787         overEvent.point = pt;
13788         overEvent.source = dd;
13789         overEvent.rawEvent = e;
13790         overEvent.dropNode = dropNode;
13791         overEvent.cancel = false;  
13792         var result = this.tree.fireEvent("nodedragover", overEvent);
13793         return overEvent.cancel === false && result !== false;
13794     },
13795     
13796     getDropPoint : function(e, n, dd)
13797     {
13798         var tn = n.node;
13799         if(tn.isRoot){
13800             return tn.allowChildren !== false ? "append" : false; // always append for root
13801         }
13802         var dragEl = n.ddel;
13803         var t = Roo.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
13804         var y = Roo.lib.Event.getPageY(e);
13805         //var noAppend = tn.allowChildren === false || tn.isLeaf();
13806         
13807         // we may drop nodes anywhere, as long as allowChildren has not been set to false..
13808         var noAppend = tn.allowChildren === false;
13809         if(this.appendOnly || tn.parentNode.allowChildren === false){
13810             return noAppend ? false : "append";
13811         }
13812         var noBelow = false;
13813         if(!this.allowParentInsert){
13814             noBelow = tn.hasChildNodes() && tn.isExpanded();
13815         }
13816         var q = (b - t) / (noAppend ? 2 : 3);
13817         if(y >= t && y < (t + q)){
13818             return "above";
13819         }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
13820             return "below";
13821         }else{
13822             return "append";
13823         }
13824     },
13825     
13826     onNodeEnter : function(n, dd, e, data)
13827     {
13828         this.cancelExpand();
13829     },
13830     
13831     onNodeOver : function(n, dd, e, data)
13832     {
13833        
13834         var pt = this.getDropPoint(e, n, dd);
13835         var node = n.node;
13836         
13837         // auto node expand check
13838         if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
13839             this.queueExpand(node);
13840         }else if(pt != "append"){
13841             this.cancelExpand();
13842         }
13843         
13844         // set the insert point style on the target node
13845         var returnCls = this.dropNotAllowed;
13846         if(this.isValidDropPoint(n, pt, dd, e, data)){
13847            if(pt){
13848                var el = n.ddel;
13849                var cls;
13850                if(pt == "above"){
13851                    returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
13852                    cls = "x-tree-drag-insert-above";
13853                }else if(pt == "below"){
13854                    returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
13855                    cls = "x-tree-drag-insert-below";
13856                }else{
13857                    returnCls = "x-tree-drop-ok-append";
13858                    cls = "x-tree-drag-append";
13859                }
13860                if(this.lastInsertClass != cls){
13861                    Roo.fly(el).replaceClass(this.lastInsertClass, cls);
13862                    this.lastInsertClass = cls;
13863                }
13864            }
13865        }
13866        return returnCls;
13867     },
13868     
13869     onNodeOut : function(n, dd, e, data){
13870         
13871         this.cancelExpand();
13872         this.removeDropIndicators(n);
13873     },
13874     
13875     onNodeDrop : function(n, dd, e, data){
13876         var point = this.getDropPoint(e, n, dd);
13877         var targetNode = n.node;
13878         targetNode.ui.startDrop();
13879         if(!this.isValidDropPoint(n, point, dd, e, data)){
13880             targetNode.ui.endDrop();
13881             return false;
13882         }
13883         // first try to find the drop node
13884         var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
13885         var dropEvent = {
13886             tree : this.tree,
13887             target: targetNode,
13888             data: data,
13889             point: point,
13890             source: dd,
13891             rawEvent: e,
13892             dropNode: dropNode,
13893             cancel: !dropNode   
13894         };
13895         var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
13896         if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
13897             targetNode.ui.endDrop();
13898             return false;
13899         }
13900         // allow target changing
13901         targetNode = dropEvent.target;
13902         if(point == "append" && !targetNode.isExpanded()){
13903             targetNode.expand(false, null, function(){
13904                 this.completeDrop(dropEvent);
13905             }.createDelegate(this));
13906         }else{
13907             this.completeDrop(dropEvent);
13908         }
13909         return true;
13910     },
13911     
13912     completeDrop : function(de){
13913         var ns = de.dropNode, p = de.point, t = de.target;
13914         if(!(ns instanceof Array)){
13915             ns = [ns];
13916         }
13917         var n;
13918         for(var i = 0, len = ns.length; i < len; i++){
13919             n = ns[i];
13920             if(p == "above"){
13921                 t.parentNode.insertBefore(n, t);
13922             }else if(p == "below"){
13923                 t.parentNode.insertBefore(n, t.nextSibling);
13924             }else{
13925                 t.appendChild(n);
13926             }
13927         }
13928         n.ui.focus();
13929         if(this.tree.hlDrop){
13930             n.ui.highlight();
13931         }
13932         t.ui.endDrop();
13933         this.tree.fireEvent("nodedrop", de);
13934     },
13935     
13936     afterNodeMoved : function(dd, data, e, targetNode, dropNode){
13937         if(this.tree.hlDrop){
13938             dropNode.ui.focus();
13939             dropNode.ui.highlight();
13940         }
13941         this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
13942     },
13943     
13944     getTree : function(){
13945         return this.tree;
13946     },
13947     
13948     removeDropIndicators : function(n){
13949         if(n && n.ddel){
13950             var el = n.ddel;
13951             Roo.fly(el).removeClass([
13952                     "x-tree-drag-insert-above",
13953                     "x-tree-drag-insert-below",
13954                     "x-tree-drag-append"]);
13955             this.lastInsertClass = "_noclass";
13956         }
13957     },
13958     
13959     beforeDragDrop : function(target, e, id){
13960         this.cancelExpand();
13961         return true;
13962     },
13963     
13964     afterRepair : function(data){
13965         if(data && Roo.enableFx){
13966             data.node.ui.highlight();
13967         }
13968         this.hideProxy();
13969     } 
13970     
13971 });
13972
13973 }
13974 /*
13975  * Based on:
13976  * Ext JS Library 1.1.1
13977  * Copyright(c) 2006-2007, Ext JS, LLC.
13978  *
13979  * Originally Released Under LGPL - original licence link has changed is not relivant.
13980  *
13981  * Fork - LGPL
13982  * <script type="text/javascript">
13983  */
13984  
13985
13986 if(Roo.dd.DragZone){
13987 Roo.tree.TreeDragZone = function(tree, config){
13988     Roo.tree.TreeDragZone.superclass.constructor.call(this, tree.getTreeEl(), config);
13989     this.tree = tree;
13990 };
13991
13992 Roo.extend(Roo.tree.TreeDragZone, Roo.dd.DragZone, {
13993     ddGroup : "TreeDD",
13994    
13995     onBeforeDrag : function(data, e){
13996         var n = data.node;
13997         return n && n.draggable && !n.disabled;
13998     },
13999      
14000     
14001     onInitDrag : function(e){
14002         var data = this.dragData;
14003         this.tree.getSelectionModel().select(data.node);
14004         this.proxy.update("");
14005         data.node.ui.appendDDGhost(this.proxy.ghost.dom);
14006         this.tree.fireEvent("startdrag", this.tree, data.node, e);
14007     },
14008     
14009     getRepairXY : function(e, data){
14010         return data.node.ui.getDDRepairXY();
14011     },
14012     
14013     onEndDrag : function(data, e){
14014         this.tree.fireEvent("enddrag", this.tree, data.node, e);
14015         
14016         
14017     },
14018     
14019     onValidDrop : function(dd, e, id){
14020         this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
14021         this.hideProxy();
14022     },
14023     
14024     beforeInvalidDrop : function(e, id){
14025         // this scrolls the original position back into view
14026         var sm = this.tree.getSelectionModel();
14027         sm.clearSelections();
14028         sm.select(this.dragData.node);
14029     }
14030 });
14031 }/*
14032  * Based on:
14033  * Ext JS Library 1.1.1
14034  * Copyright(c) 2006-2007, Ext JS, LLC.
14035  *
14036  * Originally Released Under LGPL - original licence link has changed is not relivant.
14037  *
14038  * Fork - LGPL
14039  * <script type="text/javascript">
14040  */
14041 /**
14042  * @class Roo.tree.TreeEditor
14043  * @extends Roo.Editor
14044  * Provides editor functionality for inline tree node editing.  Any valid {@link Roo.form.Field} can be used
14045  * as the editor field.
14046  * @constructor
14047  * @param {Object} config (used to be the tree panel.)
14048  * @param {Object} oldconfig DEPRECIATED Either a prebuilt {@link Roo.form.Field} instance or a Field config object
14049  * 
14050  * @cfg {Roo.tree.TreePanel} tree The tree to bind to.
14051  * @cfg {Roo.form.TextField} field [required] The field configuration
14052  *
14053  * 
14054  */
14055 Roo.tree.TreeEditor = function(config, oldconfig) { // was -- (tree, config){
14056     var tree = config;
14057     var field;
14058     if (oldconfig) { // old style..
14059         field = oldconfig.events ? oldconfig : new Roo.form.TextField(oldconfig);
14060     } else {
14061         // new style..
14062         tree = config.tree;
14063         config.field = config.field  || {};
14064         config.field.xtype = 'TextField';
14065         field = Roo.factory(config.field, Roo.form);
14066     }
14067     config = config || {};
14068     
14069     
14070     this.addEvents({
14071         /**
14072          * @event beforenodeedit
14073          * Fires when editing is initiated, but before the value changes.  Editing can be canceled by returning
14074          * false from the handler of this event.
14075          * @param {Editor} this
14076          * @param {Roo.tree.Node} node 
14077          */
14078         "beforenodeedit" : true
14079     });
14080     
14081     //Roo.log(config);
14082     Roo.tree.TreeEditor.superclass.constructor.call(this, field, config);
14083
14084     this.tree = tree;
14085
14086     tree.on('beforeclick', this.beforeNodeClick, this);
14087     tree.getTreeEl().on('mousedown', this.hide, this);
14088     this.on('complete', this.updateNode, this);
14089     this.on('beforestartedit', this.fitToTree, this);
14090     this.on('startedit', this.bindScroll, this, {delay:10});
14091     this.on('specialkey', this.onSpecialKey, this);
14092 };
14093
14094 Roo.extend(Roo.tree.TreeEditor, Roo.Editor, {
14095     /**
14096      * @cfg {String} alignment
14097      * The position to align to (see {@link Roo.Element#alignTo} for more details, defaults to "l-l").
14098      */
14099     alignment: "l-l",
14100     // inherit
14101     autoSize: false,
14102     /**
14103      * @cfg {Boolean} hideEl
14104      * True to hide the bound element while the editor is displayed (defaults to false)
14105      */
14106     hideEl : false,
14107     /**
14108      * @cfg {String} cls
14109      * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
14110      */
14111     cls: "x-small-editor x-tree-editor",
14112     /**
14113      * @cfg {Boolean} shim
14114      * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
14115      */
14116     shim:false,
14117     // inherit
14118     shadow:"frame",
14119     /**
14120      * @cfg {Number} maxWidth
14121      * The maximum width in pixels of the editor field (defaults to 250).  Note that if the maxWidth would exceed
14122      * the containing tree element's size, it will be automatically limited for you to the container width, taking
14123      * scroll and client offsets into account prior to each edit.
14124      */
14125     maxWidth: 250,
14126
14127     editDelay : 350,
14128
14129     // private
14130     fitToTree : function(ed, el){
14131         var td = this.tree.getTreeEl().dom, nd = el.dom;
14132         if(td.scrollLeft >  nd.offsetLeft){ // ensure the node left point is visible
14133             td.scrollLeft = nd.offsetLeft;
14134         }
14135         var w = Math.min(
14136                 this.maxWidth,
14137                 (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
14138         this.setSize(w, '');
14139         
14140         return this.fireEvent('beforenodeedit', this, this.editNode);
14141         
14142     },
14143
14144     // private
14145     triggerEdit : function(node){
14146         this.completeEdit();
14147         this.editNode = node;
14148         this.startEdit(node.ui.textNode, node.text);
14149     },
14150
14151     // private
14152     bindScroll : function(){
14153         this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
14154     },
14155
14156     // private
14157     beforeNodeClick : function(node, e){
14158         var sinceLast = (this.lastClick ? this.lastClick.getElapsed() : 0);
14159         this.lastClick = new Date();
14160         if(sinceLast > this.editDelay && this.tree.getSelectionModel().isSelected(node)){
14161             e.stopEvent();
14162             this.triggerEdit(node);
14163             return false;
14164         }
14165         return true;
14166     },
14167
14168     // private
14169     updateNode : function(ed, value){
14170         this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
14171         this.editNode.setText(value);
14172     },
14173
14174     // private
14175     onHide : function(){
14176         Roo.tree.TreeEditor.superclass.onHide.call(this);
14177         if(this.editNode){
14178             this.editNode.ui.focus();
14179         }
14180     },
14181
14182     // private
14183     onSpecialKey : function(field, e){
14184         var k = e.getKey();
14185         if(k == e.ESC){
14186             e.stopEvent();
14187             this.cancelEdit();
14188         }else if(k == e.ENTER && !e.hasModifier()){
14189             e.stopEvent();
14190             this.completeEdit();
14191         }
14192     }
14193 });//<Script type="text/javascript">
14194 /*
14195  * Based on:
14196  * Ext JS Library 1.1.1
14197  * Copyright(c) 2006-2007, Ext JS, LLC.
14198  *
14199  * Originally Released Under LGPL - original licence link has changed is not relivant.
14200  *
14201  * Fork - LGPL
14202  * <script type="text/javascript">
14203  */
14204  
14205 /**
14206  * Not documented??? - probably should be...
14207  */
14208
14209 Roo.tree.ColumnNodeUI = Roo.extend(Roo.tree.TreeNodeUI, {
14210     //focus: Roo.emptyFn, // prevent odd scrolling behavior
14211     
14212     renderElements : function(n, a, targetNode, bulkRender){
14213         //consel.log("renderElements?");
14214         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
14215
14216         var t = n.getOwnerTree();
14217         var tid = Pman.Tab.Document_TypesTree.tree.el.id;
14218         
14219         var cols = t.columns;
14220         var bw = t.borderWidth;
14221         var c = cols[0];
14222         var href = a.href ? a.href : Roo.isGecko ? "" : "#";
14223          var cb = typeof a.checked == "boolean";
14224         var tx = String.format('{0}',n.text || (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]));
14225         var colcls = 'x-t-' + tid + '-c0';
14226         var buf = [
14227             '<li class="x-tree-node">',
14228             
14229                 
14230                 '<div class="x-tree-node-el ', a.cls,'">',
14231                     // extran...
14232                     '<div class="x-tree-col ', colcls, '" style="width:', c.width-bw, 'px;">',
14233                 
14234                 
14235                         '<span class="x-tree-node-indent">',this.indentMarkup,'</span>',
14236                         '<img src="', this.emptyIcon, '" class="x-tree-ec-icon  " />',
14237                         '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',
14238                            (a.icon ? ' x-tree-node-inline-icon' : ''),
14239                            (a.iconCls ? ' '+a.iconCls : ''),
14240                            '" unselectable="on" />',
14241                         (cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + 
14242                              (a.checked ? 'checked="checked" />' : ' />')) : ''),
14243                              
14244                         '<a class="x-tree-node-anchor" hidefocus="on" href="',href,'" tabIndex="1" ',
14245                             (a.hrefTarget ? ' target="' +a.hrefTarget + '"' : ''), '>',
14246                             '<span unselectable="on" qtip="' + tx + '">',
14247                              tx,
14248                              '</span></a>' ,
14249                     '</div>',
14250                      '<a class="x-tree-node-anchor" hidefocus="on" href="',href,'" tabIndex="1" ',
14251                             (a.hrefTarget ? ' target="' +a.hrefTarget + '"' : ''), '>'
14252                  ];
14253         for(var i = 1, len = cols.length; i < len; i++){
14254             c = cols[i];
14255             colcls = 'x-t-' + tid + '-c' +i;
14256             tx = String.format('{0}', (c.renderer ? c.renderer(a[c.dataIndex], n, a) : a[c.dataIndex]));
14257             buf.push('<div class="x-tree-col ', colcls, ' ' ,(c.cls?c.cls:''),'" style="width:',c.width-bw,'px;">',
14258                         '<div class="x-tree-col-text" qtip="' + tx +'">',tx,"</div>",
14259                       "</div>");
14260          }
14261          
14262          buf.push(
14263             '</a>',
14264             '<div class="x-clear"></div></div>',
14265             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
14266             "</li>");
14267         
14268         if(bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()){
14269             this.wrap = Roo.DomHelper.insertHtml("beforeBegin",
14270                                 n.nextSibling.ui.getEl(), buf.join(""));
14271         }else{
14272             this.wrap = Roo.DomHelper.insertHtml("beforeEnd", targetNode, buf.join(""));
14273         }
14274         var el = this.wrap.firstChild;
14275         this.elRow = el;
14276         this.elNode = el.firstChild;
14277         this.ranchor = el.childNodes[1];
14278         this.ctNode = this.wrap.childNodes[1];
14279         var cs = el.firstChild.childNodes;
14280         this.indentNode = cs[0];
14281         this.ecNode = cs[1];
14282         this.iconNode = cs[2];
14283         var index = 3;
14284         if(cb){
14285             this.checkbox = cs[3];
14286             index++;
14287         }
14288         this.anchor = cs[index];
14289         
14290         this.textNode = cs[index].firstChild;
14291         
14292         //el.on("click", this.onClick, this);
14293         //el.on("dblclick", this.onDblClick, this);
14294         
14295         
14296        // console.log(this);
14297     },
14298     initEvents : function(){
14299         Roo.tree.ColumnNodeUI.superclass.initEvents.call(this);
14300         
14301             
14302         var a = this.ranchor;
14303
14304         var el = Roo.get(a);
14305
14306         if(Roo.isOpera){ // opera render bug ignores the CSS
14307             el.setStyle("text-decoration", "none");
14308         }
14309
14310         el.on("click", this.onClick, this);
14311         el.on("dblclick", this.onDblClick, this);
14312         el.on("contextmenu", this.onContextMenu, this);
14313         
14314     },
14315     
14316     /*onSelectedChange : function(state){
14317         if(state){
14318             this.focus();
14319             this.addClass("x-tree-selected");
14320         }else{
14321             //this.blur();
14322             this.removeClass("x-tree-selected");
14323         }
14324     },*/
14325     addClass : function(cls){
14326         if(this.elRow){
14327             Roo.fly(this.elRow).addClass(cls);
14328         }
14329         
14330     },
14331     
14332     
14333     removeClass : function(cls){
14334         if(this.elRow){
14335             Roo.fly(this.elRow).removeClass(cls);
14336         }
14337     }
14338
14339     
14340     
14341 });//<Script type="text/javascript">
14342
14343 /*
14344  * Based on:
14345  * Ext JS Library 1.1.1
14346  * Copyright(c) 2006-2007, Ext JS, LLC.
14347  *
14348  * Originally Released Under LGPL - original licence link has changed is not relivant.
14349  *
14350  * Fork - LGPL
14351  * <script type="text/javascript">
14352  */
14353  
14354
14355 /**
14356  * @class Roo.tree.ColumnTree
14357  * @extends Roo.tree.TreePanel
14358  * @cfg {Object} columns  Including width, header, renderer, cls, dataIndex 
14359  * @cfg {int} borderWidth  compined right/left border allowance
14360  * @constructor
14361  * @param {String/HTMLElement/Element} el The container element
14362  * @param {Object} config
14363  */
14364 Roo.tree.ColumnTree =  function(el, config)
14365 {
14366    Roo.tree.ColumnTree.superclass.constructor.call(this, el , config);
14367    this.addEvents({
14368         /**
14369         * @event resize
14370         * Fire this event on a container when it resizes
14371         * @param {int} w Width
14372         * @param {int} h Height
14373         */
14374        "resize" : true
14375     });
14376     this.on('resize', this.onResize, this);
14377 };
14378
14379 Roo.extend(Roo.tree.ColumnTree, Roo.tree.TreePanel, {
14380     //lines:false,
14381     
14382     
14383     borderWidth: Roo.isBorderBox ? 0 : 2, 
14384     headEls : false,
14385     
14386     render : function(){
14387         // add the header.....
14388        
14389         Roo.tree.ColumnTree.superclass.render.apply(this);
14390         
14391         this.el.addClass('x-column-tree');
14392         
14393         this.headers = this.el.createChild(
14394             {cls:'x-tree-headers'},this.innerCt.dom);
14395    
14396         var cols = this.columns, c;
14397         var totalWidth = 0;
14398         this.headEls = [];
14399         var  len = cols.length;
14400         for(var i = 0; i < len; i++){
14401              c = cols[i];
14402              totalWidth += c.width;
14403             this.headEls.push(this.headers.createChild({
14404                  cls:'x-tree-hd ' + (c.cls?c.cls+'-hd':''),
14405                  cn: {
14406                      cls:'x-tree-hd-text',
14407                      html: c.header
14408                  },
14409                  style:'width:'+(c.width-this.borderWidth)+'px;'
14410              }));
14411         }
14412         this.headers.createChild({cls:'x-clear'});
14413         // prevent floats from wrapping when clipped
14414         this.headers.setWidth(totalWidth);
14415         //this.innerCt.setWidth(totalWidth);
14416         this.innerCt.setStyle({ overflow: 'auto' });
14417         this.onResize(this.width, this.height);
14418              
14419         
14420     },
14421     onResize : function(w,h)
14422     {
14423         this.height = h;
14424         this.width = w;
14425         // resize cols..
14426         this.innerCt.setWidth(this.width);
14427         this.innerCt.setHeight(this.height-20);
14428         
14429         // headers...
14430         var cols = this.columns, c;
14431         var totalWidth = 0;
14432         var expEl = false;
14433         var len = cols.length;
14434         for(var i = 0; i < len; i++){
14435             c = cols[i];
14436             if (this.autoExpandColumn !== false && c.dataIndex == this.autoExpandColumn) {
14437                 // it's the expander..
14438                 expEl  = this.headEls[i];
14439                 continue;
14440             }
14441             totalWidth += c.width;
14442             
14443         }
14444         if (expEl) {
14445             expEl.setWidth(  ((w - totalWidth)-this.borderWidth - 20));
14446         }
14447         this.headers.setWidth(w-20);
14448
14449         
14450         
14451         
14452     }
14453 });
14454 /*
14455  * Based on:
14456  * Ext JS Library 1.1.1
14457  * Copyright(c) 2006-2007, Ext JS, LLC.
14458  *
14459  * Originally Released Under LGPL - original licence link has changed is not relivant.
14460  *
14461  * Fork - LGPL
14462  * <script type="text/javascript">
14463  */
14464  
14465 /**
14466  * @class Roo.menu.Menu
14467  * @extends Roo.util.Observable
14468  * @children Roo.menu.Item Roo.menu.Separator Roo.menu.TextItem
14469  * A menu object.  This is the container to which you add all other menu items.  Menu can also serve a as a base class
14470  * when you want a specialzed menu based off of another component (like {@link Roo.menu.DateMenu} for example).
14471  * @constructor
14472  * Creates a new Menu
14473  * @param {Object} config Configuration options
14474  */
14475 Roo.menu.Menu = function(config){
14476     
14477     Roo.menu.Menu.superclass.constructor.call(this, config);
14478     
14479     this.id = this.id || Roo.id();
14480     this.addEvents({
14481         /**
14482          * @event beforeshow
14483          * Fires before this menu is displayed
14484          * @param {Roo.menu.Menu} this
14485          */
14486         beforeshow : true,
14487         /**
14488          * @event beforehide
14489          * Fires before this menu is hidden
14490          * @param {Roo.menu.Menu} this
14491          */
14492         beforehide : true,
14493         /**
14494          * @event show
14495          * Fires after this menu is displayed
14496          * @param {Roo.menu.Menu} this
14497          */
14498         show : true,
14499         /**
14500          * @event hide
14501          * Fires after this menu is hidden
14502          * @param {Roo.menu.Menu} this
14503          */
14504         hide : true,
14505         /**
14506          * @event click
14507          * Fires when this menu is clicked (or when the enter key is pressed while it is active)
14508          * @param {Roo.menu.Menu} this
14509          * @param {Roo.menu.Item} menuItem The menu item that was clicked
14510          * @param {Roo.EventObject} e
14511          */
14512         click : true,
14513         /**
14514          * @event mouseover
14515          * Fires when the mouse is hovering over this menu
14516          * @param {Roo.menu.Menu} this
14517          * @param {Roo.EventObject} e
14518          * @param {Roo.menu.Item} menuItem The menu item that was clicked
14519          */
14520         mouseover : true,
14521         /**
14522          * @event mouseout
14523          * Fires when the mouse exits this menu
14524          * @param {Roo.menu.Menu} this
14525          * @param {Roo.EventObject} e
14526          * @param {Roo.menu.Item} menuItem The menu item that was clicked
14527          */
14528         mouseout : true,
14529         /**
14530          * @event itemclick
14531          * Fires when a menu item contained in this menu is clicked
14532          * @param {Roo.menu.BaseItem} baseItem The BaseItem that was clicked
14533          * @param {Roo.EventObject} e
14534          */
14535         itemclick: true
14536     });
14537     if (this.registerMenu) {
14538         Roo.menu.MenuMgr.register(this);
14539     }
14540     
14541     var mis = this.items;
14542     this.items = new Roo.util.MixedCollection();
14543     if(mis){
14544         this.add.apply(this, mis);
14545     }
14546 };
14547
14548 Roo.extend(Roo.menu.Menu, Roo.util.Observable, {
14549     /**
14550      * @cfg {Number} minWidth The minimum width of the menu in pixels (defaults to 120)
14551      */
14552     minWidth : 120,
14553     /**
14554      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop"
14555      * for bottom-right shadow (defaults to "sides")
14556      */
14557     shadow : "sides",
14558     /**
14559      * @cfg {String} subMenuAlign The {@link Roo.Element#alignTo} anchor position value to use for submenus of
14560      * this menu (defaults to "tl-tr?")
14561      */
14562     subMenuAlign : "tl-tr?",
14563     /**
14564      * @cfg {String} defaultAlign The default {@link Roo.Element#alignTo) anchor position value for this menu
14565      * relative to its element of origin (defaults to "tl-bl?")
14566      */
14567     defaultAlign : "tl-bl?",
14568     /**
14569      * @cfg {Boolean} allowOtherMenus True to allow multiple menus to be displayed at the same time (defaults to false)
14570      */
14571     allowOtherMenus : false,
14572     /**
14573      * @cfg {Boolean} registerMenu True (default) - means that clicking on screen etc. hides it.
14574      */
14575     registerMenu : true,
14576
14577     hidden:true,
14578
14579     // private
14580     render : function(){
14581         if(this.el){
14582             return;
14583         }
14584         var el = this.el = new Roo.Layer({
14585             cls: "x-menu",
14586             shadow:this.shadow,
14587             constrain: false,
14588             parentEl: this.parentEl || document.body,
14589             zindex:15000
14590         });
14591
14592         this.keyNav = new Roo.menu.MenuNav(this);
14593
14594         if(this.plain){
14595             el.addClass("x-menu-plain");
14596         }
14597         if(this.cls){
14598             el.addClass(this.cls);
14599         }
14600         // generic focus element
14601         this.focusEl = el.createChild({
14602             tag: "a", cls: "x-menu-focus", href: "#", onclick: "return false;", tabIndex:"-1"
14603         });
14604         var ul = el.createChild({tag: "ul", cls: "x-menu-list"});
14605         //disabling touch- as it's causing issues ..
14606         //ul.on(Roo.isTouch ? 'touchstart' : 'click'   , this.onClick, this);
14607         ul.on('click'   , this.onClick, this);
14608         
14609         
14610         ul.on("mouseover", this.onMouseOver, this);
14611         ul.on("mouseout", this.onMouseOut, this);
14612         this.items.each(function(item){
14613             if (item.hidden) {
14614                 return;
14615             }
14616             
14617             var li = document.createElement("li");
14618             li.className = "x-menu-list-item";
14619             ul.dom.appendChild(li);
14620             item.render(li, this);
14621         }, this);
14622         this.ul = ul;
14623         this.autoWidth();
14624     },
14625
14626     // private
14627     autoWidth : function(){
14628         var el = this.el, ul = this.ul;
14629         if(!el){
14630             return;
14631         }
14632         var w = this.width;
14633         if(w){
14634             el.setWidth(w);
14635         }else if(Roo.isIE){
14636             el.setWidth(this.minWidth);
14637             var t = el.dom.offsetWidth; // force recalc
14638             el.setWidth(ul.getWidth()+el.getFrameWidth("lr"));
14639         }
14640     },
14641
14642     // private
14643     delayAutoWidth : function(){
14644         if(this.rendered){
14645             if(!this.awTask){
14646                 this.awTask = new Roo.util.DelayedTask(this.autoWidth, this);
14647             }
14648             this.awTask.delay(20);
14649         }
14650     },
14651
14652     // private
14653     findTargetItem : function(e){
14654         var t = e.getTarget(".x-menu-list-item", this.ul,  true);
14655         if(t && t.menuItemId){
14656             return this.items.get(t.menuItemId);
14657         }
14658     },
14659
14660     // private
14661     onClick : function(e){
14662         Roo.log("menu.onClick");
14663         var t = this.findTargetItem(e);
14664         if(!t){
14665             return;
14666         }
14667         Roo.log(e);
14668         if (Roo.isTouch && e.type == 'touchstart' && t.menu  && !t.disabled) {
14669             if(t == this.activeItem && t.shouldDeactivate(e)){
14670                 this.activeItem.deactivate();
14671                 delete this.activeItem;
14672                 return;
14673             }
14674             if(t.canActivate){
14675                 this.setActiveItem(t, true);
14676             }
14677             return;
14678             
14679             
14680         }
14681         
14682         t.onClick(e);
14683         this.fireEvent("click", this, t, e);
14684     },
14685
14686     // private
14687     setActiveItem : function(item, autoExpand){
14688         if(item != this.activeItem){
14689             if(this.activeItem){
14690                 this.activeItem.deactivate();
14691             }
14692             this.activeItem = item;
14693             item.activate(autoExpand);
14694         }else if(autoExpand){
14695             item.expandMenu();
14696         }
14697     },
14698
14699     // private
14700     tryActivate : function(start, step){
14701         var items = this.items;
14702         for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
14703             var item = items.get(i);
14704             if(!item.disabled && item.canActivate){
14705                 this.setActiveItem(item, false);
14706                 return item;
14707             }
14708         }
14709         return false;
14710     },
14711
14712     // private
14713     onMouseOver : function(e){
14714         var t;
14715         if(t = this.findTargetItem(e)){
14716             if(t.canActivate && !t.disabled){
14717                 this.setActiveItem(t, true);
14718             }
14719         }
14720         this.fireEvent("mouseover", this, e, t);
14721     },
14722
14723     // private
14724     onMouseOut : function(e){
14725         var t;
14726         if(t = this.findTargetItem(e)){
14727             if(t == this.activeItem && t.shouldDeactivate(e)){
14728                 this.activeItem.deactivate();
14729                 delete this.activeItem;
14730             }
14731         }
14732         this.fireEvent("mouseout", this, e, t);
14733     },
14734
14735     /**
14736      * Read-only.  Returns true if the menu is currently displayed, else false.
14737      * @type Boolean
14738      */
14739     isVisible : function(){
14740         return this.el && !this.hidden;
14741     },
14742
14743     /**
14744      * Displays this menu relative to another element
14745      * @param {String/HTMLElement/Roo.Element} element The element to align to
14746      * @param {String} position (optional) The {@link Roo.Element#alignTo} anchor position to use in aligning to
14747      * the element (defaults to this.defaultAlign)
14748      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
14749      */
14750     show : function(el, pos, parentMenu){
14751         this.parentMenu = parentMenu;
14752         if(!this.el){
14753             this.render();
14754         }
14755         this.fireEvent("beforeshow", this);
14756         this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign), parentMenu, false);
14757     },
14758
14759     /**
14760      * Displays this menu at a specific xy position
14761      * @param {Array} xyPosition Contains X & Y [x, y] values for the position at which to show the menu (coordinates are page-based)
14762      * @param {Roo.menu.Menu} parentMenu (optional) This menu's parent menu, if applicable (defaults to undefined)
14763      */
14764     showAt : function(xy, parentMenu, /* private: */_e){
14765         this.parentMenu = parentMenu;
14766         if(!this.el){
14767             this.render();
14768         }
14769         if(_e !== false){
14770             this.fireEvent("beforeshow", this);
14771             xy = this.el.adjustForConstraints(xy);
14772         }
14773         this.el.setXY(xy);
14774         this.el.show();
14775         this.hidden = false;
14776         this.focus();
14777         this.fireEvent("show", this);
14778     },
14779
14780     focus : function(){
14781         if(!this.hidden){
14782             this.doFocus.defer(50, this);
14783         }
14784     },
14785
14786     doFocus : function(){
14787         if(!this.hidden){
14788             this.focusEl.focus();
14789         }
14790     },
14791
14792     /**
14793      * Hides this menu and optionally all parent menus
14794      * @param {Boolean} deep (optional) True to hide all parent menus recursively, if any (defaults to false)
14795      */
14796     hide : function(deep){
14797         if(this.el && this.isVisible()){
14798             this.fireEvent("beforehide", this);
14799             if(this.activeItem){
14800                 this.activeItem.deactivate();
14801                 this.activeItem = null;
14802             }
14803             this.el.hide();
14804             this.hidden = true;
14805             this.fireEvent("hide", this);
14806         }
14807         if(deep === true && this.parentMenu){
14808             this.parentMenu.hide(true);
14809         }
14810     },
14811
14812     /**
14813      * Addds one or more items of any type supported by the Menu class, or that can be converted into menu items.
14814      * Any of the following are valid:
14815      * <ul>
14816      * <li>Any menu item object based on {@link Roo.menu.Item}</li>
14817      * <li>An HTMLElement object which will be converted to a menu item</li>
14818      * <li>A menu item config object that will be created as a new menu item</li>
14819      * <li>A string, which can either be '-' or 'separator' to add a menu separator, otherwise
14820      * it will be converted into a {@link Roo.menu.TextItem} and added</li>
14821      * </ul>
14822      * Usage:
14823      * <pre><code>
14824 // Create the menu
14825 var menu = new Roo.menu.Menu();
14826
14827 // Create a menu item to add by reference
14828 var menuItem = new Roo.menu.Item({ text: 'New Item!' });
14829
14830 // Add a bunch of items at once using different methods.
14831 // Only the last item added will be returned.
14832 var item = menu.add(
14833     menuItem,                // add existing item by ref
14834     'Dynamic Item',          // new TextItem
14835     '-',                     // new separator
14836     { text: 'Config Item' }  // new item by config
14837 );
14838 </code></pre>
14839      * @param {Mixed} args One or more menu items, menu item configs or other objects that can be converted to menu items
14840      * @return {Roo.menu.Item} The menu item that was added, or the last one if multiple items were added
14841      */
14842     add : function(){
14843         var a = arguments, l = a.length, item;
14844         for(var i = 0; i < l; i++){
14845             var el = a[i];
14846             if ((typeof(el) == "object") && el.xtype && el.xns) {
14847                 el = Roo.factory(el, Roo.menu);
14848             }
14849             
14850             if(el.render){ // some kind of Item
14851                 item = this.addItem(el);
14852             }else if(typeof el == "string"){ // string
14853                 if(el == "separator" || el == "-"){
14854                     item = this.addSeparator();
14855                 }else{
14856                     item = this.addText(el);
14857                 }
14858             }else if(el.tagName || el.el){ // element
14859                 item = this.addElement(el);
14860             }else if(typeof el == "object"){ // must be menu item config?
14861                 item = this.addMenuItem(el);
14862             }
14863         }
14864         return item;
14865     },
14866
14867     /**
14868      * Returns this menu's underlying {@link Roo.Element} object
14869      * @return {Roo.Element} The element
14870      */
14871     getEl : function(){
14872         if(!this.el){
14873             this.render();
14874         }
14875         return this.el;
14876     },
14877
14878     /**
14879      * Adds a separator bar to the menu
14880      * @return {Roo.menu.Item} The menu item that was added
14881      */
14882     addSeparator : function(){
14883         return this.addItem(new Roo.menu.Separator());
14884     },
14885
14886     /**
14887      * Adds an {@link Roo.Element} object to the menu
14888      * @param {String/HTMLElement/Roo.Element} el The element or DOM node to add, or its id
14889      * @return {Roo.menu.Item} The menu item that was added
14890      */
14891     addElement : function(el){
14892         return this.addItem(new Roo.menu.BaseItem(el));
14893     },
14894
14895     /**
14896      * Adds an existing object based on {@link Roo.menu.Item} to the menu
14897      * @param {Roo.menu.Item} item The menu item to add
14898      * @return {Roo.menu.Item} The menu item that was added
14899      */
14900     addItem : function(item){
14901         this.items.add(item);
14902         if(this.ul){
14903             var li = document.createElement("li");
14904             li.className = "x-menu-list-item";
14905             this.ul.dom.appendChild(li);
14906             item.render(li, this);
14907             this.delayAutoWidth();
14908         }
14909         return item;
14910     },
14911
14912     /**
14913      * Creates a new {@link Roo.menu.Item} based an the supplied config object and adds it to the menu
14914      * @param {Object} config A MenuItem config object
14915      * @return {Roo.menu.Item} The menu item that was added
14916      */
14917     addMenuItem : function(config){
14918         if(!(config instanceof Roo.menu.Item)){
14919             if(typeof config.checked == "boolean"){ // must be check menu item config?
14920                 config = new Roo.menu.CheckItem(config);
14921             }else{
14922                 config = new Roo.menu.Item(config);
14923             }
14924         }
14925         return this.addItem(config);
14926     },
14927
14928     /**
14929      * Creates a new {@link Roo.menu.TextItem} with the supplied text and adds it to the menu
14930      * @param {String} text The text to display in the menu item
14931      * @return {Roo.menu.Item} The menu item that was added
14932      */
14933     addText : function(text){
14934         return this.addItem(new Roo.menu.TextItem({ text : text }));
14935     },
14936
14937     /**
14938      * Inserts an existing object based on {@link Roo.menu.Item} to the menu at a specified index
14939      * @param {Number} index The index in the menu's list of current items where the new item should be inserted
14940      * @param {Roo.menu.Item} item The menu item to add
14941      * @return {Roo.menu.Item} The menu item that was added
14942      */
14943     insert : function(index, item){
14944         this.items.insert(index, item);
14945         if(this.ul){
14946             var li = document.createElement("li");
14947             li.className = "x-menu-list-item";
14948             this.ul.dom.insertBefore(li, this.ul.dom.childNodes[index]);
14949             item.render(li, this);
14950             this.delayAutoWidth();
14951         }
14952         return item;
14953     },
14954
14955     /**
14956      * Removes an {@link Roo.menu.Item} from the menu and destroys the object
14957      * @param {Roo.menu.Item} item The menu item to remove
14958      */
14959     remove : function(item){
14960         this.items.removeKey(item.id);
14961         item.destroy();
14962     },
14963
14964     /**
14965      * Removes and destroys all items in the menu
14966      */
14967     removeAll : function(){
14968         var f;
14969         while(f = this.items.first()){
14970             this.remove(f);
14971         }
14972     }
14973 });
14974
14975 // MenuNav is a private utility class used internally by the Menu
14976 Roo.menu.MenuNav = function(menu){
14977     Roo.menu.MenuNav.superclass.constructor.call(this, menu.el);
14978     this.scope = this.menu = menu;
14979 };
14980
14981 Roo.extend(Roo.menu.MenuNav, Roo.KeyNav, {
14982     doRelay : function(e, h){
14983         var k = e.getKey();
14984         if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
14985             this.menu.tryActivate(0, 1);
14986             return false;
14987         }
14988         return h.call(this.scope || this, e, this.menu);
14989     },
14990
14991     up : function(e, m){
14992         if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
14993             m.tryActivate(m.items.length-1, -1);
14994         }
14995     },
14996
14997     down : function(e, m){
14998         if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
14999             m.tryActivate(0, 1);
15000         }
15001     },
15002
15003     right : function(e, m){
15004         if(m.activeItem){
15005             m.activeItem.expandMenu(true);
15006         }
15007     },
15008
15009     left : function(e, m){
15010         m.hide();
15011         if(m.parentMenu && m.parentMenu.activeItem){
15012             m.parentMenu.activeItem.activate();
15013         }
15014     },
15015
15016     enter : function(e, m){
15017         if(m.activeItem){
15018             e.stopPropagation();
15019             m.activeItem.onClick(e);
15020             m.fireEvent("click", this, m.activeItem);
15021             return true;
15022         }
15023     }
15024 });/*
15025  * Based on:
15026  * Ext JS Library 1.1.1
15027  * Copyright(c) 2006-2007, Ext JS, LLC.
15028  *
15029  * Originally Released Under LGPL - original licence link has changed is not relivant.
15030  *
15031  * Fork - LGPL
15032  * <script type="text/javascript">
15033  */
15034  
15035 /**
15036  * @class Roo.menu.MenuMgr
15037  * Provides a common registry of all menu items on a page so that they can be easily accessed by id.
15038  * @static
15039  */
15040 Roo.menu.MenuMgr = function(){
15041    var menus, active, groups = {}, attached = false, lastShow = new Date();
15042
15043    // private - called when first menu is created
15044    function init(){
15045        menus = {};
15046        active = new Roo.util.MixedCollection();
15047        Roo.get(document).addKeyListener(27, function(){
15048            if(active.length > 0){
15049                hideAll();
15050            }
15051        });
15052    }
15053
15054    // private
15055    function hideAll(){
15056        if(active && active.length > 0){
15057            var c = active.clone();
15058            c.each(function(m){
15059                m.hide();
15060            });
15061        }
15062    }
15063
15064    // private
15065    function onHide(m){
15066        active.remove(m);
15067        if(active.length < 1){
15068            Roo.get(document).un("mousedown", onMouseDown);
15069            attached = false;
15070        }
15071    }
15072
15073    // private
15074    function onShow(m){
15075        var last = active.last();
15076        lastShow = new Date();
15077        active.add(m);
15078        if(!attached){
15079            Roo.get(document).on("mousedown", onMouseDown);
15080            attached = true;
15081        }
15082        if(m.parentMenu){
15083           m.getEl().setZIndex(parseInt(m.parentMenu.getEl().getStyle("z-index"), 10) + 3);
15084           m.parentMenu.activeChild = m;
15085        }else if(last && last.isVisible()){
15086           m.getEl().setZIndex(parseInt(last.getEl().getStyle("z-index"), 10) + 3);
15087        }
15088    }
15089
15090    // private
15091    function onBeforeHide(m){
15092        if(m.activeChild){
15093            m.activeChild.hide();
15094        }
15095        if(m.autoHideTimer){
15096            clearTimeout(m.autoHideTimer);
15097            delete m.autoHideTimer;
15098        }
15099    }
15100
15101    // private
15102    function onBeforeShow(m){
15103        var pm = m.parentMenu;
15104        if(!pm && !m.allowOtherMenus){
15105            hideAll();
15106        }else if(pm && pm.activeChild && active != m){
15107            pm.activeChild.hide();
15108        }
15109    }
15110
15111    // private
15112    function onMouseDown(e){
15113        if(lastShow.getElapsed() > 50 && active.length > 0 && !e.getTarget(".x-menu")){
15114            hideAll();
15115        }
15116    }
15117
15118    // private
15119    function onBeforeCheck(mi, state){
15120        if(state){
15121            var g = groups[mi.group];
15122            for(var i = 0, l = g.length; i < l; i++){
15123                if(g[i] != mi){
15124                    g[i].setChecked(false);
15125                }
15126            }
15127        }
15128    }
15129
15130    return {
15131
15132        /**
15133         * Hides all menus that are currently visible
15134         */
15135        hideAll : function(){
15136             hideAll();  
15137        },
15138
15139        // private
15140        register : function(menu){
15141            if(!menus){
15142                init();
15143            }
15144            menus[menu.id] = menu;
15145            menu.on("beforehide", onBeforeHide);
15146            menu.on("hide", onHide);
15147            menu.on("beforeshow", onBeforeShow);
15148            menu.on("show", onShow);
15149            var g = menu.group;
15150            if(g && menu.events["checkchange"]){
15151                if(!groups[g]){
15152                    groups[g] = [];
15153                }
15154                groups[g].push(menu);
15155                menu.on("checkchange", onCheck);
15156            }
15157        },
15158
15159         /**
15160          * Returns a {@link Roo.menu.Menu} object
15161          * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
15162          * be used to generate and return a new Menu instance.
15163          */
15164        get : function(menu){
15165            if(typeof menu == "string"){ // menu id
15166                return menus[menu];
15167            }else if(menu.events){  // menu instance
15168                return menu;
15169            }else if(typeof menu.length == 'number'){ // array of menu items?
15170                return new Roo.menu.Menu({items:menu});
15171            }else{ // otherwise, must be a config
15172                return new Roo.menu.Menu(menu);
15173            }
15174        },
15175
15176        // private
15177        unregister : function(menu){
15178            delete menus[menu.id];
15179            menu.un("beforehide", onBeforeHide);
15180            menu.un("hide", onHide);
15181            menu.un("beforeshow", onBeforeShow);
15182            menu.un("show", onShow);
15183            var g = menu.group;
15184            if(g && menu.events["checkchange"]){
15185                groups[g].remove(menu);
15186                menu.un("checkchange", onCheck);
15187            }
15188        },
15189
15190        // private
15191        registerCheckable : function(menuItem){
15192            var g = menuItem.group;
15193            if(g){
15194                if(!groups[g]){
15195                    groups[g] = [];
15196                }
15197                groups[g].push(menuItem);
15198                menuItem.on("beforecheckchange", onBeforeCheck);
15199            }
15200        },
15201
15202        // private
15203        unregisterCheckable : function(menuItem){
15204            var g = menuItem.group;
15205            if(g){
15206                groups[g].remove(menuItem);
15207                menuItem.un("beforecheckchange", onBeforeCheck);
15208            }
15209        }
15210    };
15211 }();/*
15212  * Based on:
15213  * Ext JS Library 1.1.1
15214  * Copyright(c) 2006-2007, Ext JS, LLC.
15215  *
15216  * Originally Released Under LGPL - original licence link has changed is not relivant.
15217  *
15218  * Fork - LGPL
15219  * <script type="text/javascript">
15220  */
15221  
15222
15223 /**
15224  * @class Roo.menu.BaseItem
15225  * @extends Roo.Component
15226  * @abstract
15227  * The base class for all items that render into menus.  BaseItem provides default rendering, activated state
15228  * management and base configuration options shared by all menu components.
15229  * @constructor
15230  * Creates a new BaseItem
15231  * @param {Object} config Configuration options
15232  */
15233 Roo.menu.BaseItem = function(config){
15234     Roo.menu.BaseItem.superclass.constructor.call(this, config);
15235
15236     this.addEvents({
15237         /**
15238          * @event click
15239          * Fires when this item is clicked
15240          * @param {Roo.menu.BaseItem} this
15241          * @param {Roo.EventObject} e
15242          */
15243         click: true,
15244         /**
15245          * @event activate
15246          * Fires when this item is activated
15247          * @param {Roo.menu.BaseItem} this
15248          */
15249         activate : true,
15250         /**
15251          * @event deactivate
15252          * Fires when this item is deactivated
15253          * @param {Roo.menu.BaseItem} this
15254          */
15255         deactivate : true
15256     });
15257
15258     if(this.handler){
15259         this.on("click", this.handler, this.scope, true);
15260     }
15261 };
15262
15263 Roo.extend(Roo.menu.BaseItem, Roo.Component, {
15264     /**
15265      * @cfg {Function} handler
15266      * A function that will handle the click event of this menu item (defaults to undefined)
15267      */
15268     /**
15269      * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to false)
15270      */
15271     canActivate : false,
15272     
15273      /**
15274      * @cfg {Boolean} hidden True to prevent creation of this menu item (defaults to false)
15275      */
15276     hidden: false,
15277     
15278     /**
15279      * @cfg {String} activeClass The CSS class to use when the item becomes activated (defaults to "x-menu-item-active")
15280      */
15281     activeClass : "x-menu-item-active",
15282     /**
15283      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to true)
15284      */
15285     hideOnClick : true,
15286     /**
15287      * @cfg {Number} hideDelay Length of time in milliseconds to wait before hiding after a click (defaults to 100)
15288      */
15289     hideDelay : 100,
15290
15291     // private
15292     ctype: "Roo.menu.BaseItem",
15293
15294     // private
15295     actionMode : "container",
15296
15297     // private
15298     render : function(container, parentMenu){
15299         this.parentMenu = parentMenu;
15300         Roo.menu.BaseItem.superclass.render.call(this, container);
15301         this.container.menuItemId = this.id;
15302     },
15303
15304     // private
15305     onRender : function(container, position){
15306         this.el = Roo.get(this.el);
15307         container.dom.appendChild(this.el.dom);
15308     },
15309
15310     // private
15311     onClick : function(e){
15312         if(!this.disabled && this.fireEvent("click", this, e) !== false
15313                 && this.parentMenu.fireEvent("itemclick", this, e) !== false){
15314             this.handleClick(e);
15315         }else{
15316             e.stopEvent();
15317         }
15318     },
15319
15320     // private
15321     activate : function(){
15322         if(this.disabled){
15323             return false;
15324         }
15325         var li = this.container;
15326         li.addClass(this.activeClass);
15327         this.region = li.getRegion().adjust(2, 2, -2, -2);
15328         this.fireEvent("activate", this);
15329         return true;
15330     },
15331
15332     // private
15333     deactivate : function(){
15334         this.container.removeClass(this.activeClass);
15335         this.fireEvent("deactivate", this);
15336     },
15337
15338     // private
15339     shouldDeactivate : function(e){
15340         return !this.region || !this.region.contains(e.getPoint());
15341     },
15342
15343     // private
15344     handleClick : function(e){
15345         if(this.hideOnClick){
15346             this.parentMenu.hide.defer(this.hideDelay, this.parentMenu, [true]);
15347         }
15348     },
15349
15350     // private
15351     expandMenu : function(autoActivate){
15352         // do nothing
15353     },
15354
15355     // private
15356     hideMenu : function(){
15357         // do nothing
15358     }
15359 });/*
15360  * Based on:
15361  * Ext JS Library 1.1.1
15362  * Copyright(c) 2006-2007, Ext JS, LLC.
15363  *
15364  * Originally Released Under LGPL - original licence link has changed is not relivant.
15365  *
15366  * Fork - LGPL
15367  * <script type="text/javascript">
15368  */
15369  
15370 /**
15371  * @class Roo.menu.Adapter
15372  * @extends Roo.menu.BaseItem
15373  * @abstract
15374  * A base utility class that adapts a non-menu component so that it can be wrapped by a menu item and added to a menu.
15375  * It provides basic rendering, activation management and enable/disable logic required to work in menus.
15376  * @constructor
15377  * Creates a new Adapter
15378  * @param {Object} config Configuration options
15379  */
15380 Roo.menu.Adapter = function(component, config){
15381     Roo.menu.Adapter.superclass.constructor.call(this, config);
15382     this.component = component;
15383 };
15384 Roo.extend(Roo.menu.Adapter, Roo.menu.BaseItem, {
15385     // private
15386     canActivate : true,
15387
15388     // private
15389     onRender : function(container, position){
15390         this.component.render(container);
15391         this.el = this.component.getEl();
15392     },
15393
15394     // private
15395     activate : function(){
15396         if(this.disabled){
15397             return false;
15398         }
15399         this.component.focus();
15400         this.fireEvent("activate", this);
15401         return true;
15402     },
15403
15404     // private
15405     deactivate : function(){
15406         this.fireEvent("deactivate", this);
15407     },
15408
15409     // private
15410     disable : function(){
15411         this.component.disable();
15412         Roo.menu.Adapter.superclass.disable.call(this);
15413     },
15414
15415     // private
15416     enable : function(){
15417         this.component.enable();
15418         Roo.menu.Adapter.superclass.enable.call(this);
15419     }
15420 });/*
15421  * Based on:
15422  * Ext JS Library 1.1.1
15423  * Copyright(c) 2006-2007, Ext JS, LLC.
15424  *
15425  * Originally Released Under LGPL - original licence link has changed is not relivant.
15426  *
15427  * Fork - LGPL
15428  * <script type="text/javascript">
15429  */
15430
15431 /**
15432  * @class Roo.menu.TextItem
15433  * @extends Roo.menu.BaseItem
15434  * Adds a static text string to a menu, usually used as either a heading or group separator.
15435  * Note: old style constructor with text is still supported.
15436  * 
15437  * @constructor
15438  * Creates a new TextItem
15439  * @param {Object} cfg Configuration
15440  */
15441 Roo.menu.TextItem = function(cfg){
15442     if (typeof(cfg) == 'string') {
15443         this.text = cfg;
15444     } else {
15445         Roo.apply(this,cfg);
15446     }
15447     
15448     Roo.menu.TextItem.superclass.constructor.call(this);
15449 };
15450
15451 Roo.extend(Roo.menu.TextItem, Roo.menu.BaseItem, {
15452     /**
15453      * @cfg {String} text Text to show on item.
15454      */
15455     text : '',
15456     
15457     /**
15458      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
15459      */
15460     hideOnClick : false,
15461     /**
15462      * @cfg {String} itemCls The default CSS class to use for text items (defaults to "x-menu-text")
15463      */
15464     itemCls : "x-menu-text",
15465
15466     // private
15467     onRender : function(){
15468         var s = document.createElement("span");
15469         s.className = this.itemCls;
15470         s.innerHTML = this.text;
15471         this.el = s;
15472         Roo.menu.TextItem.superclass.onRender.apply(this, arguments);
15473     }
15474 });/*
15475  * Based on:
15476  * Ext JS Library 1.1.1
15477  * Copyright(c) 2006-2007, Ext JS, LLC.
15478  *
15479  * Originally Released Under LGPL - original licence link has changed is not relivant.
15480  *
15481  * Fork - LGPL
15482  * <script type="text/javascript">
15483  */
15484
15485 /**
15486  * @class Roo.menu.Separator
15487  * @extends Roo.menu.BaseItem
15488  * Adds a separator bar to a menu, used to divide logical groups of menu items. Generally you will
15489  * add one of these by using "-" in you call to add() or in your items config rather than creating one directly.
15490  * @constructor
15491  * @param {Object} config Configuration options
15492  */
15493 Roo.menu.Separator = function(config){
15494     Roo.menu.Separator.superclass.constructor.call(this, config);
15495 };
15496
15497 Roo.extend(Roo.menu.Separator, Roo.menu.BaseItem, {
15498     /**
15499      * @cfg {String} itemCls The default CSS class to use for separators (defaults to "x-menu-sep")
15500      */
15501     itemCls : "x-menu-sep",
15502     /**
15503      * @cfg {Boolean} hideOnClick True to hide the containing menu after this item is clicked (defaults to false)
15504      */
15505     hideOnClick : false,
15506
15507     // private
15508     onRender : function(li){
15509         var s = document.createElement("span");
15510         s.className = this.itemCls;
15511         s.innerHTML = "&#160;";
15512         this.el = s;
15513         li.addClass("x-menu-sep-li");
15514         Roo.menu.Separator.superclass.onRender.apply(this, arguments);
15515     }
15516 });/*
15517  * Based on:
15518  * Ext JS Library 1.1.1
15519  * Copyright(c) 2006-2007, Ext JS, LLC.
15520  *
15521  * Originally Released Under LGPL - original licence link has changed is not relivant.
15522  *
15523  * Fork - LGPL
15524  * <script type="text/javascript">
15525  */
15526 /**
15527  * @class Roo.menu.Item
15528  * @extends Roo.menu.BaseItem
15529  * A base class for all menu items that require menu-related functionality (like sub-menus) and are not static
15530  * display items.  Item extends the base functionality of {@link Roo.menu.BaseItem} by adding menu-specific
15531  * activation and click handling.
15532  * @constructor
15533  * Creates a new Item
15534  * @param {Object} config Configuration options
15535  */
15536 Roo.menu.Item = function(config){
15537     Roo.menu.Item.superclass.constructor.call(this, config);
15538     if(this.menu){
15539         this.menu = Roo.menu.MenuMgr.get(this.menu);
15540     }
15541 };
15542 Roo.extend(Roo.menu.Item, Roo.menu.BaseItem, {
15543     /**
15544      * @cfg {Roo.menu.Menu} menu
15545      * A Sub menu
15546      */
15547     /**
15548      * @cfg {String} text
15549      * The text to show on the menu item.
15550      */
15551     text: '',
15552      /**
15553      * @cfg {String} html to render in menu
15554      * The text to show on the menu item (HTML version).
15555      */
15556     html: '',
15557     /**
15558      * @cfg {String} icon
15559      * The path to an icon to display in this menu item (defaults to Roo.BLANK_IMAGE_URL)
15560      */
15561     icon: undefined,
15562     /**
15563      * @cfg {String} itemCls The default CSS class to use for menu items (defaults to "x-menu-item")
15564      */
15565     itemCls : "x-menu-item",
15566     /**
15567      * @cfg {Boolean} canActivate True if this item can be visually activated (defaults to true)
15568      */
15569     canActivate : true,
15570     /**
15571      * @cfg {Number} showDelay Length of time in milliseconds to wait before showing this item (defaults to 200)
15572      */
15573     showDelay: 200,
15574     // doc'd in BaseItem
15575     hideDelay: 200,
15576
15577     // private
15578     ctype: "Roo.menu.Item",
15579     
15580     // private
15581     onRender : function(container, position){
15582         var el = document.createElement("a");
15583         el.hideFocus = true;
15584         el.unselectable = "on";
15585         el.href = this.href || "#";
15586         if(this.hrefTarget){
15587             el.target = this.hrefTarget;
15588         }
15589         el.className = this.itemCls + (this.menu ?  " x-menu-item-arrow" : "") + (this.cls ?  " " + this.cls : "");
15590         
15591         var html = this.html.length ? this.html  : String.format('{0}',this.text);
15592         
15593         el.innerHTML = String.format(
15594                 '<img src="{0}" class="x-menu-item-icon {1}" />' + html,
15595                 this.icon || Roo.BLANK_IMAGE_URL, this.iconCls || '');
15596         this.el = el;
15597         Roo.menu.Item.superclass.onRender.call(this, container, position);
15598     },
15599
15600     /**
15601      * Sets the text to display in this menu item
15602      * @param {String} text The text to display
15603      * @param {Boolean} isHTML true to indicate text is pure html.
15604      */
15605     setText : function(text, isHTML){
15606         if (isHTML) {
15607             this.html = text;
15608         } else {
15609             this.text = text;
15610             this.html = '';
15611         }
15612         if(this.rendered){
15613             var html = this.html.length ? this.html  : String.format('{0}',this.text);
15614      
15615             this.el.update(String.format(
15616                 '<img src="{0}" class="x-menu-item-icon {2}">' + html,
15617                 this.icon || Roo.BLANK_IMAGE_URL, this.text, this.iconCls || ''));
15618             this.parentMenu.autoWidth();
15619         }
15620     },
15621
15622     // private
15623     handleClick : function(e){
15624         if(!this.href){ // if no link defined, stop the event automatically
15625             e.stopEvent();
15626         }
15627         Roo.menu.Item.superclass.handleClick.apply(this, arguments);
15628     },
15629
15630     // private
15631     activate : function(autoExpand){
15632         if(Roo.menu.Item.superclass.activate.apply(this, arguments)){
15633             this.focus();
15634             if(autoExpand){
15635                 this.expandMenu();
15636             }
15637         }
15638         return true;
15639     },
15640
15641     // private
15642     shouldDeactivate : function(e){
15643         if(Roo.menu.Item.superclass.shouldDeactivate.call(this, e)){
15644             if(this.menu && this.menu.isVisible()){
15645                 return !this.menu.getEl().getRegion().contains(e.getPoint());
15646             }
15647             return true;
15648         }
15649         return false;
15650     },
15651
15652     // private
15653     deactivate : function(){
15654         Roo.menu.Item.superclass.deactivate.apply(this, arguments);
15655         this.hideMenu();
15656     },
15657
15658     // private
15659     expandMenu : function(autoActivate){
15660         if(!this.disabled && this.menu){
15661             clearTimeout(this.hideTimer);
15662             delete this.hideTimer;
15663             if(!this.menu.isVisible() && !this.showTimer){
15664                 this.showTimer = this.deferExpand.defer(this.showDelay, this, [autoActivate]);
15665             }else if (this.menu.isVisible() && autoActivate){
15666                 this.menu.tryActivate(0, 1);
15667             }
15668         }
15669     },
15670
15671     // private
15672     deferExpand : function(autoActivate){
15673         delete this.showTimer;
15674         this.menu.show(this.container, this.parentMenu.subMenuAlign || "tl-tr?", this.parentMenu);
15675         if(autoActivate){
15676             this.menu.tryActivate(0, 1);
15677         }
15678     },
15679
15680     // private
15681     hideMenu : function(){
15682         clearTimeout(this.showTimer);
15683         delete this.showTimer;
15684         if(!this.hideTimer && this.menu && this.menu.isVisible()){
15685             this.hideTimer = this.deferHide.defer(this.hideDelay, this);
15686         }
15687     },
15688
15689     // private
15690     deferHide : function(){
15691         delete this.hideTimer;
15692         this.menu.hide();
15693     }
15694 });/*
15695  * Based on:
15696  * Ext JS Library 1.1.1
15697  * Copyright(c) 2006-2007, Ext JS, LLC.
15698  *
15699  * Originally Released Under LGPL - original licence link has changed is not relivant.
15700  *
15701  * Fork - LGPL
15702  * <script type="text/javascript">
15703  */
15704  
15705 /**
15706  * @class Roo.menu.CheckItem
15707  * @extends Roo.menu.Item
15708  * Adds a menu item that contains a checkbox by default, but can also be part of a radio group.
15709  * @constructor
15710  * Creates a new CheckItem
15711  * @param {Object} config Configuration options
15712  */
15713 Roo.menu.CheckItem = function(config){
15714     Roo.menu.CheckItem.superclass.constructor.call(this, config);
15715     this.addEvents({
15716         /**
15717          * @event beforecheckchange
15718          * Fires before the checked value is set, providing an opportunity to cancel if needed
15719          * @param {Roo.menu.CheckItem} this
15720          * @param {Boolean} checked The new checked value that will be set
15721          */
15722         "beforecheckchange" : true,
15723         /**
15724          * @event checkchange
15725          * Fires after the checked value has been set
15726          * @param {Roo.menu.CheckItem} this
15727          * @param {Boolean} checked The checked value that was set
15728          */
15729         "checkchange" : true
15730     });
15731     if(this.checkHandler){
15732         this.on('checkchange', this.checkHandler, this.scope);
15733     }
15734 };
15735 Roo.extend(Roo.menu.CheckItem, Roo.menu.Item, {
15736     /**
15737      * @cfg {String} group
15738      * All check items with the same group name will automatically be grouped into a single-select
15739      * radio button group (defaults to '')
15740      */
15741     /**
15742      * @cfg {String} itemCls The default CSS class to use for check items (defaults to "x-menu-item x-menu-check-item")
15743      */
15744     itemCls : "x-menu-item x-menu-check-item",
15745     /**
15746      * @cfg {String} groupClass The default CSS class to use for radio group check items (defaults to "x-menu-group-item")
15747      */
15748     groupClass : "x-menu-group-item",
15749
15750     /**
15751      * @cfg {Boolean} checked True to initialize this checkbox as checked (defaults to false).  Note that
15752      * if this checkbox is part of a radio group (group = true) only the last item in the group that is
15753      * initialized with checked = true will be rendered as checked.
15754      */
15755     checked: false,
15756
15757     // private
15758     ctype: "Roo.menu.CheckItem",
15759
15760     // private
15761     onRender : function(c){
15762         Roo.menu.CheckItem.superclass.onRender.apply(this, arguments);
15763         if(this.group){
15764             this.el.addClass(this.groupClass);
15765         }
15766         Roo.menu.MenuMgr.registerCheckable(this);
15767         if(this.checked){
15768             this.checked = false;
15769             this.setChecked(true, true);
15770         }
15771     },
15772
15773     // private
15774     destroy : function(){
15775         if(this.rendered){
15776             Roo.menu.MenuMgr.unregisterCheckable(this);
15777         }
15778         Roo.menu.CheckItem.superclass.destroy.apply(this, arguments);
15779     },
15780
15781     /**
15782      * Set the checked state of this item
15783      * @param {Boolean} checked The new checked value
15784      * @param {Boolean} suppressEvent (optional) True to prevent the checkchange event from firing (defaults to false)
15785      */
15786     setChecked : function(state, suppressEvent){
15787         if(this.checked != state && this.fireEvent("beforecheckchange", this, state) !== false){
15788             if(this.container){
15789                 this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
15790             }
15791             this.checked = state;
15792             if(suppressEvent !== true){
15793                 this.fireEvent("checkchange", this, state);
15794             }
15795         }
15796     },
15797
15798     // private
15799     handleClick : function(e){
15800        if(!this.disabled && !(this.checked && this.group)){// disable unselect on radio item
15801            this.setChecked(!this.checked);
15802        }
15803        Roo.menu.CheckItem.superclass.handleClick.apply(this, arguments);
15804     }
15805 });/*
15806  * Based on:
15807  * Ext JS Library 1.1.1
15808  * Copyright(c) 2006-2007, Ext JS, LLC.
15809  *
15810  * Originally Released Under LGPL - original licence link has changed is not relivant.
15811  *
15812  * Fork - LGPL
15813  * <script type="text/javascript">
15814  */
15815  
15816 /**
15817  * @class Roo.menu.DateItem
15818  * @extends Roo.menu.Adapter
15819  * A menu item that wraps the {@link Roo.DatPicker} component.
15820  * @constructor
15821  * Creates a new DateItem
15822  * @param {Object} config Configuration options
15823  */
15824 Roo.menu.DateItem = function(config){
15825     Roo.menu.DateItem.superclass.constructor.call(this, new Roo.DatePicker(config), config);
15826     /** The Roo.DatePicker object @type Roo.DatePicker */
15827     this.picker = this.component;
15828     this.addEvents({select: true});
15829     
15830     this.picker.on("render", function(picker){
15831         picker.getEl().swallowEvent("click");
15832         picker.container.addClass("x-menu-date-item");
15833     });
15834
15835     this.picker.on("select", this.onSelect, this);
15836 };
15837
15838 Roo.extend(Roo.menu.DateItem, Roo.menu.Adapter, {
15839     // private
15840     onSelect : function(picker, date){
15841         this.fireEvent("select", this, date, picker);
15842         Roo.menu.DateItem.superclass.handleClick.call(this);
15843     }
15844 });/*
15845  * Based on:
15846  * Ext JS Library 1.1.1
15847  * Copyright(c) 2006-2007, Ext JS, LLC.
15848  *
15849  * Originally Released Under LGPL - original licence link has changed is not relivant.
15850  *
15851  * Fork - LGPL
15852  * <script type="text/javascript">
15853  */
15854  
15855 /**
15856  * @class Roo.menu.ColorItem
15857  * @extends Roo.menu.Adapter
15858  * A menu item that wraps the {@link Roo.ColorPalette} component.
15859  * @constructor
15860  * Creates a new ColorItem
15861  * @param {Object} config Configuration options
15862  */
15863 Roo.menu.ColorItem = function(config){
15864     Roo.menu.ColorItem.superclass.constructor.call(this, new Roo.ColorPalette(config), config);
15865     /** The Roo.ColorPalette object @type Roo.ColorPalette */
15866     this.palette = this.component;
15867     this.relayEvents(this.palette, ["select"]);
15868     if(this.selectHandler){
15869         this.on('select', this.selectHandler, this.scope);
15870     }
15871 };
15872 Roo.extend(Roo.menu.ColorItem, Roo.menu.Adapter);/*
15873  * Based on:
15874  * Ext JS Library 1.1.1
15875  * Copyright(c) 2006-2007, Ext JS, LLC.
15876  *
15877  * Originally Released Under LGPL - original licence link has changed is not relivant.
15878  *
15879  * Fork - LGPL
15880  * <script type="text/javascript">
15881  */
15882  
15883
15884 /**
15885  * @class Roo.menu.DateMenu
15886  * @extends Roo.menu.Menu
15887  * A menu containing a {@link Roo.menu.DateItem} component (which provides a date picker).
15888  * @constructor
15889  * Creates a new DateMenu
15890  * @param {Object} config Configuration options
15891  */
15892 Roo.menu.DateMenu = function(config){
15893     Roo.menu.DateMenu.superclass.constructor.call(this, config);
15894     this.plain = true;
15895     var di = new Roo.menu.DateItem(config);
15896     this.add(di);
15897     /**
15898      * The {@link Roo.DatePicker} instance for this DateMenu
15899      * @type DatePicker
15900      */
15901     this.picker = di.picker;
15902     /**
15903      * @event select
15904      * @param {DatePicker} picker
15905      * @param {Date} date
15906      */
15907     this.relayEvents(di, ["select"]);
15908     this.on('beforeshow', function(){
15909         if(this.picker){
15910             this.picker.hideMonthPicker(false);
15911         }
15912     }, this);
15913 };
15914 Roo.extend(Roo.menu.DateMenu, Roo.menu.Menu, {
15915     cls:'x-date-menu'
15916 });/*
15917  * Based on:
15918  * Ext JS Library 1.1.1
15919  * Copyright(c) 2006-2007, Ext JS, LLC.
15920  *
15921  * Originally Released Under LGPL - original licence link has changed is not relivant.
15922  *
15923  * Fork - LGPL
15924  * <script type="text/javascript">
15925  */
15926  
15927
15928 /**
15929  * @class Roo.menu.ColorMenu
15930  * @extends Roo.menu.Menu
15931  * A menu containing a {@link Roo.menu.ColorItem} component (which provides a basic color picker).
15932  * @constructor
15933  * Creates a new ColorMenu
15934  * @param {Object} config Configuration options
15935  */
15936 Roo.menu.ColorMenu = function(config){
15937     Roo.menu.ColorMenu.superclass.constructor.call(this, config);
15938     this.plain = true;
15939     var ci = new Roo.menu.ColorItem(config);
15940     this.add(ci);
15941     /**
15942      * The {@link Roo.ColorPalette} instance for this ColorMenu
15943      * @type ColorPalette
15944      */
15945     this.palette = ci.palette;
15946     /**
15947      * @event select
15948      * @param {ColorPalette} palette
15949      * @param {String} color
15950      */
15951     this.relayEvents(ci, ["select"]);
15952 };
15953 Roo.extend(Roo.menu.ColorMenu, Roo.menu.Menu);/*
15954  * Based on:
15955  * Ext JS Library 1.1.1
15956  * Copyright(c) 2006-2007, Ext JS, LLC.
15957  *
15958  * Originally Released Under LGPL - original licence link has changed is not relivant.
15959  *
15960  * Fork - LGPL
15961  * <script type="text/javascript">
15962  */
15963  
15964 /**
15965  * @class Roo.form.TextItem
15966  * @extends Roo.BoxComponent
15967  * Base class for form fields that provides default event handling, sizing, value handling and other functionality.
15968  * @constructor
15969  * Creates a new TextItem
15970  * @param {Object} config Configuration options
15971  */
15972 Roo.form.TextItem = function(config){
15973     Roo.form.TextItem.superclass.constructor.call(this, config);
15974 };
15975
15976 Roo.extend(Roo.form.TextItem, Roo.BoxComponent,  {
15977     
15978     /**
15979      * @cfg {String} tag the tag for this item (default div)
15980      */
15981     tag : 'div',
15982     /**
15983      * @cfg {String} html the content for this item
15984      */
15985     html : '',
15986     
15987     getAutoCreate : function()
15988     {
15989         var cfg = {
15990             id: this.id,
15991             tag: this.tag,
15992             html: this.html,
15993             cls: 'x-form-item'
15994         };
15995         
15996         return cfg;
15997         
15998     },
15999     
16000     onRender : function(ct, position)
16001     {
16002         Roo.form.TextItem.superclass.onRender.call(this, ct, position);
16003         
16004         if(!this.el){
16005             var cfg = this.getAutoCreate();
16006             if(!cfg.name){
16007                 cfg.name = typeof(this.name) == 'undefined' ? this.id : this.name;
16008             }
16009             if (!cfg.name.length) {
16010                 delete cfg.name;
16011             }
16012             this.el = ct.createChild(cfg, position);
16013         }
16014     },
16015     /*
16016      * setHTML
16017      * @param {String} html update the Contents of the element.
16018      */
16019     setHTML : function(html)
16020     {
16021         this.fieldEl.dom.innerHTML = html;
16022     }
16023     
16024 });/*
16025  * Based on:
16026  * Ext JS Library 1.1.1
16027  * Copyright(c) 2006-2007, Ext JS, LLC.
16028  *
16029  * Originally Released Under LGPL - original licence link has changed is not relivant.
16030  *
16031  * Fork - LGPL
16032  * <script type="text/javascript">
16033  */
16034  
16035 /**
16036  * @class Roo.form.Field
16037  * @extends Roo.BoxComponent
16038  * Base class for form fields that provides default event handling, sizing, value handling and other functionality.
16039  * @constructor
16040  * Creates a new Field
16041  * @param {Object} config Configuration options
16042  */
16043 Roo.form.Field = function(config){
16044     Roo.form.Field.superclass.constructor.call(this, config);
16045 };
16046
16047 Roo.extend(Roo.form.Field, Roo.BoxComponent,  {
16048     /**
16049      * @cfg {String} fieldLabel Label to use when rendering a form.
16050      */
16051        /**
16052      * @cfg {String} qtip Mouse over tip
16053      */
16054      
16055     /**
16056      * @cfg {String} invalidClass The CSS class to use when marking a field invalid (defaults to "x-form-invalid")
16057      */
16058     invalidClass : "x-form-invalid",
16059     /**
16060      * @cfg {String} invalidText The error text to use when marking a field invalid and no message is provided (defaults to "The value in this field is invalid")
16061      */
16062     invalidText : "The value in this field is invalid",
16063     /**
16064      * @cfg {String} focusClass The CSS class to use when the field receives focus (defaults to "x-form-focus")
16065      */
16066     focusClass : "x-form-focus",
16067     /**
16068      * @cfg {String/Boolean} validationEvent The event that should initiate field validation. Set to false to disable
16069       automatic validation (defaults to "keyup").
16070      */
16071     validationEvent : "keyup",
16072     /**
16073      * @cfg {Boolean} validateOnBlur Whether the field should validate when it loses focus (defaults to true).
16074      */
16075     validateOnBlur : true,
16076     /**
16077      * @cfg {Number} validationDelay The length of time in milliseconds after user input begins until validation is initiated (defaults to 250)
16078      */
16079     validationDelay : 250,
16080     /**
16081      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
16082      * {tag: "input", type: "text", size: "20", autocomplete: "off"})
16083      */
16084     defaultAutoCreate : {tag: "input", type: "text", size: "20", autocomplete: "new-password"},
16085     /**
16086      * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field")
16087      */
16088     fieldClass : "x-form-field",
16089     /**
16090      * @cfg {String} msgTarget The location where error text should display.  Should be one of the following values (defaults to 'qtip'):
16091      *<pre>
16092 Value         Description
16093 -----------   ----------------------------------------------------------------------
16094 qtip          Display a quick tip when the user hovers over the field
16095 title         Display a default browser title attribute popup
16096 under         Add a block div beneath the field containing the error text
16097 side          Add an error icon to the right of the field with a popup on hover
16098 [element id]  Add the error text directly to the innerHTML of the specified element
16099 </pre>
16100      */
16101     msgTarget : 'qtip',
16102     /**
16103      * @cfg {String} msgFx <b>Experimental</b> The effect used when displaying a validation message under the field (defaults to 'normal').
16104      */
16105     msgFx : 'normal',
16106
16107     /**
16108      * @cfg {Boolean} readOnly True to mark the field as readOnly in HTML (defaults to false) -- Note: this only sets the element's readOnly DOM attribute.
16109      */
16110     readOnly : false,
16111
16112     /**
16113      * @cfg {Boolean} disabled True to disable the field (defaults to false).
16114      */
16115     disabled : false,
16116
16117     /**
16118      * @cfg {String} inputType The type attribute for input fields -- e.g. radio, text, password (defaults to "text").
16119      */
16120     inputType : undefined,
16121     
16122     /**
16123      * @cfg {Number} tabIndex The tabIndex for this field. Note this only applies to fields that are rendered, not those which are built via applyTo (defaults to undefined).
16124          */
16125         tabIndex : undefined,
16126         
16127     // private
16128     isFormField : true,
16129
16130     // private
16131     hasFocus : false,
16132     /**
16133      * @property {Roo.Element} fieldEl
16134      * Element Containing the rendered Field (with label etc.)
16135      */
16136     /**
16137      * @cfg {Mixed} value A value to initialize this field with.
16138      */
16139     value : undefined,
16140
16141     /**
16142      * @cfg {String} name The field's HTML name attribute.
16143      */
16144     /**
16145      * @cfg {String} cls A CSS class to apply to the field's underlying element.
16146      */
16147     // private
16148     loadedValue : false,
16149      
16150      
16151         // private ??
16152         initComponent : function(){
16153         Roo.form.Field.superclass.initComponent.call(this);
16154         this.addEvents({
16155             /**
16156              * @event focus
16157              * Fires when this field receives input focus.
16158              * @param {Roo.form.Field} this
16159              */
16160             focus : true,
16161             /**
16162              * @event blur
16163              * Fires when this field loses input focus.
16164              * @param {Roo.form.Field} this
16165              */
16166             blur : true,
16167             /**
16168              * @event specialkey
16169              * Fires when any key related to navigation (arrows, tab, enter, esc, etc.) is pressed.  You can check
16170              * {@link Roo.EventObject#getKey} to determine which key was pressed.
16171              * @param {Roo.form.Field} this
16172              * @param {Roo.EventObject} e The event object
16173              */
16174             specialkey : true,
16175             /**
16176              * @event change
16177              * Fires just before the field blurs if the field value has changed.
16178              * @param {Roo.form.Field} this
16179              * @param {Mixed} newValue The new value
16180              * @param {Mixed} oldValue The original value
16181              */
16182             change : true,
16183             /**
16184              * @event invalid
16185              * Fires after the field has been marked as invalid.
16186              * @param {Roo.form.Field} this
16187              * @param {String} msg The validation message
16188              */
16189             invalid : true,
16190             /**
16191              * @event valid
16192              * Fires after the field has been validated with no errors.
16193              * @param {Roo.form.Field} this
16194              */
16195             valid : true,
16196              /**
16197              * @event keyup
16198              * Fires after the key up
16199              * @param {Roo.form.Field} this
16200              * @param {Roo.EventObject}  e The event Object
16201              */
16202             keyup : true
16203         });
16204     },
16205
16206     /**
16207      * Returns the name attribute of the field if available
16208      * @return {String} name The field name
16209      */
16210     getName: function(){
16211          return this.rendered && this.el.dom.name ? this.el.dom.name : (this.hiddenName || '');
16212     },
16213
16214     // private
16215     onRender : function(ct, position){
16216         Roo.form.Field.superclass.onRender.call(this, ct, position);
16217         if(!this.el){
16218             var cfg = this.getAutoCreate();
16219             if(!cfg.name){
16220                 cfg.name = typeof(this.name) == 'undefined' ? this.id : this.name;
16221             }
16222             if (!cfg.name.length) {
16223                 delete cfg.name;
16224             }
16225             if(this.inputType){
16226                 cfg.type = this.inputType;
16227             }
16228             this.el = ct.createChild(cfg, position);
16229         }
16230         var type = this.el.dom.type;
16231         if(type){
16232             if(type == 'password'){
16233                 type = 'text';
16234             }
16235             this.el.addClass('x-form-'+type);
16236         }
16237         if(this.readOnly){
16238             this.el.dom.readOnly = true;
16239         }
16240         if(this.tabIndex !== undefined){
16241             this.el.dom.setAttribute('tabIndex', this.tabIndex);
16242         }
16243
16244         this.el.addClass([this.fieldClass, this.cls]);
16245         this.initValue();
16246     },
16247
16248     /**
16249      * Apply the behaviors of this component to an existing element. <b>This is used instead of render().</b>
16250      * @param {String/HTMLElement/Element} el The id of the node, a DOM node or an existing Element
16251      * @return {Roo.form.Field} this
16252      */
16253     applyTo : function(target){
16254         this.allowDomMove = false;
16255         this.el = Roo.get(target);
16256         this.render(this.el.dom.parentNode);
16257         return this;
16258     },
16259
16260     // private
16261     initValue : function(){
16262         if(this.value !== undefined){
16263             this.setValue(this.value);
16264         }else if(this.el.dom.value.length > 0){
16265             this.setValue(this.el.dom.value);
16266         }
16267     },
16268
16269     /**
16270      * Returns true if this field has been changed since it was originally loaded and is not disabled.
16271      * DEPRICATED  - it never worked well - use hasChanged/resetHasChanged.
16272      */
16273     isDirty : function() {
16274         if(this.disabled) {
16275             return false;
16276         }
16277         return String(this.getValue()) !== String(this.originalValue);
16278     },
16279
16280     /**
16281      * stores the current value in loadedValue
16282      */
16283     resetHasChanged : function()
16284     {
16285         this.loadedValue = String(this.getValue());
16286     },
16287     /**
16288      * checks the current value against the 'loaded' value.
16289      * Note - will return false if 'resetHasChanged' has not been called first.
16290      */
16291     hasChanged : function()
16292     {
16293         if(this.disabled || this.readOnly) {
16294             return false;
16295         }
16296         return this.loadedValue !== false && String(this.getValue()) !== this.loadedValue;
16297     },
16298     
16299     
16300     
16301     // private
16302     afterRender : function(){
16303         Roo.form.Field.superclass.afterRender.call(this);
16304         this.initEvents();
16305     },
16306
16307     // private
16308     fireKey : function(e){
16309         //Roo.log('field ' + e.getKey());
16310         if(e.isNavKeyPress()){
16311             this.fireEvent("specialkey", this, e);
16312         }
16313     },
16314
16315     /**
16316      * Resets the current field value to the originally loaded value and clears any validation messages
16317      */
16318     reset : function(){
16319         this.setValue(this.resetValue);
16320         this.originalValue = this.getValue();
16321         this.clearInvalid();
16322     },
16323
16324     // private
16325     initEvents : function(){
16326         // safari killled keypress - so keydown is now used..
16327         this.el.on("keydown" , this.fireKey,  this);
16328         this.el.on("focus", this.onFocus,  this);
16329         this.el.on("blur", this.onBlur,  this);
16330         this.el.relayEvent('keyup', this);
16331
16332         // reference to original value for reset
16333         this.originalValue = this.getValue();
16334         this.resetValue =  this.getValue();
16335     },
16336
16337     // private
16338     onFocus : function(){
16339         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
16340             this.el.addClass(this.focusClass);
16341         }
16342         if(!this.hasFocus){
16343             this.hasFocus = true;
16344             this.startValue = this.getValue();
16345             this.fireEvent("focus", this);
16346         }
16347     },
16348
16349     beforeBlur : Roo.emptyFn,
16350
16351     // private
16352     onBlur : function(){
16353         this.beforeBlur();
16354         if(!Roo.isOpera && this.focusClass){ // don't touch in Opera
16355             this.el.removeClass(this.focusClass);
16356         }
16357         this.hasFocus = false;
16358         if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
16359             this.validate();
16360         }
16361         var v = this.getValue();
16362         if(String(v) !== String(this.startValue)){
16363             this.fireEvent('change', this, v, this.startValue);
16364         }
16365         this.fireEvent("blur", this);
16366     },
16367
16368     /**
16369      * Returns whether or not the field value is currently valid
16370      * @param {Boolean} preventMark True to disable marking the field invalid
16371      * @return {Boolean} True if the value is valid, else false
16372      */
16373     isValid : function(preventMark){
16374         if(this.disabled){
16375             return true;
16376         }
16377         var restore = this.preventMark;
16378         this.preventMark = preventMark === true;
16379         var v = this.validateValue(this.processValue(this.getRawValue()));
16380         this.preventMark = restore;
16381         return v;
16382     },
16383
16384     /**
16385      * Validates the field value
16386      * @return {Boolean} True if the value is valid, else false
16387      */
16388     validate : function(){
16389         if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
16390             this.clearInvalid();
16391             return true;
16392         }
16393         return false;
16394     },
16395
16396     processValue : function(value){
16397         return value;
16398     },
16399
16400     // private
16401     // Subclasses should provide the validation implementation by overriding this
16402     validateValue : function(value){
16403         return true;
16404     },
16405
16406     /**
16407      * Mark this field as invalid
16408      * @param {String} msg The validation message
16409      */
16410     markInvalid : function(msg){
16411         if(!this.rendered || this.preventMark){ // not rendered
16412             return;
16413         }
16414         
16415         var obj = (typeof(this.combo) != 'undefined') ? this.combo : this; // fix the combox array!!
16416         
16417         obj.el.addClass(this.invalidClass);
16418         msg = msg || this.invalidText;
16419         switch(this.msgTarget){
16420             case 'qtip':
16421                 obj.el.dom.qtip = msg;
16422                 obj.el.dom.qclass = 'x-form-invalid-tip';
16423                 if(Roo.QuickTips){ // fix for floating editors interacting with DND
16424                     Roo.QuickTips.enable();
16425                 }
16426                 break;
16427             case 'title':
16428                 this.el.dom.title = msg;
16429                 break;
16430             case 'under':
16431                 if(!this.errorEl){
16432                     var elp = this.el.findParent('.x-form-element', 5, true);
16433                     this.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
16434                     this.errorEl.setWidth(elp.getWidth(true)-20);
16435                 }
16436                 this.errorEl.update(msg);
16437                 Roo.form.Field.msgFx[this.msgFx].show(this.errorEl, this);
16438                 break;
16439             case 'side':
16440                 if(!this.errorIcon){
16441                     var elp = this.el.findParent('.x-form-element', 5, true);
16442                     this.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
16443                 }
16444                 this.alignErrorIcon();
16445                 this.errorIcon.dom.qtip = msg;
16446                 this.errorIcon.dom.qclass = 'x-form-invalid-tip';
16447                 this.errorIcon.show();
16448                 this.on('resize', this.alignErrorIcon, this);
16449                 break;
16450             default:
16451                 var t = Roo.getDom(this.msgTarget);
16452                 t.innerHTML = msg;
16453                 t.style.display = this.msgDisplay;
16454                 break;
16455         }
16456         this.fireEvent('invalid', this, msg);
16457     },
16458
16459     // private
16460     alignErrorIcon : function(){
16461         this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]);
16462     },
16463
16464     /**
16465      * Clear any invalid styles/messages for this field
16466      */
16467     clearInvalid : function(){
16468         if(!this.rendered || this.preventMark){ // not rendered
16469             return;
16470         }
16471         var obj = (typeof(this.combo) != 'undefined') ? this.combo : this; // fix the combox array!!
16472         
16473         obj.el.removeClass(this.invalidClass);
16474         switch(this.msgTarget){
16475             case 'qtip':
16476                 obj.el.dom.qtip = '';
16477                 break;
16478             case 'title':
16479                 this.el.dom.title = '';
16480                 break;
16481             case 'under':
16482                 if(this.errorEl){
16483                     Roo.form.Field.msgFx[this.msgFx].hide(this.errorEl, this);
16484                 }
16485                 break;
16486             case 'side':
16487                 if(this.errorIcon){
16488                     this.errorIcon.dom.qtip = '';
16489                     this.errorIcon.hide();
16490                     this.un('resize', this.alignErrorIcon, this);
16491                 }
16492                 break;
16493             default:
16494                 var t = Roo.getDom(this.msgTarget);
16495                 t.innerHTML = '';
16496                 t.style.display = 'none';
16497                 break;
16498         }
16499         this.fireEvent('valid', this);
16500     },
16501
16502     /**
16503      * Returns the raw data value which may or may not be a valid, defined value.  To return a normalized value see {@link #getValue}.
16504      * @return {Mixed} value The field value
16505      */
16506     getRawValue : function(){
16507         var v = this.el.getValue();
16508         
16509         return v;
16510     },
16511
16512     /**
16513      * Returns the normalized data value (undefined or emptyText will be returned as '').  To return the raw value see {@link #getRawValue}.
16514      * @return {Mixed} value The field value
16515      */
16516     getValue : function(){
16517         var v = this.el.getValue();
16518          
16519         return v;
16520     },
16521
16522     /**
16523      * Sets the underlying DOM field's value directly, bypassing validation.  To set the value with validation see {@link #setValue}.
16524      * @param {Mixed} value The value to set
16525      */
16526     setRawValue : function(v){
16527         return this.el.dom.value = (v === null || v === undefined ? '' : v);
16528     },
16529
16530     /**
16531      * Sets a data value into the field and validates it.  To set the value directly without validation see {@link #setRawValue}.
16532      * @param {Mixed} value The value to set
16533      */
16534     setValue : function(v){
16535         this.value = v;
16536         if(this.rendered){
16537             this.el.dom.value = (v === null || v === undefined ? '' : v);
16538              this.validate();
16539         }
16540     },
16541
16542     adjustSize : function(w, h){
16543         var s = Roo.form.Field.superclass.adjustSize.call(this, w, h);
16544         s.width = this.adjustWidth(this.el.dom.tagName, s.width);
16545         return s;
16546     },
16547
16548     adjustWidth : function(tag, w){
16549         tag = tag.toLowerCase();
16550         if(typeof w == 'number' && Roo.isStrict && !Roo.isSafari){
16551             if(Roo.isIE && (tag == 'input' || tag == 'textarea')){
16552                 if(tag == 'input'){
16553                     return w + 2;
16554                 }
16555                 if(tag == 'textarea'){
16556                     return w-2;
16557                 }
16558             }else if(Roo.isOpera){
16559                 if(tag == 'input'){
16560                     return w + 2;
16561                 }
16562                 if(tag == 'textarea'){
16563                     return w-2;
16564                 }
16565             }
16566         }
16567         return w;
16568     }
16569 });
16570
16571
16572 // anything other than normal should be considered experimental
16573 Roo.form.Field.msgFx = {
16574     normal : {
16575         show: function(msgEl, f){
16576             msgEl.setDisplayed('block');
16577         },
16578
16579         hide : function(msgEl, f){
16580             msgEl.setDisplayed(false).update('');
16581         }
16582     },
16583
16584     slide : {
16585         show: function(msgEl, f){
16586             msgEl.slideIn('t', {stopFx:true});
16587         },
16588
16589         hide : function(msgEl, f){
16590             msgEl.slideOut('t', {stopFx:true,useDisplay:true});
16591         }
16592     },
16593
16594     slideRight : {
16595         show: function(msgEl, f){
16596             msgEl.fixDisplay();
16597             msgEl.alignTo(f.el, 'tl-tr');
16598             msgEl.slideIn('l', {stopFx:true});
16599         },
16600
16601         hide : function(msgEl, f){
16602             msgEl.slideOut('l', {stopFx:true,useDisplay:true});
16603         }
16604     }
16605 };/*
16606  * Based on:
16607  * Ext JS Library 1.1.1
16608  * Copyright(c) 2006-2007, Ext JS, LLC.
16609  *
16610  * Originally Released Under LGPL - original licence link has changed is not relivant.
16611  *
16612  * Fork - LGPL
16613  * <script type="text/javascript">
16614  */
16615  
16616
16617 /**
16618  * @class Roo.form.TextField
16619  * @extends Roo.form.Field
16620  * Basic text field.  Can be used as a direct replacement for traditional text inputs, or as the base
16621  * class for more sophisticated input controls (like {@link Roo.form.TextArea} and {@link Roo.form.ComboBox}).
16622  * @constructor
16623  * Creates a new TextField
16624  * @param {Object} config Configuration options
16625  */
16626 Roo.form.TextField = function(config){
16627     Roo.form.TextField.superclass.constructor.call(this, config);
16628     this.addEvents({
16629         /**
16630          * @event autosize
16631          * Fires when the autosize function is triggered.  The field may or may not have actually changed size
16632          * according to the default logic, but this event provides a hook for the developer to apply additional
16633          * logic at runtime to resize the field if needed.
16634              * @param {Roo.form.Field} this This text field
16635              * @param {Number} width The new field width
16636              */
16637         autosize : true
16638     });
16639 };
16640
16641 Roo.extend(Roo.form.TextField, Roo.form.Field,  {
16642     /**
16643      * @cfg {Boolean} grow True if this field should automatically grow and shrink to its content
16644      */
16645     grow : false,
16646     /**
16647      * @cfg {Number} growMin The minimum width to allow when grow = true (defaults to 30)
16648      */
16649     growMin : 30,
16650     /**
16651      * @cfg {Number} growMax The maximum width to allow when grow = true (defaults to 800)
16652      */
16653     growMax : 800,
16654     /**
16655      * @cfg {String} vtype A validation type name as defined in {@link Roo.form.VTypes} (defaults to null)
16656      */
16657     vtype : null,
16658     /**
16659      * @cfg {String} maskRe An input mask regular expression that will be used to filter keystrokes that don't match (defaults to null)
16660      */
16661     maskRe : null,
16662     /**
16663      * @cfg {Boolean} disableKeyFilter True to disable input keystroke filtering (defaults to false)
16664      */
16665     disableKeyFilter : false,
16666     /**
16667      * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to true)
16668      */
16669     allowBlank : true,
16670     /**
16671      * @cfg {Number} minLength Minimum input field length required (defaults to 0)
16672      */
16673     minLength : 0,
16674     /**
16675      * @cfg {Number} maxLength Maximum input field length allowed (defaults to Number.MAX_VALUE)
16676      */
16677     maxLength : Number.MAX_VALUE,
16678     /**
16679      * @cfg {String} minLengthText Error text to display if the minimum length validation fails (defaults to "The minimum length for this field is {minLength}")
16680      */
16681     minLengthText : "The minimum length for this field is {0}",
16682     /**
16683      * @cfg {String} maxLengthText Error text to display if the maximum length validation fails (defaults to "The maximum length for this field is {maxLength}")
16684      */
16685     maxLengthText : "The maximum length for this field is {0}",
16686     /**
16687      * @cfg {Boolean} selectOnFocus True to automatically select any existing field text when the field receives input focus (defaults to false)
16688      */
16689     selectOnFocus : false,
16690     /**
16691      * @cfg {Boolean} allowLeadingSpace True to prevent the stripping of leading white space 
16692      */    
16693     allowLeadingSpace : false,
16694     /**
16695      * @cfg {String} blankText Error text to display if the allow blank validation fails (defaults to "This field is required")
16696      */
16697     blankText : "This field is required",
16698     /**
16699      * @cfg {Function} validator A custom validation function to be called during field validation (defaults to null).
16700      * If available, this function will be called only after the basic validators all return true, and will be passed the
16701      * current field value and expected to return boolean true if the value is valid or a string error message if invalid.
16702      */
16703     validator : null,
16704     /**
16705      * @cfg {RegExp} regex A JavaScript RegExp object to be tested against the field value during validation (defaults to null).
16706      * If available, this regex will be evaluated only after the basic validators all return true, and will be passed the
16707      * current field value.  If the test fails, the field will be marked invalid using {@link #regexText}.
16708      */
16709     regex : null,
16710     /**
16711      * @cfg {String} regexText The error text to display if {@link #regex} is used and the test fails during validation (defaults to "")
16712      */
16713     regexText : "",
16714     /**
16715      * @cfg {String} emptyText The default text to display in an empty field - placeholder... (defaults to null).
16716      */
16717     emptyText : null,
16718    
16719
16720     // private
16721     initEvents : function()
16722     {
16723         if (this.emptyText) {
16724             this.el.attr('placeholder', this.emptyText);
16725         }
16726         
16727         Roo.form.TextField.superclass.initEvents.call(this);
16728         if(this.validationEvent == 'keyup'){
16729             this.validationTask = new Roo.util.DelayedTask(this.validate, this);
16730             this.el.on('keyup', this.filterValidation, this);
16731         }
16732         else if(this.validationEvent !== false){
16733             this.el.on(this.validationEvent, this.validate, this, {buffer: this.validationDelay});
16734         }
16735         
16736         if(this.selectOnFocus){
16737             this.on("focus", this.preFocus, this);
16738         }
16739         if (!this.allowLeadingSpace) {
16740             this.on('blur', this.cleanLeadingSpace, this);
16741         }
16742         
16743         if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Roo.form.VTypes[this.vtype+'Mask']))){
16744             this.el.on("keypress", this.filterKeys, this);
16745         }
16746         if(this.grow){
16747             this.el.on("keyup", this.onKeyUp,  this, {buffer:50});
16748             this.el.on("click", this.autoSize,  this);
16749         }
16750         if(this.el.is('input[type=password]') && Roo.isSafari){
16751             this.el.on('keydown', this.SafariOnKeyDown, this);
16752         }
16753     },
16754
16755     processValue : function(value){
16756         if(this.stripCharsRe){
16757             var newValue = value.replace(this.stripCharsRe, '');
16758             if(newValue !== value){
16759                 this.setRawValue(newValue);
16760                 return newValue;
16761             }
16762         }
16763         return value;
16764     },
16765
16766     filterValidation : function(e){
16767         if(!e.isNavKeyPress()){
16768             this.validationTask.delay(this.validationDelay);
16769         }
16770     },
16771
16772     // private
16773     onKeyUp : function(e){
16774         if(!e.isNavKeyPress()){
16775             this.autoSize();
16776         }
16777     },
16778     // private - clean the leading white space
16779     cleanLeadingSpace : function(e)
16780     {
16781         if ( this.inputType == 'file') {
16782             return;
16783         }
16784         
16785         this.setValue((this.getValue() + '').replace(/^\s+/,''));
16786     },
16787     /**
16788      * Resets the current field value to the originally-loaded value and clears any validation messages.
16789      *  
16790      */
16791     reset : function(){
16792         Roo.form.TextField.superclass.reset.call(this);
16793        
16794     }, 
16795     // private
16796     preFocus : function(){
16797         
16798         if(this.selectOnFocus){
16799             this.el.dom.select();
16800         }
16801     },
16802
16803     
16804     // private
16805     filterKeys : function(e){
16806         var k = e.getKey();
16807         if(!Roo.isIE && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
16808             return;
16809         }
16810         var c = e.getCharCode(), cc = String.fromCharCode(c);
16811         if(Roo.isIE && (e.isSpecialKey() || !cc)){
16812             return;
16813         }
16814         if(!this.maskRe.test(cc)){
16815             e.stopEvent();
16816         }
16817     },
16818
16819     setValue : function(v){
16820         
16821         Roo.form.TextField.superclass.setValue.apply(this, arguments);
16822         
16823         this.autoSize();
16824     },
16825
16826     /**
16827      * Validates a value according to the field's validation rules and marks the field as invalid
16828      * if the validation fails
16829      * @param {Mixed} value The value to validate
16830      * @return {Boolean} True if the value is valid, else false
16831      */
16832     validateValue : function(value){
16833         if(value.length < 1)  { // if it's blank
16834              if(this.allowBlank){
16835                 this.clearInvalid();
16836                 return true;
16837              }else{
16838                 this.markInvalid(this.blankText);
16839                 return false;
16840              }
16841         }
16842         if(value.length < this.minLength){
16843             this.markInvalid(String.format(this.minLengthText, this.minLength));
16844             return false;
16845         }
16846         if(value.length > this.maxLength){
16847             this.markInvalid(String.format(this.maxLengthText, this.maxLength));
16848             return false;
16849         }
16850         if(this.vtype){
16851             var vt = Roo.form.VTypes;
16852             if(!vt[this.vtype](value, this)){
16853                 this.markInvalid(this.vtypeText || vt[this.vtype +'Text']);
16854                 return false;
16855             }
16856         }
16857         if(typeof this.validator == "function"){
16858             var msg = this.validator(value);
16859             if(msg !== true){
16860                 this.markInvalid(msg);
16861                 return false;
16862             }
16863         }
16864         if(this.regex && !this.regex.test(value)){
16865             this.markInvalid(this.regexText);
16866             return false;
16867         }
16868         return true;
16869     },
16870
16871     /**
16872      * Selects text in this field
16873      * @param {Number} start (optional) The index where the selection should start (defaults to 0)
16874      * @param {Number} end (optional) The index where the selection should end (defaults to the text length)
16875      */
16876     selectText : function(start, end){
16877         var v = this.getRawValue();
16878         if(v.length > 0){
16879             start = start === undefined ? 0 : start;
16880             end = end === undefined ? v.length : end;
16881             var d = this.el.dom;
16882             if(d.setSelectionRange){
16883                 d.setSelectionRange(start, end);
16884             }else if(d.createTextRange){
16885                 var range = d.createTextRange();
16886                 range.moveStart("character", start);
16887                 range.moveEnd("character", v.length-end);
16888                 range.select();
16889             }
16890         }
16891     },
16892
16893     /**
16894      * Automatically grows the field to accomodate the width of the text up to the maximum field width allowed.
16895      * This only takes effect if grow = true, and fires the autosize event.
16896      */
16897     autoSize : function(){
16898         if(!this.grow || !this.rendered){
16899             return;
16900         }
16901         if(!this.metrics){
16902             this.metrics = Roo.util.TextMetrics.createInstance(this.el);
16903         }
16904         var el = this.el;
16905         var v = el.dom.value;
16906         var d = document.createElement('div');
16907         d.appendChild(document.createTextNode(v));
16908         v = d.innerHTML;
16909         d = null;
16910         v += "&#160;";
16911         var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + /* add extra padding */ 10, this.growMin));
16912         this.el.setWidth(w);
16913         this.fireEvent("autosize", this, w);
16914     },
16915     
16916     // private
16917     SafariOnKeyDown : function(event)
16918     {
16919         // this is a workaround for a password hang bug on chrome/ webkit.
16920         
16921         var isSelectAll = false;
16922         
16923         if(this.el.dom.selectionEnd > 0){
16924             isSelectAll = (this.el.dom.selectionEnd - this.el.dom.selectionStart - this.getValue().length == 0) ? true : false;
16925         }
16926         if(((event.getKey() == 8 || event.getKey() == 46) && this.getValue().length ==1)){ // backspace and delete key
16927             event.preventDefault();
16928             this.setValue('');
16929             return;
16930         }
16931         
16932         if(isSelectAll && event.getCharCode() > 31){ // backspace and delete key
16933             
16934             event.preventDefault();
16935             // this is very hacky as keydown always get's upper case.
16936             
16937             var cc = String.fromCharCode(event.getCharCode());
16938             
16939             
16940             this.setValue( event.shiftKey ?  cc : cc.toLowerCase());
16941             
16942         }
16943         
16944         
16945     }
16946 });/*
16947  * Based on:
16948  * Ext JS Library 1.1.1
16949  * Copyright(c) 2006-2007, Ext JS, LLC.
16950  *
16951  * Originally Released Under LGPL - original licence link has changed is not relivant.
16952  *
16953  * Fork - LGPL
16954  * <script type="text/javascript">
16955  */
16956  
16957 /**
16958  * @class Roo.form.Hidden
16959  * @extends Roo.form.TextField
16960  * Simple Hidden element used on forms 
16961  * 
16962  * usage: form.add(new Roo.form.HiddenField({ 'name' : 'test1' }));
16963  * 
16964  * @constructor
16965  * Creates a new Hidden form element.
16966  * @param {Object} config Configuration options
16967  */
16968
16969
16970
16971 // easy hidden field...
16972 Roo.form.Hidden = function(config){
16973     Roo.form.Hidden.superclass.constructor.call(this, config);
16974 };
16975   
16976 Roo.extend(Roo.form.Hidden, Roo.form.TextField, {
16977     fieldLabel:      '',
16978     inputType:      'hidden',
16979     width:          50,
16980     allowBlank:     true,
16981     labelSeparator: '',
16982     hidden:         true,
16983     itemCls :       'x-form-item-display-none'
16984
16985
16986 });
16987
16988
16989 /*
16990  * Based on:
16991  * Ext JS Library 1.1.1
16992  * Copyright(c) 2006-2007, Ext JS, LLC.
16993  *
16994  * Originally Released Under LGPL - original licence link has changed is not relivant.
16995  *
16996  * Fork - LGPL
16997  * <script type="text/javascript">
16998  */
16999  
17000 /**
17001  * @class Roo.form.TriggerField
17002  * @extends Roo.form.TextField
17003  * Provides a convenient wrapper for TextFields that adds a clickable trigger button (looks like a combobox by default).
17004  * The trigger has no default action, so you must assign a function to implement the trigger click handler by
17005  * overriding {@link #onTriggerClick}. You can create a TriggerField directly, as it renders exactly like a combobox
17006  * for which you can provide a custom implementation.  For example:
17007  * <pre><code>
17008 var trigger = new Roo.form.TriggerField();
17009 trigger.onTriggerClick = myTriggerFn;
17010 trigger.applyTo('my-field');
17011 </code></pre>
17012  *
17013  * However, in general you will most likely want to use TriggerField as the base class for a reusable component.
17014  * {@link Roo.form.DateField} and {@link Roo.form.ComboBox} are perfect examples of this.
17015  * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always get the
17016  * class 'x-form-trigger' by default and triggerClass will be <b>appended</b> if specified.
17017  * @constructor
17018  * Create a new TriggerField.
17019  * @param {Object} config Configuration options (valid {@Roo.form.TextField} config options will also be applied
17020  * to the base TextField)
17021  */
17022 Roo.form.TriggerField = function(config){
17023     this.mimicing = false;
17024     Roo.form.TriggerField.superclass.constructor.call(this, config);
17025 };
17026
17027 Roo.extend(Roo.form.TriggerField, Roo.form.TextField,  {
17028     /**
17029      * @cfg {String} triggerClass A CSS class to apply to the trigger
17030      */
17031     /**
17032      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
17033      * {tag: "input", type: "text", size: "16", autocomplete: "off"})
17034      */
17035     defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "new-password"},
17036     /**
17037      * @cfg {Boolean} hideTrigger True to hide the trigger element and display only the base text field (defaults to false)
17038      */
17039     hideTrigger:false,
17040
17041     /** @cfg {Boolean} grow @hide */
17042     /** @cfg {Number} growMin @hide */
17043     /** @cfg {Number} growMax @hide */
17044
17045     /**
17046      * @hide 
17047      * @method
17048      */
17049     autoSize: Roo.emptyFn,
17050     // private
17051     monitorTab : true,
17052     // private
17053     deferHeight : true,
17054
17055     
17056     actionMode : 'wrap',
17057     // private
17058     onResize : function(w, h){
17059         Roo.form.TriggerField.superclass.onResize.apply(this, arguments);
17060         if(typeof w == 'number'){
17061             var x = w - this.trigger.getWidth();
17062             this.el.setWidth(this.adjustWidth('input', x));
17063             this.trigger.setStyle('left', x+'px');
17064         }
17065     },
17066
17067     // private
17068     adjustSize : Roo.BoxComponent.prototype.adjustSize,
17069
17070     // private
17071     getResizeEl : function(){
17072         return this.wrap;
17073     },
17074
17075     // private
17076     getPositionEl : function(){
17077         return this.wrap;
17078     },
17079
17080     // private
17081     alignErrorIcon : function(){
17082         this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
17083     },
17084
17085     // private
17086     onRender : function(ct, position){
17087         Roo.form.TriggerField.superclass.onRender.call(this, ct, position);
17088         this.wrap = this.el.wrap({cls: "x-form-field-wrap"});
17089         this.trigger = this.wrap.createChild(this.triggerConfig ||
17090                 {tag: "img", src: Roo.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.triggerClass});
17091         if(this.hideTrigger){
17092             this.trigger.setDisplayed(false);
17093         }
17094         this.initTrigger();
17095         if(!this.width){
17096             this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
17097         }
17098     },
17099
17100     // private
17101     initTrigger : function(){
17102         this.trigger.on("click", this.onTriggerClick, this, {preventDefault:true});
17103         this.trigger.addClassOnOver('x-form-trigger-over');
17104         this.trigger.addClassOnClick('x-form-trigger-click');
17105     },
17106
17107     // private
17108     onDestroy : function(){
17109         if(this.trigger){
17110             this.trigger.removeAllListeners();
17111             this.trigger.remove();
17112         }
17113         if(this.wrap){
17114             this.wrap.remove();
17115         }
17116         Roo.form.TriggerField.superclass.onDestroy.call(this);
17117     },
17118
17119     // private
17120     onFocus : function(){
17121         Roo.form.TriggerField.superclass.onFocus.call(this);
17122         if(!this.mimicing){
17123             this.wrap.addClass('x-trigger-wrap-focus');
17124             this.mimicing = true;
17125             Roo.get(Roo.isIE ? document.body : document).on("mousedown", this.mimicBlur, this);
17126             if(this.monitorTab){
17127                 this.el.on("keydown", this.checkTab, this);
17128             }
17129         }
17130     },
17131
17132     // private
17133     checkTab : function(e){
17134         if(e.getKey() == e.TAB){
17135             this.triggerBlur();
17136         }
17137     },
17138
17139     // private
17140     onBlur : function(){
17141         // do nothing
17142     },
17143
17144     // private
17145     mimicBlur : function(e, t){
17146         if(!this.wrap.contains(t) && this.validateBlur()){
17147             this.triggerBlur();
17148         }
17149     },
17150
17151     // private
17152     triggerBlur : function(){
17153         this.mimicing = false;
17154         Roo.get(Roo.isIE ? document.body : document).un("mousedown", this.mimicBlur);
17155         if(this.monitorTab){
17156             this.el.un("keydown", this.checkTab, this);
17157         }
17158         this.wrap.removeClass('x-trigger-wrap-focus');
17159         Roo.form.TriggerField.superclass.onBlur.call(this);
17160     },
17161
17162     // private
17163     // This should be overriden by any subclass that needs to check whether or not the field can be blurred.
17164     validateBlur : function(e, t){
17165         return true;
17166     },
17167
17168     // private
17169     onDisable : function(){
17170         Roo.form.TriggerField.superclass.onDisable.call(this);
17171         if(this.wrap){
17172             this.wrap.addClass('x-item-disabled');
17173         }
17174     },
17175
17176     // private
17177     onEnable : function(){
17178         Roo.form.TriggerField.superclass.onEnable.call(this);
17179         if(this.wrap){
17180             this.wrap.removeClass('x-item-disabled');
17181         }
17182     },
17183
17184     // private
17185     onShow : function(){
17186         var ae = this.getActionEl();
17187         
17188         if(ae){
17189             ae.dom.style.display = '';
17190             ae.dom.style.visibility = 'visible';
17191         }
17192     },
17193
17194     // private
17195     
17196     onHide : function(){
17197         var ae = this.getActionEl();
17198         ae.dom.style.display = 'none';
17199     },
17200
17201     /**
17202      * The function that should handle the trigger's click event.  This method does nothing by default until overridden
17203      * by an implementing function.
17204      * @method
17205      * @param {EventObject} e
17206      */
17207     onTriggerClick : Roo.emptyFn
17208 });
17209
17210 // TwinTriggerField is not a public class to be used directly.  It is meant as an abstract base class
17211 // to be extended by an implementing class.  For an example of implementing this class, see the custom
17212 // SearchField implementation here: http://extjs.com/deploy/ext/examples/form/custom.html
17213 Roo.form.TwinTriggerField = Roo.extend(Roo.form.TriggerField, {
17214     initComponent : function(){
17215         Roo.form.TwinTriggerField.superclass.initComponent.call(this);
17216
17217         this.triggerConfig = {
17218             tag:'span', cls:'x-form-twin-triggers', cn:[
17219             {tag: "img", src: Roo.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger1Class},
17220             {tag: "img", src: Roo.BLANK_IMAGE_URL, cls: "x-form-trigger " + this.trigger2Class}
17221         ]};
17222     },
17223
17224     getTrigger : function(index){
17225         return this.triggers[index];
17226     },
17227
17228     initTrigger : function(){
17229         var ts = this.trigger.select('.x-form-trigger', true);
17230         this.wrap.setStyle('overflow', 'hidden');
17231         var triggerField = this;
17232         ts.each(function(t, all, index){
17233             t.hide = function(){
17234                 var w = triggerField.wrap.getWidth();
17235                 this.dom.style.display = 'none';
17236                 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
17237             };
17238             t.show = function(){
17239                 var w = triggerField.wrap.getWidth();
17240                 this.dom.style.display = '';
17241                 triggerField.el.setWidth(w-triggerField.trigger.getWidth());
17242             };
17243             var triggerIndex = 'Trigger'+(index+1);
17244
17245             if(this['hide'+triggerIndex]){
17246                 t.dom.style.display = 'none';
17247             }
17248             t.on("click", this['on'+triggerIndex+'Click'], this, {preventDefault:true});
17249             t.addClassOnOver('x-form-trigger-over');
17250             t.addClassOnClick('x-form-trigger-click');
17251         }, this);
17252         this.triggers = ts.elements;
17253     },
17254
17255     onTrigger1Click : Roo.emptyFn,
17256     onTrigger2Click : Roo.emptyFn
17257 });/*
17258  * Based on:
17259  * Ext JS Library 1.1.1
17260  * Copyright(c) 2006-2007, Ext JS, LLC.
17261  *
17262  * Originally Released Under LGPL - original licence link has changed is not relivant.
17263  *
17264  * Fork - LGPL
17265  * <script type="text/javascript">
17266  */
17267  
17268 /**
17269  * @class Roo.form.TextArea
17270  * @extends Roo.form.TextField
17271  * Multiline text field.  Can be used as a direct replacement for traditional textarea fields, plus adds
17272  * support for auto-sizing.
17273  * @constructor
17274  * Creates a new TextArea
17275  * @param {Object} config Configuration options
17276  */
17277 Roo.form.TextArea = function(config){
17278     Roo.form.TextArea.superclass.constructor.call(this, config);
17279     // these are provided exchanges for backwards compat
17280     // minHeight/maxHeight were replaced by growMin/growMax to be
17281     // compatible with TextField growing config values
17282     if(this.minHeight !== undefined){
17283         this.growMin = this.minHeight;
17284     }
17285     if(this.maxHeight !== undefined){
17286         this.growMax = this.maxHeight;
17287     }
17288 };
17289
17290 Roo.extend(Roo.form.TextArea, Roo.form.TextField,  {
17291     /**
17292      * @cfg {Number} growMin The minimum height to allow when grow = true (defaults to 60)
17293      */
17294     growMin : 60,
17295     /**
17296      * @cfg {Number} growMax The maximum height to allow when grow = true (defaults to 1000)
17297      */
17298     growMax: 1000,
17299     /**
17300      * @cfg {Boolean} preventScrollbars True to prevent scrollbars from appearing regardless of how much text is
17301      * in the field (equivalent to setting overflow: hidden, defaults to false)
17302      */
17303     preventScrollbars: false,
17304     /**
17305      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
17306      * {tag: "textarea", style: "width:300px;height:60px;", autocomplete: "off"})
17307      */
17308
17309     // private
17310     onRender : function(ct, position){
17311         if(!this.el){
17312             this.defaultAutoCreate = {
17313                 tag: "textarea",
17314                 style:"width:300px;height:60px;",
17315                 autocomplete: "new-password"
17316             };
17317         }
17318         Roo.form.TextArea.superclass.onRender.call(this, ct, position);
17319         if(this.grow){
17320             this.textSizeEl = Roo.DomHelper.append(document.body, {
17321                 tag: "pre", cls: "x-form-grow-sizer"
17322             });
17323             if(this.preventScrollbars){
17324                 this.el.setStyle("overflow", "hidden");
17325             }
17326             this.el.setHeight(this.growMin);
17327         }
17328     },
17329
17330     onDestroy : function(){
17331         if(this.textSizeEl){
17332             this.textSizeEl.parentNode.removeChild(this.textSizeEl);
17333         }
17334         Roo.form.TextArea.superclass.onDestroy.call(this);
17335     },
17336
17337     // private
17338     onKeyUp : function(e){
17339         if(!e.isNavKeyPress() || e.getKey() == e.ENTER){
17340             this.autoSize();
17341         }
17342     },
17343
17344     /**
17345      * Automatically grows the field to accomodate the height of the text up to the maximum field height allowed.
17346      * This only takes effect if grow = true, and fires the autosize event if the height changes.
17347      */
17348     autoSize : function(){
17349         if(!this.grow || !this.textSizeEl){
17350             return;
17351         }
17352         var el = this.el;
17353         var v = el.dom.value;
17354         var ts = this.textSizeEl;
17355
17356         ts.innerHTML = '';
17357         ts.appendChild(document.createTextNode(v));
17358         v = ts.innerHTML;
17359
17360         Roo.fly(ts).setWidth(this.el.getWidth());
17361         if(v.length < 1){
17362             v = "&#160;&#160;";
17363         }else{
17364             if(Roo.isIE){
17365                 v = v.replace(/\n/g, '<p>&#160;</p>');
17366             }
17367             v += "&#160;\n&#160;";
17368         }
17369         ts.innerHTML = v;
17370         var h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin));
17371         if(h != this.lastHeight){
17372             this.lastHeight = h;
17373             this.el.setHeight(h);
17374             this.fireEvent("autosize", this, h);
17375         }
17376     }
17377 });/*
17378  * Based on:
17379  * Ext JS Library 1.1.1
17380  * Copyright(c) 2006-2007, Ext JS, LLC.
17381  *
17382  * Originally Released Under LGPL - original licence link has changed is not relivant.
17383  *
17384  * Fork - LGPL
17385  * <script type="text/javascript">
17386  */
17387  
17388
17389 /**
17390  * @class Roo.form.NumberField
17391  * @extends Roo.form.TextField
17392  * Numeric text field that provides automatic keystroke filtering and numeric validation.
17393  * @constructor
17394  * Creates a new NumberField
17395  * @param {Object} config Configuration options
17396  */
17397 Roo.form.NumberField = function(config){
17398     Roo.form.NumberField.superclass.constructor.call(this, config);
17399 };
17400
17401 Roo.extend(Roo.form.NumberField, Roo.form.TextField,  {
17402     /**
17403      * @cfg {String} fieldClass The default CSS class for the field (defaults to "x-form-field x-form-num-field")
17404      */
17405     fieldClass: "x-form-field x-form-num-field",
17406     /**
17407      * @cfg {Boolean} allowDecimals False to disallow decimal values (defaults to true)
17408      */
17409     allowDecimals : true,
17410     /**
17411      * @cfg {String} decimalSeparator Character(s) to allow as the decimal separator (defaults to '.')
17412      */
17413     decimalSeparator : ".",
17414     /**
17415      * @cfg {Number} decimalPrecision The maximum precision to display after the decimal separator (defaults to 2)
17416      */
17417     decimalPrecision : 2,
17418     /**
17419      * @cfg {Boolean} allowNegative False to prevent entering a negative sign (defaults to true)
17420      */
17421     allowNegative : true,
17422     /**
17423      * @cfg {Number} minValue The minimum allowed value (defaults to Number.NEGATIVE_INFINITY)
17424      */
17425     minValue : Number.NEGATIVE_INFINITY,
17426     /**
17427      * @cfg {Number} maxValue The maximum allowed value (defaults to Number.MAX_VALUE)
17428      */
17429     maxValue : Number.MAX_VALUE,
17430     /**
17431      * @cfg {String} minText Error text to display if the minimum value validation fails (defaults to "The minimum value for this field is {minValue}")
17432      */
17433     minText : "The minimum value for this field is {0}",
17434     /**
17435      * @cfg {String} maxText Error text to display if the maximum value validation fails (defaults to "The maximum value for this field is {maxValue}")
17436      */
17437     maxText : "The maximum value for this field is {0}",
17438     /**
17439      * @cfg {String} nanText Error text to display if the value is not a valid number.  For example, this can happen
17440      * if a valid character like '.' or '-' is left in the field with no number (defaults to "{value} is not a valid number")
17441      */
17442     nanText : "{0} is not a valid number",
17443
17444     // private
17445     initEvents : function(){
17446         Roo.form.NumberField.superclass.initEvents.call(this);
17447         var allowed = "0123456789";
17448         if(this.allowDecimals){
17449             allowed += this.decimalSeparator;
17450         }
17451         if(this.allowNegative){
17452             allowed += "-";
17453         }
17454         this.stripCharsRe = new RegExp('[^'+allowed+']', 'gi');
17455         var keyPress = function(e){
17456             var k = e.getKey();
17457             if(!Roo.isIE && (e.isSpecialKey() || k == e.BACKSPACE || k == e.DELETE)){
17458                 return;
17459             }
17460             var c = e.getCharCode();
17461             if(allowed.indexOf(String.fromCharCode(c)) === -1){
17462                 e.stopEvent();
17463             }
17464         };
17465         this.el.on("keypress", keyPress, this);
17466     },
17467
17468     // private
17469     validateValue : function(value){
17470         if(!Roo.form.NumberField.superclass.validateValue.call(this, value)){
17471             return false;
17472         }
17473         if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
17474              return true;
17475         }
17476         var num = this.parseValue(value);
17477         if(isNaN(num)){
17478             this.markInvalid(String.format(this.nanText, value));
17479             return false;
17480         }
17481         if(num < this.minValue){
17482             this.markInvalid(String.format(this.minText, this.minValue));
17483             return false;
17484         }
17485         if(num > this.maxValue){
17486             this.markInvalid(String.format(this.maxText, this.maxValue));
17487             return false;
17488         }
17489         return true;
17490     },
17491
17492     getValue : function(){
17493         return this.fixPrecision(this.parseValue(Roo.form.NumberField.superclass.getValue.call(this)));
17494     },
17495
17496     // private
17497     parseValue : function(value){
17498         value = parseFloat(String(value).replace(this.decimalSeparator, "."));
17499         return isNaN(value) ? '' : value;
17500     },
17501
17502     // private
17503     fixPrecision : function(value){
17504         var nan = isNaN(value);
17505         if(!this.allowDecimals || this.decimalPrecision == -1 || nan || !value){
17506             return nan ? '' : value;
17507         }
17508         return parseFloat(value).toFixed(this.decimalPrecision);
17509     },
17510
17511     setValue : function(v){
17512         v = this.fixPrecision(v);
17513         Roo.form.NumberField.superclass.setValue.call(this, String(v).replace(".", this.decimalSeparator));
17514     },
17515
17516     // private
17517     decimalPrecisionFcn : function(v){
17518         return Math.floor(v);
17519     },
17520
17521     beforeBlur : function(){
17522         var v = this.parseValue(this.getRawValue());
17523         if(v){
17524             this.setValue(v);
17525         }
17526     }
17527 });/*
17528  * Based on:
17529  * Ext JS Library 1.1.1
17530  * Copyright(c) 2006-2007, Ext JS, LLC.
17531  *
17532  * Originally Released Under LGPL - original licence link has changed is not relivant.
17533  *
17534  * Fork - LGPL
17535  * <script type="text/javascript">
17536  */
17537  
17538 /**
17539  * @class Roo.form.DateField
17540  * @extends Roo.form.TriggerField
17541  * Provides a date input field with a {@link Roo.DatePicker} dropdown and automatic date validation.
17542 * @constructor
17543 * Create a new DateField
17544 * @param {Object} config
17545  */
17546 Roo.form.DateField = function(config)
17547 {
17548     Roo.form.DateField.superclass.constructor.call(this, config);
17549     
17550       this.addEvents({
17551          
17552         /**
17553          * @event select
17554          * Fires when a date is selected
17555              * @param {Roo.form.DateField} combo This combo box
17556              * @param {Date} date The date selected
17557              */
17558         'select' : true
17559          
17560     });
17561     
17562     
17563     if(typeof this.minValue == "string") {
17564         this.minValue = this.parseDate(this.minValue);
17565     }
17566     if(typeof this.maxValue == "string") {
17567         this.maxValue = this.parseDate(this.maxValue);
17568     }
17569     this.ddMatch = null;
17570     if(this.disabledDates){
17571         var dd = this.disabledDates;
17572         var re = "(?:";
17573         for(var i = 0; i < dd.length; i++){
17574             re += dd[i];
17575             if(i != dd.length-1) {
17576                 re += "|";
17577             }
17578         }
17579         this.ddMatch = new RegExp(re + ")");
17580     }
17581 };
17582
17583 Roo.extend(Roo.form.DateField, Roo.form.TriggerField,  {
17584     /**
17585      * @cfg {String} format
17586      * The default date format string which can be overriden for localization support.  The format must be
17587      * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
17588      */
17589     format : "m/d/y",
17590     /**
17591      * @cfg {String} altFormats
17592      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
17593      * format (defaults to 'm/d/Y|m-d-y|m-d-Y|m/d|m-d|d').
17594      */
17595     altFormats : "m/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d",
17596     /**
17597      * @cfg {Array} disabledDays
17598      * An array of days to disable, 0 based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
17599      */
17600     disabledDays : null,
17601     /**
17602      * @cfg {String} disabledDaysText
17603      * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled')
17604      */
17605     disabledDaysText : "Disabled",
17606     /**
17607      * @cfg {Array} disabledDates
17608      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
17609      * expression so they are very powerful. Some examples:
17610      * <ul>
17611      * <li>["03/08/2003", "09/16/2003"] would disable those exact dates</li>
17612      * <li>["03/08", "09/16"] would disable those days for every year</li>
17613      * <li>["^03/08"] would only match the beginning (useful if you are using short years)</li>
17614      * <li>["03/../2006"] would disable every day in March 2006</li>
17615      * <li>["^03"] would disable every day in every March</li>
17616      * </ul>
17617      * In order to support regular expressions, if you are using a date format that has "." in it, you will have to
17618      * escape the dot when restricting dates. For example: ["03\\.08\\.03"].
17619      */
17620     disabledDates : null,
17621     /**
17622      * @cfg {String} disabledDatesText
17623      * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
17624      */
17625     disabledDatesText : "Disabled",
17626         
17627         
17628         /**
17629      * @cfg {Date/String} zeroValue
17630      * if the date is less that this number, then the field is rendered as empty
17631      * default is 1800
17632      */
17633         zeroValue : '1800-01-01',
17634         
17635         
17636     /**
17637      * @cfg {Date/String} minValue
17638      * The minimum allowed date. Can be either a Javascript date object or a string date in a
17639      * valid format (defaults to null).
17640      */
17641     minValue : null,
17642     /**
17643      * @cfg {Date/String} maxValue
17644      * The maximum allowed date. Can be either a Javascript date object or a string date in a
17645      * valid format (defaults to null).
17646      */
17647     maxValue : null,
17648     /**
17649      * @cfg {String} minText
17650      * The error text to display when the date in the cell is before minValue (defaults to
17651      * 'The date in this field must be after {minValue}').
17652      */
17653     minText : "The date in this field must be equal to or after {0}",
17654     /**
17655      * @cfg {String} maxText
17656      * The error text to display when the date in the cell is after maxValue (defaults to
17657      * 'The date in this field must be before {maxValue}').
17658      */
17659     maxText : "The date in this field must be equal to or before {0}",
17660     /**
17661      * @cfg {String} invalidText
17662      * The error text to display when the date in the field is invalid (defaults to
17663      * '{value} is not a valid date - it must be in the format {format}').
17664      */
17665     invalidText : "{0} is not a valid date - it must be in the format {1}",
17666     /**
17667      * @cfg {String} triggerClass
17668      * An additional CSS class used to style the trigger button.  The trigger will always get the
17669      * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-date-trigger'
17670      * which displays a calendar icon).
17671      */
17672     triggerClass : 'x-form-date-trigger',
17673     
17674
17675     /**
17676      * @cfg {Boolean} useIso
17677      * if enabled, then the date field will use a hidden field to store the 
17678      * real value as iso formated date. default (false)
17679      */ 
17680     useIso : false,
17681     /**
17682      * @cfg {String/Object} autoCreate
17683      * A DomHelper element spec, or true for a default element spec (defaults to
17684      * {tag: "input", type: "text", size: "10", autocomplete: "off"})
17685      */ 
17686     // private
17687     defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"},
17688     
17689     // private
17690     hiddenField: false,
17691     
17692     onRender : function(ct, position)
17693     {
17694         Roo.form.DateField.superclass.onRender.call(this, ct, position);
17695         if (this.useIso) {
17696             //this.el.dom.removeAttribute('name'); 
17697             Roo.log("Changing name?");
17698             this.el.dom.setAttribute('name', this.name + '____hidden___' ); 
17699             this.hiddenField = this.el.insertSibling({ tag:'input', type:'hidden', name: this.name },
17700                     'before', true);
17701             this.hiddenField.value = this.value ? this.formatDate(this.value, 'Y-m-d') : '';
17702             // prevent input submission
17703             this.hiddenName = this.name;
17704         }
17705             
17706             
17707     },
17708     
17709     // private
17710     validateValue : function(value)
17711     {
17712         value = this.formatDate(value);
17713         if(!Roo.form.DateField.superclass.validateValue.call(this, value)){
17714             Roo.log('super failed');
17715             return false;
17716         }
17717         if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
17718              return true;
17719         }
17720         var svalue = value;
17721         value = this.parseDate(value);
17722         if(!value){
17723             Roo.log('parse date failed' + svalue);
17724             this.markInvalid(String.format(this.invalidText, svalue, this.format));
17725             return false;
17726         }
17727         var time = value.getTime();
17728         if(this.minValue && time < this.minValue.getTime()){
17729             this.markInvalid(String.format(this.minText, this.formatDate(this.minValue)));
17730             return false;
17731         }
17732         if(this.maxValue && time > this.maxValue.getTime()){
17733             this.markInvalid(String.format(this.maxText, this.formatDate(this.maxValue)));
17734             return false;
17735         }
17736         if(this.disabledDays){
17737             var day = value.getDay();
17738             for(var i = 0; i < this.disabledDays.length; i++) {
17739                 if(day === this.disabledDays[i]){
17740                     this.markInvalid(this.disabledDaysText);
17741                     return false;
17742                 }
17743             }
17744         }
17745         var fvalue = this.formatDate(value);
17746         if(this.ddMatch && this.ddMatch.test(fvalue)){
17747             this.markInvalid(String.format(this.disabledDatesText, fvalue));
17748             return false;
17749         }
17750         return true;
17751     },
17752
17753     // private
17754     // Provides logic to override the default TriggerField.validateBlur which just returns true
17755     validateBlur : function(){
17756         return !this.menu || !this.menu.isVisible();
17757     },
17758     
17759     getName: function()
17760     {
17761         // returns hidden if it's set..
17762         if (!this.rendered) {return ''};
17763         return !this.hiddenName && this.el.dom.name  ? this.el.dom.name : (this.hiddenName || '');
17764         
17765     },
17766
17767     /**
17768      * Returns the current date value of the date field.
17769      * @return {Date} The date value
17770      */
17771     getValue : function(){
17772         
17773         return  this.hiddenField ?
17774                 this.hiddenField.value :
17775                 this.parseDate(Roo.form.DateField.superclass.getValue.call(this)) || "";
17776     },
17777
17778     /**
17779      * Sets the value of the date field.  You can pass a date object or any string that can be parsed into a valid
17780      * date, using DateField.format as the date format, according to the same rules as {@link Date#parseDate}
17781      * (the default format used is "m/d/y").
17782      * <br />Usage:
17783      * <pre><code>
17784 //All of these calls set the same date value (May 4, 2006)
17785
17786 //Pass a date object:
17787 var dt = new Date('5/4/06');
17788 dateField.setValue(dt);
17789
17790 //Pass a date string (default format):
17791 dateField.setValue('5/4/06');
17792
17793 //Pass a date string (custom format):
17794 dateField.format = 'Y-m-d';
17795 dateField.setValue('2006-5-4');
17796 </code></pre>
17797      * @param {String/Date} date The date or valid date string
17798      */
17799     setValue : function(date){
17800         if (this.hiddenField) {
17801             this.hiddenField.value = this.formatDate(this.parseDate(date), 'Y-m-d');
17802         }
17803         Roo.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
17804         // make sure the value field is always stored as a date..
17805         this.value = this.parseDate(date);
17806         
17807         
17808     },
17809
17810     // private
17811     parseDate : function(value){
17812                 
17813                 if (value instanceof Date) {
17814                         if (value < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
17815                                 return  '';
17816                         }
17817                         return value;
17818                 }
17819                 
17820                 
17821         if(!value || value instanceof Date){
17822             return value;
17823         }
17824         var v = Date.parseDate(value, this.format);
17825          if (!v && this.useIso) {
17826             v = Date.parseDate(value, 'Y-m-d');
17827         }
17828         if(!v && this.altFormats){
17829             if(!this.altFormatsArray){
17830                 this.altFormatsArray = this.altFormats.split("|");
17831             }
17832             for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){
17833                 v = Date.parseDate(value, this.altFormatsArray[i]);
17834             }
17835         }
17836                 if (v < Date.parseDate(this.zeroValue, 'Y-m-d') ) {
17837                         v = '';
17838                 }
17839         return v;
17840     },
17841
17842     // private
17843     formatDate : function(date, fmt){
17844         return (!date || !(date instanceof Date)) ?
17845                date : date.dateFormat(fmt || this.format);
17846     },
17847
17848     // private
17849     menuListeners : {
17850         select: function(m, d){
17851             
17852             this.setValue(d);
17853             this.fireEvent('select', this, d);
17854         },
17855         show : function(){ // retain focus styling
17856             this.onFocus();
17857         },
17858         hide : function(){
17859             this.focus.defer(10, this);
17860             var ml = this.menuListeners;
17861             this.menu.un("select", ml.select,  this);
17862             this.menu.un("show", ml.show,  this);
17863             this.menu.un("hide", ml.hide,  this);
17864         }
17865     },
17866
17867     // private
17868     // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker
17869     onTriggerClick : function(){
17870         if(this.disabled){
17871             return;
17872         }
17873         if(this.menu == null){
17874             this.menu = new Roo.menu.DateMenu();
17875         }
17876         Roo.apply(this.menu.picker,  {
17877             showClear: this.allowBlank,
17878             minDate : this.minValue,
17879             maxDate : this.maxValue,
17880             disabledDatesRE : this.ddMatch,
17881             disabledDatesText : this.disabledDatesText,
17882             disabledDays : this.disabledDays,
17883             disabledDaysText : this.disabledDaysText,
17884             format : this.useIso ? 'Y-m-d' : this.format,
17885             minText : String.format(this.minText, this.formatDate(this.minValue)),
17886             maxText : String.format(this.maxText, this.formatDate(this.maxValue))
17887         });
17888         this.menu.on(Roo.apply({}, this.menuListeners, {
17889             scope:this
17890         }));
17891         this.menu.picker.setValue(this.getValue() || new Date());
17892         this.menu.show(this.el, "tl-bl?");
17893     },
17894
17895     beforeBlur : function(){
17896         var v = this.parseDate(this.getRawValue());
17897         if(v){
17898             this.setValue(v);
17899         }
17900     },
17901
17902     /*@
17903      * overide
17904      * 
17905      */
17906     isDirty : function() {
17907         if(this.disabled) {
17908             return false;
17909         }
17910         
17911         if(typeof(this.startValue) === 'undefined'){
17912             return false;
17913         }
17914         
17915         return String(this.getValue()) !== String(this.startValue);
17916         
17917     },
17918     // @overide
17919     cleanLeadingSpace : function(e)
17920     {
17921        return;
17922     }
17923     
17924 });/*
17925  * Based on:
17926  * Ext JS Library 1.1.1
17927  * Copyright(c) 2006-2007, Ext JS, LLC.
17928  *
17929  * Originally Released Under LGPL - original licence link has changed is not relivant.
17930  *
17931  * Fork - LGPL
17932  * <script type="text/javascript">
17933  */
17934  
17935 /**
17936  * @class Roo.form.MonthField
17937  * @extends Roo.form.TriggerField
17938  * Provides a date input field with a {@link Roo.DatePicker} dropdown and automatic date validation.
17939 * @constructor
17940 * Create a new MonthField
17941 * @param {Object} config
17942  */
17943 Roo.form.MonthField = function(config){
17944     
17945     Roo.form.MonthField.superclass.constructor.call(this, config);
17946     
17947       this.addEvents({
17948          
17949         /**
17950          * @event select
17951          * Fires when a date is selected
17952              * @param {Roo.form.MonthFieeld} combo This combo box
17953              * @param {Date} date The date selected
17954              */
17955         'select' : true
17956          
17957     });
17958     
17959     
17960     if(typeof this.minValue == "string") {
17961         this.minValue = this.parseDate(this.minValue);
17962     }
17963     if(typeof this.maxValue == "string") {
17964         this.maxValue = this.parseDate(this.maxValue);
17965     }
17966     this.ddMatch = null;
17967     if(this.disabledDates){
17968         var dd = this.disabledDates;
17969         var re = "(?:";
17970         for(var i = 0; i < dd.length; i++){
17971             re += dd[i];
17972             if(i != dd.length-1) {
17973                 re += "|";
17974             }
17975         }
17976         this.ddMatch = new RegExp(re + ")");
17977     }
17978 };
17979
17980 Roo.extend(Roo.form.MonthField, Roo.form.TriggerField,  {
17981     /**
17982      * @cfg {String} format
17983      * The default date format string which can be overriden for localization support.  The format must be
17984      * valid according to {@link Date#parseDate} (defaults to 'm/d/y').
17985      */
17986     format : "M Y",
17987     /**
17988      * @cfg {String} altFormats
17989      * Multiple date formats separated by "|" to try when parsing a user input value and it doesn't match the defined
17990      * format (defaults to 'm/d/Y|m-d-y|m-d-Y|m/d|m-d|d').
17991      */
17992     altFormats : "M Y|m/Y|m-y|m-Y|my|mY",
17993     /**
17994      * @cfg {Array} disabledDays
17995      * An array of days to disable, 0 based. For example, [0, 6] disables Sunday and Saturday (defaults to null).
17996      */
17997     disabledDays : [0,1,2,3,4,5,6],
17998     /**
17999      * @cfg {String} disabledDaysText
18000      * The tooltip to display when the date falls on a disabled day (defaults to 'Disabled')
18001      */
18002     disabledDaysText : "Disabled",
18003     /**
18004      * @cfg {Array} disabledDates
18005      * An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular
18006      * expression so they are very powerful. Some examples:
18007      * <ul>
18008      * <li>["03/08/2003", "09/16/2003"] would disable those exact dates</li>
18009      * <li>["03/08", "09/16"] would disable those days for every year</li>
18010      * <li>["^03/08"] would only match the beginning (useful if you are using short years)</li>
18011      * <li>["03/../2006"] would disable every day in March 2006</li>
18012      * <li>["^03"] would disable every day in every March</li>
18013      * </ul>
18014      * In order to support regular expressions, if you are using a date format that has "." in it, you will have to
18015      * escape the dot when restricting dates. For example: ["03\\.08\\.03"].
18016      */
18017     disabledDates : null,
18018     /**
18019      * @cfg {String} disabledDatesText
18020      * The tooltip text to display when the date falls on a disabled date (defaults to 'Disabled')
18021      */
18022     disabledDatesText : "Disabled",
18023     /**
18024      * @cfg {Date/String} minValue
18025      * The minimum allowed date. Can be either a Javascript date object or a string date in a
18026      * valid format (defaults to null).
18027      */
18028     minValue : null,
18029     /**
18030      * @cfg {Date/String} maxValue
18031      * The maximum allowed date. Can be either a Javascript date object or a string date in a
18032      * valid format (defaults to null).
18033      */
18034     maxValue : null,
18035     /**
18036      * @cfg {String} minText
18037      * The error text to display when the date in the cell is before minValue (defaults to
18038      * 'The date in this field must be after {minValue}').
18039      */
18040     minText : "The date in this field must be equal to or after {0}",
18041     /**
18042      * @cfg {String} maxTextf
18043      * The error text to display when the date in the cell is after maxValue (defaults to
18044      * 'The date in this field must be before {maxValue}').
18045      */
18046     maxText : "The date in this field must be equal to or before {0}",
18047     /**
18048      * @cfg {String} invalidText
18049      * The error text to display when the date in the field is invalid (defaults to
18050      * '{value} is not a valid date - it must be in the format {format}').
18051      */
18052     invalidText : "{0} is not a valid date - it must be in the format {1}",
18053     /**
18054      * @cfg {String} triggerClass
18055      * An additional CSS class used to style the trigger button.  The trigger will always get the
18056      * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-date-trigger'
18057      * which displays a calendar icon).
18058      */
18059     triggerClass : 'x-form-date-trigger',
18060     
18061
18062     /**
18063      * @cfg {Boolean} useIso
18064      * if enabled, then the date field will use a hidden field to store the 
18065      * real value as iso formated date. default (true)
18066      */ 
18067     useIso : true,
18068     /**
18069      * @cfg {String/Object} autoCreate
18070      * A DomHelper element spec, or true for a default element spec (defaults to
18071      * {tag: "input", type: "text", size: "10", autocomplete: "off"})
18072      */ 
18073     // private
18074     defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "new-password"},
18075     
18076     // private
18077     hiddenField: false,
18078     
18079     hideMonthPicker : false,
18080     
18081     onRender : function(ct, position)
18082     {
18083         Roo.form.MonthField.superclass.onRender.call(this, ct, position);
18084         if (this.useIso) {
18085             this.el.dom.removeAttribute('name'); 
18086             this.hiddenField = this.el.insertSibling({ tag:'input', type:'hidden', name: this.name },
18087                     'before', true);
18088             this.hiddenField.value = this.value ? this.formatDate(this.value, 'Y-m-d') : '';
18089             // prevent input submission
18090             this.hiddenName = this.name;
18091         }
18092             
18093             
18094     },
18095     
18096     // private
18097     validateValue : function(value)
18098     {
18099         value = this.formatDate(value);
18100         if(!Roo.form.MonthField.superclass.validateValue.call(this, value)){
18101             return false;
18102         }
18103         if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
18104              return true;
18105         }
18106         var svalue = value;
18107         value = this.parseDate(value);
18108         if(!value){
18109             this.markInvalid(String.format(this.invalidText, svalue, this.format));
18110             return false;
18111         }
18112         var time = value.getTime();
18113         if(this.minValue && time < this.minValue.getTime()){
18114             this.markInvalid(String.format(this.minText, this.formatDate(this.minValue)));
18115             return false;
18116         }
18117         if(this.maxValue && time > this.maxValue.getTime()){
18118             this.markInvalid(String.format(this.maxText, this.formatDate(this.maxValue)));
18119             return false;
18120         }
18121         /*if(this.disabledDays){
18122             var day = value.getDay();
18123             for(var i = 0; i < this.disabledDays.length; i++) {
18124                 if(day === this.disabledDays[i]){
18125                     this.markInvalid(this.disabledDaysText);
18126                     return false;
18127                 }
18128             }
18129         }
18130         */
18131         var fvalue = this.formatDate(value);
18132         /*if(this.ddMatch && this.ddMatch.test(fvalue)){
18133             this.markInvalid(String.format(this.disabledDatesText, fvalue));
18134             return false;
18135         }
18136         */
18137         return true;
18138     },
18139
18140     // private
18141     // Provides logic to override the default TriggerField.validateBlur which just returns true
18142     validateBlur : function(){
18143         return !this.menu || !this.menu.isVisible();
18144     },
18145
18146     /**
18147      * Returns the current date value of the date field.
18148      * @return {Date} The date value
18149      */
18150     getValue : function(){
18151         
18152         
18153         
18154         return  this.hiddenField ?
18155                 this.hiddenField.value :
18156                 this.parseDate(Roo.form.MonthField.superclass.getValue.call(this)) || "";
18157     },
18158
18159     /**
18160      * Sets the value of the date field.  You can pass a date object or any string that can be parsed into a valid
18161      * date, using MonthField.format as the date format, according to the same rules as {@link Date#parseDate}
18162      * (the default format used is "m/d/y").
18163      * <br />Usage:
18164      * <pre><code>
18165 //All of these calls set the same date value (May 4, 2006)
18166
18167 //Pass a date object:
18168 var dt = new Date('5/4/06');
18169 monthField.setValue(dt);
18170
18171 //Pass a date string (default format):
18172 monthField.setValue('5/4/06');
18173
18174 //Pass a date string (custom format):
18175 monthField.format = 'Y-m-d';
18176 monthField.setValue('2006-5-4');
18177 </code></pre>
18178      * @param {String/Date} date The date or valid date string
18179      */
18180     setValue : function(date){
18181         Roo.log('month setValue' + date);
18182         // can only be first of month..
18183         
18184         var val = this.parseDate(date);
18185         
18186         if (this.hiddenField) {
18187             this.hiddenField.value = this.formatDate(this.parseDate(date), 'Y-m-d');
18188         }
18189         Roo.form.MonthField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
18190         this.value = this.parseDate(date);
18191     },
18192
18193     // private
18194     parseDate : function(value){
18195         if(!value || value instanceof Date){
18196             value = value ? Date.parseDate(value.format('Y-m') + '-01', 'Y-m-d') : null;
18197             return value;
18198         }
18199         var v = Date.parseDate(value, this.format);
18200         if (!v && this.useIso) {
18201             v = Date.parseDate(value, 'Y-m-d');
18202         }
18203         if (v) {
18204             // 
18205             v = Date.parseDate(v.format('Y-m') +'-01', 'Y-m-d');
18206         }
18207         
18208         
18209         if(!v && this.altFormats){
18210             if(!this.altFormatsArray){
18211                 this.altFormatsArray = this.altFormats.split("|");
18212             }
18213             for(var i = 0, len = this.altFormatsArray.length; i < len && !v; i++){
18214                 v = Date.parseDate(value, this.altFormatsArray[i]);
18215             }
18216         }
18217         return v;
18218     },
18219
18220     // private
18221     formatDate : function(date, fmt){
18222         return (!date || !(date instanceof Date)) ?
18223                date : date.dateFormat(fmt || this.format);
18224     },
18225
18226     // private
18227     menuListeners : {
18228         select: function(m, d){
18229             this.setValue(d);
18230             this.fireEvent('select', this, d);
18231         },
18232         show : function(){ // retain focus styling
18233             this.onFocus();
18234         },
18235         hide : function(){
18236             this.focus.defer(10, this);
18237             var ml = this.menuListeners;
18238             this.menu.un("select", ml.select,  this);
18239             this.menu.un("show", ml.show,  this);
18240             this.menu.un("hide", ml.hide,  this);
18241         }
18242     },
18243     // private
18244     // Implements the default empty TriggerField.onTriggerClick function to display the DatePicker
18245     onTriggerClick : function(){
18246         if(this.disabled){
18247             return;
18248         }
18249         if(this.menu == null){
18250             this.menu = new Roo.menu.DateMenu();
18251            
18252         }
18253         
18254         Roo.apply(this.menu.picker,  {
18255             
18256             showClear: this.allowBlank,
18257             minDate : this.minValue,
18258             maxDate : this.maxValue,
18259             disabledDatesRE : this.ddMatch,
18260             disabledDatesText : this.disabledDatesText,
18261             
18262             format : this.useIso ? 'Y-m-d' : this.format,
18263             minText : String.format(this.minText, this.formatDate(this.minValue)),
18264             maxText : String.format(this.maxText, this.formatDate(this.maxValue))
18265             
18266         });
18267          this.menu.on(Roo.apply({}, this.menuListeners, {
18268             scope:this
18269         }));
18270        
18271         
18272         var m = this.menu;
18273         var p = m.picker;
18274         
18275         // hide month picker get's called when we called by 'before hide';
18276         
18277         var ignorehide = true;
18278         p.hideMonthPicker  = function(disableAnim){
18279             if (ignorehide) {
18280                 return;
18281             }
18282              if(this.monthPicker){
18283                 Roo.log("hideMonthPicker called");
18284                 if(disableAnim === true){
18285                     this.monthPicker.hide();
18286                 }else{
18287                     this.monthPicker.slideOut('t', {duration:.2});
18288                     p.setValue(new Date(m.picker.mpSelYear, m.picker.mpSelMonth, 1));
18289                     p.fireEvent("select", this, this.value);
18290                     m.hide();
18291                 }
18292             }
18293         }
18294         
18295         Roo.log('picker set value');
18296         Roo.log(this.getValue());
18297         p.setValue(this.getValue() ? this.parseDate(this.getValue()) : new Date());
18298         m.show(this.el, 'tl-bl?');
18299         ignorehide  = false;
18300         // this will trigger hideMonthPicker..
18301         
18302         
18303         // hidden the day picker
18304         Roo.select('.x-date-picker table', true).first().dom.style.visibility = "hidden";
18305         
18306         
18307         
18308       
18309         
18310         p.showMonthPicker.defer(100, p);
18311     
18312         
18313        
18314     },
18315
18316     beforeBlur : function(){
18317         var v = this.parseDate(this.getRawValue());
18318         if(v){
18319             this.setValue(v);
18320         }
18321     }
18322
18323     /** @cfg {Boolean} grow @hide */
18324     /** @cfg {Number} growMin @hide */
18325     /** @cfg {Number} growMax @hide */
18326     /**
18327      * @hide
18328      * @method autoSize
18329      */
18330 });/*
18331  * Based on:
18332  * Ext JS Library 1.1.1
18333  * Copyright(c) 2006-2007, Ext JS, LLC.
18334  *
18335  * Originally Released Under LGPL - original licence link has changed is not relivant.
18336  *
18337  * Fork - LGPL
18338  * <script type="text/javascript">
18339  */
18340  
18341
18342 /**
18343  * @class Roo.form.ComboBox
18344  * @extends Roo.form.TriggerField
18345  * A combobox control with support for autocomplete, remote-loading, paging and many other features.
18346  * @constructor
18347  * Create a new ComboBox.
18348  * @param {Object} config Configuration options
18349  */
18350 Roo.form.ComboBox = function(config){
18351     Roo.form.ComboBox.superclass.constructor.call(this, config);
18352     this.addEvents({
18353         /**
18354          * @event expand
18355          * Fires when the dropdown list is expanded
18356              * @param {Roo.form.ComboBox} combo This combo box
18357              */
18358         'expand' : true,
18359         /**
18360          * @event collapse
18361          * Fires when the dropdown list is collapsed
18362              * @param {Roo.form.ComboBox} combo This combo box
18363              */
18364         'collapse' : true,
18365         /**
18366          * @event beforeselect
18367          * Fires before a list item is selected. Return false to cancel the selection.
18368              * @param {Roo.form.ComboBox} combo This combo box
18369              * @param {Roo.data.Record} record The data record returned from the underlying store
18370              * @param {Number} index The index of the selected item in the dropdown list
18371              */
18372         'beforeselect' : true,
18373         /**
18374          * @event select
18375          * Fires when a list item is selected
18376              * @param {Roo.form.ComboBox} combo This combo box
18377              * @param {Roo.data.Record} record The data record returned from the underlying store (or false on clear)
18378              * @param {Number} index The index of the selected item in the dropdown list
18379              */
18380         'select' : true,
18381         /**
18382          * @event beforequery
18383          * Fires before all queries are processed. Return false to cancel the query or set cancel to true.
18384          * The event object passed has these properties:
18385              * @param {Roo.form.ComboBox} combo This combo box
18386              * @param {String} query The query
18387              * @param {Boolean} forceAll true to force "all" query
18388              * @param {Boolean} cancel true to cancel the query
18389              * @param {Object} e The query event object
18390              */
18391         'beforequery': true,
18392          /**
18393          * @event add
18394          * Fires when the 'add' icon is pressed (add a listener to enable add button)
18395              * @param {Roo.form.ComboBox} combo This combo box
18396              */
18397         'add' : true,
18398         /**
18399          * @event edit
18400          * Fires when the 'edit' icon is pressed (add a listener to enable add button)
18401              * @param {Roo.form.ComboBox} combo This combo box
18402              * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
18403              */
18404         'edit' : true
18405         
18406         
18407     });
18408     if(this.transform){
18409         this.allowDomMove = false;
18410         var s = Roo.getDom(this.transform);
18411         if(!this.hiddenName){
18412             this.hiddenName = s.name;
18413         }
18414         if(!this.store){
18415             this.mode = 'local';
18416             var d = [], opts = s.options;
18417             for(var i = 0, len = opts.length;i < len; i++){
18418                 var o = opts[i];
18419                 var value = (Roo.isIE ? o.getAttributeNode('value').specified : o.hasAttribute('value')) ? o.value : o.text;
18420                 if(o.selected) {
18421                     this.value = value;
18422                 }
18423                 d.push([value, o.text]);
18424             }
18425             this.store = new Roo.data.SimpleStore({
18426                 'id': 0,
18427                 fields: ['value', 'text'],
18428                 data : d
18429             });
18430             this.valueField = 'value';
18431             this.displayField = 'text';
18432         }
18433         s.name = Roo.id(); // wipe out the name in case somewhere else they have a reference
18434         if(!this.lazyRender){
18435             this.target = true;
18436             this.el = Roo.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
18437             s.parentNode.removeChild(s); // remove it
18438             this.render(this.el.parentNode);
18439         }else{
18440             s.parentNode.removeChild(s); // remove it
18441         }
18442
18443     }
18444     if (this.store) {
18445         this.store = Roo.factory(this.store, Roo.data);
18446     }
18447     
18448     this.selectedIndex = -1;
18449     if(this.mode == 'local'){
18450         if(config.queryDelay === undefined){
18451             this.queryDelay = 10;
18452         }
18453         if(config.minChars === undefined){
18454             this.minChars = 0;
18455         }
18456     }
18457 };
18458
18459 Roo.extend(Roo.form.ComboBox, Roo.form.TriggerField, {
18460     /**
18461      * @cfg {String/HTMLElement/Element} transform The id, DOM node or element of an existing select to convert to a ComboBox
18462      */
18463     /**
18464      * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
18465      * rendering into an Roo.Editor, defaults to false)
18466      */
18467     /**
18468      * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
18469      * {tag: "input", type: "text", size: "24", autocomplete: "off"})
18470      */
18471     /**
18472      * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
18473      */
18474     /**
18475      * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
18476      * the dropdown list (defaults to undefined, with no header element)
18477      */
18478
18479      /**
18480      * @cfg {String/Roo.Template} tpl The template to use to render the output
18481      */
18482      
18483     // private
18484     defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
18485     /**
18486      * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
18487      */
18488     listWidth: undefined,
18489     /**
18490      * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
18491      * mode = 'remote' or 'text' if mode = 'local')
18492      */
18493     displayField: undefined,
18494     /**
18495      * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
18496      * mode = 'remote' or 'value' if mode = 'local'). 
18497      * Note: use of a valueField requires the user make a selection
18498      * in order for a value to be mapped.
18499      */
18500     valueField: undefined,
18501     
18502     
18503     /**
18504      * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
18505      * field's data value (defaults to the underlying DOM element's name)
18506      */
18507     hiddenName: undefined,
18508     /**
18509      * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
18510      */
18511     listClass: '',
18512     /**
18513      * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
18514      */
18515     selectedClass: 'x-combo-selected',
18516     /**
18517      * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always get the
18518      * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-arrow-trigger'
18519      * which displays a downward arrow icon).
18520      */
18521     triggerClass : 'x-form-arrow-trigger',
18522     /**
18523      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
18524      */
18525     shadow:'sides',
18526     /**
18527      * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
18528      * anchor positions (defaults to 'tl-bl')
18529      */
18530     listAlign: 'tl-bl?',
18531     /**
18532      * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
18533      */
18534     maxHeight: 300,
18535     /**
18536      * @cfg {String} triggerAction The action to execute when the trigger field is activated.  Use 'all' to run the
18537      * query specified by the allQuery config option (defaults to 'query')
18538      */
18539     triggerAction: 'query',
18540     /**
18541      * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
18542      * (defaults to 4, does not apply if editable = false)
18543      */
18544     minChars : 4,
18545     /**
18546      * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
18547      * delay (typeAheadDelay) if it matches a known value (defaults to false)
18548      */
18549     typeAhead: false,
18550     /**
18551      * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
18552      * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
18553      */
18554     queryDelay: 500,
18555     /**
18556      * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
18557      * filter queries will execute with page start and limit parameters.  Only applies when mode = 'remote' (defaults to 0)
18558      */
18559     pageSize: 0,
18560     /**
18561      * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus.  Only applies
18562      * when editable = true (defaults to false)
18563      */
18564     selectOnFocus:false,
18565     /**
18566      * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
18567      */
18568     queryParam: 'query',
18569     /**
18570      * @cfg {String} loadingText The text to display in the dropdown list while data is loading.  Only applies
18571      * when mode = 'remote' (defaults to 'Loading...')
18572      */
18573     loadingText: 'Loading...',
18574     /**
18575      * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
18576      */
18577     resizable: false,
18578     /**
18579      * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
18580      */
18581     handleHeight : 8,
18582     /**
18583      * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
18584      * traditional select (defaults to true)
18585      */
18586     editable: true,
18587     /**
18588      * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
18589      */
18590     allQuery: '',
18591     /**
18592      * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
18593      */
18594     mode: 'remote',
18595     /**
18596      * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
18597      * listWidth has a higher value)
18598      */
18599     minListWidth : 70,
18600     /**
18601      * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
18602      * allow the user to set arbitrary text into the field (defaults to false)
18603      */
18604     forceSelection:false,
18605     /**
18606      * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
18607      * if typeAhead = true (defaults to 250)
18608      */
18609     typeAheadDelay : 250,
18610     /**
18611      * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
18612      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
18613      */
18614     valueNotFoundText : undefined,
18615     /**
18616      * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
18617      */
18618     blockFocus : false,
18619     
18620     /**
18621      * @cfg {Boolean} disableClear Disable showing of clear button.
18622      */
18623     disableClear : false,
18624     /**
18625      * @cfg {Boolean} alwaysQuery  Disable caching of results, and always send query
18626      */
18627     alwaysQuery : false,
18628     
18629     //private
18630     addicon : false,
18631     editicon: false,
18632     
18633     // element that contains real text value.. (when hidden is used..)
18634      
18635     // private
18636     onRender : function(ct, position)
18637     {
18638         Roo.form.ComboBox.superclass.onRender.call(this, ct, position);
18639         
18640         if(this.hiddenName){
18641             this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id:  (this.hiddenId||this.hiddenName)},
18642                     'before', true);
18643             this.hiddenField.value =
18644                 this.hiddenValue !== undefined ? this.hiddenValue :
18645                 this.value !== undefined ? this.value : '';
18646
18647             // prevent input submission
18648             this.el.dom.removeAttribute('name');
18649              
18650              
18651         }
18652         
18653         if(Roo.isGecko){
18654             this.el.dom.setAttribute('autocomplete', 'off');
18655         }
18656
18657         var cls = 'x-combo-list';
18658
18659         this.list = new Roo.Layer({
18660             shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
18661         });
18662
18663         var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
18664         this.list.setWidth(lw);
18665         this.list.swallowEvent('mousewheel');
18666         this.assetHeight = 0;
18667
18668         if(this.title){
18669             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
18670             this.assetHeight += this.header.getHeight();
18671         }
18672
18673         this.innerList = this.list.createChild({cls:cls+'-inner'});
18674         this.innerList.on('mouseover', this.onViewOver, this);
18675         this.innerList.on('mousemove', this.onViewMove, this);
18676         this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
18677         
18678         if(this.allowBlank && !this.pageSize && !this.disableClear){
18679             this.footer = this.list.createChild({cls:cls+'-ft'});
18680             this.pageTb = new Roo.Toolbar(this.footer);
18681            
18682         }
18683         if(this.pageSize){
18684             this.footer = this.list.createChild({cls:cls+'-ft'});
18685             this.pageTb = new Roo.PagingToolbar(this.footer, this.store,
18686                     {pageSize: this.pageSize});
18687             
18688         }
18689         
18690         if (this.pageTb && this.allowBlank && !this.disableClear) {
18691             var _this = this;
18692             this.pageTb.add(new Roo.Toolbar.Fill(), {
18693                 cls: 'x-btn-icon x-btn-clear',
18694                 text: '&#160;',
18695                 handler: function()
18696                 {
18697                     _this.collapse();
18698                     _this.clearValue();
18699                     _this.onSelect(false, -1);
18700                 }
18701             });
18702         }
18703         if (this.footer) {
18704             this.assetHeight += this.footer.getHeight();
18705         }
18706         
18707
18708         if(!this.tpl){
18709             this.tpl = '<div class="'+cls+'-item">{' + this.displayField + '}</div>';
18710         }
18711
18712         this.view = new Roo.View(this.innerList, this.tpl, {
18713             singleSelect:true,
18714             store: this.store,
18715             selectedClass: this.selectedClass
18716         });
18717
18718         this.view.on('click', this.onViewClick, this);
18719
18720         this.store.on('beforeload', this.onBeforeLoad, this);
18721         this.store.on('load', this.onLoad, this);
18722         this.store.on('loadexception', this.onLoadException, this);
18723
18724         if(this.resizable){
18725             this.resizer = new Roo.Resizable(this.list,  {
18726                pinned:true, handles:'se'
18727             });
18728             this.resizer.on('resize', function(r, w, h){
18729                 this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
18730                 this.listWidth = w;
18731                 this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
18732                 this.restrictHeight();
18733             }, this);
18734             this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
18735         }
18736         if(!this.editable){
18737             this.editable = true;
18738             this.setEditable(false);
18739         }  
18740         
18741         
18742         if (typeof(this.events.add.listeners) != 'undefined') {
18743             
18744             this.addicon = this.wrap.createChild(
18745                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-add' });  
18746        
18747             this.addicon.on('click', function(e) {
18748                 this.fireEvent('add', this);
18749             }, this);
18750         }
18751         if (typeof(this.events.edit.listeners) != 'undefined') {
18752             
18753             this.editicon = this.wrap.createChild(
18754                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-combo-edit' });  
18755             if (this.addicon) {
18756                 this.editicon.setStyle('margin-left', '40px');
18757             }
18758             this.editicon.on('click', function(e) {
18759                 
18760                 // we fire even  if inothing is selected..
18761                 this.fireEvent('edit', this, this.lastData );
18762                 
18763             }, this);
18764         }
18765         
18766         
18767         
18768     },
18769
18770     // private
18771     initEvents : function(){
18772         Roo.form.ComboBox.superclass.initEvents.call(this);
18773
18774         this.keyNav = new Roo.KeyNav(this.el, {
18775             "up" : function(e){
18776                 this.inKeyMode = true;
18777                 this.selectPrev();
18778             },
18779
18780             "down" : function(e){
18781                 if(!this.isExpanded()){
18782                     this.onTriggerClick();
18783                 }else{
18784                     this.inKeyMode = true;
18785                     this.selectNext();
18786                 }
18787             },
18788
18789             "enter" : function(e){
18790                 this.onViewClick();
18791                 //return true;
18792             },
18793
18794             "esc" : function(e){
18795                 this.collapse();
18796             },
18797
18798             "tab" : function(e){
18799                 this.onViewClick(false);
18800                 this.fireEvent("specialkey", this, e);
18801                 return true;
18802             },
18803
18804             scope : this,
18805
18806             doRelay : function(foo, bar, hname){
18807                 if(hname == 'down' || this.scope.isExpanded()){
18808                    return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
18809                 }
18810                 return true;
18811             },
18812
18813             forceKeyDown: true
18814         });
18815         this.queryDelay = Math.max(this.queryDelay || 10,
18816                 this.mode == 'local' ? 10 : 250);
18817         this.dqTask = new Roo.util.DelayedTask(this.initQuery, this);
18818         if(this.typeAhead){
18819             this.taTask = new Roo.util.DelayedTask(this.onTypeAhead, this);
18820         }
18821         if(this.editable !== false){
18822             this.el.on("keyup", this.onKeyUp, this);
18823         }
18824         if(this.forceSelection){
18825             this.on('blur', this.doForce, this);
18826         }
18827     },
18828
18829     onDestroy : function(){
18830         if(this.view){
18831             this.view.setStore(null);
18832             this.view.el.removeAllListeners();
18833             this.view.el.remove();
18834             this.view.purgeListeners();
18835         }
18836         if(this.list){
18837             this.list.destroy();
18838         }
18839         if(this.store){
18840             this.store.un('beforeload', this.onBeforeLoad, this);
18841             this.store.un('load', this.onLoad, this);
18842             this.store.un('loadexception', this.onLoadException, this);
18843         }
18844         Roo.form.ComboBox.superclass.onDestroy.call(this);
18845     },
18846
18847     // private
18848     fireKey : function(e){
18849         if(e.isNavKeyPress() && !this.list.isVisible()){
18850             this.fireEvent("specialkey", this, e);
18851         }
18852     },
18853
18854     // private
18855     onResize: function(w, h){
18856         Roo.form.ComboBox.superclass.onResize.apply(this, arguments);
18857         
18858         if(typeof w != 'number'){
18859             // we do not handle it!?!?
18860             return;
18861         }
18862         var tw = this.trigger.getWidth();
18863         tw += this.addicon ? this.addicon.getWidth() : 0;
18864         tw += this.editicon ? this.editicon.getWidth() : 0;
18865         var x = w - tw;
18866         this.el.setWidth( this.adjustWidth('input', x));
18867             
18868         this.trigger.setStyle('left', x+'px');
18869         
18870         if(this.list && this.listWidth === undefined){
18871             var lw = Math.max(x + this.trigger.getWidth(), this.minListWidth);
18872             this.list.setWidth(lw);
18873             this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
18874         }
18875         
18876     
18877         
18878     },
18879
18880     /**
18881      * Allow or prevent the user from directly editing the field text.  If false is passed,
18882      * the user will only be able to select from the items defined in the dropdown list.  This method
18883      * is the runtime equivalent of setting the 'editable' config option at config time.
18884      * @param {Boolean} value True to allow the user to directly edit the field text
18885      */
18886     setEditable : function(value){
18887         if(value == this.editable){
18888             return;
18889         }
18890         this.editable = value;
18891         if(!value){
18892             this.el.dom.setAttribute('readOnly', true);
18893             this.el.on('mousedown', this.onTriggerClick,  this);
18894             this.el.addClass('x-combo-noedit');
18895         }else{
18896             this.el.dom.setAttribute('readOnly', false);
18897             this.el.un('mousedown', this.onTriggerClick,  this);
18898             this.el.removeClass('x-combo-noedit');
18899         }
18900     },
18901
18902     // private
18903     onBeforeLoad : function(){
18904         if(!this.hasFocus){
18905             return;
18906         }
18907         this.innerList.update(this.loadingText ?
18908                '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
18909         this.restrictHeight();
18910         this.selectedIndex = -1;
18911     },
18912
18913     // private
18914     onLoad : function(){
18915         if(!this.hasFocus){
18916             return;
18917         }
18918         if(this.store.getCount() > 0){
18919             this.expand();
18920             this.restrictHeight();
18921             if(this.lastQuery == this.allQuery){
18922                 if(this.editable){
18923                     this.el.dom.select();
18924                 }
18925                 if(!this.selectByValue(this.value, true)){
18926                     this.select(0, true);
18927                 }
18928             }else{
18929                 this.selectNext();
18930                 if(this.typeAhead && this.lastKey != Roo.EventObject.BACKSPACE && this.lastKey != Roo.EventObject.DELETE){
18931                     this.taTask.delay(this.typeAheadDelay);
18932                 }
18933             }
18934         }else{
18935             this.onEmptyResults();
18936         }
18937         //this.el.focus();
18938     },
18939     // private
18940     onLoadException : function()
18941     {
18942         this.collapse();
18943         Roo.log(this.store.reader.jsonData);
18944         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
18945             Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
18946         }
18947         
18948         
18949     },
18950     // private
18951     onTypeAhead : function(){
18952         if(this.store.getCount() > 0){
18953             var r = this.store.getAt(0);
18954             var newValue = r.data[this.displayField];
18955             var len = newValue.length;
18956             var selStart = this.getRawValue().length;
18957             if(selStart != len){
18958                 this.setRawValue(newValue);
18959                 this.selectText(selStart, newValue.length);
18960             }
18961         }
18962     },
18963
18964     // private
18965     onSelect : function(record, index){
18966         if(this.fireEvent('beforeselect', this, record, index) !== false){
18967             this.setFromData(index > -1 ? record.data : false);
18968             this.collapse();
18969             this.fireEvent('select', this, record, index);
18970         }
18971     },
18972
18973     /**
18974      * Returns the currently selected field value or empty string if no value is set.
18975      * @return {String} value The selected value
18976      */
18977     getValue : function(){
18978         if(this.valueField){
18979             return typeof this.value != 'undefined' ? this.value : '';
18980         }
18981         return Roo.form.ComboBox.superclass.getValue.call(this);
18982     },
18983
18984     /**
18985      * Clears any text/value currently set in the field
18986      */
18987     clearValue : function(){
18988         if(this.hiddenField){
18989             this.hiddenField.value = '';
18990         }
18991         this.value = '';
18992         this.setRawValue('');
18993         this.lastSelectionText = '';
18994         
18995     },
18996
18997     /**
18998      * Sets the specified value into the field.  If the value finds a match, the corresponding record text
18999      * will be displayed in the field.  If the value does not match the data value of an existing item,
19000      * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
19001      * Otherwise the field will be blank (although the value will still be set).
19002      * @param {String} value The value to match
19003      */
19004     setValue : function(v){
19005         var text = v;
19006         if(this.valueField){
19007             var r = this.findRecord(this.valueField, v);
19008             if(r){
19009                 text = r.data[this.displayField];
19010             }else if(this.valueNotFoundText !== undefined){
19011                 text = this.valueNotFoundText;
19012             }
19013         }
19014         this.lastSelectionText = text;
19015         if(this.hiddenField){
19016             this.hiddenField.value = v;
19017         }
19018         Roo.form.ComboBox.superclass.setValue.call(this, text);
19019         this.value = v;
19020     },
19021     /**
19022      * @property {Object} the last set data for the element
19023      */
19024     
19025     lastData : false,
19026     /**
19027      * Sets the value of the field based on a object which is related to the record format for the store.
19028      * @param {Object} value the value to set as. or false on reset?
19029      */
19030     setFromData : function(o){
19031         var dv = ''; // display value
19032         var vv = ''; // value value..
19033         this.lastData = o;
19034         if (this.displayField) {
19035             dv = !o || typeof(o[this.displayField]) == 'undefined' ? '' : o[this.displayField];
19036         } else {
19037             // this is an error condition!!!
19038             Roo.log('no  displayField value set for '+ (this.name ? this.name : this.id));
19039         }
19040         
19041         if(this.valueField){
19042             vv = !o || typeof(o[this.valueField]) == 'undefined' ? dv : o[this.valueField];
19043         }
19044         if(this.hiddenField){
19045             this.hiddenField.value = vv;
19046             
19047             this.lastSelectionText = dv;
19048             Roo.form.ComboBox.superclass.setValue.call(this, dv);
19049             this.value = vv;
19050             return;
19051         }
19052         // no hidden field.. - we store the value in 'value', but still display
19053         // display field!!!!
19054         this.lastSelectionText = dv;
19055         Roo.form.ComboBox.superclass.setValue.call(this, dv);
19056         this.value = vv;
19057         
19058         
19059     },
19060     // private
19061     reset : function(){
19062         // overridden so that last data is reset..
19063         this.setValue(this.resetValue);
19064         this.originalValue = this.getValue();
19065         this.clearInvalid();
19066         this.lastData = false;
19067         if (this.view) {
19068             this.view.clearSelections();
19069         }
19070     },
19071     // private
19072     findRecord : function(prop, value){
19073         var record;
19074         if(this.store.getCount() > 0){
19075             this.store.each(function(r){
19076                 if(r.data[prop] == value){
19077                     record = r;
19078                     return false;
19079                 }
19080                 return true;
19081             });
19082         }
19083         return record;
19084     },
19085     
19086     getName: function()
19087     {
19088         // returns hidden if it's set..
19089         if (!this.rendered) {return ''};
19090         return !this.hiddenName && this.el.dom.name  ? this.el.dom.name : (this.hiddenName || '');
19091         
19092     },
19093     // private
19094     onViewMove : function(e, t){
19095         this.inKeyMode = false;
19096     },
19097
19098     // private
19099     onViewOver : function(e, t){
19100         if(this.inKeyMode){ // prevent key nav and mouse over conflicts
19101             return;
19102         }
19103         var item = this.view.findItemFromChild(t);
19104         if(item){
19105             var index = this.view.indexOf(item);
19106             this.select(index, false);
19107         }
19108     },
19109
19110     // private
19111     onViewClick : function(doFocus)
19112     {
19113         var index = this.view.getSelectedIndexes()[0];
19114         var r = this.store.getAt(index);
19115         if(r){
19116             this.onSelect(r, index);
19117         }
19118         if(doFocus !== false && !this.blockFocus){
19119             this.el.focus();
19120         }
19121     },
19122
19123     // private
19124     restrictHeight : function(){
19125         this.innerList.dom.style.height = '';
19126         var inner = this.innerList.dom;
19127         var h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight);
19128         this.innerList.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
19129         this.list.beginUpdate();
19130         this.list.setHeight(this.innerList.getHeight()+this.list.getFrameWidth('tb')+(this.resizable?this.handleHeight:0)+this.assetHeight);
19131         this.list.alignTo(this.el, this.listAlign);
19132         this.list.endUpdate();
19133     },
19134
19135     // private
19136     onEmptyResults : function(){
19137         this.collapse();
19138     },
19139
19140     /**
19141      * Returns true if the dropdown list is expanded, else false.
19142      */
19143     isExpanded : function(){
19144         return this.list.isVisible();
19145     },
19146
19147     /**
19148      * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
19149      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
19150      * @param {String} value The data value of the item to select
19151      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
19152      * selected item if it is not currently in view (defaults to true)
19153      * @return {Boolean} True if the value matched an item in the list, else false
19154      */
19155     selectByValue : function(v, scrollIntoView){
19156         if(v !== undefined && v !== null){
19157             var r = this.findRecord(this.valueField || this.displayField, v);
19158             if(r){
19159                 this.select(this.store.indexOf(r), scrollIntoView);
19160                 return true;
19161             }
19162         }
19163         return false;
19164     },
19165
19166     /**
19167      * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
19168      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
19169      * @param {Number} index The zero-based index of the list item to select
19170      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
19171      * selected item if it is not currently in view (defaults to true)
19172      */
19173     select : function(index, scrollIntoView){
19174         this.selectedIndex = index;
19175         this.view.select(index);
19176         if(scrollIntoView !== false){
19177             var el = this.view.getNode(index);
19178             if(el){
19179                 this.innerList.scrollChildIntoView(el, false);
19180             }
19181         }
19182     },
19183
19184     // private
19185     selectNext : function(){
19186         var ct = this.store.getCount();
19187         if(ct > 0){
19188             if(this.selectedIndex == -1){
19189                 this.select(0);
19190             }else if(this.selectedIndex < ct-1){
19191                 this.select(this.selectedIndex+1);
19192             }
19193         }
19194     },
19195
19196     // private
19197     selectPrev : function(){
19198         var ct = this.store.getCount();
19199         if(ct > 0){
19200             if(this.selectedIndex == -1){
19201                 this.select(0);
19202             }else if(this.selectedIndex != 0){
19203                 this.select(this.selectedIndex-1);
19204             }
19205         }
19206     },
19207
19208     // private
19209     onKeyUp : function(e){
19210         if(this.editable !== false && !e.isSpecialKey()){
19211             this.lastKey = e.getKey();
19212             this.dqTask.delay(this.queryDelay);
19213         }
19214     },
19215
19216     // private
19217     validateBlur : function(){
19218         return !this.list || !this.list.isVisible();   
19219     },
19220
19221     // private
19222     initQuery : function(){
19223         this.doQuery(this.getRawValue());
19224     },
19225
19226     // private
19227     doForce : function(){
19228         if(this.el.dom.value.length > 0){
19229             this.el.dom.value =
19230                 this.lastSelectionText === undefined ? '' : this.lastSelectionText;
19231              
19232         }
19233     },
19234
19235     /**
19236      * Execute a query to filter the dropdown list.  Fires the beforequery event prior to performing the
19237      * query allowing the query action to be canceled if needed.
19238      * @param {String} query The SQL query to execute
19239      * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
19240      * in the field than the minimum specified by the minChars config option.  It also clears any filter previously
19241      * saved in the current store (defaults to false)
19242      */
19243     doQuery : function(q, forceAll){
19244         if(q === undefined || q === null){
19245             q = '';
19246         }
19247         var qe = {
19248             query: q,
19249             forceAll: forceAll,
19250             combo: this,
19251             cancel:false
19252         };
19253         if(this.fireEvent('beforequery', qe)===false || qe.cancel){
19254             return false;
19255         }
19256         q = qe.query;
19257         forceAll = qe.forceAll;
19258         if(forceAll === true || (q.length >= this.minChars)){
19259             if(this.lastQuery != q || this.alwaysQuery){
19260                 this.lastQuery = q;
19261                 if(this.mode == 'local'){
19262                     this.selectedIndex = -1;
19263                     if(forceAll){
19264                         this.store.clearFilter();
19265                     }else{
19266                         this.store.filter(this.displayField, q);
19267                     }
19268                     this.onLoad();
19269                 }else{
19270                     this.store.baseParams[this.queryParam] = q;
19271                     this.store.load({
19272                         params: this.getParams(q)
19273                     });
19274                     this.expand();
19275                 }
19276             }else{
19277                 this.selectedIndex = -1;
19278                 this.onLoad();   
19279             }
19280         }
19281     },
19282
19283     // private
19284     getParams : function(q){
19285         var p = {};
19286         //p[this.queryParam] = q;
19287         if(this.pageSize){
19288             p.start = 0;
19289             p.limit = this.pageSize;
19290         }
19291         return p;
19292     },
19293
19294     /**
19295      * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
19296      */
19297     collapse : function(){
19298         if(!this.isExpanded()){
19299             return;
19300         }
19301         this.list.hide();
19302         Roo.get(document).un('mousedown', this.collapseIf, this);
19303         Roo.get(document).un('mousewheel', this.collapseIf, this);
19304         if (!this.editable) {
19305             Roo.get(document).un('keydown', this.listKeyPress, this);
19306         }
19307         this.fireEvent('collapse', this);
19308     },
19309
19310     // private
19311     collapseIf : function(e){
19312         if(!e.within(this.wrap) && !e.within(this.list)){
19313             this.collapse();
19314         }
19315     },
19316
19317     /**
19318      * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
19319      */
19320     expand : function(){
19321         if(this.isExpanded() || !this.hasFocus){
19322             return;
19323         }
19324         this.list.alignTo(this.el, this.listAlign);
19325         this.list.show();
19326         Roo.get(document).on('mousedown', this.collapseIf, this);
19327         Roo.get(document).on('mousewheel', this.collapseIf, this);
19328         if (!this.editable) {
19329             Roo.get(document).on('keydown', this.listKeyPress, this);
19330         }
19331         
19332         this.fireEvent('expand', this);
19333     },
19334
19335     // private
19336     // Implements the default empty TriggerField.onTriggerClick function
19337     onTriggerClick : function(){
19338         if(this.disabled){
19339             return;
19340         }
19341         if(this.isExpanded()){
19342             this.collapse();
19343             if (!this.blockFocus) {
19344                 this.el.focus();
19345             }
19346             
19347         }else {
19348             this.hasFocus = true;
19349             if(this.triggerAction == 'all') {
19350                 this.doQuery(this.allQuery, true);
19351             } else {
19352                 this.doQuery(this.getRawValue());
19353             }
19354             if (!this.blockFocus) {
19355                 this.el.focus();
19356             }
19357         }
19358     },
19359     listKeyPress : function(e)
19360     {
19361         //Roo.log('listkeypress');
19362         // scroll to first matching element based on key pres..
19363         if (e.isSpecialKey()) {
19364             return false;
19365         }
19366         var k = String.fromCharCode(e.getKey()).toUpperCase();
19367         //Roo.log(k);
19368         var match  = false;
19369         var csel = this.view.getSelectedNodes();
19370         var cselitem = false;
19371         if (csel.length) {
19372             var ix = this.view.indexOf(csel[0]);
19373             cselitem  = this.store.getAt(ix);
19374             if (!cselitem.get(this.displayField) || cselitem.get(this.displayField).substring(0,1).toUpperCase() != k) {
19375                 cselitem = false;
19376             }
19377             
19378         }
19379         
19380         this.store.each(function(v) { 
19381             if (cselitem) {
19382                 // start at existing selection.
19383                 if (cselitem.id == v.id) {
19384                     cselitem = false;
19385                 }
19386                 return;
19387             }
19388                 
19389             if (v.get(this.displayField) && v.get(this.displayField).substring(0,1).toUpperCase() == k) {
19390                 match = this.store.indexOf(v);
19391                 return false;
19392             }
19393         }, this);
19394         
19395         if (match === false) {
19396             return true; // no more action?
19397         }
19398         // scroll to?
19399         this.view.select(match);
19400         var sn = Roo.get(this.view.getSelectedNodes()[0]);
19401         sn.scrollIntoView(sn.dom.parentNode, false);
19402     } 
19403
19404     /** 
19405     * @cfg {Boolean} grow 
19406     * @hide 
19407     */
19408     /** 
19409     * @cfg {Number} growMin 
19410     * @hide 
19411     */
19412     /** 
19413     * @cfg {Number} growMax 
19414     * @hide 
19415     */
19416     /**
19417      * @hide
19418      * @method autoSize
19419      */
19420 });/*
19421  * Copyright(c) 2010-2012, Roo J Solutions Limited
19422  *
19423  * Licence LGPL
19424  *
19425  */
19426
19427 /**
19428  * @class Roo.form.ComboBoxArray
19429  * @extends Roo.form.TextField
19430  * A facebook style adder... for lists of email / people / countries  etc...
19431  * pick multiple items from a combo box, and shows each one.
19432  *
19433  *  Fred [x]  Brian [x]  [Pick another |v]
19434  *
19435  *
19436  *  For this to work: it needs various extra information
19437  *    - normal combo problay has
19438  *      name, hiddenName
19439  *    + displayField, valueField
19440  *
19441  *    For our purpose...
19442  *
19443  *
19444  *   If we change from 'extends' to wrapping...
19445  *   
19446  *  
19447  *
19448  
19449  
19450  * @constructor
19451  * Create a new ComboBoxArray.
19452  * @param {Object} config Configuration options
19453  */
19454  
19455
19456 Roo.form.ComboBoxArray = function(config)
19457 {
19458     this.addEvents({
19459         /**
19460          * @event beforeremove
19461          * Fires before remove the value from the list
19462              * @param {Roo.form.ComboBoxArray} _self This combo box array
19463              * @param {Roo.form.ComboBoxArray.Item} item removed item
19464              */
19465         'beforeremove' : true,
19466         /**
19467          * @event remove
19468          * Fires when remove the value from the list
19469              * @param {Roo.form.ComboBoxArray} _self This combo box array
19470              * @param {Roo.form.ComboBoxArray.Item} item removed item
19471              */
19472         'remove' : true
19473         
19474         
19475     });
19476     
19477     Roo.form.ComboBoxArray.superclass.constructor.call(this, config);
19478     
19479     this.items = new Roo.util.MixedCollection(false);
19480     
19481     // construct the child combo...
19482     
19483     
19484     
19485     
19486    
19487     
19488 }
19489
19490  
19491 Roo.extend(Roo.form.ComboBoxArray, Roo.form.TextField,
19492
19493     /**
19494      * @cfg {Roo.form.ComboBox} combo [required] The combo box that is wrapped
19495      */
19496     
19497     lastData : false,
19498     
19499     // behavies liek a hiddne field
19500     inputType:      'hidden',
19501     /**
19502      * @cfg {Number} width The width of the box that displays the selected element
19503      */ 
19504     width:          300,
19505
19506     
19507     
19508     /**
19509      * @cfg {String} name    The name of the visable items on this form (eg. titles not ids)
19510      */
19511     name : false,
19512     /**
19513      * @cfg {String} hiddenName    The hidden name of the field, often contains an comma seperated list of names
19514      */
19515     hiddenName : false,
19516       /**
19517      * @cfg {String} seperator    The value seperator normally ',' 
19518      */
19519     seperator : ',',
19520     
19521     // private the array of items that are displayed..
19522     items  : false,
19523     // private - the hidden field el.
19524     hiddenEl : false,
19525     // private - the filed el..
19526     el : false,
19527     
19528     //validateValue : function() { return true; }, // all values are ok!
19529     //onAddClick: function() { },
19530     
19531     onRender : function(ct, position) 
19532     {
19533         
19534         // create the standard hidden element
19535         //Roo.form.ComboBoxArray.superclass.onRender.call(this, ct, position);
19536         
19537         
19538         // give fake names to child combo;
19539         this.combo.hiddenName = this.hiddenName ? (this.hiddenName+'-subcombo') : this.hiddenName;
19540         this.combo.name = this.name ? (this.name+'-subcombo') : this.name;
19541         
19542         this.combo = Roo.factory(this.combo, Roo.form);
19543         this.combo.onRender(ct, position);
19544         if (typeof(this.combo.width) != 'undefined') {
19545             this.combo.onResize(this.combo.width,0);
19546         }
19547         
19548         this.combo.initEvents();
19549         
19550         // assigned so form know we need to do this..
19551         this.store          = this.combo.store;
19552         this.valueField     = this.combo.valueField;
19553         this.displayField   = this.combo.displayField ;
19554         
19555         
19556         this.combo.wrap.addClass('x-cbarray-grp');
19557         
19558         var cbwrap = this.combo.wrap.createChild(
19559             {tag: 'div', cls: 'x-cbarray-cb'},
19560             this.combo.el.dom
19561         );
19562         
19563              
19564         this.hiddenEl = this.combo.wrap.createChild({
19565             tag: 'input',  type:'hidden' , name: this.hiddenName, value : ''
19566         });
19567         this.el = this.combo.wrap.createChild({
19568             tag: 'input',  type:'hidden' , name: this.name, value : ''
19569         });
19570          //   this.el.dom.removeAttribute("name");
19571         
19572         
19573         this.outerWrap = this.combo.wrap;
19574         this.wrap = cbwrap;
19575         
19576         this.outerWrap.setWidth(this.width);
19577         this.outerWrap.dom.removeChild(this.el.dom);
19578         
19579         this.wrap.dom.appendChild(this.el.dom);
19580         this.outerWrap.dom.removeChild(this.combo.trigger.dom);
19581         this.combo.wrap.dom.appendChild(this.combo.trigger.dom);
19582         
19583         this.combo.trigger.setStyle('position','relative');
19584         this.combo.trigger.setStyle('left', '0px');
19585         this.combo.trigger.setStyle('top', '2px');
19586         
19587         this.combo.el.setStyle('vertical-align', 'text-bottom');
19588         
19589         //this.trigger.setStyle('vertical-align', 'top');
19590         
19591         // this should use the code from combo really... on('add' ....)
19592         if (this.adder) {
19593             
19594         
19595             this.adder = this.outerWrap.createChild(
19596                 {tag: 'img', src: Roo.BLANK_IMAGE_URL, cls: 'x-form-adder', style: 'margin-left:2px'});  
19597             var _t = this;
19598             this.adder.on('click', function(e) {
19599                 _t.fireEvent('adderclick', this, e);
19600             }, _t);
19601         }
19602         //var _t = this;
19603         //this.adder.on('click', this.onAddClick, _t);
19604         
19605         
19606         this.combo.on('select', function(cb, rec, ix) {
19607             this.addItem(rec.data);
19608             
19609             cb.setValue('');
19610             cb.el.dom.value = '';
19611             //cb.lastData = rec.data;
19612             // add to list
19613             
19614         }, this);
19615         
19616         
19617     },
19618     
19619     
19620     getName: function()
19621     {
19622         // returns hidden if it's set..
19623         if (!this.rendered) {return ''};
19624         return  this.hiddenName ? this.hiddenName : this.name;
19625         
19626     },
19627     
19628     
19629     onResize: function(w, h){
19630         
19631         return;
19632         // not sure if this is needed..
19633         //this.combo.onResize(w,h);
19634         
19635         if(typeof w != 'number'){
19636             // we do not handle it!?!?
19637             return;
19638         }
19639         var tw = this.combo.trigger.getWidth();
19640         tw += this.addicon ? this.addicon.getWidth() : 0;
19641         tw += this.editicon ? this.editicon.getWidth() : 0;
19642         var x = w - tw;
19643         this.combo.el.setWidth( this.combo.adjustWidth('input', x));
19644             
19645         this.combo.trigger.setStyle('left', '0px');
19646         
19647         if(this.list && this.listWidth === undefined){
19648             var lw = Math.max(x + this.combo.trigger.getWidth(), this.combo.minListWidth);
19649             this.list.setWidth(lw);
19650             this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
19651         }
19652         
19653     
19654         
19655     },
19656     
19657     addItem: function(rec)
19658     {
19659         var valueField = this.combo.valueField;
19660         var displayField = this.combo.displayField;
19661         
19662         if (this.items.indexOfKey(rec[valueField]) > -1) {
19663             //console.log("GOT " + rec.data.id);
19664             return;
19665         }
19666         
19667         var x = new Roo.form.ComboBoxArray.Item({
19668             //id : rec[this.idField],
19669             data : rec,
19670             displayField : displayField ,
19671             tipField : displayField ,
19672             cb : this
19673         });
19674         // use the 
19675         this.items.add(rec[valueField],x);
19676         // add it before the element..
19677         this.updateHiddenEl();
19678         x.render(this.outerWrap, this.wrap.dom);
19679         // add the image handler..
19680     },
19681     
19682     updateHiddenEl : function()
19683     {
19684         this.validate();
19685         if (!this.hiddenEl) {
19686             return;
19687         }
19688         var ar = [];
19689         var idField = this.combo.valueField;
19690         
19691         this.items.each(function(f) {
19692             ar.push(f.data[idField]);
19693         });
19694         this.hiddenEl.dom.value = ar.join(this.seperator);
19695         this.validate();
19696     },
19697     
19698     reset : function()
19699     {
19700         this.items.clear();
19701         
19702         Roo.each(this.outerWrap.select('.x-cbarray-item', true).elements, function(el){
19703            el.remove();
19704         });
19705         
19706         this.el.dom.value = '';
19707         if (this.hiddenEl) {
19708             this.hiddenEl.dom.value = '';
19709         }
19710         
19711     },
19712     getValue: function()
19713     {
19714         return this.hiddenEl ? this.hiddenEl.dom.value : '';
19715     },
19716     setValue: function(v) // not a valid action - must use addItems..
19717     {
19718         
19719         this.reset();
19720          
19721         if (this.store.isLocal && (typeof(v) == 'string')) {
19722             // then we can use the store to find the values..
19723             // comma seperated at present.. this needs to allow JSON based encoding..
19724             this.hiddenEl.value  = v;
19725             var v_ar = [];
19726             Roo.each(v.split(this.seperator), function(k) {
19727                 Roo.log("CHECK " + this.valueField + ',' + k);
19728                 var li = this.store.query(this.valueField, k);
19729                 if (!li.length) {
19730                     return;
19731                 }
19732                 var add = {};
19733                 add[this.valueField] = k;
19734                 add[this.displayField] = li.item(0).data[this.displayField];
19735                 
19736                 this.addItem(add);
19737             }, this) 
19738              
19739         }
19740         if (typeof(v) == 'object' ) {
19741             // then let's assume it's an array of objects..
19742             Roo.each(v, function(l) {
19743                 var add = l;
19744                 if (typeof(l) == 'string') {
19745                     add = {};
19746                     add[this.valueField] = l;
19747                     add[this.displayField] = l
19748                 }
19749                 this.addItem(add);
19750             }, this);
19751              
19752         }
19753         
19754         
19755     },
19756     setFromData: function(v)
19757     {
19758         // this recieves an object, if setValues is called.
19759         this.reset();
19760         this.el.dom.value = v[this.displayField];
19761         this.hiddenEl.dom.value = v[this.valueField];
19762         if (typeof(v[this.valueField]) != 'string' || !v[this.valueField].length) {
19763             return;
19764         }
19765         var kv = v[this.valueField];
19766         var dv = v[this.displayField];
19767         kv = typeof(kv) != 'string' ? '' : kv;
19768         dv = typeof(dv) != 'string' ? '' : dv;
19769         
19770         
19771         var keys = kv.split(this.seperator);
19772         var display = dv.split(this.seperator);
19773         for (var i = 0 ; i < keys.length; i++) {
19774             add = {};
19775             add[this.valueField] = keys[i];
19776             add[this.displayField] = display[i];
19777             this.addItem(add);
19778         }
19779       
19780         
19781     },
19782     
19783     /**
19784      * Validates the combox array value
19785      * @return {Boolean} True if the value is valid, else false
19786      */
19787     validate : function(){
19788         if(this.disabled || this.validateValue(this.processValue(this.getValue()))){
19789             this.clearInvalid();
19790             return true;
19791         }
19792         return false;
19793     },
19794     
19795     validateValue : function(value){
19796         return Roo.form.ComboBoxArray.superclass.validateValue.call(this, this.getValue());
19797         
19798     },
19799     
19800     /*@
19801      * overide
19802      * 
19803      */
19804     isDirty : function() {
19805         if(this.disabled) {
19806             return false;
19807         }
19808         
19809         try {
19810             var d = Roo.decode(String(this.originalValue));
19811         } catch (e) {
19812             return String(this.getValue()) !== String(this.originalValue);
19813         }
19814         
19815         var originalValue = [];
19816         
19817         for (var i = 0; i < d.length; i++){
19818             originalValue.push(d[i][this.valueField]);
19819         }
19820         
19821         return String(this.getValue()) !== String(originalValue.join(this.seperator));
19822         
19823     }
19824     
19825 });
19826
19827
19828
19829 /**
19830  * @class Roo.form.ComboBoxArray.Item
19831  * @extends Roo.BoxComponent
19832  * A selected item in the list
19833  *  Fred [x]  Brian [x]  [Pick another |v]
19834  * 
19835  * @constructor
19836  * Create a new item.
19837  * @param {Object} config Configuration options
19838  */
19839  
19840 Roo.form.ComboBoxArray.Item = function(config) {
19841     config.id = Roo.id();
19842     Roo.form.ComboBoxArray.Item.superclass.constructor.call(this, config);
19843 }
19844
19845 Roo.extend(Roo.form.ComboBoxArray.Item, Roo.BoxComponent, {
19846     data : {},
19847     cb: false,
19848     displayField : false,
19849     tipField : false,
19850     
19851     
19852     defaultAutoCreate : {
19853         tag: 'div',
19854         cls: 'x-cbarray-item',
19855         cn : [ 
19856             { tag: 'div' },
19857             {
19858                 tag: 'img',
19859                 width:16,
19860                 height : 16,
19861                 src : Roo.BLANK_IMAGE_URL ,
19862                 align: 'center'
19863             }
19864         ]
19865         
19866     },
19867     
19868  
19869     onRender : function(ct, position)
19870     {
19871         Roo.form.Field.superclass.onRender.call(this, ct, position);
19872         
19873         if(!this.el){
19874             var cfg = this.getAutoCreate();
19875             this.el = ct.createChild(cfg, position);
19876         }
19877         
19878         this.el.child('img').dom.setAttribute('src', Roo.BLANK_IMAGE_URL);
19879         
19880         this.el.child('div').dom.innerHTML = this.cb.renderer ? 
19881             this.cb.renderer(this.data) :
19882             String.format('{0}',this.data[this.displayField]);
19883         
19884             
19885         this.el.child('div').dom.setAttribute('qtip',
19886                         String.format('{0}',this.data[this.tipField])
19887         );
19888         
19889         this.el.child('img').on('click', this.remove, this);
19890         
19891     },
19892    
19893     remove : function()
19894     {
19895         if(this.cb.disabled){
19896             return;
19897         }
19898         
19899         if(false !== this.cb.fireEvent('beforeremove', this.cb, this)){
19900             this.cb.items.remove(this);
19901             this.el.child('img').un('click', this.remove, this);
19902             this.el.remove();
19903             this.cb.updateHiddenEl();
19904
19905             this.cb.fireEvent('remove', this.cb, this);
19906         }
19907         
19908     }
19909 });/*
19910  * RooJS Library 1.1.1
19911  * Copyright(c) 2008-2011  Alan Knowles
19912  *
19913  * License - LGPL
19914  */
19915  
19916
19917 /**
19918  * @class Roo.form.ComboNested
19919  * @extends Roo.form.ComboBox
19920  * A combobox for that allows selection of nested items in a list,
19921  * eg.
19922  *
19923  *  Book
19924  *    -> red
19925  *    -> green
19926  *  Table
19927  *    -> square
19928  *      ->red
19929  *      ->green
19930  *    -> rectangle
19931  *      ->green
19932  *      
19933  * 
19934  * @constructor
19935  * Create a new ComboNested
19936  * @param {Object} config Configuration options
19937  */
19938 Roo.form.ComboNested = function(config){
19939     Roo.form.ComboCheck.superclass.constructor.call(this, config);
19940     // should verify some data...
19941     // like
19942     // hiddenName = required..
19943     // displayField = required
19944     // valudField == required
19945     var req= [ 'hiddenName', 'displayField', 'valueField' ];
19946     var _t = this;
19947     Roo.each(req, function(e) {
19948         if ((typeof(_t[e]) == 'undefined' ) || !_t[e].length) {
19949             throw "Roo.form.ComboNested : missing value for: " + e;
19950         }
19951     });
19952      
19953     
19954 };
19955
19956 Roo.extend(Roo.form.ComboNested, Roo.form.ComboBox, {
19957    
19958     /*
19959      * @config {Number} max Number of columns to show
19960      */
19961     
19962     maxColumns : 3,
19963    
19964     list : null, // the outermost div..
19965     innerLists : null, // the
19966     views : null,
19967     stores : null,
19968     // private
19969     loadingChildren : false,
19970     
19971     onRender : function(ct, position)
19972     {
19973         Roo.form.ComboBox.superclass.onRender.call(this, ct, position); // skip parent call - got to above..
19974         
19975         if(this.hiddenName){
19976             this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName, id:  (this.hiddenId||this.hiddenName)},
19977                     'before', true);
19978             this.hiddenField.value =
19979                 this.hiddenValue !== undefined ? this.hiddenValue :
19980                 this.value !== undefined ? this.value : '';
19981
19982             // prevent input submission
19983             this.el.dom.removeAttribute('name');
19984              
19985              
19986         }
19987         
19988         if(Roo.isGecko){
19989             this.el.dom.setAttribute('autocomplete', 'off');
19990         }
19991
19992         var cls = 'x-combo-list';
19993
19994         this.list = new Roo.Layer({
19995             shadow: this.shadow, cls: [cls, this.listClass].join(' '), constrain:false
19996         });
19997
19998         var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
19999         this.list.setWidth(lw);
20000         this.list.swallowEvent('mousewheel');
20001         this.assetHeight = 0;
20002
20003         if(this.title){
20004             this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
20005             this.assetHeight += this.header.getHeight();
20006         }
20007         this.innerLists = [];
20008         this.views = [];
20009         this.stores = [];
20010         for (var i =0 ; i < this.maxColumns; i++) {
20011             this.onRenderList( cls, i);
20012         }
20013         
20014         // always needs footer, as we are going to have an 'OK' button.
20015         this.footer = this.list.createChild({cls:cls+'-ft'});
20016         this.pageTb = new Roo.Toolbar(this.footer);  
20017         var _this = this;
20018         this.pageTb.add(  {
20019             
20020             text: 'Done',
20021             handler: function()
20022             {
20023                 _this.collapse();
20024             }
20025         });
20026         
20027         if ( this.allowBlank && !this.disableClear) {
20028             
20029             this.pageTb.add(new Roo.Toolbar.Fill(), {
20030                 cls: 'x-btn-icon x-btn-clear',
20031                 text: '&#160;',
20032                 handler: function()
20033                 {
20034                     _this.collapse();
20035                     _this.clearValue();
20036                     _this.onSelect(false, -1);
20037                 }
20038             });
20039         }
20040         if (this.footer) {
20041             this.assetHeight += this.footer.getHeight();
20042         }
20043         
20044     },
20045     onRenderList : function (  cls, i)
20046     {
20047         
20048         var lw = Math.floor(
20049                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
20050         );
20051         
20052         this.list.setWidth(lw); // default to '1'
20053
20054         var il = this.innerLists[i] = this.list.createChild({cls:cls+'-inner'});
20055         //il.on('mouseover', this.onViewOver, this, { list:  i });
20056         //il.on('mousemove', this.onViewMove, this, { list:  i });
20057         il.setWidth(lw);
20058         il.setStyle({ 'overflow-x' : 'hidden'});
20059
20060         if(!this.tpl){
20061             this.tpl = new Roo.Template({
20062                 html :  '<div class="'+cls+'-item '+cls+'-item-{cn:this.isEmpty}">{' + this.displayField + '}</div>',
20063                 isEmpty: function (value, allValues) {
20064                     //Roo.log(value);
20065                     var dl = typeof(value.data) != 'undefined' ? value.data.length : value.length; ///json is a nested response..
20066                     return dl ? 'has-children' : 'no-children'
20067                 }
20068             });
20069         }
20070         
20071         var store  = this.store;
20072         if (i > 0) {
20073             store  = new Roo.data.SimpleStore({
20074                 //fields : this.store.reader.meta.fields,
20075                 reader : this.store.reader,
20076                 data : [ ]
20077             });
20078         }
20079         this.stores[i]  = store;
20080                   
20081         var view = this.views[i] = new Roo.View(
20082             il,
20083             this.tpl,
20084             {
20085                 singleSelect:true,
20086                 store: store,
20087                 selectedClass: this.selectedClass
20088             }
20089         );
20090         view.getEl().setWidth(lw);
20091         view.getEl().setStyle({
20092             position: i < 1 ? 'relative' : 'absolute',
20093             top: 0,
20094             left: (i * lw ) + 'px',
20095             display : i > 0 ? 'none' : 'block'
20096         });
20097         view.on('selectionchange', this.onSelectChange.createDelegate(this, {list : i }, true));
20098         view.on('dblclick', this.onDoubleClick.createDelegate(this, {list : i }, true));
20099         //view.on('click', this.onViewClick, this, { list : i });
20100
20101         store.on('beforeload', this.onBeforeLoad, this);
20102         store.on('load',  this.onLoad, this, { list  : i});
20103         store.on('loadexception', this.onLoadException, this);
20104
20105         // hide the other vies..
20106         
20107         
20108         
20109     },
20110       
20111     restrictHeight : function()
20112     {
20113         var mh = 0;
20114         Roo.each(this.innerLists, function(il,i) {
20115             var el = this.views[i].getEl();
20116             el.dom.style.height = '';
20117             var inner = el.dom;
20118             var h = Math.max(il.clientHeight, il.offsetHeight, il.scrollHeight);
20119             // only adjust heights on other ones..
20120             mh = Math.max(h, mh);
20121             if (i < 1) {
20122                 
20123                 el.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
20124                 il.setHeight(h < this.maxHeight ? 'auto' : this.maxHeight);
20125                
20126             }
20127             
20128             
20129         }, this);
20130         
20131         this.list.beginUpdate();
20132         this.list.setHeight(mh+this.list.getFrameWidth('tb')+this.assetHeight);
20133         this.list.alignTo(this.el, this.listAlign);
20134         this.list.endUpdate();
20135         
20136     },
20137      
20138     
20139     // -- store handlers..
20140     // private
20141     onBeforeLoad : function()
20142     {
20143         if(!this.hasFocus){
20144             return;
20145         }
20146         this.innerLists[0].update(this.loadingText ?
20147                '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
20148         this.restrictHeight();
20149         this.selectedIndex = -1;
20150     },
20151     // private
20152     onLoad : function(a,b,c,d)
20153     {
20154         if (!this.loadingChildren) {
20155             // then we are loading the top level. - hide the children
20156             for (var i = 1;i < this.views.length; i++) {
20157                 this.views[i].getEl().setStyle({ display : 'none' });
20158             }
20159             var lw = Math.floor(
20160                 ((this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')) / this.maxColumns
20161             );
20162         
20163              this.list.setWidth(lw); // default to '1'
20164
20165             
20166         }
20167         if(!this.hasFocus){
20168             return;
20169         }
20170         
20171         if(this.store.getCount() > 0) {
20172             this.expand();
20173             this.restrictHeight();   
20174         } else {
20175             this.onEmptyResults();
20176         }
20177         
20178         if (!this.loadingChildren) {
20179             this.selectActive();
20180         }
20181         /*
20182         this.stores[1].loadData([]);
20183         this.stores[2].loadData([]);
20184         this.views
20185         */    
20186     
20187         //this.el.focus();
20188     },
20189     
20190     
20191     // private
20192     onLoadException : function()
20193     {
20194         this.collapse();
20195         Roo.log(this.store.reader.jsonData);
20196         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
20197             Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
20198         }
20199         
20200         
20201     },
20202     // no cleaning of leading spaces on blur here.
20203     cleanLeadingSpace : function(e) { },
20204     
20205
20206     onSelectChange : function (view, sels, opts )
20207     {
20208         var ix = view.getSelectedIndexes();
20209          
20210         if (opts.list > this.maxColumns - 2) {
20211             if (view.store.getCount()<  1) {
20212                 this.views[opts.list ].getEl().setStyle({ display :   'none' });
20213
20214             } else  {
20215                 if (ix.length) {
20216                     // used to clear ?? but if we are loading unselected 
20217                     this.setFromData(view.store.getAt(ix[0]).data);
20218                 }
20219                 
20220             }
20221             
20222             return;
20223         }
20224         
20225         if (!ix.length) {
20226             // this get's fired when trigger opens..
20227            // this.setFromData({});
20228             var str = this.stores[opts.list+1];
20229             str.data.clear(); // removeall wihtout the fire events..
20230             return;
20231         }
20232         
20233         var rec = view.store.getAt(ix[0]);
20234          
20235         this.setFromData(rec.data);
20236         this.fireEvent('select', this, rec, ix[0]);
20237         
20238         var lw = Math.floor(
20239              (
20240                 (this.listWidth * this.maxColumns || Math.max(this.wrap.getWidth(), this.minListWidth)) - this.list.getFrameWidth('lr')
20241              ) / this.maxColumns
20242         );
20243         this.loadingChildren = true;
20244         this.stores[opts.list+1].loadDataFromChildren( rec );
20245         this.loadingChildren = false;
20246         var dl = this.stores[opts.list+1]. getTotalCount();
20247         
20248         this.views[opts.list+1].getEl().setHeight( this.innerLists[0].getHeight());
20249         
20250         this.views[opts.list+1].getEl().setStyle({ display : dl ? 'block' : 'none' });
20251         for (var i = opts.list+2; i < this.views.length;i++) {
20252             this.views[i].getEl().setStyle({ display : 'none' });
20253         }
20254         
20255         this.innerLists[opts.list+1].setHeight( this.innerLists[0].getHeight());
20256         this.list.setWidth(lw * (opts.list + (dl ? 2 : 1)));
20257         
20258         if (this.isLoading) {
20259            // this.selectActive(opts.list);
20260         }
20261          
20262     },
20263     
20264     
20265     
20266     
20267     onDoubleClick : function()
20268     {
20269         this.collapse(); //??
20270     },
20271     
20272      
20273     
20274     
20275     
20276     // private
20277     recordToStack : function(store, prop, value, stack)
20278     {
20279         var cstore = new Roo.data.SimpleStore({
20280             //fields : this.store.reader.meta.fields, // we need array reader.. for
20281             reader : this.store.reader,
20282             data : [ ]
20283         });
20284         var _this = this;
20285         var record  = false;
20286         var srec = false;
20287         if(store.getCount() < 1){
20288             return false;
20289         }
20290         store.each(function(r){
20291             if(r.data[prop] == value){
20292                 record = r;
20293             srec = r;
20294                 return false;
20295             }
20296             if (r.data.cn && r.data.cn.length) {
20297                 cstore.loadDataFromChildren( r);
20298                 var cret = _this.recordToStack(cstore, prop, value, stack);
20299                 if (cret !== false) {
20300                     record = cret;
20301                     srec = r;
20302                     return false;
20303                 }
20304             }
20305              
20306             return true;
20307         });
20308         if (record == false) {
20309             return false
20310         }
20311         stack.unshift(srec);
20312         return record;
20313     },
20314     
20315     /*
20316      * find the stack of stores that match our value.
20317      *
20318      * 
20319      */
20320     
20321     selectActive : function ()
20322     {
20323         // if store is not loaded, then we will need to wait for that to happen first.
20324         var stack = [];
20325         this.recordToStack(this.store, this.valueField, this.getValue(), stack);
20326         for (var i = 0; i < stack.length; i++ ) {
20327             this.views[i].select(stack[i].store.indexOf(stack[i]), false, false );
20328         }
20329         
20330     }
20331         
20332          
20333     
20334     
20335     
20336     
20337 });/*
20338  * Based on:
20339  * Ext JS Library 1.1.1
20340  * Copyright(c) 2006-2007, Ext JS, LLC.
20341  *
20342  * Originally Released Under LGPL - original licence link has changed is not relivant.
20343  *
20344  * Fork - LGPL
20345  * <script type="text/javascript">
20346  */
20347 /**
20348  * @class Roo.form.Checkbox
20349  * @extends Roo.form.Field
20350  * Single checkbox field.  Can be used as a direct replacement for traditional checkbox fields.
20351  * @constructor
20352  * Creates a new Checkbox
20353  * @param {Object} config Configuration options
20354  */
20355 Roo.form.Checkbox = function(config){
20356     Roo.form.Checkbox.superclass.constructor.call(this, config);
20357     this.addEvents({
20358         /**
20359          * @event check
20360          * Fires when the checkbox is checked or unchecked.
20361              * @param {Roo.form.Checkbox} this This checkbox
20362              * @param {Boolean} checked The new checked value
20363              */
20364         check : true
20365     });
20366 };
20367
20368 Roo.extend(Roo.form.Checkbox, Roo.form.Field,  {
20369     /**
20370      * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
20371      */
20372     focusClass : undefined,
20373     /**
20374      * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
20375      */
20376     fieldClass: "x-form-field",
20377     /**
20378      * @cfg {Boolean} checked True if the the checkbox should render already checked (defaults to false)
20379      */
20380     checked: false,
20381     /**
20382      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
20383      * {tag: "input", type: "checkbox", autocomplete: "off"})
20384      */
20385     defaultAutoCreate : { tag: "input", type: 'hidden', autocomplete: "off"},
20386     /**
20387      * @cfg {String} boxLabel The text that appears beside the checkbox
20388      */
20389     boxLabel : "",
20390     /**
20391      * @cfg {String} inputValue The value that should go into the generated input element's value attribute
20392      */  
20393     inputValue : '1',
20394     /**
20395      * @cfg {String} valueOff The value that should go into the generated input element's value when unchecked.
20396      */
20397      valueOff: '0', // value when not checked..
20398
20399     actionMode : 'viewEl', 
20400     //
20401     // private
20402     itemCls : 'x-menu-check-item x-form-item',
20403     groupClass : 'x-menu-group-item',
20404     inputType : 'hidden',
20405     
20406     
20407     inSetChecked: false, // check that we are not calling self...
20408     
20409     inputElement: false, // real input element?
20410     basedOn: false, // ????
20411     
20412     isFormField: true, // not sure where this is needed!!!!
20413
20414     onResize : function(){
20415         Roo.form.Checkbox.superclass.onResize.apply(this, arguments);
20416         if(!this.boxLabel){
20417             this.el.alignTo(this.wrap, 'c-c');
20418         }
20419     },
20420
20421     initEvents : function(){
20422         Roo.form.Checkbox.superclass.initEvents.call(this);
20423         this.el.on("click", this.onClick,  this);
20424         this.el.on("change", this.onClick,  this);
20425     },
20426
20427
20428     getResizeEl : function(){
20429         return this.wrap;
20430     },
20431
20432     getPositionEl : function(){
20433         return this.wrap;
20434     },
20435
20436     // private
20437     onRender : function(ct, position){
20438         Roo.form.Checkbox.superclass.onRender.call(this, ct, position);
20439         /*
20440         if(this.inputValue !== undefined){
20441             this.el.dom.value = this.inputValue;
20442         }
20443         */
20444         //this.wrap = this.el.wrap({cls: "x-form-check-wrap"});
20445         this.wrap = this.el.wrap({cls: 'x-menu-check-item '});
20446         var viewEl = this.wrap.createChild({ 
20447             tag: 'img', cls: 'x-menu-item-icon', style: 'margin: 0px;' ,src : Roo.BLANK_IMAGE_URL });
20448         this.viewEl = viewEl;   
20449         this.wrap.on('click', this.onClick,  this); 
20450         
20451         this.el.on('DOMAttrModified', this.setFromHidden,  this); //ff
20452         this.el.on('propertychange', this.setFromHidden,  this);  //ie
20453         
20454         
20455         
20456         if(this.boxLabel){
20457             this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
20458         //    viewEl.on('click', this.onClick,  this); 
20459         }
20460         //if(this.checked){
20461             this.setChecked(this.checked);
20462         //}else{
20463             //this.checked = this.el.dom;
20464         //}
20465
20466     },
20467
20468     // private
20469     initValue : Roo.emptyFn,
20470
20471     /**
20472      * Returns the checked state of the checkbox.
20473      * @return {Boolean} True if checked, else false
20474      */
20475     getValue : function(){
20476         if(this.el){
20477             return String(this.el.dom.value) == String(this.inputValue ) ? this.inputValue : this.valueOff;
20478         }
20479         return this.valueOff;
20480         
20481     },
20482
20483         // private
20484     onClick : function(){ 
20485         if (this.disabled) {
20486             return;
20487         }
20488         this.setChecked(!this.checked);
20489
20490         //if(this.el.dom.checked != this.checked){
20491         //    this.setValue(this.el.dom.checked);
20492        // }
20493     },
20494
20495     /**
20496      * Sets the checked state of the checkbox.
20497      * On is always based on a string comparison between inputValue and the param.
20498      * @param {Boolean/String} value - the value to set 
20499      * @param {Boolean/String} suppressEvent - whether to suppress the checkchange event.
20500      */
20501     setValue : function(v,suppressEvent){
20502         
20503         
20504         //this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
20505         //if(this.el && this.el.dom){
20506         //    this.el.dom.checked = this.checked;
20507         //    this.el.dom.defaultChecked = this.checked;
20508         //}
20509         this.setChecked(String(v) === String(this.inputValue), suppressEvent);
20510         //this.fireEvent("check", this, this.checked);
20511     },
20512     // private..
20513     setChecked : function(state,suppressEvent)
20514     {
20515         if (this.inSetChecked) {
20516             this.checked = state;
20517             return;
20518         }
20519         
20520     
20521         if(this.wrap){
20522             this.wrap[state ? 'addClass' : 'removeClass']('x-menu-item-checked');
20523         }
20524         this.checked = state;
20525         if(suppressEvent !== true){
20526             this.fireEvent('check', this, state);
20527         }
20528         this.inSetChecked = true;
20529                  
20530                 this.el.dom.value = state ? this.inputValue : this.valueOff;
20531                  
20532         this.inSetChecked = false;
20533         
20534     },
20535     // handle setting of hidden value by some other method!!?!?
20536     setFromHidden: function()
20537     {
20538         if(!this.el){
20539             return;
20540         }
20541         //console.log("SET FROM HIDDEN");
20542         //alert('setFrom hidden');
20543         this.setValue(this.el.dom.value);
20544     },
20545     
20546     onDestroy : function()
20547     {
20548         if(this.viewEl){
20549             Roo.get(this.viewEl).remove();
20550         }
20551          
20552         Roo.form.Checkbox.superclass.onDestroy.call(this);
20553     },
20554     
20555     setBoxLabel : function(str)
20556     {
20557         this.wrap.select('.x-form-cb-label', true).first().dom.innerHTML = str;
20558     }
20559
20560 });/*
20561  * Based on:
20562  * Ext JS Library 1.1.1
20563  * Copyright(c) 2006-2007, Ext JS, LLC.
20564  *
20565  * Originally Released Under LGPL - original licence link has changed is not relivant.
20566  *
20567  * Fork - LGPL
20568  * <script type="text/javascript">
20569  */
20570  
20571 /**
20572  * @class Roo.form.Radio
20573  * @extends Roo.form.Checkbox
20574  * Single radio field.  Same as Checkbox, but provided as a convenience for automatically setting the input type.
20575  * Radio grouping is handled automatically by the browser if you give each radio in a group the same name.
20576  * @constructor
20577  * Creates a new Radio
20578  * @param {Object} config Configuration options
20579  */
20580 Roo.form.Radio = function(){
20581     Roo.form.Radio.superclass.constructor.apply(this, arguments);
20582 };
20583 Roo.extend(Roo.form.Radio, Roo.form.Checkbox, {
20584     inputType: 'radio',
20585
20586     /**
20587      * If this radio is part of a group, it will return the selected value
20588      * @return {String}
20589      */
20590     getGroupValue : function(){
20591         return this.el.up('form').child('input[name='+this.el.dom.name+']:checked', true).value;
20592     },
20593     
20594     
20595     onRender : function(ct, position){
20596         Roo.form.Checkbox.superclass.onRender.call(this, ct, position);
20597         
20598         if(this.inputValue !== undefined){
20599             this.el.dom.value = this.inputValue;
20600         }
20601          
20602         this.wrap = this.el.wrap({cls: "x-form-check-wrap"});
20603         //this.wrap = this.el.wrap({cls: 'x-menu-check-item '});
20604         //var viewEl = this.wrap.createChild({ 
20605         //    tag: 'img', cls: 'x-menu-item-icon', style: 'margin: 0px;' ,src : Roo.BLANK_IMAGE_URL });
20606         //this.viewEl = viewEl;   
20607         //this.wrap.on('click', this.onClick,  this); 
20608         
20609         //this.el.on('DOMAttrModified', this.setFromHidden,  this); //ff
20610         //this.el.on('propertychange', this.setFromHidden,  this);  //ie
20611         
20612         
20613         
20614         if(this.boxLabel){
20615             this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
20616         //    viewEl.on('click', this.onClick,  this); 
20617         }
20618          if(this.checked){
20619             this.el.dom.checked =   'checked' ;
20620         }
20621          
20622     },
20623     /**
20624      * Sets the checked state of the checkbox.
20625      * On is always based on a string comparison between inputValue and the param.
20626      * @param {Boolean/String} value - the value to set 
20627      * @param {Boolean/String} suppressEvent - whether to suppress the checkchange event.
20628      */
20629     setValue : function(v,suppressEvent){
20630         
20631         
20632         //this.checked = (v === true || v === 'true' || v == '1' || String(v).toLowerCase() == 'on');
20633         //if(this.el && this.el.dom){
20634         //    this.el.dom.checked = this.checked;
20635         //    this.el.dom.defaultChecked = this.checked;
20636         //}
20637         this.setChecked(String(v) === String(this.inputValue), suppressEvent);
20638         this.inSetChecked = true;
20639         this.el.dom.form[this.name] = v;
20640         this.inSetChecked = false;
20641         //this.fireEvent("check", this, this.checked);
20642     },
20643     // private..
20644     setChecked : function(state,suppressEvent)
20645     {
20646         if (this.inSetChecked) {
20647             this.checked = state;
20648             return;
20649         }
20650         
20651     
20652         if(this.wrap){
20653             this.wrap[state ? 'addClass' : 'removeClass']('x-menu-item-checked');
20654         }
20655         this.checked = state;
20656         if(suppressEvent !== true){
20657             this.fireEvent('check', this, state);
20658         }
20659                  
20660                   
20661        
20662         
20663     }
20664
20665     
20666 });Roo.rtf = {}; // namespace
20667 Roo.rtf.Hex = function(hex)
20668 {
20669     this.hexstr = hex;
20670 };
20671 Roo.rtf.Paragraph = function(opts)
20672 {
20673     this.content = []; ///??? is that used?
20674 };Roo.rtf.Span = function(opts)
20675 {
20676     this.value = opts.value;
20677 };
20678
20679 Roo.rtf.Group = function(parent)
20680 {
20681     // we dont want to acutally store parent - it will make debug a nightmare..
20682     this.content = [];
20683     this.cn  = [];
20684      
20685        
20686     
20687 };
20688
20689 Roo.rtf.Group.prototype = {
20690     ignorable : false,
20691     content: false,
20692     cn: false,
20693     addContent : function(node) {
20694         // could set styles...
20695         this.content.push(node);
20696     },
20697     addChild : function(cn)
20698     {
20699         this.cn.push(cn);
20700     },
20701     // only for images really...
20702     toDataURL : function()
20703     {
20704         var mimetype = false;
20705         switch(true) {
20706             case this.content.filter(function(a) { return a.value == 'pngblip' } ).length > 0: 
20707                 mimetype = "image/png";
20708                 break;
20709              case this.content.filter(function(a) { return a.value == 'jpegblip' } ).length > 0:
20710                 mimetype = "image/jpeg";
20711                 break;
20712             default :
20713                 return 'about:blank'; // ?? error?
20714         }
20715         
20716         
20717         var hexstring = this.content[this.content.length-1].value;
20718         
20719         return 'data:' + mimetype + ';base64,' + btoa(hexstring.match(/\w{2}/g).map(function(a) {
20720             return String.fromCharCode(parseInt(a, 16));
20721         }).join(""));
20722     }
20723     
20724 };
20725 // this looks like it's normally the {rtf{ .... }}
20726 Roo.rtf.Document = function()
20727 {
20728     // we dont want to acutally store parent - it will make debug a nightmare..
20729     this.rtlch  = [];
20730     this.content = [];
20731     this.cn = [];
20732     
20733 };
20734 Roo.extend(Roo.rtf.Document, Roo.rtf.Group, { 
20735     addChild : function(cn)
20736     {
20737         this.cn.push(cn);
20738         switch(cn.type) {
20739             case 'rtlch': // most content seems to be inside this??
20740             case 'listtext':
20741             case 'shpinst':
20742                 this.rtlch.push(cn);
20743                 return;
20744             default:
20745                 this[cn.type] = cn;
20746         }
20747         
20748     },
20749     
20750     getElementsByType : function(type)
20751     {
20752         var ret =  [];
20753         this._getElementsByType(type, ret, this.cn, 'rtf');
20754         return ret;
20755     },
20756     _getElementsByType : function (type, ret, search_array, path)
20757     {
20758         search_array.forEach(function(n,i) {
20759             if (n.type == type) {
20760                 n.path = path + '/' + n.type + ':' + i;
20761                 ret.push(n);
20762             }
20763             if (n.cn.length > 0) {
20764                 this._getElementsByType(type, ret, n.cn, path + '/' + n.type+':'+i);
20765             }
20766         },this);
20767     }
20768     
20769 });
20770  
20771 Roo.rtf.Ctrl = function(opts)
20772 {
20773     this.value = opts.value;
20774     this.param = opts.param;
20775 };
20776 /**
20777  *
20778  *
20779  * based on this https://github.com/iarna/rtf-parser
20780  * it's really only designed to extract pict from pasted RTF 
20781  *
20782  * usage:
20783  *
20784  *  var images = new Roo.rtf.Parser().parse(a_string).filter(function(g) { return g.type == 'pict'; });
20785  *  
20786  *
20787  */
20788
20789  
20790
20791
20792
20793 Roo.rtf.Parser = function(text) {
20794     //super({objectMode: true})
20795     this.text = '';
20796     this.parserState = this.parseText;
20797     
20798     // these are for interpeter...
20799     this.doc = {};
20800     ///this.parserState = this.parseTop
20801     this.groupStack = [];
20802     this.hexStore = [];
20803     this.doc = false;
20804     
20805     this.groups = []; // where we put the return.
20806     
20807     for (var ii = 0; ii < text.length; ++ii) {
20808         ++this.cpos;
20809         
20810         if (text[ii] === '\n') {
20811             ++this.row;
20812             this.col = 1;
20813         } else {
20814             ++this.col;
20815         }
20816         this.parserState(text[ii]);
20817     }
20818     
20819     
20820     
20821 };
20822 Roo.rtf.Parser.prototype = {
20823     text : '', // string being parsed..
20824     controlWord : '',
20825     controlWordParam :  '',
20826     hexChar : '',
20827     doc : false,
20828     group: false,
20829     groupStack : false,
20830     hexStore : false,
20831     
20832     
20833     cpos : 0, 
20834     row : 1, // reportin?
20835     col : 1, //
20836
20837      
20838     push : function (el)
20839     {
20840         var m = 'cmd'+ el.type;
20841         if (typeof(this[m]) == 'undefined') {
20842             Roo.log('invalid cmd:' + el.type);
20843             return;
20844         }
20845         this[m](el);
20846         //Roo.log(el);
20847     },
20848     flushHexStore : function()
20849     {
20850         if (this.hexStore.length < 1) {
20851             return;
20852         }
20853         var hexstr = this.hexStore.map(
20854             function(cmd) {
20855                 return cmd.value;
20856         }).join('');
20857         
20858         this.group.addContent( new Roo.rtf.Hex( hexstr ));
20859               
20860             
20861         this.hexStore.splice(0)
20862         
20863     },
20864     
20865     cmdgroupstart : function()
20866     {
20867         this.flushHexStore();
20868         if (this.group) {
20869             this.groupStack.push(this.group);
20870         }
20871          // parent..
20872         if (this.doc === false) {
20873             this.group = this.doc = new Roo.rtf.Document();
20874             return;
20875             
20876         }
20877         this.group = new Roo.rtf.Group(this.group);
20878     },
20879     cmdignorable : function()
20880     {
20881         this.flushHexStore();
20882         this.group.ignorable = true;
20883     },
20884     cmdendparagraph : function()
20885     {
20886         this.flushHexStore();
20887         this.group.addContent(new Roo.rtf.Paragraph());
20888     },
20889     cmdgroupend : function ()
20890     {
20891         this.flushHexStore();
20892         var endingGroup = this.group;
20893         
20894         
20895         this.group = this.groupStack.pop();
20896         if (this.group) {
20897             this.group.addChild(endingGroup);
20898         }
20899         
20900         
20901         
20902         var doc = this.group || this.doc;
20903         //if (endingGroup instanceof FontTable) {
20904         //  doc.fonts = endingGroup.table
20905         //} else if (endingGroup instanceof ColorTable) {
20906         //  doc.colors = endingGroup.table
20907         //} else if (endingGroup !== this.doc && !endingGroup.get('ignorable')) {
20908         if (endingGroup.ignorable === false) {
20909             //code
20910             this.groups.push(endingGroup);
20911            // Roo.log( endingGroup );
20912         }
20913             //Roo.each(endingGroup.content, function(item)) {
20914             //    doc.addContent(item);
20915             //}
20916             //process.emit('debug', 'GROUP END', endingGroup.type, endingGroup.get('ignorable'))
20917         //}
20918     },
20919     cmdtext : function (cmd)
20920     {
20921         this.flushHexStore();
20922         if (!this.group) { // an RTF fragment, missing the {\rtf1 header
20923             //this.group = this.doc
20924             return;  // we really don't care about stray text...
20925         }
20926         this.group.addContent(new Roo.rtf.Span(cmd));
20927     },
20928     cmdcontrolword : function (cmd)
20929     {
20930         this.flushHexStore();
20931         if (!this.group.type) {
20932             this.group.type = cmd.value;
20933             return;
20934         }
20935         this.group.addContent(new Roo.rtf.Ctrl(cmd));
20936         // we actually don't care about ctrl words...
20937         return ;
20938         /*
20939         var method = 'ctrl$' + cmd.value.replace(/-(.)/g, (_, char) => char.toUpperCase())
20940         if (this[method]) {
20941             this[method](cmd.param)
20942         } else {
20943             if (!this.group.get('ignorable')) process.emit('debug', method, cmd.param)
20944         }
20945         */
20946     },
20947     cmdhexchar : function(cmd) {
20948         this.hexStore.push(cmd);
20949     },
20950     cmderror : function(cmd) {
20951         throw cmd.value;
20952     },
20953     
20954     /*
20955       _flush (done) {
20956         if (this.text !== '\u0000') this.emitText()
20957         done()
20958       }
20959       */
20960       
20961       
20962     parseText : function(c)
20963     {
20964         if (c === '\\') {
20965             this.parserState = this.parseEscapes;
20966         } else if (c === '{') {
20967             this.emitStartGroup();
20968         } else if (c === '}') {
20969             this.emitEndGroup();
20970         } else if (c === '\x0A' || c === '\x0D') {
20971             // cr/lf are noise chars
20972         } else {
20973             this.text += c;
20974         }
20975     },
20976     
20977     parseEscapes: function (c)
20978     {
20979         if (c === '\\' || c === '{' || c === '}') {
20980             this.text += c;
20981             this.parserState = this.parseText;
20982         } else {
20983             this.parserState = this.parseControlSymbol;
20984             this.parseControlSymbol(c);
20985         }
20986     },
20987     parseControlSymbol: function(c)
20988     {
20989         if (c === '~') {
20990             this.text += '\u00a0'; // nbsp
20991             this.parserState = this.parseText
20992         } else if (c === '-') {
20993              this.text += '\u00ad'; // soft hyphen
20994         } else if (c === '_') {
20995             this.text += '\u2011'; // non-breaking hyphen
20996         } else if (c === '*') {
20997             this.emitIgnorable();
20998             this.parserState = this.parseText;
20999         } else if (c === "'") {
21000             this.parserState = this.parseHexChar;
21001         } else if (c === '|') { // formula cacter
21002             this.emitFormula();
21003             this.parserState = this.parseText;
21004         } else if (c === ':') { // subentry in an index entry
21005             this.emitIndexSubEntry();
21006             this.parserState = this.parseText;
21007         } else if (c === '\x0a') {
21008             this.emitEndParagraph();
21009             this.parserState = this.parseText;
21010         } else if (c === '\x0d') {
21011             this.emitEndParagraph();
21012             this.parserState = this.parseText;
21013         } else {
21014             this.parserState = this.parseControlWord;
21015             this.parseControlWord(c);
21016         }
21017     },
21018     parseHexChar: function (c)
21019     {
21020         if (/^[A-Fa-f0-9]$/.test(c)) {
21021             this.hexChar += c;
21022             if (this.hexChar.length >= 2) {
21023               this.emitHexChar();
21024               this.parserState = this.parseText;
21025             }
21026             return;
21027         }
21028         this.emitError("Invalid character \"" + c + "\" in hex literal.");
21029         this.parserState = this.parseText;
21030         
21031     },
21032     parseControlWord : function(c)
21033     {
21034         if (c === ' ') {
21035             this.emitControlWord();
21036             this.parserState = this.parseText;
21037         } else if (/^[-\d]$/.test(c)) {
21038             this.parserState = this.parseControlWordParam;
21039             this.controlWordParam += c;
21040         } else if (/^[A-Za-z]$/.test(c)) {
21041           this.controlWord += c;
21042         } else {
21043           this.emitControlWord();
21044           this.parserState = this.parseText;
21045           this.parseText(c);
21046         }
21047     },
21048     parseControlWordParam : function (c) {
21049         if (/^\d$/.test(c)) {
21050           this.controlWordParam += c;
21051         } else if (c === ' ') {
21052           this.emitControlWord();
21053           this.parserState = this.parseText;
21054         } else {
21055           this.emitControlWord();
21056           this.parserState = this.parseText;
21057           this.parseText(c);
21058         }
21059     },
21060     
21061     
21062     
21063     
21064     emitText : function () {
21065         if (this.text === '') {
21066             return;
21067         }
21068         this.push({
21069             type: 'text',
21070             value: this.text,
21071             pos: this.cpos,
21072             row: this.row,
21073             col: this.col
21074         });
21075         this.text = ''
21076     },
21077     emitControlWord : function ()
21078     {
21079         this.emitText();
21080         if (this.controlWord === '') {
21081             // do we want to track this - it seems just to cause problems.
21082             //this.emitError('empty control word');
21083         } else {
21084             this.push({
21085                   type: 'controlword',
21086                   value: this.controlWord,
21087                   param: this.controlWordParam !== '' && Number(this.controlWordParam),
21088                   pos: this.cpos,
21089                   row: this.row,
21090                   col: this.col
21091             });
21092         }
21093         this.controlWord = '';
21094         this.controlWordParam = '';
21095     },
21096     emitStartGroup : function ()
21097     {
21098         this.emitText();
21099         this.push({
21100             type: 'groupstart',
21101             pos: this.cpos,
21102             row: this.row,
21103             col: this.col
21104         });
21105     },
21106     emitEndGroup : function ()
21107     {
21108         this.emitText();
21109         this.push({
21110             type: 'groupend',
21111             pos: this.cpos,
21112             row: this.row,
21113             col: this.col
21114         });
21115     },
21116     emitIgnorable : function ()
21117     {
21118         this.emitText();
21119         this.push({
21120             type: 'ignorable',
21121             pos: this.cpos,
21122             row: this.row,
21123             col: this.col
21124         });
21125     },
21126     emitHexChar : function ()
21127     {
21128         this.emitText();
21129         this.push({
21130             type: 'hexchar',
21131             value: this.hexChar,
21132             pos: this.cpos,
21133             row: this.row,
21134             col: this.col
21135         });
21136         this.hexChar = ''
21137     },
21138     emitError : function (message)
21139     {
21140       this.emitText();
21141       this.push({
21142             type: 'error',
21143             value: message,
21144             row: this.row,
21145             col: this.col,
21146             char: this.cpos //,
21147             //stack: new Error().stack
21148         });
21149     },
21150     emitEndParagraph : function () {
21151         this.emitText();
21152         this.push({
21153             type: 'endparagraph',
21154             pos: this.cpos,
21155             row: this.row,
21156             col: this.col
21157         });
21158     }
21159      
21160 } ;
21161 Roo.htmleditor = {};
21162  
21163 /**
21164  * @class Roo.htmleditor.Filter
21165  * Base Class for filtering htmleditor stuff. - do not use this directly - extend it.
21166  * @cfg {DomElement} node The node to iterate and filter
21167  * @cfg {boolean|String|Array} tag Tags to replace 
21168  * @constructor
21169  * Create a new Filter.
21170  * @param {Object} config Configuration options
21171  */
21172
21173
21174
21175 Roo.htmleditor.Filter = function(cfg) {
21176     Roo.apply(this.cfg);
21177     // this does not actually call walk as it's really just a abstract class
21178 }
21179
21180
21181 Roo.htmleditor.Filter.prototype = {
21182     
21183     node: false,
21184     
21185     tag: false,
21186
21187     // overrride to do replace comments.
21188     replaceComment : false,
21189     
21190     // overrride to do replace or do stuff with tags..
21191     replaceTag : false,
21192     
21193     walk : function(dom)
21194     {
21195         Roo.each( Array.from(dom.childNodes), function( e ) {
21196             switch(true) {
21197                 
21198                 case e.nodeType == 8 &&  this.replaceComment  !== false: // comment
21199                     this.replaceComment(e);
21200                     return;
21201                 
21202                 case e.nodeType != 1: //not a node.
21203                     return;
21204                 
21205                 case this.tag === true: // everything
21206                 case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1:
21207                 case e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":":
21208                 case typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1: // array and it matches.
21209                 case typeof(this.tag) == 'string' && this.tag == e.tagName: // array and it matches.
21210                     if (this.replaceTag && false === this.replaceTag(e)) {
21211                         return;
21212                     }
21213                     if (e.hasChildNodes()) {
21214                         this.walk(e);
21215                     }
21216                     return;
21217                 
21218                 default:    // tags .. that do not match.
21219                     if (e.hasChildNodes()) {
21220                         this.walk(e);
21221                     }
21222             }
21223             
21224         }, this);
21225         
21226     },
21227     
21228     
21229     removeNodeKeepChildren : function( node)
21230     {
21231     
21232         ar = Array.from(node.childNodes);
21233         for (var i = 0; i < ar.length; i++) {
21234          
21235             node.removeChild(ar[i]);
21236             // what if we need to walk these???
21237             node.parentNode.insertBefore(ar[i], node);
21238            
21239         }
21240         node.parentNode.removeChild(node);
21241     }
21242 }; 
21243
21244 /**
21245  * @class Roo.htmleditor.FilterAttributes
21246  * clean attributes and  styles including http:// etc.. in attribute
21247  * @constructor
21248 * Run a new Attribute Filter
21249 * @param {Object} config Configuration options
21250  */
21251 Roo.htmleditor.FilterAttributes = function(cfg)
21252 {
21253     Roo.apply(this, cfg);
21254     this.attrib_black = this.attrib_black || [];
21255     this.attrib_white = this.attrib_white || [];
21256
21257     this.attrib_clean = this.attrib_clean || [];
21258     this.style_white = this.style_white || [];
21259     this.style_black = this.style_black || [];
21260     this.walk(cfg.node);
21261 }
21262
21263 Roo.extend(Roo.htmleditor.FilterAttributes, Roo.htmleditor.Filter,
21264 {
21265     tag: true, // all tags
21266     
21267     attrib_black : false, // array
21268     attrib_clean : false,
21269     attrib_white : false,
21270
21271     style_white : false,
21272     style_black : false,
21273      
21274      
21275     replaceTag : function(node)
21276     {
21277         if (!node.attributes || !node.attributes.length) {
21278             return true;
21279         }
21280         
21281         for (var i = node.attributes.length-1; i > -1 ; i--) {
21282             var a = node.attributes[i];
21283             //console.log(a);
21284             if (this.attrib_white.length && this.attrib_white.indexOf(a.name.toLowerCase()) < 0) {
21285                 node.removeAttribute(a.name);
21286                 continue;
21287             }
21288             
21289             
21290             
21291             if (a.name.toLowerCase().substr(0,2)=='on')  {
21292                 node.removeAttribute(a.name);
21293                 continue;
21294             }
21295             
21296             
21297             if (this.attrib_black.indexOf(a.name.toLowerCase()) > -1) {
21298                 node.removeAttribute(a.name);
21299                 continue;
21300             }
21301             if (this.attrib_clean.indexOf(a.name.toLowerCase()) > -1) {
21302                 this.cleanAttr(node,a.name,a.value); // fixme..
21303                 continue;
21304             }
21305             if (a.name == 'style') {
21306                 this.cleanStyle(node,a.name,a.value);
21307                 continue;
21308             }
21309             /// clean up MS crap..
21310             // tecnically this should be a list of valid class'es..
21311             
21312             
21313             if (a.name == 'class') {
21314                 if (a.value.match(/^Mso/)) {
21315                     node.removeAttribute('class');
21316                 }
21317                 
21318                 if (a.value.match(/^body$/)) {
21319                     node.removeAttribute('class');
21320                 }
21321                 continue;
21322             }
21323             
21324             
21325             // style cleanup!?
21326             // class cleanup?
21327             
21328         }
21329         return true; // clean children
21330     },
21331         
21332     cleanAttr: function(node, n,v)
21333     {
21334         
21335         if (v.match(/^\./) || v.match(/^\//)) {
21336             return;
21337         }
21338         if (v.match(/^(http|https):\/\//)
21339             || v.match(/^mailto:/) 
21340             || v.match(/^ftp:/)
21341             || v.match(/^data:/)
21342             ) {
21343             return;
21344         }
21345         if (v.match(/^#/)) {
21346             return;
21347         }
21348         if (v.match(/^\{/)) { // allow template editing.
21349             return;
21350         }
21351 //            Roo.log("(REMOVE TAG)"+ node.tagName +'.' + n + '=' + v);
21352         node.removeAttribute(n);
21353         
21354     },
21355     cleanStyle : function(node,  n,v)
21356     {
21357         if (v.match(/expression/)) { //XSS?? should we even bother..
21358             node.removeAttribute(n);
21359             return;
21360         }
21361         
21362         var parts = v.split(/;/);
21363         var clean = [];
21364         
21365         Roo.each(parts, function(p) {
21366             p = p.replace(/^\s+/g,'').replace(/\s+$/g,'');
21367             if (!p.length) {
21368                 return true;
21369             }
21370             var l = p.split(':').shift().replace(/\s+/g,'');
21371             l = l.replace(/^\s+/g,'').replace(/\s+$/g,'');
21372             
21373             if ( this.style_black.length && (this.style_black.indexOf(l) > -1 || this.style_black.indexOf(l.toLowerCase()) > -1)) {
21374                 return true;
21375             }
21376             //Roo.log()
21377             // only allow 'c whitelisted system attributes'
21378             if ( this.style_white.length &&  style_white.indexOf(l) < 0 && style_white.indexOf(l.toLowerCase()) < 0 ) {
21379                 return true;
21380             }
21381             
21382             
21383             clean.push(p);
21384             return true;
21385         },this);
21386         if (clean.length) { 
21387             node.setAttribute(n, clean.join(';'));
21388         } else {
21389             node.removeAttribute(n);
21390         }
21391         
21392     }
21393         
21394         
21395         
21396     
21397 });/**
21398  * @class Roo.htmleditor.FilterBlack
21399  * remove blacklisted elements.
21400  * @constructor
21401  * Run a new Blacklisted Filter
21402  * @param {Object} config Configuration options
21403  */
21404
21405 Roo.htmleditor.FilterBlack = function(cfg)
21406 {
21407     Roo.apply(this, cfg);
21408     this.walk(cfg.node);
21409 }
21410
21411 Roo.extend(Roo.htmleditor.FilterBlack, Roo.htmleditor.Filter,
21412 {
21413     tag : true, // all elements.
21414    
21415     replaceTag : function(n)
21416     {
21417         n.parentNode.removeChild(n);
21418     }
21419 });
21420 /**
21421  * @class Roo.htmleditor.FilterComment
21422  * remove comments.
21423  * @constructor
21424 * Run a new Comments Filter
21425 * @param {Object} config Configuration options
21426  */
21427 Roo.htmleditor.FilterComment = function(cfg)
21428 {
21429     this.walk(cfg.node);
21430 }
21431
21432 Roo.extend(Roo.htmleditor.FilterComment, Roo.htmleditor.Filter,
21433 {
21434   
21435     replaceComment : function(n)
21436     {
21437         n.parentNode.removeChild(n);
21438     }
21439 });/**
21440  * @class Roo.htmleditor.FilterKeepChildren
21441  * remove tags but keep children
21442  * @constructor
21443  * Run a new Keep Children Filter
21444  * @param {Object} config Configuration options
21445  */
21446
21447 Roo.htmleditor.FilterKeepChildren = function(cfg)
21448 {
21449     Roo.apply(this, cfg);
21450     if (this.tag === false) {
21451         return; // dont walk.. (you can use this to use this just to do a child removal on a single tag )
21452     }
21453     // hacky?
21454     if ((typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)) {
21455         this.cleanNamespace = true;
21456     }
21457         
21458     this.walk(cfg.node);
21459 }
21460
21461 Roo.extend(Roo.htmleditor.FilterKeepChildren, Roo.htmleditor.FilterBlack,
21462 {
21463     cleanNamespace : false, // should really be an option, rather than using ':' inside of this tag.
21464   
21465     replaceTag : function(node)
21466     {
21467         // walk children...
21468         //Roo.log(node.tagName);
21469         var ar = Array.from(node.childNodes);
21470         //remove first..
21471         
21472         for (var i = 0; i < ar.length; i++) {
21473             var e = ar[i];
21474             if (e.nodeType == 1) {
21475                 if (
21476                     (typeof(this.tag) == 'object' && this.tag.indexOf(e.tagName) > -1)
21477                     || // array and it matches
21478                     (typeof(this.tag) == 'string' && this.tag == e.tagName)
21479                     ||
21480                     (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'object' && this.tag.indexOf(":") > -1)
21481                     ||
21482                     (e.tagName.indexOf(":") > -1 && typeof(this.tag) == 'string' && this.tag == ":")
21483                 ) {
21484                     this.replaceTag(ar[i]); // child is blacklisted as well...
21485                     continue;
21486                 }
21487             }
21488         }  
21489         ar = Array.from(node.childNodes);
21490         for (var i = 0; i < ar.length; i++) {
21491          
21492             node.removeChild(ar[i]);
21493             // what if we need to walk these???
21494             node.parentNode.insertBefore(ar[i], node);
21495             if (this.tag !== false) {
21496                 this.walk(ar[i]);
21497                 
21498             }
21499         }
21500         //Roo.log("REMOVE:" + node.tagName);
21501         node.parentNode.removeChild(node);
21502         return false; // don't walk children
21503         
21504         
21505     }
21506 });/**
21507  * @class Roo.htmleditor.FilterParagraph
21508  * paragraphs cause a nightmare for shared content - this filter is designed to be called ? at various points when editing
21509  * like on 'push' to remove the <p> tags and replace them with line breaks.
21510  * @constructor
21511  * Run a new Paragraph Filter
21512  * @param {Object} config Configuration options
21513  */
21514
21515 Roo.htmleditor.FilterParagraph = function(cfg)
21516 {
21517     // no need to apply config.
21518     this.walk(cfg.node);
21519 }
21520
21521 Roo.extend(Roo.htmleditor.FilterParagraph, Roo.htmleditor.Filter,
21522 {
21523     
21524      
21525     tag : 'P',
21526     
21527      
21528     replaceTag : function(node)
21529     {
21530         
21531         if (node.childNodes.length == 1 &&
21532             node.childNodes[0].nodeType == 3 &&
21533             node.childNodes[0].textContent.trim().length < 1
21534             ) {
21535             // remove and replace with '<BR>';
21536             node.parentNode.replaceChild(node.ownerDocument.createElement('BR'),node);
21537             return false; // no need to walk..
21538         }
21539         var ar = Array.from(node.childNodes);
21540         for (var i = 0; i < ar.length; i++) {
21541             node.removeChild(ar[i]);
21542             // what if we need to walk these???
21543             node.parentNode.insertBefore(ar[i], node);
21544         }
21545         // now what about this?
21546         // <p> &nbsp; </p>
21547         
21548         // double BR.
21549         node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
21550         node.parentNode.insertBefore(node.ownerDocument.createElement('BR'), node);
21551         node.parentNode.removeChild(node);
21552         
21553         return false;
21554
21555     }
21556     
21557 });/**
21558  * @class Roo.htmleditor.FilterSpan
21559  * filter span's with no attributes out..
21560  * @constructor
21561  * Run a new Span Filter
21562  * @param {Object} config Configuration options
21563  */
21564
21565 Roo.htmleditor.FilterSpan = function(cfg)
21566 {
21567     // no need to apply config.
21568     this.walk(cfg.node);
21569 }
21570
21571 Roo.extend(Roo.htmleditor.FilterSpan, Roo.htmleditor.FilterKeepChildren,
21572 {
21573      
21574     tag : 'SPAN',
21575      
21576  
21577     replaceTag : function(node)
21578     {
21579         if (node.attributes && node.attributes.length > 0) {
21580             return true; // walk if there are any.
21581         }
21582         Roo.htmleditor.FilterKeepChildren.prototype.replaceTag.call(this, node);
21583         return false;
21584      
21585     }
21586     
21587 });/**
21588  * @class Roo.htmleditor.FilterTableWidth
21589   try and remove table width data - as that frequently messes up other stuff.
21590  * 
21591  *      was cleanTableWidths.
21592  *
21593  * Quite often pasting from word etc.. results in tables with column and widths.
21594  * This does not work well on fluid HTML layouts - like emails. - so this code should hunt an destroy them..
21595  *
21596  * @constructor
21597  * Run a new Table Filter
21598  * @param {Object} config Configuration options
21599  */
21600
21601 Roo.htmleditor.FilterTableWidth = function(cfg)
21602 {
21603     // no need to apply config.
21604     this.tag = ['TABLE', 'TD', 'TR', 'TH', 'THEAD', 'TBODY' ];
21605     this.walk(cfg.node);
21606 }
21607
21608 Roo.extend(Roo.htmleditor.FilterTableWidth, Roo.htmleditor.Filter,
21609 {
21610      
21611      
21612     
21613     replaceTag: function(node) {
21614         
21615         
21616       
21617         if (node.hasAttribute('width')) {
21618             node.removeAttribute('width');
21619         }
21620         
21621          
21622         if (node.hasAttribute("style")) {
21623             // pretty basic...
21624             
21625             var styles = node.getAttribute("style").split(";");
21626             var nstyle = [];
21627             Roo.each(styles, function(s) {
21628                 if (!s.match(/:/)) {
21629                     return;
21630                 }
21631                 var kv = s.split(":");
21632                 if (kv[0].match(/^\s*(width|min-width)\s*$/)) {
21633                     return;
21634                 }
21635                 // what ever is left... we allow.
21636                 nstyle.push(s);
21637             });
21638             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
21639             if (!nstyle.length) {
21640                 node.removeAttribute('style');
21641             }
21642         }
21643         
21644         return true; // continue doing children..
21645     }
21646 });/**
21647  * @class Roo.htmleditor.FilterWord
21648  * try and clean up all the mess that Word generates.
21649  * 
21650  * This is the 'nice version' - see 'Heavy' that white lists a very short list of elements, and multi-filters 
21651  
21652  * @constructor
21653  * Run a new Span Filter
21654  * @param {Object} config Configuration options
21655  */
21656
21657 Roo.htmleditor.FilterWord = function(cfg)
21658 {
21659     // no need to apply config.
21660     this.replaceDocBullets(cfg.node);
21661     
21662     this.replaceAname(cfg.node);
21663     // this is disabled as the removal is done by other filters;
21664    // this.walk(cfg.node);
21665     
21666     
21667 }
21668
21669 Roo.extend(Roo.htmleditor.FilterWord, Roo.htmleditor.Filter,
21670 {
21671     tag: true,
21672      
21673     
21674     /**
21675      * Clean up MS wordisms...
21676      */
21677     replaceTag : function(node)
21678     {
21679          
21680         // no idea what this does - span with text, replaceds with just text.
21681         if(
21682                 node.nodeName == 'SPAN' &&
21683                 !node.hasAttributes() &&
21684                 node.childNodes.length == 1 &&
21685                 node.firstChild.nodeName == "#text"  
21686         ) {
21687             var textNode = node.firstChild;
21688             node.removeChild(textNode);
21689             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
21690                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" "), node);
21691             }
21692             node.parentNode.insertBefore(textNode, node);
21693             if (node.getAttribute('lang') != 'zh-CN') {   // do not space pad on chinese characters..
21694                 node.parentNode.insertBefore(node.ownerDocument.createTextNode(" ") , node);
21695             }
21696             
21697             node.parentNode.removeChild(node);
21698             return false; // dont do chidren - we have remove our node - so no need to do chdhilren?
21699         }
21700         
21701    
21702         
21703         if (node.tagName.toLowerCase().match(/^(style|script|applet|embed|noframes|noscript)$/)) {
21704             node.parentNode.removeChild(node);
21705             return false; // dont do chidlren
21706         }
21707         //Roo.log(node.tagName);
21708         // remove - but keep children..
21709         if (node.tagName.toLowerCase().match(/^(meta|link|\\?xml:|st1:|o:|v:|font)/)) {
21710             //Roo.log('-- removed');
21711             while (node.childNodes.length) {
21712                 var cn = node.childNodes[0];
21713                 node.removeChild(cn);
21714                 node.parentNode.insertBefore(cn, node);
21715                 // move node to parent - and clean it..
21716                 if (cn.nodeType == 1) {
21717                     this.replaceTag(cn);
21718                 }
21719                 
21720             }
21721             node.parentNode.removeChild(node);
21722             /// no need to iterate chidlren = it's got none..
21723             //this.iterateChildren(node, this.cleanWord);
21724             return false; // no need to iterate children.
21725         }
21726         // clean styles
21727         if (node.className.length) {
21728             
21729             var cn = node.className.split(/\W+/);
21730             var cna = [];
21731             Roo.each(cn, function(cls) {
21732                 if (cls.match(/Mso[a-zA-Z]+/)) {
21733                     return;
21734                 }
21735                 cna.push(cls);
21736             });
21737             node.className = cna.length ? cna.join(' ') : '';
21738             if (!cna.length) {
21739                 node.removeAttribute("class");
21740             }
21741         }
21742         
21743         if (node.hasAttribute("lang")) {
21744             node.removeAttribute("lang");
21745         }
21746         
21747         if (node.hasAttribute("style")) {
21748             
21749             var styles = node.getAttribute("style").split(";");
21750             var nstyle = [];
21751             Roo.each(styles, function(s) {
21752                 if (!s.match(/:/)) {
21753                     return;
21754                 }
21755                 var kv = s.split(":");
21756                 if (kv[0].match(/^(mso-|line|font|background|margin|padding|color)/)) {
21757                     return;
21758                 }
21759                 // what ever is left... we allow.
21760                 nstyle.push(s);
21761             });
21762             node.setAttribute("style", nstyle.length ? nstyle.join(';') : '');
21763             if (!nstyle.length) {
21764                 node.removeAttribute('style');
21765             }
21766         }
21767         return true; // do children
21768         
21769         
21770         
21771     },
21772     
21773     styleToObject: function(node)
21774     {
21775         var styles = (node.getAttribute("style") || '').split(";");
21776         var ret = {};
21777         Roo.each(styles, function(s) {
21778             if (!s.match(/:/)) {
21779                 return;
21780             }
21781             var kv = s.split(":");
21782              
21783             // what ever is left... we allow.
21784             ret[kv[0].trim()] = kv[1];
21785         });
21786         return ret;
21787     },
21788     
21789     
21790     replaceAname : function (doc)
21791     {
21792         // replace all the a/name without..
21793         var aa = Array.from(doc.getElementsByTagName('a'));
21794         for (var i = 0; i  < aa.length; i++) {
21795             var a = aa[i];
21796             if (a.hasAttribute("name")) {
21797                 a.removeAttribute("name");
21798             }
21799             if (a.hasAttribute("href")) {
21800                 continue;
21801             }
21802             // reparent children.
21803             this.removeNodeKeepChildren(a);
21804             
21805         }
21806         
21807         
21808         
21809     },
21810
21811     
21812     
21813     replaceDocBullets : function(doc)
21814     {
21815         // this is a bit odd - but it appears some indents use ql-indent-1
21816          //Roo.log(doc.innerHTML);
21817         
21818         var listpara = Array.from(doc.getElementsByClassName('MsoListParagraphCxSpFirst'));
21819         for( var i = 0; i < listpara.length; i ++) {
21820             listpara[i].className = "MsoListParagraph";
21821         }
21822         
21823         listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpMiddle'));
21824         for( var i = 0; i < listpara.length; i ++) {
21825             listpara[i].className = "MsoListParagraph";
21826         }
21827         listpara =  Array.from(doc.getElementsByClassName('MsoListParagraphCxSpLast'));
21828         for( var i = 0; i < listpara.length; i ++) {
21829             listpara[i].className = "MsoListParagraph";
21830         }
21831         listpara =  Array.from(doc.getElementsByClassName('ql-indent-1'));
21832         for( var i = 0; i < listpara.length; i ++) {
21833             listpara[i].className = "MsoListParagraph";
21834         }
21835         
21836         // this is a bit hacky - we had one word document where h2 had a miso-list attribute.
21837         var htwo =  Array.from(doc.getElementsByTagName('h2'));
21838         for( var i = 0; i < htwo.length; i ++) {
21839             if (htwo[i].hasAttribute('style') && htwo[i].getAttribute('style').match(/mso-list:/)) {
21840                 htwo[i].className = "MsoListParagraph";
21841             }
21842         }
21843         listpara =  Array.from(doc.getElementsByClassName('MsoNormal'));
21844         for( var i = 0; i < listpara.length; i ++) {
21845             if (listpara[i].hasAttribute('style') && listpara[i].getAttribute('style').match(/mso-list:/)) {
21846                 listpara[i].className = "MsoListParagraph";
21847             } else {
21848                 listpara[i].className = "MsoNormalx";
21849             }
21850         }
21851        
21852         listpara = doc.getElementsByClassName('MsoListParagraph');
21853         // Roo.log(doc.innerHTML);
21854         
21855         
21856         
21857         while(listpara.length) {
21858             
21859             this.replaceDocBullet(listpara.item(0));
21860         }
21861       
21862     },
21863     
21864      
21865     
21866     replaceDocBullet : function(p)
21867     {
21868         // gather all the siblings.
21869         var ns = p,
21870             parent = p.parentNode,
21871             doc = parent.ownerDocument,
21872             items = [];
21873             
21874         var listtype = 'ul';   
21875         while (ns) {
21876             if (ns.nodeType != 1) {
21877                 ns = ns.nextSibling;
21878                 continue;
21879             }
21880             if (!ns.className.match(/(MsoListParagraph|ql-indent-1)/i)) {
21881                 break;
21882             }
21883             var spans = ns.getElementsByTagName('span');
21884             if (ns.hasAttribute('style') && ns.getAttribute('style').match(/mso-list/)) {
21885                 items.push(ns);
21886                 ns = ns.nextSibling;
21887                 has_list = true;
21888                 if (spans.length && spans[0].hasAttribute('style')) {
21889                     var  style = this.styleToObject(spans[0]);
21890                     if (typeof(style['font-family']) != 'undefined' && !style['font-family'].match(/Symbol/)) {
21891                         listtype = 'ol';
21892                     }
21893                 }
21894                 
21895                 continue;
21896             }
21897             var spans = ns.getElementsByTagName('span');
21898             if (!spans.length) {
21899                 break;
21900             }
21901             var has_list  = false;
21902             for(var i = 0; i < spans.length; i++) {
21903                 if (spans[i].hasAttribute('style') && spans[i].getAttribute('style').match(/mso-list/)) {
21904                     has_list = true;
21905                     break;
21906                 }
21907             }
21908             if (!has_list) {
21909                 break;
21910             }
21911             items.push(ns);
21912             ns = ns.nextSibling;
21913             
21914             
21915         }
21916         if (!items.length) {
21917             ns.className = "";
21918             return;
21919         }
21920         
21921         var ul = parent.ownerDocument.createElement(listtype); // what about number lists...
21922         parent.insertBefore(ul, p);
21923         var lvl = 0;
21924         var stack = [ ul ];
21925         var last_li = false;
21926         
21927         var margin_to_depth = {};
21928         max_margins = -1;
21929         
21930         items.forEach(function(n, ipos) {
21931             //Roo.log("got innertHMLT=" + n.innerHTML);
21932             
21933             var spans = n.getElementsByTagName('span');
21934             if (!spans.length) {
21935                 //Roo.log("No spans found");
21936                  
21937                 parent.removeChild(n);
21938                 
21939                 
21940                 return; // skip it...
21941             }
21942            
21943                 
21944             var num = 1;
21945             var style = {};
21946             for(var i = 0; i < spans.length; i++) {
21947             
21948                 style = this.styleToObject(spans[i]);
21949                 if (typeof(style['mso-list']) == 'undefined') {
21950                     continue;
21951                 }
21952                 if (listtype == 'ol') {
21953                    num = spans[i].innerText.replace(/[^0-9]+]/g,'')  * 1;
21954                 }
21955                 spans[i].parentNode.removeChild(spans[i]); // remove the fake bullet.
21956                 break;
21957             }
21958             //Roo.log("NOW GOT innertHMLT=" + n.innerHTML);
21959             style = this.styleToObject(n); // mo-list is from the parent node.
21960             if (typeof(style['mso-list']) == 'undefined') {
21961                 //Roo.log("parent is missing level");
21962                   
21963                 parent.removeChild(n);
21964                  
21965                 return;
21966             }
21967             
21968             var margin = style['margin-left'];
21969             if (typeof(margin_to_depth[margin]) == 'undefined') {
21970                 max_margins++;
21971                 margin_to_depth[margin] = max_margins;
21972             }
21973             nlvl = margin_to_depth[margin] ;
21974              
21975             if (nlvl > lvl) {
21976                 //new indent
21977                 var nul = doc.createElement(listtype); // what about number lists...
21978                 if (!last_li) {
21979                     last_li = doc.createElement('li');
21980                     stack[lvl].appendChild(last_li);
21981                 }
21982                 last_li.appendChild(nul);
21983                 stack[nlvl] = nul;
21984                 
21985             }
21986             lvl = nlvl;
21987             
21988             // not starting at 1..
21989             if (!stack[nlvl].hasAttribute("start") && listtype == "ol") {
21990                 stack[nlvl].setAttribute("start", num);
21991             }
21992             
21993             var nli = stack[nlvl].appendChild(doc.createElement('li'));
21994             last_li = nli;
21995             nli.innerHTML = n.innerHTML;
21996             //Roo.log("innerHTML = " + n.innerHTML);
21997             parent.removeChild(n);
21998             
21999              
22000              
22001             
22002         },this);
22003         
22004         
22005         
22006         
22007     }
22008     
22009     
22010     
22011 });
22012 /**
22013  * @class Roo.htmleditor.FilterStyleToTag
22014  * part of the word stuff... - certain 'styles' should be converted to tags.
22015  * eg.
22016  *   font-weight: bold -> bold
22017  *   ?? super / subscrit etc..
22018  * 
22019  * @constructor
22020 * Run a new style to tag filter.
22021 * @param {Object} config Configuration options
22022  */
22023 Roo.htmleditor.FilterStyleToTag = function(cfg)
22024 {
22025     
22026     this.tags = {
22027         B  : [ 'fontWeight' , 'bold'],
22028         I :  [ 'fontStyle' , 'italic'],
22029         //pre :  [ 'font-style' , 'italic'],
22030         // h1.. h6 ?? font-size?
22031         SUP : [ 'verticalAlign' , 'super' ],
22032         SUB : [ 'verticalAlign' , 'sub' ]
22033         
22034         
22035     };
22036     
22037     Roo.apply(this, cfg);
22038      
22039     
22040     this.walk(cfg.node);
22041     
22042     
22043     
22044 }
22045
22046
22047 Roo.extend(Roo.htmleditor.FilterStyleToTag, Roo.htmleditor.Filter,
22048 {
22049     tag: true, // all tags
22050     
22051     tags : false,
22052     
22053     
22054     replaceTag : function(node)
22055     {
22056         
22057         
22058         if (node.getAttribute("style") === null) {
22059             return true;
22060         }
22061         var inject = [];
22062         for (var k in this.tags) {
22063             if (node.style[this.tags[k][0]] == this.tags[k][1]) {
22064                 inject.push(k);
22065                 node.style.removeProperty(this.tags[k][0]);
22066             }
22067         }
22068         if (!inject.length) {
22069             return true; 
22070         }
22071         var cn = Array.from(node.childNodes);
22072         var nn = node;
22073         Roo.each(inject, function(t) {
22074             var nc = node.ownerDocument.createElement(t);
22075             nn.appendChild(nc);
22076             nn = nc;
22077         });
22078         for(var i = 0;i < cn.length;cn++) {
22079             node.removeChild(cn[i]);
22080             nn.appendChild(cn[i]);
22081         }
22082         return true /// iterate thru
22083     }
22084     
22085 })/**
22086  * @class Roo.htmleditor.FilterLongBr
22087  * BR/BR/BR - keep a maximum of 2...
22088  * @constructor
22089  * Run a new Long BR Filter
22090  * @param {Object} config Configuration options
22091  */
22092
22093 Roo.htmleditor.FilterLongBr = function(cfg)
22094 {
22095     // no need to apply config.
22096     this.walk(cfg.node);
22097 }
22098
22099 Roo.extend(Roo.htmleditor.FilterLongBr, Roo.htmleditor.Filter,
22100 {
22101     
22102      
22103     tag : 'BR',
22104     
22105      
22106     replaceTag : function(node)
22107     {
22108         
22109         var ps = node.nextSibling;
22110         while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
22111             ps = ps.nextSibling;
22112         }
22113         
22114         if (!ps &&  [ 'TD', 'TH', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(node.parentNode.tagName) > -1) { 
22115             node.parentNode.removeChild(node); // remove last BR inside one fo these tags
22116             return false;
22117         }
22118         
22119         if (!ps || ps.nodeType != 1) {
22120             return false;
22121         }
22122         
22123         if (!ps || ps.tagName != 'BR') {
22124            
22125             return false;
22126         }
22127         
22128         
22129         
22130         
22131         
22132         if (!node.previousSibling) {
22133             return false;
22134         }
22135         var ps = node.previousSibling;
22136         
22137         while (ps && ps.nodeType == 3 && ps.nodeValue.trim().length < 1) {
22138             ps = ps.previousSibling;
22139         }
22140         if (!ps || ps.nodeType != 1) {
22141             return false;
22142         }
22143         // if header or BR before.. then it's a candidate for removal.. - as we only want '2' of these..
22144         if (!ps || [ 'BR', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6' ].indexOf(ps.tagName) < 0) {
22145             return false;
22146         }
22147         
22148         node.parentNode.removeChild(node); // remove me...
22149         
22150         return false; // no need to do children
22151
22152     }
22153     
22154 }); 
22155
22156 /**
22157  * @class Roo.htmleditor.FilterBlock
22158  * removes id / data-block and contenteditable that are associated with blocks
22159  * usage should be done on a cloned copy of the dom
22160  * @constructor
22161 * Run a new Attribute Filter { node : xxxx }}
22162 * @param {Object} config Configuration options
22163  */
22164 Roo.htmleditor.FilterBlock = function(cfg)
22165 {
22166     Roo.apply(this, cfg);
22167     var qa = cfg.node.querySelectorAll;
22168     this.removeAttributes('data-block');
22169     this.removeAttributes('contenteditable');
22170     this.removeAttributes('id');
22171     
22172 }
22173
22174 Roo.apply(Roo.htmleditor.FilterBlock.prototype,
22175 {
22176     node: true, // all tags
22177      
22178      
22179     removeAttributes : function(attr)
22180     {
22181         var ar = this.node.querySelectorAll('*[' + attr + ']');
22182         for (var i =0;i<ar.length;i++) {
22183             ar[i].removeAttribute(attr);
22184         }
22185     }
22186         
22187         
22188         
22189     
22190 });
22191 /***
22192  * This is based loosely on tinymce 
22193  * @class Roo.htmleditor.TidySerializer
22194  * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
22195  * @constructor
22196  * @method Serializer
22197  * @param {Object} settings Name/value settings object.
22198  */
22199
22200
22201 Roo.htmleditor.TidySerializer = function(settings)
22202 {
22203     Roo.apply(this, settings);
22204     
22205     this.writer = new Roo.htmleditor.TidyWriter(settings);
22206     
22207     
22208
22209 };
22210 Roo.htmleditor.TidySerializer.prototype = {
22211     
22212     /**
22213      * @param {boolean} inner do the inner of the node.
22214      */
22215     inner : false,
22216     
22217     writer : false,
22218     
22219     /**
22220     * Serializes the specified node into a string.
22221     *
22222     * @example
22223     * new tinymce.html.Serializer().serialize(new tinymce.html.DomParser().parse('<p>text</p>'));
22224     * @method serialize
22225     * @param {DomElement} node Node instance to serialize.
22226     * @return {String} String with HTML based on DOM tree.
22227     */
22228     serialize : function(node) {
22229         
22230         // = settings.validate;
22231         var writer = this.writer;
22232         var self  = this;
22233         this.handlers = {
22234             // #text
22235             3: function(node) {
22236                 
22237                 writer.text(node.nodeValue, node);
22238             },
22239             // #comment
22240             8: function(node) {
22241                 writer.comment(node.nodeValue);
22242             },
22243             // Processing instruction
22244             7: function(node) {
22245                 writer.pi(node.name, node.nodeValue);
22246             },
22247             // Doctype
22248             10: function(node) {
22249                 writer.doctype(node.nodeValue);
22250             },
22251             // CDATA
22252             4: function(node) {
22253                 writer.cdata(node.nodeValue);
22254             },
22255             // Document fragment
22256             11: function(node) {
22257                 node = node.firstChild;
22258                 if (!node) {
22259                     return;
22260                 }
22261                 while(node) {
22262                     self.walk(node);
22263                     node = node.nextSibling
22264                 }
22265             }
22266         };
22267         writer.reset();
22268         1 != node.nodeType || this.inner ? this.handlers[11](node) : this.walk(node);
22269         return writer.getContent();
22270     },
22271
22272     walk: function(node)
22273     {
22274         var attrName, attrValue, sortedAttrs, i, l, elementRule,
22275             handler = this.handlers[node.nodeType];
22276             
22277         if (handler) {
22278             handler(node);
22279             return;
22280         }
22281     
22282         var name = node.nodeName;
22283         var isEmpty = node.childNodes.length < 1;
22284       
22285         var writer = this.writer;
22286         var attrs = node.attributes;
22287         // Sort attributes
22288         
22289         writer.start(node.nodeName, attrs, isEmpty, node);
22290         if (isEmpty) {
22291             return;
22292         }
22293         node = node.firstChild;
22294         if (!node) {
22295             writer.end(name);
22296             return;
22297         }
22298         while (node) {
22299             this.walk(node);
22300             node = node.nextSibling;
22301         }
22302         writer.end(name);
22303         
22304     
22305     }
22306     // Serialize element and treat all non elements as fragments
22307    
22308 }; 
22309
22310 /***
22311  * This is based loosely on tinymce 
22312  * @class Roo.htmleditor.TidyWriter
22313  * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
22314  *
22315  * Known issues?
22316  * - not tested much with 'PRE' formated elements.
22317  * 
22318  *
22319  *
22320  */
22321
22322 Roo.htmleditor.TidyWriter = function(settings)
22323 {
22324     
22325     // indent, indentBefore, indentAfter, encode, htmlOutput, html = [];
22326     Roo.apply(this, settings);
22327     this.html = [];
22328     this.state = [];
22329      
22330     this.encode = Roo.htmleditor.TidyEntities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
22331   
22332 }
22333 Roo.htmleditor.TidyWriter.prototype = {
22334
22335  
22336     state : false,
22337     
22338     indent :  '  ',
22339     
22340     // part of state...
22341     indentstr : '',
22342     in_pre: false,
22343     in_inline : false,
22344     last_inline : false,
22345     encode : false,
22346      
22347     
22348             /**
22349     * Writes the a start element such as <p id="a">.
22350     *
22351     * @method start
22352     * @param {String} name Name of the element.
22353     * @param {Array} attrs Optional attribute array or undefined if it hasn't any.
22354     * @param {Boolean} empty Optional empty state if the tag should end like <br />.
22355     */
22356     start: function(name, attrs, empty, node)
22357     {
22358         var i, l, attr, value;
22359         
22360         // there are some situations where adding line break && indentation will not work. will not work.
22361         // <span / b / i ... formating?
22362         
22363         var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
22364         var in_pre    = this.in_pre    || Roo.htmleditor.TidyWriter.whitespace_elements.indexOf(name) > -1;
22365         
22366         var is_short   = empty ? Roo.htmleditor.TidyWriter.shortend_elements.indexOf(name) > -1 : false;
22367         
22368         var add_lb = name == 'BR' ? false : in_inline;
22369         
22370         if (!add_lb && !this.in_pre && this.lastElementEndsWS()) {
22371             i_inline = false;
22372         }
22373
22374         var indentstr =  this.indentstr;
22375         
22376         // e_inline = elements that can be inline, but still allow \n before and after?
22377         // only 'BR' ??? any others?
22378         
22379         // ADD LINE BEFORE tage
22380         if (!this.in_pre) {
22381             if (in_inline) {
22382                 //code
22383                 if (name == 'BR') {
22384                     this.addLine();
22385                 } else if (this.lastElementEndsWS()) {
22386                     this.addLine();
22387                 } else{
22388                     // otherwise - no new line. (and dont indent.)
22389                     indentstr = '';
22390                 }
22391                 
22392             } else {
22393                 this.addLine();
22394             }
22395         } else {
22396             indentstr = '';
22397         }
22398         
22399         this.html.push(indentstr + '<', name.toLowerCase());
22400         
22401         if (attrs) {
22402             for (i = 0, l = attrs.length; i < l; i++) {
22403                 attr = attrs[i];
22404                 this.html.push(' ', attr.name, '="', this.encode(attr.value, true), '"');
22405             }
22406         }
22407      
22408         if (empty) {
22409             if (is_short) {
22410                 this.html[this.html.length] = '/>';
22411             } else {
22412                 this.html[this.html.length] = '></' + name.toLowerCase() + '>';
22413             }
22414             var e_inline = name == 'BR' ? false : this.in_inline;
22415             
22416             if (!e_inline && !this.in_pre) {
22417                 this.addLine();
22418             }
22419             return;
22420         
22421         }
22422         // not empty..
22423         this.html[this.html.length] = '>';
22424         
22425         // there is a special situation, where we need to turn on in_inline - if any of the imediate chidlren are one of these.
22426         /*
22427         if (!in_inline && !in_pre) {
22428             var cn = node.firstChild;
22429             while(cn) {
22430                 if (Roo.htmleditor.TidyWriter.inline_elements.indexOf(cn.nodeName) > -1) {
22431                     in_inline = true
22432                     break;
22433                 }
22434                 cn = cn.nextSibling;
22435             }
22436              
22437         }
22438         */
22439         
22440         
22441         this.pushState({
22442             indentstr : in_pre   ? '' : (this.indentstr + this.indent),
22443             in_pre : in_pre,
22444             in_inline :  in_inline
22445         });
22446         // add a line after if we are not in a
22447         
22448         if (!in_inline && !in_pre) {
22449             this.addLine();
22450         }
22451         
22452             
22453          
22454         
22455     },
22456     
22457     lastElementEndsWS : function()
22458     {
22459         var value = this.html.length > 0 ? this.html[this.html.length-1] : false;
22460         if (value === false) {
22461             return true;
22462         }
22463         return value.match(/\s+$/);
22464         
22465     },
22466     
22467     /**
22468      * Writes the a end element such as </p>.
22469      *
22470      * @method end
22471      * @param {String} name Name of the element.
22472      */
22473     end: function(name) {
22474         var value;
22475         this.popState();
22476         var indentstr = '';
22477         var in_inline = this.in_inline || Roo.htmleditor.TidyWriter.inline_elements.indexOf(name) > -1;
22478         
22479         if (!this.in_pre && !in_inline) {
22480             this.addLine();
22481             indentstr  = this.indentstr;
22482         }
22483         this.html.push(indentstr + '</', name.toLowerCase(), '>');
22484         this.last_inline = in_inline;
22485         
22486         // pop the indent state..
22487     },
22488     /**
22489      * Writes a text node.
22490      *
22491      * In pre - we should not mess with the contents.
22492      * 
22493      *
22494      * @method text
22495      * @param {String} text String to write out.
22496      * @param {Boolean} raw Optional raw state if true the contents wont get encoded.
22497      */
22498     text: function(in_text, node)
22499     {
22500         // if not in whitespace critical
22501         if (in_text.length < 1) {
22502             return;
22503         }
22504         var text = new XMLSerializer().serializeToString(document.createTextNode(in_text)); // escape it properly?
22505         
22506         if (this.in_pre) {
22507             this.html[this.html.length] =  text;
22508             return;   
22509         }
22510         
22511         if (this.in_inline) {
22512             text = text.replace(/\s+/g,' '); // all white space inc line breaks to a slingle' '
22513             if (text != ' ') {
22514                 text = text.replace(/\s+/,' ');  // all white space to single white space
22515                 
22516                     
22517                 // if next tag is '<BR>', then we can trim right..
22518                 if (node.nextSibling &&
22519                     node.nextSibling.nodeType == 1 &&
22520                     node.nextSibling.nodeName == 'BR' )
22521                 {
22522                     text = text.replace(/\s+$/g,'');
22523                 }
22524                 // if previous tag was a BR, we can also trim..
22525                 if (node.previousSibling &&
22526                     node.previousSibling.nodeType == 1 &&
22527                     node.previousSibling.nodeName == 'BR' )
22528                 {
22529                     text = this.indentstr +  text.replace(/^\s+/g,'');
22530                 }
22531                 if (text.match(/\n/)) {
22532                     text = text.replace(
22533                         /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
22534                     );
22535                     // remoeve the last whitespace / line break.
22536                     text = text.replace(/\n\s+$/,'');
22537                 }
22538                 // repace long lines
22539                 
22540             }
22541              
22542             this.html[this.html.length] =  text;
22543             return;   
22544         }
22545         // see if previous element was a inline element.
22546         var indentstr = this.indentstr;
22547    
22548         text = text.replace(/\s+/g," "); // all whitespace into single white space.
22549         
22550         // should trim left?
22551         if (node.previousSibling &&
22552             node.previousSibling.nodeType == 1 &&
22553             Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.previousSibling.nodeName) > -1)
22554         {
22555             indentstr = '';
22556             
22557         } else {
22558             this.addLine();
22559             text = text.replace(/^\s+/,''); // trim left
22560           
22561         }
22562         // should trim right?
22563         if (node.nextSibling &&
22564             node.nextSibling.nodeType == 1 &&
22565             Roo.htmleditor.TidyWriter.inline_elements.indexOf(node.nextSibling.nodeName) > -1)
22566         {
22567           // noop
22568             
22569         }  else {
22570             text = text.replace(/\s+$/,''); // trim right
22571         }
22572          
22573               
22574         
22575         
22576         
22577         if (text.length < 1) {
22578             return;
22579         }
22580         if (!text.match(/\n/)) {
22581             this.html.push(indentstr + text);
22582             return;
22583         }
22584         
22585         text = this.indentstr + text.replace(
22586             /(?![^\n]{1,64}$)([^\n]{1,64})\s/g, '$1\n' + this.indentstr
22587         );
22588         // remoeve the last whitespace / line break.
22589         text = text.replace(/\s+$/,''); 
22590         
22591         this.html.push(text);
22592         
22593         // split and indent..
22594         
22595         
22596     },
22597     /**
22598      * Writes a cdata node such as <![CDATA[data]]>.
22599      *
22600      * @method cdata
22601      * @param {String} text String to write out inside the cdata.
22602      */
22603     cdata: function(text) {
22604         this.html.push('<![CDATA[', text, ']]>');
22605     },
22606     /**
22607     * Writes a comment node such as <!-- Comment -->.
22608     *
22609     * @method cdata
22610     * @param {String} text String to write out inside the comment.
22611     */
22612    comment: function(text) {
22613        this.html.push('<!--', text, '-->');
22614    },
22615     /**
22616      * Writes a PI node such as <?xml attr="value" ?>.
22617      *
22618      * @method pi
22619      * @param {String} name Name of the pi.
22620      * @param {String} text String to write out inside the pi.
22621      */
22622     pi: function(name, text) {
22623         text ? this.html.push('<?', name, ' ', this.encode(text), '?>') : this.html.push('<?', name, '?>');
22624         this.indent != '' && this.html.push('\n');
22625     },
22626     /**
22627      * Writes a doctype node such as <!DOCTYPE data>.
22628      *
22629      * @method doctype
22630      * @param {String} text String to write out inside the doctype.
22631      */
22632     doctype: function(text) {
22633         this.html.push('<!DOCTYPE', text, '>', this.indent != '' ? '\n' : '');
22634     },
22635     /**
22636      * Resets the internal buffer if one wants to reuse the writer.
22637      *
22638      * @method reset
22639      */
22640     reset: function() {
22641         this.html.length = 0;
22642         this.state = [];
22643         this.pushState({
22644             indentstr : '',
22645             in_pre : false, 
22646             in_inline : false
22647         })
22648     },
22649     /**
22650      * Returns the contents that got serialized.
22651      *
22652      * @method getContent
22653      * @return {String} HTML contents that got written down.
22654      */
22655     getContent: function() {
22656         return this.html.join('').replace(/\n$/, '');
22657     },
22658     
22659     pushState : function(cfg)
22660     {
22661         this.state.push(cfg);
22662         Roo.apply(this, cfg);
22663     },
22664     
22665     popState : function()
22666     {
22667         if (this.state.length < 1) {
22668             return; // nothing to push
22669         }
22670         var cfg = {
22671             in_pre: false,
22672             indentstr : ''
22673         };
22674         this.state.pop();
22675         if (this.state.length > 0) {
22676             cfg = this.state[this.state.length-1]; 
22677         }
22678         Roo.apply(this, cfg);
22679     },
22680     
22681     addLine: function()
22682     {
22683         if (this.html.length < 1) {
22684             return;
22685         }
22686         
22687         
22688         var value = this.html[this.html.length - 1];
22689         if (value.length > 0 && '\n' !== value) {
22690             this.html.push('\n');
22691         }
22692     }
22693     
22694     
22695 //'pre script noscript style textarea video audio iframe object code'
22696 // shortended... 'area base basefont br col frame hr img input isindex link  meta param embed source wbr track');
22697 // inline 
22698 };
22699
22700 Roo.htmleditor.TidyWriter.inline_elements = [
22701         'SPAN','STRONG','B','EM','I','FONT','STRIKE','U','VAR',
22702         'CITE','DFN','CODE','MARK','Q','SUP','SUB','SAMP', 'A'
22703 ];
22704 Roo.htmleditor.TidyWriter.shortend_elements = [
22705     'AREA','BASE','BASEFONT','BR','COL','FRAME','HR','IMG','INPUT',
22706     'ISINDEX','LINK','','META','PARAM','EMBED','SOURCE','WBR','TRACK'
22707 ];
22708
22709 Roo.htmleditor.TidyWriter.whitespace_elements = [
22710     'PRE','SCRIPT','NOSCRIPT','STYLE','TEXTAREA','VIDEO','AUDIO','IFRAME','OBJECT','CODE'
22711 ];/***
22712  * This is based loosely on tinymce 
22713  * @class Roo.htmleditor.TidyEntities
22714  * @static
22715  * https://github.com/thorn0/tinymce.html/blob/master/tinymce.html.js
22716  *
22717  * Not 100% sure this is actually used or needed.
22718  */
22719
22720 Roo.htmleditor.TidyEntities = {
22721     
22722     /**
22723      * initialize data..
22724      */
22725     init : function (){
22726      
22727         this.namedEntities = this.buildEntitiesLookup(this.namedEntitiesData, 32);
22728        
22729     },
22730
22731
22732     buildEntitiesLookup: function(items, radix) {
22733         var i, chr, entity, lookup = {};
22734         if (!items) {
22735             return {};
22736         }
22737         items = typeof(items) == 'string' ? items.split(',') : items;
22738         radix = radix || 10;
22739         // Build entities lookup table
22740         for (i = 0; i < items.length; i += 2) {
22741             chr = String.fromCharCode(parseInt(items[i], radix));
22742             // Only add non base entities
22743             if (!this.baseEntities[chr]) {
22744                 entity = '&' + items[i + 1] + ';';
22745                 lookup[chr] = entity;
22746                 lookup[entity] = chr;
22747             }
22748         }
22749         return lookup;
22750         
22751     },
22752     
22753     asciiMap : {
22754             128: '€',
22755             130: '‚',
22756             131: 'ƒ',
22757             132: '„',
22758             133: '…',
22759             134: '†',
22760             135: '‡',
22761             136: 'ˆ',
22762             137: '‰',
22763             138: 'Š',
22764             139: '‹',
22765             140: 'Œ',
22766             142: 'Ž',
22767             145: '‘',
22768             146: '’',
22769             147: '“',
22770             148: '”',
22771             149: '•',
22772             150: '–',
22773             151: '—',
22774             152: '˜',
22775             153: '™',
22776             154: 'š',
22777             155: '›',
22778             156: 'œ',
22779             158: 'ž',
22780             159: 'Ÿ'
22781     },
22782     // Raw entities
22783     baseEntities : {
22784         '"': '&quot;',
22785         // Needs to be escaped since the YUI compressor would otherwise break the code
22786         '\'': '&#39;',
22787         '<': '&lt;',
22788         '>': '&gt;',
22789         '&': '&amp;',
22790         '`': '&#96;'
22791     },
22792     // Reverse lookup table for raw entities
22793     reverseEntities : {
22794         '&lt;': '<',
22795         '&gt;': '>',
22796         '&amp;': '&',
22797         '&quot;': '"',
22798         '&apos;': '\''
22799     },
22800     
22801     attrsCharsRegExp : /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
22802     textCharsRegExp : /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
22803     rawCharsRegExp : /[<>&\"\']/g,
22804     entityRegExp : /&#([a-z0-9]+);?|&([a-z0-9]+);/gi,
22805     namedEntities  : false,
22806     namedEntitiesData : [ 
22807         '50',
22808         'nbsp',
22809         '51',
22810         'iexcl',
22811         '52',
22812         'cent',
22813         '53',
22814         'pound',
22815         '54',
22816         'curren',
22817         '55',
22818         'yen',
22819         '56',
22820         'brvbar',
22821         '57',
22822         'sect',
22823         '58',
22824         'uml',
22825         '59',
22826         'copy',
22827         '5a',
22828         'ordf',
22829         '5b',
22830         'laquo',
22831         '5c',
22832         'not',
22833         '5d',
22834         'shy',
22835         '5e',
22836         'reg',
22837         '5f',
22838         'macr',
22839         '5g',
22840         'deg',
22841         '5h',
22842         'plusmn',
22843         '5i',
22844         'sup2',
22845         '5j',
22846         'sup3',
22847         '5k',
22848         'acute',
22849         '5l',
22850         'micro',
22851         '5m',
22852         'para',
22853         '5n',
22854         'middot',
22855         '5o',
22856         'cedil',
22857         '5p',
22858         'sup1',
22859         '5q',
22860         'ordm',
22861         '5r',
22862         'raquo',
22863         '5s',
22864         'frac14',
22865         '5t',
22866         'frac12',
22867         '5u',
22868         'frac34',
22869         '5v',
22870         'iquest',
22871         '60',
22872         'Agrave',
22873         '61',
22874         'Aacute',
22875         '62',
22876         'Acirc',
22877         '63',
22878         'Atilde',
22879         '64',
22880         'Auml',
22881         '65',
22882         'Aring',
22883         '66',
22884         'AElig',
22885         '67',
22886         'Ccedil',
22887         '68',
22888         'Egrave',
22889         '69',
22890         'Eacute',
22891         '6a',
22892         'Ecirc',
22893         '6b',
22894         'Euml',
22895         '6c',
22896         'Igrave',
22897         '6d',
22898         'Iacute',
22899         '6e',
22900         'Icirc',
22901         '6f',
22902         'Iuml',
22903         '6g',
22904         'ETH',
22905         '6h',
22906         'Ntilde',
22907         '6i',
22908         'Ograve',
22909         '6j',
22910         'Oacute',
22911         '6k',
22912         'Ocirc',
22913         '6l',
22914         'Otilde',
22915         '6m',
22916         'Ouml',
22917         '6n',
22918         'times',
22919         '6o',
22920         'Oslash',
22921         '6p',
22922         'Ugrave',
22923         '6q',
22924         'Uacute',
22925         '6r',
22926         'Ucirc',
22927         '6s',
22928         'Uuml',
22929         '6t',
22930         'Yacute',
22931         '6u',
22932         'THORN',
22933         '6v',
22934         'szlig',
22935         '70',
22936         'agrave',
22937         '71',
22938         'aacute',
22939         '72',
22940         'acirc',
22941         '73',
22942         'atilde',
22943         '74',
22944         'auml',
22945         '75',
22946         'aring',
22947         '76',
22948         'aelig',
22949         '77',
22950         'ccedil',
22951         '78',
22952         'egrave',
22953         '79',
22954         'eacute',
22955         '7a',
22956         'ecirc',
22957         '7b',
22958         'euml',
22959         '7c',
22960         'igrave',
22961         '7d',
22962         'iacute',
22963         '7e',
22964         'icirc',
22965         '7f',
22966         'iuml',
22967         '7g',
22968         'eth',
22969         '7h',
22970         'ntilde',
22971         '7i',
22972         'ograve',
22973         '7j',
22974         'oacute',
22975         '7k',
22976         'ocirc',
22977         '7l',
22978         'otilde',
22979         '7m',
22980         'ouml',
22981         '7n',
22982         'divide',
22983         '7o',
22984         'oslash',
22985         '7p',
22986         'ugrave',
22987         '7q',
22988         'uacute',
22989         '7r',
22990         'ucirc',
22991         '7s',
22992         'uuml',
22993         '7t',
22994         'yacute',
22995         '7u',
22996         'thorn',
22997         '7v',
22998         'yuml',
22999         'ci',
23000         'fnof',
23001         'sh',
23002         'Alpha',
23003         'si',
23004         'Beta',
23005         'sj',
23006         'Gamma',
23007         'sk',
23008         'Delta',
23009         'sl',
23010         'Epsilon',
23011         'sm',
23012         'Zeta',
23013         'sn',
23014         'Eta',
23015         'so',
23016         'Theta',
23017         'sp',
23018         'Iota',
23019         'sq',
23020         'Kappa',
23021         'sr',
23022         'Lambda',
23023         'ss',
23024         'Mu',
23025         'st',
23026         'Nu',
23027         'su',
23028         'Xi',
23029         'sv',
23030         'Omicron',
23031         't0',
23032         'Pi',
23033         't1',
23034         'Rho',
23035         't3',
23036         'Sigma',
23037         't4',
23038         'Tau',
23039         't5',
23040         'Upsilon',
23041         't6',
23042         'Phi',
23043         't7',
23044         'Chi',
23045         't8',
23046         'Psi',
23047         't9',
23048         'Omega',
23049         'th',
23050         'alpha',
23051         'ti',
23052         'beta',
23053         'tj',
23054         'gamma',
23055         'tk',
23056         'delta',
23057         'tl',
23058         'epsilon',
23059         'tm',
23060         'zeta',
23061         'tn',
23062         'eta',
23063         'to',
23064         'theta',
23065         'tp',
23066         'iota',
23067         'tq',
23068         'kappa',
23069         'tr',
23070         'lambda',
23071         'ts',
23072         'mu',
23073         'tt',
23074         'nu',
23075         'tu',
23076         'xi',
23077         'tv',
23078         'omicron',
23079         'u0',
23080         'pi',
23081         'u1',
23082         'rho',
23083         'u2',
23084         'sigmaf',
23085         'u3',
23086         'sigma',
23087         'u4',
23088         'tau',
23089         'u5',
23090         'upsilon',
23091         'u6',
23092         'phi',
23093         'u7',
23094         'chi',
23095         'u8',
23096         'psi',
23097         'u9',
23098         'omega',
23099         'uh',
23100         'thetasym',
23101         'ui',
23102         'upsih',
23103         'um',
23104         'piv',
23105         '812',
23106         'bull',
23107         '816',
23108         'hellip',
23109         '81i',
23110         'prime',
23111         '81j',
23112         'Prime',
23113         '81u',
23114         'oline',
23115         '824',
23116         'frasl',
23117         '88o',
23118         'weierp',
23119         '88h',
23120         'image',
23121         '88s',
23122         'real',
23123         '892',
23124         'trade',
23125         '89l',
23126         'alefsym',
23127         '8cg',
23128         'larr',
23129         '8ch',
23130         'uarr',
23131         '8ci',
23132         'rarr',
23133         '8cj',
23134         'darr',
23135         '8ck',
23136         'harr',
23137         '8dl',
23138         'crarr',
23139         '8eg',
23140         'lArr',
23141         '8eh',
23142         'uArr',
23143         '8ei',
23144         'rArr',
23145         '8ej',
23146         'dArr',
23147         '8ek',
23148         'hArr',
23149         '8g0',
23150         'forall',
23151         '8g2',
23152         'part',
23153         '8g3',
23154         'exist',
23155         '8g5',
23156         'empty',
23157         '8g7',
23158         'nabla',
23159         '8g8',
23160         'isin',
23161         '8g9',
23162         'notin',
23163         '8gb',
23164         'ni',
23165         '8gf',
23166         'prod',
23167         '8gh',
23168         'sum',
23169         '8gi',
23170         'minus',
23171         '8gn',
23172         'lowast',
23173         '8gq',
23174         'radic',
23175         '8gt',
23176         'prop',
23177         '8gu',
23178         'infin',
23179         '8h0',
23180         'ang',
23181         '8h7',
23182         'and',
23183         '8h8',
23184         'or',
23185         '8h9',
23186         'cap',
23187         '8ha',
23188         'cup',
23189         '8hb',
23190         'int',
23191         '8hk',
23192         'there4',
23193         '8hs',
23194         'sim',
23195         '8i5',
23196         'cong',
23197         '8i8',
23198         'asymp',
23199         '8j0',
23200         'ne',
23201         '8j1',
23202         'equiv',
23203         '8j4',
23204         'le',
23205         '8j5',
23206         'ge',
23207         '8k2',
23208         'sub',
23209         '8k3',
23210         'sup',
23211         '8k4',
23212         'nsub',
23213         '8k6',
23214         'sube',
23215         '8k7',
23216         'supe',
23217         '8kl',
23218         'oplus',
23219         '8kn',
23220         'otimes',
23221         '8l5',
23222         'perp',
23223         '8m5',
23224         'sdot',
23225         '8o8',
23226         'lceil',
23227         '8o9',
23228         'rceil',
23229         '8oa',
23230         'lfloor',
23231         '8ob',
23232         'rfloor',
23233         '8p9',
23234         'lang',
23235         '8pa',
23236         'rang',
23237         '9ea',
23238         'loz',
23239         '9j0',
23240         'spades',
23241         '9j3',
23242         'clubs',
23243         '9j5',
23244         'hearts',
23245         '9j6',
23246         'diams',
23247         'ai',
23248         'OElig',
23249         'aj',
23250         'oelig',
23251         'b0',
23252         'Scaron',
23253         'b1',
23254         'scaron',
23255         'bo',
23256         'Yuml',
23257         'm6',
23258         'circ',
23259         'ms',
23260         'tilde',
23261         '802',
23262         'ensp',
23263         '803',
23264         'emsp',
23265         '809',
23266         'thinsp',
23267         '80c',
23268         'zwnj',
23269         '80d',
23270         'zwj',
23271         '80e',
23272         'lrm',
23273         '80f',
23274         'rlm',
23275         '80j',
23276         'ndash',
23277         '80k',
23278         'mdash',
23279         '80o',
23280         'lsquo',
23281         '80p',
23282         'rsquo',
23283         '80q',
23284         'sbquo',
23285         '80s',
23286         'ldquo',
23287         '80t',
23288         'rdquo',
23289         '80u',
23290         'bdquo',
23291         '810',
23292         'dagger',
23293         '811',
23294         'Dagger',
23295         '81g',
23296         'permil',
23297         '81p',
23298         'lsaquo',
23299         '81q',
23300         'rsaquo',
23301         '85c',
23302         'euro'
23303     ],
23304
23305          
23306     /**
23307      * Encodes the specified string using raw entities. This means only the required XML base entities will be encoded.
23308      *
23309      * @method encodeRaw
23310      * @param {String} text Text to encode.
23311      * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
23312      * @return {String} Entity encoded text.
23313      */
23314     encodeRaw: function(text, attr)
23315     {
23316         var t = this;
23317         return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
23318             return t.baseEntities[chr] || chr;
23319         });
23320     },
23321     /**
23322      * Encoded the specified text with both the attributes and text entities. This function will produce larger text contents
23323      * since it doesn't know if the context is within a attribute or text node. This was added for compatibility
23324      * and is exposed as the DOMUtils.encode function.
23325      *
23326      * @method encodeAllRaw
23327      * @param {String} text Text to encode.
23328      * @return {String} Entity encoded text.
23329      */
23330     encodeAllRaw: function(text) {
23331         var t = this;
23332         return ('' + text).replace(this.rawCharsRegExp, function(chr) {
23333             return t.baseEntities[chr] || chr;
23334         });
23335     },
23336     /**
23337      * Encodes the specified string using numeric entities. The core entities will be
23338      * encoded as named ones but all non lower ascii characters will be encoded into numeric entities.
23339      *
23340      * @method encodeNumeric
23341      * @param {String} text Text to encode.
23342      * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
23343      * @return {String} Entity encoded text.
23344      */
23345     encodeNumeric: function(text, attr) {
23346         var t = this;
23347         return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
23348             // Multi byte sequence convert it to a single entity
23349             if (chr.length > 1) {
23350                 return '&#' + (1024 * (chr.charCodeAt(0) - 55296) + (chr.charCodeAt(1) - 56320) + 65536) + ';';
23351             }
23352             return t.baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
23353         });
23354     },
23355     /**
23356      * Encodes the specified string using named entities. The core entities will be encoded
23357      * as named ones but all non lower ascii characters will be encoded into named entities.
23358      *
23359      * @method encodeNamed
23360      * @param {String} text Text to encode.
23361      * @param {Boolean} attr Optional flag to specify if the text is attribute contents.
23362      * @param {Object} entities Optional parameter with entities to use.
23363      * @return {String} Entity encoded text.
23364      */
23365     encodeNamed: function(text, attr, entities) {
23366         var t = this;
23367         entities = entities || this.namedEntities;
23368         return text.replace(attr ? this.attrsCharsRegExp : this.textCharsRegExp, function(chr) {
23369             return t.baseEntities[chr] || entities[chr] || chr;
23370         });
23371     },
23372     /**
23373      * Returns an encode function based on the name(s) and it's optional entities.
23374      *
23375      * @method getEncodeFunc
23376      * @param {String} name Comma separated list of encoders for example named,numeric.
23377      * @param {String} entities Optional parameter with entities to use instead of the built in set.
23378      * @return {function} Encode function to be used.
23379      */
23380     getEncodeFunc: function(name, entities) {
23381         entities = this.buildEntitiesLookup(entities) || this.namedEntities;
23382         var t = this;
23383         function encodeNamedAndNumeric(text, attr) {
23384             return text.replace(attr ? t.attrsCharsRegExp : t.textCharsRegExp, function(chr) {
23385                 return t.baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
23386             });
23387         }
23388
23389         function encodeCustomNamed(text, attr) {
23390             return t.encodeNamed(text, attr, entities);
23391         }
23392         // Replace + with , to be compatible with previous TinyMCE versions
23393         name = this.makeMap(name.replace(/\+/g, ','));
23394         // Named and numeric encoder
23395         if (name.named && name.numeric) {
23396             return this.encodeNamedAndNumeric;
23397         }
23398         // Named encoder
23399         if (name.named) {
23400             // Custom names
23401             if (entities) {
23402                 return encodeCustomNamed;
23403             }
23404             return this.encodeNamed;
23405         }
23406         // Numeric
23407         if (name.numeric) {
23408             return this.encodeNumeric;
23409         }
23410         // Raw encoder
23411         return this.encodeRaw;
23412     },
23413     /**
23414      * Decodes the specified string, this will replace entities with raw UTF characters.
23415      *
23416      * @method decode
23417      * @param {String} text Text to entity decode.
23418      * @return {String} Entity decoded string.
23419      */
23420     decode: function(text)
23421     {
23422         var  t = this;
23423         return text.replace(this.entityRegExp, function(all, numeric) {
23424             if (numeric) {
23425                 numeric = 'x' === numeric.charAt(0).toLowerCase() ? parseInt(numeric.substr(1), 16) : parseInt(numeric, 10);
23426                 // Support upper UTF
23427                 if (numeric > 65535) {
23428                     numeric -= 65536;
23429                     return String.fromCharCode(55296 + (numeric >> 10), 56320 + (1023 & numeric));
23430                 }
23431                 return t.asciiMap[numeric] || String.fromCharCode(numeric);
23432             }
23433             return t.reverseEntities[all] || t.namedEntities[all] || t.nativeDecode(all);
23434         });
23435     },
23436     nativeDecode : function (text) {
23437         return text;
23438     },
23439     makeMap : function (items, delim, map) {
23440                 var i;
23441                 items = items || [];
23442                 delim = delim || ',';
23443                 if (typeof items == "string") {
23444                         items = items.split(delim);
23445                 }
23446                 map = map || {};
23447                 i = items.length;
23448                 while (i--) {
23449                         map[items[i]] = {};
23450                 }
23451                 return map;
23452         }
23453 };
23454     
23455     
23456     
23457 Roo.htmleditor.TidyEntities.init();
23458 /**
23459  * @class Roo.htmleditor.KeyEnter
23460  * Handle Enter press..
23461  * @cfg {Roo.HtmlEditorCore} core the editor.
23462  * @constructor
23463  * Create a new Filter.
23464  * @param {Object} config Configuration options
23465  */
23466
23467
23468
23469
23470
23471 Roo.htmleditor.KeyEnter = function(cfg) {
23472     Roo.apply(this, cfg);
23473     // this does not actually call walk as it's really just a abstract class
23474  
23475     Roo.get(this.core.doc.body).on('keypress', this.keypress, this);
23476 }
23477
23478 //Roo.htmleditor.KeyEnter.i = 0;
23479
23480
23481 Roo.htmleditor.KeyEnter.prototype = {
23482     
23483     core : false,
23484     
23485     keypress : function(e)
23486     {
23487         if (e.charCode != 13 && e.charCode != 10) {
23488             Roo.log([e.charCode,e]);
23489             return true;
23490         }
23491         e.preventDefault();
23492         // https://stackoverflow.com/questions/18552336/prevent-contenteditable-adding-div-on-enter-chrome
23493         var doc = this.core.doc;
23494           //add a new line
23495        
23496     
23497         var sel = this.core.getSelection();
23498         var range = sel.getRangeAt(0);
23499         var n = range.commonAncestorContainer;
23500         var pc = range.closest([ 'ol', 'ul']);
23501         var pli = range.closest('li');
23502         if (!pc || e.ctrlKey) {
23503             // on it list, or ctrl pressed.
23504             if (!e.ctrlKey) {
23505                 sel.insertNode('br', 'after'); 
23506             } else {
23507                 // only do this if we have ctrl key..
23508                 var br = doc.createElement('br');
23509                 br.className = 'clear';
23510                 br.setAttribute('style', 'clear: both');
23511                 sel.insertNode(br, 'after'); 
23512             }
23513             
23514          
23515             this.core.undoManager.addEvent();
23516             this.core.fireEditorEvent(e);
23517             return false;
23518         }
23519         
23520         // deal with <li> insetion
23521         if (pli.innerText.trim() == '' &&
23522             pli.previousSibling &&
23523             pli.previousSibling.nodeName == 'LI' &&
23524             pli.previousSibling.innerText.trim() ==  '') {
23525             pli.parentNode.removeChild(pli.previousSibling);
23526             sel.cursorAfter(pc);
23527             this.core.undoManager.addEvent();
23528             this.core.fireEditorEvent(e);
23529             return false;
23530         }
23531     
23532         var li = doc.createElement('LI');
23533         li.innerHTML = '&nbsp;';
23534         if (!pli || !pli.firstSibling) {
23535             pc.appendChild(li);
23536         } else {
23537             pli.parentNode.insertBefore(li, pli.firstSibling);
23538         }
23539         sel.cursorText (li.firstChild);
23540       
23541         this.core.undoManager.addEvent();
23542         this.core.fireEditorEvent(e);
23543
23544         return false;
23545         
23546     
23547         
23548         
23549          
23550     }
23551 };
23552      
23553 /**
23554  * @class Roo.htmleditor.Block
23555  * Base class for html editor blocks - do not use it directly .. extend it..
23556  * @cfg {DomElement} node The node to apply stuff to.
23557  * @cfg {String} friendly_name the name that appears in the context bar about this block
23558  * @cfg {Object} Context menu - see Roo.form.HtmlEditor.ToolbarContext
23559  
23560  * @constructor
23561  * Create a new Filter.
23562  * @param {Object} config Configuration options
23563  */
23564
23565 Roo.htmleditor.Block  = function(cfg)
23566 {
23567     // do nothing .. should not be called really.
23568 }
23569 /**
23570  * factory method to get the block from an element (using cache if necessary)
23571  * @static
23572  * @param {HtmlElement} the dom element
23573  */
23574 Roo.htmleditor.Block.factory = function(node)
23575 {
23576     var cc = Roo.htmleditor.Block.cache;
23577     var id = Roo.get(node).id;
23578     if (typeof(cc[id]) != 'undefined' && (!cc[id].node || cc[id].node.closest('body'))) {
23579         Roo.htmleditor.Block.cache[id].readElement(node);
23580         return Roo.htmleditor.Block.cache[id];
23581     }
23582     var db  = node.getAttribute('data-block');
23583     if (!db) {
23584         db = node.nodeName.toLowerCase().toUpperCaseFirst();
23585     }
23586     var cls = Roo.htmleditor['Block' + db];
23587     if (typeof(cls) == 'undefined') {
23588         //Roo.log(node.getAttribute('data-block'));
23589         Roo.log("OOps missing block : " + 'Block' + db);
23590         return false;
23591     }
23592     Roo.htmleditor.Block.cache[id] = new cls({ node: node });
23593     return Roo.htmleditor.Block.cache[id];  /// should trigger update element
23594 };
23595
23596 /**
23597  * initalize all Elements from content that are 'blockable'
23598  * @static
23599  * @param the body element
23600  */
23601 Roo.htmleditor.Block.initAll = function(body, type)
23602 {
23603     if (typeof(type) == 'undefined') {
23604         var ia = Roo.htmleditor.Block.initAll;
23605         ia(body,'table');
23606         ia(body,'td');
23607         ia(body,'figure');
23608         return;
23609     }
23610     Roo.each(Roo.get(body).query(type), function(e) {
23611         Roo.htmleditor.Block.factory(e);    
23612     },this);
23613 };
23614 // question goes here... do we need to clear out this cache sometimes?
23615 // or show we make it relivant to the htmleditor.
23616 Roo.htmleditor.Block.cache = {};
23617
23618 Roo.htmleditor.Block.prototype = {
23619     
23620     node : false,
23621     
23622      // used by context menu
23623     friendly_name : 'Based Block',
23624     
23625     // text for button to delete this element
23626     deleteTitle : false,
23627     
23628     context : false,
23629     /**
23630      * Update a node with values from this object
23631      * @param {DomElement} node
23632      */
23633     updateElement : function(node)
23634     {
23635         Roo.DomHelper.update(node === undefined ? this.node : node, this.toObject());
23636     },
23637      /**
23638      * convert to plain HTML for calling insertAtCursor..
23639      */
23640     toHTML : function()
23641     {
23642         return Roo.DomHelper.markup(this.toObject());
23643     },
23644     /**
23645      * used by readEleemnt to extract data from a node
23646      * may need improving as it's pretty basic
23647      
23648      * @param {DomElement} node
23649      * @param {String} tag - tag to find, eg. IMG ?? might be better to use DomQuery ?
23650      * @param {String} attribute (use html - for contents, style for using next param as style, or false to return the node)
23651      * @param {String} style the style property - eg. text-align
23652      */
23653     getVal : function(node, tag, attr, style)
23654     {
23655         var n = node;
23656         if (tag !== true && n.tagName != tag.toUpperCase()) {
23657             // in theory we could do figure[3] << 3rd figure? or some more complex search..?
23658             // but kiss for now.
23659             n = node.getElementsByTagName(tag).item(0);
23660         }
23661         if (!n) {
23662             return '';
23663         }
23664         if (attr === false) {
23665             return n;
23666         }
23667         if (attr == 'html') {
23668             return n.innerHTML;
23669         }
23670         if (attr == 'style') {
23671             return n.style[style]; 
23672         }
23673         
23674         return n.hasAttribute(attr) ? n.getAttribute(attr) : '';
23675             
23676     },
23677     /**
23678      * create a DomHelper friendly object - for use with 
23679      * Roo.DomHelper.markup / overwrite / etc..
23680      * (override this)
23681      */
23682     toObject : function()
23683     {
23684         return {};
23685     },
23686       /**
23687      * Read a node that has a 'data-block' property - and extract the values from it.
23688      * @param {DomElement} node - the node
23689      */
23690     readElement : function(node)
23691     {
23692         
23693     } 
23694     
23695     
23696 };
23697
23698  
23699
23700 /**
23701  * @class Roo.htmleditor.BlockFigure
23702  * Block that has an image and a figcaption
23703  * @cfg {String} image_src the url for the image
23704  * @cfg {String} align (left|right) alignment for the block default left
23705  * @cfg {String} caption the text to appear below  (and in the alt tag)
23706  * @cfg {String} caption_display (block|none) display or not the caption
23707  * @cfg {String|number} image_width the width of the image number or %?
23708  * @cfg {String|number} image_height the height of the image number or %?
23709  * 
23710  * @constructor
23711  * Create a new Filter.
23712  * @param {Object} config Configuration options
23713  */
23714
23715 Roo.htmleditor.BlockFigure = function(cfg)
23716 {
23717     if (cfg.node) {
23718         this.readElement(cfg.node);
23719         this.updateElement(cfg.node);
23720     }
23721     Roo.apply(this, cfg);
23722 }
23723 Roo.extend(Roo.htmleditor.BlockFigure, Roo.htmleditor.Block, {
23724  
23725     
23726     // setable values.
23727     image_src: '',
23728     align: 'center',
23729     caption : '',
23730     caption_display : 'block',
23731     width : '100%',
23732     cls : '',
23733     href: '',
23734     video_url : '',
23735     
23736     // margin: '2%', not used
23737     
23738     text_align: 'left', //   (left|right) alignment for the text caption default left. - not used at present
23739
23740     
23741     // used by context menu
23742     friendly_name : 'Image with caption',
23743     deleteTitle : "Delete Image and Caption",
23744     
23745     contextMenu : function(toolbar)
23746     {
23747         
23748         var block = function() {
23749             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
23750         };
23751         
23752         
23753         var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
23754         
23755         var syncValue = toolbar.editorcore.syncValue;
23756         
23757         var fields = {};
23758         
23759         return [
23760              {
23761                 xtype : 'TextItem',
23762                 text : "Source: ",
23763                 xns : rooui.Toolbar  //Boostrap?
23764             },
23765             {
23766                 xtype : 'Button',
23767                 text: 'Change Image URL',
23768                  
23769                 listeners : {
23770                     click: function (btn, state)
23771                     {
23772                         var b = block();
23773                         
23774                         Roo.MessageBox.show({
23775                             title : "Image Source URL",
23776                             msg : "Enter the url for the image",
23777                             buttons: Roo.MessageBox.OKCANCEL,
23778                             fn: function(btn, val){
23779                                 if (btn != 'ok') {
23780                                     return;
23781                                 }
23782                                 b.image_src = val;
23783                                 b.updateElement();
23784                                 syncValue();
23785                                 toolbar.editorcore.onEditorEvent();
23786                             },
23787                             minWidth:250,
23788                             prompt:true,
23789                             //multiline: multiline,
23790                             modal : true,
23791                             value : b.image_src
23792                         });
23793                     }
23794                 },
23795                 xns : rooui.Toolbar
23796             },
23797          
23798             {
23799                 xtype : 'Button',
23800                 text: 'Change Link URL',
23801                  
23802                 listeners : {
23803                     click: function (btn, state)
23804                     {
23805                         var b = block();
23806                         
23807                         Roo.MessageBox.show({
23808                             title : "Link URL",
23809                             msg : "Enter the url for the link - leave blank to have no link",
23810                             buttons: Roo.MessageBox.OKCANCEL,
23811                             fn: function(btn, val){
23812                                 if (btn != 'ok') {
23813                                     return;
23814                                 }
23815                                 b.href = val;
23816                                 b.updateElement();
23817                                 syncValue();
23818                                 toolbar.editorcore.onEditorEvent();
23819                             },
23820                             minWidth:250,
23821                             prompt:true,
23822                             //multiline: multiline,
23823                             modal : true,
23824                             value : b.href
23825                         });
23826                     }
23827                 },
23828                 xns : rooui.Toolbar
23829             },
23830             {
23831                 xtype : 'Button',
23832                 text: 'Show Video URL',
23833                  
23834                 listeners : {
23835                     click: function (btn, state)
23836                     {
23837                         Roo.MessageBox.alert("Video URL",
23838                             block().video_url == '' ? 'This image is not linked ot a video' :
23839                                 'The image is linked to: <a target="_new" href="' + block().video_url + '">' + block().video_url + '</a>');
23840                     }
23841                 },
23842                 xns : rooui.Toolbar
23843             },
23844             
23845             
23846             {
23847                 xtype : 'TextItem',
23848                 text : "Width: ",
23849                 xns : rooui.Toolbar  //Boostrap?
23850             },
23851             {
23852                 xtype : 'ComboBox',
23853                 allowBlank : false,
23854                 displayField : 'val',
23855                 editable : true,
23856                 listWidth : 100,
23857                 triggerAction : 'all',
23858                 typeAhead : true,
23859                 valueField : 'val',
23860                 width : 70,
23861                 name : 'width',
23862                 listeners : {
23863                     select : function (combo, r, index)
23864                     {
23865                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
23866                         var b = block();
23867                         b.width = r.get('val');
23868                         b.updateElement();
23869                         syncValue();
23870                         toolbar.editorcore.onEditorEvent();
23871                     }
23872                 },
23873                 xns : rooui.form,
23874                 store : {
23875                     xtype : 'SimpleStore',
23876                     data : [
23877                         ['100%'],
23878                         ['80%'],
23879                         ['50%'],
23880                         ['20%'],
23881                         ['10%']
23882                     ],
23883                     fields : [ 'val'],
23884                     xns : Roo.data
23885                 }
23886             },
23887             {
23888                 xtype : 'TextItem',
23889                 text : "Align: ",
23890                 xns : rooui.Toolbar  //Boostrap?
23891             },
23892             {
23893                 xtype : 'ComboBox',
23894                 allowBlank : false,
23895                 displayField : 'val',
23896                 editable : true,
23897                 listWidth : 100,
23898                 triggerAction : 'all',
23899                 typeAhead : true,
23900                 valueField : 'val',
23901                 width : 70,
23902                 name : 'align',
23903                 listeners : {
23904                     select : function (combo, r, index)
23905                     {
23906                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
23907                         var b = block();
23908                         b.align = r.get('val');
23909                         b.updateElement();
23910                         syncValue();
23911                         toolbar.editorcore.onEditorEvent();
23912                     }
23913                 },
23914                 xns : rooui.form,
23915                 store : {
23916                     xtype : 'SimpleStore',
23917                     data : [
23918                         ['left'],
23919                         ['right'],
23920                         ['center']
23921                     ],
23922                     fields : [ 'val'],
23923                     xns : Roo.data
23924                 }
23925             },
23926             
23927             
23928             {
23929                 xtype : 'Button',
23930                 text: 'Hide Caption',
23931                 name : 'caption_display',
23932                 pressed : false,
23933                 enableToggle : true,
23934                 setValue : function(v) {
23935                     // this trigger toggle.
23936                      
23937                     this.setText(v ? "Hide Caption" : "Show Caption");
23938                     this.setPressed(v != 'block');
23939                 },
23940                 listeners : {
23941                     toggle: function (btn, state)
23942                     {
23943                         var b  = block();
23944                         b.caption_display = b.caption_display == 'block' ? 'none' : 'block';
23945                         this.setText(b.caption_display == 'block' ? "Hide Caption" : "Show Caption");
23946                         b.updateElement();
23947                         syncValue();
23948                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
23949                         toolbar.editorcore.onEditorEvent();
23950                     }
23951                 },
23952                 xns : rooui.Toolbar
23953             }
23954         ];
23955         
23956     },
23957     /**
23958      * create a DomHelper friendly object - for use with
23959      * Roo.DomHelper.markup / overwrite / etc..
23960      */
23961     toObject : function()
23962     {
23963         var d = document.createElement('div');
23964         d.innerHTML = this.caption;
23965         
23966         var m = this.width != '100%' && this.align == 'center' ? '0 auto' : 0; 
23967         
23968         var iw = this.align == 'center' ? this.width : '100%';
23969         var img =   {
23970             tag : 'img',
23971             contenteditable : 'false',
23972             src : this.image_src,
23973             alt : d.innerText.replace(/\n/g, " ").replace(/\s+/g, ' ').trim(), // removeHTML and reduce spaces..
23974             style: {
23975                 width : iw,
23976                 maxWidth : iw + ' !important', // this is not getting rendered?
23977                 margin : m  
23978                 
23979             }
23980         };
23981         /*
23982         '<div class="{0}" width="420" height="315" src="{1}" frameborder="0" allowfullscreen>' +
23983                     '<a href="{2}">' + 
23984                         '<img class="{0}-thumbnail" src="{3}/Images/{4}/{5}#image-{4}" />' + 
23985                     '</a>' + 
23986                 '</div>',
23987         */
23988                 
23989         if (this.href.length > 0) {
23990             img = {
23991                 tag : 'a',
23992                 href: this.href,
23993                 contenteditable : 'true',
23994                 cn : [
23995                     img
23996                 ]
23997             };
23998         }
23999         
24000         
24001         if (this.video_url.length > 0) {
24002             img = {
24003                 tag : 'div',
24004                 cls : this.cls,
24005                 frameborder : 0,
24006                 allowfullscreen : true,
24007                 width : 420,  // these are for video tricks - that we replace the outer
24008                 height : 315,
24009                 src : this.video_url,
24010                 cn : [
24011                     img
24012                 ]
24013             };
24014         }
24015         // we remove caption totally if its hidden... - will delete data.. but otherwise we end up with fake caption
24016         var captionhtml = this.caption_display == 'none' ? '' : (this.caption.length ? this.caption : "Caption");
24017         
24018   
24019         var ret =   {
24020             tag: 'figure',
24021             'data-block' : 'Figure',
24022             'data-width' : this.width, 
24023             contenteditable : 'false',
24024             
24025             style : {
24026                 display: 'block',
24027                 float :  this.align ,
24028                 maxWidth :  this.align == 'center' ? '100% !important' : (this.width + ' !important'),
24029                 width : this.align == 'center' ? '100%' : this.width,
24030                 margin:  '0px',
24031                 padding: this.align == 'center' ? '0' : '0 10px' ,
24032                 textAlign : this.align   // seems to work for email..
24033                 
24034             },
24035            
24036             
24037             align : this.align,
24038             cn : [
24039                 img,
24040               
24041                 {
24042                     tag: 'figcaption',
24043                     'data-display' : this.caption_display,
24044                     style : {
24045                         textAlign : 'left',
24046                         fontSize : '16px',
24047                         lineHeight : '24px',
24048                         display : this.caption_display,
24049                         maxWidth : (this.align == 'center' ?  this.width : '100%' ) + ' !important',
24050                         margin: m,
24051                         width: this.align == 'center' ?  this.width : '100%' 
24052                     
24053                          
24054                     },
24055                     cls : this.cls.length > 0 ? (this.cls  + '-thumbnail' ) : '',
24056                     cn : [
24057                         {
24058                             tag: 'div',
24059                             style  : {
24060                                 marginTop : '16px',
24061                                 textAlign : 'left'
24062                             },
24063                             align: 'left',
24064                             cn : [
24065                                 {
24066                                     // we can not rely on yahoo syndication to use CSS elements - so have to use  '<i>' to encase stuff.
24067                                     tag : 'i',
24068                                     contenteditable : true,
24069                                     html : captionhtml
24070                                 }
24071                                 
24072                             ]
24073                         }
24074                         
24075                     ]
24076                     
24077                 }
24078             ]
24079         };
24080         return ret;
24081          
24082     },
24083     
24084     readElement : function(node)
24085     {
24086         // this should not really come from the link...
24087         this.video_url = this.getVal(node, 'div', 'src');
24088         this.cls = this.getVal(node, 'div', 'class');
24089         this.href = this.getVal(node, 'a', 'href');
24090         
24091         
24092         this.image_src = this.getVal(node, 'img', 'src');
24093          
24094         this.align = this.getVal(node, 'figure', 'align');
24095         var figcaption = this.getVal(node, 'figcaption', false);
24096         if (figcaption !== '') {
24097             this.caption = this.getVal(figcaption, 'i', 'html');
24098         }
24099         
24100
24101         this.caption_display = this.getVal(node, 'figcaption', 'data-display');
24102         //this.text_align = this.getVal(node, 'figcaption', 'style','text-align');
24103         this.width = this.getVal(node, true, 'data-width');
24104         //this.margin = this.getVal(node, 'figure', 'style', 'margin');
24105         
24106     },
24107     removeNode : function()
24108     {
24109         return this.node;
24110     }
24111     
24112   
24113    
24114      
24115     
24116     
24117     
24118     
24119 })
24120
24121  
24122
24123 /**
24124  * @class Roo.htmleditor.BlockTable
24125  * Block that manages a table
24126  * 
24127  * @constructor
24128  * Create a new Filter.
24129  * @param {Object} config Configuration options
24130  */
24131
24132 Roo.htmleditor.BlockTable = function(cfg)
24133 {
24134     if (cfg.node) {
24135         this.readElement(cfg.node);
24136         this.updateElement(cfg.node);
24137     }
24138     Roo.apply(this, cfg);
24139     if (!cfg.node) {
24140         this.rows = [];
24141         for(var r = 0; r < this.no_row; r++) {
24142             this.rows[r] = [];
24143             for(var c = 0; c < this.no_col; c++) {
24144                 this.rows[r][c] = this.emptyCell();
24145             }
24146         }
24147     }
24148     
24149     
24150 }
24151 Roo.extend(Roo.htmleditor.BlockTable, Roo.htmleditor.Block, {
24152  
24153     rows : false,
24154     no_col : 1,
24155     no_row : 1,
24156     
24157     
24158     width: '100%',
24159     
24160     // used by context menu
24161     friendly_name : 'Table',
24162     deleteTitle : 'Delete Table',
24163     // context menu is drawn once..
24164     
24165     contextMenu : function(toolbar)
24166     {
24167         
24168         var block = function() {
24169             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
24170         };
24171         
24172         
24173         var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
24174         
24175         var syncValue = toolbar.editorcore.syncValue;
24176         
24177         var fields = {};
24178         
24179         return [
24180             {
24181                 xtype : 'TextItem',
24182                 text : "Width: ",
24183                 xns : rooui.Toolbar  //Boostrap?
24184             },
24185             {
24186                 xtype : 'ComboBox',
24187                 allowBlank : false,
24188                 displayField : 'val',
24189                 editable : true,
24190                 listWidth : 100,
24191                 triggerAction : 'all',
24192                 typeAhead : true,
24193                 valueField : 'val',
24194                 width : 100,
24195                 name : 'width',
24196                 listeners : {
24197                     select : function (combo, r, index)
24198                     {
24199                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24200                         var b = block();
24201                         b.width = r.get('val');
24202                         b.updateElement();
24203                         syncValue();
24204                         toolbar.editorcore.onEditorEvent();
24205                     }
24206                 },
24207                 xns : rooui.form,
24208                 store : {
24209                     xtype : 'SimpleStore',
24210                     data : [
24211                         ['100%'],
24212                         ['auto']
24213                     ],
24214                     fields : [ 'val'],
24215                     xns : Roo.data
24216                 }
24217             },
24218             // -------- Cols
24219             
24220             {
24221                 xtype : 'TextItem',
24222                 text : "Columns: ",
24223                 xns : rooui.Toolbar  //Boostrap?
24224             },
24225          
24226             {
24227                 xtype : 'Button',
24228                 text: '-',
24229                 listeners : {
24230                     click : function (_self, e)
24231                     {
24232                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24233                         block().removeColumn();
24234                         syncValue();
24235                         toolbar.editorcore.onEditorEvent();
24236                     }
24237                 },
24238                 xns : rooui.Toolbar
24239             },
24240             {
24241                 xtype : 'Button',
24242                 text: '+',
24243                 listeners : {
24244                     click : function (_self, e)
24245                     {
24246                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24247                         block().addColumn();
24248                         syncValue();
24249                         toolbar.editorcore.onEditorEvent();
24250                     }
24251                 },
24252                 xns : rooui.Toolbar
24253             },
24254             // -------- ROWS
24255             {
24256                 xtype : 'TextItem',
24257                 text : "Rows: ",
24258                 xns : rooui.Toolbar  //Boostrap?
24259             },
24260          
24261             {
24262                 xtype : 'Button',
24263                 text: '-',
24264                 listeners : {
24265                     click : function (_self, e)
24266                     {
24267                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24268                         block().removeRow();
24269                         syncValue();
24270                         toolbar.editorcore.onEditorEvent();
24271                     }
24272                 },
24273                 xns : rooui.Toolbar
24274             },
24275             {
24276                 xtype : 'Button',
24277                 text: '+',
24278                 listeners : {
24279                     click : function (_self, e)
24280                     {
24281                         block().addRow();
24282                         syncValue();
24283                         toolbar.editorcore.onEditorEvent();
24284                     }
24285                 },
24286                 xns : rooui.Toolbar
24287             },
24288             // -------- ROWS
24289             {
24290                 xtype : 'Button',
24291                 text: 'Reset Column Widths',
24292                 listeners : {
24293                     
24294                     click : function (_self, e)
24295                     {
24296                         block().resetWidths();
24297                         syncValue();
24298                         toolbar.editorcore.onEditorEvent();
24299                     }
24300                 },
24301                 xns : rooui.Toolbar
24302             } 
24303             
24304             
24305             
24306         ];
24307         
24308     },
24309     
24310     
24311   /**
24312      * create a DomHelper friendly object - for use with
24313      * Roo.DomHelper.markup / overwrite / etc..
24314      * ?? should it be called with option to hide all editing features?
24315      */
24316     toObject : function()
24317     {
24318         
24319         var ret = {
24320             tag : 'table',
24321             contenteditable : 'false', // this stops cell selection from picking the table.
24322             'data-block' : 'Table',
24323             style : {
24324                 width:  this.width,
24325                 border : 'solid 1px #000', // ??? hard coded?
24326                 'border-collapse' : 'collapse' 
24327             },
24328             cn : [
24329                 { tag : 'tbody' , cn : [] }
24330             ]
24331         };
24332         
24333         // do we have a head = not really 
24334         var ncols = 0;
24335         Roo.each(this.rows, function( row ) {
24336             var tr = {
24337                 tag: 'tr',
24338                 style : {
24339                     margin: '6px',
24340                     border : 'solid 1px #000',
24341                     textAlign : 'left' 
24342                 },
24343                 cn : [ ]
24344             };
24345             
24346             ret.cn[0].cn.push(tr);
24347             // does the row have any properties? ?? height?
24348             var nc = 0;
24349             Roo.each(row, function( cell ) {
24350                 
24351                 var td = {
24352                     tag : 'td',
24353                     contenteditable :  'true',
24354                     'data-block' : 'Td',
24355                     html : cell.html,
24356                     style : cell.style
24357                 };
24358                 if (cell.colspan > 1) {
24359                     td.colspan = cell.colspan ;
24360                     nc += cell.colspan;
24361                 } else {
24362                     nc++;
24363                 }
24364                 if (cell.rowspan > 1) {
24365                     td.rowspan = cell.rowspan ;
24366                 }
24367                 
24368                 
24369                 // widths ?
24370                 tr.cn.push(td);
24371                     
24372                 
24373             }, this);
24374             ncols = Math.max(nc, ncols);
24375             
24376             
24377         }, this);
24378         // add the header row..
24379         
24380         ncols++;
24381          
24382         
24383         return ret;
24384          
24385     },
24386     
24387     readElement : function(node)
24388     {
24389         node  = node ? node : this.node ;
24390         this.width = this.getVal(node, true, 'style', 'width') || '100%';
24391         
24392         this.rows = [];
24393         this.no_row = 0;
24394         var trs = Array.from(node.rows);
24395         trs.forEach(function(tr) {
24396             var row =  [];
24397             this.rows.push(row);
24398             
24399             this.no_row++;
24400             var no_column = 0;
24401             Array.from(tr.cells).forEach(function(td) {
24402                 
24403                 var add = {
24404                     colspan : td.hasAttribute('colspan') ? td.getAttribute('colspan')*1 : 1,
24405                     rowspan : td.hasAttribute('rowspan') ? td.getAttribute('rowspan')*1 : 1,
24406                     style : td.hasAttribute('style') ? td.getAttribute('style') : '',
24407                     html : td.innerHTML
24408                 };
24409                 no_column += add.colspan;
24410                      
24411                 
24412                 row.push(add);
24413                 
24414                 
24415             },this);
24416             this.no_col = Math.max(this.no_col, no_column);
24417             
24418             
24419         },this);
24420         
24421         
24422     },
24423     normalizeRows: function()
24424     {
24425         var ret= [];
24426         var rid = -1;
24427         this.rows.forEach(function(row) {
24428             rid++;
24429             ret[rid] = [];
24430             row = this.normalizeRow(row);
24431             var cid = 0;
24432             row.forEach(function(c) {
24433                 while (typeof(ret[rid][cid]) != 'undefined') {
24434                     cid++;
24435                 }
24436                 if (typeof(ret[rid]) == 'undefined') {
24437                     ret[rid] = [];
24438                 }
24439                 ret[rid][cid] = c;
24440                 c.row = rid;
24441                 c.col = cid;
24442                 if (c.rowspan < 2) {
24443                     return;
24444                 }
24445                 
24446                 for(var i = 1 ;i < c.rowspan; i++) {
24447                     if (typeof(ret[rid+i]) == 'undefined') {
24448                         ret[rid+i] = [];
24449                     }
24450                     ret[rid+i][cid] = c;
24451                 }
24452             });
24453         }, this);
24454         return ret;
24455     
24456     },
24457     
24458     normalizeRow: function(row)
24459     {
24460         var ret= [];
24461         row.forEach(function(c) {
24462             if (c.colspan < 2) {
24463                 ret.push(c);
24464                 return;
24465             }
24466             for(var i =0 ;i < c.colspan; i++) {
24467                 ret.push(c);
24468             }
24469         });
24470         return ret;
24471     
24472     },
24473     
24474     deleteColumn : function(sel)
24475     {
24476         if (!sel || sel.type != 'col') {
24477             return;
24478         }
24479         if (this.no_col < 2) {
24480             return;
24481         }
24482         
24483         this.rows.forEach(function(row) {
24484             var cols = this.normalizeRow(row);
24485             var col = cols[sel.col];
24486             if (col.colspan > 1) {
24487                 col.colspan --;
24488             } else {
24489                 row.remove(col);
24490             }
24491             
24492         }, this);
24493         this.no_col--;
24494         
24495     },
24496     removeColumn : function()
24497     {
24498         this.deleteColumn({
24499             type: 'col',
24500             col : this.no_col-1
24501         });
24502         this.updateElement();
24503     },
24504     
24505      
24506     addColumn : function()
24507     {
24508         
24509         this.rows.forEach(function(row) {
24510             row.push(this.emptyCell());
24511            
24512         }, this);
24513         this.updateElement();
24514     },
24515     
24516     deleteRow : function(sel)
24517     {
24518         if (!sel || sel.type != 'row') {
24519             return;
24520         }
24521         
24522         if (this.no_row < 2) {
24523             return;
24524         }
24525         
24526         var rows = this.normalizeRows();
24527         
24528         
24529         rows[sel.row].forEach(function(col) {
24530             if (col.rowspan > 1) {
24531                 col.rowspan--;
24532             } else {
24533                 col.remove = 1; // flage it as removed.
24534             }
24535             
24536         }, this);
24537         var newrows = [];
24538         this.rows.forEach(function(row) {
24539             newrow = [];
24540             row.forEach(function(c) {
24541                 if (typeof(c.remove) == 'undefined') {
24542                     newrow.push(c);
24543                 }
24544                 
24545             });
24546             if (newrow.length > 0) {
24547                 newrows.push(row);
24548             }
24549         });
24550         this.rows =  newrows;
24551         
24552         
24553         
24554         this.no_row--;
24555         this.updateElement();
24556         
24557     },
24558     removeRow : function()
24559     {
24560         this.deleteRow({
24561             type: 'row',
24562             row : this.no_row-1
24563         });
24564         
24565     },
24566     
24567      
24568     addRow : function()
24569     {
24570         
24571         var row = [];
24572         for (var i = 0; i < this.no_col; i++ ) {
24573             
24574             row.push(this.emptyCell());
24575            
24576         }
24577         this.rows.push(row);
24578         this.updateElement();
24579         
24580     },
24581      
24582     // the default cell object... at present...
24583     emptyCell : function() {
24584         return (new Roo.htmleditor.BlockTd({})).toObject();
24585         
24586      
24587     },
24588     
24589     removeNode : function()
24590     {
24591         return this.node;
24592     },
24593     
24594     
24595     
24596     resetWidths : function()
24597     {
24598         Array.from(this.node.getElementsByTagName('td')).forEach(function(n) {
24599             var nn = Roo.htmleditor.Block.factory(n);
24600             nn.width = '';
24601             nn.updateElement(n);
24602         });
24603     }
24604     
24605     
24606     
24607     
24608 })
24609
24610 /**
24611  *
24612  * editing a TD?
24613  *
24614  * since selections really work on the table cell, then editing really should work from there
24615  *
24616  * The original plan was to support merging etc... - but that may not be needed yet..
24617  *
24618  * So this simple version will support:
24619  *   add/remove cols
24620  *   adjust the width +/-
24621  *   reset the width...
24622  *   
24623  *
24624  */
24625
24626
24627  
24628
24629 /**
24630  * @class Roo.htmleditor.BlockTable
24631  * Block that manages a table
24632  * 
24633  * @constructor
24634  * Create a new Filter.
24635  * @param {Object} config Configuration options
24636  */
24637
24638 Roo.htmleditor.BlockTd = function(cfg)
24639 {
24640     if (cfg.node) {
24641         this.readElement(cfg.node);
24642         this.updateElement(cfg.node);
24643     }
24644     Roo.apply(this, cfg);
24645      
24646     
24647     
24648 }
24649 Roo.extend(Roo.htmleditor.BlockTd, Roo.htmleditor.Block, {
24650  
24651     node : false,
24652     
24653     width: '',
24654     textAlign : 'left',
24655     valign : 'top',
24656     
24657     colspan : 1,
24658     rowspan : 1,
24659     
24660     
24661     // used by context menu
24662     friendly_name : 'Table Cell',
24663     deleteTitle : false, // use our customer delete
24664     
24665     // context menu is drawn once..
24666     
24667     contextMenu : function(toolbar)
24668     {
24669         
24670         var cell = function() {
24671             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode);
24672         };
24673         
24674         var table = function() {
24675             return Roo.htmleditor.Block.factory(toolbar.tb.selectedNode.closest('table'));
24676         };
24677         
24678         var lr = false;
24679         var saveSel = function()
24680         {
24681             lr = toolbar.editorcore.getSelection().getRangeAt(0);
24682         }
24683         var restoreSel = function()
24684         {
24685             if (lr) {
24686                 (function() {
24687                     toolbar.editorcore.focus();
24688                     var cr = toolbar.editorcore.getSelection();
24689                     cr.removeAllRanges();
24690                     cr.addRange(lr);
24691                     toolbar.editorcore.onEditorEvent();
24692                 }).defer(10, this);
24693                 
24694                 
24695             }
24696         }
24697         
24698         var rooui =  typeof(Roo.bootstrap) == 'undefined' ? Roo : Roo.bootstrap;
24699         
24700         var syncValue = toolbar.editorcore.syncValue;
24701         
24702         var fields = {};
24703         
24704         return [
24705             {
24706                 xtype : 'Button',
24707                 text : 'Edit Table',
24708                 listeners : {
24709                     click : function() {
24710                         var t = toolbar.tb.selectedNode.closest('table');
24711                         toolbar.editorcore.selectNode(t);
24712                         toolbar.editorcore.onEditorEvent();                        
24713                     }
24714                 }
24715                 
24716             },
24717               
24718            
24719              
24720             {
24721                 xtype : 'TextItem',
24722                 text : "Column Width: ",
24723                  xns : rooui.Toolbar 
24724                
24725             },
24726             {
24727                 xtype : 'Button',
24728                 text: '-',
24729                 listeners : {
24730                     click : function (_self, e)
24731                     {
24732                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24733                         cell().shrinkColumn();
24734                         syncValue();
24735                          toolbar.editorcore.onEditorEvent();
24736                     }
24737                 },
24738                 xns : rooui.Toolbar
24739             },
24740             {
24741                 xtype : 'Button',
24742                 text: '+',
24743                 listeners : {
24744                     click : function (_self, e)
24745                     {
24746                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24747                         cell().growColumn();
24748                         syncValue();
24749                         toolbar.editorcore.onEditorEvent();
24750                     }
24751                 },
24752                 xns : rooui.Toolbar
24753             },
24754             
24755             {
24756                 xtype : 'TextItem',
24757                 text : "Vertical Align: ",
24758                 xns : rooui.Toolbar  //Boostrap?
24759             },
24760             {
24761                 xtype : 'ComboBox',
24762                 allowBlank : false,
24763                 displayField : 'val',
24764                 editable : true,
24765                 listWidth : 100,
24766                 triggerAction : 'all',
24767                 typeAhead : true,
24768                 valueField : 'val',
24769                 width : 100,
24770                 name : 'valign',
24771                 listeners : {
24772                     select : function (combo, r, index)
24773                     {
24774                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24775                         var b = cell();
24776                         b.valign = r.get('val');
24777                         b.updateElement();
24778                         syncValue();
24779                         toolbar.editorcore.onEditorEvent();
24780                     }
24781                 },
24782                 xns : rooui.form,
24783                 store : {
24784                     xtype : 'SimpleStore',
24785                     data : [
24786                         ['top'],
24787                         ['middle'],
24788                         ['bottom'] // there are afew more... 
24789                     ],
24790                     fields : [ 'val'],
24791                     xns : Roo.data
24792                 }
24793             },
24794             
24795             {
24796                 xtype : 'TextItem',
24797                 text : "Merge Cells: ",
24798                  xns : rooui.Toolbar 
24799                
24800             },
24801             
24802             
24803             {
24804                 xtype : 'Button',
24805                 text: 'Right',
24806                 listeners : {
24807                     click : function (_self, e)
24808                     {
24809                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24810                         cell().mergeRight();
24811                         //block().growColumn();
24812                         syncValue();
24813                         toolbar.editorcore.onEditorEvent();
24814                     }
24815                 },
24816                 xns : rooui.Toolbar
24817             },
24818              
24819             {
24820                 xtype : 'Button',
24821                 text: 'Below',
24822                 listeners : {
24823                     click : function (_self, e)
24824                     {
24825                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24826                         cell().mergeBelow();
24827                         //block().growColumn();
24828                         syncValue();
24829                         toolbar.editorcore.onEditorEvent();
24830                     }
24831                 },
24832                 xns : rooui.Toolbar
24833             },
24834             {
24835                 xtype : 'TextItem',
24836                 text : "| ",
24837                  xns : rooui.Toolbar 
24838                
24839             },
24840             
24841             {
24842                 xtype : 'Button',
24843                 text: 'Split',
24844                 listeners : {
24845                     click : function (_self, e)
24846                     {
24847                         //toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24848                         cell().split();
24849                         syncValue();
24850                         toolbar.editorcore.selectNode(toolbar.tb.selectedNode);
24851                         toolbar.editorcore.onEditorEvent();
24852                                              
24853                     }
24854                 },
24855                 xns : rooui.Toolbar
24856             },
24857             {
24858                 xtype : 'Fill',
24859                 xns : rooui.Toolbar 
24860                
24861             },
24862         
24863           
24864             {
24865                 xtype : 'Button',
24866                 text: 'Delete',
24867                  
24868                 xns : rooui.Toolbar,
24869                 menu : {
24870                     xtype : 'Menu',
24871                     xns : rooui.menu,
24872                     items : [
24873                         {
24874                             xtype : 'Item',
24875                             html: 'Column',
24876                             listeners : {
24877                                 click : function (_self, e)
24878                                 {
24879                                     var t = table();
24880                                     
24881                                     cell().deleteColumn();
24882                                     syncValue();
24883                                     toolbar.editorcore.selectNode(t.node);
24884                                     toolbar.editorcore.onEditorEvent();   
24885                                 }
24886                             },
24887                             xns : rooui.menu
24888                         },
24889                         {
24890                             xtype : 'Item',
24891                             html: 'Row',
24892                             listeners : {
24893                                 click : function (_self, e)
24894                                 {
24895                                     var t = table();
24896                                     cell().deleteRow();
24897                                     syncValue();
24898                                     
24899                                     toolbar.editorcore.selectNode(t.node);
24900                                     toolbar.editorcore.onEditorEvent();   
24901                                                          
24902                                 }
24903                             },
24904                             xns : rooui.menu
24905                         },
24906                        {
24907                             xtype : 'Separator',
24908                             xns : rooui.menu
24909                         },
24910                         {
24911                             xtype : 'Item',
24912                             html: 'Table',
24913                             listeners : {
24914                                 click : function (_self, e)
24915                                 {
24916                                     var t = table();
24917                                     var nn = t.node.nextSibling || t.node.previousSibling;
24918                                     t.node.parentNode.removeChild(t.node);
24919                                     if (nn) { 
24920                                         toolbar.editorcore.selectNode(nn, true);
24921                                     }
24922                                     toolbar.editorcore.onEditorEvent();   
24923                                                          
24924                                 }
24925                             },
24926                             xns : rooui.menu
24927                         }
24928                     ]
24929                 }
24930             }
24931             
24932             // align... << fixme
24933             
24934         ];
24935         
24936     },
24937     
24938     
24939   /**
24940      * create a DomHelper friendly object - for use with
24941      * Roo.DomHelper.markup / overwrite / etc..
24942      * ?? should it be called with option to hide all editing features?
24943      */
24944  /**
24945      * create a DomHelper friendly object - for use with
24946      * Roo.DomHelper.markup / overwrite / etc..
24947      * ?? should it be called with option to hide all editing features?
24948      */
24949     toObject : function()
24950     {
24951         var ret = {
24952             tag : 'td',
24953             contenteditable : 'true', // this stops cell selection from picking the table.
24954             'data-block' : 'Td',
24955             valign : this.valign,
24956             style : {  
24957                 'text-align' :  this.textAlign,
24958                 border : 'solid 1px rgb(0, 0, 0)', // ??? hard coded?
24959                 'border-collapse' : 'collapse',
24960                 padding : '6px', // 8 for desktop / 4 for mobile
24961                 'vertical-align': this.valign
24962             },
24963             html : this.html
24964         };
24965         if (this.width != '') {
24966             ret.width = this.width;
24967             ret.style.width = this.width;
24968         }
24969         
24970         
24971         if (this.colspan > 1) {
24972             ret.colspan = this.colspan ;
24973         } 
24974         if (this.rowspan > 1) {
24975             ret.rowspan = this.rowspan ;
24976         }
24977         
24978            
24979         
24980         return ret;
24981          
24982     },
24983     
24984     readElement : function(node)
24985     {
24986         node  = node ? node : this.node ;
24987         this.width = node.style.width;
24988         this.colspan = Math.max(1,1*node.getAttribute('colspan'));
24989         this.rowspan = Math.max(1,1*node.getAttribute('rowspan'));
24990         this.html = node.innerHTML;
24991         if (node.style.textAlign != '') {
24992             this.textAlign = node.style.textAlign;
24993         }
24994         
24995         
24996     },
24997      
24998     // the default cell object... at present...
24999     emptyCell : function() {
25000         return {
25001             colspan :  1,
25002             rowspan :  1,
25003             textAlign : 'left',
25004             html : "&nbsp;" // is this going to be editable now?
25005         };
25006      
25007     },
25008     
25009     removeNode : function()
25010     {
25011         return this.node.closest('table');
25012          
25013     },
25014     
25015     cellData : false,
25016     
25017     colWidths : false,
25018     
25019     toTableArray  : function()
25020     {
25021         var ret = [];
25022         var tab = this.node.closest('tr').closest('table');
25023         Array.from(tab.rows).forEach(function(r, ri){
25024             ret[ri] = [];
25025         });
25026         var rn = 0;
25027         this.colWidths = [];
25028         var all_auto = true;
25029         Array.from(tab.rows).forEach(function(r, ri){
25030             
25031             var cn = 0;
25032             Array.from(r.cells).forEach(function(ce, ci){
25033                 var c =  {
25034                     cell : ce,
25035                     row : rn,
25036                     col: cn,
25037                     colspan : ce.colSpan,
25038                     rowspan : ce.rowSpan
25039                 };
25040                 if (ce.isEqualNode(this.node)) {
25041                     this.cellData = c;
25042                 }
25043                 // if we have been filled up by a row?
25044                 if (typeof(ret[rn][cn]) != 'undefined') {
25045                     while(typeof(ret[rn][cn]) != 'undefined') {
25046                         cn++;
25047                     }
25048                     c.col = cn;
25049                 }
25050                 
25051                 if (typeof(this.colWidths[cn]) == 'undefined' && c.colspan < 2) {
25052                     this.colWidths[cn] =   ce.style.width;
25053                     if (this.colWidths[cn] != '') {
25054                         all_auto = false;
25055                     }
25056                 }
25057                 
25058                 
25059                 if (c.colspan < 2 && c.rowspan < 2 ) {
25060                     ret[rn][cn] = c;
25061                     cn++;
25062                     return;
25063                 }
25064                 for(var j = 0; j < c.rowspan; j++) {
25065                     if (typeof(ret[rn+j]) == 'undefined') {
25066                         continue; // we have a problem..
25067                     }
25068                     ret[rn+j][cn] = c;
25069                     for(var i = 0; i < c.colspan; i++) {
25070                         ret[rn+j][cn+i] = c;
25071                     }
25072                 }
25073                 
25074                 cn += c.colspan;
25075             }, this);
25076             rn++;
25077         }, this);
25078         
25079         // initalize widths.?
25080         // either all widths or no widths..
25081         if (all_auto) {
25082             this.colWidths[0] = false; // no widths flag.
25083         }
25084         
25085         
25086         return ret;
25087         
25088     },
25089     
25090     
25091     
25092     
25093     mergeRight: function()
25094     {
25095          
25096         // get the contents of the next cell along..
25097         var tr = this.node.closest('tr');
25098         var i = Array.prototype.indexOf.call(tr.childNodes, this.node);
25099         if (i >= tr.childNodes.length - 1) {
25100             return; // no cells on right to merge with.
25101         }
25102         var table = this.toTableArray();
25103         
25104         if (typeof(table[this.cellData.row][this.cellData.col+this.cellData.colspan]) == 'undefined') {
25105             return; // nothing right?
25106         }
25107         var rc = table[this.cellData.row][this.cellData.col+this.cellData.colspan];
25108         // right cell - must be same rowspan and on the same row.
25109         if (rc.rowspan != this.cellData.rowspan || rc.row != this.cellData.row) {
25110             return; // right hand side is not same rowspan.
25111         }
25112         
25113         
25114         
25115         this.node.innerHTML += ' ' + rc.cell.innerHTML;
25116         tr.removeChild(rc.cell);
25117         this.colspan += rc.colspan;
25118         this.node.setAttribute('colspan', this.colspan);
25119
25120         var table = this.toTableArray();
25121         this.normalizeWidths(table);
25122         this.updateWidths(table);
25123     },
25124     
25125     
25126     mergeBelow : function()
25127     {
25128         var table = this.toTableArray();
25129         if (typeof(table[this.cellData.row+this.cellData.rowspan]) == 'undefined') {
25130             return; // no row below
25131         }
25132         if (typeof(table[this.cellData.row+this.cellData.rowspan][this.cellData.col]) == 'undefined') {
25133             return; // nothing right?
25134         }
25135         var rc = table[this.cellData.row+this.cellData.rowspan][this.cellData.col];
25136         
25137         if (rc.colspan != this.cellData.colspan || rc.col != this.cellData.col) {
25138             return; // right hand side is not same rowspan.
25139         }
25140         this.node.innerHTML =  this.node.innerHTML + rc.cell.innerHTML ;
25141         rc.cell.parentNode.removeChild(rc.cell);
25142         this.rowspan += rc.rowspan;
25143         this.node.setAttribute('rowspan', this.rowspan);
25144     },
25145     
25146     split: function()
25147     {
25148         if (this.node.rowSpan < 2 && this.node.colSpan < 2) {
25149             return;
25150         }
25151         var table = this.toTableArray();
25152         var cd = this.cellData;
25153         this.rowspan = 1;
25154         this.colspan = 1;
25155         
25156         for(var r = cd.row; r < cd.row + cd.rowspan; r++) {
25157              
25158             
25159             for(var c = cd.col; c < cd.col + cd.colspan; c++) {
25160                 if (r == cd.row && c == cd.col) {
25161                     this.node.removeAttribute('rowspan');
25162                     this.node.removeAttribute('colspan');
25163                 }
25164                  
25165                 var ntd = this.node.cloneNode(); // which col/row should be 0..
25166                 ntd.removeAttribute('id'); 
25167                 ntd.style.width  = this.colWidths[c];
25168                 ntd.innerHTML = '';
25169                 table[r][c] = { cell : ntd, col : c, row: r , colspan : 1 , rowspan : 1   };
25170             }
25171             
25172         }
25173         this.redrawAllCells(table);
25174         
25175     },
25176     
25177     
25178     
25179     redrawAllCells: function(table)
25180     {
25181         
25182          
25183         var tab = this.node.closest('tr').closest('table');
25184         var ctr = tab.rows[0].parentNode;
25185         Array.from(tab.rows).forEach(function(r, ri){
25186             
25187             Array.from(r.cells).forEach(function(ce, ci){
25188                 ce.parentNode.removeChild(ce);
25189             });
25190             r.parentNode.removeChild(r);
25191         });
25192         for(var r = 0 ; r < table.length; r++) {
25193             var re = tab.rows[r];
25194             
25195             var re = tab.ownerDocument.createElement('tr');
25196             ctr.appendChild(re);
25197             for(var c = 0 ; c < table[r].length; c++) {
25198                 if (table[r][c].cell === false) {
25199                     continue;
25200                 }
25201                 
25202                 re.appendChild(table[r][c].cell);
25203                  
25204                 table[r][c].cell = false;
25205             }
25206         }
25207         
25208     },
25209     updateWidths : function(table)
25210     {
25211         for(var r = 0 ; r < table.length; r++) {
25212            
25213             for(var c = 0 ; c < table[r].length; c++) {
25214                 if (table[r][c].cell === false) {
25215                     continue;
25216                 }
25217                 
25218                 if (this.colWidths[0] != false && table[r][c].colspan < 2) {
25219                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
25220                     el.width = Math.floor(this.colWidths[c])  +'%';
25221                     el.updateElement(el.node);
25222                 }
25223                 if (this.colWidths[0] != false && table[r][c].colspan > 1) {
25224                     var el = Roo.htmleditor.Block.factory(table[r][c].cell);
25225                     var width = 0;
25226                     for(var i = 0; i < table[r][c].colspan; i ++) {
25227                         width += Math.floor(this.colWidths[c + i]);
25228                     }
25229                     el.width = width  +'%';
25230                     el.updateElement(el.node);
25231                 }
25232                 table[r][c].cell = false; // done
25233             }
25234         }
25235     },
25236     normalizeWidths : function(table)
25237     {
25238         if (this.colWidths[0] === false) {
25239             var nw = 100.0 / this.colWidths.length;
25240             this.colWidths.forEach(function(w,i) {
25241                 this.colWidths[i] = nw;
25242             },this);
25243             return;
25244         }
25245     
25246         var t = 0, missing = [];
25247         
25248         this.colWidths.forEach(function(w,i) {
25249             //if you mix % and
25250             this.colWidths[i] = this.colWidths[i] == '' ? 0 : (this.colWidths[i]+'').replace(/[^0-9]+/g,'')*1;
25251             var add =  this.colWidths[i];
25252             if (add > 0) {
25253                 t+=add;
25254                 return;
25255             }
25256             missing.push(i);
25257             
25258             
25259         },this);
25260         var nc = this.colWidths.length;
25261         if (missing.length) {
25262             var mult = (nc - missing.length) / (1.0 * nc);
25263             var t = mult * t;
25264             var ew = (100 -t) / (1.0 * missing.length);
25265             this.colWidths.forEach(function(w,i) {
25266                 if (w > 0) {
25267                     this.colWidths[i] = w * mult;
25268                     return;
25269                 }
25270                 
25271                 this.colWidths[i] = ew;
25272             }, this);
25273             // have to make up numbers..
25274              
25275         }
25276         // now we should have all the widths..
25277         
25278     
25279     },
25280     
25281     shrinkColumn : function()
25282     {
25283         var table = this.toTableArray();
25284         this.normalizeWidths(table);
25285         var col = this.cellData.col;
25286         var nw = this.colWidths[col] * 0.8;
25287         if (nw < 5) {
25288             return;
25289         }
25290         var otherAdd = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
25291         this.colWidths.forEach(function(w,i) {
25292             if (i == col) {
25293                  this.colWidths[i] = nw;
25294                 return;
25295             }
25296             this.colWidths[i] += otherAdd
25297         }, this);
25298         this.updateWidths(table);
25299          
25300     },
25301     growColumn : function()
25302     {
25303         var table = this.toTableArray();
25304         this.normalizeWidths(table);
25305         var col = this.cellData.col;
25306         var nw = this.colWidths[col] * 1.2;
25307         if (nw > 90) {
25308             return;
25309         }
25310         var otherSub = (this.colWidths[col]  * 0.2) / (this.colWidths.length -1);
25311         this.colWidths.forEach(function(w,i) {
25312             if (i == col) {
25313                 this.colWidths[i] = nw;
25314                 return;
25315             }
25316             this.colWidths[i] -= otherSub
25317         }, this);
25318         this.updateWidths(table);
25319          
25320     },
25321     deleteRow : function()
25322     {
25323         // delete this rows 'tr'
25324         // if any of the cells in this row have a rowspan > 1 && row!= this row..
25325         // then reduce the rowspan.
25326         var table = this.toTableArray();
25327         // this.cellData.row;
25328         for (var i =0;i< table[this.cellData.row].length ; i++) {
25329             var c = table[this.cellData.row][i];
25330             if (c.row != this.cellData.row) {
25331                 
25332                 c.rowspan--;
25333                 c.cell.setAttribute('rowspan', c.rowspan);
25334                 continue;
25335             }
25336             if (c.rowspan > 1) {
25337                 c.rowspan--;
25338                 c.cell.setAttribute('rowspan', c.rowspan);
25339             }
25340         }
25341         table.splice(this.cellData.row,1);
25342         this.redrawAllCells(table);
25343         
25344     },
25345     deleteColumn : function()
25346     {
25347         var table = this.toTableArray();
25348         
25349         for (var i =0;i< table.length ; i++) {
25350             var c = table[i][this.cellData.col];
25351             if (c.col != this.cellData.col) {
25352                 table[i][this.cellData.col].colspan--;
25353             } else if (c.colspan > 1) {
25354                 c.colspan--;
25355                 c.cell.setAttribute('colspan', c.colspan);
25356             }
25357             table[i].splice(this.cellData.col,1);
25358         }
25359         
25360         this.redrawAllCells(table);
25361     }
25362     
25363     
25364     
25365     
25366 })
25367
25368 //<script type="text/javascript">
25369
25370 /*
25371  * Based  Ext JS Library 1.1.1
25372  * Copyright(c) 2006-2007, Ext JS, LLC.
25373  * LGPL
25374  *
25375  */
25376  
25377 /**
25378  * @class Roo.HtmlEditorCore
25379  * @extends Roo.Component
25380  * Provides a the editing component for the HTML editors in Roo. (bootstrap and Roo.form)
25381  *
25382  * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
25383  */
25384
25385 Roo.HtmlEditorCore = function(config){
25386     
25387     
25388     Roo.HtmlEditorCore.superclass.constructor.call(this, config);
25389     
25390     
25391     this.addEvents({
25392         /**
25393          * @event initialize
25394          * Fires when the editor is fully initialized (including the iframe)
25395          * @param {Roo.HtmlEditorCore} this
25396          */
25397         initialize: true,
25398         /**
25399          * @event activate
25400          * Fires when the editor is first receives the focus. Any insertion must wait
25401          * until after this event.
25402          * @param {Roo.HtmlEditorCore} this
25403          */
25404         activate: true,
25405          /**
25406          * @event beforesync
25407          * Fires before the textarea is updated with content from the editor iframe. Return false
25408          * to cancel the sync.
25409          * @param {Roo.HtmlEditorCore} this
25410          * @param {String} html
25411          */
25412         beforesync: true,
25413          /**
25414          * @event beforepush
25415          * Fires before the iframe editor is updated with content from the textarea. Return false
25416          * to cancel the push.
25417          * @param {Roo.HtmlEditorCore} this
25418          * @param {String} html
25419          */
25420         beforepush: true,
25421          /**
25422          * @event sync
25423          * Fires when the textarea is updated with content from the editor iframe.
25424          * @param {Roo.HtmlEditorCore} this
25425          * @param {String} html
25426          */
25427         sync: true,
25428          /**
25429          * @event push
25430          * Fires when the iframe editor is updated with content from the textarea.
25431          * @param {Roo.HtmlEditorCore} this
25432          * @param {String} html
25433          */
25434         push: true,
25435         
25436         /**
25437          * @event editorevent
25438          * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
25439          * @param {Roo.HtmlEditorCore} this
25440          */
25441         editorevent: true 
25442          
25443         
25444     });
25445     
25446     // at this point this.owner is set, so we can start working out the whitelisted / blacklisted elements
25447     
25448     // defaults : white / black...
25449     this.applyBlacklists();
25450     
25451     
25452     
25453 };
25454
25455
25456 Roo.extend(Roo.HtmlEditorCore, Roo.Component,  {
25457
25458
25459      /**
25460      * @cfg {Roo.form.HtmlEditor|Roo.bootstrap.HtmlEditor} the owner field 
25461      */
25462     
25463     owner : false,
25464     
25465      /**
25466      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
25467      *                        Roo.resizable.
25468      */
25469     resizable : false,
25470      /**
25471      * @cfg {Number} height (in pixels)
25472      */   
25473     height: 300,
25474    /**
25475      * @cfg {Number} width (in pixels)
25476      */   
25477     width: 500,
25478      /**
25479      * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
25480      *         if you are doing an email editor, this probably needs disabling, it's designed
25481      */
25482     autoClean: true,
25483     
25484     /**
25485      * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
25486      */
25487     enableBlocks : true,
25488     /**
25489      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets.
25490      * 
25491      */
25492     stylesheets: false,
25493      /**
25494      * @cfg {String} language default en - language of text (usefull for rtl languages)
25495      * 
25496      */
25497     language: 'en',
25498     
25499     /**
25500      * @cfg {boolean} allowComments - default false - allow comments in HTML source
25501      *          - by default they are stripped - if you are editing email you may need this.
25502      */
25503     allowComments: false,
25504     // id of frame..
25505     frameId: false,
25506     
25507     // private properties
25508     validationEvent : false,
25509     deferHeight: true,
25510     initialized : false,
25511     activated : false,
25512     sourceEditMode : false,
25513     onFocus : Roo.emptyFn,
25514     iframePad:3,
25515     hideMode:'offsets',
25516     
25517     clearUp: true,
25518     
25519     // blacklist + whitelisted elements..
25520     black: false,
25521     white: false,
25522      
25523     bodyCls : '',
25524
25525     
25526     undoManager : false,
25527     /**
25528      * Protected method that will not generally be called directly. It
25529      * is called when the editor initializes the iframe with HTML contents. Override this method if you
25530      * want to change the initialization markup of the iframe (e.g. to add stylesheets).
25531      */
25532     getDocMarkup : function(){
25533         // body styles..
25534         var st = '';
25535         
25536         // inherit styels from page...?? 
25537         if (this.stylesheets === false) {
25538             
25539             Roo.get(document.head).select('style').each(function(node) {
25540                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
25541             });
25542             
25543             Roo.get(document.head).select('link').each(function(node) { 
25544                 st += node.dom.outerHTML || new XMLSerializer().serializeToString(node.dom);
25545             });
25546             
25547         } else if (!this.stylesheets.length) {
25548                 // simple..
25549                 st = '<style type="text/css">' +
25550                     'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
25551                    '</style>';
25552         } else {
25553             for (var i in this.stylesheets) {
25554                 if (typeof(this.stylesheets[i]) != 'string') {
25555                     continue;
25556                 }
25557                 st += '<link rel="stylesheet" href="' + this.stylesheets[i] +'" type="text/css">';
25558             }
25559             
25560         }
25561         
25562         st +=  '<style type="text/css">' +
25563             'IMG { cursor: pointer } ' +
25564         '</style>';
25565         
25566         st += '<meta name="google" content="notranslate">';
25567         
25568         var cls = 'notranslate roo-htmleditor-body';
25569         
25570         if(this.bodyCls.length){
25571             cls += ' ' + this.bodyCls;
25572         }
25573         
25574         return '<html  class="notranslate" translate="no"><head>' + st  +
25575             //<style type="text/css">' +
25576             //'body{border:0;margin:0;padding:3px;height:98%;cursor:text;}' +
25577             //'</style>' +
25578             ' </head><body contenteditable="true" data-enable-grammerly="true" class="' +  cls + '"></body></html>';
25579     },
25580
25581     // private
25582     onRender : function(ct, position)
25583     {
25584         var _t = this;
25585         //Roo.HtmlEditorCore.superclass.onRender.call(this, ct, position);
25586         this.el = this.owner.inputEl ? this.owner.inputEl() : this.owner.el;
25587         
25588         
25589         this.el.dom.style.border = '0 none';
25590         this.el.dom.setAttribute('tabIndex', -1);
25591         this.el.addClass('x-hidden hide');
25592         
25593         
25594         
25595         if(Roo.isIE){ // fix IE 1px bogus margin
25596             this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;')
25597         }
25598        
25599         
25600         this.frameId = Roo.id();
25601         
25602          
25603         
25604         var iframe = this.owner.wrap.createChild({
25605             tag: 'iframe',
25606             cls: 'form-control', // bootstrap..
25607             id: this.frameId,
25608             name: this.frameId,
25609             frameBorder : 'no',
25610             'src' : Roo.SSL_SECURE_URL ? Roo.SSL_SECURE_URL  :  "javascript:false"
25611         }, this.el
25612         );
25613         
25614         
25615         this.iframe = iframe.dom;
25616
25617         this.assignDocWin();
25618         
25619         this.doc.designMode = 'on';
25620        
25621         this.doc.open();
25622         this.doc.write(this.getDocMarkup());
25623         this.doc.close();
25624
25625         
25626         var task = { // must defer to wait for browser to be ready
25627             run : function(){
25628                 //console.log("run task?" + this.doc.readyState);
25629                 this.assignDocWin();
25630                 if(this.doc.body || this.doc.readyState == 'complete'){
25631                     try {
25632                         this.doc.designMode="on";
25633                         
25634                     } catch (e) {
25635                         return;
25636                     }
25637                     Roo.TaskMgr.stop(task);
25638                     this.initEditor.defer(10, this);
25639                 }
25640             },
25641             interval : 10,
25642             duration: 10000,
25643             scope: this
25644         };
25645         Roo.TaskMgr.start(task);
25646
25647     },
25648
25649     // private
25650     onResize : function(w, h)
25651     {
25652          Roo.log('resize: ' +w + ',' + h );
25653         //Roo.HtmlEditorCore.superclass.onResize.apply(this, arguments);
25654         if(!this.iframe){
25655             return;
25656         }
25657         if(typeof w == 'number'){
25658             
25659             this.iframe.style.width = w + 'px';
25660         }
25661         if(typeof h == 'number'){
25662             
25663             this.iframe.style.height = h + 'px';
25664             if(this.doc){
25665                 (this.doc.body || this.doc.documentElement).style.height = (h - (this.iframePad*2)) + 'px';
25666             }
25667         }
25668         
25669     },
25670
25671     /**
25672      * Toggles the editor between standard and source edit mode.
25673      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
25674      */
25675     toggleSourceEdit : function(sourceEditMode){
25676         
25677         this.sourceEditMode = sourceEditMode === true;
25678         
25679         if(this.sourceEditMode){
25680  
25681             Roo.get(this.iframe).addClass(['x-hidden','hide', 'd-none']);     //FIXME - what's the BS styles for these
25682             
25683         }else{
25684             Roo.get(this.iframe).removeClass(['x-hidden','hide', 'd-none']);
25685             //this.iframe.className = '';
25686             this.deferFocus();
25687         }
25688         //this.setSize(this.owner.wrap.getSize());
25689         //this.fireEvent('editmodechange', this, this.sourceEditMode);
25690     },
25691
25692     
25693   
25694
25695     /**
25696      * Protected method that will not generally be called directly. If you need/want
25697      * custom HTML cleanup, this is the method you should override.
25698      * @param {String} html The HTML to be cleaned
25699      * return {String} The cleaned HTML
25700      */
25701     cleanHtml : function(html)
25702     {
25703         html = String(html);
25704         if(html.length > 5){
25705             if(Roo.isSafari){ // strip safari nonsense
25706                 html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
25707             }
25708         }
25709         if(html == '&nbsp;'){
25710             html = '';
25711         }
25712         return html;
25713     },
25714
25715     /**
25716      * HTML Editor -> Textarea
25717      * Protected method that will not generally be called directly. Syncs the contents
25718      * of the editor iframe with the textarea.
25719      */
25720     syncValue : function()
25721     {
25722         //Roo.log("HtmlEditorCore:syncValue (EDITOR->TEXT)");
25723         if(this.initialized){
25724             
25725             if (this.undoManager) {
25726                 this.undoManager.addEvent();
25727             }
25728
25729             
25730             var bd = (this.doc.body || this.doc.documentElement);
25731            
25732             
25733             var sel = this.win.getSelection();
25734             
25735             var div = document.createElement('div');
25736             div.innerHTML = bd.innerHTML;
25737             var gtx = div.getElementsByClassName('gtx-trans-icon'); // google translate - really annoying and difficult to get rid of.
25738             if (gtx.length > 0) {
25739                 var rm = gtx.item(0).parentNode;
25740                 rm.parentNode.removeChild(rm);
25741             }
25742             
25743            
25744             if (this.enableBlocks) {
25745                 new Roo.htmleditor.FilterBlock({ node : div });
25746             }
25747             
25748             var html = div.innerHTML;
25749             
25750             //?? tidy?
25751             if (this.autoClean) {
25752                 
25753                 new Roo.htmleditor.FilterAttributes({
25754                     node : div,
25755                     attrib_white : [
25756                             'href',
25757                             'src',
25758                             'name',
25759                             'align',
25760                             'colspan',
25761                             'rowspan',
25762                             'data-display',
25763                             'data-width',
25764                             'start' ,
25765                             'style',
25766                             // youtube embed.
25767                             'class',
25768                             'allowfullscreen',
25769                             'frameborder',
25770                             'width',
25771                             'height',
25772                             'alt'
25773                             ],
25774                     attrib_clean : ['href', 'src' ] 
25775                 });
25776                 
25777                 var tidy = new Roo.htmleditor.TidySerializer({
25778                     inner:  true
25779                 });
25780                 html  = tidy.serialize(div);
25781                 
25782             }
25783             
25784             
25785             if(Roo.isSafari){
25786                 var bs = bd.getAttribute('style'); // Safari puts text-align styles on the body element!
25787                 var m = bs ? bs.match(/text-align:(.*?);/i) : false;
25788                 if(m && m[1]){
25789                     html = '<div style="'+m[0]+'">' + html + '</div>';
25790                 }
25791             }
25792             html = this.cleanHtml(html);
25793             // fix up the special chars.. normaly like back quotes in word...
25794             // however we do not want to do this with chinese..
25795             html = html.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0080-\uFFFF]/g, function(match) {
25796                 
25797                 var cc = match.charCodeAt();
25798
25799                 // Get the character value, handling surrogate pairs
25800                 if (match.length == 2) {
25801                     // It's a surrogate pair, calculate the Unicode code point
25802                     var high = match.charCodeAt(0) - 0xD800;
25803                     var low  = match.charCodeAt(1) - 0xDC00;
25804                     cc = (high * 0x400) + low + 0x10000;
25805                 }  else if (
25806                     (cc >= 0x4E00 && cc < 0xA000 ) ||
25807                     (cc >= 0x3400 && cc < 0x4E00 ) ||
25808                     (cc >= 0xf900 && cc < 0xfb00 )
25809                 ) {
25810                         return match;
25811                 }  
25812          
25813                 // No, use a numeric entity. Here we brazenly (and possibly mistakenly)
25814                 return "&#" + cc + ";";
25815                 
25816                 
25817             });
25818             
25819             
25820              
25821             if(this.owner.fireEvent('beforesync', this, html) !== false){
25822                 this.el.dom.value = html;
25823                 this.owner.fireEvent('sync', this, html);
25824             }
25825         }
25826     },
25827
25828     /**
25829      * TEXTAREA -> EDITABLE
25830      * Protected method that will not generally be called directly. Pushes the value of the textarea
25831      * into the iframe editor.
25832      */
25833     pushValue : function()
25834     {
25835         //Roo.log("HtmlEditorCore:pushValue (TEXT->EDITOR)");
25836         if(this.initialized){
25837             var v = this.el.dom.value.trim();
25838             
25839             
25840             if(this.owner.fireEvent('beforepush', this, v) !== false){
25841                 var d = (this.doc.body || this.doc.documentElement);
25842                 d.innerHTML = v;
25843                  
25844                 this.el.dom.value = d.innerHTML;
25845                 this.owner.fireEvent('push', this, v);
25846             }
25847             if (this.autoClean) {
25848                 new Roo.htmleditor.FilterParagraph({node : this.doc.body}); // paragraphs
25849                 new Roo.htmleditor.FilterSpan({node : this.doc.body}); // empty spans
25850             }
25851             if (this.enableBlocks) {
25852                 Roo.htmleditor.Block.initAll(this.doc.body);
25853             }
25854             
25855             this.updateLanguage();
25856             
25857             var lc = this.doc.body.lastChild;
25858             if (lc && lc.nodeType == 1 && lc.getAttribute("contenteditable") == "false") {
25859                 // add an extra line at the end.
25860                 this.doc.body.appendChild(this.doc.createElement('br'));
25861             }
25862             
25863             
25864         }
25865     },
25866
25867     // private
25868     deferFocus : function(){
25869         this.focus.defer(10, this);
25870     },
25871
25872     // doc'ed in Field
25873     focus : function(){
25874         if(this.win && !this.sourceEditMode){
25875             this.win.focus();
25876         }else{
25877             this.el.focus();
25878         }
25879     },
25880     
25881     assignDocWin: function()
25882     {
25883         var iframe = this.iframe;
25884         
25885          if(Roo.isIE){
25886             this.doc = iframe.contentWindow.document;
25887             this.win = iframe.contentWindow;
25888         } else {
25889 //            if (!Roo.get(this.frameId)) {
25890 //                return;
25891 //            }
25892 //            this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
25893 //            this.win = Roo.get(this.frameId).dom.contentWindow;
25894             
25895             if (!Roo.get(this.frameId) && !iframe.contentDocument) {
25896                 return;
25897             }
25898             
25899             this.doc = (iframe.contentDocument || Roo.get(this.frameId).dom.document);
25900             this.win = (iframe.contentWindow || Roo.get(this.frameId).dom.contentWindow);
25901         }
25902     },
25903     
25904     // private
25905     initEditor : function(){
25906         //console.log("INIT EDITOR");
25907         this.assignDocWin();
25908         
25909         
25910         
25911         this.doc.designMode="on";
25912         this.doc.open();
25913         this.doc.write(this.getDocMarkup());
25914         this.doc.close();
25915         
25916         var dbody = (this.doc.body || this.doc.documentElement);
25917         //var ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat');
25918         // this copies styles from the containing element into thsi one..
25919         // not sure why we need all of this..
25920         //var ss = this.el.getStyles('font-size', 'background-image', 'background-repeat');
25921         
25922         //var ss = this.el.getStyles( 'background-image', 'background-repeat');
25923         //ss['background-attachment'] = 'fixed'; // w3c
25924         dbody.bgProperties = 'fixed'; // ie
25925         dbody.setAttribute("translate", "no");
25926         
25927         //Roo.DomHelper.applyStyles(dbody, ss);
25928         Roo.EventManager.on(this.doc, {
25929              
25930             'mouseup': this.onEditorEvent,
25931             'dblclick': this.onEditorEvent,
25932             'click': this.onEditorEvent,
25933             'keyup': this.onEditorEvent,
25934             
25935             buffer:100,
25936             scope: this
25937         });
25938         Roo.EventManager.on(this.doc, {
25939             'paste': this.onPasteEvent,
25940             scope : this
25941         });
25942         if(Roo.isGecko){
25943             Roo.EventManager.on(this.doc, 'keypress', this.mozKeyPress, this);
25944         }
25945         //??? needed???
25946         if(Roo.isIE || Roo.isSafari || Roo.isOpera){
25947             Roo.EventManager.on(this.doc, 'keydown', this.fixKeys, this);
25948         }
25949         this.initialized = true;
25950
25951         
25952         // initialize special key events - enter
25953         new Roo.htmleditor.KeyEnter({core : this});
25954         
25955          
25956         
25957         this.owner.fireEvent('initialize', this);
25958         this.pushValue();
25959     },
25960     // this is to prevent a href clicks resulting in a redirect?
25961    
25962     onPasteEvent : function(e,v)
25963     {
25964         // I think we better assume paste is going to be a dirty load of rubish from word..
25965         
25966         // even pasting into a 'email version' of this widget will have to clean up that mess.
25967         var cd = (e.browserEvent.clipboardData || window.clipboardData);
25968         
25969         // check what type of paste - if it's an image, then handle it differently.
25970         if (cd.files && cd.files.length > 0) {
25971             // pasting images?
25972             var urlAPI = (window.createObjectURL && window) || 
25973                 (window.URL && URL.revokeObjectURL && URL) || 
25974                 (window.webkitURL && webkitURL);
25975     
25976             var url = urlAPI.createObjectURL( cd.files[0]);
25977             this.insertAtCursor('<img src=" + url + ">');
25978             return false;
25979         }
25980         if (cd.types.indexOf('text/html') < 0 ) {
25981             return false;
25982         }
25983         var images = [];
25984         var html = cd.getData('text/html'); // clipboard event
25985         if (cd.types.indexOf('text/rtf') > -1) {
25986             var parser = new Roo.rtf.Parser(cd.getData('text/rtf'));
25987             images = parser.doc ? parser.doc.getElementsByType('pict') : [];
25988         }
25989         //Roo.log(images);
25990         //Roo.log(imgs);
25991         // fixme..
25992         images = images.filter(function(g) { return !g.path.match(/^rtf\/(head|pgdsctbl|listtable|footerf)/); }) // ignore headers/footers etc.
25993                        .map(function(g) { return g.toDataURL(); })
25994                        .filter(function(g) { return g != 'about:blank'; });
25995         
25996         //Roo.log(html);
25997         html = this.cleanWordChars(html);
25998         
25999         var d = (new DOMParser().parseFromString(html, 'text/html')).body;
26000         
26001         
26002         var sn = this.getParentElement();
26003         // check if d contains a table, and prevent nesting??
26004         //Roo.log(d.getElementsByTagName('table'));
26005         //Roo.log(sn);
26006         //Roo.log(sn.closest('table'));
26007         if (d.getElementsByTagName('table').length && sn && sn.closest('table')) {
26008             e.preventDefault();
26009             this.insertAtCursor("You can not nest tables");
26010             //Roo.log("prevent?"); // fixme - 
26011             return false;
26012         }
26013         
26014         
26015         
26016         if (images.length > 0) {
26017             // replace all v:imagedata - with img.
26018             var ar = Array.from(d.getElementsByTagName('v:imagedata'));
26019             Roo.each(ar, function(node) {
26020                 node.parentNode.insertBefore(d.ownerDocument.createElement('img'), node );
26021                 node.parentNode.removeChild(node);
26022             });
26023             
26024             
26025             Roo.each(d.getElementsByTagName('img'), function(img, i) {
26026                 img.setAttribute('src', images[i]);
26027             });
26028         }
26029         if (this.autoClean) {
26030             new Roo.htmleditor.FilterWord({ node : d });
26031             
26032             new Roo.htmleditor.FilterStyleToTag({ node : d });
26033             new Roo.htmleditor.FilterAttributes({
26034                 node : d,
26035                 attrib_white : ['href', 'src', 'name', 'align', 'colspan', 'rowspan', 'data-display', 'data-width', 'start'],
26036                 attrib_clean : ['href', 'src' ] 
26037             });
26038             new Roo.htmleditor.FilterBlack({ node : d, tag : this.black});
26039             // should be fonts..
26040             new Roo.htmleditor.FilterKeepChildren({node : d, tag : [ 'FONT', ':' ]} );
26041             new Roo.htmleditor.FilterParagraph({ node : d });
26042             new Roo.htmleditor.FilterSpan({ node : d });
26043             new Roo.htmleditor.FilterLongBr({ node : d });
26044             new Roo.htmleditor.FilterComment({ node : d });
26045             
26046             
26047         }
26048         if (this.enableBlocks) {
26049                 
26050             Array.from(d.getElementsByTagName('img')).forEach(function(img) {
26051                 if (img.closest('figure')) { // assume!! that it's aready
26052                     return;
26053                 }
26054                 var fig  = new Roo.htmleditor.BlockFigure({
26055                     image_src  : img.src
26056                 });
26057                 fig.updateElement(img); // replace it..
26058                 
26059             });
26060         }
26061         
26062         
26063         this.insertAtCursor(d.innerHTML.replace(/&nbsp;/g,' '));
26064         if (this.enableBlocks) {
26065             Roo.htmleditor.Block.initAll(this.doc.body);
26066         }
26067          
26068         
26069         e.preventDefault();
26070         return false;
26071         // default behaveiour should be our local cleanup paste? (optional?)
26072         // for simple editor - we want to hammer the paste and get rid of everything... - so over-rideable..
26073         //this.owner.fireEvent('paste', e, v);
26074     },
26075     // private
26076     onDestroy : function(){
26077         
26078         
26079         
26080         if(this.rendered){
26081             
26082             //for (var i =0; i < this.toolbars.length;i++) {
26083             //    // fixme - ask toolbars for heights?
26084             //    this.toolbars[i].onDestroy();
26085            // }
26086             
26087             //this.wrap.dom.innerHTML = '';
26088             //this.wrap.remove();
26089         }
26090     },
26091
26092     // private
26093     onFirstFocus : function(){
26094         
26095         this.assignDocWin();
26096         this.undoManager = new Roo.lib.UndoManager(100,(this.doc.body || this.doc.documentElement));
26097         
26098         this.activated = true;
26099          
26100     
26101         if(Roo.isGecko){ // prevent silly gecko errors
26102             this.win.focus();
26103             var s = this.win.getSelection();
26104             if(!s.focusNode || s.focusNode.nodeType != 3){
26105                 var r = s.getRangeAt(0);
26106                 r.selectNodeContents((this.doc.body || this.doc.documentElement));
26107                 r.collapse(true);
26108                 this.deferFocus();
26109             }
26110             try{
26111                 this.execCmd('useCSS', true);
26112                 this.execCmd('styleWithCSS', false);
26113             }catch(e){}
26114         }
26115         this.owner.fireEvent('activate', this);
26116     },
26117
26118     // private
26119     adjustFont: function(btn){
26120         var adjust = btn.cmd == 'increasefontsize' ? 1 : -1;
26121         //if(Roo.isSafari){ // safari
26122         //    adjust *= 2;
26123        // }
26124         var v = parseInt(this.doc.queryCommandValue('FontSize')|| 3, 10);
26125         if(Roo.isSafari){ // safari
26126             var sm = { 10 : 1, 13: 2, 16:3, 18:4, 24: 5, 32:6, 48: 7 };
26127             v =  (v < 10) ? 10 : v;
26128             v =  (v > 48) ? 48 : v;
26129             v = typeof(sm[v]) == 'undefined' ? 1 : sm[v];
26130             
26131         }
26132         
26133         
26134         v = Math.max(1, v+adjust);
26135         
26136         this.execCmd('FontSize', v  );
26137     },
26138
26139     onEditorEvent : function(e)
26140     {
26141          
26142         
26143         if (e && (e.ctrlKey || e.metaKey) && e.keyCode === 90) {
26144             return; // we do not handle this.. (undo manager does..)
26145         }
26146         // in theory this detects if the last element is not a br, then we try and do that.
26147         // its so clicking in space at bottom triggers adding a br and moving the cursor.
26148         if (e &&
26149             e.target.nodeName == 'BODY' &&
26150             e.type == "mouseup" &&
26151             this.doc.body.lastChild
26152            ) {
26153             var lc = this.doc.body.lastChild;
26154             // gtx-trans is google translate plugin adding crap.
26155             while ((lc.nodeType == 3 && lc.nodeValue == '') || lc.id == 'gtx-trans') {
26156                 lc = lc.previousSibling;
26157             }
26158             if (lc.nodeType == 1 && lc.nodeName != 'BR') {
26159             // if last element is <BR> - then dont do anything.
26160             
26161                 var ns = this.doc.createElement('br');
26162                 this.doc.body.appendChild(ns);
26163                 range = this.doc.createRange();
26164                 range.setStartAfter(ns);
26165                 range.collapse(true);
26166                 var sel = this.win.getSelection();
26167                 sel.removeAllRanges();
26168                 sel.addRange(range);
26169             }
26170         }
26171         
26172         
26173         
26174         this.fireEditorEvent(e);
26175       //  this.updateToolbar();
26176         this.syncValue(); //we can not sync so often.. sync cleans, so this breaks stuff
26177     },
26178     
26179     fireEditorEvent: function(e)
26180     {
26181         this.owner.fireEvent('editorevent', this, e);
26182     },
26183
26184     insertTag : function(tg)
26185     {
26186         // could be a bit smarter... -> wrap the current selected tRoo..
26187         if (tg.toLowerCase() == 'span' ||
26188             tg.toLowerCase() == 'code' ||
26189             tg.toLowerCase() == 'sup' ||
26190             tg.toLowerCase() == 'sub' 
26191             ) {
26192             
26193             range = this.createRange(this.getSelection());
26194             var wrappingNode = this.doc.createElement(tg.toLowerCase());
26195             wrappingNode.appendChild(range.extractContents());
26196             range.insertNode(wrappingNode);
26197
26198             return;
26199             
26200             
26201             
26202         }
26203         this.execCmd("formatblock",   tg);
26204         this.undoManager.addEvent(); 
26205     },
26206     
26207     insertText : function(txt)
26208     {
26209         
26210         
26211         var range = this.createRange();
26212         range.deleteContents();
26213                //alert(Sender.getAttribute('label'));
26214                
26215         range.insertNode(this.doc.createTextNode(txt));
26216         this.undoManager.addEvent();
26217     } ,
26218     
26219      
26220
26221     /**
26222      * Executes a Midas editor command on the editor document and performs necessary focus and
26223      * toolbar updates. <b>This should only be called after the editor is initialized.</b>
26224      * @param {String} cmd The Midas command
26225      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
26226      */
26227     relayCmd : function(cmd, value)
26228     {
26229         
26230         switch (cmd) {
26231             case 'justifyleft':
26232             case 'justifyright':
26233             case 'justifycenter':
26234                 // if we are in a cell, then we will adjust the
26235                 var n = this.getParentElement();
26236                 var td = n.closest('td');
26237                 if (td) {
26238                     var bl = Roo.htmleditor.Block.factory(td);
26239                     bl.textAlign = cmd.replace('justify','');
26240                     bl.updateElement();
26241                     this.owner.fireEvent('editorevent', this);
26242                     return;
26243                 }
26244                 this.execCmd('styleWithCSS', true); // 
26245                 break;
26246             case 'bold':
26247             case 'italic':
26248                 // if there is no selection, then we insert, and set the curson inside it..
26249                 this.execCmd('styleWithCSS', false); 
26250                 break;
26251                 
26252         
26253             default:
26254                 break;
26255         }
26256         
26257         
26258         this.win.focus();
26259         this.execCmd(cmd, value);
26260         this.owner.fireEvent('editorevent', this);
26261         //this.updateToolbar();
26262         this.owner.deferFocus();
26263     },
26264
26265     /**
26266      * Executes a Midas editor command directly on the editor document.
26267      * For visual commands, you should use {@link #relayCmd} instead.
26268      * <b>This should only be called after the editor is initialized.</b>
26269      * @param {String} cmd The Midas command
26270      * @param {String/Boolean} value (optional) The value to pass to the command (defaults to null)
26271      */
26272     execCmd : function(cmd, value){
26273         this.doc.execCommand(cmd, false, value === undefined ? null : value);
26274         this.syncValue();
26275     },
26276  
26277  
26278    
26279     /**
26280      * Inserts the passed text at the current cursor position. Note: the editor must be initialized and activated
26281      * to insert tRoo.
26282      * @param {String} text | dom node.. 
26283      */
26284     insertAtCursor : function(text)
26285     {
26286         
26287         if(!this.activated){
26288             return;
26289         }
26290          
26291         if(Roo.isGecko || Roo.isOpera || Roo.isSafari){
26292             this.win.focus();
26293             
26294             
26295             // from jquery ui (MIT licenced)
26296             var range, node;
26297             var win = this.win;
26298             
26299             if (win.getSelection && win.getSelection().getRangeAt) {
26300                 
26301                 // delete the existing?
26302                 
26303                 this.createRange(this.getSelection()).deleteContents();
26304                 range = win.getSelection().getRangeAt(0);
26305                 node = typeof(text) == 'string' ? range.createContextualFragment(text) : text;
26306                 range.insertNode(node);
26307                 range = range.cloneRange();
26308                 range.collapse(false);
26309                  
26310                 win.getSelection().removeAllRanges();
26311                 win.getSelection().addRange(range);
26312                 
26313                 
26314                 
26315             } else if (win.document.selection && win.document.selection.createRange) {
26316                 // no firefox support
26317                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
26318                 win.document.selection.createRange().pasteHTML(txt);
26319             
26320             } else {
26321                 // no firefox support
26322                 var txt = typeof(text) == 'string' ? text : text.outerHTML;
26323                 this.execCmd('InsertHTML', txt);
26324             } 
26325             this.syncValue();
26326             
26327             this.deferFocus();
26328         }
26329     },
26330  // private
26331     mozKeyPress : function(e){
26332         if(e.ctrlKey){
26333             var c = e.getCharCode(), cmd;
26334           
26335             if(c > 0){
26336                 c = String.fromCharCode(c).toLowerCase();
26337                 switch(c){
26338                     case 'b':
26339                         cmd = 'bold';
26340                         break;
26341                     case 'i':
26342                         cmd = 'italic';
26343                         break;
26344                     
26345                     case 'u':
26346                         cmd = 'underline';
26347                         break;
26348                     
26349                     //case 'v':
26350                       //  this.cleanUpPaste.defer(100, this);
26351                       //  return;
26352                         
26353                 }
26354                 if(cmd){
26355                     
26356                     this.relayCmd(cmd);
26357                     //this.win.focus();
26358                     //this.execCmd(cmd);
26359                     //this.deferFocus();
26360                     e.preventDefault();
26361                 }
26362                 
26363             }
26364         }
26365     },
26366
26367     // private
26368     fixKeys : function(){ // load time branching for fastest keydown performance
26369         
26370         
26371         if(Roo.isIE){
26372             return function(e){
26373                 var k = e.getKey(), r;
26374                 if(k == e.TAB){
26375                     e.stopEvent();
26376                     r = this.doc.selection.createRange();
26377                     if(r){
26378                         r.collapse(true);
26379                         r.pasteHTML('&#160;&#160;&#160;&#160;');
26380                         this.deferFocus();
26381                     }
26382                     return;
26383                 }
26384                 /// this is handled by Roo.htmleditor.KeyEnter
26385                  /*
26386                 if(k == e.ENTER){
26387                     r = this.doc.selection.createRange();
26388                     if(r){
26389                         var target = r.parentElement();
26390                         if(!target || target.tagName.toLowerCase() != 'li'){
26391                             e.stopEvent();
26392                             r.pasteHTML('<br/>');
26393                             r.collapse(false);
26394                             r.select();
26395                         }
26396                     }
26397                 }
26398                 */
26399                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
26400                 //    this.cleanUpPaste.defer(100, this);
26401                 //    return;
26402                 //}
26403                 
26404                 
26405             };
26406         }else if(Roo.isOpera){
26407             return function(e){
26408                 var k = e.getKey();
26409                 if(k == e.TAB){
26410                     e.stopEvent();
26411                     this.win.focus();
26412                     this.execCmd('InsertHTML','&#160;&#160;&#160;&#160;');
26413                     this.deferFocus();
26414                 }
26415                
26416                 //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
26417                 //    this.cleanUpPaste.defer(100, this);
26418                  //   return;
26419                 //}
26420                 
26421             };
26422         }else if(Roo.isSafari){
26423             return function(e){
26424                 var k = e.getKey();
26425                 
26426                 if(k == e.TAB){
26427                     e.stopEvent();
26428                     this.execCmd('InsertText','\t');
26429                     this.deferFocus();
26430                     return;
26431                 }
26432                  this.mozKeyPress(e);
26433                 
26434                //if (String.fromCharCode(k).toLowerCase() == 'v') { // paste
26435                  //   this.cleanUpPaste.defer(100, this);
26436                  //   return;
26437                // }
26438                 
26439              };
26440         }
26441     }(),
26442     
26443     getAllAncestors: function()
26444     {
26445         var p = this.getSelectedNode();
26446         var a = [];
26447         if (!p) {
26448             a.push(p); // push blank onto stack..
26449             p = this.getParentElement();
26450         }
26451         
26452         
26453         while (p && (p.nodeType == 1) && (p.tagName.toLowerCase() != 'body')) {
26454             a.push(p);
26455             p = p.parentNode;
26456         }
26457         a.push(this.doc.body);
26458         return a;
26459     },
26460     lastSel : false,
26461     lastSelNode : false,
26462     
26463     
26464     getSelection : function() 
26465     {
26466         this.assignDocWin();
26467         return Roo.lib.Selection.wrap(Roo.isIE ? this.doc.selection : this.win.getSelection(), this.doc);
26468     },
26469     /**
26470      * Select a dom node
26471      * @param {DomElement} node the node to select
26472      */
26473     selectNode : function(node, collapse)
26474     {
26475         var nodeRange = node.ownerDocument.createRange();
26476         try {
26477             nodeRange.selectNode(node);
26478         } catch (e) {
26479             nodeRange.selectNodeContents(node);
26480         }
26481         if (collapse === true) {
26482             nodeRange.collapse(true);
26483         }
26484         //
26485         var s = this.win.getSelection();
26486         s.removeAllRanges();
26487         s.addRange(nodeRange);
26488     },
26489     
26490     getSelectedNode: function() 
26491     {
26492         // this may only work on Gecko!!!
26493         
26494         // should we cache this!!!!
26495         
26496          
26497          
26498         var range = this.createRange(this.getSelection()).cloneRange();
26499         
26500         if (Roo.isIE) {
26501             var parent = range.parentElement();
26502             while (true) {
26503                 var testRange = range.duplicate();
26504                 testRange.moveToElementText(parent);
26505                 if (testRange.inRange(range)) {
26506                     break;
26507                 }
26508                 if ((parent.nodeType != 1) || (parent.tagName.toLowerCase() == 'body')) {
26509                     break;
26510                 }
26511                 parent = parent.parentElement;
26512             }
26513             return parent;
26514         }
26515         
26516         // is ancestor a text element.
26517         var ac =  range.commonAncestorContainer;
26518         if (ac.nodeType == 3) {
26519             ac = ac.parentNode;
26520         }
26521         
26522         var ar = ac.childNodes;
26523          
26524         var nodes = [];
26525         var other_nodes = [];
26526         var has_other_nodes = false;
26527         for (var i=0;i<ar.length;i++) {
26528             if ((ar[i].nodeType == 3) && (!ar[i].data.length)) { // empty text ? 
26529                 continue;
26530             }
26531             // fullly contained node.
26532             
26533             if (this.rangeIntersectsNode(range,ar[i]) && this.rangeCompareNode(range,ar[i]) == 3) {
26534                 nodes.push(ar[i]);
26535                 continue;
26536             }
26537             
26538             // probably selected..
26539             if ((ar[i].nodeType == 1) && this.rangeIntersectsNode(range,ar[i]) && (this.rangeCompareNode(range,ar[i]) > 0)) {
26540                 other_nodes.push(ar[i]);
26541                 continue;
26542             }
26543             // outer..
26544             if (!this.rangeIntersectsNode(range,ar[i])|| (this.rangeCompareNode(range,ar[i]) == 0))  {
26545                 continue;
26546             }
26547             
26548             
26549             has_other_nodes = true;
26550         }
26551         if (!nodes.length && other_nodes.length) {
26552             nodes= other_nodes;
26553         }
26554         if (has_other_nodes || !nodes.length || (nodes.length > 1)) {
26555             return false;
26556         }
26557         
26558         return nodes[0];
26559     },
26560     
26561     
26562     createRange: function(sel)
26563     {
26564         // this has strange effects when using with 
26565         // top toolbar - not sure if it's a great idea.
26566         //this.editor.contentWindow.focus();
26567         if (typeof sel != "undefined") {
26568             try {
26569                 return sel.getRangeAt ? sel.getRangeAt(0) : sel.createRange();
26570             } catch(e) {
26571                 return this.doc.createRange();
26572             }
26573         } else {
26574             return this.doc.createRange();
26575         }
26576     },
26577     getParentElement: function()
26578     {
26579         
26580         this.assignDocWin();
26581         var sel = Roo.isIE ? this.doc.selection : this.win.getSelection();
26582         
26583         var range = this.createRange(sel);
26584          
26585         try {
26586             var p = range.commonAncestorContainer;
26587             while (p.nodeType == 3) { // text node
26588                 p = p.parentNode;
26589             }
26590             return p;
26591         } catch (e) {
26592             return null;
26593         }
26594     
26595     },
26596     /***
26597      *
26598      * Range intersection.. the hard stuff...
26599      *  '-1' = before
26600      *  '0' = hits..
26601      *  '1' = after.
26602      *         [ -- selected range --- ]
26603      *   [fail]                        [fail]
26604      *
26605      *    basically..
26606      *      if end is before start or  hits it. fail.
26607      *      if start is after end or hits it fail.
26608      *
26609      *   if either hits (but other is outside. - then it's not 
26610      *   
26611      *    
26612      **/
26613     
26614     
26615     // @see http://www.thismuchiknow.co.uk/?p=64.
26616     rangeIntersectsNode : function(range, node)
26617     {
26618         var nodeRange = node.ownerDocument.createRange();
26619         try {
26620             nodeRange.selectNode(node);
26621         } catch (e) {
26622             nodeRange.selectNodeContents(node);
26623         }
26624     
26625         var rangeStartRange = range.cloneRange();
26626         rangeStartRange.collapse(true);
26627     
26628         var rangeEndRange = range.cloneRange();
26629         rangeEndRange.collapse(false);
26630     
26631         var nodeStartRange = nodeRange.cloneRange();
26632         nodeStartRange.collapse(true);
26633     
26634         var nodeEndRange = nodeRange.cloneRange();
26635         nodeEndRange.collapse(false);
26636     
26637         return rangeStartRange.compareBoundaryPoints(
26638                  Range.START_TO_START, nodeEndRange) == -1 &&
26639                rangeEndRange.compareBoundaryPoints(
26640                  Range.START_TO_START, nodeStartRange) == 1;
26641         
26642          
26643     },
26644     rangeCompareNode : function(range, node)
26645     {
26646         var nodeRange = node.ownerDocument.createRange();
26647         try {
26648             nodeRange.selectNode(node);
26649         } catch (e) {
26650             nodeRange.selectNodeContents(node);
26651         }
26652         
26653         
26654         range.collapse(true);
26655     
26656         nodeRange.collapse(true);
26657      
26658         var ss = range.compareBoundaryPoints( Range.START_TO_START, nodeRange);
26659         var ee = range.compareBoundaryPoints(  Range.END_TO_END, nodeRange);
26660          
26661         //Roo.log(node.tagName + ': ss='+ss +', ee='+ee)
26662         
26663         var nodeIsBefore   =  ss == 1;
26664         var nodeIsAfter    = ee == -1;
26665         
26666         if (nodeIsBefore && nodeIsAfter) {
26667             return 0; // outer
26668         }
26669         if (!nodeIsBefore && nodeIsAfter) {
26670             return 1; //right trailed.
26671         }
26672         
26673         if (nodeIsBefore && !nodeIsAfter) {
26674             return 2;  // left trailed.
26675         }
26676         // fully contined.
26677         return 3;
26678     },
26679  
26680     cleanWordChars : function(input) {// change the chars to hex code
26681         
26682        var swapCodes  = [ 
26683             [    8211, "&#8211;" ], 
26684             [    8212, "&#8212;" ], 
26685             [    8216,  "'" ],  
26686             [    8217, "'" ],  
26687             [    8220, '"' ],  
26688             [    8221, '"' ],  
26689             [    8226, "*" ],  
26690             [    8230, "..." ]
26691         ]; 
26692         var output = input;
26693         Roo.each(swapCodes, function(sw) { 
26694             var swapper = new RegExp("\\u" + sw[0].toString(16), "g"); // hex codes
26695             
26696             output = output.replace(swapper, sw[1]);
26697         });
26698         
26699         return output;
26700     },
26701     
26702      
26703     
26704         
26705     
26706     cleanUpChild : function (node)
26707     {
26708         
26709         new Roo.htmleditor.FilterComment({node : node});
26710         new Roo.htmleditor.FilterAttributes({
26711                 node : node,
26712                 attrib_black : this.ablack,
26713                 attrib_clean : this.aclean,
26714                 style_white : this.cwhite,
26715                 style_black : this.cblack
26716         });
26717         new Roo.htmleditor.FilterBlack({ node : node, tag : this.black});
26718         new Roo.htmleditor.FilterKeepChildren({node : node, tag : this.tag_remove} );
26719          
26720         
26721     },
26722     
26723     /**
26724      * Clean up MS wordisms...
26725      * @deprecated - use filter directly
26726      */
26727     cleanWord : function(node)
26728     {
26729         new Roo.htmleditor.FilterWord({ node : node ? node : this.doc.body });
26730         new Roo.htmleditor.FilterKeepChildren({node : node ? node : this.doc.body, tag : [ 'FONT', ':' ]} );
26731         
26732     },
26733    
26734     
26735     /**
26736
26737      * @deprecated - use filters
26738      */
26739     cleanTableWidths : function(node)
26740     {
26741         new Roo.htmleditor.FilterTableWidth({ node : node ? node : this.doc.body});
26742         
26743  
26744     },
26745     
26746      
26747         
26748     applyBlacklists : function()
26749     {
26750         var w = typeof(this.owner.white) != 'undefined' && this.owner.white ? this.owner.white  : [];
26751         var b = typeof(this.owner.black) != 'undefined' && this.owner.black ? this.owner.black :  [];
26752         
26753         this.aclean = typeof(this.owner.aclean) != 'undefined' && this.owner.aclean ? this.owner.aclean :  Roo.HtmlEditorCore.aclean;
26754         this.ablack = typeof(this.owner.ablack) != 'undefined' && this.owner.ablack ? this.owner.ablack :  Roo.HtmlEditorCore.ablack;
26755         this.tag_remove = typeof(this.owner.tag_remove) != 'undefined' && this.owner.tag_remove ? this.owner.tag_remove :  Roo.HtmlEditorCore.tag_remove;
26756         
26757         this.white = [];
26758         this.black = [];
26759         Roo.each(Roo.HtmlEditorCore.white, function(tag) {
26760             if (b.indexOf(tag) > -1) {
26761                 return;
26762             }
26763             this.white.push(tag);
26764             
26765         }, this);
26766         
26767         Roo.each(w, function(tag) {
26768             if (b.indexOf(tag) > -1) {
26769                 return;
26770             }
26771             if (this.white.indexOf(tag) > -1) {
26772                 return;
26773             }
26774             this.white.push(tag);
26775             
26776         }, this);
26777         
26778         
26779         Roo.each(Roo.HtmlEditorCore.black, function(tag) {
26780             if (w.indexOf(tag) > -1) {
26781                 return;
26782             }
26783             this.black.push(tag);
26784             
26785         }, this);
26786         
26787         Roo.each(b, function(tag) {
26788             if (w.indexOf(tag) > -1) {
26789                 return;
26790             }
26791             if (this.black.indexOf(tag) > -1) {
26792                 return;
26793             }
26794             this.black.push(tag);
26795             
26796         }, this);
26797         
26798         
26799         w = typeof(this.owner.cwhite) != 'undefined' && this.owner.cwhite ? this.owner.cwhite  : [];
26800         b = typeof(this.owner.cblack) != 'undefined' && this.owner.cblack ? this.owner.cblack :  [];
26801         
26802         this.cwhite = [];
26803         this.cblack = [];
26804         Roo.each(Roo.HtmlEditorCore.cwhite, function(tag) {
26805             if (b.indexOf(tag) > -1) {
26806                 return;
26807             }
26808             this.cwhite.push(tag);
26809             
26810         }, this);
26811         
26812         Roo.each(w, function(tag) {
26813             if (b.indexOf(tag) > -1) {
26814                 return;
26815             }
26816             if (this.cwhite.indexOf(tag) > -1) {
26817                 return;
26818             }
26819             this.cwhite.push(tag);
26820             
26821         }, this);
26822         
26823         
26824         Roo.each(Roo.HtmlEditorCore.cblack, function(tag) {
26825             if (w.indexOf(tag) > -1) {
26826                 return;
26827             }
26828             this.cblack.push(tag);
26829             
26830         }, this);
26831         
26832         Roo.each(b, function(tag) {
26833             if (w.indexOf(tag) > -1) {
26834                 return;
26835             }
26836             if (this.cblack.indexOf(tag) > -1) {
26837                 return;
26838             }
26839             this.cblack.push(tag);
26840             
26841         }, this);
26842     },
26843     
26844     setStylesheets : function(stylesheets)
26845     {
26846         if(typeof(stylesheets) == 'string'){
26847             Roo.get(this.iframe.contentDocument.head).createChild({
26848                 tag : 'link',
26849                 rel : 'stylesheet',
26850                 type : 'text/css',
26851                 href : stylesheets
26852             });
26853             
26854             return;
26855         }
26856         var _this = this;
26857      
26858         Roo.each(stylesheets, function(s) {
26859             if(!s.length){
26860                 return;
26861             }
26862             
26863             Roo.get(_this.iframe.contentDocument.head).createChild({
26864                 tag : 'link',
26865                 rel : 'stylesheet',
26866                 type : 'text/css',
26867                 href : s
26868             });
26869         });
26870
26871         
26872     },
26873     
26874     
26875     updateLanguage : function()
26876     {
26877         if (!this.iframe || !this.iframe.contentDocument) {
26878             return;
26879         }
26880         Roo.get(this.iframe.contentDocument.body).attr("lang", this.language);
26881     },
26882     
26883     
26884     removeStylesheets : function()
26885     {
26886         var _this = this;
26887         
26888         Roo.each(Roo.get(_this.iframe.contentDocument.head).select('link[rel=stylesheet]', true).elements, function(s){
26889             s.remove();
26890         });
26891     },
26892     
26893     setStyle : function(style)
26894     {
26895         Roo.get(this.iframe.contentDocument.head).createChild({
26896             tag : 'style',
26897             type : 'text/css',
26898             html : style
26899         });
26900
26901         return;
26902     }
26903     
26904     // hide stuff that is not compatible
26905     /**
26906      * @event blur
26907      * @hide
26908      */
26909     /**
26910      * @event change
26911      * @hide
26912      */
26913     /**
26914      * @event focus
26915      * @hide
26916      */
26917     /**
26918      * @event specialkey
26919      * @hide
26920      */
26921     /**
26922      * @cfg {String} fieldClass @hide
26923      */
26924     /**
26925      * @cfg {String} focusClass @hide
26926      */
26927     /**
26928      * @cfg {String} autoCreate @hide
26929      */
26930     /**
26931      * @cfg {String} inputType @hide
26932      */
26933     /**
26934      * @cfg {String} invalidClass @hide
26935      */
26936     /**
26937      * @cfg {String} invalidText @hide
26938      */
26939     /**
26940      * @cfg {String} msgFx @hide
26941      */
26942     /**
26943      * @cfg {String} validateOnBlur @hide
26944      */
26945 });
26946
26947 Roo.HtmlEditorCore.white = [
26948         'AREA', 'BR', 'IMG', 'INPUT', 'HR', 'WBR',
26949         
26950        'ADDRESS', 'BLOCKQUOTE', 'CENTER', 'DD',      'DIR',       'DIV', 
26951        'DL',      'DT',         'H1',     'H2',      'H3',        'H4', 
26952        'H5',      'H6',         'HR',     'ISINDEX', 'LISTING',   'MARQUEE', 
26953        'MENU',    'MULTICOL',   'OL',     'P',       'PLAINTEXT', 'PRE', 
26954        'TABLE',   'UL',         'XMP', 
26955        
26956        'CAPTION', 'COL', 'COLGROUP', 'TBODY', 'TD', 'TFOOT', 'TH', 
26957       'THEAD',   'TR', 
26958      
26959       'DIR', 'MENU', 'OL', 'UL', 'DL',
26960        
26961       'EMBED',  'OBJECT'
26962 ];
26963
26964
26965 Roo.HtmlEditorCore.black = [
26966     //    'embed',  'object', // enable - backend responsiblity to clean thiese
26967         'APPLET', // 
26968         'BASE',   'BASEFONT', 'BGSOUND', 'BLINK',  'BODY', 
26969         'FRAME',  'FRAMESET', 'HEAD',    'HTML',   'ILAYER', 
26970         'IFRAME', 'LAYER',  'LINK',     'META',    'OBJECT',   
26971         'SCRIPT', 'STYLE' ,'TITLE',  'XML',
26972         //'FONT' // CLEAN LATER..
26973         'COLGROUP', 'COL'   // messy tables.
26974         
26975         
26976 ];
26977 Roo.HtmlEditorCore.clean = [ // ?? needed???
26978      'SCRIPT', 'STYLE', 'TITLE', 'XML'
26979 ];
26980 Roo.HtmlEditorCore.tag_remove = [
26981     'FONT', 'TBODY'  
26982 ];
26983 // attributes..
26984
26985 Roo.HtmlEditorCore.ablack = [
26986     'on'
26987 ];
26988     
26989 Roo.HtmlEditorCore.aclean = [ 
26990     'action', 'background', 'codebase', 'dynsrc', 'href', 'lowsrc' 
26991 ];
26992
26993 // protocols..
26994 Roo.HtmlEditorCore.pwhite= [
26995         'http',  'https',  'mailto'
26996 ];
26997
26998 // white listed style attributes.
26999 Roo.HtmlEditorCore.cwhite= [
27000       //  'text-align', /// default is to allow most things..
27001       
27002          
27003 //        'font-size'//??
27004 ];
27005
27006 // black listed style attributes.
27007 Roo.HtmlEditorCore.cblack= [
27008       //  'font-size' -- this can be set by the project 
27009 ];
27010
27011
27012
27013
27014     //<script type="text/javascript">
27015
27016 /*
27017  * Ext JS Library 1.1.1
27018  * Copyright(c) 2006-2007, Ext JS, LLC.
27019  * Licence LGPL
27020  * 
27021  */
27022  
27023  
27024 Roo.form.HtmlEditor = function(config){
27025     
27026     
27027     
27028     Roo.form.HtmlEditor.superclass.constructor.call(this, config);
27029     
27030     if (!this.toolbars) {
27031         this.toolbars = [];
27032     }
27033     this.editorcore = new Roo.HtmlEditorCore(Roo.apply({ owner : this} , config));
27034     
27035     
27036 };
27037
27038 /**
27039  * @class Roo.form.HtmlEditor
27040  * @extends Roo.form.Field
27041  * Provides a lightweight HTML Editor component.
27042  *
27043  * This has been tested on Fireforx / Chrome.. IE may not be so great..
27044  * 
27045  * <br><br><b>Note: The focus/blur and validation marking functionality inherited from Ext.form.Field is NOT
27046  * supported by this editor.</b><br/><br/>
27047  * An Editor is a sensitive component that can't be used in all spots standard fields can be used. Putting an Editor within
27048  * any element that has display set to 'none' can cause problems in Safari and Firefox.<br/><br/>
27049  */
27050 Roo.extend(Roo.form.HtmlEditor, Roo.form.Field, {
27051     /**
27052      * @cfg {Boolean} clearUp
27053      */
27054     clearUp : true,
27055       /**
27056      * @cfg {Array} toolbars Array of toolbars. - defaults to just the Standard one
27057      */
27058     toolbars : false,
27059    
27060      /**
27061      * @cfg {String} resizable  's' or 'se' or 'e' - wrapps the element in a
27062      *                        Roo.resizable.
27063      */
27064     resizable : false,
27065      /**
27066      * @cfg {Number} height (in pixels)
27067      */   
27068     height: 300,
27069    /**
27070      * @cfg {Number} width (in pixels)
27071      */   
27072     width: 500,
27073     
27074     /**
27075      * @cfg {Array} stylesheets url of stylesheets. set to [] to disable stylesheets - this is usally a good idea  rootURL + '/roojs1/css/undoreset.css',   .
27076      * 
27077      */
27078     stylesheets: false,
27079     
27080     
27081      /**
27082      * @cfg {Array} blacklist of css styles style attributes (blacklist overrides whitelist)
27083      * 
27084      */
27085     cblack: false,
27086     /**
27087      * @cfg {Array} whitelist of css styles style attributes (blacklist overrides whitelist)
27088      * 
27089      */
27090     cwhite: false,
27091     
27092      /**
27093      * @cfg {Array} blacklist of html tags - in addition to standard blacklist.
27094      * 
27095      */
27096     black: false,
27097     /**
27098      * @cfg {Array} whitelist of html tags - in addition to statndard whitelist
27099      * 
27100      */
27101     white: false,
27102     /**
27103      * @cfg {boolean} allowComments - default false - allow comments in HTML source - by default they are stripped - if you are editing email you may need this.
27104      */
27105     allowComments: false,
27106     /**
27107      * @cfg {boolean} enableBlocks - default true - if the block editor (table and figure should be enabled)
27108      */
27109     enableBlocks : true,
27110     
27111     /**
27112      * @cfg {boolean} autoClean - default true - loading and saving will remove quite a bit of formating,
27113      *         if you are doing an email editor, this probably needs disabling, it's designed
27114      */
27115     autoClean: true,
27116     /**
27117      * @cfg {string} bodyCls default '' default classes to add to body of editable area - usually undoreset is a good start..
27118      */
27119     bodyCls : '',
27120     /**
27121      * @cfg {String} language default en - language of text (usefull for rtl languages)
27122      * 
27123      */
27124     language: 'en',
27125     
27126      
27127     // id of frame..
27128     frameId: false,
27129     
27130     // private properties
27131     validationEvent : false,
27132     deferHeight: true,
27133     initialized : false,
27134     activated : false,
27135     
27136     onFocus : Roo.emptyFn,
27137     iframePad:3,
27138     hideMode:'offsets',
27139     
27140     actionMode : 'container', // defaults to hiding it...
27141     
27142     defaultAutoCreate : { // modified by initCompnoent..
27143         tag: "textarea",
27144         style:"width:500px;height:300px;",
27145         autocomplete: "new-password"
27146     },
27147
27148     // private
27149     initComponent : function(){
27150         this.addEvents({
27151             /**
27152              * @event initialize
27153              * Fires when the editor is fully initialized (including the iframe)
27154              * @param {HtmlEditor} this
27155              */
27156             initialize: true,
27157             /**
27158              * @event activate
27159              * Fires when the editor is first receives the focus. Any insertion must wait
27160              * until after this event.
27161              * @param {HtmlEditor} this
27162              */
27163             activate: true,
27164              /**
27165              * @event beforesync
27166              * Fires before the textarea is updated with content from the editor iframe. Return false
27167              * to cancel the sync.
27168              * @param {HtmlEditor} this
27169              * @param {String} html
27170              */
27171             beforesync: true,
27172              /**
27173              * @event beforepush
27174              * Fires before the iframe editor is updated with content from the textarea. Return false
27175              * to cancel the push.
27176              * @param {HtmlEditor} this
27177              * @param {String} html
27178              */
27179             beforepush: true,
27180              /**
27181              * @event sync
27182              * Fires when the textarea is updated with content from the editor iframe.
27183              * @param {HtmlEditor} this
27184              * @param {String} html
27185              */
27186             sync: true,
27187              /**
27188              * @event push
27189              * Fires when the iframe editor is updated with content from the textarea.
27190              * @param {HtmlEditor} this
27191              * @param {String} html
27192              */
27193             push: true,
27194              /**
27195              * @event editmodechange
27196              * Fires when the editor switches edit modes
27197              * @param {HtmlEditor} this
27198              * @param {Boolean} sourceEdit True if source edit, false if standard editing.
27199              */
27200             editmodechange: true,
27201             /**
27202              * @event editorevent
27203              * Fires when on any editor (mouse up/down cursor movement etc.) - used for toolbar hooks.
27204              * @param {HtmlEditor} this
27205              */
27206             editorevent: true,
27207             /**
27208              * @event firstfocus
27209              * Fires when on first focus - needed by toolbars..
27210              * @param {HtmlEditor} this
27211              */
27212             firstfocus: true,
27213             /**
27214              * @event autosave
27215              * Auto save the htmlEditor value as a file into Events
27216              * @param {HtmlEditor} this
27217              */
27218             autosave: true,
27219             /**
27220              * @event savedpreview
27221              * preview the saved version of htmlEditor
27222              * @param {HtmlEditor} this
27223              */
27224             savedpreview: true,
27225             
27226             /**
27227             * @event stylesheetsclick
27228             * Fires when press the Sytlesheets button
27229             * @param {Roo.HtmlEditorCore} this
27230             */
27231             stylesheetsclick: true,
27232             /**
27233             * @event paste
27234             * Fires when press user pastes into the editor
27235             * @param {Roo.HtmlEditorCore} this
27236             */
27237             paste: true 
27238         });
27239         this.defaultAutoCreate =  {
27240             tag: "textarea",
27241             style:'width: ' + this.width + 'px;height: ' + this.height + 'px;',
27242             autocomplete: "new-password"
27243         };
27244     },
27245
27246     /**
27247      * Protected method that will not generally be called directly. It
27248      * is called when the editor creates its toolbar. Override this method if you need to
27249      * add custom toolbar buttons.
27250      * @param {HtmlEditor} editor
27251      */
27252     createToolbar : function(editor){
27253         Roo.log("create toolbars");
27254         if (!editor.toolbars || !editor.toolbars.length) {
27255             editor.toolbars = [ new Roo.form.HtmlEditor.ToolbarStandard() ]; // can be empty?
27256         }
27257         
27258         for (var i =0 ; i < editor.toolbars.length;i++) {
27259             editor.toolbars[i] = Roo.factory(
27260                     typeof(editor.toolbars[i]) == 'string' ?
27261                         { xtype: editor.toolbars[i]} : editor.toolbars[i],
27262                 Roo.form.HtmlEditor);
27263             editor.toolbars[i].init(editor);
27264         }
27265          
27266         
27267     },
27268     /**
27269      * get the Context selected node
27270      * @returns {DomElement|boolean} selected node if active or false if none
27271      * 
27272      */
27273     getSelectedNode : function()
27274     {
27275         if (this.toolbars.length < 2 || !this.toolbars[1].tb) {
27276             return false;
27277         }
27278         return this.toolbars[1].tb.selectedNode;
27279     
27280     },
27281     // private
27282     onRender : function(ct, position)
27283     {
27284         var _t = this;
27285         Roo.form.HtmlEditor.superclass.onRender.call(this, ct, position);
27286         
27287         this.wrap = this.el.wrap({
27288             cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
27289         });
27290         
27291         this.editorcore.onRender(ct, position);
27292          
27293         if (this.resizable) {
27294             this.resizeEl = new Roo.Resizable(this.wrap, {
27295                 pinned : true,
27296                 wrap: true,
27297                 dynamic : true,
27298                 minHeight : this.height,
27299                 height: this.height,
27300                 handles : this.resizable,
27301                 width: this.width,
27302                 listeners : {
27303                     resize : function(r, w, h) {
27304                         _t.onResize(w,h); // -something
27305                     }
27306                 }
27307             });
27308             
27309         }
27310         this.createToolbar(this);
27311        
27312         
27313         if(!this.width){
27314             this.setSize(this.wrap.getSize());
27315         }
27316         if (this.resizeEl) {
27317             this.resizeEl.resizeTo.defer(100, this.resizeEl,[ this.width,this.height ] );
27318             // should trigger onReize..
27319         }
27320         
27321         this.keyNav = new Roo.KeyNav(this.el, {
27322             
27323             "tab" : function(e){
27324                 e.preventDefault();
27325                 
27326                 var value = this.getValue();
27327                 
27328                 var start = this.el.dom.selectionStart;
27329                 var end = this.el.dom.selectionEnd;
27330                 
27331                 if(!e.shiftKey){
27332                     
27333                     this.setValue(value.substring(0, start) + "\t" + value.substring(end));
27334                     this.el.dom.setSelectionRange(end + 1, end + 1);
27335                     return;
27336                 }
27337                 
27338                 var f = value.substring(0, start).split("\t");
27339                 
27340                 if(f.pop().length != 0){
27341                     return;
27342                 }
27343                 
27344                 this.setValue(f.join("\t") + value.substring(end));
27345                 this.el.dom.setSelectionRange(start - 1, start - 1);
27346                 
27347             },
27348             
27349             "home" : function(e){
27350                 e.preventDefault();
27351                 
27352                 var curr = this.el.dom.selectionStart;
27353                 var lines = this.getValue().split("\n");
27354                 
27355                 if(!lines.length){
27356                     return;
27357                 }
27358                 
27359                 if(e.ctrlKey){
27360                     this.el.dom.setSelectionRange(0, 0);
27361                     return;
27362                 }
27363                 
27364                 var pos = 0;
27365                 
27366                 for (var i = 0; i < lines.length;i++) {
27367                     pos += lines[i].length;
27368                     
27369                     if(i != 0){
27370                         pos += 1;
27371                     }
27372                     
27373                     if(pos < curr){
27374                         continue;
27375                     }
27376                     
27377                     pos -= lines[i].length;
27378                     
27379                     break;
27380                 }
27381                 
27382                 if(!e.shiftKey){
27383                     this.el.dom.setSelectionRange(pos, pos);
27384                     return;
27385                 }
27386                 
27387                 this.el.dom.selectionStart = pos;
27388                 this.el.dom.selectionEnd = curr;
27389             },
27390             
27391             "end" : function(e){
27392                 e.preventDefault();
27393                 
27394                 var curr = this.el.dom.selectionStart;
27395                 var lines = this.getValue().split("\n");
27396                 
27397                 if(!lines.length){
27398                     return;
27399                 }
27400                 
27401                 if(e.ctrlKey){
27402                     this.el.dom.setSelectionRange(this.getValue().length, this.getValue().length);
27403                     return;
27404                 }
27405                 
27406                 var pos = 0;
27407                 
27408                 for (var i = 0; i < lines.length;i++) {
27409                     
27410                     pos += lines[i].length;
27411                     
27412                     if(i != 0){
27413                         pos += 1;
27414                     }
27415                     
27416                     if(pos < curr){
27417                         continue;
27418                     }
27419                     
27420                     break;
27421                 }
27422                 
27423                 if(!e.shiftKey){
27424                     this.el.dom.setSelectionRange(pos, pos);
27425                     return;
27426                 }
27427                 
27428                 this.el.dom.selectionStart = curr;
27429                 this.el.dom.selectionEnd = pos;
27430             },
27431
27432             scope : this,
27433
27434             doRelay : function(foo, bar, hname){
27435                 return Roo.KeyNav.prototype.doRelay.apply(this, arguments);
27436             },
27437
27438             forceKeyDown: true
27439         });
27440         
27441 //        if(this.autosave && this.w){
27442 //            this.autoSaveFn = setInterval(this.autosave, 1000);
27443 //        }
27444     },
27445
27446     // private
27447     onResize : function(w, h)
27448     {
27449         Roo.form.HtmlEditor.superclass.onResize.apply(this, arguments);
27450         var ew = false;
27451         var eh = false;
27452         
27453         if(this.el ){
27454             if(typeof w == 'number'){
27455                 var aw = w - this.wrap.getFrameWidth('lr');
27456                 this.el.setWidth(this.adjustWidth('textarea', aw));
27457                 ew = aw;
27458             }
27459             if(typeof h == 'number'){
27460                 var tbh = 0;
27461                 for (var i =0; i < this.toolbars.length;i++) {
27462                     // fixme - ask toolbars for heights?
27463                     tbh += this.toolbars[i].tb.el.getHeight();
27464                     if (this.toolbars[i].footer) {
27465                         tbh += this.toolbars[i].footer.el.getHeight();
27466                     }
27467                 }
27468                 
27469                 
27470                 
27471                 
27472                 var ah = h - this.wrap.getFrameWidth('tb') - tbh;// this.tb.el.getHeight();
27473                 ah -= 5; // knock a few pixes off for look..
27474 //                Roo.log(ah);
27475                 this.el.setHeight(this.adjustWidth('textarea', ah));
27476                 var eh = ah;
27477             }
27478         }
27479         Roo.log('onResize:' + [w,h,ew,eh].join(',') );
27480         this.editorcore.onResize(ew,eh);
27481         
27482     },
27483
27484     /**
27485      * Toggles the editor between standard and source edit mode.
27486      * @param {Boolean} sourceEdit (optional) True for source edit, false for standard
27487      */
27488     toggleSourceEdit : function(sourceEditMode)
27489     {
27490         this.editorcore.toggleSourceEdit(sourceEditMode);
27491         
27492         if(this.editorcore.sourceEditMode){
27493             Roo.log('editor - showing textarea');
27494             
27495 //            Roo.log('in');
27496 //            Roo.log(this.syncValue());
27497             this.editorcore.syncValue();
27498             this.el.removeClass('x-hidden');
27499             this.el.dom.removeAttribute('tabIndex');
27500             this.el.focus();
27501             this.el.dom.scrollTop = 0;
27502             
27503             
27504             for (var i = 0; i < this.toolbars.length; i++) {
27505                 if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
27506                     this.toolbars[i].tb.hide();
27507                     this.toolbars[i].footer.hide();
27508                 }
27509             }
27510             
27511         }else{
27512             Roo.log('editor - hiding textarea');
27513 //            Roo.log('out')
27514 //            Roo.log(this.pushValue()); 
27515             this.editorcore.pushValue();
27516             
27517             this.el.addClass('x-hidden');
27518             this.el.dom.setAttribute('tabIndex', -1);
27519             
27520             for (var i = 0; i < this.toolbars.length; i++) {
27521                 if(this.toolbars[i] instanceof Roo.form.HtmlEditor.ToolbarContext){
27522                     this.toolbars[i].tb.show();
27523                     this.toolbars[i].footer.show();
27524                 }
27525             }
27526             
27527             //this.deferFocus();
27528         }
27529         
27530         this.setSize(this.wrap.getSize());
27531         this.onResize(this.wrap.getSize().width, this.wrap.getSize().height);
27532         
27533         this.fireEvent('editmodechange', this, this.editorcore.sourceEditMode);
27534     },
27535  
27536     // private (for BoxComponent)
27537     adjustSize : Roo.BoxComponent.prototype.adjustSize,
27538
27539     // private (for BoxComponent)
27540     getResizeEl : function(){
27541         return this.wrap;
27542     },
27543
27544     // private (for BoxComponent)
27545     getPositionEl : function(){
27546         return this.wrap;
27547     },
27548
27549     // private
27550     initEvents : function(){
27551         this.originalValue = this.getValue();
27552     },
27553
27554     /**
27555      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
27556      * @method
27557      */
27558     markInvalid : Roo.emptyFn,
27559     /**
27560      * Overridden and disabled. The editor element does not support standard valid/invalid marking. @hide
27561      * @method
27562      */
27563     clearInvalid : Roo.emptyFn,
27564
27565     setValue : function(v){
27566         Roo.form.HtmlEditor.superclass.setValue.call(this, v);
27567         this.editorcore.pushValue();
27568     },
27569
27570     /**
27571      * update the language in the body - really done by core
27572      * @param {String} language - eg. en / ar / zh-CN etc..
27573      */
27574     updateLanguage : function(lang)
27575     {
27576         this.language = lang;
27577         this.editorcore.language = lang;
27578         this.editorcore.updateLanguage();
27579      
27580     },
27581     // private
27582     deferFocus : function(){
27583         this.focus.defer(10, this);
27584     },
27585
27586     // doc'ed in Field
27587     focus : function(){
27588         this.editorcore.focus();
27589         
27590     },
27591       
27592
27593     // private
27594     onDestroy : function(){
27595         
27596         
27597         
27598         if(this.rendered){
27599             
27600             for (var i =0; i < this.toolbars.length;i++) {
27601                 // fixme - ask toolbars for heights?
27602                 this.toolbars[i].onDestroy();
27603             }
27604             
27605             this.wrap.dom.innerHTML = '';
27606             this.wrap.remove();
27607         }
27608     },
27609
27610     // private
27611     onFirstFocus : function(){
27612         //Roo.log("onFirstFocus");
27613         this.editorcore.onFirstFocus();
27614          for (var i =0; i < this.toolbars.length;i++) {
27615             this.toolbars[i].onFirstFocus();
27616         }
27617         
27618     },
27619     
27620     // private
27621     syncValue : function()
27622     {
27623         this.editorcore.syncValue();
27624     },
27625     
27626     pushValue : function()
27627     {
27628         this.editorcore.pushValue();
27629     },
27630     
27631     setStylesheets : function(stylesheets)
27632     {
27633         this.editorcore.setStylesheets(stylesheets);
27634     },
27635     
27636     removeStylesheets : function()
27637     {
27638         this.editorcore.removeStylesheets();
27639     }
27640      
27641     
27642     // hide stuff that is not compatible
27643     /**
27644      * @event blur
27645      * @hide
27646      */
27647     /**
27648      * @event change
27649      * @hide
27650      */
27651     /**
27652      * @event focus
27653      * @hide
27654      */
27655     /**
27656      * @event specialkey
27657      * @hide
27658      */
27659     /**
27660      * @cfg {String} fieldClass @hide
27661      */
27662     /**
27663      * @cfg {String} focusClass @hide
27664      */
27665     /**
27666      * @cfg {String} autoCreate @hide
27667      */
27668     /**
27669      * @cfg {String} inputType @hide
27670      */
27671     /**
27672      * @cfg {String} invalidClass @hide
27673      */
27674     /**
27675      * @cfg {String} invalidText @hide
27676      */
27677     /**
27678      * @cfg {String} msgFx @hide
27679      */
27680     /**
27681      * @cfg {String} validateOnBlur @hide
27682      */
27683 });
27684  
27685     /*
27686  * Based on
27687  * Ext JS Library 1.1.1
27688  * Copyright(c) 2006-2007, Ext JS, LLC.
27689  *  
27690  
27691  */
27692
27693 /**
27694  * @class Roo.form.HtmlEditor.ToolbarStandard
27695  * Basic Toolbar
27696
27697  * Usage:
27698  *
27699  new Roo.form.HtmlEditor({
27700     ....
27701     toolbars : [
27702         new Roo.form.HtmlEditorToolbar1({
27703             disable : { fonts: 1 , format: 1, ..., ... , ...],
27704             btns : [ .... ]
27705         })
27706     }
27707      
27708  * 
27709  * @cfg {Object} disable List of elements to disable..
27710  * @cfg {Roo.Toolbar.Item|Roo.Toolbar.Button|Roo.Toolbar.SplitButton|Roo.form.Field} btns[] List of additional buttons.
27711  * 
27712  * 
27713  * NEEDS Extra CSS? 
27714  * .x-html-editor-tb .x-edit-none .x-btn-text { background: none; }
27715  */
27716  
27717 Roo.form.HtmlEditor.ToolbarStandard = function(config)
27718 {
27719     
27720     Roo.apply(this, config);
27721     
27722     // default disabled, based on 'good practice'..
27723     this.disable = this.disable || {};
27724     Roo.applyIf(this.disable, {
27725         fontSize : true,
27726         colors : true,
27727         specialElements : true
27728     });
27729     
27730     
27731     //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
27732     // dont call parent... till later.
27733 }
27734
27735 Roo.form.HtmlEditor.ToolbarStandard.prototype = {
27736     
27737     tb: false,
27738     
27739     rendered: false,
27740     
27741     editor : false,
27742     editorcore : false,
27743     /**
27744      * @cfg {Object} disable  List of toolbar elements to disable
27745          
27746      */
27747     disable : false,
27748     
27749     
27750      /**
27751      * @cfg {String} createLinkText The default text for the create link prompt
27752      */
27753     createLinkText : 'Please enter the URL for the link:',
27754     /**
27755      * @cfg {String} defaultLinkValue The default value for the create link prompt (defaults to http:/ /)
27756      */
27757     defaultLinkValue : 'http:/'+'/',
27758    
27759     
27760       /**
27761      * @cfg {Array} fontFamilies An array of available font families
27762      */
27763     fontFamilies : [
27764         'Arial',
27765         'Courier New',
27766         'Tahoma',
27767         'Times New Roman',
27768         'Verdana'
27769     ],
27770     
27771     specialChars : [
27772            "&#169;",
27773           "&#174;",     
27774           "&#8482;",    
27775           "&#163;" ,    
27776          // "&#8212;",    
27777           "&#8230;",    
27778           "&#247;" ,    
27779         //  "&#225;" ,     ?? a acute?
27780            "&#8364;"    , //Euro
27781        //   "&#8220;"    ,
27782         //  "&#8221;"    ,
27783         //  "&#8226;"    ,
27784           "&#176;"  //   , // degrees
27785
27786          // "&#233;"     , // e ecute
27787          // "&#250;"     , // u ecute?
27788     ],
27789     
27790     specialElements : [
27791         {
27792             text: "Insert Table",
27793             xtype: 'MenuItem',
27794             xns : Roo.Menu,
27795             ihtml :  '<table><tr><td>Cell</td></tr></table>' 
27796                 
27797         },
27798         {    
27799             text: "Insert Image",
27800             xtype: 'MenuItem',
27801             xns : Roo.Menu,
27802             ihtml : '<img src="about:blank"/>'
27803             
27804         }
27805         
27806          
27807     ],
27808     
27809     
27810     inputElements : [ 
27811             "form", "input:text", "input:hidden", "input:checkbox", "input:radio", "input:password", 
27812             "input:submit", "input:button", "select", "textarea", "label" ],
27813     formats : [
27814         ["p"] ,  
27815         ["h1"],["h2"],["h3"],["h4"],["h5"],["h6"], 
27816         ["pre"],[ "code"], 
27817         ["abbr"],[ "acronym"],[ "address"],[ "cite"],[ "samp"],[ "var"],
27818         ['div'],['span'],
27819         ['sup'],['sub']
27820     ],
27821     
27822     cleanStyles : [
27823         "font-size"
27824     ],
27825      /**
27826      * @cfg {String} defaultFont default font to use.
27827      */
27828     defaultFont: 'tahoma',
27829    
27830     fontSelect : false,
27831     
27832     
27833     formatCombo : false,
27834     
27835     init : function(editor)
27836     {
27837         this.editor = editor;
27838         this.editorcore = editor.editorcore ? editor.editorcore : editor;
27839         var editorcore = this.editorcore;
27840         
27841         var _t = this;
27842         
27843         var fid = editorcore.frameId;
27844         var etb = this;
27845         function btn(id, toggle, handler){
27846             var xid = fid + '-'+ id ;
27847             return {
27848                 id : xid,
27849                 cmd : id,
27850                 cls : 'x-btn-icon x-edit-'+id,
27851                 enableToggle:toggle !== false,
27852                 scope: _t, // was editor...
27853                 handler:handler||_t.relayBtnCmd,
27854                 clickEvent:'mousedown',
27855                 tooltip: etb.buttonTips[id] || undefined, ///tips ???
27856                 tabIndex:-1
27857             };
27858         }
27859         
27860         
27861         
27862         var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
27863         this.tb = tb;
27864          // stop form submits
27865         tb.el.on('click', function(e){
27866             e.preventDefault(); // what does this do?
27867         });
27868
27869         if(!this.disable.font) { // && !Roo.isSafari){
27870             /* why no safari for fonts 
27871             editor.fontSelect = tb.el.createChild({
27872                 tag:'select',
27873                 tabIndex: -1,
27874                 cls:'x-font-select',
27875                 html: this.createFontOptions()
27876             });
27877             
27878             editor.fontSelect.on('change', function(){
27879                 var font = editor.fontSelect.dom.value;
27880                 editor.relayCmd('fontname', font);
27881                 editor.deferFocus();
27882             }, editor);
27883             
27884             tb.add(
27885                 editor.fontSelect.dom,
27886                 '-'
27887             );
27888             */
27889             
27890         };
27891         if(!this.disable.formats){
27892             this.formatCombo = new Roo.form.ComboBox({
27893                 store: new Roo.data.SimpleStore({
27894                     id : 'tag',
27895                     fields: ['tag'],
27896                     data : this.formats // from states.js
27897                 }),
27898                 blockFocus : true,
27899                 name : '',
27900                 //autoCreate : {tag: "div",  size: "20"},
27901                 displayField:'tag',
27902                 typeAhead: false,
27903                 mode: 'local',
27904                 editable : false,
27905                 triggerAction: 'all',
27906                 emptyText:'Add tag',
27907                 selectOnFocus:true,
27908                 width:135,
27909                 listeners : {
27910                     'select': function(c, r, i) {
27911                         editorcore.insertTag(r.get('tag'));
27912                         editor.focus();
27913                     }
27914                 }
27915
27916             });
27917             tb.addField(this.formatCombo);
27918             
27919         }
27920         
27921         if(!this.disable.format){
27922             tb.add(
27923                 btn('bold'),
27924                 btn('italic'),
27925                 btn('underline'),
27926                 btn('strikethrough')
27927             );
27928         };
27929         if(!this.disable.fontSize){
27930             tb.add(
27931                 '-',
27932                 
27933                 
27934                 btn('increasefontsize', false, editorcore.adjustFont),
27935                 btn('decreasefontsize', false, editorcore.adjustFont)
27936             );
27937         };
27938         
27939         
27940         if(!this.disable.colors){
27941             tb.add(
27942                 '-', {
27943                     id:editorcore.frameId +'-forecolor',
27944                     cls:'x-btn-icon x-edit-forecolor',
27945                     clickEvent:'mousedown',
27946                     tooltip: this.buttonTips['forecolor'] || undefined,
27947                     tabIndex:-1,
27948                     menu : new Roo.menu.ColorMenu({
27949                         allowReselect: true,
27950                         focus: Roo.emptyFn,
27951                         value:'000000',
27952                         plain:true,
27953                         selectHandler: function(cp, color){
27954                             editorcore.execCmd('forecolor', Roo.isSafari || Roo.isIE ? '#'+color : color);
27955                             editor.deferFocus();
27956                         },
27957                         scope: editorcore,
27958                         clickEvent:'mousedown'
27959                     })
27960                 }, {
27961                     id:editorcore.frameId +'backcolor',
27962                     cls:'x-btn-icon x-edit-backcolor',
27963                     clickEvent:'mousedown',
27964                     tooltip: this.buttonTips['backcolor'] || undefined,
27965                     tabIndex:-1,
27966                     menu : new Roo.menu.ColorMenu({
27967                         focus: Roo.emptyFn,
27968                         value:'FFFFFF',
27969                         plain:true,
27970                         allowReselect: true,
27971                         selectHandler: function(cp, color){
27972                             if(Roo.isGecko){
27973                                 editorcore.execCmd('useCSS', false);
27974                                 editorcore.execCmd('hilitecolor', color);
27975                                 editorcore.execCmd('useCSS', true);
27976                                 editor.deferFocus();
27977                             }else{
27978                                 editorcore.execCmd(Roo.isOpera ? 'hilitecolor' : 'backcolor', 
27979                                     Roo.isSafari || Roo.isIE ? '#'+color : color);
27980                                 editor.deferFocus();
27981                             }
27982                         },
27983                         scope:editorcore,
27984                         clickEvent:'mousedown'
27985                     })
27986                 }
27987             );
27988         };
27989         // now add all the items...
27990         
27991
27992         if(!this.disable.alignments){
27993             tb.add(
27994                 '-',
27995                 btn('justifyleft'),
27996                 btn('justifycenter'),
27997                 btn('justifyright')
27998             );
27999         };
28000
28001         //if(!Roo.isSafari){
28002             if(!this.disable.links){
28003                 tb.add(
28004                     '-',
28005                     btn('createlink', false, this.createLink)    /// MOVE TO HERE?!!?!?!?!
28006                 );
28007             };
28008
28009             if(!this.disable.lists){
28010                 tb.add(
28011                     '-',
28012                     btn('insertorderedlist'),
28013                     btn('insertunorderedlist')
28014                 );
28015             }
28016             if(!this.disable.sourceEdit){
28017                 tb.add(
28018                     '-',
28019                     btn('sourceedit', true, function(btn){
28020                         this.toggleSourceEdit(btn.pressed);
28021                     })
28022                 );
28023             }
28024         //}
28025         
28026         var smenu = { };
28027         // special menu.. - needs to be tidied up..
28028         if (!this.disable.special) {
28029             smenu = {
28030                 text: "&#169;",
28031                 cls: 'x-edit-none',
28032                 
28033                 menu : {
28034                     items : []
28035                 }
28036             };
28037             for (var i =0; i < this.specialChars.length; i++) {
28038                 smenu.menu.items.push({
28039                     
28040                     html: this.specialChars[i],
28041                     handler: function(a,b) {
28042                         editorcore.insertAtCursor(String.fromCharCode(a.html.replace('&#','').replace(';', '')));
28043                         //editor.insertAtCursor(a.html);
28044                         
28045                     },
28046                     tabIndex:-1
28047                 });
28048             }
28049             
28050             
28051             tb.add(smenu);
28052             
28053             
28054         }
28055         
28056         var cmenu = { };
28057         if (!this.disable.cleanStyles) {
28058             cmenu = {
28059                 cls: 'x-btn-icon x-btn-clear',
28060                 
28061                 menu : {
28062                     items : []
28063                 }
28064             };
28065             for (var i =0; i < this.cleanStyles.length; i++) {
28066                 cmenu.menu.items.push({
28067                     actiontype : this.cleanStyles[i],
28068                     html: 'Remove ' + this.cleanStyles[i],
28069                     handler: function(a,b) {
28070 //                        Roo.log(a);
28071 //                        Roo.log(b);
28072                         var c = Roo.get(editorcore.doc.body);
28073                         c.select('[style]').each(function(s) {
28074                             s.dom.style.removeProperty(a.actiontype);
28075                         });
28076                         editorcore.syncValue();
28077                     },
28078                     tabIndex:-1
28079                 });
28080             }
28081             cmenu.menu.items.push({
28082                 actiontype : 'tablewidths',
28083                 html: 'Remove Table Widths',
28084                 handler: function(a,b) {
28085                     editorcore.cleanTableWidths();
28086                     editorcore.syncValue();
28087                 },
28088                 tabIndex:-1
28089             });
28090             cmenu.menu.items.push({
28091                 actiontype : 'word',
28092                 html: 'Remove MS Word Formating',
28093                 handler: function(a,b) {
28094                     editorcore.cleanWord();
28095                     editorcore.syncValue();
28096                 },
28097                 tabIndex:-1
28098             });
28099             
28100             cmenu.menu.items.push({
28101                 actiontype : 'all',
28102                 html: 'Remove All Styles',
28103                 handler: function(a,b) {
28104                     
28105                     var c = Roo.get(editorcore.doc.body);
28106                     c.select('[style]').each(function(s) {
28107                         s.dom.removeAttribute('style');
28108                     });
28109                     editorcore.syncValue();
28110                 },
28111                 tabIndex:-1
28112             });
28113             
28114             cmenu.menu.items.push({
28115                 actiontype : 'all',
28116                 html: 'Remove All CSS Classes',
28117                 handler: function(a,b) {
28118                     
28119                     var c = Roo.get(editorcore.doc.body);
28120                     c.select('[class]').each(function(s) {
28121                         s.dom.removeAttribute('class');
28122                     });
28123                     editorcore.cleanWord();
28124                     editorcore.syncValue();
28125                 },
28126                 tabIndex:-1
28127             });
28128             
28129              cmenu.menu.items.push({
28130                 actiontype : 'tidy',
28131                 html: 'Tidy HTML Source',
28132                 handler: function(a,b) {
28133                     new Roo.htmleditor.Tidy(editorcore.doc.body);
28134                     editorcore.syncValue();
28135                 },
28136                 tabIndex:-1
28137             });
28138             
28139             
28140             tb.add(cmenu);
28141         }
28142          
28143         if (!this.disable.specialElements) {
28144             var semenu = {
28145                 text: "Other;",
28146                 cls: 'x-edit-none',
28147                 menu : {
28148                     items : []
28149                 }
28150             };
28151             for (var i =0; i < this.specialElements.length; i++) {
28152                 semenu.menu.items.push(
28153                     Roo.apply({ 
28154                         handler: function(a,b) {
28155                             editor.insertAtCursor(this.ihtml);
28156                         }
28157                     }, this.specialElements[i])
28158                 );
28159                     
28160             }
28161             
28162             tb.add(semenu);
28163             
28164             
28165         }
28166          
28167         
28168         if (this.btns) {
28169             for(var i =0; i< this.btns.length;i++) {
28170                 var b = Roo.factory(this.btns[i],this.btns[i].xns || Roo.form);
28171                 b.cls =  'x-edit-none';
28172                 
28173                 if(typeof(this.btns[i].cls) != 'undefined' && this.btns[i].cls.indexOf('x-init-enable') !== -1){
28174                     b.cls += ' x-init-enable';
28175                 }
28176                 
28177                 b.scope = editorcore;
28178                 tb.add(b);
28179             }
28180         
28181         }
28182         
28183         
28184         
28185         // disable everything...
28186         
28187         this.tb.items.each(function(item){
28188             
28189            if(
28190                 item.id != editorcore.frameId+ '-sourceedit' && 
28191                 (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)
28192             ){
28193                 
28194                 item.disable();
28195             }
28196         });
28197         this.rendered = true;
28198         
28199         // the all the btns;
28200         editor.on('editorevent', this.updateToolbar, this);
28201         // other toolbars need to implement this..
28202         //editor.on('editmodechange', this.updateToolbar, this);
28203     },
28204     
28205     
28206     relayBtnCmd : function(btn) {
28207         this.editorcore.relayCmd(btn.cmd);
28208     },
28209     // private used internally
28210     createLink : function(){
28211         //Roo.log("create link?");
28212         var ec = this.editorcore;
28213         var ar = ec.getAllAncestors();
28214         var n = false;
28215         for(var i = 0;i< ar.length;i++) {
28216             if (ar[i] && ar[i].nodeName == 'A') {
28217                 n = ar[i];
28218                 break;
28219             }
28220         }
28221         
28222         (function() {
28223             
28224             Roo.MessageBox.show({
28225                 title : "Add / Edit Link URL",
28226                 msg : "Enter the url for the link",
28227                 buttons: Roo.MessageBox.OKCANCEL,
28228                 fn: function(btn, url){
28229                     if (btn != 'ok') {
28230                         return;
28231                     }
28232                     if(url && url != 'http:/'+'/'){
28233                         if (n) {
28234                             n.setAttribute('href', url);
28235                         } else {
28236                             ec.relayCmd('createlink', url);
28237                         }
28238                     }
28239                 },
28240                 minWidth:250,
28241                 prompt:true,
28242                 //multiline: multiline,
28243                 modal : true,
28244                 value :  n  ? n.getAttribute('href') : '' 
28245             });
28246             
28247              
28248         }).defer(100, this); // we have to defer this , otherwise the mouse click gives focus to the main window.
28249         
28250     },
28251
28252     
28253     /**
28254      * Protected method that will not generally be called directly. It triggers
28255      * a toolbar update by reading the markup state of the current selection in the editor.
28256      */
28257     updateToolbar: function(){
28258
28259         if(!this.editorcore.activated){
28260             this.editor.onFirstFocus();
28261             return;
28262         }
28263
28264         var btns = this.tb.items.map, 
28265             doc = this.editorcore.doc,
28266             frameId = this.editorcore.frameId;
28267
28268         if(!this.disable.font && !Roo.isSafari){
28269             /*
28270             var name = (doc.queryCommandValue('FontName')||this.editor.defaultFont).toLowerCase();
28271             if(name != this.fontSelect.dom.value){
28272                 this.fontSelect.dom.value = name;
28273             }
28274             */
28275         }
28276         if(!this.disable.format){
28277             btns[frameId + '-bold'].toggle(doc.queryCommandState('bold'));
28278             btns[frameId + '-italic'].toggle(doc.queryCommandState('italic'));
28279             btns[frameId + '-underline'].toggle(doc.queryCommandState('underline'));
28280             btns[frameId + '-strikethrough'].toggle(doc.queryCommandState('strikethrough'));
28281         }
28282         if(!this.disable.alignments){
28283             btns[frameId + '-justifyleft'].toggle(doc.queryCommandState('justifyleft'));
28284             btns[frameId + '-justifycenter'].toggle(doc.queryCommandState('justifycenter'));
28285             btns[frameId + '-justifyright'].toggle(doc.queryCommandState('justifyright'));
28286         }
28287         if(!Roo.isSafari && !this.disable.lists){
28288             btns[frameId + '-insertorderedlist'].toggle(doc.queryCommandState('insertorderedlist'));
28289             btns[frameId + '-insertunorderedlist'].toggle(doc.queryCommandState('insertunorderedlist'));
28290         }
28291         
28292         var ans = this.editorcore.getAllAncestors();
28293         if (this.formatCombo) {
28294             
28295             
28296             var store = this.formatCombo.store;
28297             this.formatCombo.setValue("");
28298             for (var i =0; i < ans.length;i++) {
28299                 if (ans[i] && store.query('tag',ans[i].tagName.toLowerCase(), false).length) {
28300                     // select it..
28301                     this.formatCombo.setValue(ans[i].tagName.toLowerCase());
28302                     break;
28303                 }
28304             }
28305         }
28306         
28307         
28308         
28309         // hides menus... - so this cant be on a menu...
28310         Roo.menu.MenuMgr.hideAll();
28311
28312         //this.editorsyncValue();
28313     },
28314    
28315     
28316     createFontOptions : function(){
28317         var buf = [], fs = this.fontFamilies, ff, lc;
28318         
28319         
28320         
28321         for(var i = 0, len = fs.length; i< len; i++){
28322             ff = fs[i];
28323             lc = ff.toLowerCase();
28324             buf.push(
28325                 '<option value="',lc,'" style="font-family:',ff,';"',
28326                     (this.defaultFont == lc ? ' selected="true">' : '>'),
28327                     ff,
28328                 '</option>'
28329             );
28330         }
28331         return buf.join('');
28332     },
28333     
28334     toggleSourceEdit : function(sourceEditMode){
28335         
28336         Roo.log("toolbar toogle");
28337         if(sourceEditMode === undefined){
28338             sourceEditMode = !this.sourceEditMode;
28339         }
28340         this.sourceEditMode = sourceEditMode === true;
28341         var btn = this.tb.items.get(this.editorcore.frameId +'-sourceedit');
28342         // just toggle the button?
28343         if(btn.pressed !== this.sourceEditMode){
28344             btn.toggle(this.sourceEditMode);
28345             return;
28346         }
28347         
28348         if(sourceEditMode){
28349             Roo.log("disabling buttons");
28350             this.tb.items.each(function(item){
28351                 if(item.cmd != 'sourceedit' && (typeof(item.cls) != 'undefined' && item.cls.indexOf('x-init-enable') === -1)){
28352                     item.disable();
28353                 }
28354             });
28355           
28356         }else{
28357             Roo.log("enabling buttons");
28358             if(this.editorcore.initialized){
28359                 this.tb.items.each(function(item){
28360                     item.enable();
28361                 });
28362                 // initialize 'blocks'
28363                 Roo.each(Roo.get(this.editorcore.doc.body).query('*[data-block]'), function(e) {
28364                     Roo.htmleditor.Block.factory(e).updateElement(e);
28365                 },this);
28366             
28367             }
28368             
28369         }
28370         Roo.log("calling toggole on editor");
28371         // tell the editor that it's been pressed..
28372         this.editor.toggleSourceEdit(sourceEditMode);
28373        
28374     },
28375      /**
28376      * Object collection of toolbar tooltips for the buttons in the editor. The key
28377      * is the command id associated with that button and the value is a valid QuickTips object.
28378      * For example:
28379 <pre><code>
28380 {
28381     bold : {
28382         title: 'Bold (Ctrl+B)',
28383         text: 'Make the selected text bold.',
28384         cls: 'x-html-editor-tip'
28385     },
28386     italic : {
28387         title: 'Italic (Ctrl+I)',
28388         text: 'Make the selected text italic.',
28389         cls: 'x-html-editor-tip'
28390     },
28391     ...
28392 </code></pre>
28393     * @type Object
28394      */
28395     buttonTips : {
28396         bold : {
28397             title: 'Bold (Ctrl+B)',
28398             text: 'Make the selected text bold.',
28399             cls: 'x-html-editor-tip'
28400         },
28401         italic : {
28402             title: 'Italic (Ctrl+I)',
28403             text: 'Make the selected text italic.',
28404             cls: 'x-html-editor-tip'
28405         },
28406         underline : {
28407             title: 'Underline (Ctrl+U)',
28408             text: 'Underline the selected text.',
28409             cls: 'x-html-editor-tip'
28410         },
28411         strikethrough : {
28412             title: 'Strikethrough',
28413             text: 'Strikethrough the selected text.',
28414             cls: 'x-html-editor-tip'
28415         },
28416         increasefontsize : {
28417             title: 'Grow Text',
28418             text: 'Increase the font size.',
28419             cls: 'x-html-editor-tip'
28420         },
28421         decreasefontsize : {
28422             title: 'Shrink Text',
28423             text: 'Decrease the font size.',
28424             cls: 'x-html-editor-tip'
28425         },
28426         backcolor : {
28427             title: 'Text Highlight Color',
28428             text: 'Change the background color of the selected text.',
28429             cls: 'x-html-editor-tip'
28430         },
28431         forecolor : {
28432             title: 'Font Color',
28433             text: 'Change the color of the selected text.',
28434             cls: 'x-html-editor-tip'
28435         },
28436         justifyleft : {
28437             title: 'Align Text Left',
28438             text: 'Align text to the left.',
28439             cls: 'x-html-editor-tip'
28440         },
28441         justifycenter : {
28442             title: 'Center Text',
28443             text: 'Center text in the editor.',
28444             cls: 'x-html-editor-tip'
28445         },
28446         justifyright : {
28447             title: 'Align Text Right',
28448             text: 'Align text to the right.',
28449             cls: 'x-html-editor-tip'
28450         },
28451         insertunorderedlist : {
28452             title: 'Bullet List',
28453             text: 'Start a bulleted list.',
28454             cls: 'x-html-editor-tip'
28455         },
28456         insertorderedlist : {
28457             title: 'Numbered List',
28458             text: 'Start a numbered list.',
28459             cls: 'x-html-editor-tip'
28460         },
28461         createlink : {
28462             title: 'Hyperlink',
28463             text: 'Make the selected text a hyperlink.',
28464             cls: 'x-html-editor-tip'
28465         },
28466         sourceedit : {
28467             title: 'Source Edit',
28468             text: 'Switch to source editing mode.',
28469             cls: 'x-html-editor-tip'
28470         }
28471     },
28472     // private
28473     onDestroy : function(){
28474         if(this.rendered){
28475             
28476             this.tb.items.each(function(item){
28477                 if(item.menu){
28478                     item.menu.removeAll();
28479                     if(item.menu.el){
28480                         item.menu.el.destroy();
28481                     }
28482                 }
28483                 item.destroy();
28484             });
28485              
28486         }
28487     },
28488     onFirstFocus: function() {
28489         this.tb.items.each(function(item){
28490            item.enable();
28491         });
28492     }
28493 };
28494
28495
28496
28497
28498 // <script type="text/javascript">
28499 /*
28500  * Based on
28501  * Ext JS Library 1.1.1
28502  * Copyright(c) 2006-2007, Ext JS, LLC.
28503  *  
28504  
28505  */
28506
28507  
28508 /**
28509  * @class Roo.form.HtmlEditor.ToolbarContext
28510  * Context Toolbar
28511  * 
28512  * Usage:
28513  *
28514  new Roo.form.HtmlEditor({
28515     ....
28516     toolbars : [
28517         { xtype: 'ToolbarStandard', styles : {} }
28518         { xtype: 'ToolbarContext', disable : {} }
28519     ]
28520 })
28521
28522      
28523  * 
28524  * @config : {Object} disable List of elements to disable.. (not done yet.)
28525  * @config : {Object} styles  Map of styles available.
28526  * 
28527  */
28528
28529 Roo.form.HtmlEditor.ToolbarContext = function(config)
28530 {
28531     
28532     Roo.apply(this, config);
28533     //Roo.form.HtmlEditorToolbar1.superclass.constructor.call(this, editor.wrap.dom.firstChild, [], config);
28534     // dont call parent... till later.
28535     this.styles = this.styles || {};
28536 }
28537
28538  
28539
28540 Roo.form.HtmlEditor.ToolbarContext.types = {
28541     'IMG' : [
28542         {
28543             name : 'width',
28544             title: "Width",
28545             width: 40
28546         },
28547         {
28548             name : 'height',
28549             title: "Height",
28550             width: 40
28551         },
28552         {
28553             name : 'align',
28554             title: "Align",
28555             opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
28556             width : 80
28557             
28558         },
28559         {
28560             name : 'border',
28561             title: "Border",
28562             width: 40
28563         },
28564         {
28565             name : 'alt',
28566             title: "Alt",
28567             width: 120
28568         },
28569         {
28570             name : 'src',
28571             title: "Src",
28572             width: 220
28573         }
28574         
28575     ],
28576     
28577     'FIGURE' : [
28578         {
28579             name : 'align',
28580             title: "Align",
28581             opts : [ [""],[ "left"],[ "right"],[ "center"],[ "top"]],
28582             width : 80  
28583         }
28584     ],
28585     'A' : [
28586         {
28587             name : 'name',
28588             title: "Name",
28589             width: 50
28590         },
28591         {
28592             name : 'target',
28593             title: "Target",
28594             width: 120
28595         },
28596         {
28597             name : 'href',
28598             title: "Href",
28599             width: 220
28600         } // border?
28601         
28602     ],
28603     
28604     'INPUT' : [
28605         {
28606             name : 'name',
28607             title: "name",
28608             width: 120
28609         },
28610         {
28611             name : 'value',
28612             title: "Value",
28613             width: 120
28614         },
28615         {
28616             name : 'width',
28617             title: "Width",
28618             width: 40
28619         }
28620     ],
28621     'LABEL' : [
28622          {
28623             name : 'for',
28624             title: "For",
28625             width: 120
28626         }
28627     ],
28628     'TEXTAREA' : [
28629         {
28630             name : 'name',
28631             title: "name",
28632             width: 120
28633         },
28634         {
28635             name : 'rows',
28636             title: "Rows",
28637             width: 20
28638         },
28639         {
28640             name : 'cols',
28641             title: "Cols",
28642             width: 20
28643         }
28644     ],
28645     'SELECT' : [
28646         {
28647             name : 'name',
28648             title: "name",
28649             width: 120
28650         },
28651         {
28652             name : 'selectoptions',
28653             title: "Options",
28654             width: 200
28655         }
28656     ],
28657     
28658     // should we really allow this??
28659     // should this just be 
28660     'BODY' : [
28661         
28662         {
28663             name : 'title',
28664             title: "Title",
28665             width: 200,
28666             disabled : true
28667         }
28668     ],
28669  
28670     '*' : [
28671         // empty.
28672     ]
28673
28674 };
28675
28676 // this should be configurable.. - you can either set it up using stores, or modify options somehwere..
28677 Roo.form.HtmlEditor.ToolbarContext.stores = false;
28678
28679 Roo.form.HtmlEditor.ToolbarContext.options = {
28680         'font-family'  : [ 
28681                 [ 'Helvetica,Arial,sans-serif', 'Helvetica'],
28682                 [ 'Courier New', 'Courier New'],
28683                 [ 'Tahoma', 'Tahoma'],
28684                 [ 'Times New Roman,serif', 'Times'],
28685                 [ 'Verdana','Verdana' ]
28686         ]
28687 };
28688
28689 // fixme - these need to be configurable..
28690  
28691
28692 //Roo.form.HtmlEditor.ToolbarContext.types
28693
28694
28695 Roo.apply(Roo.form.HtmlEditor.ToolbarContext.prototype,  {
28696     
28697     tb: false,
28698     
28699     rendered: false,
28700     
28701     editor : false,
28702     editorcore : false,
28703     /**
28704      * @cfg {Object} disable  List of toolbar elements to disable
28705          
28706      */
28707     disable : false,
28708     /**
28709      * @cfg {Object} styles List of styles 
28710      *    eg. { '*' : [ 'headline' ] , 'TD' : [ 'underline', 'double-underline' ] } 
28711      *
28712      * These must be defined in the page, so they get rendered correctly..
28713      * .headline { }
28714      * TD.underline { }
28715      * 
28716      */
28717     styles : false,
28718     
28719     options: false,
28720     
28721     toolbars : false,
28722     
28723     init : function(editor)
28724     {
28725         this.editor = editor;
28726         this.editorcore = editor.editorcore ? editor.editorcore : editor;
28727         var editorcore = this.editorcore;
28728         
28729         var fid = editorcore.frameId;
28730         var etb = this;
28731         function btn(id, toggle, handler){
28732             var xid = fid + '-'+ id ;
28733             return {
28734                 id : xid,
28735                 cmd : id,
28736                 cls : 'x-btn-icon x-edit-'+id,
28737                 enableToggle:toggle !== false,
28738                 scope: editorcore, // was editor...
28739                 handler:handler||editorcore.relayBtnCmd,
28740                 clickEvent:'mousedown',
28741                 tooltip: etb.buttonTips[id] || undefined, ///tips ???
28742                 tabIndex:-1
28743             };
28744         }
28745         // create a new element.
28746         var wdiv = editor.wrap.createChild({
28747                 tag: 'div'
28748             }, editor.wrap.dom.firstChild.nextSibling, true);
28749         
28750         // can we do this more than once??
28751         
28752          // stop form submits
28753       
28754  
28755         // disable everything...
28756         var ty= Roo.form.HtmlEditor.ToolbarContext.types;
28757         this.toolbars = {};
28758         // block toolbars are built in updateToolbar when needed.
28759         for (var i in  ty) {
28760             
28761             this.toolbars[i] = this.buildToolbar(ty[i],i);
28762         }
28763         this.tb = this.toolbars.BODY;
28764         this.tb.el.show();
28765         this.buildFooter();
28766         this.footer.show();
28767         editor.on('hide', function( ) { this.footer.hide() }, this);
28768         editor.on('show', function( ) { this.footer.show() }, this);
28769         
28770          
28771         this.rendered = true;
28772         
28773         // the all the btns;
28774         editor.on('editorevent', this.updateToolbar, this);
28775         // other toolbars need to implement this..
28776         //editor.on('editmodechange', this.updateToolbar, this);
28777     },
28778     
28779     
28780     
28781     /**
28782      * Protected method that will not generally be called directly. It triggers
28783      * a toolbar update by reading the markup state of the current selection in the editor.
28784      *
28785      * Note you can force an update by calling on('editorevent', scope, false)
28786      */
28787     updateToolbar: function(editor ,ev, sel)
28788     {
28789         
28790         if (ev) {
28791             ev.stopEvent(); // se if we can stop this looping with mutiple events.
28792         }
28793         
28794         //Roo.log(ev);
28795         // capture mouse up - this is handy for selecting images..
28796         // perhaps should go somewhere else...
28797         if(!this.editorcore.activated){
28798              this.editor.onFirstFocus();
28799             return;
28800         }
28801         //Roo.log(ev ? ev.target : 'NOTARGET');
28802         
28803         
28804         // http://developer.yahoo.com/yui/docs/simple-editor.js.html
28805         // selectNode - might want to handle IE?
28806         
28807         
28808         
28809         if (ev &&
28810             (ev.type == 'mouseup' || ev.type == 'click' ) &&
28811             ev.target && ev.target.tagName != 'BODY' ) { // && ev.target.tagName == 'IMG') {
28812             // they have click on an image...
28813             // let's see if we can change the selection...
28814             sel = ev.target;
28815             
28816             // this triggers looping?
28817             //this.editorcore.selectNode(sel);
28818              
28819         }
28820         
28821         // this forces an id..
28822         Array.from(this.editorcore.doc.body.querySelectorAll('.roo-ed-selection')).forEach(function(e) {
28823              e.classList.remove('roo-ed-selection');
28824         });
28825         //Roo.select('.roo-ed-selection', false, this.editorcore.doc).removeClass('roo-ed-selection');
28826         //Roo.get(node).addClass('roo-ed-selection');
28827       
28828         //var updateFooter = sel ? false : true; 
28829         
28830         
28831         var ans = this.editorcore.getAllAncestors();
28832         
28833         // pick
28834         var ty = Roo.form.HtmlEditor.ToolbarContext.types;
28835         
28836         if (!sel) { 
28837             sel = ans.length ? (ans[0] ?  ans[0]  : ans[1]) : this.editorcore.doc.body;
28838             sel = sel ? sel : this.editorcore.doc.body;
28839             sel = sel.tagName.length ? sel : this.editorcore.doc.body;
28840             
28841         }
28842         
28843         var tn = sel.tagName.toUpperCase();
28844         var lastSel = this.tb.selectedNode;
28845         this.tb.selectedNode = sel;
28846         var left_label = tn;
28847         
28848         // ok see if we are editing a block?
28849         
28850         var db = false;
28851         // you are not actually selecting the block.
28852         if (sel && sel.hasAttribute('data-block')) {
28853             db = sel;
28854         } else if (sel && sel.closest('[data-block]')) {
28855             
28856             db = sel.closest('[data-block]');
28857             //var cepar = sel.closest('[contenteditable=true]');
28858             //if (db && cepar && cepar.tagName != 'BODY') {
28859             //   db = false; // we are inside an editable block.. = not sure how we are going to handle nested blocks!?
28860             //}   
28861         }
28862         
28863         
28864         var block = false;
28865         //if (db && !sel.hasAttribute('contenteditable') && sel.getAttribute('contenteditable') != 'true' ) {
28866         if (db && this.editorcore.enableBlocks) {
28867             block = Roo.htmleditor.Block.factory(db);
28868             
28869             
28870             if (block) {
28871                  db.className = (
28872                         db.classList.length > 0  ? db.className + ' ' : ''
28873                     )  + 'roo-ed-selection';
28874                  
28875                  // since we removed it earlier... its not there..
28876                 tn = 'BLOCK.' + db.getAttribute('data-block');
28877                 
28878                 //this.editorcore.selectNode(db);
28879                 if (typeof(this.toolbars[tn]) == 'undefined') {
28880                    this.toolbars[tn] = this.buildToolbar( false  ,tn ,block.friendly_name, block);
28881                 }
28882                 this.toolbars[tn].selectedNode = db;
28883                 left_label = block.friendly_name;
28884                 ans = this.editorcore.getAllAncestors();
28885             }
28886             
28887                 
28888             
28889         }
28890         
28891         
28892         if (this.tb.name == tn && lastSel == this.tb.selectedNode && ev !== false) {
28893             return; // no change?
28894         }
28895         
28896         
28897           
28898         this.tb.el.hide();
28899         ///console.log("show: " + tn);
28900         this.tb =  typeof(this.toolbars[tn]) != 'undefined' ? this.toolbars[tn] : this.toolbars['*'];
28901         
28902         this.tb.el.show();
28903         // update name
28904         this.tb.items.first().el.innerHTML = left_label + ':&nbsp;';
28905         
28906         
28907         // update attributes
28908         if (block && this.tb.fields) {
28909              
28910             this.tb.fields.each(function(e) {
28911                 e.setValue(block[e.name]);
28912             });
28913             
28914             
28915         } else  if (this.tb.fields && this.tb.selectedNode) {
28916             this.tb.fields.each( function(e) {
28917                 if (e.stylename) {
28918                     e.setValue(this.tb.selectedNode.style[e.stylename]);
28919                     return;
28920                 } 
28921                 e.setValue(this.tb.selectedNode.getAttribute(e.attrname));
28922             }, this);
28923             this.updateToolbarStyles(this.tb.selectedNode);  
28924         }
28925         
28926         
28927        
28928         Roo.menu.MenuMgr.hideAll();
28929
28930         
28931         
28932     
28933         // update the footer
28934         //
28935         this.updateFooter(ans);
28936              
28937     },
28938     
28939     updateToolbarStyles : function(sel)
28940     {
28941         var hasStyles = false;
28942         for(var i in this.styles) {
28943             hasStyles = true;
28944             break;
28945         }
28946         
28947         // update styles
28948         if (hasStyles && this.tb.hasStyles) { 
28949             var st = this.tb.fields.item(0);
28950             
28951             st.store.removeAll();
28952             var cn = sel.className.split(/\s+/);
28953             
28954             var avs = [];
28955             if (this.styles['*']) {
28956                 
28957                 Roo.each(this.styles['*'], function(v) {
28958                     avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );         
28959                 });
28960             }
28961             if (this.styles[tn]) { 
28962                 Roo.each(this.styles[tn], function(v) {
28963                     avs.push( [ v , cn.indexOf(v) > -1 ? 1 : 0 ] );         
28964                 });
28965             }
28966             
28967             st.store.loadData(avs);
28968             st.collapse();
28969             st.setValue(cn);
28970         }
28971     },
28972     
28973      
28974     updateFooter : function(ans)
28975     {
28976         var html = '';
28977         if (ans === false) {
28978             this.footDisp.dom.innerHTML = '';
28979             return;
28980         }
28981         
28982         this.footerEls = ans.reverse();
28983         Roo.each(this.footerEls, function(a,i) {
28984             if (!a) { return; }
28985             html += html.length ? ' &gt; '  :  '';
28986             
28987             html += '<span class="x-ed-loc-' + i + '">' + a.tagName + '</span>';
28988             
28989         });
28990        
28991         // 
28992         var sz = this.footDisp.up('td').getSize();
28993         this.footDisp.dom.style.width = (sz.width -10) + 'px';
28994         this.footDisp.dom.style.marginLeft = '5px';
28995         
28996         this.footDisp.dom.style.overflow = 'hidden';
28997         
28998         this.footDisp.dom.innerHTML = html;
28999             
29000         
29001     },
29002    
29003        
29004     // private
29005     onDestroy : function(){
29006         if(this.rendered){
29007             
29008             this.tb.items.each(function(item){
29009                 if(item.menu){
29010                     item.menu.removeAll();
29011                     if(item.menu.el){
29012                         item.menu.el.destroy();
29013                     }
29014                 }
29015                 item.destroy();
29016             });
29017              
29018         }
29019     },
29020     onFirstFocus: function() {
29021         // need to do this for all the toolbars..
29022         this.tb.items.each(function(item){
29023            item.enable();
29024         });
29025     },
29026     buildToolbar: function(tlist, nm, friendly_name, block)
29027     {
29028         var editor = this.editor;
29029         var editorcore = this.editorcore;
29030          // create a new element.
29031         var wdiv = editor.wrap.createChild({
29032                 tag: 'div'
29033             }, editor.wrap.dom.firstChild.nextSibling, true);
29034         
29035        
29036         var tb = new Roo.Toolbar(wdiv);
29037         ///this.tb = tb; // << this sets the active toolbar..
29038         if (tlist === false && block) {
29039             tlist = block.contextMenu(this);
29040         }
29041         
29042         tb.hasStyles = false;
29043         tb.name = nm;
29044         
29045         tb.add((typeof(friendly_name) == 'undefined' ? nm : friendly_name) + ":&nbsp;");
29046         
29047         var styles = Array.from(this.styles);
29048         
29049         
29050         // styles...
29051         if (styles && styles.length) {
29052             tb.hasStyles = true;
29053             // this needs a multi-select checkbox...
29054             tb.addField( new Roo.form.ComboBox({
29055                 store: new Roo.data.SimpleStore({
29056                     id : 'val',
29057                     fields: ['val', 'selected'],
29058                     data : [] 
29059                 }),
29060                 name : '-roo-edit-className',
29061                 attrname : 'className',
29062                 displayField: 'val',
29063                 typeAhead: false,
29064                 mode: 'local',
29065                 editable : false,
29066                 triggerAction: 'all',
29067                 emptyText:'Select Style',
29068                 selectOnFocus:true,
29069                 width: 130,
29070                 listeners : {
29071                     'select': function(c, r, i) {
29072                         // initial support only for on class per el..
29073                         tb.selectedNode.className =  r ? r.get('val') : '';
29074                         editorcore.syncValue();
29075                     }
29076                 }
29077     
29078             }));
29079         }
29080         
29081         var tbc = Roo.form.HtmlEditor.ToolbarContext;
29082         
29083         
29084         for (var i = 0; i < tlist.length; i++) {
29085             
29086             // newer versions will use xtype cfg to create menus.
29087             if (typeof(tlist[i].xtype) != 'undefined') {
29088                 
29089                 tb[typeof(tlist[i].name)== 'undefined' ? 'add' : 'addField'](Roo.factory(tlist[i]));
29090                 
29091                 
29092                 continue;
29093             }
29094             
29095             var item = tlist[i];
29096             tb.add(item.title + ":&nbsp;");
29097             
29098             
29099             //optname == used so you can configure the options available..
29100             var opts = item.opts ? item.opts : false;
29101             if (item.optname) { // use the b
29102                 opts = Roo.form.HtmlEditor.ToolbarContext.options[item.optname];
29103            
29104             }
29105             
29106             if (opts) {
29107                 // opts == pulldown..
29108                 tb.addField( new Roo.form.ComboBox({
29109                     store:   typeof(tbc.stores[i]) != 'undefined' ?  Roo.factory(tbc.stores[i],Roo.data) : new Roo.data.SimpleStore({
29110                         id : 'val',
29111                         fields: ['val', 'display'],
29112                         data : opts  
29113                     }),
29114                     name : '-roo-edit-' + tlist[i].name,
29115                     
29116                     attrname : tlist[i].name,
29117                     stylename : item.style ? item.style : false,
29118                     
29119                     displayField: item.displayField ? item.displayField : 'val',
29120                     valueField :  'val',
29121                     typeAhead: false,
29122                     mode: typeof(tbc.stores[tlist[i].name]) != 'undefined'  ? 'remote' : 'local',
29123                     editable : false,
29124                     triggerAction: 'all',
29125                     emptyText:'Select',
29126                     selectOnFocus:true,
29127                     width: item.width ? item.width  : 130,
29128                     listeners : {
29129                         'select': function(c, r, i) {
29130                              
29131                             
29132                             if (c.stylename) {
29133                                 tb.selectedNode.style[c.stylename] =  r.get('val');
29134                                 editorcore.syncValue();
29135                                 return;
29136                             }
29137                             if (r === false) {
29138                                 tb.selectedNode.removeAttribute(c.attrname);
29139                                 editorcore.syncValue();
29140                                 return;
29141                             }
29142                             tb.selectedNode.setAttribute(c.attrname, r.get('val'));
29143                             editorcore.syncValue();
29144                         }
29145                     }
29146
29147                 }));
29148                 continue;
29149                     
29150                  
29151                 /*
29152                 tb.addField( new Roo.form.TextField({
29153                     name: i,
29154                     width: 100,
29155                     //allowBlank:false,
29156                     value: ''
29157                 }));
29158                 continue;
29159                 */
29160             }
29161             tb.addField( new Roo.form.TextField({
29162                 name: '-roo-edit-' + tlist[i].name,
29163                 attrname : tlist[i].name,
29164                 
29165                 width: item.width,
29166                 //allowBlank:true,
29167                 value: '',
29168                 listeners: {
29169                     'change' : function(f, nv, ov) {
29170                         
29171                          
29172                         tb.selectedNode.setAttribute(f.attrname, nv);
29173                         editorcore.syncValue();
29174                     }
29175                 }
29176             }));
29177              
29178         }
29179         
29180         var _this = this;
29181         var show_delete = !block || block.deleteTitle !== false;
29182         if(nm == 'BODY'){
29183             show_delete = false;
29184             tb.addSeparator();
29185         
29186             tb.addButton( {
29187                 text: 'Stylesheets',
29188
29189                 listeners : {
29190                     click : function ()
29191                     {
29192                         _this.editor.fireEvent('stylesheetsclick', _this.editor);
29193                     }
29194                 }
29195             });
29196         }
29197         
29198         tb.addFill();
29199         if (show_delete) {
29200             tb.addButton({
29201                 text: block && block.deleteTitle ? block.deleteTitle  : 'Remove Block or Formating', // remove the tag, and puts the children outside...
29202         
29203                 listeners : {
29204                     click : function ()
29205                     {
29206                         var sn = tb.selectedNode;
29207                         if (block) {
29208                             sn = Roo.htmleditor.Block.factory(tb.selectedNode).removeNode();
29209                             
29210                         }
29211                         if (!sn) {
29212                             return;
29213                         }
29214                         var stn =  sn.childNodes[0] || sn.nextSibling || sn.previousSibling || sn.parentNode;
29215                         if (sn.hasAttribute('data-block')) {
29216                             stn =  sn.nextSibling || sn.previousSibling || sn.parentNode;
29217                             sn.parentNode.removeChild(sn);
29218                             
29219                         } else if (sn && sn.tagName != 'BODY') {
29220                             // remove and keep parents.
29221                             a = new Roo.htmleditor.FilterKeepChildren({tag : false});
29222                             a.replaceTag(sn);
29223                         }
29224                         
29225                         
29226                         var range = editorcore.createRange();
29227             
29228                         range.setStart(stn,0);
29229                         range.setEnd(stn,0); 
29230                         var selection = editorcore.getSelection();
29231                         selection.removeAllRanges();
29232                         selection.addRange(range);
29233                         
29234                         
29235                         //_this.updateToolbar(null, null, pn);
29236                         _this.updateToolbar(null, null, null);
29237                         _this.updateFooter(false);
29238                         
29239                     }
29240                 }
29241                 
29242                         
29243                     
29244                 
29245             });
29246         }    
29247         
29248         tb.el.on('click', function(e){
29249             e.preventDefault(); // what does this do?
29250         });
29251         tb.el.setVisibilityMode( Roo.Element.DISPLAY);
29252         tb.el.hide();
29253         
29254         // dont need to disable them... as they will get hidden
29255         return tb;
29256          
29257         
29258     },
29259     buildFooter : function()
29260     {
29261         
29262         var fel = this.editor.wrap.createChild();
29263         this.footer = new Roo.Toolbar(fel);
29264         // toolbar has scrolly on left / right?
29265         var footDisp= new Roo.Toolbar.Fill();
29266         var _t = this;
29267         this.footer.add(
29268             {
29269                 text : '&lt;',
29270                 xtype: 'Button',
29271                 handler : function() {
29272                     _t.footDisp.scrollTo('left',0,true)
29273                 }
29274             }
29275         );
29276         this.footer.add( footDisp );
29277         this.footer.add( 
29278             {
29279                 text : '&gt;',
29280                 xtype: 'Button',
29281                 handler : function() {
29282                     // no animation..
29283                     _t.footDisp.select('span').last().scrollIntoView(_t.footDisp,true);
29284                 }
29285             }
29286         );
29287         var fel = Roo.get(footDisp.el);
29288         fel.addClass('x-editor-context');
29289         this.footDispWrap = fel; 
29290         this.footDispWrap.overflow  = 'hidden';
29291         
29292         this.footDisp = fel.createChild();
29293         this.footDispWrap.on('click', this.onContextClick, this)
29294         
29295         
29296     },
29297     // when the footer contect changes
29298     onContextClick : function (ev,dom)
29299     {
29300         ev.preventDefault();
29301         var  cn = dom.className;
29302         //Roo.log(cn);
29303         if (!cn.match(/x-ed-loc-/)) {
29304             return;
29305         }
29306         var n = cn.split('-').pop();
29307         var ans = this.footerEls;
29308         var sel = ans[n];
29309         
29310         this.editorcore.selectNode(sel);
29311         
29312         
29313         this.updateToolbar(null, null, sel);
29314         
29315         
29316     }
29317     
29318     
29319     
29320     
29321     
29322 });
29323
29324
29325
29326
29327
29328 /*
29329  * Based on:
29330  * Ext JS Library 1.1.1
29331  * Copyright(c) 2006-2007, Ext JS, LLC.
29332  *
29333  * Originally Released Under LGPL - original licence link has changed is not relivant.
29334  *
29335  * Fork - LGPL
29336  * <script type="text/javascript">
29337  */
29338  
29339 /**
29340  * @class Roo.form.BasicForm
29341  * @extends Roo.util.Observable
29342  * Supplies the functionality to do "actions" on forms and initialize Roo.form.Field types on existing markup.
29343  * @constructor
29344  * @param {String/HTMLElement/Roo.Element} el The form element or its id
29345  * @param {Object} config Configuration options
29346  */
29347 Roo.form.BasicForm = function(el, config){
29348     this.allItems = [];
29349     this.childForms = [];
29350     Roo.apply(this, config);
29351     /*
29352      * The Roo.form.Field items in this form.
29353      * @type MixedCollection
29354      */
29355      
29356      
29357     this.items = new Roo.util.MixedCollection(false, function(o){
29358         return o.id || (o.id = Roo.id());
29359     });
29360     this.addEvents({
29361         /**
29362          * @event beforeaction
29363          * Fires before any action is performed. Return false to cancel the action.
29364          * @param {Form} this
29365          * @param {Action} action The action to be performed
29366          */
29367         beforeaction: true,
29368         /**
29369          * @event actionfailed
29370          * Fires when an action fails.
29371          * @param {Form} this
29372          * @param {Action} action The action that failed
29373          */
29374         actionfailed : true,
29375         /**
29376          * @event actioncomplete
29377          * Fires when an action is completed.
29378          * @param {Form} this
29379          * @param {Action} action The action that completed
29380          */
29381         actioncomplete : true
29382     });
29383     if(el){
29384         this.initEl(el);
29385     }
29386     Roo.form.BasicForm.superclass.constructor.call(this);
29387     
29388     Roo.form.BasicForm.popover.apply();
29389 };
29390
29391 Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
29392     /**
29393      * @cfg {String} method
29394      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
29395      */
29396     /**
29397      * @cfg {DataReader} reader
29398      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
29399      * This is optional as there is built-in support for processing JSON.
29400      */
29401     /**
29402      * @cfg {DataReader} errorReader
29403      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
29404      * This is completely optional as there is built-in support for processing JSON.
29405      */
29406     /**
29407      * @cfg {String} url
29408      * The URL to use for form actions if one isn't supplied in the action options.
29409      */
29410     /**
29411      * @cfg {Boolean} fileUpload
29412      * Set to true if this form is a file upload.
29413      */
29414      
29415     /**
29416      * @cfg {Object} baseParams
29417      * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
29418      */
29419      /**
29420      
29421     /**
29422      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
29423      */
29424     timeout: 30,
29425
29426     // private
29427     activeAction : null,
29428
29429     /**
29430      * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
29431      * or setValues() data instead of when the form was first created.
29432      */
29433     trackResetOnLoad : false,
29434     
29435     
29436     /**
29437      * childForms - used for multi-tab forms
29438      * @type {Array}
29439      */
29440     childForms : false,
29441     
29442     /**
29443      * allItems - full list of fields.
29444      * @type {Array}
29445      */
29446     allItems : false,
29447     
29448     /**
29449      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
29450      * element by passing it or its id or mask the form itself by passing in true.
29451      * @type Mixed
29452      */
29453     waitMsgTarget : false,
29454     
29455     /**
29456      * @type Boolean
29457      */
29458     disableMask : false,
29459     
29460     /**
29461      * @cfg {Boolean} errorMask (true|false) default false
29462      */
29463     errorMask : false,
29464     
29465     /**
29466      * @cfg {Number} maskOffset Default 100
29467      */
29468     maskOffset : 100,
29469
29470     // private
29471     initEl : function(el){
29472         this.el = Roo.get(el);
29473         this.id = this.el.id || Roo.id();
29474         this.el.on('submit', this.onSubmit, this);
29475         this.el.addClass('x-form');
29476     },
29477
29478     // private
29479     onSubmit : function(e){
29480         e.stopEvent();
29481     },
29482
29483     /**
29484      * Returns true if client-side validation on the form is successful.
29485      * @return Boolean
29486      */
29487     isValid : function(){
29488         var valid = true;
29489         var target = false;
29490         this.items.each(function(f){
29491             if(f.validate()){
29492                 return;
29493             }
29494             
29495             valid = false;
29496                 
29497             if(!target && f.el.isVisible(true)){
29498                 target = f;
29499             }
29500         });
29501         
29502         if(this.errorMask && !valid){
29503             Roo.form.BasicForm.popover.mask(this, target);
29504         }
29505         
29506         return valid;
29507     },
29508     /**
29509      * Returns array of invalid form fields.
29510      * @return Array
29511      */
29512     
29513     invalidFields : function()
29514     {
29515         var ret = [];
29516         this.items.each(function(f){
29517             if(f.validate()){
29518                 return;
29519             }
29520             ret.push(f);
29521             
29522         });
29523         
29524         return ret;
29525     },
29526     
29527     
29528     /**
29529      * DEPRICATED Returns true if any fields in this form have changed since their original load. 
29530      * @return Boolean
29531      */
29532     isDirty : function(){
29533         var dirty = false;
29534         this.items.each(function(f){
29535            if(f.isDirty()){
29536                dirty = true;
29537                return false;
29538            }
29539         });
29540         return dirty;
29541     },
29542     
29543     /**
29544      * Returns true if any fields in this form have changed since their original load. (New version)
29545      * @return Boolean
29546      */
29547     
29548     hasChanged : function()
29549     {
29550         var dirty = false;
29551         this.items.each(function(f){
29552            if(f.hasChanged()){
29553                dirty = true;
29554                return false;
29555            }
29556         });
29557         return dirty;
29558         
29559     },
29560     /**
29561      * Resets all hasChanged to 'false' -
29562      * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
29563      * So hasChanged storage is only to be used for this purpose
29564      * @return Boolean
29565      */
29566     resetHasChanged : function()
29567     {
29568         this.items.each(function(f){
29569            f.resetHasChanged();
29570         });
29571         
29572     },
29573     
29574     
29575     /**
29576      * Performs a predefined action (submit or load) or custom actions you define on this form.
29577      * @param {String} actionName The name of the action type
29578      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
29579      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
29580      * accept other config options):
29581      * <pre>
29582 Property          Type             Description
29583 ----------------  ---------------  ----------------------------------------------------------------------------------
29584 url               String           The url for the action (defaults to the form's url)
29585 method            String           The form method to use (defaults to the form's method, or POST if not defined)
29586 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
29587 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
29588                                    validate the form on the client (defaults to false)
29589      * </pre>
29590      * @return {BasicForm} this
29591      */
29592     doAction : function(action, options){
29593         if(typeof action == 'string'){
29594             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
29595         }
29596         if(this.fireEvent('beforeaction', this, action) !== false){
29597             this.beforeAction(action);
29598             action.run.defer(100, action);
29599         }
29600         return this;
29601     },
29602
29603     /**
29604      * Shortcut to do a submit action.
29605      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
29606      * @return {BasicForm} this
29607      */
29608     submit : function(options){
29609         this.doAction('submit', options);
29610         return this;
29611     },
29612
29613     /**
29614      * Shortcut to do a load action.
29615      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
29616      * @return {BasicForm} this
29617      */
29618     load : function(options){
29619         this.doAction('load', options);
29620         return this;
29621     },
29622
29623     /**
29624      * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
29625      * @param {Record} record The record to edit
29626      * @return {BasicForm} this
29627      */
29628     updateRecord : function(record){
29629         record.beginEdit();
29630         var fs = record.fields;
29631         fs.each(function(f){
29632             var field = this.findField(f.name);
29633             if(field){
29634                 record.set(f.name, field.getValue());
29635             }
29636         }, this);
29637         record.endEdit();
29638         return this;
29639     },
29640
29641     /**
29642      * Loads an Roo.data.Record into this form.
29643      * @param {Record} record The record to load
29644      * @return {BasicForm} this
29645      */
29646     loadRecord : function(record){
29647         this.setValues(record.data);
29648         return this;
29649     },
29650
29651     // private
29652     beforeAction : function(action){
29653         var o = action.options;
29654         
29655         if(!this.disableMask) {
29656             if(this.waitMsgTarget === true){
29657                 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
29658             }else if(this.waitMsgTarget){
29659                 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
29660                 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
29661             }else {
29662                 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
29663             }
29664         }
29665         
29666          
29667     },
29668
29669     // private
29670     afterAction : function(action, success){
29671         this.activeAction = null;
29672         var o = action.options;
29673         
29674         if(!this.disableMask) {
29675             if(this.waitMsgTarget === true){
29676                 this.el.unmask();
29677             }else if(this.waitMsgTarget){
29678                 this.waitMsgTarget.unmask();
29679             }else{
29680                 Roo.MessageBox.updateProgress(1);
29681                 Roo.MessageBox.hide();
29682             }
29683         }
29684         
29685         if(success){
29686             if(o.reset){
29687                 this.reset();
29688             }
29689             Roo.callback(o.success, o.scope, [this, action]);
29690             this.fireEvent('actioncomplete', this, action);
29691             
29692         }else{
29693             
29694             // failure condition..
29695             // we have a scenario where updates need confirming.
29696             // eg. if a locking scenario exists..
29697             // we look for { errors : { needs_confirm : true }} in the response.
29698             if (
29699                 (typeof(action.result) != 'undefined')  &&
29700                 (typeof(action.result.errors) != 'undefined')  &&
29701                 (typeof(action.result.errors.needs_confirm) != 'undefined')
29702            ){
29703                 var _t = this;
29704                 Roo.MessageBox.confirm(
29705                     "Change requires confirmation",
29706                     action.result.errorMsg,
29707                     function(r) {
29708                         if (r != 'yes') {
29709                             return;
29710                         }
29711                         _t.doAction('submit', { params :  { _submit_confirmed : 1 } }  );
29712                     }
29713                     
29714                 );
29715                 
29716                 
29717                 
29718                 return;
29719             }
29720             
29721             Roo.callback(o.failure, o.scope, [this, action]);
29722             // show an error message if no failed handler is set..
29723             if (!this.hasListener('actionfailed')) {
29724                 Roo.MessageBox.alert("Error",
29725                     (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
29726                         action.result.errorMsg :
29727                         "Saving Failed, please check your entries or try again"
29728                 );
29729             }
29730             
29731             this.fireEvent('actionfailed', this, action);
29732         }
29733         
29734     },
29735
29736     /**
29737      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
29738      * @param {String} id The value to search for
29739      * @return Field
29740      */
29741     findField : function(id){
29742         var field = this.items.get(id);
29743         if(!field){
29744             this.items.each(function(f){
29745                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
29746                     field = f;
29747                     return false;
29748                 }
29749             });
29750         }
29751         return field || null;
29752     },
29753
29754     /**
29755      * Add a secondary form to this one, 
29756      * Used to provide tabbed forms. One form is primary, with hidden values 
29757      * which mirror the elements from the other forms.
29758      * 
29759      * @param {Roo.form.Form} form to add.
29760      * 
29761      */
29762     addForm : function(form)
29763     {
29764        
29765         if (this.childForms.indexOf(form) > -1) {
29766             // already added..
29767             return;
29768         }
29769         this.childForms.push(form);
29770         var n = '';
29771         Roo.each(form.allItems, function (fe) {
29772             
29773             n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
29774             if (this.findField(n)) { // already added..
29775                 return;
29776             }
29777             var add = new Roo.form.Hidden({
29778                 name : n
29779             });
29780             add.render(this.el);
29781             
29782             this.add( add );
29783         }, this);
29784         
29785     },
29786     /**
29787      * Mark fields in this form invalid in bulk.
29788      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
29789      * @return {BasicForm} this
29790      */
29791     markInvalid : function(errors){
29792         if(errors instanceof Array){
29793             for(var i = 0, len = errors.length; i < len; i++){
29794                 var fieldError = errors[i];
29795                 var f = this.findField(fieldError.id);
29796                 if(f){
29797                     f.markInvalid(fieldError.msg);
29798                 }
29799             }
29800         }else{
29801             var field, id;
29802             for(id in errors){
29803                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
29804                     field.markInvalid(errors[id]);
29805                 }
29806             }
29807         }
29808         Roo.each(this.childForms || [], function (f) {
29809             f.markInvalid(errors);
29810         });
29811         
29812         return this;
29813     },
29814
29815     /**
29816      * Set values for fields in this form in bulk.
29817      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
29818      * @return {BasicForm} this
29819      */
29820     setValues : function(values){
29821         if(values instanceof Array){ // array of objects
29822             for(var i = 0, len = values.length; i < len; i++){
29823                 var v = values[i];
29824                 var f = this.findField(v.id);
29825                 if(f){
29826                     f.setValue(v.value);
29827                     if(this.trackResetOnLoad){
29828                         f.originalValue = f.getValue();
29829                     }
29830                 }
29831             }
29832         }else{ // object hash
29833             var field, id;
29834             for(id in values){
29835                 if(typeof values[id] != 'function' && (field = this.findField(id))){
29836                     
29837                     
29838                     
29839                     
29840                     if (field.setFromData && 
29841                         field.valueField && 
29842                         field.displayField &&
29843                         // combos' with local stores can 
29844                         // be queried via setValue()
29845                         // to set their value..
29846                         (field.store && !field.store.isLocal)
29847                         ) {
29848                         // it's a combo
29849                         var sd = { };
29850                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
29851                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
29852                         field.setFromData(sd);
29853                         
29854                     } else if (field.inputType && field.inputType == 'radio') {
29855                         
29856                         field.setValue(values[id]);
29857                     } else {
29858                         field.setValue(values[id]);
29859                     }
29860                     
29861                     
29862                     if(this.trackResetOnLoad){
29863                         field.originalValue = field.getValue();
29864                     }
29865                 }
29866             }
29867         }
29868         this.resetHasChanged();
29869         
29870         
29871         Roo.each(this.childForms || [], function (f) {
29872             f.setValues(values);
29873             f.resetHasChanged();
29874         });
29875                 
29876         return this;
29877     },
29878  
29879     /**
29880      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
29881      * they are returned as an array.
29882      * @param {Boolean} asString (def)
29883      * @return {Object}
29884      */
29885     getValues : function(asString)
29886     {
29887         if (this.childForms) {
29888             // copy values from the child forms
29889             Roo.each(this.childForms, function (f) {
29890                 this.setValues(f.getFieldValues()); // get the full set of data, as we might be copying comboboxes from external into this one.
29891             }, this);
29892         }
29893         
29894         // use formdata
29895         if (typeof(FormData) != 'undefined' && asString !== true) {
29896             // this relies on a 'recent' version of chrome apparently...
29897             try {
29898                 var fd = (new FormData(this.el.dom)).entries();
29899                 var ret = {};
29900                 var ent = fd.next();
29901                 while (!ent.done) {
29902                     ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
29903                     ent = fd.next();
29904                 };
29905                 return ret;
29906             } catch(e) {
29907                 
29908             }
29909             
29910         }
29911         
29912         
29913         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
29914         if(asString === true){
29915             return fs;
29916         }
29917         return Roo.urlDecode(fs);
29918     },
29919     
29920     /**
29921      * Returns the fields in this form as an object with key/value pairs. 
29922      * This differs from getValues as it calls getValue on each child item, rather than using dom data.
29923      * Normally this will not return readOnly data 
29924      * @param {Boolean} with_readonly return readonly field data.
29925      * @return {Object}
29926      */
29927     getFieldValues : function(with_readonly)
29928     {
29929         if (this.childForms) {
29930             // copy values from the child forms
29931             // should this call getFieldValues - probably not as we do not currently copy
29932             // hidden fields when we generate..
29933             Roo.each(this.childForms, function (f) {
29934                 this.setValues(f.getFieldValues());
29935             }, this);
29936         }
29937         
29938         var ret = {};
29939         this.items.each(function(f){
29940             
29941             if (f.readOnly && with_readonly !== true) {
29942                 return; // skip read only values. - this is in theory to stop 'old' values being copied over new ones
29943                         // if a subform contains a copy of them.
29944                         // if you have subforms with the same editable data, you will need to copy the data back
29945                         // and forth.
29946             }
29947             
29948             if (!f.getName()) {
29949                 return;
29950             }
29951             var v = f.getValue();
29952             if (f.inputType =='radio') {
29953                 if (typeof(ret[f.getName()]) == 'undefined') {
29954                     ret[f.getName()] = ''; // empty..
29955                 }
29956                 
29957                 if (!f.el.dom.checked) {
29958                     return;
29959                     
29960                 }
29961                 v = f.el.dom.value;
29962                 
29963             }
29964             
29965             // not sure if this supported any more..
29966             if ((typeof(v) == 'object') && f.getRawValue) {
29967                 v = f.getRawValue() ; // dates..
29968             }
29969             // combo boxes where name != hiddenName...
29970             if (f.name != f.getName()) {
29971                 ret[f.name] = f.getRawValue();
29972             }
29973             ret[f.getName()] = v;
29974         });
29975         
29976         return ret;
29977     },
29978
29979     /**
29980      * Clears all invalid messages in this form.
29981      * @return {BasicForm} this
29982      */
29983     clearInvalid : function(){
29984         this.items.each(function(f){
29985            f.clearInvalid();
29986         });
29987         
29988         Roo.each(this.childForms || [], function (f) {
29989             f.clearInvalid();
29990         });
29991         
29992         
29993         return this;
29994     },
29995
29996     /**
29997      * Resets this form.
29998      * @return {BasicForm} this
29999      */
30000     reset : function(){
30001         this.items.each(function(f){
30002             f.reset();
30003         });
30004         
30005         Roo.each(this.childForms || [], function (f) {
30006             f.reset();
30007         });
30008         this.resetHasChanged();
30009         
30010         return this;
30011     },
30012
30013     /**
30014      * Add Roo.form components to this form.
30015      * @param {Field} field1
30016      * @param {Field} field2 (optional)
30017      * @param {Field} etc (optional)
30018      * @return {BasicForm} this
30019      */
30020     add : function(){
30021         this.items.addAll(Array.prototype.slice.call(arguments, 0));
30022         return this;
30023     },
30024
30025
30026     /**
30027      * Removes a field from the items collection (does NOT remove its markup).
30028      * @param {Field} field
30029      * @return {BasicForm} this
30030      */
30031     remove : function(field){
30032         this.items.remove(field);
30033         return this;
30034     },
30035
30036     /**
30037      * Looks at the fields in this form, checks them for an id attribute,
30038      * and calls applyTo on the existing dom element with that id.
30039      * @return {BasicForm} this
30040      */
30041     render : function(){
30042         this.items.each(function(f){
30043             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
30044                 f.applyTo(f.id);
30045             }
30046         });
30047         return this;
30048     },
30049
30050     /**
30051      * Calls {@link Ext#apply} for all fields in this form with the passed object.
30052      * @param {Object} values
30053      * @return {BasicForm} this
30054      */
30055     applyToFields : function(o){
30056         this.items.each(function(f){
30057            Roo.apply(f, o);
30058         });
30059         return this;
30060     },
30061
30062     /**
30063      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
30064      * @param {Object} values
30065      * @return {BasicForm} this
30066      */
30067     applyIfToFields : function(o){
30068         this.items.each(function(f){
30069            Roo.applyIf(f, o);
30070         });
30071         return this;
30072     }
30073 });
30074
30075 // back compat
30076 Roo.BasicForm = Roo.form.BasicForm;
30077
30078 Roo.apply(Roo.form.BasicForm, {
30079     
30080     popover : {
30081         
30082         padding : 5,
30083         
30084         isApplied : false,
30085         
30086         isMasked : false,
30087         
30088         form : false,
30089         
30090         target : false,
30091         
30092         intervalID : false,
30093         
30094         maskEl : false,
30095         
30096         apply : function()
30097         {
30098             if(this.isApplied){
30099                 return;
30100             }
30101             
30102             this.maskEl = {
30103                 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
30104                 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
30105                 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
30106                 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
30107             };
30108             
30109             this.maskEl.top.enableDisplayMode("block");
30110             this.maskEl.left.enableDisplayMode("block");
30111             this.maskEl.bottom.enableDisplayMode("block");
30112             this.maskEl.right.enableDisplayMode("block");
30113             
30114             Roo.get(document.body).on('click', function(){
30115                 this.unmask();
30116             }, this);
30117             
30118             Roo.get(document.body).on('touchstart', function(){
30119                 this.unmask();
30120             }, this);
30121             
30122             this.isApplied = true
30123         },
30124         
30125         mask : function(form, target)
30126         {
30127             this.form = form;
30128             
30129             this.target = target;
30130             
30131             if(!this.form.errorMask || !target.el){
30132                 return;
30133             }
30134             
30135             var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
30136             
30137             var ot = this.target.el.calcOffsetsTo(scrollable);
30138             
30139             var scrollTo = ot[1] - this.form.maskOffset;
30140             
30141             scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
30142             
30143             scrollable.scrollTo('top', scrollTo);
30144             
30145             var el = this.target.wrap || this.target.el;
30146             
30147             var box = el.getBox();
30148             
30149             this.maskEl.top.setStyle('position', 'absolute');
30150             this.maskEl.top.setStyle('z-index', 10000);
30151             this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
30152             this.maskEl.top.setLeft(0);
30153             this.maskEl.top.setTop(0);
30154             this.maskEl.top.show();
30155             
30156             this.maskEl.left.setStyle('position', 'absolute');
30157             this.maskEl.left.setStyle('z-index', 10000);
30158             this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
30159             this.maskEl.left.setLeft(0);
30160             this.maskEl.left.setTop(box.y - this.padding);
30161             this.maskEl.left.show();
30162
30163             this.maskEl.bottom.setStyle('position', 'absolute');
30164             this.maskEl.bottom.setStyle('z-index', 10000);
30165             this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
30166             this.maskEl.bottom.setLeft(0);
30167             this.maskEl.bottom.setTop(box.bottom + this.padding);
30168             this.maskEl.bottom.show();
30169
30170             this.maskEl.right.setStyle('position', 'absolute');
30171             this.maskEl.right.setStyle('z-index', 10000);
30172             this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
30173             this.maskEl.right.setLeft(box.right + this.padding);
30174             this.maskEl.right.setTop(box.y - this.padding);
30175             this.maskEl.right.show();
30176
30177             this.intervalID = window.setInterval(function() {
30178                 Roo.form.BasicForm.popover.unmask();
30179             }, 10000);
30180
30181             window.onwheel = function(){ return false;};
30182             
30183             (function(){ this.isMasked = true; }).defer(500, this);
30184             
30185         },
30186         
30187         unmask : function()
30188         {
30189             if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
30190                 return;
30191             }
30192             
30193             this.maskEl.top.setStyle('position', 'absolute');
30194             this.maskEl.top.setSize(0, 0).setXY([0, 0]);
30195             this.maskEl.top.hide();
30196
30197             this.maskEl.left.setStyle('position', 'absolute');
30198             this.maskEl.left.setSize(0, 0).setXY([0, 0]);
30199             this.maskEl.left.hide();
30200
30201             this.maskEl.bottom.setStyle('position', 'absolute');
30202             this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
30203             this.maskEl.bottom.hide();
30204
30205             this.maskEl.right.setStyle('position', 'absolute');
30206             this.maskEl.right.setSize(0, 0).setXY([0, 0]);
30207             this.maskEl.right.hide();
30208             
30209             window.onwheel = function(){ return true;};
30210             
30211             if(this.intervalID){
30212                 window.clearInterval(this.intervalID);
30213                 this.intervalID = false;
30214             }
30215             
30216             this.isMasked = false;
30217             
30218         }
30219         
30220     }
30221     
30222 });/*
30223  * Based on:
30224  * Ext JS Library 1.1.1
30225  * Copyright(c) 2006-2007, Ext JS, LLC.
30226  *
30227  * Originally Released Under LGPL - original licence link has changed is not relivant.
30228  *
30229  * Fork - LGPL
30230  * <script type="text/javascript">
30231  */
30232
30233 /**
30234  * @class Roo.form.Form
30235  * @extends Roo.form.BasicForm
30236  * @children Roo.form.Column Roo.form.FieldSet Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem
30237  * Adds the ability to dynamically render forms with JavaScript to {@link Roo.form.BasicForm}.
30238  * @constructor
30239  * @param {Object} config Configuration options
30240  */
30241 Roo.form.Form = function(config){
30242     var xitems =  [];
30243     if (config.items) {
30244         xitems = config.items;
30245         delete config.items;
30246     }
30247    
30248     
30249     Roo.form.Form.superclass.constructor.call(this, null, config);
30250     this.url = this.url || this.action;
30251     if(!this.root){
30252         this.root = new Roo.form.Layout(Roo.applyIf({
30253             id: Roo.id()
30254         }, config));
30255     }
30256     this.active = this.root;
30257     /**
30258      * Array of all the buttons that have been added to this form via {@link addButton}
30259      * @type Array
30260      */
30261     this.buttons = [];
30262     this.allItems = [];
30263     this.addEvents({
30264         /**
30265          * @event clientvalidation
30266          * If the monitorValid config option is true, this event fires repetitively to notify of valid state
30267          * @param {Form} this
30268          * @param {Boolean} valid true if the form has passed client-side validation
30269          */
30270         clientvalidation: true,
30271         /**
30272          * @event rendered
30273          * Fires when the form is rendered
30274          * @param {Roo.form.Form} form
30275          */
30276         rendered : true
30277     });
30278     
30279     if (this.progressUrl) {
30280             // push a hidden field onto the list of fields..
30281             this.addxtype( {
30282                     xns: Roo.form, 
30283                     xtype : 'Hidden', 
30284                     name : 'UPLOAD_IDENTIFIER' 
30285             });
30286         }
30287         
30288     
30289     Roo.each(xitems, this.addxtype, this);
30290     
30291 };
30292
30293 Roo.extend(Roo.form.Form, Roo.form.BasicForm, {
30294      /**
30295      * @cfg {Roo.Button} buttons[] buttons at bottom of form
30296      */
30297     
30298     /**
30299      * @cfg {Number} labelWidth The width of labels. This property cascades to child containers.
30300      */
30301     /**
30302      * @cfg {String} itemCls A css class to apply to the x-form-item of fields. This property cascades to child containers.
30303      */
30304     /**
30305      * @cfg {String} (left|center|right) buttonAlign Valid values are "left," "center" and "right" (defaults to "center")
30306      */
30307     buttonAlign:'center',
30308
30309     /**
30310      * @cfg {Number} minButtonWidth Minimum width of all buttons in pixels (defaults to 75)
30311      */
30312     minButtonWidth:75,
30313
30314     /**
30315      * @cfg {String} labelAlign (left|top|right) Valid values are "left," "top" and "right" (defaults to "left").
30316      * This property cascades to child containers if not set.
30317      */
30318     labelAlign:'left',
30319
30320     /**
30321      * @cfg {Boolean} monitorValid If true the form monitors its valid state <b>client-side</b> and
30322      * fires a looping event with that state. This is required to bind buttons to the valid
30323      * state using the config value formBind:true on the button.
30324      */
30325     monitorValid : false,
30326
30327     /**
30328      * @cfg {Number} monitorPoll The milliseconds to poll valid state, ignored if monitorValid is not true (defaults to 200)
30329      */
30330     monitorPoll : 200,
30331     
30332     /**
30333      * @cfg {String} progressUrl - Url to return progress data 
30334      */
30335     
30336     progressUrl : false,
30337     /**
30338      * @cfg {boolean|FormData} formData - true to use new 'FormData' post, or set to a new FormData({dom form}) Object, if
30339      * sending a formdata with extra parameters - eg uploaded elements.
30340      */
30341     
30342     formData : false,
30343     
30344     /**
30345      * Opens a new {@link Roo.form.Column} container in the layout stack. If fields are passed after the config, the
30346      * fields are added and the column is closed. If no fields are passed the column remains open
30347      * until end() is called.
30348      * @param {Object} config The config to pass to the column
30349      * @param {Field} field1 (optional)
30350      * @param {Field} field2 (optional)
30351      * @param {Field} etc (optional)
30352      * @return Column The column container object
30353      */
30354     column : function(c){
30355         var col = new Roo.form.Column(c);
30356         this.start(col);
30357         if(arguments.length > 1){ // duplicate code required because of Opera
30358             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
30359             this.end();
30360         }
30361         return col;
30362     },
30363
30364     /**
30365      * Opens a new {@link Roo.form.FieldSet} container in the layout stack. If fields are passed after the config, the
30366      * fields are added and the fieldset is closed. If no fields are passed the fieldset remains open
30367      * until end() is called.
30368      * @param {Object} config The config to pass to the fieldset
30369      * @param {Field} field1 (optional)
30370      * @param {Field} field2 (optional)
30371      * @param {Field} etc (optional)
30372      * @return FieldSet The fieldset container object
30373      */
30374     fieldset : function(c){
30375         var fs = new Roo.form.FieldSet(c);
30376         this.start(fs);
30377         if(arguments.length > 1){ // duplicate code required because of Opera
30378             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
30379             this.end();
30380         }
30381         return fs;
30382     },
30383
30384     /**
30385      * Opens a new {@link Roo.form.Layout} container in the layout stack. If fields are passed after the config, the
30386      * fields are added and the container is closed. If no fields are passed the container remains open
30387      * until end() is called.
30388      * @param {Object} config The config to pass to the Layout
30389      * @param {Field} field1 (optional)
30390      * @param {Field} field2 (optional)
30391      * @param {Field} etc (optional)
30392      * @return Layout The container object
30393      */
30394     container : function(c){
30395         var l = new Roo.form.Layout(c);
30396         this.start(l);
30397         if(arguments.length > 1){ // duplicate code required because of Opera
30398             this.add.apply(this, Array.prototype.slice.call(arguments, 1));
30399             this.end();
30400         }
30401         return l;
30402     },
30403
30404     /**
30405      * Opens the passed container in the layout stack. The container can be any {@link Roo.form.Layout} or subclass.
30406      * @param {Object} container A Roo.form.Layout or subclass of Layout
30407      * @return {Form} this
30408      */
30409     start : function(c){
30410         // cascade label info
30411         Roo.applyIf(c, {'labelAlign': this.active.labelAlign, 'labelWidth': this.active.labelWidth, 'itemCls': this.active.itemCls});
30412         this.active.stack.push(c);
30413         c.ownerCt = this.active;
30414         this.active = c;
30415         return this;
30416     },
30417
30418     /**
30419      * Closes the current open container
30420      * @return {Form} this
30421      */
30422     end : function(){
30423         if(this.active == this.root){
30424             return this;
30425         }
30426         this.active = this.active.ownerCt;
30427         return this;
30428     },
30429
30430     /**
30431      * Add Roo.form components to the current open container (e.g. column, fieldset, etc.).  Fields added via this method
30432      * can also be passed with an additional property of fieldLabel, which if supplied, will provide the text to display
30433      * as the label of the field.
30434      * @param {Field} field1
30435      * @param {Field} field2 (optional)
30436      * @param {Field} etc. (optional)
30437      * @return {Form} this
30438      */
30439     add : function(){
30440         this.active.stack.push.apply(this.active.stack, arguments);
30441         this.allItems.push.apply(this.allItems,arguments);
30442         var r = [];
30443         for(var i = 0, a = arguments, len = a.length; i < len; i++) {
30444             if(a[i].isFormField){
30445                 r.push(a[i]);
30446             }
30447         }
30448         if(r.length > 0){
30449             Roo.form.Form.superclass.add.apply(this, r);
30450         }
30451         return this;
30452     },
30453     
30454
30455     
30456     
30457     
30458      /**
30459      * Find any element that has been added to a form, using it's ID or name
30460      * This can include framesets, columns etc. along with regular fields..
30461      * @param {String} id - id or name to find.
30462      
30463      * @return {Element} e - or false if nothing found.
30464      */
30465     findbyId : function(id)
30466     {
30467         var ret = false;
30468         if (!id) {
30469             return ret;
30470         }
30471         Roo.each(this.allItems, function(f){
30472             if (f.id == id || f.name == id ){
30473                 ret = f;
30474                 return false;
30475             }
30476         });
30477         return ret;
30478     },
30479
30480     
30481     
30482     /**
30483      * Render this form into the passed container. This should only be called once!
30484      * @param {String/HTMLElement/Element} container The element this component should be rendered into
30485      * @return {Form} this
30486      */
30487     render : function(ct)
30488     {
30489         
30490         
30491         
30492         ct = Roo.get(ct);
30493         var o = this.autoCreate || {
30494             tag: 'form',
30495             method : this.method || 'POST',
30496             id : this.id || Roo.id()
30497         };
30498         this.initEl(ct.createChild(o));
30499
30500         this.root.render(this.el);
30501         
30502        
30503              
30504         this.items.each(function(f){
30505             f.render('x-form-el-'+f.id);
30506         });
30507
30508         if(this.buttons.length > 0){
30509             // tables are required to maintain order and for correct IE layout
30510             var tb = this.el.createChild({cls:'x-form-btns-ct', cn: {
30511                 cls:"x-form-btns x-form-btns-"+this.buttonAlign,
30512                 html:'<table cellspacing="0"><tbody><tr></tr></tbody></table><div class="x-clear"></div>'
30513             }}, null, true);
30514             var tr = tb.getElementsByTagName('tr')[0];
30515             for(var i = 0, len = this.buttons.length; i < len; i++) {
30516                 var b = this.buttons[i];
30517                 var td = document.createElement('td');
30518                 td.className = 'x-form-btn-td';
30519                 b.render(tr.appendChild(td));
30520             }
30521         }
30522         if(this.monitorValid){ // initialize after render
30523             this.startMonitoring();
30524         }
30525         this.fireEvent('rendered', this);
30526         return this;
30527     },
30528
30529     /**
30530      * Adds a button to the footer of the form - this <b>must</b> be called before the form is rendered.
30531      * @param {String/Object} config A string becomes the button text, an object can either be a Button config
30532      * object or a valid Roo.DomHelper element config
30533      * @param {Function} handler The function called when the button is clicked
30534      * @param {Object} scope (optional) The scope of the handler function
30535      * @return {Roo.Button}
30536      */
30537     addButton : function(config, handler, scope){
30538         var bc = {
30539             handler: handler,
30540             scope: scope,
30541             minWidth: this.minButtonWidth,
30542             hideParent:true
30543         };
30544         if(typeof config == "string"){
30545             bc.text = config;
30546         }else{
30547             Roo.apply(bc, config);
30548         }
30549         var btn = new Roo.Button(null, bc);
30550         this.buttons.push(btn);
30551         return btn;
30552     },
30553
30554      /**
30555      * Adds a series of form elements (using the xtype property as the factory method.
30556      * Valid xtypes are:  TextField, TextArea .... Button, Layout, FieldSet, Column, (and 'end' to close a block)
30557      * @param {Object} config 
30558      */
30559     
30560     addxtype : function()
30561     {
30562         var ar = Array.prototype.slice.call(arguments, 0);
30563         var ret = false;
30564         for(var i = 0; i < ar.length; i++) {
30565             if (!ar[i]) {
30566                 continue; // skip -- if this happends something invalid got sent, we 
30567                 // should ignore it, as basically that interface element will not show up
30568                 // and that should be pretty obvious!!
30569             }
30570             
30571             if (Roo.form[ar[i].xtype]) {
30572                 ar[i].form = this;
30573                 var fe = Roo.factory(ar[i], Roo.form);
30574                 if (!ret) {
30575                     ret = fe;
30576                 }
30577                 fe.form = this;
30578                 if (fe.store) {
30579                     fe.store.form = this;
30580                 }
30581                 if (fe.isLayout) {  
30582                          
30583                     this.start(fe);
30584                     this.allItems.push(fe);
30585                     if (fe.items && fe.addxtype) {
30586                         fe.addxtype.apply(fe, fe.items);
30587                         delete fe.items;
30588                     }
30589                      this.end();
30590                     continue;
30591                 }
30592                 
30593                 
30594                  
30595                 this.add(fe);
30596               //  console.log('adding ' + ar[i].xtype);
30597             }
30598             if (ar[i].xtype == 'Button') {  
30599                 //console.log('adding button');
30600                 //console.log(ar[i]);
30601                 this.addButton(ar[i]);
30602                 this.allItems.push(fe);
30603                 continue;
30604             }
30605             
30606             if (ar[i].xtype == 'end') { // so we can add fieldsets... / layout etc.
30607                 alert('end is not supported on xtype any more, use items');
30608             //    this.end();
30609             //    //console.log('adding end');
30610             }
30611             
30612         }
30613         return ret;
30614     },
30615     
30616     /**
30617      * Starts monitoring of the valid state of this form. Usually this is done by passing the config
30618      * option "monitorValid"
30619      */
30620     startMonitoring : function(){
30621         if(!this.bound){
30622             this.bound = true;
30623             Roo.TaskMgr.start({
30624                 run : this.bindHandler,
30625                 interval : this.monitorPoll || 200,
30626                 scope: this
30627             });
30628         }
30629     },
30630
30631     /**
30632      * Stops monitoring of the valid state of this form
30633      */
30634     stopMonitoring : function(){
30635         this.bound = false;
30636     },
30637
30638     // private
30639     bindHandler : function(){
30640         if(!this.bound){
30641             return false; // stops binding
30642         }
30643         var valid = true;
30644         this.items.each(function(f){
30645             if(!f.isValid(true)){
30646                 valid = false;
30647                 return false;
30648             }
30649         });
30650         for(var i = 0, len = this.buttons.length; i < len; i++){
30651             var btn = this.buttons[i];
30652             if(btn.formBind === true && btn.disabled === valid){
30653                 btn.setDisabled(!valid);
30654             }
30655         }
30656         this.fireEvent('clientvalidation', this, valid);
30657     }
30658     
30659     
30660     
30661     
30662     
30663     
30664     
30665     
30666 });
30667
30668
30669 // back compat
30670 Roo.Form = Roo.form.Form;
30671 /*
30672  * Based on:
30673  * Ext JS Library 1.1.1
30674  * Copyright(c) 2006-2007, Ext JS, LLC.
30675  *
30676  * Originally Released Under LGPL - original licence link has changed is not relivant.
30677  *
30678  * Fork - LGPL
30679  * <script type="text/javascript">
30680  */
30681
30682 // as we use this in bootstrap.
30683 Roo.namespace('Roo.form');
30684  /**
30685  * @class Roo.form.Action
30686  * Internal Class used to handle form actions
30687  * @constructor
30688  * @param {Roo.form.BasicForm} el The form element or its id
30689  * @param {Object} config Configuration options
30690  */
30691
30692  
30693  
30694 // define the action interface
30695 Roo.form.Action = function(form, options){
30696     this.form = form;
30697     this.options = options || {};
30698 };
30699 /**
30700  * Client Validation Failed
30701  * @const 
30702  */
30703 Roo.form.Action.CLIENT_INVALID = 'client';
30704 /**
30705  * Server Validation Failed
30706  * @const 
30707  */
30708 Roo.form.Action.SERVER_INVALID = 'server';
30709  /**
30710  * Connect to Server Failed
30711  * @const 
30712  */
30713 Roo.form.Action.CONNECT_FAILURE = 'connect';
30714 /**
30715  * Reading Data from Server Failed
30716  * @const 
30717  */
30718 Roo.form.Action.LOAD_FAILURE = 'load';
30719
30720 Roo.form.Action.prototype = {
30721     type : 'default',
30722     failureType : undefined,
30723     response : undefined,
30724     result : undefined,
30725
30726     // interface method
30727     run : function(options){
30728
30729     },
30730
30731     // interface method
30732     success : function(response){
30733
30734     },
30735
30736     // interface method
30737     handleResponse : function(response){
30738
30739     },
30740
30741     // default connection failure
30742     failure : function(response){
30743         
30744         this.response = response;
30745         this.failureType = Roo.form.Action.CONNECT_FAILURE;
30746         this.form.afterAction(this, false);
30747     },
30748
30749     processResponse : function(response){
30750         this.response = response;
30751         if(!response.responseText){
30752             return true;
30753         }
30754         this.result = this.handleResponse(response);
30755         return this.result;
30756     },
30757
30758     // utility functions used internally
30759     getUrl : function(appendParams){
30760         var url = this.options.url || this.form.url || this.form.el.dom.action;
30761         if(appendParams){
30762             var p = this.getParams();
30763             if(p){
30764                 url += (url.indexOf('?') != -1 ? '&' : '?') + p;
30765             }
30766         }
30767         return url;
30768     },
30769
30770     getMethod : function(){
30771         return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
30772     },
30773
30774     getParams : function(){
30775         var bp = this.form.baseParams;
30776         var p = this.options.params;
30777         if(p){
30778             if(typeof p == "object"){
30779                 p = Roo.urlEncode(Roo.applyIf(p, bp));
30780             }else if(typeof p == 'string' && bp){
30781                 p += '&' + Roo.urlEncode(bp);
30782             }
30783         }else if(bp){
30784             p = Roo.urlEncode(bp);
30785         }
30786         return p;
30787     },
30788
30789     createCallback : function(){
30790         return {
30791             success: this.success,
30792             failure: this.failure,
30793             scope: this,
30794             timeout: (this.form.timeout*1000),
30795             upload: this.form.fileUpload ? this.success : undefined
30796         };
30797     }
30798 };
30799
30800 Roo.form.Action.Submit = function(form, options){
30801     Roo.form.Action.Submit.superclass.constructor.call(this, form, options);
30802 };
30803
30804 Roo.extend(Roo.form.Action.Submit, Roo.form.Action, {
30805     type : 'submit',
30806
30807     haveProgress : false,
30808     uploadComplete : false,
30809     
30810     // uploadProgress indicator.
30811     uploadProgress : function()
30812     {
30813         if (!this.form.progressUrl) {
30814             return;
30815         }
30816         
30817         if (!this.haveProgress) {
30818             Roo.MessageBox.progress("Uploading", "Uploading");
30819         }
30820         if (this.uploadComplete) {
30821            Roo.MessageBox.hide();
30822            return;
30823         }
30824         
30825         this.haveProgress = true;
30826    
30827         var uid = this.form.findField('UPLOAD_IDENTIFIER').getValue();
30828         
30829         var c = new Roo.data.Connection();
30830         c.request({
30831             url : this.form.progressUrl,
30832             params: {
30833                 id : uid
30834             },
30835             method: 'GET',
30836             success : function(req){
30837                //console.log(data);
30838                 var rdata = false;
30839                 var edata;
30840                 try  {
30841                    rdata = Roo.decode(req.responseText)
30842                 } catch (e) {
30843                     Roo.log("Invalid data from server..");
30844                     Roo.log(edata);
30845                     return;
30846                 }
30847                 if (!rdata || !rdata.success) {
30848                     Roo.log(rdata);
30849                     Roo.MessageBox.alert(Roo.encode(rdata));
30850                     return;
30851                 }
30852                 var data = rdata.data;
30853                 
30854                 if (this.uploadComplete) {
30855                    Roo.MessageBox.hide();
30856                    return;
30857                 }
30858                    
30859                 if (data){
30860                     Roo.MessageBox.updateProgress(data.bytes_uploaded/data.bytes_total,
30861                        Math.floor((data.bytes_total - data.bytes_uploaded)/1000) + 'k remaining'
30862                     );
30863                 }
30864                 this.uploadProgress.defer(2000,this);
30865             },
30866        
30867             failure: function(data) {
30868                 Roo.log('progress url failed ');
30869                 Roo.log(data);
30870             },
30871             scope : this
30872         });
30873            
30874     },
30875     
30876     
30877     run : function()
30878     {
30879         // run get Values on the form, so it syncs any secondary forms.
30880         this.form.getValues();
30881         
30882         var o = this.options;
30883         var method = this.getMethod();
30884         var isPost = method == 'POST';
30885         if(o.clientValidation === false || this.form.isValid()){
30886             
30887             if (this.form.progressUrl) {
30888                 this.form.findField('UPLOAD_IDENTIFIER').setValue(
30889                     (new Date() * 1) + '' + Math.random());
30890                     
30891             } 
30892             
30893             
30894             Roo.Ajax.request(Roo.apply(this.createCallback(), {
30895                 form:this.form.el.dom,
30896                 url:this.getUrl(!isPost),
30897                 method: method,
30898                 params:isPost ? this.getParams() : null,
30899                 isUpload: this.form.fileUpload,
30900                 formData : this.form.formData
30901             }));
30902             
30903             this.uploadProgress();
30904
30905         }else if (o.clientValidation !== false){ // client validation failed
30906             this.failureType = Roo.form.Action.CLIENT_INVALID;
30907             this.form.afterAction(this, false);
30908         }
30909     },
30910
30911     success : function(response)
30912     {
30913         this.uploadComplete= true;
30914         if (this.haveProgress) {
30915             Roo.MessageBox.hide();
30916         }
30917         
30918         
30919         var result = this.processResponse(response);
30920         if(result === true || result.success){
30921             this.form.afterAction(this, true);
30922             return;
30923         }
30924         if(result.errors){
30925             this.form.markInvalid(result.errors);
30926             this.failureType = Roo.form.Action.SERVER_INVALID;
30927         }
30928         this.form.afterAction(this, false);
30929     },
30930     failure : function(response)
30931     {
30932         this.uploadComplete= true;
30933         if (this.haveProgress) {
30934             Roo.MessageBox.hide();
30935         }
30936         
30937         this.response = response;
30938         this.failureType = Roo.form.Action.CONNECT_FAILURE;
30939         this.form.afterAction(this, false);
30940     },
30941     
30942     handleResponse : function(response){
30943         if(this.form.errorReader){
30944             var rs = this.form.errorReader.read(response);
30945             var errors = [];
30946             if(rs.records){
30947                 for(var i = 0, len = rs.records.length; i < len; i++) {
30948                     var r = rs.records[i];
30949                     errors[i] = r.data;
30950                 }
30951             }
30952             if(errors.length < 1){
30953                 errors = null;
30954             }
30955             return {
30956                 success : rs.success,
30957                 errors : errors
30958             };
30959         }
30960         var ret = false;
30961         try {
30962             ret = Roo.decode(response.responseText);
30963         } catch (e) {
30964             ret = {
30965                 success: false,
30966                 errorMsg: "Failed to read server message: " + (response ? response.responseText : ' - no message'),
30967                 errors : []
30968             };
30969         }
30970         return ret;
30971         
30972     }
30973 });
30974
30975
30976 Roo.form.Action.Load = function(form, options){
30977     Roo.form.Action.Load.superclass.constructor.call(this, form, options);
30978     this.reader = this.form.reader;
30979 };
30980
30981 Roo.extend(Roo.form.Action.Load, Roo.form.Action, {
30982     type : 'load',
30983
30984     run : function(){
30985         
30986         Roo.Ajax.request(Roo.apply(
30987                 this.createCallback(), {
30988                     method:this.getMethod(),
30989                     url:this.getUrl(false),
30990                     params:this.getParams()
30991         }));
30992     },
30993
30994     success : function(response){
30995         
30996         var result = this.processResponse(response);
30997         if(result === true || !result.success || !result.data){
30998             this.failureType = Roo.form.Action.LOAD_FAILURE;
30999             this.form.afterAction(this, false);
31000             return;
31001         }
31002         this.form.clearInvalid();
31003         this.form.setValues(result.data);
31004         this.form.afterAction(this, true);
31005     },
31006
31007     handleResponse : function(response){
31008         if(this.form.reader){
31009             var rs = this.form.reader.read(response);
31010             var data = rs.records && rs.records[0] ? rs.records[0].data : null;
31011             return {
31012                 success : rs.success,
31013                 data : data
31014             };
31015         }
31016         return Roo.decode(response.responseText);
31017     }
31018 });
31019
31020 Roo.form.Action.ACTION_TYPES = {
31021     'load' : Roo.form.Action.Load,
31022     'submit' : Roo.form.Action.Submit
31023 };/*
31024  * Based on:
31025  * Ext JS Library 1.1.1
31026  * Copyright(c) 2006-2007, Ext JS, LLC.
31027  *
31028  * Originally Released Under LGPL - original licence link has changed is not relivant.
31029  *
31030  * Fork - LGPL
31031  * <script type="text/javascript">
31032  */
31033  
31034 /**
31035  * @class Roo.form.Layout
31036  * @extends Roo.Component
31037  * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
31038  * Creates a container for layout and rendering of fields in an {@link Roo.form.Form}.
31039  * @constructor
31040  * @param {Object} config Configuration options
31041  */
31042 Roo.form.Layout = function(config){
31043     var xitems = [];
31044     if (config.items) {
31045         xitems = config.items;
31046         delete config.items;
31047     }
31048     Roo.form.Layout.superclass.constructor.call(this, config);
31049     this.stack = [];
31050     Roo.each(xitems, this.addxtype, this);
31051      
31052 };
31053
31054 Roo.extend(Roo.form.Layout, Roo.Component, {
31055     /**
31056      * @cfg {String/Object} autoCreate
31057      * A DomHelper element spec used to autocreate the layout (defaults to {tag: 'div', cls: 'x-form-ct'})
31058      */
31059     /**
31060      * @cfg {String/Object/Function} style
31061      * A style specification string, e.g. "width:100px", or object in the form {width:"100px"}, or
31062      * a function which returns such a specification.
31063      */
31064     /**
31065      * @cfg {String} labelAlign (left|top|right)
31066      * Valid values are "left," "top" and "right" (defaults to "left")
31067      */
31068     /**
31069      * @cfg {Number} labelWidth
31070      * Fixed width in pixels of all field labels (defaults to undefined)
31071      */
31072     /**
31073      * @cfg {Boolean} clear
31074      * True to add a clearing element at the end of this layout, equivalent to CSS clear: both (defaults to true)
31075      */
31076     clear : true,
31077     /**
31078      * @cfg {String} labelSeparator
31079      * The separator to use after field labels (defaults to ':')
31080      */
31081     labelSeparator : ':',
31082     /**
31083      * @cfg {Boolean} hideLabels
31084      * True to suppress the display of field labels in this layout (defaults to false)
31085      */
31086     hideLabels : false,
31087
31088     // private
31089     defaultAutoCreate : {tag: 'div', cls: 'x-form-ct'},
31090     
31091     isLayout : true,
31092     
31093     // private
31094     onRender : function(ct, position){
31095         if(this.el){ // from markup
31096             this.el = Roo.get(this.el);
31097         }else {  // generate
31098             var cfg = this.getAutoCreate();
31099             this.el = ct.createChild(cfg, position);
31100         }
31101         if(this.style){
31102             this.el.applyStyles(this.style);
31103         }
31104         if(this.labelAlign){
31105             this.el.addClass('x-form-label-'+this.labelAlign);
31106         }
31107         if(this.hideLabels){
31108             this.labelStyle = "display:none";
31109             this.elementStyle = "padding-left:0;";
31110         }else{
31111             if(typeof this.labelWidth == 'number'){
31112                 this.labelStyle = "width:"+this.labelWidth+"px;";
31113                 this.elementStyle = "padding-left:"+((this.labelWidth+(typeof this.labelPad == 'number' ? this.labelPad : 5))+'px')+";";
31114             }
31115             if(this.labelAlign == 'top'){
31116                 this.labelStyle = "width:auto;";
31117                 this.elementStyle = "padding-left:0;";
31118             }
31119         }
31120         var stack = this.stack;
31121         var slen = stack.length;
31122         if(slen > 0){
31123             if(!this.fieldTpl){
31124                 var t = new Roo.Template(
31125                     '<div class="x-form-item {5}">',
31126                         '<label for="{0}" style="{2}">{1}{4}</label>',
31127                         '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
31128                         '</div>',
31129                     '</div><div class="x-form-clear-left"></div>'
31130                 );
31131                 t.disableFormats = true;
31132                 t.compile();
31133                 Roo.form.Layout.prototype.fieldTpl = t;
31134             }
31135             for(var i = 0; i < slen; i++) {
31136                 if(stack[i].isFormField){
31137                     this.renderField(stack[i]);
31138                 }else{
31139                     this.renderComponent(stack[i]);
31140                 }
31141             }
31142         }
31143         if(this.clear){
31144             this.el.createChild({cls:'x-form-clear'});
31145         }
31146     },
31147
31148     // private
31149     renderField : function(f){
31150         f.fieldEl = Roo.get(this.fieldTpl.append(this.el, [
31151                f.id, //0
31152                f.fieldLabel, //1
31153                f.labelStyle||this.labelStyle||'', //2
31154                this.elementStyle||'', //3
31155                typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator, //4
31156                f.itemCls||this.itemCls||''  //5
31157        ], true).getPrevSibling());
31158     },
31159
31160     // private
31161     renderComponent : function(c){
31162         c.render(c.isLayout ? this.el : this.el.createChild());    
31163     },
31164     /**
31165      * Adds a object form elements (using the xtype property as the factory method.)
31166      * Valid xtypes are:  TextField, TextArea .... Button, Layout, FieldSet, Column
31167      * @param {Object} config 
31168      */
31169     addxtype : function(o)
31170     {
31171         // create the lement.
31172         o.form = this.form;
31173         var fe = Roo.factory(o, Roo.form);
31174         this.form.allItems.push(fe);
31175         this.stack.push(fe);
31176         
31177         if (fe.isFormField) {
31178             this.form.items.add(fe);
31179         }
31180          
31181         return fe;
31182     }
31183 });
31184
31185
31186 /**
31187  * @class Roo.form.Column
31188  * @extends Roo.form.Layout
31189  * @children Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
31190  * Creates a column container for layout and rendering of fields in an {@link Roo.form.Form}.
31191  * @constructor
31192  * @param {Object} config Configuration options
31193  */
31194 Roo.form.Column = function(config){
31195     Roo.form.Column.superclass.constructor.call(this, config);
31196 };
31197
31198 Roo.extend(Roo.form.Column, Roo.form.Layout, {
31199     /**
31200      * @cfg {Number/String} width
31201      * The fixed width of the column in pixels or CSS value (defaults to "auto")
31202      */
31203     /**
31204      * @cfg {String/Object} autoCreate
31205      * A DomHelper element spec used to autocreate the column (defaults to {tag: 'div', cls: 'x-form-ct x-form-column'})
31206      */
31207
31208     // private
31209     defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-column'},
31210
31211     // private
31212     onRender : function(ct, position){
31213         Roo.form.Column.superclass.onRender.call(this, ct, position);
31214         if(this.width){
31215             this.el.setWidth(this.width);
31216         }
31217     }
31218 });
31219
31220 /**
31221  * @class Roo.form.Row
31222  * @extends Roo.form.Layout
31223  * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem Roo.form.FieldSet
31224  * Creates a row container for layout and rendering of fields in an {@link Roo.form.Form}.
31225  * @constructor
31226  * @param {Object} config Configuration options
31227  */
31228
31229  
31230 Roo.form.Row = function(config){
31231     Roo.form.Row.superclass.constructor.call(this, config);
31232 };
31233  
31234 Roo.extend(Roo.form.Row, Roo.form.Layout, {
31235       /**
31236      * @cfg {Number/String} width
31237      * The fixed width of the column in pixels or CSS value (defaults to "auto")
31238      */
31239     /**
31240      * @cfg {Number/String} height
31241      * The fixed height of the column in pixels or CSS value (defaults to "auto")
31242      */
31243     defaultAutoCreate : {tag: 'div', cls: 'x-form-ct x-form-row'},
31244     
31245     padWidth : 20,
31246     // private
31247     onRender : function(ct, position){
31248         //console.log('row render');
31249         if(!this.rowTpl){
31250             var t = new Roo.Template(
31251                 '<div class="x-form-item {5}" style="float:left;width:{6}px">',
31252                     '<label for="{0}" style="{2}">{1}{4}</label>',
31253                     '<div class="x-form-element" id="x-form-el-{0}" style="{3}">',
31254                     '</div>',
31255                 '</div>'
31256             );
31257             t.disableFormats = true;
31258             t.compile();
31259             Roo.form.Layout.prototype.rowTpl = t;
31260         }
31261         this.fieldTpl = this.rowTpl;
31262         
31263         //console.log('lw' + this.labelWidth +', la:' + this.labelAlign);
31264         var labelWidth = 100;
31265         
31266         if ((this.labelAlign != 'top')) {
31267             if (typeof this.labelWidth == 'number') {
31268                 labelWidth = this.labelWidth
31269             }
31270             this.padWidth =  20 + labelWidth;
31271             
31272         }
31273         
31274         Roo.form.Column.superclass.onRender.call(this, ct, position);
31275         if(this.width){
31276             this.el.setWidth(this.width);
31277         }
31278         if(this.height){
31279             this.el.setHeight(this.height);
31280         }
31281     },
31282     
31283     // private
31284     renderField : function(f){
31285         f.fieldEl = this.fieldTpl.append(this.el, [
31286                f.id, f.fieldLabel,
31287                f.labelStyle||this.labelStyle||'',
31288                this.elementStyle||'',
31289                typeof f.labelSeparator == 'undefined' ? this.labelSeparator : f.labelSeparator,
31290                f.itemCls||this.itemCls||'',
31291                f.width ? f.width + this.padWidth : 160 + this.padWidth
31292        ],true);
31293     }
31294 });
31295  
31296
31297 /**
31298  * @class Roo.form.FieldSet
31299  * @extends Roo.form.Layout
31300  * @children Roo.form.Column Roo.form.Row Roo.form.Field Roo.Button Roo.form.TextItem
31301  * Creates a fieldset container for layout and rendering of fields in an {@link Roo.form.Form}.
31302  * @constructor
31303  * @param {Object} config Configuration options
31304  */
31305 Roo.form.FieldSet = function(config){
31306     Roo.form.FieldSet.superclass.constructor.call(this, config);
31307 };
31308
31309 Roo.extend(Roo.form.FieldSet, Roo.form.Layout, {
31310     /**
31311      * @cfg {String} legend
31312      * The text to display as the legend for the FieldSet (defaults to '')
31313      */
31314     /**
31315      * @cfg {String/Object} autoCreate
31316      * A DomHelper element spec used to autocreate the fieldset (defaults to {tag: 'fieldset', cn: {tag:'legend'}})
31317      */
31318
31319     // private
31320     defaultAutoCreate : {tag: 'fieldset', cn: {tag:'legend'}},
31321
31322     // private
31323     onRender : function(ct, position){
31324         Roo.form.FieldSet.superclass.onRender.call(this, ct, position);
31325         if(this.legend){
31326             this.setLegend(this.legend);
31327         }
31328     },
31329
31330     // private
31331     setLegend : function(text){
31332         if(this.rendered){
31333             this.el.child('legend').update(text);
31334         }
31335     }
31336 });/*
31337  * Based on:
31338  * Ext JS Library 1.1.1
31339  * Copyright(c) 2006-2007, Ext JS, LLC.
31340  *
31341  * Originally Released Under LGPL - original licence link has changed is not relivant.
31342  *
31343  * Fork - LGPL
31344  * <script type="text/javascript">
31345  */
31346 /**
31347  * @class Roo.form.VTypes
31348  * Overridable validation definitions. The validations provided are basic and intended to be easily customizable and extended.
31349  * @static
31350  */
31351 Roo.form.VTypes = function(){
31352     // closure these in so they are only created once.
31353     var alpha = /^[a-zA-Z_]+$/;
31354     var alphanum = /^[a-zA-Z0-9_]+$/;
31355     var email = /^([\w]+)(.[\w]+)*@([\w-]+\.){1,5}([A-Za-z]){2,24}$/;
31356     var url = /(((https?)|(ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
31357
31358     // All these messages and functions are configurable
31359     return {
31360         /**
31361          * The function used to validate email addresses
31362          * @param {String} value The email address
31363          */
31364         'email' : function(v){
31365             return email.test(v);
31366         },
31367         /**
31368          * The error text to display when the email validation function returns false
31369          * @type String
31370          */
31371         'emailText' : 'This field should be an e-mail address in the format "user@domain.com"',
31372         /**
31373          * The keystroke filter mask to be applied on email input
31374          * @type RegExp
31375          */
31376         'emailMask' : /[a-z0-9_\.\-@]/i,
31377
31378         /**
31379          * The function used to validate URLs
31380          * @param {String} value The URL
31381          */
31382         'url' : function(v){
31383             return url.test(v);
31384         },
31385         /**
31386          * The error text to display when the url validation function returns false
31387          * @type String
31388          */
31389         'urlText' : 'This field should be a URL in the format "http:/'+'/www.domain.com"',
31390         
31391         /**
31392          * The function used to validate alpha values
31393          * @param {String} value The value
31394          */
31395         'alpha' : function(v){
31396             return alpha.test(v);
31397         },
31398         /**
31399          * The error text to display when the alpha validation function returns false
31400          * @type String
31401          */
31402         'alphaText' : 'This field should only contain letters and _',
31403         /**
31404          * The keystroke filter mask to be applied on alpha input
31405          * @type RegExp
31406          */
31407         'alphaMask' : /[a-z_]/i,
31408
31409         /**
31410          * The function used to validate alphanumeric values
31411          * @param {String} value The value
31412          */
31413         'alphanum' : function(v){
31414             return alphanum.test(v);
31415         },
31416         /**
31417          * The error text to display when the alphanumeric validation function returns false
31418          * @type String
31419          */
31420         'alphanumText' : 'This field should only contain letters, numbers and _',
31421         /**
31422          * The keystroke filter mask to be applied on alphanumeric input
31423          * @type RegExp
31424          */
31425         'alphanumMask' : /[a-z0-9_]/i
31426     };
31427 }();//<script type="text/javascript">
31428
31429 /**
31430  * @class Roo.form.FCKeditor
31431  * @extends Roo.form.TextArea
31432  * Wrapper around the FCKEditor http://www.fckeditor.net
31433  * @constructor
31434  * Creates a new FCKeditor
31435  * @param {Object} config Configuration options
31436  */
31437 Roo.form.FCKeditor = function(config){
31438     Roo.form.FCKeditor.superclass.constructor.call(this, config);
31439     this.addEvents({
31440          /**
31441          * @event editorinit
31442          * Fired when the editor is initialized - you can add extra handlers here..
31443          * @param {FCKeditor} this
31444          * @param {Object} the FCK object.
31445          */
31446         editorinit : true
31447     });
31448     
31449     
31450 };
31451 Roo.form.FCKeditor.editors = { };
31452 Roo.extend(Roo.form.FCKeditor, Roo.form.TextArea,
31453 {
31454     //defaultAutoCreate : {
31455     //    tag : "textarea",style   : "width:100px;height:60px;" ,autocomplete    : "off"
31456     //},
31457     // private
31458     /**
31459      * @cfg {Object} fck options - see fck manual for details.
31460      */
31461     fckconfig : false,
31462     
31463     /**
31464      * @cfg {Object} fck toolbar set (Basic or Default)
31465      */
31466     toolbarSet : 'Basic',
31467     /**
31468      * @cfg {Object} fck BasePath
31469      */ 
31470     basePath : '/fckeditor/',
31471     
31472     
31473     frame : false,
31474     
31475     value : '',
31476     
31477    
31478     onRender : function(ct, position)
31479     {
31480         if(!this.el){
31481             this.defaultAutoCreate = {
31482                 tag: "textarea",
31483                 style:"width:300px;height:60px;",
31484                 autocomplete: "new-password"
31485             };
31486         }
31487         Roo.form.FCKeditor.superclass.onRender.call(this, ct, position);
31488         /*
31489         if(this.grow){
31490             this.textSizeEl = Roo.DomHelper.append(document.body, {tag: "pre", cls: "x-form-grow-sizer"});
31491             if(this.preventScrollbars){
31492                 this.el.setStyle("overflow", "hidden");
31493             }
31494             this.el.setHeight(this.growMin);
31495         }
31496         */
31497         //console.log('onrender' + this.getId() );
31498         Roo.form.FCKeditor.editors[this.getId()] = this;
31499          
31500
31501         this.replaceTextarea() ;
31502         
31503     },
31504     
31505     getEditor : function() {
31506         return this.fckEditor;
31507     },
31508     /**
31509      * Sets a data value into the field and validates it.  To set the value directly without validation see {@link #setRawValue}.
31510      * @param {Mixed} value The value to set
31511      */
31512     
31513     
31514     setValue : function(value)
31515     {
31516         //console.log('setValue: ' + value);
31517         
31518         if(typeof(value) == 'undefined') { // not sure why this is happending...
31519             return;
31520         }
31521         Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
31522         
31523         //if(!this.el || !this.getEditor()) {
31524         //    this.value = value;
31525             //this.setValue.defer(100,this,[value]);    
31526         //    return;
31527         //} 
31528         
31529         if(!this.getEditor()) {
31530             return;
31531         }
31532         
31533         this.getEditor().SetData(value);
31534         
31535         //
31536
31537     },
31538
31539     /**
31540      * Returns the normalized data value (undefined or emptyText will be returned as '').  To return the raw value see {@link #getRawValue}.
31541      * @return {Mixed} value The field value
31542      */
31543     getValue : function()
31544     {
31545         
31546         if (this.frame && this.frame.dom.style.display == 'none') {
31547             return Roo.form.FCKeditor.superclass.getValue.call(this);
31548         }
31549         
31550         if(!this.el || !this.getEditor()) {
31551            
31552            // this.getValue.defer(100,this); 
31553             return this.value;
31554         }
31555        
31556         
31557         var value=this.getEditor().GetData();
31558         Roo.form.FCKeditor.superclass.setValue.apply(this,[value]);
31559         return Roo.form.FCKeditor.superclass.getValue.call(this);
31560         
31561
31562     },
31563
31564     /**
31565      * Returns the raw data value which may or may not be a valid, defined value.  To return a normalized value see {@link #getValue}.
31566      * @return {Mixed} value The field value
31567      */
31568     getRawValue : function()
31569     {
31570         if (this.frame && this.frame.dom.style.display == 'none') {
31571             return Roo.form.FCKeditor.superclass.getRawValue.call(this);
31572         }
31573         
31574         if(!this.el || !this.getEditor()) {
31575             //this.getRawValue.defer(100,this); 
31576             return this.value;
31577             return;
31578         }
31579         
31580         
31581         
31582         var value=this.getEditor().GetData();
31583         Roo.form.FCKeditor.superclass.setRawValue.apply(this,[value]);
31584         return Roo.form.FCKeditor.superclass.getRawValue.call(this);
31585          
31586     },
31587     
31588     setSize : function(w,h) {
31589         
31590         
31591         
31592         //if (this.frame && this.frame.dom.style.display == 'none') {
31593         //    Roo.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
31594         //    return;
31595         //}
31596         //if(!this.el || !this.getEditor()) {
31597         //    this.setSize.defer(100,this, [w,h]); 
31598         //    return;
31599         //}
31600         
31601         
31602         
31603         Roo.form.FCKeditor.superclass.setSize.apply(this, [w, h]);
31604         
31605         this.frame.dom.setAttribute('width', w);
31606         this.frame.dom.setAttribute('height', h);
31607         this.frame.setSize(w,h);
31608         
31609     },
31610     
31611     toggleSourceEdit : function(value) {
31612         
31613       
31614          
31615         this.el.dom.style.display = value ? '' : 'none';
31616         this.frame.dom.style.display = value ?  'none' : '';
31617         
31618     },
31619     
31620     
31621     focus: function(tag)
31622     {
31623         if (this.frame.dom.style.display == 'none') {
31624             return Roo.form.FCKeditor.superclass.focus.call(this);
31625         }
31626         if(!this.el || !this.getEditor()) {
31627             this.focus.defer(100,this, [tag]); 
31628             return;
31629         }
31630         
31631         
31632         
31633         
31634         var tgs = this.getEditor().EditorDocument.getElementsByTagName(tag);
31635         this.getEditor().Focus();
31636         if (tgs.length) {
31637             if (!this.getEditor().Selection.GetSelection()) {
31638                 this.focus.defer(100,this, [tag]); 
31639                 return;
31640             }
31641             
31642             
31643             var r = this.getEditor().EditorDocument.createRange();
31644             r.setStart(tgs[0],0);
31645             r.setEnd(tgs[0],0);
31646             this.getEditor().Selection.GetSelection().removeAllRanges();
31647             this.getEditor().Selection.GetSelection().addRange(r);
31648             this.getEditor().Focus();
31649         }
31650         
31651     },
31652     
31653     
31654     
31655     replaceTextarea : function()
31656     {
31657         if ( document.getElementById( this.getId() + '___Frame' ) ) {
31658             return ;
31659         }
31660         //if ( !this.checkBrowser || this._isCompatibleBrowser() )
31661         //{
31662             // We must check the elements firstly using the Id and then the name.
31663         var oTextarea = document.getElementById( this.getId() );
31664         
31665         var colElementsByName = document.getElementsByName( this.getId() ) ;
31666          
31667         oTextarea.style.display = 'none' ;
31668
31669         if ( oTextarea.tabIndex ) {            
31670             this.TabIndex = oTextarea.tabIndex ;
31671         }
31672         
31673         this._insertHtmlBefore( this._getConfigHtml(), oTextarea ) ;
31674         this._insertHtmlBefore( this._getIFrameHtml(), oTextarea ) ;
31675         this.frame = Roo.get(this.getId() + '___Frame')
31676     },
31677     
31678     _getConfigHtml : function()
31679     {
31680         var sConfig = '' ;
31681
31682         for ( var o in this.fckconfig ) {
31683             sConfig += sConfig.length > 0  ? '&amp;' : '';
31684             sConfig += encodeURIComponent( o ) + '=' + encodeURIComponent( this.fckconfig[o] ) ;
31685         }
31686
31687         return '<input type="hidden" id="' + this.getId() + '___Config" value="' + sConfig + '" style="display:none" />' ;
31688     },
31689     
31690     
31691     _getIFrameHtml : function()
31692     {
31693         var sFile = 'fckeditor.html' ;
31694         /* no idea what this is about..
31695         try
31696         {
31697             if ( (/fcksource=true/i).test( window.top.location.search ) )
31698                 sFile = 'fckeditor.original.html' ;
31699         }
31700         catch (e) { 
31701         */
31702
31703         var sLink = this.basePath + 'editor/' + sFile + '?InstanceName=' + encodeURIComponent( this.getId() ) ;
31704         sLink += this.toolbarSet ? ( '&amp;Toolbar=' + this.toolbarSet)  : '';
31705         
31706         
31707         var html = '<iframe id="' + this.getId() +
31708             '___Frame" src="' + sLink +
31709             '" width="' + this.width +
31710             '" height="' + this.height + '"' +
31711             (this.tabIndex ?  ' tabindex="' + this.tabIndex + '"' :'' ) +
31712             ' frameborder="0" scrolling="no"></iframe>' ;
31713
31714         return html ;
31715     },
31716     
31717     _insertHtmlBefore : function( html, element )
31718     {
31719         if ( element.insertAdjacentHTML )       {
31720             // IE
31721             element.insertAdjacentHTML( 'beforeBegin', html ) ;
31722         } else { // Gecko
31723             var oRange = document.createRange() ;
31724             oRange.setStartBefore( element ) ;
31725             var oFragment = oRange.createContextualFragment( html );
31726             element.parentNode.insertBefore( oFragment, element ) ;
31727         }
31728     }
31729     
31730     
31731   
31732     
31733     
31734     
31735     
31736
31737 });
31738
31739 //Roo.reg('fckeditor', Roo.form.FCKeditor);
31740
31741 function FCKeditor_OnComplete(editorInstance){
31742     var f = Roo.form.FCKeditor.editors[editorInstance.Name];
31743     f.fckEditor = editorInstance;
31744     //console.log("loaded");
31745     f.fireEvent('editorinit', f, editorInstance);
31746
31747   
31748
31749  
31750
31751
31752
31753
31754
31755
31756
31757
31758
31759
31760
31761
31762
31763
31764
31765 //<script type="text/javascript">
31766 /**
31767  * @class Roo.form.GridField
31768  * @extends Roo.form.Field
31769  * Embed a grid (or editable grid into a form)
31770  * STATUS ALPHA
31771  * 
31772  * This embeds a grid in a form, the value of the field should be the json encoded array of rows
31773  * it needs 
31774  * xgrid.store = Roo.data.Store
31775  * xgrid.store.proxy = Roo.data.MemoryProxy (data = [] )
31776  * xgrid.store.reader = Roo.data.JsonReader 
31777  * 
31778  * 
31779  * @constructor
31780  * Creates a new GridField
31781  * @param {Object} config Configuration options
31782  */
31783 Roo.form.GridField = function(config){
31784     Roo.form.GridField.superclass.constructor.call(this, config);
31785      
31786 };
31787
31788 Roo.extend(Roo.form.GridField, Roo.form.Field,  {
31789     /**
31790      * @cfg {Number} width  - used to restrict width of grid..
31791      */
31792     width : 100,
31793     /**
31794      * @cfg {Number} height - used to restrict height of grid..
31795      */
31796     height : 50,
31797      /**
31798      * @cfg {Object} xgrid (xtype'd description of grid) { xtype : 'Grid', dataSource: .... }
31799          * 
31800          *}
31801      */
31802     xgrid : false, 
31803     /**
31804      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
31805      * {tag: "input", type: "checkbox", autocomplete: "off"})
31806      */
31807    // defaultAutoCreate : { tag: 'div' },
31808     defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'new-password'},
31809     /**
31810      * @cfg {String} addTitle Text to include for adding a title.
31811      */
31812     addTitle : false,
31813     //
31814     onResize : function(){
31815         Roo.form.Field.superclass.onResize.apply(this, arguments);
31816     },
31817
31818     initEvents : function(){
31819         // Roo.form.Checkbox.superclass.initEvents.call(this);
31820         // has no events...
31821        
31822     },
31823
31824
31825     getResizeEl : function(){
31826         return this.wrap;
31827     },
31828
31829     getPositionEl : function(){
31830         return this.wrap;
31831     },
31832
31833     // private
31834     onRender : function(ct, position){
31835         
31836         this.style = this.style || 'overflow: hidden; border:1px solid #c3daf9;';
31837         var style = this.style;
31838         delete this.style;
31839         
31840         Roo.form.GridField.superclass.onRender.call(this, ct, position);
31841         this.wrap = this.el.wrap({cls: ''}); // not sure why ive done thsi...
31842         this.viewEl = this.wrap.createChild({ tag: 'div' });
31843         if (style) {
31844             this.viewEl.applyStyles(style);
31845         }
31846         if (this.width) {
31847             this.viewEl.setWidth(this.width);
31848         }
31849         if (this.height) {
31850             this.viewEl.setHeight(this.height);
31851         }
31852         //if(this.inputValue !== undefined){
31853         //this.setValue(this.value);
31854         
31855         
31856         this.grid = new Roo.grid[this.xgrid.xtype](this.viewEl, this.xgrid);
31857         
31858         
31859         this.grid.render();
31860         this.grid.getDataSource().on('remove', this.refreshValue, this);
31861         this.grid.getDataSource().on('update', this.refreshValue, this);
31862         this.grid.on('afteredit', this.refreshValue, this);
31863  
31864     },
31865      
31866     
31867     /**
31868      * Sets the value of the item. 
31869      * @param {String} either an object  or a string..
31870      */
31871     setValue : function(v){
31872         //this.value = v;
31873         v = v || []; // empty set..
31874         // this does not seem smart - it really only affects memoryproxy grids..
31875         if (this.grid && this.grid.getDataSource() && typeof(v) != 'undefined') {
31876             var ds = this.grid.getDataSource();
31877             // assumes a json reader..
31878             var data = {}
31879             data[ds.reader.meta.root ] =  typeof(v) == 'string' ? Roo.decode(v) : v;
31880             ds.loadData( data);
31881         }
31882         // clear selection so it does not get stale.
31883         if (this.grid.sm) { 
31884             this.grid.sm.clearSelections();
31885         }
31886         
31887         Roo.form.GridField.superclass.setValue.call(this, v);
31888         this.refreshValue();
31889         // should load data in the grid really....
31890     },
31891     
31892     // private
31893     refreshValue: function() {
31894          var val = [];
31895         this.grid.getDataSource().each(function(r) {
31896             val.push(r.data);
31897         });
31898         this.el.dom.value = Roo.encode(val);
31899     }
31900     
31901      
31902     
31903     
31904 });/*
31905  * Based on:
31906  * Ext JS Library 1.1.1
31907  * Copyright(c) 2006-2007, Ext JS, LLC.
31908  *
31909  * Originally Released Under LGPL - original licence link has changed is not relivant.
31910  *
31911  * Fork - LGPL
31912  * <script type="text/javascript">
31913  */
31914 /**
31915  * @class Roo.form.DisplayField
31916  * @extends Roo.form.Field
31917  * A generic Field to display non-editable data.
31918  * @cfg {Boolean} closable (true|false) default false
31919  * @constructor
31920  * Creates a new Display Field item.
31921  * @param {Object} config Configuration options
31922  */
31923 Roo.form.DisplayField = function(config){
31924     Roo.form.DisplayField.superclass.constructor.call(this, config);
31925     
31926     this.addEvents({
31927         /**
31928          * @event close
31929          * Fires after the click the close btn
31930              * @param {Roo.form.DisplayField} this
31931              */
31932         close : true
31933     });
31934 };
31935
31936 Roo.extend(Roo.form.DisplayField, Roo.form.TextField,  {
31937     inputType:      'hidden',
31938     allowBlank:     true,
31939     readOnly:         true,
31940     
31941  
31942     /**
31943      * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
31944      */
31945     focusClass : undefined,
31946     /**
31947      * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
31948      */
31949     fieldClass: 'x-form-field',
31950     
31951      /**
31952      * @cfg {Function} valueRenderer The renderer for the field (so you can reformat output). should return raw HTML
31953      */
31954     valueRenderer: undefined,
31955     
31956     width: 100,
31957     /**
31958      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
31959      * {tag: "input", type: "checkbox", autocomplete: "off"})
31960      */
31961      
31962  //   defaultAutoCreate : { tag: 'input', type: 'hidden', autocomplete: 'off'},
31963  
31964     closable : false,
31965     
31966     onResize : function(){
31967         Roo.form.DisplayField.superclass.onResize.apply(this, arguments);
31968         
31969     },
31970
31971     initEvents : function(){
31972         // Roo.form.Checkbox.superclass.initEvents.call(this);
31973         // has no events...
31974         
31975         if(this.closable){
31976             this.closeEl.on('click', this.onClose, this);
31977         }
31978        
31979     },
31980
31981
31982     getResizeEl : function(){
31983         return this.wrap;
31984     },
31985
31986     getPositionEl : function(){
31987         return this.wrap;
31988     },
31989
31990     // private
31991     onRender : function(ct, position){
31992         
31993         Roo.form.DisplayField.superclass.onRender.call(this, ct, position);
31994         //if(this.inputValue !== undefined){
31995         this.wrap = this.el.wrap();
31996         
31997         this.viewEl = this.wrap.createChild({ tag: 'div', cls: 'x-form-displayfield'});
31998         
31999         if(this.closable){
32000             this.closeEl = this.wrap.createChild({ tag: 'div', cls: 'x-dlg-close'});
32001         }
32002         
32003         if (this.bodyStyle) {
32004             this.viewEl.applyStyles(this.bodyStyle);
32005         }
32006         //this.viewEl.setStyle('padding', '2px');
32007         
32008         this.setValue(this.value);
32009         
32010     },
32011 /*
32012     // private
32013     initValue : Roo.emptyFn,
32014
32015   */
32016
32017         // private
32018     onClick : function(){
32019         
32020     },
32021
32022     /**
32023      * Sets the checked state of the checkbox.
32024      * @param {Boolean/String} checked True, 'true', '1', or 'on' to check the checkbox, any other value will uncheck it.
32025      */
32026     setValue : function(v){
32027         this.value = v;
32028         var html = this.valueRenderer ?  this.valueRenderer(v) : String.format('{0}', v);
32029         // this might be called before we have a dom element..
32030         if (!this.viewEl) {
32031             return;
32032         }
32033         this.viewEl.dom.innerHTML = html;
32034         Roo.form.DisplayField.superclass.setValue.call(this, v);
32035
32036     },
32037     
32038     onClose : function(e)
32039     {
32040         e.preventDefault();
32041         
32042         this.fireEvent('close', this);
32043     }
32044 });/*
32045  * 
32046  * Licence- LGPL
32047  * 
32048  */
32049
32050 /**
32051  * @class Roo.form.DayPicker
32052  * @extends Roo.form.Field
32053  * A Day picker show [M] [T] [W] ....
32054  * @constructor
32055  * Creates a new Day Picker
32056  * @param {Object} config Configuration options
32057  */
32058 Roo.form.DayPicker= function(config){
32059     Roo.form.DayPicker.superclass.constructor.call(this, config);
32060      
32061 };
32062
32063 Roo.extend(Roo.form.DayPicker, Roo.form.Field,  {
32064     /**
32065      * @cfg {String} focusClass The CSS class to use when the checkbox receives focus (defaults to undefined)
32066      */
32067     focusClass : undefined,
32068     /**
32069      * @cfg {String} fieldClass The default CSS class for the checkbox (defaults to "x-form-field")
32070      */
32071     fieldClass: "x-form-field",
32072    
32073     /**
32074      * @cfg {String/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to
32075      * {tag: "input", type: "checkbox", autocomplete: "off"})
32076      */
32077     defaultAutoCreate : { tag: "input", type: 'hidden', autocomplete: "new-password"},
32078     
32079    
32080     actionMode : 'viewEl', 
32081     //
32082     // private
32083  
32084     inputType : 'hidden',
32085     
32086      
32087     inputElement: false, // real input element?
32088     basedOn: false, // ????
32089     
32090     isFormField: true, // not sure where this is needed!!!!
32091
32092     onResize : function(){
32093         Roo.form.Checkbox.superclass.onResize.apply(this, arguments);
32094         if(!this.boxLabel){
32095             this.el.alignTo(this.wrap, 'c-c');
32096         }
32097     },
32098
32099     initEvents : function(){
32100         Roo.form.Checkbox.superclass.initEvents.call(this);
32101         this.el.on("click", this.onClick,  this);
32102         this.el.on("change", this.onClick,  this);
32103     },
32104
32105
32106     getResizeEl : function(){
32107         return this.wrap;
32108     },
32109
32110     getPositionEl : function(){
32111         return this.wrap;
32112     },
32113
32114     
32115     // private
32116     onRender : function(ct, position){
32117         Roo.form.Checkbox.superclass.onRender.call(this, ct, position);
32118        
32119         this.wrap = this.el.wrap({cls: 'x-form-daypick-item '});
32120         
32121         var r1 = '<table><tr>';
32122         var r2 = '<tr class="x-form-daypick-icons">';
32123         for (var i=0; i < 7; i++) {
32124             r1+= '<td><div>' + Date.dayNames[i].substring(0,3) + '</div></td>';
32125             r2+= '<td><img class="x-menu-item-icon" src="' + Roo.BLANK_IMAGE_URL  +'"></td>';
32126         }
32127         
32128         var viewEl = this.wrap.createChild( r1 + '</tr>' + r2 + '</tr></table>');
32129         viewEl.select('img').on('click', this.onClick, this);
32130         this.viewEl = viewEl;   
32131         
32132         
32133         // this will not work on Chrome!!!
32134         this.el.on('DOMAttrModified', this.setFromHidden,  this); //ff
32135         this.el.on('propertychange', this.setFromHidden,  this);  //ie
32136         
32137         
32138           
32139
32140     },
32141
32142     // private
32143     initValue : Roo.emptyFn,
32144
32145     /**
32146      * Returns the checked state of the checkbox.
32147      * @return {Boolean} True if checked, else false
32148      */
32149     getValue : function(){
32150         return this.el.dom.value;
32151         
32152     },
32153
32154         // private
32155     onClick : function(e){ 
32156         //this.setChecked(!this.checked);
32157         Roo.get(e.target).toggleClass('x-menu-item-checked');
32158         this.refreshValue();
32159         //if(this.el.dom.checked != this.checked){
32160         //    this.setValue(this.el.dom.checked);
32161        // }
32162     },
32163     
32164     // private
32165     refreshValue : function()
32166     {
32167         var val = '';
32168         this.viewEl.select('img',true).each(function(e,i,n)  {
32169             val += e.is(".x-menu-item-checked") ? String(n) : '';
32170         });
32171         this.setValue(val, true);
32172     },
32173
32174     /**
32175      * Sets the checked state of the checkbox.
32176      * On is always based on a string comparison between inputValue and the param.
32177      * @param {Boolean/String} value - the value to set 
32178      * @param {Boolean/String} suppressEvent - whether to suppress the checkchange event.
32179      */
32180     setValue : function(v,suppressEvent){
32181         if (!this.el.dom) {
32182             return;
32183         }
32184         var old = this.el.dom.value ;
32185         this.el.dom.value = v;
32186         if (suppressEvent) {
32187             return ;
32188         }
32189          
32190         // update display..
32191         this.viewEl.select('img',true).each(function(e,i,n)  {
32192             
32193             var on = e.is(".x-menu-item-checked");
32194             var newv = v.indexOf(String(n)) > -1;
32195             if (on != newv) {
32196                 e.toggleClass('x-menu-item-checked');
32197             }
32198             
32199         });
32200         
32201         
32202         this.fireEvent('change', this, v, old);
32203         
32204         
32205     },
32206    
32207     // handle setting of hidden value by some other method!!?!?
32208     setFromHidden: function()
32209     {
32210         if(!this.el){
32211             return;
32212         }
32213         //console.log("SET FROM HIDDEN");
32214         //alert('setFrom hidden');
32215         this.setValue(this.el.dom.value);
32216     },
32217     
32218     onDestroy : function()
32219     {
32220         if(this.viewEl){
32221             Roo.get(this.viewEl).remove();
32222         }
32223          
32224         Roo.form.DayPicker.superclass.onDestroy.call(this);
32225     }
32226
32227 });/*
32228  * RooJS Library 1.1.1
32229  * Copyright(c) 2008-2011  Alan Knowles
32230  *
32231  * License - LGPL
32232  */
32233  
32234
32235 /**
32236  * @class Roo.form.ComboCheck
32237  * @extends Roo.form.ComboBox
32238  * A combobox for multiple select items.
32239  *
32240  * FIXME - could do with a reset button..
32241  * 
32242  * @constructor
32243  * Create a new ComboCheck
32244  * @param {Object} config Configuration options
32245  */
32246 Roo.form.ComboCheck = function(config){
32247     Roo.form.ComboCheck.superclass.constructor.call(this, config);
32248     // should verify some data...
32249     // like
32250     // hiddenName = required..
32251     // displayField = required
32252     // valudField == required
32253     var req= [ 'hiddenName', 'displayField', 'valueField' ];
32254     var _t = this;
32255     Roo.each(req, function(e) {
32256         if ((typeof(_t[e]) == 'undefined' ) || !_t[e].length) {
32257             throw "Roo.form.ComboCheck : missing value for: " + e;
32258         }
32259     });
32260     
32261     
32262 };
32263
32264 Roo.extend(Roo.form.ComboCheck, Roo.form.ComboBox, {
32265      
32266      
32267     editable : false,
32268      
32269     selectedClass: 'x-menu-item-checked', 
32270     
32271     // private
32272     onRender : function(ct, position){
32273         var _t = this;
32274         
32275         
32276         
32277         if(!this.tpl){
32278             var cls = 'x-combo-list';
32279
32280             
32281             this.tpl =  new Roo.Template({
32282                 html :  '<div class="'+cls+'-item x-menu-check-item">' +
32283                    '<img class="x-menu-item-icon" style="margin: 0px;" src="' + Roo.BLANK_IMAGE_URL + '">' + 
32284                    '<span>{' + this.displayField + '}</span>' +
32285                     '</div>' 
32286                 
32287             });
32288         }
32289  
32290         
32291         Roo.form.ComboCheck.superclass.onRender.call(this, ct, position);
32292         this.view.singleSelect = false;
32293         this.view.multiSelect = true;
32294         this.view.toggleSelect = true;
32295         this.pageTb.add(new Roo.Toolbar.Fill(), {
32296             
32297             text: 'Done',
32298             handler: function()
32299             {
32300                 _t.collapse();
32301             }
32302         });
32303     },
32304     
32305     onViewOver : function(e, t){
32306         // do nothing...
32307         return;
32308         
32309     },
32310     
32311     onViewClick : function(doFocus,index){
32312         return;
32313         
32314     },
32315     select: function () {
32316         //Roo.log("SELECT CALLED");
32317     },
32318      
32319     selectByValue : function(xv, scrollIntoView){
32320         var ar = this.getValueArray();
32321         var sels = [];
32322         
32323         Roo.each(ar, function(v) {
32324             if(v === undefined || v === null){
32325                 return;
32326             }
32327             var r = this.findRecord(this.valueField, v);
32328             if(r){
32329                 sels.push(this.store.indexOf(r))
32330                 
32331             }
32332         },this);
32333         this.view.select(sels);
32334         return false;
32335     },
32336     
32337     
32338     
32339     onSelect : function(record, index){
32340        // Roo.log("onselect Called");
32341        // this is only called by the clear button now..
32342         this.view.clearSelections();
32343         this.setValue('[]');
32344         if (this.value != this.valueBefore) {
32345             this.fireEvent('change', this, this.value, this.valueBefore);
32346             this.valueBefore = this.value;
32347         }
32348     },
32349     getValueArray : function()
32350     {
32351         var ar = [] ;
32352         
32353         try {
32354             //Roo.log(this.value);
32355             if (typeof(this.value) == 'undefined') {
32356                 return [];
32357             }
32358             var ar = Roo.decode(this.value);
32359             return  ar instanceof Array ? ar : []; //?? valid?
32360             
32361         } catch(e) {
32362             Roo.log(e + "\nRoo.form.ComboCheck:getValueArray  invalid data:" + this.getValue());
32363             return [];
32364         }
32365          
32366     },
32367     expand : function ()
32368     {
32369         
32370         Roo.form.ComboCheck.superclass.expand.call(this);
32371         this.valueBefore = typeof(this.value) == 'undefined' ? '' : this.value;
32372         //this.valueBefore = typeof(this.valueBefore) == 'undefined' ? '' : this.valueBefore;
32373         
32374
32375     },
32376     
32377     collapse : function(){
32378         Roo.form.ComboCheck.superclass.collapse.call(this);
32379         var sl = this.view.getSelectedIndexes();
32380         var st = this.store;
32381         var nv = [];
32382         var tv = [];
32383         var r;
32384         Roo.each(sl, function(i) {
32385             r = st.getAt(i);
32386             nv.push(r.get(this.valueField));
32387         },this);
32388         this.setValue(Roo.encode(nv));
32389         if (this.value != this.valueBefore) {
32390
32391             this.fireEvent('change', this, this.value, this.valueBefore);
32392             this.valueBefore = this.value;
32393         }
32394         
32395     },
32396     
32397     setValue : function(v){
32398         // Roo.log(v);
32399         this.value = v;
32400         
32401         var vals = this.getValueArray();
32402         var tv = [];
32403         Roo.each(vals, function(k) {
32404             var r = this.findRecord(this.valueField, k);
32405             if(r){
32406                 tv.push(r.data[this.displayField]);
32407             }else if(this.valueNotFoundText !== undefined){
32408                 tv.push( this.valueNotFoundText );
32409             }
32410         },this);
32411        // Roo.log(tv);
32412         
32413         Roo.form.ComboBox.superclass.setValue.call(this, tv.join(', '));
32414         this.hiddenField.value = v;
32415         this.value = v;
32416     }
32417     
32418 });/*
32419  * Based on:
32420  * Ext JS Library 1.1.1
32421  * Copyright(c) 2006-2007, Ext JS, LLC.
32422  *
32423  * Originally Released Under LGPL - original licence link has changed is not relivant.
32424  *
32425  * Fork - LGPL
32426  * <script type="text/javascript">
32427  */
32428  
32429 /**
32430  * @class Roo.form.Signature
32431  * @extends Roo.form.Field
32432  * Signature field.  
32433  * @constructor
32434  * 
32435  * @param {Object} config Configuration options
32436  */
32437
32438 Roo.form.Signature = function(config){
32439     Roo.form.Signature.superclass.constructor.call(this, config);
32440     
32441     this.addEvents({// not in used??
32442          /**
32443          * @event confirm
32444          * Fires when the 'confirm' icon is pressed (add a listener to enable add button)
32445              * @param {Roo.form.Signature} combo This combo box
32446              */
32447         'confirm' : true,
32448         /**
32449          * @event reset
32450          * Fires when the 'edit' icon is pressed (add a listener to enable add button)
32451              * @param {Roo.form.ComboBox} combo This combo box
32452              * @param {Roo.data.Record|false} record The data record returned from the underlying store (or false on nothing selected)
32453              */
32454         'reset' : true
32455     });
32456 };
32457
32458 Roo.extend(Roo.form.Signature, Roo.form.Field,  {
32459     /**
32460      * @cfg {Object} labels Label to use when rendering a form.
32461      * defaults to 
32462      * labels : { 
32463      *      clear : "Clear",
32464      *      confirm : "Confirm"
32465      *  }
32466      */
32467     labels : { 
32468         clear : "Clear",
32469         confirm : "Confirm"
32470     },
32471     /**
32472      * @cfg {Number} width The signature panel width (defaults to 300)
32473      */
32474     width: 300,
32475     /**
32476      * @cfg {Number} height The signature panel height (defaults to 100)
32477      */
32478     height : 100,
32479     /**
32480      * @cfg {Boolean} allowBlank False to validate that the value length > 0 (defaults to false)
32481      */
32482     allowBlank : false,
32483     
32484     //private
32485     // {Object} signPanel The signature SVG panel element (defaults to {})
32486     signPanel : {},
32487     // {Boolean} isMouseDown False to validate that the mouse down event (defaults to false)
32488     isMouseDown : false,
32489     // {Boolean} isConfirmed validate the signature is confirmed or not for submitting form (defaults to false)
32490     isConfirmed : false,
32491     // {String} signatureTmp SVG mapping string (defaults to empty string)
32492     signatureTmp : '',
32493     
32494     
32495     defaultAutoCreate : { // modified by initCompnoent..
32496         tag: "input",
32497         type:"hidden"
32498     },
32499
32500     // private
32501     onRender : function(ct, position){
32502         
32503         Roo.form.Signature.superclass.onRender.call(this, ct, position);
32504         
32505         this.wrap = this.el.wrap({
32506             cls:'x-form-signature-wrap', style : 'width: ' + this.width + 'px', cn:{cls:'x-form-signature'}
32507         });
32508         
32509         this.createToolbar(this);
32510         this.signPanel = this.wrap.createChild({
32511                 tag: 'div',
32512                 style: 'width: ' + this.width + 'px; height: ' + this.height + 'px; border: 0;'
32513             }, this.el
32514         );
32515             
32516         this.svgID = Roo.id();
32517         this.svgEl = this.signPanel.createChild({
32518               xmlns : 'http://www.w3.org/2000/svg',
32519               tag : 'svg',
32520               id : this.svgID + "-svg",
32521               width: this.width,
32522               height: this.height,
32523               viewBox: '0 0 '+this.width+' '+this.height,
32524               cn : [
32525                 {
32526                     tag: "rect",
32527                     id: this.svgID + "-svg-r",
32528                     width: this.width,
32529                     height: this.height,
32530                     fill: "#ffa"
32531                 },
32532                 {
32533                     tag: "line",
32534                     id: this.svgID + "-svg-l",
32535                     x1: "0", // start
32536                     y1: (this.height*0.8), // start set the line in 80% of height
32537                     x2: this.width, // end
32538                     y2: (this.height*0.8), // end set the line in 80% of height
32539                     'stroke': "#666",
32540                     'stroke-width': "1",
32541                     'stroke-dasharray': "3",
32542                     'shape-rendering': "crispEdges",
32543                     'pointer-events': "none"
32544                 },
32545                 {
32546                     tag: "path",
32547                     id: this.svgID + "-svg-p",
32548                     'stroke': "navy",
32549                     'stroke-width': "3",
32550                     'fill': "none",
32551                     'pointer-events': 'none'
32552                 }
32553               ]
32554         });
32555         this.createSVG();
32556         this.svgBox = this.svgEl.dom.getScreenCTM();
32557     },
32558     createSVG : function(){ 
32559         var svg = this.signPanel;
32560         var r = svg.select('#'+ this.svgID + '-svg-r', true).first().dom;
32561         var t = this;
32562
32563         r.addEventListener('mousedown', function(e) { return t.down(e); }, false);
32564         r.addEventListener('mousemove', function(e) { return t.move(e); }, false);
32565         r.addEventListener('mouseup', function(e) { return t.up(e); }, false);
32566         r.addEventListener('mouseout', function(e) { return t.up(e); }, false);
32567         r.addEventListener('touchstart', function(e) { return t.down(e); }, false);
32568         r.addEventListener('touchmove', function(e) { return t.move(e); }, false);
32569         r.addEventListener('touchend', function(e) { return t.up(e); }, false);
32570         
32571     },
32572     isTouchEvent : function(e){
32573         return e.type.match(/^touch/);
32574     },
32575     getCoords : function (e) {
32576         var pt    = this.svgEl.dom.createSVGPoint();
32577         pt.x = e.clientX; 
32578         pt.y = e.clientY;
32579         if (this.isTouchEvent(e)) {
32580             pt.x =  e.targetTouches[0].clientX;
32581             pt.y = e.targetTouches[0].clientY;
32582         }
32583         var a = this.svgEl.dom.getScreenCTM();
32584         var b = a.inverse();
32585         var mx = pt.matrixTransform(b);
32586         return mx.x + ',' + mx.y;
32587     },
32588     //mouse event headler 
32589     down : function (e) {
32590         this.signatureTmp += 'M' + this.getCoords(e) + ' ';
32591         this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr('d', this.signatureTmp);
32592         
32593         this.isMouseDown = true;
32594         
32595         e.preventDefault();
32596     },
32597     move : function (e) {
32598         if (this.isMouseDown) {
32599             this.signatureTmp += 'L' + this.getCoords(e) + ' ';
32600             this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', this.signatureTmp);
32601         }
32602         
32603         e.preventDefault();
32604     },
32605     up : function (e) {
32606         this.isMouseDown = false;
32607         var sp = this.signatureTmp.split(' ');
32608         
32609         if(sp.length > 1){
32610             if(!sp[sp.length-2].match(/^L/)){
32611                 sp.pop();
32612                 sp.pop();
32613                 sp.push("");
32614                 this.signatureTmp = sp.join(" ");
32615             }
32616         }
32617         if(this.getValue() != this.signatureTmp){
32618             this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
32619             this.isConfirmed = false;
32620         }
32621         e.preventDefault();
32622     },
32623     
32624     /**
32625      * Protected method that will not generally be called directly. It
32626      * is called when the editor creates its toolbar. Override this method if you need to
32627      * add custom toolbar buttons.
32628      * @param {HtmlEditor} editor
32629      */
32630     createToolbar : function(editor){
32631          function btn(id, toggle, handler){
32632             var xid = fid + '-'+ id ;
32633             return {
32634                 id : xid,
32635                 cmd : id,
32636                 cls : 'x-btn-icon x-edit-'+id,
32637                 enableToggle:toggle !== false,
32638                 scope: editor, // was editor...
32639                 handler:handler||editor.relayBtnCmd,
32640                 clickEvent:'mousedown',
32641                 tooltip: etb.buttonTips[id] || undefined, ///tips ???
32642                 tabIndex:-1
32643             };
32644         }
32645         
32646         
32647         var tb = new Roo.Toolbar(editor.wrap.dom.firstChild);
32648         this.tb = tb;
32649         this.tb.add(
32650            {
32651                 cls : ' x-signature-btn x-signature-'+id,
32652                 scope: editor, // was editor...
32653                 handler: this.reset,
32654                 clickEvent:'mousedown',
32655                 text: this.labels.clear
32656             },
32657             {
32658                  xtype : 'Fill',
32659                  xns: Roo.Toolbar
32660             }, 
32661             {
32662                 cls : '  x-signature-btn x-signature-'+id,
32663                 scope: editor, // was editor...
32664                 handler: this.confirmHandler,
32665                 clickEvent:'mousedown',
32666                 text: this.labels.confirm
32667             }
32668         );
32669     
32670     },
32671     //public
32672     /**
32673      * when user is clicked confirm then show this image.....
32674      * 
32675      * @return {String} Image Data URI
32676      */
32677     getImageDataURI : function(){
32678         var svg = this.svgEl.dom.parentNode.innerHTML;
32679         var src = 'data:image/svg+xml;base64,'+window.btoa(svg);
32680         return src; 
32681     },
32682     /**
32683      * 
32684      * @return {Boolean} this.isConfirmed
32685      */
32686     getConfirmed : function(){
32687         return this.isConfirmed;
32688     },
32689     /**
32690      * 
32691      * @return {Number} this.width
32692      */
32693     getWidth : function(){
32694         return this.width;
32695     },
32696     /**
32697      * 
32698      * @return {Number} this.height
32699      */
32700     getHeight : function(){
32701         return this.height;
32702     },
32703     // private
32704     getSignature : function(){
32705         return this.signatureTmp;
32706     },
32707     // private
32708     reset : function(){
32709         this.signatureTmp = '';
32710         this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
32711         this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', '');
32712         this.isConfirmed = false;
32713         Roo.form.Signature.superclass.reset.call(this);
32714     },
32715     setSignature : function(s){
32716         this.signatureTmp = s;
32717         this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#ffa');
32718         this.signPanel.select('#'+ this.svgID + '-svg-p', true).first().attr( 'd', s);
32719         this.setValue(s);
32720         this.isConfirmed = false;
32721         Roo.form.Signature.superclass.reset.call(this);
32722     }, 
32723     test : function(){
32724 //        Roo.log(this.signPanel.dom.contentWindow.up())
32725     },
32726     //private
32727     setConfirmed : function(){
32728         
32729         
32730         
32731 //        Roo.log(Roo.get(this.signPanel.dom.contentWindow.r).attr('fill', '#cfc'));
32732     },
32733     // private
32734     confirmHandler : function(){
32735         if(!this.getSignature()){
32736             return;
32737         }
32738         
32739         this.signPanel.select('#'+ this.svgID + '-svg-r', true).first().attr('fill', '#cfc');
32740         this.setValue(this.getSignature());
32741         this.isConfirmed = true;
32742         
32743         this.fireEvent('confirm', this);
32744     },
32745     // private
32746     // Subclasses should provide the validation implementation by overriding this
32747     validateValue : function(value){
32748         if(this.allowBlank){
32749             return true;
32750         }
32751         
32752         if(this.isConfirmed){
32753             return true;
32754         }
32755         return false;
32756     }
32757 });/*
32758  * Based on:
32759  * Ext JS Library 1.1.1
32760  * Copyright(c) 2006-2007, Ext JS, LLC.
32761  *
32762  * Originally Released Under LGPL - original licence link has changed is not relivant.
32763  *
32764  * Fork - LGPL
32765  * <script type="text/javascript">
32766  */
32767  
32768
32769 /**
32770  * @class Roo.form.ComboBox
32771  * @extends Roo.form.TriggerField
32772  * A combobox control with support for autocomplete, remote-loading, paging and many other features.
32773  * @constructor
32774  * Create a new ComboBox.
32775  * @param {Object} config Configuration options
32776  */
32777 Roo.form.Select = function(config){
32778     Roo.form.Select.superclass.constructor.call(this, config);
32779      
32780 };
32781
32782 Roo.extend(Roo.form.Select , Roo.form.ComboBox, {
32783     /**
32784      * @cfg {String/HTMLElement/Element} transform The id, DOM node or element of an existing select to convert to a ComboBox
32785      */
32786     /**
32787      * @cfg {Boolean} lazyRender True to prevent the ComboBox from rendering until requested (should always be used when
32788      * rendering into an Roo.Editor, defaults to false)
32789      */
32790     /**
32791      * @cfg {Boolean/Object} autoCreate A DomHelper element spec, or true for a default element spec (defaults to:
32792      * {tag: "input", type: "text", size: "24", autocomplete: "off"})
32793      */
32794     /**
32795      * @cfg {Roo.data.Store} store The data store to which this combo is bound (defaults to undefined)
32796      */
32797     /**
32798      * @cfg {String} title If supplied, a header element is created containing this text and added into the top of
32799      * the dropdown list (defaults to undefined, with no header element)
32800      */
32801
32802      /**
32803      * @cfg {String/Roo.Template} tpl The template to use to render the output
32804      */
32805      
32806     // private
32807     defaultAutoCreate : {tag: "select"  },
32808     /**
32809      * @cfg {Number} listWidth The width in pixels of the dropdown list (defaults to the width of the ComboBox field)
32810      */
32811     listWidth: undefined,
32812     /**
32813      * @cfg {String} displayField The underlying data field name to bind to this CombBox (defaults to undefined if
32814      * mode = 'remote' or 'text' if mode = 'local')
32815      */
32816     displayField: undefined,
32817     /**
32818      * @cfg {String} valueField The underlying data value name to bind to this CombBox (defaults to undefined if
32819      * mode = 'remote' or 'value' if mode = 'local'). 
32820      * Note: use of a valueField requires the user make a selection
32821      * in order for a value to be mapped.
32822      */
32823     valueField: undefined,
32824     
32825     
32826     /**
32827      * @cfg {String} hiddenName If specified, a hidden form field with this name is dynamically generated to store the
32828      * field's data value (defaults to the underlying DOM element's name)
32829      */
32830     hiddenName: undefined,
32831     /**
32832      * @cfg {String} listClass CSS class to apply to the dropdown list element (defaults to '')
32833      */
32834     listClass: '',
32835     /**
32836      * @cfg {String} selectedClass CSS class to apply to the selected item in the dropdown list (defaults to 'x-combo-selected')
32837      */
32838     selectedClass: 'x-combo-selected',
32839     /**
32840      * @cfg {String} triggerClass An additional CSS class used to style the trigger button.  The trigger will always get the
32841      * class 'x-form-trigger' and triggerClass will be <b>appended</b> if specified (defaults to 'x-form-arrow-trigger'
32842      * which displays a downward arrow icon).
32843      */
32844     triggerClass : 'x-form-arrow-trigger',
32845     /**
32846      * @cfg {Boolean/String} shadow True or "sides" for the default effect, "frame" for 4-way shadow, and "drop" for bottom-right
32847      */
32848     shadow:'sides',
32849     /**
32850      * @cfg {String} listAlign A valid anchor position value. See {@link Roo.Element#alignTo} for details on supported
32851      * anchor positions (defaults to 'tl-bl')
32852      */
32853     listAlign: 'tl-bl?',
32854     /**
32855      * @cfg {Number} maxHeight The maximum height in pixels of the dropdown list before scrollbars are shown (defaults to 300)
32856      */
32857     maxHeight: 300,
32858     /**
32859      * @cfg {String} triggerAction The action to execute when the trigger field is activated.  Use 'all' to run the
32860      * query specified by the allQuery config option (defaults to 'query')
32861      */
32862     triggerAction: 'query',
32863     /**
32864      * @cfg {Number} minChars The minimum number of characters the user must type before autocomplete and typeahead activate
32865      * (defaults to 4, does not apply if editable = false)
32866      */
32867     minChars : 4,
32868     /**
32869      * @cfg {Boolean} typeAhead True to populate and autoselect the remainder of the text being typed after a configurable
32870      * delay (typeAheadDelay) if it matches a known value (defaults to false)
32871      */
32872     typeAhead: false,
32873     /**
32874      * @cfg {Number} queryDelay The length of time in milliseconds to delay between the start of typing and sending the
32875      * query to filter the dropdown list (defaults to 500 if mode = 'remote' or 10 if mode = 'local')
32876      */
32877     queryDelay: 500,
32878     /**
32879      * @cfg {Number} pageSize If greater than 0, a paging toolbar is displayed in the footer of the dropdown list and the
32880      * filter queries will execute with page start and limit parameters.  Only applies when mode = 'remote' (defaults to 0)
32881      */
32882     pageSize: 0,
32883     /**
32884      * @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus.  Only applies
32885      * when editable = true (defaults to false)
32886      */
32887     selectOnFocus:false,
32888     /**
32889      * @cfg {String} queryParam Name of the query as it will be passed on the querystring (defaults to 'query')
32890      */
32891     queryParam: 'query',
32892     /**
32893      * @cfg {String} loadingText The text to display in the dropdown list while data is loading.  Only applies
32894      * when mode = 'remote' (defaults to 'Loading...')
32895      */
32896     loadingText: 'Loading...',
32897     /**
32898      * @cfg {Boolean} resizable True to add a resize handle to the bottom of the dropdown list (defaults to false)
32899      */
32900     resizable: false,
32901     /**
32902      * @cfg {Number} handleHeight The height in pixels of the dropdown list resize handle if resizable = true (defaults to 8)
32903      */
32904     handleHeight : 8,
32905     /**
32906      * @cfg {Boolean} editable False to prevent the user from typing text directly into the field, just like a
32907      * traditional select (defaults to true)
32908      */
32909     editable: true,
32910     /**
32911      * @cfg {String} allQuery The text query to send to the server to return all records for the list with no filtering (defaults to '')
32912      */
32913     allQuery: '',
32914     /**
32915      * @cfg {String} mode Set to 'local' if the ComboBox loads local data (defaults to 'remote' which loads from the server)
32916      */
32917     mode: 'remote',
32918     /**
32919      * @cfg {Number} minListWidth The minimum width of the dropdown list in pixels (defaults to 70, will be ignored if
32920      * listWidth has a higher value)
32921      */
32922     minListWidth : 70,
32923     /**
32924      * @cfg {Boolean} forceSelection True to restrict the selected value to one of the values in the list, false to
32925      * allow the user to set arbitrary text into the field (defaults to false)
32926      */
32927     forceSelection:false,
32928     /**
32929      * @cfg {Number} typeAheadDelay The length of time in milliseconds to wait until the typeahead text is displayed
32930      * if typeAhead = true (defaults to 250)
32931      */
32932     typeAheadDelay : 250,
32933     /**
32934      * @cfg {String} valueNotFoundText When using a name/value combo, if the value passed to setValue is not found in
32935      * the store, valueNotFoundText will be displayed as the field text if defined (defaults to undefined)
32936      */
32937     valueNotFoundText : undefined,
32938     
32939     /**
32940      * @cfg {String} defaultValue The value displayed after loading the store.
32941      */
32942     defaultValue: '',
32943     
32944     /**
32945      * @cfg {Boolean} blockFocus Prevents all focus calls, so it can work with things like HTML edtor bar
32946      */
32947     blockFocus : false,
32948     
32949     /**
32950      * @cfg {Boolean} disableClear Disable showing of clear button.
32951      */
32952     disableClear : false,
32953     /**
32954      * @cfg {Boolean} alwaysQuery  Disable caching of results, and always send query
32955      */
32956     alwaysQuery : false,
32957     
32958     //private
32959     addicon : false,
32960     editicon: false,
32961     
32962     // element that contains real text value.. (when hidden is used..)
32963      
32964     // private
32965     onRender : function(ct, position){
32966         Roo.form.Field.prototype.onRender.call(this, ct, position);
32967         
32968         if(this.store){
32969             this.store.on('beforeload', this.onBeforeLoad, this);
32970             this.store.on('load', this.onLoad, this);
32971             this.store.on('loadexception', this.onLoadException, this);
32972             this.store.load({});
32973         }
32974         
32975         
32976         
32977     },
32978
32979     // private
32980     initEvents : function(){
32981         //Roo.form.ComboBox.superclass.initEvents.call(this);
32982  
32983     },
32984
32985     onDestroy : function(){
32986        
32987         if(this.store){
32988             this.store.un('beforeload', this.onBeforeLoad, this);
32989             this.store.un('load', this.onLoad, this);
32990             this.store.un('loadexception', this.onLoadException, this);
32991         }
32992         //Roo.form.ComboBox.superclass.onDestroy.call(this);
32993     },
32994
32995     // private
32996     fireKey : function(e){
32997         if(e.isNavKeyPress() && !this.list.isVisible()){
32998             this.fireEvent("specialkey", this, e);
32999         }
33000     },
33001
33002     // private
33003     onResize: function(w, h){
33004         
33005         return; 
33006     
33007         
33008     },
33009
33010     /**
33011      * Allow or prevent the user from directly editing the field text.  If false is passed,
33012      * the user will only be able to select from the items defined in the dropdown list.  This method
33013      * is the runtime equivalent of setting the 'editable' config option at config time.
33014      * @param {Boolean} value True to allow the user to directly edit the field text
33015      */
33016     setEditable : function(value){
33017          
33018     },
33019
33020     // private
33021     onBeforeLoad : function(){
33022         
33023         Roo.log("Select before load");
33024         return;
33025     
33026         this.innerList.update(this.loadingText ?
33027                '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
33028         //this.restrictHeight();
33029         this.selectedIndex = -1;
33030     },
33031
33032     // private
33033     onLoad : function(){
33034
33035     
33036         var dom = this.el.dom;
33037         dom.innerHTML = '';
33038          var od = dom.ownerDocument;
33039          
33040         if (this.emptyText) {
33041             var op = od.createElement('option');
33042             op.setAttribute('value', '');
33043             op.innerHTML = String.format('{0}', this.emptyText);
33044             dom.appendChild(op);
33045         }
33046         if(this.store.getCount() > 0){
33047            
33048             var vf = this.valueField;
33049             var df = this.displayField;
33050             this.store.data.each(function(r) {
33051                 // which colmsn to use... testing - cdoe / title..
33052                 var op = od.createElement('option');
33053                 op.setAttribute('value', r.data[vf]);
33054                 op.innerHTML = String.format('{0}', r.data[df]);
33055                 dom.appendChild(op);
33056             });
33057             if (typeof(this.defaultValue != 'undefined')) {
33058                 this.setValue(this.defaultValue);
33059             }
33060             
33061              
33062         }else{
33063             //this.onEmptyResults();
33064         }
33065         //this.el.focus();
33066     },
33067     // private
33068     onLoadException : function()
33069     {
33070         dom.innerHTML = '';
33071             
33072         Roo.log("Select on load exception");
33073         return;
33074     
33075         this.collapse();
33076         Roo.log(this.store.reader.jsonData);
33077         if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
33078             Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
33079         }
33080         
33081         
33082     },
33083     // private
33084     onTypeAhead : function(){
33085          
33086     },
33087
33088     // private
33089     onSelect : function(record, index){
33090         Roo.log('on select?');
33091         return;
33092         if(this.fireEvent('beforeselect', this, record, index) !== false){
33093             this.setFromData(index > -1 ? record.data : false);
33094             this.collapse();
33095             this.fireEvent('select', this, record, index);
33096         }
33097     },
33098
33099     /**
33100      * Returns the currently selected field value or empty string if no value is set.
33101      * @return {String} value The selected value
33102      */
33103     getValue : function(){
33104         var dom = this.el.dom;
33105         this.value = dom.options[dom.selectedIndex].value;
33106         return this.value;
33107         
33108     },
33109
33110     /**
33111      * Clears any text/value currently set in the field
33112      */
33113     clearValue : function(){
33114         this.value = '';
33115         this.el.dom.selectedIndex = this.emptyText ? 0 : -1;
33116         
33117     },
33118
33119     /**
33120      * Sets the specified value into the field.  If the value finds a match, the corresponding record text
33121      * will be displayed in the field.  If the value does not match the data value of an existing item,
33122      * and the valueNotFoundText config option is defined, it will be displayed as the default field text.
33123      * Otherwise the field will be blank (although the value will still be set).
33124      * @param {String} value The value to match
33125      */
33126     setValue : function(v){
33127         var d = this.el.dom;
33128         for (var i =0; i < d.options.length;i++) {
33129             if (v == d.options[i].value) {
33130                 d.selectedIndex = i;
33131                 this.value = v;
33132                 return;
33133             }
33134         }
33135         this.clearValue();
33136     },
33137     /**
33138      * @property {Object} the last set data for the element
33139      */
33140     
33141     lastData : false,
33142     /**
33143      * Sets the value of the field based on a object which is related to the record format for the store.
33144      * @param {Object} value the value to set as. or false on reset?
33145      */
33146     setFromData : function(o){
33147         Roo.log('setfrom data?');
33148          
33149         
33150         
33151     },
33152     // private
33153     reset : function(){
33154         this.clearValue();
33155     },
33156     // private
33157     findRecord : function(prop, value){
33158         
33159         return false;
33160     
33161         var record;
33162         if(this.store.getCount() > 0){
33163             this.store.each(function(r){
33164                 if(r.data[prop] == value){
33165                     record = r;
33166                     return false;
33167                 }
33168                 return true;
33169             });
33170         }
33171         return record;
33172     },
33173     
33174     getName: function()
33175     {
33176         // returns hidden if it's set..
33177         if (!this.rendered) {return ''};
33178         return !this.hiddenName && this.el.dom.name  ? this.el.dom.name : (this.hiddenName || '');
33179         
33180     },
33181      
33182
33183     
33184
33185     // private
33186     onEmptyResults : function(){
33187         Roo.log('empty results');
33188         //this.collapse();
33189     },
33190
33191     /**
33192      * Returns true if the dropdown list is expanded, else false.
33193      */
33194     isExpanded : function(){
33195         return false;
33196     },
33197
33198     /**
33199      * Select an item in the dropdown list by its data value. This function does NOT cause the select event to fire.
33200      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
33201      * @param {String} value The data value of the item to select
33202      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
33203      * selected item if it is not currently in view (defaults to true)
33204      * @return {Boolean} True if the value matched an item in the list, else false
33205      */
33206     selectByValue : function(v, scrollIntoView){
33207         Roo.log('select By Value');
33208         return false;
33209     
33210         if(v !== undefined && v !== null){
33211             var r = this.findRecord(this.valueField || this.displayField, v);
33212             if(r){
33213                 this.select(this.store.indexOf(r), scrollIntoView);
33214                 return true;
33215             }
33216         }
33217         return false;
33218     },
33219
33220     /**
33221      * Select an item in the dropdown list by its numeric index in the list. This function does NOT cause the select event to fire.
33222      * The store must be loaded and the list expanded for this function to work, otherwise use setValue.
33223      * @param {Number} index The zero-based index of the list item to select
33224      * @param {Boolean} scrollIntoView False to prevent the dropdown list from autoscrolling to display the
33225      * selected item if it is not currently in view (defaults to true)
33226      */
33227     select : function(index, scrollIntoView){
33228         Roo.log('select ');
33229         return  ;
33230         
33231         this.selectedIndex = index;
33232         this.view.select(index);
33233         if(scrollIntoView !== false){
33234             var el = this.view.getNode(index);
33235             if(el){
33236                 this.innerList.scrollChildIntoView(el, false);
33237             }
33238         }
33239     },
33240
33241       
33242
33243     // private
33244     validateBlur : function(){
33245         
33246         return;
33247         
33248     },
33249
33250     // private
33251     initQuery : function(){
33252         this.doQuery(this.getRawValue());
33253     },
33254
33255     // private
33256     doForce : function(){
33257         if(this.el.dom.value.length > 0){
33258             this.el.dom.value =
33259                 this.lastSelectionText === undefined ? '' : this.lastSelectionText;
33260              
33261         }
33262     },
33263
33264     /**
33265      * Execute a query to filter the dropdown list.  Fires the beforequery event prior to performing the
33266      * query allowing the query action to be canceled if needed.
33267      * @param {String} query The SQL query to execute
33268      * @param {Boolean} forceAll True to force the query to execute even if there are currently fewer characters
33269      * in the field than the minimum specified by the minChars config option.  It also clears any filter previously
33270      * saved in the current store (defaults to false)
33271      */
33272     doQuery : function(q, forceAll){
33273         
33274         Roo.log('doQuery?');
33275         if(q === undefined || q === null){
33276             q = '';
33277         }
33278         var qe = {
33279             query: q,
33280             forceAll: forceAll,
33281             combo: this,
33282             cancel:false
33283         };
33284         if(this.fireEvent('beforequery', qe)===false || qe.cancel){
33285             return false;
33286         }
33287         q = qe.query;
33288         forceAll = qe.forceAll;
33289         if(forceAll === true || (q.length >= this.minChars)){
33290             if(this.lastQuery != q || this.alwaysQuery){
33291                 this.lastQuery = q;
33292                 if(this.mode == 'local'){
33293                     this.selectedIndex = -1;
33294                     if(forceAll){
33295                         this.store.clearFilter();
33296                     }else{
33297                         this.store.filter(this.displayField, q);
33298                     }
33299                     this.onLoad();
33300                 }else{
33301                     this.store.baseParams[this.queryParam] = q;
33302                     this.store.load({
33303                         params: this.getParams(q)
33304                     });
33305                     this.expand();
33306                 }
33307             }else{
33308                 this.selectedIndex = -1;
33309                 this.onLoad();   
33310             }
33311         }
33312     },
33313
33314     // private
33315     getParams : function(q){
33316         var p = {};
33317         //p[this.queryParam] = q;
33318         if(this.pageSize){
33319             p.start = 0;
33320             p.limit = this.pageSize;
33321         }
33322         return p;
33323     },
33324
33325     /**
33326      * Hides the dropdown list if it is currently expanded. Fires the 'collapse' event on completion.
33327      */
33328     collapse : function(){
33329         
33330     },
33331
33332     // private
33333     collapseIf : function(e){
33334         
33335     },
33336
33337     /**
33338      * Expands the dropdown list if it is currently hidden. Fires the 'expand' event on completion.
33339      */
33340     expand : function(){
33341         
33342     } ,
33343
33344     // private
33345      
33346
33347     /** 
33348     * @cfg {Boolean} grow 
33349     * @hide 
33350     */
33351     /** 
33352     * @cfg {Number} growMin 
33353     * @hide 
33354     */
33355     /** 
33356     * @cfg {Number} growMax 
33357     * @hide 
33358     */
33359     /**
33360      * @hide
33361      * @method autoSize
33362      */
33363     
33364     setWidth : function()
33365     {
33366         
33367     },
33368     getResizeEl : function(){
33369         return this.el;
33370     }
33371 });//<script type="text/javasscript">
33372  
33373
33374 /**
33375  * @class Roo.DDView
33376  * A DnD enabled version of Roo.View.
33377  * @param {Element/String} container The Element in which to create the View.
33378  * @param {String} tpl The template string used to create the markup for each element of the View
33379  * @param {Object} config The configuration properties. These include all the config options of
33380  * {@link Roo.View} plus some specific to this class.<br>
33381  * <p>
33382  * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
33383  * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
33384  * <p>
33385  * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
33386 .x-view-drag-insert-above {
33387         border-top:1px dotted #3366cc;
33388 }
33389 .x-view-drag-insert-below {
33390         border-bottom:1px dotted #3366cc;
33391 }
33392 </code></pre>
33393  * 
33394  */
33395  
33396 Roo.DDView = function(container, tpl, config) {
33397     Roo.DDView.superclass.constructor.apply(this, arguments);
33398     this.getEl().setStyle("outline", "0px none");
33399     this.getEl().unselectable();
33400     if (this.dragGroup) {
33401         this.setDraggable(this.dragGroup.split(","));
33402     }
33403     if (this.dropGroup) {
33404         this.setDroppable(this.dropGroup.split(","));
33405     }
33406     if (this.deletable) {
33407         this.setDeletable();
33408     }
33409     this.isDirtyFlag = false;
33410         this.addEvents({
33411                 "drop" : true
33412         });
33413 };
33414
33415 Roo.extend(Roo.DDView, Roo.View, {
33416 /**     @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
33417 /**     @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
33418 /**     @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
33419 /**     @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
33420
33421         isFormField: true,
33422
33423         reset: Roo.emptyFn,
33424         
33425         clearInvalid: Roo.form.Field.prototype.clearInvalid,
33426
33427         validate: function() {
33428                 return true;
33429         },
33430         
33431         destroy: function() {
33432                 this.purgeListeners();
33433                 this.getEl.removeAllListeners();
33434                 this.getEl().remove();
33435                 if (this.dragZone) {
33436                         if (this.dragZone.destroy) {
33437                                 this.dragZone.destroy();
33438                         }
33439                 }
33440                 if (this.dropZone) {
33441                         if (this.dropZone.destroy) {
33442                                 this.dropZone.destroy();
33443                         }
33444                 }
33445         },
33446
33447 /**     Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
33448         getName: function() {
33449                 return this.name;
33450         },
33451
33452 /**     Loads the View from a JSON string representing the Records to put into the Store. */
33453         setValue: function(v) {
33454                 if (!this.store) {
33455                         throw "DDView.setValue(). DDView must be constructed with a valid Store";
33456                 }
33457                 var data = {};
33458                 data[this.store.reader.meta.root] = v ? [].concat(v) : [];
33459                 this.store.proxy = new Roo.data.MemoryProxy(data);
33460                 this.store.load();
33461         },
33462
33463 /**     @return {String} a parenthesised list of the ids of the Records in the View. */
33464         getValue: function() {
33465                 var result = '(';
33466                 this.store.each(function(rec) {
33467                         result += rec.id + ',';
33468                 });
33469                 return result.substr(0, result.length - 1) + ')';
33470         },
33471         
33472         getIds: function() {
33473                 var i = 0, result = new Array(this.store.getCount());
33474                 this.store.each(function(rec) {
33475                         result[i++] = rec.id;
33476                 });
33477                 return result;
33478         },
33479         
33480         isDirty: function() {
33481                 return this.isDirtyFlag;
33482         },
33483
33484 /**
33485  *      Part of the Roo.dd.DropZone interface. If no target node is found, the
33486  *      whole Element becomes the target, and this causes the drop gesture to append.
33487  */
33488     getTargetFromEvent : function(e) {
33489                 var target = e.getTarget();
33490                 while ((target !== null) && (target.parentNode != this.el.dom)) {
33491                 target = target.parentNode;
33492                 }
33493                 if (!target) {
33494                         target = this.el.dom.lastChild || this.el.dom;
33495                 }
33496                 return target;
33497     },
33498
33499 /**
33500  *      Create the drag data which consists of an object which has the property "ddel" as
33501  *      the drag proxy element. 
33502  */
33503     getDragData : function(e) {
33504         var target = this.findItemFromChild(e.getTarget());
33505                 if(target) {
33506                         this.handleSelection(e);
33507                         var selNodes = this.getSelectedNodes();
33508             var dragData = {
33509                 source: this,
33510                 copy: this.copy || (this.allowCopy && e.ctrlKey),
33511                 nodes: selNodes,
33512                 records: []
33513                         };
33514                         var selectedIndices = this.getSelectedIndexes();
33515                         for (var i = 0; i < selectedIndices.length; i++) {
33516                                 dragData.records.push(this.store.getAt(selectedIndices[i]));
33517                         }
33518                         if (selNodes.length == 1) {
33519                                 dragData.ddel = target.cloneNode(true); // the div element
33520                         } else {
33521                                 var div = document.createElement('div'); // create the multi element drag "ghost"
33522                                 div.className = 'multi-proxy';
33523                                 for (var i = 0, len = selNodes.length; i < len; i++) {
33524                                         div.appendChild(selNodes[i].cloneNode(true));
33525                                 }
33526                                 dragData.ddel = div;
33527                         }
33528             //console.log(dragData)
33529             //console.log(dragData.ddel.innerHTML)
33530                         return dragData;
33531                 }
33532         //console.log('nodragData')
33533                 return false;
33534     },
33535     
33536 /**     Specify to which ddGroup items in this DDView may be dragged. */
33537     setDraggable: function(ddGroup) {
33538         if (ddGroup instanceof Array) {
33539                 Roo.each(ddGroup, this.setDraggable, this);
33540                 return;
33541         }
33542         if (this.dragZone) {
33543                 this.dragZone.addToGroup(ddGroup);
33544         } else {
33545                         this.dragZone = new Roo.dd.DragZone(this.getEl(), {
33546                                 containerScroll: true,
33547                                 ddGroup: ddGroup 
33548
33549                         });
33550 //                      Draggability implies selection. DragZone's mousedown selects the element.
33551                         if (!this.multiSelect) { this.singleSelect = true; }
33552
33553 //                      Wire the DragZone's handlers up to methods in *this*
33554                         this.dragZone.getDragData = this.getDragData.createDelegate(this);
33555                 }
33556     },
33557
33558 /**     Specify from which ddGroup this DDView accepts drops. */
33559     setDroppable: function(ddGroup) {
33560         if (ddGroup instanceof Array) {
33561                 Roo.each(ddGroup, this.setDroppable, this);
33562                 return;
33563         }
33564         if (this.dropZone) {
33565                 this.dropZone.addToGroup(ddGroup);
33566         } else {
33567                         this.dropZone = new Roo.dd.DropZone(this.getEl(), {
33568                                 containerScroll: true,
33569                                 ddGroup: ddGroup
33570                         });
33571
33572 //                      Wire the DropZone's handlers up to methods in *this*
33573                         this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
33574                         this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
33575                         this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
33576                         this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
33577                         this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
33578                 }
33579     },
33580
33581 /**     Decide whether to drop above or below a View node. */
33582     getDropPoint : function(e, n, dd){
33583         if (n == this.el.dom) { return "above"; }
33584                 var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
33585                 var c = t + (b - t) / 2;
33586                 var y = Roo.lib.Event.getPageY(e);
33587                 if(y <= c) {
33588                         return "above";
33589                 }else{
33590                         return "below";
33591                 }
33592     },
33593
33594     onNodeEnter : function(n, dd, e, data){
33595                 return false;
33596     },
33597     
33598     onNodeOver : function(n, dd, e, data){
33599                 var pt = this.getDropPoint(e, n, dd);
33600                 // set the insert point style on the target node
33601                 var dragElClass = this.dropNotAllowed;
33602                 if (pt) {
33603                         var targetElClass;
33604                         if (pt == "above"){
33605                                 dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
33606                                 targetElClass = "x-view-drag-insert-above";
33607                         } else {
33608                                 dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
33609                                 targetElClass = "x-view-drag-insert-below";
33610                         }
33611                         if (this.lastInsertClass != targetElClass){
33612                                 Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
33613                                 this.lastInsertClass = targetElClass;
33614                         }
33615                 }
33616                 return dragElClass;
33617         },
33618
33619     onNodeOut : function(n, dd, e, data){
33620                 this.removeDropIndicators(n);
33621     },
33622
33623     onNodeDrop : function(n, dd, e, data){
33624         if (this.fireEvent("drop", this, n, dd, e, data) === false) {
33625                 return false;
33626         }
33627         var pt = this.getDropPoint(e, n, dd);
33628                 var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
33629                 if (pt == "below") { insertAt++; }
33630                 for (var i = 0; i < data.records.length; i++) {
33631                         var r = data.records[i];
33632                         var dup = this.store.getById(r.id);
33633                         if (dup && (dd != this.dragZone)) {
33634                                 Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
33635                         } else {
33636                                 if (data.copy) {
33637                                         this.store.insert(insertAt++, r.copy());
33638                                 } else {
33639                                         data.source.isDirtyFlag = true;
33640                                         r.store.remove(r);
33641                                         this.store.insert(insertAt++, r);
33642                                 }
33643                                 this.isDirtyFlag = true;
33644                         }
33645                 }
33646                 this.dragZone.cachedTarget = null;
33647                 return true;
33648     },
33649
33650     removeDropIndicators : function(n){
33651                 if(n){
33652                         Roo.fly(n).removeClass([
33653                                 "x-view-drag-insert-above",
33654                                 "x-view-drag-insert-below"]);
33655                         this.lastInsertClass = "_noclass";
33656                 }
33657     },
33658
33659 /**
33660  *      Utility method. Add a delete option to the DDView's context menu.
33661  *      @param {String} imageUrl The URL of the "delete" icon image.
33662  */
33663         setDeletable: function(imageUrl) {
33664                 if (!this.singleSelect && !this.multiSelect) {
33665                         this.singleSelect = true;
33666                 }
33667                 var c = this.getContextMenu();
33668                 this.contextMenu.on("itemclick", function(item) {
33669                         switch (item.id) {
33670                                 case "delete":
33671                                         this.remove(this.getSelectedIndexes());
33672                                         break;
33673                         }
33674                 }, this);
33675                 this.contextMenu.add({
33676                         icon: imageUrl,
33677                         id: "delete",
33678                         text: 'Delete'
33679                 });
33680         },
33681         
33682 /**     Return the context menu for this DDView. */
33683         getContextMenu: function() {
33684                 if (!this.contextMenu) {
33685 //                      Create the View's context menu
33686                         this.contextMenu = new Roo.menu.Menu({
33687                                 id: this.id + "-contextmenu"
33688                         });
33689                         this.el.on("contextmenu", this.showContextMenu, this);
33690                 }
33691                 return this.contextMenu;
33692         },
33693         
33694         disableContextMenu: function() {
33695                 if (this.contextMenu) {
33696                         this.el.un("contextmenu", this.showContextMenu, this);
33697                 }
33698         },
33699
33700         showContextMenu: function(e, item) {
33701         item = this.findItemFromChild(e.getTarget());
33702                 if (item) {
33703                         e.stopEvent();
33704                         this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
33705                         this.contextMenu.showAt(e.getXY());
33706             }
33707     },
33708
33709 /**
33710  *      Remove {@link Roo.data.Record}s at the specified indices.
33711  *      @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
33712  */
33713     remove: function(selectedIndices) {
33714                 selectedIndices = [].concat(selectedIndices);
33715                 for (var i = 0; i < selectedIndices.length; i++) {
33716                         var rec = this.store.getAt(selectedIndices[i]);
33717                         this.store.remove(rec);
33718                 }
33719     },
33720
33721 /**
33722  *      Double click fires the event, but also, if this is draggable, and there is only one other
33723  *      related DropZone, it transfers the selected node.
33724  */
33725     onDblClick : function(e){
33726         var item = this.findItemFromChild(e.getTarget());
33727         if(item){
33728             if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
33729                 return false;
33730             }
33731             if (this.dragGroup) {
33732                     var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
33733                     while (targets.indexOf(this.dropZone) > -1) {
33734                             targets.remove(this.dropZone);
33735                                 }
33736                     if (targets.length == 1) {
33737                                         this.dragZone.cachedTarget = null;
33738                         var el = Roo.get(targets[0].getEl());
33739                         var box = el.getBox(true);
33740                         targets[0].onNodeDrop(el.dom, {
33741                                 target: el.dom,
33742                                 xy: [box.x, box.y + box.height - 1]
33743                         }, null, this.getDragData(e));
33744                     }
33745                 }
33746         }
33747     },
33748     
33749     handleSelection: function(e) {
33750                 this.dragZone.cachedTarget = null;
33751         var item = this.findItemFromChild(e.getTarget());
33752         if (!item) {
33753                 this.clearSelections(true);
33754                 return;
33755         }
33756                 if (item && (this.multiSelect || this.singleSelect)){
33757                         if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
33758                                 this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
33759                         }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
33760                                 this.unselect(item);
33761                         } else {
33762                                 this.select(item, this.multiSelect && e.ctrlKey);
33763                                 this.lastSelection = item;
33764                         }
33765                 }
33766     },
33767
33768     onItemClick : function(item, index, e){
33769                 if(this.fireEvent("beforeclick", this, index, item, e) === false){
33770                         return false;
33771                 }
33772                 return true;
33773     },
33774
33775     unselect : function(nodeInfo, suppressEvent){
33776                 var node = this.getNode(nodeInfo);
33777                 if(node && this.isSelected(node)){
33778                         if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
33779                                 Roo.fly(node).removeClass(this.selectedClass);
33780                                 this.selections.remove(node);
33781                                 if(!suppressEvent){
33782                                         this.fireEvent("selectionchange", this, this.selections);
33783                                 }
33784                         }
33785                 }
33786     }
33787 });
33788 /*
33789  * Based on:
33790  * Ext JS Library 1.1.1
33791  * Copyright(c) 2006-2007, Ext JS, LLC.
33792  *
33793  * Originally Released Under LGPL - original licence link has changed is not relivant.
33794  *
33795  * Fork - LGPL
33796  * <script type="text/javascript">
33797  */
33798  
33799 /**
33800  * @class Roo.LayoutManager
33801  * @extends Roo.util.Observable
33802  * Base class for layout managers.
33803  */
33804 Roo.LayoutManager = function(container, config){
33805     Roo.LayoutManager.superclass.constructor.call(this);
33806     this.el = Roo.get(container);
33807     // ie scrollbar fix
33808     if(this.el.dom == document.body && Roo.isIE && !config.allowScroll){
33809         document.body.scroll = "no";
33810     }else if(this.el.dom != document.body && this.el.getStyle('position') == 'static'){
33811         this.el.position('relative');
33812     }
33813     this.id = this.el.id;
33814     this.el.addClass("x-layout-container");
33815     /** false to disable window resize monitoring @type Boolean */
33816     this.monitorWindowResize = true;
33817     this.regions = {};
33818     this.addEvents({
33819         /**
33820          * @event layout
33821          * Fires when a layout is performed. 
33822          * @param {Roo.LayoutManager} this
33823          */
33824         "layout" : true,
33825         /**
33826          * @event regionresized
33827          * Fires when the user resizes a region. 
33828          * @param {Roo.LayoutRegion} region The resized region
33829          * @param {Number} newSize The new size (width for east/west, height for north/south)
33830          */
33831         "regionresized" : true,
33832         /**
33833          * @event regioncollapsed
33834          * Fires when a region is collapsed. 
33835          * @param {Roo.LayoutRegion} region The collapsed region
33836          */
33837         "regioncollapsed" : true,
33838         /**
33839          * @event regionexpanded
33840          * Fires when a region is expanded.  
33841          * @param {Roo.LayoutRegion} region The expanded region
33842          */
33843         "regionexpanded" : true
33844     });
33845     this.updating = false;
33846     Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
33847 };
33848
33849 Roo.extend(Roo.LayoutManager, Roo.util.Observable, {
33850     /**
33851      * Returns true if this layout is currently being updated
33852      * @return {Boolean}
33853      */
33854     isUpdating : function(){
33855         return this.updating; 
33856     },
33857     
33858     /**
33859      * Suspend the LayoutManager from doing auto-layouts while
33860      * making multiple add or remove calls
33861      */
33862     beginUpdate : function(){
33863         this.updating = true;    
33864     },
33865     
33866     /**
33867      * Restore auto-layouts and optionally disable the manager from performing a layout
33868      * @param {Boolean} noLayout true to disable a layout update 
33869      */
33870     endUpdate : function(noLayout){
33871         this.updating = false;
33872         if(!noLayout){
33873             this.layout();
33874         }    
33875     },
33876     
33877     layout: function(){
33878         
33879     },
33880     
33881     onRegionResized : function(region, newSize){
33882         this.fireEvent("regionresized", region, newSize);
33883         this.layout();
33884     },
33885     
33886     onRegionCollapsed : function(region){
33887         this.fireEvent("regioncollapsed", region);
33888     },
33889     
33890     onRegionExpanded : function(region){
33891         this.fireEvent("regionexpanded", region);
33892     },
33893         
33894     /**
33895      * Returns the size of the current view. This method normalizes document.body and element embedded layouts and
33896      * performs box-model adjustments.
33897      * @return {Object} The size as an object {width: (the width), height: (the height)}
33898      */
33899     getViewSize : function(){
33900         var size;
33901         if(this.el.dom != document.body){
33902             size = this.el.getSize();
33903         }else{
33904             size = {width: Roo.lib.Dom.getViewWidth(), height: Roo.lib.Dom.getViewHeight()};
33905         }
33906         size.width -= this.el.getBorderWidth("lr")-this.el.getPadding("lr");
33907         size.height -= this.el.getBorderWidth("tb")-this.el.getPadding("tb");
33908         return size;
33909     },
33910     
33911     /**
33912      * Returns the Element this layout is bound to.
33913      * @return {Roo.Element}
33914      */
33915     getEl : function(){
33916         return this.el;
33917     },
33918     
33919     /**
33920      * Returns the specified region.
33921      * @param {String} target The region key ('center', 'north', 'south', 'east' or 'west')
33922      * @return {Roo.LayoutRegion}
33923      */
33924     getRegion : function(target){
33925         return this.regions[target.toLowerCase()];
33926     },
33927     
33928     onWindowResize : function(){
33929         if(this.monitorWindowResize){
33930             this.layout();
33931         }
33932     }
33933 });/*
33934  * Based on:
33935  * Ext JS Library 1.1.1
33936  * Copyright(c) 2006-2007, Ext JS, LLC.
33937  *
33938  * Originally Released Under LGPL - original licence link has changed is not relivant.
33939  *
33940  * Fork - LGPL
33941  * <script type="text/javascript">
33942  */
33943 /**
33944  * @class Roo.BorderLayout
33945  * @extends Roo.LayoutManager
33946  * @children Roo.ContentPanel
33947  * This class represents a common layout manager used in desktop applications. For screenshots and more details,
33948  * please see: <br><br>
33949  * <a href="http://www.jackslocum.com/yui/2006/10/19/cross-browser-web-20-layouts-with-yahoo-ui/">Cross Browser Layouts - Part 1</a><br>
33950  * <a href="http://www.jackslocum.com/yui/2006/10/28/cross-browser-web-20-layouts-part-2-ajax-feed-viewer-20/">Cross Browser Layouts - Part 2</a><br><br>
33951  * Example:
33952  <pre><code>
33953  var layout = new Roo.BorderLayout(document.body, {
33954     north: {
33955         initialSize: 25,
33956         titlebar: false
33957     },
33958     west: {
33959         split:true,
33960         initialSize: 200,
33961         minSize: 175,
33962         maxSize: 400,
33963         titlebar: true,
33964         collapsible: true
33965     },
33966     east: {
33967         split:true,
33968         initialSize: 202,
33969         minSize: 175,
33970         maxSize: 400,
33971         titlebar: true,
33972         collapsible: true
33973     },
33974     south: {
33975         split:true,
33976         initialSize: 100,
33977         minSize: 100,
33978         maxSize: 200,
33979         titlebar: true,
33980         collapsible: true
33981     },
33982     center: {
33983         titlebar: true,
33984         autoScroll:true,
33985         resizeTabs: true,
33986         minTabWidth: 50,
33987         preferredTabWidth: 150
33988     }
33989 });
33990
33991 // shorthand
33992 var CP = Roo.ContentPanel;
33993
33994 layout.beginUpdate();
33995 layout.add("north", new CP("north", "North"));
33996 layout.add("south", new CP("south", {title: "South", closable: true}));
33997 layout.add("west", new CP("west", {title: "West"}));
33998 layout.add("east", new CP("autoTabs", {title: "Auto Tabs", closable: true}));
33999 layout.add("center", new CP("center1", {title: "Close Me", closable: true}));
34000 layout.add("center", new CP("center2", {title: "Center Panel", closable: false}));
34001 layout.getRegion("center").showPanel("center1");
34002 layout.endUpdate();
34003 </code></pre>
34004
34005 <b>The container the layout is rendered into can be either the body element or any other element.
34006 If it is not the body element, the container needs to either be an absolute positioned element,
34007 or you will need to add "position:relative" to the css of the container.  You will also need to specify
34008 the container size if it is not the body element.</b>
34009
34010 * @constructor
34011 * Create a new BorderLayout
34012 * @param {String/HTMLElement/Element} container The container this layout is bound to
34013 * @param {Object} config Configuration options
34014  */
34015 Roo.BorderLayout = function(container, config){
34016     config = config || {};
34017     Roo.BorderLayout.superclass.constructor.call(this, container, config);
34018     this.factory = config.factory || Roo.BorderLayout.RegionFactory;
34019     for(var i = 0, len = this.factory.validRegions.length; i < len; i++) {
34020         var target = this.factory.validRegions[i];
34021         if(config[target]){
34022             this.addRegion(target, config[target]);
34023         }
34024     }
34025 };
34026
34027 Roo.extend(Roo.BorderLayout, Roo.LayoutManager, {
34028         
34029         /**
34030          * @cfg {Roo.LayoutRegion} east
34031          */
34032         /**
34033          * @cfg {Roo.LayoutRegion} west
34034          */
34035         /**
34036          * @cfg {Roo.LayoutRegion} north
34037          */
34038         /**
34039          * @cfg {Roo.LayoutRegion} south
34040          */
34041         /**
34042          * @cfg {Roo.LayoutRegion} center
34043          */
34044     /**
34045      * Creates and adds a new region if it doesn't already exist.
34046      * @param {String} target The target region key (north, south, east, west or center).
34047      * @param {Object} config The regions config object
34048      * @return {BorderLayoutRegion} The new region
34049      */
34050     addRegion : function(target, config){
34051         if(!this.regions[target]){
34052             var r = this.factory.create(target, this, config);
34053             this.bindRegion(target, r);
34054         }
34055         return this.regions[target];
34056     },
34057
34058     // private (kinda)
34059     bindRegion : function(name, r){
34060         this.regions[name] = r;
34061         r.on("visibilitychange", this.layout, this);
34062         r.on("paneladded", this.layout, this);
34063         r.on("panelremoved", this.layout, this);
34064         r.on("invalidated", this.layout, this);
34065         r.on("resized", this.onRegionResized, this);
34066         r.on("collapsed", this.onRegionCollapsed, this);
34067         r.on("expanded", this.onRegionExpanded, this);
34068     },
34069
34070     /**
34071      * Performs a layout update.
34072      */
34073     layout : function(){
34074         if(this.updating) {
34075             return;
34076         }
34077         var size = this.getViewSize();
34078         var w = size.width;
34079         var h = size.height;
34080         var centerW = w;
34081         var centerH = h;
34082         var centerY = 0;
34083         var centerX = 0;
34084         //var x = 0, y = 0;
34085
34086         var rs = this.regions;
34087         var north = rs["north"];
34088         var south = rs["south"]; 
34089         var west = rs["west"];
34090         var east = rs["east"];
34091         var center = rs["center"];
34092         //if(this.hideOnLayout){ // not supported anymore
34093             //c.el.setStyle("display", "none");
34094         //}
34095         if(north && north.isVisible()){
34096             var b = north.getBox();
34097             var m = north.getMargins();
34098             b.width = w - (m.left+m.right);
34099             b.x = m.left;
34100             b.y = m.top;
34101             centerY = b.height + b.y + m.bottom;
34102             centerH -= centerY;
34103             north.updateBox(this.safeBox(b));
34104         }
34105         if(south && south.isVisible()){
34106             var b = south.getBox();
34107             var m = south.getMargins();
34108             b.width = w - (m.left+m.right);
34109             b.x = m.left;
34110             var totalHeight = (b.height + m.top + m.bottom);
34111             b.y = h - totalHeight + m.top;
34112             centerH -= totalHeight;
34113             south.updateBox(this.safeBox(b));
34114         }
34115         if(west && west.isVisible()){
34116             var b = west.getBox();
34117             var m = west.getMargins();
34118             b.height = centerH - (m.top+m.bottom);
34119             b.x = m.left;
34120             b.y = centerY + m.top;
34121             var totalWidth = (b.width + m.left + m.right);
34122             centerX += totalWidth;
34123             centerW -= totalWidth;
34124             west.updateBox(this.safeBox(b));
34125         }
34126         if(east && east.isVisible()){
34127             var b = east.getBox();
34128             var m = east.getMargins();
34129             b.height = centerH - (m.top+m.bottom);
34130             var totalWidth = (b.width + m.left + m.right);
34131             b.x = w - totalWidth + m.left;
34132             b.y = centerY + m.top;
34133             centerW -= totalWidth;
34134             east.updateBox(this.safeBox(b));
34135         }
34136         if(center){
34137             var m = center.getMargins();
34138             var centerBox = {
34139                 x: centerX + m.left,
34140                 y: centerY + m.top,
34141                 width: centerW - (m.left+m.right),
34142                 height: centerH - (m.top+m.bottom)
34143             };
34144             //if(this.hideOnLayout){
34145                 //center.el.setStyle("display", "block");
34146             //}
34147             center.updateBox(this.safeBox(centerBox));
34148         }
34149         this.el.repaint();
34150         this.fireEvent("layout", this);
34151     },
34152
34153     // private
34154     safeBox : function(box){
34155         box.width = Math.max(0, box.width);
34156         box.height = Math.max(0, box.height);
34157         return box;
34158     },
34159
34160     /**
34161      * Adds a ContentPanel (or subclass) to this layout.
34162      * @param {String} target The target region key (north, south, east, west or center).
34163      * @param {Roo.ContentPanel} panel The panel to add
34164      * @return {Roo.ContentPanel} The added panel
34165      */
34166     add : function(target, panel){
34167          
34168         target = target.toLowerCase();
34169         return this.regions[target].add(panel);
34170     },
34171
34172     /**
34173      * Remove a ContentPanel (or subclass) to this layout.
34174      * @param {String} target The target region key (north, south, east, west or center).
34175      * @param {Number/String/Roo.ContentPanel} panel The index, id or panel to remove
34176      * @return {Roo.ContentPanel} The removed panel
34177      */
34178     remove : function(target, panel){
34179         target = target.toLowerCase();
34180         return this.regions[target].remove(panel);
34181     },
34182
34183     /**
34184      * Searches all regions for a panel with the specified id
34185      * @param {String} panelId
34186      * @return {Roo.ContentPanel} The panel or null if it wasn't found
34187      */
34188     findPanel : function(panelId){
34189         var rs = this.regions;
34190         for(var target in rs){
34191             if(typeof rs[target] != "function"){
34192                 var p = rs[target].getPanel(panelId);
34193                 if(p){
34194                     return p;
34195                 }
34196             }
34197         }
34198         return null;
34199     },
34200
34201     /**
34202      * Searches all regions for a panel with the specified id and activates (shows) it.
34203      * @param {String/ContentPanel} panelId The panels id or the panel itself
34204      * @return {Roo.ContentPanel} The shown panel or null
34205      */
34206     showPanel : function(panelId) {
34207       var rs = this.regions;
34208       for(var target in rs){
34209          var r = rs[target];
34210          if(typeof r != "function"){
34211             if(r.hasPanel(panelId)){
34212                return r.showPanel(panelId);
34213             }
34214          }
34215       }
34216       return null;
34217    },
34218
34219    /**
34220      * Restores this layout's state using Roo.state.Manager or the state provided by the passed provider.
34221      * @param {Roo.state.Provider} provider (optional) An alternate state provider
34222      */
34223     restoreState : function(provider){
34224         if(!provider){
34225             provider = Roo.state.Manager;
34226         }
34227         var sm = new Roo.LayoutStateManager();
34228         sm.init(this, provider);
34229     },
34230
34231     /**
34232      * Adds a batch of multiple ContentPanels dynamically by passing a special regions config object.  This config
34233      * object should contain properties for each region to add ContentPanels to, and each property's value should be
34234      * a valid ContentPanel config object.  Example:
34235      * <pre><code>
34236 // Create the main layout
34237 var layout = new Roo.BorderLayout('main-ct', {
34238     west: {
34239         split:true,
34240         minSize: 175,
34241         titlebar: true
34242     },
34243     center: {
34244         title:'Components'
34245     }
34246 }, 'main-ct');
34247
34248 // Create and add multiple ContentPanels at once via configs
34249 layout.batchAdd({
34250    west: {
34251        id: 'source-files',
34252        autoCreate:true,
34253        title:'Ext Source Files',
34254        autoScroll:true,
34255        fitToFrame:true
34256    },
34257    center : {
34258        el: cview,
34259        autoScroll:true,
34260        fitToFrame:true,
34261        toolbar: tb,
34262        resizeEl:'cbody'
34263    }
34264 });
34265 </code></pre>
34266      * @param {Object} regions An object containing ContentPanel configs by region name
34267      */
34268     batchAdd : function(regions){
34269         this.beginUpdate();
34270         for(var rname in regions){
34271             var lr = this.regions[rname];
34272             if(lr){
34273                 this.addTypedPanels(lr, regions[rname]);
34274             }
34275         }
34276         this.endUpdate();
34277     },
34278
34279     // private
34280     addTypedPanels : function(lr, ps){
34281         if(typeof ps == 'string'){
34282             lr.add(new Roo.ContentPanel(ps));
34283         }
34284         else if(ps instanceof Array){
34285             for(var i =0, len = ps.length; i < len; i++){
34286                 this.addTypedPanels(lr, ps[i]);
34287             }
34288         }
34289         else if(!ps.events){ // raw config?
34290             var el = ps.el;
34291             delete ps.el; // prevent conflict
34292             lr.add(new Roo.ContentPanel(el || Roo.id(), ps));
34293         }
34294         else {  // panel object assumed!
34295             lr.add(ps);
34296         }
34297     },
34298     /**
34299      * Adds a xtype elements to the layout.
34300      * <pre><code>
34301
34302 layout.addxtype({
34303        xtype : 'ContentPanel',
34304        region: 'west',
34305        items: [ .... ]
34306    }
34307 );
34308
34309 layout.addxtype({
34310         xtype : 'NestedLayoutPanel',
34311         region: 'west',
34312         layout: {
34313            center: { },
34314            west: { }   
34315         },
34316         items : [ ... list of content panels or nested layout panels.. ]
34317    }
34318 );
34319 </code></pre>
34320      * @param {Object} cfg Xtype definition of item to add.
34321      */
34322     addxtype : function(cfg)
34323     {
34324         // basically accepts a pannel...
34325         // can accept a layout region..!?!?
34326         //Roo.log('Roo.BorderLayout add ' + cfg.xtype)
34327         
34328         if (!cfg.xtype.match(/Panel$/)) {
34329             return false;
34330         }
34331         var ret = false;
34332         
34333         if (typeof(cfg.region) == 'undefined') {
34334             Roo.log("Failed to add Panel, region was not set");
34335             Roo.log(cfg);
34336             return false;
34337         }
34338         var region = cfg.region;
34339         delete cfg.region;
34340         
34341           
34342         var xitems = [];
34343         if (cfg.items) {
34344             xitems = cfg.items;
34345             delete cfg.items;
34346         }
34347         var nb = false;
34348         
34349         switch(cfg.xtype) 
34350         {
34351             case 'ContentPanel':  // ContentPanel (el, cfg)
34352             case 'ScrollPanel':  // ContentPanel (el, cfg)
34353             case 'ViewPanel': 
34354                 if(cfg.autoCreate) {
34355                     ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
34356                 } else {
34357                     var el = this.el.createChild();
34358                     ret = new Roo[cfg.xtype](el, cfg); // new panel!!!!!
34359                 }
34360                 
34361                 this.add(region, ret);
34362                 break;
34363             
34364             
34365             case 'TreePanel': // our new panel!
34366                 cfg.el = this.el.createChild();
34367                 ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
34368                 this.add(region, ret);
34369                 break;
34370             
34371             case 'NestedLayoutPanel': 
34372                 // create a new Layout (which is  a Border Layout...
34373                 var el = this.el.createChild();
34374                 var clayout = cfg.layout;
34375                 delete cfg.layout;
34376                 clayout.items   = clayout.items  || [];
34377                 // replace this exitems with the clayout ones..
34378                 xitems = clayout.items;
34379                  
34380                 
34381                 if (region == 'center' && this.active && this.getRegion('center').panels.length < 1) {
34382                     cfg.background = false;
34383                 }
34384                 var layout = new Roo.BorderLayout(el, clayout);
34385                 
34386                 ret = new Roo[cfg.xtype](layout, cfg); // new panel!!!!!
34387                 //console.log('adding nested layout panel '  + cfg.toSource());
34388                 this.add(region, ret);
34389                 nb = {}; /// find first...
34390                 break;
34391                 
34392             case 'GridPanel': 
34393             
34394                 // needs grid and region
34395                 
34396                 //var el = this.getRegion(region).el.createChild();
34397                 var el = this.el.createChild();
34398                 // create the grid first...
34399                 
34400                 var grid = new Roo.grid[cfg.grid.xtype](el, cfg.grid);
34401                 delete cfg.grid;
34402                 if (region == 'center' && this.active ) {
34403                     cfg.background = false;
34404                 }
34405                 ret = new Roo[cfg.xtype](grid, cfg); // new panel!!!!!
34406                 
34407                 this.add(region, ret);
34408                 if (cfg.background) {
34409                     ret.on('activate', function(gp) {
34410                         if (!gp.grid.rendered) {
34411                             gp.grid.render();
34412                         }
34413                     });
34414                 } else {
34415                     grid.render();
34416                 }
34417                 break;
34418            
34419            
34420            
34421                 
34422                 
34423                 
34424             default:
34425                 if (typeof(Roo[cfg.xtype]) != 'undefined') {
34426                     
34427                     ret = new Roo[cfg.xtype](cfg); // new panel!!!!!
34428                     this.add(region, ret);
34429                 } else {
34430                 
34431                     alert("Can not add '" + cfg.xtype + "' to BorderLayout");
34432                     return null;
34433                 }
34434                 
34435              // GridPanel (grid, cfg)
34436             
34437         }
34438         this.beginUpdate();
34439         // add children..
34440         var region = '';
34441         var abn = {};
34442         Roo.each(xitems, function(i)  {
34443             region = nb && i.region ? i.region : false;
34444             
34445             var add = ret.addxtype(i);
34446            
34447             if (region) {
34448                 nb[region] = nb[region] == undefined ? 0 : nb[region]+1;
34449                 if (!i.background) {
34450                     abn[region] = nb[region] ;
34451                 }
34452             }
34453             
34454         });
34455         this.endUpdate();
34456
34457         // make the last non-background panel active..
34458         //if (nb) { Roo.log(abn); }
34459         if (nb) {
34460             
34461             for(var r in abn) {
34462                 region = this.getRegion(r);
34463                 if (region) {
34464                     // tried using nb[r], but it does not work..
34465                      
34466                     region.showPanel(abn[r]);
34467                    
34468                 }
34469             }
34470         }
34471         return ret;
34472         
34473     }
34474 });
34475
34476 /**
34477  * Shortcut for creating a new BorderLayout object and adding one or more ContentPanels to it in a single step, handling
34478  * the beginUpdate and endUpdate calls internally.  The key to this method is the <b>panels</b> property that can be
34479  * provided with each region config, which allows you to add ContentPanel configs in addition to the region configs
34480  * during creation.  The following code is equivalent to the constructor-based example at the beginning of this class:
34481  * <pre><code>
34482 // shorthand
34483 var CP = Roo.ContentPanel;
34484
34485 var layout = Roo.BorderLayout.create({
34486     north: {
34487         initialSize: 25,
34488         titlebar: false,
34489         panels: [new CP("north", "North")]
34490     },
34491     west: {
34492         split:true,
34493         initialSize: 200,
34494         minSize: 175,
34495         maxSize: 400,
34496         titlebar: true,
34497         collapsible: true,
34498         panels: [new CP("west", {title: "West"})]
34499     },
34500     east: {
34501         split:true,
34502         initialSize: 202,
34503         minSize: 175,
34504         maxSize: 400,
34505         titlebar: true,
34506         collapsible: true,
34507         panels: [new CP("autoTabs", {title: "Auto Tabs", closable: true})]
34508     },
34509     south: {
34510         split:true,
34511         initialSize: 100,
34512         minSize: 100,
34513         maxSize: 200,
34514         titlebar: true,
34515         collapsible: true,
34516         panels: [new CP("south", {title: "South", closable: true})]
34517     },
34518     center: {
34519         titlebar: true,
34520         autoScroll:true,
34521         resizeTabs: true,
34522         minTabWidth: 50,
34523         preferredTabWidth: 150,
34524         panels: [
34525             new CP("center1", {title: "Close Me", closable: true}),
34526             new CP("center2", {title: "Center Panel", closable: false})
34527         ]
34528     }
34529 }, document.body);
34530
34531 layout.getRegion("center").showPanel("center1");
34532 </code></pre>
34533  * @param config
34534  * @param targetEl
34535  */
34536 Roo.BorderLayout.create = function(config, targetEl){
34537     var layout = new Roo.BorderLayout(targetEl || document.body, config);
34538     layout.beginUpdate();
34539     var regions = Roo.BorderLayout.RegionFactory.validRegions;
34540     for(var j = 0, jlen = regions.length; j < jlen; j++){
34541         var lr = regions[j];
34542         if(layout.regions[lr] && config[lr].panels){
34543             var r = layout.regions[lr];
34544             var ps = config[lr].panels;
34545             layout.addTypedPanels(r, ps);
34546         }
34547     }
34548     layout.endUpdate();
34549     return layout;
34550 };
34551
34552 // private
34553 Roo.BorderLayout.RegionFactory = {
34554     // private
34555     validRegions : ["north","south","east","west","center"],
34556
34557     // private
34558     create : function(target, mgr, config){
34559         target = target.toLowerCase();
34560         if(config.lightweight || config.basic){
34561             return new Roo.BasicLayoutRegion(mgr, config, target);
34562         }
34563         switch(target){
34564             case "north":
34565                 return new Roo.NorthLayoutRegion(mgr, config);
34566             case "south":
34567                 return new Roo.SouthLayoutRegion(mgr, config);
34568             case "east":
34569                 return new Roo.EastLayoutRegion(mgr, config);
34570             case "west":
34571                 return new Roo.WestLayoutRegion(mgr, config);
34572             case "center":
34573                 return new Roo.CenterLayoutRegion(mgr, config);
34574         }
34575         throw 'Layout region "'+target+'" not supported.';
34576     }
34577 };/*
34578  * Based on:
34579  * Ext JS Library 1.1.1
34580  * Copyright(c) 2006-2007, Ext JS, LLC.
34581  *
34582  * Originally Released Under LGPL - original licence link has changed is not relivant.
34583  *
34584  * Fork - LGPL
34585  * <script type="text/javascript">
34586  */
34587  
34588 /**
34589  * @class Roo.BasicLayoutRegion
34590  * @extends Roo.util.Observable
34591  * This class represents a lightweight region in a layout manager. This region does not move dom nodes
34592  * and does not have a titlebar, tabs or any other features. All it does is size and position 
34593  * panels. To create a BasicLayoutRegion, add lightweight:true or basic:true to your regions config.
34594  */
34595 Roo.BasicLayoutRegion = function(mgr, config, pos, skipConfig){
34596     this.mgr = mgr;
34597     this.position  = pos;
34598     this.events = {
34599         /**
34600          * @scope Roo.BasicLayoutRegion
34601          */
34602         
34603         /**
34604          * @event beforeremove
34605          * Fires before a panel is removed (or closed). To cancel the removal set "e.cancel = true" on the event argument.
34606          * @param {Roo.LayoutRegion} this
34607          * @param {Roo.ContentPanel} panel The panel
34608          * @param {Object} e The cancel event object
34609          */
34610         "beforeremove" : true,
34611         /**
34612          * @event invalidated
34613          * Fires when the layout for this region is changed.
34614          * @param {Roo.LayoutRegion} this
34615          */
34616         "invalidated" : true,
34617         /**
34618          * @event visibilitychange
34619          * Fires when this region is shown or hidden 
34620          * @param {Roo.LayoutRegion} this
34621          * @param {Boolean} visibility true or false
34622          */
34623         "visibilitychange" : true,
34624         /**
34625          * @event paneladded
34626          * Fires when a panel is added. 
34627          * @param {Roo.LayoutRegion} this
34628          * @param {Roo.ContentPanel} panel The panel
34629          */
34630         "paneladded" : true,
34631         /**
34632          * @event panelremoved
34633          * Fires when a panel is removed. 
34634          * @param {Roo.LayoutRegion} this
34635          * @param {Roo.ContentPanel} panel The panel
34636          */
34637         "panelremoved" : true,
34638         /**
34639          * @event beforecollapse
34640          * Fires when this region before collapse.
34641          * @param {Roo.LayoutRegion} this
34642          */
34643         "beforecollapse" : true,
34644         /**
34645          * @event collapsed
34646          * Fires when this region is collapsed.
34647          * @param {Roo.LayoutRegion} this
34648          */
34649         "collapsed" : true,
34650         /**
34651          * @event expanded
34652          * Fires when this region is expanded.
34653          * @param {Roo.LayoutRegion} this
34654          */
34655         "expanded" : true,
34656         /**
34657          * @event slideshow
34658          * Fires when this region is slid into view.
34659          * @param {Roo.LayoutRegion} this
34660          */
34661         "slideshow" : true,
34662         /**
34663          * @event slidehide
34664          * Fires when this region slides out of view. 
34665          * @param {Roo.LayoutRegion} this
34666          */
34667         "slidehide" : true,
34668         /**
34669          * @event panelactivated
34670          * Fires when a panel is activated. 
34671          * @param {Roo.LayoutRegion} this
34672          * @param {Roo.ContentPanel} panel The activated panel
34673          */
34674         "panelactivated" : true,
34675         /**
34676          * @event resized
34677          * Fires when the user resizes this region. 
34678          * @param {Roo.LayoutRegion} this
34679          * @param {Number} newSize The new size (width for east/west, height for north/south)
34680          */
34681         "resized" : true
34682     };
34683     /** A collection of panels in this region. @type Roo.util.MixedCollection */
34684     this.panels = new Roo.util.MixedCollection();
34685     this.panels.getKey = this.getPanelId.createDelegate(this);
34686     this.box = null;
34687     this.activePanel = null;
34688     // ensure listeners are added...
34689     
34690     if (config.listeners || config.events) {
34691         Roo.BasicLayoutRegion.superclass.constructor.call(this, {
34692             listeners : config.listeners || {},
34693             events : config.events || {}
34694         });
34695     }
34696     
34697     if(skipConfig !== true){
34698         this.applyConfig(config);
34699     }
34700 };
34701
34702 Roo.extend(Roo.BasicLayoutRegion, Roo.util.Observable, {
34703     getPanelId : function(p){
34704         return p.getId();
34705     },
34706     
34707     applyConfig : function(config){
34708         this.margins = config.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
34709         this.config = config;
34710         
34711     },
34712     
34713     /**
34714      * Resizes the region to the specified size. For vertical regions (west, east) this adjusts 
34715      * the width, for horizontal (north, south) the height.
34716      * @param {Number} newSize The new width or height
34717      */
34718     resizeTo : function(newSize){
34719         var el = this.el ? this.el :
34720                  (this.activePanel ? this.activePanel.getEl() : null);
34721         if(el){
34722             switch(this.position){
34723                 case "east":
34724                 case "west":
34725                     el.setWidth(newSize);
34726                     this.fireEvent("resized", this, newSize);
34727                 break;
34728                 case "north":
34729                 case "south":
34730                     el.setHeight(newSize);
34731                     this.fireEvent("resized", this, newSize);
34732                 break;                
34733             }
34734         }
34735     },
34736     
34737     getBox : function(){
34738         return this.activePanel ? this.activePanel.getEl().getBox(false, true) : null;
34739     },
34740     
34741     getMargins : function(){
34742         return this.margins;
34743     },
34744     
34745     updateBox : function(box){
34746         this.box = box;
34747         var el = this.activePanel.getEl();
34748         el.dom.style.left = box.x + "px";
34749         el.dom.style.top = box.y + "px";
34750         this.activePanel.setSize(box.width, box.height);
34751     },
34752     
34753     /**
34754      * Returns the container element for this region.
34755      * @return {Roo.Element}
34756      */
34757     getEl : function(){
34758         return this.activePanel;
34759     },
34760     
34761     /**
34762      * Returns true if this region is currently visible.
34763      * @return {Boolean}
34764      */
34765     isVisible : function(){
34766         return this.activePanel ? true : false;
34767     },
34768     
34769     setActivePanel : function(panel){
34770         panel = this.getPanel(panel);
34771         if(this.activePanel && this.activePanel != panel){
34772             this.activePanel.setActiveState(false);
34773             this.activePanel.getEl().setLeftTop(-10000,-10000);
34774         }
34775         this.activePanel = panel;
34776         panel.setActiveState(true);
34777         if(this.box){
34778             panel.setSize(this.box.width, this.box.height);
34779         }
34780         this.fireEvent("panelactivated", this, panel);
34781         this.fireEvent("invalidated");
34782     },
34783     
34784     /**
34785      * Show the specified panel.
34786      * @param {Number/String/ContentPanel} panelId The panels index, id or the panel itself
34787      * @return {Roo.ContentPanel} The shown panel or null
34788      */
34789     showPanel : function(panel){
34790         if(panel = this.getPanel(panel)){
34791             this.setActivePanel(panel);
34792         }
34793         return panel;
34794     },
34795     
34796     /**
34797      * Get the active panel for this region.
34798      * @return {Roo.ContentPanel} The active panel or null
34799      */
34800     getActivePanel : function(){
34801         return this.activePanel;
34802     },
34803     
34804     /**
34805      * Add the passed ContentPanel(s)
34806      * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
34807      * @return {Roo.ContentPanel} The panel added (if only one was added)
34808      */
34809     add : function(panel){
34810         if(arguments.length > 1){
34811             for(var i = 0, len = arguments.length; i < len; i++) {
34812                 this.add(arguments[i]);
34813             }
34814             return null;
34815         }
34816         if(this.hasPanel(panel)){
34817             this.showPanel(panel);
34818             return panel;
34819         }
34820         var el = panel.getEl();
34821         if(el.dom.parentNode != this.mgr.el.dom){
34822             this.mgr.el.dom.appendChild(el.dom);
34823         }
34824         if(panel.setRegion){
34825             panel.setRegion(this);
34826         }
34827         this.panels.add(panel);
34828         el.setStyle("position", "absolute");
34829         if(!panel.background){
34830             this.setActivePanel(panel);
34831             if(this.config.initialSize && this.panels.getCount()==1){
34832                 this.resizeTo(this.config.initialSize);
34833             }
34834         }
34835         this.fireEvent("paneladded", this, panel);
34836         return panel;
34837     },
34838     
34839     /**
34840      * Returns true if the panel is in this region.
34841      * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
34842      * @return {Boolean}
34843      */
34844     hasPanel : function(panel){
34845         if(typeof panel == "object"){ // must be panel obj
34846             panel = panel.getId();
34847         }
34848         return this.getPanel(panel) ? true : false;
34849     },
34850     
34851     /**
34852      * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
34853      * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
34854      * @param {Boolean} preservePanel Overrides the config preservePanel option
34855      * @return {Roo.ContentPanel} The panel that was removed
34856      */
34857     remove : function(panel, preservePanel){
34858         panel = this.getPanel(panel);
34859         if(!panel){
34860             return null;
34861         }
34862         var e = {};
34863         this.fireEvent("beforeremove", this, panel, e);
34864         if(e.cancel === true){
34865             return null;
34866         }
34867         var panelId = panel.getId();
34868         this.panels.removeKey(panelId);
34869         return panel;
34870     },
34871     
34872     /**
34873      * Returns the panel specified or null if it's not in this region.
34874      * @param {Number/String/ContentPanel} panel The panels index, id or the panel itself
34875      * @return {Roo.ContentPanel}
34876      */
34877     getPanel : function(id){
34878         if(typeof id == "object"){ // must be panel obj
34879             return id;
34880         }
34881         return this.panels.get(id);
34882     },
34883     
34884     /**
34885      * Returns this regions position (north/south/east/west/center).
34886      * @return {String} 
34887      */
34888     getPosition: function(){
34889         return this.position;    
34890     }
34891 });/*
34892  * Based on:
34893  * Ext JS Library 1.1.1
34894  * Copyright(c) 2006-2007, Ext JS, LLC.
34895  *
34896  * Originally Released Under LGPL - original licence link has changed is not relivant.
34897  *
34898  * Fork - LGPL
34899  * <script type="text/javascript">
34900  */
34901  
34902 /**
34903  * @class Roo.LayoutRegion
34904  * @extends Roo.BasicLayoutRegion
34905  * This class represents a region in a layout manager.
34906  * @cfg {Boolean}   collapsible     False to disable collapsing (defaults to true)
34907  * @cfg {Boolean}   collapsed       True to set the initial display to collapsed (defaults to false)
34908  * @cfg {Boolean}   floatable       False to disable floating (defaults to true)
34909  * @cfg {Object}    margins         Margins for the element (defaults to {top: 0, left: 0, right:0, bottom: 0})
34910  * @cfg {Object}    cmargins        Margins for the element when collapsed (defaults to: north/south {top: 2, left: 0, right:0, bottom: 2} or east/west {top: 0, left: 2, right:2, bottom: 0})
34911  * @cfg {String}    tabPosition     (top|bottom) "top" or "bottom" (defaults to "bottom")
34912  * @cfg {String}    collapsedTitle  Optional string message to display in the collapsed block of a north or south region
34913  * @cfg {Boolean}   alwaysShowTabs  True to always display tabs even when there is only 1 panel (defaults to false)
34914  * @cfg {Boolean}   autoScroll      True to enable overflow scrolling (defaults to false)
34915  * @cfg {Boolean}   titlebar        True to display a title bar (defaults to true)
34916  * @cfg {String}    title           The title for the region (overrides panel titles)
34917  * @cfg {Boolean}   animate         True to animate expand/collapse (defaults to false)
34918  * @cfg {Boolean}   autoHide        False to disable auto hiding when the mouse leaves the "floated" region (defaults to true)
34919  * @cfg {Boolean}   preservePanels  True to preserve removed panels so they can be readded later (defaults to false)
34920  * @cfg {Boolean}   closeOnTab      True to place the close icon on the tabs instead of the region titlebar (defaults to false)
34921  * @cfg {Boolean}   hideTabs        True to hide the tab strip (defaults to false)
34922  * @cfg {Boolean}   resizeTabs      True to enable automatic tab resizing. This will resize the tabs so they are all the same size and fit within
34923  *                      the space available, similar to FireFox 1.5 tabs (defaults to false)
34924  * @cfg {Number}    minTabWidth     The minimum tab width (defaults to 40)
34925  * @cfg {Number}    preferredTabWidth The preferred tab width (defaults to 150)
34926  * @cfg {Boolean}   showPin         True to show a pin button
34927  * @cfg {Boolean}   hidden          True to start the region hidden (defaults to false)
34928  * @cfg {Boolean}   hideWhenEmpty   True to hide the region when it has no panels
34929  * @cfg {Boolean}   disableTabTips  True to disable tab tooltips
34930  * @cfg {Number}    width           For East/West panels
34931  * @cfg {Number}    height          For North/South panels
34932  * @cfg {Boolean}   split           To show the splitter
34933  * @cfg {Boolean}   toolbar         xtype configuration for a toolbar - shows on right of tabbar
34934  */
34935 Roo.LayoutRegion = function(mgr, config, pos){
34936     Roo.LayoutRegion.superclass.constructor.call(this, mgr, config, pos, true);
34937     var dh = Roo.DomHelper;
34938     /** This region's container element 
34939     * @type Roo.Element */
34940     this.el = dh.append(mgr.el.dom, {tag: "div", cls: "x-layout-panel x-layout-panel-" + this.position}, true);
34941     /** This region's title element 
34942     * @type Roo.Element */
34943
34944     this.titleEl = dh.append(this.el.dom, {tag: "div", unselectable: "on", cls: "x-unselectable x-layout-panel-hd x-layout-title-"+this.position, children:[
34945         {tag: "span", cls: "x-unselectable x-layout-panel-hd-text", unselectable: "on", html: "&#160;"},
34946         {tag: "div", cls: "x-unselectable x-layout-panel-hd-tools", unselectable: "on"}
34947     ]}, true);
34948     this.titleEl.enableDisplayMode();
34949     /** This region's title text element 
34950     * @type HTMLElement */
34951     this.titleTextEl = this.titleEl.dom.firstChild;
34952     this.tools = Roo.get(this.titleEl.dom.childNodes[1], true);
34953     this.closeBtn = this.createTool(this.tools.dom, "x-layout-close");
34954     this.closeBtn.enableDisplayMode();
34955     this.closeBtn.on("click", this.closeClicked, this);
34956     this.closeBtn.hide();
34957
34958     this.createBody(config);
34959     this.visible = true;
34960     this.collapsed = false;
34961
34962     if(config.hideWhenEmpty){
34963         this.hide();
34964         this.on("paneladded", this.validateVisibility, this);
34965         this.on("panelremoved", this.validateVisibility, this);
34966     }
34967     this.applyConfig(config);
34968 };
34969
34970 Roo.extend(Roo.LayoutRegion, Roo.BasicLayoutRegion, {
34971
34972     createBody : function(){
34973         /** This region's body element 
34974         * @type Roo.Element */
34975         this.bodyEl = this.el.createChild({tag: "div", cls: "x-layout-panel-body"});
34976     },
34977
34978     applyConfig : function(c){
34979         if(c.collapsible && this.position != "center" && !this.collapsedEl){
34980             var dh = Roo.DomHelper;
34981             if(c.titlebar !== false){
34982                 this.collapseBtn = this.createTool(this.tools.dom, "x-layout-collapse-"+this.position);
34983                 this.collapseBtn.on("click", this.collapse, this);
34984                 this.collapseBtn.enableDisplayMode();
34985
34986                 if(c.showPin === true || this.showPin){
34987                     this.stickBtn = this.createTool(this.tools.dom, "x-layout-stick");
34988                     this.stickBtn.enableDisplayMode();
34989                     this.stickBtn.on("click", this.expand, this);
34990                     this.stickBtn.hide();
34991                 }
34992             }
34993             /** This region's collapsed element
34994             * @type Roo.Element */
34995             this.collapsedEl = dh.append(this.mgr.el.dom, {cls: "x-layout-collapsed x-layout-collapsed-"+this.position, children:[
34996                 {cls: "x-layout-collapsed-tools", children:[{cls: "x-layout-ctools-inner"}]}
34997             ]}, true);
34998             if(c.floatable !== false){
34999                this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
35000                this.collapsedEl.on("click", this.collapseClick, this);
35001             }
35002
35003             if(c.collapsedTitle && (this.position == "north" || this.position== "south")) {
35004                 this.collapsedTitleTextEl = dh.append(this.collapsedEl.dom, {tag: "div", cls: "x-unselectable x-layout-panel-hd-text",
35005                    id: "message", unselectable: "on", style:{"float":"left"}});
35006                this.collapsedTitleTextEl.innerHTML = c.collapsedTitle;
35007              }
35008             this.expandBtn = this.createTool(this.collapsedEl.dom.firstChild.firstChild, "x-layout-expand-"+this.position);
35009             this.expandBtn.on("click", this.expand, this);
35010         }
35011         if(this.collapseBtn){
35012             this.collapseBtn.setVisible(c.collapsible == true);
35013         }
35014         this.cmargins = c.cmargins || this.cmargins ||
35015                          (this.position == "west" || this.position == "east" ?
35016                              {top: 0, left: 2, right:2, bottom: 0} :
35017                              {top: 2, left: 0, right:0, bottom: 2});
35018         this.margins = c.margins || this.margins || {top: 0, left: 0, right:0, bottom: 0};
35019         this.bottomTabs = c.tabPosition != "top";
35020         this.autoScroll = c.autoScroll || false;
35021         if(this.autoScroll){
35022             this.bodyEl.setStyle("overflow", "auto");
35023         }else{
35024             this.bodyEl.setStyle("overflow", "hidden");
35025         }
35026         //if(c.titlebar !== false){
35027             if((!c.titlebar && !c.title) || c.titlebar === false){
35028                 this.titleEl.hide();
35029             }else{
35030                 this.titleEl.show();
35031                 if(c.title){
35032                     this.titleTextEl.innerHTML = c.title;
35033                 }
35034             }
35035         //}
35036         this.duration = c.duration || .30;
35037         this.slideDuration = c.slideDuration || .45;
35038         this.config = c;
35039         if(c.collapsed){
35040             this.collapse(true);
35041         }
35042         if(c.hidden){
35043             this.hide();
35044         }
35045     },
35046     /**
35047      * Returns true if this region is currently visible.
35048      * @return {Boolean}
35049      */
35050     isVisible : function(){
35051         return this.visible;
35052     },
35053
35054     /**
35055      * Updates the title for collapsed north/south regions (used with {@link #collapsedTitle} config option)
35056      * @param {String} title (optional) The title text (accepts HTML markup, defaults to the numeric character reference for a non-breaking space, "&amp;#160;")
35057      */
35058     setCollapsedTitle : function(title){
35059         title = title || "&#160;";
35060         if(this.collapsedTitleTextEl){
35061             this.collapsedTitleTextEl.innerHTML = title;
35062         }
35063     },
35064
35065     getBox : function(){
35066         var b;
35067         if(!this.collapsed){
35068             b = this.el.getBox(false, true);
35069         }else{
35070             b = this.collapsedEl.getBox(false, true);
35071         }
35072         return b;
35073     },
35074
35075     getMargins : function(){
35076         return this.collapsed ? this.cmargins : this.margins;
35077     },
35078
35079     highlight : function(){
35080         this.el.addClass("x-layout-panel-dragover");
35081     },
35082
35083     unhighlight : function(){
35084         this.el.removeClass("x-layout-panel-dragover");
35085     },
35086
35087     updateBox : function(box){
35088         this.box = box;
35089         if(!this.collapsed){
35090             this.el.dom.style.left = box.x + "px";
35091             this.el.dom.style.top = box.y + "px";
35092             this.updateBody(box.width, box.height);
35093         }else{
35094             this.collapsedEl.dom.style.left = box.x + "px";
35095             this.collapsedEl.dom.style.top = box.y + "px";
35096             this.collapsedEl.setSize(box.width, box.height);
35097         }
35098         if(this.tabs){
35099             this.tabs.autoSizeTabs();
35100         }
35101     },
35102
35103     updateBody : function(w, h){
35104         if(w !== null){
35105             this.el.setWidth(w);
35106             w -= this.el.getBorderWidth("rl");
35107             if(this.config.adjustments){
35108                 w += this.config.adjustments[0];
35109             }
35110         }
35111         if(h !== null){
35112             this.el.setHeight(h);
35113             h = this.titleEl && this.titleEl.isDisplayed() ? h - (this.titleEl.getHeight()||0) : h;
35114             h -= this.el.getBorderWidth("tb");
35115             if(this.config.adjustments){
35116                 h += this.config.adjustments[1];
35117             }
35118             this.bodyEl.setHeight(h);
35119             if(this.tabs){
35120                 h = this.tabs.syncHeight(h);
35121             }
35122         }
35123         if(this.panelSize){
35124             w = w !== null ? w : this.panelSize.width;
35125             h = h !== null ? h : this.panelSize.height;
35126         }
35127         if(this.activePanel){
35128             var el = this.activePanel.getEl();
35129             w = w !== null ? w : el.getWidth();
35130             h = h !== null ? h : el.getHeight();
35131             this.panelSize = {width: w, height: h};
35132             this.activePanel.setSize(w, h);
35133         }
35134         if(Roo.isIE && this.tabs){
35135             this.tabs.el.repaint();
35136         }
35137     },
35138
35139     /**
35140      * Returns the container element for this region.
35141      * @return {Roo.Element}
35142      */
35143     getEl : function(){
35144         return this.el;
35145     },
35146
35147     /**
35148      * Hides this region.
35149      */
35150     hide : function(){
35151         if(!this.collapsed){
35152             this.el.dom.style.left = "-2000px";
35153             this.el.hide();
35154         }else{
35155             this.collapsedEl.dom.style.left = "-2000px";
35156             this.collapsedEl.hide();
35157         }
35158         this.visible = false;
35159         this.fireEvent("visibilitychange", this, false);
35160     },
35161
35162     /**
35163      * Shows this region if it was previously hidden.
35164      */
35165     show : function(){
35166         if(!this.collapsed){
35167             this.el.show();
35168         }else{
35169             this.collapsedEl.show();
35170         }
35171         this.visible = true;
35172         this.fireEvent("visibilitychange", this, true);
35173     },
35174
35175     closeClicked : function(){
35176         if(this.activePanel){
35177             this.remove(this.activePanel);
35178         }
35179     },
35180
35181     collapseClick : function(e){
35182         if(this.isSlid){
35183            e.stopPropagation();
35184            this.slideIn();
35185         }else{
35186            e.stopPropagation();
35187            this.slideOut();
35188         }
35189     },
35190
35191     /**
35192      * Collapses this region.
35193      * @param {Boolean} skipAnim (optional) true to collapse the element without animation (if animate is true)
35194      */
35195     collapse : function(skipAnim, skipCheck){
35196         if(this.collapsed) {
35197             return;
35198         }
35199         
35200         if(skipCheck || this.fireEvent("beforecollapse", this) != false){
35201             
35202             this.collapsed = true;
35203             if(this.split){
35204                 this.split.el.hide();
35205             }
35206             if(this.config.animate && skipAnim !== true){
35207                 this.fireEvent("invalidated", this);
35208                 this.animateCollapse();
35209             }else{
35210                 this.el.setLocation(-20000,-20000);
35211                 this.el.hide();
35212                 this.collapsedEl.show();
35213                 this.fireEvent("collapsed", this);
35214                 this.fireEvent("invalidated", this);
35215             }
35216         }
35217         
35218     },
35219
35220     animateCollapse : function(){
35221         // overridden
35222     },
35223
35224     /**
35225      * Expands this region if it was previously collapsed.
35226      * @param {Roo.EventObject} e The event that triggered the expand (or null if calling manually)
35227      * @param {Boolean} skipAnim (optional) true to expand the element without animation (if animate is true)
35228      */
35229     expand : function(e, skipAnim){
35230         if(e) {
35231             e.stopPropagation();
35232         }
35233         if(!this.collapsed || this.el.hasActiveFx()) {
35234             return;
35235         }
35236         if(this.isSlid){
35237             this.afterSlideIn();
35238             skipAnim = true;
35239         }
35240         this.collapsed = false;
35241         if(this.config.animate && skipAnim !== true){
35242             this.animateExpand();
35243         }else{
35244             this.el.show();
35245             if(this.split){
35246                 this.split.el.show();
35247             }
35248             this.collapsedEl.setLocation(-2000,-2000);
35249             this.collapsedEl.hide();
35250             this.fireEvent("invalidated", this);
35251             this.fireEvent("expanded", this);
35252         }
35253     },
35254
35255     animateExpand : function(){
35256         // overridden
35257     },
35258
35259     initTabs : function()
35260     {
35261         this.bodyEl.setStyle("overflow", "hidden");
35262         var ts = new Roo.TabPanel(
35263                 this.bodyEl.dom,
35264                 {
35265                     tabPosition: this.bottomTabs ? 'bottom' : 'top',
35266                     disableTooltips: this.config.disableTabTips,
35267                     toolbar : this.config.toolbar
35268                 }
35269         );
35270         if(this.config.hideTabs){
35271             ts.stripWrap.setDisplayed(false);
35272         }
35273         this.tabs = ts;
35274         ts.resizeTabs = this.config.resizeTabs === true;
35275         ts.minTabWidth = this.config.minTabWidth || 40;
35276         ts.maxTabWidth = this.config.maxTabWidth || 250;
35277         ts.preferredTabWidth = this.config.preferredTabWidth || 150;
35278         ts.monitorResize = false;
35279         ts.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
35280         ts.bodyEl.addClass('x-layout-tabs-body');
35281         this.panels.each(this.initPanelAsTab, this);
35282     },
35283
35284     initPanelAsTab : function(panel){
35285         var ti = this.tabs.addTab(panel.getEl().id, panel.getTitle(), null,
35286                     this.config.closeOnTab && panel.isClosable());
35287         if(panel.tabTip !== undefined){
35288             ti.setTooltip(panel.tabTip);
35289         }
35290         ti.on("activate", function(){
35291               this.setActivePanel(panel);
35292         }, this);
35293         if(this.config.closeOnTab){
35294             ti.on("beforeclose", function(t, e){
35295                 e.cancel = true;
35296                 this.remove(panel);
35297             }, this);
35298         }
35299         return ti;
35300     },
35301
35302     updatePanelTitle : function(panel, title){
35303         if(this.activePanel == panel){
35304             this.updateTitle(title);
35305         }
35306         if(this.tabs){
35307             var ti = this.tabs.getTab(panel.getEl().id);
35308             ti.setText(title);
35309             if(panel.tabTip !== undefined){
35310                 ti.setTooltip(panel.tabTip);
35311             }
35312         }
35313     },
35314
35315     updateTitle : function(title){
35316         if(this.titleTextEl && !this.config.title){
35317             this.titleTextEl.innerHTML = (typeof title != "undefined" && title.length > 0 ? title : "&#160;");
35318         }
35319     },
35320
35321     setActivePanel : function(panel){
35322         panel = this.getPanel(panel);
35323         if(this.activePanel && this.activePanel != panel){
35324             this.activePanel.setActiveState(false);
35325         }
35326         this.activePanel = panel;
35327         panel.setActiveState(true);
35328         if(this.panelSize){
35329             panel.setSize(this.panelSize.width, this.panelSize.height);
35330         }
35331         if(this.closeBtn){
35332             this.closeBtn.setVisible(!this.config.closeOnTab && !this.isSlid && panel.isClosable());
35333         }
35334         this.updateTitle(panel.getTitle());
35335         if(this.tabs){
35336             this.fireEvent("invalidated", this);
35337         }
35338         this.fireEvent("panelactivated", this, panel);
35339     },
35340
35341     /**
35342      * Shows the specified panel.
35343      * @param {Number/String/ContentPanel} panelId The panel's index, id or the panel itself
35344      * @return {Roo.ContentPanel} The shown panel, or null if a panel could not be found from panelId
35345      */
35346     showPanel : function(panel)
35347     {
35348         panel = this.getPanel(panel);
35349         if(panel){
35350             if(this.tabs){
35351                 var tab = this.tabs.getTab(panel.getEl().id);
35352                 if(tab.isHidden()){
35353                     this.tabs.unhideTab(tab.id);
35354                 }
35355                 tab.activate();
35356             }else{
35357                 this.setActivePanel(panel);
35358             }
35359         }
35360         return panel;
35361     },
35362
35363     /**
35364      * Get the active panel for this region.
35365      * @return {Roo.ContentPanel} The active panel or null
35366      */
35367     getActivePanel : function(){
35368         return this.activePanel;
35369     },
35370
35371     validateVisibility : function(){
35372         if(this.panels.getCount() < 1){
35373             this.updateTitle("&#160;");
35374             this.closeBtn.hide();
35375             this.hide();
35376         }else{
35377             if(!this.isVisible()){
35378                 this.show();
35379             }
35380         }
35381     },
35382
35383     /**
35384      * Adds the passed ContentPanel(s) to this region.
35385      * @param {ContentPanel...} panel The ContentPanel(s) to add (you can pass more than one)
35386      * @return {Roo.ContentPanel} The panel added (if only one was added; null otherwise)
35387      */
35388     add : function(panel){
35389         if(arguments.length > 1){
35390             for(var i = 0, len = arguments.length; i < len; i++) {
35391                 this.add(arguments[i]);
35392             }
35393             return null;
35394         }
35395         if(this.hasPanel(panel)){
35396             this.showPanel(panel);
35397             return panel;
35398         }
35399         panel.setRegion(this);
35400         this.panels.add(panel);
35401         if(this.panels.getCount() == 1 && !this.config.alwaysShowTabs){
35402             this.bodyEl.dom.appendChild(panel.getEl().dom);
35403             if(panel.background !== true){
35404                 this.setActivePanel(panel);
35405             }
35406             this.fireEvent("paneladded", this, panel);
35407             return panel;
35408         }
35409         if(!this.tabs){
35410             this.initTabs();
35411         }else{
35412             this.initPanelAsTab(panel);
35413         }
35414         if(panel.background !== true){
35415             this.tabs.activate(panel.getEl().id);
35416         }
35417         this.fireEvent("paneladded", this, panel);
35418         return panel;
35419     },
35420
35421     /**
35422      * Hides the tab for the specified panel.
35423      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
35424      */
35425     hidePanel : function(panel){
35426         if(this.tabs && (panel = this.getPanel(panel))){
35427             this.tabs.hideTab(panel.getEl().id);
35428         }
35429     },
35430
35431     /**
35432      * Unhides the tab for a previously hidden panel.
35433      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
35434      */
35435     unhidePanel : function(panel){
35436         if(this.tabs && (panel = this.getPanel(panel))){
35437             this.tabs.unhideTab(panel.getEl().id);
35438         }
35439     },
35440
35441     clearPanels : function(){
35442         while(this.panels.getCount() > 0){
35443              this.remove(this.panels.first());
35444         }
35445     },
35446
35447     /**
35448      * Removes the specified panel. If preservePanel is not true (either here or in the config), the panel is destroyed.
35449      * @param {Number/String/ContentPanel} panel The panel's index, id or the panel itself
35450      * @param {Boolean} preservePanel Overrides the config preservePanel option
35451      * @return {Roo.ContentPanel} The panel that was removed
35452      */
35453     remove : function(panel, preservePanel){
35454         panel = this.getPanel(panel);
35455         if(!panel){
35456             return null;
35457         }
35458         var e = {};
35459         this.fireEvent("beforeremove", this, panel, e);
35460         if(e.cancel === true){
35461             return null;
35462         }
35463         preservePanel = (typeof preservePanel != "undefined" ? preservePanel : (this.config.preservePanels === true || panel.preserve === true));
35464         var panelId = panel.getId();
35465         this.panels.removeKey(panelId);
35466         if(preservePanel){
35467             document.body.appendChild(panel.getEl().dom);
35468         }
35469         if(this.tabs){
35470             this.tabs.removeTab(panel.getEl().id);
35471         }else if (!preservePanel){
35472             this.bodyEl.dom.removeChild(panel.getEl().dom);
35473         }
35474         if(this.panels.getCount() == 1 && this.tabs && !this.config.alwaysShowTabs){
35475             var p = this.panels.first();
35476             var tempEl = document.createElement("div"); // temp holder to keep IE from deleting the node
35477             tempEl.appendChild(p.getEl().dom);
35478             this.bodyEl.update("");
35479             this.bodyEl.dom.appendChild(p.getEl().dom);
35480             tempEl = null;
35481             this.updateTitle(p.getTitle());
35482             this.tabs = null;
35483             this.bodyEl.setStyle("overflow", this.config.autoScroll ? "auto" : "hidden");
35484             this.setActivePanel(p);
35485         }
35486         panel.setRegion(null);
35487         if(this.activePanel == panel){
35488             this.activePanel = null;
35489         }
35490         if(this.config.autoDestroy !== false && preservePanel !== true){
35491             try{panel.destroy();}catch(e){}
35492         }
35493         this.fireEvent("panelremoved", this, panel);
35494         return panel;
35495     },
35496
35497     /**
35498      * Returns the TabPanel component used by this region
35499      * @return {Roo.TabPanel}
35500      */
35501     getTabs : function(){
35502         return this.tabs;
35503     },
35504
35505     createTool : function(parentEl, className){
35506         var btn = Roo.DomHelper.append(parentEl, {tag: "div", cls: "x-layout-tools-button",
35507             children: [{tag: "div", cls: "x-layout-tools-button-inner " + className, html: "&#160;"}]}, true);
35508         btn.addClassOnOver("x-layout-tools-button-over");
35509         return btn;
35510     }
35511 });/*
35512  * Based on:
35513  * Ext JS Library 1.1.1
35514  * Copyright(c) 2006-2007, Ext JS, LLC.
35515  *
35516  * Originally Released Under LGPL - original licence link has changed is not relivant.
35517  *
35518  * Fork - LGPL
35519  * <script type="text/javascript">
35520  */
35521  
35522
35523
35524 /**
35525  * @class Roo.SplitLayoutRegion
35526  * @extends Roo.LayoutRegion
35527  * Adds a splitbar and other (private) useful functionality to a {@link Roo.LayoutRegion}.
35528  */
35529 Roo.SplitLayoutRegion = function(mgr, config, pos, cursor){
35530     this.cursor = cursor;
35531     Roo.SplitLayoutRegion.superclass.constructor.call(this, mgr, config, pos);
35532 };
35533
35534 Roo.extend(Roo.SplitLayoutRegion, Roo.LayoutRegion, {
35535     splitTip : "Drag to resize.",
35536     collapsibleSplitTip : "Drag to resize. Double click to hide.",
35537     useSplitTips : false,
35538
35539     applyConfig : function(config){
35540         Roo.SplitLayoutRegion.superclass.applyConfig.call(this, config);
35541         if(config.split){
35542             if(!this.split){
35543                 var splitEl = Roo.DomHelper.append(this.mgr.el.dom, 
35544                         {tag: "div", id: this.el.id + "-split", cls: "x-layout-split x-layout-split-"+this.position, html: "&#160;"});
35545                 /** The SplitBar for this region 
35546                 * @type Roo.SplitBar */
35547                 this.split = new Roo.SplitBar(splitEl, this.el, this.orientation);
35548                 this.split.on("moved", this.onSplitMove, this);
35549                 this.split.useShim = config.useShim === true;
35550                 this.split.getMaximumSize = this[this.position == 'north' || this.position == 'south' ? 'getVMaxSize' : 'getHMaxSize'].createDelegate(this);
35551                 if(this.useSplitTips){
35552                     this.split.el.dom.title = config.collapsible ? this.collapsibleSplitTip : this.splitTip;
35553                 }
35554                 if(config.collapsible){
35555                     this.split.el.on("dblclick", this.collapse,  this);
35556                 }
35557             }
35558             if(typeof config.minSize != "undefined"){
35559                 this.split.minSize = config.minSize;
35560             }
35561             if(typeof config.maxSize != "undefined"){
35562                 this.split.maxSize = config.maxSize;
35563             }
35564             if(config.hideWhenEmpty || config.hidden || config.collapsed){
35565                 this.hideSplitter();
35566             }
35567         }
35568     },
35569
35570     getHMaxSize : function(){
35571          var cmax = this.config.maxSize || 10000;
35572          var center = this.mgr.getRegion("center");
35573          return Math.min(cmax, (this.el.getWidth()+center.getEl().getWidth())-center.getMinWidth());
35574     },
35575
35576     getVMaxSize : function(){
35577          var cmax = this.config.maxSize || 10000;
35578          var center = this.mgr.getRegion("center");
35579          return Math.min(cmax, (this.el.getHeight()+center.getEl().getHeight())-center.getMinHeight());
35580     },
35581
35582     onSplitMove : function(split, newSize){
35583         this.fireEvent("resized", this, newSize);
35584     },
35585     
35586     /** 
35587      * Returns the {@link Roo.SplitBar} for this region.
35588      * @return {Roo.SplitBar}
35589      */
35590     getSplitBar : function(){
35591         return this.split;
35592     },
35593     
35594     hide : function(){
35595         this.hideSplitter();
35596         Roo.SplitLayoutRegion.superclass.hide.call(this);
35597     },
35598
35599     hideSplitter : function(){
35600         if(this.split){
35601             this.split.el.setLocation(-2000,-2000);
35602             this.split.el.hide();
35603         }
35604     },
35605
35606     show : function(){
35607         if(this.split){
35608             this.split.el.show();
35609         }
35610         Roo.SplitLayoutRegion.superclass.show.call(this);
35611     },
35612     
35613     beforeSlide: function(){
35614         if(Roo.isGecko){// firefox overflow auto bug workaround
35615             this.bodyEl.clip();
35616             if(this.tabs) {
35617                 this.tabs.bodyEl.clip();
35618             }
35619             if(this.activePanel){
35620                 this.activePanel.getEl().clip();
35621                 
35622                 if(this.activePanel.beforeSlide){
35623                     this.activePanel.beforeSlide();
35624                 }
35625             }
35626         }
35627     },
35628     
35629     afterSlide : function(){
35630         if(Roo.isGecko){// firefox overflow auto bug workaround
35631             this.bodyEl.unclip();
35632             if(this.tabs) {
35633                 this.tabs.bodyEl.unclip();
35634             }
35635             if(this.activePanel){
35636                 this.activePanel.getEl().unclip();
35637                 if(this.activePanel.afterSlide){
35638                     this.activePanel.afterSlide();
35639                 }
35640             }
35641         }
35642     },
35643
35644     initAutoHide : function(){
35645         if(this.autoHide !== false){
35646             if(!this.autoHideHd){
35647                 var st = new Roo.util.DelayedTask(this.slideIn, this);
35648                 this.autoHideHd = {
35649                     "mouseout": function(e){
35650                         if(!e.within(this.el, true)){
35651                             st.delay(500);
35652                         }
35653                     },
35654                     "mouseover" : function(e){
35655                         st.cancel();
35656                     },
35657                     scope : this
35658                 };
35659             }
35660             this.el.on(this.autoHideHd);
35661         }
35662     },
35663
35664     clearAutoHide : function(){
35665         if(this.autoHide !== false){
35666             this.el.un("mouseout", this.autoHideHd.mouseout);
35667             this.el.un("mouseover", this.autoHideHd.mouseover);
35668         }
35669     },
35670
35671     clearMonitor : function(){
35672         Roo.get(document).un("click", this.slideInIf, this);
35673     },
35674
35675     // these names are backwards but not changed for compat
35676     slideOut : function(){
35677         if(this.isSlid || this.el.hasActiveFx()){
35678             return;
35679         }
35680         this.isSlid = true;
35681         if(this.collapseBtn){
35682             this.collapseBtn.hide();
35683         }
35684         this.closeBtnState = this.closeBtn.getStyle('display');
35685         this.closeBtn.hide();
35686         if(this.stickBtn){
35687             this.stickBtn.show();
35688         }
35689         this.el.show();
35690         this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
35691         this.beforeSlide();
35692         this.el.setStyle("z-index", 10001);
35693         this.el.slideIn(this.getSlideAnchor(), {
35694             callback: function(){
35695                 this.afterSlide();
35696                 this.initAutoHide();
35697                 Roo.get(document).on("click", this.slideInIf, this);
35698                 this.fireEvent("slideshow", this);
35699             },
35700             scope: this,
35701             block: true
35702         });
35703     },
35704
35705     afterSlideIn : function(){
35706         this.clearAutoHide();
35707         this.isSlid = false;
35708         this.clearMonitor();
35709         this.el.setStyle("z-index", "");
35710         if(this.collapseBtn){
35711             this.collapseBtn.show();
35712         }
35713         this.closeBtn.setStyle('display', this.closeBtnState);
35714         if(this.stickBtn){
35715             this.stickBtn.hide();
35716         }
35717         this.fireEvent("slidehide", this);
35718     },
35719
35720     slideIn : function(cb){
35721         if(!this.isSlid || this.el.hasActiveFx()){
35722             Roo.callback(cb);
35723             return;
35724         }
35725         this.isSlid = false;
35726         this.beforeSlide();
35727         this.el.slideOut(this.getSlideAnchor(), {
35728             callback: function(){
35729                 this.el.setLeftTop(-10000, -10000);
35730                 this.afterSlide();
35731                 this.afterSlideIn();
35732                 Roo.callback(cb);
35733             },
35734             scope: this,
35735             block: true
35736         });
35737     },
35738     
35739     slideInIf : function(e){
35740         if(!e.within(this.el)){
35741             this.slideIn();
35742         }
35743     },
35744
35745     animateCollapse : function(){
35746         this.beforeSlide();
35747         this.el.setStyle("z-index", 20000);
35748         var anchor = this.getSlideAnchor();
35749         this.el.slideOut(anchor, {
35750             callback : function(){
35751                 this.el.setStyle("z-index", "");
35752                 this.collapsedEl.slideIn(anchor, {duration:.3});
35753                 this.afterSlide();
35754                 this.el.setLocation(-10000,-10000);
35755                 this.el.hide();
35756                 this.fireEvent("collapsed", this);
35757             },
35758             scope: this,
35759             block: true
35760         });
35761     },
35762
35763     animateExpand : function(){
35764         this.beforeSlide();
35765         this.el.alignTo(this.collapsedEl, this.getCollapseAnchor(), this.getExpandAdj());
35766         this.el.setStyle("z-index", 20000);
35767         this.collapsedEl.hide({
35768             duration:.1
35769         });
35770         this.el.slideIn(this.getSlideAnchor(), {
35771             callback : function(){
35772                 this.el.setStyle("z-index", "");
35773                 this.afterSlide();
35774                 if(this.split){
35775                     this.split.el.show();
35776                 }
35777                 this.fireEvent("invalidated", this);
35778                 this.fireEvent("expanded", this);
35779             },
35780             scope: this,
35781             block: true
35782         });
35783     },
35784
35785     anchors : {
35786         "west" : "left",
35787         "east" : "right",
35788         "north" : "top",
35789         "south" : "bottom"
35790     },
35791
35792     sanchors : {
35793         "west" : "l",
35794         "east" : "r",
35795         "north" : "t",
35796         "south" : "b"
35797     },
35798
35799     canchors : {
35800         "west" : "tl-tr",
35801         "east" : "tr-tl",
35802         "north" : "tl-bl",
35803         "south" : "bl-tl"
35804     },
35805
35806     getAnchor : function(){
35807         return this.anchors[this.position];
35808     },
35809
35810     getCollapseAnchor : function(){
35811         return this.canchors[this.position];
35812     },
35813
35814     getSlideAnchor : function(){
35815         return this.sanchors[this.position];
35816     },
35817
35818     getAlignAdj : function(){
35819         var cm = this.cmargins;
35820         switch(this.position){
35821             case "west":
35822                 return [0, 0];
35823             break;
35824             case "east":
35825                 return [0, 0];
35826             break;
35827             case "north":
35828                 return [0, 0];
35829             break;
35830             case "south":
35831                 return [0, 0];
35832             break;
35833         }
35834     },
35835
35836     getExpandAdj : function(){
35837         var c = this.collapsedEl, cm = this.cmargins;
35838         switch(this.position){
35839             case "west":
35840                 return [-(cm.right+c.getWidth()+cm.left), 0];
35841             break;
35842             case "east":
35843                 return [cm.right+c.getWidth()+cm.left, 0];
35844             break;
35845             case "north":
35846                 return [0, -(cm.top+cm.bottom+c.getHeight())];
35847             break;
35848             case "south":
35849                 return [0, cm.top+cm.bottom+c.getHeight()];
35850             break;
35851         }
35852     }
35853 });/*
35854  * Based on:
35855  * Ext JS Library 1.1.1
35856  * Copyright(c) 2006-2007, Ext JS, LLC.
35857  *
35858  * Originally Released Under LGPL - original licence link has changed is not relivant.
35859  *
35860  * Fork - LGPL
35861  * <script type="text/javascript">
35862  */
35863 /*
35864  * These classes are private internal classes
35865  */
35866 Roo.CenterLayoutRegion = function(mgr, config){
35867     Roo.LayoutRegion.call(this, mgr, config, "center");
35868     this.visible = true;
35869     this.minWidth = config.minWidth || 20;
35870     this.minHeight = config.minHeight || 20;
35871 };
35872
35873 Roo.extend(Roo.CenterLayoutRegion, Roo.LayoutRegion, {
35874     hide : function(){
35875         // center panel can't be hidden
35876     },
35877     
35878     show : function(){
35879         // center panel can't be hidden
35880     },
35881     
35882     getMinWidth: function(){
35883         return this.minWidth;
35884     },
35885     
35886     getMinHeight: function(){
35887         return this.minHeight;
35888     }
35889 });
35890
35891
35892 Roo.NorthLayoutRegion = function(mgr, config){
35893     Roo.LayoutRegion.call(this, mgr, config, "north", "n-resize");
35894     if(this.split){
35895         this.split.placement = Roo.SplitBar.TOP;
35896         this.split.orientation = Roo.SplitBar.VERTICAL;
35897         this.split.el.addClass("x-layout-split-v");
35898     }
35899     var size = config.initialSize || config.height;
35900     if(typeof size != "undefined"){
35901         this.el.setHeight(size);
35902     }
35903 };
35904 Roo.extend(Roo.NorthLayoutRegion, Roo.SplitLayoutRegion, {
35905     orientation: Roo.SplitBar.VERTICAL,
35906     getBox : function(){
35907         if(this.collapsed){
35908             return this.collapsedEl.getBox();
35909         }
35910         var box = this.el.getBox();
35911         if(this.split){
35912             box.height += this.split.el.getHeight();
35913         }
35914         return box;
35915     },
35916     
35917     updateBox : function(box){
35918         if(this.split && !this.collapsed){
35919             box.height -= this.split.el.getHeight();
35920             this.split.el.setLeft(box.x);
35921             this.split.el.setTop(box.y+box.height);
35922             this.split.el.setWidth(box.width);
35923         }
35924         if(this.collapsed){
35925             this.updateBody(box.width, null);
35926         }
35927         Roo.LayoutRegion.prototype.updateBox.call(this, box);
35928     }
35929 });
35930
35931 Roo.SouthLayoutRegion = function(mgr, config){
35932     Roo.SplitLayoutRegion.call(this, mgr, config, "south", "s-resize");
35933     if(this.split){
35934         this.split.placement = Roo.SplitBar.BOTTOM;
35935         this.split.orientation = Roo.SplitBar.VERTICAL;
35936         this.split.el.addClass("x-layout-split-v");
35937     }
35938     var size = config.initialSize || config.height;
35939     if(typeof size != "undefined"){
35940         this.el.setHeight(size);
35941     }
35942 };
35943 Roo.extend(Roo.SouthLayoutRegion, Roo.SplitLayoutRegion, {
35944     orientation: Roo.SplitBar.VERTICAL,
35945     getBox : function(){
35946         if(this.collapsed){
35947             return this.collapsedEl.getBox();
35948         }
35949         var box = this.el.getBox();
35950         if(this.split){
35951             var sh = this.split.el.getHeight();
35952             box.height += sh;
35953             box.y -= sh;
35954         }
35955         return box;
35956     },
35957     
35958     updateBox : function(box){
35959         if(this.split && !this.collapsed){
35960             var sh = this.split.el.getHeight();
35961             box.height -= sh;
35962             box.y += sh;
35963             this.split.el.setLeft(box.x);
35964             this.split.el.setTop(box.y-sh);
35965             this.split.el.setWidth(box.width);
35966         }
35967         if(this.collapsed){
35968             this.updateBody(box.width, null);
35969         }
35970         Roo.LayoutRegion.prototype.updateBox.call(this, box);
35971     }
35972 });
35973
35974 Roo.EastLayoutRegion = function(mgr, config){
35975     Roo.SplitLayoutRegion.call(this, mgr, config, "east", "e-resize");
35976     if(this.split){
35977         this.split.placement = Roo.SplitBar.RIGHT;
35978         this.split.orientation = Roo.SplitBar.HORIZONTAL;
35979         this.split.el.addClass("x-layout-split-h");
35980     }
35981     var size = config.initialSize || config.width;
35982     if(typeof size != "undefined"){
35983         this.el.setWidth(size);
35984     }
35985 };
35986 Roo.extend(Roo.EastLayoutRegion, Roo.SplitLayoutRegion, {
35987     orientation: Roo.SplitBar.HORIZONTAL,
35988     getBox : function(){
35989         if(this.collapsed){
35990             return this.collapsedEl.getBox();
35991         }
35992         var box = this.el.getBox();
35993         if(this.split){
35994             var sw = this.split.el.getWidth();
35995             box.width += sw;
35996             box.x -= sw;
35997         }
35998         return box;
35999     },
36000
36001     updateBox : function(box){
36002         if(this.split && !this.collapsed){
36003             var sw = this.split.el.getWidth();
36004             box.width -= sw;
36005             this.split.el.setLeft(box.x);
36006             this.split.el.setTop(box.y);
36007             this.split.el.setHeight(box.height);
36008             box.x += sw;
36009         }
36010         if(this.collapsed){
36011             this.updateBody(null, box.height);
36012         }
36013         Roo.LayoutRegion.prototype.updateBox.call(this, box);
36014     }
36015 });
36016
36017 Roo.WestLayoutRegion = function(mgr, config){
36018     Roo.SplitLayoutRegion.call(this, mgr, config, "west", "w-resize");
36019     if(this.split){
36020         this.split.placement = Roo.SplitBar.LEFT;
36021         this.split.orientation = Roo.SplitBar.HORIZONTAL;
36022         this.split.el.addClass("x-layout-split-h");
36023     }
36024     var size = config.initialSize || config.width;
36025     if(typeof size != "undefined"){
36026         this.el.setWidth(size);
36027     }
36028 };
36029 Roo.extend(Roo.WestLayoutRegion, Roo.SplitLayoutRegion, {
36030     orientation: Roo.SplitBar.HORIZONTAL,
36031     getBox : function(){
36032         if(this.collapsed){
36033             return this.collapsedEl.getBox();
36034         }
36035         var box = this.el.getBox();
36036         if(this.split){
36037             box.width += this.split.el.getWidth();
36038         }
36039         return box;
36040     },
36041     
36042     updateBox : function(box){
36043         if(this.split && !this.collapsed){
36044             var sw = this.split.el.getWidth();
36045             box.width -= sw;
36046             this.split.el.setLeft(box.x+box.width);
36047             this.split.el.setTop(box.y);
36048             this.split.el.setHeight(box.height);
36049         }
36050         if(this.collapsed){
36051             this.updateBody(null, box.height);
36052         }
36053         Roo.LayoutRegion.prototype.updateBox.call(this, box);
36054     }
36055 });
36056 /*
36057  * Based on:
36058  * Ext JS Library 1.1.1
36059  * Copyright(c) 2006-2007, Ext JS, LLC.
36060  *
36061  * Originally Released Under LGPL - original licence link has changed is not relivant.
36062  *
36063  * Fork - LGPL
36064  * <script type="text/javascript">
36065  */
36066  
36067  
36068 /*
36069  * Private internal class for reading and applying state
36070  */
36071 Roo.LayoutStateManager = function(layout){
36072      // default empty state
36073      this.state = {
36074         north: {},
36075         south: {},
36076         east: {},
36077         west: {}       
36078     };
36079 };
36080
36081 Roo.LayoutStateManager.prototype = {
36082     init : function(layout, provider){
36083         this.provider = provider;
36084         var state = provider.get(layout.id+"-layout-state");
36085         if(state){
36086             var wasUpdating = layout.isUpdating();
36087             if(!wasUpdating){
36088                 layout.beginUpdate();
36089             }
36090             for(var key in state){
36091                 if(typeof state[key] != "function"){
36092                     var rstate = state[key];
36093                     var r = layout.getRegion(key);
36094                     if(r && rstate){
36095                         if(rstate.size){
36096                             r.resizeTo(rstate.size);
36097                         }
36098                         if(rstate.collapsed == true){
36099                             r.collapse(true);
36100                         }else{
36101                             r.expand(null, true);
36102                         }
36103                     }
36104                 }
36105             }
36106             if(!wasUpdating){
36107                 layout.endUpdate();
36108             }
36109             this.state = state; 
36110         }
36111         this.layout = layout;
36112         layout.on("regionresized", this.onRegionResized, this);
36113         layout.on("regioncollapsed", this.onRegionCollapsed, this);
36114         layout.on("regionexpanded", this.onRegionExpanded, this);
36115     },
36116     
36117     storeState : function(){
36118         this.provider.set(this.layout.id+"-layout-state", this.state);
36119     },
36120     
36121     onRegionResized : function(region, newSize){
36122         this.state[region.getPosition()].size = newSize;
36123         this.storeState();
36124     },
36125     
36126     onRegionCollapsed : function(region){
36127         this.state[region.getPosition()].collapsed = true;
36128         this.storeState();
36129     },
36130     
36131     onRegionExpanded : function(region){
36132         this.state[region.getPosition()].collapsed = false;
36133         this.storeState();
36134     }
36135 };/*
36136  * Based on:
36137  * Ext JS Library 1.1.1
36138  * Copyright(c) 2006-2007, Ext JS, LLC.
36139  *
36140  * Originally Released Under LGPL - original licence link has changed is not relivant.
36141  *
36142  * Fork - LGPL
36143  * <script type="text/javascript">
36144  */
36145 /**
36146  * @class Roo.ContentPanel
36147  * @extends Roo.util.Observable
36148  * @children Roo.form.Form Roo.JsonView Roo.View
36149  * @parent Roo.BorderLayout Roo.LayoutDialog builder
36150  * A basic ContentPanel element.
36151  * @cfg {Boolean}   fitToFrame    True for this panel to adjust its size to fit when the region resizes  (defaults to false)
36152  * @cfg {Boolean}   fitContainer   When using {@link #fitToFrame} and {@link #resizeEl}, you can also fit the parent container  (defaults to false)
36153  * @cfg {Boolean|Object} autoCreate True to auto generate the DOM element for this panel, or a {@link Roo.DomHelper} config of the element to create
36154  * @cfg {Boolean}   closable      True if the panel can be closed/removed
36155  * @cfg {Boolean}   background    True if the panel should not be activated when it is added (defaults to false)
36156  * @cfg {String|HTMLElement|Element} resizeEl An element to resize if {@link #fitToFrame} is true (instead of this panel's element)
36157  * @cfg {Roo.Toolbar}   toolbar       A toolbar for this panel
36158  * @cfg {Boolean} autoScroll    True to scroll overflow in this panel (use with {@link #fitToFrame})
36159  * @cfg {String} title          The title for this panel
36160  * @cfg {Array} adjustments     Values to <b>add</b> to the width/height when doing a {@link #fitToFrame} (default is [0, 0])
36161  * @cfg {String} url            Calls {@link #setUrl} with this value
36162  * @cfg {String} region (center|north|south|east|west) [required] which region to put this panel on (when used with xtype constructors)
36163  * @cfg {String|Object} params  When used with {@link #url}, calls {@link #setUrl} with this value
36164  * @cfg {Boolean} loadOnce      When used with {@link #url}, calls {@link #setUrl} with this value
36165  * @cfg {String}    content        Raw content to fill content panel with (uses setContent on construction.)
36166  * @cfg {String}    style  Extra style to add to the content panel
36167  * @cfg {Roo.menu.Menu} menu  popup menu
36168
36169  * @constructor
36170  * Create a new ContentPanel.
36171  * @param {String/HTMLElement/Roo.Element} el The container element for this panel
36172  * @param {String/Object} config A string to set only the title or a config object
36173  * @param {String} content (optional) Set the HTML content for this panel
36174  * @param {String} region (optional) Used by xtype constructors to add to regions. (values center,east,west,south,north)
36175  */
36176 Roo.ContentPanel = function(el, config, content){
36177     
36178     /*
36179     if(el.autoCreate || el.xtype){ // xtype is available if this is called from factory
36180         config = el;
36181         el = Roo.id();
36182     }
36183     if (config && config.parentLayout) { 
36184         el = config.parentLayout.el.createChild(); 
36185     }
36186     */
36187     if(el.autoCreate){ // xtype is available if this is called from factory
36188         config = el;
36189         el = Roo.id();
36190     }
36191     this.el = Roo.get(el);
36192     if(!this.el && config && config.autoCreate){
36193         if(typeof config.autoCreate == "object"){
36194             if(!config.autoCreate.id){
36195                 config.autoCreate.id = config.id||el;
36196             }
36197             this.el = Roo.DomHelper.append(document.body,
36198                         config.autoCreate, true);
36199         }else{
36200             this.el = Roo.DomHelper.append(document.body,
36201                         {tag: "div", cls: "x-layout-inactive-content", id: config.id||el}, true);
36202         }
36203     }
36204     
36205     
36206     this.closable = false;
36207     this.loaded = false;
36208     this.active = false;
36209     if(typeof config == "string"){
36210         this.title = config;
36211     }else{
36212         Roo.apply(this, config);
36213     }
36214     
36215     if (this.toolbar && !this.toolbar.el && this.toolbar.xtype) {
36216         this.wrapEl = this.el.wrap();
36217         this.toolbar.container = this.el.insertSibling(false, 'before');
36218         this.toolbar = new Roo.Toolbar(this.toolbar);
36219     }
36220     
36221     // xtype created footer. - not sure if will work as we normally have to render first..
36222     if (this.footer && !this.footer.el && this.footer.xtype) {
36223         if (!this.wrapEl) {
36224             this.wrapEl = this.el.wrap();
36225         }
36226     
36227         this.footer.container = this.wrapEl.createChild();
36228          
36229         this.footer = Roo.factory(this.footer, Roo);
36230         
36231     }
36232     
36233     if(this.resizeEl){
36234         this.resizeEl = Roo.get(this.resizeEl, true);
36235     }else{
36236         this.resizeEl = this.el;
36237     }
36238     // handle view.xtype
36239     
36240  
36241     
36242     
36243     this.addEvents({
36244         /**
36245          * @event activate
36246          * Fires when this panel is activated. 
36247          * @param {Roo.ContentPanel} this
36248          */
36249         "activate" : true,
36250         /**
36251          * @event deactivate
36252          * Fires when this panel is activated. 
36253          * @param {Roo.ContentPanel} this
36254          */
36255         "deactivate" : true,
36256
36257         /**
36258          * @event resize
36259          * Fires when this panel is resized if fitToFrame is true.
36260          * @param {Roo.ContentPanel} this
36261          * @param {Number} width The width after any component adjustments
36262          * @param {Number} height The height after any component adjustments
36263          */
36264         "resize" : true,
36265         
36266          /**
36267          * @event render
36268          * Fires when this tab is created
36269          * @param {Roo.ContentPanel} this
36270          */
36271         "render" : true
36272          
36273         
36274     });
36275     
36276
36277     
36278     
36279     if(this.autoScroll){
36280         this.resizeEl.setStyle("overflow", "auto");
36281     } else {
36282         // fix randome scrolling
36283         this.el.on('scroll', function() {
36284             Roo.log('fix random scolling');
36285             this.scrollTo('top',0); 
36286         });
36287     }
36288     content = content || this.content;
36289     if(content){
36290         this.setContent(content);
36291     }
36292     if(config && config.url){
36293         this.setUrl(this.url, this.params, this.loadOnce);
36294     }
36295     
36296     
36297     
36298     Roo.ContentPanel.superclass.constructor.call(this);
36299     
36300     if (this.view && typeof(this.view.xtype) != 'undefined') {
36301         this.view.el = this.el.appendChild(document.createElement("div"));
36302         this.view = Roo.factory(this.view); 
36303         this.view.render  &&  this.view.render(false, '');  
36304     }
36305     
36306     
36307     this.fireEvent('render', this);
36308 };
36309
36310 Roo.extend(Roo.ContentPanel, Roo.util.Observable, {
36311     tabTip:'',
36312     setRegion : function(region){
36313         this.region = region;
36314         if(region){
36315            this.el.replaceClass("x-layout-inactive-content", "x-layout-active-content");
36316         }else{
36317            this.el.replaceClass("x-layout-active-content", "x-layout-inactive-content");
36318         } 
36319     },
36320     
36321     /**
36322      * Returns the toolbar for this Panel if one was configured. 
36323      * @return {Roo.Toolbar} 
36324      */
36325     getToolbar : function(){
36326         return this.toolbar;
36327     },
36328     
36329     setActiveState : function(active){
36330         this.active = active;
36331         if(!active){
36332             this.fireEvent("deactivate", this);
36333         }else{
36334             this.fireEvent("activate", this);
36335         }
36336     },
36337     /**
36338      * Updates this panel's element
36339      * @param {String} content The new content
36340      * @param {Boolean} loadScripts (optional) true to look for and process scripts
36341     */
36342     setContent : function(content, loadScripts){
36343         this.el.update(content, loadScripts);
36344     },
36345
36346     ignoreResize : function(w, h){
36347         if(this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
36348             return true;
36349         }else{
36350             this.lastSize = {width: w, height: h};
36351             return false;
36352         }
36353     },
36354     /**
36355      * Get the {@link Roo.UpdateManager} for this panel. Enables you to perform Ajax updates.
36356      * @return {Roo.UpdateManager} The UpdateManager
36357      */
36358     getUpdateManager : function(){
36359         return this.el.getUpdateManager();
36360     },
36361      /**
36362      * Loads this content panel immediately with content from XHR. Note: to delay loading until the panel is activated, use {@link #setUrl}.
36363      * @param {Object/String/Function} url The url for this request or a function to call to get the url or a config object containing any of the following options:
36364 <pre><code>
36365 panel.load({
36366     url: "your-url.php",
36367     params: {param1: "foo", param2: "bar"}, // or a URL encoded string
36368     callback: yourFunction,
36369     scope: yourObject, //(optional scope)
36370     discardUrl: false,
36371     nocache: false,
36372     text: "Loading...",
36373     timeout: 30,
36374     scripts: false
36375 });
36376 </code></pre>
36377      * The only required property is <i>url</i>. The optional properties <i>nocache</i>, <i>text</i> and <i>scripts</i>
36378      * are shorthand for <i>disableCaching</i>, <i>indicatorText</i> and <i>loadScripts</i> and are used to set their associated property on this panel UpdateManager instance.
36379      * @param {String/Object} params (optional) The parameters to pass as either a URL encoded string "param1=1&amp;param2=2" or an object {param1: 1, param2: 2}
36380      * @param {Function} callback (optional) Callback when transaction is complete -- called with signature (oElement, bSuccess, oResponse)
36381      * @param {Boolean} discardUrl (optional) By default when you execute an update the defaultUrl is changed to the last used URL. If true, it will not store the URL.
36382      * @return {Roo.ContentPanel} this
36383      */
36384     load : function(){
36385         var um = this.el.getUpdateManager();
36386         um.update.apply(um, arguments);
36387         return this;
36388     },
36389
36390
36391     /**
36392      * Set a URL to be used to load the content for this panel. When this panel is activated, the content will be loaded from that URL.
36393      * @param {String/Function} url The URL to load the content from or a function to call to get the URL
36394      * @param {String/Object} params (optional) The string params for the update call or an object of the params. See {@link Roo.UpdateManager#update} for more details. (Defaults to null)
36395      * @param {Boolean} loadOnce (optional) Whether to only load the content once. If this is false it makes the Ajax call every time this panel is activated. (Defaults to false)
36396      * @return {Roo.UpdateManager} The UpdateManager
36397      */
36398     setUrl : function(url, params, loadOnce){
36399         if(this.refreshDelegate){
36400             this.removeListener("activate", this.refreshDelegate);
36401         }
36402         this.refreshDelegate = this._handleRefresh.createDelegate(this, [url, params, loadOnce]);
36403         this.on("activate", this.refreshDelegate);
36404         return this.el.getUpdateManager();
36405     },
36406     
36407     _handleRefresh : function(url, params, loadOnce){
36408         if(!loadOnce || !this.loaded){
36409             var updater = this.el.getUpdateManager();
36410             updater.update(url, params, this._setLoaded.createDelegate(this));
36411         }
36412     },
36413     
36414     _setLoaded : function(){
36415         this.loaded = true;
36416     }, 
36417     
36418     /**
36419      * Returns this panel's id
36420      * @return {String} 
36421      */
36422     getId : function(){
36423         return this.el.id;
36424     },
36425     
36426     /** 
36427      * Returns this panel's element - used by regiosn to add.
36428      * @return {Roo.Element} 
36429      */
36430     getEl : function(){
36431         return this.wrapEl || this.el;
36432     },
36433     
36434     adjustForComponents : function(width, height)
36435     {
36436         //Roo.log('adjustForComponents ');
36437         if(this.resizeEl != this.el){
36438             width -= this.el.getFrameWidth('lr');
36439             height -= this.el.getFrameWidth('tb');
36440         }
36441         if(this.toolbar){
36442             var te = this.toolbar.getEl();
36443             height -= te.getHeight();
36444             te.setWidth(width);
36445         }
36446         if(this.footer){
36447             var te = this.footer.getEl();
36448             //Roo.log("footer:" + te.getHeight());
36449             
36450             height -= te.getHeight();
36451             te.setWidth(width);
36452         }
36453         
36454         
36455         if(this.adjustments){
36456             width += this.adjustments[0];
36457             height += this.adjustments[1];
36458         }
36459         return {"width": width, "height": height};
36460     },
36461     
36462     setSize : function(width, height){
36463         if(this.fitToFrame && !this.ignoreResize(width, height)){
36464             if(this.fitContainer && this.resizeEl != this.el){
36465                 this.el.setSize(width, height);
36466             }
36467             var size = this.adjustForComponents(width, height);
36468             this.resizeEl.setSize(this.autoWidth ? "auto" : size.width, this.autoHeight ? "auto" : size.height);
36469             this.fireEvent('resize', this, size.width, size.height);
36470         }
36471     },
36472     
36473     /**
36474      * Returns this panel's title
36475      * @return {String} 
36476      */
36477     getTitle : function(){
36478         return this.title;
36479     },
36480     
36481     /**
36482      * Set this panel's title
36483      * @param {String} title
36484      */
36485     setTitle : function(title){
36486         this.title = title;
36487         if(this.region){
36488             this.region.updatePanelTitle(this, title);
36489         }
36490     },
36491     
36492     /**
36493      * Returns true is this panel was configured to be closable
36494      * @return {Boolean} 
36495      */
36496     isClosable : function(){
36497         return this.closable;
36498     },
36499     
36500     beforeSlide : function(){
36501         this.el.clip();
36502         this.resizeEl.clip();
36503     },
36504     
36505     afterSlide : function(){
36506         this.el.unclip();
36507         this.resizeEl.unclip();
36508     },
36509     
36510     /**
36511      *   Force a content refresh from the URL specified in the {@link #setUrl} method.
36512      *   Will fail silently if the {@link #setUrl} method has not been called.
36513      *   This does not activate the panel, just updates its content.
36514      */
36515     refresh : function(){
36516         if(this.refreshDelegate){
36517            this.loaded = false;
36518            this.refreshDelegate();
36519         }
36520     },
36521     
36522     /**
36523      * Destroys this panel
36524      */
36525     destroy : function(){
36526         this.el.removeAllListeners();
36527         var tempEl = document.createElement("span");
36528         tempEl.appendChild(this.el.dom);
36529         tempEl.innerHTML = "";
36530         this.el.remove();
36531         this.el = null;
36532     },
36533     
36534     /**
36535      * form - if the content panel contains a form - this is a reference to it.
36536      * @type {Roo.form.Form}
36537      */
36538     form : false,
36539     /**
36540      * view - if the content panel contains a view (Roo.DatePicker / Roo.View / Roo.JsonView)
36541      *    This contains a reference to it.
36542      * @type {Roo.View}
36543      */
36544     view : false,
36545     
36546       /**
36547      * Adds a xtype elements to the panel - currently only supports Forms, View, JsonView.
36548      * <pre><code>
36549
36550 layout.addxtype({
36551        xtype : 'Form',
36552        items: [ .... ]
36553    }
36554 );
36555
36556 </code></pre>
36557      * @param {Object} cfg Xtype definition of item to add.
36558      */
36559     
36560     addxtype : function(cfg) {
36561         if(cfg.xtype.match(/^UploadCropbox$/)) {
36562
36563             this.cropbox = new Roo.factory(cfg);
36564
36565             this.cropbox.render(this.el);
36566
36567             return this.cropbox;
36568         }
36569         // add form..
36570         if (cfg.xtype.match(/^Form$/)) {
36571             
36572             var el;
36573             //if (this.footer) {
36574             //    el = this.footer.container.insertSibling(false, 'before');
36575             //} else {
36576                 el = this.el.createChild();
36577             //}
36578
36579             this.form = new  Roo.form.Form(cfg);
36580             
36581             
36582             if ( this.form.allItems.length) {
36583                 this.form.render(el.dom);
36584             }
36585             return this.form;
36586         }
36587         // should only have one of theses..
36588         if ([ 'View', 'JsonView', 'DatePicker'].indexOf(cfg.xtype) > -1) {
36589             // views.. should not be just added - used named prop 'view''
36590             
36591             cfg.el = this.el.appendChild(document.createElement("div"));
36592             // factory?
36593             
36594             var ret = new Roo.factory(cfg);
36595              
36596              ret.render && ret.render(false, ''); // render blank..
36597             this.view = ret;
36598             return ret;
36599         }
36600         return false;
36601     }
36602 });
36603
36604
36605
36606
36607
36608
36609
36610
36611
36612
36613
36614
36615 /**
36616  * @class Roo.GridPanel
36617  * @extends Roo.ContentPanel
36618  * @parent Roo.BorderLayout Roo.LayoutDialog builder
36619  * @constructor
36620  * Create a new GridPanel.
36621  * @cfg {Roo.grid.Grid} grid The grid for this panel
36622  */
36623 Roo.GridPanel = function(grid, config){
36624     
36625     // universal ctor...
36626     if (typeof(grid.grid) != 'undefined') {
36627         config = grid;
36628         grid = config.grid;
36629     }
36630     this.wrapper = Roo.DomHelper.append(document.body, // wrapper for IE7 strict & safari scroll issue
36631         {tag: "div", cls: "x-layout-grid-wrapper x-layout-inactive-content"}, true);
36632         
36633     this.wrapper.dom.appendChild(grid.getGridEl().dom);
36634     
36635     Roo.GridPanel.superclass.constructor.call(this, this.wrapper, config);
36636     
36637     if(this.toolbar){
36638         this.toolbar.el.insertBefore(this.wrapper.dom.firstChild);
36639     }
36640     // xtype created footer. - not sure if will work as we normally have to render first..
36641     if (this.footer && !this.footer.el && this.footer.xtype) {
36642         
36643         this.footer.container = this.grid.getView().getFooterPanel(true);
36644         this.footer.dataSource = this.grid.dataSource;
36645         this.footer = Roo.factory(this.footer, Roo);
36646         
36647     }
36648     
36649     grid.monitorWindowResize = false; // turn off autosizing
36650     grid.autoHeight = false;
36651     grid.autoWidth = false;
36652     this.grid = grid;
36653     this.grid.getGridEl().replaceClass("x-layout-inactive-content", "x-layout-component-panel");
36654 };
36655
36656 Roo.extend(Roo.GridPanel, Roo.ContentPanel, {
36657     getId : function(){
36658         return this.grid.id;
36659     },
36660     
36661     /**
36662      * Returns the grid for this panel
36663      * @return {Roo.grid.Grid} 
36664      */
36665     getGrid : function(){
36666         return this.grid;    
36667     },
36668     
36669     setSize : function(width, height){
36670         if(!this.ignoreResize(width, height)){
36671             var grid = this.grid;
36672             var size = this.adjustForComponents(width, height);
36673             grid.getGridEl().setSize(size.width, size.height);
36674             grid.autoSize();
36675         }
36676     },
36677     
36678     beforeSlide : function(){
36679         this.grid.getView().scroller.clip();
36680     },
36681     
36682     afterSlide : function(){
36683         this.grid.getView().scroller.unclip();
36684     },
36685     
36686     destroy : function(){
36687         this.grid.destroy();
36688         delete this.grid;
36689         Roo.GridPanel.superclass.destroy.call(this); 
36690     }
36691 });
36692
36693
36694 /**
36695  * @class Roo.NestedLayoutPanel
36696  * @extends Roo.ContentPanel
36697  * @parent Roo.BorderLayout Roo.LayoutDialog builder
36698  * @cfg {Roo.BorderLayout} layout   [required] The layout for this panel
36699  *
36700  * 
36701  * @constructor
36702  * Create a new NestedLayoutPanel.
36703  * 
36704  * 
36705  * @param {Roo.BorderLayout} layout [required] The layout for this panel
36706  * @param {String/Object} config A string to set only the title or a config object
36707  */
36708 Roo.NestedLayoutPanel = function(layout, config)
36709 {
36710     // construct with only one argument..
36711     /* FIXME - implement nicer consturctors
36712     if (layout.layout) {
36713         config = layout;
36714         layout = config.layout;
36715         delete config.layout;
36716     }
36717     if (layout.xtype && !layout.getEl) {
36718         // then layout needs constructing..
36719         layout = Roo.factory(layout, Roo);
36720     }
36721     */
36722     
36723     
36724     Roo.NestedLayoutPanel.superclass.constructor.call(this, layout.getEl(), config);
36725     
36726     layout.monitorWindowResize = false; // turn off autosizing
36727     this.layout = layout;
36728     this.layout.getEl().addClass("x-layout-nested-layout");
36729     
36730     
36731     
36732     
36733 };
36734
36735 Roo.extend(Roo.NestedLayoutPanel, Roo.ContentPanel, {
36736
36737     layout : false,
36738
36739     setSize : function(width, height){
36740         if(!this.ignoreResize(width, height)){
36741             var size = this.adjustForComponents(width, height);
36742             var el = this.layout.getEl();
36743             el.setSize(size.width, size.height);
36744             var touch = el.dom.offsetWidth;
36745             this.layout.layout();
36746             // ie requires a double layout on the first pass
36747             if(Roo.isIE && !this.initialized){
36748                 this.initialized = true;
36749                 this.layout.layout();
36750             }
36751         }
36752     },
36753     
36754     // activate all subpanels if not currently active..
36755     
36756     setActiveState : function(active){
36757         this.active = active;
36758         if(!active){
36759             this.fireEvent("deactivate", this);
36760             return;
36761         }
36762         
36763         this.fireEvent("activate", this);
36764         // not sure if this should happen before or after..
36765         if (!this.layout) {
36766             return; // should not happen..
36767         }
36768         var reg = false;
36769         for (var r in this.layout.regions) {
36770             reg = this.layout.getRegion(r);
36771             if (reg.getActivePanel()) {
36772                 //reg.showPanel(reg.getActivePanel()); // force it to activate.. 
36773                 reg.setActivePanel(reg.getActivePanel());
36774                 continue;
36775             }
36776             if (!reg.panels.length) {
36777                 continue;
36778             }
36779             reg.showPanel(reg.getPanel(0));
36780         }
36781         
36782         
36783         
36784         
36785     },
36786     
36787     /**
36788      * Returns the nested BorderLayout for this panel
36789      * @return {Roo.BorderLayout}
36790      */
36791     getLayout : function(){
36792         return this.layout;
36793     },
36794     
36795      /**
36796      * Adds a xtype elements to the layout of the nested panel
36797      * <pre><code>
36798
36799 panel.addxtype({
36800        xtype : 'ContentPanel',
36801        region: 'west',
36802        items: [ .... ]
36803    }
36804 );
36805
36806 panel.addxtype({
36807         xtype : 'NestedLayoutPanel',
36808         region: 'west',
36809         layout: {
36810            center: { },
36811            west: { }   
36812         },
36813         items : [ ... list of content panels or nested layout panels.. ]
36814    }
36815 );
36816 </code></pre>
36817      * @param {Object} cfg Xtype definition of item to add.
36818      */
36819     addxtype : function(cfg) {
36820         return this.layout.addxtype(cfg);
36821     
36822     }
36823 });
36824
36825 Roo.ScrollPanel = function(el, config, content){
36826     config = config || {};
36827     config.fitToFrame = true;
36828     Roo.ScrollPanel.superclass.constructor.call(this, el, config, content);
36829     
36830     this.el.dom.style.overflow = "hidden";
36831     var wrap = this.el.wrap({cls: "x-scroller x-layout-inactive-content"});
36832     this.el.removeClass("x-layout-inactive-content");
36833     this.el.on("mousewheel", this.onWheel, this);
36834
36835     var up = wrap.createChild({cls: "x-scroller-up", html: "&#160;"}, this.el.dom);
36836     var down = wrap.createChild({cls: "x-scroller-down", html: "&#160;"});
36837     up.unselectable(); down.unselectable();
36838     up.on("click", this.scrollUp, this);
36839     down.on("click", this.scrollDown, this);
36840     up.addClassOnOver("x-scroller-btn-over");
36841     down.addClassOnOver("x-scroller-btn-over");
36842     up.addClassOnClick("x-scroller-btn-click");
36843     down.addClassOnClick("x-scroller-btn-click");
36844     this.adjustments = [0, -(up.getHeight() + down.getHeight())];
36845
36846     this.resizeEl = this.el;
36847     this.el = wrap; this.up = up; this.down = down;
36848 };
36849
36850 Roo.extend(Roo.ScrollPanel, Roo.ContentPanel, {
36851     increment : 100,
36852     wheelIncrement : 5,
36853     scrollUp : function(){
36854         this.resizeEl.scroll("up", this.increment, {callback: this.afterScroll, scope: this});
36855     },
36856
36857     scrollDown : function(){
36858         this.resizeEl.scroll("down", this.increment, {callback: this.afterScroll, scope: this});
36859     },
36860
36861     afterScroll : function(){
36862         var el = this.resizeEl;
36863         var t = el.dom.scrollTop, h = el.dom.scrollHeight, ch = el.dom.clientHeight;
36864         this.up[t == 0 ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
36865         this.down[h - t <= ch ? "addClass" : "removeClass"]("x-scroller-btn-disabled");
36866     },
36867
36868     setSize : function(){
36869         Roo.ScrollPanel.superclass.setSize.apply(this, arguments);
36870         this.afterScroll();
36871     },
36872
36873     onWheel : function(e){
36874         var d = e.getWheelDelta();
36875         this.resizeEl.dom.scrollTop -= (d*this.wheelIncrement);
36876         this.afterScroll();
36877         e.stopEvent();
36878     },
36879
36880     setContent : function(content, loadScripts){
36881         this.resizeEl.update(content, loadScripts);
36882     }
36883
36884 });
36885
36886
36887
36888 /**
36889  * @class Roo.TreePanel
36890  * @extends Roo.ContentPanel
36891  * @parent Roo.BorderLayout Roo.LayoutDialog builder
36892  * Treepanel component
36893  * 
36894  * @constructor
36895  * Create a new TreePanel. - defaults to fit/scoll contents.
36896  * @param {String/Object} config A string to set only the panel's title, or a config object
36897  */
36898 Roo.TreePanel = function(config){
36899     var el = config.el;
36900     var tree = config.tree;
36901     delete config.tree; 
36902     delete config.el; // hopefull!
36903     
36904     // wrapper for IE7 strict & safari scroll issue
36905     
36906     var treeEl = el.createChild();
36907     config.resizeEl = treeEl;
36908     
36909     
36910     
36911     Roo.TreePanel.superclass.constructor.call(this, el, config);
36912  
36913  
36914     this.tree = new Roo.tree.TreePanel(treeEl , tree);
36915     //console.log(tree);
36916     this.on('activate', function()
36917     {
36918         if (this.tree.rendered) {
36919             return;
36920         }
36921         //console.log('render tree');
36922         this.tree.render();
36923     });
36924     // this should not be needed.. - it's actually the 'el' that resizes?
36925     // actuall it breaks the containerScroll - dragging nodes auto scroll at top
36926     
36927     //this.on('resize',  function (cp, w, h) {
36928     //        this.tree.innerCt.setWidth(w);
36929     //        this.tree.innerCt.setHeight(h);
36930     //        //this.tree.innerCt.setStyle('overflow-y', 'auto');
36931     //});
36932
36933         
36934     
36935 };
36936
36937 Roo.extend(Roo.TreePanel, Roo.ContentPanel, {   
36938     fitToFrame : true,
36939     autoScroll : true,
36940     /*
36941      * @cfg {Roo.tree.TreePanel} tree [required] The tree TreePanel, with config etc.
36942      */
36943     tree : false
36944
36945 });
36946 /*
36947  * Based on:
36948  * Ext JS Library 1.1.1
36949  * Copyright(c) 2006-2007, Ext JS, LLC.
36950  *
36951  * Originally Released Under LGPL - original licence link has changed is not relivant.
36952  *
36953  * Fork - LGPL
36954  * <script type="text/javascript">
36955  */
36956  
36957
36958 /**
36959  * @class Roo.ReaderLayout
36960  * @extends Roo.BorderLayout
36961  * This is a pre-built layout that represents a classic, 5-pane application.  It consists of a header, a primary
36962  * center region containing two nested regions (a top one for a list view and one for item preview below),
36963  * and regions on either side that can be used for navigation, application commands, informational displays, etc.
36964  * The setup and configuration work exactly the same as it does for a {@link Roo.BorderLayout} - this class simply
36965  * expedites the setup of the overall layout and regions for this common application style.
36966  * Example:
36967  <pre><code>
36968 var reader = new Roo.ReaderLayout();
36969 var CP = Roo.ContentPanel;  // shortcut for adding
36970
36971 reader.beginUpdate();
36972 reader.add("north", new CP("north", "North"));
36973 reader.add("west", new CP("west", {title: "West"}));
36974 reader.add("east", new CP("east", {title: "East"}));
36975
36976 reader.regions.listView.add(new CP("listView", "List"));
36977 reader.regions.preview.add(new CP("preview", "Preview"));
36978 reader.endUpdate();
36979 </code></pre>
36980 * @constructor
36981 * Create a new ReaderLayout
36982 * @param {Object} config Configuration options
36983 * @param {String/HTMLElement/Element} container (optional) The container this layout is bound to (defaults to
36984 * document.body if omitted)
36985 */
36986 Roo.ReaderLayout = function(config, renderTo){
36987     var c = config || {size:{}};
36988     Roo.ReaderLayout.superclass.constructor.call(this, renderTo || document.body, {
36989         north: c.north !== false ? Roo.apply({
36990             split:false,
36991             initialSize: 32,
36992             titlebar: false
36993         }, c.north) : false,
36994         west: c.west !== false ? Roo.apply({
36995             split:true,
36996             initialSize: 200,
36997             minSize: 175,
36998             maxSize: 400,
36999             titlebar: true,
37000             collapsible: true,
37001             animate: true,
37002             margins:{left:5,right:0,bottom:5,top:5},
37003             cmargins:{left:5,right:5,bottom:5,top:5}
37004         }, c.west) : false,
37005         east: c.east !== false ? Roo.apply({
37006             split:true,
37007             initialSize: 200,
37008             minSize: 175,
37009             maxSize: 400,
37010             titlebar: true,
37011             collapsible: true,
37012             animate: true,
37013             margins:{left:0,right:5,bottom:5,top:5},
37014             cmargins:{left:5,right:5,bottom:5,top:5}
37015         }, c.east) : false,
37016         center: Roo.apply({
37017             tabPosition: 'top',
37018             autoScroll:false,
37019             closeOnTab: true,
37020             titlebar:false,
37021             margins:{left:c.west!==false ? 0 : 5,right:c.east!==false ? 0 : 5,bottom:5,top:2}
37022         }, c.center)
37023     });
37024
37025     this.el.addClass('x-reader');
37026
37027     this.beginUpdate();
37028
37029     var inner = new Roo.BorderLayout(Roo.get(document.body).createChild(), {
37030         south: c.preview !== false ? Roo.apply({
37031             split:true,
37032             initialSize: 200,
37033             minSize: 100,
37034             autoScroll:true,
37035             collapsible:true,
37036             titlebar: true,
37037             cmargins:{top:5,left:0, right:0, bottom:0}
37038         }, c.preview) : false,
37039         center: Roo.apply({
37040             autoScroll:false,
37041             titlebar:false,
37042             minHeight:200
37043         }, c.listView)
37044     });
37045     this.add('center', new Roo.NestedLayoutPanel(inner,
37046             Roo.apply({title: c.mainTitle || '',tabTip:''},c.innerPanelCfg)));
37047
37048     this.endUpdate();
37049
37050     this.regions.preview = inner.getRegion('south');
37051     this.regions.listView = inner.getRegion('center');
37052 };
37053
37054 Roo.extend(Roo.ReaderLayout, Roo.BorderLayout);/*
37055  * Based on:
37056  * Ext JS Library 1.1.1
37057  * Copyright(c) 2006-2007, Ext JS, LLC.
37058  *
37059  * Originally Released Under LGPL - original licence link has changed is not relivant.
37060  *
37061  * Fork - LGPL
37062  * <script type="text/javascript">
37063  */
37064  
37065 /**
37066  * @class Roo.grid.Grid
37067  * @extends Roo.util.Observable
37068  * This class represents the primary interface of a component based grid control.
37069  * <br><br>Usage:<pre><code>
37070  var grid = new Roo.grid.Grid("my-container-id", {
37071      ds: myDataStore,
37072      cm: myColModel,
37073      selModel: mySelectionModel,
37074      autoSizeColumns: true,
37075      monitorWindowResize: false,
37076      trackMouseOver: true
37077  });
37078  // set any options
37079  grid.render();
37080  * </code></pre>
37081  * <b>Common Problems:</b><br/>
37082  * - Grid does not resize properly when going smaller: Setting overflow hidden on the container
37083  * element will correct this<br/>
37084  * - If you get el.style[camel]= NaNpx or -2px or something related, be certain you have given your container element
37085  * dimensions. The grid adapts to your container's size, if your container has no size defined then the results
37086  * are unpredictable.<br/>
37087  * - Do not render the grid into an element with display:none. Try using visibility:hidden. Otherwise there is no way for the
37088  * grid to calculate dimensions/offsets.<br/>
37089   * @constructor
37090  * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
37091  * The container MUST have some type of size defined for the grid to fill. The container will be
37092  * automatically set to position relative if it isn't already.
37093  * @param {Object} config A config object that sets properties on this grid.
37094  */
37095 Roo.grid.Grid = function(container, config){
37096         // initialize the container
37097         this.container = Roo.get(container);
37098         this.container.update("");
37099         this.container.setStyle("overflow", "hidden");
37100     this.container.addClass('x-grid-container');
37101
37102     this.id = this.container.id;
37103
37104     Roo.apply(this, config);
37105     // check and correct shorthanded configs
37106     if(this.ds){
37107         this.dataSource = this.ds;
37108         delete this.ds;
37109     }
37110     if(this.cm){
37111         this.colModel = this.cm;
37112         delete this.cm;
37113     }
37114     if(this.sm){
37115         this.selModel = this.sm;
37116         delete this.sm;
37117     }
37118
37119     if (this.selModel) {
37120         this.selModel = Roo.factory(this.selModel, Roo.grid);
37121         this.sm = this.selModel;
37122         this.sm.xmodule = this.xmodule || false;
37123     }
37124     if (typeof(this.colModel.config) == 'undefined') {
37125         this.colModel = new Roo.grid.ColumnModel(this.colModel);
37126         this.cm = this.colModel;
37127         this.cm.xmodule = this.xmodule || false;
37128     }
37129     if (this.dataSource) {
37130         this.dataSource= Roo.factory(this.dataSource, Roo.data);
37131         this.ds = this.dataSource;
37132         this.ds.xmodule = this.xmodule || false;
37133          
37134     }
37135     
37136     
37137     
37138     if(this.width){
37139         this.container.setWidth(this.width);
37140     }
37141
37142     if(this.height){
37143         this.container.setHeight(this.height);
37144     }
37145     /** @private */
37146         this.addEvents({
37147         // raw events
37148         /**
37149          * @event click
37150          * The raw click event for the entire grid.
37151          * @param {Roo.EventObject} e
37152          */
37153         "click" : true,
37154         /**
37155          * @event dblclick
37156          * The raw dblclick event for the entire grid.
37157          * @param {Roo.EventObject} e
37158          */
37159         "dblclick" : true,
37160         /**
37161          * @event contextmenu
37162          * The raw contextmenu event for the entire grid.
37163          * @param {Roo.EventObject} e
37164          */
37165         "contextmenu" : true,
37166         /**
37167          * @event mousedown
37168          * The raw mousedown event for the entire grid.
37169          * @param {Roo.EventObject} e
37170          */
37171         "mousedown" : true,
37172         /**
37173          * @event mouseup
37174          * The raw mouseup event for the entire grid.
37175          * @param {Roo.EventObject} e
37176          */
37177         "mouseup" : true,
37178         /**
37179          * @event mouseover
37180          * The raw mouseover event for the entire grid.
37181          * @param {Roo.EventObject} e
37182          */
37183         "mouseover" : true,
37184         /**
37185          * @event mouseout
37186          * The raw mouseout event for the entire grid.
37187          * @param {Roo.EventObject} e
37188          */
37189         "mouseout" : true,
37190         /**
37191          * @event keypress
37192          * The raw keypress event for the entire grid.
37193          * @param {Roo.EventObject} e
37194          */
37195         "keypress" : true,
37196         /**
37197          * @event keydown
37198          * The raw keydown event for the entire grid.
37199          * @param {Roo.EventObject} e
37200          */
37201         "keydown" : true,
37202
37203         // custom events
37204
37205         /**
37206          * @event cellclick
37207          * Fires when a cell is clicked
37208          * @param {Grid} this
37209          * @param {Number} rowIndex
37210          * @param {Number} columnIndex
37211          * @param {Roo.EventObject} e
37212          */
37213         "cellclick" : true,
37214         /**
37215          * @event celldblclick
37216          * Fires when a cell is double clicked
37217          * @param {Grid} this
37218          * @param {Number} rowIndex
37219          * @param {Number} columnIndex
37220          * @param {Roo.EventObject} e
37221          */
37222         "celldblclick" : true,
37223         /**
37224          * @event rowclick
37225          * Fires when a row is clicked
37226          * @param {Grid} this
37227          * @param {Number} rowIndex
37228          * @param {Roo.EventObject} e
37229          */
37230         "rowclick" : true,
37231         /**
37232          * @event rowdblclick
37233          * Fires when a row is double clicked
37234          * @param {Grid} this
37235          * @param {Number} rowIndex
37236          * @param {Roo.EventObject} e
37237          */
37238         "rowdblclick" : true,
37239         /**
37240          * @event headerclick
37241          * Fires when a header is clicked
37242          * @param {Grid} this
37243          * @param {Number} columnIndex
37244          * @param {Roo.EventObject} e
37245          */
37246         "headerclick" : true,
37247         /**
37248          * @event headerdblclick
37249          * Fires when a header cell is double clicked
37250          * @param {Grid} this
37251          * @param {Number} columnIndex
37252          * @param {Roo.EventObject} e
37253          */
37254         "headerdblclick" : true,
37255         /**
37256          * @event rowcontextmenu
37257          * Fires when a row is right clicked
37258          * @param {Grid} this
37259          * @param {Number} rowIndex
37260          * @param {Roo.EventObject} e
37261          */
37262         "rowcontextmenu" : true,
37263         /**
37264          * @event cellcontextmenu
37265          * Fires when a cell is right clicked
37266          * @param {Grid} this
37267          * @param {Number} rowIndex
37268          * @param {Number} cellIndex
37269          * @param {Roo.EventObject} e
37270          */
37271          "cellcontextmenu" : true,
37272         /**
37273          * @event headercontextmenu
37274          * Fires when a header is right clicked
37275          * @param {Grid} this
37276          * @param {Number} columnIndex
37277          * @param {Roo.EventObject} e
37278          */
37279         "headercontextmenu" : true,
37280         /**
37281          * @event bodyscroll
37282          * Fires when the body element is scrolled
37283          * @param {Number} scrollLeft
37284          * @param {Number} scrollTop
37285          */
37286         "bodyscroll" : true,
37287         /**
37288          * @event columnresize
37289          * Fires when the user resizes a column
37290          * @param {Number} columnIndex
37291          * @param {Number} newSize
37292          */
37293         "columnresize" : true,
37294         /**
37295          * @event columnmove
37296          * Fires when the user moves a column
37297          * @param {Number} oldIndex
37298          * @param {Number} newIndex
37299          */
37300         "columnmove" : true,
37301         /**
37302          * @event startdrag
37303          * Fires when row(s) start being dragged
37304          * @param {Grid} this
37305          * @param {Roo.GridDD} dd The drag drop object
37306          * @param {event} e The raw browser event
37307          */
37308         "startdrag" : true,
37309         /**
37310          * @event enddrag
37311          * Fires when a drag operation is complete
37312          * @param {Grid} this
37313          * @param {Roo.GridDD} dd The drag drop object
37314          * @param {event} e The raw browser event
37315          */
37316         "enddrag" : true,
37317         /**
37318          * @event dragdrop
37319          * Fires when dragged row(s) are dropped on a valid DD target
37320          * @param {Grid} this
37321          * @param {Roo.GridDD} dd The drag drop object
37322          * @param {String} targetId The target drag drop object
37323          * @param {event} e The raw browser event
37324          */
37325         "dragdrop" : true,
37326         /**
37327          * @event dragover
37328          * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
37329          * @param {Grid} this
37330          * @param {Roo.GridDD} dd The drag drop object
37331          * @param {String} targetId The target drag drop object
37332          * @param {event} e The raw browser event
37333          */
37334         "dragover" : true,
37335         /**
37336          * @event dragenter
37337          *  Fires when the dragged row(s) first cross another DD target while being dragged
37338          * @param {Grid} this
37339          * @param {Roo.GridDD} dd The drag drop object
37340          * @param {String} targetId The target drag drop object
37341          * @param {event} e The raw browser event
37342          */
37343         "dragenter" : true,
37344         /**
37345          * @event dragout
37346          * Fires when the dragged row(s) leave another DD target while being dragged
37347          * @param {Grid} this
37348          * @param {Roo.GridDD} dd The drag drop object
37349          * @param {String} targetId The target drag drop object
37350          * @param {event} e The raw browser event
37351          */
37352         "dragout" : true,
37353         /**
37354          * @event rowclass
37355          * Fires when a row is rendered, so you can change add a style to it.
37356          * @param {GridView} gridview   The grid view
37357          * @param {Object} rowcfg   contains record  rowIndex and rowClass - set rowClass to add a style.
37358          */
37359         'rowclass' : true,
37360
37361         /**
37362          * @event render
37363          * Fires when the grid is rendered
37364          * @param {Grid} grid
37365          */
37366         'render' : true
37367     });
37368
37369     Roo.grid.Grid.superclass.constructor.call(this);
37370 };
37371 Roo.extend(Roo.grid.Grid, Roo.util.Observable, {
37372     
37373     /**
37374          * @cfg {Roo.grid.AbstractSelectionModel} sm The selection Model (default = Roo.grid.RowSelectionModel)
37375          */
37376         /**
37377          * @cfg {Roo.grid.GridView} view  The view that renders the grid (default = Roo.grid.GridView)
37378          */
37379         /**
37380          * @cfg {Roo.grid.ColumnModel} cm[] The columns of the grid
37381          */
37382         /**
37383          * @cfg {Roo.data.Store} ds The data store for the grid
37384          */
37385         /**
37386          * @cfg {Roo.Toolbar} toolbar a toolbar for buttons etc.
37387          */
37388         /**
37389      * @cfg {String} ddGroup - drag drop group.
37390      */
37391       /**
37392      * @cfg {String} dragGroup - drag group (?? not sure if needed.)
37393      */
37394
37395     /**
37396      * @cfg {Number} minColumnWidth The minimum width a column can be resized to. Default is 25.
37397      */
37398     minColumnWidth : 25,
37399
37400     /**
37401      * @cfg {Boolean} autoSizeColumns True to automatically resize the columns to fit their content
37402      * <b>on initial render.</b> It is more efficient to explicitly size the columns
37403      * through the ColumnModel's {@link Roo.grid.ColumnModel#width} config option.  Default is false.
37404      */
37405     autoSizeColumns : false,
37406
37407     /**
37408      * @cfg {Boolean} autoSizeHeaders True to measure headers with column data when auto sizing columns. Default is true.
37409      */
37410     autoSizeHeaders : true,
37411
37412     /**
37413      * @cfg {Boolean} monitorWindowResize True to autoSize the grid when the window resizes. Default is true.
37414      */
37415     monitorWindowResize : true,
37416
37417     /**
37418      * @cfg {Boolean} maxRowsToMeasure If autoSizeColumns is on, maxRowsToMeasure can be used to limit the number of
37419      * rows measured to get a columns size. Default is 0 (all rows).
37420      */
37421     maxRowsToMeasure : 0,
37422
37423     /**
37424      * @cfg {Boolean} trackMouseOver True to highlight rows when the mouse is over. Default is true.
37425      */
37426     trackMouseOver : true,
37427
37428     /**
37429     * @cfg {Boolean} enableDrag  True to enable drag of rows. Default is false. (double check if this is needed?)
37430     */
37431       /**
37432     * @cfg {Boolean} enableDrop  True to enable drop of elements. Default is false. (double check if this is needed?)
37433     */
37434     
37435     /**
37436     * @cfg {Boolean} enableDragDrop True to enable drag and drop of rows. Default is false.
37437     */
37438     enableDragDrop : false,
37439     
37440     /**
37441     * @cfg {Boolean} enableColumnMove True to enable drag and drop reorder of columns. Default is true.
37442     */
37443     enableColumnMove : true,
37444     
37445     /**
37446     * @cfg {Boolean} enableColumnHide True to enable hiding of columns with the header context menu. Default is true.
37447     */
37448     enableColumnHide : true,
37449     
37450     /**
37451     * @cfg {Boolean} enableRowHeightSync True to manually sync row heights across locked and not locked rows. Default is false.
37452     */
37453     enableRowHeightSync : false,
37454     
37455     /**
37456     * @cfg {Boolean} stripeRows True to stripe the rows.  Default is true.
37457     */
37458     stripeRows : true,
37459     
37460     /**
37461     * @cfg {Boolean} autoHeight True to fit the height of the grid container to the height of the data. Default is false.
37462     */
37463     autoHeight : false,
37464
37465     /**
37466      * @cfg {String} autoExpandColumn The id (or dataIndex) of a column in this grid that should expand to fill unused space. This id can not be 0. Default is false.
37467      */
37468     autoExpandColumn : false,
37469
37470     /**
37471     * @cfg {Number} autoExpandMin The minimum width the autoExpandColumn can have (if enabled).
37472     * Default is 50.
37473     */
37474     autoExpandMin : 50,
37475
37476     /**
37477     * @cfg {Number} autoExpandMax The maximum width the autoExpandColumn can have (if enabled). Default is 1000.
37478     */
37479     autoExpandMax : 1000,
37480
37481     /**
37482     * @cfg {Object} view The {@link Roo.grid.GridView} used by the grid. This can be set before a call to render().
37483     */
37484     view : null,
37485
37486     /**
37487     * @cfg {Object} loadMask An {@link Roo.LoadMask} config or true to mask the grid while loading. Default is false.
37488     */
37489     loadMask : false,
37490     /**
37491     * @cfg {Roo.dd.DropTarget} dropTarget An {@link Roo.dd.DropTarget} config
37492     */
37493     dropTarget: false,
37494      /**
37495     * @cfg {boolean} sortColMenu Sort the column order menu when it shows (usefull for long lists..) default false
37496     */ 
37497     sortColMenu : false,
37498     
37499     // private
37500     rendered : false,
37501
37502     /**
37503     * @cfg {Boolean} autoWidth True to set the grid's width to the default total width of the grid's columns instead
37504     * of a fixed width. Default is false.
37505     */
37506     /**
37507     * @cfg {Number} maxHeight Sets the maximum height of the grid - ignored if autoHeight is not on.
37508     */
37509     
37510     
37511     /**
37512     * @cfg {String} ddText Configures the text is the drag proxy (defaults to "%0 selected row(s)").
37513     * %0 is replaced with the number of selected rows.
37514     */
37515     ddText : "{0} selected row{1}",
37516     
37517     
37518     /**
37519      * Called once after all setup has been completed and the grid is ready to be rendered.
37520      * @return {Roo.grid.Grid} this
37521      */
37522     render : function()
37523     {
37524         var c = this.container;
37525         // try to detect autoHeight/width mode
37526         if((!c.dom.offsetHeight || c.dom.offsetHeight < 20) || c.getStyle("height") == "auto"){
37527             this.autoHeight = true;
37528         }
37529         var view = this.getView();
37530         view.init(this);
37531
37532         c.on("click", this.onClick, this);
37533         c.on("dblclick", this.onDblClick, this);
37534         c.on("contextmenu", this.onContextMenu, this);
37535         c.on("keydown", this.onKeyDown, this);
37536         if (Roo.isTouch) {
37537             c.on("touchstart", this.onTouchStart, this);
37538         }
37539
37540         this.relayEvents(c, ["mousedown","mouseup","mouseover","mouseout","keypress"]);
37541
37542         this.getSelectionModel().init(this);
37543
37544         view.render();
37545
37546         if(this.loadMask){
37547             this.loadMask = new Roo.LoadMask(this.container,
37548                     Roo.apply({store:this.dataSource}, this.loadMask));
37549         }
37550         
37551         
37552         if (this.toolbar && this.toolbar.xtype) {
37553             this.toolbar.container = this.getView().getHeaderPanel(true);
37554             this.toolbar = new Roo.Toolbar(this.toolbar);
37555         }
37556         if (this.footer && this.footer.xtype) {
37557             this.footer.dataSource = this.getDataSource();
37558             this.footer.container = this.getView().getFooterPanel(true);
37559             this.footer = Roo.factory(this.footer, Roo);
37560         }
37561         if (this.dropTarget && this.dropTarget.xtype) {
37562             delete this.dropTarget.xtype;
37563             this.dropTarget =  new Roo.dd.DropTarget(this.getView().mainBody, this.dropTarget);
37564         }
37565         
37566         
37567         this.rendered = true;
37568         this.fireEvent('render', this);
37569         return this;
37570     },
37571
37572     /**
37573      * Reconfigures the grid to use a different Store and Column Model.
37574      * The View will be bound to the new objects and refreshed.
37575      * @param {Roo.data.Store} dataSource The new {@link Roo.data.Store} object
37576      * @param {Roo.grid.ColumnModel} The new {@link Roo.grid.ColumnModel} object
37577      */
37578     reconfigure : function(dataSource, colModel){
37579         if(this.loadMask){
37580             this.loadMask.destroy();
37581             this.loadMask = new Roo.LoadMask(this.container,
37582                     Roo.apply({store:dataSource}, this.loadMask));
37583         }
37584         this.view.bind(dataSource, colModel);
37585         this.dataSource = dataSource;
37586         this.colModel = colModel;
37587         this.view.refresh(true);
37588     },
37589     /**
37590      * addColumns
37591      * Add's a column, default at the end..
37592      
37593      * @param {int} position to add (default end)
37594      * @param {Array} of objects of column configuration see {@link Roo.grid.ColumnModel} 
37595      */
37596     addColumns : function(pos, ar)
37597     {
37598         
37599         for (var i =0;i< ar.length;i++) {
37600             var cfg = ar[i];
37601             cfg.id = typeof(cfg.id) == 'undefined' ? Roo.id() : cfg.id; // don't normally use this..
37602             this.cm.lookup[cfg.id] = cfg;
37603         }
37604         
37605         
37606         if (typeof(pos) == 'undefined' || pos >= this.cm.config.length) {
37607             pos = this.cm.config.length; //this.cm.config.push(cfg);
37608         } 
37609         pos = Math.max(0,pos);
37610         ar.unshift(0);
37611         ar.unshift(pos);
37612         this.cm.config.splice.apply(this.cm.config, ar);
37613         
37614         
37615         
37616         this.view.generateRules(this.cm);
37617         this.view.refresh(true);
37618         
37619     },
37620     
37621     
37622     
37623     
37624     // private
37625     onKeyDown : function(e){
37626         this.fireEvent("keydown", e);
37627     },
37628
37629     /**
37630      * Destroy this grid.
37631      * @param {Boolean} removeEl True to remove the element
37632      */
37633     destroy : function(removeEl, keepListeners){
37634         if(this.loadMask){
37635             this.loadMask.destroy();
37636         }
37637         var c = this.container;
37638         c.removeAllListeners();
37639         this.view.destroy();
37640         this.colModel.purgeListeners();
37641         if(!keepListeners){
37642             this.purgeListeners();
37643         }
37644         c.update("");
37645         if(removeEl === true){
37646             c.remove();
37647         }
37648     },
37649
37650     // private
37651     processEvent : function(name, e){
37652         // does this fire select???
37653         //Roo.log('grid:processEvent '  + name);
37654         
37655         if (name != 'touchstart' ) {
37656             this.fireEvent(name, e);    
37657         }
37658         
37659         var t = e.getTarget();
37660         var v = this.view;
37661         var header = v.findHeaderIndex(t);
37662         if(header !== false){
37663             var ename = name == 'touchstart' ? 'click' : name;
37664              
37665             this.fireEvent("header" + ename, this, header, e);
37666         }else{
37667             var row = v.findRowIndex(t);
37668             var cell = v.findCellIndex(t);
37669             if (name == 'touchstart') {
37670                 // first touch is always a click.
37671                 // hopefull this happens after selection is updated.?
37672                 name = false;
37673                 
37674                 if (typeof(this.selModel.getSelectedCell) != 'undefined') {
37675                     var cs = this.selModel.getSelectedCell();
37676                     if (row == cs[0] && cell == cs[1]){
37677                         name = 'dblclick';
37678                     }
37679                 }
37680                 if (typeof(this.selModel.getSelections) != 'undefined') {
37681                     var cs = this.selModel.getSelections();
37682                     var ds = this.dataSource;
37683                     if (cs.length == 1 && ds.getAt(row) == cs[0]){
37684                         name = 'dblclick';
37685                     }
37686                 }
37687                 if (!name) {
37688                     return;
37689                 }
37690             }
37691             
37692             
37693             if(row !== false){
37694                 this.fireEvent("row" + name, this, row, e);
37695                 if(cell !== false){
37696                     this.fireEvent("cell" + name, this, row, cell, e);
37697                 }
37698             }
37699         }
37700     },
37701
37702     // private
37703     onClick : function(e){
37704         this.processEvent("click", e);
37705     },
37706    // private
37707     onTouchStart : function(e){
37708         this.processEvent("touchstart", e);
37709     },
37710
37711     // private
37712     onContextMenu : function(e, t){
37713         this.processEvent("contextmenu", e);
37714     },
37715
37716     // private
37717     onDblClick : function(e){
37718         this.processEvent("dblclick", e);
37719     },
37720
37721     // private
37722     walkCells : function(row, col, step, fn, scope){
37723         var cm = this.colModel, clen = cm.getColumnCount();
37724         var ds = this.dataSource, rlen = ds.getCount(), first = true;
37725         if(step < 0){
37726             if(col < 0){
37727                 row--;
37728                 first = false;
37729             }
37730             while(row >= 0){
37731                 if(!first){
37732                     col = clen-1;
37733                 }
37734                 first = false;
37735                 while(col >= 0){
37736                     if(fn.call(scope || this, row, col, cm) === true){
37737                         return [row, col];
37738                     }
37739                     col--;
37740                 }
37741                 row--;
37742             }
37743         } else {
37744             if(col >= clen){
37745                 row++;
37746                 first = false;
37747             }
37748             while(row < rlen){
37749                 if(!first){
37750                     col = 0;
37751                 }
37752                 first = false;
37753                 while(col < clen){
37754                     if(fn.call(scope || this, row, col, cm) === true){
37755                         return [row, col];
37756                     }
37757                     col++;
37758                 }
37759                 row++;
37760             }
37761         }
37762         return null;
37763     },
37764
37765     // private
37766     getSelections : function(){
37767         return this.selModel.getSelections();
37768     },
37769
37770     /**
37771      * Causes the grid to manually recalculate its dimensions. Generally this is done automatically,
37772      * but if manual update is required this method will initiate it.
37773      */
37774     autoSize : function(){
37775         if(this.rendered){
37776             this.view.layout();
37777             if(this.view.adjustForScroll){
37778                 this.view.adjustForScroll();
37779             }
37780         }
37781     },
37782
37783     /**
37784      * Returns the grid's underlying element.
37785      * @return {Element} The element
37786      */
37787     getGridEl : function(){
37788         return this.container;
37789     },
37790
37791     // private for compatibility, overridden by editor grid
37792     stopEditing : function(){},
37793
37794     /**
37795      * Returns the grid's SelectionModel.
37796      * @return {SelectionModel}
37797      */
37798     getSelectionModel : function(){
37799         if(!this.selModel){
37800             this.selModel = new Roo.grid.RowSelectionModel();
37801         }
37802         return this.selModel;
37803     },
37804
37805     /**
37806      * Returns the grid's DataSource.
37807      * @return {DataSource}
37808      */
37809     getDataSource : function(){
37810         return this.dataSource;
37811     },
37812
37813     /**
37814      * Returns the grid's ColumnModel.
37815      * @return {ColumnModel}
37816      */
37817     getColumnModel : function(){
37818         return this.colModel;
37819     },
37820
37821     /**
37822      * Returns the grid's GridView object.
37823      * @return {GridView}
37824      */
37825     getView : function(){
37826         if(!this.view){
37827             this.view = new Roo.grid.GridView(this.viewConfig);
37828             this.relayEvents(this.view, [
37829                 "beforerowremoved", "beforerowsinserted",
37830                 "beforerefresh", "rowremoved",
37831                 "rowsinserted", "rowupdated" ,"refresh"
37832             ]);
37833         }
37834         return this.view;
37835     },
37836     /**
37837      * Called to get grid's drag proxy text, by default returns this.ddText.
37838      * Override this to put something different in the dragged text.
37839      * @return {String}
37840      */
37841     getDragDropText : function(){
37842         var count = this.selModel.getCount();
37843         return String.format(this.ddText, count, count == 1 ? '' : 's');
37844     }
37845 });
37846 /*
37847  * Based on:
37848  * Ext JS Library 1.1.1
37849  * Copyright(c) 2006-2007, Ext JS, LLC.
37850  *
37851  * Originally Released Under LGPL - original licence link has changed is not relivant.
37852  *
37853  * Fork - LGPL
37854  * <script type="text/javascript">
37855  */
37856  /**
37857  * @class Roo.grid.AbstractGridView
37858  * @extends Roo.util.Observable
37859  * @abstract
37860  * Abstract base class for grid Views
37861  * @constructor
37862  */
37863 Roo.grid.AbstractGridView = function(){
37864         this.grid = null;
37865         
37866         this.events = {
37867             "beforerowremoved" : true,
37868             "beforerowsinserted" : true,
37869             "beforerefresh" : true,
37870             "rowremoved" : true,
37871             "rowsinserted" : true,
37872             "rowupdated" : true,
37873             "refresh" : true
37874         };
37875     Roo.grid.AbstractGridView.superclass.constructor.call(this);
37876 };
37877
37878 Roo.extend(Roo.grid.AbstractGridView, Roo.util.Observable, {
37879     rowClass : "x-grid-row",
37880     cellClass : "x-grid-cell",
37881     tdClass : "x-grid-td",
37882     hdClass : "x-grid-hd",
37883     splitClass : "x-grid-hd-split",
37884     
37885     init: function(grid){
37886         this.grid = grid;
37887                 var cid = this.grid.getGridEl().id;
37888         this.colSelector = "#" + cid + " ." + this.cellClass + "-";
37889         this.tdSelector = "#" + cid + " ." + this.tdClass + "-";
37890         this.hdSelector = "#" + cid + " ." + this.hdClass + "-";
37891         this.splitSelector = "#" + cid + " ." + this.splitClass + "-";
37892         },
37893         
37894     getColumnRenderers : function(){
37895         var renderers = [];
37896         var cm = this.grid.colModel;
37897         var colCount = cm.getColumnCount();
37898         for(var i = 0; i < colCount; i++){
37899             renderers[i] = cm.getRenderer(i);
37900         }
37901         return renderers;
37902     },
37903     
37904     getColumnIds : function(){
37905         var ids = [];
37906         var cm = this.grid.colModel;
37907         var colCount = cm.getColumnCount();
37908         for(var i = 0; i < colCount; i++){
37909             ids[i] = cm.getColumnId(i);
37910         }
37911         return ids;
37912     },
37913     
37914     getDataIndexes : function(){
37915         if(!this.indexMap){
37916             this.indexMap = this.buildIndexMap();
37917         }
37918         return this.indexMap.colToData;
37919     },
37920     
37921     getColumnIndexByDataIndex : function(dataIndex){
37922         if(!this.indexMap){
37923             this.indexMap = this.buildIndexMap();
37924         }
37925         return this.indexMap.dataToCol[dataIndex];
37926     },
37927     
37928     /**
37929      * Set a css style for a column dynamically. 
37930      * @param {Number} colIndex The index of the column
37931      * @param {String} name The css property name
37932      * @param {String} value The css value
37933      */
37934     setCSSStyle : function(colIndex, name, value){
37935         var selector = "#" + this.grid.id + " .x-grid-col-" + colIndex;
37936         Roo.util.CSS.updateRule(selector, name, value);
37937     },
37938     
37939     generateRules : function(cm){
37940         var ruleBuf = [], rulesId = this.grid.id + '-cssrules';
37941         Roo.util.CSS.removeStyleSheet(rulesId);
37942         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
37943             var cid = cm.getColumnId(i);
37944             ruleBuf.push(this.colSelector, cid, " {\n", cm.config[i].css, "}\n",
37945                          this.tdSelector, cid, " {\n}\n",
37946                          this.hdSelector, cid, " {\n}\n",
37947                          this.splitSelector, cid, " {\n}\n");
37948         }
37949         return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
37950     }
37951 });/*
37952  * Based on:
37953  * Ext JS Library 1.1.1
37954  * Copyright(c) 2006-2007, Ext JS, LLC.
37955  *
37956  * Originally Released Under LGPL - original licence link has changed is not relivant.
37957  *
37958  * Fork - LGPL
37959  * <script type="text/javascript">
37960  */
37961
37962 // private
37963 // This is a support class used internally by the Grid components
37964 Roo.grid.HeaderDragZone = function(grid, hd, hd2){
37965     this.grid = grid;
37966     this.view = grid.getView();
37967     this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
37968     Roo.grid.HeaderDragZone.superclass.constructor.call(this, hd);
37969     if(hd2){
37970         this.setHandleElId(Roo.id(hd));
37971         this.setOuterHandleElId(Roo.id(hd2));
37972     }
37973     this.scroll = false;
37974 };
37975 Roo.extend(Roo.grid.HeaderDragZone, Roo.dd.DragZone, {
37976     maxDragWidth: 120,
37977     getDragData : function(e){
37978         var t = Roo.lib.Event.getTarget(e);
37979         var h = this.view.findHeaderCell(t);
37980         if(h){
37981             return {ddel: h.firstChild, header:h};
37982         }
37983         return false;
37984     },
37985
37986     onInitDrag : function(e){
37987         this.view.headersDisabled = true;
37988         var clone = this.dragData.ddel.cloneNode(true);
37989         clone.id = Roo.id();
37990         clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
37991         this.proxy.update(clone);
37992         return true;
37993     },
37994
37995     afterValidDrop : function(){
37996         var v = this.view;
37997         setTimeout(function(){
37998             v.headersDisabled = false;
37999         }, 50);
38000     },
38001
38002     afterInvalidDrop : function(){
38003         var v = this.view;
38004         setTimeout(function(){
38005             v.headersDisabled = false;
38006         }, 50);
38007     }
38008 });
38009 /*
38010  * Based on:
38011  * Ext JS Library 1.1.1
38012  * Copyright(c) 2006-2007, Ext JS, LLC.
38013  *
38014  * Originally Released Under LGPL - original licence link has changed is not relivant.
38015  *
38016  * Fork - LGPL
38017  * <script type="text/javascript">
38018  */
38019 // private
38020 // This is a support class used internally by the Grid components
38021 Roo.grid.HeaderDropZone = function(grid, hd, hd2){
38022     this.grid = grid;
38023     this.view = grid.getView();
38024     // split the proxies so they don't interfere with mouse events
38025     this.proxyTop = Roo.DomHelper.append(document.body, {
38026         cls:"col-move-top", html:"&#160;"
38027     }, true);
38028     this.proxyBottom = Roo.DomHelper.append(document.body, {
38029         cls:"col-move-bottom", html:"&#160;"
38030     }, true);
38031     this.proxyTop.hide = this.proxyBottom.hide = function(){
38032         this.setLeftTop(-100,-100);
38033         this.setStyle("visibility", "hidden");
38034     };
38035     this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
38036     // temporarily disabled
38037     //Roo.dd.ScrollManager.register(this.view.scroller.dom);
38038     Roo.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
38039 };
38040 Roo.extend(Roo.grid.HeaderDropZone, Roo.dd.DropZone, {
38041     proxyOffsets : [-4, -9],
38042     fly: Roo.Element.fly,
38043
38044     getTargetFromEvent : function(e){
38045         var t = Roo.lib.Event.getTarget(e);
38046         var cindex = this.view.findCellIndex(t);
38047         if(cindex !== false){
38048             return this.view.getHeaderCell(cindex);
38049         }
38050         return null;
38051     },
38052
38053     nextVisible : function(h){
38054         var v = this.view, cm = this.grid.colModel;
38055         h = h.nextSibling;
38056         while(h){
38057             if(!cm.isHidden(v.getCellIndex(h))){
38058                 return h;
38059             }
38060             h = h.nextSibling;
38061         }
38062         return null;
38063     },
38064
38065     prevVisible : function(h){
38066         var v = this.view, cm = this.grid.colModel;
38067         h = h.prevSibling;
38068         while(h){
38069             if(!cm.isHidden(v.getCellIndex(h))){
38070                 return h;
38071             }
38072             h = h.prevSibling;
38073         }
38074         return null;
38075     },
38076
38077     positionIndicator : function(h, n, e){
38078         var x = Roo.lib.Event.getPageX(e);
38079         var r = Roo.lib.Dom.getRegion(n.firstChild);
38080         var px, pt, py = r.top + this.proxyOffsets[1];
38081         if((r.right - x) <= (r.right-r.left)/2){
38082             px = r.right+this.view.borderWidth;
38083             pt = "after";
38084         }else{
38085             px = r.left;
38086             pt = "before";
38087         }
38088         var oldIndex = this.view.getCellIndex(h);
38089         var newIndex = this.view.getCellIndex(n);
38090
38091         if(this.grid.colModel.isFixed(newIndex)){
38092             return false;
38093         }
38094
38095         var locked = this.grid.colModel.isLocked(newIndex);
38096
38097         if(pt == "after"){
38098             newIndex++;
38099         }
38100         if(oldIndex < newIndex){
38101             newIndex--;
38102         }
38103         if(oldIndex == newIndex && (locked == this.grid.colModel.isLocked(oldIndex))){
38104             return false;
38105         }
38106         px +=  this.proxyOffsets[0];
38107         this.proxyTop.setLeftTop(px, py);
38108         this.proxyTop.show();
38109         if(!this.bottomOffset){
38110             this.bottomOffset = this.view.mainHd.getHeight();
38111         }
38112         this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
38113         this.proxyBottom.show();
38114         return pt;
38115     },
38116
38117     onNodeEnter : function(n, dd, e, data){
38118         if(data.header != n){
38119             this.positionIndicator(data.header, n, e);
38120         }
38121     },
38122
38123     onNodeOver : function(n, dd, e, data){
38124         var result = false;
38125         if(data.header != n){
38126             result = this.positionIndicator(data.header, n, e);
38127         }
38128         if(!result){
38129             this.proxyTop.hide();
38130             this.proxyBottom.hide();
38131         }
38132         return result ? this.dropAllowed : this.dropNotAllowed;
38133     },
38134
38135     onNodeOut : function(n, dd, e, data){
38136         this.proxyTop.hide();
38137         this.proxyBottom.hide();
38138     },
38139
38140     onNodeDrop : function(n, dd, e, data){
38141         var h = data.header;
38142         if(h != n){
38143             var cm = this.grid.colModel;
38144             var x = Roo.lib.Event.getPageX(e);
38145             var r = Roo.lib.Dom.getRegion(n.firstChild);
38146             var pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before";
38147             var oldIndex = this.view.getCellIndex(h);
38148             var newIndex = this.view.getCellIndex(n);
38149             var locked = cm.isLocked(newIndex);
38150             if(pt == "after"){
38151                 newIndex++;
38152             }
38153             if(oldIndex < newIndex){
38154                 newIndex--;
38155             }
38156             if(oldIndex == newIndex && (locked == cm.isLocked(oldIndex))){
38157                 return false;
38158             }
38159             cm.setLocked(oldIndex, locked, true);
38160             cm.moveColumn(oldIndex, newIndex);
38161             this.grid.fireEvent("columnmove", oldIndex, newIndex);
38162             return true;
38163         }
38164         return false;
38165     }
38166 });
38167 /*
38168  * Based on:
38169  * Ext JS Library 1.1.1
38170  * Copyright(c) 2006-2007, Ext JS, LLC.
38171  *
38172  * Originally Released Under LGPL - original licence link has changed is not relivant.
38173  *
38174  * Fork - LGPL
38175  * <script type="text/javascript">
38176  */
38177   
38178 /**
38179  * @class Roo.grid.GridView
38180  * @extends Roo.util.Observable
38181  *
38182  * @constructor
38183  * @param {Object} config
38184  */
38185 Roo.grid.GridView = function(config){
38186     Roo.grid.GridView.superclass.constructor.call(this);
38187     this.el = null;
38188
38189     Roo.apply(this, config);
38190 };
38191
38192 Roo.extend(Roo.grid.GridView, Roo.grid.AbstractGridView, {
38193
38194     unselectable :  'unselectable="on"',
38195     unselectableCls :  'x-unselectable',
38196     
38197     
38198     rowClass : "x-grid-row",
38199
38200     cellClass : "x-grid-col",
38201
38202     tdClass : "x-grid-td",
38203
38204     hdClass : "x-grid-hd",
38205
38206     splitClass : "x-grid-split",
38207
38208     sortClasses : ["sort-asc", "sort-desc"],
38209
38210     enableMoveAnim : false,
38211
38212     hlColor: "C3DAF9",
38213
38214     dh : Roo.DomHelper,
38215
38216     fly : Roo.Element.fly,
38217
38218     css : Roo.util.CSS,
38219
38220     borderWidth: 1,
38221
38222     splitOffset: 3,
38223
38224     scrollIncrement : 22,
38225
38226     cellRE: /(?:.*?)x-grid-(?:hd|cell|csplit)-(?:[\d]+)-([\d]+)(?:.*?)/,
38227
38228     findRE: /\s?(?:x-grid-hd|x-grid-col|x-grid-csplit)\s/,
38229
38230     bind : function(ds, cm){
38231         if(this.ds){
38232             this.ds.un("load", this.onLoad, this);
38233             this.ds.un("datachanged", this.onDataChange, this);
38234             this.ds.un("add", this.onAdd, this);
38235             this.ds.un("remove", this.onRemove, this);
38236             this.ds.un("update", this.onUpdate, this);
38237             this.ds.un("clear", this.onClear, this);
38238         }
38239         if(ds){
38240             ds.on("load", this.onLoad, this);
38241             ds.on("datachanged", this.onDataChange, this);
38242             ds.on("add", this.onAdd, this);
38243             ds.on("remove", this.onRemove, this);
38244             ds.on("update", this.onUpdate, this);
38245             ds.on("clear", this.onClear, this);
38246         }
38247         this.ds = ds;
38248
38249         if(this.cm){
38250             this.cm.un("widthchange", this.onColWidthChange, this);
38251             this.cm.un("headerchange", this.onHeaderChange, this);
38252             this.cm.un("hiddenchange", this.onHiddenChange, this);
38253             this.cm.un("columnmoved", this.onColumnMove, this);
38254             this.cm.un("columnlockchange", this.onColumnLock, this);
38255         }
38256         if(cm){
38257             this.generateRules(cm);
38258             cm.on("widthchange", this.onColWidthChange, this);
38259             cm.on("headerchange", this.onHeaderChange, this);
38260             cm.on("hiddenchange", this.onHiddenChange, this);
38261             cm.on("columnmoved", this.onColumnMove, this);
38262             cm.on("columnlockchange", this.onColumnLock, this);
38263         }
38264         this.cm = cm;
38265     },
38266
38267     init: function(grid){
38268         Roo.grid.GridView.superclass.init.call(this, grid);
38269
38270         this.bind(grid.dataSource, grid.colModel);
38271
38272         grid.on("headerclick", this.handleHeaderClick, this);
38273
38274         if(grid.trackMouseOver){
38275             grid.on("mouseover", this.onRowOver, this);
38276             grid.on("mouseout", this.onRowOut, this);
38277         }
38278         grid.cancelTextSelection = function(){};
38279         this.gridId = grid.id;
38280
38281         var tpls = this.templates || {};
38282
38283         if(!tpls.master){
38284             tpls.master = new Roo.Template(
38285                '<div class="x-grid" hidefocus="true">',
38286                 '<a href="#" class="x-grid-focus" tabIndex="-1"></a>',
38287                   '<div class="x-grid-topbar"></div>',
38288                   '<div class="x-grid-scroller"><div></div></div>',
38289                   '<div class="x-grid-locked">',
38290                       '<div class="x-grid-header">{lockedHeader}</div>',
38291                       '<div class="x-grid-body">{lockedBody}</div>',
38292                   "</div>",
38293                   '<div class="x-grid-viewport">',
38294                       '<div class="x-grid-header">{header}</div>',
38295                       '<div class="x-grid-body">{body}</div>',
38296                   "</div>",
38297                   '<div class="x-grid-bottombar"></div>',
38298                  
38299                   '<div class="x-grid-resize-proxy">&#160;</div>',
38300                "</div>"
38301             );
38302             tpls.master.disableformats = true;
38303         }
38304
38305         if(!tpls.header){
38306             tpls.header = new Roo.Template(
38307                '<table border="0" cellspacing="0" cellpadding="0">',
38308                '<tbody><tr class="x-grid-hd-row">{cells}</tr></tbody>',
38309                "</table>{splits}"
38310             );
38311             tpls.header.disableformats = true;
38312         }
38313         tpls.header.compile();
38314
38315         if(!tpls.hcell){
38316             tpls.hcell = new Roo.Template(
38317                 '<td class="x-grid-hd x-grid-td-{id} {cellId}"><div title="{title}" class="x-grid-hd-inner x-grid-hd-{id}">',
38318                 '<div class="x-grid-hd-text ' + this.unselectableCls +  '" ' + this.unselectable +'>{value}<img class="x-grid-sort-icon" src="', Roo.BLANK_IMAGE_URL, '" /></div>',
38319                 "</div></td>"
38320              );
38321              tpls.hcell.disableFormats = true;
38322         }
38323         tpls.hcell.compile();
38324
38325         if(!tpls.hsplit){
38326             tpls.hsplit = new Roo.Template('<div class="x-grid-split {splitId} x-grid-split-{id}" style="{style} ' +
38327                                             this.unselectableCls +  '" ' + this.unselectable +'>&#160;</div>');
38328             tpls.hsplit.disableFormats = true;
38329         }
38330         tpls.hsplit.compile();
38331
38332         if(!tpls.body){
38333             tpls.body = new Roo.Template(
38334                '<table border="0" cellspacing="0" cellpadding="0">',
38335                "<tbody>{rows}</tbody>",
38336                "</table>"
38337             );
38338             tpls.body.disableFormats = true;
38339         }
38340         tpls.body.compile();
38341
38342         if(!tpls.row){
38343             tpls.row = new Roo.Template('<tr class="x-grid-row {alt}">{cells}</tr>');
38344             tpls.row.disableFormats = true;
38345         }
38346         tpls.row.compile();
38347
38348         if(!tpls.cell){
38349             tpls.cell = new Roo.Template(
38350                 '<td class="x-grid-col x-grid-td-{id} {cellId} {css}" tabIndex="0">',
38351                 '<div class="x-grid-col-{id} x-grid-cell-inner"><div class="x-grid-cell-text ' +
38352                     this.unselectableCls +  '" ' + this.unselectable +'" {attr}>{value}</div></div>',
38353                 "</td>"
38354             );
38355             tpls.cell.disableFormats = true;
38356         }
38357         tpls.cell.compile();
38358
38359         this.templates = tpls;
38360     },
38361
38362     // remap these for backwards compat
38363     onColWidthChange : function(){
38364         this.updateColumns.apply(this, arguments);
38365     },
38366     onHeaderChange : function(){
38367         this.updateHeaders.apply(this, arguments);
38368     }, 
38369     onHiddenChange : function(){
38370         this.handleHiddenChange.apply(this, arguments);
38371     },
38372     onColumnMove : function(){
38373         this.handleColumnMove.apply(this, arguments);
38374     },
38375     onColumnLock : function(){
38376         this.handleLockChange.apply(this, arguments);
38377     },
38378
38379     onDataChange : function(){
38380         this.refresh();
38381         this.updateHeaderSortState();
38382     },
38383
38384     onClear : function(){
38385         this.refresh();
38386     },
38387
38388     onUpdate : function(ds, record){
38389         this.refreshRow(record);
38390     },
38391
38392     refreshRow : function(record){
38393         var ds = this.ds, index;
38394         if(typeof record == 'number'){
38395             index = record;
38396             record = ds.getAt(index);
38397         }else{
38398             index = ds.indexOf(record);
38399         }
38400         this.insertRows(ds, index, index, true);
38401         this.onRemove(ds, record, index+1, true);
38402         this.syncRowHeights(index, index);
38403         this.layout();
38404         this.fireEvent("rowupdated", this, index, record);
38405     },
38406
38407     onAdd : function(ds, records, index){
38408         this.insertRows(ds, index, index + (records.length-1));
38409     },
38410
38411     onRemove : function(ds, record, index, isUpdate){
38412         if(isUpdate !== true){
38413             this.fireEvent("beforerowremoved", this, index, record);
38414         }
38415         var bt = this.getBodyTable(), lt = this.getLockedTable();
38416         if(bt.rows[index]){
38417             bt.firstChild.removeChild(bt.rows[index]);
38418         }
38419         if(lt.rows[index]){
38420             lt.firstChild.removeChild(lt.rows[index]);
38421         }
38422         if(isUpdate !== true){
38423             this.stripeRows(index);
38424             this.syncRowHeights(index, index);
38425             this.layout();
38426             this.fireEvent("rowremoved", this, index, record);
38427         }
38428     },
38429
38430     onLoad : function(){
38431         this.scrollToTop();
38432     },
38433
38434     /**
38435      * Scrolls the grid to the top
38436      */
38437     scrollToTop : function(){
38438         if(this.scroller){
38439             this.scroller.dom.scrollTop = 0;
38440             this.syncScroll();
38441         }
38442     },
38443
38444     /**
38445      * Gets a panel in the header of the grid that can be used for toolbars etc.
38446      * After modifying the contents of this panel a call to grid.autoSize() may be
38447      * required to register any changes in size.
38448      * @param {Boolean} doShow By default the header is hidden. Pass true to show the panel
38449      * @return Roo.Element
38450      */
38451     getHeaderPanel : function(doShow){
38452         if(doShow){
38453             this.headerPanel.show();
38454         }
38455         return this.headerPanel;
38456     },
38457
38458     /**
38459      * Gets a panel in the footer of the grid that can be used for toolbars etc.
38460      * After modifying the contents of this panel a call to grid.autoSize() may be
38461      * required to register any changes in size.
38462      * @param {Boolean} doShow By default the footer is hidden. Pass true to show the panel
38463      * @return Roo.Element
38464      */
38465     getFooterPanel : function(doShow){
38466         if(doShow){
38467             this.footerPanel.show();
38468         }
38469         return this.footerPanel;
38470     },
38471
38472     initElements : function(){
38473         var E = Roo.Element;
38474         var el = this.grid.getGridEl().dom.firstChild;
38475         var cs = el.childNodes;
38476
38477         this.el = new E(el);
38478         
38479          this.focusEl = new E(el.firstChild);
38480         this.focusEl.swallowEvent("click", true);
38481         
38482         this.headerPanel = new E(cs[1]);
38483         this.headerPanel.enableDisplayMode("block");
38484
38485         this.scroller = new E(cs[2]);
38486         this.scrollSizer = new E(this.scroller.dom.firstChild);
38487
38488         this.lockedWrap = new E(cs[3]);
38489         this.lockedHd = new E(this.lockedWrap.dom.firstChild);
38490         this.lockedBody = new E(this.lockedWrap.dom.childNodes[1]);
38491
38492         this.mainWrap = new E(cs[4]);
38493         this.mainHd = new E(this.mainWrap.dom.firstChild);
38494         this.mainBody = new E(this.mainWrap.dom.childNodes[1]);
38495
38496         this.footerPanel = new E(cs[5]);
38497         this.footerPanel.enableDisplayMode("block");
38498
38499         this.resizeProxy = new E(cs[6]);
38500
38501         this.headerSelector = String.format(
38502            '#{0} td.x-grid-hd, #{1} td.x-grid-hd',
38503            this.lockedHd.id, this.mainHd.id
38504         );
38505
38506         this.splitterSelector = String.format(
38507            '#{0} div.x-grid-split, #{1} div.x-grid-split',
38508            this.idToCssName(this.lockedHd.id), this.idToCssName(this.mainHd.id)
38509         );
38510     },
38511     idToCssName : function(s)
38512     {
38513         return s.replace(/[^a-z0-9]+/ig, '-');
38514     },
38515
38516     getHeaderCell : function(index){
38517         return Roo.DomQuery.select(this.headerSelector)[index];
38518     },
38519
38520     getHeaderCellMeasure : function(index){
38521         return this.getHeaderCell(index).firstChild;
38522     },
38523
38524     getHeaderCellText : function(index){
38525         return this.getHeaderCell(index).firstChild.firstChild;
38526     },
38527
38528     getLockedTable : function(){
38529         return this.lockedBody.dom.firstChild;
38530     },
38531
38532     getBodyTable : function(){
38533         return this.mainBody.dom.firstChild;
38534     },
38535
38536     getLockedRow : function(index){
38537         return this.getLockedTable().rows[index];
38538     },
38539
38540     getRow : function(index){
38541         return this.getBodyTable().rows[index];
38542     },
38543
38544     getRowComposite : function(index){
38545         if(!this.rowEl){
38546             this.rowEl = new Roo.CompositeElementLite();
38547         }
38548         var els = [], lrow, mrow;
38549         if(lrow = this.getLockedRow(index)){
38550             els.push(lrow);
38551         }
38552         if(mrow = this.getRow(index)){
38553             els.push(mrow);
38554         }
38555         this.rowEl.elements = els;
38556         return this.rowEl;
38557     },
38558     /**
38559      * Gets the 'td' of the cell
38560      * 
38561      * @param {Integer} rowIndex row to select
38562      * @param {Integer} colIndex column to select
38563      * 
38564      * @return {Object} 
38565      */
38566     getCell : function(rowIndex, colIndex){
38567         var locked = this.cm.getLockedCount();
38568         var source;
38569         if(colIndex < locked){
38570             source = this.lockedBody.dom.firstChild;
38571         }else{
38572             source = this.mainBody.dom.firstChild;
38573             colIndex -= locked;
38574         }
38575         return source.rows[rowIndex].childNodes[colIndex];
38576     },
38577
38578     getCellText : function(rowIndex, colIndex){
38579         return this.getCell(rowIndex, colIndex).firstChild.firstChild;
38580     },
38581
38582     getCellBox : function(cell){
38583         var b = this.fly(cell).getBox();
38584         if(Roo.isOpera){ // opera fails to report the Y
38585             b.y = cell.offsetTop + this.mainBody.getY();
38586         }
38587         return b;
38588     },
38589
38590     getCellIndex : function(cell){
38591         var id = String(cell.className).match(this.cellRE);
38592         if(id){
38593             return parseInt(id[1], 10);
38594         }
38595         return 0;
38596     },
38597
38598     findHeaderIndex : function(n){
38599         var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
38600         return r ? this.getCellIndex(r) : false;
38601     },
38602
38603     findHeaderCell : function(n){
38604         var r = Roo.fly(n).findParent("td." + this.hdClass, 6);
38605         return r ? r : false;
38606     },
38607
38608     findRowIndex : function(n){
38609         if(!n){
38610             return false;
38611         }
38612         var r = Roo.fly(n).findParent("tr." + this.rowClass, 6);
38613         return r ? r.rowIndex : false;
38614     },
38615
38616     findCellIndex : function(node){
38617         var stop = this.el.dom;
38618         while(node && node != stop){
38619             if(this.findRE.test(node.className)){
38620                 return this.getCellIndex(node);
38621             }
38622             node = node.parentNode;
38623         }
38624         return false;
38625     },
38626
38627     getColumnId : function(index){
38628         return this.cm.getColumnId(index);
38629     },
38630
38631     getSplitters : function()
38632     {
38633         if(this.splitterSelector){
38634            return Roo.DomQuery.select(this.splitterSelector);
38635         }else{
38636             return null;
38637       }
38638     },
38639
38640     getSplitter : function(index){
38641         return this.getSplitters()[index];
38642     },
38643
38644     onRowOver : function(e, t){
38645         var row;
38646         if((row = this.findRowIndex(t)) !== false){
38647             this.getRowComposite(row).addClass("x-grid-row-over");
38648         }
38649     },
38650
38651     onRowOut : function(e, t){
38652         var row;
38653         if((row = this.findRowIndex(t)) !== false && row !== this.findRowIndex(e.getRelatedTarget())){
38654             this.getRowComposite(row).removeClass("x-grid-row-over");
38655         }
38656     },
38657
38658     renderHeaders : function(){
38659         var cm = this.cm;
38660         var ct = this.templates.hcell, ht = this.templates.header, st = this.templates.hsplit;
38661         var cb = [], lb = [], sb = [], lsb = [], p = {};
38662         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
38663             p.cellId = "x-grid-hd-0-" + i;
38664             p.splitId = "x-grid-csplit-0-" + i;
38665             p.id = cm.getColumnId(i);
38666             p.value = cm.getColumnHeader(i) || "";
38667             p.title = cm.getColumnTooltip(i) || (''+p.value).match(/\</)  ? '' :  p.value  || "";
38668             p.style = (this.grid.enableColumnResize === false || !cm.isResizable(i) || cm.isFixed(i)) ? 'cursor:default' : '';
38669             if(!cm.isLocked(i)){
38670                 cb[cb.length] = ct.apply(p);
38671                 sb[sb.length] = st.apply(p);
38672             }else{
38673                 lb[lb.length] = ct.apply(p);
38674                 lsb[lsb.length] = st.apply(p);
38675             }
38676         }
38677         return [ht.apply({cells: lb.join(""), splits:lsb.join("")}),
38678                 ht.apply({cells: cb.join(""), splits:sb.join("")})];
38679     },
38680
38681     updateHeaders : function(){
38682         var html = this.renderHeaders();
38683         this.lockedHd.update(html[0]);
38684         this.mainHd.update(html[1]);
38685     },
38686
38687     /**
38688      * Focuses the specified row.
38689      * @param {Number} row The row index
38690      */
38691     focusRow : function(row)
38692     {
38693         //Roo.log('GridView.focusRow');
38694         var x = this.scroller.dom.scrollLeft;
38695         this.focusCell(row, 0, false);
38696         this.scroller.dom.scrollLeft = x;
38697     },
38698
38699     /**
38700      * Focuses the specified cell.
38701      * @param {Number} row The row index
38702      * @param {Number} col The column index
38703      * @param {Boolean} hscroll false to disable horizontal scrolling
38704      */
38705     focusCell : function(row, col, hscroll)
38706     {
38707         //Roo.log('GridView.focusCell');
38708         var el = this.ensureVisible(row, col, hscroll);
38709         this.focusEl.alignTo(el, "tl-tl");
38710         if(Roo.isGecko){
38711             this.focusEl.focus();
38712         }else{
38713             this.focusEl.focus.defer(1, this.focusEl);
38714         }
38715     },
38716
38717     /**
38718      * Scrolls the specified cell into view
38719      * @param {Number} row The row index
38720      * @param {Number} col The column index
38721      * @param {Boolean} hscroll false to disable horizontal scrolling
38722      */
38723     ensureVisible : function(row, col, hscroll)
38724     {
38725         //Roo.log('GridView.ensureVisible,' + row + ',' + col);
38726         //return null; //disable for testing.
38727         if(typeof row != "number"){
38728             row = row.rowIndex;
38729         }
38730         if(row < 0 && row >= this.ds.getCount()){
38731             return  null;
38732         }
38733         col = (col !== undefined ? col : 0);
38734         var cm = this.grid.colModel;
38735         while(cm.isHidden(col)){
38736             col++;
38737         }
38738
38739         var el = this.getCell(row, col);
38740         if(!el){
38741             return null;
38742         }
38743         var c = this.scroller.dom;
38744
38745         var ctop = parseInt(el.offsetTop, 10);
38746         var cleft = parseInt(el.offsetLeft, 10);
38747         var cbot = ctop + el.offsetHeight;
38748         var cright = cleft + el.offsetWidth;
38749         
38750         var ch = c.clientHeight - this.mainHd.dom.offsetHeight;
38751         var stop = parseInt(c.scrollTop, 10);
38752         var sleft = parseInt(c.scrollLeft, 10);
38753         var sbot = stop + ch;
38754         var sright = sleft + c.clientWidth;
38755         /*
38756         Roo.log('GridView.ensureVisible:' +
38757                 ' ctop:' + ctop +
38758                 ' c.clientHeight:' + c.clientHeight +
38759                 ' this.mainHd.dom.offsetHeight:' + this.mainHd.dom.offsetHeight +
38760                 ' stop:' + stop +
38761                 ' cbot:' + cbot +
38762                 ' sbot:' + sbot +
38763                 ' ch:' + ch  
38764                 );
38765         */
38766         if(ctop < stop){
38767             c.scrollTop = ctop;
38768             //Roo.log("set scrolltop to ctop DISABLE?");
38769         }else if(cbot > sbot){
38770             //Roo.log("set scrolltop to cbot-ch");
38771             c.scrollTop = cbot-ch;
38772         }
38773         
38774         if(hscroll !== false){
38775             if(cleft < sleft){
38776                 c.scrollLeft = cleft;
38777             }else if(cright > sright){
38778                 c.scrollLeft = cright-c.clientWidth;
38779             }
38780         }
38781          
38782         return el;
38783     },
38784
38785     updateColumns : function(){
38786         this.grid.stopEditing();
38787         var cm = this.grid.colModel, colIds = this.getColumnIds();
38788         //var totalWidth = cm.getTotalWidth();
38789         var pos = 0;
38790         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
38791             //if(cm.isHidden(i)) continue;
38792             var w = cm.getColumnWidth(i);
38793             this.css.updateRule(this.colSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
38794             this.css.updateRule(this.hdSelector+this.idToCssName(colIds[i]), "width", (w - this.borderWidth) + "px");
38795         }
38796         this.updateSplitters();
38797     },
38798
38799     generateRules : function(cm){
38800         var ruleBuf = [], rulesId = this.idToCssName(this.grid.id)+ '-cssrules';
38801         Roo.util.CSS.removeStyleSheet(rulesId);
38802         for(var i = 0, len = cm.getColumnCount(); i < len; i++){
38803             var cid = cm.getColumnId(i);
38804             var align = '';
38805             if(cm.config[i].align){
38806                 align = 'text-align:'+cm.config[i].align+';';
38807             }
38808             var hidden = '';
38809             if(cm.isHidden(i)){
38810                 hidden = 'display:none;';
38811             }
38812             var width = "width:" + (cm.getColumnWidth(i) - this.borderWidth) + "px;";
38813             ruleBuf.push(
38814                     this.colSelector, cid, " {\n", cm.config[i].css, align, width, "\n}\n",
38815                     this.hdSelector, cid, " {\n", align, width, "}\n",
38816                     this.tdSelector, cid, " {\n",hidden,"\n}\n",
38817                     this.splitSelector, cid, " {\n", hidden , "\n}\n");
38818         }
38819         return Roo.util.CSS.createStyleSheet(ruleBuf.join(""), rulesId);
38820     },
38821
38822     updateSplitters : function(){
38823         var cm = this.cm, s = this.getSplitters();
38824         if(s){ // splitters not created yet
38825             var pos = 0, locked = true;
38826             for(var i = 0, len = cm.getColumnCount(); i < len; i++){
38827                 if(cm.isHidden(i)) {
38828                     continue;
38829                 }
38830                 var w = cm.getColumnWidth(i); // make sure it's a number
38831                 if(!cm.isLocked(i) && locked){
38832                     pos = 0;
38833                     locked = false;
38834                 }
38835                 pos += w;
38836                 s[i].style.left = (pos-this.splitOffset) + "px";
38837             }
38838         }
38839     },
38840
38841     handleHiddenChange : function(colModel, colIndex, hidden){
38842         if(hidden){
38843             this.hideColumn(colIndex);
38844         }else{
38845             this.unhideColumn(colIndex);
38846         }
38847     },
38848
38849     hideColumn : function(colIndex){
38850         var cid = this.getColumnId(colIndex);
38851         this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "none");
38852         this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "none");
38853         if(Roo.isSafari){
38854             this.updateHeaders();
38855         }
38856         this.updateSplitters();
38857         this.layout();
38858     },
38859
38860     unhideColumn : function(colIndex){
38861         var cid = this.getColumnId(colIndex);
38862         this.css.updateRule(this.tdSelector+this.idToCssName(cid), "display", "");
38863         this.css.updateRule(this.splitSelector+this.idToCssName(cid), "display", "");
38864
38865         if(Roo.isSafari){
38866             this.updateHeaders();
38867         }
38868         this.updateSplitters();
38869         this.layout();
38870     },
38871
38872     insertRows : function(dm, firstRow, lastRow, isUpdate){
38873         if(firstRow == 0 && lastRow == dm.getCount()-1){
38874             this.refresh();
38875         }else{
38876             if(!isUpdate){
38877                 this.fireEvent("beforerowsinserted", this, firstRow, lastRow);
38878             }
38879             var s = this.getScrollState();
38880             var markup = this.renderRows(firstRow, lastRow);
38881             this.bufferRows(markup[0], this.getLockedTable(), firstRow);
38882             this.bufferRows(markup[1], this.getBodyTable(), firstRow);
38883             this.restoreScroll(s);
38884             if(!isUpdate){
38885                 this.fireEvent("rowsinserted", this, firstRow, lastRow);
38886                 this.syncRowHeights(firstRow, lastRow);
38887                 this.stripeRows(firstRow);
38888                 this.layout();
38889             }
38890         }
38891     },
38892
38893     bufferRows : function(markup, target, index){
38894         var before = null, trows = target.rows, tbody = target.tBodies[0];
38895         if(index < trows.length){
38896             before = trows[index];
38897         }
38898         var b = document.createElement("div");
38899         b.innerHTML = "<table><tbody>"+markup+"</tbody></table>";
38900         var rows = b.firstChild.rows;
38901         for(var i = 0, len = rows.length; i < len; i++){
38902             if(before){
38903                 tbody.insertBefore(rows[0], before);
38904             }else{
38905                 tbody.appendChild(rows[0]);
38906             }
38907         }
38908         b.innerHTML = "";
38909         b = null;
38910     },
38911
38912     deleteRows : function(dm, firstRow, lastRow){
38913         if(dm.getRowCount()<1){
38914             this.fireEvent("beforerefresh", this);
38915             this.mainBody.update("");
38916             this.lockedBody.update("");
38917             this.fireEvent("refresh", this);
38918         }else{
38919             this.fireEvent("beforerowsdeleted", this, firstRow, lastRow);
38920             var bt = this.getBodyTable();
38921             var tbody = bt.firstChild;
38922             var rows = bt.rows;
38923             for(var rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
38924                 tbody.removeChild(rows[firstRow]);
38925             }
38926             this.stripeRows(firstRow);
38927             this.fireEvent("rowsdeleted", this, firstRow, lastRow);
38928         }
38929     },
38930
38931     updateRows : function(dataSource, firstRow, lastRow){
38932         var s = this.getScrollState();
38933         this.refresh();
38934         this.restoreScroll(s);
38935     },
38936
38937     handleSort : function(dataSource, sortColumnIndex, sortDir, noRefresh){
38938         if(!noRefresh){
38939            this.refresh();
38940         }
38941         this.updateHeaderSortState();
38942     },
38943
38944     getScrollState : function(){
38945         
38946         var sb = this.scroller.dom;
38947         return {left: sb.scrollLeft, top: sb.scrollTop};
38948     },
38949
38950     stripeRows : function(startRow){
38951         if(!this.grid.stripeRows || this.ds.getCount() < 1){
38952             return;
38953         }
38954         startRow = startRow || 0;
38955         var rows = this.getBodyTable().rows;
38956         var lrows = this.getLockedTable().rows;
38957         var cls = ' x-grid-row-alt ';
38958         for(var i = startRow, len = rows.length; i < len; i++){
38959             var row = rows[i], lrow = lrows[i];
38960             var isAlt = ((i+1) % 2 == 0);
38961             var hasAlt = (' '+row.className + ' ').indexOf(cls) != -1;
38962             if(isAlt == hasAlt){
38963                 continue;
38964             }
38965             if(isAlt){
38966                 row.className += " x-grid-row-alt";
38967             }else{
38968                 row.className = row.className.replace("x-grid-row-alt", "");
38969             }
38970             if(lrow){
38971                 lrow.className = row.className;
38972             }
38973         }
38974     },
38975
38976     restoreScroll : function(state){
38977         //Roo.log('GridView.restoreScroll');
38978         var sb = this.scroller.dom;
38979         sb.scrollLeft = state.left;
38980         sb.scrollTop = state.top;
38981         this.syncScroll();
38982     },
38983
38984     syncScroll : function(){
38985         //Roo.log('GridView.syncScroll');
38986         var sb = this.scroller.dom;
38987         var sh = this.mainHd.dom;
38988         var bs = this.mainBody.dom;
38989         var lv = this.lockedBody.dom;
38990         sh.scrollLeft = bs.scrollLeft = sb.scrollLeft;
38991         lv.scrollTop = bs.scrollTop = sb.scrollTop;
38992     },
38993
38994     handleScroll : function(e){
38995         this.syncScroll();
38996         var sb = this.scroller.dom;
38997         this.grid.fireEvent("bodyscroll", sb.scrollLeft, sb.scrollTop);
38998         e.stopEvent();
38999     },
39000
39001     handleWheel : function(e){
39002         var d = e.getWheelDelta();
39003         this.scroller.dom.scrollTop -= d*22;
39004         // set this here to prevent jumpy scrolling on large tables
39005         this.lockedBody.dom.scrollTop = this.mainBody.dom.scrollTop = this.scroller.dom.scrollTop;
39006         e.stopEvent();
39007     },
39008
39009     renderRows : function(startRow, endRow){
39010         // pull in all the crap needed to render rows
39011         var g = this.grid, cm = g.colModel, ds = g.dataSource, stripe = g.stripeRows;
39012         var colCount = cm.getColumnCount();
39013
39014         if(ds.getCount() < 1){
39015             return ["", ""];
39016         }
39017
39018         // build a map for all the columns
39019         var cs = [];
39020         for(var i = 0; i < colCount; i++){
39021             var name = cm.getDataIndex(i);
39022             cs[i] = {
39023                 name : typeof name == 'undefined' ? ds.fields.get(i).name : name,
39024                 renderer : cm.getRenderer(i),
39025                 id : cm.getColumnId(i),
39026                 locked : cm.isLocked(i),
39027                 has_editor : cm.isCellEditable(i)
39028             };
39029         }
39030
39031         startRow = startRow || 0;
39032         endRow = typeof endRow == "undefined"? ds.getCount()-1 : endRow;
39033
39034         // records to render
39035         var rs = ds.getRange(startRow, endRow);
39036
39037         return this.doRender(cs, rs, ds, startRow, colCount, stripe);
39038     },
39039
39040     // As much as I hate to duplicate code, this was branched because FireFox really hates
39041     // [].join("") on strings. The performance difference was substantial enough to
39042     // branch this function
39043     doRender : Roo.isGecko ?
39044             function(cs, rs, ds, startRow, colCount, stripe){
39045                 var ts = this.templates, ct = ts.cell, rt = ts.row;
39046                 // buffers
39047                 var buf = "", lbuf = "", cb, lcb, c, p = {}, rp = {}, r, rowIndex;
39048                 
39049                 var hasListener = this.grid.hasListener('rowclass');
39050                 var rowcfg = {};
39051                 for(var j = 0, len = rs.length; j < len; j++){
39052                     r = rs[j]; cb = ""; lcb = ""; rowIndex = (j+startRow);
39053                     for(var i = 0; i < colCount; i++){
39054                         c = cs[i];
39055                         p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
39056                         p.id = c.id;
39057                         p.css = p.attr = "";
39058                         p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
39059                         if(p.value == undefined || p.value === "") {
39060                             p.value = "&#160;";
39061                         }
39062                         if(c.has_editor){
39063                             p.css += ' x-grid-editable-cell';
39064                         }
39065                         if(c.dirty && typeof r.modified[c.name] !== 'undefined'){
39066                             p.css +=  ' x-grid-dirty-cell';
39067                         }
39068                         var markup = ct.apply(p);
39069                         if(!c.locked){
39070                             cb+= markup;
39071                         }else{
39072                             lcb+= markup;
39073                         }
39074                     }
39075                     var alt = [];
39076                     if(stripe && ((rowIndex+1) % 2 == 0)){
39077                         alt.push("x-grid-row-alt")
39078                     }
39079                     if(r.dirty){
39080                         alt.push(  " x-grid-dirty-row");
39081                     }
39082                     rp.cells = lcb;
39083                     if(this.getRowClass){
39084                         alt.push(this.getRowClass(r, rowIndex));
39085                     }
39086                     if (hasListener) {
39087                         rowcfg = {
39088                              
39089                             record: r,
39090                             rowIndex : rowIndex,
39091                             rowClass : ''
39092                         };
39093                         this.grid.fireEvent('rowclass', this, rowcfg);
39094                         alt.push(rowcfg.rowClass);
39095                     }
39096                     rp.alt = alt.join(" ");
39097                     lbuf+= rt.apply(rp);
39098                     rp.cells = cb;
39099                     buf+=  rt.apply(rp);
39100                 }
39101                 return [lbuf, buf];
39102             } :
39103             function(cs, rs, ds, startRow, colCount, stripe){
39104                 var ts = this.templates, ct = ts.cell, rt = ts.row;
39105                 // buffers
39106                 var buf = [], lbuf = [], cb, lcb, c, p = {}, rp = {}, r, rowIndex;
39107                 var hasListener = this.grid.hasListener('rowclass');
39108  
39109                 var rowcfg = {};
39110                 for(var j = 0, len = rs.length; j < len; j++){
39111                     r = rs[j]; cb = []; lcb = []; rowIndex = (j+startRow);
39112                     for(var i = 0; i < colCount; i++){
39113                         c = cs[i];
39114                         p.cellId = "x-grid-cell-" + rowIndex + "-" + i;
39115                         p.id = c.id;
39116                         p.css = p.attr = "";
39117                         p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
39118                         if(p.value == undefined || p.value === "") {
39119                             p.value = "&#160;";
39120                         }
39121                         //Roo.log(c);
39122                          if(c.has_editor){
39123                             p.css += ' x-grid-editable-cell';
39124                         }
39125                         if(r.dirty && typeof r.modified[c.name] !== 'undefined'){
39126                             p.css += ' x-grid-dirty-cell' 
39127                         }
39128                         
39129                         var markup = ct.apply(p);
39130                         if(!c.locked){
39131                             cb[cb.length] = markup;
39132                         }else{
39133                             lcb[lcb.length] = markup;
39134                         }
39135                     }
39136                     var alt = [];
39137                     if(stripe && ((rowIndex+1) % 2 == 0)){
39138                         alt.push( "x-grid-row-alt");
39139                     }
39140                     if(r.dirty){
39141                         alt.push(" x-grid-dirty-row");
39142                     }
39143                     rp.cells = lcb;
39144                     if(this.getRowClass){
39145                         alt.push( this.getRowClass(r, rowIndex));
39146                     }
39147                     if (hasListener) {
39148                         rowcfg = {
39149                              
39150                             record: r,
39151                             rowIndex : rowIndex,
39152                             rowClass : ''
39153                         };
39154                         this.grid.fireEvent('rowclass', this, rowcfg);
39155                         alt.push(rowcfg.rowClass);
39156                     }
39157                     
39158                     rp.alt = alt.join(" ");
39159                     rp.cells = lcb.join("");
39160                     lbuf[lbuf.length] = rt.apply(rp);
39161                     rp.cells = cb.join("");
39162                     buf[buf.length] =  rt.apply(rp);
39163                 }
39164                 return [lbuf.join(""), buf.join("")];
39165             },
39166
39167     renderBody : function(){
39168         var markup = this.renderRows();
39169         var bt = this.templates.body;
39170         return [bt.apply({rows: markup[0]}), bt.apply({rows: markup[1]})];
39171     },
39172
39173     /**
39174      * Refreshes the grid
39175      * @param {Boolean} headersToo
39176      */
39177     refresh : function(headersToo){
39178         this.fireEvent("beforerefresh", this);
39179         this.grid.stopEditing();
39180         var result = this.renderBody();
39181         this.lockedBody.update(result[0]);
39182         this.mainBody.update(result[1]);
39183         if(headersToo === true){
39184             this.updateHeaders();
39185             this.updateColumns();
39186             this.updateSplitters();
39187             this.updateHeaderSortState();
39188         }
39189         this.syncRowHeights();
39190         this.layout();
39191         this.fireEvent("refresh", this);
39192     },
39193
39194     handleColumnMove : function(cm, oldIndex, newIndex){
39195         this.indexMap = null;
39196         var s = this.getScrollState();
39197         this.refresh(true);
39198         this.restoreScroll(s);
39199         this.afterMove(newIndex);
39200     },
39201
39202     afterMove : function(colIndex){
39203         if(this.enableMoveAnim && Roo.enableFx){
39204             this.fly(this.getHeaderCell(colIndex).firstChild).highlight(this.hlColor);
39205         }
39206         // if multisort - fix sortOrder, and reload..
39207         if (this.grid.dataSource.multiSort) {
39208             // the we can call sort again..
39209             var dm = this.grid.dataSource;
39210             var cm = this.grid.colModel;
39211             var so = [];
39212             for(var i = 0; i < cm.config.length; i++ ) {
39213                 
39214                 if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined')) {
39215                     continue; // dont' bother, it's not in sort list or being set.
39216                 }
39217                 
39218                 so.push(cm.config[i].dataIndex);
39219             };
39220             dm.sortOrder = so;
39221             dm.load(dm.lastOptions);
39222             
39223             
39224         }
39225         
39226     },
39227
39228     updateCell : function(dm, rowIndex, dataIndex){
39229         var colIndex = this.getColumnIndexByDataIndex(dataIndex);
39230         if(typeof colIndex == "undefined"){ // not present in grid
39231             return;
39232         }
39233         var cm = this.grid.colModel;
39234         var cell = this.getCell(rowIndex, colIndex);
39235         var cellText = this.getCellText(rowIndex, colIndex);
39236
39237         var p = {
39238             cellId : "x-grid-cell-" + rowIndex + "-" + colIndex,
39239             id : cm.getColumnId(colIndex),
39240             css: colIndex == cm.getColumnCount()-1 ? "x-grid-col-last" : ""
39241         };
39242         var renderer = cm.getRenderer(colIndex);
39243         var val = renderer(dm.getValueAt(rowIndex, dataIndex), p, rowIndex, colIndex, dm);
39244         if(typeof val == "undefined" || val === "") {
39245             val = "&#160;";
39246         }
39247         cellText.innerHTML = val;
39248         cell.className = this.cellClass + " " + this.idToCssName(p.cellId) + " " + p.css;
39249         this.syncRowHeights(rowIndex, rowIndex);
39250     },
39251
39252     calcColumnWidth : function(colIndex, maxRowsToMeasure){
39253         var maxWidth = 0;
39254         if(this.grid.autoSizeHeaders){
39255             var h = this.getHeaderCellMeasure(colIndex);
39256             maxWidth = Math.max(maxWidth, h.scrollWidth);
39257         }
39258         var tb, index;
39259         if(this.cm.isLocked(colIndex)){
39260             tb = this.getLockedTable();
39261             index = colIndex;
39262         }else{
39263             tb = this.getBodyTable();
39264             index = colIndex - this.cm.getLockedCount();
39265         }
39266         if(tb && tb.rows){
39267             var rows = tb.rows;
39268             var stopIndex = Math.min(maxRowsToMeasure || rows.length, rows.length);
39269             for(var i = 0; i < stopIndex; i++){
39270                 var cell = rows[i].childNodes[index].firstChild;
39271                 maxWidth = Math.max(maxWidth, cell.scrollWidth);
39272             }
39273         }
39274         return maxWidth + /*margin for error in IE*/ 5;
39275     },
39276     /**
39277      * Autofit a column to its content.
39278      * @param {Number} colIndex
39279      * @param {Boolean} forceMinSize true to force the column to go smaller if possible
39280      */
39281      autoSizeColumn : function(colIndex, forceMinSize, suppressEvent){
39282          if(this.cm.isHidden(colIndex)){
39283              return; // can't calc a hidden column
39284          }
39285         if(forceMinSize){
39286             var cid = this.cm.getColumnId(colIndex);
39287             this.css.updateRule(this.colSelector +this.idToCssName( cid), "width", this.grid.minColumnWidth + "px");
39288            if(this.grid.autoSizeHeaders){
39289                this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", this.grid.minColumnWidth + "px");
39290            }
39291         }
39292         var newWidth = this.calcColumnWidth(colIndex);
39293         this.cm.setColumnWidth(colIndex,
39294             Math.max(this.grid.minColumnWidth, newWidth), suppressEvent);
39295         if(!suppressEvent){
39296             this.grid.fireEvent("columnresize", colIndex, newWidth);
39297         }
39298     },
39299
39300     /**
39301      * Autofits all columns to their content and then expands to fit any extra space in the grid
39302      */
39303      autoSizeColumns : function(){
39304         var cm = this.grid.colModel;
39305         var colCount = cm.getColumnCount();
39306         for(var i = 0; i < colCount; i++){
39307             this.autoSizeColumn(i, true, true);
39308         }
39309         if(cm.getTotalWidth() < this.scroller.dom.clientWidth){
39310             this.fitColumns();
39311         }else{
39312             this.updateColumns();
39313             this.layout();
39314         }
39315     },
39316
39317     /**
39318      * Autofits all columns to the grid's width proportionate with their current size
39319      * @param {Boolean} reserveScrollSpace Reserve space for a scrollbar
39320      */
39321     fitColumns : function(reserveScrollSpace){
39322         var cm = this.grid.colModel;
39323         var colCount = cm.getColumnCount();
39324         var cols = [];
39325         var width = 0;
39326         var i, w;
39327         for (i = 0; i < colCount; i++){
39328             if(!cm.isHidden(i) && !cm.isFixed(i)){
39329                 w = cm.getColumnWidth(i);
39330                 cols.push(i);
39331                 cols.push(w);
39332                 width += w;
39333             }
39334         }
39335         var avail = Math.min(this.scroller.dom.clientWidth, this.el.getWidth());
39336         if(reserveScrollSpace){
39337             avail -= 17;
39338         }
39339         var frac = (avail - cm.getTotalWidth())/width;
39340         while (cols.length){
39341             w = cols.pop();
39342             i = cols.pop();
39343             cm.setColumnWidth(i, Math.floor(w + w*frac), true);
39344         }
39345         this.updateColumns();
39346         this.layout();
39347     },
39348
39349     onRowSelect : function(rowIndex){
39350         var row = this.getRowComposite(rowIndex);
39351         row.addClass("x-grid-row-selected");
39352     },
39353
39354     onRowDeselect : function(rowIndex){
39355         var row = this.getRowComposite(rowIndex);
39356         row.removeClass("x-grid-row-selected");
39357     },
39358
39359     onCellSelect : function(row, col){
39360         var cell = this.getCell(row, col);
39361         if(cell){
39362             Roo.fly(cell).addClass("x-grid-cell-selected");
39363         }
39364     },
39365
39366     onCellDeselect : function(row, col){
39367         var cell = this.getCell(row, col);
39368         if(cell){
39369             Roo.fly(cell).removeClass("x-grid-cell-selected");
39370         }
39371     },
39372
39373     updateHeaderSortState : function(){
39374         
39375         // sort state can be single { field: xxx, direction : yyy}
39376         // or   { xxx=>ASC , yyy : DESC ..... }
39377         
39378         var mstate = {};
39379         if (!this.ds.multiSort) { 
39380             var state = this.ds.getSortState();
39381             if(!state){
39382                 return;
39383             }
39384             mstate[state.field] = state.direction;
39385             // FIXME... - this is not used here.. but might be elsewhere..
39386             this.sortState = state;
39387             
39388         } else {
39389             mstate = this.ds.sortToggle;
39390         }
39391         //remove existing sort classes..
39392         
39393         var sc = this.sortClasses;
39394         var hds = this.el.select(this.headerSelector).removeClass(sc);
39395         
39396         for(var f in mstate) {
39397         
39398             var sortColumn = this.cm.findColumnIndex(f);
39399             
39400             if(sortColumn != -1){
39401                 var sortDir = mstate[f];        
39402                 hds.item(sortColumn).addClass(sc[sortDir == "DESC" ? 1 : 0]);
39403             }
39404         }
39405         
39406          
39407         
39408     },
39409
39410
39411     handleHeaderClick : function(g, index,e){
39412         
39413         Roo.log("header click");
39414         
39415         if (Roo.isTouch) {
39416             // touch events on header are handled by context
39417             this.handleHdCtx(g,index,e);
39418             return;
39419         }
39420         
39421         
39422         if(this.headersDisabled){
39423             return;
39424         }
39425         var dm = g.dataSource, cm = g.colModel;
39426         if(!cm.isSortable(index)){
39427             return;
39428         }
39429         g.stopEditing();
39430         
39431         if (dm.multiSort) {
39432             // update the sortOrder
39433             var so = [];
39434             for(var i = 0; i < cm.config.length; i++ ) {
39435                 
39436                 if ((typeof(dm.sortToggle[cm.config[i].dataIndex]) == 'undefined') && (index != i)) {
39437                     continue; // dont' bother, it's not in sort list or being set.
39438                 }
39439                 
39440                 so.push(cm.config[i].dataIndex);
39441             };
39442             dm.sortOrder = so;
39443         }
39444         
39445         
39446         dm.sort(cm.getDataIndex(index));
39447     },
39448
39449
39450     destroy : function(){
39451         if(this.colMenu){
39452             this.colMenu.removeAll();
39453             Roo.menu.MenuMgr.unregister(this.colMenu);
39454             this.colMenu.getEl().remove();
39455             delete this.colMenu;
39456         }
39457         if(this.hmenu){
39458             this.hmenu.removeAll();
39459             Roo.menu.MenuMgr.unregister(this.hmenu);
39460             this.hmenu.getEl().remove();
39461             delete this.hmenu;
39462         }
39463         if(this.grid.enableColumnMove){
39464             var dds = Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
39465             if(dds){
39466                 for(var dd in dds){
39467                     if(!dds[dd].config.isTarget && dds[dd].dragElId){
39468                         var elid = dds[dd].dragElId;
39469                         dds[dd].unreg();
39470                         Roo.get(elid).remove();
39471                     } else if(dds[dd].config.isTarget){
39472                         dds[dd].proxyTop.remove();
39473                         dds[dd].proxyBottom.remove();
39474                         dds[dd].unreg();
39475                     }
39476                     if(Roo.dd.DDM.locationCache[dd]){
39477                         delete Roo.dd.DDM.locationCache[dd];
39478                     }
39479                 }
39480                 delete Roo.dd.DDM.ids['gridHeader' + this.grid.getGridEl().id];
39481             }
39482         }
39483         Roo.util.CSS.removeStyleSheet(this.idToCssName(this.grid.id) + '-cssrules');
39484         this.bind(null, null);
39485         Roo.EventManager.removeResizeListener(this.onWindowResize, this);
39486     },
39487
39488     handleLockChange : function(){
39489         this.refresh(true);
39490     },
39491
39492     onDenyColumnLock : function(){
39493
39494     },
39495
39496     onDenyColumnHide : function(){
39497
39498     },
39499
39500     handleHdMenuClick : function(item){
39501         var index = this.hdCtxIndex;
39502         var cm = this.cm, ds = this.ds;
39503         switch(item.id){
39504             case "asc":
39505                 ds.sort(cm.getDataIndex(index), "ASC");
39506                 break;
39507             case "desc":
39508                 ds.sort(cm.getDataIndex(index), "DESC");
39509                 break;
39510             case "lock":
39511                 var lc = cm.getLockedCount();
39512                 if(cm.getColumnCount(true) <= lc+1){
39513                     this.onDenyColumnLock();
39514                     return;
39515                 }
39516                 if(lc != index){
39517                     cm.setLocked(index, true, true);
39518                     cm.moveColumn(index, lc);
39519                     this.grid.fireEvent("columnmove", index, lc);
39520                 }else{
39521                     cm.setLocked(index, true);
39522                 }
39523             break;
39524             case "unlock":
39525                 var lc = cm.getLockedCount();
39526                 if((lc-1) != index){
39527                     cm.setLocked(index, false, true);
39528                     cm.moveColumn(index, lc-1);
39529                     this.grid.fireEvent("columnmove", index, lc-1);
39530                 }else{
39531                     cm.setLocked(index, false);
39532                 }
39533             break;
39534             case 'wider': // used to expand cols on touch..
39535             case 'narrow':
39536                 var cw = cm.getColumnWidth(index);
39537                 cw += (item.id == 'wider' ? 1 : -1) * 50;
39538                 cw = Math.max(0, cw);
39539                 cw = Math.min(cw,4000);
39540                 cm.setColumnWidth(index, cw);
39541                 break;
39542                 
39543             default:
39544                 index = cm.getIndexById(item.id.substr(4));
39545                 if(index != -1){
39546                     if(item.checked && cm.getColumnCount(true) <= 1){
39547                         this.onDenyColumnHide();
39548                         return false;
39549                     }
39550                     cm.setHidden(index, item.checked);
39551                 }
39552         }
39553         return true;
39554     },
39555
39556     beforeColMenuShow : function(){
39557         var cm = this.cm,  colCount = cm.getColumnCount();
39558         this.colMenu.removeAll();
39559         
39560         var items = [];
39561         for(var i = 0; i < colCount; i++){
39562             items.push({
39563                 id: "col-"+cm.getColumnId(i),
39564                 text: cm.getColumnHeader(i),
39565                 checked: !cm.isHidden(i),
39566                 hideOnClick:false
39567             });
39568         }
39569         
39570         if (this.grid.sortColMenu) {
39571             items.sort(function(a,b) {
39572                 if (a.text == b.text) {
39573                     return 0;
39574                 }
39575                 return a.text.toUpperCase() > b.text.toUpperCase() ? 1 : -1;
39576             });
39577         }
39578         
39579         for(var i = 0; i < colCount; i++){
39580             this.colMenu.add(new Roo.menu.CheckItem(items[i]));
39581         }
39582     },
39583
39584     handleHdCtx : function(g, index, e){
39585         e.stopEvent();
39586         var hd = this.getHeaderCell(index);
39587         this.hdCtxIndex = index;
39588         var ms = this.hmenu.items, cm = this.cm;
39589         ms.get("asc").setDisabled(!cm.isSortable(index));
39590         ms.get("desc").setDisabled(!cm.isSortable(index));
39591         if(this.grid.enableColLock !== false){
39592             ms.get("lock").setDisabled(cm.isLocked(index));
39593             ms.get("unlock").setDisabled(!cm.isLocked(index));
39594         }
39595         this.hmenu.show(hd, "tl-bl");
39596     },
39597
39598     handleHdOver : function(e){
39599         var hd = this.findHeaderCell(e.getTarget());
39600         if(hd && !this.headersDisabled){
39601             if(this.grid.colModel.isSortable(this.getCellIndex(hd))){
39602                this.fly(hd).addClass("x-grid-hd-over");
39603             }
39604         }
39605     },
39606
39607     handleHdOut : function(e){
39608         var hd = this.findHeaderCell(e.getTarget());
39609         if(hd){
39610             this.fly(hd).removeClass("x-grid-hd-over");
39611         }
39612     },
39613
39614     handleSplitDblClick : function(e, t){
39615         var i = this.getCellIndex(t);
39616         if(this.grid.enableColumnResize !== false && this.cm.isResizable(i) && !this.cm.isFixed(i)){
39617             this.autoSizeColumn(i, true);
39618             this.layout();
39619         }
39620     },
39621
39622     render : function(){
39623
39624         var cm = this.cm;
39625         var colCount = cm.getColumnCount();
39626
39627         if(this.grid.monitorWindowResize === true){
39628             Roo.EventManager.onWindowResize(this.onWindowResize, this, true);
39629         }
39630         var header = this.renderHeaders();
39631         var body = this.templates.body.apply({rows:""});
39632         var html = this.templates.master.apply({
39633             lockedBody: body,
39634             body: body,
39635             lockedHeader: header[0],
39636             header: header[1]
39637         });
39638
39639         //this.updateColumns();
39640
39641         this.grid.getGridEl().dom.innerHTML = html;
39642
39643         this.initElements();
39644         
39645         // a kludge to fix the random scolling effect in webkit
39646         this.el.on("scroll", function() {
39647             this.el.dom.scrollTop=0; // hopefully not recursive..
39648         },this);
39649
39650         this.scroller.on("scroll", this.handleScroll, this);
39651         this.lockedBody.on("mousewheel", this.handleWheel, this);
39652         this.mainBody.on("mousewheel", this.handleWheel, this);
39653
39654         this.mainHd.on("mouseover", this.handleHdOver, this);
39655         this.mainHd.on("mouseout", this.handleHdOut, this);
39656         this.mainHd.on("dblclick", this.handleSplitDblClick, this,
39657                 {delegate: "."+this.splitClass});
39658
39659         this.lockedHd.on("mouseover", this.handleHdOver, this);
39660         this.lockedHd.on("mouseout", this.handleHdOut, this);
39661         this.lockedHd.on("dblclick", this.handleSplitDblClick, this,
39662                 {delegate: "."+this.splitClass});
39663
39664         if(this.grid.enableColumnResize !== false && Roo.grid.SplitDragZone){
39665             new Roo.grid.SplitDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
39666         }
39667
39668         this.updateSplitters();
39669
39670         if(this.grid.enableColumnMove && Roo.grid.HeaderDragZone){
39671             new Roo.grid.HeaderDragZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
39672             new Roo.grid.HeaderDropZone(this.grid, this.lockedHd.dom, this.mainHd.dom);
39673         }
39674
39675         if(this.grid.enableCtxMenu !== false && Roo.menu.Menu){
39676             this.hmenu = new Roo.menu.Menu({id: this.grid.id + "-hctx"});
39677             this.hmenu.add(
39678                 {id:"asc", text: this.sortAscText, cls: "xg-hmenu-sort-asc"},
39679                 {id:"desc", text: this.sortDescText, cls: "xg-hmenu-sort-desc"}
39680             );
39681             if(this.grid.enableColLock !== false){
39682                 this.hmenu.add('-',
39683                     {id:"lock", text: this.lockText, cls: "xg-hmenu-lock"},
39684                     {id:"unlock", text: this.unlockText, cls: "xg-hmenu-unlock"}
39685                 );
39686             }
39687             if (Roo.isTouch) {
39688                  this.hmenu.add('-',
39689                     {id:"wider", text: this.columnsWiderText},
39690                     {id:"narrow", text: this.columnsNarrowText }
39691                 );
39692                 
39693                  
39694             }
39695             
39696             if(this.grid.enableColumnHide !== false){
39697
39698                 this.colMenu = new Roo.menu.Menu({id:this.grid.id + "-hcols-menu"});
39699                 this.colMenu.on("beforeshow", this.beforeColMenuShow, this);
39700                 this.colMenu.on("itemclick", this.handleHdMenuClick, this);
39701
39702                 this.hmenu.add('-',
39703                     {id:"columns", text: this.columnsText, menu: this.colMenu}
39704                 );
39705             }
39706             this.hmenu.on("itemclick", this.handleHdMenuClick, this);
39707
39708             this.grid.on("headercontextmenu", this.handleHdCtx, this);
39709         }
39710
39711         if((this.grid.enableDragDrop || this.grid.enableDrag) && Roo.grid.GridDragZone){
39712             this.dd = new Roo.grid.GridDragZone(this.grid, {
39713                 ddGroup : this.grid.ddGroup || 'GridDD'
39714             });
39715             
39716         }
39717
39718         /*
39719         for(var i = 0; i < colCount; i++){
39720             if(cm.isHidden(i)){
39721                 this.hideColumn(i);
39722             }
39723             if(cm.config[i].align){
39724                 this.css.updateRule(this.colSelector + i, "textAlign", cm.config[i].align);
39725                 this.css.updateRule(this.hdSelector + i, "textAlign", cm.config[i].align);
39726             }
39727         }*/
39728         
39729         this.updateHeaderSortState();
39730
39731         this.beforeInitialResize();
39732         this.layout(true);
39733
39734         // two part rendering gives faster view to the user
39735         this.renderPhase2.defer(1, this);
39736     },
39737
39738     renderPhase2 : function(){
39739         // render the rows now
39740         this.refresh();
39741         if(this.grid.autoSizeColumns){
39742             this.autoSizeColumns();
39743         }
39744     },
39745
39746     beforeInitialResize : function(){
39747
39748     },
39749
39750     onColumnSplitterMoved : function(i, w){
39751         this.userResized = true;
39752         var cm = this.grid.colModel;
39753         cm.setColumnWidth(i, w, true);
39754         var cid = cm.getColumnId(i);
39755         this.css.updateRule(this.colSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
39756         this.css.updateRule(this.hdSelector + this.idToCssName(cid), "width", (w-this.borderWidth) + "px");
39757         this.updateSplitters();
39758         this.layout();
39759         this.grid.fireEvent("columnresize", i, w);
39760     },
39761
39762     syncRowHeights : function(startIndex, endIndex){
39763         if(this.grid.enableRowHeightSync === true && this.cm.getLockedCount() > 0){
39764             startIndex = startIndex || 0;
39765             var mrows = this.getBodyTable().rows;
39766             var lrows = this.getLockedTable().rows;
39767             var len = mrows.length-1;
39768             endIndex = Math.min(endIndex || len, len);
39769             for(var i = startIndex; i <= endIndex; i++){
39770                 var m = mrows[i], l = lrows[i];
39771                 var h = Math.max(m.offsetHeight, l.offsetHeight);
39772                 m.style.height = l.style.height = h + "px";
39773             }
39774         }
39775     },
39776
39777     layout : function(initialRender, is2ndPass)
39778     {
39779         var g = this.grid;
39780         var auto = g.autoHeight;
39781         var scrollOffset = 16;
39782         var c = g.getGridEl(), cm = this.cm,
39783                 expandCol = g.autoExpandColumn,
39784                 gv = this;
39785         //c.beginMeasure();
39786
39787         if(!c.dom.offsetWidth){ // display:none?
39788             if(initialRender){
39789                 this.lockedWrap.show();
39790                 this.mainWrap.show();
39791             }
39792             return;
39793         }
39794
39795         var hasLock = this.cm.isLocked(0);
39796
39797         var tbh = this.headerPanel.getHeight();
39798         var bbh = this.footerPanel.getHeight();
39799
39800         if(auto){
39801             var ch = this.getBodyTable().offsetHeight + tbh + bbh + this.mainHd.getHeight();
39802             var newHeight = ch + c.getBorderWidth("tb");
39803             if(g.maxHeight){
39804                 newHeight = Math.min(g.maxHeight, newHeight);
39805             }
39806             c.setHeight(newHeight);
39807         }
39808
39809         if(g.autoWidth){
39810             c.setWidth(cm.getTotalWidth()+c.getBorderWidth('lr'));
39811         }
39812
39813         var s = this.scroller;
39814
39815         var csize = c.getSize(true);
39816
39817         this.el.setSize(csize.width, csize.height);
39818
39819         this.headerPanel.setWidth(csize.width);
39820         this.footerPanel.setWidth(csize.width);
39821
39822         var hdHeight = this.mainHd.getHeight();
39823         var vw = csize.width;
39824         var vh = csize.height - (tbh + bbh);
39825
39826         s.setSize(vw, vh);
39827
39828         var bt = this.getBodyTable();
39829         
39830         if(cm.getLockedCount() == cm.config.length){
39831             bt = this.getLockedTable();
39832         }
39833         
39834         var ltWidth = hasLock ?
39835                       Math.max(this.getLockedTable().offsetWidth, this.lockedHd.dom.firstChild.offsetWidth) : 0;
39836
39837         var scrollHeight = bt.offsetHeight;
39838         var scrollWidth = ltWidth + bt.offsetWidth;
39839         var vscroll = false, hscroll = false;
39840
39841         this.scrollSizer.setSize(scrollWidth, scrollHeight+hdHeight);
39842
39843         var lw = this.lockedWrap, mw = this.mainWrap;
39844         var lb = this.lockedBody, mb = this.mainBody;
39845
39846         setTimeout(function(){
39847             var t = s.dom.offsetTop;
39848             var w = s.dom.clientWidth,
39849                 h = s.dom.clientHeight;
39850
39851             lw.setTop(t);
39852             lw.setSize(ltWidth, h);
39853
39854             mw.setLeftTop(ltWidth, t);
39855             mw.setSize(w-ltWidth, h);
39856
39857             lb.setHeight(h-hdHeight);
39858             mb.setHeight(h-hdHeight);
39859
39860             if(is2ndPass !== true && !gv.userResized && expandCol){
39861                 // high speed resize without full column calculation
39862                 
39863                 var ci = cm.getIndexById(expandCol);
39864                 if (ci < 0) {
39865                     ci = cm.findColumnIndex(expandCol);
39866                 }
39867                 ci = Math.max(0, ci); // make sure it's got at least the first col.
39868                 var expandId = cm.getColumnId(ci);
39869                 var  tw = cm.getTotalWidth(false);
39870                 var currentWidth = cm.getColumnWidth(ci);
39871                 var cw = Math.min(Math.max(((w-tw)+currentWidth-2)-/*scrollbar*/(w <= s.dom.offsetWidth ? 0 : 18), g.autoExpandMin), g.autoExpandMax);
39872                 if(currentWidth != cw){
39873                     cm.setColumnWidth(ci, cw, true);
39874                     gv.css.updateRule(gv.colSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
39875                     gv.css.updateRule(gv.hdSelector+gv.idToCssName(expandId), "width", (cw - gv.borderWidth) + "px");
39876                     gv.updateSplitters();
39877                     gv.layout(false, true);
39878                 }
39879             }
39880
39881             if(initialRender){
39882                 lw.show();
39883                 mw.show();
39884             }
39885             //c.endMeasure();
39886         }, 10);
39887     },
39888
39889     onWindowResize : function(){
39890         if(!this.grid.monitorWindowResize || this.grid.autoHeight){
39891             return;
39892         }
39893         this.layout();
39894     },
39895
39896     appendFooter : function(parentEl){
39897         return null;
39898     },
39899
39900     sortAscText : "Sort Ascending",
39901     sortDescText : "Sort Descending",
39902     lockText : "Lock Column",
39903     unlockText : "Unlock Column",
39904     columnsText : "Columns",
39905  
39906     columnsWiderText : "Wider",
39907     columnsNarrowText : "Thinner"
39908 });
39909
39910
39911 Roo.grid.GridView.ColumnDragZone = function(grid, hd){
39912     Roo.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
39913     this.proxy.el.addClass('x-grid3-col-dd');
39914 };
39915
39916 Roo.extend(Roo.grid.GridView.ColumnDragZone, Roo.grid.HeaderDragZone, {
39917     handleMouseDown : function(e){
39918
39919     },
39920
39921     callHandleMouseDown : function(e){
39922         Roo.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
39923     }
39924 });
39925 /*
39926  * Based on:
39927  * Ext JS Library 1.1.1
39928  * Copyright(c) 2006-2007, Ext JS, LLC.
39929  *
39930  * Originally Released Under LGPL - original licence link has changed is not relivant.
39931  *
39932  * Fork - LGPL
39933  * <script type="text/javascript">
39934  */
39935  /**
39936  * @extends Roo.dd.DDProxy
39937  * @class Roo.grid.SplitDragZone
39938  * Support for Column Header resizing
39939  * @constructor
39940  * @param {Object} config
39941  */
39942 // private
39943 // This is a support class used internally by the Grid components
39944 Roo.grid.SplitDragZone = function(grid, hd, hd2){
39945     this.grid = grid;
39946     this.view = grid.getView();
39947     this.proxy = this.view.resizeProxy;
39948     Roo.grid.SplitDragZone.superclass.constructor.call(
39949         this,
39950         hd, // ID
39951         "gridSplitters" + this.grid.getGridEl().id, // SGROUP
39952         {  // CONFIG
39953             dragElId : Roo.id(this.proxy.dom),
39954             resizeFrame:false
39955         }
39956     );
39957     
39958     this.setHandleElId(Roo.id(hd));
39959     if (hd2 !== false) {
39960         this.setOuterHandleElId(Roo.id(hd2));
39961     }
39962     
39963     this.scroll = false;
39964 };
39965 Roo.extend(Roo.grid.SplitDragZone, Roo.dd.DDProxy, {
39966     fly: Roo.Element.fly,
39967
39968     b4StartDrag : function(x, y){
39969         this.view.headersDisabled = true;
39970         var h = this.view.mainWrap ? this.view.mainWrap.getHeight() : (
39971                     this.view.headEl.getHeight() + this.view.bodyEl.getHeight()
39972         );
39973         this.proxy.setHeight(h);
39974         
39975         // for old system colWidth really stored the actual width?
39976         // in bootstrap we tried using xs/ms/etc.. to do % sizing?
39977         // which in reality did not work.. - it worked only for fixed sizes
39978         // for resizable we need to use actual sizes.
39979         var w = this.cm.getColumnWidth(this.cellIndex);
39980         if (!this.view.mainWrap) {
39981             // bootstrap.
39982             w = this.view.getHeaderIndex(this.cellIndex).getWidth();
39983         }
39984         
39985         
39986         
39987         // this was w-this.grid.minColumnWidth;
39988         // doesnt really make sense? - w = thie curren width or the rendered one?
39989         var minw = Math.max(w-this.grid.minColumnWidth, 0);
39990         this.resetConstraints();
39991         this.setXConstraint(minw, 1000);
39992         this.setYConstraint(0, 0);
39993         this.minX = x - minw;
39994         this.maxX = x + 1000;
39995         this.startPos = x;
39996         if (!this.view.mainWrap) { // this is Bootstrap code..
39997             this.getDragEl().style.display='block';
39998         }
39999         
40000         Roo.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
40001     },
40002
40003
40004     handleMouseDown : function(e){
40005         ev = Roo.EventObject.setEvent(e);
40006         var t = this.fly(ev.getTarget());
40007         if(t.hasClass("x-grid-split")){
40008             this.cellIndex = this.view.getCellIndex(t.dom);
40009             this.split = t.dom;
40010             this.cm = this.grid.colModel;
40011             if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
40012                 Roo.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
40013             }
40014         }
40015     },
40016
40017     endDrag : function(e){
40018         this.view.headersDisabled = false;
40019         var endX = Math.max(this.minX, Roo.lib.Event.getPageX(e));
40020         var diff = endX - this.startPos;
40021         // 
40022         var w = this.cm.getColumnWidth(this.cellIndex);
40023         if (!this.view.mainWrap) {
40024             w = 0;
40025         }
40026         this.view.onColumnSplitterMoved(this.cellIndex, w+diff);
40027     },
40028
40029     autoOffset : function(){
40030         this.setDelta(0,0);
40031     }
40032 });/*
40033  * Based on:
40034  * Ext JS Library 1.1.1
40035  * Copyright(c) 2006-2007, Ext JS, LLC.
40036  *
40037  * Originally Released Under LGPL - original licence link has changed is not relivant.
40038  *
40039  * Fork - LGPL
40040  * <script type="text/javascript">
40041  */
40042  
40043 // private
40044 // This is a support class used internally by the Grid components
40045 Roo.grid.GridDragZone = function(grid, config){
40046     this.view = grid.getView();
40047     Roo.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
40048     if(this.view.lockedBody){
40049         this.setHandleElId(Roo.id(this.view.mainBody.dom));
40050         this.setOuterHandleElId(Roo.id(this.view.lockedBody.dom));
40051     }
40052     this.scroll = false;
40053     this.grid = grid;
40054     this.ddel = document.createElement('div');
40055     this.ddel.className = 'x-grid-dd-wrap';
40056 };
40057
40058 Roo.extend(Roo.grid.GridDragZone, Roo.dd.DragZone, {
40059     ddGroup : "GridDD",
40060
40061     getDragData : function(e){
40062         var t = Roo.lib.Event.getTarget(e);
40063         var rowIndex = this.view.findRowIndex(t);
40064         var sm = this.grid.selModel;
40065             
40066         //Roo.log(rowIndex);
40067         
40068         if (sm.getSelectedCell) {
40069             // cell selection..
40070             if (!sm.getSelectedCell()) {
40071                 return false;
40072             }
40073             if (rowIndex != sm.getSelectedCell()[0]) {
40074                 return false;
40075             }
40076         
40077         }
40078         if (sm.getSelections && sm.getSelections().length < 1) {
40079             return false;
40080         }
40081         
40082         
40083         // before it used to all dragging of unseleted... - now we dont do that.
40084         if(rowIndex !== false){
40085             
40086             // if editorgrid.. 
40087             
40088             
40089             //Roo.log([ sm.getSelectedCell() ? sm.getSelectedCell()[0] : 'NO' , rowIndex ]);
40090                
40091             //if(!sm.isSelected(rowIndex) || e.hasModifier()){
40092               //  
40093             //}
40094             if (e.hasModifier()){
40095                 sm.handleMouseDown(e, t); // non modifier buttons are handled by row select.
40096             }
40097             
40098             Roo.log("getDragData");
40099             
40100             return {
40101                 grid: this.grid,
40102                 ddel: this.ddel,
40103                 rowIndex: rowIndex,
40104                 selections: sm.getSelections ? sm.getSelections() : (
40105                     sm.getSelectedCell() ? [ this.grid.ds.getAt(sm.getSelectedCell()[0]) ] : [])
40106             };
40107         }
40108         return false;
40109     },
40110     
40111     
40112     onInitDrag : function(e){
40113         var data = this.dragData;
40114         this.ddel.innerHTML = this.grid.getDragDropText();
40115         this.proxy.update(this.ddel);
40116         // fire start drag?
40117     },
40118
40119     afterRepair : function(){
40120         this.dragging = false;
40121     },
40122
40123     getRepairXY : function(e, data){
40124         return false;
40125     },
40126
40127     onEndDrag : function(data, e){
40128         // fire end drag?
40129     },
40130
40131     onValidDrop : function(dd, e, id){
40132         // fire drag drop?
40133         this.hideProxy();
40134     },
40135
40136     beforeInvalidDrop : function(e, id){
40137
40138     }
40139 });/*
40140  * Based on:
40141  * Ext JS Library 1.1.1
40142  * Copyright(c) 2006-2007, Ext JS, LLC.
40143  *
40144  * Originally Released Under LGPL - original licence link has changed is not relivant.
40145  *
40146  * Fork - LGPL
40147  * <script type="text/javascript">
40148  */
40149  
40150
40151 /**
40152  * @class Roo.grid.ColumnModel
40153  * @extends Roo.util.Observable
40154  * This is the default implementation of a ColumnModel used by the Grid. It defines
40155  * the columns in the grid.
40156  * <br>Usage:<br>
40157  <pre><code>
40158  var colModel = new Roo.grid.ColumnModel([
40159         {header: "Ticker", width: 60, sortable: true, locked: true},
40160         {header: "Company Name", width: 150, sortable: true},
40161         {header: "Market Cap.", width: 100, sortable: true},
40162         {header: "$ Sales", width: 100, sortable: true, renderer: money},
40163         {header: "Employees", width: 100, sortable: true, resizable: false}
40164  ]);
40165  </code></pre>
40166  * <p>
40167  
40168  * The config options listed for this class are options which may appear in each
40169  * individual column definition.
40170  * <br/>RooJS Fix - column id's are not sequential but use Roo.id() - fixes bugs with layouts.
40171  * @constructor
40172  * @param {Object} config An Array of column config objects. See this class's
40173  * config objects for details.
40174 */
40175 Roo.grid.ColumnModel = function(config){
40176         /**
40177      * The config passed into the constructor
40178      */
40179     this.config = []; //config;
40180     this.lookup = {};
40181
40182     // if no id, create one
40183     // if the column does not have a dataIndex mapping,
40184     // map it to the order it is in the config
40185     for(var i = 0, len = config.length; i < len; i++){
40186         this.addColumn(config[i]);
40187         
40188     }
40189
40190     /**
40191      * The width of columns which have no width specified (defaults to 100)
40192      * @type Number
40193      */
40194     this.defaultWidth = 100;
40195
40196     /**
40197      * Default sortable of columns which have no sortable specified (defaults to false)
40198      * @type Boolean
40199      */
40200     this.defaultSortable = false;
40201
40202     this.addEvents({
40203         /**
40204              * @event widthchange
40205              * Fires when the width of a column changes.
40206              * @param {ColumnModel} this
40207              * @param {Number} columnIndex The column index
40208              * @param {Number} newWidth The new width
40209              */
40210             "widthchange": true,
40211         /**
40212              * @event headerchange
40213              * Fires when the text of a header changes.
40214              * @param {ColumnModel} this
40215              * @param {Number} columnIndex The column index
40216              * @param {Number} newText The new header text
40217              */
40218             "headerchange": true,
40219         /**
40220              * @event hiddenchange
40221              * Fires when a column is hidden or "unhidden".
40222              * @param {ColumnModel} this
40223              * @param {Number} columnIndex The column index
40224              * @param {Boolean} hidden true if hidden, false otherwise
40225              */
40226             "hiddenchange": true,
40227             /**
40228          * @event columnmoved
40229          * Fires when a column is moved.
40230          * @param {ColumnModel} this
40231          * @param {Number} oldIndex
40232          * @param {Number} newIndex
40233          */
40234         "columnmoved" : true,
40235         /**
40236          * @event columlockchange
40237          * Fires when a column's locked state is changed
40238          * @param {ColumnModel} this
40239          * @param {Number} colIndex
40240          * @param {Boolean} locked true if locked
40241          */
40242         "columnlockchange" : true
40243     });
40244     Roo.grid.ColumnModel.superclass.constructor.call(this);
40245 };
40246 Roo.extend(Roo.grid.ColumnModel, Roo.util.Observable, {
40247     /**
40248      * @cfg {String} header [required] The header text to display in the Grid view.
40249      */
40250         /**
40251      * @cfg {String} xsHeader Header at Bootsrap Extra Small width (default for all)
40252      */
40253         /**
40254      * @cfg {String} smHeader Header at Bootsrap Small width
40255      */
40256         /**
40257      * @cfg {String} mdHeader Header at Bootsrap Medium width
40258      */
40259         /**
40260      * @cfg {String} lgHeader Header at Bootsrap Large width
40261      */
40262         /**
40263      * @cfg {String} xlHeader Header at Bootsrap extra Large width
40264      */
40265     /**
40266      * @cfg {String} dataIndex  The name of the field in the grid's {@link Roo.data.Store}'s
40267      * {@link Roo.data.Record} definition from which to draw the column's value. If not
40268      * specified, the column's index is used as an index into the Record's data Array.
40269      */
40270     /**
40271      * @cfg {Number} width  The initial width in pixels of the column. Using this
40272      * instead of {@link Roo.grid.Grid#autoSizeColumns} is more efficient.
40273      */
40274     /**
40275      * @cfg {Boolean} sortable True if sorting is to be allowed on this column.
40276      * Defaults to the value of the {@link #defaultSortable} property.
40277      * Whether local/remote sorting is used is specified in {@link Roo.data.Store#remoteSort}.
40278      */
40279     /**
40280      * @cfg {Boolean} locked  True to lock the column in place while scrolling the Grid.  Defaults to false.
40281      */
40282     /**
40283      * @cfg {Boolean} fixed  True if the column width cannot be changed.  Defaults to false.
40284      */
40285     /**
40286      * @cfg {Boolean} resizable  False to disable column resizing. Defaults to true.
40287      */
40288     /**
40289      * @cfg {Boolean} hidden  True to hide the column. Defaults to false.
40290      */
40291     /**
40292      * @cfg {Function} renderer A function used to generate HTML markup for a cell
40293      * given the cell's data value. See {@link #setRenderer}. If not specified, the
40294      * default renderer returns the escaped data value. If an object is returned (bootstrap only)
40295      * then it is treated as a Roo Component object instance, and it is rendered after the initial row is rendered
40296      */
40297        /**
40298      * @cfg {Roo.grid.GridEditor} editor  For grid editors - returns the grid editor 
40299      */
40300     /**
40301      * @cfg {String} align (left|right) Set the CSS text-align property of the column.  Defaults to undefined (left).
40302      */
40303     /**
40304      * @cfg {String} valign (top|bottom|middle) Set the CSS vertical-align property of the column (eg. middle, top, bottom etc).  Defaults to undefined (middle)
40305      */
40306     /**
40307      * @cfg {String} cursor ( auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|e-resize|n-resize|ne-resize|nw-resize|s-resize|se-resize|sw-resize|w-resize|ew-resize|ns-resize|nesw-resize|nwse-resize|col-resize|row-resize|all-scroll|zoom-in|zoom-out|grab|grabbing)
40308      */
40309     /**
40310      * @cfg {String} tooltip mouse over tooltip text
40311      */
40312     /**
40313      * @cfg {Number} xs  can be '0' for hidden at this size (number less than 12)
40314      */
40315     /**
40316      * @cfg {Number} sm can be '0' for hidden at this size (number less than 12)
40317      */
40318     /**
40319      * @cfg {Number} md can be '0' for hidden at this size (number less than 12)
40320      */
40321     /**
40322      * @cfg {Number} lg   can be '0' for hidden at this size (number less than 12)
40323      */
40324         /**
40325      * @cfg {Number} xl   can be '0' for hidden at this size (number less than 12)
40326      */
40327     /**
40328      * Returns the id of the column at the specified index.
40329      * @param {Number} index The column index
40330      * @return {String} the id
40331      */
40332     getColumnId : function(index){
40333         return this.config[index].id;
40334     },
40335
40336     /**
40337      * Returns the column for a specified id.
40338      * @param {String} id The column id
40339      * @return {Object} the column
40340      */
40341     getColumnById : function(id){
40342         return this.lookup[id];
40343     },
40344
40345     
40346     /**
40347      * Returns the column Object for a specified dataIndex.
40348      * @param {String} dataIndex The column dataIndex
40349      * @return {Object|Boolean} the column or false if not found
40350      */
40351     getColumnByDataIndex: function(dataIndex){
40352         var index = this.findColumnIndex(dataIndex);
40353         return index > -1 ? this.config[index] : false;
40354     },
40355     
40356     /**
40357      * Returns the index for a specified column id.
40358      * @param {String} id The column id
40359      * @return {Number} the index, or -1 if not found
40360      */
40361     getIndexById : function(id){
40362         for(var i = 0, len = this.config.length; i < len; i++){
40363             if(this.config[i].id == id){
40364                 return i;
40365             }
40366         }
40367         return -1;
40368     },
40369     
40370     /**
40371      * Returns the index for a specified column dataIndex.
40372      * @param {String} dataIndex The column dataIndex
40373      * @return {Number} the index, or -1 if not found
40374      */
40375     
40376     findColumnIndex : function(dataIndex){
40377         for(var i = 0, len = this.config.length; i < len; i++){
40378             if(this.config[i].dataIndex == dataIndex){
40379                 return i;
40380             }
40381         }
40382         return -1;
40383     },
40384     
40385     
40386     moveColumn : function(oldIndex, newIndex){
40387         var c = this.config[oldIndex];
40388         this.config.splice(oldIndex, 1);
40389         this.config.splice(newIndex, 0, c);
40390         this.dataMap = null;
40391         this.fireEvent("columnmoved", this, oldIndex, newIndex);
40392     },
40393
40394     isLocked : function(colIndex){
40395         return this.config[colIndex].locked === true;
40396     },
40397
40398     setLocked : function(colIndex, value, suppressEvent){
40399         if(this.isLocked(colIndex) == value){
40400             return;
40401         }
40402         this.config[colIndex].locked = value;
40403         if(!suppressEvent){
40404             this.fireEvent("columnlockchange", this, colIndex, value);
40405         }
40406     },
40407
40408     getTotalLockedWidth : function(){
40409         var totalWidth = 0;
40410         for(var i = 0; i < this.config.length; i++){
40411             if(this.isLocked(i) && !this.isHidden(i)){
40412                 this.totalWidth += this.getColumnWidth(i);
40413             }
40414         }
40415         return totalWidth;
40416     },
40417
40418     getLockedCount : function(){
40419         for(var i = 0, len = this.config.length; i < len; i++){
40420             if(!this.isLocked(i)){
40421                 return i;
40422             }
40423         }
40424         
40425         return this.config.length;
40426     },
40427
40428     /**
40429      * Returns the number of columns.
40430      * @return {Number}
40431      */
40432     getColumnCount : function(visibleOnly){
40433         if(visibleOnly === true){
40434             var c = 0;
40435             for(var i = 0, len = this.config.length; i < len; i++){
40436                 if(!this.isHidden(i)){
40437                     c++;
40438                 }
40439             }
40440             return c;
40441         }
40442         return this.config.length;
40443     },
40444
40445     /**
40446      * Returns the column configs that return true by the passed function that is called with (columnConfig, index)
40447      * @param {Function} fn
40448      * @param {Object} scope (optional)
40449      * @return {Array} result
40450      */
40451     getColumnsBy : function(fn, scope){
40452         var r = [];
40453         for(var i = 0, len = this.config.length; i < len; i++){
40454             var c = this.config[i];
40455             if(fn.call(scope||this, c, i) === true){
40456                 r[r.length] = c;
40457             }
40458         }
40459         return r;
40460     },
40461
40462     /**
40463      * Returns true if the specified column is sortable.
40464      * @param {Number} col The column index
40465      * @return {Boolean}
40466      */
40467     isSortable : function(col){
40468         if(typeof this.config[col].sortable == "undefined"){
40469             return this.defaultSortable;
40470         }
40471         return this.config[col].sortable;
40472     },
40473
40474     /**
40475      * Returns the rendering (formatting) function defined for the column.
40476      * @param {Number} col The column index.
40477      * @return {Function} The function used to render the cell. See {@link #setRenderer}.
40478      */
40479     getRenderer : function(col){
40480         if(!this.config[col].renderer){
40481             return Roo.grid.ColumnModel.defaultRenderer;
40482         }
40483         return this.config[col].renderer;
40484     },
40485
40486     /**
40487      * Sets the rendering (formatting) function for a column.
40488      * @param {Number} col The column index
40489      * @param {Function} fn The function to use to process the cell's raw data
40490      * to return HTML markup for the grid view. The render function is called with
40491      * the following parameters:<ul>
40492      * <li>Data value.</li>
40493      * <li>Cell metadata. An object in which you may set the following attributes:<ul>
40494      * <li>css A CSS style string to apply to the table cell.</li>
40495      * <li>attr An HTML attribute definition string to apply to the data container element <i>within</i> the table cell.</li></ul>
40496      * <li>The {@link Roo.data.Record} from which the data was extracted.</li>
40497      * <li>Row index</li>
40498      * <li>Column index</li>
40499      * <li>The {@link Roo.data.Store} object from which the Record was extracted</li></ul>
40500      */
40501     setRenderer : function(col, fn){
40502         this.config[col].renderer = fn;
40503     },
40504
40505     /**
40506      * Returns the width for the specified column.
40507      * @param {Number} col The column index
40508      * @param (optional) {String} gridSize bootstrap width size.
40509      * @return {Number}
40510      */
40511     getColumnWidth : function(col, gridSize)
40512         {
40513                 var cfg = this.config[col];
40514                 
40515                 if (typeof(gridSize) == 'undefined') {
40516                         return cfg.width * 1 || this.defaultWidth;
40517                 }
40518                 if (gridSize === false) { // if we set it..
40519                         return cfg.width || false;
40520                 }
40521                 var sizes = ['xl', 'lg', 'md', 'sm', 'xs'];
40522                 
40523                 for(var i = sizes.indexOf(gridSize); i < sizes.length; i++) {
40524                         if (typeof(cfg[ sizes[i] ] ) == 'undefined') {
40525                                 continue;
40526                         }
40527                         return cfg[ sizes[i] ];
40528                 }
40529                 return 1;
40530                 
40531     },
40532
40533     /**
40534      * Sets the width for a column.
40535      * @param {Number} col The column index
40536      * @param {Number} width The new width
40537      */
40538     setColumnWidth : function(col, width, suppressEvent){
40539         this.config[col].width = width;
40540         this.totalWidth = null;
40541         if(!suppressEvent){
40542              this.fireEvent("widthchange", this, col, width);
40543         }
40544     },
40545
40546     /**
40547      * Returns the total width of all columns.
40548      * @param {Boolean} includeHidden True to include hidden column widths
40549      * @return {Number}
40550      */
40551     getTotalWidth : function(includeHidden){
40552         if(!this.totalWidth){
40553             this.totalWidth = 0;
40554             for(var i = 0, len = this.config.length; i < len; i++){
40555                 if(includeHidden || !this.isHidden(i)){
40556                     this.totalWidth += this.getColumnWidth(i);
40557                 }
40558             }
40559         }
40560         return this.totalWidth;
40561     },
40562
40563     /**
40564      * Returns the header for the specified column.
40565      * @param {Number} col The column index
40566      * @return {String}
40567      */
40568     getColumnHeader : function(col){
40569         return this.config[col].header;
40570     },
40571
40572     /**
40573      * Sets the header for a column.
40574      * @param {Number} col The column index
40575      * @param {String} header The new header
40576      */
40577     setColumnHeader : function(col, header){
40578         this.config[col].header = header;
40579         this.fireEvent("headerchange", this, col, header);
40580     },
40581
40582     /**
40583      * Returns the tooltip for the specified column.
40584      * @param {Number} col The column index
40585      * @return {String}
40586      */
40587     getColumnTooltip : function(col){
40588             return this.config[col].tooltip;
40589     },
40590     /**
40591      * Sets the tooltip for a column.
40592      * @param {Number} col The column index
40593      * @param {String} tooltip The new tooltip
40594      */
40595     setColumnTooltip : function(col, tooltip){
40596             this.config[col].tooltip = tooltip;
40597     },
40598
40599     /**
40600      * Returns the dataIndex for the specified column.
40601      * @param {Number} col The column index
40602      * @return {Number}
40603      */
40604     getDataIndex : function(col){
40605         return this.config[col].dataIndex;
40606     },
40607
40608     /**
40609      * Sets the dataIndex for a column.
40610      * @param {Number} col The column index
40611      * @param {Number} dataIndex The new dataIndex
40612      */
40613     setDataIndex : function(col, dataIndex){
40614         this.config[col].dataIndex = dataIndex;
40615     },
40616
40617     
40618     
40619     /**
40620      * Returns true if the cell is editable.
40621      * @param {Number} colIndex The column index
40622      * @param {Number} rowIndex The row index - this is nto actually used..?
40623      * @return {Boolean}
40624      */
40625     isCellEditable : function(colIndex, rowIndex){
40626         return (this.config[colIndex].editable || (typeof this.config[colIndex].editable == "undefined" && this.config[colIndex].editor)) ? true : false;
40627     },
40628
40629     /**
40630      * Returns the editor defined for the cell/column.
40631      * return false or null to disable editing.
40632      * @param {Number} colIndex The column index
40633      * @param {Number} rowIndex The row index
40634      * @return {Object}
40635      */
40636     getCellEditor : function(colIndex, rowIndex){
40637         return this.config[colIndex].editor;
40638     },
40639
40640     /**
40641      * Sets if a column is editable.
40642      * @param {Number} col The column index
40643      * @param {Boolean} editable True if the column is editable
40644      */
40645     setEditable : function(col, editable){
40646         this.config[col].editable = editable;
40647     },
40648
40649
40650     /**
40651      * Returns true if the column is hidden.
40652      * @param {Number} colIndex The column index
40653      * @return {Boolean}
40654      */
40655     isHidden : function(colIndex){
40656         return this.config[colIndex].hidden;
40657     },
40658
40659
40660     /**
40661      * Returns true if the column width cannot be changed
40662      */
40663     isFixed : function(colIndex){
40664         return this.config[colIndex].fixed;
40665     },
40666
40667     /**
40668      * Returns true if the column can be resized
40669      * @return {Boolean}
40670      */
40671     isResizable : function(colIndex){
40672         return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
40673     },
40674     /**
40675      * Sets if a column is hidden.
40676      * @param {Number} colIndex The column index
40677      * @param {Boolean} hidden True if the column is hidden
40678      */
40679     setHidden : function(colIndex, hidden){
40680         this.config[colIndex].hidden = hidden;
40681         this.totalWidth = null;
40682         this.fireEvent("hiddenchange", this, colIndex, hidden);
40683     },
40684
40685     /**
40686      * Sets the editor for a column.
40687      * @param {Number} col The column index
40688      * @param {Object} editor The editor object
40689      */
40690     setEditor : function(col, editor){
40691         this.config[col].editor = editor;
40692     },
40693     /**
40694      * Add a column (experimental...) - defaults to adding to the end..
40695      * @param {Object} config 
40696     */
40697     addColumn : function(c)
40698     {
40699     
40700         var i = this.config.length;
40701         this.config[i] = c;
40702         
40703         if(typeof c.dataIndex == "undefined"){
40704             c.dataIndex = i;
40705         }
40706         if(typeof c.renderer == "string"){
40707             c.renderer = Roo.util.Format[c.renderer];
40708         }
40709         if(typeof c.id == "undefined"){
40710             c.id = Roo.id();
40711         }
40712         if(c.editor && c.editor.xtype){
40713             c.editor  = Roo.factory(c.editor, Roo.grid);
40714         }
40715         if(c.editor && c.editor.isFormField){
40716             c.editor = new Roo.grid.GridEditor(c.editor);
40717         }
40718         this.lookup[c.id] = c;
40719     }
40720     
40721 });
40722
40723 Roo.grid.ColumnModel.defaultRenderer = function(value)
40724 {
40725     if(typeof value == "object") {
40726         return value;
40727     }
40728         if(typeof value == "string" && value.length < 1){
40729             return "&#160;";
40730         }
40731     
40732         return String.format("{0}", value);
40733 };
40734
40735 // Alias for backwards compatibility
40736 Roo.grid.DefaultColumnModel = Roo.grid.ColumnModel;
40737 /*
40738  * Based on:
40739  * Ext JS Library 1.1.1
40740  * Copyright(c) 2006-2007, Ext JS, LLC.
40741  *
40742  * Originally Released Under LGPL - original licence link has changed is not relivant.
40743  *
40744  * Fork - LGPL
40745  * <script type="text/javascript">
40746  */
40747
40748 /**
40749  * @class Roo.grid.AbstractSelectionModel
40750  * @extends Roo.util.Observable
40751  * @abstract
40752  * Abstract base class for grid SelectionModels.  It provides the interface that should be
40753  * implemented by descendant classes.  This class should not be directly instantiated.
40754  * @constructor
40755  */
40756 Roo.grid.AbstractSelectionModel = function(){
40757     this.locked = false;
40758     Roo.grid.AbstractSelectionModel.superclass.constructor.call(this);
40759 };
40760
40761 Roo.extend(Roo.grid.AbstractSelectionModel, Roo.util.Observable,  {
40762     /** @ignore Called by the grid automatically. Do not call directly. */
40763     init : function(grid){
40764         this.grid = grid;
40765         this.initEvents();
40766     },
40767
40768     /**
40769      * Locks the selections.
40770      */
40771     lock : function(){
40772         this.locked = true;
40773     },
40774
40775     /**
40776      * Unlocks the selections.
40777      */
40778     unlock : function(){
40779         this.locked = false;
40780     },
40781
40782     /**
40783      * Returns true if the selections are locked.
40784      * @return {Boolean}
40785      */
40786     isLocked : function(){
40787         return this.locked;
40788     }
40789 });/*
40790  * Based on:
40791  * Ext JS Library 1.1.1
40792  * Copyright(c) 2006-2007, Ext JS, LLC.
40793  *
40794  * Originally Released Under LGPL - original licence link has changed is not relivant.
40795  *
40796  * Fork - LGPL
40797  * <script type="text/javascript">
40798  */
40799 /**
40800  * @extends Roo.grid.AbstractSelectionModel
40801  * @class Roo.grid.RowSelectionModel
40802  * The default SelectionModel used by {@link Roo.grid.Grid}.
40803  * It supports multiple selections and keyboard selection/navigation. 
40804  * @constructor
40805  * @param {Object} config
40806  */
40807 Roo.grid.RowSelectionModel = function(config){
40808     Roo.apply(this, config);
40809     this.selections = new Roo.util.MixedCollection(false, function(o){
40810         return o.id;
40811     });
40812
40813     this.last = false;
40814     this.lastActive = false;
40815
40816     this.addEvents({
40817         /**
40818         * @event selectionchange
40819         * Fires when the selection changes
40820         * @param {SelectionModel} this
40821         */
40822        "selectionchange" : true,
40823        /**
40824         * @event afterselectionchange
40825         * Fires after the selection changes (eg. by key press or clicking)
40826         * @param {SelectionModel} this
40827         */
40828        "afterselectionchange" : true,
40829        /**
40830         * @event beforerowselect
40831         * Fires when a row is selected being selected, return false to cancel.
40832         * @param {SelectionModel} this
40833         * @param {Number} rowIndex The selected index
40834         * @param {Boolean} keepExisting False if other selections will be cleared
40835         */
40836        "beforerowselect" : true,
40837        /**
40838         * @event rowselect
40839         * Fires when a row is selected.
40840         * @param {SelectionModel} this
40841         * @param {Number} rowIndex The selected index
40842         * @param {Roo.data.Record} r The record
40843         */
40844        "rowselect" : true,
40845        /**
40846         * @event rowdeselect
40847         * Fires when a row is deselected.
40848         * @param {SelectionModel} this
40849         * @param {Number} rowIndex The selected index
40850         */
40851         "rowdeselect" : true
40852     });
40853     Roo.grid.RowSelectionModel.superclass.constructor.call(this);
40854     this.locked = false;
40855 };
40856
40857 Roo.extend(Roo.grid.RowSelectionModel, Roo.grid.AbstractSelectionModel,  {
40858     /**
40859      * @cfg {Boolean} singleSelect
40860      * True to allow selection of only one row at a time (defaults to false)
40861      */
40862     singleSelect : false,
40863
40864     // private
40865     initEvents : function(){
40866
40867         if(!this.grid.enableDragDrop && !this.grid.enableDrag){
40868             this.grid.on("mousedown", this.handleMouseDown, this);
40869         }else{ // allow click to work like normal
40870             this.grid.on("rowclick", this.handleDragableRowClick, this);
40871         }
40872         // bootstrap does not have a view..
40873         var view = this.grid.view ? this.grid.view : this.grid;
40874         this.rowNav = new Roo.KeyNav(this.grid.getGridEl(), {
40875             "up" : function(e){
40876                 if(!e.shiftKey){
40877                     this.selectPrevious(e.shiftKey);
40878                 }else if(this.last !== false && this.lastActive !== false){
40879                     var last = this.last;
40880                     this.selectRange(this.last,  this.lastActive-1);
40881                     view.focusRow(this.lastActive);
40882                     if(last !== false){
40883                         this.last = last;
40884                     }
40885                 }else{
40886                     this.selectFirstRow();
40887                 }
40888                 this.fireEvent("afterselectionchange", this);
40889             },
40890             "down" : function(e){
40891                 if(!e.shiftKey){
40892                     this.selectNext(e.shiftKey);
40893                 }else if(this.last !== false && this.lastActive !== false){
40894                     var last = this.last;
40895                     this.selectRange(this.last,  this.lastActive+1);
40896                     view.focusRow(this.lastActive);
40897                     if(last !== false){
40898                         this.last = last;
40899                     }
40900                 }else{
40901                     this.selectFirstRow();
40902                 }
40903                 this.fireEvent("afterselectionchange", this);
40904             },
40905             scope: this
40906         });
40907
40908          
40909         view.on("refresh", this.onRefresh, this);
40910         view.on("rowupdated", this.onRowUpdated, this);
40911         view.on("rowremoved", this.onRemove, this);
40912     },
40913
40914     // private
40915     onRefresh : function(){
40916         var ds = this.grid.ds, i, v = this.grid.view;
40917         var s = this.selections;
40918         s.each(function(r){
40919             if((i = ds.indexOfId(r.id)) != -1){
40920                 v.onRowSelect(i);
40921                 s.add(ds.getAt(i)); // updating the selection relate data
40922             }else{
40923                 s.remove(r);
40924             }
40925         });
40926     },
40927
40928     // private
40929     onRemove : function(v, index, r){
40930         this.selections.remove(r);
40931     },
40932
40933     // private
40934     onRowUpdated : function(v, index, r){
40935         if(this.isSelected(r)){
40936             v.onRowSelect(index);
40937         }
40938     },
40939
40940     /**
40941      * Select records.
40942      * @param {Array} records The records to select
40943      * @param {Boolean} keepExisting (optional) True to keep existing selections
40944      */
40945     selectRecords : function(records, keepExisting){
40946         if(!keepExisting){
40947             this.clearSelections();
40948         }
40949         var ds = this.grid.ds;
40950         for(var i = 0, len = records.length; i < len; i++){
40951             this.selectRow(ds.indexOf(records[i]), true);
40952         }
40953     },
40954
40955     /**
40956      * Gets the number of selected rows.
40957      * @return {Number}
40958      */
40959     getCount : function(){
40960         return this.selections.length;
40961     },
40962
40963     /**
40964      * Selects the first row in the grid.
40965      */
40966     selectFirstRow : function(){
40967         this.selectRow(0);
40968     },
40969
40970     /**
40971      * Select the last row.
40972      * @param {Boolean} keepExisting (optional) True to keep existing selections
40973      */
40974     selectLastRow : function(keepExisting){
40975         this.selectRow(this.grid.ds.getCount() - 1, keepExisting);
40976     },
40977
40978     /**
40979      * Selects the row immediately following the last selected row.
40980      * @param {Boolean} keepExisting (optional) True to keep existing selections
40981      */
40982     selectNext : function(keepExisting){
40983         if(this.last !== false && (this.last+1) < this.grid.ds.getCount()){
40984             this.selectRow(this.last+1, keepExisting);
40985             var view = this.grid.view ? this.grid.view : this.grid;
40986             view.focusRow(this.last);
40987         }
40988     },
40989
40990     /**
40991      * Selects the row that precedes the last selected row.
40992      * @param {Boolean} keepExisting (optional) True to keep existing selections
40993      */
40994     selectPrevious : function(keepExisting){
40995         if(this.last){
40996             this.selectRow(this.last-1, keepExisting);
40997             var view = this.grid.view ? this.grid.view : this.grid;
40998             view.focusRow(this.last);
40999         }
41000     },
41001
41002     /**
41003      * Returns the selected records
41004      * @return {Array} Array of selected records
41005      */
41006     getSelections : function(){
41007         return [].concat(this.selections.items);
41008     },
41009
41010     /**
41011      * Returns the first selected record.
41012      * @return {Record}
41013      */
41014     getSelected : function(){
41015         return this.selections.itemAt(0);
41016     },
41017
41018
41019     /**
41020      * Clears all selections.
41021      */
41022     clearSelections : function(fast){
41023         if(this.locked) {
41024             return;
41025         }
41026         if(fast !== true){
41027             var ds = this.grid.ds;
41028             var s = this.selections;
41029             s.each(function(r){
41030                 this.deselectRow(ds.indexOfId(r.id));
41031             }, this);
41032             s.clear();
41033         }else{
41034             this.selections.clear();
41035         }
41036         this.last = false;
41037     },
41038
41039
41040     /**
41041      * Selects all rows.
41042      */
41043     selectAll : function(){
41044         if(this.locked) {
41045             return;
41046         }
41047         this.selections.clear();
41048         for(var i = 0, len = this.grid.ds.getCount(); i < len; i++){
41049             this.selectRow(i, true);
41050         }
41051     },
41052
41053     /**
41054      * Returns True if there is a selection.
41055      * @return {Boolean}
41056      */
41057     hasSelection : function(){
41058         return this.selections.length > 0;
41059     },
41060
41061     /**
41062      * Returns True if the specified row is selected.
41063      * @param {Number/Record} record The record or index of the record to check
41064      * @return {Boolean}
41065      */
41066     isSelected : function(index){
41067         var r = typeof index == "number" ? this.grid.ds.getAt(index) : index;
41068         return (r && this.selections.key(r.id) ? true : false);
41069     },
41070
41071     /**
41072      * Returns True if the specified record id is selected.
41073      * @param {String} id The id of record to check
41074      * @return {Boolean}
41075      */
41076     isIdSelected : function(id){
41077         return (this.selections.key(id) ? true : false);
41078     },
41079
41080     // private
41081     handleMouseDown : function(e, t)
41082     {
41083         var view = this.grid.view ? this.grid.view : this.grid;
41084         var rowIndex;
41085         if(this.isLocked() || (rowIndex = view.findRowIndex(t)) === false){
41086             return;
41087         };
41088         if(e.shiftKey && this.last !== false){
41089             var last = this.last;
41090             this.selectRange(last, rowIndex, e.ctrlKey);
41091             this.last = last; // reset the last
41092             view.focusRow(rowIndex);
41093         }else{
41094             var isSelected = this.isSelected(rowIndex);
41095             if(e.button !== 0 && isSelected){
41096                 view.focusRow(rowIndex);
41097             }else if(e.ctrlKey && isSelected){
41098                 this.deselectRow(rowIndex);
41099             }else if(!isSelected){
41100                 this.selectRow(rowIndex, e.button === 0 && (e.ctrlKey || e.shiftKey));
41101                 view.focusRow(rowIndex);
41102             }
41103         }
41104         this.fireEvent("afterselectionchange", this);
41105     },
41106     // private
41107     handleDragableRowClick :  function(grid, rowIndex, e) 
41108     {
41109         if(e.button === 0 && !e.shiftKey && !e.ctrlKey) {
41110             this.selectRow(rowIndex, false);
41111             var view = this.grid.view ? this.grid.view : this.grid;
41112             view.focusRow(rowIndex);
41113              this.fireEvent("afterselectionchange", this);
41114         }
41115     },
41116     
41117     /**
41118      * Selects multiple rows.
41119      * @param {Array} rows Array of the indexes of the row to select
41120      * @param {Boolean} keepExisting (optional) True to keep existing selections
41121      */
41122     selectRows : function(rows, keepExisting){
41123         if(!keepExisting){
41124             this.clearSelections();
41125         }
41126         for(var i = 0, len = rows.length; i < len; i++){
41127             this.selectRow(rows[i], true);
41128         }
41129     },
41130
41131     /**
41132      * Selects a range of rows. All rows in between startRow and endRow are also selected.
41133      * @param {Number} startRow The index of the first row in the range
41134      * @param {Number} endRow The index of the last row in the range
41135      * @param {Boolean} keepExisting (optional) True to retain existing selections
41136      */
41137     selectRange : function(startRow, endRow, keepExisting){
41138         if(this.locked) {
41139             return;
41140         }
41141         if(!keepExisting){
41142             this.clearSelections();
41143         }
41144         if(startRow <= endRow){
41145             for(var i = startRow; i <= endRow; i++){
41146                 this.selectRow(i, true);
41147             }
41148         }else{
41149             for(var i = startRow; i >= endRow; i--){
41150                 this.selectRow(i, true);
41151             }
41152         }
41153     },
41154
41155     /**
41156      * Deselects a range of rows. All rows in between startRow and endRow are also deselected.
41157      * @param {Number} startRow The index of the first row in the range
41158      * @param {Number} endRow The index of the last row in the range
41159      */
41160     deselectRange : function(startRow, endRow, preventViewNotify){
41161         if(this.locked) {
41162             return;
41163         }
41164         for(var i = startRow; i <= endRow; i++){
41165             this.deselectRow(i, preventViewNotify);
41166         }
41167     },
41168
41169     /**
41170      * Selects a row.
41171      * @param {Number} row The index of the row to select
41172      * @param {Boolean} keepExisting (optional) True to keep existing selections
41173      */
41174     selectRow : function(index, keepExisting, preventViewNotify){
41175         if(this.locked || (index < 0 || index >= this.grid.ds.getCount())) {
41176             return;
41177         }
41178         if(this.fireEvent("beforerowselect", this, index, keepExisting) !== false){
41179             if(!keepExisting || this.singleSelect){
41180                 this.clearSelections();
41181             }
41182             var r = this.grid.ds.getAt(index);
41183             this.selections.add(r);
41184             this.last = this.lastActive = index;
41185             if(!preventViewNotify){
41186                 var view = this.grid.view ? this.grid.view : this.grid;
41187                 view.onRowSelect(index);
41188             }
41189             this.fireEvent("rowselect", this, index, r);
41190             this.fireEvent("selectionchange", this);
41191         }
41192     },
41193
41194     /**
41195      * Deselects a row.
41196      * @param {Number} row The index of the row to deselect
41197      */
41198     deselectRow : function(index, preventViewNotify){
41199         if(this.locked) {
41200             return;
41201         }
41202         if(this.last == index){
41203             this.last = false;
41204         }
41205         if(this.lastActive == index){
41206             this.lastActive = false;
41207         }
41208         var r = this.grid.ds.getAt(index);
41209         this.selections.remove(r);
41210         if(!preventViewNotify){
41211             var view = this.grid.view ? this.grid.view : this.grid;
41212             view.onRowDeselect(index);
41213         }
41214         this.fireEvent("rowdeselect", this, index);
41215         this.fireEvent("selectionchange", this);
41216     },
41217
41218     // private
41219     restoreLast : function(){
41220         if(this._last){
41221             this.last = this._last;
41222         }
41223     },
41224
41225     // private
41226     acceptsNav : function(row, col, cm){
41227         return !cm.isHidden(col) && cm.isCellEditable(col, row);
41228     },
41229
41230     // private
41231     onEditorKey : function(field, e){
41232         var k = e.getKey(), newCell, g = this.grid, ed = g.activeEditor;
41233         if(k == e.TAB){
41234             e.stopEvent();
41235             ed.completeEdit();
41236             if(e.shiftKey){
41237                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
41238             }else{
41239                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
41240             }
41241         }else if(k == e.ENTER && !e.ctrlKey){
41242             e.stopEvent();
41243             ed.completeEdit();
41244             if(e.shiftKey){
41245                 newCell = g.walkCells(ed.row-1, ed.col, -1, this.acceptsNav, this);
41246             }else{
41247                 newCell = g.walkCells(ed.row+1, ed.col, 1, this.acceptsNav, this);
41248             }
41249         }else if(k == e.ESC){
41250             ed.cancelEdit();
41251         }
41252         if(newCell){
41253             g.startEditing(newCell[0], newCell[1]);
41254         }
41255     }
41256 });/*
41257  * Based on:
41258  * Ext JS Library 1.1.1
41259  * Copyright(c) 2006-2007, Ext JS, LLC.
41260  *
41261  * Originally Released Under LGPL - original licence link has changed is not relivant.
41262  *
41263  * Fork - LGPL
41264  * <script type="text/javascript">
41265  */
41266 /**
41267  * @class Roo.grid.CellSelectionModel
41268  * @extends Roo.grid.AbstractSelectionModel
41269  * This class provides the basic implementation for cell selection in a grid.
41270  * @constructor
41271  * @param {Object} config The object containing the configuration of this model.
41272  * @cfg {Boolean} enter_is_tab Enter behaves the same as tab. (eg. goes to next cell) default: false
41273  */
41274 Roo.grid.CellSelectionModel = function(config){
41275     Roo.apply(this, config);
41276
41277     this.selection = null;
41278
41279     this.addEvents({
41280         /**
41281              * @event beforerowselect
41282              * Fires before a cell is selected.
41283              * @param {SelectionModel} this
41284              * @param {Number} rowIndex The selected row index
41285              * @param {Number} colIndex The selected cell index
41286              */
41287             "beforecellselect" : true,
41288         /**
41289              * @event cellselect
41290              * Fires when a cell is selected.
41291              * @param {SelectionModel} this
41292              * @param {Number} rowIndex The selected row index
41293              * @param {Number} colIndex The selected cell index
41294              */
41295             "cellselect" : true,
41296         /**
41297              * @event selectionchange
41298              * Fires when the active selection changes.
41299              * @param {SelectionModel} this
41300              * @param {Object} selection null for no selection or an object (o) with two properties
41301                 <ul>
41302                 <li>o.record: the record object for the row the selection is in</li>
41303                 <li>o.cell: An array of [rowIndex, columnIndex]</li>
41304                 </ul>
41305              */
41306             "selectionchange" : true,
41307         /**
41308              * @event tabend
41309              * Fires when the tab (or enter) was pressed on the last editable cell
41310              * You can use this to trigger add new row.
41311              * @param {SelectionModel} this
41312              */
41313             "tabend" : true,
41314          /**
41315              * @event beforeeditnext
41316              * Fires before the next editable sell is made active
41317              * You can use this to skip to another cell or fire the tabend
41318              *    if you set cell to false
41319              * @param {Object} eventdata object : { cell : [ row, col ] } 
41320              */
41321             "beforeeditnext" : true
41322     });
41323     Roo.grid.CellSelectionModel.superclass.constructor.call(this);
41324 };
41325
41326 Roo.extend(Roo.grid.CellSelectionModel, Roo.grid.AbstractSelectionModel,  {
41327     
41328     enter_is_tab: false,
41329
41330     /** @ignore */
41331     initEvents : function(){
41332         this.grid.on("mousedown", this.handleMouseDown, this);
41333         this.grid.getGridEl().on(Roo.isIE ? "keydown" : "keypress", this.handleKeyDown, this);
41334         var view = this.grid.view;
41335         view.on("refresh", this.onViewChange, this);
41336         view.on("rowupdated", this.onRowUpdated, this);
41337         view.on("beforerowremoved", this.clearSelections, this);
41338         view.on("beforerowsinserted", this.clearSelections, this);
41339         if(this.grid.isEditor){
41340             this.grid.on("beforeedit", this.beforeEdit,  this);
41341         }
41342     },
41343
41344         //private
41345     beforeEdit : function(e){
41346         this.select(e.row, e.column, false, true, e.record);
41347     },
41348
41349         //private
41350     onRowUpdated : function(v, index, r){
41351         if(this.selection && this.selection.record == r){
41352             v.onCellSelect(index, this.selection.cell[1]);
41353         }
41354     },
41355
41356         //private
41357     onViewChange : function(){
41358         this.clearSelections(true);
41359     },
41360
41361         /**
41362          * Returns the currently selected cell,.
41363          * @return {Array} The selected cell (row, column) or null if none selected.
41364          */
41365     getSelectedCell : function(){
41366         return this.selection ? this.selection.cell : null;
41367     },
41368
41369     /**
41370      * Clears all selections.
41371      * @param {Boolean} true to prevent the gridview from being notified about the change.
41372      */
41373     clearSelections : function(preventNotify){
41374         var s = this.selection;
41375         if(s){
41376             if(preventNotify !== true){
41377                 this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
41378             }
41379             this.selection = null;
41380             this.fireEvent("selectionchange", this, null);
41381         }
41382     },
41383
41384     /**
41385      * Returns true if there is a selection.
41386      * @return {Boolean}
41387      */
41388     hasSelection : function(){
41389         return this.selection ? true : false;
41390     },
41391
41392     /** @ignore */
41393     handleMouseDown : function(e, t){
41394         var v = this.grid.getView();
41395         if(this.isLocked()){
41396             return;
41397         };
41398         var row = v.findRowIndex(t);
41399         var cell = v.findCellIndex(t);
41400         if(row !== false && cell !== false){
41401             this.select(row, cell);
41402         }
41403     },
41404
41405     /**
41406      * Selects a cell.
41407      * @param {Number} rowIndex
41408      * @param {Number} collIndex
41409      */
41410     select : function(rowIndex, colIndex, preventViewNotify, preventFocus, /*internal*/ r){
41411         if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
41412             this.clearSelections();
41413             r = r || this.grid.dataSource.getAt(rowIndex);
41414             this.selection = {
41415                 record : r,
41416                 cell : [rowIndex, colIndex]
41417             };
41418             if(!preventViewNotify){
41419                 var v = this.grid.getView();
41420                 v.onCellSelect(rowIndex, colIndex);
41421                 if(preventFocus !== true){
41422                     v.focusCell(rowIndex, colIndex);
41423                 }
41424             }
41425             this.fireEvent("cellselect", this, rowIndex, colIndex);
41426             this.fireEvent("selectionchange", this, this.selection);
41427         }
41428     },
41429
41430         //private
41431     isSelectable : function(rowIndex, colIndex, cm){
41432         return !cm.isHidden(colIndex);
41433     },
41434
41435     /** @ignore */
41436     handleKeyDown : function(e){
41437         //Roo.log('Cell Sel Model handleKeyDown');
41438         if(!e.isNavKeyPress()){
41439             return;
41440         }
41441         var g = this.grid, s = this.selection;
41442         if(!s){
41443             e.stopEvent();
41444             var cell = g.walkCells(0, 0, 1, this.isSelectable,  this);
41445             if(cell){
41446                 this.select(cell[0], cell[1]);
41447             }
41448             return;
41449         }
41450         var sm = this;
41451         var walk = function(row, col, step){
41452             return g.walkCells(row, col, step, sm.isSelectable,  sm);
41453         };
41454         var k = e.getKey(), r = s.cell[0], c = s.cell[1];
41455         var newCell;
41456
41457       
41458
41459         switch(k){
41460             case e.TAB:
41461                 // handled by onEditorKey
41462                 if (g.isEditor && g.editing) {
41463                     return;
41464                 }
41465                 if(e.shiftKey) {
41466                     newCell = walk(r, c-1, -1);
41467                 } else {
41468                     newCell = walk(r, c+1, 1);
41469                 }
41470                 break;
41471             
41472             case e.DOWN:
41473                newCell = walk(r+1, c, 1);
41474                 break;
41475             
41476             case e.UP:
41477                 newCell = walk(r-1, c, -1);
41478                 break;
41479             
41480             case e.RIGHT:
41481                 newCell = walk(r, c+1, 1);
41482                 break;
41483             
41484             case e.LEFT:
41485                 newCell = walk(r, c-1, -1);
41486                 break;
41487             
41488             case e.ENTER:
41489                 
41490                 if(g.isEditor && !g.editing){
41491                    g.startEditing(r, c);
41492                    e.stopEvent();
41493                    return;
41494                 }
41495                 
41496                 
41497              break;
41498         };
41499         if(newCell){
41500             this.select(newCell[0], newCell[1]);
41501             e.stopEvent();
41502             
41503         }
41504     },
41505
41506     acceptsNav : function(row, col, cm){
41507         return !cm.isHidden(col) && cm.isCellEditable(col, row);
41508     },
41509     /**
41510      * Selects a cell.
41511      * @param {Number} field (not used) - as it's normally used as a listener
41512      * @param {Number} e - event - fake it by using
41513      *
41514      * var e = Roo.EventObjectImpl.prototype;
41515      * e.keyCode = e.TAB
41516      *
41517      * 
41518      */
41519     onEditorKey : function(field, e){
41520         
41521         var k = e.getKey(),
41522             newCell,
41523             g = this.grid,
41524             ed = g.activeEditor,
41525             forward = false;
41526         ///Roo.log('onEditorKey' + k);
41527         
41528         
41529         if (this.enter_is_tab && k == e.ENTER) {
41530             k = e.TAB;
41531         }
41532         
41533         if(k == e.TAB){
41534             if(e.shiftKey){
41535                 newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
41536             }else{
41537                 newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
41538                 forward = true;
41539             }
41540             
41541             e.stopEvent();
41542             
41543         } else if(k == e.ENTER &&  !e.ctrlKey){
41544             ed.completeEdit();
41545             e.stopEvent();
41546             newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
41547         
41548                 } else if(k == e.ESC){
41549             ed.cancelEdit();
41550         }
41551                 
41552         if (newCell) {
41553             var ecall = { cell : newCell, forward : forward };
41554             this.fireEvent('beforeeditnext', ecall );
41555             newCell = ecall.cell;
41556                         forward = ecall.forward;
41557         }
41558                 
41559         if(newCell){
41560             //Roo.log('next cell after edit');
41561             g.startEditing.defer(100, g, [newCell[0], newCell[1]]);
41562         } else if (forward) {
41563             // tabbed past last
41564             this.fireEvent.defer(100, this, ['tabend',this]);
41565         }
41566     }
41567 });/*
41568  * Based on:
41569  * Ext JS Library 1.1.1
41570  * Copyright(c) 2006-2007, Ext JS, LLC.
41571  *
41572  * Originally Released Under LGPL - original licence link has changed is not relivant.
41573  *
41574  * Fork - LGPL
41575  * <script type="text/javascript">
41576  */
41577  
41578 /**
41579  * @class Roo.grid.EditorGrid
41580  * @extends Roo.grid.Grid
41581  * Class for creating and editable grid.
41582  * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered - 
41583  * The container MUST have some type of size defined for the grid to fill. The container will be 
41584  * automatically set to position relative if it isn't already.
41585  * @param {Object} dataSource The data model to bind to
41586  * @param {Object} colModel The column model with info about this grid's columns
41587  */
41588 Roo.grid.EditorGrid = function(container, config){
41589     Roo.grid.EditorGrid.superclass.constructor.call(this, container, config);
41590     this.getGridEl().addClass("xedit-grid");
41591
41592     if(!this.selModel){
41593         this.selModel = new Roo.grid.CellSelectionModel();
41594     }
41595
41596     this.activeEditor = null;
41597
41598         this.addEvents({
41599             /**
41600              * @event beforeedit
41601              * Fires before cell editing is triggered. The edit event object has the following properties <br />
41602              * <ul style="padding:5px;padding-left:16px;">
41603              * <li>grid - This grid</li>
41604              * <li>record - The record being edited</li>
41605              * <li>field - The field name being edited</li>
41606              * <li>value - The value for the field being edited.</li>
41607              * <li>row - The grid row index</li>
41608              * <li>column - The grid column index</li>
41609              * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
41610              * </ul>
41611              * @param {Object} e An edit event (see above for description)
41612              */
41613             "beforeedit" : true,
41614             /**
41615              * @event afteredit
41616              * Fires after a cell is edited. <br />
41617              * <ul style="padding:5px;padding-left:16px;">
41618              * <li>grid - This grid</li>
41619              * <li>record - The record being edited</li>
41620              * <li>field - The field name being edited</li>
41621              * <li>value - The value being set</li>
41622              * <li>originalValue - The original value for the field, before the edit.</li>
41623              * <li>row - The grid row index</li>
41624              * <li>column - The grid column index</li>
41625              * </ul>
41626              * @param {Object} e An edit event (see above for description)
41627              */
41628             "afteredit" : true,
41629             /**
41630              * @event validateedit
41631              * Fires after a cell is edited, but before the value is set in the record. 
41632          * You can use this to modify the value being set in the field, Return false
41633              * to cancel the change. The edit event object has the following properties <br />
41634              * <ul style="padding:5px;padding-left:16px;">
41635          * <li>editor - This editor</li>
41636              * <li>grid - This grid</li>
41637              * <li>record - The record being edited</li>
41638              * <li>field - The field name being edited</li>
41639              * <li>value - The value being set</li>
41640              * <li>originalValue - The original value for the field, before the edit.</li>
41641              * <li>row - The grid row index</li>
41642              * <li>column - The grid column index</li>
41643              * <li>cancel - Set this to true to cancel the edit or return false from your handler.</li>
41644              * </ul>
41645              * @param {Object} e An edit event (see above for description)
41646              */
41647             "validateedit" : true
41648         });
41649     this.on("bodyscroll", this.stopEditing,  this);
41650     this.on(this.clicksToEdit == 1 ? "cellclick" : "celldblclick", this.onCellDblClick,  this);
41651 };
41652
41653 Roo.extend(Roo.grid.EditorGrid, Roo.grid.Grid, {
41654     /**
41655      * @cfg {Number} clicksToEdit
41656      * The number of clicks on a cell required to display the cell's editor (defaults to 2)
41657      */
41658     clicksToEdit: 2,
41659
41660     // private
41661     isEditor : true,
41662     // private
41663     trackMouseOver: false, // causes very odd FF errors
41664
41665     onCellDblClick : function(g, row, col){
41666         this.startEditing(row, col);
41667     },
41668
41669     onEditComplete : function(ed, value, startValue){
41670         this.editing = false;
41671         this.activeEditor = null;
41672         ed.un("specialkey", this.selModel.onEditorKey, this.selModel);
41673         var r = ed.record;
41674         var field = this.colModel.getDataIndex(ed.col);
41675         var e = {
41676             grid: this,
41677             record: r,
41678             field: field,
41679             originalValue: startValue,
41680             value: value,
41681             row: ed.row,
41682             column: ed.col,
41683             cancel:false,
41684             editor: ed
41685         };
41686         var cell = Roo.get(this.view.getCell(ed.row,ed.col));
41687         cell.show();
41688           
41689         if(String(value) !== String(startValue)){
41690             
41691             if(this.fireEvent("validateedit", e) !== false && !e.cancel){
41692                 r.set(field, e.value);
41693                 // if we are dealing with a combo box..
41694                 // then we also set the 'name' colum to be the displayField
41695                 if (ed.field.displayField && ed.field.name) {
41696                     r.set(ed.field.name, ed.field.el.dom.value);
41697                 }
41698                 
41699                 delete e.cancel; //?? why!!!
41700                 this.fireEvent("afteredit", e);
41701             }
41702         } else {
41703             this.fireEvent("afteredit", e); // always fire it!
41704         }
41705         this.view.focusCell(ed.row, ed.col);
41706     },
41707
41708     /**
41709      * Starts editing the specified for the specified row/column
41710      * @param {Number} rowIndex
41711      * @param {Number} colIndex
41712      */
41713     startEditing : function(row, col){
41714         this.stopEditing();
41715         if(this.colModel.isCellEditable(col, row)){
41716             this.view.ensureVisible(row, col, true);
41717           
41718             var r = this.dataSource.getAt(row);
41719             var field = this.colModel.getDataIndex(col);
41720             var cell = Roo.get(this.view.getCell(row,col));
41721             var e = {
41722                 grid: this,
41723                 record: r,
41724                 field: field,
41725                 value: r.data[field],
41726                 row: row,
41727                 column: col,
41728                 cancel:false 
41729             };
41730             if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
41731                 this.editing = true;
41732                 var ed = this.colModel.getCellEditor(col, row);
41733                 
41734                 if (!ed) {
41735                     return;
41736                 }
41737                 if(!ed.rendered){
41738                     ed.render(ed.parentEl || document.body);
41739                 }
41740                 ed.field.reset();
41741                
41742                 cell.hide();
41743                 
41744                 (function(){ // complex but required for focus issues in safari, ie and opera
41745                     ed.row = row;
41746                     ed.col = col;
41747                     ed.record = r;
41748                     ed.on("complete",   this.onEditComplete,        this,       {single: true});
41749                     ed.on("specialkey", this.selModel.onEditorKey,  this.selModel);
41750                     this.activeEditor = ed;
41751                     var v = r.data[field];
41752                     ed.startEdit(this.view.getCell(row, col), v);
41753                     // combo's with 'displayField and name set
41754                     if (ed.field.displayField && ed.field.name) {
41755                         ed.field.el.dom.value = r.data[ed.field.name];
41756                     }
41757                     
41758                     
41759                 }).defer(50, this);
41760             }
41761         }
41762     },
41763         
41764     /**
41765      * Stops any active editing
41766      */
41767     stopEditing : function(){
41768         if(this.activeEditor){
41769             this.activeEditor.completeEdit();
41770         }
41771         this.activeEditor = null;
41772     },
41773         
41774          /**
41775      * Called to get grid's drag proxy text, by default returns this.ddText.
41776      * @return {String}
41777      */
41778     getDragDropText : function(){
41779         var count = this.selModel.getSelectedCell() ? 1 : 0;
41780         return String.format(this.ddText, count, count == 1 ? '' : 's');
41781     }
41782         
41783 });/*
41784  * Based on:
41785  * Ext JS Library 1.1.1
41786  * Copyright(c) 2006-2007, Ext JS, LLC.
41787  *
41788  * Originally Released Under LGPL - original licence link has changed is not relivant.
41789  *
41790  * Fork - LGPL
41791  * <script type="text/javascript">
41792  */
41793
41794 // private - not really -- you end up using it !
41795 // This is a support class used internally by the Grid components
41796
41797 /**
41798  * @class Roo.grid.GridEditor
41799  * @extends Roo.Editor
41800  * Class for creating and editable grid elements.
41801  * @param {Object} config any settings (must include field)
41802  */
41803 Roo.grid.GridEditor = function(field, config){
41804     if (!config && field.field) {
41805         config = field;
41806         field = Roo.factory(config.field, Roo.form);
41807     }
41808     Roo.grid.GridEditor.superclass.constructor.call(this, field, config);
41809     field.monitorTab = false;
41810 };
41811
41812 Roo.extend(Roo.grid.GridEditor, Roo.Editor, {
41813     
41814     /**
41815      * @cfg {Roo.form.Field} field Field to wrap (or xtyped)
41816      */
41817     
41818     alignment: "tl-tl",
41819     autoSize: "width",
41820     hideEl : false,
41821     cls: "x-small-editor x-grid-editor",
41822     shim:false,
41823     shadow:"frame"
41824 });/*
41825  * Based on:
41826  * Ext JS Library 1.1.1
41827  * Copyright(c) 2006-2007, Ext JS, LLC.
41828  *
41829  * Originally Released Under LGPL - original licence link has changed is not relivant.
41830  *
41831  * Fork - LGPL
41832  * <script type="text/javascript">
41833  */
41834   
41835
41836   
41837 Roo.grid.PropertyRecord = Roo.data.Record.create([
41838     {name:'name',type:'string'},  'value'
41839 ]);
41840
41841
41842 Roo.grid.PropertyStore = function(grid, source){
41843     this.grid = grid;
41844     this.store = new Roo.data.Store({
41845         recordType : Roo.grid.PropertyRecord
41846     });
41847     this.store.on('update', this.onUpdate,  this);
41848     if(source){
41849         this.setSource(source);
41850     }
41851     Roo.grid.PropertyStore.superclass.constructor.call(this);
41852 };
41853
41854
41855
41856 Roo.extend(Roo.grid.PropertyStore, Roo.util.Observable, {
41857     setSource : function(o){
41858         this.source = o;
41859         this.store.removeAll();
41860         var data = [];
41861         for(var k in o){
41862             if(this.isEditableValue(o[k])){
41863                 data.push(new Roo.grid.PropertyRecord({name: k, value: o[k]}, k));
41864             }
41865         }
41866         this.store.loadRecords({records: data}, {}, true);
41867     },
41868
41869     onUpdate : function(ds, record, type){
41870         if(type == Roo.data.Record.EDIT){
41871             var v = record.data['value'];
41872             var oldValue = record.modified['value'];
41873             if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
41874                 this.source[record.id] = v;
41875                 record.commit();
41876                 this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
41877             }else{
41878                 record.reject();
41879             }
41880         }
41881     },
41882
41883     getProperty : function(row){
41884        return this.store.getAt(row);
41885     },
41886
41887     isEditableValue: function(val){
41888         if(val && val instanceof Date){
41889             return true;
41890         }else if(typeof val == 'object' || typeof val == 'function'){
41891             return false;
41892         }
41893         return true;
41894     },
41895
41896     setValue : function(prop, value){
41897         this.source[prop] = value;
41898         this.store.getById(prop).set('value', value);
41899     },
41900
41901     getSource : function(){
41902         return this.source;
41903     }
41904 });
41905
41906 Roo.grid.PropertyColumnModel = function(grid, store){
41907     this.grid = grid;
41908     var g = Roo.grid;
41909     g.PropertyColumnModel.superclass.constructor.call(this, [
41910         {header: this.nameText, sortable: true, dataIndex:'name', id: 'name'},
41911         {header: this.valueText, resizable:false, dataIndex: 'value', id: 'value'}
41912     ]);
41913     this.store = store;
41914     this.bselect = Roo.DomHelper.append(document.body, {
41915         tag: 'select', style:'display:none', cls: 'x-grid-editor', children: [
41916             {tag: 'option', value: 'true', html: 'true'},
41917             {tag: 'option', value: 'false', html: 'false'}
41918         ]
41919     });
41920     Roo.id(this.bselect);
41921     var f = Roo.form;
41922     this.editors = {
41923         'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
41924         'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
41925         'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
41926         'int' : new g.GridEditor(new f.NumberField({selectOnFocus:true, allowDecimals:false, style:'text-align:left;'})),
41927         'boolean' : new g.GridEditor(new f.Field({el:this.bselect,selectOnFocus:true}))
41928     };
41929     this.renderCellDelegate = this.renderCell.createDelegate(this);
41930     this.renderPropDelegate = this.renderProp.createDelegate(this);
41931 };
41932
41933 Roo.extend(Roo.grid.PropertyColumnModel, Roo.grid.ColumnModel, {
41934     
41935     
41936     nameText : 'Name',
41937     valueText : 'Value',
41938     
41939     dateFormat : 'm/j/Y',
41940     
41941     
41942     renderDate : function(dateVal){
41943         return dateVal.dateFormat(this.dateFormat);
41944     },
41945
41946     renderBool : function(bVal){
41947         return bVal ? 'true' : 'false';
41948     },
41949
41950     isCellEditable : function(colIndex, rowIndex){
41951         return colIndex == 1;
41952     },
41953
41954     getRenderer : function(col){
41955         return col == 1 ?
41956             this.renderCellDelegate : this.renderPropDelegate;
41957     },
41958
41959     renderProp : function(v){
41960         return this.getPropertyName(v);
41961     },
41962
41963     renderCell : function(val){
41964         var rv = val;
41965         if(val instanceof Date){
41966             rv = this.renderDate(val);
41967         }else if(typeof val == 'boolean'){
41968             rv = this.renderBool(val);
41969         }
41970         return Roo.util.Format.htmlEncode(rv);
41971     },
41972
41973     getPropertyName : function(name){
41974         var pn = this.grid.propertyNames;
41975         return pn && pn[name] ? pn[name] : name;
41976     },
41977
41978     getCellEditor : function(colIndex, rowIndex){
41979         var p = this.store.getProperty(rowIndex);
41980         var n = p.data['name'], val = p.data['value'];
41981         
41982         if(typeof(this.grid.customEditors[n]) == 'string'){
41983             return this.editors[this.grid.customEditors[n]];
41984         }
41985         if(typeof(this.grid.customEditors[n]) != 'undefined'){
41986             return this.grid.customEditors[n];
41987         }
41988         if(val instanceof Date){
41989             return this.editors['date'];
41990         }else if(typeof val == 'number'){
41991             return this.editors['number'];
41992         }else if(typeof val == 'boolean'){
41993             return this.editors['boolean'];
41994         }else{
41995             return this.editors['string'];
41996         }
41997     }
41998 });
41999
42000 /**
42001  * @class Roo.grid.PropertyGrid
42002  * @extends Roo.grid.EditorGrid
42003  * This class represents the  interface of a component based property grid control.
42004  * <br><br>Usage:<pre><code>
42005  var grid = new Roo.grid.PropertyGrid("my-container-id", {
42006       
42007  });
42008  // set any options
42009  grid.render();
42010  * </code></pre>
42011   
42012  * @constructor
42013  * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
42014  * The container MUST have some type of size defined for the grid to fill. The container will be
42015  * automatically set to position relative if it isn't already.
42016  * @param {Object} config A config object that sets properties on this grid.
42017  */
42018 Roo.grid.PropertyGrid = function(container, config){
42019     config = config || {};
42020     var store = new Roo.grid.PropertyStore(this);
42021     this.store = store;
42022     var cm = new Roo.grid.PropertyColumnModel(this, store);
42023     store.store.sort('name', 'ASC');
42024     Roo.grid.PropertyGrid.superclass.constructor.call(this, container, Roo.apply({
42025         ds: store.store,
42026         cm: cm,
42027         enableColLock:false,
42028         enableColumnMove:false,
42029         stripeRows:false,
42030         trackMouseOver: false,
42031         clicksToEdit:1
42032     }, config));
42033     this.getGridEl().addClass('x-props-grid');
42034     this.lastEditRow = null;
42035     this.on('columnresize', this.onColumnResize, this);
42036     this.addEvents({
42037          /**
42038              * @event beforepropertychange
42039              * Fires before a property changes (return false to stop?)
42040              * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
42041              * @param {String} id Record Id
42042              * @param {String} newval New Value
42043          * @param {String} oldval Old Value
42044              */
42045         "beforepropertychange": true,
42046         /**
42047              * @event propertychange
42048              * Fires after a property changes
42049              * @param {Roo.grid.PropertyGrid} grid property grid? (check could be store)
42050              * @param {String} id Record Id
42051              * @param {String} newval New Value
42052          * @param {String} oldval Old Value
42053              */
42054         "propertychange": true
42055     });
42056     this.customEditors = this.customEditors || {};
42057 };
42058 Roo.extend(Roo.grid.PropertyGrid, Roo.grid.EditorGrid, {
42059     
42060      /**
42061      * @cfg {Object} customEditors map of colnames=> custom editors.
42062      * the custom editor can be one of the standard ones (date|string|number|int|boolean), or a
42063      * grid editor eg. Roo.grid.GridEditor(new Roo.form.TextArea({selectOnFocus:true})),
42064      * false disables editing of the field.
42065          */
42066     
42067       /**
42068      * @cfg {Object} propertyNames map of property Names to their displayed value
42069          */
42070     
42071     render : function(){
42072         Roo.grid.PropertyGrid.superclass.render.call(this);
42073         this.autoSize.defer(100, this);
42074     },
42075
42076     autoSize : function(){
42077         Roo.grid.PropertyGrid.superclass.autoSize.call(this);
42078         if(this.view){
42079             this.view.fitColumns();
42080         }
42081     },
42082
42083     onColumnResize : function(){
42084         this.colModel.setColumnWidth(1, this.container.getWidth(true)-this.colModel.getColumnWidth(0));
42085         this.autoSize();
42086     },
42087     /**
42088      * Sets the data for the Grid
42089      * accepts a Key => Value object of all the elements avaiable.
42090      * @param {Object} data  to appear in grid.
42091      */
42092     setSource : function(source){
42093         this.store.setSource(source);
42094         //this.autoSize();
42095     },
42096     /**
42097      * Gets all the data from the grid.
42098      * @return {Object} data  data stored in grid
42099      */
42100     getSource : function(){
42101         return this.store.getSource();
42102     }
42103 });/*
42104   
42105  * Licence LGPL
42106  
42107  */
42108  
42109 /**
42110  * @class Roo.grid.Calendar
42111  * @extends Roo.grid.Grid
42112  * This class extends the Grid to provide a calendar widget
42113  * <br><br>Usage:<pre><code>
42114  var grid = new Roo.grid.Calendar("my-container-id", {
42115      ds: myDataStore,
42116      cm: myColModel,
42117      selModel: mySelectionModel,
42118      autoSizeColumns: true,
42119      monitorWindowResize: false,
42120      trackMouseOver: true
42121      eventstore : real data store..
42122  });
42123  // set any options
42124  grid.render();
42125   
42126   * @constructor
42127  * @param {String/HTMLElement/Roo.Element} container The element into which this grid will be rendered -
42128  * The container MUST have some type of size defined for the grid to fill. The container will be
42129  * automatically set to position relative if it isn't already.
42130  * @param {Object} config A config object that sets properties on this grid.
42131  */
42132 Roo.grid.Calendar = function(container, config){
42133         // initialize the container
42134         this.container = Roo.get(container);
42135         this.container.update("");
42136         this.container.setStyle("overflow", "hidden");
42137     this.container.addClass('x-grid-container');
42138
42139     this.id = this.container.id;
42140
42141     Roo.apply(this, config);
42142     // check and correct shorthanded configs
42143     
42144     var rows = [];
42145     var d =1;
42146     for (var r = 0;r < 6;r++) {
42147         
42148         rows[r]=[];
42149         for (var c =0;c < 7;c++) {
42150             rows[r][c]= '';
42151         }
42152     }
42153     if (this.eventStore) {
42154         this.eventStore= Roo.factory(this.eventStore, Roo.data);
42155         this.eventStore.on('load',this.onLoad, this);
42156         this.eventStore.on('beforeload',this.clearEvents, this);
42157          
42158     }
42159     
42160     this.dataSource = new Roo.data.Store({
42161             proxy: new Roo.data.MemoryProxy(rows),
42162             reader: new Roo.data.ArrayReader({}, [
42163                    'weekday0', 'weekday1', 'weekday2', 'weekday3', 'weekday4', 'weekday5', 'weekday6' ])
42164     });
42165
42166     this.dataSource.load();
42167     this.ds = this.dataSource;
42168     this.ds.xmodule = this.xmodule || false;
42169     
42170     
42171     var cellRender = function(v,x,r)
42172     {
42173         return String.format(
42174             '<div class="fc-day  fc-widget-content"><div>' +
42175                 '<div class="fc-event-container"></div>' +
42176                 '<div class="fc-day-number">{0}</div>'+
42177                 
42178                 '<div class="fc-day-content"><div style="position:relative"></div></div>' +
42179             '</div></div>', v);
42180     
42181     }
42182     
42183     
42184     this.colModel = new Roo.grid.ColumnModel( [
42185         {
42186             xtype: 'ColumnModel',
42187             xns: Roo.grid,
42188             dataIndex : 'weekday0',
42189             header : 'Sunday',
42190             renderer : cellRender
42191         },
42192         {
42193             xtype: 'ColumnModel',
42194             xns: Roo.grid,
42195             dataIndex : 'weekday1',
42196             header : 'Monday',
42197             renderer : cellRender
42198         },
42199         {
42200             xtype: 'ColumnModel',
42201             xns: Roo.grid,
42202             dataIndex : 'weekday2',
42203             header : 'Tuesday',
42204             renderer : cellRender
42205         },
42206         {
42207             xtype: 'ColumnModel',
42208             xns: Roo.grid,
42209             dataIndex : 'weekday3',
42210             header : 'Wednesday',
42211             renderer : cellRender
42212         },
42213         {
42214             xtype: 'ColumnModel',
42215             xns: Roo.grid,
42216             dataIndex : 'weekday4',
42217             header : 'Thursday',
42218             renderer : cellRender
42219         },
42220         {
42221             xtype: 'ColumnModel',
42222             xns: Roo.grid,
42223             dataIndex : 'weekday5',
42224             header : 'Friday',
42225             renderer : cellRender
42226         },
42227         {
42228             xtype: 'ColumnModel',
42229             xns: Roo.grid,
42230             dataIndex : 'weekday6',
42231             header : 'Saturday',
42232             renderer : cellRender
42233         }
42234     ]);
42235     this.cm = this.colModel;
42236     this.cm.xmodule = this.xmodule || false;
42237  
42238         
42239           
42240     //this.selModel = new Roo.grid.CellSelectionModel();
42241     //this.sm = this.selModel;
42242     //this.selModel.init(this);
42243     
42244     
42245     if(this.width){
42246         this.container.setWidth(this.width);
42247     }
42248
42249     if(this.height){
42250         this.container.setHeight(this.height);
42251     }
42252     /** @private */
42253         this.addEvents({
42254         // raw events
42255         /**
42256          * @event click
42257          * The raw click event for the entire grid.
42258          * @param {Roo.EventObject} e
42259          */
42260         "click" : true,
42261         /**
42262          * @event dblclick
42263          * The raw dblclick event for the entire grid.
42264          * @param {Roo.EventObject} e
42265          */
42266         "dblclick" : true,
42267         /**
42268          * @event contextmenu
42269          * The raw contextmenu event for the entire grid.
42270          * @param {Roo.EventObject} e
42271          */
42272         "contextmenu" : true,
42273         /**
42274          * @event mousedown
42275          * The raw mousedown event for the entire grid.
42276          * @param {Roo.EventObject} e
42277          */
42278         "mousedown" : true,
42279         /**
42280          * @event mouseup
42281          * The raw mouseup event for the entire grid.
42282          * @param {Roo.EventObject} e
42283          */
42284         "mouseup" : true,
42285         /**
42286          * @event mouseover
42287          * The raw mouseover event for the entire grid.
42288          * @param {Roo.EventObject} e
42289          */
42290         "mouseover" : true,
42291         /**
42292          * @event mouseout
42293          * The raw mouseout event for the entire grid.
42294          * @param {Roo.EventObject} e
42295          */
42296         "mouseout" : true,
42297         /**
42298          * @event keypress
42299          * The raw keypress event for the entire grid.
42300          * @param {Roo.EventObject} e
42301          */
42302         "keypress" : true,
42303         /**
42304          * @event keydown
42305          * The raw keydown event for the entire grid.
42306          * @param {Roo.EventObject} e
42307          */
42308         "keydown" : true,
42309
42310         // custom events
42311
42312         /**
42313          * @event cellclick
42314          * Fires when a cell is clicked
42315          * @param {Grid} this
42316          * @param {Number} rowIndex
42317          * @param {Number} columnIndex
42318          * @param {Roo.EventObject} e
42319          */
42320         "cellclick" : true,
42321         /**
42322          * @event celldblclick
42323          * Fires when a cell is double clicked
42324          * @param {Grid} this
42325          * @param {Number} rowIndex
42326          * @param {Number} columnIndex
42327          * @param {Roo.EventObject} e
42328          */
42329         "celldblclick" : true,
42330         /**
42331          * @event rowclick
42332          * Fires when a row is clicked
42333          * @param {Grid} this
42334          * @param {Number} rowIndex
42335          * @param {Roo.EventObject} e
42336          */
42337         "rowclick" : true,
42338         /**
42339          * @event rowdblclick
42340          * Fires when a row is double clicked
42341          * @param {Grid} this
42342          * @param {Number} rowIndex
42343          * @param {Roo.EventObject} e
42344          */
42345         "rowdblclick" : true,
42346         /**
42347          * @event headerclick
42348          * Fires when a header is clicked
42349          * @param {Grid} this
42350          * @param {Number} columnIndex
42351          * @param {Roo.EventObject} e
42352          */
42353         "headerclick" : true,
42354         /**
42355          * @event headerdblclick
42356          * Fires when a header cell is double clicked
42357          * @param {Grid} this
42358          * @param {Number} columnIndex
42359          * @param {Roo.EventObject} e
42360          */
42361         "headerdblclick" : true,
42362         /**
42363          * @event rowcontextmenu
42364          * Fires when a row is right clicked
42365          * @param {Grid} this
42366          * @param {Number} rowIndex
42367          * @param {Roo.EventObject} e
42368          */
42369         "rowcontextmenu" : true,
42370         /**
42371          * @event cellcontextmenu
42372          * Fires when a cell is right clicked
42373          * @param {Grid} this
42374          * @param {Number} rowIndex
42375          * @param {Number} cellIndex
42376          * @param {Roo.EventObject} e
42377          */
42378          "cellcontextmenu" : true,
42379         /**
42380          * @event headercontextmenu
42381          * Fires when a header is right clicked
42382          * @param {Grid} this
42383          * @param {Number} columnIndex
42384          * @param {Roo.EventObject} e
42385          */
42386         "headercontextmenu" : true,
42387         /**
42388          * @event bodyscroll
42389          * Fires when the body element is scrolled
42390          * @param {Number} scrollLeft
42391          * @param {Number} scrollTop
42392          */
42393         "bodyscroll" : true,
42394         /**
42395          * @event columnresize
42396          * Fires when the user resizes a column
42397          * @param {Number} columnIndex
42398          * @param {Number} newSize
42399          */
42400         "columnresize" : true,
42401         /**
42402          * @event columnmove
42403          * Fires when the user moves a column
42404          * @param {Number} oldIndex
42405          * @param {Number} newIndex
42406          */
42407         "columnmove" : true,
42408         /**
42409          * @event startdrag
42410          * Fires when row(s) start being dragged
42411          * @param {Grid} this
42412          * @param {Roo.GridDD} dd The drag drop object
42413          * @param {event} e The raw browser event
42414          */
42415         "startdrag" : true,
42416         /**
42417          * @event enddrag
42418          * Fires when a drag operation is complete
42419          * @param {Grid} this
42420          * @param {Roo.GridDD} dd The drag drop object
42421          * @param {event} e The raw browser event
42422          */
42423         "enddrag" : true,
42424         /**
42425          * @event dragdrop
42426          * Fires when dragged row(s) are dropped on a valid DD target
42427          * @param {Grid} this
42428          * @param {Roo.GridDD} dd The drag drop object
42429          * @param {String} targetId The target drag drop object
42430          * @param {event} e The raw browser event
42431          */
42432         "dragdrop" : true,
42433         /**
42434          * @event dragover
42435          * Fires while row(s) are being dragged. "targetId" is the id of the Yahoo.util.DD object the selected rows are being dragged over.
42436          * @param {Grid} this
42437          * @param {Roo.GridDD} dd The drag drop object
42438          * @param {String} targetId The target drag drop object
42439          * @param {event} e The raw browser event
42440          */
42441         "dragover" : true,
42442         /**
42443          * @event dragenter
42444          *  Fires when the dragged row(s) first cross another DD target while being dragged
42445          * @param {Grid} this
42446          * @param {Roo.GridDD} dd The drag drop object
42447          * @param {String} targetId The target drag drop object
42448          * @param {event} e The raw browser event
42449          */
42450         "dragenter" : true,
42451         /**
42452          * @event dragout
42453          * Fires when the dragged row(s) leave another DD target while being dragged
42454          * @param {Grid} this
42455          * @param {Roo.GridDD} dd The drag drop object
42456          * @param {String} targetId The target drag drop object
42457          * @param {event} e The raw browser event
42458          */
42459         "dragout" : true,
42460         /**
42461          * @event rowclass
42462          * Fires when a row is rendered, so you can change add a style to it.
42463          * @param {GridView} gridview   The grid view
42464          * @param {Object} rowcfg   contains record  rowIndex and rowClass - set rowClass to add a style.
42465          */
42466         'rowclass' : true,
42467
42468         /**
42469          * @event render
42470          * Fires when the grid is rendered
42471          * @param {Grid} grid
42472          */
42473         'render' : true,
42474             /**
42475              * @event select
42476              * Fires when a date is selected
42477              * @param {DatePicker} this
42478              * @param {Date} date The selected date
42479              */
42480         'select': true,
42481         /**
42482              * @event monthchange
42483              * Fires when the displayed month changes 
42484              * @param {DatePicker} this
42485              * @param {Date} date The selected month
42486              */
42487         'monthchange': true,
42488         /**
42489              * @event evententer
42490              * Fires when mouse over an event
42491              * @param {Calendar} this
42492              * @param {event} Event
42493              */
42494         'evententer': true,
42495         /**
42496              * @event eventleave
42497              * Fires when the mouse leaves an
42498              * @param {Calendar} this
42499              * @param {event}
42500              */
42501         'eventleave': true,
42502         /**
42503              * @event eventclick
42504              * Fires when the mouse click an
42505              * @param {Calendar} this
42506              * @param {event}
42507              */
42508         'eventclick': true,
42509         /**
42510              * @event eventrender
42511              * Fires before each cell is rendered, so you can modify the contents, like cls / title / qtip
42512              * @param {Calendar} this
42513              * @param {data} data to be modified
42514              */
42515         'eventrender': true
42516         
42517     });
42518
42519     Roo.grid.Grid.superclass.constructor.call(this);
42520     this.on('render', function() {
42521         this.view.el.addClass('x-grid-cal'); 
42522         
42523         (function() { this.setDate(new Date()); }).defer(100,this); //default today..
42524
42525     },this);
42526     
42527     if (!Roo.grid.Calendar.style) {
42528         Roo.grid.Calendar.style = Roo.util.CSS.createStyleSheet({
42529             
42530             
42531             '.x-grid-cal .x-grid-col' :  {
42532                 height: 'auto !important',
42533                 'vertical-align': 'top'
42534             },
42535             '.x-grid-cal  .fc-event-hori' : {
42536                 height: '14px'
42537             }
42538              
42539             
42540         }, Roo.id());
42541     }
42542
42543     
42544     
42545 };
42546 Roo.extend(Roo.grid.Calendar, Roo.grid.Grid, {
42547     /**
42548      * @cfg {Store} eventStore The store that loads events.
42549      */
42550     eventStore : 25,
42551
42552      
42553     activeDate : false,
42554     startDay : 0,
42555     autoWidth : true,
42556     monitorWindowResize : false,
42557
42558     
42559     resizeColumns : function() {
42560         var col = (this.view.el.getWidth() / 7) - 3;
42561         // loop through cols, and setWidth
42562         for(var i =0 ; i < 7 ; i++){
42563             this.cm.setColumnWidth(i, col);
42564         }
42565     },
42566      setDate :function(date) {
42567         
42568         Roo.log('setDate?');
42569         
42570         this.resizeColumns();
42571         var vd = this.activeDate;
42572         this.activeDate = date;
42573 //        if(vd && this.el){
42574 //            var t = date.getTime();
42575 //            if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
42576 //                Roo.log('using add remove');
42577 //                
42578 //                this.fireEvent('monthchange', this, date);
42579 //                
42580 //                this.cells.removeClass("fc-state-highlight");
42581 //                this.cells.each(function(c){
42582 //                   if(c.dateValue == t){
42583 //                       c.addClass("fc-state-highlight");
42584 //                       setTimeout(function(){
42585 //                            try{c.dom.firstChild.focus();}catch(e){}
42586 //                       }, 50);
42587 //                       return false;
42588 //                   }
42589 //                   return true;
42590 //                });
42591 //                return;
42592 //            }
42593 //        }
42594         
42595         var days = date.getDaysInMonth();
42596         
42597         var firstOfMonth = date.getFirstDateOfMonth();
42598         var startingPos = firstOfMonth.getDay()-this.startDay;
42599         
42600         if(startingPos < this.startDay){
42601             startingPos += 7;
42602         }
42603         
42604         var pm = date.add(Date.MONTH, -1);
42605         var prevStart = pm.getDaysInMonth()-startingPos;
42606 //        
42607         
42608         
42609         this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
42610         
42611         this.textNodes = this.view.el.query('.x-grid-row .x-grid-col .x-grid-cell-text');
42612         //this.cells.addClassOnOver('fc-state-hover');
42613         
42614         var cells = this.cells.elements;
42615         var textEls = this.textNodes;
42616         
42617         //Roo.each(cells, function(cell){
42618         //    cell.removeClass([ 'fc-past', 'fc-other-month', 'fc-future', 'fc-state-highlight', 'fc-state-disabled']);
42619         //});
42620         
42621         days += startingPos;
42622
42623         // convert everything to numbers so it's fast
42624         var day = 86400000;
42625         var d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart)).clearTime();
42626         //Roo.log(d);
42627         //Roo.log(pm);
42628         //Roo.log(prevStart);
42629         
42630         var today = new Date().clearTime().getTime();
42631         var sel = date.clearTime().getTime();
42632         var min = this.minDate ? this.minDate.clearTime() : Number.NEGATIVE_INFINITY;
42633         var max = this.maxDate ? this.maxDate.clearTime() : Number.POSITIVE_INFINITY;
42634         var ddMatch = this.disabledDatesRE;
42635         var ddText = this.disabledDatesText;
42636         var ddays = this.disabledDays ? this.disabledDays.join("") : false;
42637         var ddaysText = this.disabledDaysText;
42638         var format = this.format;
42639         
42640         var setCellClass = function(cal, cell){
42641             
42642             //Roo.log('set Cell Class');
42643             cell.title = "";
42644             var t = d.getTime();
42645             
42646             //Roo.log(d);
42647             
42648             
42649             cell.dateValue = t;
42650             if(t == today){
42651                 cell.className += " fc-today";
42652                 cell.className += " fc-state-highlight";
42653                 cell.title = cal.todayText;
42654             }
42655             if(t == sel){
42656                 // disable highlight in other month..
42657                 cell.className += " fc-state-highlight";
42658                 
42659             }
42660             // disabling
42661             if(t < min) {
42662                 //cell.className = " fc-state-disabled";
42663                 cell.title = cal.minText;
42664                 return;
42665             }
42666             if(t > max) {
42667                 //cell.className = " fc-state-disabled";
42668                 cell.title = cal.maxText;
42669                 return;
42670             }
42671             if(ddays){
42672                 if(ddays.indexOf(d.getDay()) != -1){
42673                     // cell.title = ddaysText;
42674                    // cell.className = " fc-state-disabled";
42675                 }
42676             }
42677             if(ddMatch && format){
42678                 var fvalue = d.dateFormat(format);
42679                 if(ddMatch.test(fvalue)){
42680                     cell.title = ddText.replace("%0", fvalue);
42681                    cell.className = " fc-state-disabled";
42682                 }
42683             }
42684             
42685             if (!cell.initialClassName) {
42686                 cell.initialClassName = cell.dom.className;
42687             }
42688             
42689             cell.dom.className = cell.initialClassName  + ' ' +  cell.className;
42690         };
42691
42692         var i = 0;
42693         
42694         for(; i < startingPos; i++) {
42695             cells[i].dayName =  (++prevStart);
42696             Roo.log(textEls[i]);
42697             d.setDate(d.getDate()+1);
42698             
42699             //cells[i].className = "fc-past fc-other-month";
42700             setCellClass(this, cells[i]);
42701         }
42702         
42703         var intDay = 0;
42704         
42705         for(; i < days; i++){
42706             intDay = i - startingPos + 1;
42707             cells[i].dayName =  (intDay);
42708             d.setDate(d.getDate()+1);
42709             
42710             cells[i].className = ''; // "x-date-active";
42711             setCellClass(this, cells[i]);
42712         }
42713         var extraDays = 0;
42714         
42715         for(; i < 42; i++) {
42716             //textEls[i].innerHTML = (++extraDays);
42717             
42718             d.setDate(d.getDate()+1);
42719             cells[i].dayName = (++extraDays);
42720             cells[i].className = "fc-future fc-other-month";
42721             setCellClass(this, cells[i]);
42722         }
42723         
42724         //this.el.select('.fc-header-title h2',true).update(Date.monthNames[date.getMonth()] + " " + date.getFullYear());
42725         
42726         var totalRows = Math.ceil((date.getDaysInMonth() + date.getFirstDateOfMonth().getDay()) / 7);
42727         
42728         // this will cause all the cells to mis
42729         var rows= [];
42730         var i =0;
42731         for (var r = 0;r < 6;r++) {
42732             for (var c =0;c < 7;c++) {
42733                 this.ds.getAt(r).set('weekday' + c ,cells[i++].dayName );
42734             }    
42735         }
42736         
42737         this.cells = this.view.el.select('.x-grid-row .x-grid-col',true);
42738         for(i=0;i<cells.length;i++) {
42739             
42740             this.cells.elements[i].dayName = cells[i].dayName ;
42741             this.cells.elements[i].className = cells[i].className;
42742             this.cells.elements[i].initialClassName = cells[i].initialClassName ;
42743             this.cells.elements[i].title = cells[i].title ;
42744             this.cells.elements[i].dateValue = cells[i].dateValue ;
42745         }
42746         
42747         
42748         
42749         
42750         //this.el.select('tr.fc-week.fc-prev-last',true).removeClass('fc-last');
42751         //this.el.select('tr.fc-week.fc-next-last',true).addClass('fc-last').show();
42752         
42753         ////if(totalRows != 6){
42754             //this.el.select('tr.fc-week.fc-last',true).removeClass('fc-last').addClass('fc-next-last').hide();
42755            // this.el.select('tr.fc-week.fc-prev-last',true).addClass('fc-last');
42756        // }
42757         
42758         this.fireEvent('monthchange', this, date);
42759         
42760         
42761     },
42762  /**
42763      * Returns the grid's SelectionModel.
42764      * @return {SelectionModel}
42765      */
42766     getSelectionModel : function(){
42767         if(!this.selModel){
42768             this.selModel = new Roo.grid.CellSelectionModel();
42769         }
42770         return this.selModel;
42771     },
42772
42773     load: function() {
42774         this.eventStore.load()
42775         
42776         
42777         
42778     },
42779     
42780     findCell : function(dt) {
42781         dt = dt.clearTime().getTime();
42782         var ret = false;
42783         this.cells.each(function(c){
42784             //Roo.log("check " +c.dateValue + '?=' + dt);
42785             if(c.dateValue == dt){
42786                 ret = c;
42787                 return false;
42788             }
42789             return true;
42790         });
42791         
42792         return ret;
42793     },
42794     
42795     findCells : function(rec) {
42796         var s = rec.data.start_dt.clone().clearTime().getTime();
42797        // Roo.log(s);
42798         var e= rec.data.end_dt.clone().clearTime().getTime();
42799        // Roo.log(e);
42800         var ret = [];
42801         this.cells.each(function(c){
42802              ////Roo.log("check " +c.dateValue + '<' + e + ' > ' + s);
42803             
42804             if(c.dateValue > e){
42805                 return ;
42806             }
42807             if(c.dateValue < s){
42808                 return ;
42809             }
42810             ret.push(c);
42811         });
42812         
42813         return ret;    
42814     },
42815     
42816     findBestRow: function(cells)
42817     {
42818         var ret = 0;
42819         
42820         for (var i =0 ; i < cells.length;i++) {
42821             ret  = Math.max(cells[i].rows || 0,ret);
42822         }
42823         return ret;
42824         
42825     },
42826     
42827     
42828     addItem : function(rec)
42829     {
42830         // look for vertical location slot in
42831         var cells = this.findCells(rec);
42832         
42833         rec.row = this.findBestRow(cells);
42834         
42835         // work out the location.
42836         
42837         var crow = false;
42838         var rows = [];
42839         for(var i =0; i < cells.length; i++) {
42840             if (!crow) {
42841                 crow = {
42842                     start : cells[i],
42843                     end :  cells[i]
42844                 };
42845                 continue;
42846             }
42847             if (crow.start.getY() == cells[i].getY()) {
42848                 // on same row.
42849                 crow.end = cells[i];
42850                 continue;
42851             }
42852             // different row.
42853             rows.push(crow);
42854             crow = {
42855                 start: cells[i],
42856                 end : cells[i]
42857             };
42858             
42859         }
42860         
42861         rows.push(crow);
42862         rec.els = [];
42863         rec.rows = rows;
42864         rec.cells = cells;
42865         for (var i = 0; i < cells.length;i++) {
42866             cells[i].rows = Math.max(cells[i].rows || 0 , rec.row + 1 );
42867             
42868         }
42869         
42870         
42871     },
42872     
42873     clearEvents: function() {
42874         
42875         if (!this.eventStore.getCount()) {
42876             return;
42877         }
42878         // reset number of rows in cells.
42879         Roo.each(this.cells.elements, function(c){
42880             c.rows = 0;
42881         });
42882         
42883         this.eventStore.each(function(e) {
42884             this.clearEvent(e);
42885         },this);
42886         
42887     },
42888     
42889     clearEvent : function(ev)
42890     {
42891         if (ev.els) {
42892             Roo.each(ev.els, function(el) {
42893                 el.un('mouseenter' ,this.onEventEnter, this);
42894                 el.un('mouseleave' ,this.onEventLeave, this);
42895                 el.remove();
42896             },this);
42897             ev.els = [];
42898         }
42899     },
42900     
42901     
42902     renderEvent : function(ev,ctr) {
42903         if (!ctr) {
42904              ctr = this.view.el.select('.fc-event-container',true).first();
42905         }
42906         
42907          
42908         this.clearEvent(ev);
42909             //code
42910        
42911         
42912         
42913         ev.els = [];
42914         var cells = ev.cells;
42915         var rows = ev.rows;
42916         this.fireEvent('eventrender', this, ev);
42917         
42918         for(var i =0; i < rows.length; i++) {
42919             
42920             cls = '';
42921             if (i == 0) {
42922                 cls += ' fc-event-start';
42923             }
42924             if ((i+1) == rows.length) {
42925                 cls += ' fc-event-end';
42926             }
42927             
42928             //Roo.log(ev.data);
42929             // how many rows should it span..
42930             var cg = this.eventTmpl.append(ctr,Roo.apply({
42931                 fccls : cls
42932                 
42933             }, ev.data) , true);
42934             
42935             
42936             cg.on('mouseenter' ,this.onEventEnter, this, ev);
42937             cg.on('mouseleave' ,this.onEventLeave, this, ev);
42938             cg.on('click', this.onEventClick, this, ev);
42939             
42940             ev.els.push(cg);
42941             
42942             var sbox = rows[i].start.select('.fc-day-content',true).first().getBox();
42943             var ebox = rows[i].end.select('.fc-day-content',true).first().getBox();
42944             //Roo.log(cg);
42945              
42946             cg.setXY([sbox.x +2, sbox.y +(ev.row * 20)]);    
42947             cg.setWidth(ebox.right - sbox.x -2);
42948         }
42949     },
42950     
42951     renderEvents: function()
42952     {   
42953         // first make sure there is enough space..
42954         
42955         if (!this.eventTmpl) {
42956             this.eventTmpl = new Roo.Template(
42957                 '<div class="roo-dynamic fc-event fc-event-hori fc-event-draggable ui-draggable {fccls} {cls}"  style="position: absolute" unselectable="on">' +
42958                     '<div class="fc-event-inner">' +
42959                         '<span class="fc-event-time">{time}</span>' +
42960                         '<span class="fc-event-title" qtip="{qtip}">{title}</span>' +
42961                     '</div>' +
42962                     '<div class="ui-resizable-heandle ui-resizable-e">&nbsp;&nbsp;&nbsp;</div>' +
42963                 '</div>'
42964             );
42965                 
42966         }
42967                
42968         
42969         
42970         this.cells.each(function(c) {
42971             //Roo.log(c.select('.fc-day-content div',true).first());
42972             c.select('.fc-day-content div',true).first().setHeight(Math.max(34, (c.rows || 1) * 20));
42973         });
42974         
42975         var ctr = this.view.el.select('.fc-event-container',true).first();
42976         
42977         var cls;
42978         this.eventStore.each(function(ev){
42979             
42980             this.renderEvent(ev);
42981              
42982              
42983         }, this);
42984         this.view.layout();
42985         
42986     },
42987     
42988     onEventEnter: function (e, el,event,d) {
42989         this.fireEvent('evententer', this, el, event);
42990     },
42991     
42992     onEventLeave: function (e, el,event,d) {
42993         this.fireEvent('eventleave', this, el, event);
42994     },
42995     
42996     onEventClick: function (e, el,event,d) {
42997         this.fireEvent('eventclick', this, el, event);
42998     },
42999     
43000     onMonthChange: function () {
43001         this.store.load();
43002     },
43003     
43004     onLoad: function () {
43005         
43006         //Roo.log('calendar onload');
43007 //         
43008         if(this.eventStore.getCount() > 0){
43009             
43010            
43011             
43012             this.eventStore.each(function(d){
43013                 
43014                 
43015                 // FIXME..
43016                 var add =   d.data;
43017                 if (typeof(add.end_dt) == 'undefined')  {
43018                     Roo.log("Missing End time in calendar data: ");
43019                     Roo.log(d);
43020                     return;
43021                 }
43022                 if (typeof(add.start_dt) == 'undefined')  {
43023                     Roo.log("Missing Start time in calendar data: ");
43024                     Roo.log(d);
43025                     return;
43026                 }
43027                 add.start_dt = typeof(add.start_dt) == 'string' ? Date.parseDate(add.start_dt,'Y-m-d H:i:s') : add.start_dt,
43028                 add.end_dt = typeof(add.end_dt) == 'string' ? Date.parseDate(add.end_dt,'Y-m-d H:i:s') : add.end_dt,
43029                 add.id = add.id || d.id;
43030                 add.title = add.title || '??';
43031                 
43032                 this.addItem(d);
43033                 
43034              
43035             },this);
43036         }
43037         
43038         this.renderEvents();
43039     }
43040     
43041
43042 });
43043 /*
43044  grid : {
43045                 xtype: 'Grid',
43046                 xns: Roo.grid,
43047                 listeners : {
43048                     render : function ()
43049                     {
43050                         _this.grid = this;
43051                         
43052                         if (!this.view.el.hasClass('course-timesheet')) {
43053                             this.view.el.addClass('course-timesheet');
43054                         }
43055                         if (this.tsStyle) {
43056                             this.ds.load({});
43057                             return; 
43058                         }
43059                         Roo.log('width');
43060                         Roo.log(_this.grid.view.el.getWidth());
43061                         
43062                         
43063                         this.tsStyle =  Roo.util.CSS.createStyleSheet({
43064                             '.course-timesheet .x-grid-row' : {
43065                                 height: '80px'
43066                             },
43067                             '.x-grid-row td' : {
43068                                 'vertical-align' : 0
43069                             },
43070                             '.course-edit-link' : {
43071                                 'color' : 'blue',
43072                                 'text-overflow' : 'ellipsis',
43073                                 'overflow' : 'hidden',
43074                                 'white-space' : 'nowrap',
43075                                 'cursor' : 'pointer'
43076                             },
43077                             '.sub-link' : {
43078                                 'color' : 'green'
43079                             },
43080                             '.de-act-sup-link' : {
43081                                 'color' : 'purple',
43082                                 'text-decoration' : 'line-through'
43083                             },
43084                             '.de-act-link' : {
43085                                 'color' : 'red',
43086                                 'text-decoration' : 'line-through'
43087                             },
43088                             '.course-timesheet .course-highlight' : {
43089                                 'border-top-style': 'dashed !important',
43090                                 'border-bottom-bottom': 'dashed !important'
43091                             },
43092                             '.course-timesheet .course-item' : {
43093                                 'font-family'   : 'tahoma, arial, helvetica',
43094                                 'font-size'     : '11px',
43095                                 'overflow'      : 'hidden',
43096                                 'padding-left'  : '10px',
43097                                 'padding-right' : '10px',
43098                                 'padding-top' : '10px' 
43099                             }
43100                             
43101                         }, Roo.id());
43102                                 this.ds.load({});
43103                     }
43104                 },
43105                 autoWidth : true,
43106                 monitorWindowResize : false,
43107                 cellrenderer : function(v,x,r)
43108                 {
43109                     return v;
43110                 },
43111                 sm : {
43112                     xtype: 'CellSelectionModel',
43113                     xns: Roo.grid
43114                 },
43115                 dataSource : {
43116                     xtype: 'Store',
43117                     xns: Roo.data,
43118                     listeners : {
43119                         beforeload : function (_self, options)
43120                         {
43121                             options.params = options.params || {};
43122                             options.params._month = _this.monthField.getValue();
43123                             options.params.limit = 9999;
43124                             options.params['sort'] = 'when_dt';    
43125                             options.params['dir'] = 'ASC';    
43126                             this.proxy.loadResponse = this.loadResponse;
43127                             Roo.log("load?");
43128                             //this.addColumns();
43129                         },
43130                         load : function (_self, records, options)
43131                         {
43132                             _this.grid.view.el.select('.course-edit-link', true).on('click', function() {
43133                                 // if you click on the translation.. you can edit it...
43134                                 var el = Roo.get(this);
43135                                 var id = el.dom.getAttribute('data-id');
43136                                 var d = el.dom.getAttribute('data-date');
43137                                 var t = el.dom.getAttribute('data-time');
43138                                 //var id = this.child('span').dom.textContent;
43139                                 
43140                                 //Roo.log(this);
43141                                 Pman.Dialog.CourseCalendar.show({
43142                                     id : id,
43143                                     when_d : d,
43144                                     when_t : t,
43145                                     productitem_active : id ? 1 : 0
43146                                 }, function() {
43147                                     _this.grid.ds.load({});
43148                                 });
43149                            
43150                            });
43151                            
43152                            _this.panel.fireEvent('resize', [ '', '' ]);
43153                         }
43154                     },
43155                     loadResponse : function(o, success, response){
43156                             // this is overridden on before load..
43157                             
43158                             Roo.log("our code?");       
43159                             //Roo.log(success);
43160                             //Roo.log(response)
43161                             delete this.activeRequest;
43162                             if(!success){
43163                                 this.fireEvent("loadexception", this, o, response);
43164                                 o.request.callback.call(o.request.scope, null, o.request.arg, false);
43165                                 return;
43166                             }
43167                             var result;
43168                             try {
43169                                 result = o.reader.read(response);
43170                             }catch(e){
43171                                 Roo.log("load exception?");
43172                                 this.fireEvent("loadexception", this, o, response, e);
43173                                 o.request.callback.call(o.request.scope, null, o.request.arg, false);
43174                                 return;
43175                             }
43176                             Roo.log("ready...");        
43177                             // loop through result.records;
43178                             // and set this.tdate[date] = [] << array of records..
43179                             _this.tdata  = {};
43180                             Roo.each(result.records, function(r){
43181                                 //Roo.log(r.data);
43182                                 if(typeof(_this.tdata[r.data.when_dt.format('j')]) == 'undefined'){
43183                                     _this.tdata[r.data.when_dt.format('j')] = [];
43184                                 }
43185                                 _this.tdata[r.data.when_dt.format('j')].push(r.data);
43186                             });
43187                             
43188                             //Roo.log(_this.tdata);
43189                             
43190                             result.records = [];
43191                             result.totalRecords = 6;
43192                     
43193                             // let's generate some duumy records for the rows.
43194                             //var st = _this.dateField.getValue();
43195                             
43196                             // work out monday..
43197                             //st = st.add(Date.DAY, -1 * st.format('w'));
43198                             
43199                             var date = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
43200                             
43201                             var firstOfMonth = date.getFirstDayOfMonth();
43202                             var days = date.getDaysInMonth();
43203                             var d = 1;
43204                             var firstAdded = false;
43205                             for (var i = 0; i < result.totalRecords ; i++) {
43206                                 //var d= st.add(Date.DAY, i);
43207                                 var row = {};
43208                                 var added = 0;
43209                                 for(var w = 0 ; w < 7 ; w++){
43210                                     if(!firstAdded && firstOfMonth != w){
43211                                         continue;
43212                                     }
43213                                     if(d > days){
43214                                         continue;
43215                                     }
43216                                     firstAdded = true;
43217                                     var dd = (d > 0 && d < 10) ? "0"+d : d;
43218                                     row['weekday'+w] = String.format(
43219                                                     '<span style="font-size: 16px;"><b>{0}</b></span>'+
43220                                                     '<span class="course-edit-link" style="color:blue;" data-id="0" data-date="{1}"> Add New</span>',
43221                                                     d,
43222                                                     date.format('Y-m-')+dd
43223                                                 );
43224                                     added++;
43225                                     if(typeof(_this.tdata[d]) != 'undefined'){
43226                                         Roo.each(_this.tdata[d], function(r){
43227                                             var is_sub = '';
43228                                             var deactive = '';
43229                                             var id = r.id;
43230                                             var desc = (r.productitem_id_descrip) ? r.productitem_id_descrip : '';
43231                                             if(r.parent_id*1>0){
43232                                                 is_sub = (r.productitem_id_visible*1 < 1) ? 'de-act-sup-link' :'sub-link';
43233                                                 id = r.parent_id;
43234                                             }
43235                                             if(r.productitem_id_visible*1 < 1 && r.parent_id*1 < 1){
43236                                                 deactive = 'de-act-link';
43237                                             }
43238                                             
43239                                             row['weekday'+w] += String.format(
43240                                                     '<br /><span class="course-edit-link {3} {4}" qtip="{5}" data-id="{0}">{2} - {1}</span>',
43241                                                     id, //0
43242                                                     r.product_id_name, //1
43243                                                     r.when_dt.format('h:ia'), //2
43244                                                     is_sub, //3
43245                                                     deactive, //4
43246                                                     desc // 5
43247                                             );
43248                                         });
43249                                     }
43250                                     d++;
43251                                 }
43252                                 
43253                                 // only do this if something added..
43254                                 if(added > 0){ 
43255                                     result.records.push(_this.grid.dataSource.reader.newRow(row));
43256                                 }
43257                                 
43258                                 
43259                                 // push it twice. (second one with an hour..
43260                                 
43261                             }
43262                             //Roo.log(result);
43263                             this.fireEvent("load", this, o, o.request.arg);
43264                             o.request.callback.call(o.request.scope, result, o.request.arg, true);
43265                         },
43266                     sortInfo : {field: 'when_dt', direction : 'ASC' },
43267                     proxy : {
43268                         xtype: 'HttpProxy',
43269                         xns: Roo.data,
43270                         method : 'GET',
43271                         url : baseURL + '/Roo/Shop_course.php'
43272                     },
43273                     reader : {
43274                         xtype: 'JsonReader',
43275                         xns: Roo.data,
43276                         id : 'id',
43277                         fields : [
43278                             {
43279                                 'name': 'id',
43280                                 'type': 'int'
43281                             },
43282                             {
43283                                 'name': 'when_dt',
43284                                 'type': 'string'
43285                             },
43286                             {
43287                                 'name': 'end_dt',
43288                                 'type': 'string'
43289                             },
43290                             {
43291                                 'name': 'parent_id',
43292                                 'type': 'int'
43293                             },
43294                             {
43295                                 'name': 'product_id',
43296                                 'type': 'int'
43297                             },
43298                             {
43299                                 'name': 'productitem_id',
43300                                 'type': 'int'
43301                             },
43302                             {
43303                                 'name': 'guid',
43304                                 'type': 'int'
43305                             }
43306                         ]
43307                     }
43308                 },
43309                 toolbar : {
43310                     xtype: 'Toolbar',
43311                     xns: Roo,
43312                     items : [
43313                         {
43314                             xtype: 'Button',
43315                             xns: Roo.Toolbar,
43316                             listeners : {
43317                                 click : function (_self, e)
43318                                 {
43319                                     var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
43320                                     sd.setMonth(sd.getMonth()-1);
43321                                     _this.monthField.setValue(sd.format('Y-m-d'));
43322                                     _this.grid.ds.load({});
43323                                 }
43324                             },
43325                             text : "Back"
43326                         },
43327                         {
43328                             xtype: 'Separator',
43329                             xns: Roo.Toolbar
43330                         },
43331                         {
43332                             xtype: 'MonthField',
43333                             xns: Roo.form,
43334                             listeners : {
43335                                 render : function (_self)
43336                                 {
43337                                     _this.monthField = _self;
43338                                    // _this.monthField.set  today
43339                                 },
43340                                 select : function (combo, date)
43341                                 {
43342                                     _this.grid.ds.load({});
43343                                 }
43344                             },
43345                             value : (function() { return new Date(); })()
43346                         },
43347                         {
43348                             xtype: 'Separator',
43349                             xns: Roo.Toolbar
43350                         },
43351                         {
43352                             xtype: 'TextItem',
43353                             xns: Roo.Toolbar,
43354                             text : "Blue: in-active, green: in-active sup-event, red: de-active, purple: de-active sup-event"
43355                         },
43356                         {
43357                             xtype: 'Fill',
43358                             xns: Roo.Toolbar
43359                         },
43360                         {
43361                             xtype: 'Button',
43362                             xns: Roo.Toolbar,
43363                             listeners : {
43364                                 click : function (_self, e)
43365                                 {
43366                                     var sd = Date.parseDate(_this.monthField.getValue(), "Y-m-d");
43367                                     sd.setMonth(sd.getMonth()+1);
43368                                     _this.monthField.setValue(sd.format('Y-m-d'));
43369                                     _this.grid.ds.load({});
43370                                 }
43371                             },
43372                             text : "Next"
43373                         }
43374                     ]
43375                 },
43376                  
43377             }
43378         };
43379         
43380         *//*
43381  * Based on:
43382  * Ext JS Library 1.1.1
43383  * Copyright(c) 2006-2007, Ext JS, LLC.
43384  *
43385  * Originally Released Under LGPL - original licence link has changed is not relivant.
43386  *
43387  * Fork - LGPL
43388  * <script type="text/javascript">
43389  */
43390  
43391 /**
43392  * @class Roo.LoadMask
43393  * A simple utility class for generically masking elements while loading data.  If the element being masked has
43394  * an underlying {@link Roo.data.Store}, the masking will be automatically synchronized with the store's loading
43395  * process and the mask element will be cached for reuse.  For all other elements, this mask will replace the
43396  * element's UpdateManager load indicator and will be destroyed after the initial load.
43397  * @constructor
43398  * Create a new LoadMask
43399  * @param {String/HTMLElement/Roo.Element} el The element or DOM node, or its id
43400  * @param {Object} config The config object
43401  */
43402 Roo.LoadMask = function(el, config){
43403     this.el = Roo.get(el);
43404     Roo.apply(this, config);
43405     if(this.store){
43406         this.store.on('beforeload', this.onBeforeLoad, this);
43407         this.store.on('load', this.onLoad, this);
43408         this.store.on('loadexception', this.onLoadException, this);
43409         this.removeMask = false;
43410     }else{
43411         var um = this.el.getUpdateManager();
43412         um.showLoadIndicator = false; // disable the default indicator
43413         um.on('beforeupdate', this.onBeforeLoad, this);
43414         um.on('update', this.onLoad, this);
43415         um.on('failure', this.onLoad, this);
43416         this.removeMask = true;
43417     }
43418 };
43419
43420 Roo.LoadMask.prototype = {
43421     /**
43422      * @cfg {Boolean} removeMask
43423      * True to create a single-use mask that is automatically destroyed after loading (useful for page loads),
43424      * False to persist the mask element reference for multiple uses (e.g., for paged data widgets).  Defaults to false.
43425      */
43426     removeMask : false,
43427     /**
43428      * @cfg {String} msg
43429      * The text to display in a centered loading message box (defaults to 'Loading...')
43430      */
43431     msg : 'Loading...',
43432     /**
43433      * @cfg {String} msgCls
43434      * The CSS class to apply to the loading message element (defaults to "x-mask-loading")
43435      */
43436     msgCls : 'x-mask-loading',
43437
43438     /**
43439      * Read-only. True if the mask is currently disabled so that it will not be displayed (defaults to false)
43440      * @type Boolean
43441      */
43442     disabled: false,
43443
43444     /**
43445      * Disables the mask to prevent it from being displayed
43446      */
43447     disable : function(){
43448        this.disabled = true;
43449     },
43450
43451     /**
43452      * Enables the mask so that it can be displayed
43453      */
43454     enable : function(){
43455         this.disabled = false;
43456     },
43457     
43458     onLoadException : function()
43459     {
43460         Roo.log(arguments);
43461         
43462         if (typeof(arguments[3]) != 'undefined') {
43463             Roo.MessageBox.alert("Error loading",arguments[3]);
43464         } 
43465         /*
43466         try {
43467             if (this.store && typeof(this.store.reader.jsonData.errorMsg) != 'undefined') {
43468                 Roo.MessageBox.alert("Error loading",this.store.reader.jsonData.errorMsg);
43469             }   
43470         } catch(e) {
43471             
43472         }
43473         */
43474     
43475         (function() { this.el.unmask(this.removeMask); }).defer(50, this);
43476     },
43477     // private
43478     onLoad : function()
43479     {
43480         (function() { this.el.unmask(this.removeMask); }).defer(50, this);
43481     },
43482
43483     // private
43484     onBeforeLoad : function(){
43485         if(!this.disabled){
43486             (function() { this.el.mask(this.msg, this.msgCls); }).defer(50, this);
43487         }
43488     },
43489
43490     // private
43491     destroy : function(){
43492         if(this.store){
43493             this.store.un('beforeload', this.onBeforeLoad, this);
43494             this.store.un('load', this.onLoad, this);
43495             this.store.un('loadexception', this.onLoadException, this);
43496         }else{
43497             var um = this.el.getUpdateManager();
43498             um.un('beforeupdate', this.onBeforeLoad, this);
43499             um.un('update', this.onLoad, this);
43500             um.un('failure', this.onLoad, this);
43501         }
43502     }
43503 };/*
43504  * Based on:
43505  * Ext JS Library 1.1.1
43506  * Copyright(c) 2006-2007, Ext JS, LLC.
43507  *
43508  * Originally Released Under LGPL - original licence link has changed is not relivant.
43509  *
43510  * Fork - LGPL
43511  * <script type="text/javascript">
43512  */
43513
43514
43515 /**
43516  * @class Roo.XTemplate
43517  * @extends Roo.Template
43518  * Provides a template that can have nested templates for loops or conditionals. The syntax is:
43519 <pre><code>
43520 var t = new Roo.XTemplate(
43521         '&lt;select name="{name}"&gt;',
43522                 '&lt;tpl for="options"&gt;&lt;option value="{value:trim}"&gt;{text:ellipsis(10)}&lt;/option&gt;&lt;/tpl&gt;',
43523         '&lt;/select&gt;'
43524 );
43525  
43526 // then append, applying the master template values
43527  </code></pre>
43528  *
43529  * Supported features:
43530  *
43531  *  Tags:
43532
43533 <pre><code>
43534       {a_variable} - output encoded.
43535       {a_variable.format:("Y-m-d")} - call a method on the variable
43536       {a_variable:raw} - unencoded output
43537       {a_variable:toFixed(1,2)} - Roo.util.Format."toFixed"
43538       {a_variable:this.method_on_template(...)} - call a method on the template object.
43539  
43540 </code></pre>
43541  *  The tpl tag:
43542 <pre><code>
43543         &lt;tpl for="a_variable or condition.."&gt;&lt;/tpl&gt;
43544         &lt;tpl if="a_variable or condition"&gt;&lt;/tpl&gt;
43545         &lt;tpl exec="some javascript"&gt;&lt;/tpl&gt;
43546         &lt;tpl name="named_template"&gt;&lt;/tpl&gt; (experimental)
43547   
43548         &lt;tpl for="."&gt;&lt;/tpl&gt; - just iterate the property..
43549         &lt;tpl for=".."&gt;&lt;/tpl&gt; - iterates with the parent (probably the template) 
43550 </code></pre>
43551  *      
43552  */
43553 Roo.XTemplate = function()
43554 {
43555     Roo.XTemplate.superclass.constructor.apply(this, arguments);
43556     if (this.html) {
43557         this.compile();
43558     }
43559 };
43560
43561
43562 Roo.extend(Roo.XTemplate, Roo.Template, {
43563
43564     /**
43565      * The various sub templates
43566      */
43567     tpls : false,
43568     /**
43569      *
43570      * basic tag replacing syntax
43571      * WORD:WORD()
43572      *
43573      * // you can fake an object call by doing this
43574      *  x.t:(test,tesT) 
43575      * 
43576      */
43577     re : /\{([\w-\.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
43578
43579     /**
43580      * compile the template
43581      *
43582      * This is not recursive, so I'm not sure how nested templates are really going to be handled..
43583      *
43584      */
43585     compile: function()
43586     {
43587         var s = this.html;
43588      
43589         s = ['<tpl>', s, '</tpl>'].join('');
43590     
43591         var re     = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
43592             nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
43593             ifRe   = /^<tpl\b[^>]*?if="(.*?)"/,
43594             execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
43595             namedRe = /^<tpl\b[^>]*?name="(\w+)"/,  // named templates..
43596             m,
43597             id     = 0,
43598             tpls   = [];
43599     
43600         while(true == !!(m = s.match(re))){
43601             var forMatch   = m[0].match(nameRe),
43602                 ifMatch   = m[0].match(ifRe),
43603                 execMatch   = m[0].match(execRe),
43604                 namedMatch   = m[0].match(namedRe),
43605                 
43606                 exp  = null, 
43607                 fn   = null,
43608                 exec = null,
43609                 name = forMatch && forMatch[1] ? forMatch[1] : '';
43610                 
43611             if (ifMatch) {
43612                 // if - puts fn into test..
43613                 exp = ifMatch && ifMatch[1] ? ifMatch[1] : null;
43614                 if(exp){
43615                    fn = new Function('values', 'parent', 'with(values){ return '+(Roo.util.Format.htmlDecode(exp))+'; }');
43616                 }
43617             }
43618             
43619             if (execMatch) {
43620                 // exec - calls a function... returns empty if true is  returned.
43621                 exp = execMatch && execMatch[1] ? execMatch[1] : null;
43622                 if(exp){
43623                    exec = new Function('values', 'parent', 'with(values){ '+(Roo.util.Format.htmlDecode(exp))+'; }');
43624                 }
43625             }
43626             
43627             
43628             if (name) {
43629                 // for = 
43630                 switch(name){
43631                     case '.':  name = new Function('values', 'parent', 'with(values){ return values; }'); break;
43632                     case '..': name = new Function('values', 'parent', 'with(values){ return parent; }'); break;
43633                     default:   name = new Function('values', 'parent', 'with(values){ return '+name+'; }');
43634                 }
43635             }
43636             var uid = namedMatch ? namedMatch[1] : id;
43637             
43638             
43639             tpls.push({
43640                 id:     namedMatch ? namedMatch[1] : id,
43641                 target: name,
43642                 exec:   exec,
43643                 test:   fn,
43644                 body:   m[1] || ''
43645             });
43646             if (namedMatch) {
43647                 s = s.replace(m[0], '');
43648             } else { 
43649                 s = s.replace(m[0], '{xtpl'+ id + '}');
43650             }
43651             ++id;
43652         }
43653         this.tpls = [];
43654         for(var i = tpls.length-1; i >= 0; --i){
43655             this.compileTpl(tpls[i]);
43656             this.tpls[tpls[i].id] = tpls[i];
43657         }
43658         this.master = tpls[tpls.length-1];
43659         return this;
43660     },
43661     /**
43662      * same as applyTemplate, except it's done to one of the subTemplates
43663      * when using named templates, you can do:
43664      *
43665      * var str = pl.applySubTemplate('your-name', values);
43666      *
43667      * 
43668      * @param {Number} id of the template
43669      * @param {Object} values to apply to template
43670      * @param {Object} parent (normaly the instance of this object)
43671      */
43672     applySubTemplate : function(id, values, parent)
43673     {
43674         
43675         
43676         var t = this.tpls[id];
43677         
43678         
43679         try { 
43680             if(t.test && !t.test.call(this, values, parent)){
43681                 return '';
43682             }
43683         } catch(e) {
43684             Roo.log("Xtemplate.applySubTemplate 'test': Exception thrown");
43685             Roo.log(e.toString());
43686             Roo.log(t.test);
43687             return ''
43688         }
43689         try { 
43690             
43691             if(t.exec && t.exec.call(this, values, parent)){
43692                 return '';
43693             }
43694         } catch(e) {
43695             Roo.log("Xtemplate.applySubTemplate 'exec': Exception thrown");
43696             Roo.log(e.toString());
43697             Roo.log(t.exec);
43698             return ''
43699         }
43700         try {
43701             var vs = t.target ? t.target.call(this, values, parent) : values;
43702             parent = t.target ? values : parent;
43703             if(t.target && vs instanceof Array){
43704                 var buf = [];
43705                 for(var i = 0, len = vs.length; i < len; i++){
43706                     buf[buf.length] = t.compiled.call(this, vs[i], parent);
43707                 }
43708                 return buf.join('');
43709             }
43710             return t.compiled.call(this, vs, parent);
43711         } catch (e) {
43712             Roo.log("Xtemplate.applySubTemplate : Exception thrown");
43713             Roo.log(e.toString());
43714             Roo.log(t.compiled);
43715             return '';
43716         }
43717     },
43718
43719     compileTpl : function(tpl)
43720     {
43721         var fm = Roo.util.Format;
43722         var useF = this.disableFormats !== true;
43723         var sep = Roo.isGecko ? "+" : ",";
43724         var undef = function(str) {
43725             Roo.log("Property not found :"  + str);
43726             return '';
43727         };
43728         
43729         var fn = function(m, name, format, args)
43730         {
43731             //Roo.log(arguments);
43732             args = args ? args.replace(/\\'/g,"'") : args;
43733             //["{TEST:(a,b,c)}", "TEST", "", "a,b,c", 0, "{TEST:(a,b,c)}"]
43734             if (typeof(format) == 'undefined') {
43735                 format= 'htmlEncode';
43736             }
43737             if (format == 'raw' ) {
43738                 format = false;
43739             }
43740             
43741             if(name.substr(0, 4) == 'xtpl'){
43742                 return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent)'+sep+"'";
43743             }
43744             
43745             // build an array of options to determine if value is undefined..
43746             
43747             // basically get 'xxxx.yyyy' then do
43748             // (typeof(xxxx) == 'undefined' || typeof(xxx.yyyy) == 'undefined') ?
43749             //    (function () { Roo.log("Property not found"); return ''; })() :
43750             //    ......
43751             
43752             var udef_ar = [];
43753             var lookfor = '';
43754             Roo.each(name.split('.'), function(st) {
43755                 lookfor += (lookfor.length ? '.': '') + st;
43756                 udef_ar.push(  "(typeof(" + lookfor + ") == 'undefined')"  );
43757             });
43758             
43759             var udef_st = '((' + udef_ar.join(" || ") +") ? undef('" + name + "') : "; // .. needs )
43760             
43761             
43762             if(format && useF){
43763                 
43764                 args = args ? ',' + args : "";
43765                  
43766                 if(format.substr(0, 5) != "this."){
43767                     format = "fm." + format + '(';
43768                 }else{
43769                     format = 'this.call("'+ format.substr(5) + '", ';
43770                     args = ", values";
43771                 }
43772                 
43773                 return "'"+ sep +   udef_st   +    format + name + args + "))"+sep+"'";
43774             }
43775              
43776             if (args.length) {
43777                 // called with xxyx.yuu:(test,test)
43778                 // change to ()
43779                 return "'"+ sep + udef_st  + name + '(' +  args + "))"+sep+"'";
43780             }
43781             // raw.. - :raw modifier..
43782             return "'"+ sep + udef_st  + name + ")"+sep+"'";
43783             
43784         };
43785         var body;
43786         // branched to use + in gecko and [].join() in others
43787         if(Roo.isGecko){
43788             body = "tpl.compiled = function(values, parent){  with(values) { return '" +
43789                    tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
43790                     "';};};";
43791         }else{
43792             body = ["tpl.compiled = function(values, parent){  with (values) { return ['"];
43793             body.push(tpl.body.replace(/(\r\n|\n)/g,
43794                             '\\n').replace(/'/g, "\\'").replace(this.re, fn));
43795             body.push("'].join('');};};");
43796             body = body.join('');
43797         }
43798         
43799         Roo.debug && Roo.log(body.replace(/\\n/,'\n'));
43800        
43801         /** eval:var:tpl eval:var:fm eval:var:useF eval:var:undef  */
43802         eval(body);
43803         
43804         return this;
43805     },
43806
43807     applyTemplate : function(values){
43808         return this.master.compiled.call(this, values, {});
43809         //var s = this.subs;
43810     },
43811
43812     apply : function(){
43813         return this.applyTemplate.apply(this, arguments);
43814     }
43815
43816  });
43817
43818 Roo.XTemplate.from = function(el){
43819     el = Roo.getDom(el);
43820     return new Roo.XTemplate(el.value || el.innerHTML);
43821 };Roo.dialog = {};
43822 /*
43823 * Licence: LGPL
43824 */
43825
43826 /**
43827  * @class Roo.dialog.UploadCropbox
43828  * @extends Roo.BoxComponent
43829  * Dialog UploadCropbox class
43830  * @cfg {String} emptyText show when image has been loaded
43831  * @cfg {String} rotateNotify show when image too small to rotate
43832  * @cfg {Number} errorTimeout default 3000
43833  * @cfg {Number} minWidth default 300
43834  * @cfg {Number} minHeight default 300
43835  * @cfg {Number} outputMaxWidth default 1200
43836  * @cfg {Number} windowSize default 300
43837  * @cfg {Array} buttons default ['rotateLeft', 'pictureBtn', 'rotateRight']
43838  * @cfg {Boolean} isDocument (true|false) default false
43839  * @cfg {String} url action url
43840  * @cfg {String} paramName default 'imageUpload'
43841  * @cfg {String} method default POST
43842  * @cfg {Boolean} loadMask (true|false) default true
43843  * @cfg {Boolean} loadingText default 'Loading...'
43844  * 
43845  * @constructor
43846  * Create a new UploadCropbox
43847  * @param {Object} config The config object
43848  */
43849
43850  Roo.dialog.UploadCropbox = function(config){
43851     Roo.dialog.UploadCropbox.superclass.constructor.call(this, config);
43852     
43853     this.addEvents({
43854         /**
43855          * @event beforeselectfile
43856          * Fire before select file
43857          * @param {Roo.dialog.UploadCropbox} this
43858          */
43859         "beforeselectfile" : true,
43860         /**
43861          * @event initial
43862          * Fire after initEvent
43863          * @param {Roo.dialog.UploadCropbox} this
43864          */
43865         "initial" : true,
43866         /**
43867          * @event crop
43868          * Fire after initEvent
43869          * @param {Roo.dialog.UploadCropbox} this
43870          * @param {String} data
43871          */
43872         "crop" : true,
43873         /**
43874          * @event prepare
43875          * Fire when preparing the file data
43876          * @param {Roo.dialog.UploadCropbox} this
43877          * @param {Object} file
43878          */
43879         "prepare" : true,
43880         /**
43881          * @event exception
43882          * Fire when get exception
43883          * @param {Roo.dialog.UploadCropbox} this
43884          * @param {XMLHttpRequest} xhr
43885          */
43886         "exception" : true,
43887         /**
43888          * @event beforeloadcanvas
43889          * Fire before load the canvas
43890          * @param {Roo.dialog.UploadCropbox} this
43891          * @param {String} src
43892          */
43893         "beforeloadcanvas" : true,
43894         /**
43895          * @event trash
43896          * Fire when trash image
43897          * @param {Roo.dialog.UploadCropbox} this
43898          */
43899         "trash" : true,
43900         /**
43901          * @event download
43902          * Fire when download the image
43903          * @param {Roo.dialog.UploadCropbox} this
43904          */
43905         "download" : true,
43906         /**
43907          * @event footerbuttonclick
43908          * Fire when footerbuttonclick
43909          * @param {Roo.dialog.UploadCropbox} this
43910          * @param {String} type
43911          */
43912         "footerbuttonclick" : true,
43913         /**
43914          * @event resize
43915          * Fire when resize
43916          * @param {Roo.dialog.UploadCropbox} this
43917          */
43918         "resize" : true,
43919         /**
43920          * @event rotate
43921          * Fire when rotate the image
43922          * @param {Roo.dialog.UploadCropbox} this
43923          * @param {String} pos
43924          */
43925         "rotate" : true,
43926         /**
43927          * @event inspect
43928          * Fire when inspect the file
43929          * @param {Roo.dialog.UploadCropbox} this
43930          * @param {Object} file
43931          */
43932         "inspect" : true,
43933         /**
43934          * @event upload
43935          * Fire when xhr upload the file
43936          * @param {Roo.dialog.UploadCropbox} this
43937          * @param {Object} data
43938          */
43939         "upload" : true,
43940         /**
43941          * @event arrange
43942          * Fire when arrange the file data
43943          * @param {Roo.dialog.UploadCropbox} this
43944          * @param {Object} formData
43945          */
43946         "arrange" : true,
43947         /**
43948          * @event loadcanvas
43949          * Fire after load the canvas
43950          * @param {Roo.dialog.UploadCropbox}
43951          * @param {Object} imgEl
43952          */
43953         "loadcanvas" : true
43954     });
43955     
43956     this.buttons = this.buttons || Roo.dialog.UploadCropbox.footer.STANDARD;
43957 };
43958
43959 Roo.extend(Roo.dialog.UploadCropbox, Roo.Component,  {
43960     
43961     emptyText : 'Click to upload image',
43962     rotateNotify : 'Image is too small to rotate',
43963     errorTimeout : 3000,
43964     scale : 0,
43965     baseScale : 1,
43966     rotate : 0,
43967     dragable : false,
43968     pinching : false,
43969     mouseX : 0,
43970     mouseY : 0,
43971     cropData : false,
43972     minWidth : 300,
43973     minHeight : 300,
43974     outputMaxWidth : 1200,
43975     windowSize : 300,
43976     file : false,
43977     exif : {},
43978     baseRotate : 1,
43979     cropType : 'image/jpeg',
43980     buttons : false,
43981     canvasLoaded : false,
43982     isDocument : false,
43983     method : 'POST',
43984     paramName : 'imageUpload',
43985     loadMask : true,
43986     loadingText : 'Loading...',
43987     maskEl : false,
43988     
43989     getAutoCreate : function()
43990     {
43991         var cfg = {
43992             tag : 'div',
43993             cls : 'roo-upload-cropbox',
43994             cn : [
43995                 {
43996                     tag : 'input',
43997                     cls : 'roo-upload-cropbox-selector',
43998                     type : 'file'
43999                 },
44000                 {
44001                     tag : 'div',
44002                     cls : 'roo-upload-cropbox-body',
44003                     style : 'cursor:pointer',
44004                     cn : [
44005                         {
44006                             tag : 'div',
44007                             cls : 'roo-upload-cropbox-preview'
44008                         },
44009                         {
44010                             tag : 'div',
44011                             cls : 'roo-upload-cropbox-thumb'
44012                         },
44013                         {
44014                             tag : 'div',
44015                             cls : 'roo-upload-cropbox-empty-notify',
44016                             html : this.emptyText
44017                         },
44018                         {
44019                             tag : 'div',
44020                             cls : 'roo-upload-cropbox-error-notify alert alert-danger',
44021                             html : this.rotateNotify
44022                         }
44023                     ]
44024                 },
44025                 {
44026                     tag : 'div',
44027                     cls : 'roo-upload-cropbox-footer',
44028                     cn : {
44029                         tag : 'div',
44030                         cls : 'btn-group btn-group-justified roo-upload-cropbox-btn-group',
44031                         cn : []
44032                     }
44033                 }
44034             ]
44035         };
44036         
44037         return cfg;
44038     },
44039     
44040     onRender : function(ct, position)
44041     {
44042         Roo.dialog.UploadCropbox.superclass.onRender.call(this, ct, position);
44043
44044         if(this.el){
44045             if (this.el.attr('xtype')) {
44046                 this.el.attr('xtypex', this.el.attr('xtype'));
44047                 this.el.dom.removeAttribute('xtype');
44048                 
44049                 this.initEvents();
44050             }
44051         }
44052         else {
44053             var cfg = Roo.apply({},  this.getAutoCreate());
44054         
44055             cfg.id = this.id || Roo.id();
44056             
44057             if (this.cls) {
44058                 cfg.cls = (typeof(cfg.cls) == 'undefined' ? this.cls : cfg.cls) + ' ' + this.cls;
44059             }
44060             
44061             if (this.style) { // fixme needs to support more complex style data.
44062                 cfg.style = (typeof(cfg.style) == 'undefined' ? this.style : cfg.style) + '; ' + this.style;
44063             }
44064             
44065             this.el = ct.createChild(cfg, position);
44066             
44067             this.initEvents();
44068         }
44069         
44070         if (this.buttons.length) {
44071             
44072             Roo.each(this.buttons, function(bb) {
44073                 
44074                 var btn = this.el.select('.roo-upload-cropbox-footer div.roo-upload-cropbox-btn-group').first().createChild(bb);
44075                 
44076                 btn.on('click', this.onFooterButtonClick.createDelegate(this, [bb.action], true));
44077                 
44078             }, this);
44079         }
44080         
44081         if(this.loadMask){
44082             this.maskEl = this.el;
44083         }
44084     },
44085     
44086     initEvents : function()
44087     {
44088         this.urlAPI = (window.createObjectURL && window) || 
44089                                 (window.URL && URL.revokeObjectURL && URL) || 
44090                                 (window.webkitURL && webkitURL);
44091                         
44092         this.bodyEl = this.el.select('.roo-upload-cropbox-body', true).first();
44093         this.bodyEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44094         
44095         this.selectorEl = this.el.select('.roo-upload-cropbox-selector', true).first();
44096         this.selectorEl.hide();
44097         
44098         this.previewEl = this.el.select('.roo-upload-cropbox-preview', true).first();
44099         this.previewEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44100         
44101         this.thumbEl = this.el.select('.roo-upload-cropbox-thumb', true).first();
44102         this.thumbEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44103         this.thumbEl.hide();
44104         
44105         this.notifyEl = this.el.select('.roo-upload-cropbox-empty-notify', true).first();
44106         this.notifyEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44107         
44108         this.errorEl = this.el.select('.roo-upload-cropbox-error-notify', true).first();
44109         this.errorEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44110         this.errorEl.hide();
44111         
44112         this.footerEl = this.el.select('.roo-upload-cropbox-footer', true).first();
44113         this.footerEl.setVisibilityMode(Roo.Element.DISPLAY).originalDisplay = 'block';
44114         this.footerEl.hide();
44115         
44116         this.setThumbBoxSize();
44117         
44118         this.bind();
44119         
44120         this.resize();
44121         
44122         this.fireEvent('initial', this);
44123     },
44124
44125     bind : function()
44126     {
44127         var _this = this;
44128         
44129         window.addEventListener("resize", function() { _this.resize(); } );
44130         
44131         this.bodyEl.on('click', this.beforeSelectFile, this);
44132         
44133         if(Roo.isTouch){
44134             this.bodyEl.on('touchstart', this.onTouchStart, this);
44135             this.bodyEl.on('touchmove', this.onTouchMove, this);
44136             this.bodyEl.on('touchend', this.onTouchEnd, this);
44137         }
44138         
44139         if(!Roo.isTouch){
44140             this.bodyEl.on('mousedown', this.onMouseDown, this);
44141             this.bodyEl.on('mousemove', this.onMouseMove, this);
44142             var mousewheel = (/Firefox/i.test(navigator.userAgent))? 'DOMMouseScroll' : 'mousewheel';
44143             this.bodyEl.on(mousewheel, this.onMouseWheel, this);
44144             Roo.get(document).on('mouseup', this.onMouseUp, this);
44145         }
44146         
44147         this.selectorEl.on('change', this.onFileSelected, this);
44148     },
44149     
44150     reset : function()
44151     {    
44152         this.scale = 0;
44153         this.baseScale = 1;
44154         this.rotate = 0;
44155         this.baseRotate = 1;
44156         this.dragable = false;
44157         this.pinching = false;
44158         this.mouseX = 0;
44159         this.mouseY = 0;
44160         this.cropData = false;
44161         this.notifyEl.dom.innerHTML = this.emptyText;
44162         
44163         // this.selectorEl.dom.value = '';
44164         
44165     },
44166     
44167     resize : function()
44168     {
44169         if(this.fireEvent('resize', this) != false){
44170             this.setThumbBoxPosition();
44171             this.setCanvasPosition();
44172         }
44173     },
44174     
44175     onFooterButtonClick : function(e, el, o, type)
44176     {
44177         switch (type) {
44178             case 'rotate-left' :
44179                 this.onRotateLeft(e);
44180                 break;
44181             case 'rotate-right' :
44182                 this.onRotateRight(e);
44183                 break;
44184             case 'picture' :
44185                 this.beforeSelectFile(e);
44186                 break;
44187             case 'trash' :
44188                 this.trash(e);
44189                 break;
44190             case 'crop' :
44191                 this.crop(e);
44192                 break;
44193             case 'download' :
44194                 this.download(e);
44195                 break;
44196             case 'center' :
44197                 this.center(e);
44198                 break;
44199             default :
44200                 break;
44201         }
44202         
44203         this.fireEvent('footerbuttonclick', this, type);
44204     },
44205     
44206     beforeSelectFile : function(e)
44207     {
44208         e.preventDefault();
44209         
44210         if(this.fireEvent('beforeselectfile', this) != false){
44211             this.selectorEl.dom.click();
44212         }
44213     },
44214     
44215     onFileSelected : function(e)
44216     {
44217         e.preventDefault();
44218         
44219         if(typeof(this.selectorEl.dom.files) == 'undefined' || !this.selectorEl.dom.files.length){
44220             return;
44221         }
44222         
44223         var file = this.selectorEl.dom.files[0];
44224         
44225         if(this.fireEvent('inspect', this, file) != false){
44226             this.prepare(file);
44227         }
44228         
44229     },
44230     
44231     trash : function(e)
44232     {
44233         this.fireEvent('trash', this);
44234     },
44235     
44236     download : function(e)
44237     {
44238         this.fireEvent('download', this);
44239     },
44240
44241     center : function(e)
44242     {
44243         this.setCanvasPosition();
44244     },
44245     
44246     loadCanvas : function(src)
44247     {   
44248         if(this.fireEvent('beforeloadcanvas', this, src) != false){
44249             
44250             this.reset();
44251             
44252             this.imageEl = document.createElement('img');
44253             
44254             var _this = this;
44255             
44256             this.imageEl.addEventListener("load", function(){ _this.onLoadCanvas(); });
44257             
44258             this.imageEl.src = src;
44259         }
44260     },
44261     
44262     onLoadCanvas : function()
44263     {   
44264         this.imageEl.OriginWidth = this.imageEl.naturalWidth || this.imageEl.width;
44265         this.imageEl.OriginHeight = this.imageEl.naturalHeight || this.imageEl.height;
44266
44267         if(this.fireEvent('loadcanvas', this, this.imageEl) != false){
44268         
44269             this.bodyEl.un('click', this.beforeSelectFile, this);
44270             
44271             this.notifyEl.hide();
44272             this.thumbEl.show();
44273             this.footerEl.show();
44274             
44275             this.baseRotateLevel();
44276             
44277             if(this.isDocument){
44278                 this.setThumbBoxSize();
44279             }
44280             
44281             this.setThumbBoxPosition();
44282             
44283             this.baseScaleLevel();
44284             
44285             this.draw();
44286             
44287             this.resize();
44288             
44289             this.canvasLoaded = true;
44290         
44291         }
44292         
44293         if(this.loadMask){
44294             this.maskEl.unmask();
44295         }
44296         
44297     },
44298     
44299     setCanvasPosition : function(center = true)
44300     {   
44301         if(!this.canvasEl){
44302             return;
44303         }
44304
44305         var newCenterLeft = Math.ceil((this.bodyEl.getWidth() - this.canvasEl.width) / 2);
44306         var newCenterTop = Math.ceil((this.bodyEl.getHeight() - this.canvasEl.height) / 2);
44307
44308         if(center) {
44309             this.previewEl.setLeft(newCenterLeft);
44310             this.previewEl.setTop(newCenterTop);
44311
44312             return;
44313         }
44314         
44315         var oldScaleLevel = this.baseScale * Math.pow(1.02, this.startScale);
44316         var oldCanvasWidth = Math.floor(this.imageEl.OriginWidth * oldScaleLevel);
44317         var oldCanvasHeight = Math.floor(this.imageEl.OriginHeight * oldScaleLevel);
44318
44319         var oldCenterLeft = Math.ceil((this.bodyEl.getWidth() - oldCanvasWidth) / 2);
44320         var oldCenterTop = Math.ceil((this.bodyEl.getHeight() - oldCanvasHeight) / 2);
44321
44322         var leftDiff = newCenterLeft - oldCenterLeft;
44323         var topDiff = newCenterTop - oldCenterTop;
44324
44325         var newPreviewLeft = this.previewEl.getLeft(true) + leftDiff;
44326         var newPreviewTop = this.previewEl.getTop(true) + topDiff;
44327
44328         this.previewEl.setLeft(newPreviewLeft);
44329         this.previewEl.setTop(newPreviewTop);
44330         
44331     },
44332     
44333     onMouseDown : function(e)
44334     {   
44335         e.stopEvent();
44336         
44337         this.dragable = true;
44338         this.pinching = false;
44339         
44340         if(this.isDocument && (this.canvasEl.width < this.thumbEl.getWidth() || this.canvasEl.height < this.thumbEl.getHeight())){
44341             this.dragable = false;
44342             return;
44343         }
44344         
44345         this.mouseX = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
44346         this.mouseY = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
44347         
44348     },
44349     
44350     onMouseMove : function(e)
44351     {   
44352         e.stopEvent();
44353         
44354         if(!this.canvasLoaded){
44355             return;
44356         }
44357         
44358         if (!this.dragable){
44359             return;
44360         }
44361
44362         var maxPaddingLeft = this.canvasEl.width / 0.9 * 0.05;
44363         var maxPaddingTop = maxPaddingLeft * this.minHeight / this.minWidth;
44364
44365         if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight <= this.minWidth / this.minHeight)) {
44366             maxPaddingLeft = (this.canvasEl.height * this.minWidth / this.minHeight - this.canvasEl.width) / 2 + maxPaddingLeft;
44367         }
44368
44369         if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight >= this.minWidth / this.minHeight)) {
44370             maxPaddingTop = (this.canvasEl.width * this.minHeight / this.minWidth - this.canvasEl.height) / 2 + maxPaddingTop;
44371         }
44372         
44373         var minX = Math.ceil(this.thumbEl.getLeft(true) + this.thumbEl.getWidth() - this.canvasEl.width - maxPaddingLeft);
44374         var minY = Math.ceil(this.thumbEl.getTop(true) + this.thumbEl.getHeight() - this.canvasEl.height - maxPaddingTop);
44375         
44376         var maxX = Math.ceil(this.thumbEl.getLeft(true) + maxPaddingLeft);
44377         var maxY = Math.ceil(this.thumbEl.getTop(true) +  maxPaddingTop);
44378
44379         if(minX > maxX) {
44380             var tempX = minX;
44381             minX = maxX;
44382             maxX = tempX;
44383         }
44384
44385         if(minY > maxY) {
44386             var tempY = minY;
44387             minY = maxY;
44388             maxY = tempY;
44389         }
44390
44391         var x = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
44392         var y = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
44393         
44394         x = x - this.mouseX;
44395         y = y - this.mouseY;
44396
44397         var bgX = Math.ceil(x + this.previewEl.getLeft(true));
44398         var bgY = Math.ceil(y + this.previewEl.getTop(true));
44399         
44400         bgX = (bgX < minX) ? minX : ((bgX > maxX) ? maxX : bgX);
44401         bgY = (bgY < minY) ? minY : ((bgY > maxY) ? maxY : bgY);
44402         
44403         this.previewEl.setLeft(bgX);
44404         this.previewEl.setTop(bgY);
44405         
44406         this.mouseX = Roo.isTouch ? e.browserEvent.touches[0].pageX : e.getPageX();
44407         this.mouseY = Roo.isTouch ? e.browserEvent.touches[0].pageY : e.getPageY();
44408     },
44409     
44410     onMouseUp : function(e)
44411     {   
44412         e.stopEvent();
44413         
44414         this.dragable = false;
44415     },
44416     
44417     onMouseWheel : function(e)
44418     {   
44419         e.stopEvent();
44420         
44421         this.startScale = this.scale;
44422         this.scale = (e.getWheelDelta() > 0) ? (this.scale + 1) : (this.scale - 1);
44423         
44424         if(!this.zoomable()){
44425             this.scale = this.startScale;
44426             return;
44427         }
44428
44429         
44430         this.draw();
44431         
44432         return;
44433     },
44434     
44435     zoomable : function()
44436     {
44437         var minScale = this.thumbEl.getWidth() / this.minWidth;
44438         
44439         if(this.minWidth < this.minHeight){
44440             minScale = this.thumbEl.getHeight() / this.minHeight;
44441         }
44442         
44443         var width = Math.ceil(this.imageEl.OriginWidth * this.getScaleLevel() / minScale);
44444         var height = Math.ceil(this.imageEl.OriginHeight * this.getScaleLevel() / minScale);
44445  
44446         var maxWidth = this.imageEl.OriginWidth;
44447         var maxHeight = this.imageEl.OriginHeight;
44448
44449
44450         var newCanvasWidth = Math.floor(this.imageEl.OriginWidth * this.getScaleLevel());
44451         var newCanvasHeight = Math.floor(this.imageEl.OriginHeight * this.getScaleLevel());
44452
44453         var oldCenterLeft = Math.ceil((this.bodyEl.getWidth() - this.canvasEl.width) / 2);
44454         var oldCenterTop = Math.ceil((this.bodyEl.getHeight() - this.canvasEl.height) / 2);
44455
44456         var newCenterLeft = Math.ceil((this.bodyEl.getWidth() - newCanvasWidth) / 2);
44457         var newCenterTop = Math.ceil((this.bodyEl.getHeight() - newCanvasHeight) / 2);
44458
44459         var leftDiff = newCenterLeft - oldCenterLeft;
44460         var topDiff = newCenterTop - oldCenterTop;
44461
44462         var newPreviewLeft = this.previewEl.getLeft(true) + leftDiff;
44463         var newPreviewTop = this.previewEl.getTop(true) + topDiff;
44464
44465         var paddingLeft = newPreviewLeft - this.thumbEl.getLeft(true);
44466         var paddingTop = newPreviewTop - this.thumbEl.getTop(true);
44467
44468         var paddingRight = this.thumbEl.getLeft(true) + this.thumbEl.getWidth() - newCanvasWidth - newPreviewLeft;
44469         var paddingBottom = this.thumbEl.getTop(true) + this.thumbEl.getHeight() - newCanvasHeight - newPreviewTop;
44470
44471         var maxPaddingLeft = newCanvasWidth / 0.9 * 0.05;
44472         var maxPaddingTop = maxPaddingLeft * this.minHeight / this.minWidth;
44473
44474         if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight <= this.minWidth / this.minHeight)) {
44475             maxPaddingLeft = (newCanvasHeight * this.minWidth / this.minHeight - newCanvasWidth) / 2 + maxPaddingLeft;
44476         }
44477
44478         if ((this.imageEl.OriginWidth / this.imageEl.OriginHeight >= this.minWidth / this.minHeight)) {
44479             maxPaddingTop = (newCanvasWidth * this.minHeight / this.minWidth - newCanvasHeight) / 2 + maxPaddingTop;
44480         }
44481         
44482         if(
44483                 this.isDocument &&
44484                 (this.rotate == 0 || this.rotate == 180) && 
44485                 (
44486                     width > this.imageEl.OriginWidth || 
44487                     height > this.imageEl.OriginHeight ||
44488                     (width < this.minWidth && height < this.minHeight)
44489                 )
44490         ){
44491             return false;
44492         }
44493         
44494         if(
44495                 this.isDocument &&
44496                 (this.rotate == 90 || this.rotate == 270) && 
44497                 (
44498                     width > this.imageEl.OriginWidth || 
44499                     height > this.imageEl.OriginHeight ||
44500                     (width < this.minHeight && height < this.minWidth)
44501                 )
44502         ){
44503             return false;
44504         }
44505         
44506         if(
44507                 !this.isDocument &&
44508                 (this.rotate == 0 || this.rotate == 180) && 
44509                 (
44510                     // for zoom out
44511                     paddingLeft > maxPaddingLeft ||
44512                     paddingRight > maxPaddingLeft ||
44513                     paddingTop > maxPaddingTop ||
44514                     paddingBottom > maxPaddingTop ||
44515                     // for zoom in
44516                     width > maxWidth ||
44517                     height > maxHeight
44518                 )
44519         ){
44520             return false;
44521         }
44522         
44523         if(
44524                 !this.isDocument &&
44525                 (this.rotate == 90 || this.rotate == 270) && 
44526                 (
44527                     width < this.minHeight || 
44528                     width > this.imageEl.OriginWidth || 
44529                     height < this.minWidth || 
44530                     height > this.imageEl.OriginHeight
44531                 )
44532         ){
44533             return false;
44534         }
44535         
44536         return true;
44537         
44538     },
44539     
44540     onRotateLeft : function(e)
44541     {   
44542         if(!this.isDocument && (this.canvasEl.height < this.thumbEl.getWidth() || this.canvasEl.width < this.thumbEl.getHeight())){
44543             
44544             var minScale = this.thumbEl.getWidth() / this.minWidth;
44545             
44546             var bw = Math.ceil(this.canvasEl.width / this.getScaleLevel());
44547             var bh = Math.ceil(this.canvasEl.height / this.getScaleLevel());
44548             
44549             this.startScale = this.scale;
44550             
44551             while (this.getScaleLevel() < minScale){
44552             
44553                 this.scale = this.scale + 1;
44554                 
44555                 if(!this.zoomable()){
44556                     break;
44557                 }
44558                 
44559                 if(
44560                         Math.ceil(bw * this.getScaleLevel()) < this.thumbEl.getHeight() ||
44561                         Math.ceil(bh * this.getScaleLevel()) < this.thumbEl.getWidth()
44562                 ){
44563                     continue;
44564                 }
44565                 
44566                 this.rotate = (this.rotate < 90) ? 270 : this.rotate - 90;
44567
44568                 this.draw();
44569                 
44570                 return;
44571             }
44572             
44573             this.scale = this.startScale;
44574             
44575             this.onRotateFail();
44576             
44577             return false;
44578         }
44579         
44580         this.rotate = (this.rotate < 90) ? 270 : this.rotate - 90;
44581
44582         if(this.isDocument){
44583             this.setThumbBoxSize();
44584             this.setThumbBoxPosition();
44585             this.setCanvasPosition();
44586         }
44587         
44588         this.draw();
44589         
44590         this.fireEvent('rotate', this, 'left');
44591         
44592     },
44593     
44594     onRotateRight : function(e)
44595     {
44596         if(!this.isDocument && (this.canvasEl.height < this.thumbEl.getWidth() || this.canvasEl.width < this.thumbEl.getHeight())){
44597             
44598             var minScale = this.thumbEl.getWidth() / this.minWidth;
44599         
44600             var bw = Math.ceil(this.canvasEl.width / this.getScaleLevel());
44601             var bh = Math.ceil(this.canvasEl.height / this.getScaleLevel());
44602             
44603             this.startScale = this.scale;
44604             
44605             while (this.getScaleLevel() < minScale){
44606             
44607                 this.scale = this.scale + 1;
44608                 
44609                 if(!this.zoomable()){
44610                     break;
44611                 }
44612                 
44613                 if(
44614                         Math.ceil(bw * this.getScaleLevel()) < this.thumbEl.getHeight() ||
44615                         Math.ceil(bh * this.getScaleLevel()) < this.thumbEl.getWidth()
44616                 ){
44617                     continue;
44618                 }
44619                 
44620                 this.rotate = (this.rotate > 180) ? 0 : this.rotate + 90;
44621
44622                 this.draw();
44623                 
44624                 return;
44625             }
44626             
44627             this.scale = this.startScale;
44628             
44629             this.onRotateFail();
44630             
44631             return false;
44632         }
44633         
44634         this.rotate = (this.rotate > 180) ? 0 : this.rotate + 90;
44635
44636         if(this.isDocument){
44637             this.setThumbBoxSize();
44638             this.setThumbBoxPosition();
44639             this.setCanvasPosition();
44640         }
44641         
44642         this.draw();
44643         
44644         this.fireEvent('rotate', this, 'right');
44645     },
44646     
44647     onRotateFail : function()
44648     {
44649         this.errorEl.show(true);
44650         
44651         var _this = this;
44652         
44653         (function() { _this.errorEl.hide(true); }).defer(this.errorTimeout);
44654     },
44655     
44656     draw : function()
44657     {
44658         this.previewEl.dom.innerHTML = '';
44659         
44660         var canvasEl = document.createElement("canvas");
44661         
44662         var contextEl = canvasEl.getContext("2d");
44663         
44664         canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
44665         canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
44666         var center = this.imageEl.OriginWidth / 2;
44667         
44668         if(this.imageEl.OriginWidth < this.imageEl.OriginHeight){
44669             canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
44670             canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
44671             center = this.imageEl.OriginHeight / 2;
44672         }
44673         
44674         contextEl.scale(this.getScaleLevel(), this.getScaleLevel());
44675         
44676         contextEl.translate(center, center);
44677         contextEl.rotate(this.rotate * Math.PI / 180);
44678
44679         contextEl.drawImage(this.imageEl, 0, 0, this.imageEl.OriginWidth, this.imageEl.OriginHeight, center * -1, center * -1, this.imageEl.OriginWidth, this.imageEl.OriginHeight);
44680         
44681         this.canvasEl = document.createElement("canvas");
44682         
44683         this.contextEl = this.canvasEl.getContext("2d");
44684         
44685         switch (this.rotate) {
44686             case 0 :
44687                 
44688                 this.canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
44689                 this.canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
44690                 
44691                 this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44692                 
44693                 break;
44694             case 90 : 
44695                 
44696                 this.canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
44697                 this.canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
44698                 
44699                 if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
44700                     this.contextEl.drawImage(canvasEl, Math.abs(this.canvasEl.width - this.canvasEl.height), 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44701                     break;
44702                 }
44703                 
44704                 this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44705                 
44706                 break;
44707             case 180 :
44708                 
44709                 this.canvasEl.width = this.imageEl.OriginWidth * this.getScaleLevel();
44710                 this.canvasEl.height = this.imageEl.OriginHeight * this.getScaleLevel();
44711                 
44712                 if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
44713                     this.contextEl.drawImage(canvasEl, 0, Math.abs(this.canvasEl.width - this.canvasEl.height), this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44714                     break;
44715                 }
44716                 
44717                 this.contextEl.drawImage(canvasEl, Math.abs(this.canvasEl.width - this.canvasEl.height), 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44718                 
44719                 break;
44720             case 270 :
44721                 
44722                 this.canvasEl.width = this.imageEl.OriginHeight * this.getScaleLevel();
44723                 this.canvasEl.height = this.imageEl.OriginWidth * this.getScaleLevel();
44724         
44725                 if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
44726                     this.contextEl.drawImage(canvasEl, 0, 0, this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44727                     break;
44728                 }
44729                 
44730                 this.contextEl.drawImage(canvasEl, 0, Math.abs(this.canvasEl.width - this.canvasEl.height), this.canvasEl.width, this.canvasEl.height, 0, 0, this.canvasEl.width, this.canvasEl.height);
44731                 
44732                 break;
44733             default : 
44734                 break;
44735         }
44736         
44737         this.previewEl.appendChild(this.canvasEl);
44738         
44739         this.setCanvasPosition(false);
44740     },
44741     
44742     crop : function()
44743     {
44744         if(!this.canvasLoaded){
44745             return;
44746         }
44747         
44748         var imageCanvas = document.createElement("canvas");
44749         
44750         var imageContext = imageCanvas.getContext("2d");
44751         
44752         imageCanvas.width = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? this.imageEl.OriginWidth : this.imageEl.OriginHeight;
44753         imageCanvas.height = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? this.imageEl.OriginWidth : this.imageEl.OriginHeight;
44754         
44755         var center = imageCanvas.width / 2;
44756         
44757         imageContext.translate(center, center);
44758         
44759         imageContext.rotate(this.rotate * Math.PI / 180);
44760         
44761         imageContext.drawImage(this.imageEl, 0, 0, this.imageEl.OriginWidth, this.imageEl.OriginHeight, center * -1, center * -1, this.imageEl.OriginWidth, this.imageEl.OriginHeight);
44762         
44763         var canvas = document.createElement("canvas");
44764         
44765         var context = canvas.getContext("2d");
44766
44767         canvas.width = this.thumbEl.getWidth() / this.getScaleLevel();
44768         
44769         canvas.height = this.thumbEl.getHeight() / this.getScaleLevel();
44770
44771         switch (this.rotate) {
44772             case 0 :
44773                 
44774                 var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getWidth() / this.getScaleLevel());
44775                 var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getHeight() / this.getScaleLevel());
44776                 
44777                 var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
44778                 var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
44779                 
44780                 var sx = this.thumbEl.getLeft(true) - this.previewEl.getLeft(true);
44781                 var sy = this.thumbEl.getTop(true) - this.previewEl.getTop(true);
44782
44783                 sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
44784                 sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
44785
44786                 if(canvas.width > this.outputMaxWidth) {
44787                     var scale = this.outputMaxWidth / canvas.width;
44788                     canvas.width = canvas.width * scale;
44789                     canvas.height = canvas.height * scale;
44790                     context.scale(scale, scale);
44791                 }
44792
44793                 context.fillStyle = 'white';
44794                 context.fillRect(0, 0, this.thumbEl.getWidth() / this.getScaleLevel(), this.thumbEl.getHeight() / this.getScaleLevel());
44795
44796
44797                 context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
44798                 
44799                 break;
44800             case 90 : 
44801                 
44802                 var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getWidth() / this.getScaleLevel());
44803                 var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getHeight() / this.getScaleLevel());
44804                 
44805                 var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
44806                 var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
44807                 
44808                 var targetWidth = this.minWidth - 2 * x;
44809                 var targetHeight = this.minHeight - 2 * y;
44810                 
44811                 var scale = 1;
44812                 
44813                 if((x == 0 && y == 0) || (x == 0 && y > 0)){
44814                     scale = targetWidth / width;
44815                 }
44816                 
44817                 if(x > 0 && y == 0){
44818                     scale = targetHeight / height;
44819                 }
44820                 
44821                 if(x > 0 && y > 0){
44822                     scale = targetWidth / width;
44823                     
44824                     if(width < height){
44825                         scale = targetHeight / height;
44826                     }
44827                 }
44828                 
44829                 context.scale(scale, scale);
44830                 
44831                 var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
44832                 var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
44833
44834                 sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
44835                 sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
44836                 
44837                 sx += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight) : 0;
44838                 
44839                 context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
44840                 
44841                 break;
44842             case 180 :
44843                 
44844                 var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getWidth() / this.getScaleLevel());
44845                 var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getHeight() / this.getScaleLevel());
44846                 
44847                 var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
44848                 var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
44849                 
44850                 var targetWidth = this.minWidth - 2 * x;
44851                 var targetHeight = this.minHeight - 2 * y;
44852                 
44853                 var scale = 1;
44854                 
44855                 if((x == 0 && y == 0) || (x == 0 && y > 0)){
44856                     scale = targetWidth / width;
44857                 }
44858                 
44859                 if(x > 0 && y == 0){
44860                     scale = targetHeight / height;
44861                 }
44862                 
44863                 if(x > 0 && y > 0){
44864                     scale = targetWidth / width;
44865                     
44866                     if(width < height){
44867                         scale = targetHeight / height;
44868                     }
44869                 }
44870                 
44871                 context.scale(scale, scale);
44872                 
44873                 var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
44874                 var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
44875
44876                 sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
44877                 sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
44878
44879                 sx += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? 0 : Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight);
44880                 sy += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight) : 0;
44881                 
44882                 context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
44883                 
44884                 break;
44885             case 270 :
44886                 
44887                 var width = (this.thumbEl.getWidth() / this.getScaleLevel() > this.imageEl.OriginHeight) ? this.imageEl.OriginHeight : (this.thumbEl.getWidth() / this.getScaleLevel());
44888                 var height = (this.thumbEl.getHeight() / this.getScaleLevel() > this.imageEl.OriginWidth) ? this.imageEl.OriginWidth : (this.thumbEl.getHeight() / this.getScaleLevel());
44889                 
44890                 var x = (this.thumbEl.getLeft(true) > this.previewEl.getLeft(true)) ? 0 : ((this.previewEl.getLeft(true) - this.thumbEl.getLeft(true)) / this.getScaleLevel());
44891                 var y = (this.thumbEl.getTop(true) > this.previewEl.getTop(true)) ? 0 : ((this.previewEl.getTop(true) - this.thumbEl.getTop(true)) / this.getScaleLevel());
44892                 
44893                 var targetWidth = this.minWidth - 2 * x;
44894                 var targetHeight = this.minHeight - 2 * y;
44895                 
44896                 var scale = 1;
44897                 
44898                 if((x == 0 && y == 0) || (x == 0 && y > 0)){
44899                     scale = targetWidth / width;
44900                 }
44901                 
44902                 if(x > 0 && y == 0){
44903                     scale = targetHeight / height;
44904                 }
44905                 
44906                 if(x > 0 && y > 0){
44907                     scale = targetWidth / width;
44908                     
44909                     if(width < height){
44910                         scale = targetHeight / height;
44911                     }
44912                 }
44913                 
44914                 context.scale(scale, scale);
44915                 var sx = Math.min(this.canvasEl.width - this.thumbEl.getWidth(), this.thumbEl.getLeft(true) - this.previewEl.getLeft(true));
44916                 var sy = Math.min(this.canvasEl.height - this.thumbEl.getHeight(), this.thumbEl.getTop(true) - this.previewEl.getTop(true));
44917
44918                 sx = sx < 0 ? 0 : (sx / this.getScaleLevel());
44919                 sy = sy < 0 ? 0 : (sy / this.getScaleLevel());
44920                 
44921                 sy += (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? 0 : Math.abs(this.imageEl.OriginWidth - this.imageEl.OriginHeight);
44922                 
44923                 context.drawImage(imageCanvas, sx, sy, width, height, x, y, width, height);
44924                 
44925                 break;
44926             default : 
44927                 break;
44928         }
44929         
44930         this.cropData = canvas.toDataURL(this.cropType);
44931         
44932         if(this.fireEvent('crop', this, this.cropData) !== false){
44933             this.process(this.file, this.cropData);
44934         }
44935         
44936         return;
44937         
44938     },
44939     
44940     setThumbBoxSize : function()
44941     {
44942         var width, height;
44943         
44944         if(this.isDocument && typeof(this.imageEl) != 'undefined'){
44945             width = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.max(this.minWidth, this.minHeight) : Math.min(this.minWidth, this.minHeight);
44946             height = (this.imageEl.OriginWidth > this.imageEl.OriginHeight) ? Math.min(this.minWidth, this.minHeight) : Math.max(this.minWidth, this.minHeight);
44947             
44948             this.minWidth = width;
44949             this.minHeight = height;
44950             
44951             if(this.rotate == 90 || this.rotate == 270){
44952                 this.minWidth = height;
44953                 this.minHeight = width;
44954             }
44955         }
44956         
44957         height = this.windowSize;
44958         width = Math.ceil(this.minWidth * height / this.minHeight);
44959         
44960         if(this.minWidth > this.minHeight){
44961             width = this.windowSize;
44962             height = Math.ceil(this.minHeight * width / this.minWidth);
44963         }
44964         
44965         this.thumbEl.setStyle({
44966             width : width + 'px',
44967             height : height + 'px'
44968         });
44969
44970         return;
44971             
44972     },
44973     
44974     setThumbBoxPosition : function()
44975     {
44976         var x = Math.ceil((this.bodyEl.getWidth() - this.thumbEl.getWidth()) / 2 );
44977         var y = Math.ceil((this.bodyEl.getHeight() - this.thumbEl.getHeight()) / 2);
44978         
44979         this.thumbEl.setLeft(x);
44980         this.thumbEl.setTop(y);
44981         
44982     },
44983     
44984     baseRotateLevel : function()
44985     {
44986         this.baseRotate = 1;
44987         
44988         if(
44989                 typeof(this.exif) != 'undefined' &&
44990                 typeof(this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']]) != 'undefined' &&
44991                 [1, 3, 6, 8].indexOf(this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']]) != -1
44992         ){
44993             this.baseRotate = this.exif[Roo.dialog.UploadCropbox['tags']['Orientation']];
44994         }
44995         
44996         this.rotate = Roo.dialog.UploadCropbox['Orientation'][this.baseRotate];
44997         
44998     },
44999     
45000     baseScaleLevel : function()
45001     {
45002         var width, height;
45003         
45004         if(this.isDocument){
45005             
45006             if(this.baseRotate == 6 || this.baseRotate == 8){
45007             
45008                 height = this.thumbEl.getHeight();
45009                 this.baseScale = height / this.imageEl.OriginWidth;
45010
45011                 if(this.imageEl.OriginHeight * this.baseScale > this.thumbEl.getWidth()){
45012                     width = this.thumbEl.getWidth();
45013                     this.baseScale = width / this.imageEl.OriginHeight;
45014                 }
45015
45016                 return;
45017             }
45018
45019             height = this.thumbEl.getHeight();
45020             this.baseScale = height / this.imageEl.OriginHeight;
45021
45022             if(this.imageEl.OriginWidth * this.baseScale > this.thumbEl.getWidth()){
45023                 width = this.thumbEl.getWidth();
45024                 this.baseScale = width / this.imageEl.OriginWidth;
45025             }
45026
45027             return;
45028         }
45029         
45030         if(this.baseRotate == 6 || this.baseRotate == 8){
45031             
45032             width = this.thumbEl.getHeight();
45033             this.baseScale = width / this.imageEl.OriginHeight;
45034             
45035             if(this.imageEl.OriginHeight * this.baseScale < this.thumbEl.getWidth()){
45036                 height = this.thumbEl.getWidth();
45037                 this.baseScale = height / this.imageEl.OriginHeight;
45038             }
45039             
45040             if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
45041                 height = this.thumbEl.getWidth();
45042                 this.baseScale = height / this.imageEl.OriginHeight;
45043                 
45044                 if(this.imageEl.OriginWidth * this.baseScale < this.thumbEl.getHeight()){
45045                     width = this.thumbEl.getHeight();
45046                     this.baseScale = width / this.imageEl.OriginWidth;
45047                 }
45048             }
45049             
45050             return;
45051         }
45052         
45053         width = this.thumbEl.getWidth();
45054         this.baseScale = width / this.imageEl.OriginWidth;
45055         
45056         if(this.imageEl.OriginHeight * this.baseScale < this.thumbEl.getHeight()){
45057             height = this.thumbEl.getHeight();
45058             this.baseScale = height / this.imageEl.OriginHeight;
45059         }
45060         
45061         if(this.imageEl.OriginWidth > this.imageEl.OriginHeight){
45062             
45063             height = this.thumbEl.getHeight();
45064             this.baseScale = height / this.imageEl.OriginHeight;
45065             
45066             if(this.imageEl.OriginWidth * this.baseScale < this.thumbEl.getWidth()){
45067                 width = this.thumbEl.getWidth();
45068                 this.baseScale = width / this.imageEl.OriginWidth;
45069             }
45070             
45071         }
45072
45073         if(this.imageEl.OriginWidth < this.minWidth || this.imageEl.OriginHeight < this.minHeight) {
45074             this.baseScale = width / this.minWidth;
45075         }
45076
45077         return;
45078     },
45079     
45080     getScaleLevel : function()
45081     {
45082         return this.baseScale * Math.pow(1.02, this.scale);
45083     },
45084     
45085     onTouchStart : function(e)
45086     {
45087         if(!this.canvasLoaded){
45088             this.beforeSelectFile(e);
45089             return;
45090         }
45091         
45092         var touches = e.browserEvent.touches;
45093         
45094         if(!touches){
45095             return;
45096         }
45097         
45098         if(touches.length == 1){
45099             this.onMouseDown(e);
45100             return;
45101         }
45102         
45103         if(touches.length != 2){
45104             return;
45105         }
45106         
45107         var coords = [];
45108         
45109         for(var i = 0, finger; finger = touches[i]; i++){
45110             coords.push(finger.pageX, finger.pageY);
45111         }
45112         
45113         var x = Math.pow(coords[0] - coords[2], 2);
45114         var y = Math.pow(coords[1] - coords[3], 2);
45115         
45116         this.startDistance = Math.sqrt(x + y);
45117         
45118         this.startScale = this.scale;
45119         
45120         this.pinching = true;
45121         this.dragable = false;
45122         
45123     },
45124     
45125     onTouchMove : function(e)
45126     {
45127         if(!this.pinching && !this.dragable){
45128             return;
45129         }
45130         
45131         var touches = e.browserEvent.touches;
45132         
45133         if(!touches){
45134             return;
45135         }
45136         
45137         if(this.dragable){
45138             this.onMouseMove(e);
45139             return;
45140         }
45141         
45142         var coords = [];
45143         
45144         for(var i = 0, finger; finger = touches[i]; i++){
45145             coords.push(finger.pageX, finger.pageY);
45146         }
45147         
45148         var x = Math.pow(coords[0] - coords[2], 2);
45149         var y = Math.pow(coords[1] - coords[3], 2);
45150         
45151         this.endDistance = Math.sqrt(x + y);
45152         
45153         this.scale = this.startScale + Math.floor(Math.log(this.endDistance / this.startDistance) / Math.log(1.1));
45154         
45155         if(!this.zoomable()){
45156             this.scale = this.startScale;
45157             return;
45158         }
45159         
45160         this.draw();
45161         
45162     },
45163     
45164     onTouchEnd : function(e)
45165     {
45166         this.pinching = false;
45167         this.dragable = false;
45168         
45169     },
45170     
45171     process : function(file, crop)
45172     {
45173         if(this.loadMask){
45174             this.maskEl.mask(this.loadingText);
45175         }
45176         
45177         this.xhr = new XMLHttpRequest();
45178         
45179         file.xhr = this.xhr;
45180
45181         this.xhr.open(this.method, this.url, true);
45182         
45183         var headers = {
45184             "Accept": "application/json",
45185             "Cache-Control": "no-cache",
45186             "X-Requested-With": "XMLHttpRequest"
45187         };
45188         
45189         for (var headerName in headers) {
45190             var headerValue = headers[headerName];
45191             if (headerValue) {
45192                 this.xhr.setRequestHeader(headerName, headerValue);
45193             }
45194         }
45195         
45196         var _this = this;
45197         
45198         this.xhr.onload = function()
45199         {
45200             _this.xhrOnLoad(_this.xhr);
45201         }
45202         
45203         this.xhr.onerror = function()
45204         {
45205             _this.xhrOnError(_this.xhr);
45206         }
45207         
45208         var formData = new FormData();
45209
45210         formData.append('returnHTML', 'NO');
45211
45212         if(crop){
45213             formData.append('crop', crop);
45214             var blobBin = atob(crop.split(',')[1]);
45215             var array = [];
45216             for(var i = 0; i < blobBin.length; i++) {
45217                 array.push(blobBin.charCodeAt(i));
45218             }
45219             var croppedFile =new Blob([new Uint8Array(array)], {type: this.cropType});
45220             formData.append(this.paramName, croppedFile, file.name);
45221         }
45222         
45223         if(typeof(file.filename) != 'undefined'){
45224             formData.append('filename', file.filename);
45225         }
45226         
45227         if(typeof(file.mimetype) != 'undefined'){
45228             formData.append('mimetype', file.mimetype);
45229         }
45230
45231         if(this.fireEvent('arrange', this, formData) != false){
45232             this.xhr.send(formData);
45233         };
45234     },
45235     
45236     xhrOnLoad : function(xhr)
45237     {
45238         if(this.loadMask){
45239             this.maskEl.unmask();
45240         }
45241         
45242         if (xhr.readyState !== 4) {
45243             this.fireEvent('exception', this, xhr);
45244             return;
45245         }
45246
45247         var response = Roo.decode(xhr.responseText);
45248         
45249         if(!response.success){
45250             this.fireEvent('exception', this, xhr);
45251             return;
45252         }
45253         
45254         var response = Roo.decode(xhr.responseText);
45255         
45256         this.fireEvent('upload', this, response);
45257         
45258     },
45259     
45260     xhrOnError : function()
45261     {
45262         if(this.loadMask){
45263             this.maskEl.unmask();
45264         }
45265         
45266         Roo.log('xhr on error');
45267         
45268         var response = Roo.decode(xhr.responseText);
45269           
45270         Roo.log(response);
45271         
45272     },
45273     
45274     prepare : function(file)
45275     {   
45276         if(this.loadMask){
45277             this.maskEl.mask(this.loadingText);
45278         }
45279         
45280         this.file = false;
45281         this.exif = {};
45282         
45283         if(typeof(file) === 'string'){
45284             this.loadCanvas(file);
45285             return;
45286         }
45287         
45288         if(!file || !this.urlAPI){
45289             return;
45290         }
45291         
45292         this.file = file;
45293         if(typeof(file.type) != 'undefined' && file.type.length != 0) {
45294             this.cropType = file.type;
45295         }
45296         
45297         var _this = this;
45298         
45299         if(this.fireEvent('prepare', this, this.file) != false){
45300             
45301             var reader = new FileReader();
45302             
45303             reader.onload = function (e) {
45304                 if (e.target.error) {
45305                     Roo.log(e.target.error);
45306                     return;
45307                 }
45308                 
45309                 var buffer = e.target.result,
45310                     dataView = new DataView(buffer),
45311                     offset = 2,
45312                     maxOffset = dataView.byteLength - 4,
45313                     markerBytes,
45314                     markerLength;
45315                 
45316                 if (dataView.getUint16(0) === 0xffd8) {
45317                     while (offset < maxOffset) {
45318                         markerBytes = dataView.getUint16(offset);
45319                         
45320                         if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) || markerBytes === 0xfffe) {
45321                             markerLength = dataView.getUint16(offset + 2) + 2;
45322                             if (offset + markerLength > dataView.byteLength) {
45323                                 Roo.log('Invalid meta data: Invalid segment size.');
45324                                 break;
45325                             }
45326                             
45327                             if(markerBytes == 0xffe1){
45328                                 _this.parseExifData(
45329                                     dataView,
45330                                     offset,
45331                                     markerLength
45332                                 );
45333                             }
45334                             
45335                             offset += markerLength;
45336                             
45337                             continue;
45338                         }
45339                         
45340                         break;
45341                     }
45342                     
45343                 }
45344                 
45345                 var url = _this.urlAPI.createObjectURL(_this.file);
45346                 
45347                 _this.loadCanvas(url);
45348                 
45349                 return;
45350             }
45351             
45352             reader.readAsArrayBuffer(this.file);
45353             
45354         }
45355         
45356     },
45357     
45358     parseExifData : function(dataView, offset, length)
45359     {
45360         var tiffOffset = offset + 10,
45361             littleEndian,
45362             dirOffset;
45363     
45364         if (dataView.getUint32(offset + 4) !== 0x45786966) {
45365             // No Exif data, might be XMP data instead
45366             return;
45367         }
45368         
45369         // Check for the ASCII code for "Exif" (0x45786966):
45370         if (dataView.getUint32(offset + 4) !== 0x45786966) {
45371             // No Exif data, might be XMP data instead
45372             return;
45373         }
45374         if (tiffOffset + 8 > dataView.byteLength) {
45375             Roo.log('Invalid Exif data: Invalid segment size.');
45376             return;
45377         }
45378         // Check for the two null bytes:
45379         if (dataView.getUint16(offset + 8) !== 0x0000) {
45380             Roo.log('Invalid Exif data: Missing byte alignment offset.');
45381             return;
45382         }
45383         // Check the byte alignment:
45384         switch (dataView.getUint16(tiffOffset)) {
45385         case 0x4949:
45386             littleEndian = true;
45387             break;
45388         case 0x4D4D:
45389             littleEndian = false;
45390             break;
45391         default:
45392             Roo.log('Invalid Exif data: Invalid byte alignment marker.');
45393             return;
45394         }
45395         // Check for the TIFF tag marker (0x002A):
45396         if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
45397             Roo.log('Invalid Exif data: Missing TIFF marker.');
45398             return;
45399         }
45400         // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
45401         dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
45402         
45403         this.parseExifTags(
45404             dataView,
45405             tiffOffset,
45406             tiffOffset + dirOffset,
45407             littleEndian
45408         );
45409     },
45410     
45411     parseExifTags : function(dataView, tiffOffset, dirOffset, littleEndian)
45412     {
45413         var tagsNumber,
45414             dirEndOffset,
45415             i;
45416         if (dirOffset + 6 > dataView.byteLength) {
45417             Roo.log('Invalid Exif data: Invalid directory offset.');
45418             return;
45419         }
45420         tagsNumber = dataView.getUint16(dirOffset, littleEndian);
45421         dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
45422         if (dirEndOffset + 4 > dataView.byteLength) {
45423             Roo.log('Invalid Exif data: Invalid directory size.');
45424             return;
45425         }
45426         for (i = 0; i < tagsNumber; i += 1) {
45427             this.parseExifTag(
45428                 dataView,
45429                 tiffOffset,
45430                 dirOffset + 2 + 12 * i, // tag offset
45431                 littleEndian
45432             );
45433         }
45434         // Return the offset to the next directory:
45435         return dataView.getUint32(dirEndOffset, littleEndian);
45436     },
45437     
45438     parseExifTag : function (dataView, tiffOffset, offset, littleEndian) 
45439     {
45440         var tag = dataView.getUint16(offset, littleEndian);
45441         
45442         this.exif[tag] = this.getExifValue(
45443             dataView,
45444             tiffOffset,
45445             offset,
45446             dataView.getUint16(offset + 2, littleEndian), // tag type
45447             dataView.getUint32(offset + 4, littleEndian), // tag length
45448             littleEndian
45449         );
45450     },
45451     
45452     getExifValue : function (dataView, tiffOffset, offset, type, length, littleEndian)
45453     {
45454         var tagType = Roo.dialog.UploadCropbox.exifTagTypes[type],
45455             tagSize,
45456             dataOffset,
45457             values,
45458             i,
45459             str,
45460             c;
45461     
45462         if (!tagType) {
45463             Roo.log('Invalid Exif data: Invalid tag type.');
45464             return;
45465         }
45466         
45467         tagSize = tagType.size * length;
45468         // Determine if the value is contained in the dataOffset bytes,
45469         // or if the value at the dataOffset is a pointer to the actual data:
45470         dataOffset = tagSize > 4 ?
45471                 tiffOffset + dataView.getUint32(offset + 8, littleEndian) : (offset + 8);
45472         if (dataOffset + tagSize > dataView.byteLength) {
45473             Roo.log('Invalid Exif data: Invalid data offset.');
45474             return;
45475         }
45476         if (length === 1) {
45477             return tagType.getValue(dataView, dataOffset, littleEndian);
45478         }
45479         values = [];
45480         for (i = 0; i < length; i += 1) {
45481             values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian);
45482         }
45483         
45484         if (tagType.ascii) {
45485             str = '';
45486             // Concatenate the chars:
45487             for (i = 0; i < values.length; i += 1) {
45488                 c = values[i];
45489                 // Ignore the terminating NULL byte(s):
45490                 if (c === '\u0000') {
45491                     break;
45492                 }
45493                 str += c;
45494             }
45495             return str;
45496         }
45497         return values;
45498     }
45499     
45500 });
45501
45502 Roo.apply(Roo.dialog.UploadCropbox, {
45503     tags : {
45504         'Orientation': 0x0112
45505     },
45506     
45507     Orientation: {
45508             1: 0, //'top-left',
45509 //            2: 'top-right',
45510             3: 180, //'bottom-right',
45511 //            4: 'bottom-left',
45512 //            5: 'left-top',
45513             6: 90, //'right-top',
45514 //            7: 'right-bottom',
45515             8: 270 //'left-bottom'
45516     },
45517     
45518     exifTagTypes : {
45519         // byte, 8-bit unsigned int:
45520         1: {
45521             getValue: function (dataView, dataOffset) {
45522                 return dataView.getUint8(dataOffset);
45523             },
45524             size: 1
45525         },
45526         // ascii, 8-bit byte:
45527         2: {
45528             getValue: function (dataView, dataOffset) {
45529                 return String.fromCharCode(dataView.getUint8(dataOffset));
45530             },
45531             size: 1,
45532             ascii: true
45533         },
45534         // short, 16 bit int:
45535         3: {
45536             getValue: function (dataView, dataOffset, littleEndian) {
45537                 return dataView.getUint16(dataOffset, littleEndian);
45538             },
45539             size: 2
45540         },
45541         // long, 32 bit int:
45542         4: {
45543             getValue: function (dataView, dataOffset, littleEndian) {
45544                 return dataView.getUint32(dataOffset, littleEndian);
45545             },
45546             size: 4
45547         },
45548         // rational = two long values, first is numerator, second is denominator:
45549         5: {
45550             getValue: function (dataView, dataOffset, littleEndian) {
45551                 return dataView.getUint32(dataOffset, littleEndian) /
45552                     dataView.getUint32(dataOffset + 4, littleEndian);
45553             },
45554             size: 8
45555         },
45556         // slong, 32 bit signed int:
45557         9: {
45558             getValue: function (dataView, dataOffset, littleEndian) {
45559                 return dataView.getInt32(dataOffset, littleEndian);
45560             },
45561             size: 4
45562         },
45563         // srational, two slongs, first is numerator, second is denominator:
45564         10: {
45565             getValue: function (dataView, dataOffset, littleEndian) {
45566                 return dataView.getInt32(dataOffset, littleEndian) /
45567                     dataView.getInt32(dataOffset + 4, littleEndian);
45568             },
45569             size: 8
45570         }
45571     },
45572     
45573     footer : {
45574         STANDARD : [
45575             {
45576                 tag : 'div',
45577                 cls : 'btn-group roo-upload-cropbox-rotate-left',
45578                 action : 'rotate-left',
45579                 cn : [
45580                     {
45581                         tag : 'button',
45582                         cls : 'btn btn-default',
45583                         html : '<i class="fa fa-undo"></i>'
45584                     }
45585                 ]
45586             },
45587             {
45588                 tag : 'div',
45589                 cls : 'btn-group roo-upload-cropbox-picture',
45590                 action : 'picture',
45591                 cn : [
45592                     {
45593                         tag : 'button',
45594                         cls : 'btn btn-default',
45595                         html : '<i class="fa fa-picture-o"></i>'
45596                     }
45597                 ]
45598             },
45599             {
45600                 tag : 'div',
45601                 cls : 'btn-group roo-upload-cropbox-rotate-right',
45602                 action : 'rotate-right',
45603                 cn : [
45604                     {
45605                         tag : 'button',
45606                         cls : 'btn btn-default',
45607                         html : '<i class="fa fa-repeat"></i>'
45608                     }
45609                 ]
45610             }
45611         ],
45612         DOCUMENT : [
45613             {
45614                 tag : 'div',
45615                 cls : 'btn-group roo-upload-cropbox-rotate-left',
45616                 action : 'rotate-left',
45617                 cn : [
45618                     {
45619                         tag : 'button',
45620                         cls : 'btn btn-default',
45621                         html : '<i class="fa fa-undo"></i>'
45622                     }
45623                 ]
45624             },
45625             {
45626                 tag : 'div',
45627                 cls : 'btn-group roo-upload-cropbox-download',
45628                 action : 'download',
45629                 cn : [
45630                     {
45631                         tag : 'button',
45632                         cls : 'btn btn-default',
45633                         html : '<i class="fa fa-download"></i>'
45634                     }
45635                 ]
45636             },
45637             {
45638                 tag : 'div',
45639                 cls : 'btn-group roo-upload-cropbox-crop',
45640                 action : 'crop',
45641                 cn : [
45642                     {
45643                         tag : 'button',
45644                         cls : 'btn btn-default',
45645                         html : '<i class="fa fa-crop"></i>'
45646                     }
45647                 ]
45648             },
45649             {
45650                 tag : 'div',
45651                 cls : 'btn-group roo-upload-cropbox-trash',
45652                 action : 'trash',
45653                 cn : [
45654                     {
45655                         tag : 'button',
45656                         cls : 'btn btn-default',
45657                         html : '<i class="fa fa-trash"></i>'
45658                     }
45659                 ]
45660             },
45661             {
45662                 tag : 'div',
45663                 cls : 'btn-group roo-upload-cropbox-rotate-right',
45664                 action : 'rotate-right',
45665                 cn : [
45666                     {
45667                         tag : 'button',
45668                         cls : 'btn btn-default',
45669                         html : '<i class="fa fa-repeat"></i>'
45670                     }
45671                 ]
45672             }
45673         ],
45674         ROTATOR : [
45675             {
45676                 tag : 'div',
45677                 cls : 'btn-group roo-upload-cropbox-rotate-left',
45678                 action : 'rotate-left',
45679                 cn : [
45680                     {
45681                         tag : 'button',
45682                         cls : 'btn btn-default',
45683                         html : '<i class="fa fa-undo"></i>'
45684                     }
45685                 ]
45686             },
45687             {
45688                 tag : 'div',
45689                 cls : 'btn-group roo-upload-cropbox-rotate-right',
45690                 action : 'rotate-right',
45691                 cn : [
45692                     {
45693                         tag : 'button',
45694                         cls : 'btn btn-default',
45695                         html : '<i class="fa fa-repeat"></i>'
45696                     }
45697                 ]
45698             }
45699         ],
45700         CENTER : [
45701             {
45702                 tag : 'div',
45703                 cls : 'btn-group roo-upload-cropbox-center',
45704                 action : 'center',
45705                 cn : [
45706                     {
45707                         tag : 'button',
45708                         cls : 'btn btn-default',
45709                         html : 'CENTER'
45710                     }
45711                 ]
45712             }
45713         ]
45714     }
45715 });