diff options
Diffstat (limited to 'lib/dojo/data/ObjectStore.js')
-rw-r--r-- | lib/dojo/data/ObjectStore.js | 487 |
1 files changed, 487 insertions, 0 deletions
diff --git a/lib/dojo/data/ObjectStore.js b/lib/dojo/data/ObjectStore.js new file mode 100644 index 000000000..8ccc4f853 --- /dev/null +++ b/lib/dojo/data/ObjectStore.js @@ -0,0 +1,487 @@ +/* + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + + +if(!dojo._hasResource["dojo.data.ObjectStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.data.ObjectStore"] = true; +dojo.provide("dojo.data.ObjectStore"); +dojo.require("dojo.regexp"); + + + +dojo.declare("dojo.data.ObjectStore", null,{ + objectStore: null, + constructor: function(options){ + // summary: + // A Dojo Data implementation that wraps Dojo object stores for backwards + // compatibility. + // options: + // The configuration information to pass into the data store. + // options.objectStore: + // The object store to use as the source provider for this data store + dojo.mixin(this, options); + }, + labelProperty: "label", + + getValue: function(/*Object*/ item, /*String*/property, /*value?*/defaultValue){ + // summary: + // Gets the value of an item's 'property' + // + // item: + // The item to get the value from + // property: + // property to look up value for + // defaultValue: + // the default value + + return typeof item.get === "function" ? item.get(property) : + property in item ? + item[property] : defaultValue; + }, + getValues: function(item, property){ + // summary: + // Gets the value of an item's 'property' and returns + // it. If this value is an array it is just returned, + // if not, the value is added to an array and that is returned. + // + // item: /* object */ + // property: /* string */ + // property to look up value for + + var val = this.getValue(item,property); + return val instanceof Array ? val : val === undefined ? [] : [val]; + }, + + getAttributes: function(item){ + // summary: + // Gets the available attributes of an item's 'property' and returns + // it as an array. + // + // item: /* object */ + + var res = []; + for(var i in item){ + if(item.hasOwnProperty(i) && !(i.charAt(0) == '_' && i.charAt(1) == '_')){ + res.push(i); + } + } + return res; + }, + + hasAttribute: function(item,attribute){ + // summary: + // Checks to see if item has attribute + // + // item: /* object */ + // attribute: /* string */ + return attribute in item; + }, + + containsValue: function(item, attribute, value){ + // summary: + // Checks to see if 'item' has 'value' at 'attribute' + // + // item: /* object */ + // attribute: /* string */ + // value: /* anything */ + return dojo.indexOf(this.getValues(item,attribute),value) > -1; + }, + + + isItem: function(item){ + // summary: + // Checks to see if the argument is an item + // + // item: /* object */ + // attribute: /* string */ + + // we have no way of determining if it belongs, we just have object returned from + // service queries + return (typeof item == 'object') && item && !(item instanceof Date); + }, + + isItemLoaded: function(item){ + // summary: + // Checks to see if the item is loaded. + // + // item: /* object */ + + return item && typeof item.load !== "function"; + }, + + loadItem: function(args){ + // summary: + // Loads an item and calls the callback handler. Note, that this will call the callback + // handler even if the item is loaded. Consequently, you can use loadItem to ensure + // that an item is loaded is situations when the item may or may not be loaded yet. + // If you access a value directly through property access, you can use this to load + // a lazy value as well (doesn't need to be an item). + // + // example: + // store.loadItem({ + // item: item, // this item may or may not be loaded + // onItem: function(item){ + // // do something with the item + // } + // }); + + var item; + if(typeof args.item.load === "function"){ + dojo.when(args.item.load(), function(result){ + item = result; // in synchronous mode this can allow loadItem to return the value + var func = result instanceof Error ? args.onError : args.onItem; + if(func){ + func.call(args.scope, result); + } + }); + }else if(args.onItem){ + // even if it is already loaded, we will use call the callback, this makes it easier to + // use when it is not known if the item is loaded (you can always safely call loadItem). + args.onItem.call(args.scope, args.item); + } + return item; + }, + close: function(request){ + return request && request.abort && request.abort(); + }, + fetch: function(args){ + // summary: + // See dojo.data.api.Read.fetch + // + + args = args || {}; + var self = this; + var scope = args.scope || self; + var query = args.query; + if(typeof query == "object"){ // can be null, but that is ignore by for-in + query = dojo.delegate(query); // don't modify the original + for(var i in query){ + // find any strings and convert them to regular expressions for wildcard support + var required = query[i]; + if(typeof required == "string"){ + query[i] = RegExp("^" + dojo.regexp.escapeString(required, "*?").replace(/\*/g, '.*').replace(/\?/g, '.') + "$", args.queryOptions && args.queryOptions.ignoreCase ? "mi" : "m"); + query[i].toString = (function(original){ + return function(){ + return original; + } + })(required); + } + } + } + + var results = this.objectStore.query(query, args); + dojo.when(results.total, function(totalCount){ + dojo.when(results, function(results){ + if(args.onBegin){ + args.onBegin.call(scope, totalCount || results.length, args); + } + if(args.onItem){ + for(var i=0; i<results.length;i++){ + args.onItem.call(scope, results[i], args); + } + } + if(args.onComplete){ + args.onComplete.call(scope, args.onItem ? null : results, args); + } + return results; + }, errorHandler); + }, errorHandler); + function errorHandler(error){ + if(args.onError){ + args.onError.call(scope, error, args); + } + } + args.abort = function(){ + // abort the request + if(results.cancel){ + results.cancel(); + } + }; + args.store = this; + return args; + }, + getFeatures: function(){ + // summary: + // return the store feature set + + return { + "dojo.data.api.Read": !!this.objectStore.get, + "dojo.data.api.Identity": true, + "dojo.data.api.Write": !!this.objectStore.put, + "dojo.data.api.Notification": true + }; + }, + + getLabel: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabel() + if(this.isItem(item)){ + return this.getValue(item,this.labelProperty); //String + } + return undefined; //undefined + }, + + getLabelAttributes: function(/* item */ item){ + // summary: + // See dojo.data.api.Read.getLabelAttributes() + return [this.labelProperty]; //array + }, + + //Identity API Support + + + getIdentity: function(item){ + return item.getId ? item.getId() : item[this.objectStore.idProperty || "id"]; + }, + + getIdentityAttributes: function(item){ + // summary: + // returns the attributes which are used to make up the + // identity of an item. Basically returns this.objectStore.idProperty + + return [this.objectStore.idProperty]; + }, + + fetchItemByIdentity: function(args){ + // summary: + // fetch an item by its identity, by looking in our index of what we have loaded + var item; + dojo.when(this.objectStore.get(args.identity), + function(result){ + item = result; + args.onItem.call(args.scope, result); + }, + function(error){ + args.onError.call(args.scope, error); + } + ); + return item; + }, + + newItem: function(data, parentInfo){ + // 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. + if(parentInfo){ + // get the previous value or any empty array + var values = this.getValue(parentInfo.parent,parentInfo.attribute,[]); + // set the new value + values = values.concat([data]); + data.__parent = values; + this.setValue(parentInfo.parent, parentInfo.attribute, values); + } + this._dirtyObjects.push({object:data, save: true}); + this.onNew(data); + return data; + }, + deleteItem: function(item){ + // summary: + // deletes item and any references to that item from the store. + // + // item: + // item to delete + // + + // If the desire is to delete only one reference, unsetAttribute or + // setValue is the way to go. + this.changing(item, true); + + this.onDelete(item); + }, + setValue: function(item, attribute, value){ + // summary: + // sets 'attribute' on 'item' to 'value' + + var old = item[attribute]; + this.changing(item); + item[attribute]=value; + this.onSet(item,attribute,old,value); + }, + setValues: function(item, attribute, values){ + // summary: + // sets 'attribute' on 'item' to 'value' value + // must be an array. + + + if(!dojo.isArray(values)){ + throw new Error("setValues expects to be passed an Array object as its value"); + } + this.setValue(item,attribute,values); + }, + + unsetAttribute: function(item, attribute){ + // summary: + // unsets 'attribute' on 'item' + + this.changing(item); + var old = item[attribute]; + delete item[attribute]; + this.onSet(item,attribute,old,undefined); + }, + + _dirtyObjects: [], + + changing: function(object,_deleting){ + // summary: + // adds an object to the list of dirty objects. This object + // contains a reference to the object itself as well as a + // cloned and trimmed version of old object for use with + // revert. + object.__isDirty = true; + //if an object is already in the list of dirty objects, don't add it again + //or it will overwrite the premodification data set. + for(var i=0; i<this._dirtyObjects.length; i++){ + var dirty = this._dirtyObjects[i]; + if(object==dirty.object){ + if(_deleting){ + // we are deleting, no object is an indicator of deletiong + dirty.object = false; + if(!this._saveNotNeeded){ + dirty.save = true; + } + } + return; + } + } + var old = object instanceof Array ? [] : {}; + for(i in object){ + if(object.hasOwnProperty(i)){ + old[i] = object[i]; + } + } + this._dirtyObjects.push({object: !_deleting && object, old: old, save: !this._saveNotNeeded}); + }, + + save: function(kwArgs){ + // summary: + // Saves the dirty data using object store provider. See dojo.data.api.Write for API. + // + // kwArgs.global: + // This will cause the save to commit the dirty data for all + // ObjectStores as a single transaction. + // + // kwArgs.revertOnError + // This will cause the changes to be reverted if there is an + // error on the save. By default a revert is executed unless + // a value of false is provide for this parameter. + + kwArgs = kwArgs || {}; + var result, actions = []; + var alreadyRecorded = {}; + var savingObjects = []; + var self; + var dirtyObjects = this._dirtyObjects; + var left = dirtyObjects.length;// this is how many changes are remaining to be received from the server + try{ + dojo.connect(kwArgs,"onError",function(){ + if(kwArgs.revertOnError !== false){ + var postCommitDirtyObjects = dirtyObjects; + dirtyObjects = savingObjects; + var numDirty = 0; // make sure this does't do anything if it is called again + jr.revert(); // revert if there was an error + self._dirtyObjects = postCommitDirtyObjects; + } + else{ + self._dirtyObjects = dirtyObject.concat(savingObjects); + } + }); + if(this.objectStore.transaction){ + var transaction = this.objectStore.transaction(); + } + for(var i = 0; i < dirtyObjects.length; i++){ + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + delete object.__isDirty; + if(object){ + result = this.objectStore.put(object, {overwrite: !!old}); + } + else{ + result = this.objectStore.remove(this.getIdentity(old)); + } + savingObjects.push(dirty); + dirtyObjects.splice(i--,1); + dojo.when(result, function(value){ + if(!(--left)){ + if(kwArgs.onComplete){ + kwArgs.onComplete.call(kwArgs.scope, actions); + } + } + },function(value){ + + // on an error we want to revert, first we want to separate any changes that were made since the commit + left = -1; // first make sure that success isn't called + kwArgs.onError.call(kwArgs.scope, value); + }); + + } + if(transaction){ + transaction.commit(); + } + }catch(e){ + kwArgs.onError.call(kwArgs.scope, value); + } + + + }, + + revert: function(kwArgs){ + // summary + // returns any modified data to its original state prior to a save(); + // + var dirtyObjects = this._dirtyObjects; + for(var i = dirtyObjects.length; i > 0;){ + i--; + var dirty = dirtyObjects[i]; + var object = dirty.object; + var old = dirty.old; + if(object && old){ + // changed + for(var j in old){ + if(old.hasOwnProperty(j) && object[j] !== old[j]){ + this.onSet(object, j, object[j], old[j]); + object[j] = old[j]; + } + } + for(j in object){ + if(!old.hasOwnProperty(j)){ + this.onSet(object, j, object[j]); + delete object[j]; + } + } + }else if(!old){ + // was an addition, remove it + this.onDelete(object); + }else{ + // was a deletion, we will add it back + this.onNew(old); + } + delete (object || old).__isDirty; + dirtyObjects.splice(i, 1); + } + + + }, + isDirty: function(item){ + // summary + // returns true if the item is marked as dirty or true if there are any dirty items + if(!item){ + return !!this._dirtyObjects.length; + } + return item.__isDirty; + }, + //Notifcation Support + + onSet: function(){}, + onNew: function(){}, + onDelete: function(){} + } +); + +} |