1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
6 font-family:tahoma,arial,helvetica,sans-serif;
7 background-color:#EDF3FA;
15 background-color:lightblue;
16 border-bottom:1px groove;
21 background-color:#000070 !important;
25 .x-view-drag-insert-above {
26 border-top:1px dotted #3366cc;
28 .x-view-drag-insert-below {
29 border-bottom:1px dotted #3366cc;
33 <link rel="stylesheet" type="text/css" href="../../cssX/roojs-all.css"/>
34 <link rel="stylesheet" type="text/css" href="../../cssX/xtheme-slate.css"/>
36 <script type="text/javascript" src="../../roojs-all.js"></script>
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) {
47 Roo.namespace("Roo.ux");
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>
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>
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;
64 .x-view-drag-insert-below {
65 border-bottom:1px dotted #3366cc;
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();
75 this.setDraggable(this.dragGroup.split(","));
78 this.setDroppable(this.dropGroup.split(","));
83 this.isDirtyFlag = false;
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. */
99 clearInvalid: Roo.form.Field.prototype.clearInvalid,
101 validate: function() {
105 destroy: function() {
106 this.purgeListeners();
107 this.getEl.removeAllListeners();
108 this.getEl().remove();
110 if (this.dragZone.destroy) {
111 this.dragZone.destroy();
115 if (this.dropZone.destroy) {
116 this.dropZone.destroy();
121 /** Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
122 getName: function() {
126 /** Loads the View from a JSON string representing the Records to put into the Store. */
127 setValue: function(v) {
129 throw "DDView.setValue(). DDView must be constructed with a valid Store";
132 data[this.store.reader.meta.root] = v ? [].concat(v) : [];
133 this.store.proxy = new Roo.data.MemoryProxy(data);
137 /** @return {String} a parenthesised list of the ids of the Records in the View. */
138 getValue: function() {
140 this.store.each(function(rec) {
141 result += rec.id + ',';
143 return result.substr(0, result.length - 1) + ')';
147 var i = 0, result = new Array(this.store.getCount());
148 this.store.each(function(rec) {
149 result[i++] = rec.id;
154 isDirty: function() {
155 return this.isDirtyFlag;
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.
162 getTargetFromEvent : function(e) {
163 var target = e.getTarget();
164 while ((target !== null) && (target.parentNode != this.el.dom)) {
165 target = target.parentNode;
168 target = this.el.dom.lastChild || this.el.dom;
174 * Create the drag data which consists of an object which has the property "ddel" as
175 * the drag proxy element.
177 getDragData : function(e) {
178 var target = this.findItemFromChild(e.getTarget());
180 this.handleSelection(e);
181 var selNodes = this.getSelectedNodes();
184 copy: this.copy || (this.allowCopy && e.ctrlKey),
188 var selectedIndices = this.getSelectedIndexes();
189 for (var i = 0; i < selectedIndices.length; i++) {
190 dragData.records.push(this.store.getAt(selectedIndices[i]));
192 if (selNodes.length == 1) {
193 dragData.ddel = target.cloneNode(true); // the div element
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));
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);
214 this.dragZone.addToGroup(ddGroup);
216 this.dragZone = new Roo.dd.DragZone(this.getEl(), {
217 containerScroll: true,
220 // Draggability implies selection. DragZone's mousedown selects the element.
221 if (!this.multiSelect) { this.singleSelect = true; }
223 // Wire the DragZone's handlers up to methods in *this*
224 this.dragZone.getDragData = this.getDragData.createDelegate(this);
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);
235 this.dropZone.addToGroup(ddGroup);
237 this.dropZone = new Roo.dd.DropZone(this.getEl(), {
238 containerScroll: true,
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);
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);
264 onNodeEnter : function(n, dd, e, data){
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;
275 dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
276 targetElClass = "x-view-drag-insert-above";
278 dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
279 targetElClass = "x-view-drag-insert-below";
281 if (this.lastInsertClass != targetElClass){
282 Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
283 this.lastInsertClass = targetElClass;
289 onNodeOut : function(n, dd, e, data){
290 this.removeDropIndicators(n);
293 onNodeDrop : function(n, dd, e, data){
294 if (this.fireEvent("drop", this, n, dd, e, data) === false) {
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);
307 this.store.insert(insertAt++, r.copy());
309 data.source.isDirtyFlag = true;
311 this.store.insert(insertAt++, r);
313 this.isDirtyFlag = true;
316 this.dragZone.cachedTarget = null;
320 removeDropIndicators : function(n){
322 Roo.fly(n).removeClass([
323 "x-view-drag-insert-above",
324 "x-view-drag-insert-below"]);
325 this.lastInsertClass = "_noclass";
330 * Utility method. Add a delete option to the DDView's context menu.
331 * @param {String} imageUrl The URL of the "delete" icon image.
333 setDeletable: function(imageUrl) {
334 if (!this.singleSelect && !this.multiSelect) {
335 this.singleSelect = true;
337 var c = this.getContextMenu();
338 this.contextMenu.on("itemclick", function(item) {
341 this.remove(this.getSelectedIndexes());
345 this.contextMenu.add({
346 icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
348 text: AU.getMessage("deleteItem")
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"
359 this.el.on("contextmenu", this.showContextMenu, this);
361 return this.contextMenu;
364 disableContextMenu: function() {
365 if (this.contextMenu) {
366 this.el.un("contextmenu", this.showContextMenu, this);
370 showContextMenu: function(e, item) {
371 item = this.findItemFromChild(e.getTarget());
374 this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
375 this.contextMenu.showAt(e.getXY());
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.
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);
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.
395 onDblClick : function(e){
396 var item = this.findItemFromChild(e.getTarget());
398 if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
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);
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, {
412 xy: [box.x, box.y + box.height - 1]
413 }, null, this.getDragData(e));
419 handleSelection: function(e) {
420 this.dragZone.cachedTarget = null;
421 var item = this.findItemFromChild(e.getTarget());
423 this.clearSelections(true);
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){
432 this.select(item, this.multiSelect && e.ctrlKey);
433 this.lastSelection = item;
438 onItemClick : function(item, index, e){
439 if(this.fireEvent("beforeclick", this, index, item, e) === false){
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);
452 this.fireEvent("selectionchange", this, this.selections);
459 function initializePage() {
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'}
465 var rec=Roo.data.Record.create([
467 {name:'entityImageUrl'},
468 {name:'componentDescription'}
470 var reader=new Roo.data.JsonReader({
474 var ds=new Roo.data.Store({
475 proxy:new Roo.data.MemoryProxy({collection:collection}),
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>',{
480 name:'subComponents',
481 dragGroup:'availComponentDDGroup,subComponentDDGroup',
482 dropGroup:'availComponentDDGroup,subComponentDDGroup',
483 selectedClass: 'asp-selected',
484 jsonRoot: 'collection',
490 rec=Roo.data.Record.create([
492 {name:'entityImageUrl'},
493 {name:'componentDescription'}
495 reader=new Roo.data.JsonReader({
499 ds=new Roo.data.Store({
500 proxy:new Roo.data.MemoryProxy({collection:collection}),
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>',{
505 name:'availableSubComponents',
507 dragGroup:'subComponentDDGroup',
508 dropGroup:'availComponentDDGroup',
509 selectedClass: 'asp-selected',
510 jsonRoot: 'collection',
515 Roo.onReady(initializePage);
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">
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">