1 //<script type="text/javasscript">
6 * A DnD enabled version of Roo.View.
7 * @param {Element/String} container The Element in which to create the View.
8 * @param {String} tpl The template string used to create the markup for each element of the View
9 * @param {Object} config The configuration properties. These include all the config options of
10 * {@link Roo.View} plus some specific to this class.<br>
12 * Drag/drop is implemented by adding {@link Roo.data.Record}s to the target DDView. If copying is
13 * not being performed, the original {@link Roo.data.Record} is removed from the source DDView.<br>
15 * The following extra CSS rules are needed to provide insertion point highlighting:<pre><code>
16 .x-view-drag-insert-above {
17 border-top:1px dotted #3366cc;
19 .x-view-drag-insert-below {
20 border-bottom:1px dotted #3366cc;
26 Roo.DDView = function(container, tpl, config) {
27 Roo.DDView.superclass.constructor.apply(this, arguments);
28 this.getEl().setStyle("outline", "0px none");
29 this.getEl().unselectable();
31 this.setDraggable(this.dragGroup.split(","));
34 this.setDroppable(this.dropGroup.split(","));
39 this.isDirtyFlag = false;
45 Roo.extend(Roo.DDView, Roo.View, {
46 /** @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone. */
47 /** @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone. */
48 /** @cfg {Boolean} copy Causes drag operations to copy nodes rather than move. */
49 /** @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move. */
55 clearInvalid: Roo.form.Field.prototype.clearInvalid,
57 validate: function() {
62 this.purgeListeners();
63 this.getEl.removeAllListeners();
64 this.getEl().remove();
66 if (this.dragZone.destroy) {
67 this.dragZone.destroy();
71 if (this.dropZone.destroy) {
72 this.dropZone.destroy();
77 /** Allows this class to be an Roo.form.Field so it can be found using {@link Roo.form.BasicForm#findField}. */
82 /** Loads the View from a JSON string representing the Records to put into the Store. */
83 setValue: function(v) {
85 throw "DDView.setValue(). DDView must be constructed with a valid Store";
88 data[this.store.reader.meta.root] = v ? [].concat(v) : [];
89 this.store.proxy = new Roo.data.MemoryProxy(data);
93 /** @return {String} a parenthesised list of the ids of the Records in the View. */
94 getValue: function() {
96 this.store.each(function(rec) {
97 result += rec.id + ',';
99 return result.substr(0, result.length - 1) + ')';
103 var i = 0, result = new Array(this.store.getCount());
104 this.store.each(function(rec) {
105 result[i++] = rec.id;
110 isDirty: function() {
111 return this.isDirtyFlag;
115 * Part of the Roo.dd.DropZone interface. If no target node is found, the
116 * whole Element becomes the target, and this causes the drop gesture to append.
118 getTargetFromEvent : function(e) {
119 var target = e.getTarget();
120 while ((target !== null) && (target.parentNode != this.el.dom)) {
121 target = target.parentNode;
124 target = this.el.dom.lastChild || this.el.dom;
130 * Create the drag data which consists of an object which has the property "ddel" as
131 * the drag proxy element.
133 getDragData : function(e) {
134 var target = this.findItemFromChild(e.getTarget());
136 this.handleSelection(e);
137 var selNodes = this.getSelectedNodes();
140 copy: this.copy || (this.allowCopy && e.ctrlKey),
144 var selectedIndices = this.getSelectedIndexes();
145 for (var i = 0; i < selectedIndices.length; i++) {
146 dragData.records.push(this.store.getAt(selectedIndices[i]));
148 if (selNodes.length == 1) {
149 dragData.ddel = target.cloneNode(true); // the div element
151 var div = document.createElement('div'); // create the multi element drag "ghost"
152 div.className = 'multi-proxy';
153 for (var i = 0, len = selNodes.length; i < len; i++) {
154 div.appendChild(selNodes[i].cloneNode(true));
158 //console.log(dragData)
159 //console.log(dragData.ddel.innerHTML)
162 //console.log('nodragData')
166 /** Specify to which ddGroup items in this DDView may be dragged. */
167 setDraggable: function(ddGroup) {
168 if (ddGroup instanceof Array) {
169 Roo.each(ddGroup, this.setDraggable, this);
173 this.dragZone.addToGroup(ddGroup);
175 this.dragZone = new Roo.dd.DragZone(this.getEl(), {
176 containerScroll: true,
180 // Draggability implies selection. DragZone's mousedown selects the element.
181 if (!this.multiSelect) { this.singleSelect = true; }
183 // Wire the DragZone's handlers up to methods in *this*
184 this.dragZone.getDragData = this.getDragData.createDelegate(this);
188 /** Specify from which ddGroup this DDView accepts drops. */
189 setDroppable: function(ddGroup) {
190 if (ddGroup instanceof Array) {
191 Roo.each(ddGroup, this.setDroppable, this);
195 this.dropZone.addToGroup(ddGroup);
197 this.dropZone = new Roo.dd.DropZone(this.getEl(), {
198 containerScroll: true,
202 // Wire the DropZone's handlers up to methods in *this*
203 this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
204 this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
205 this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
206 this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
207 this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
211 /** Decide whether to drop above or below a View node. */
212 getDropPoint : function(e, n, dd){
213 if (n == this.el.dom) { return "above"; }
214 var t = Roo.lib.Dom.getY(n), b = t + n.offsetHeight;
215 var c = t + (b - t) / 2;
216 var y = Roo.lib.Event.getPageY(e);
224 onNodeEnter : function(n, dd, e, data){
228 onNodeOver : function(n, dd, e, data){
229 var pt = this.getDropPoint(e, n, dd);
230 // set the insert point style on the target node
231 var dragElClass = this.dropNotAllowed;
235 dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
236 targetElClass = "x-view-drag-insert-above";
238 dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
239 targetElClass = "x-view-drag-insert-below";
241 if (this.lastInsertClass != targetElClass){
242 Roo.fly(n).replaceClass(this.lastInsertClass, targetElClass);
243 this.lastInsertClass = targetElClass;
249 onNodeOut : function(n, dd, e, data){
250 this.removeDropIndicators(n);
253 onNodeDrop : function(n, dd, e, data){
254 if (this.fireEvent("drop", this, n, dd, e, data) === false) {
257 var pt = this.getDropPoint(e, n, dd);
258 var insertAt = (n == this.el.dom) ? this.nodes.length : n.nodeIndex;
259 if (pt == "below") { insertAt++; }
260 for (var i = 0; i < data.records.length; i++) {
261 var r = data.records[i];
262 var dup = this.store.getById(r.id);
263 if (dup && (dd != this.dragZone)) {
264 Roo.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
267 this.store.insert(insertAt++, r.copy());
269 data.source.isDirtyFlag = true;
271 this.store.insert(insertAt++, r);
273 this.isDirtyFlag = true;
276 this.dragZone.cachedTarget = null;
280 removeDropIndicators : function(n){
282 Roo.fly(n).removeClass([
283 "x-view-drag-insert-above",
284 "x-view-drag-insert-below"]);
285 this.lastInsertClass = "_noclass";
290 * Utility method. Add a delete option to the DDView's context menu.
291 * @param {String} imageUrl The URL of the "delete" icon image.
293 setDeletable: function(imageUrl) {
294 if (!this.singleSelect && !this.multiSelect) {
295 this.singleSelect = true;
297 var c = this.getContextMenu();
298 this.contextMenu.on("itemclick", function(item) {
301 this.remove(this.getSelectedIndexes());
305 this.contextMenu.add({
312 /** Return the context menu for this DDView. */
313 getContextMenu: function() {
314 if (!this.contextMenu) {
315 // Create the View's context menu
316 this.contextMenu = new Roo.menu.Menu({
317 id: this.id + "-contextmenu"
319 this.el.on("contextmenu", this.showContextMenu, this);
321 return this.contextMenu;
324 disableContextMenu: function() {
325 if (this.contextMenu) {
326 this.el.un("contextmenu", this.showContextMenu, this);
330 showContextMenu: function(e, item) {
331 item = this.findItemFromChild(e.getTarget());
334 this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
335 this.contextMenu.showAt(e.getXY());
340 * Remove {@link Roo.data.Record}s at the specified indices.
341 * @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
343 remove: function(selectedIndices) {
344 selectedIndices = [].concat(selectedIndices);
345 for (var i = 0; i < selectedIndices.length; i++) {
346 var rec = this.store.getAt(selectedIndices[i]);
347 this.store.remove(rec);
352 * Double click fires the event, but also, if this is draggable, and there is only one other
353 * related DropZone, it transfers the selected node.
355 onDblClick : function(e){
356 var item = this.findItemFromChild(e.getTarget());
358 if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
361 if (this.dragGroup) {
362 var targets = Roo.dd.DragDropMgr.getRelated(this.dragZone, true);
363 while (targets.indexOf(this.dropZone) > -1) {
364 targets.remove(this.dropZone);
366 if (targets.length == 1) {
367 this.dragZone.cachedTarget = null;
368 var el = Roo.get(targets[0].getEl());
369 var box = el.getBox(true);
370 targets[0].onNodeDrop(el.dom, {
372 xy: [box.x, box.y + box.height - 1]
373 }, null, this.getDragData(e));
379 handleSelection: function(e) {
380 this.dragZone.cachedTarget = null;
381 var item = this.findItemFromChild(e.getTarget());
383 this.clearSelections(true);
386 if (item && (this.multiSelect || this.singleSelect)){
387 if(this.multiSelect && e.shiftKey && (!e.ctrlKey) && this.lastSelection){
388 this.select(this.getNodes(this.indexOf(this.lastSelection), item.nodeIndex), false);
389 }else if (this.isSelected(this.getNode(item)) && e.ctrlKey){
392 this.select(item, this.multiSelect && e.ctrlKey);
393 this.lastSelection = item;
398 onItemClick : function(item, index, e){
399 if(this.fireEvent("beforeclick", this, index, item, e) === false){
405 unselect : function(nodeInfo, suppressEvent){
406 var node = this.getNode(nodeInfo);
407 if(node && this.isSelected(node)){
408 if(this.fireEvent("beforeselect", this, node, this.selections) !== false){
409 Roo.fly(node).removeClass(this.selectedClass);
410 this.selections.remove(node);
412 this.fireEvent("selectionchange", this, this.selections);