diff options
Diffstat (limited to 'js/dojo/dojox/data/ItemExplorer.js')
| -rw-r--r-- | js/dojo/dojox/data/ItemExplorer.js | 631 |
1 files changed, 631 insertions, 0 deletions
diff --git a/js/dojo/dojox/data/ItemExplorer.js b/js/dojo/dojox/data/ItemExplorer.js new file mode 100644 index 0000000..fcc6343 --- /dev/null +++ b/js/dojo/dojox/data/ItemExplorer.js @@ -0,0 +1,631 @@ +//>>built +// wrapped by build app +define("dojox/data/ItemExplorer", ["dijit","dojo","dojox","dojo/require!dijit/Tree,dijit/Dialog,dijit/Menu,dijit/form/ValidationTextBox,dijit/form/Textarea,dijit/form/Button,dijit/form/RadioButton,dijit/form/FilteringSelect"], function(dijit,dojo,dojox){ +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.RadioButton"); +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(); + } +// + } + } +}); +})(); + + +}); |
