From e44a7e37b6c7b5961adaffc62b9042b8d442938e Mon Sep 17 00:00:00 2001 From: mensonge Date: Thu, 13 Nov 2008 09:49:11 +0000 Subject: New feature: basic Ajax suggestion for tags and implementation of Dojo toolkit git-svn-id: https://semanticscuttle.svn.sourceforge.net/svnroot/semanticscuttle/trunk@151 b3834d28-1941-0410-a4f8-b48e95affb8f --- includes/js/dojox/data/jsonPathStore.js | 1191 +++++++++++++++++++++++++++++++ 1 file changed, 1191 insertions(+) create mode 100644 includes/js/dojox/data/jsonPathStore.js (limited to 'includes/js/dojox/data/jsonPathStore.js') diff --git a/includes/js/dojox/data/jsonPathStore.js b/includes/js/dojox/data/jsonPathStore.js new file mode 100644 index 0000000..01e4e23 --- /dev/null +++ b/includes/js/dojox/data/jsonPathStore.js @@ -0,0 +1,1191 @@ +if(!dojo._hasResource["dojox.data.jsonPathStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.data.jsonPathStore"] = true; +dojo.provide("dojox.data.jsonPathStore"); +dojo.require("dojox.jsonPath"); +dojo.require("dojo.date"); +dojo.require("dojo.date.locale"); +dojo.require("dojo.date.stamp"); + +dojox.data.ASYNC_MODE = 0; +dojox.data.SYNC_MODE = 1; + +dojo.declare("dojox.data.jsonPathStore", + null, + { + mode: dojox.data.ASYNC_MODE, + metaLabel: "_meta", + hideMetaAttributes: false, + autoIdPrefix: "_auto_", + autoIdentity: true, + idAttribute: "_id", + indexOnLoad: true, + labelAttribute: "", + url: "", + _replaceRegex: /\'\]/gi, + + constructor: function(options){ + //summary: + // jsonPathStore constructor, instantiate a new jsonPathStore + // + // Takes a single optional parameter in the form of a Javascript object + // containing one or more of the following properties. + // + // data: /*JSON String*/ || /* Javascript Object */, + // JSON String or Javascript object this store will control + // JSON is converted into an object, and an object passed to + // the store will be used directly. If no data and no url + // is provide, an empty object, {}, will be used as the initial + // store. + // + // url: /* string url */ + // Load data from this url in JSON format and use the Object + // created from the data as the data source. + // + // indexOnLoad: /* boolean */ + // Defaults to true, but this may change in the near future. + // Parse the data object and set individual objects up as + // appropriate. This will add meta data and assign + // id's to objects that dont' have them as defined by the + // idAttribute option. Disabling this option will keep this + // parsing from happening until a query is performed at which + // time only the top level of an item has meta info stored. + // This might work in some situations, but you will almost + // always want to indexOnLoad or use another option which + // will create an index. In the future we will support a + // generated index that maps by jsonPath allowing the + // server to take some of this load for larger data sets. + // + // idAttribute: /* string */ + // Defaults to '_id'. The name of the attribute that holds an objects id. + // This can be a preexisting id provided by the server. + // If an ID isn't already provided when an object + // is fetched or added to the store, the autoIdentity system + // will generate an id for it and add it to the index. There + // are utility routines for exporting data from the store + // that can clean any generated IDs before exporting and leave + // preexisting id's in tact. + // + // metaLabel: /* string */ + // Defaults to '_meta' overrides the attribute name that is used by the store + // for attaching meta information to an object while + // in the store's control. Defaults to '_meta'. + // + // hideMetaAttributes: /* boolean */ + // Defaults to False. When enabled, calls to getAttributes() will not + // include the meta attribute. + // + // autoIdPrefix: /*string*/ + // Defaults to "_auto_". This string is used as the prefix to any + // objects which have a generated id. A numeric index is appended + // to this string to complete the ID + // + // mode: dojox.data.ASYNC_MODE || dojox.data.SYNC_MODE + // Defaults to ASYNC_MODE. This option sets the default mode for this store. + // Sync calls return their data immediately from the calling function + // instead of calling the callback functions. Functions such as + // fetchItemByIdentity() and fetch() both accept a string parameter in addtion + // to the normal keywordArgs parameter. When passed this option, SYNC_MODE will + // automatically be used even when the default mode of the system is ASYNC_MODE. + // A normal request to fetch or fetchItemByIdentity (with kwArgs object) can also + // include a mode property to override this setting for that one request. + + //setup a byId alias to the api call + this.byId=this.fetchItemByIdentity; + + if (options){ + dojo.mixin(this,options); + } + + this._dirtyItems=[]; + this._autoId=0; + this._referenceId=0; + this._references={}; + this._fetchQueue=[]; + this.index={}; + + //regex to identify when we're travelling down metaObject (which we don't want to do) + var expr="("+this.metaLabel+"\'\])"; + this.metaRegex = new RegExp(expr); + + + //no data or url, start with an empty object for a store + if (!this.data && !this.url){ + this.setData({}); + } + + //we have data, but no url, set the store as the data + if (this.data && !this.url){ + this.setData(this.data); + + //remove the original refernce, we're now using _data from here on out + delete this.data; + } + + //given a url, load json data from as the store + if (this.url){ + dojo.xhrGet({ + url: options.url, + handleAs: "json", + load: dojo.hitch(this, "setData"), + sync: this.mode + }); + } + }, + + _loadData: function(data){ + // summary: + // load data into the store. Index it if appropriate. + if (this._data){ + delete this._data; + } + + if (dojo.isString(data)){ + this._data = dojo.fromJson(data); + }else{ + this._data = data; + } + + if (this.indexOnLoad){ + this.buildIndex(); + } + + this._updateMeta(this._data, {path: "$"}); + + this.onLoadData(this._data); + }, + + onLoadData: function(data){ + // summary + // Called after data has been loaded in the store. + // If any requests happened while the startup is happening + // then process them now. + + while (this._fetchQueue.length>0){ + var req = this._fetchQueue.shift(); + this.fetch(req); + } + + }, + + setData: function(data){ + // summary: + // set the stores' data to the supplied object and then + // load and/or setup that data with the required meta info + this._loadData(data); + }, + + buildIndex: function(path, item){ + //summary: + // parse the object structure, and turn any objects into + // jsonPathStore items. Basically this just does a recursive + // series of fetches which itself already examines any items + // as they are retrieved and setups up the required meta information. + // + // path: /* string */ + // jsonPath Query for the starting point of this index construction. + + if (!this.idAttribute){ + throw new Error("buildIndex requires idAttribute for the store"); + } + + item = item || this._data; + var origPath = path; + path = path||"$"; + path += "[*]"; + var data = this.fetch({query: path,mode: dojox.data.SYNC_MODE}); + for(var i=0; i= args.count)) { continue; } + + var item = results[i]["value"]; + var path = results[i]["path"]; + if (!dojo.isObject(item)){continue;} + if(this.metaRegex.exec(path)){continue;} + + //this automatically records the objects path + this._updateMeta(item,{path: results[i].path}); + + //if autoIdentity and no id, generate one and add it to the item + if(this.autoIdentity && !item[this.idAttribute]){ + var newId = this.autoIdPrefix + this._autoId++; + item[this.idAttribute]=newId; + item[this.metaLabel]["autoId"]=true; + } + + //add item to the item index if appropriate + if(item[this.idAttribute]){this.index[item[this.idAttribute]]=item} + count++; + tmp.push(item); + } + results = tmp; + var scope = args.scope || dojo.global; + + if ("sort" in args){ + console.log("TODO::add support for sorting in the fetch"); + } + + if (args.mode==dojox.data.SYNC_MODE){ + return results; + }; + + if (args.onBegin){ + args["onBegin"].call(scope, results.length, args); + } + + if (args.onItem){ + for (var i=0; i0) { label+=" ";} + label += item[this.labelAttribute[i]]; + } + return label; + }else{ + return item[this.labelAttribute]; + } + } + return item.toString(); + }, + + getLabelAttributes: function(item){ + // summary: + // returns an array of attributes that are used to create the label of an item + item = this._correctReference(item); + return dojo.isArray(this.labelAttribute) ? this.labelAttribute : [this.labelAttribute]; + }, + + sort: function(a,b){ + console.log("TODO::implement default sort algo"); + }, + + //Identity API Support + + getIdentity: function(item){ + // summary + // returns the identity of an item or throws + // a not found error. + + if (this.isItem(item)){ + return item[this.idAttribute]; + } + throw new Error("Id not found for item"); + }, + + getIdentityAttributes: function(item){ + // summary: + // returns the attributes which are used to make up the + // identity of an item. Basically returns this.idAttribute + + return [this.idAttribute]; + }, + + fetchItemByIdentity: function(args){ + // summary: + // fetch an item by its identity. This store also provides + // a much more finger friendly alias, 'byId' which does the + // same thing as this function. If provided a string + // this call will be treated as a SYNC request and will + // return the identified item immediatly. Alternatively it + // takes a object as a set of keywordArgs: + // + // identity: /* string */ + // the id of the item you want to retrieve + // + // mode: dojox.data.SYNC_MODE || dojox.data.ASYNC_MODE + // overrides the default store fetch mode + // + // onItem: /* function */ + // Result call back. Passed the fetched item. + // + // onError: /* function */ + // error callback. + var id; + if (dojo.isString(args)){ + id = args; + args = {identity: id, mode: dojox.data.SYNC_MODE} + }else{ + if (args){ + id = args["identity"]; + } + if (!args.mode){args.mode = this.mode} + } + + if (this.index && (this.index[id] || this.index["identity"])){ + + if (args.mode==dojox.data.SYNC_MODE){ + return this.index[id]; + } + + if (args.onItem){ + args["onItem"].call(args.scope || dojo.global, this.index[id], args); + } + + return args; + }else{ + if (args.mode==dojox.data.SYNC_MODE){ + return false; + } + } + + + if(args.onError){ + args["onItem"].call(args.scope || dojo.global, new Error("Item Not Found: " + id), args); + } + + return args; + }, + + //Write API Support + newItem: function(data, options){ + // summary: + // adds a new item to the store at the specified point. + // Takes two parameters, data, and options. + // + // data: /* object */ + // The data to be added in as an item. This could be a + // new javascript object, or it could be an item that + // already exists in the store. If it already exists in the + // store, then this will be added as a reference. + // + // options: /* object */ + // + // item: /* item */ + // reference to an existing store item + // + // attribute: /* string */ + // attribute to add the item at. If this is + // not provided, the item's id will be used as the + // attribute name. If specified attribute is an + // array, the new item will be push()d on to the + // end of it. + // oldValue: /* old value of item[attribute] + // newValue: new value item[attribute] + + var meta={}; + + //default parent to the store root; + var pInfo ={item:this._data}; + + if (options){ + if (options.parent){ + options.item = options.parent; + } + + dojo.mixin(pInfo, options); + } + + if (this.idAttribute && !data[this.idAttribute]){ + if (this.requireId){throw new Error("requireId is enabled, new items must have an id defined to be added");} + if (this.autoIdentity){ + var newId = this.autoIdPrefix + this._autoId++; + data[this.idAttribute]=newId; + meta["autoId"]=true; + } + } + + if (!pInfo && !pInfo.attribute && !this.idAttribute && !data[this.idAttribute]){ + throw new Error("Adding a new item requires, at a minumum, either the pInfo information, including the pInfo.attribute, or an id on the item in the field identified by idAttribute"); + } + + //pInfo.parent = this._correctReference(pInfo.parent); + //if there is no parent info supplied, default to the store root + //and add to the pInfo.attribute or if that doestn' exist create an + //attribute with the same name as the new items ID + if(!pInfo.attribute){pInfo.attribute = data[this.idAttribute]} + + pInfo.oldValue = this._trimItem(pInfo.item[pInfo.attribute]); + if (dojo.isArray(pInfo.item[pInfo.attribute])){ + this._setDirty(pInfo.item); + pInfo.item[pInfo.attribute].push(data); + }else{ + this._setDirty(pInfo.item); + pInfo.item[pInfo.attribute]=data; + } + + pInfo.newValue = pInfo.item[pInfo.attribute]; + + //add this item to the index + if(data[this.idAttribute]){this.index[data[this.idAttribute]]=data} + + this._updateMeta(data, meta) + + //keep track of all references in the store so we can delete them as necessary + this._addReference(data, pInfo); + + //mark this new item as dirty + this._setDirty(data); + + //Notification API + this.onNew(data, pInfo); + + //returns the original item, now decorated with some meta info + return data; + }, + + _addReference: function(item, pInfo){ + // summary + // adds meta information to an item containing a reference id + // so that references can be deleted as necessary, when passed + // only a string, the string for parent info, it will only + // it will be treated as a string reference + + //console.log("_addReference: ", item, pInfo); + var rid = '_ref_' + this._referenceId++; + if (!item[this.metaLabel]["referenceIds"]){ + item[this.metaLabel]["referenceIds"]=[]; + } + + item[this.metaLabel]["referenceIds"].push(rid); + this._references[rid] = pInfo; + }, + + deleteItem: function(item){ + // summary + // deletes item and any references to that item from the store. + // If the desire is to delete only one reference, unsetAttribute or + // setValue is the way to go. + + item = this._correctReference(item); + console.log("Item: ", item); + if (this.isItem(item)){ + while(item[this.metaLabel]["referenceIds"].length>0){ + console.log("refs map: " , this._references); + console.log("item to delete: ", item); + var rid = item[this.metaLabel]["referenceIds"].pop(); + var pInfo = this._references[rid]; + + console.log("deleteItem(): ", pInfo, pInfo.parent); + parentItem = pInfo.parent; + var attribute = pInfo.attribute; + if(parentItem && parentItem[attribute] && !dojo.isArray(parentItem[attribute])){ + this._setDirty(parentItem); + this.unsetAttribute(parentItem, attribute); + delete parentItem[attribute]; + } + + if (dojo.isArray(parentItem[attribute])){ + console.log("Parent is array"); + var oldValue = this._trimItem(parentItem[attribute]); + var found=false; + for (var i=0; i 0){ + var item = this._dirtyItems.pop()["item"]; + var t = this._trimItem(item); + var d; + switch(kwArgs.format){ + case "json": + d = dojo.toJson(t); + break; + case "raw": + default: + d = t; + } + data.push(d); + this._markClean(item); + } + + this.onSave(data); + }, + + _markClean: function(item){ + // summary + // remove this meta information marking an item as "dirty" + + if (item && item[this.metaLabel] && item[this.metaLabel]["isDirty"]){ + delete item[this.metaLabel]["isDirty"]; + } + }, + + revert: function(){ + // summary + // returns any modified data to its original state prior to a save(); + + while (this._dirtyItems.length>0){ + var d = this._dirtyItems.pop(); + this._mixin(d.item, d.old); + } + this.onRevert(); + }, + + _mixin: function(target, data){ + // summary: + // specialized mixin that hooks up objects in the store where references are identified. + + if (dojo.isObject(data)){ + if (dojo.isArray(data)){ + while(target.length>0){target.pop();} + for (var i=0; i