]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/dnd/Container.js
build custom layer of Dojo to speed up loading of tt-rss (refs #293)
[tt-rss.git] / lib / dojo / dnd / Container.js
CommitLineData
2f01fe57
AD
1/*
2 Copyright (c) 2004-2010, 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
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 10dojo.provide("dojo.dnd.Container");
a089699c 11
2f01fe57
AD
12dojo.require("dojo.dnd.common");
13dojo.require("dojo.parser");
a089699c
AD
14
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:
66 // a Container object, which knows when mouse hovers over it,
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:
143 // iterates over a data map skipping members that
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]);
330 dojo.removeClass(this.node, prefix + this[state]);
331 dojo.addClass(this.node, prefix + newState);
332 this[state] = newState;
333 },
334 _addItemClass: function(node, type){
335 // summary:
336 // adds a class with prefix "dojoDndItem"
337 // node: Node
338 // a node
339 // type: String
340 // a variable suffix for a class name
341 dojo.addClass(node, "dojoDndItem" + type);
342 },
343 _removeItemClass: function(node, type){
344 // summary:
345 // removes a class with prefix "dojoDndItem"
346 // node: Node
347 // a node
348 // type: String
349 // a variable suffix for a class name
350 dojo.removeClass(node, "dojoDndItem" + type);
351 },
352 _getChildByEvent: function(e){
353 // summary:
354 // gets a child, which is under the mouse at the moment, or null
355 // e: Event
356 // a mouse event
357 var node = e.target;
358 if(node){
359 for(var parent = node.parentNode; parent; node = parent, parent = node.parentNode){
360 if(parent == this.parent && dojo.hasClass(node, "dojoDndItem")){ return node; }
361 }
362 }
363 return null;
364 },
365 _normalizedCreator: function(/*dojo.dnd.Item*/ item, /*String*/ hint){
366 // summary:
367 // adds all necessary data to the output of the user-supplied creator function
368 var t = (this.creator || this.defaultCreator).call(this, item, hint);
369 if(!dojo.isArray(t.type)){ t.type = ["text"]; }
370 if(!t.node.id){ t.node.id = dojo.dnd.getUniqueId(); }
371 dojo.addClass(t.node, "dojoDndItem");
372 return t;
373 }
374});
375
376dojo.dnd._createNode = function(tag){
377 // summary:
378 // returns a function, which creates an element of given tag
379 // (SPAN by default) and sets its innerHTML to given text
380 // tag: String
381 // a tag name or empty for SPAN
382 if(!tag){ return dojo.dnd._createSpan; }
383 return function(text){ // Function
384 return dojo.create(tag, {innerHTML: text}); // Node
385 };
2f01fe57 386};
a089699c
AD
387
388dojo.dnd._createTrTd = function(text){
389 // summary:
390 // creates a TR/TD structure with given text as an innerHTML of TD
391 // text: String
392 // a text for TD
393 var tr = dojo.create("tr");
394 dojo.create("td", {innerHTML: text}, tr);
395 return tr; // Node
2f01fe57 396};
a089699c
AD
397
398dojo.dnd._createSpan = function(text){
399 // summary:
400 // creates a SPAN element with given text as its innerHTML
401 // text: String
402 // a text for SPAN
403 return dojo.create("span", {innerHTML: text}); // Node
2f01fe57 404};
a089699c
AD
405
406// dojo.dnd._defaultCreatorNodes: Object
407// a dictionary that maps container tag names to child tag names
408dojo.dnd._defaultCreatorNodes = {ul: "li", ol: "li", div: "div", p: "div"};
409
410dojo.dnd._defaultCreator = function(node){
411 // summary:
412 // takes a parent node, and returns an appropriate creator function
413 // node: Node
414 // a container node
415 var tag = node.tagName.toLowerCase();
416 var c = tag == "tbody" || tag == "thead" ? dojo.dnd._createTrTd :
417 dojo.dnd._createNode(dojo.dnd._defaultCreatorNodes[tag]);
418 return function(item, hint){ // Function
419 var isObj = item && dojo.isObject(item), data, type, n;
420 if(isObj && item.tagName && item.nodeType && item.getAttribute){
421 // process a DOM node
422 data = item.getAttribute("dndData") || item.innerHTML;
423 type = item.getAttribute("dndType");
424 type = type ? type.split(/\s*,\s*/) : ["text"];
425 n = item; // this node is going to be moved rather than copied
426 }else{
427 // process a DnD item object or a string
428 data = (isObj && item.data) ? item.data : item;
429 type = (isObj && item.type) ? item.type : ["text"];
430 n = (hint == "avatar" ? dojo.dnd._createSpan : c)(String(data));
431 }
432 if(!n.id){
433 n.id = dojo.dnd.getUniqueId();
434 }
435 return {node: n, data: data, type: type};
436 };
2f01fe57 437};
a089699c 438
2f01fe57 439}