123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 |
- // 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();
- }
- //
- }
- }
- });
- })();
- });
|