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