source: Dev/trunk/src/client/dijit/tree/_dndSelector.js

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

Added Dojo 1.9.3 release.

File size: 11.1 KB
Line 
1define([
2        "dojo/_base/array", // array.filter array.forEach array.map
3        "dojo/_base/connect", // connect.isCopyKey
4        "dojo/_base/declare", // declare
5        "dojo/_base/kernel",    // global
6        "dojo/_base/lang", // lang.hitch
7        "dojo/dom", // isDescendant
8        "dojo/mouse", // mouse.isLeft
9        "dojo/on",
10        "dojo/touch",
11        "../a11yclick",
12        "./_dndContainer"
13], function(array, connect, declare, kernel, lang, dom, mouse, on, touch, a11yclick, _dndContainer){
14
15        // module:
16        //              dijit/tree/_dndSelector
17
18        return declare("dijit.tree._dndSelector", _dndContainer, {
19                // summary:
20                //              This is a base class for `dijit/tree/dndSource`, and isn't meant to be used directly.
21                //              It's based on `dojo/dnd/Selector`.
22                // tags:
23                //              protected
24
25                /*=====
26                // selection: Object
27                //              (id to DomNode) map for every TreeNode that's currently selected.
28                //              The DOMNode is the TreeNode.rowNode.
29                selection: {},
30                =====*/
31
32                constructor: function(){
33                        // summary:
34                        //              Initialization
35                        // tags:
36                        //              private
37
38                        this.selection={};
39                        this.anchor = null;
40
41                        this.events.push(
42                                // listeners setup here but no longer used (left for backwards compatibility
43                                on(this.tree.domNode, touch.press, lang.hitch(this,"onMouseDown")),
44                                on(this.tree.domNode, touch.release, lang.hitch(this,"onMouseUp")),
45
46                                // listeners used in this module
47                                on(this.tree.domNode, touch.move, lang.hitch(this,"onMouseMove")),
48                                on(this.tree.domNode, a11yclick.press, lang.hitch(this,"onClickPress")),
49                                on(this.tree.domNode, a11yclick.release, lang.hitch(this,"onClickRelease"))
50                        );
51                },
52
53                // singular: Boolean
54                //              Allows selection of only one element, if true.
55                //              Tree hasn't been tested in singular=true mode, unclear if it works.
56                singular: false,
57
58                // methods
59                getSelectedTreeNodes: function(){
60                        // summary:
61                        //              Returns a list of selected node(s).
62                        //              Used by dndSource on the start of a drag.
63                        // tags:
64                        //              protected
65                        var nodes=[], sel = this.selection;
66                        for(var i in sel){
67                                nodes.push(sel[i]);
68                        }
69                        return nodes;
70                },
71
72                selectNone: function(){
73                        // summary:
74                        //              Unselects all items
75                        // tags:
76                        //              private
77
78                        this.setSelection([]);
79                        return this;    // self
80                },
81
82                destroy: function(){
83                        // summary:
84                        //              Prepares the object to be garbage-collected
85                        this.inherited(arguments);
86                        this.selection = this.anchor = null;
87                },
88                addTreeNode: function(/*dijit/Tree._TreeNode*/ node, /*Boolean?*/isAnchor){
89                        // summary:
90                        //              add node to current selection
91                        // node: Node
92                        //              node to add
93                        // isAnchor: Boolean
94                        //              Whether the node should become anchor.
95
96                        this.setSelection(this.getSelectedTreeNodes().concat( [node] ));
97                        if(isAnchor){ this.anchor = node; }
98                        return node;
99                },
100                removeTreeNode: function(/*dijit/Tree._TreeNode*/ node){
101                        // summary:
102                        //              remove node and it's descendants from current selection
103                        // node: Node
104                        //              node to remove
105                        var newSelection = array.filter(this.getSelectedTreeNodes(), function(selectedNode){
106                                return !dom.isDescendant(selectedNode.domNode, node.domNode); // also matches when selectedNode == node
107                        });
108                        this.setSelection(newSelection);
109                        return node;
110                },
111                isTreeNodeSelected: function(/*dijit/Tree._TreeNode*/ node){
112                        // summary:
113                        //              return true if node is currently selected
114                        // node: Node
115                        //              the node to check whether it's in the current selection
116
117                        return node.id && !!this.selection[node.id];
118                },
119                setSelection: function(/*dijit/Tree._TreeNode[]*/ newSelection){
120                        // summary:
121                        //              set the list of selected nodes to be exactly newSelection. All changes to the
122                        //              selection should be passed through this function, which ensures that derived
123                        //              attributes are kept up to date. Anchor will be deleted if it has been removed
124                        //              from the selection, but no new anchor will be added by this function.
125                        // newSelection: Node[]
126                        //              list of tree nodes to make selected
127                        var oldSelection = this.getSelectedTreeNodes();
128                        array.forEach(this._setDifference(oldSelection, newSelection), lang.hitch(this, function(node){
129                                node.setSelected(false);
130                                if(this.anchor == node){
131                                        delete this.anchor;
132                                }
133                                delete this.selection[node.id];
134                        }));
135                        array.forEach(this._setDifference(newSelection, oldSelection), lang.hitch(this, function(node){
136                                node.setSelected(true);
137                                this.selection[node.id] = node;
138                        }));
139                        this._updateSelectionProperties();
140                },
141                _setDifference: function(xs,ys){
142                        // summary:
143                        //              Returns a copy of xs which lacks any objects
144                        //              occurring in ys. Checks for membership by
145                        //              modifying and then reading the object, so it will
146                        //              not properly handle sets of numbers or strings.
147
148                        array.forEach(ys, function(y){ y.__exclude__ = true; });
149                        var ret = array.filter(xs, function(x){ return !x.__exclude__; });
150
151                        // clean up after ourselves.
152                        array.forEach(ys, function(y){ delete y['__exclude__'] });
153                        return ret;
154                },
155                _updateSelectionProperties: function(){
156                        // summary:
157                        //              Update the following tree properties from the current selection:
158                        //              path[s], selectedItem[s], selectedNode[s]
159
160                        var selected = this.getSelectedTreeNodes();
161                        var paths = [], nodes = [], selects = [];
162                        array.forEach(selected, function(node){
163                                var ary = node.getTreePath(), model = this.tree.model;
164                                nodes.push(node);
165                                paths.push(ary);
166                                ary = array.map(ary, function(item){
167                                        return model.getIdentity(item);
168                                }, this);
169                                selects.push(ary.join("/"))
170                        }, this);
171                        var items = array.map(nodes,function(node){ return node.item; });
172                        this.tree._set("paths", paths);
173                        this.tree._set("path", paths[0] || []);
174                        this.tree._set("selectedNodes", nodes);
175                        this.tree._set("selectedNode", nodes[0] || null);
176                        this.tree._set("selectedItems", items);
177                        this.tree._set("selectedItem", items[0] || null);
178                },
179
180                // selection related events
181                onClickPress: function(e){
182                        // summary:
183                        //              Event processor for onmousedown/ontouchstart/onkeydown corresponding to a click event
184                        // e: Event
185                        //              onmousedown/ontouchstart/onkeydown event
186                        // tags:
187                        //              protected
188
189                        // ignore mouse or touch on expando node
190                        if(this.current && this.current.isExpandable && this.tree.isExpandoNode(e.target, this.current)){ return; }
191
192                        if(mouse.isLeft(e)){
193                                // Prevent text selection while dragging on desktop, see #16328.   But don't call preventDefault()
194                                // for mobile because it will break things completely, see #15838.
195                                e.preventDefault();
196                        }
197
198                        var treeNode = e.type == "keydown" ? this.tree.focusedChild : this.current;
199
200                        if(!treeNode){
201                                // Click must be on the Tree but not on a TreeNode, happens especially when Tree is stretched to fill
202                                // a pane of a BorderContainer, etc.
203                                return;
204                        }
205
206                        var copy = connect.isCopyKey(e), id = treeNode.id;
207
208                        // if shift key is not pressed, and the node is already in the selection,
209                        // delay deselection until onmouseup so in the case of DND, deselection
210                        // will be canceled by onmousemove.
211                        if(!this.singular && !e.shiftKey && this.selection[id]){
212                                this._doDeselect = true;
213                                return;
214                        }else{
215                                this._doDeselect = false;
216                        }
217                        this.userSelect(treeNode, copy, e.shiftKey);
218                },
219
220                onClickRelease: function(e){
221                        // summary:
222                        //              Event processor for onmouseup/ontouchend/onkeyup corresponding to a click event
223                        // e: Event
224                        //              onmouseup/ontouchend/onkeyup event
225                        // tags:
226                        //              protected
227
228                        // _doDeselect is the flag to indicate that the user wants to either ctrl+click on
229                        // an already selected item (to deselect the item), or click on a not-yet selected item
230                        // (which should remove all current selection, and add the clicked item). This can not
231                        // be done in onMouseDown, because the user may start a drag after mousedown. By moving
232                        // the deselection logic here, the user can drag an already selected item.
233                        if(!this._doDeselect){ return; }
234                        this._doDeselect = false;
235                        this.userSelect(e.type == "keyup" ? this.tree.focusedChild : this.current, connect.isCopyKey(e), e.shiftKey);
236                },
237                onMouseMove: function(/*===== e =====*/){
238                        // summary:
239                        //              event processor for onmousemove/ontouchmove
240                        // e: Event
241                        //              onmousemove/ontouchmove event
242                        this._doDeselect = false;
243                },
244
245                // mouse/touch events that are no longer used
246                onMouseDown: function(){
247                        // summary:
248                        //              Event processor for onmousedown/ontouchstart
249                        // e: Event
250                        //              onmousedown/ontouchstart event
251                        // tags:
252                        //              protected
253                },
254                onMouseUp: function(){
255                        // summary:
256                        //              Event processor for onmouseup/ontouchend
257                        // e: Event
258                        //              onmouseup/ontouchend event
259                        // tags:
260                        //              protected
261                },
262
263                _compareNodes: function(n1, n2){
264                        if(n1 === n2){
265                                return 0;
266                        }
267
268                        if('sourceIndex' in document.documentElement){ //IE
269                                //TODO: does not yet work if n1 and/or n2 is a text node
270                                return n1.sourceIndex - n2.sourceIndex;
271                        }else if('compareDocumentPosition' in document.documentElement){ //FF, Opera
272                                return n1.compareDocumentPosition(n2) & 2 ? 1: -1;
273                        }else if(document.createRange){ //Webkit
274                                var r1 = doc.createRange();
275                                r1.setStartBefore(n1);
276
277                                var r2 = doc.createRange();
278                                r2.setStartBefore(n2);
279
280                                return r1.compareBoundaryPoints(r1.END_TO_END, r2);
281                        }else{
282                                throw Error("dijit.tree._compareNodes don't know how to compare two different nodes in this browser");
283                        }
284                },
285
286                userSelect: function(node, multi, range){
287                        // summary:
288                        //              Add or remove the given node from selection, responding
289                        //              to a user action such as a click or keypress.
290                        // multi: Boolean
291                        //              Indicates whether this is meant to be a multi-select action (e.g. ctrl-click)
292                        // range: Boolean
293                        //              Indicates whether this is meant to be a ranged action (e.g. shift-click)
294                        // tags:
295                        //              protected
296
297                        if(this.singular){
298                                if(this.anchor == node && multi){
299                                        this.selectNone();
300                                }else{
301                                        this.setSelection([node]);
302                                        this.anchor = node;
303                                }
304                        }else{
305                                if(range && this.anchor){
306                                        var cr = this._compareNodes(this.anchor.rowNode, node.rowNode),
307                                        begin, end, anchor = this.anchor;
308
309                                        if(cr < 0){ //current is after anchor
310                                                begin = anchor;
311                                                end = node;
312                                        }else{ //current is before anchor
313                                                begin = node;
314                                                end = anchor;
315                                        }
316                                        var nodes = [];
317                                        //add everything betweeen begin and end inclusively
318                                        while(begin != end){
319                                                nodes.push(begin);
320                                                begin = this.tree._getNext(begin);
321                                        }
322                                        nodes.push(end);
323
324                                        this.setSelection(nodes);
325                                }else{
326                                        if( this.selection[ node.id ] && multi ){
327                                                this.removeTreeNode( node );
328                                        }else if(multi){
329                                                this.addTreeNode(node, true);
330                                        }else{
331                                                this.setSelection([node]);
332                                                this.anchor = node;
333                                        }
334                                }
335                        }
336                },
337
338                getItem: function(/*String*/ key){
339                        // summary:
340                        //              Returns the dojo/dnd/Container._Item (representing a dragged node) by it's key (id).
341                        //              Called by dojo/dnd/Source.checkAcceptance().
342                        // tags:
343                        //              protected
344
345                        var widget = this.selection[key];
346                        return {
347                                data: widget,
348                                type: ["treeNode"]
349                        }; // dojo/dnd/Container._Item
350                },
351
352                forInSelectedItems: function(/*Function*/ f, /*Object?*/ o){
353                        // summary:
354                        //              Iterates over selected items;
355                        //              see `dojo/dnd/Container.forInItems()` for details
356                        o = o || kernel.global;
357                        for(var id in this.selection){
358                                // console.log("selected item id: " + id);
359                                f.call(o, this.getItem(id), id, this);
360                        }
361                }
362        });
363});
Note: See TracBrowser for help on using the repository browser.