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; } }); }