Container.js 12 KB

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