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