source: Dev/trunk/src/client/dojox/grid/enhanced/plugins/DnD.js

Last change on this file was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 30.3 KB
Line 
1define([
2        "dojo/_base/kernel",
3        "dojo/_base/declare",
4        "dojo/_base/connect",
5        "dojo/_base/array",
6        "dojo/_base/lang",
7        "dojo/_base/html",
8        "dojo/_base/json",
9        "dojo/_base/window",
10        "dojo/query",
11        "dojo/keys",
12        "dojo/dnd/Source",
13        "dojo/dnd/Avatar",
14        "../_Plugin",
15        "../../EnhancedGrid",
16        "dojo/dnd/Manager",
17        "./Selector",
18        "./Rearrange"
19], function(dojo, declare, connect, array, lang, html, json, win, query, keys, Source, Avatar, _Plugin, EnhancedGrid, Manager){
20
21var _devideToArrays = function(a){
22                a.sort(function(v1, v2){
23                        return v1 - v2;
24                });
25                var arr = [[a[0]]];
26                for(var i = 1, j = 0; i < a.length; ++i){
27                        if(a[i] == a[i-1] + 1){
28                                arr[j].push(a[i]);
29                        }else{
30                                arr[++j] = [a[i]];
31                        }
32                }
33                return arr;
34        },
35        _joinToArray = function(arrays){
36                var a = arrays[0];
37                for(var i = 1; i < arrays.length; ++i){
38                        a = a.concat(arrays[i]);
39                }
40                return a;
41        };
42var GridDnDElement = declare("dojox.grid.enhanced.plugins.GridDnDElement", null, {
43        constructor: function(dndPlugin){
44                this.plugin = dndPlugin;
45                this.node = html.create("div");
46                this._items = {};
47        },
48        destroy: function(){
49                this.plugin = null;
50                html.destroy(this.node);
51                this.node = null;
52                this._items = null;
53        },
54        createDnDNodes: function(dndRegion){
55                this.destroyDnDNodes();
56                var acceptType = ["grid/" + dndRegion.type + "s"];
57                var itemNodeIdBase = this.plugin.grid.id + "_dndItem";
58                array.forEach(dndRegion.selected, function(range, i){
59                        var id = itemNodeIdBase + i;
60                        this._items[id] = {
61                                "type": acceptType,
62                                "data": range,
63                                "dndPlugin": this.plugin
64                        };
65                        this.node.appendChild(html.create("div", {
66                                "id": id
67                        }));
68                }, this);
69        },
70        getDnDNodes: function(){
71                return array.map(this.node.childNodes, function(node){
72                        return node;
73                });
74        },
75        destroyDnDNodes: function(){
76                html.empty(this.node);
77                this._items = {};
78        },
79        getItem: function(nodeId){
80                return this._items[nodeId];
81        }
82});
83var GridDnDSource = declare("dojox.grid.enhanced.plugins.GridDnDSource", Source,{
84        accept: ["grid/cells", "grid/rows", "grid/cols"],
85        constructor: function(node, param){
86                this.grid = param.grid;
87                this.dndElem = param.dndElem;
88                this.dndPlugin = param.dnd;
89                this.sourcePlugin = null;
90        },
91        destroy: function(){
92                this.inherited(arguments);
93                this.grid = null;
94                this.dndElem = null;
95                this.dndPlugin = null;
96                this.sourcePlugin = null;
97        },
98        getItem: function(nodeId){
99                return this.dndElem.getItem(nodeId);
100        },
101        checkAcceptance: function(source, nodes){
102                if(this != source && nodes[0]){
103                        var item = source.getItem(nodes[0].id);
104                        if(item.dndPlugin){
105                                var type = item.type;
106                                for(var j = 0; j < type.length; ++j){
107                                        if(type[j] in this.accept){
108                                                if(this.dndPlugin._canAccept(item.dndPlugin)){
109                                                        this.sourcePlugin = item.dndPlugin;
110                                                }else{
111                                                        return false;
112                                                }
113                                                break;
114                                        }
115                                }
116                        }else if("grid/rows" in this.accept){
117                                var rows = [];
118                                array.forEach(nodes, function(node){
119                                        var item = source.getItem(node.id);
120                                        if(item.data && array.indexOf(item.type, "grid/rows") >= 0){
121                                                var rowData = item.data;
122                                                if(typeof item.data == "string"){
123                                                        rowData = json.fromJson(item.data);
124                                                }
125                                                if(rowData){
126                                                        rows.push(rowData);
127                                                }
128                                        }
129                                });
130                                if(rows.length){
131                                        this.sourcePlugin = {
132                                                _dndRegion: {
133                                                        type: "row",
134                                                        selected: [rows]
135                                                }
136                                        };
137                                }else{
138                                        return false;
139                                }
140                        }
141                }
142                return this.inherited(arguments);
143        },
144        onDraggingOver: function(){
145                this.dndPlugin.onDraggingOver(this.sourcePlugin);
146        },
147        onDraggingOut: function(){
148                this.dndPlugin.onDraggingOut(this.sourcePlugin);
149        },
150        onDndDrop: function(source, nodes, copy, target){
151                //this.inherited(arguments);
152                this.onDndCancel();
153                if(this != source && this == target){
154                        this.dndPlugin.onDragIn(this.sourcePlugin, copy);
155                }
156        }
157});
158
159var GridDnDAvatar = declare("dojox.grid.enhanced.plugins.GridDnDAvatar", Avatar, {
160        construct: function(){
161                // summary:
162                //              constructor function;
163                //              it is separate so it can be (dynamically) overwritten in case of need
164                this._itemType = this.manager._dndPlugin._dndRegion.type;
165                this._itemCount = this._getItemCount();
166                       
167                this.isA11y = html.hasClass(win.body(), "dijit_a11y");
168                var a = html.create("table", {
169                                "border": "0",
170                                "cellspacing": "0",
171                                "class": "dojoxGridDndAvatar",
172                                "style": {
173                                        position: "absolute",
174                                        zIndex: "1999",
175                                        margin: "0px"
176                                }
177                        }),
178                        source = this.manager.source,
179                        b = html.create("tbody", null, a),
180                        tr = html.create("tr", null, b),
181                        td = html.create("td", {
182                                "class": "dojoxGridDnDIcon"
183                        }, tr);
184                if(this.isA11y){
185                        html.create("span", {
186                                "id" : "a11yIcon",
187                                "innerHTML" : this.manager.copy ? '+' : "<"
188                        }, td);
189                }
190                td = html.create("td", {
191                        "class" : "dojoxGridDnDItemIcon " + this._getGridDnDIconClass()
192                }, tr);
193                td = html.create("td", null, tr);
194                html.create("span", {
195                        "class": "dojoxGridDnDItemCount",
196                        "innerHTML": source.generateText ? this._generateText() : ""
197                }, td);
198                // we have to set the opacity on IE only after the node is live
199                html.style(tr, {
200                        "opacity": 0.9
201                });
202                this.node = a;
203        },
204        _getItemCount: function(){
205                var selected = this.manager._dndPlugin._dndRegion.selected,
206                        count = 0;
207                switch(this._itemType){
208                        case "cell":
209                                selected = selected[0];
210                                var cells = this.manager._dndPlugin.grid.layout.cells,
211                                        colCount = selected.max.col - selected.min.col + 1,
212                                        rowCount = selected.max.row - selected.min.row + 1;
213                                if(colCount > 1){
214                                        for(var i = selected.min.col; i <= selected.max.col; ++i){
215                                                if(cells[i].hidden){
216                                                        --colCount;
217                                                }
218                                        }
219                                }
220                                count = colCount * rowCount;
221                                break;
222                        case "row":
223                        case "col":
224                                count = _joinToArray(selected).length;
225                }
226                return count;
227        },
228        _getGridDnDIconClass: function(){
229                return {
230                        "row": ["dojoxGridDnDIconRowSingle", "dojoxGridDnDIconRowMulti"],
231                        "col": ["dojoxGridDnDIconColSingle", "dojoxGridDnDIconColMulti"],
232                        "cell": ["dojoxGridDnDIconCellSingle", "dojoxGridDnDIconCellMulti"]
233                }[this._itemType][this._itemCount == 1 ? 0 : 1];
234        },
235        _generateText: function(){
236                // summary:
237                //              generates a proper text to reflect copying or moving of items
238                return "(" + this._itemCount + ")";
239        }
240});
241var DnD = declare("dojox.grid.enhanced.plugins.DnD", _Plugin, {
242        // summary:
243        //              Provide drag and drop for grid columns/rows/cells within grid and out of grid.
244        //              The store of grid must implement dojo.data.api.Write.
245        //
246        //              DnD selected columns:
247        //              Support moving within grid, moving/copying out of grid to a non-grid DnD target.
248        //
249        //              DnD selected rows:
250        //              Support moving within grid, moving/copying out of grid to any DnD target.
251        //
252        //              DnD selected cells (in rectangle shape only):
253        //              Support moving/copying within grid, moving/copying out of grid to any DnD target.
254       
255        // name: String,
256        //              plugin name;
257        name: "dnd",
258       
259        _targetAnchorBorderWidth: 2,
260        _copyOnly: false,
261        _config: {
262                "row":{
263                        "within":true,
264                        "in":true,
265                        "out":true
266                },
267                "col":{
268                        "within":true,
269                        "in":true,
270                        "out":true
271                },
272                "cell":{
273                        "within":true,
274                        "in":true,
275                        "out":true
276                }
277        },
278        constructor: function(grid, args){
279                this.grid = grid;
280                this._config = lang.clone(this._config);
281                args = lang.isObject(args) ? args : {};
282                this.setupConfig(args.dndConfig);
283                this._copyOnly = !!args.copyOnly;
284               
285                //Get the plugins we are dependent on.
286                this._mixinGrid();
287                this.selector = grid.pluginMgr.getPlugin("selector");
288                this.rearranger = grid.pluginMgr.getPlugin("rearrange");
289                //TODO: waiting for a better plugin framework to pass args to dependent plugins.
290                this.rearranger.setArgs(args);
291               
292                //Initialized the components we need.
293                this._clear();
294                this._elem = new GridDnDElement(this);
295                this._source = new GridDnDSource(this._elem.node, {
296                        "grid": grid,
297                        "dndElem": this._elem,
298                        "dnd": this
299                });
300                this._container = query(".dojoxGridMasterView", this.grid.domNode)[0];
301                this._initEvents();
302        },
303        destroy: function(){
304                this.inherited(arguments);
305                this._clear();
306                this._source.destroy();
307                this._elem.destroy();
308                this._container = null;
309                this.grid = null;
310                this.selector = null;
311                this.rearranger = null;
312                this._config = null;
313        },
314        _mixinGrid: function(){
315                // summary:
316                //              Provide APIs for grid.
317                this.grid.setupDnDConfig = lang.hitch(this, "setupConfig");
318                this.grid.dndCopyOnly = lang.hitch(this, "copyOnly");
319        },
320        setupConfig: function(config){
321                // summary:
322                //              Configure which DnD functionalities are needed.
323                //              Combination of any item from type set ("row", "col", "cell")
324                //              and any item from mode set("within", "in", "out") is configurable.
325                //
326                //              "row", "col", "cell" are straightforward, while the other 3 are explained below:
327                //
328                //              - "within": DnD within grid, that is, column/row reordering and cell moving/copying.
329                //              - "in": Whether allowed to accept rows/cells (currently not support columns) from another grid.
330                //              - "out": Whether allowed to drag out of grid, to another grid or even to any other DnD target.
331                //
332                //              If not provided in the config, will use the default.
333                //              When declared together, Mode set has higher priority than type set.
334                // config: Object
335                //              DnD configuration object.
336                //              See the examples below.
337                // example:
338                //              The following code disables row DnD within grid,
339                //              but still can drag rows out of grid or drag rows from other gird.
340                //      |       setUpConfig({
341                //      |               "row": {
342                //      |                       "within": false
343                //      |               }
344                //      |       });
345                //
346                //              The opposite way is also okay:
347                //      |       setUpConfig({
348                //      |               "within": {
349                //      |                       "row": false
350                //      |               }
351                //      |       });
352                //
353                //              And if you'd like to disable/enable a whole set, here's a shortcut:
354                //      |       setUpConfig({
355                //      |               "cell", true,
356                //      |               "out": false
357                //      |       });
358                //
359                //              Because mode has higher priority than type, the following will disable row dnd within grid:
360                //      |       setUpConfig({
361                //      |               "within", {
362                //      |                       "row": false;
363                //      |               },
364                //      |               "row", {
365                //      |                       "within": true
366                //      |               }
367                //      |       });
368                if(config && lang.isObject(config)){
369                        var firstLevel = ["row", "col", "cell"],
370                                secondLevel = ["within", "in", "out"],
371                                cfg = this._config;
372                        array.forEach(firstLevel, function(type){
373                                if(type in config){
374                                        var t = config[type];
375                                        if(t && lang.isObject(t)){
376                                                array.forEach(secondLevel, function(mode){
377                                                        if(mode in t){
378                                                                cfg[type][mode] = !!t[mode];
379                                                        }
380                                                });
381                                        }else{
382                                                array.forEach(secondLevel, function(mode){
383                                                        cfg[type][mode] = !!t;
384                                                });
385                                        }
386                                }
387                        });
388                        array.forEach(secondLevel, function(mode){
389                                if(mode in config){
390                                        var m = config[mode];
391                                        if(m && lang.isObject(m)){
392                                                array.forEach(firstLevel, function(type){
393                                                        if(type in m){
394                                                                cfg[type][mode] = !!m[type];
395                                                        }
396                                                });
397                                        }else{
398                                                array.forEach(firstLevel, function(type){
399                                                        cfg[type][mode] = !!m;
400                                                });
401                                        }
402                                }
403                        });
404                }
405        },
406        copyOnly: function(isCopyOnly){
407                // summary:
408                //              Setter/getter of this._copyOnly.
409                if(typeof isCopyOnly != "undefined"){
410                        this._copyOnly = !!isCopyOnly;
411                }
412                return this._copyOnly;
413        },
414        _isOutOfGrid: function(evt){
415                var gridPos = html.position(this.grid.domNode), x = evt.clientX, y = evt.clientY;
416                return y < gridPos.y || y > gridPos.y + gridPos.h ||
417                        x < gridPos.x || x > gridPos.x + gridPos.w;
418        },
419        _onMouseMove: function(evt){
420                if(this._dndRegion && !this._dnding && !this._externalDnd){
421                        this._dnding = true;
422                        this._startDnd(evt);
423                }else{
424                        if(this._isMouseDown && !this._dndRegion){
425                                delete this._isMouseDown;
426                                this._oldCursor = html.style(win.body(), "cursor");
427                                html.style(win.body(), "cursor", "not-allowed");
428                        }
429                        //TODO: should implement as mouseenter/mouseleave
430                        //But we have an avatar under mouse when dnd, and this will cause a lot of mouseenter in FF.
431                        var isOut = this._isOutOfGrid(evt);
432                        if(!this._alreadyOut && isOut){
433                                this._alreadyOut = true;
434                                if(this._dnding){
435                                        this._destroyDnDUI(true, false);
436                                }
437                                this._moveEvent = evt;
438                                this._source.onOutEvent();
439                        }else if(this._alreadyOut && !isOut){
440                                this._alreadyOut = false;
441                                if(this._dnding){
442                                        this._createDnDUI(evt, true);
443                                }
444                                this._moveEvent = evt;
445                                this._source.onOverEvent();
446                        }
447                }
448        },
449        _onMouseUp: function(){
450                if(!this._extDnding && !this._isSource){
451                        var isInner = this._dnding && !this._alreadyOut;
452                        if(isInner && this._config[this._dndRegion.type]["within"]){
453                                this._rearrange();
454                        }
455                        this._endDnd(isInner);
456                }
457                html.style(win.body(), "cursor", this._oldCursor || "");
458                delete this._isMouseDown;
459        },
460        _initEvents: function(){
461                var g = this.grid, s = this.selector;
462                this.connect(win.doc, "onmousemove", "_onMouseMove");
463                this.connect(win.doc, "onmouseup", "_onMouseUp");
464               
465                this.connect(g, "onCellMouseOver", function(evt){
466                        if(!this._dnding && !s.isSelecting() && !evt.ctrlKey){
467                                this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
468                                s.selectEnabled(!this._dndReady);
469                        }
470                });
471                this.connect(g, "onHeaderCellMouseOver", function(evt){
472                        if(this._dndReady){
473                                s.selectEnabled(true);
474                        }
475                });
476                this.connect(g, "onRowMouseOver", function(evt){
477                        if(this._dndReady && !evt.cell){
478                                s.selectEnabled(true);
479                        }
480                });
481                this.connect(g, "onCellMouseDown", function(evt){
482                        if(!evt.ctrlKey && this._dndReady){
483                                this._dndRegion = this._getDnDRegion(evt.rowIndex, evt.cell.index);
484                                this._isMouseDown = true;
485                        }
486                });
487                this.connect(g, "onCellMouseUp", function(evt){
488                        if(!this._dndReady && !s.isSelecting() && evt.cell){
489                                this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
490                                s.selectEnabled(!this._dndReady);
491                        }
492                });
493                this.connect(g, "onCellClick", function(evt){
494                        if(this._dndReady && !evt.ctrlKey && !evt.shiftKey){
495                                s.select("cell", evt.rowIndex, evt.cell.index);
496                        }
497                });
498                this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target, evt){
499                        if(this._dnding){
500                                this._markTargetAnchor(evt);
501                        }
502                });
503                this.connect(win.doc, "onkeydown", function(evt){
504                        if(evt.keyCode == keys.ESCAPE){
505                                this._endDnd(false);
506                        }else if(evt.keyCode == keys.CTRL){
507                                s.selectEnabled(true);
508                                this._isCopy = true;
509                        }
510                });
511                this.connect(win.doc, "onkeyup", function(evt){
512                        if(evt.keyCode == keys.CTRL){
513                                s.selectEnabled(!this._dndReady);
514                                this._isCopy = false;
515                        }
516                });
517        },
518        _clear: function(){
519                this._dndRegion = null;
520                this._target = null;
521                this._moveEvent = null;
522                this._targetAnchor = {};
523                this._dnding = false;
524                this._externalDnd = false;
525                this._isSource = false;
526                this._alreadyOut = false;
527                this._extDnding = false;
528        },
529        _getDnDRegion: function(rowIndex, colIndex){
530                var s = this.selector,
531                        selected = s._selected,
532                        flag = (!!selected.cell.length) | (!!selected.row.length << 1) | (!!selected.col.length << 2),
533                        type;
534                switch(flag){
535                        case 1:
536                                type = "cell";
537                                if(!this._config[type]["within"] && !this._config[type]["out"]){
538                                        return null;
539                                }
540                                var cells = this.grid.layout.cells,
541                                        getCount = function(range){
542                                                var hiddenColCnt = 0;
543                                                for(var i = range.min.col; i <= range.max.col; ++i){
544                                                        if(cells[i].hidden){
545                                                                ++hiddenColCnt;
546                                                        }
547                                                }
548                                                return (range.max.row - range.min.row + 1) * (range.max.col - range.min.col + 1 - hiddenColCnt);
549                                        },
550                                        inRange = function(item, range){
551                                                return item.row >= range.min.row && item.row <= range.max.row &&
552                                                        item.col >= range.min.col && item.col <= range.max.col;
553                                        },
554                                        range = {
555                                                max: {
556                                                        row: -1,
557                                                        col: -1
558                                                },
559                                                min: {
560                                                        row: Infinity,
561                                                        col: Infinity
562                                                }
563                                        };
564                               
565                                array.forEach(selected[type], function(item){
566                                        if(item.row < range.min.row){
567                                                range.min.row = item.row;
568                                        }
569                                        if(item.row > range.max.row){
570                                                range.max.row = item.row;
571                                        }
572                                        if(item.col < range.min.col){
573                                                range.min.col = item.col;
574                                        }
575                                        if(item.col > range.max.col){
576                                                range.max.col = item.col;
577                                        }
578                                });
579                                if(array.some(selected[type], function(item){
580                                        return item.row == rowIndex && item.col == colIndex;
581                                })){
582                                        if(getCount(range) == selected[type].length && array.every(selected[type], function(item){
583                                                return inRange(item, range);
584                                        })){
585                                                return {
586                                                        "type": type,
587                                                        "selected": [range],
588                                                        "handle": {
589                                                                "row": rowIndex,
590                                                                "col": colIndex
591                                                        }
592                                                };
593                                        }
594                                }
595                                return null;
596                        case 2: case 4:
597                                type = flag == 2 ? "row" : "col";
598                                if(!this._config[type]["within"] && !this._config[type]["out"]){
599                                        return null;
600                                }
601                                var res = s.getSelected(type);
602                                if(res.length){
603                                        return {
604                                                "type": type,
605                                                "selected": _devideToArrays(res),
606                                                "handle": flag == 2 ? rowIndex : colIndex
607                                        };
608                                }
609                                return null;
610                }
611                return null;
612        },
613        _startDnd: function(evt){
614                this._createDnDUI(evt);
615        },
616        _endDnd: function(destroySource){
617                this._destroyDnDUI(false, destroySource);
618                this._clear();
619        },
620        _createDnDUI: function(evt, isMovingIn){
621                //By default the master view of grid do not have height, because the children in it are all positioned absolutely.
622                //But we need it to contain avatars.
623                var viewPos = html.position(this.grid.views.views[0].domNode);
624                html.style(this._container, "height", viewPos.h + "px");
625                try{
626                        //If moving in from out side, dnd source is already created.
627                        if(!isMovingIn){
628                                this._createSource(evt);
629                        }
630                        this._createMoveable(evt);
631                        this._oldCursor = html.style(win.body(), "cursor");
632                        html.style(win.body(), "cursor", "default");
633                }catch(e){
634                        console.warn("DnD._createDnDUI() error:", e);
635                }
636        },
637        _destroyDnDUI: function(isMovingOut, destroySource){
638                try{
639                        if(destroySource){
640                                this._destroySource();
641                        }
642                        this._unmarkTargetAnchor();
643                        if(!isMovingOut){
644                                this._destroyMoveable();
645                        }
646                        html.style(win.body(), "cursor", this._oldCursor);
647                }catch(e){
648                        console.warn("DnD._destroyDnDUI() error:", this.grid.id, e);
649                }
650        },
651        _createSource: function(evt){
652                this._elem.createDnDNodes(this._dndRegion);
653                var m = Manager.manager();
654                var oldMakeAvatar = m.makeAvatar;
655                m._dndPlugin = this;
656                m.makeAvatar = function(){
657                        var avatar = new GridDnDAvatar(m);
658                        delete m._dndPlugin;
659                        return avatar;
660                };
661                m.startDrag(this._source, this._elem.getDnDNodes(), evt.ctrlKey);
662                m.makeAvatar = oldMakeAvatar;
663                m.onMouseMove(evt);
664        },
665        _destroySource: function(){
666                connect.publish("/dnd/cancel");
667        },
668        _createMoveable: function(evt){
669                if(!this._markTagetAnchorHandler){
670                        this._markTagetAnchorHandler = this.connect(win.doc, "onmousemove", "_markTargetAnchor");
671                }
672        },
673        _destroyMoveable: function(){
674                this.disconnect(this._markTagetAnchorHandler);
675                delete this._markTagetAnchorHandler;
676        },
677        _calcColTargetAnchorPos: function(evt, containerPos){
678                // summary:
679                //              Calculate the position of the column DnD avatar
680                var i, headPos, left, target, ex = evt.clientX,
681                        cells = this.grid.layout.cells,
682                        ltr = html._isBodyLtr(),
683                        headers = this._getVisibleHeaders();
684                for(i = 0; i < headers.length; ++i){
685                        headPos = html.position(headers[i].node);
686                        if(ltr ? ((i === 0 || ex >= headPos.x) && ex < headPos.x + headPos.w) :
687                                ((i === 0 || ex < headPos.x + headPos.w) && ex >= headPos.x)){
688                                left = headPos.x + (ltr ? 0 : headPos.w);
689                                break;
690                        }else if(ltr ? (i === headers.length - 1 && ex >= headPos.x + headPos.w) :
691                                (i === headers.length - 1 && ex < headPos.x)){
692                                ++i;
693                                left = headPos.x + (ltr ? headPos.w : 0);
694                                break;
695                        }
696                }
697                if(i < headers.length){
698                        target = headers[i].cell.index;
699                        if(this.selector.isSelected("col", target) && this.selector.isSelected("col", target - 1)){
700                                var ranges = this._dndRegion.selected;
701                                for(i = 0; i < ranges.length; ++i){
702                                        if(array.indexOf(ranges[i], target) >= 0){
703                                                target = ranges[i][0];
704                                                headPos = html.position(cells[target].getHeaderNode());
705                                                left = headPos.x + (ltr ? 0 : headPos.w);
706                                                break;
707                                        }
708                                }
709                        }
710                }else{
711                        target = cells.length;
712                }
713                this._target = target;
714                return left - containerPos.x;
715        },
716        _calcRowTargetAnchorPos: function(evt, containerPos){
717                // summary:
718                //              Calculate the position of the row DnD avatar
719                var g = this.grid, top, i = 0,
720                        cells = g.layout.cells;
721                while(cells[i].hidden){ ++i; }
722                var cell = g.layout.cells[i],
723                        rowIndex = g.scroller.firstVisibleRow,
724                        cellNode = cell.getNode(rowIndex);
725                if(!cellNode){
726                        //if the target grid is empty, set to -1
727                        //which will be processed in Rearrange
728                    this._target = -1;
729                    return 0; //position of the insert bar
730                }
731                var nodePos = html.position(cellNode);
732                while(nodePos.y + nodePos.h < evt.clientY){
733                        if(++rowIndex >= g.rowCount){
734                                break;
735                        }
736                        nodePos = html.position(cell.getNode(rowIndex));
737                }
738                if(rowIndex < g.rowCount){
739                        if(this.selector.isSelected("row", rowIndex) && this.selector.isSelected("row", rowIndex - 1)){
740                                var ranges = this._dndRegion.selected;
741                                for(i = 0; i < ranges.length; ++i){
742                                        if(array.indexOf(ranges[i], rowIndex) >= 0){
743                                                rowIndex = ranges[i][0];
744                                                nodePos = html.position(cell.getNode(rowIndex));
745                                                break;
746                                        }
747                                }
748                        }
749                        top = nodePos.y;
750                }else{
751                        top = nodePos.y + nodePos.h;
752                }
753                this._target = rowIndex;
754                return top - containerPos.y;
755        },
756        _calcCellTargetAnchorPos: function(evt, containerPos, targetAnchor){
757                // summary:
758                //              Calculate the position of the cell DnD avatar
759                var s = this._dndRegion.selected[0],
760                        origin = this._dndRegion.handle,
761                        g = this.grid, ltr = html._isBodyLtr(),
762                        cells = g.layout.cells, headPos,
763                        minPos, maxPos, headers,
764                        height, width, left, top,
765                        minCol, maxCol, i,
766                        preSpan = origin.col - s.min.col,
767                        postSpan = s.max.col - origin.col,
768                        leftTopDiv, rightBottomDiv;
769                if(!targetAnchor.childNodes.length){
770                        leftTopDiv = html.create("div", {
771                                "class": "dojoxGridCellBorderLeftTopDIV"
772                        }, targetAnchor);
773                        rightBottomDiv = html.create("div", {
774                                "class": "dojoxGridCellBorderRightBottomDIV"
775                        }, targetAnchor);
776                }else{
777                        leftTopDiv = query(".dojoxGridCellBorderLeftTopDIV", targetAnchor)[0];
778                        rightBottomDiv = query(".dojoxGridCellBorderRightBottomDIV", targetAnchor)[0];
779                }
780                for(i = s.min.col + 1; i < origin.col; ++i){
781                        if(cells[i].hidden){
782                                --preSpan;
783                        }
784                }
785                for(i = origin.col + 1; i < s.max.col; ++i){
786                        if(cells[i].hidden){
787                                --postSpan;
788                        }
789                }
790                headers = this._getVisibleHeaders();
791                //calc width
792                for(i = preSpan; i < headers.length - postSpan; ++i){
793                        headPos = html.position(headers[i].node);
794                        if((evt.clientX >= headPos.x && evt.clientX < headPos.x + headPos.w) || //within in this column
795                                //prior to this column, but within range
796                                (i == preSpan && (ltr ? evt.clientX < headPos.x : evt.clientX >= headPos.x + headPos.w)) ||
797                                //post to this column, but within range
798                                (i == headers.length - postSpan - 1 && (ltr ? evt.clientX >= headPos.x + headPos.w : evt < headPos.x))){
799                                        minCol = headers[i - preSpan];
800                                        maxCol = headers[i + postSpan];
801                                        minPos = html.position(minCol.node);
802                                        maxPos = html.position(maxCol.node);
803                                        minCol = minCol.cell.index;
804                                        maxCol = maxCol.cell.index;
805                                        left = ltr ? minPos.x : maxPos.x;
806                                        width = ltr ? (maxPos.x + maxPos.w - minPos.x) : (minPos.x + minPos.w - maxPos.x);
807                                        break;
808                        }
809                }
810                //calc height
811                i = 0;
812                while(cells[i].hidden){ ++i; }
813                var cell = cells[i],
814                        rowIndex = g.scroller.firstVisibleRow,
815                        nodePos = html.position(cell.getNode(rowIndex));
816                while(nodePos.y + nodePos.h < evt.clientY){
817                        if(++rowIndex < g.rowCount){
818                                nodePos = html.position(cell.getNode(rowIndex));
819                        }else{
820                                break;
821                        }
822                }
823                var minRow = rowIndex >= origin.row - s.min.row ? rowIndex - origin.row + s.min.row : 0;
824                var maxRow = minRow + s.max.row - s.min.row;
825                if(maxRow >= g.rowCount){
826                        maxRow = g.rowCount - 1;
827                        minRow = maxRow - s.max.row + s.min.row;
828                }
829                minPos = html.position(cell.getNode(minRow));
830                maxPos = html.position(cell.getNode(maxRow));
831                top = minPos.y;
832                height = maxPos.y + maxPos.h - minPos.y;
833                this._target = {
834                        "min":{
835                                "row": minRow,
836                                "col": minCol
837                        },
838                        "max":{
839                                "row": maxRow,
840                                "col": maxCol
841                        }
842                };
843                var anchorBorderSize = (html.marginBox(leftTopDiv).w - html.contentBox(leftTopDiv).w) / 2;
844                var leftTopCellPos = html.position(cells[minCol].getNode(minRow));
845                html.style(leftTopDiv, {
846                        "width": (leftTopCellPos.w - anchorBorderSize) + "px",
847                        "height": (leftTopCellPos.h - anchorBorderSize) + "px"
848                });
849                var rightBottomCellPos = html.position(cells[maxCol].getNode(maxRow));
850                html.style(rightBottomDiv, {
851                        "width": (rightBottomCellPos.w - anchorBorderSize) + "px",
852                        "height": (rightBottomCellPos.h - anchorBorderSize) + "px"
853                });
854                return {
855                        h: height,
856                        w: width,
857                        l: left - containerPos.x,
858                        t: top - containerPos.y
859                };
860        },
861        _markTargetAnchor: function(evt){
862                try{
863                var t = this._dndRegion.type;
864                if(this._alreadyOut || (this._dnding && !this._config[t]["within"]) || (this._extDnding && !this._config[t]["in"])){
865                        return;
866                }
867                var height, width, left, top,
868                        targetAnchor = this._targetAnchor[t],
869                        pos = html.position(this._container);
870                if(!targetAnchor){
871                        targetAnchor = this._targetAnchor[t] = html.create("div", {
872                                "class": (t == "cell") ? "dojoxGridCellBorderDIV" : "dojoxGridBorderDIV"
873                        });
874                        html.style(targetAnchor, "display", "none");
875                        this._container.appendChild(targetAnchor);
876                }
877                switch(t){
878                        case "col":
879                                height = pos.h;
880                                width = this._targetAnchorBorderWidth;
881                                left = this._calcColTargetAnchorPos(evt, pos);
882                                top = 0;
883                                break;
884                        case "row":
885                                height = this._targetAnchorBorderWidth;
886                                width = pos.w;
887                                left = 0;
888                                top = this._calcRowTargetAnchorPos(evt, pos);
889                                break;
890                        case "cell":
891                                var cellPos = this._calcCellTargetAnchorPos(evt, pos, targetAnchor);
892                                height = cellPos.h;
893                                width = cellPos.w;
894                                left = cellPos.l;
895                                top = cellPos.t;
896                }
897                if(typeof height == "number" && typeof width == "number" && typeof left == "number" && typeof top == "number"){
898                        html.style(targetAnchor, {
899                                "height": height + "px",
900                                "width": width + "px",
901                                "left": left + "px",
902                                "top": top + "px"
903                        });
904                        html.style(targetAnchor, "display", "");
905                }else{
906                        this._target = null;
907                }
908                }catch(e){
909                        console.warn("DnD._markTargetAnchor() error:",e);
910                }
911        },
912        _unmarkTargetAnchor: function(){
913                if(this._dndRegion){
914                        var targetAnchor = this._targetAnchor[this._dndRegion.type];
915                        if(targetAnchor){
916                                html.style(this._targetAnchor[this._dndRegion.type], "display", "none");
917                        }
918                }
919        },
920        _getVisibleHeaders: function(){
921                return array.map(array.filter(this.grid.layout.cells, function(cell){
922                        return !cell.hidden;
923                }), function(cell){
924                        return {
925                                "node": cell.getHeaderNode(),
926                                "cell": cell
927                        };
928                });
929        },
930        _rearrange: function(){
931                if(this._target === null){
932                        return;
933                }
934                var t = this._dndRegion.type;
935                var ranges = this._dndRegion.selected;
936                if(t === "cell"){
937                        this.rearranger[(this._isCopy || this._copyOnly) ? "copyCells" : "moveCells"](ranges[0], this._target === -1 ? null : this._target);
938                }else{
939                        this.rearranger[t == "col" ? "moveColumns" : "moveRows"](_joinToArray(ranges), this._target === -1 ? null: this._target);
940                }
941                this._target = null;
942        },
943        onDraggingOver: function(sourcePlugin){
944                if(!this._dnding && sourcePlugin){
945                        sourcePlugin._isSource = true;
946                        this._extDnding = true;
947                        if(!this._externalDnd){
948                                this._externalDnd = true;
949                                this._dndRegion = this._mapRegion(sourcePlugin.grid, sourcePlugin._dndRegion);
950                        }
951                        this._createDnDUI(this._moveEvent,true);
952                        this.grid.pluginMgr.getPlugin("autoScroll").readyForAutoScroll = true;
953                }
954        },
955        _mapRegion: function(srcGrid, dndRegion){
956                if(dndRegion.type === "cell"){
957                        var srcRange = dndRegion.selected[0];
958                        var cells = this.grid.layout.cells;
959                        var srcCells = srcGrid.layout.cells;
960                        var c, cnt = 0;
961                        for(c = srcRange.min.col; c <= srcRange.max.col; ++c){
962                                if(!srcCells[c].hidden){
963                                        ++cnt;
964                                }
965                        }
966                        for(c = 0; cnt > 0; ++c){
967                                if(!cells[c].hidden){
968                                        --cnt;
969                                }
970                        }
971                        var region = lang.clone(dndRegion);
972                        region.selected[0].min.col = 0;
973                        region.selected[0].max.col = c - 1;
974                        for(c = srcRange.min.col; c <= dndRegion.handle.col; ++c){
975                                if(!srcCells[c].hidden){
976                                        ++cnt;
977                                }
978                        }
979                        for(c = 0; cnt > 0; ++c){
980                                if(!cells[c].hidden){
981                                        --cnt;
982                                }
983                        }
984                        region.handle.col = c;
985                }
986                return dndRegion;
987        },
988        onDraggingOut: function(sourcePlugin){
989                if(this._externalDnd){
990                        this._extDnding = false;
991                        this._destroyDnDUI(true, false);
992                        if(sourcePlugin){
993                                sourcePlugin._isSource = false;
994                        }
995                }
996        },
997        onDragIn: function(sourcePlugin, isCopy){
998                var success = false;
999                if(this._target !== null){
1000                        var type = sourcePlugin._dndRegion.type;
1001                        var ranges = sourcePlugin._dndRegion.selected;
1002                        switch(type){
1003                                case "cell":
1004                                        this.rearranger.changeCells(sourcePlugin.grid, ranges[0], this._target);
1005                                        break;
1006                                case "row":
1007                                        var range = _joinToArray(ranges);
1008                                        this.rearranger.insertRows(sourcePlugin.grid, range, this._target);
1009                                        break;
1010                        }
1011                        success = true;
1012                }
1013                this._endDnd(true);
1014                if(sourcePlugin.onDragOut){
1015                        sourcePlugin.onDragOut(success && !isCopy);
1016                }
1017        },
1018        onDragOut: function(isMove){
1019                if(isMove && !this._copyOnly){
1020                        var type = this._dndRegion.type;
1021                        var ranges = this._dndRegion.selected;
1022                        switch(type){
1023                                case "cell":
1024                                        this.rearranger.clearCells(ranges[0]);
1025                                        break;
1026                                case "row":
1027                                        this.rearranger.removeRows(_joinToArray(ranges));
1028                                        break;
1029                        }
1030                }
1031                this._endDnd(true);
1032        },
1033        _canAccept: function(sourcePlugin){
1034                if(!sourcePlugin){
1035                        return false;
1036                }
1037                var srcRegion = sourcePlugin._dndRegion;
1038                var type = srcRegion.type;
1039                if(!this._config[type]["in"] || !sourcePlugin._config[type]["out"]){
1040                        return false;
1041                }
1042                var g = this.grid;
1043                var ranges = srcRegion.selected;
1044                var colCnt = array.filter(g.layout.cells, function(cell){
1045                        return !cell.hidden;
1046                }).length;
1047                var rowCnt = g.rowCount;
1048                var res = true;
1049                switch(type){
1050                        case "cell":
1051                                ranges = ranges[0];
1052                                res = g.store.getFeatures()["dojo.data.api.Write"] &&
1053                                        (ranges.max.row - ranges.min.row) <= rowCnt &&
1054                                        array.filter(sourcePlugin.grid.layout.cells, function(cell){
1055                                                return cell.index >= ranges.min.col && cell.index <= ranges.max.col && !cell.hidden;
1056                                        }).length <= colCnt;
1057                                //intentional drop through - don't break
1058                        case "row":
1059                                if(sourcePlugin._allDnDItemsLoaded()){
1060                                        return res;
1061                                }
1062                }
1063                return false;
1064        },
1065        _allDnDItemsLoaded: function(){
1066                if(this._dndRegion){
1067                        var type = this._dndRegion.type,
1068                                ranges = this._dndRegion.selected,
1069                                rows = [];
1070                        switch(type){
1071                                case "cell":
1072                                        for(var i = ranges[0].min.row, max = ranges[0].max.row; i <= max; ++i){
1073                                                rows.push(i);
1074                                        }
1075                                        break;
1076                                case "row":
1077                                        rows = _joinToArray(ranges);
1078                                        break;
1079                                default:
1080                                        return false;
1081                        }
1082                        var cache = this.grid._by_idx;
1083                        return array.every(rows, function(rowIndex){
1084                                return !!cache[rowIndex];
1085                        });
1086                }
1087                return false;
1088        }
1089});
1090
1091EnhancedGrid.registerPlugin(DnD/*name:'dnd'*/, {
1092        "dependency": ["selector", "rearrange"]
1093});
1094
1095return DnD;
1096});
Note: See TracBrowser for help on using the repository browser.