UNPKG

dojo

Version:

Dojo core is a powerful, lightweight library that makes common tasks quicker and easier. Animate elements, manipulate the DOM, and query with easy CSS syntax, all without sacrificing performance.

547 lines (496 loc) 15.8 kB
define(["../_base/lang", "../Evented", "../_base/declare", "../_base/Deferred", "../promise/all", "../_base/array", "../_base/connect", "../regexp" ], function(lang, Evented, declare, Deferred, all, array, connect, regexp){ // module: // dojo/data/ObjectStore function convertRegex(character){ return character == '*' ? '.*' : character == '?' ? '.' : character; } return declare("dojo.data.ObjectStore", [Evented],{ // summary: // A Dojo Data implementation that wraps Dojo object stores for backwards // compatibility. objectStore: null, constructor: function(options){ // 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 this._dirtyObjects = []; if(options.labelAttribute){ // accept the old labelAttribute to make it easier to switch from old data stores options.labelProperty = options.labelAttribute; } lang.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 // The item to check // attribute: String // The attribute to check return attribute in item; }, containsValue: function(item, attribute, value){ // summary: // Checks to see if 'item' has 'value' at 'attribute' // item: Object // The item to check // attribute: String // The attribute to check // value: Anything // The value to look for return array.indexOf(this.getValues(item,attribute),value) > -1; }, isItem: function(item){ // summary: // Checks to see if the argument is an item // item: Object // The item to check // 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 // The item to check 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). // args: Object // See dojo/data/api/Read.fetch() // 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"){ Deferred.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){ // summary: // See dojo/data/api/Read.close() return request && request.abort && request.abort(); }, fetch: function(args){ // summary: // See dojo/data/api/Read.fetch() args = lang.delegate(args, args && args.queryOptions); 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 = lang.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("^" + regexp.escapeString(required, "*?\\").replace(/\\.|\*|\?/g, convertRegex) + "$", args.ignoreCase ? "mi" : "m"); query[i].toString = (function(original){ return function(){ return original; }; })(required); } } } var results = this.objectStore.query(query, args); Deferred.when(results.total, function(totalCount){ Deferred.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(); } }; if(results.observe){ if(this.observing){ // if we were previously observing, cancel the last time to avoid multiple notifications. Just the best we can do for the impedance mismatch between APIs this.observing.cancel(); } this.observing = results.observe(function(object, removedFrom, insertedInto){ if(array.indexOf(self._dirtyObjects, object) == -1){ if(removedFrom == -1){ self.onNew(object); } else if(insertedInto == -1){ self.onDelete(object); } else{ for(var i in object){ if(i != self.objectStore.idProperty){ self.onSet(object, i, null, object[i]); } } } } }, true); } this.onFetch(results); 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(/* dojo/data/api/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(/* dojo/data/api/Item */ item){ // summary: // See dojo/data/api/Read.getLabelAttributes() return [this.labelProperty]; //array }, //Identity API Support getIdentity: function(item){ // summary: // returns the identity of the given item // See dojo/data/api/Read.getIdentity() return this.objectStore.getIdentity ? this.objectStore.getIdentity(item) : 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 // See dojo/data/api/Read.getIdentityAttributes() 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; Deferred.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. // data: Object // See dojo/data/api/Write.newItem() 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' // See dojo/data/api/Write.setValue() 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. // See dojo/data/api/Write.setValues() if(!lang.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' // See dojo/data/api/Write.unsetAttribute() this.changing(item); var old = item[attribute]; delete item[attribute]; this.onSet(item,attribute,old,undefined); }, 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: Object // Indicates that the given object is changing and should be marked as // dirty for the next save // _deleting: [private] Boolean 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: // - 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.onError: // Called when an error occurs in the commit // // - kwArgs.onComplete: // Called when an the save/commit is completed kwArgs = kwArgs || {}; var result, actions = []; var savingObjects = []; var self = this; var dirtyObjects = this._dirtyObjects; var left = dirtyObjects.length;// this is how many changes are remaining to be received from the server try{ connect.connect(kwArgs,"onError",function(){ if(kwArgs.revertOnError !== false){ var postCommitDirtyObjects = dirtyObjects; dirtyObjects = savingObjects; self.revert(); // revert if there was an error self._dirtyObjects = postCommitDirtyObjects; } else{ self._dirtyObjects = dirtyObjects.concat(savingObjects); } }); var transaction; if(this.objectStore.transaction){ 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}); actions.push(result); } else if(typeof old != "undefined"){ result = this.objectStore.remove(this.getIdentity(old)); actions.push(result); } savingObjects.push(dirty); dirtyObjects.splice(i--,1); } all(actions).then(function(value){ if(kwArgs.onComplete){ kwArgs.onComplete.call(kwArgs.scope, value); } }, function(error){ if(kwArgs.onError) { kwArgs.onError.call(kwArgs.scope, error); } }); if(transaction){ transaction.commit(); } }catch(e){ kwArgs.onError.call(kwArgs.scope, value); } }, revert: function(){ // 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 // item: Object // The item to check if(!item){ return !!this._dirtyObjects.length; } return item.__isDirty; }, // Notification Support onSet: function(){ // summary: // See dojo/data/api/Notification.onSet() }, onNew: function(){ // summary: // See dojo/data/api/Notification.onNew() }, onDelete: function(){ // summary: // See dojo/data/api/Notification.onDelete() }, // an extra to get result sets onFetch: function(results){ // summary: // Called when a fetch occurs } } ); });