dijit/tree/TreeStoreModel.js

  • Provides:

    • dijit.tree.TreeStoreModel
  • dijit.tree.TreeStoreModel

    • type
      Function
    • summary
      Passed the arguments listed above (store, etc)
    • example
      
      	{id:'ROOT'}
    • parameters:
      • args: (typeof Object)
    • source: [view]
         dojo.mixin(this, args);


         this.connects = [];


         var store = this.store;
         if(!store.getFeatures()['dojo.data.api.Identity']){
          throw new Error("dijit.Tree: store must support dojo.data.Identity");
         }


         // if the store supports Notification, subscribe to the notification events
         if(store.getFeatures()['dojo.data.api.Notification']){
          this.connects = this.connects.concat([
           dojo.connect(store, "onNew", this, "onNewItem"),
           dojo.connect(store, "onDelete", this, "onDeleteItem"),
           dojo.connect(store, "onSet", this, "onSetItem")
          ]);
         }
    • tags:
  • dijit.tree.TreeStoreModel.store

    • type
      dojo.data.Store
    • summary
      Underlying store
  • dijit.tree.TreeStoreModel.childrenAttrs

    • type
      String[
    • summary
      One or more attribute names (attributes in the dojo.data item) that specify that item's children
  • dijit.tree.TreeStoreModel.newItemIdAttr

    • type
      String
    • summary
      Name of attribute in the Object passed to newItem() that specifies the id.
      
      If newItemIdAttr is set then it's used when newItem() is called to see if an
      item with the same id already exists, and if so just links to the old item
      (so that the old item ends up with two parents).
      
      Setting this to null or "" will make every drop create a new item.
  • dijit.tree.TreeStoreModel.labelAttr

    • type
      String
    • summary
      If specified, get label for tree node from this attribute, rather
      than by calling store.getLabel()
  • dijit.tree.TreeStoreModel.root

    • tags: readonly
    • type
      dojo.data.Item
    • summary
      Pointer to the root item (read only, not a parameter)
  • dijit.tree.TreeStoreModel.query

    • type
      anything
    • summary
      Specifies datastore query to return the root item for the tree.
      Must only return a single item.   Alternately can just pass in pointer
      to root item.
  • dijit.tree.TreeStoreModel.deferItemLoadingUntilExpand

    • type
      Boolean
    • summary
      Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
      until they are expanded. This allows for lazying loading where only one
      loadItem (and generally one network call, consequently) per expansion
      (rather than one for each child).
      This relies on partial loading of the children items; each children item of a
      fully loaded item should contain the label and info about having children.
  • dijit.tree.TreeStoreModel.destroy

    • type
      Function
    • source: [view]
         dojo.forEach(this.connects, dojo.disconnect);
         // TODO: should cancel any in-progress processing of getRoot(), getChildren()
    • summary
  • dijit.tree.TreeStoreModel.getRoot

    • type
      Function
    • parameters:
      • onItem: (typeof )
      • onError: (typeof )
    • source: [view]
         if(this.root){
          onItem(this.root);
         }else{
          this.store.fetch({
           query: this.query,
           onComplete: dojo.hitch(this, function(items){
            if(items.length != 1){
             throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length +
               " items, but must return exactly one item");
            }
            this.root = items[0];
            onItem(this.root);
           }),
           onError: onError
          });
         }
    • summary
      Calls onItem with the root item for the tree, possibly a fabricated item.
      Calls onError on error.
  • dijit.tree.TreeStoreModel.mayHaveChildren

    • type
      Function
    • parameters:
      • item: (typeof dojo.data.Item)
    • source: [view]
         return dojo.some(this.childrenAttrs, function(attr){
          return this.store.hasAttribute(item, attr);
         }, this);
    • summary
      Tells if an item has or may have children.  Implementing logic here
      avoids showing +/- expando icon for nodes that we know don't have children.
      (For efficiency reasons we may not want to check if an element actually
      has children until user clicks the expando node)
  • dijit.tree.TreeStoreModel.getChildren

    • type
      Function
    • parameters:
      • parentItem: (typeof dojo.data.Item)
      • onComplete: (typeof function(items))
      • onError: (typeof function)
    • source: [view]
         var store = this.store;
         if(!store.isItemLoaded(parentItem)){
          // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
          // mode, so we will load it and just return the children (without loading each
          // child item)
          var getChildren = dojo.hitch(this, arguments.callee);
          store.loadItem({
           item: parentItem,
           onItem: function(parentItem){
            getChildren(parentItem, onComplete, onError);
           },
           onError: onError
          });
          return;
         }
         // get children of specified item
         var childItems = [];
         for(var i=0; i    var vals = store.getValues(parentItem, this.childrenAttrs[i]);
          childItems = childItems.concat(vals);
         }


         // count how many items need to be loaded
         var _waitCount = 0;
         if(!this.deferItemLoadingUntilExpand){
          dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
         }


         if(_waitCount == 0){
          // all items are already loaded (or we aren't loading them). proceed...
          onComplete(childItems);
         }else{
          // still waiting for some or all of the items to load
          dojo.forEach(childItems, function(item, idx){
           if(!store.isItemLoaded(item)){
            store.loadItem({
             item: item,
             onItem: function(item){
              childItems[idx] = item;
              if(--_waitCount == 0){
               // all nodes have been loaded, send them to the tree
               onComplete(childItems);
              }
             },
             onError: onError
            });
           }
          });
         }
    • summary
      Calls onComplete() with array of child items of given parent item, all loaded.
  • dijit.tree.TreeStoreModel.isItem

    • type
      Function
    • parameters:
      • something: (typeof anything)
    • source: [view]
         return this.store.isItem(something); // Boolean
    • returns
      Boolean
    • summary
  • dijit.tree.TreeStoreModel.fetchItemByIdentity

    • type
      Function
    • parameters:
      • keywordArgs: (typeof object)
    • source: [view]
         this.store.fetchItemByIdentity(keywordArgs);
    • summary
  • dijit.tree.TreeStoreModel.getIdentity

    • type
      Function
    • parameters:
      • item: (typeof item)
    • source: [view]
         return this.store.getIdentity(item); // Object
    • returns
      Object
    • summary
  • dijit.tree.TreeStoreModel.getLabel

    • type
      Function
    • parameters:
      • item: (typeof dojo.data.Item)
    • source: [view]
         if(this.labelAttr){
          return this.store.getValue(item,this.labelAttr); // String
         }else{
          return this.store.getLabel(item); // String
         }
    • summary
      Get the label for an item
    • returns
      String
  • dijit.tree.TreeStoreModel.newItem

    • type
      Function
    • parameters:
      • args: (typeof dojo.dnd.Item)
      • parent: (typeof Item)
      • insertIndex: (typeof int)
    • source: [view]
         var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;


         if(this.newItemIdAttr && args[this.newItemIdAttr]){
          // Maybe there's already a corresponding item in the store; if so, reuse it.
          this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){
           if(item){
            // There's already a matching item in store, use it
            this.pasteItem(item, null, parent, true, insertIndex);
           }else{
            // Create new item in the tree, based on the drag source.
            LnewItem=this.store.newItem(args, pInfo);
            if (LnewItem && (insertIndex!=undefined)){
             // Move new item to desired position
             this.pasteItem(LnewItem, parent, parent, false, insertIndex);
            }
           }
          }});
         }else{
          // [as far as we know] there is no id so we must assume this is a new item
          LnewItem=this.store.newItem(args, pInfo);
          if (LnewItem && (insertIndex!=undefined)){
           // Move new item to desired position
           this.pasteItem(LnewItem, parent, parent, false, insertIndex);
          }
         }
    • summary
      Creates a new item.   See `dojo.data.api.Write` for details on args.
      Used in drag & drop when item from external source dropped onto tree.
    • description
      Developers will need to override this method if new items get added
      to parents with multiple children attributes, in order to define which
      children attribute points to the new item.
  • dijit.tree.TreeStoreModel.pasteItem

    • type
      Function
    • parameters:
      • childItem: (typeof Item)
      • oldParentItem: (typeof Item)
      • newParentItem: (typeof Item)
      • bCopy: (typeof Boolean)
      • insertIndex: (typeof int)
    • source: [view]
         var store = this.store,
          parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item


         // remove child from source item, and record the attribute that child occurred in
         if(oldParentItem){
          dojo.forEach(this.childrenAttrs, function(attr){
           if(store.containsValue(oldParentItem, attr, childItem)){
            if(!bCopy){
             var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){
              return x != childItem;
             });
             store.setValues(oldParentItem, attr, values);
            }
            parentAttr = attr;
           }
          });
         }


         // modify target item's children attribute to include this item
         if(newParentItem){
          if(typeof insertIndex == "number"){
           // call slice() to avoid modifying the original array, confusing the data store
           var childItems = store.getValues(newParentItem, parentAttr).slice();
           childItems.splice(insertIndex, 0, childItem);
           store.setValues(newParentItem, parentAttr, childItems);
          }else{
           store.setValues(newParentItem, parentAttr,
            store.getValues(newParentItem, parentAttr).concat(childItem));
          }
         }
    • summary
      Move or copy an item from one parent item to another.
      Used in drag & drop
  • dijit.tree.TreeStoreModel.onChange

    • type
      Function
    • parameters:
      • item: (typeof dojo.data.Item)
    • source: [view]
         // summary:
         //  Callback whenever an item has changed, so that Tree
         //  can update the label, icon, etc. Note that changes
         //  to an item's children or parent(s) will trigger an
         //  onChildrenChange() so you can ignore those changes here.
         // tags:
         //  callback
    • summary
      Callback whenever an item has changed, so that Tree
      can update the label, icon, etc.   Note that changes
      to an item's children or parent(s) will trigger an
      onChildrenChange() so you can ignore those changes here.
    • tags:
  • dijit.tree.TreeStoreModel.onChildrenChange

    • type
      Function
    • parameters:
      • parent: (typeof dojo.data.Item)
      • newChildrenList: (typeof dojo.data.Item[])
    • source: [view]
         // summary:
         //  Callback to do notifications about new, updated, or deleted items.
         // tags:
         //  callback
    • summary
      Callback to do notifications about new, updated, or deleted items.
    • tags:
  • dijit.tree.TreeStoreModel.onDelete

    • type
      Function
    • parameters:
      • parent: (typeof dojo.data.Item)
      • newChildrenList: (typeof dojo.data.Item[])
    • source: [view]
         // summary:
         //  Callback when an item has been deleted.
         // description:
         //  Note that there will also be an onChildrenChange() callback for the parent
         //  of this item.
         // tags:
         //  callback
    • summary
      Callback when an item has been deleted.
    • description
      Note that there will also be an onChildrenChange() callback for the parent
      of this item.
    • tags:
  • dijit.tree.TreeStoreModel.onNewItem

    • type
      Function
    • parameters:
      • item: (typeof dojo.data.Item)
      • parentInfo: (typeof Object)
    • source: [view]
      define("dijit/tree/TreeStoreModel", ["dojo", "dijit"], function(dojo, dijit) {


      dojo.declare(
        "dijit.tree.TreeStoreModel",
        null,
       {
        // summary:
        //  Implements dijit.Tree.model connecting to a store with a single
        //  root item. Any methods passed into the constructor will override
        //  the ones defined here.


        // store: dojo.data.Store
        //  Underlying store
        store: null,


        // childrenAttrs: String[]
        //  One or more attribute names (attributes in the dojo.data item) that specify that item's children
        childrenAttrs: ["children"],


        // newItemIdAttr: String
        //  Name of attribute in the Object passed to newItem() that specifies the id.
        //
        //  If newItemIdAttr is set then it's used when newItem() is called to see if an
        //  item with the same id already exists, and if so just links to the old item
        //  (so that the old item ends up with two parents).
        //
        //  Setting this to null or "" will make every drop create a new item.
        newItemIdAttr: "id",


        // labelAttr: String
        //  If specified, get label for tree node from this attribute, rather
        //  than by calling store.getLabel()
        labelAttr: "",


         // root: [readonly] dojo.data.Item
        //  Pointer to the root item (read only, not a parameter)
        root: null,


        // query: anything
        //  Specifies datastore query to return the root item for the tree.
        //  Must only return a single item. Alternately can just pass in pointer
        //  to root item.
        // example:
        // | {id:'ROOT'}
        query: null,


        // deferItemLoadingUntilExpand: Boolean
        //  Setting this to true will cause the TreeStoreModel to defer calling loadItem on nodes
        //   until they are expanded. This allows for lazying loading where only one
        //  loadItem (and generally one network call, consequently) per expansion
        //   (rather than one for each child).
        //   This relies on partial loading of the children items; each children item of a
        //   fully loaded item should contain the label and info about having children.
        deferItemLoadingUntilExpand: false,


        constructor: function(/* Object */ args){
         // summary:
         //  Passed the arguments listed above (store, etc)
         // tags:
         //  private


         dojo.mixin(this, args);


         this.connects = [];


         var store = this.store;
         if(!store.getFeatures()['dojo.data.api.Identity']){
          throw new Error("dijit.Tree: store must support dojo.data.Identity");
         }


         // if the store supports Notification, subscribe to the notification events
         if(store.getFeatures()['dojo.data.api.Notification']){
          this.connects = this.connects.concat([
           dojo.connect(store, "onNew", this, "onNewItem"),
           dojo.connect(store, "onDelete", this, "onDeleteItem"),
           dojo.connect(store, "onSet", this, "onSetItem")
          ]);
         }
        },


        destroy: function(){
         dojo.forEach(this.connects, dojo.disconnect);
         // TODO: should cancel any in-progress processing of getRoot(), getChildren()
        },


        // =======================================================================
        // Methods for traversing hierarchy


        getRoot: function(onItem, onError){
         // summary:
         //  Calls onItem with the root item for the tree, possibly a fabricated item.
         //  Calls onError on error.
         if(this.root){
          onItem(this.root);
         }else{
          this.store.fetch({
           query: this.query,
           onComplete: dojo.hitch(this, function(items){
            if(items.length != 1){
             throw new Error(this.declaredClass + ": query " + dojo.toJson(this.query) + " returned " + items.length +
               " items, but must return exactly one item");
            }
            this.root = items[0];
            onItem(this.root);
           }),
           onError: onError
          });
         }
        },


        mayHaveChildren: function(/*dojo.data.Item*/ item){
         // summary:
         //  Tells if an item has or may have children. Implementing logic here
         //  avoids showing +/- expando icon for nodes that we know don't have children.
         //  (For efficiency reasons we may not want to check if an element actually
         //  has children until user clicks the expando node)
         return dojo.some(this.childrenAttrs, function(attr){
          return this.store.hasAttribute(item, attr);
         }, this);
        },


        getChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){
         // summary:
         //   Calls onComplete() with array of child items of given parent item, all loaded.


         var store = this.store;
         if(!store.isItemLoaded(parentItem)){
          // The parent is not loaded yet, we must be in deferItemLoadingUntilExpand
          // mode, so we will load it and just return the children (without loading each
          // child item)
          var getChildren = dojo.hitch(this, arguments.callee);
          store.loadItem({
           item: parentItem,
           onItem: function(parentItem){
            getChildren(parentItem, onComplete, onError);
           },
           onError: onError
          });
          return;
         }
         // get children of specified item
         var childItems = [];
         for(var i=0; i    var vals = store.getValues(parentItem, this.childrenAttrs[i]);
          childItems = childItems.concat(vals);
         }


         // count how many items need to be loaded
         var _waitCount = 0;
         if(!this.deferItemLoadingUntilExpand){
          dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
         }


         if(_waitCount == 0){
          // all items are already loaded (or we aren't loading them). proceed...
          onComplete(childItems);
         }else{
          // still waiting for some or all of the items to load
          dojo.forEach(childItems, function(item, idx){
           if(!store.isItemLoaded(item)){
            store.loadItem({
             item: item,
             onItem: function(item){
              childItems[idx] = item;
              if(--_waitCount == 0){
               // all nodes have been loaded, send them to the tree
               onComplete(childItems);
              }
             },
             onError: onError
            });
           }
          });
         }
        },


        // =======================================================================
        // Inspecting items


        isItem: function(/* anything */ something){
         return this.store.isItem(something); // Boolean
        },


        fetchItemByIdentity: function(/* object */ keywordArgs){
         this.store.fetchItemByIdentity(keywordArgs);
        },


        getIdentity: function(/* item */ item){
         return this.store.getIdentity(item); // Object
        },


        getLabel: function(/*dojo.data.Item*/ item){
         // summary:
         //  Get the label for an item
         if(this.labelAttr){
          return this.store.getValue(item,this.labelAttr); // String
         }else{
          return this.store.getLabel(item); // String
         }
        },


        // =======================================================================
        // Write interface


        newItem: function(/* dojo.dnd.Item */ args, /*Item*/ parent, /*int?*/ insertIndex){
         // summary:
         //  Creates a new item. See `dojo.data.api.Write` for details on args.
         //  Used in drag & drop when item from external source dropped onto tree.
         // description:
         //  Developers will need to override this method if new items get added
         //  to parents with multiple children attributes, in order to define which
         //  children attribute points to the new item.


         var pInfo = {parent: parent, attribute: this.childrenAttrs[0]}, LnewItem;


         if(this.newItemIdAttr && args[this.newItemIdAttr]){
          // Maybe there's already a corresponding item in the store; if so, reuse it.
          this.fetchItemByIdentity({identity: args[this.newItemIdAttr], scope: this, onItem: function(item){
           if(item){
            // There's already a matching item in store, use it
            this.pasteItem(item, null, parent, true, insertIndex);
           }else{
            // Create new item in the tree, based on the drag source.
            LnewItem=this.store.newItem(args, pInfo);
            if (LnewItem && (insertIndex!=undefined)){
             // Move new item to desired position
             this.pasteItem(LnewItem, parent, parent, false, insertIndex);
            }
           }
          }});
         }else{
          // [as far as we know] there is no id so we must assume this is a new item
          LnewItem=this.store.newItem(args, pInfo);
          if (LnewItem && (insertIndex!=undefined)){
           // Move new item to desired position
           this.pasteItem(LnewItem, parent, parent, false, insertIndex);
          }
         }
        },


        pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){
         // summary:
         //  Move or copy an item from one parent item to another.
         //  Used in drag & drop
         var store = this.store,
          parentAttr = this.childrenAttrs[0]; // name of "children" attr in parent item


         // remove child from source item, and record the attribute that child occurred in
         if(oldParentItem){
          dojo.forEach(this.childrenAttrs, function(attr){
           if(store.containsValue(oldParentItem, attr, childItem)){
            if(!bCopy){
             var values = dojo.filter(store.getValues(oldParentItem, attr), function(x){
              return x != childItem;
             });
             store.setValues(oldParentItem, attr, values);
            }
            parentAttr = attr;
           }
          });
         }


         // modify target item's children attribute to include this item
         if(newParentItem){
          if(typeof insertIndex == "number"){
           // call slice() to avoid modifying the original array, confusing the data store
           var childItems = store.getValues(newParentItem, parentAttr).slice();
           childItems.splice(insertIndex, 0, childItem);
           store.setValues(newParentItem, parentAttr, childItems);
          }else{
           store.setValues(newParentItem, parentAttr,
            store.getValues(newParentItem, parentAttr).concat(childItem));
          }
         }
        },


        // =======================================================================
        // Callbacks


        onChange: function(/*dojo.data.Item*/ item){
         // summary:
         //  Callback whenever an item has changed, so that Tree
         //  can update the label, icon, etc. Note that changes
         //  to an item's children or parent(s) will trigger an
         //  onChildrenChange() so you can ignore those changes here.
         // tags:
         //  callback
        },


        onChildrenChange: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
         // summary:
         //  Callback to do notifications about new, updated, or deleted items.
         // tags:
         //  callback
        },


        onDelete: function(/*dojo.data.Item*/ parent, /*dojo.data.Item[]*/ newChildrenList){
         // summary:
         //  Callback when an item has been deleted.
         // description:
         //  Note that there will also be an onChildrenChange() callback for the parent
         //  of this item.
         // tags:
         //  callback
        },


        // =======================================================================
        // Events from data store


        onNewItem: function(/* dojo.data.Item */ item, /* Object */ parentInfo){
         // summary:
         //  Handler for when new items appear in the store, either from a drop operation
         //  or some other way. Updates the tree view (if necessary).
         // description:
         //  If the new item is a child of an existing item,
         //  calls onChildrenChange() with the new list of children
         //  for that existing item.
         //
         // tags:
         //  extension


         // We only care about the new item if it has a parent that corresponds to a TreeNode
         // we are currently displaying
         if(!parentInfo){
          return;
         }


         // Call onChildrenChange() on parent (ie, existing) item with new list of children
         // In the common case, the new list of children is simply parentInfo.newValue or
         // [ parentInfo.newValue ], although if items in the store has multiple
         // child attributes (see `childrenAttr`), then it's a superset of parentInfo.newValue,
         // so call getChildren() to be sure to get right answer.
         this.getChildren(parentInfo.item, dojo.hitch(this, function(children){
          this.onChildrenChange(parentInfo.item, children);
         }));
    • summary
      Handler for when new items appear in the store, either from a drop operation
      or some other way.   Updates the tree view (if necessary).
    • description
      If the new item is a child of an existing item,
      calls onChildrenChange() with the new list of children
      for that existing item.
    • returns
      Boolean|Object|String
  • dijit.tree.TreeStoreModel.onDeleteItem

    • type
      Function
    • parameters:
      • item: (typeof Object)
    • source: [view]
         this.onDelete(item);
    • summary
      Handler for delete notifications from underlying store
  • dijit.tree.TreeStoreModel.onSetItem

    • type
      Function
    • parameters:
      • item: (typeof item)
      • attribute: (typeof attribute-name-string)
      • oldValue: (typeof object | array)
      • newValue: (typeof object | array)
    • source: [view]
         if(dojo.indexOf(this.childrenAttrs, attribute) != -1){
          // item's children list changed
          this.getChildren(item, dojo.hitch(this, function(children){
           // See comments in onNewItem() about calling getChildren()
           this.onChildrenChange(item, children);
          }));
         }else{
          // item's label/icon/etc. changed.
          this.onChange(item);
         }
    • summary
      Updates the tree view according to changes in the data store.
    • description
      Handles updates to an item's children by calling onChildrenChange(), and
      other updates to an item by calling onChange().
      
      See `onNewItem` for more details on handling updates to an item's children.
    • tags:
  • dijit.tree.TreeStoreModel.connects

    • summary
  • this

    • mixins:
      • args: (normal)
    • summary
  • dijit.tree

    • type
      Object
    • summary
  • dijit

    • type
      Object
    • summary