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 this.getAllNodes().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}; }; }; }