initial import
[roojs1] / examples / view / ddview.html
1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
2 <html>
3 <head>
4 <style>
5 * {
6         font-family:tahoma,arial,helvetica,sans-serif;
7         background-color:#EDF3FA;
8 }
9
10 .x-form-view {
11         cursor:default;
12 }
13
14 .Component {
15         background-color:lightblue;
16         border-bottom:1px groove;
17         cursor:pointer;
18 }
19
20 .asp-selected {
21         background-color:#000070 !important;
22         color:white;
23 }
24
25 .x-view-drag-insert-above {
26         border-top:1px dotted #3366cc;
27 }
28 .x-view-drag-insert-below {
29         border-bottom:1px dotted #3366cc;
30 }
31
32 </style>
33  <link rel="stylesheet" type="text/css" href="../../cssX/roojs-all.css"/>
34     <link rel="stylesheet" type="text/css" href="../../cssX/xtheme-slate.css"/>
35
36      <script type="text/javascript" src="../../roojs-all.js"></script>   
37   
38 <script type="text/javascript">
39 Array.prototype.contains = function(element) {
40     for (var i = 0; i < this.length; i++) {
41             if (this[i] == element) {
42                 return true;
43             }
44     }        
45         return false;
46 };
47 Roo.namespace("Roo.ux");
48
49 /**
50  * @class Roo.ux.DDView
51  * A DnD enabled version of Roo.View.
52  * @param {Element/String} container The Element in which to create the View.
53  * @param {String} tpl The template string used to create the markup for each element of the View
54  * @param {Object} config The configuration properties. These include all the config options of
55  * {@link Roo.View} plus some specific to this class.<br>
56  * <p>
57  * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
58  * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
59  * <p>
60  * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
61 .x-view-drag-insert-above {
62         border-top:1px dotted #3366cc;
63 }
64 .x-view-drag-insert-below {
65         border-bottom:1px dotted #3366cc;
66 }
67 </code></pre>
68  * 
69  */
70 Roo.ux.DDView = function(container, tpl, config) {
71     Roo.ux.DDView.superclass.constructor.apply(this, arguments);
72     this.getEl().setStyle("outline", "0px none");
73     this.getEl().unselectable();
74     if (this.dragGroup) {
75                 this.setDraggable(this.dragGroup.split(","));
76     }
77     if (this.dropGroup) {
78                 this.setDroppable(this.dropGroup.split(","));
79     }
80     if (this.deletable) {
81         this.setDeletable();
82     }
83     this.isDirtyFlag = false;
84         this.addEvents({
85                 "drop" : true
86         });
87 };
88
89 Roo.extend(Roo.ux.DDView, Roo.View, {
90 /**     @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
91 /**     @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
92 /**     @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
93 /**     @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
94
95         isFormField: true,
96
97         reset: Roo.emptyFn,
98         
99         clearInvalid: Roo.form.Field.prototype.clearInvalid,
100
101         validate: function() {
102                 return true;
103         },
104         
105         destroy: function() {
106                 this.purgeListeners();
107                 this.getEl.removeAllListeners();
108                 this.getEl().remove();
109                 if (this.dragZone) {
110                         if (this.dragZone.destroy) {
111                                 this.dragZone.destroy();
112                         }
113                 }
114                 if (this.dropZone) {
115                         if (this.dropZone.destroy) {
116                                 this.dropZone.destroy();
117                         }
118                 }
119         },
120
121 /**     Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
122         getName: function() {
123                 return this.name;
124         },
125
126 /**     Loads the View from a JSON string representing the Records to put into the Store. */
127         setValue: function(v) {
128                 if (!this.store) {
129                         throw "DDView.setValue(). DDView must be constructed with a valid Store";
130                 }
131                 var data = {};
132                 data[this.store.reader.meta.root] = v ? [].concat(v) : [];
133                 this.store.proxy = new Roo.data.MemoryProxy(data);
134                 this.store.load();
135         },
136
137 /**     @return {String} a parenthesised list of the ids of the Records in the View. */
138         getValue: function() {
139                 var result = '(';
140                 this.store.each(function(rec) {
141                         result += rec.id + ',';
142                 });
143                 return result.substr(0, result.length - 1) + ')';
144         },
145         
146         getIds: function() {
147                 var i = 0, result = new Array(this.store.getCount());
148                 this.store.each(function(rec) {
149                         result[i++] = rec.id;
150                 });
151                 return result;
152         },
153         
154         isDirty: function() {
155                 return this.isDirtyFlag;
156         },
157
158 /**
159  *      Part of the Roo.dd.DropZone interface. If no target node is found, the
160  *      whole Element becomes the target, and this causes the drop gesture to append.
161  */
162     getTargetFromEvent : function(e) {
163                 var target = e.getTarget();
164                 while ((target !== null) && (target.parentNode != this.el.dom)) {
165                 target = target.parentNode;
166                 }
167                 if (!target) {
168                         target = this.el.dom.lastChild || this.el.dom;
169                 }
170                 return target;
171     },
172
173 /**
174  *      Create the drag data which consists of an object which has the property "ddel" as
175  *      the drag proxy element. 
176  */
177     getDragData : function(e) {
178         var target = this.findItemFromChild(e.getTarget());
179                 if(target) {
180                         this.handleSelection(e);
181                         var selNodes = this.getSelectedNodes();
182             var dragData = {
183                 source: this,
184                 copy: this.copy || (this.allowCopy && e.ctrlKey),
185                 nodes: selNodes,
186                 records: []
187                         };
188                         var selectedIndices = this.getSelectedIndexes();
189                         for (var i = 0; i < selectedIndices.length; i++) {
190                                 dragData.records.push(this.store.getAt(selectedIndices[i]));
191                         }
192                         if (selNodes.length == 1) {
193                                 dragData.ddel = target.cloneNode(true); // the div element
194                         } else {
195                                 var div = document.createElement('div'); // create the multi element drag "ghost"
196                                 div.className = 'multi-proxy';
197                                 for (var i = 0, len = selNodes.length; i < len; i++) {
198                                         div.appendChild(selNodes[i].cloneNode(true));
199                                 }
200                                 dragData.ddel = div;
201                         }
202                         return dragData;
203                 }
204                 return false;
205     },
206     
207 /**     Specify to which ddGroup items in this DDView may be dragged. */
208     setDraggable: function(ddGroup) {
209         if (ddGroup instanceof Array) {
210                 Roo.each(ddGroup, this.setDraggable, this);
211                 return;
212         }
213         if (this.dragZone) {
214                 this.dragZone.addToGroup(ddGroup);
215         } else {
216                         this.dragZone = new Roo.dd.DragZone(this.getEl(), {
217                                 containerScroll: true,
218                                 ddGroup: ddGroup
219                         });
220 //                      Draggability implies selection. DragZone's mousedown selects the element.
221                         if (!this.multiSelect) { this.singleSelect = true; }
222
223 //                      Wire the DragZone's handlers up to methods in *this*
224                         this.dragZone.getDragData = this.getDragData.createDelegate(this);
225                 }
226     },
227
228 /**     Specify from which ddGroup this DDView accepts drops. */
229     setDroppable: function(ddGroup) {
230         if (ddGroup instanceof Array) {
231                 Roo.each(ddGroup, this.setDroppable, this);
232                 return;
233         }
234         if (this.dropZone) {
235                 this.dropZone.addToGroup(ddGroup);
236         } else {
237                         this.dropZone = new Roo.dd.DropZone(this.getEl(), {
238                                 containerScroll: true,
239                                 ddGroup: ddGroup
240                         });
241
242 //                      Wire the DropZone's handlers up to methods in *this*
243                         this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
244                         this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
245                         this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
246                         this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
247                         this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
248                 }
249     },
250
251 /**     Decide whether to drop above or below a View node. */
252     getDropPoint : function(e, n, dd){
253         if (n == this.el.dom) { return "above"; }
254                 var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
255                 var c = t + (b - t) / 2;
256                 var y = Roo.lib.Event.getPageY(e);
257                 if(y <= c) {
258                         return "above";
259                 }else{
260                         return "below";
261                 }
262     },
263
264     onNodeEnter : function(n, dd, e, data){
265                 return false;
266     },
267     
268     onNodeOver : function(n, dd, e, data){
269                 var pt = this.getDropPoint(e, n, dd);
270                 // set the insert point style on the target node
271                 var dragElClass = this.dropNotAllowed;
272                 if (pt) {
273                         var targetElClass;
274                         if (pt == "above"){
275                                 dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
276                                 targetElClass = "x-view-drag-insert-above";
277                         } else {
278                                 dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
279                                 targetElClass = "x-view-drag-insert-below";
280                         }
281                         if (this.lastInsertClass != targetElClass){
282                                 Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
283                                 this.lastInsertClass = targetElClass;
284                         }
285                 }
286                 return dragElClass;
287         },
288
289     onNodeOut : function(n, dd, e, data){
290                 this.removeDropIndicators(n);
291     },
292
293     onNodeDrop : function(n, dd, e, data){
294         if (this.fireEvent("drop", this, n, dd, e, data) === false) {
295                 return false;
296         }
297         var pt = this.getDropPoint(e, n, dd);
298                 var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
299                 if (pt == "below") { insertAt++; }
300                 for (var i = 0; i < data.records.length; i++) {
301                         var r = data.records[i];
302                         var dup = this.store.getById(r.id);
303                         if (dup && (dd != this.dragZone)) {
304                                 Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
305                         } else {
306                                 if (data.copy) {
307                                         this.store.insert(insertAt++, r.copy());
308                                 } else {
309                                         data.source.isDirtyFlag = true;
310                                         r.store.remove(r);
311                                         this.store.insert(insertAt++, r);
312                                 }
313                                 this.isDirtyFlag = true;
314                         }
315                 }
316                 this.dragZone.cachedTarget = null;
317                 return true;
318     },
319
320     removeDropIndicators : function(n){
321                 if(n){
322                         Roo.fly(n).removeClass([
323                                 "x-view-drag-insert-above",
324                                 "x-view-drag-insert-below"]);
325                         this.lastInsertClass = "_noclass";
326                 }
327     },
328
329 /**
330  *      Utility method. Add a delete option to the DDView's context menu.
331  *      @param {String} imageUrl The URL of the "delete" icon image.
332  */
333         setDeletable: function(imageUrl) {
334                 if (!this.singleSelect && !this.multiSelect) {
335                         this.singleSelect = true;
336                 }
337                 var c = this.getContextMenu();
338                 this.contextMenu.on("itemclick", function(item) {
339                         switch (item.id) {
340                                 case "delete":
341                                         this.remove(this.getSelectedIndexes());
342                                         break;
343                         }
344                 }, this);
345                 this.contextMenu.add({
346                         icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
347                         id: "delete",
348                         text: AU.getMessage("deleteItem")
349                 });
350         },
351         
352 /**     Return the context menu for this DDView. */
353         getContextMenu: function() {
354                 if (!this.contextMenu) {
355 //                      Create the View's context menu
356                         this.contextMenu = new Roo.menu.Menu({
357                                 id: this.id + "-contextmenu"
358                         });
359                         this.el.on("contextmenu", this.showContextMenu, this);
360                 }
361                 return this.contextMenu;
362         },
363         
364         disableContextMenu: function() {
365                 if (this.contextMenu) {
366                         this.el.un("contextmenu", this.showContextMenu, this);
367                 }
368         },
369
370         showContextMenu: function(e, item) {
371         item = this.findItemFromChild(e.getTarget());
372                 if (item) {
373                         e.stopEvent();
374                         this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
375                         this.contextMenu.showAt(e.getXY());
376             }
377     },
378
379 /**
380  *      Remove {@link Roo.data.Record}s at the specified indices.
381  *      @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
382  */
383     remove: function(selectedIndices) {
384                 selectedIndices = [].concat(selectedIndices);
385                 for (var i = 0; i < selectedIndices.length; i++) {
386                         var rec = this.store.getAt(selectedIndices[i]);
387                         this.store.remove(rec);
388                 }
389     },
390
391 /**
392  *      Double click fires the event, but also, if this is draggable, and there is only one other
393  *      related DropZone, it transfers the selected node.
394  */
395     onDblClick : function(e){
396         var item = this.findItemFromChild(e.getTarget());
397         if(item){
398             if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
399                 return false;
400             }
401             if (this.dragGroup) {
402                     var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
403                     while (targets.contains(this.dropZone)) {
404                             targets.remove(this.dropZone);
405                                 }
406                     if (targets.length == 1) {
407                                         this.dragZone.cachedTarget = null;
408                         var el = Roo.get(targets[0].getEl());
409                         var box = el.getBox(true);
410                         targets[0].onNodeDrop(el.dom, {
411                                 target: el.dom,
412                                 xy: [box.x, box.y + box.height - 1]
413                         }, null, this.getDragData(e));
414                     }
415                 }
416         }
417     },
418     
419     handleSelection: function(e) {
420                 this.dragZone.cachedTarget = null;
421         var item = this.findItemFromChild(e.getTarget());
422         if (!item) {
423                 this.clearSelections(true);
424                 return;
425         }
426                 if (item && (this.multiSelect || this.singleSelect)){
427                         if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
428                                 this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
429                         }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
430                                 this.unselect(item);
431                         } else {
432                                 this.select(item, this.multiSelect && e.ctrlKey);
433                                 this.lastSelection = item;
434                         }
435                 }
436     },
437
438     onItemClick : function(item, index, e){
439                 if(this.fireEvent("beforeclick", this, index, item, e) === false){
440                         return false;
441                 }
442                 return true;
443     },
444
445     unselect : function(nodeInfo, suppressEvent){
446                 var node = this.getNode(nodeInfo);
447                 if(node && this.isSelected(node)){
448                         if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
449                                 Roo.fly(node).removeClass(this.selectedClass);
450                                 this.selections.remove(node);
451                                 if(!suppressEvent){
452                                         this.fireEvent("selectionchange", this, this.selections);
453                                 }
454                         }
455                 }
456     }
457 });
458
459 function initializePage() {
460         var collection=[
461                 {'id':'40','entityImageUrl':'../shared/icons/fam/user_add.png','componentDescription':'Add User'},
462                 {'id':'27','entityImageUrl':'../shared/icons/fam/user_delete.png','componentDescription':'Delete User'},
463                 {'id':'28','entityImageUrl':'../shared/icons/fam/user_comment.png','componentDescription':'Comment on User'}
464         ];
465         var rec=Roo.data.Record.create([
466                 {name:'id'},
467                 {name:'entityImageUrl'},
468                 {name:'componentDescription'}
469         ]);
470         var reader=new Roo.data.JsonReader({
471                 root:'collection',
472                 id:'id'
473         },rec);
474         var ds=new Roo.data.Store({
475                 proxy:new Roo.data.MemoryProxy({collection:collection}),
476                 reader:reader
477         });
478         var view=new Roo.ux.DDView('left-view-container','<div id=\u0027subcomponent_{id}\u0027 class=\u0027Subcomponent\u0027><img align=\u0027top\u0027 height=\u002716px\u0027 width=\u002716px\u0027 src=\u0027{entityImageUrl}\u0027>{componentDescription}</div>',{
479                 isFormField:true,
480                 name:'subComponents',
481                 dragGroup:'availComponentDDGroup,subComponentDDGroup',
482                 dropGroup:'availComponentDDGroup,subComponentDDGroup',
483                 selectedClass: 'asp-selected',
484                 jsonRoot: 'collection',
485                 store: ds
486         });
487         ds.load();
488
489         collection=[];
490         rec=Roo.data.Record.create([
491                 {name:'id'},
492                 {name:'entityImageUrl'},
493                 {name:'componentDescription'}
494         ]);
495         reader=new Roo.data.JsonReader({
496                 root:'collection',
497                 id:'id'
498         },rec);
499         ds=new Roo.data.Store({
500                 proxy:new Roo.data.MemoryProxy({collection:collection}),
501                 reader:reader
502         });
503         view=new Roo.ux.DDView('right-view-container','<div id=\u0027component_{id}\u0027 class=\u0027Component\u0027><img align=\u0027top\u0027 height=\u002716px\u0027 width=\u002716px\u0027 src=\u0027{entityImageUrl}\u0027>{componentDescription}</div>',{
504                 isFormField:true,
505                 name:'availableSubComponents',
506                 multiSelect: true,
507                 dragGroup:'subComponentDDGroup',
508                 dropGroup:'availComponentDDGroup',
509                 selectedClass: 'asp-selected',
510                 jsonRoot: 'collection',
511                 store: ds
512         });
513         ds.load();
514 }
515 Roo.onReady(initializePage);
516 </script>
517 </head>
518 <body>
519 <form  class="x-form" id="the-form">
520         <div class="x-form-ct x-form-column x-form-label-left">
521                 <fieldset class="x-form-fieldset x-form-label-left" style="margin-left:10px" >
522                         <legend>Subcomponents</legend>
523                         <div id="left-view-container" class="x-form-view" style="overflow:auto;height:215px">
524                         </div>
525                 </fieldset>
526         </div>
527         <div class="x-form-ct x-form-column x-form-label-left ">
528                 <fieldset class="x-form-fieldset x-form-label-left" style="margin-left:10px">
529                         <legend>Available subcomponents</legend>
530                         <div id="right-view-container" class="x-form-view" style="overflow:auto;height:215px">
531                         </div>
532                 </fieldset>
533         </div>
534 </form>
535 </body>
536 </html>