Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / docs / source / MultiSelect.html
1 <html>
2 <head>
3   <title>The source code</title>
4     <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
5     <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
6 </head>
7 <body  onload="prettyPrint();">
8     <pre class="prettyprint lang-js">Ext.ns('Ext.ux.form');\r
9 \r
10 <div id="cls-Ext.ux.form.MultiSelect"></div>/**\r
11  * @class Ext.ux.form.MultiSelect\r
12  * @extends Ext.form.Field\r
13  * A control that allows selection and form submission of multiple list items.\r
14  *\r
15  *  @history\r
16  *    2008-06-19 bpm Original code contributed by Toby Stuart (with contributions from Robert Williams)\r
17  *    2008-06-19 bpm Docs and demo code clean up\r
18  *\r
19  * @constructor\r
20  * Create a new MultiSelect\r
21  * @param {Object} config Configuration options\r
22  * @xtype multiselect \r
23  */\r
24 Ext.ux.form.MultiSelect = Ext.extend(Ext.form.Field,  {\r
25     <div id="cfg-Ext.ux.form.MultiSelect-legend"></div>/**\r
26      * @cfg {String} legend Wraps the object with a fieldset and specified legend.\r
27      */\r
28     <div id="cfg-Ext.ux.form.MultiSelect-view"></div>/**\r
29      * @cfg {Ext.ListView} view The {@link Ext.ListView} used to render the multiselect list.\r
30      */\r
31     <div id="cfg-Ext.ux.form.MultiSelect-dragGroup"></div>/**\r
32      * @cfg {String/Array} dragGroup The ddgroup name(s) for the MultiSelect DragZone (defaults to undefined).\r
33      */\r
34     <div id="cfg-Ext.ux.form.MultiSelect-dropGroup"></div>/**\r
35      * @cfg {String/Array} dropGroup The ddgroup name(s) for the MultiSelect DropZone (defaults to undefined).\r
36      */\r
37     <div id="cfg-Ext.ux.form.MultiSelect-ddReorder"></div>/**\r
38      * @cfg {Boolean} ddReorder Whether the items in the MultiSelect list are drag/drop reorderable (defaults to false).\r
39      */\r
40     ddReorder:false,\r
41     <div id="cfg-Ext.ux.form.MultiSelect-tbar"></div>/**\r
42      * @cfg {Object/Array} tbar The top toolbar of the control. This can be a {@link Ext.Toolbar} object, a\r
43      * toolbar config, or an array of buttons/button configs to be added to the toolbar.\r
44      */\r
45     <div id="cfg-Ext.ux.form.MultiSelect-appendOnly"></div>/**\r
46      * @cfg {String} appendOnly True if the list should only allow append drops when drag/drop is enabled\r
47      * (use for lists which are sorted, defaults to false).\r
48      */\r
49     appendOnly:false,\r
50     <div id="cfg-Ext.ux.form.MultiSelect-width"></div>/**\r
51      * @cfg {Number} width Width in pixels of the control (defaults to 100).\r
52      */\r
53     width:100,\r
54     <div id="cfg-Ext.ux.form.MultiSelect-height"></div>/**\r
55      * @cfg {Number} height Height in pixels of the control (defaults to 100).\r
56      */\r
57     height:100,\r
58     <div id="cfg-Ext.ux.form.MultiSelect-displayField"></div>/**\r
59      * @cfg {String/Number} displayField Name/Index of the desired display field in the dataset (defaults to 0).\r
60      */\r
61     displayField:0,\r
62     <div id="cfg-Ext.ux.form.MultiSelect-valueField"></div>/**\r
63      * @cfg {String/Number} valueField Name/Index of the desired value field in the dataset (defaults to 1).\r
64      */\r
65     valueField:1,\r
66     <div id="cfg-Ext.ux.form.MultiSelect-allowBlank"></div>/**\r
67      * @cfg {Boolean} allowBlank False to require at least one item in the list to be selected, true to allow no\r
68      * selection (defaults to true).\r
69      */\r
70     allowBlank:true,\r
71     <div id="cfg-Ext.ux.form.MultiSelect-minSelections"></div>/**\r
72      * @cfg {Number} minSelections Minimum number of selections allowed (defaults to 0).\r
73      */\r
74     minSelections:0,\r
75     <div id="cfg-Ext.ux.form.MultiSelect-maxSelections"></div>/**\r
76      * @cfg {Number} maxSelections Maximum number of selections allowed (defaults to Number.MAX_VALUE).\r
77      */\r
78     maxSelections:Number.MAX_VALUE,\r
79     <div id="cfg-Ext.ux.form.MultiSelect-blankText"></div>/**\r
80      * @cfg {String} blankText Default text displayed when the control contains no items (defaults to the same value as\r
81      * {@link Ext.form.TextField#blankText}.\r
82      */\r
83     blankText:Ext.form.TextField.prototype.blankText,\r
84     <div id="cfg-Ext.ux.form.MultiSelect-minSelectionsText"></div>/**\r
85      * @cfg {String} minSelectionsText Validation message displayed when {@link #minSelections} is not met (defaults to 'Minimum {0}\r
86      * item(s) required').  The {0} token will be replaced by the value of {@link #minSelections}.\r
87      */\r
88     minSelectionsText:'Minimum {0} item(s) required',\r
89     <div id="cfg-Ext.ux.form.MultiSelect-maxSelectionsText"></div>/**\r
90      * @cfg {String} maxSelectionsText Validation message displayed when {@link #maxSelections} is not met (defaults to 'Maximum {0}\r
91      * item(s) allowed').  The {0} token will be replaced by the value of {@link #maxSelections}.\r
92      */\r
93     maxSelectionsText:'Maximum {0} item(s) allowed',\r
94     <div id="cfg-Ext.ux.form.MultiSelect-delimiter"></div>/**\r
95      * @cfg {String} delimiter The string used to delimit between items when set or returned as a string of values\r
96      * (defaults to ',').\r
97      */\r
98     delimiter:',',\r
99     <div id="cfg-Ext.ux.form.MultiSelect-store"></div>/**\r
100      * @cfg {Ext.data.Store/Array} store The data source to which this MultiSelect is bound (defaults to <tt>undefined</tt>).\r
101      * Acceptable values for this property are:\r
102      * <div class="mdetail-params"><ul>\r
103      * <li><b>any {@link Ext.data.Store Store} subclass</b></li>\r
104      * <li><b>an Array</b> : Arrays will be converted to a {@link Ext.data.ArrayStore} internally.\r
105      * <div class="mdetail-params"><ul>\r
106      * <li><b>1-dimensional array</b> : (e.g., <tt>['Foo','Bar']</tt>)<div class="sub-desc">\r
107      * A 1-dimensional array will automatically be expanded (each array item will be the combo\r
108      * {@link #valueField value} and {@link #displayField text})</div></li>\r
109      * <li><b>2-dimensional array</b> : (e.g., <tt>[['f','Foo'],['b','Bar']]</tt>)<div class="sub-desc">\r
110      * For a multi-dimensional array, the value in index 0 of each item will be assumed to be the combo\r
111      * {@link #valueField value}, while the value at index 1 is assumed to be the combo {@link #displayField text}.\r
112      * </div></li></ul></div></li></ul></div>\r
113      */\r
114 \r
115     // private\r
116     defaultAutoCreate : {tag: "div"},\r
117 \r
118     // private\r
119     initComponent: function(){\r
120         Ext.ux.form.MultiSelect.superclass.initComponent.call(this);\r
121 \r
122         if(Ext.isArray(this.store)){\r
123             if (Ext.isArray(this.store[0])){\r
124                 this.store = new Ext.data.ArrayStore({\r
125                     fields: ['value','text'],\r
126                     data: this.store\r
127                 });\r
128                 this.valueField = 'value';\r
129             }else{\r
130                 this.store = new Ext.data.ArrayStore({\r
131                     fields: ['text'],\r
132                     data: this.store,\r
133                     expandData: true\r
134                 });\r
135                 this.valueField = 'text';\r
136             }\r
137             this.displayField = 'text';\r
138         } else {\r
139             this.store = Ext.StoreMgr.lookup(this.store);\r
140         }\r
141 \r
142         this.addEvents({\r
143             'dblclick' : true,\r
144             'click' : true,\r
145             'change' : true,\r
146             'drop' : true\r
147         });\r
148     },\r
149 \r
150     // private\r
151     onRender: function(ct, position){\r
152         Ext.ux.form.MultiSelect.superclass.onRender.call(this, ct, position);\r
153 \r
154         var fs = this.fs = new Ext.form.FieldSet({\r
155             renderTo: this.el,\r
156             title: this.legend,\r
157             height: this.height,\r
158             width: this.width,\r
159             style: "padding:0;",\r
160             tbar: this.tbar\r
161         });\r
162         fs.body.addClass('ux-mselect');\r
163 \r
164         this.view = new Ext.ListView({\r
165             multiSelect: true,\r
166             store: this.store,\r
167             columns: [{ header: 'Value', width: 1, dataIndex: this.displayField }],\r
168             hideHeaders: true\r
169         });\r
170 \r
171         fs.add(this.view);\r
172 \r
173         this.view.on('click', this.onViewClick, this);\r
174         this.view.on('beforeclick', this.onViewBeforeClick, this);\r
175         this.view.on('dblclick', this.onViewDblClick, this);\r
176 \r
177         this.hiddenName = this.name || Ext.id();\r
178         var hiddenTag = { tag: "input", type: "hidden", value: "", name: this.hiddenName };\r
179         this.hiddenField = this.el.createChild(hiddenTag);\r
180         this.hiddenField.dom.disabled = this.hiddenName != this.name;\r
181         fs.doLayout();\r
182     },\r
183 \r
184     // private\r
185     afterRender: function(){\r
186         Ext.ux.form.MultiSelect.superclass.afterRender.call(this);\r
187 \r
188         if (this.ddReorder && !this.dragGroup && !this.dropGroup){\r
189             this.dragGroup = this.dropGroup = 'MultiselectDD-' + Ext.id();\r
190         }\r
191 \r
192         if (this.draggable || this.dragGroup){\r
193             this.dragZone = new Ext.ux.form.MultiSelect.DragZone(this, {\r
194                 ddGroup: this.dragGroup\r
195             });\r
196         }\r
197         if (this.droppable || this.dropGroup){\r
198             this.dropZone = new Ext.ux.form.MultiSelect.DropZone(this, {\r
199                 ddGroup: this.dropGroup\r
200             });\r
201         }\r
202     },\r
203 \r
204     // private\r
205     onViewClick: function(vw, index, node, e) {\r
206         this.fireEvent('change', this, this.getValue(), this.hiddenField.dom.value);\r
207         this.hiddenField.dom.value = this.getValue();\r
208         this.fireEvent('click', this, e);\r
209         this.validate();\r
210     },\r
211 \r
212     // private\r
213     onViewBeforeClick: function(vw, index, node, e) {\r
214         if (this.disabled) {return false;}\r
215     },\r
216 \r
217     // private\r
218     onViewDblClick : function(vw, index, node, e) {\r
219         return this.fireEvent('dblclick', vw, index, node, e);\r
220     },\r
221 \r
222     <div id="method-Ext.ux.form.MultiSelect-getValue"></div>/**\r
223      * Returns an array of data values for the selected items in the list. The values will be separated\r
224      * by {@link #delimiter}.\r
225      * @return {Array} value An array of string data values\r
226      */\r
227     getValue: function(valueField){\r
228         var returnArray = [];\r
229         var selectionsArray = this.view.getSelectedIndexes();\r
230         if (selectionsArray.length == 0) {return '';}\r
231         for (var i=0; i<selectionsArray.length; i++) {\r
232             returnArray.push(this.store.getAt(selectionsArray[i]).get((valueField != null) ? valueField : this.valueField));\r
233         }\r
234         return returnArray.join(this.delimiter);\r
235     },\r
236 \r
237     <div id="method-Ext.ux.form.MultiSelect-setValue"></div>/**\r
238      * Sets a delimited string (using {@link #delimiter}) or array of data values into the list.\r
239      * @param {String/Array} values The values to set\r
240      */\r
241     setValue: function(values) {\r
242         var index;\r
243         var selections = [];\r
244         this.view.clearSelections();\r
245         this.hiddenField.dom.value = '';\r
246 \r
247         if (!values || (values == '')) { return; }\r
248 \r
249         if (!Ext.isArray(values)) { values = values.split(this.delimiter); }\r
250         for (var i=0; i<values.length; i++) {\r
251             index = this.view.store.indexOf(this.view.store.query(this.valueField,\r
252                 new RegExp('^' + values[i] + '$', "i")).itemAt(0));\r
253             selections.push(index);\r
254         }\r
255         this.view.select(selections);\r
256         this.hiddenField.dom.value = this.getValue();\r
257         this.validate();\r
258     },\r
259 \r
260     // inherit docs\r
261     reset : function() {\r
262         this.setValue('');\r
263     },\r
264 \r
265     // inherit docs\r
266     getRawValue: function(valueField) {\r
267         var tmp = this.getValue(valueField);\r
268         if (tmp.length) {\r
269             tmp = tmp.split(this.delimiter);\r
270         }\r
271         else {\r
272             tmp = [];\r
273         }\r
274         return tmp;\r
275     },\r
276 \r
277     // inherit docs\r
278     setRawValue: function(values){\r
279         setValue(values);\r
280     },\r
281 \r
282     // inherit docs\r
283     validateValue : function(value){\r
284         if (value.length < 1) { // if it has no value\r
285              if (this.allowBlank) {\r
286                  this.clearInvalid();\r
287                  return true;\r
288              } else {\r
289                  this.markInvalid(this.blankText);\r
290                  return false;\r
291              }\r
292         }\r
293         if (value.length < this.minSelections) {\r
294             this.markInvalid(String.format(this.minSelectionsText, this.minSelections));\r
295             return false;\r
296         }\r
297         if (value.length > this.maxSelections) {\r
298             this.markInvalid(String.format(this.maxSelectionsText, this.maxSelections));\r
299             return false;\r
300         }\r
301         return true;\r
302     },\r
303 \r
304     // inherit docs\r
305     disable: function(){\r
306         this.disabled = true;\r
307         this.hiddenField.dom.disabled = true;\r
308         this.fs.disable();\r
309     },\r
310 \r
311     // inherit docs\r
312     enable: function(){\r
313         this.disabled = false;\r
314         this.hiddenField.dom.disabled = false;\r
315         this.fs.enable();\r
316     },\r
317 \r
318     // inherit docs\r
319     destroy: function(){\r
320         Ext.destroy(this.fs, this.dragZone, this.dropZone);\r
321         Ext.ux.form.MultiSelect.superclass.destroy.call(this);\r
322     }\r
323 });\r
324 \r
325 \r
326 Ext.reg('multiselect', Ext.ux.form.MultiSelect);\r
327 \r
328 //backwards compat\r
329 Ext.ux.Multiselect = Ext.ux.form.MultiSelect;\r
330 \r
331 \r
332 Ext.ux.form.MultiSelect.DragZone = function(ms, config){\r
333     this.ms = ms;\r
334     this.view = ms.view;\r
335     var ddGroup = config.ddGroup || 'MultiselectDD';\r
336     var dd;\r
337     if (Ext.isArray(ddGroup)){\r
338         dd = ddGroup.shift();\r
339     } else {\r
340         dd = ddGroup;\r
341         ddGroup = null;\r
342     }\r
343     Ext.ux.form.MultiSelect.DragZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
344     this.setDraggable(ddGroup);\r
345 };\r
346 \r
347 Ext.extend(Ext.ux.form.MultiSelect.DragZone, Ext.dd.DragZone, {\r
348     onInitDrag : function(x, y){\r
349         var el = Ext.get(this.dragData.ddel.cloneNode(true));\r
350         this.proxy.update(el.dom);\r
351         el.setWidth(el.child('em').getWidth());\r
352         this.onStartDrag(x, y);\r
353         return true;\r
354     },\r
355     \r
356     // private\r
357     collectSelection: function(data) {\r
358         data.repairXY = Ext.fly(this.view.getSelectedNodes()[0]).getXY();\r
359         var i = 0;\r
360         this.view.store.each(function(rec){\r
361             if (this.view.isSelected(i)) {\r
362                 var n = this.view.getNode(i);\r
363                 var dragNode = n.cloneNode(true);\r
364                 dragNode.id = Ext.id();\r
365                 data.ddel.appendChild(dragNode);\r
366                 data.records.push(this.view.store.getAt(i));\r
367                 data.viewNodes.push(n);\r
368             }\r
369             i++;\r
370         }, this);\r
371     },\r
372 \r
373     // override\r
374     onEndDrag: function(data, e) {\r
375         var d = Ext.get(this.dragData.ddel);\r
376         if (d && d.hasClass("multi-proxy")) {\r
377             d.remove();\r
378         }\r
379     },\r
380 \r
381     // override\r
382     getDragData: function(e){\r
383         var target = this.view.findItemFromChild(e.getTarget());\r
384         if(target) {\r
385             if (!this.view.isSelected(target) && !e.ctrlKey && !e.shiftKey) {\r
386                 this.view.select(target);\r
387                 this.ms.setValue(this.ms.getValue());\r
388             }\r
389             if (this.view.getSelectionCount() == 0 || e.ctrlKey || e.shiftKey) return false;\r
390             var dragData = {\r
391                 sourceView: this.view,\r
392                 viewNodes: [],\r
393                 records: []\r
394             };\r
395             if (this.view.getSelectionCount() == 1) {\r
396                 var i = this.view.getSelectedIndexes()[0];\r
397                 var n = this.view.getNode(i);\r
398                 dragData.viewNodes.push(dragData.ddel = n);\r
399                 dragData.records.push(this.view.store.getAt(i));\r
400                 dragData.repairXY = Ext.fly(n).getXY();\r
401             } else {\r
402                 dragData.ddel = document.createElement('div');\r
403                 dragData.ddel.className = 'multi-proxy';\r
404                 this.collectSelection(dragData);\r
405             }\r
406             return dragData;\r
407         }\r
408         return false;\r
409     },\r
410 \r
411     // override the default repairXY.\r
412     getRepairXY : function(e){\r
413         return this.dragData.repairXY;\r
414     },\r
415 \r
416     // private\r
417     setDraggable: function(ddGroup){\r
418         if (!ddGroup) return;\r
419         if (Ext.isArray(ddGroup)) {\r
420             Ext.each(ddGroup, this.setDraggable, this);\r
421             return;\r
422         }\r
423         this.addToGroup(ddGroup);\r
424     }\r
425 });\r
426 \r
427 Ext.ux.form.MultiSelect.DropZone = function(ms, config){\r
428     this.ms = ms;\r
429     this.view = ms.view;\r
430     var ddGroup = config.ddGroup || 'MultiselectDD';\r
431     var dd;\r
432     if (Ext.isArray(ddGroup)){\r
433         dd = ddGroup.shift();\r
434     } else {\r
435         dd = ddGroup;\r
436         ddGroup = null;\r
437     }\r
438     Ext.ux.form.MultiSelect.DropZone.superclass.constructor.call(this, this.ms.fs.body, { containerScroll: true, ddGroup: dd });\r
439     this.setDroppable(ddGroup);\r
440 };\r
441 \r
442 Ext.extend(Ext.ux.form.MultiSelect.DropZone, Ext.dd.DropZone, {\r
443     <div id="method-Ext.ux.form.MultiSelect-getTargetFromEvent"></div>/**\r
444          * Part of the Ext.dd.DropZone interface. If no target node is found, the\r
445          * whole Element becomes the target, and this causes the drop gesture to append.\r
446          */\r
447     getTargetFromEvent : function(e) {\r
448         var target = e.getTarget();\r
449         return target;\r
450     },\r
451 \r
452     // private\r
453     getDropPoint : function(e, n, dd){\r
454         if (n == this.ms.fs.body.dom) { return "below"; }\r
455         var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;\r
456         var c = t + (b - t) / 2;\r
457         var y = Ext.lib.Event.getPageY(e);\r
458         if(y <= c) {\r
459             return "above";\r
460         }else{\r
461             return "below";\r
462         }\r
463     },\r
464 \r
465     // private\r
466     isValidDropPoint: function(pt, n, data) {\r
467         if (!data.viewNodes || (data.viewNodes.length != 1)) {\r
468             return true;\r
469         }\r
470         var d = data.viewNodes[0];\r
471         if (d == n) {\r
472             return false;\r
473         }\r
474         if ((pt == "below") && (n.nextSibling == d)) {\r
475             return false;\r
476         }\r
477         if ((pt == "above") && (n.previousSibling == d)) {\r
478             return false;\r
479         }\r
480         return true;\r
481     },\r
482 \r
483     // override\r
484     onNodeEnter : function(n, dd, e, data){\r
485         return false;\r
486     },\r
487 \r
488     // override\r
489     onNodeOver : function(n, dd, e, data){\r
490         var dragElClass = this.dropNotAllowed;\r
491         var pt = this.getDropPoint(e, n, dd);\r
492         if (this.isValidDropPoint(pt, n, data)) {\r
493             if (this.ms.appendOnly) {\r
494                 return "x-tree-drop-ok-below";\r
495             }\r
496 \r
497             // set the insert point style on the target node\r
498             if (pt) {\r
499                 var targetElClass;\r
500                 if (pt == "above"){\r
501                     dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";\r
502                     targetElClass = "x-view-drag-insert-above";\r
503                 } else {\r
504                     dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";\r
505                     targetElClass = "x-view-drag-insert-below";\r
506                 }\r
507                 if (this.lastInsertClass != targetElClass){\r
508                     Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);\r
509                     this.lastInsertClass = targetElClass;\r
510                 }\r
511             }\r
512         }\r
513         return dragElClass;\r
514     },\r
515 \r
516     // private\r
517     onNodeOut : function(n, dd, e, data){\r
518         this.removeDropIndicators(n);\r
519     },\r
520 \r
521     // private\r
522     onNodeDrop : function(n, dd, e, data){\r
523         if (this.ms.fireEvent("drop", this, n, dd, e, data) === false) {\r
524             return false;\r
525         }\r
526         var pt = this.getDropPoint(e, n, dd);\r
527         if (n != this.ms.fs.body.dom)\r
528             n = this.view.findItemFromChild(n);\r
529         var insertAt = (this.ms.appendOnly || (n == this.ms.fs.body.dom)) ? this.view.store.getCount() : this.view.indexOf(n);\r
530         if (pt == "below") {\r
531             insertAt++;\r
532         }\r
533 \r
534         var dir = false;\r
535 \r
536         // Validate if dragging within the same MultiSelect\r
537         if (data.sourceView == this.view) {\r
538             // If the first element to be inserted below is the target node, remove it\r
539             if (pt == "below") {\r
540                 if (data.viewNodes[0] == n) {\r
541                     data.viewNodes.shift();\r
542                 }\r
543             } else {  // If the last element to be inserted above is the target node, remove it\r
544                 if (data.viewNodes[data.viewNodes.length - 1] == n) {\r
545                     data.viewNodes.pop();\r
546                 }\r
547             }\r
548 \r
549             // Nothing to drop...\r
550             if (!data.viewNodes.length) {\r
551                 return false;\r
552             }\r
553 \r
554             // If we are moving DOWN, then because a store.remove() takes place first,\r
555             // the insertAt must be decremented.\r
556             if (insertAt > this.view.store.indexOf(data.records[0])) {\r
557                 dir = 'down';\r
558                 insertAt--;\r
559             }\r
560         }\r
561 \r
562         for (var i = 0; i < data.records.length; i++) {\r
563             var r = data.records[i];\r
564             if (data.sourceView) {\r
565                 data.sourceView.store.remove(r);\r
566             }\r
567             this.view.store.insert(dir == 'down' ? insertAt : insertAt++, r);\r
568             var si = this.view.store.sortInfo;\r
569             if(si){\r
570                 this.view.store.sort(si.field, si.direction);\r
571             }\r
572         }\r
573         return true;\r
574     },\r
575 \r
576     // private\r
577     removeDropIndicators : function(n){\r
578         if(n){\r
579             Ext.fly(n).removeClass([\r
580                 "x-view-drag-insert-above",\r
581                 "x-view-drag-insert-left",\r
582                 "x-view-drag-insert-right",\r
583                 "x-view-drag-insert-below"]);\r
584             this.lastInsertClass = "_noclass";\r
585         }\r
586     },\r
587 \r
588     // private\r
589     setDroppable: function(ddGroup){\r
590         if (!ddGroup) return;\r
591         if (Ext.isArray(ddGroup)) {\r
592             Ext.each(ddGroup, this.setDroppable, this);\r
593             return;\r
594         }\r
595         this.addToGroup(ddGroup);\r
596     }\r
597 });\r
598 </pre>
599 </body>
600 </html>