mirror of
				https://git.tt-rss.org/fox/tt-rss.git
				synced 2025-10-31 11:51:17 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			545 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| define("dojo/data/ObjectStore", ["../_base/lang", "../Evented", "../_base/declare", "../_base/Deferred", "../_base/array", 
 | |
| 	"../_base/connect", "../regexp"
 | |
| ], function(lang, Evented, declare, Deferred, 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);
 | |
| 					}
 | |
| 				});
 | |
| 				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 if(typeof old != "undefined"){
 | |
| 						result = this.objectStore.remove(this.getIdentity(old));
 | |
| 					}
 | |
| 					savingObjects.push(dirty);
 | |
| 					dirtyObjects.splice(i--,1);
 | |
| 					Deferred.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(){
 | |
| 			// 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			
 | |
| 		}
 | |
| 
 | |
| 	}
 | |
| );
 | |
| });
 |