source: Dev/trunk/src/client/dojo/dnd/Container.js @ 485

Last change on this file since 485 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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