dojox/data/ItemExplorer.js

  • Provides:

    • dojox.data.ItemExplorer
  • Requires:

    • dijit.Tree in common in project dijit
    • dijit.Dialog in common in project dijit
    • dijit.Menu in common in project dijit
    • dijit.form.ValidationTextBox in common in project dijit
    • dijit.form.Textarea in common in project dijit
    • dijit.form.Button in common in project dijit
    • dijit.form.CheckBox in common in project dijit
    • dijit.form.FilteringSelect in common in project dijit
  • dojox.data.ItemExplorer

    • type
      Function
    • chains:
      • dijit.Tree: (prototype)
      • dijit.Tree: (call)
    • parameters:
      • options: (typeof )
    • source: [view]
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
    • summary
  • dojox.data.ItemExplorer.useSelect

    • summary
  • dojox.data.ItemExplorer.refSelectSearchAttr

    • summary
  • dojox.data.ItemExplorer.postCreate

    • type
      Function
    • source: [view]
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
    • summary
  • dojox.data.ItemExplorer.store

    • summary
  • dojox.data.ItemExplorer.setStore

    • type
      Function
    • parameters:
      • store: (typeof )
    • source: [view]
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
    • summary
  • dojox.data.ItemExplorer.setItem

    • type
      Function
    • parameters:
      • item: (typeof )
    • source: [view]
      dojo.provide("dojox.data.ItemExplorer");
      dojo.require("dijit.Tree");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.Menu");
      dojo.require("dijit.form.ValidationTextBox");
      dojo.require("dijit.form.Textarea");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.form.CheckBox");
      dojo.require("dijit.form.FilteringSelect");


      (function(){
       var getValue = function(store, item, prop){
        var value = store.getValues(item, prop);
        if(value.length < 2){
         value = store.getValue(item, prop);
        }
        return value;
       }


      dojo.declare("dojox.data.ItemExplorer", dijit.Tree, {
       useSelect: false,
       refSelectSearchAttr: null,
       constructor: function(options){
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
       },
       postCreate: function(){
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
       },
       store: null,
       setStore: function(store){
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
       },
       setItem: function(item){
        // this is called to show a different item


        // reset the maps, for the root getIdentity is not called, so we pre-initialize it here
        (this._modelNodeIdMap = {})[this.store.getIdentity(item)] = [this.rootModelNode];
        this._modelNodePropMap = {};


        this.rootModelNode.value = item;
        var self = this;
        this.model.getChildren(this.rootModelNode, function(children){
         self.rootNode.setChildItems(children);
        });
    • summary
  • dojox.data.ItemExplorer.refreshItem

    • type
      Function
    • source: [view]
        this.setItem(this.rootModelNode.value);
    • summary
  • dojox.data.ItemExplorer._createEditDialog

    • type
      Function
    • source: [view]
        this._editDialog = new dijit.Dialog({
          title: "Edit Property",
          execute: dojo.hitch(this, "_updateItem"),
          preload: true
        });
        this._editDialog.placeAt(dojo.body());
        this._editDialog.startup();


        // handle for dialog content
        var pane = dojo.doc.createElement('div');


        // label for property
        var labelProp = dojo.doc.createElement('label');
        dojo.attr(labelProp, "for", "property");
        dojo.style(labelProp, "fontWeight", "bold");
        dojo.attr(labelProp, "innerHTML", "Property:")
        pane.appendChild(labelProp);


        // property name field
        var propName = new dijit.form.ValidationTextBox({
         name: "property",
         value: "",
         required: true,
         disabled: true
        }).placeAt(pane);


        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // radio button for "value"
        var value = new dijit.form.RadioButton({
         name: "itemType",
         value: "value",
         onClick: dojo.hitch(this, function(){this._enableFields("value");})
        }).placeAt(pane);


        // label for value
        var labelVal = dojo.doc.createElement('label');
        dojo.attr(labelVal, "for", "value");
        dojo.attr(labelVal, "innerHTML", "Value (JSON):")
        pane.appendChild(labelVal);


         // container for value fields
        var valueDiv = dojo.doc.createElement("div");
        dojo.addClass(valueDiv, "value");


        // textarea
        var textarea = new dijit.form.Textarea({
         name: "jsonVal"
        }).placeAt(valueDiv);
        pane.appendChild(valueDiv);


        // radio button for "reference"
        var reference = new dijit.form.RadioButton({
         name: "itemType",
         value: "reference",
         onClick: dojo.hitch(this, function(){this._enableFields("reference");})
        }).placeAt(pane);


        // label for reference
        var labelRef = dojo.doc.createElement('label');
        dojo.attr(labelRef, "for", "_reference");
        dojo.attr(labelRef, "innerHTML", "Reference (ID):")
        pane.appendChild(labelRef);
        pane.appendChild(dojo.doc.createElement("br"));


        // container for reference fields
        var refDiv = dojo.doc.createElement("div");
        dojo.addClass(refDiv, "reference");


        if(this.useSelect){
         // filteringselect
         // TODO: see if there is a way to sort the items in this list
         var refSelect = new dijit.form.FilteringSelect({
          name: "_reference",
          store: this.store,
          searchAttr: this.refSelectSearchAttr || this.store.getIdentityAttributes()[0],
          required: false,
          value: null,  // need to file a ticket about the fetch that happens when declared with value: null
          pageSize: 10
         }).placeAt(refDiv);
        }else{
         var refTextbox = new dijit.form.ValidationTextBox({
          name: "_reference",
          value: "",
          promptMessage: "Enter the ID of the item to reference",
          isValid: dojo.hitch(this, function(isFocused){
           // don't validate while it's focused
           return true;//isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
          })
         }).placeAt(refDiv);
        }
        pane.appendChild(refDiv);
        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // buttons
        var buttons = document.createElement('div');
        buttons.setAttribute("dir", "rtl");
        var cancelButton = new dijit.form.Button({type: "reset", label: "Cancel"}).placeAt(buttons);
        cancelButton.onClick = dojo.hitch(this._editDialog, "onCancel");
        var okButton = new dijit.form.Button({type: "submit", label: "OK"}).placeAt(buttons);
        pane.appendChild(buttons);


        this._editDialog.attr("content", pane);
    • returns
      isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
    • summary
  • dojox.data.ItemExplorer._enableFields

    • type
      Function
    • parameters:
      • selection: (typeof )
    • source: [view]
      dojo.provide("dojox.data.ItemExplorer");
      dojo.require("dijit.Tree");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.Menu");
      dojo.require("dijit.form.ValidationTextBox");
      dojo.require("dijit.form.Textarea");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.form.CheckBox");
      dojo.require("dijit.form.FilteringSelect");


      (function(){
       var getValue = function(store, item, prop){
        var value = store.getValues(item, prop);
        if(value.length < 2){
         value = store.getValue(item, prop);
        }
        return value;
       }


      dojo.declare("dojox.data.ItemExplorer", dijit.Tree, {
       useSelect: false,
       refSelectSearchAttr: null,
       constructor: function(options){
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
       },
       postCreate: function(){
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
       },
       store: null,
       setStore: function(store){
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
       },
       setItem: function(item){
        // this is called to show a different item


        // reset the maps, for the root getIdentity is not called, so we pre-initialize it here
        (this._modelNodeIdMap = {})[this.store.getIdentity(item)] = [this.rootModelNode];
        this._modelNodePropMap = {};


        this.rootModelNode.value = item;
        var self = this;
        this.model.getChildren(this.rootModelNode, function(children){
         self.rootNode.setChildItems(children);
        });


       },
       refreshItem: function(){
        this.setItem(this.rootModelNode.value);
       },
       _createEditDialog: function(){
        this._editDialog = new dijit.Dialog({
          title: "Edit Property",
          execute: dojo.hitch(this, "_updateItem"),
          preload: true
        });
        this._editDialog.placeAt(dojo.body());
        this._editDialog.startup();


        // handle for dialog content
        var pane = dojo.doc.createElement('div');


        // label for property
        var labelProp = dojo.doc.createElement('label');
        dojo.attr(labelProp, "for", "property");
        dojo.style(labelProp, "fontWeight", "bold");
        dojo.attr(labelProp, "innerHTML", "Property:")
        pane.appendChild(labelProp);


        // property name field
        var propName = new dijit.form.ValidationTextBox({
         name: "property",
         value: "",
         required: true,
         disabled: true
        }).placeAt(pane);


        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // radio button for "value"
        var value = new dijit.form.RadioButton({
         name: "itemType",
         value: "value",
         onClick: dojo.hitch(this, function(){this._enableFields("value");})
        }).placeAt(pane);


        // label for value
        var labelVal = dojo.doc.createElement('label');
        dojo.attr(labelVal, "for", "value");
        dojo.attr(labelVal, "innerHTML", "Value (JSON):")
        pane.appendChild(labelVal);


         // container for value fields
        var valueDiv = dojo.doc.createElement("div");
        dojo.addClass(valueDiv, "value");


        // textarea
        var textarea = new dijit.form.Textarea({
         name: "jsonVal"
        }).placeAt(valueDiv);
        pane.appendChild(valueDiv);


        // radio button for "reference"
        var reference = new dijit.form.RadioButton({
         name: "itemType",
         value: "reference",
         onClick: dojo.hitch(this, function(){this._enableFields("reference");})
        }).placeAt(pane);


        // label for reference
        var labelRef = dojo.doc.createElement('label');
        dojo.attr(labelRef, "for", "_reference");
        dojo.attr(labelRef, "innerHTML", "Reference (ID):")
        pane.appendChild(labelRef);
        pane.appendChild(dojo.doc.createElement("br"));


        // container for reference fields
        var refDiv = dojo.doc.createElement("div");
        dojo.addClass(refDiv, "reference");


        if(this.useSelect){
         // filteringselect
         // TODO: see if there is a way to sort the items in this list
         var refSelect = new dijit.form.FilteringSelect({
          name: "_reference",
          store: this.store,
          searchAttr: this.refSelectSearchAttr || this.store.getIdentityAttributes()[0],
          required: false,
          value: null,  // need to file a ticket about the fetch that happens when declared with value: null
          pageSize: 10
         }).placeAt(refDiv);
        }else{
         var refTextbox = new dijit.form.ValidationTextBox({
          name: "_reference",
          value: "",
          promptMessage: "Enter the ID of the item to reference",
          isValid: dojo.hitch(this, function(isFocused){
           // don't validate while it's focused
           return true;//isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
          })
         }).placeAt(refDiv);
        }
        pane.appendChild(refDiv);
        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // buttons
        var buttons = document.createElement('div');
        buttons.setAttribute("dir", "rtl");
        var cancelButton = new dijit.form.Button({type: "reset", label: "Cancel"}).placeAt(buttons);
        cancelButton.onClick = dojo.hitch(this._editDialog, "onCancel");
        var okButton = new dijit.form.Button({type: "submit", label: "OK"}).placeAt(buttons);
        pane.appendChild(buttons);


        this._editDialog.attr("content", pane);
       },
       _enableFields: function(selection){
        // enables/disables fields based on whether the value in this._editDialog is a reference or a primitive value
        switch(selection){
         case "reference":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          break;
         case "value":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          break;
        }
    • returns
      isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
    • summary
  • dojox.data.ItemExplorer._updateItem

    • type
      Function
    • parameters:
      • vals: (typeof )
    • source: [view]
      dojo.provide("dojox.data.ItemExplorer");
      dojo.require("dijit.Tree");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.Menu");
      dojo.require("dijit.form.ValidationTextBox");
      dojo.require("dijit.form.Textarea");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.form.CheckBox");
      dojo.require("dijit.form.FilteringSelect");


      (function(){
       var getValue = function(store, item, prop){
        var value = store.getValues(item, prop);
        if(value.length < 2){
         value = store.getValue(item, prop);
        }
        return value;
       }


      dojo.declare("dojox.data.ItemExplorer", dijit.Tree, {
       useSelect: false,
       refSelectSearchAttr: null,
       constructor: function(options){
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
       },
       postCreate: function(){
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
       },
       store: null,
       setStore: function(store){
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
       },
       setItem: function(item){
        // this is called to show a different item


        // reset the maps, for the root getIdentity is not called, so we pre-initialize it here
        (this._modelNodeIdMap = {})[this.store.getIdentity(item)] = [this.rootModelNode];
        this._modelNodePropMap = {};


        this.rootModelNode.value = item;
        var self = this;
        this.model.getChildren(this.rootModelNode, function(children){
         self.rootNode.setChildItems(children);
        });


       },
       refreshItem: function(){
        this.setItem(this.rootModelNode.value);
       },
       _createEditDialog: function(){
        this._editDialog = new dijit.Dialog({
          title: "Edit Property",
          execute: dojo.hitch(this, "_updateItem"),
          preload: true
        });
        this._editDialog.placeAt(dojo.body());
        this._editDialog.startup();


        // handle for dialog content
        var pane = dojo.doc.createElement('div');


        // label for property
        var labelProp = dojo.doc.createElement('label');
        dojo.attr(labelProp, "for", "property");
        dojo.style(labelProp, "fontWeight", "bold");
        dojo.attr(labelProp, "innerHTML", "Property:")
        pane.appendChild(labelProp);


        // property name field
        var propName = new dijit.form.ValidationTextBox({
         name: "property",
         value: "",
         required: true,
         disabled: true
        }).placeAt(pane);


        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // radio button for "value"
        var value = new dijit.form.RadioButton({
         name: "itemType",
         value: "value",
         onClick: dojo.hitch(this, function(){this._enableFields("value");})
        }).placeAt(pane);


        // label for value
        var labelVal = dojo.doc.createElement('label');
        dojo.attr(labelVal, "for", "value");
        dojo.attr(labelVal, "innerHTML", "Value (JSON):")
        pane.appendChild(labelVal);


         // container for value fields
        var valueDiv = dojo.doc.createElement("div");
        dojo.addClass(valueDiv, "value");


        // textarea
        var textarea = new dijit.form.Textarea({
         name: "jsonVal"
        }).placeAt(valueDiv);
        pane.appendChild(valueDiv);


        // radio button for "reference"
        var reference = new dijit.form.RadioButton({
         name: "itemType",
         value: "reference",
         onClick: dojo.hitch(this, function(){this._enableFields("reference");})
        }).placeAt(pane);


        // label for reference
        var labelRef = dojo.doc.createElement('label');
        dojo.attr(labelRef, "for", "_reference");
        dojo.attr(labelRef, "innerHTML", "Reference (ID):")
        pane.appendChild(labelRef);
        pane.appendChild(dojo.doc.createElement("br"));


        // container for reference fields
        var refDiv = dojo.doc.createElement("div");
        dojo.addClass(refDiv, "reference");


        if(this.useSelect){
         // filteringselect
         // TODO: see if there is a way to sort the items in this list
         var refSelect = new dijit.form.FilteringSelect({
          name: "_reference",
          store: this.store,
          searchAttr: this.refSelectSearchAttr || this.store.getIdentityAttributes()[0],
          required: false,
          value: null,  // need to file a ticket about the fetch that happens when declared with value: null
          pageSize: 10
         }).placeAt(refDiv);
        }else{
         var refTextbox = new dijit.form.ValidationTextBox({
          name: "_reference",
          value: "",
          promptMessage: "Enter the ID of the item to reference",
          isValid: dojo.hitch(this, function(isFocused){
           // don't validate while it's focused
           return true;//isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
          })
         }).placeAt(refDiv);
        }
        pane.appendChild(refDiv);
        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // buttons
        var buttons = document.createElement('div');
        buttons.setAttribute("dir", "rtl");
        var cancelButton = new dijit.form.Button({type: "reset", label: "Cancel"}).placeAt(buttons);
        cancelButton.onClick = dojo.hitch(this._editDialog, "onCancel");
        var okButton = new dijit.form.Button({type: "submit", label: "OK"}).placeAt(buttons);
        pane.appendChild(buttons);


        this._editDialog.attr("content", pane);
       },
       _enableFields: function(selection){
        // enables/disables fields based on whether the value in this._editDialog is a reference or a primitive value
        switch(selection){
         case "reference":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          break;
         case "value":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          break;
        }
       },
       _updateItem: function(vals){
        // a single "execute" function that handles adding and editing of values and references.
        var node, item, val, storeItemVal, editingItem = this._editDialog.attr("title") == "Edit Property";
        var editDialog = this._editDialog;
        var store = this.store;
        function setValue(){
         try{
          var itemVal, propPath = [];
          var prop = vals.property;
          if(editingItem){
           while(!store.isItem(item.parent, true)){
            node = node.getParent();
            propPath.push(item.property);
            item = node.item;
           }
           if(propPath.length == 0){
            // working with an item attribute already
            store.setValue(item.parent, item.property, val);
           }else{
            // need to walk back down the item property to the object
            storeItemVal = getValue(store, item.parent, item.property);
            if(storeItemVal instanceof Array){
             // create a copy for modification
             storeItemVal = storeItemVal.concat();
            }
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val; // this change is reflected in storeItemVal as well
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }else{
           // adding a property
           if(store.isItem(value, true)){
            // adding a top-level property to an item
            if(!store.isItemLoaded(value)){
             // fetch the value and see if it is an array
             store.loadItem({
              item: value,
              onItem: function(loadedItem){
               if(loadedItem instanceof Array){
                prop = loadedItem.length;
               }
               store.setValue(loadedItem, prop, val);
              }
             });
            }else{
             if(value instanceof Array){
              prop = value.length;
             }
             store.setValue(value, prop, val);
            }
           }else{
            // adding a property to a lower level in an item
            if(item.value instanceof Array){
             propPath.push(item.value.length);
            }else{
             propPath.push(vals.property);
            }
            while(!store.isItem(item.parent, true)){
             node = node.getParent();
             propPath.push(item.property);
             item = node.item;
            }
            storeItemVal = getValue(store, item.parent, item.property);
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val;
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }
         }catch(e){
          alert(e);
         }
        }


        if(editDialog.validate()){
         node = this.lastFocused;
         item = node.item;
         var value = item.value;
         // var property = null;
         if(item.addNew){
          // we are adding a property to the parent item
          // the real value of the parent is in the parent property of the lastFocused item
          // this.lastFocused.getParent().item.value may be a reference to an item
          value = node.item.parent;
          node = node.getParent();
          item = node.item;
         }
         val = null;
         switch(vals.itemType){
          case "reference":
           this.store.fetchItemByIdentity({identity:vals._reference,
            onItem:function(item){
             val = item;
             setValue();
            },
            onError:function(){
             alert("The id could not be found");
            }
           });
           break;
          case "value":
           var jsonVal = vals.jsonVal;
           val = dojo.fromJson(jsonVal);
           // ifit is a function we want to preserve the source (comments, et al)
           if(typeof val == 'function'){
            val.toString = function(){
             return jsonVal;
            }
           }
           setValue();
           break;
         }
        }else{
         // the form didn't validate - show it again.
         editDialog.show();
        }
    • returns
      isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
    • summary
  • dojox.data.ItemExplorer._editProperty

    • type
      Function
    • source: [view]
      dojo.provide("dojox.data.ItemExplorer");
      dojo.require("dijit.Tree");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.Menu");
      dojo.require("dijit.form.ValidationTextBox");
      dojo.require("dijit.form.Textarea");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.form.CheckBox");
      dojo.require("dijit.form.FilteringSelect");


      (function(){
       var getValue = function(store, item, prop){
        var value = store.getValues(item, prop);
        if(value.length < 2){
         value = store.getValue(item, prop);
        }
        return value;
       }


      dojo.declare("dojox.data.ItemExplorer", dijit.Tree, {
       useSelect: false,
       refSelectSearchAttr: null,
       constructor: function(options){
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
       },
       postCreate: function(){
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
       },
       store: null,
       setStore: function(store){
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
       },
       setItem: function(item){
        // this is called to show a different item


        // reset the maps, for the root getIdentity is not called, so we pre-initialize it here
        (this._modelNodeIdMap = {})[this.store.getIdentity(item)] = [this.rootModelNode];
        this._modelNodePropMap = {};


        this.rootModelNode.value = item;
        var self = this;
        this.model.getChildren(this.rootModelNode, function(children){
         self.rootNode.setChildItems(children);
        });


       },
       refreshItem: function(){
        this.setItem(this.rootModelNode.value);
       },
       _createEditDialog: function(){
        this._editDialog = new dijit.Dialog({
          title: "Edit Property",
          execute: dojo.hitch(this, "_updateItem"),
          preload: true
        });
        this._editDialog.placeAt(dojo.body());
        this._editDialog.startup();


        // handle for dialog content
        var pane = dojo.doc.createElement('div');


        // label for property
        var labelProp = dojo.doc.createElement('label');
        dojo.attr(labelProp, "for", "property");
        dojo.style(labelProp, "fontWeight", "bold");
        dojo.attr(labelProp, "innerHTML", "Property:")
        pane.appendChild(labelProp);


        // property name field
        var propName = new dijit.form.ValidationTextBox({
         name: "property",
         value: "",
         required: true,
         disabled: true
        }).placeAt(pane);


        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // radio button for "value"
        var value = new dijit.form.RadioButton({
         name: "itemType",
         value: "value",
         onClick: dojo.hitch(this, function(){this._enableFields("value");})
        }).placeAt(pane);


        // label for value
        var labelVal = dojo.doc.createElement('label');
        dojo.attr(labelVal, "for", "value");
        dojo.attr(labelVal, "innerHTML", "Value (JSON):")
        pane.appendChild(labelVal);


         // container for value fields
        var valueDiv = dojo.doc.createElement("div");
        dojo.addClass(valueDiv, "value");


        // textarea
        var textarea = new dijit.form.Textarea({
         name: "jsonVal"
        }).placeAt(valueDiv);
        pane.appendChild(valueDiv);


        // radio button for "reference"
        var reference = new dijit.form.RadioButton({
         name: "itemType",
         value: "reference",
         onClick: dojo.hitch(this, function(){this._enableFields("reference");})
        }).placeAt(pane);


        // label for reference
        var labelRef = dojo.doc.createElement('label');
        dojo.attr(labelRef, "for", "_reference");
        dojo.attr(labelRef, "innerHTML", "Reference (ID):")
        pane.appendChild(labelRef);
        pane.appendChild(dojo.doc.createElement("br"));


        // container for reference fields
        var refDiv = dojo.doc.createElement("div");
        dojo.addClass(refDiv, "reference");


        if(this.useSelect){
         // filteringselect
         // TODO: see if there is a way to sort the items in this list
         var refSelect = new dijit.form.FilteringSelect({
          name: "_reference",
          store: this.store,
          searchAttr: this.refSelectSearchAttr || this.store.getIdentityAttributes()[0],
          required: false,
          value: null,  // need to file a ticket about the fetch that happens when declared with value: null
          pageSize: 10
         }).placeAt(refDiv);
        }else{
         var refTextbox = new dijit.form.ValidationTextBox({
          name: "_reference",
          value: "",
          promptMessage: "Enter the ID of the item to reference",
          isValid: dojo.hitch(this, function(isFocused){
           // don't validate while it's focused
           return true;//isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
          })
         }).placeAt(refDiv);
        }
        pane.appendChild(refDiv);
        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // buttons
        var buttons = document.createElement('div');
        buttons.setAttribute("dir", "rtl");
        var cancelButton = new dijit.form.Button({type: "reset", label: "Cancel"}).placeAt(buttons);
        cancelButton.onClick = dojo.hitch(this._editDialog, "onCancel");
        var okButton = new dijit.form.Button({type: "submit", label: "OK"}).placeAt(buttons);
        pane.appendChild(buttons);


        this._editDialog.attr("content", pane);
       },
       _enableFields: function(selection){
        // enables/disables fields based on whether the value in this._editDialog is a reference or a primitive value
        switch(selection){
         case "reference":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          break;
         case "value":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          break;
        }
       },
       _updateItem: function(vals){
        // a single "execute" function that handles adding and editing of values and references.
        var node, item, val, storeItemVal, editingItem = this._editDialog.attr("title") == "Edit Property";
        var editDialog = this._editDialog;
        var store = this.store;
        function setValue(){
         try{
          var itemVal, propPath = [];
          var prop = vals.property;
          if(editingItem){
           while(!store.isItem(item.parent, true)){
            node = node.getParent();
            propPath.push(item.property);
            item = node.item;
           }
           if(propPath.length == 0){
            // working with an item attribute already
            store.setValue(item.parent, item.property, val);
           }else{
            // need to walk back down the item property to the object
            storeItemVal = getValue(store, item.parent, item.property);
            if(storeItemVal instanceof Array){
             // create a copy for modification
             storeItemVal = storeItemVal.concat();
            }
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val; // this change is reflected in storeItemVal as well
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }else{
           // adding a property
           if(store.isItem(value, true)){
            // adding a top-level property to an item
            if(!store.isItemLoaded(value)){
             // fetch the value and see if it is an array
             store.loadItem({
              item: value,
              onItem: function(loadedItem){
               if(loadedItem instanceof Array){
                prop = loadedItem.length;
               }
               store.setValue(loadedItem, prop, val);
              }
             });
            }else{
             if(value instanceof Array){
              prop = value.length;
             }
             store.setValue(value, prop, val);
            }
           }else{
            // adding a property to a lower level in an item
            if(item.value instanceof Array){
             propPath.push(item.value.length);
            }else{
             propPath.push(vals.property);
            }
            while(!store.isItem(item.parent, true)){
             node = node.getParent();
             propPath.push(item.property);
             item = node.item;
            }
            storeItemVal = getValue(store, item.parent, item.property);
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val;
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }
         }catch(e){
          alert(e);
         }
        }


        if(editDialog.validate()){
         node = this.lastFocused;
         item = node.item;
         var value = item.value;
         // var property = null;
         if(item.addNew){
          // we are adding a property to the parent item
          // the real value of the parent is in the parent property of the lastFocused item
          // this.lastFocused.getParent().item.value may be a reference to an item
          value = node.item.parent;
          node = node.getParent();
          item = node.item;
         }
         val = null;
         switch(vals.itemType){
          case "reference":
           this.store.fetchItemByIdentity({identity:vals._reference,
            onItem:function(item){
             val = item;
             setValue();
            },
            onError:function(){
             alert("The id could not be found");
            }
           });
           break;
          case "value":
           var jsonVal = vals.jsonVal;
           val = dojo.fromJson(jsonVal);
           // ifit is a function we want to preserve the source (comments, et al)
           if(typeof val == 'function'){
            val.toString = function(){
             return jsonVal;
            }
           }
           setValue();
           break;
         }
        }else{
         // the form didn't validate - show it again.
         editDialog.show();
        }
       },
       _editProperty: function(){
        // this mixin stops us polluting the tree item with jsonVal etc.
        // FIXME: if a store identifies items by instanceof checks, this will fail
        var item = dojo.mixin({}, this.lastFocused.item);
        // create the dialog or reset it if it already exists
        if(!this._editDialog){
         this._createEditDialog();
        }else{
         this._editDialog.reset();
        }
        // not allowed to edit an item's id - so check for that and stop it.
        if(dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){
         alert("Cannot Edit an Identifier!");
        }else{
         this._editDialog.attr("title", "Edit Property");
         // make sure the property input is disabled
         dijit.getEnclosingWidget(dojo.query("input", this._editDialog.containerNode)[0]).attr("disabled", true);
         if(this.store.isItem(item.value, true)){
          // root node || Item reference
          if(item.parent){
           // Item reference
           item.itemType = "reference";
           this._enableFields(item.itemType);
           item._reference = this.store.getIdentity(item.value);
           this._editDialog.attr("value", item);
           this._editDialog.show();
          } // else root node
         }else{
          if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // item.value is an object but it's NOT an item from the store - no-op
           // only allow editing on a property not on the node that represents the object/array
          }else{
           // this is a primitive
           item.itemType = "value";
           this._enableFields(item.itemType);
           item.jsonVal = typeof item.value == 'function' ?
             // use the plain toString for functions, dojo.toJson doesn't support functions
             item.value.toString() :
              item.value instanceof Date ?
               // A json-ish form of a date:
               'new Date("' + item.value + '")' :
               dojo.toJson(item.value);
           this._editDialog.attr("value", item);
           this._editDialog.show();
          }
         }
        }
    • returns
      isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
    • summary
  • dojox.data.ItemExplorer._destroyProperty

    • type
      Function
    • source: [view]
        var node = this.lastFocused;
        var item = node.item;
        var propPath = [];
        // we have to walk up the tree to the item before we can know if we're working with the identifier
        while(!this.store.isItem(item.parent, true) || item.parent instanceof Array){
         node = node.getParent();
         propPath.push(item.property);
         item = node.item;
        }
        // this will prevent any part of the identifier from being changed
        if(dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){
         alert("Cannot Delete an Identifier!");
        }else{
         try{
          if(propPath.length > 0){
           // not deleting a top-level property of an item so get the top-level store item to change
           var itemVal, storeItemVal = getValue(this.store, item.parent, item.property);
           itemVal = storeItemVal;
           // walk back down the object if needed
           while(propPath.length > 1){
            itemVal = itemVal[propPath.pop()];
           }
           // delete the property
           if(dojo.isArray(itemVal)){
            // the value being deleted represents an array element
            itemVal.splice(propPath, 1);
           }else{
            // object property
            delete itemVal[propPath];
           }
           // save it back to the store
           this.store.setValue(item.parent, item.property, storeItemVal);
          }else{
           // deleting an item property
           this.store.unsetAttribute(item.parent, item.property);
          }
         }catch(e){
          alert(e);
         }
        }
    • summary
  • dojox.data.ItemExplorer._addProperty

    • type
      Function
    • source: [view]
      dojo.provide("dojox.data.ItemExplorer");
      dojo.require("dijit.Tree");
      dojo.require("dijit.Dialog");
      dojo.require("dijit.Menu");
      dojo.require("dijit.form.ValidationTextBox");
      dojo.require("dijit.form.Textarea");
      dojo.require("dijit.form.Button");
      dojo.require("dijit.form.CheckBox");
      dojo.require("dijit.form.FilteringSelect");


      (function(){
       var getValue = function(store, item, prop){
        var value = store.getValues(item, prop);
        if(value.length < 2){
         value = store.getValue(item, prop);
        }
        return value;
       }


      dojo.declare("dojox.data.ItemExplorer", dijit.Tree, {
       useSelect: false,
       refSelectSearchAttr: null,
       constructor: function(options){
        dojo.mixin(this, options);
        var self = this;
        var initialRootValue = {};
        var root = this.rootModelNode = {value:initialRootValue,id:"root"};


        this._modelNodeIdMap = {};
        this._modelNodePropMap = {};
        var nextId = 1;
        this.model = {
         getRoot: function(onItem){
          onItem(root);
         },
         mayHaveChildren: function(modelNode){
          return modelNode.value && typeof modelNode.value == 'object' && !(modelNode.value instanceof Date);
         },
         getChildren: function(parentModelNode, onComplete, onError){
          var keys, parent, item = parentModelNode.value;
          var children = [];
          if(item == initialRootValue){
           onComplete([]);
           return;
          }
          var isItem = self.store && self.store.isItem(item, true);
          if(isItem && !self.store.isItemLoaded(item)){
           // if it is not loaded, do so now.
           self.store.loadItem({
            item:item,
            onItem:function(loadedItem){
             item = loadedItem;
             enumerate();
            }
           });
          }else{
           enumerate();
          }
          function enumerate(){
           // once loaded, enumerate the keys
           if(isItem){
            // get the properties through the dojo data API
            keys = self.store.getAttributes(item);
            parent = item;
           }else if(item && typeof item == 'object'){
            parent = parentModelNode.value;
            keys = [];
            // also we want to be able to drill down into plain JS objects/arrays
            for(var i in item){
             if(item.hasOwnProperty(i) && i != '__id' && i != '__clientId'){
              keys.push(i);
             }
            }
           }
           if(keys){
            for(var key, k=0; key = keys[k++];){
             children.push({
              property:key,
              value: isItem ? getValue(self.store, item, key) : item[key],
              parent: parent});
            }
            children.push({addNew:true, parent: parent, parentNode : parentModelNode});
           }
           onComplete(children);
          }
         },
         getIdentity: function(modelNode){
          if(!modelNode.id){
           if(modelNode.addNew){
            modelNode.property = "--addNew";
           }
           modelNode.id = nextId++;
           if(self.store){
            if(self.store.isItem(modelNode.value)){
             var identity = self.store.getIdentity(modelNode.value);
             (self._modelNodeIdMap[identity] = self._modelNodeIdMap[identity] || []).push(modelNode);
            }
            if(modelNode.parent){
             identity = self.store.getIdentity(modelNode.parent) + '.' + modelNode.property;
             (self._modelNodePropMap[identity] = self._modelNodePropMap[identity] || []).push(modelNode);
            }
           }
          }
          return modelNode.id;
         },
         getLabel: function(modelNode){
          return modelNode === root ?
            "Object Properties" :
             modelNode.addNew ? (modelNode.parent instanceof Array ? "Add new value" : "Add new property") :
              modelNode.property + ": " +
               (modelNode.value instanceof Array ? "(" + modelNode.value.length + " elements)" : modelNode.value);
         },
         onChildrenChange: function(modelNode){
         },
         onChange: function(modelNode){
         }
        };
       },
       postCreate: function(){
        this.inherited(arguments);
        // handle the clicking on the "add new property item"
        dojo.connect(this, "onClick", function(modelNode, treeNode){
         this.lastFocused = treeNode;
         if(modelNode.addNew){
          //this.focusNode(treeNode.getParent());
          this._addProperty();
         }else{
          this._editProperty();
         }
        });
        var contextMenu = new dijit.Menu({
         targetNodeIds: [this.rootNode.domNode],
         id: "contextMenu"
         });
        dojo.connect(contextMenu, "_openMyself", this, function(e){
         var node = dijit.getEnclosingWidget(e.target);
         if(node){
          var item = node.item;
          if(this.store.isItem(item.value, true) && !item.parent){
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add"));
           });
           this.lastFocused = node;
           // TODO: Root Node - allow Edit when mutli-value editing is possible
          }else if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // an object that's not a Date - could be a store item
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Add") && (widget.label != "Delete"));
           });
           this.lastFocused = node;
           // TODO: Object - allow Edit when mutli-value editing is possible
          }else if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){ // id node
           this.focusNode(node);
           alert("Cannot modify an Identifier node.");
          }else if(item.addNew){
           this.focusNode(node);
          }else{
           dojo.forEach(contextMenu.getChildren(), function(widget){
            widget.attr("disabled", (widget.label != "Edit") && (widget.label != "Delete"));
           })
           // this won't focus the node but gives us a way to reference the node
           this.lastFocused = node;
          }
         }
        });
        contextMenu.addChild(new dijit.MenuItem({label: "Add", onClick: dojo.hitch(this, "_addProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Edit", onClick: dojo.hitch(this, "_editProperty")}));
        contextMenu.addChild(new dijit.MenuItem({label: "Delete", onClick: dojo.hitch(this, "_destroyProperty")}));
        contextMenu.startup();
       },
       store: null,
       setStore: function(store){
        this.store = store;
        var self = this;
        if(this._editDialog){
         this._editDialog.destroyRecursive();
         delete this._editDialog;
        }
        // i think we should just destroy this._editDialog and let _createEditDialog take care of everything
        // once it gets called again by either _editProperty or _addProperty - it will create everything again
        // using the new store. this way we don't need to keep track of what is in the dialog if we change it.
        /*if(this._editDialog && this.useSelect){
         dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
          dijit.getEnclosingWidget(node).attr("store", store);
         });
        }*/
        dojo.connect(store, "onSet", function(item, attribute, oldValue, newValue){
         var nodes, i, identity = self.store.getIdentity(item);
         nodes = self._modelNodeIdMap[identity];


         if(nodes &&
           (oldValue === undefined || newValue === undefined ||
           oldValue instanceof Array || newValue instanceof Array || typeof oldValue == 'object' || typeof newValue == 'object')){
          for(i = 0; i < nodes.length; i++){
           (function(node){
            self.model.getChildren(node, function(children){
             self.model.onChildrenChange(node, children);
            });
           })(nodes[i]);
          }
         }
         nodes = self._modelNodePropMap[identity + "." + attribute];


         if(nodes){
          for(i = 0; i < nodes.length; i++){
           nodes[i].value = newValue;
           self.model.onChange(nodes[i]);
          }
         }
        });
        this.rootNode.setChildItems([]);
       },
       setItem: function(item){
        // this is called to show a different item


        // reset the maps, for the root getIdentity is not called, so we pre-initialize it here
        (this._modelNodeIdMap = {})[this.store.getIdentity(item)] = [this.rootModelNode];
        this._modelNodePropMap = {};


        this.rootModelNode.value = item;
        var self = this;
        this.model.getChildren(this.rootModelNode, function(children){
         self.rootNode.setChildItems(children);
        });


       },
       refreshItem: function(){
        this.setItem(this.rootModelNode.value);
       },
       _createEditDialog: function(){
        this._editDialog = new dijit.Dialog({
          title: "Edit Property",
          execute: dojo.hitch(this, "_updateItem"),
          preload: true
        });
        this._editDialog.placeAt(dojo.body());
        this._editDialog.startup();


        // handle for dialog content
        var pane = dojo.doc.createElement('div');


        // label for property
        var labelProp = dojo.doc.createElement('label');
        dojo.attr(labelProp, "for", "property");
        dojo.style(labelProp, "fontWeight", "bold");
        dojo.attr(labelProp, "innerHTML", "Property:")
        pane.appendChild(labelProp);


        // property name field
        var propName = new dijit.form.ValidationTextBox({
         name: "property",
         value: "",
         required: true,
         disabled: true
        }).placeAt(pane);


        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // radio button for "value"
        var value = new dijit.form.RadioButton({
         name: "itemType",
         value: "value",
         onClick: dojo.hitch(this, function(){this._enableFields("value");})
        }).placeAt(pane);


        // label for value
        var labelVal = dojo.doc.createElement('label');
        dojo.attr(labelVal, "for", "value");
        dojo.attr(labelVal, "innerHTML", "Value (JSON):")
        pane.appendChild(labelVal);


         // container for value fields
        var valueDiv = dojo.doc.createElement("div");
        dojo.addClass(valueDiv, "value");


        // textarea
        var textarea = new dijit.form.Textarea({
         name: "jsonVal"
        }).placeAt(valueDiv);
        pane.appendChild(valueDiv);


        // radio button for "reference"
        var reference = new dijit.form.RadioButton({
         name: "itemType",
         value: "reference",
         onClick: dojo.hitch(this, function(){this._enableFields("reference");})
        }).placeAt(pane);


        // label for reference
        var labelRef = dojo.doc.createElement('label');
        dojo.attr(labelRef, "for", "_reference");
        dojo.attr(labelRef, "innerHTML", "Reference (ID):")
        pane.appendChild(labelRef);
        pane.appendChild(dojo.doc.createElement("br"));


        // container for reference fields
        var refDiv = dojo.doc.createElement("div");
        dojo.addClass(refDiv, "reference");


        if(this.useSelect){
         // filteringselect
         // TODO: see if there is a way to sort the items in this list
         var refSelect = new dijit.form.FilteringSelect({
          name: "_reference",
          store: this.store,
          searchAttr: this.refSelectSearchAttr || this.store.getIdentityAttributes()[0],
          required: false,
          value: null,  // need to file a ticket about the fetch that happens when declared with value: null
          pageSize: 10
         }).placeAt(refDiv);
        }else{
         var refTextbox = new dijit.form.ValidationTextBox({
          name: "_reference",
          value: "",
          promptMessage: "Enter the ID of the item to reference",
          isValid: dojo.hitch(this, function(isFocused){
           // don't validate while it's focused
           return true;//isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
          })
         }).placeAt(refDiv);
        }
        pane.appendChild(refDiv);
        pane.appendChild(dojo.doc.createElement("br"));
        pane.appendChild(dojo.doc.createElement("br"));


        // buttons
        var buttons = document.createElement('div');
        buttons.setAttribute("dir", "rtl");
        var cancelButton = new dijit.form.Button({type: "reset", label: "Cancel"}).placeAt(buttons);
        cancelButton.onClick = dojo.hitch(this._editDialog, "onCancel");
        var okButton = new dijit.form.Button({type: "submit", label: "OK"}).placeAt(buttons);
        pane.appendChild(buttons);


        this._editDialog.attr("content", pane);
       },
       _enableFields: function(selection){
        // enables/disables fields based on whether the value in this._editDialog is a reference or a primitive value
        switch(selection){
         case "reference":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          break;
         case "value":
          dojo.query(".value [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", false);
          });
          dojo.query(".reference [widgetId]", this._editDialog.containerNode).forEach(function(node){
           dijit.getEnclosingWidget(node).attr("disabled", true);
          });
          break;
        }
       },
       _updateItem: function(vals){
        // a single "execute" function that handles adding and editing of values and references.
        var node, item, val, storeItemVal, editingItem = this._editDialog.attr("title") == "Edit Property";
        var editDialog = this._editDialog;
        var store = this.store;
        function setValue(){
         try{
          var itemVal, propPath = [];
          var prop = vals.property;
          if(editingItem){
           while(!store.isItem(item.parent, true)){
            node = node.getParent();
            propPath.push(item.property);
            item = node.item;
           }
           if(propPath.length == 0){
            // working with an item attribute already
            store.setValue(item.parent, item.property, val);
           }else{
            // need to walk back down the item property to the object
            storeItemVal = getValue(store, item.parent, item.property);
            if(storeItemVal instanceof Array){
             // create a copy for modification
             storeItemVal = storeItemVal.concat();
            }
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val; // this change is reflected in storeItemVal as well
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }else{
           // adding a property
           if(store.isItem(value, true)){
            // adding a top-level property to an item
            if(!store.isItemLoaded(value)){
             // fetch the value and see if it is an array
             store.loadItem({
              item: value,
              onItem: function(loadedItem){
               if(loadedItem instanceof Array){
                prop = loadedItem.length;
               }
               store.setValue(loadedItem, prop, val);
              }
             });
            }else{
             if(value instanceof Array){
              prop = value.length;
             }
             store.setValue(value, prop, val);
            }
           }else{
            // adding a property to a lower level in an item
            if(item.value instanceof Array){
             propPath.push(item.value.length);
            }else{
             propPath.push(vals.property);
            }
            while(!store.isItem(item.parent, true)){
             node = node.getParent();
             propPath.push(item.property);
             item = node.item;
            }
            storeItemVal = getValue(store, item.parent, item.property);
            itemVal = storeItemVal;
            while(propPath.length > 1){
             itemVal = itemVal[propPath.pop()];
            }
            itemVal[propPath] = val;
            store.setValue(item.parent, item.property, storeItemVal);
           }
          }
         }catch(e){
          alert(e);
         }
        }


        if(editDialog.validate()){
         node = this.lastFocused;
         item = node.item;
         var value = item.value;
         // var property = null;
         if(item.addNew){
          // we are adding a property to the parent item
          // the real value of the parent is in the parent property of the lastFocused item
          // this.lastFocused.getParent().item.value may be a reference to an item
          value = node.item.parent;
          node = node.getParent();
          item = node.item;
         }
         val = null;
         switch(vals.itemType){
          case "reference":
           this.store.fetchItemByIdentity({identity:vals._reference,
            onItem:function(item){
             val = item;
             setValue();
            },
            onError:function(){
             alert("The id could not be found");
            }
           });
           break;
          case "value":
           var jsonVal = vals.jsonVal;
           val = dojo.fromJson(jsonVal);
           // ifit is a function we want to preserve the source (comments, et al)
           if(typeof val == 'function'){
            val.toString = function(){
             return jsonVal;
            }
           }
           setValue();
           break;
         }
        }else{
         // the form didn't validate - show it again.
         editDialog.show();
        }
       },
       _editProperty: function(){
        // this mixin stops us polluting the tree item with jsonVal etc.
        // FIXME: if a store identifies items by instanceof checks, this will fail
        var item = dojo.mixin({}, this.lastFocused.item);
        // create the dialog or reset it if it already exists
        if(!this._editDialog){
         this._createEditDialog();
        }else{
         this._editDialog.reset();
        }
        // not allowed to edit an item's id - so check for that and stop it.
        if(dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){
         alert("Cannot Edit an Identifier!");
        }else{
         this._editDialog.attr("title", "Edit Property");
         // make sure the property input is disabled
         dijit.getEnclosingWidget(dojo.query("input", this._editDialog.containerNode)[0]).attr("disabled", true);
         if(this.store.isItem(item.value, true)){
          // root node || Item reference
          if(item.parent){
           // Item reference
           item.itemType = "reference";
           this._enableFields(item.itemType);
           item._reference = this.store.getIdentity(item.value);
           this._editDialog.attr("value", item);
           this._editDialog.show();
          } // else root node
         }else{
          if(item.value && typeof item.value == 'object' && !(item.value instanceof Date)){
           // item.value is an object but it's NOT an item from the store - no-op
           // only allow editing on a property not on the node that represents the object/array
          }else{
           // this is a primitive
           item.itemType = "value";
           this._enableFields(item.itemType);
           item.jsonVal = typeof item.value == 'function' ?
             // use the plain toString for functions, dojo.toJson doesn't support functions
             item.value.toString() :
              item.value instanceof Date ?
               // A json-ish form of a date:
               'new Date("' + item.value + '")' :
               dojo.toJson(item.value);
           this._editDialog.attr("value", item);
           this._editDialog.show();
          }
         }
        }
       },
       _destroyProperty: function(){
        var node = this.lastFocused;
        var item = node.item;
        var propPath = [];
        // we have to walk up the tree to the item before we can know if we're working with the identifier
        while(!this.store.isItem(item.parent, true) || item.parent instanceof Array){
         node = node.getParent();
         propPath.push(item.property);
         item = node.item;
        }
        // this will prevent any part of the identifier from being changed
        if(dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){
         alert("Cannot Delete an Identifier!");
        }else{
         try{
          if(propPath.length > 0){
           // not deleting a top-level property of an item so get the top-level store item to change
           var itemVal, storeItemVal = getValue(this.store, item.parent, item.property);
           itemVal = storeItemVal;
           // walk back down the object if needed
           while(propPath.length > 1){
            itemVal = itemVal[propPath.pop()];
           }
           // delete the property
           if(dojo.isArray(itemVal)){
            // the value being deleted represents an array element
            itemVal.splice(propPath, 1);
           }else{
            // object property
            delete itemVal[propPath];
           }
           // save it back to the store
           this.store.setValue(item.parent, item.property, storeItemVal);
          }else{
           // deleting an item property
           this.store.unsetAttribute(item.parent, item.property);
          }
         }catch(e){
          alert(e);
         }
        }
       },
       _addProperty: function(){
        // item is what we are adding a property to
        var item = this.lastFocused.item;
        // value is the real value of the item - not a reference to a store item
        var value = item.value;
        var showDialog = dojo.hitch(this, function(){
         var property = null;
         if(!this._editDialog){
          this._createEditDialog();
         }else{
          this._editDialog.reset();
         }
         // are we adding another item to an array?
         if(value instanceof Array){
          // preset the property to the next index in the array and disable the property field
          property = value.length;
          dijit.getEnclosingWidget(dojo.query("input", this._editDialog.containerNode)[0]).attr("disabled", true);
         }else{
          // enable the property TextBox
          dijit.getEnclosingWidget(dojo.query("input", this._editDialog.containerNode)[0]).attr("disabled", false);
         }
         this._editDialog.attr("title", "Add Property");
         // default to a value type
         this._enableFields("value");
         this._editDialog.attr("value", {itemType: "value", property: property});
         this._editDialog.show();
        });


        if(item.addNew){
         // we are adding a property to the parent item
         item = this.lastFocused.getParent().item;
         // the real value of the parent is in the parent property of the lastFocused item
         // this.lastFocused.getParent().item.value may be a reference to an item
         value = this.lastFocused.item.parent;
        }
        if(item.property && dojo.indexOf(this.store.getIdentityAttributes(), item.property) >= 0){
         alert("Cannot add properties to an ID node!");
        }else{
         // ifthe value is an item then we need to get the item's value
         if(this.store.isItem(value, true) && !this.store.isItemLoaded(value)){
          // fetch the value and see if it is an array
          this.store.loadItem({
           item: value,
           onItem: function(loadedItem){
            value = loadedItem;
            showDialog();
           }
          });
         }else{
          showDialog();
         }
      //
        }
    • returns
      isFocused || this.store.getItemByIdentity(this._editDialog.attr("value")._reference);
    • summary
  • dojox.data.ItemExplorer.lastFocused

    • summary
  • dojox.data.ItemExplorer._modelNodeIdMap

    • summary
  • dojox.data.ItemExplorer._modelNodePropMap

    • summary
  • dojox.data.ItemExplorer.rootModelNode.value

    • summary
  • dojox.data.ItemExplorer._editDialog

    • summary
  • dojox.data.ItemExplorer.rootModelNode

    • summary
  • dojox.data.ItemExplorer.model

    • summary
  • dojox.data

    • type
      Object
    • summary
  • dojox

    • type
      Object
    • summary