| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 | // wrapped by build appdefine("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();			}//		}	}});})();});
 |