source: trunk/web/addons/job_monarch/lib/extjs/examples/multiselect/DDView.js @ 619

Last change on this file since 619 was 619, checked in by ramonb, 15 years ago

lib/:

  • added new AJAX dependancies: ExtJS, pChart, Lightbox2
File size: 21.0 KB
Line 
1/*
2 * Ext JS Library 2.2.1
3 * Copyright(c) 2006-2009, Ext JS, LLC.
4 * licensing@extjs.com
5 *
6 * http://extjs.com/license
7 */
8
9/*
10 * Software License Agreement (BSD License)
11 * Copyright (c) 2008, Nige "Animal" White
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without modification,
15 * are permitted provided that the following conditions are met:
16 *
17 *     * Redistributions of source code must retain the above copyright notice,
18 *       this list of conditions and the following disclaimer.
19 *     * Redistributions in binary form must reproduce the above copyright notice,
20 *       this list of conditions and the following disclaimer in the documentation
21 *       and/or other materials provided with the distribution.
22 *     * Neither the name of the original author nor the names of its contributors
23 *       may be used to endorse or promote products derived from this software
24 *       without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
29 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
31 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
32 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
33 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 */
37/**
38 * @class Ext.ux.DDView
39 * <p>A DnD-enabled version of {@link Ext.DataView}. Drag/drop is implemented by adding
40 * {@link Ext.data.Record}s to the target DDView. If copying is not being performed,
41 * the original {@link Ext.data.Record} is removed from the source DDView.</p>
42 * @constructor
43 * Create a new DDView
44 * @param {Object} config The configuration properties.
45 */
46Ext.ux.DDView = function(config) {
47    if (!config.itemSelector) {
48        var tpl = config.tpl;
49        if (this.classRe.test(tpl)) {
50            config.tpl = tpl.replace(this.classRe, 'class=$1x-combo-list-item $2$1');
51        }
52        else {
53            config.tpl = tpl.replace(this.tagRe, '$1 class="x-combo-list-item" $2');
54        }
55        config.itemSelector = ".x-combo-list-item";
56    }
57    Ext.ux.DDView.superclass.constructor.call(this, Ext.apply(config, {
58        border: false
59    }));
60};
61
62Ext.extend(Ext.ux.DDView, Ext.DataView, {
63    /**
64     * @cfg {String/Array} dragGroup The ddgroup name(s) for the View's DragZone (defaults to undefined).
65     */
66    /**
67     * @cfg {String/Array} dropGroup The ddgroup name(s) for the View's DropZone (defaults to undefined).
68     */
69    /**
70     * @cfg {Boolean} copy Causes drag operations to copy nodes rather than move (defaults to false).
71     */
72    /**
73     * @cfg {Boolean} allowCopy Causes ctrl/drag operations to copy nodes rather than move (defaults to false).
74     */
75    /**
76     * @cfg {String} sortDir Sort direction for the view, 'ASC' or 'DESC' (defaults to 'ASC').
77     */
78    sortDir: 'ASC',
79
80    // private
81    isFormField: true,
82    classRe: /class=(['"])(.*)\1/,
83    tagRe: /(<\w*)(.*?>)/,
84    reset: Ext.emptyFn,
85    clearInvalid: Ext.form.Field.prototype.clearInvalid,
86
87    // private
88    afterRender: function() {
89        Ext.ux.DDView.superclass.afterRender.call(this);
90        if (this.dragGroup) {
91            this.setDraggable(this.dragGroup.split(","));
92        }
93        if (this.dropGroup) {
94            this.setDroppable(this.dropGroup.split(","));
95        }
96        if (this.deletable) {
97            this.setDeletable();
98        }
99        this.isDirtyFlag = false;
100        this.addEvents(
101            "drop"
102        );
103    },
104
105    // private
106    validate: function() {
107        return true;
108    },
109
110    // private
111    destroy: function() {
112        this.purgeListeners();
113        this.getEl().removeAllListeners();
114        this.getEl().remove();
115        if (this.dragZone) {
116            if (this.dragZone.destroy) {
117                this.dragZone.destroy();
118            }
119        }
120        if (this.dropZone) {
121            if (this.dropZone.destroy) {
122                this.dropZone.destroy();
123            }
124        }
125    },
126
127        /**
128         * Allows this class to be an Ext.form.Field so it can be found using {@link Ext.form.BasicForm#findField}.
129         */
130    getName: function() {
131        return this.name;
132    },
133
134        /**
135         * Loads the View from a JSON string representing the Records to put into the Store.
136     * @param {String} value The JSON string
137         */
138    setValue: function(v) {
139        if (!this.store) {
140            throw "DDView.setValue(). DDView must be constructed with a valid Store";
141        }
142        var data = {};
143        data[this.store.reader.meta.root] = v ? [].concat(v) : [];
144        this.store.proxy = new Ext.data.MemoryProxy(data);
145        this.store.load();
146    },
147
148        /**
149         * Returns the view's data value as a list of ids.
150     * @return {String} A parenthesised list of the ids of the Records in the View, e.g. (1,3,8).
151         */
152    getValue: function() {
153        var result = '(';
154        this.store.each(function(rec) {
155            result += rec.id + ',';
156        });
157        return result.substr(0, result.length - 1) + ')';
158    },
159
160    getIds: function() {
161        var i = 0, result = new Array(this.store.getCount());
162        this.store.each(function(rec) {
163            result[i++] = rec.id;
164        });
165        return result;
166    },
167
168    /**
169     * Returns true if the view's data has changed, else false.
170     * @return {Boolean}
171     */
172    isDirty: function() {
173        return this.isDirtyFlag;
174    },
175
176        /**
177         * Part of the Ext.dd.DropZone interface. If no target node is found, the
178         * whole Element becomes the target, and this causes the drop gesture to append.
179         */
180    getTargetFromEvent : function(e) {
181        var target = e.getTarget();
182        while ((target !== null) && (target.parentNode != this.el.dom)) {
183            target = target.parentNode;
184        }
185        if (!target) {
186            target = this.el.dom.lastChild || this.el.dom;
187        }
188        return target;
189    },
190
191        /**
192         * Create the drag data which consists of an object which has the property "ddel" as
193         * the drag proxy element.
194         */
195    getDragData : function(e) {
196        var target = this.findItemFromChild(e.getTarget());
197        if(target) {
198            if (!this.isSelected(target)) {
199                delete this.ignoreNextClick;
200                this.onItemClick(target, this.indexOf(target), e);
201                this.ignoreNextClick = true;
202            }
203            var dragData = {
204                sourceView: this,
205                viewNodes: [],
206                records: [],
207                copy: this.copy || (this.allowCopy && e.ctrlKey)
208            };
209            if (this.getSelectionCount() == 1) {
210                var i = this.getSelectedIndexes()[0];
211                var n = this.getNode(i);
212                dragData.viewNodes.push(dragData.ddel = n);
213                dragData.records.push(this.store.getAt(i));
214                dragData.repairXY = Ext.fly(n).getXY();
215            } else {
216                dragData.ddel = document.createElement('div');
217                dragData.ddel.className = 'multi-proxy';
218                this.collectSelection(dragData);
219            }
220            return dragData;
221        }
222        return false;
223    },
224
225    // override the default repairXY.
226    getRepairXY : function(e){
227        return this.dragData.repairXY;
228    },
229
230        // private
231    collectSelection: function(data) {
232        data.repairXY = Ext.fly(this.getSelectedNodes()[0]).getXY();
233        if (this.preserveSelectionOrder === true) {
234            Ext.each(this.getSelectedIndexes(), function(i) {
235                var n = this.getNode(i);
236                var dragNode = n.cloneNode(true);
237                dragNode.id = Ext.id();
238                data.ddel.appendChild(dragNode);
239                data.records.push(this.store.getAt(i));
240                data.viewNodes.push(n);
241            }, this);
242        } else {
243            var i = 0;
244            this.store.each(function(rec){
245                if (this.isSelected(i)) {
246                    var n = this.getNode(i);
247                    var dragNode = n.cloneNode(true);
248                    dragNode.id = Ext.id();
249                    data.ddel.appendChild(dragNode);
250                    data.records.push(this.store.getAt(i));
251                    data.viewNodes.push(n);
252                }
253                i++;
254            }, this);
255        }
256    },
257
258        /**
259         * Specify to which ddGroup items in this DDView may be dragged.
260     * @param {String} ddGroup The DD group name to assign this view to.
261         */
262    setDraggable: function(ddGroup) {
263        if (ddGroup instanceof Array) {
264            Ext.each(ddGroup, this.setDraggable, this);
265            return;
266        }
267        if (this.dragZone) {
268            this.dragZone.addToGroup(ddGroup);
269        } else {
270            this.dragZone = new Ext.dd.DragZone(this.getEl(), {
271                containerScroll: true,
272                ddGroup: ddGroup
273            });
274            // Draggability implies selection. DragZone's mousedown selects the element.
275            if (!this.multiSelect) { this.singleSelect = true; }
276
277            // Wire the DragZone's handlers up to methods in *this*
278            this.dragZone.getDragData = this.getDragData.createDelegate(this);
279            this.dragZone.getRepairXY = this.getRepairXY;
280            this.dragZone.onEndDrag = this.onEndDrag;
281        }
282    },
283
284        /**
285         * Specify from which ddGroup this DDView accepts drops.
286     * @param {String} ddGroup The DD group name from which to accept drops.
287         */
288    setDroppable: function(ddGroup) {
289        if (ddGroup instanceof Array) {
290            Ext.each(ddGroup, this.setDroppable, this);
291            return;
292        }
293        if (this.dropZone) {
294            this.dropZone.addToGroup(ddGroup);
295        } else {
296            this.dropZone = new Ext.dd.DropZone(this.getEl(), {
297                owningView: this,
298                containerScroll: true,
299                ddGroup: ddGroup
300            });
301
302            // Wire the DropZone's handlers up to methods in *this*
303            this.dropZone.getTargetFromEvent = this.getTargetFromEvent.createDelegate(this);
304            this.dropZone.onNodeEnter = this.onNodeEnter.createDelegate(this);
305            this.dropZone.onNodeOver = this.onNodeOver.createDelegate(this);
306            this.dropZone.onNodeOut = this.onNodeOut.createDelegate(this);
307            this.dropZone.onNodeDrop = this.onNodeDrop.createDelegate(this);
308        }
309    },
310
311        // private
312    getDropPoint : function(e, n, dd){
313        if (n == this.el.dom) { return "above"; }
314        var t = Ext.lib.Dom.getY(n), b = t + n.offsetHeight;
315        var c = t + (b - t) / 2;
316        var y = Ext.lib.Event.getPageY(e);
317        if(y <= c) {
318            return "above";
319        }else{
320            return "below";
321        }
322    },
323
324    // private
325    isValidDropPoint: function(pt, n, data) {
326        if (!data.viewNodes || (data.viewNodes.length != 1)) {
327            return true;
328        }
329        var d = data.viewNodes[0];
330        if (d == n) {
331            return false;
332        }
333        if ((pt == "below") && (n.nextSibling == d)) {
334            return false;
335        }
336        if ((pt == "above") && (n.previousSibling == d)) {
337            return false;
338        }
339        return true;
340    },
341
342    // private
343    onNodeEnter : function(n, dd, e, data){
344        if (this.highlightColor && (data.sourceView != this)) {
345            this.el.highlight(this.highlightColor);
346        }
347        return false;
348    },
349
350    // private
351    onNodeOver : function(n, dd, e, data){
352        var dragElClass = this.dropNotAllowed;
353        var pt = this.getDropPoint(e, n, dd);
354        if (this.isValidDropPoint(pt, n, data)) {
355            if (this.appendOnly || this.sortField) {
356                return "x-tree-drop-ok-below";
357            }
358
359            // set the insert point style on the target node
360            if (pt) {
361                var targetElClass;
362                if (pt == "above"){
363                    dragElClass = n.previousSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-above";
364                    targetElClass = "x-view-drag-insert-above";
365                } else {
366                    dragElClass = n.nextSibling ? "x-tree-drop-ok-between" : "x-tree-drop-ok-below";
367                    targetElClass = "x-view-drag-insert-below";
368                }
369                if (this.lastInsertClass != targetElClass){
370                    Ext.fly(n).replaceClass(this.lastInsertClass, targetElClass);
371                    this.lastInsertClass = targetElClass;
372                }
373            }
374        }
375        return dragElClass;
376    },
377
378    // private
379    onNodeOut : function(n, dd, e, data){
380        this.removeDropIndicators(n);
381    },
382
383    // private
384    onNodeDrop : function(n, dd, e, data){
385        if (this.fireEvent("drop", this, n, dd, e, data) === false) {
386            return false;
387        }
388        var pt = this.getDropPoint(e, n, dd);
389        var insertAt = (this.appendOnly || (n == this.el.dom)) ? this.store.getCount() : n.viewIndex;
390        if (pt == "below") {
391            insertAt++;
392        }
393
394        // Validate if dragging within a DDView
395        if (data.sourceView == this) {
396            // If the first element to be inserted below is the target node, remove it
397            if (pt == "below") {
398                if (data.viewNodes[0] == n) {
399                    data.viewNodes.shift();
400                }
401            } else {  // If the last element to be inserted above is the target node, remove it
402                if (data.viewNodes[data.viewNodes.length - 1] == n) {
403                    data.viewNodes.pop();
404                }
405            }
406
407            // Nothing to drop...
408            if (!data.viewNodes.length) {
409                return false;
410            }
411
412            // If we are moving DOWN, then because a store.remove() takes place first,
413            // the insertAt must be decremented.
414            if (insertAt > this.store.indexOf(data.records[0])) {
415                insertAt--;
416            }
417        }
418
419        // Dragging from a Tree. Use the Tree's recordFromNode function.
420        if (data.node instanceof Ext.tree.TreeNode) {
421            var r = data.node.getOwnerTree().recordFromNode(data.node);
422            if (r) {
423                data.records = [ r ];
424            }
425        }
426
427        if (!data.records) {
428            alert("Programming problem. Drag data contained no Records");
429            return false;
430        }
431
432        for (var i = 0; i < data.records.length; i++) {
433            var r = data.records[i];
434            var dup = this.store.getById(r.id);
435            if (dup && (dd != this.dragZone)) {
436                if(!this.allowDup && !this.allowTrash){
437                    Ext.fly(this.getNode(this.store.indexOf(dup))).frame("red", 1);
438                    return true
439                }
440                var x=new Ext.data.Record();
441                r.id=x.id;
442                delete x;
443            }
444            if (data.copy) {
445                this.store.insert(insertAt++, r.copy());
446            } else {
447                if (data.sourceView) {
448                    data.sourceView.isDirtyFlag = true;
449                    data.sourceView.store.remove(r);
450                }
451                if(!this.allowTrash)this.store.insert(insertAt++, r);
452            }
453            if(this.sortField){
454                this.store.sort(this.sortField, this.sortDir);
455            }
456            this.isDirtyFlag = true;
457        }
458        this.dragZone.cachedTarget = null;
459        return true;
460    },
461
462    // private
463    onEndDrag: function(data, e) {
464        var d = Ext.get(this.dragData.ddel);
465        if (d && d.hasClass("multi-proxy")) {
466            d.remove();
467            //delete this.dragData.ddel;
468        }
469    },
470
471    // private
472    removeDropIndicators : function(n){
473        if(n){
474            Ext.fly(n).removeClass([
475                "x-view-drag-insert-above",
476                "x-view-drag-insert-left",
477                "x-view-drag-insert-right",
478                "x-view-drag-insert-below"]);
479            this.lastInsertClass = "_noclass";
480        }
481    },
482
483        /**
484         * Add a delete option to the DDView's context menu.
485         * @param {String} imageUrl The URL of the "delete" icon image.
486         */
487    setDeletable: function(imageUrl) {
488        if (!this.singleSelect && !this.multiSelect) {
489            this.singleSelect = true;
490        }
491        var c = this.getContextMenu();
492        this.contextMenu.on("itemclick", function(item) {
493            switch (item.id) {
494                case "delete":
495                    this.remove(this.getSelectedIndexes());
496                    break;
497            }
498        }, this);
499        this.contextMenu.add({
500            icon: imageUrl || AU.resolveUrl("/images/delete.gif"),
501            id: "delete",
502            text: AU.getMessage("deleteItem")
503        });
504    },
505
506        /**
507         * Return the context menu for this DDView.
508     * @return {Ext.menu.Menu} The context menu
509         */
510    getContextMenu: function() {
511        if (!this.contextMenu) {
512            // Create the View's context menu
513            this.contextMenu = new Ext.menu.Menu({
514                id: this.id + "-contextmenu"
515            });
516            this.el.on("contextmenu", this.showContextMenu, this);
517        }
518        return this.contextMenu;
519    },
520
521    /**
522     * Disables the view's context menu.
523     */
524    disableContextMenu: function() {
525        if (this.contextMenu) {
526            this.el.un("contextmenu", this.showContextMenu, this);
527        }
528    },
529
530    // private
531    showContextMenu: function(e, item) {
532        item = this.findItemFromChild(e.getTarget());
533        if (item) {
534            e.stopEvent();
535            this.select(this.getNode(item), this.multiSelect && e.ctrlKey, true);
536            this.contextMenu.showAt(e.getXY());
537        }
538    },
539
540        /**
541         * Remove {@link Ext.data.Record}s at the specified indices.
542         * @param {Array/Number} selectedIndices The index (or Array of indices) of Records to remove.
543         */
544    remove: function(selectedIndices) {
545        selectedIndices = [].concat(selectedIndices);
546        for (var i = 0; i < selectedIndices.length; i++) {
547            var rec = this.store.getAt(selectedIndices[i]);
548            this.store.remove(rec);
549        }
550    },
551
552        /**
553         * Double click fires the {@link #dblclick} event. Additionally, if this DDView is draggable, and there is only one other
554         * related DropZone that is in another DDView, it drops the selected node on that DDView.
555         */
556    onDblClick : function(e){
557        var item = this.findItemFromChild(e.getTarget());
558        if(item){
559            if (this.fireEvent("dblclick", this, this.indexOf(item), item, e) === false) {
560                return false;
561            }
562            if (this.dragGroup) {
563                var targets = Ext.dd.DragDropMgr.getRelated(this.dragZone, true);
564
565                // Remove instances of this View's DropZone
566                while (targets.indexOf(this.dropZone) !== -1) {
567                    targets.remove(this.dropZone);
568                }
569
570                // If there's only one other DropZone, and it is owned by a DDView, then drop it in
571                if ((targets.length == 1) && (targets[0].owningView)) {
572                    this.dragZone.cachedTarget = null;
573                    var el = Ext.get(targets[0].getEl());
574                    var box = el.getBox(true);
575                    targets[0].onNodeDrop(el.dom, {
576                        target: el.dom,
577                        xy: [box.x, box.y + box.height - 1]
578                    }, null, this.getDragData(e));
579                }
580            }
581        }
582    },
583
584    // private
585    onItemClick : function(item, index, e){
586        // The DragZone's mousedown->getDragData already handled selection
587        if (this.ignoreNextClick) {
588            delete this.ignoreNextClick;
589            return;
590        }
591
592        if(this.fireEvent("beforeclick", this, index, item, e) === false){
593            return false;
594        }
595        if(this.multiSelect || this.singleSelect){
596            if(this.multiSelect && e.shiftKey && this.lastSelection){
597                this.select(this.getNodes(this.indexOf(this.lastSelection), index), false);
598            } else if (this.isSelected(item) && e.ctrlKey) {
599                this.deselect(item);
600            }else{
601                this.deselect(item);
602                this.select(item, this.multiSelect && e.ctrlKey);
603                this.lastSelection = item;
604            }
605            e.preventDefault();
606        }
607        return true;
608    }
609});
Note: See TracBrowser for help on using the repository browser.