New file |
0,0 → 1,311 |
if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
dojo._hasResource["dojo.dnd.Container"] = true; |
dojo.provide("dojo.dnd.Container"); |
|
dojo.require("dojo.dnd.common"); |
dojo.require("dojo.parser"); |
|
/* |
Container states: |
"" - normal state |
"Over" - mouse over a container |
Container item states: |
"" - normal state |
"Over" - mouse over a container item |
*/ |
|
dojo.declare("dojo.dnd.Container", null, { |
// summary: a Container object, which knows when mouse hovers over it, |
// and know over which element it hovers |
|
// object attributes (for markup) |
skipForm: false, |
|
constructor: function(node, params){ |
// summary: a constructor of the Container |
// node: Node: node or node's id to build the container on |
// params: Object: a dict of parameters, recognized parameters are: |
// creator: Function: a creator function, which takes a data item, and returns an object like that: |
// {node: newNode, data: usedData, type: arrayOfStrings} |
// skipForm: Boolean: don't start the drag operation, if clicked on form elements |
// _skipStartup: Boolean: skip startup(), which collects children, for deferred initialization |
// (this is used in the markup mode) |
this.node = dojo.byId(node); |
if(!params){ params = {}; } |
this.creator = params.creator || null; |
this.skipForm = params.skipForm; |
this.defaultCreator = dojo.dnd._defaultCreator(this.node); |
|
// class-specific variables |
this.map = {}; |
this.current = null; |
|
// states |
this.containerState = ""; |
dojo.addClass(this.node, "dojoDndContainer"); |
|
// mark up children |
if(!(params && params._skipStartup)){ |
this.startup(); |
} |
|
// set up events |
this.events = [ |
dojo.connect(this.node, "onmouseover", this, "onMouseOver"), |
dojo.connect(this.node, "onmouseout", this, "onMouseOut"), |
// cancel text selection and text dragging |
dojo.connect(this.node, "ondragstart", this, "onSelectStart"), |
dojo.connect(this.node, "onselectstart", this, "onSelectStart") |
]; |
}, |
|
// object attributes (for markup) |
creator: function(){}, // creator function, dummy at the moment |
|
// abstract access to the map |
getItem: function(/*String*/ key){ |
// summary: returns a data item by its key (id) |
return this.map[key]; // Object |
}, |
setItem: function(/*String*/ key, /*Object*/ data){ |
// summary: associates a data item with its key (id) |
this.map[key] = data; |
}, |
delItem: function(/*String*/ key){ |
// summary: removes a data item from the map by its key (id) |
delete this.map[key]; |
}, |
forInItems: function(/*Function*/ f, /*Object?*/ o){ |
// summary: iterates over a data map skipping members, which |
// are present in the empty object (IE and/or 3rd-party libraries). |
o = o || dojo.global; |
var m = this.map, e = dojo.dnd._empty; |
for(var i in this.map){ |
if(i in e){ continue; } |
f.call(o, m[i], i, m); |
} |
}, |
clearItems: function(){ |
// summary: removes all data items from the map |
this.map = {}; |
}, |
|
// methods |
getAllNodes: function(){ |
// summary: returns a list (an array) of all valid child nodes |
return dojo.query("> .dojoDndItem", this.parent); // NodeList |
}, |
insertNodes: function(data, before, anchor){ |
// summary: inserts an array of new nodes before/after an anchor node |
// data: Array: a list of data items, which should be processed by the creator function |
// before: Boolean: insert before the anchor, if true, and after the anchor otherwise |
// anchor: Node: the anchor node to be used as a point of insertion |
if(!this.parent.firstChild){ |
anchor = null; |
}else if(before){ |
if(!anchor){ |
anchor = this.parent.firstChild; |
} |
}else{ |
if(anchor){ |
anchor = anchor.nextSibling; |
} |
} |
if(anchor){ |
for(var i = 0; i < data.length; ++i){ |
var t = this._normalizedCreator(data[i]); |
this.setItem(t.node.id, {data: t.data, type: t.type}); |
this.parent.insertBefore(t.node, anchor); |
} |
}else{ |
for(var i = 0; i < data.length; ++i){ |
var t = this._normalizedCreator(data[i]); |
this.setItem(t.node.id, {data: t.data, type: t.type}); |
this.parent.appendChild(t.node); |
} |
} |
return this; // self |
}, |
destroy: function(){ |
// summary: prepares the object to be garbage-collected |
dojo.forEach(this.events, dojo.disconnect); |
this.clearItems(); |
this.node = this.parent = this.current; |
}, |
|
// markup methods |
markupFactory: function(params, node){ |
params._skipStartup = true; |
return new dojo.dnd.Container(node, params); |
}, |
startup: function(){ |
// summary: collects valid child items and populate the map |
|
// set up the real parent node |
this.parent = this.node; |
if(this.parent.tagName.toLowerCase() == "table"){ |
var c = this.parent.getElementsByTagName("tbody"); |
if(c && c.length){ this.parent = c[0]; } |
} |
|
// process specially marked children |
dojo.query("> .dojoDndItem", this.parent).forEach(function(node){ |
if(!node.id){ node.id = dojo.dnd.getUniqueId(); } |
var type = node.getAttribute("dndType"), |
data = node.getAttribute("dndData"); |
this.setItem(node.id, { |
data: data ? data : node.innerHTML, |
type: type ? type.split(/\s*,\s*/) : ["text"] |
}); |
}, this); |
}, |
|
// mouse events |
onMouseOver: function(e){ |
// summary: event processor for onmouseover |
// e: Event: mouse event |
var n = e.relatedTarget; |
while(n){ |
if(n == this.node){ break; } |
try{ |
n = n.parentNode; |
}catch(x){ |
n = null; |
} |
} |
if(!n){ |
this._changeState("Container", "Over"); |
this.onOverEvent(); |
} |
n = this._getChildByEvent(e); |
if(this.current == n){ return; } |
if(this.current){ this._removeItemClass(this.current, "Over"); } |
if(n){ this._addItemClass(n, "Over"); } |
this.current = n; |
}, |
onMouseOut: function(e){ |
// summary: event processor for onmouseout |
// e: Event: mouse event |
for(var n = e.relatedTarget; n;){ |
if(n == this.node){ return; } |
try{ |
n = n.parentNode; |
}catch(x){ |
n = null; |
} |
} |
if(this.current){ |
this._removeItemClass(this.current, "Over"); |
this.current = null; |
} |
this._changeState("Container", ""); |
this.onOutEvent(); |
}, |
onSelectStart: function(e){ |
// summary: event processor for onselectevent and ondragevent |
// e: Event: mouse event |
if(!this.skipForm || !dojo.dnd.isFormElement(e)){ |
dojo.stopEvent(e); |
} |
}, |
|
// utilities |
onOverEvent: function(){ |
// summary: this function is called once, when mouse is over our container |
}, |
onOutEvent: function(){ |
// summary: this function is called once, when mouse is out of our container |
}, |
_changeState: function(type, newState){ |
// summary: changes a named state to new state value |
// type: String: a name of the state to change |
// newState: String: new state |
var prefix = "dojoDnd" + type; |
var state = type.toLowerCase() + "State"; |
//dojo.replaceClass(this.node, prefix + newState, prefix + this[state]); |
dojo.removeClass(this.node, prefix + this[state]); |
dojo.addClass(this.node, prefix + newState); |
this[state] = newState; |
}, |
_addItemClass: function(node, type){ |
// summary: adds a class with prefix "dojoDndItem" |
// node: Node: a node |
// type: String: a variable suffix for a class name |
dojo.addClass(node, "dojoDndItem" + type); |
}, |
_removeItemClass: function(node, type){ |
// summary: removes a class with prefix "dojoDndItem" |
// node: Node: a node |
// type: String: a variable suffix for a class name |
dojo.removeClass(node, "dojoDndItem" + type); |
}, |
_getChildByEvent: function(e){ |
// summary: gets a child, which is under the mouse at the moment, or null |
// e: Event: a mouse event |
var node = e.target; |
if(node){ |
for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){ |
if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; } |
} |
} |
return null; |
}, |
_normalizedCreator: function(item, hint){ |
// summary: adds all necessary data to the output of the user-supplied creator function |
var t = (this.creator ? this.creator : this.defaultCreator)(item, hint); |
if(!dojo.isArray(t.type)){ t.type = ["text"]; } |
if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); } |
dojo.addClass(t.node, "dojoDndItem"); |
return t; |
} |
}); |
|
dojo.dnd._createNode = function(tag){ |
// summary: returns a function, which creates an element of given tag |
// (SPAN by default) and sets its innerHTML to given text |
// tag: String: a tag name or empty for SPAN |
if(!tag){ return dojo.dnd._createSpan; } |
return function(text){ // Function |
var n = dojo.doc.createElement(tag); |
n.innerHTML = text; |
return n; |
}; |
}; |
|
dojo.dnd._createTrTd = function(text){ |
// summary: creates a TR/TD structure with given text as an innerHTML of TD |
// text: String: a text for TD |
var tr = dojo.doc.createElement("tr"); |
var td = dojo.doc.createElement("td"); |
td.innerHTML = text; |
tr.appendChild(td); |
return tr; // Node |
}; |
|
dojo.dnd._createSpan = function(text){ |
// summary: creates a SPAN element with given text as its innerHTML |
// text: String: a text for SPAN |
var n = dojo.doc.createElement("span"); |
n.innerHTML = text; |
return n; // Node |
}; |
|
// dojo.dnd._defaultCreatorNodes: Object: a dicitionary, which maps container tag names to child tag names |
dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"}; |
|
dojo.dnd._defaultCreator = function(node){ |
// summary: takes a container node, and returns an appropriate creator function |
// node: Node: a container node |
var tag = node.tagName.toLowerCase(); |
var c = tag == "table" ? dojo.dnd._createTrTd : dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]); |
return function(item, hint){ // Function |
var isObj = dojo.isObject(item) && item; |
var data = (isObj && item.data) ? item.data : item; |
var type = (isObj && item.type) ? item.type : ["text"]; |
var t = String(data), n = (hint == "avatar" ? dojo.dnd._createSpan : c)(t); |
n.id = dojo.dnd.getUniqueId(); |
return {node: n, data: data, type: type}; |
}; |
}; |
|
} |