1095 lines
30 KiB
JavaScript
1095 lines
30 KiB
JavaScript
//>>built
|
|
define("dojox/grid/enhanced/plugins/DnD", [
|
|
"dojo/_base/kernel",
|
|
"dojo/_base/declare",
|
|
"dojo/_base/connect",
|
|
"dojo/_base/array",
|
|
"dojo/_base/lang",
|
|
"dojo/_base/html",
|
|
"dojo/_base/json",
|
|
"dojo/_base/window",
|
|
"dojo/query",
|
|
"dojo/keys",
|
|
"dojo/dnd/Source",
|
|
"dojo/dnd/Avatar",
|
|
"../_Plugin",
|
|
"../../EnhancedGrid",
|
|
"./Selector",
|
|
"./Rearrange",
|
|
"dojo/dnd/Manager"
|
|
], function(dojo, declare, connect, array, lang, html, json, win, query, keys, Source, Avatar, _Plugin, EnhancedGrid){
|
|
|
|
var _devideToArrays = function(a){
|
|
a.sort(function(v1, v2){
|
|
return v1 - v2;
|
|
});
|
|
var arr = [[a[0]]];
|
|
for(var i = 1, j = 0; i < a.length; ++i){
|
|
if(a[i] == a[i-1] + 1){
|
|
arr[j].push(a[i]);
|
|
}else{
|
|
arr[++j] = [a[i]];
|
|
}
|
|
}
|
|
return arr;
|
|
},
|
|
_joinToArray = function(arrays){
|
|
var a = arrays[0];
|
|
for(var i = 1; i < arrays.length; ++i){
|
|
a = a.concat(arrays[i]);
|
|
}
|
|
return a;
|
|
};
|
|
var GridDnDElement = declare("dojox.grid.enhanced.plugins.GridDnDElement", null, {
|
|
constructor: function(dndPlugin){
|
|
this.plugin = dndPlugin;
|
|
this.node = html.create("div");
|
|
this._items = {};
|
|
},
|
|
destroy: function(){
|
|
this.plugin = null;
|
|
html.destroy(this.node);
|
|
this.node = null;
|
|
this._items = null;
|
|
},
|
|
createDnDNodes: function(dndRegion){
|
|
this.destroyDnDNodes();
|
|
var acceptType = ["grid/" + dndRegion.type + "s"];
|
|
var itemNodeIdBase = this.plugin.grid.id + "_dndItem";
|
|
array.forEach(dndRegion.selected, function(range, i){
|
|
var id = itemNodeIdBase + i;
|
|
this._items[id] = {
|
|
"type": acceptType,
|
|
"data": range,
|
|
"dndPlugin": this.plugin
|
|
};
|
|
this.node.appendChild(html.create("div", {
|
|
"id": id
|
|
}));
|
|
}, this);
|
|
},
|
|
getDnDNodes: function(){
|
|
return array.map(this.node.childNodes, function(node){
|
|
return node;
|
|
});
|
|
},
|
|
destroyDnDNodes: function(){
|
|
html.empty(this.node);
|
|
this._items = {};
|
|
},
|
|
getItem: function(nodeId){
|
|
return this._items[nodeId];
|
|
}
|
|
});
|
|
var GridDnDSource = declare("dojox.grid.enhanced.plugins.GridDnDSource", Source,{
|
|
accept: ["grid/cells", "grid/rows", "grid/cols"],
|
|
constructor: function(node, param){
|
|
this.grid = param.grid;
|
|
this.dndElem = param.dndElem;
|
|
this.dndPlugin = param.dnd;
|
|
this.sourcePlugin = null;
|
|
},
|
|
destroy: function(){
|
|
this.inherited(arguments);
|
|
this.grid = null;
|
|
this.dndElem = null;
|
|
this.dndPlugin = null;
|
|
this.sourcePlugin = null;
|
|
},
|
|
getItem: function(nodeId){
|
|
return this.dndElem.getItem(nodeId);
|
|
},
|
|
checkAcceptance: function(source, nodes){
|
|
if(this != source && nodes[0]){
|
|
var item = source.getItem(nodes[0].id);
|
|
if(item.dndPlugin){
|
|
var type = item.type;
|
|
for(var j = 0; j < type.length; ++j){
|
|
if(type[j] in this.accept){
|
|
if(this.dndPlugin._canAccept(item.dndPlugin)){
|
|
this.sourcePlugin = item.dndPlugin;
|
|
}else{
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}else if("grid/rows" in this.accept){
|
|
var rows = [];
|
|
array.forEach(nodes, function(node){
|
|
var item = source.getItem(node.id);
|
|
if(item.data && array.indexOf(item.type, "grid/rows") >= 0){
|
|
var rowData = item.data;
|
|
if(typeof item.data == "string"){
|
|
rowData = json.fromJson(item.data);
|
|
}
|
|
if(rowData){
|
|
rows.push(rowData);
|
|
}
|
|
}
|
|
});
|
|
if(rows.length){
|
|
this.sourcePlugin = {
|
|
_dndRegion: {
|
|
type: "row",
|
|
selected: [rows]
|
|
}
|
|
};
|
|
}else{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return this.inherited(arguments);
|
|
},
|
|
onDraggingOver: function(){
|
|
this.dndPlugin.onDraggingOver(this.sourcePlugin);
|
|
},
|
|
onDraggingOut: function(){
|
|
this.dndPlugin.onDraggingOut(this.sourcePlugin);
|
|
},
|
|
onDndDrop: function(source, nodes, copy, target){
|
|
//this.inherited(arguments);
|
|
this.onDndCancel();
|
|
if(this != source && this == target){
|
|
this.dndPlugin.onDragIn(this.sourcePlugin, copy);
|
|
}
|
|
}
|
|
});
|
|
|
|
var GridDnDAvatar = declare("dojox.grid.enhanced.plugins.GridDnDAvatar", Avatar, {
|
|
construct: function(){
|
|
// summary:
|
|
// constructor function;
|
|
// it is separate so it can be (dynamically) overwritten in case of need
|
|
this._itemType = this.manager._dndPlugin._dndRegion.type;
|
|
this._itemCount = this._getItemCount();
|
|
|
|
this.isA11y = html.hasClass(win.body(), "dijit_a11y");
|
|
var a = html.create("table", {
|
|
"border": "0",
|
|
"cellspacing": "0",
|
|
"class": "dojoxGridDndAvatar",
|
|
"style": {
|
|
position: "absolute",
|
|
zIndex: "1999",
|
|
margin: "0px"
|
|
}
|
|
}),
|
|
source = this.manager.source,
|
|
b = html.create("tbody", null, a),
|
|
tr = html.create("tr", null, b),
|
|
td = html.create("td", {
|
|
"class": "dojoxGridDnDIcon"
|
|
}, tr);
|
|
if(this.isA11y){
|
|
html.create("span", {
|
|
"id" : "a11yIcon",
|
|
"innerHTML" : this.manager.copy ? '+' : "<"
|
|
}, td);
|
|
}
|
|
td = html.create("td", {
|
|
"class" : "dojoxGridDnDItemIcon " + this._getGridDnDIconClass()
|
|
}, tr);
|
|
td = html.create("td", null, tr);
|
|
html.create("span", {
|
|
"class": "dojoxGridDnDItemCount",
|
|
"innerHTML": source.generateText ? this._generateText() : ""
|
|
}, td);
|
|
// we have to set the opacity on IE only after the node is live
|
|
html.style(tr, {
|
|
"opacity": 0.9
|
|
});
|
|
this.node = a;
|
|
},
|
|
_getItemCount: function(){
|
|
var selected = this.manager._dndPlugin._dndRegion.selected,
|
|
count = 0;
|
|
switch(this._itemType){
|
|
case "cell":
|
|
selected = selected[0];
|
|
var cells = this.manager._dndPlugin.grid.layout.cells,
|
|
colCount = selected.max.col - selected.min.col + 1,
|
|
rowCount = selected.max.row - selected.min.row + 1;
|
|
if(colCount > 1){
|
|
for(var i = selected.min.col; i <= selected.max.col; ++i){
|
|
if(cells[i].hidden){
|
|
--colCount;
|
|
}
|
|
}
|
|
}
|
|
count = colCount * rowCount;
|
|
break;
|
|
case "row":
|
|
case "col":
|
|
count = _joinToArray(selected).length;
|
|
}
|
|
return count;
|
|
},
|
|
_getGridDnDIconClass: function(){
|
|
return {
|
|
"row": ["dojoxGridDnDIconRowSingle", "dojoxGridDnDIconRowMulti"],
|
|
"col": ["dojoxGridDnDIconColSingle", "dojoxGridDnDIconColMulti"],
|
|
"cell": ["dojoxGridDnDIconCellSingle", "dojoxGridDnDIconCellMulti"]
|
|
}[this._itemType][this._itemCount == 1 ? 0 : 1];
|
|
},
|
|
_generateText: function(){
|
|
// summary:
|
|
// generates a proper text to reflect copying or moving of items
|
|
return "(" + this._itemCount + ")";
|
|
}
|
|
});
|
|
var DnD = declare("dojox.grid.enhanced.plugins.DnD", _Plugin, {
|
|
// summary:
|
|
// Provide drag and drop for grid columns/rows/cells within grid and out of grid.
|
|
// The store of grid must implement dojo.data.api.Write.
|
|
// DnD selected columns:
|
|
// Support moving within grid, moving/copying out of grid to a non-grid DnD target.
|
|
// DnD selected rows:
|
|
// Support moving within grid, moving/copying out of grid to any DnD target.
|
|
// DnD selected cells (in rectangle shape only):
|
|
// Support moving/copying within grid, moving/copying out of grid to any DnD target.
|
|
//
|
|
|
|
// name: String,
|
|
// plugin name;
|
|
name: "dnd",
|
|
|
|
_targetAnchorBorderWidth: 2,
|
|
_copyOnly: false,
|
|
_config: {
|
|
"row":{
|
|
"within":true,
|
|
"in":true,
|
|
"out":true
|
|
},
|
|
"col":{
|
|
"within":true,
|
|
"in":true,
|
|
"out":true
|
|
},
|
|
"cell":{
|
|
"within":true,
|
|
"in":true,
|
|
"out":true
|
|
}
|
|
},
|
|
constructor: function(grid, args){
|
|
this.grid = grid;
|
|
this._config = lang.clone(this._config);
|
|
args = lang.isObject(args) ? args : {};
|
|
this.setupConfig(args.dndConfig);
|
|
this._copyOnly = !!args.copyOnly;
|
|
|
|
//Get the plugins we are dependent on.
|
|
this._mixinGrid();
|
|
this.selector = grid.pluginMgr.getPlugin("selector");
|
|
this.rearranger = grid.pluginMgr.getPlugin("rearrange");
|
|
//TODO: waiting for a better plugin framework to pass args to dependent plugins.
|
|
this.rearranger.setArgs(args);
|
|
|
|
//Initialized the components we need.
|
|
this._clear();
|
|
this._elem = new GridDnDElement(this);
|
|
this._source = new GridDnDSource(this._elem.node, {
|
|
"grid": grid,
|
|
"dndElem": this._elem,
|
|
"dnd": this
|
|
});
|
|
this._container = query(".dojoxGridMasterView", this.grid.domNode)[0];
|
|
this._initEvents();
|
|
},
|
|
destroy: function(){
|
|
this.inherited(arguments);
|
|
this._clear();
|
|
this._source.destroy();
|
|
this._elem.destroy();
|
|
this._container = null;
|
|
this.grid = null;
|
|
this.selector = null;
|
|
this.rearranger = null;
|
|
this._config = null;
|
|
},
|
|
_mixinGrid: function(){
|
|
// summary:
|
|
// Provide APIs for grid.
|
|
this.grid.setupDnDConfig = lang.hitch(this, "setupConfig");
|
|
this.grid.dndCopyOnly = lang.hitch(this, "copyOnly");
|
|
},
|
|
setupConfig: function(config){
|
|
// summary:
|
|
// Configure which DnD functionalities are needed.
|
|
// Combination of any item from type set ("row", "col", "cell")
|
|
// and any item from mode set("within", "in", "out") is configurable.
|
|
//
|
|
// "row", "col", "cell" are straitforward, while the other 3 are explained below:
|
|
// "within": DnD within grid, that is, column/row reordering and cell moving/copying.
|
|
// "in": Whether allowed to accept rows/cells (currently not support columns) from another grid.
|
|
// "out": Whether allowed to drag out of grid, to another grid or even to any other DnD target.
|
|
//
|
|
// If not provided in the config, will use the default.
|
|
// When declared together, Mode set has higher priority than type set.
|
|
// config: Object
|
|
// DnD configuration object.
|
|
// See the examples below.
|
|
// example:
|
|
// The following code disables row DnD within grid,
|
|
// but still can drag rows out of grid or drag rows from other gird.
|
|
// | setUpConfig({
|
|
// | "row": {
|
|
// | "within": false
|
|
// | }
|
|
// | });
|
|
//
|
|
// The opposite way is also okay:
|
|
// | setUpConfig({
|
|
// | "within": {
|
|
// | "row": false
|
|
// | }
|
|
// | });
|
|
//
|
|
// And if you'd like to disable/enable a whole set, here's a shortcut:
|
|
// | setUpConfig({
|
|
// | "cell", true,
|
|
// | "out": false
|
|
// | });
|
|
//
|
|
// Because mode has higher priority than type, the following will disable row dnd within grid:
|
|
// | setUpConfig({
|
|
// | "within", {
|
|
// | "row": false;
|
|
// | },
|
|
// | "row", {
|
|
// | "within": true
|
|
// | }
|
|
// | });
|
|
if(config && lang.isObject(config)){
|
|
var firstLevel = ["row", "col", "cell"],
|
|
secondLevel = ["within", "in", "out"],
|
|
cfg = this._config;
|
|
array.forEach(firstLevel, function(type){
|
|
if(type in config){
|
|
var t = config[type];
|
|
if(t && lang.isObject(t)){
|
|
array.forEach(secondLevel, function(mode){
|
|
if(mode in t){
|
|
cfg[type][mode] = !!t[mode];
|
|
}
|
|
});
|
|
}else{
|
|
array.forEach(secondLevel, function(mode){
|
|
cfg[type][mode] = !!t;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
array.forEach(secondLevel, function(mode){
|
|
if(mode in config){
|
|
var m = config[mode];
|
|
if(m && lang.isObject(m)){
|
|
array.forEach(firstLevel, function(type){
|
|
if(type in m){
|
|
cfg[type][mode] = !!m[type];
|
|
}
|
|
});
|
|
}else{
|
|
array.forEach(firstLevel, function(type){
|
|
cfg[type][mode] = !!m;
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
copyOnly: function(isCopyOnly){
|
|
// summary:
|
|
// Setter/getter of this._copyOnly.
|
|
if(typeof isCopyOnly != "undefined"){
|
|
this._copyOnly = !!isCopyOnly;
|
|
}
|
|
return this._copyOnly;
|
|
},
|
|
_isOutOfGrid: function(evt){
|
|
var gridPos = html.position(this.grid.domNode), x = evt.clientX, y = evt.clientY;
|
|
return y < gridPos.y || y > gridPos.y + gridPos.h ||
|
|
x < gridPos.x || x > gridPos.x + gridPos.w;
|
|
},
|
|
_onMouseMove: function(evt){
|
|
if(this._dndRegion && !this._dnding && !this._externalDnd){
|
|
this._dnding = true;
|
|
this._startDnd(evt);
|
|
}else{
|
|
if(this._isMouseDown && !this._dndRegion){
|
|
delete this._isMouseDown;
|
|
this._oldCursor = html.style(win.body(), "cursor");
|
|
html.style(win.body(), "cursor", "not-allowed");
|
|
}
|
|
//TODO: should implement as mouseenter/mouseleave
|
|
//But we have an avatar under mouse when dnd, and this will cause a lot of mouseenter in FF.
|
|
var isOut = this._isOutOfGrid(evt);
|
|
if(!this._alreadyOut && isOut){
|
|
this._alreadyOut = true;
|
|
if(this._dnding){
|
|
this._destroyDnDUI(true, false);
|
|
}
|
|
this._moveEvent = evt;
|
|
this._source.onOutEvent();
|
|
}else if(this._alreadyOut && !isOut){
|
|
this._alreadyOut = false;
|
|
if(this._dnding){
|
|
this._createDnDUI(evt, true);
|
|
}
|
|
this._moveEvent = evt;
|
|
this._source.onOverEvent();
|
|
}
|
|
}
|
|
},
|
|
_onMouseUp: function(){
|
|
if(!this._extDnding && !this._isSource){
|
|
var isInner = this._dnding && !this._alreadyOut;
|
|
if(isInner && this._config[this._dndRegion.type]["within"]){
|
|
this._rearrange();
|
|
}
|
|
this._endDnd(isInner);
|
|
}
|
|
html.style(win.body(), "cursor", this._oldCursor || "");
|
|
delete this._isMouseDown;
|
|
},
|
|
_initEvents: function(){
|
|
var g = this.grid, s = this.selector;
|
|
this.connect(win.doc, "onmousemove", "_onMouseMove");
|
|
this.connect(win.doc, "onmouseup", "_onMouseUp");
|
|
|
|
this.connect(g, "onCellMouseOver", function(evt){
|
|
if(!this._dnding && !s.isSelecting() && !evt.ctrlKey){
|
|
this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
|
|
s.selectEnabled(!this._dndReady);
|
|
}
|
|
});
|
|
this.connect(g, "onHeaderCellMouseOver", function(evt){
|
|
if(this._dndReady){
|
|
s.selectEnabled(true);
|
|
}
|
|
});
|
|
this.connect(g, "onRowMouseOver", function(evt){
|
|
if(this._dndReady && !evt.cell){
|
|
s.selectEnabled(true);
|
|
}
|
|
});
|
|
this.connect(g, "onCellMouseDown", function(evt){
|
|
if(!evt.ctrlKey && this._dndReady){
|
|
this._dndRegion = this._getDnDRegion(evt.rowIndex, evt.cell.index);
|
|
this._isMouseDown = true;
|
|
}
|
|
});
|
|
this.connect(g, "onCellMouseUp", function(evt){
|
|
if(!this._dndReady && !s.isSelecting() && evt.cell){
|
|
this._dndReady = s.isSelected("cell", evt.rowIndex, evt.cell.index);
|
|
s.selectEnabled(!this._dndReady);
|
|
}
|
|
});
|
|
this.connect(g, "onCellClick", function(evt){
|
|
if(this._dndReady && !evt.ctrlKey && !evt.shiftKey){
|
|
s.select("cell", evt.rowIndex, evt.cell.index);
|
|
}
|
|
});
|
|
this.connect(g, "onEndAutoScroll", function(isVertical, isForward, view, target, evt){
|
|
if(this._dnding){
|
|
this._markTargetAnchor(evt);
|
|
}
|
|
});
|
|
this.connect(win.doc, "onkeydown", function(evt){
|
|
if(evt.keyCode == keys.ESCAPE){
|
|
this._endDnd(false);
|
|
}else if(evt.keyCode == keys.CTRL){
|
|
s.selectEnabled(true);
|
|
this._isCopy = true;
|
|
}
|
|
});
|
|
this.connect(win.doc, "onkeyup", function(evt){
|
|
if(evt.keyCode == keys.CTRL){
|
|
s.selectEnabled(!this._dndReady);
|
|
this._isCopy = false;
|
|
}
|
|
});
|
|
},
|
|
_clear: function(){
|
|
this._dndRegion = null;
|
|
this._target = null;
|
|
this._moveEvent = null;
|
|
this._targetAnchor = {};
|
|
this._dnding = false;
|
|
this._externalDnd = false;
|
|
this._isSource = false;
|
|
this._alreadyOut = false;
|
|
this._extDnding = false;
|
|
},
|
|
_getDnDRegion: function(rowIndex, colIndex){
|
|
var s = this.selector,
|
|
selected = s._selected,
|
|
flag = (!!selected.cell.length) | (!!selected.row.length << 1) | (!!selected.col.length << 2),
|
|
type;
|
|
switch(flag){
|
|
case 1:
|
|
type = "cell";
|
|
if(!this._config[type]["within"] && !this._config[type]["out"]){
|
|
return null;
|
|
}
|
|
var cells = this.grid.layout.cells,
|
|
getCount = function(range){
|
|
var hiddenColCnt = 0;
|
|
for(var i = range.min.col; i <= range.max.col; ++i){
|
|
if(cells[i].hidden){
|
|
++hiddenColCnt;
|
|
}
|
|
}
|
|
return (range.max.row - range.min.row + 1) * (range.max.col - range.min.col + 1 - hiddenColCnt);
|
|
},
|
|
inRange = function(item, range){
|
|
return item.row >= range.min.row && item.row <= range.max.row &&
|
|
item.col >= range.min.col && item.col <= range.max.col;
|
|
},
|
|
range = {
|
|
max: {
|
|
row: -1,
|
|
col: -1
|
|
},
|
|
min: {
|
|
row: Infinity,
|
|
col: Infinity
|
|
}
|
|
};
|
|
|
|
array.forEach(selected[type], function(item){
|
|
if(item.row < range.min.row){
|
|
range.min.row = item.row;
|
|
}
|
|
if(item.row > range.max.row){
|
|
range.max.row = item.row;
|
|
}
|
|
if(item.col < range.min.col){
|
|
range.min.col = item.col;
|
|
}
|
|
if(item.col > range.max.col){
|
|
range.max.col = item.col;
|
|
}
|
|
});
|
|
if(array.some(selected[type], function(item){
|
|
return item.row == rowIndex && item.col == colIndex;
|
|
})){
|
|
if(getCount(range) == selected[type].length && array.every(selected[type], function(item){
|
|
return inRange(item, range);
|
|
})){
|
|
return {
|
|
"type": type,
|
|
"selected": [range],
|
|
"handle": {
|
|
"row": rowIndex,
|
|
"col": colIndex
|
|
}
|
|
};
|
|
}
|
|
}
|
|
return null;
|
|
case 2: case 4:
|
|
type = flag == 2 ? "row" : "col";
|
|
if(!this._config[type]["within"] && !this._config[type]["out"]){
|
|
return null;
|
|
}
|
|
var res = s.getSelected(type);
|
|
if(res.length){
|
|
return {
|
|
"type": type,
|
|
"selected": _devideToArrays(res),
|
|
"handle": flag == 2 ? rowIndex : colIndex
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
return null;
|
|
},
|
|
_startDnd: function(evt){
|
|
this._createDnDUI(evt);
|
|
},
|
|
_endDnd: function(destroySource){
|
|
this._destroyDnDUI(false, destroySource);
|
|
this._clear();
|
|
},
|
|
_createDnDUI: function(evt, isMovingIn){
|
|
//By default the master view of grid do not have height, because the children in it are all positioned absolutely.
|
|
//But we need it to contain avatars.
|
|
var viewPos = html.position(this.grid.views.views[0].domNode);
|
|
html.style(this._container, "height", viewPos.h + "px");
|
|
try{
|
|
//If moving in from out side, dnd source is already created.
|
|
if(!isMovingIn){
|
|
this._createSource(evt);
|
|
}
|
|
this._createMoveable(evt);
|
|
this._oldCursor = html.style(win.body(), "cursor");
|
|
html.style(win.body(), "cursor", "default");
|
|
}catch(e){
|
|
console.warn("DnD._createDnDUI() error:", e);
|
|
}
|
|
},
|
|
_destroyDnDUI: function(isMovingOut, destroySource){
|
|
try{
|
|
if(destroySource){
|
|
this._destroySource();
|
|
}
|
|
this._unmarkTargetAnchor();
|
|
if(!isMovingOut){
|
|
this._destroyMoveable();
|
|
}
|
|
html.style(win.body(), "cursor", this._oldCursor);
|
|
}catch(e){
|
|
console.warn("DnD._destroyDnDUI() error:", this.grid.id, e);
|
|
}
|
|
},
|
|
_createSource: function(evt){
|
|
this._elem.createDnDNodes(this._dndRegion);
|
|
var m = dojo.dnd.manager();
|
|
var oldMakeAvatar = m.makeAvatar;
|
|
m._dndPlugin = this;
|
|
m.makeAvatar = function(){
|
|
var avatar = new GridDnDAvatar(m);
|
|
delete m._dndPlugin;
|
|
return avatar;
|
|
};
|
|
m.startDrag(this._source, this._elem.getDnDNodes(), evt.ctrlKey);
|
|
m.makeAvatar = oldMakeAvatar;
|
|
m.onMouseMove(evt);
|
|
},
|
|
_destroySource: function(){
|
|
connect.publish("/dnd/cancel");
|
|
},
|
|
_createMoveable: function(evt){
|
|
if(!this._markTagetAnchorHandler){
|
|
this._markTagetAnchorHandler = this.connect(win.doc, "onmousemove", "_markTargetAnchor");
|
|
}
|
|
},
|
|
_destroyMoveable: function(){
|
|
this.disconnect(this._markTagetAnchorHandler);
|
|
delete this._markTagetAnchorHandler;
|
|
},
|
|
_calcColTargetAnchorPos: function(evt, containerPos){
|
|
// summary:
|
|
// Calculate the position of the column DnD avatar
|
|
var i, headPos, left, target, ex = evt.clientX,
|
|
cells = this.grid.layout.cells,
|
|
ltr = html._isBodyLtr(),
|
|
headers = this._getVisibleHeaders();
|
|
for(i = 0; i < headers.length; ++i){
|
|
headPos = html.position(headers[i].node);
|
|
if(ltr ? ((i === 0 || ex >= headPos.x) && ex < headPos.x + headPos.w) :
|
|
((i === 0 || ex < headPos.x + headPos.w) && ex >= headPos.x)){
|
|
left = headPos.x + (ltr ? 0 : headPos.w);
|
|
break;
|
|
}else if(ltr ? (i === headers.length - 1 && ex >= headPos.x + headPos.w) :
|
|
(i === headers.length - 1 && ex < headPos.x)){
|
|
++i;
|
|
left = headPos.x + (ltr ? headPos.w : 0);
|
|
break;
|
|
}
|
|
}
|
|
if(i < headers.length){
|
|
target = headers[i].cell.index;
|
|
if(this.selector.isSelected("col", target) && this.selector.isSelected("col", target - 1)){
|
|
var ranges = this._dndRegion.selected;
|
|
for(i = 0; i < ranges.length; ++i){
|
|
if(array.indexOf(ranges[i], target) >= 0){
|
|
target = ranges[i][0];
|
|
headPos = html.position(cells[target].getHeaderNode());
|
|
left = headPos.x + (ltr ? 0 : headPos.w);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}else{
|
|
target = cells.length;
|
|
}
|
|
this._target = target;
|
|
return left - containerPos.x;
|
|
},
|
|
_calcRowTargetAnchorPos: function(evt, containerPos){
|
|
// summary:
|
|
// Calculate the position of the row DnD avatar
|
|
var g = this.grid, top, i = 0,
|
|
cells = g.layout.cells;
|
|
while(cells[i].hidden){ ++i; }
|
|
var cell = g.layout.cells[i],
|
|
rowIndex = g.scroller.firstVisibleRow,
|
|
cellNode = cell.getNode(rowIndex);
|
|
if(!cellNode){
|
|
//if the target grid is empty, set to -1
|
|
//which will be processed in Rearrange
|
|
this._target = -1;
|
|
return 0; //position of the insert bar
|
|
}
|
|
var nodePos = html.position(cellNode);
|
|
while(nodePos.y + nodePos.h < evt.clientY){
|
|
if(++rowIndex >= g.rowCount){
|
|
break;
|
|
}
|
|
nodePos = html.position(cell.getNode(rowIndex));
|
|
}
|
|
if(rowIndex < g.rowCount){
|
|
if(this.selector.isSelected("row", rowIndex) && this.selector.isSelected("row", rowIndex - 1)){
|
|
var ranges = this._dndRegion.selected;
|
|
for(i = 0; i < ranges.length; ++i){
|
|
if(array.indexOf(ranges[i], rowIndex) >= 0){
|
|
rowIndex = ranges[i][0];
|
|
nodePos = html.position(cell.getNode(rowIndex));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
top = nodePos.y;
|
|
}else{
|
|
top = nodePos.y + nodePos.h;
|
|
}
|
|
this._target = rowIndex;
|
|
return top - containerPos.y;
|
|
},
|
|
_calcCellTargetAnchorPos: function(evt, containerPos, targetAnchor){
|
|
// summary:
|
|
// Calculate the position of the cell DnD avatar
|
|
var s = this._dndRegion.selected[0],
|
|
origin = this._dndRegion.handle,
|
|
g = this.grid, ltr = html._isBodyLtr(),
|
|
cells = g.layout.cells, headPos,
|
|
minPos, maxPos, headers,
|
|
height, width, left, top,
|
|
minCol, maxCol, i,
|
|
preSpan = origin.col - s.min.col,
|
|
postSpan = s.max.col - origin.col,
|
|
leftTopDiv, rightBottomDiv;
|
|
if(!targetAnchor.childNodes.length){
|
|
leftTopDiv = html.create("div", {
|
|
"class": "dojoxGridCellBorderLeftTopDIV"
|
|
}, targetAnchor);
|
|
rightBottomDiv = html.create("div", {
|
|
"class": "dojoxGridCellBorderRightBottomDIV"
|
|
}, targetAnchor);
|
|
}else{
|
|
leftTopDiv = query(".dojoxGridCellBorderLeftTopDIV", targetAnchor)[0];
|
|
rightBottomDiv = query(".dojoxGridCellBorderRightBottomDIV", targetAnchor)[0];
|
|
}
|
|
for(i = s.min.col + 1; i < origin.col; ++i){
|
|
if(cells[i].hidden){
|
|
--preSpan;
|
|
}
|
|
}
|
|
for(i = origin.col + 1; i < s.max.col; ++i){
|
|
if(cells[i].hidden){
|
|
--postSpan;
|
|
}
|
|
}
|
|
headers = this._getVisibleHeaders();
|
|
//calc width
|
|
for(i = preSpan; i < headers.length - postSpan; ++i){
|
|
headPos = html.position(headers[i].node);
|
|
if((evt.clientX >= headPos.x && evt.clientX < headPos.x + headPos.w) || //within in this column
|
|
//prior to this column, but within range
|
|
(i == preSpan && (ltr ? evt.clientX < headPos.x : evt.clientX >= headPos.x + headPos.w)) ||
|
|
//post to this column, but within range
|
|
(i == headers.length - postSpan - 1 && (ltr ? evt.clientX >= headPos.x + headPos.w : evt < headPos.x))){
|
|
minCol = headers[i - preSpan];
|
|
maxCol = headers[i + postSpan];
|
|
minPos = html.position(minCol.node);
|
|
maxPos = html.position(maxCol.node);
|
|
minCol = minCol.cell.index;
|
|
maxCol = maxCol.cell.index;
|
|
left = ltr ? minPos.x : maxPos.x;
|
|
width = ltr ? (maxPos.x + maxPos.w - minPos.x) : (minPos.x + minPos.w - maxPos.x);
|
|
break;
|
|
}
|
|
}
|
|
//calc height
|
|
i = 0;
|
|
while(cells[i].hidden){ ++i; }
|
|
var cell = cells[i],
|
|
rowIndex = g.scroller.firstVisibleRow,
|
|
nodePos = html.position(cell.getNode(rowIndex));
|
|
while(nodePos.y + nodePos.h < evt.clientY){
|
|
if(++rowIndex < g.rowCount){
|
|
nodePos = html.position(cell.getNode(rowIndex));
|
|
}else{
|
|
break;
|
|
}
|
|
}
|
|
var minRow = rowIndex >= origin.row - s.min.row ? rowIndex - origin.row + s.min.row : 0;
|
|
var maxRow = minRow + s.max.row - s.min.row;
|
|
if(maxRow >= g.rowCount){
|
|
maxRow = g.rowCount - 1;
|
|
minRow = maxRow - s.max.row + s.min.row;
|
|
}
|
|
minPos = html.position(cell.getNode(minRow));
|
|
maxPos = html.position(cell.getNode(maxRow));
|
|
top = minPos.y;
|
|
height = maxPos.y + maxPos.h - minPos.y;
|
|
this._target = {
|
|
"min":{
|
|
"row": minRow,
|
|
"col": minCol
|
|
},
|
|
"max":{
|
|
"row": maxRow,
|
|
"col": maxCol
|
|
}
|
|
};
|
|
var anchorBorderSize = (html.marginBox(leftTopDiv).w - html.contentBox(leftTopDiv).w) / 2;
|
|
var leftTopCellPos = html.position(cells[minCol].getNode(minRow));
|
|
html.style(leftTopDiv, {
|
|
"width": (leftTopCellPos.w - anchorBorderSize) + "px",
|
|
"height": (leftTopCellPos.h - anchorBorderSize) + "px"
|
|
});
|
|
var rightBottomCellPos = html.position(cells[maxCol].getNode(maxRow));
|
|
html.style(rightBottomDiv, {
|
|
"width": (rightBottomCellPos.w - anchorBorderSize) + "px",
|
|
"height": (rightBottomCellPos.h - anchorBorderSize) + "px"
|
|
});
|
|
return {
|
|
h: height,
|
|
w: width,
|
|
l: left - containerPos.x,
|
|
t: top - containerPos.y
|
|
};
|
|
},
|
|
_markTargetAnchor: function(evt){
|
|
try{
|
|
var t = this._dndRegion.type;
|
|
if(this._alreadyOut || (this._dnding && !this._config[t]["within"]) || (this._extDnding && !this._config[t]["in"])){
|
|
return;
|
|
}
|
|
var height, width, left, top,
|
|
targetAnchor = this._targetAnchor[t],
|
|
pos = html.position(this._container);
|
|
if(!targetAnchor){
|
|
targetAnchor = this._targetAnchor[t] = html.create("div", {
|
|
"class": (t == "cell") ? "dojoxGridCellBorderDIV" : "dojoxGridBorderDIV"
|
|
});
|
|
html.style(targetAnchor, "display", "none");
|
|
this._container.appendChild(targetAnchor);
|
|
}
|
|
switch(t){
|
|
case "col":
|
|
height = pos.h;
|
|
width = this._targetAnchorBorderWidth;
|
|
left = this._calcColTargetAnchorPos(evt, pos);
|
|
top = 0;
|
|
break;
|
|
case "row":
|
|
height = this._targetAnchorBorderWidth;
|
|
width = pos.w;
|
|
left = 0;
|
|
top = this._calcRowTargetAnchorPos(evt, pos);
|
|
break;
|
|
case "cell":
|
|
var cellPos = this._calcCellTargetAnchorPos(evt, pos, targetAnchor);
|
|
height = cellPos.h;
|
|
width = cellPos.w;
|
|
left = cellPos.l;
|
|
top = cellPos.t;
|
|
}
|
|
if(typeof height == "number" && typeof width == "number" && typeof left == "number" && typeof top == "number"){
|
|
html.style(targetAnchor, {
|
|
"height": height + "px",
|
|
"width": width + "px",
|
|
"left": left + "px",
|
|
"top": top + "px"
|
|
});
|
|
html.style(targetAnchor, "display", "");
|
|
}else{
|
|
this._target = null;
|
|
}
|
|
}catch(e){
|
|
console.warn("DnD._markTargetAnchor() error:",e);
|
|
}
|
|
},
|
|
_unmarkTargetAnchor: function(){
|
|
if(this._dndRegion){
|
|
var targetAnchor = this._targetAnchor[this._dndRegion.type];
|
|
if(targetAnchor){
|
|
html.style(this._targetAnchor[this._dndRegion.type], "display", "none");
|
|
}
|
|
}
|
|
},
|
|
_getVisibleHeaders: function(){
|
|
return array.map(array.filter(this.grid.layout.cells, function(cell){
|
|
return !cell.hidden;
|
|
}), function(cell){
|
|
return {
|
|
"node": cell.getHeaderNode(),
|
|
"cell": cell
|
|
};
|
|
});
|
|
},
|
|
_rearrange: function(){
|
|
if(this._target === null){
|
|
return;
|
|
}
|
|
var t = this._dndRegion.type;
|
|
var ranges = this._dndRegion.selected;
|
|
if(t === "cell"){
|
|
this.rearranger[(this._isCopy || this._copyOnly) ? "copyCells" : "moveCells"](ranges[0], this._target === -1 ? null : this._target);
|
|
}else{
|
|
this.rearranger[t == "col" ? "moveColumns" : "moveRows"](_joinToArray(ranges), this._target === -1 ? null: this._target);
|
|
}
|
|
this._target = null;
|
|
},
|
|
onDraggingOver: function(sourcePlugin){
|
|
if(!this._dnding && sourcePlugin){
|
|
sourcePlugin._isSource = true;
|
|
this._extDnding = true;
|
|
if(!this._externalDnd){
|
|
this._externalDnd = true;
|
|
this._dndRegion = this._mapRegion(sourcePlugin.grid, sourcePlugin._dndRegion);
|
|
}
|
|
this._createDnDUI(this._moveEvent,true);
|
|
this.grid.pluginMgr.getPlugin("autoScroll").readyForAutoScroll = true;
|
|
}
|
|
},
|
|
_mapRegion: function(srcGrid, dndRegion){
|
|
if(dndRegion.type === "cell"){
|
|
var srcRange = dndRegion.selected[0];
|
|
var cells = this.grid.layout.cells;
|
|
var srcCells = srcGrid.layout.cells;
|
|
var c, cnt = 0;
|
|
for(c = srcRange.min.col; c <= srcRange.max.col; ++c){
|
|
if(!srcCells[c].hidden){
|
|
++cnt;
|
|
}
|
|
}
|
|
for(c = 0; cnt > 0; ++c){
|
|
if(!cells[c].hidden){
|
|
--cnt;
|
|
}
|
|
}
|
|
var region = lang.clone(dndRegion);
|
|
region.selected[0].min.col = 0;
|
|
region.selected[0].max.col = c - 1;
|
|
for(c = srcRange.min.col; c <= dndRegion.handle.col; ++c){
|
|
if(!srcCells[c].hidden){
|
|
++cnt;
|
|
}
|
|
}
|
|
for(c = 0; cnt > 0; ++c){
|
|
if(!cells[c].hidden){
|
|
--cnt;
|
|
}
|
|
}
|
|
region.handle.col = c;
|
|
}
|
|
return dndRegion;
|
|
},
|
|
onDraggingOut: function(sourcePlugin){
|
|
if(this._externalDnd){
|
|
this._extDnding = false;
|
|
this._destroyDnDUI(true, false);
|
|
if(sourcePlugin){
|
|
sourcePlugin._isSource = false;
|
|
}
|
|
}
|
|
},
|
|
onDragIn: function(sourcePlugin, isCopy){
|
|
var success = false;
|
|
if(this._target !== null){
|
|
var type = sourcePlugin._dndRegion.type;
|
|
var ranges = sourcePlugin._dndRegion.selected;
|
|
switch(type){
|
|
case "cell":
|
|
this.rearranger.changeCells(sourcePlugin.grid, ranges[0], this._target);
|
|
break;
|
|
case "row":
|
|
var range = _joinToArray(ranges);
|
|
this.rearranger.insertRows(sourcePlugin.grid, range, this._target);
|
|
break;
|
|
}
|
|
success = true;
|
|
}
|
|
this._endDnd(true);
|
|
if(sourcePlugin.onDragOut){
|
|
sourcePlugin.onDragOut(success && !isCopy);
|
|
}
|
|
},
|
|
onDragOut: function(isMove){
|
|
if(isMove && !this._copyOnly){
|
|
var type = this._dndRegion.type;
|
|
var ranges = this._dndRegion.selected;
|
|
switch(type){
|
|
case "cell":
|
|
this.rearranger.clearCells(ranges[0]);
|
|
break;
|
|
case "row":
|
|
this.rearranger.removeRows(_joinToArray(ranges));
|
|
break;
|
|
}
|
|
}
|
|
this._endDnd(true);
|
|
},
|
|
_canAccept: function(sourcePlugin){
|
|
if(!sourcePlugin){
|
|
return false;
|
|
}
|
|
var srcRegion = sourcePlugin._dndRegion;
|
|
var type = srcRegion.type;
|
|
if(!this._config[type]["in"] || !sourcePlugin._config[type]["out"]){
|
|
return false;
|
|
}
|
|
var g = this.grid;
|
|
var ranges = srcRegion.selected;
|
|
var colCnt = array.filter(g.layout.cells, function(cell){
|
|
return !cell.hidden;
|
|
}).length;
|
|
var rowCnt = g.rowCount;
|
|
var res = true;
|
|
switch(type){
|
|
case "cell":
|
|
ranges = ranges[0];
|
|
res = g.store.getFeatures()["dojo.data.api.Write"] &&
|
|
(ranges.max.row - ranges.min.row) <= rowCnt &&
|
|
array.filter(sourcePlugin.grid.layout.cells, function(cell){
|
|
return cell.index >= ranges.min.col && cell.index <= ranges.max.col && !cell.hidden;
|
|
}).length <= colCnt;
|
|
//intentional drop through - don't break
|
|
case "row":
|
|
if(sourcePlugin._allDnDItemsLoaded()){
|
|
return res;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
_allDnDItemsLoaded: function(){
|
|
if(this._dndRegion){
|
|
var type = this._dndRegion.type,
|
|
ranges = this._dndRegion.selected,
|
|
rows = [];
|
|
switch(type){
|
|
case "cell":
|
|
for(var i = ranges[0].min.row, max = ranges[0].max.row; i <= max; ++i){
|
|
rows.push(i);
|
|
}
|
|
break;
|
|
case "row":
|
|
rows = _joinToArray(ranges);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
var cache = this.grid._by_idx;
|
|
return array.every(rows, function(rowIndex){
|
|
return !!cache[rowIndex];
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
|
|
EnhancedGrid.registerPlugin(DnD/*name:'dnd'*/, {
|
|
"dependency": ["selector", "rearrange"]
|
|
});
|
|
|
|
return DnD;
|
|
});
|