/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.grid.enhanced.plugins.DnD"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.grid.enhanced.plugins.DnD"] = true;
dojo.provide("dojox.grid.enhanced.plugins.DnD");
dojo.require("dojox.grid.enhanced._Plugin");
dojo.require("dojox.grid.enhanced.plugins.Selector");
dojo.require("dojox.grid.enhanced.plugins.Rearrange");
dojo.require("dojo.dnd.move");
dojo.require("dojo.dnd.Source");
(function(){
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;
};
dojo.declare("dojox.grid.enhanced.plugins.DnD", dojox.grid.enhanced._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 = dojo.clone(this._config);
args = dojo.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 dojox.grid.enhanced.plugins.GridDnDElement(this);
this._source = new dojox.grid.enhanced.plugins.GridDnDSource(this._elem.node, {
"grid": grid,
"dndElem": this._elem,
"dnd": this
});
this._container = dojo.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 = dojo.hitch(this, "setupConfig");
this.grid.dndCopyOnly = dojo.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 && dojo.isObject(config)){
var firstLevel = ["row", "col", "cell"],
secondLevel = ["within", "in", "out"],
cfg = this._config;
dojo.forEach(firstLevel, function(type){
if(type in config){
var t = config[type];
if(t && dojo.isObject(t)){
dojo.forEach(secondLevel, function(mode){
if(mode in t){
cfg[type][mode] = !!t[mode];
}
});
}else{
dojo.forEach(secondLevel, function(mode){
cfg[type][mode] = !!t;
});
}
}
});
dojo.forEach(secondLevel, function(mode){
if(mode in config){
var m = config[mode];
if(m && dojo.isObject(m)){
dojo.forEach(firstLevel, function(type){
if(type in m){
cfg[type][mode] = !!m[type];
}
});
}else{
dojo.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 = dojo.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 = dojo.style(dojo.body(), "cursor");
dojo.style(dojo.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);
}
dojo.style(dojo.body(), "cursor", this._oldCursor || "");
delete this._isMouseDown;
},
_initEvents: function(){
var g = this.grid, s = this.selector;
this.connect(dojo.doc, "onmousemove", "_onMouseMove");
this.connect(dojo.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(dojo.doc, "onkeydown", function(evt){
if(evt.keyCode == dojo.keys.ESCAPE){
this._endDnd(false);
}else if(evt.keyCode == dojo.keys.CTRL){
s.selectEnabled(true);
this._isCopy = true;
}
});
this.connect(dojo.doc, "onkeyup", function(evt){
if(evt.keyCode == dojo.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
}
};
dojo.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(dojo.some(selected[type], function(item){
return item.row == rowIndex && item.col == colIndex;
})){
if(getCount(range) == selected[type].length && dojo.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 = dojo.position(this.grid.views.views[0].domNode);
dojo.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 = dojo.style(dojo.body(), "cursor");
dojo.style(dojo.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();
}
dojo.style(dojo.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 dojox.grid.enhanced.plugins.GridDnDAvatar(m);
delete m._dndPlugin;
return avatar;
};
m.startDrag(this._source, this._elem.getDnDNodes(), evt.ctrlKey);
m.makeAvatar = oldMakeAvatar;
m.onMouseMove(evt);
},
_destroySource: function(){
dojo.publish("/dnd/cancel");
this._elem.destroyDnDNodes();
},
_createMoveable: function(evt){
if(!this._markTagetAnchorHandler){
this._markTagetAnchorHandler = this.connect(dojo.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 = dojo._isBodyLtr(),
headers = this._getVisibleHeaders();
for(i = 0; i < headers.length; ++i){
headPos = dojo.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(dojo.indexOf(ranges[i], target) >= 0){
target = ranges[i][0];
headPos = dojo.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,
nodePos = dojo.position(cell.getNode(rowIndex));
while(nodePos.y + nodePos.h < evt.clientY){
if(++rowIndex >= g.rowCount){
break;
}
nodePos = dojo.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(dojo.indexOf(ranges[i], rowIndex) >= 0){
rowIndex = ranges[i][0];
nodePos = dojo.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 = dojo._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 = dojo.create("div", {
"class": "dojoxGridCellBorderLeftTopDIV"
}, targetAnchor);
rightBottomDiv = dojo.create("div", {
"class": "dojoxGridCellBorderRightBottomDIV"
}, targetAnchor);
}else{
leftTopDiv = dojo.query(".dojoxGridCellBorderLeftTopDIV", targetAnchor)[0];
rightBottomDiv = dojo.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 = dojo.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 = dojo.position(minCol.node);
maxPos = dojo.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 = dojo.position(cell.getNode(rowIndex));
while(nodePos.y + nodePos.h < evt.clientY){
if(++rowIndex < g.rowCount){
nodePos = dojo.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 = dojo.position(cell.getNode(minRow));
maxPos = dojo.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 = (dojo.marginBox(leftTopDiv).w - dojo.contentBox(leftTopDiv).w) / 2;
var leftTopCellPos = dojo.position(cells[minCol].getNode(minRow));
dojo.style(leftTopDiv, {
"width": (leftTopCellPos.w - anchorBorderSize) + "px",
"height": (leftTopCellPos.h - anchorBorderSize) + "px"
});
var rightBottomCellPos = dojo.position(cells[maxCol].getNode(maxRow));
dojo.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 = dojo.position(this._container);
if(!targetAnchor){
targetAnchor = this._targetAnchor[t] = dojo.create("div", {
"class": (t == "cell") ? "dojoxGridCellBorderDIV" : "dojoxGridBorderDIV"
});
dojo.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"){
dojo.style(targetAnchor, {
"height": height + "px",
"width": width + "px",
"left": left + "px",
"top": top + "px"
});
dojo.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){
dojo.style(this._targetAnchor[this._dndRegion.type], "display", "none");
}
}
},
_getVisibleHeaders: function(){
return dojo.map(dojo.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);
}else{
this.rearranger[t == "col" ? "moveColumns" : "moveRows"](_joinToArray(ranges), 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 = dojo.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 = dojo.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 &&
dojo.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 dojo.every(rows, function(rowIndex){
return !!cache[rowIndex];
});
}
return false;
}
});
dojo.declare("dojox.grid.enhanced.plugins.GridDnDElement", null, {
constructor: function(dndPlugin){
this.plugin = dndPlugin;
this.node = dojo.create("div");
this._items = {};
},
destroy: function(){
this.plugin = null;
dojo.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";
dojo.forEach(dndRegion.selected, function(range, i){
var id = itemNodeIdBase + i;
this._items[id] = {
"type": acceptType,
"data": range,
"dndPlugin": this.plugin
};
this.node.appendChild(dojo.create("div", {
"id": id
}));
}, this);
},
getDnDNodes: function(){
return dojo.map(this.node.childNodes, function(node){
return node;
});
},
destroyDnDNodes: function(){
dojo.empty(this.node);
this._items = {};
},
getItem: function(nodeId){
return this._items[nodeId];
}
});
dojo.declare("dojox.grid.enhanced.plugins.GridDnDSource",dojo.dnd.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 = [];
dojo.forEach(nodes, function(node){
var item = source.getItem(node.id);
if(item.data && dojo.indexOf(item.type, "grid/rows") >= 0){
var rowData = item.data;
if(typeof item.data == "string"){
rowData = dojo.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);
}
}
});
dojo.declare("dojox.grid.enhanced.plugins.GridDnDAvatar", dojo.dnd.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 = dojo.hasClass(dojo.body(), "dijit_a11y");
var a = dojo.create("table", {
"border": "0",
"cellspacing": "0",
"class": "dojoxGridDndAvatar",
"style": {
position: "absolute",
zIndex: "1999",
margin: "0px"
}
}),
source = this.manager.source,
b = dojo.create("tbody", null, a),
tr = dojo.create("tr", null, b),
td = dojo.create("td", {
"class": "dojoxGridDnDIcon"
}, tr);
if(this.isA11y){
dojo.create("span", {
"id" : "a11yIcon",
"innerHTML" : this.manager.copy ? '+' : "<"
}, td);
}
td = dojo.create("td", {
"class" : "dojoxGridDnDItemIcon " + this._getGridDnDIconClass()
}, tr);
td = dojo.create("td", null, tr);
dojo.create("span", {
"class": "dojoxGridDnDItemCount",
"innerHTML": source.generateText ? this._generateText() : ""
}, td);
// we have to set the opacity on IE only after the node is live
dojo.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 + ")";
}
});
dojox.grid.EnhancedGrid.registerPlugin(dojox.grid.enhanced.plugins.DnD/*name:'dnd'*/, {
"dependency": ["selector", "rearrange"]
});
})();
}