]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/tree/TreeStoreModel.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dijit / tree / TreeStoreModel.js.uncompressed.js
1 define("dijit/tree/TreeStoreModel", [
2         "dojo/_base/array", // array.filter array.forEach array.indexOf array.some
3         "dojo/aspect", // aspect.after
4         "dojo/_base/declare", // declare
5         "dojo/_base/lang" // lang.hitch
6 ], function(array, aspect, declare, lang){
7
8         // module:
9         //              dijit/tree/TreeStoreModel
10
11         return declare("dijit.tree.TreeStoreModel", null, {
12                 // summary:
13                 //              Implements dijit/Tree/model connecting to a dojo.data store with a single
14                 //              root item.  Any methods passed into the constructor will override
15                 //              the ones defined here.
16
17                 // store: dojo/data/api/Read
18                 //              Underlying store
19                 store: null,
20
21                 // childrenAttrs: String[]
22                 //              One or more attribute names (attributes in the dojo.data item) that specify that item's children
23                 childrenAttrs: ["children"],
24
25                 // newItemIdAttr: String
26                 //              Name of attribute in the Object passed to newItem() that specifies the id.
27                 //
28                 //              If newItemIdAttr is set then it's used when newItem() is called to see if an
29                 //              item with the same id already exists, and if so just links to the old item
30                 //              (so that the old item ends up with two parents).
31                 //
32                 //              Setting this to null or "" will make every drop create a new item.
33                 newItemIdAttr: "id",
34
35                 // labelAttr: String
36                 //              If specified, get label for tree node from this attribute, rather
37                 //              than by calling store.getLabel()
38                 labelAttr: "",
39
40                 // root: [readonly] dojo/data/Item
41                 //              Pointer to the root item (read only, not a parameter)
42                 root: null,
43
44                 // query: anything
45                 //              Specifies datastore query to return the root item for the tree.
46                 //              Must only return a single item.   Alternately can just pass in pointer
47                 //              to root item.
48                 // example:
49                 //      |       {id:'ROOT'}
50                 query: null,
51
52                 // deferItemLoadingUntilExpand: Boolean
53                 //              Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
54                 //              until they are expanded. This allows for lazying loading where only one
55                 //              loadItem (and generally one network call, consequently) per expansion
56                 //              (rather than one for each child).
57                 //              This relies on partial loading of the children items; each children item of a
58                 //              fully loaded item should contain the label and info about having children.
59                 deferItemLoadingUntilExpand: false,
60
61                 constructor: function(/* Object */ args){
62                         // summary:
63                         //              Passed the arguments listed above (store, etc)
64                         // tags:
65                         //              private
66
67                         lang.mixin(this, args);
68
69                         this.connects = [];
70
71                         var store = this.store;
72                         if(!store.getFeatures()['dojo.data.api.Identity']){
73                                 throw new Error("dijit.tree.TreeStoreModel: store must support dojo.data.Identity");
74                         }
75
76                         // if the store supports Notification, subscribe to the notification events
77                         if(store.getFeatures()['dojo.data.api.Notification']){
78                                 this.connects = this.connects.concat([
79                                         aspect.after(store, "onNew", lang.hitch(this, "onNewItem"), true),
80                                         aspect.after(store, "onDelete", lang.hitch(this, "onDeleteItem"), true),
81                                         aspect.after(store, "onSet", lang.hitch(this, "onSetItem"), true)
82                                 ]);
83                         }
84                 },
85
86                 destroy: function(){
87                         var h;
88                         while(h = this.connects.pop()){ h.remove(); }
89                         // TODO: should cancel any in-progress processing of getRoot(), getChildren()
90                 },
91
92                 // =======================================================================
93                 // Methods for traversing hierarchy
94
95                 getRoot: function(onItem, onError){
96                         // summary:
97                         //              Calls onItem with the root item for the tree, possibly a fabricated item.
98                         //              Calls onError on error.
99                         if(this.root){
100                                 onItem(this.root);
101                         }else{
102                                 this.store.fetch({
103                                         query: this.query,
104                                         onComplete: lang.hitch(this, function(items){
105                                                 if(items.length != 1){
106                                                         throw new Error("dijit.tree.TreeStoreModel: root query returned " + items.length +
107                                                                 " items, but must return exactly one");
108                                                 }
109                                                 this.root = items[0];
110                                                 onItem(this.root);
111                                         }),
112                                         onError: onError
113                                 });
114                         }
115                 },
116
117                 mayHaveChildren: function(/*dojo/data/Item*/ item){
118                         // summary:
119                         //              Tells if an item has or may have children.  Implementing logic here
120                         //              avoids showing +/- expando icon for nodes that we know don't have children.
121                         //              (For efficiency reasons we may not want to check if an element actually
122                         //              has children until user clicks the expando node)
123                         return array.some(this.childrenAttrs, function(attr){
124                                 return this.store.hasAttribute(item, attr);
125                         }, this);
126                 },
127
128                 getChildren: function(/*dojo/data/Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
129                         // summary:
130                         //              Calls onComplete() with array of child items of given parent item, all loaded.
131
132                         var store = this.store;
133                         if(!store.isItemLoaded(parentItem)){
134                                 // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
135                                 // mode, so we will load it and just return the children (without loading each
136                                 // child item)
137                                 var getChildren = lang.hitch(this, arguments.callee);
138                                 store.loadItem({
139                                         item: parentItem,
140                                         onItem: function(parentItem){
141                                                 getChildren(parentItem, onComplete, onError);
142                                         },
143                                         onError: onError
144                                 });
145                                 return;
146                         }
147                         // get children of specified item
148                         var childItems = [];
149                         for(var i=0; i<this.childrenAttrs.length; i++){
150                                 var vals = store.getValues(parentItem, this.childrenAttrs[i]);
151                                 childItems = childItems.concat(vals);
152                         }
153
154                         // count how many items need to be loaded
155                         var _waitCount = 0;
156                         if(!this.deferItemLoadingUntilExpand){
157                                 array.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
158                         }
159
160                         if(_waitCount == 0){
161                                 // all items are already loaded (or we aren't loading them).  proceed...
162                                 onComplete(childItems);
163                         }else{
164                                 // still waiting for some or all of the items to load
165                                 array.forEach(childItems, function(item, idx){
166                                         if(!store.isItemLoaded(item)){
167                                                 store.loadItem({
168                                                         item: item,
169                                                         onItem: function(item){
170                                                                 childItems[idx] = item;
171                                                                 if(--_waitCount == 0){
172                                                                         // all nodes have been loaded, send them to the tree
173                                                                         onComplete(childItems);
174                                                                 }
175                                                         },
176                                                         onError: onError
177                                                 });
178                                         }
179                                 });
180                         }
181                 },
182
183                 // =======================================================================
184                 // Inspecting items
185
186                 isItem: function(/* anything */ something){
187                         return this.store.isItem(something);    // Boolean
188                 },
189
190                 fetchItemByIdentity: function(/* object */ keywordArgs){
191                         this.store.fetchItemByIdentity(keywordArgs);
192                 },
193
194                 getIdentity: function(/* item */ item){
195                         return this.store.getIdentity(item);    // Object
196                 },
197
198                 getLabel: function(/*dojo/data/Item*/ item){
199                         // summary:
200                         //              Get the label for an item
201                         if(this.labelAttr){
202                                 return this.store.getValue(item,this.labelAttr);        // String
203                         }else{
204                                 return this.store.getLabel(item);       // String
205                         }
206                 },
207
208                 // =======================================================================
209                 // Write interface
210
211                 newItem: function(/* dijit/tree/dndSource.__Item */ args, /*dojo/data/api/Item*/ parent, /*int?*/ insertIndex){
212                         // summary:
213                         //              Creates a new item.   See `dojo/data/api/Write` for details on args.
214                         //              Used in drag & drop when item from external source dropped onto tree.
215                         // description:
216                         //              Developers will need to override this method if new items get added
217                         //              to parents with multiple children attributes, in order to define which
218                         //              children attribute points to the new item.
219
220                         var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;
221
222                         if(this.newItemIdAttr && args[this.newItemIdAttr]){
223                                 // Maybe there's already a corresponding item in the store; if so, reuse it.
224                                 this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){
225                                         if(item){
226                                                 // There's already a matching item in store, use it
227                                                 this.pasteItem(item, null, parent, true, insertIndex);
228                                         }else{
229                                                 // Create new item in the tree, based on the drag source.
230                                                 LnewItem=this.store.newItem(args, pInfo);
231                                                 if(LnewItem && (insertIndex!=undefined)){
232                                                         // Move new item to desired position
233                                                         this.pasteItem(LnewItem, parent, parent, false, insertIndex);
234                                                 }
235                                         }
236                                 }});
237                         }else{
238                                 // [as far as we know] there is no id so we must assume this is a new item
239                                 LnewItem=this.store.newItem(args, pInfo);
240                                 if(LnewItem && (insertIndex!=undefined)){
241                                         // Move new item to desired position
242                                         this.pasteItem(LnewItem, parent, parent, false, insertIndex);
243                                 }
244                         }
245                 },
246
247                 pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
248                         // summary:
249                         //              Move or copy an item from one parent item to another.
250                         //              Used in drag & drop
251                         var store = this.store,
252                                 parentAttr = this.childrenAttrs[0];     // name of "children" attr in parent item
253
254                         // remove child from source item, and record the attribute that child occurred in
255                         if(oldParentItem){
256                                 array.forEach(this.childrenAttrs, function(attr){
257                                         if(store.containsValue(oldParentItem, attr, childItem)){
258                                                 if(!bCopy){
259                                                         var values = array.filter(store.getValues(oldParentItem, attr), function(x){
260                                                                 return x != childItem;
261                                                         });
262                                                         store.setValues(oldParentItem, attr, values);
263                                                 }
264                                                 parentAttr = attr;
265                                         }
266                                 });
267                         }
268
269                         // modify target item's children attribute to include this item
270                         if(newParentItem){
271                                 if(typeof insertIndex == "number"){
272                                         // call slice() to avoid modifying the original array, confusing the data store
273                                         var childItems = store.getValues(newParentItem, parentAttr).slice();
274                                         childItems.splice(insertIndex, 0, childItem);
275                                         store.setValues(newParentItem, parentAttr, childItems);
276                                 }else{
277                                         store.setValues(newParentItem, parentAttr,
278                                                 store.getValues(newParentItem, parentAttr).concat(childItem));
279                                 }
280                         }
281                 },
282
283                 // =======================================================================
284                 // Callbacks
285
286                 onChange: function(/*dojo/data/Item*/ /*===== item =====*/){
287                         // summary:
288                         //              Callback whenever an item has changed, so that Tree
289                         //              can update the label, icon, etc.   Note that changes
290                         //              to an item's children or parent(s) will trigger an
291                         //              onChildrenChange() so you can ignore those changes here.
292                         // tags:
293                         //              callback
294                 },
295
296                 onChildrenChange: function(/*===== parent, newChildrenList =====*/){
297                         // summary:
298                         //              Callback to do notifications about new, updated, or deleted items.
299                         // parent: dojo/data/Item
300                         // newChildrenList: dojo/data/Item[]
301                         // tags:
302                         //              callback
303                 },
304
305                 onDelete: function(/*dojo/data/Item*/ /*===== item =====*/){
306                         // summary:
307                         //              Callback when an item has been deleted.
308                         // description:
309                         //              Note that there will also be an onChildrenChange() callback for the parent
310                         //              of this item.
311                         // tags:
312                         //              callback
313                 },
314
315                 // =======================================================================
316                 // Events from data store
317
318                 onNewItem: function(/* dojo/data/Item */ item, /* Object */ parentInfo){
319                         // summary:
320                         //              Handler for when new items appear in the store, either from a drop operation
321                         //              or some other way.   Updates the tree view (if necessary).
322                         // description:
323                         //              If the new item is a child of an existing item,
324                         //              calls onChildrenChange() with the new list of children
325                         //              for that existing item.
326                         //
327                         // tags:
328                         //              extension
329
330                         // We only care about the new item if it has a parent that corresponds to a TreeNode
331                         // we are currently displaying
332                         if(!parentInfo){
333                                 return;
334                         }
335
336                         // Call onChildrenChange() on parent (ie, existing) item with new list of children
337                         // In the common case, the new list of children is simply parentInfo.newValue or
338                         // [ parentInfo.newValue ], although if items in the store has multiple
339                         // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue,
340                         // so call getChildren() to be sure to get right answer.
341                         this.getChildren(parentInfo.item, lang.hitch(this, function(children){
342                                 this.onChildrenChange(parentInfo.item, children);
343                         }));
344                 },
345
346                 onDeleteItem: function(/*Object*/ item){
347                         // summary:
348                         //              Handler for delete notifications from underlying store
349                         this.onDelete(item);
350                 },
351
352                 onSetItem: function(item, attribute /*===== , oldValue, newValue =====*/){
353                         // summary:
354                         //              Updates the tree view according to changes in the data store.
355                         // description:
356                         //              Handles updates to an item's children by calling onChildrenChange(), and
357                         //              other updates to an item by calling onChange().
358                         //
359                         //              See `onNewItem` for more details on handling updates to an item's children.
360                         // item: Item
361                         // attribute: attribute-name-string
362                         // oldValue: Object|Array
363                         // newValue: Object|Array
364                         // tags:
365                         //              extension
366
367                         if(array.indexOf(this.childrenAttrs, attribute) != -1){
368                                 // item's children list changed
369                                 this.getChildren(item, lang.hitch(this, function(children){
370                                         // See comments in onNewItem() about calling getChildren()
371                                         this.onChildrenChange(item, children);
372                                 }));
373                         }else{
374                                 // item's label/icon/etc. changed.
375                                 this.onChange(item);
376                         }
377                 }
378         });
379 });