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/FlickrRestStore.js | 471 ++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) create mode 100644 includes/js/dojox/data/FlickrRestStore.js (limited to 'includes/js/dojox/data/FlickrRestStore.js') diff --git a/includes/js/dojox/data/FlickrRestStore.js b/includes/js/dojox/data/FlickrRestStore.js new file mode 100644 index 0000000..466d6df --- /dev/null +++ b/includes/js/dojox/data/FlickrRestStore.js @@ -0,0 +1,471 @@ +if(!dojo._hasResource["dojox.data.FlickrRestStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojox.data.FlickrRestStore"] = true; +dojo.provide("dojox.data.FlickrRestStore"); + +dojo.require("dojox.data.FlickrStore"); + +dojo.declare("dojox.data.FlickrRestStore", + dojox.data.FlickrStore, { + constructor: function(/*Object*/args){ + // summary: + // Initializer for the FlickrRestStore store. + // description: + // The FlickrRestStore is a Datastore interface to one of the basic services + // of the Flickr service, the public photo feed. This does not provide + // access to all the services of Flickr. + // This store cannot do * and ? filtering as the flickr service + // provides no interface for wildcards. + if(args && args.label){ + if(args.label) { + this.label = args.label; + } + if(args.apikey) { + this._apikey = args.apikey; + } + } + this._cache = []; + this._prevRequests = {}; + this._handlers = {}; + this._prevRequestRanges = []; + this._maxPhotosPerUser = {}; + this._id = dojox.data.FlickrRestStore.prototype._id++; + }, + + // _id: Integer + // A unique identifier for this store. + _id: 0, + + // _requestCount: Integer + // A counter for the number of requests made. This is used to define + // the callback function that Flickr will use. + _requestCount: 0, + + // _flickrRestUrl: String + // The URL to the Flickr REST services. + _flickrRestUrl: "http://www.flickr.com/services/rest/", + + // _apikey: String + // The users API key to be used when accessing Flickr REST services. + _apikey: null, + + // _storeRef: String + // A key used to mark an data store item as belonging to this store. + _storeRef: "_S", + + // _cache: Array + // An Array of all previously downloaded picture info. + _cache: null, + + // _prevRequests: Object + // A HashMap used to record the signature of a request to prevent duplicate + // request being made. + _prevRequests: null, + + // _handlers: Object + // A HashMap used to record the handlers registered for a single remote request. Multiple + // requests may be made for the same information before the first request has finished. + // Each element of this Object is an array of handlers to call back when the request finishes. + // This prevents multiple requests being made for the same information. + _handlers: null, + + // _sortAttributes: Object + // A quick lookup of valid attribute names in a sort query. + _sortAttributes: { + "date-posted": true, + "date-taken": true, + "interestingness": true + }, + + _fetchItems: function(request, fetchHandler, errorHandler){ + // summary: Fetch flickr items that match to a query + // request: + // A request object + // fetchHandler: + // A function to call for fetched items + // errorHandler: + // A function to call on error + var query = {}; + if(!request.query){ + request.query = query = {}; + } else { + dojo.mixin(query, request.query); + } + + var primaryKey = []; + var secondaryKey = []; + + //Generate a unique function to be called back + var callbackFn = "FlickrRestStoreCallback_" + this._id + "_" + (++this._requestCount); + //Build up the content to send the request for. + var content = { + format: "json", + method: "flickr.photos.search", + api_key: this._apikey, + extras: "owner_name,date_upload,date_taken", + jsoncallback: callbackFn + }; + var isRest = false; + if(query.userid){ + isRest = true; + content.user_id = request.query.userid; + primaryKey.push("userid"+request.query.userid); + } + if(query.apikey){ + isRest = true; + content.api_key = request.query.apikey; + secondaryKey.push("api"+request.query.apikey); + } else{ + throw Error("dojox.data.FlickrRestStore: An API key must be specified."); + } + request._curCount = request.count; + if(query.page){ + content.page = request.query.page; + secondaryKey.push("page" + content.page); + }else if(typeof(request.start) != "undefined" && request.start != null) { + if(!request.count){ + request.count = 20; + } + var diff = request.start % request.count; + var start = request.start, count = request.count; + //If the count does not divide cleanly into the start number, + //more work has to be done to figure out the best page to request + if(diff != 0) { + if(start < count / 2) { + //If the first record requested is less than half the amount requested, + //then request from 0 to the count record + count = start + count; + start = 0; + } else { + var divLimit = 20, div = 2; + for(var i = divLimit; i > 0; i--) { + if(start % i == 0 && (start/i) >= count){ + div = i; + break; + } + } + count = start/div; + } + request._realStart = request.start; + request._realCount = request.count; + request._curStart = start; + request._curCount = count; + } else { + request._realStart = request._realCount = null; + request._curStart = request.start; + request._curCount = request.count; + } + + content.page = (start / count) + 1; + secondaryKey.push("page" + content.page); + } + if(request._curCount){ + content.per_page = request._curCount; + secondaryKey.push("count" + request._curCount); + } + + if(query.lang){ + content.lang = request.query.lang; + primaryKey.push("lang" + request.lang); + } + var url = this._flickrRestUrl; + + if(query.setid){ + content.method = "flickr.photosets.getPhotos"; + content.photoset_id = request.query.set; + primaryKey.push("set" + request.query.set); + } + + if(query.tags){ + if(query.tags instanceof Array){ + content.tags = query.tags.join(","); + } else { + content.tags=query.tags; + } + primaryKey.push("tags" + content.tags); + + if(query["tag_mode"] && (query.tag_mode.toLowerCase() == "any" + || query.tag_mode.toLowerCase() == "all")){ + content.tag_mode = query.tag_mode; + } + } + if(query.text){ + content.text=query.text; + primaryKey.push("text:"+query.text); + } + + //The store only supports a single sort attribute, even though the + //Read API technically allows multiple sort attributes + if(query.sort && query.sort.length > 0){ + //The default sort attribute is 'date-posted' + if(!query.sort[0].attribute){ + query.sort[0].attribute = "date-posted"; + } + + //If the sort attribute is valid, check if it is ascending or + //descending. + if(this._sortAttributes[query.sort[0].attribute]) { + if(query.sort[0].descending){ + content.sort = query.sort[0].attribute + "-desc"; + } else { + content.sort = query.sort[0].attribute + "-asc"; + } + } + } else { + //The default sort in the Dojo Data API is ascending. + content.sort = "date-posted-asc"; + } + primaryKey.push("sort:"+content.sort); + + //Generate a unique key for this request, so the store can + //detect duplicate requests. + primaryKey = primaryKey.join("."); + secondaryKey = secondaryKey.length > 0 ? "." + secondaryKey.join(".") : ""; + var requestKey = primaryKey + secondaryKey; + + //Make a copy of the request, in case the source object is modified + //before the request completes + request = { + query: query, + count: request._curCount, + start: request._curStart, + _realCount: request._realCount, + _realStart: request._realStart, + onBegin: request.onBegin, + onComplete: request.onComplete, + onItem: request.onItem + }; + + var thisHandler = { + request: request, + fetchHandler: fetchHandler, + errorHandler: errorHandler + }; + + //If the request has already been made, but not yet completed, + //then add the callback handler to the list of handlers + //for this request, and finish. + if(this._handlers[requestKey]){ + this._handlers[requestKey].push(thisHandler); + return; + } + + this._handlers[requestKey] = [thisHandler]; + + //Linking this up to Flickr is a PAIN! + var self = this; + var handle = null; + var getArgs = { + url: this._flickrRestUrl, + preventCache: true, + content: content + }; + + var doHandle = function(processedData, data, handler){ + var onBegin = handler.request.onBegin; + handler.request.onBegin = null; + var maxPhotos; + var req = handler.request; + + if(typeof(req._realStart) != undefined && req._realStart != null) { + req.start = req._realStart; + req.count = req._realCount; + req._realStart = req._realCount = null; + } + + //If the request contains an onBegin method, the total number + //of photos must be calculated. + if(onBegin){ + if(data && typeof(data.photos.perpage) != "undefined" && typeof(data.photos.pages) != "undefined"){ + if(data.photos.perpage * data.photos.pages <= handler.request.start + handler.request.count){ + //If the final page of results has been received, it is possible to + //know exactly how many photos there are + maxPhotos = handler.request.start + data.photos.photo.length; + }else{ + //If the final page of results has not yet been received, + //it is not possible to tell exactly how many photos exist, so + //return the number of pages multiplied by the number of photos per page. + maxPhotos = data.photos.perpage * data.photos.pages; + } + self._maxPhotosPerUser[primaryKey] = maxPhotos; + onBegin(maxPhotos, handler.request); + } else if(self._maxPhotosPerUser[primaryKey]) { + onBegin(self._maxPhotosPerUser[primaryKey], handler.request); + } + } + //Call whatever functions the caller has defined on the request object, except for onBegin + handler.fetchHandler(processedData, handler.request); + if(onBegin){ + //Replace the onBegin function, if it existed. + handler.request.onBegin = onBegin; + } + }; + + //Define a callback for the script that iterates through a list of + //handlers for this piece of data. Multiple requests can come into + //the store for the same data. + var myHandler = function(data){ + //The handler should not be called more than once, so disconnect it. + //if(handle !== null){ dojo.disconnect(handle); } + if(data.stat != "ok"){ + errorHandler(null, request); + }else{ //Process the items... + var handlers = self._handlers[requestKey]; + if(!handlers){ + console.log("FlickrRestStore: no handlers for data", data); + return; + } + + self._handlers[requestKey] = null; + self._prevRequests[requestKey] = data; + + //Process the data once. + var processedData = self._processFlickrData(data, request, primaryKey); + if(!self._prevRequestRanges[primaryKey]) { + self._prevRequestRanges[primaryKey] = []; + } + self._prevRequestRanges[primaryKey].push({ + start: request.start, + end: request.start + data.photos.photo.length + }); + + //Iterate through the array of handlers, calling each one. + for(var i = 0; i < handlers.length; i++ ){ + doHandle(processedData, data, handlers[i]); + } + } + }; + + var data = this._prevRequests[requestKey]; + + //If the data was previously retrieved, there is no need to fetch it again. + if(data){ + this._handlers[requestKey] = null; + doHandle(this._cache[primaryKey], data, thisHandler); + return; + } else if(this._checkPrevRanges(primaryKey, request.start, request.count)) { + //If this range of data has already been retrieved, reuse it. + this._handlers[requestKey] = null; + doHandle(this._cache[primaryKey], null, thisHandler); + return; + } + + dojo.global[callbackFn] = function(data){ + myHandler(data); + //Clean up the function, it should never be called again + dojo.global[callbackFn] = null; + }; + + var deferred = dojo.io.script.get(getArgs); + + //We only set up the errback, because the callback isn't ever really used because we have + //to link to the jsonFlickrFeed function.... + deferred.addErrback(function(error){ + dojo.disconnect(handle); + errorHandler(error, request); + }); + }, + + getAttributes: function(item){ + // summary: + // See dojo.data.api.Read.getAttributes() + return ["title", "author", "imageUrl", "imageUrlSmall", + "imageUrlMedium", "imageUrlThumb", "link", + "dateTaken", "datePublished"]; + }, + + getValues: function(item, attribute){ + // summary: + // See dojo.data.api.Read.getValue() + this._assertIsItem(item); + this._assertIsAttribute(attribute); + if(attribute === "title"){ + return [this._unescapeHtml(item.title)]; // String + }else if(attribute === "author"){ + return [item.ownername]; // String + }else if(attribute === "imageUrlSmall"){ + return [item.media.s]; // String + }else if(attribute === "imageUrl"){ + return [item.media.l]; // String + }else if(attribute === "imageUrlMedium"){ + return [item.media.m]; // String + }else if(attribute === "imageUrlThumb"){ + return [item.media.t]; // String + }else if(attribute === "link"){ + return ["http://www.flickr.com/photos/" + item.owner + "/" + item.id]; // String + }else if(attribute === "dateTaken"){ + return item.datetaken; + }else if(attribute === "datePublished"){ + return item.datepublished; + } + + return undefined; + }, + + _processFlickrData: function(/* Object */data, /* Object */request, /* String */ cacheKey){ + // summary: Processes the raw data from Flickr and updates the internal cache. + // data: + // Data returned from Flickr + // request: + // The original dojo.data.Request object passed in by the user. + + //If the data contains an 'item' object, it has not come from the REST services, + //so process it using the FlickrStore. + if(data.items){ + return dojox.data.FlickrStore.prototype._processFlickrData.apply(this,arguments); + } + + var template = ["http://farm", null, ".static.flickr.com/", null, "/", null, "_", null]; + + var items = []; + if(data.stat == "ok" && data.photos && data.photos.photo){ + items = data.photos.photo; + + //Add on the store ref so that isItem can work. + for(var i = 0; i < items.length; i++){ + var item = items[i]; + item[this._storeRef] = this; + + template[1] = item.farm; + template[3] = item.server; + template[5] = item.id; + template[7] = item.secret; + + var base = template.join(""); + item.media = { + s: base + "_s.jpg", + m: base + "_m.jpg", + l: base + ".jpg", + t: base + "_t.jpg" + }; + } + } + var start = request.start ? request.start : 0; + var arr = this._cache[cacheKey]; + if(!arr) { + this._cache[cacheKey] = arr = []; + } + for(var count = 0; count < items.length; count++){ + arr[count + start] = items[count]; + } + + return arr; // Array + }, + + _checkPrevRanges: function(primaryKey, start, count) { + var end = start + count; + var arr = this._prevRequestRanges[primaryKey]; + if(!arr) { + return false; + } + for(var i = 0; i< arr.length; i++) { + if(start >= arr[i].start && + end <= arr[i].end) { + return true; + } + } + return false; + } +}); + + +} -- cgit v1.2.3-54-g00ecf