1 Array.prototype.contains = function(element) {
2 for (var i = 0; i < this.length; i++) {
3 if (this[i] == element) {
9 Roo.namespace("Roo.ux");
12 * @class Roo.ux.DDView
13 * A DnD enabled version of Roo.View.
14 * @param {Element/String} container The Element in which to create the View.
15 * @param {String} tpl The template string used to create the markup for each element of the View
16 * @param {Object} config The configuration properties. These include all the config options of
17 * {@link Roo.View} plus some specific to this class.<br>
19 * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
20 * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
22 * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
23 .x-view-drag-insert-above {
24 border-top:1px dotted #3366cc;
26 .x-view-drag-insert-below {
27 border-bottom:1px dotted #3366cc;
32 Roo.ux.DDView = function(container, tpl, config) {
33 Roo.ux.DDView.superclass.constructor.apply(this, arguments);
34 this.getEl().setStyle("outline", "0px none");
35 this.getEl().unselectable();
37 this.setDraggable(this.dragGroup.split(","));
40 this.setDroppable(this.dropGroup.split(","));
45 this.isDirtyFlag = false;
51 Roo.extend(Roo.ux.DDView, Roo.View, {
52 /** @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
53 /** @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
54 /** @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
55 /** @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
61 clearInvalid: Roo.form.Field.prototype.clearInvalid,
63 validate: function() {
68 this.purgeListeners();
69 this.getEl.removeAllListeners();
70 this.getEl().remove();
72 if (this.dragZone.destroy) {
73 this.dragZone.destroy();
77 if (this.dropZone.destroy) {
78 this.dropZone.destroy();
83 /** Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
88 /** Loads the View from a JSON string representing the Records to put into the Store. */
89 setValue: function(v) {
91 throw "DDView.setValue(). DDView must be constructed with a valid Store";
94 data[this.store.reader.meta.root] = v ? [].concat(v) : [];
95 this.store.proxy = new Roo.data.MemoryProxy(data);
99 /** @return {String} a parenthesised list of the ids of the Records in the View. */
100 getValue: function() {
102 this.store.each(function(rec) {
103 result += rec.id + ',';
105 return result.substr(0, result.length - 1) + ')';
109 var i = 0, result = new Array(this.store.getCount());
110 this.store.each(function(rec) {
111 result[i++] = rec.id;
116 isDirty: function() {
117 return this.isDirtyFlag;
121 * Part of the Roo.dd.DropZone interface. If no target node is found, the
122 * whole Element becomes the target, and this causes the drop gesture to append.
124 getTargetFromEvent : function(e) {
125 var target = e.getTarget();
126 while ((target !== null) && (target.parentNode != this.el.dom)) {
127 target = target.parentNode;
130 target = this.el.dom.lastChild || this.el.dom;
136 * Create the drag data which consists of an object which has the property "ddel" as
137 * the drag proxy element.
139 getDragData : function(e) {
140 var target = this.findItemFromChild(e.getTarget());
142 this.handleSelection(e);
143 var selNodes = this.getSelectedNodes();
146 copy: this.copy || (this.allowCopy && e.ctrlKey),
150 var selectedIndices = this.getSelectedIndexes();
151 for (var i = 0; i < selectedIndices.length; i++) {
152 dragData.records.push(this.store.getAt(selectedIndices[i]));
154 if (selNodes.length == 1) {
155 dragData.ddel = target.cloneNode(true); // the div element
157 var div = document.createElement('div'); // create the multi element drag "ghost"
158 div.className = 'multi-proxy';
159 for (var i = 0, len = selNodes.length; i < len; i++) {
160 div.appendChild(selNodes[i].cloneNode(true));
169 /** Specify to which ddGroup items in this DDView may be dragged. */
170 setDraggable: function(ddGroup) {
171 if (ddGroup instanceof Array) {
172 Roo.each(ddGroup, this.setDraggable, this);
176 this.dragZone.addToGroup(ddGroup);
178 this.dragZone = new Roo.dd.DragZone(this.getEl(), {
179 containerScroll: true,
182 // Draggability implies selection. DragZone's mousedown selects the element.
183 if (!this.multiSelect) { this.singleSelect = true; }
185 // Wire the DragZone's handlers up to methods in *this*
186 this.dragZone.getDragData = this.getDragData.createDelegate(this);
190 /** Specify from which ddGroup this DDView accepts drops. */
191 setDroppable: function(ddGroup) {
192 if (ddGroup instanceof Array) {
193 Roo.each(ddGroup, this.setDroppable, this);
197 this.dropZone.addToGroup(ddGroup);
199 this.dropZone = new Roo.dd.DropZone(this.getEl(), {
200 containerScroll: true,
204 // Wire the DropZone's handlers up to methods in *this*
205 this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
206 this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
207 this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
208 this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
209 this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
213 /** Decide whether to drop above or below a View node. */
214 getDropPoint : function(e, n, dd){
215 if (n == this.el.dom) { return "above"; }
216 var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
217 var c = t + (b - t) / 2;
218 var y = Roo.lib.Event.getPageY(e);
226 onNodeEnter : function(n, dd, e, data){
230 onNodeOver : function(n, dd, e, data){
231 var pt = this.getDropPoint(e, n, dd);
232 // set the insert point style on the target node
233 var dragElClass = this.dropNotAllowed;
237 dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
238 targetElClass = "x-view-drag-insert-above";
240 dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
241 targetElClass = "x-view-drag-insert-below";
243 if (this.lastInsertClass != targetElClass){
244 Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
245 this.lastInsertClass = targetElClass;
251 onNodeOut : function(n, dd, e, data){
252 this.removeDropIndicators(n);
255 onNodeDrop : function(n, dd, e, data){
256 if (this.fireEvent("drop", this, n, dd, e, data) === false) {
259 var pt = this.getDropPoint(e, n, dd);
260 var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
261 if (pt == "below") { insertAt++; }
262 for (var i = 0; i < data.records.length; i++) {
263 var r = data.records[i];
264 var dup = this.store.getById(r.id);
265 if (dup && (dd != this.dragZone)) {
266 Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
269 this.store.insert(insertAt++, r.copy());
271 data.source.isDirtyFlag = true;
273 this.store.insert(insertAt++, r);
275 this.isDirtyFlag = true;
278 this.dragZone.cachedTarget = null;
282 removeDropIndicators : function(n){
284 Roo.fly(n).removeClass([
285 "x-view-drag-insert-above",
286 "x-view-drag-insert-below"]);
287 this.lastInsertClass = "_noclass";
292 * Utility method. Add a delete option to the DDView's context menu.
293 * @param {String} imageUrl The URL of the "delete" icon image.
295 setDeletable: function(imageUrl) {
296 if (!this.singleSelect && !this.multiSelect) {
297 this.singleSelect = true;
299 var c = this.getContextMenu();
300 this.contextMenu.on("itemclick", function(item) {
303 this.remove(this.getSelectedIndexes());
307 this.contextMenu.add({
308 icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
310 text: AU.getMessage("deleteItem")
314 /** Return the context menu for this DDView. */
315 getContextMenu: function() {
316 if (!this.contextMenu) {
317 // Create the View's context menu
318 this.contextMenu = new Roo.menu.Menu({
319 id: this.id + "-contextmenu"
321 this.el.on("contextmenu", this.showContextMenu, this);
323 return this.contextMenu;
326 disableContextMenu: function() {
327 if (this.contextMenu) {
328 this.el.un("contextmenu", this.showContextMenu, this);
332 showContextMenu: function(e, item) {
333 item = this.findItemFromChild(e.getTarget());
336 this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
337 this.contextMenu.showAt(e.getXY());
342 * Remove {@link Roo.data.Record}s at the specified indices.
343 * @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
345 remove: function(selectedIndices) {
346 selectedIndices = [].concat(selectedIndices);
347 for (var i = 0; i < selectedIndices.length; i++) {
348 var rec = this.store.getAt(selectedIndices[i]);
349 this.store.remove(rec);
354 * Double click fires the event, but also, if this is draggable, and there is only one other
355 * related DropZone, it transfers the selected node.
357 onDblClick : function(e){
358 var item = this.findItemFromChild(e.getTarget());
360 if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
363 if (this.dragGroup) {
364 var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
365 while (targets.contains(this.dropZone)) {
366 targets.remove(this.dropZone);
368 if (targets.length == 1) {
369 this.dragZone.cachedTarget = null;
370 var el = Roo.get(targets[0].getEl());
371 var box = el.getBox(true);
372 targets[0].onNodeDrop(el.dom, {
374 xy: [box.x, box.y + box.height - 1]
375 }, null, this.getDragData(e));
381 handleSelection: function(e) {
382 this.dragZone.cachedTarget = null;
383 var item = this.findItemFromChild(e.getTarget());
385 this.clearSelections(true);
388 if (item && (this.multiSelect || this.singleSelect)){
389 if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
390 this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
391 }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
394 this.select(item, this.multiSelect && e.ctrlKey);
395 this.lastSelection = item;
400 onItemClick : function(item, index, e){
401 if(this.fireEvent("beforeclick", this, index, item, e) === false){
407 unselect : function(nodeInfo, suppressEvent){
408 var node = this.getNode(nodeInfo);
409 if(node && this.isSelected(node)){
410 if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
411 Roo.fly(node).removeClass(this.selectedClass);
412 this.selections.remove(node);
414 this.fireEvent("selectionchange", this, this.selections);
421 function initializePage() {
423 {'id':'40','entityImageUrl':'../shared/icons/fam/user_add.png','componentDescription':'Add User'},
424 {'id':'27','entityImageUrl':'../shared/icons/fam/user_delete.png','componentDescription':'Delete User'},
425 {'id':'28','entityImageUrl':'../shared/icons/fam/user_comment.png','componentDescription':'Comment on User'}
427 var rec=Roo.data.Record.create([
429 {name:'entityImageUrl'},
430 {name:'componentDescription'}
432 var reader=new Roo.data.JsonReader({
436 var ds=new Roo.data.Store({
437 proxy:new Roo.data.MemoryProxy({collection:collection}),
440 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>',{
442 name:'subComponents',
443 dragGroup:'availComponentDDGroup,subComponentDDGroup',
444 dropGroup:'availComponentDDGroup,subComponentDDGroup',
445 selectedClass: 'asp-selected',
446 jsonRoot: 'collection',
452 rec=Roo.data.Record.create([
454 {name:'entityImageUrl'},
455 {name:'componentDescription'}
457 reader=new Roo.data.JsonReader({
461 ds=new Roo.data.Store({
462 proxy:new Roo.data.MemoryProxy({collection:collection}),
465 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>',{
467 name:'availableSubComponents',
469 dragGroup:'subComponentDDGroup',
470 dropGroup:'availComponentDDGroup',
471 selectedClass: 'asp-selected',
472 jsonRoot: 'collection',
477 Roo.onReady(initializePage);