source: Dev/branches/rest-dojo-ui/client/dojo/dnd/Container.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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