source: Dev/trunk/src/client/dijit/tree/ObjectStoreModel.js @ 536

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

Added Dojo 1.9.3 release.

File size: 8.7 KB
Line 
1define([
2        "dojo/_base/array", // array.filter array.forEach array.indexOf array.some
3        "dojo/aspect", // aspect.before, aspect.after
4        "dojo/_base/declare", // declare
5        "dojo/_base/lang", // lang.hitch
6        "dojo/when"
7], function(array, aspect, declare, lang, when){
8
9        // module:
10        //              dijit/tree/ObjectStoreModel
11
12        return declare("dijit.tree.ObjectStoreModel", null, {
13                // summary:
14                //              Implements dijit/tree/model connecting dijit/Tree to a dojo/store/api/Store that implements
15                //              getChildren().
16                //
17                //              If getChildren() returns an array with an observe() method, then it will be leveraged to reflect
18                //              store updates to the tree.   So, this class will work best when:
19                //
20                //                      1. the store implements dojo/store/Observable
21                //                      2. getChildren() is implemented as a query to the server (i.e. it calls store.query())
22                //
23                //              Drag and Drop: To support drag and drop, besides implementing getChildren()
24                //              and dojo/store/Observable, the store must support the parent option to put().
25                //              And in order to have child elements ordered according to how the user dropped them,
26                //              put() must support the before option.
27
28                // store: dojo/store/api/Store
29                //              Underlying store
30                store: null,
31
32                // labelAttr: String
33                //              Get label for tree node from this attribute
34                labelAttr: "name",
35
36                // labelType: [const] String
37                //              Specifies how to interpret the labelAttr in the data store items.
38                //              Can be "html" or "text".
39                labelType: "text",
40
41                // root: [readonly] Object
42                //              Pointer to the root item from the dojo/store/api/Store (read only, not a parameter)
43                root: null,
44
45                // query: anything
46                //              Specifies datastore query to return the root item for the tree.
47                //              Must only return a single item.   Alternately can just pass in pointer
48                //              to root item.
49                // example:
50                //      |       {id:'ROOT'}
51                query: null,
52
53                constructor: function(/* Object */ args){
54                        // summary:
55                        //              Passed the arguments listed above (store, etc)
56                        // tags:
57                        //              private
58
59                        lang.mixin(this, args);
60
61                        this.childrenCache = {};        // map from id to array of children
62                },
63
64                destroy: function(){
65                        // TODO: should cancel any in-progress processing of getRoot(), getChildren()
66                        for(var id in this.childrenCache){
67                                this.childrenCache[id].close && this.childrenCache[id].close();
68                        }
69                },
70
71                // =======================================================================
72                // Methods for traversing hierarchy
73
74                getRoot: function(onItem, onError){
75                        // summary:
76                        //              Calls onItem with the root item for the tree, possibly a fabricated item.
77                        //              Calls onError on error.
78                        if(this.root){
79                                onItem(this.root);
80                        }else{
81                                var res;
82                                when(res = this.store.query(this.query),
83                                        lang.hitch(this, function(items){
84                                                //console.log("queried root: ", res);
85                                                if(items.length != 1){
86                                                        throw new Error("dijit.tree.ObjectStoreModel: root query returned " + items.length +
87                                                                " items, but must return exactly one");
88                                                }
89                                                this.root = items[0];
90                                                onItem(this.root);
91
92                                                // Setup listener to detect if root item changes
93                                                if(res.observe){
94                                                        res.observe(lang.hitch(this, function(obj){
95                                                                // Presumably removedFrom == insertedInto == 1, and this call indicates item has changed.
96                                                                //console.log("root changed: ", obj);
97                                                                this.onChange(obj);
98                                                        }), true);      // true to listen for updates to obj
99                                                }
100                                        }),
101                                        onError
102                                );
103                        }
104                },
105
106                mayHaveChildren: function(/*===== item =====*/){
107                        // summary:
108                        //              Tells if an item has or might have children.  Implementing logic here
109                        //              avoids showing +/- expando icon for nodes that we know won't have children.
110                        //              (For efficiency reasons we may not want to check if an element actually
111                        //              has children until user clicks the expando node).
112                        //
113                        //              Application code should override this method based on the data, for example
114                        //              it could be `return item.leaf == true;`.
115                        //
116                        //              Note that mayHaveChildren() must return true for an item if it could possibly
117                        //              have children in the future, due to drag-an-drop or some other data store update.
118                        //              Also note that it may return true if it's just too expensive to check during tree
119                        //              creation whether or not the item has children.
120                        // item: Object
121                        //              Item from the dojo/store
122                        return true;
123                },
124
125                getChildren: function(/*Object*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
126                        // summary:
127                        //              Calls onComplete() with array of child items of given parent item.
128                        // parentItem:
129                        //              Item from the dojo/store
130
131                        var id = this.store.getIdentity(parentItem);
132                        if(this.childrenCache[id]){
133                                when(this.childrenCache[id], onComplete, onError);
134                                return;
135                        }
136
137                        var res = this.childrenCache[id] = this.store.getChildren(parentItem);
138
139                        // User callback
140                        when(res, onComplete, onError);
141
142                        // Setup listener in case children list changes, or the item(s) in the children list are
143                        // updated in some way.
144                        if(res.observe){
145                                res.observe(lang.hitch(this, function(obj, removedFrom, insertedInto){
146                                        //console.log("observe on children of ", id, ": ", obj, removedFrom, insertedInto);
147
148                                        // If removedFrom == insertedInto, this call indicates that the item has changed.
149                                        // Even if removedFrom != insertedInto, the item may have changed.
150                                        this.onChange(obj);
151
152                                        if(removedFrom != insertedInto){
153                                                // Indicates an item was added, removed, or re-parented.  The children[] array (returned from
154                                                // res.then(...)) has already been updated (like a live collection), so just use it.
155                                                when(res, lang.hitch(this, "onChildrenChange", parentItem));
156                                        }
157                                }), true);      // true means to notify on item changes
158                        }
159                },
160
161                // =======================================================================
162                // Inspecting items
163
164                isItem: function(/*===== something =====*/){
165                        return true;    // Boolean
166                },
167
168                getIdentity: function(/* item */ item){
169                        return this.store.getIdentity(item);    // Object
170                },
171
172                getLabel: function(/*dojo/data/Item*/ item){
173                        // summary:
174                        //              Get the label for an item
175                        return item[this.labelAttr];    // String
176                },
177
178                // =======================================================================
179                // Write interface, for DnD
180
181                newItem: function(/* dijit/tree/dndSource.__Item */ args, /*Item*/ parent, /*int?*/ insertIndex, /*Item*/ before){
182                        // summary:
183                        //              Creates a new item.   See `dojo/data/api/Write` for details on args.
184                        //              Used in drag & drop when item from external source dropped onto tree.
185
186                        return this.store.put(args, {
187                                parent: parent,
188                                before: before
189                        });
190                },
191
192                pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem,
193                                        /*Boolean*/ bCopy, /*int?*/ insertIndex, /*Item*/ before){
194                        // summary:
195                        //              Move or copy an item from one parent item to another.
196                        //              Used in drag & drop
197
198                        if(!bCopy){
199                                // In order for DnD moves to work correctly, childItem needs to be orphaned from oldParentItem
200                                // before being adopted by newParentItem.   That way, the TreeNode is moved rather than
201                                // an additional TreeNode being created, and the old TreeNode subsequently being deleted.
202                                // The latter loses information such as selection and opened/closed children TreeNodes.
203                                // Unfortunately simply calling this.store.put() will send notifications in a random order, based
204                                // on when the TreeNodes in question originally appeared, and not based on the drag-from
205                                // TreeNode vs. the drop-onto TreeNode.
206
207                                var oldParentChildren = [].concat(this.childrenCache[this.getIdentity(oldParentItem)]), // concat to make copy
208                                        index = array.indexOf(oldParentChildren, childItem);
209                                oldParentChildren.splice(index, 1);
210                                this.onChildrenChange(oldParentItem, oldParentChildren);
211                        }
212
213                        return this.store.put(childItem, {
214                                overwrite: true,
215                                parent: newParentItem,
216                                before: before
217                        });
218                },
219
220                // =======================================================================
221                // Callbacks
222
223                onChange: function(/*dojo/data/Item*/ /*===== item =====*/){
224                        // summary:
225                        //              Callback whenever an item has changed, so that Tree
226                        //              can update the label, icon, etc.   Note that changes
227                        //              to an item's children or parent(s) will trigger an
228                        //              onChildrenChange() so you can ignore those changes here.
229                        // tags:
230                        //              callback
231                },
232
233                onChildrenChange: function(/*===== parent, newChildrenList =====*/){
234                        // summary:
235                        //              Callback to do notifications about new, updated, or deleted items.
236                        // parent: dojo/data/Item
237                        // newChildrenList: Object[]
238                        //              Items from the store
239                        // tags:
240                        //              callback
241                },
242
243                onDelete: function(/*dojo/data/Item*/ /*===== item =====*/){
244                        // summary:
245                        //              Callback when an item has been deleted.
246                        //              Actually we have no way of knowing this with the new dojo.store API,
247                        //              so this method is never called (but it's left here since Tree connects
248                        //              to it).
249                        // tags:
250                        //              callback
251                }
252        });
253});
Note: See TracBrowser for help on using the repository browser.