Container.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojo.dnd.Container"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojo.dnd.Container"] = true;
  8. dojo.provide("dojo.dnd.Container");
  9. dojo.require("dojo.dnd.common");
  10. dojo.require("dojo.parser");
  11. /*
  12. Container states:
  13. "" - normal state
  14. "Over" - mouse over a container
  15. Container item states:
  16. "" - normal state
  17. "Over" - mouse over a container item
  18. */
  19. /*=====
  20. dojo.declare("dojo.dnd.__ContainerArgs", [], {
  21. creator: function(){
  22. // summary:
  23. // a creator function, which takes a data item, and returns an object like that:
  24. // {node: newNode, data: usedData, type: arrayOfStrings}
  25. },
  26. // skipForm: Boolean
  27. // don't start the drag operation, if clicked on form elements
  28. skipForm: false,
  29. // dropParent: Node||String
  30. // node or node's id to use as the parent node for dropped items
  31. // (must be underneath the 'node' parameter in the DOM)
  32. dropParent: null,
  33. // _skipStartup: Boolean
  34. // skip startup(), which collects children, for deferred initialization
  35. // (this is used in the markup mode)
  36. _skipStartup: false
  37. });
  38. dojo.dnd.Item = function(){
  39. // summary:
  40. // Represents (one of) the source node(s) being dragged.
  41. // Contains (at least) the "type" and "data" attributes.
  42. // type: String[]
  43. // Type(s) of this item, by default this is ["text"]
  44. // data: Object
  45. // Logical representation of the object being dragged.
  46. // If the drag object's type is "text" then data is a String,
  47. // if it's another type then data could be a different Object,
  48. // perhaps a name/value hash.
  49. this.type = type;
  50. this.data = data;
  51. }
  52. =====*/
  53. dojo.declare("dojo.dnd.Container", null, {
  54. // summary:
  55. // a Container object, which knows when mouse hovers over it,
  56. // and over which element it hovers
  57. // object attributes (for markup)
  58. skipForm: false,
  59. /*=====
  60. // current: DomNode
  61. // The DOM node the mouse is currently hovered over
  62. current: null,
  63. // map: Hash<String, dojo.dnd.Item>
  64. // Map from an item's id (which is also the DOMNode's id) to
  65. // the dojo.dnd.Item itself.
  66. map: {},
  67. =====*/
  68. constructor: function(node, params){
  69. // summary:
  70. // a constructor of the Container
  71. // node: Node
  72. // node or node's id to build the container on
  73. // params: dojo.dnd.__ContainerArgs
  74. // a dictionary of parameters
  75. this.node = dojo.byId(node);
  76. if(!params){ params = {}; }
  77. this.creator = params.creator || null;
  78. this.skipForm = params.skipForm;
  79. this.parent = params.dropParent && dojo.byId(params.dropParent);
  80. // class-specific variables
  81. this.map = {};
  82. this.current = null;
  83. // states
  84. this.containerState = "";
  85. dojo.addClass(this.node, "dojoDndContainer");
  86. // mark up children
  87. if(!(params && params._skipStartup)){
  88. this.startup();
  89. }
  90. // set up events
  91. this.events = [
  92. dojo.connect(this.node, "onmouseover", this, "onMouseOver"),
  93. dojo.connect(this.node, "onmouseout", this, "onMouseOut"),
  94. // cancel text selection and text dragging
  95. dojo.connect(this.node, "ondragstart", this, "onSelectStart"),
  96. dojo.connect(this.node, "onselectstart", this, "onSelectStart")
  97. ];
  98. },
  99. // object attributes (for markup)
  100. creator: function(){
  101. // summary:
  102. // creator function, dummy at the moment
  103. },
  104. // abstract access to the map
  105. getItem: function(/*String*/ key){
  106. // summary:
  107. // returns a data item by its key (id)
  108. return this.map[key]; // dojo.dnd.Item
  109. },
  110. setItem: function(/*String*/ key, /*dojo.dnd.Item*/ data){
  111. // summary:
  112. // associates a data item with its key (id)
  113. this.map[key] = data;
  114. },
  115. delItem: function(/*String*/ key){
  116. // summary:
  117. // removes a data item from the map by its key (id)
  118. delete this.map[key];
  119. },
  120. forInItems: function(/*Function*/ f, /*Object?*/ o){
  121. // summary:
  122. // iterates over a data map skipping members that
  123. // are present in the empty object (IE and/or 3rd-party libraries).
  124. o = o || dojo.global;
  125. var m = this.map, e = dojo.dnd._empty;
  126. for(var i in m){
  127. if(i in e){ continue; }
  128. f.call(o, m[i], i, this);
  129. }
  130. return o; // Object
  131. },
  132. clearItems: function(){
  133. // summary:
  134. // removes all data items from the map
  135. this.map = {};
  136. },
  137. // methods
  138. getAllNodes: function(){
  139. // summary:
  140. // returns a list (an array) of all valid child nodes
  141. return dojo.query("> .dojoDndItem", this.parent); // NodeList
  142. },
  143. sync: function(){
  144. // summary:
  145. // sync up the node list with the data map
  146. var map = {};
  147. this.getAllNodes().forEach(function(node){
  148. if(node.id){
  149. var item = this.getItem(node.id);
  150. if(item){
  151. map[node.id] = item;
  152. return;
  153. }
  154. }else{
  155. node.id = dojo.dnd.getUniqueId();
  156. }
  157. var type = node.getAttribute("dndType"),
  158. data = node.getAttribute("dndData");
  159. map[node.id] = {
  160. data: data || node.innerHTML,
  161. type: type ? type.split(/\s*,\s*/) : ["text"]
  162. };
  163. }, this);
  164. this.map = map;
  165. return this; // self
  166. },
  167. insertNodes: function(data, before, anchor){
  168. // summary:
  169. // inserts an array of new nodes before/after an anchor node
  170. // data: Array
  171. // a list of data items, which should be processed by the creator function
  172. // before: Boolean
  173. // insert before the anchor, if true, and after the anchor otherwise
  174. // anchor: Node
  175. // the anchor node to be used as a point of insertion
  176. if(!this.parent.firstChild){
  177. anchor = null;
  178. }else if(before){
  179. if(!anchor){
  180. anchor = this.parent.firstChild;
  181. }
  182. }else{
  183. if(anchor){
  184. anchor = anchor.nextSibling;
  185. }
  186. }
  187. if(anchor){
  188. for(var i = 0; i < data.length; ++i){
  189. var t = this._normalizedCreator(data[i]);
  190. this.setItem(t.node.id, {data: t.data, type: t.type});
  191. this.parent.insertBefore(t.node, anchor);
  192. }
  193. }else{
  194. for(var i = 0; i < data.length; ++i){
  195. var t = this._normalizedCreator(data[i]);
  196. this.setItem(t.node.id, {data: t.data, type: t.type});
  197. this.parent.appendChild(t.node);
  198. }
  199. }
  200. return this; // self
  201. },
  202. destroy: function(){
  203. // summary:
  204. // prepares this object to be garbage-collected
  205. dojo.forEach(this.events, dojo.disconnect);
  206. this.clearItems();
  207. this.node = this.parent = this.current = null;
  208. },
  209. // markup methods
  210. markupFactory: function(params, node){
  211. params._skipStartup = true;
  212. return new dojo.dnd.Container(node, params);
  213. },
  214. startup: function(){
  215. // summary:
  216. // collects valid child items and populate the map
  217. // set up the real parent node
  218. if(!this.parent){
  219. // use the standard algorithm, if not assigned
  220. this.parent = this.node;
  221. if(this.parent.tagName.toLowerCase() == "table"){
  222. var c = this.parent.getElementsByTagName("tbody");
  223. if(c && c.length){ this.parent = c[0]; }
  224. }
  225. }
  226. this.defaultCreator = dojo.dnd._defaultCreator(this.parent);
  227. // process specially marked children
  228. this.sync();
  229. },
  230. // mouse events
  231. onMouseOver: function(e){
  232. // summary:
  233. // event processor for onmouseover
  234. // e: Event
  235. // mouse event
  236. var n = e.relatedTarget;
  237. while(n){
  238. if(n == this.node){ break; }
  239. try{
  240. n = n.parentNode;
  241. }catch(x){
  242. n = null;
  243. }
  244. }
  245. if(!n){
  246. this._changeState("Container", "Over");
  247. this.onOverEvent();
  248. }
  249. n = this._getChildByEvent(e);
  250. if(this.current == n){ return; }
  251. if(this.current){ this._removeItemClass(this.current, "Over"); }
  252. if(n){ this._addItemClass(n, "Over"); }
  253. this.current = n;
  254. },
  255. onMouseOut: function(e){
  256. // summary:
  257. // event processor for onmouseout
  258. // e: Event
  259. // mouse event
  260. for(var n = e.relatedTarget; n;){
  261. if(n == this.node){ return; }
  262. try{
  263. n = n.parentNode;
  264. }catch(x){
  265. n = null;
  266. }
  267. }
  268. if(this.current){
  269. this._removeItemClass(this.current, "Over");
  270. this.current = null;
  271. }
  272. this._changeState("Container", "");
  273. this.onOutEvent();
  274. },
  275. onSelectStart: function(e){
  276. // summary:
  277. // event processor for onselectevent and ondragevent
  278. // e: Event
  279. // mouse event
  280. if(!this.skipForm || !dojo.dnd.isFormElement(e)){
  281. dojo.stopEvent(e);
  282. }
  283. },
  284. // utilities
  285. onOverEvent: function(){
  286. // summary:
  287. // this function is called once, when mouse is over our container
  288. },
  289. onOutEvent: function(){
  290. // summary:
  291. // this function is called once, when mouse is out of our container
  292. },
  293. _changeState: function(type, newState){
  294. // summary:
  295. // changes a named state to new state value
  296. // type: String
  297. // a name of the state to change
  298. // newState: String
  299. // new state
  300. var prefix = "dojoDnd" + type;
  301. var state = type.toLowerCase() + "State";
  302. //dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
  303. dojo.replaceClass(this.node, prefix + newState, prefix + this[state]);
  304. this[state] = newState;
  305. },
  306. _addItemClass: function(node, type){
  307. // summary:
  308. // adds a class with prefix "dojoDndItem"
  309. // node: Node
  310. // a node
  311. // type: String
  312. // a variable suffix for a class name
  313. dojo.addClass(node, "dojoDndItem" + type);
  314. },
  315. _removeItemClass: function(node, type){
  316. // summary:
  317. // removes a class with prefix "dojoDndItem"
  318. // node: Node
  319. // a node
  320. // type: String
  321. // a variable suffix for a class name
  322. dojo.removeClass(node, "dojoDndItem" + type);
  323. },
  324. _getChildByEvent: function(e){
  325. // summary:
  326. // gets a child, which is under the mouse at the moment, or null
  327. // e: Event
  328. // a mouse event
  329. var node = e.target;
  330. if(node){
  331. for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
  332. if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
  333. }
  334. }
  335. return null;
  336. },
  337. _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
  338. // summary:
  339. // adds all necessary data to the output of the user-supplied creator function
  340. var t = (this.creator || this.defaultCreator).call(this, item, hint);
  341. if(!dojo.isArray(t.type)){ t.type = ["text"]; }
  342. if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
  343. dojo.addClass(t.node, "dojoDndItem");
  344. return t;
  345. }
  346. });
  347. dojo.dnd._createNode = function(tag){
  348. // summary:
  349. // returns a function, which creates an element of given tag
  350. // (SPAN by default) and sets its innerHTML to given text
  351. // tag: String
  352. // a tag name or empty for SPAN
  353. if(!tag){ return dojo.dnd._createSpan; }
  354. return function(text){ // Function
  355. return dojo.create(tag, {innerHTML: text}); // Node
  356. };
  357. };
  358. dojo.dnd._createTrTd = function(text){
  359. // summary:
  360. // creates a TR/TD structure with given text as an innerHTML of TD
  361. // text: String
  362. // a text for TD
  363. var tr = dojo.create("tr");
  364. dojo.create("td", {innerHTML: text}, tr);
  365. return tr; // Node
  366. };
  367. dojo.dnd._createSpan = function(text){
  368. // summary:
  369. // creates a SPAN element with given text as its innerHTML
  370. // text: String
  371. // a text for SPAN
  372. return dojo.create("span", {innerHTML: text}); // Node
  373. };
  374. // dojo.dnd._defaultCreatorNodes: Object
  375. // a dictionary that maps container tag names to child tag names
  376. dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
  377. dojo.dnd._defaultCreator = function(node){
  378. // summary:
  379. // takes a parent node, and returns an appropriate creator function
  380. // node: Node
  381. // a container node
  382. var tag = node.tagName.toLowerCase();
  383. var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
  384. dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
  385. return function(item, hint){ // Function
  386. var isObj = item && dojo.isObject(item), data, type, n;
  387. if(isObj && item.tagName && item.nodeType && item.getAttribute){
  388. // process a DOM node
  389. data = item.getAttribute("dndData") || item.innerHTML;
  390. type = item.getAttribute("dndType");
  391. type = type ? type.split(/\s*,\s*/) : ["text"];
  392. n = item; // this node is going to be moved rather than copied
  393. }else{
  394. // process a DnD item object or a string
  395. data = (isObj && item.data) ? item.data : item;
  396. type = (isObj && item.type) ? item.type : ["text"];
  397. n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
  398. }
  399. if(!n.id){
  400. n.id = dojo.dnd.getUniqueId();
  401. }
  402. return {node: n, data: data, type: type};
  403. };
  404. };
  405. }