1 if(!dojo._hasResource["dojox.data.FlickrRestStore"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojox.data.FlickrRestStore"] = true;
3 dojo.provide("dojox.data.FlickrRestStore");
5 dojo.require("dojox.data.FlickrStore");
7 dojo.declare("dojox.data.FlickrRestStore",
8 dojox.data.FlickrStore, {
9 constructor: function(/*Object*/args){
11 // Initializer for the FlickrRestStore store.
13 // The FlickrRestStore is a Datastore interface to one of the basic services
14 // of the Flickr service, the public photo feed. This does not provide
15 // access to all the services of Flickr.
16 // This store cannot do * and ? filtering as the flickr service
17 // provides no interface for wildcards.
18 if(args && args.label){
20 this.label = args.label;
23 this._apikey = args.apikey;
27 this._prevRequests = {};
29 this._prevRequestRanges = [];
30 this._maxPhotosPerUser = {};
31 this._id = dojox.data.FlickrRestStore.prototype._id++;
35 // A unique identifier for this store.
38 // _requestCount: Integer
39 // A counter for the number of requests made. This is used to define
40 // the callback function that Flickr will use.
43 // _flickrRestUrl: String
44 // The URL to the Flickr REST services.
45 _flickrRestUrl: "http://www.flickr.com/services/rest/",
48 // The users API key to be used when accessing Flickr REST services.
52 // A key used to mark an data store item as belonging to this store.
56 // An Array of all previously downloaded picture info.
59 // _prevRequests: Object
60 // A HashMap used to record the signature of a request to prevent duplicate
61 // request being made.
65 // A HashMap used to record the handlers registered for a single remote request. Multiple
66 // requests may be made for the same information before the first request has finished.
67 // Each element of this Object is an array of handlers to call back when the request finishes.
68 // This prevents multiple requests being made for the same information.
71 // _sortAttributes: Object
72 // A quick lookup of valid attribute names in a sort query.
76 "interestingness": true
79 _fetchItems: function(request, fetchHandler, errorHandler){
80 // summary: Fetch flickr items that match to a query
84 // A function to call for fetched items
86 // A function to call on error
89 request.query = query = {};
91 dojo.mixin(query, request.query);
95 var secondaryKey = [];
97 //Generate a unique function to be called back
98 var callbackFn = "FlickrRestStoreCallback_" + this._id + "_" + (++this._requestCount);
99 //Build up the content to send the request for.
102 method: "flickr.photos.search",
103 api_key: this._apikey,
104 extras: "owner_name,date_upload,date_taken",
105 jsoncallback: callbackFn
110 content.user_id = request.query.userid;
111 primaryKey.push("userid"+request.query.userid);
115 content.api_key = request.query.apikey;
116 secondaryKey.push("api"+request.query.apikey);
118 throw Error("dojox.data.FlickrRestStore: An API key must be specified.");
120 request._curCount = request.count;
122 content.page = request.query.page;
123 secondaryKey.push("page" + content.page);
124 }else if(typeof(request.start) != "undefined" && request.start != null) {
128 var diff = request.start % request.count;
129 var start = request.start, count = request.count;
130 //If the count does not divide cleanly into the start number,
131 //more work has to be done to figure out the best page to request
133 if(start < count / 2) {
134 //If the first record requested is less than half the amount requested,
135 //then request from 0 to the count record
136 count = start + count;
139 var divLimit = 20, div = 2;
140 for(var i = divLimit; i > 0; i--) {
141 if(start % i == 0 && (start/i) >= count){
148 request._realStart = request.start;
149 request._realCount = request.count;
150 request._curStart = start;
151 request._curCount = count;
153 request._realStart = request._realCount = null;
154 request._curStart = request.start;
155 request._curCount = request.count;
158 content.page = (start / count) + 1;
159 secondaryKey.push("page" + content.page);
161 if(request._curCount){
162 content.per_page = request._curCount;
163 secondaryKey.push("count" + request._curCount);
167 content.lang = request.query.lang;
168 primaryKey.push("lang" + request.lang);
170 var url = this._flickrRestUrl;
173 content.method = "flickr.photosets.getPhotos";
174 content.photoset_id = request.query.set;
175 primaryKey.push("set" + request.query.set);
179 if(query.tags instanceof Array){
180 content.tags = query.tags.join(",");
182 content.tags=query.tags;
184 primaryKey.push("tags" + content.tags);
186 if(query["tag_mode"] && (query.tag_mode.toLowerCase() == "any"
187 || query.tag_mode.toLowerCase() == "all")){
188 content.tag_mode = query.tag_mode;
192 content.text=query.text;
193 primaryKey.push("text:"+query.text);
196 //The store only supports a single sort attribute, even though the
197 //Read API technically allows multiple sort attributes
198 if(query.sort && query.sort.length > 0){
199 //The default sort attribute is 'date-posted'
200 if(!query.sort[0].attribute){
201 query.sort[0].attribute = "date-posted";
204 //If the sort attribute is valid, check if it is ascending or
206 if(this._sortAttributes[query.sort[0].attribute]) {
207 if(query.sort[0].descending){
208 content.sort = query.sort[0].attribute + "-desc";
210 content.sort = query.sort[0].attribute + "-asc";
214 //The default sort in the Dojo Data API is ascending.
215 content.sort = "date-posted-asc";
217 primaryKey.push("sort:"+content.sort);
219 //Generate a unique key for this request, so the store can
220 //detect duplicate requests.
221 primaryKey = primaryKey.join(".");
222 secondaryKey = secondaryKey.length > 0 ? "." + secondaryKey.join(".") : "";
223 var requestKey = primaryKey + secondaryKey;
225 //Make a copy of the request, in case the source object is modified
226 //before the request completes
229 count: request._curCount,
230 start: request._curStart,
231 _realCount: request._realCount,
232 _realStart: request._realStart,
233 onBegin: request.onBegin,
234 onComplete: request.onComplete,
235 onItem: request.onItem
240 fetchHandler: fetchHandler,
241 errorHandler: errorHandler
244 //If the request has already been made, but not yet completed,
245 //then add the callback handler to the list of handlers
246 //for this request, and finish.
247 if(this._handlers[requestKey]){
248 this._handlers[requestKey].push(thisHandler);
252 this._handlers[requestKey] = [thisHandler];
254 //Linking this up to Flickr is a PAIN!
258 url: this._flickrRestUrl,
263 var doHandle = function(processedData, data, handler){
264 var onBegin = handler.request.onBegin;
265 handler.request.onBegin = null;
267 var req = handler.request;
269 if(typeof(req._realStart) != undefined && req._realStart != null) {
270 req.start = req._realStart;
271 req.count = req._realCount;
272 req._realStart = req._realCount = null;
275 //If the request contains an onBegin method, the total number
276 //of photos must be calculated.
278 if(data && typeof(data.photos.perpage) != "undefined" && typeof(data.photos.pages) != "undefined"){
279 if(data.photos.perpage * data.photos.pages <= handler.request.start + handler.request.count){
280 //If the final page of results has been received, it is possible to
281 //know exactly how many photos there are
282 maxPhotos = handler.request.start + data.photos.photo.length;
284 //If the final page of results has not yet been received,
285 //it is not possible to tell exactly how many photos exist, so
286 //return the number of pages multiplied by the number of photos per page.
287 maxPhotos = data.photos.perpage * data.photos.pages;
289 self._maxPhotosPerUser[primaryKey] = maxPhotos;
290 onBegin(maxPhotos, handler.request);
291 } else if(self._maxPhotosPerUser[primaryKey]) {
292 onBegin(self._maxPhotosPerUser[primaryKey], handler.request);
295 //Call whatever functions the caller has defined on the request object, except for onBegin
296 handler.fetchHandler(processedData, handler.request);
298 //Replace the onBegin function, if it existed.
299 handler.request.onBegin = onBegin;
303 //Define a callback for the script that iterates through a list of
304 //handlers for this piece of data. Multiple requests can come into
305 //the store for the same data.
306 var myHandler = function(data){
307 //The handler should not be called more than once, so disconnect it.
308 //if(handle !== null){ dojo.disconnect(handle); }
309 if(data.stat != "ok"){
310 errorHandler(null, request);
311 }else{ //Process the items...
312 var handlers = self._handlers[requestKey];
314 console.log("FlickrRestStore: no handlers for data", data);
318 self._handlers[requestKey] = null;
319 self._prevRequests[requestKey] = data;
321 //Process the data once.
322 var processedData = self._processFlickrData(data, request, primaryKey);
323 if(!self._prevRequestRanges[primaryKey]) {
324 self._prevRequestRanges[primaryKey] = [];
326 self._prevRequestRanges[primaryKey].push({
327 start: request.start,
328 end: request.start + data.photos.photo.length
331 //Iterate through the array of handlers, calling each one.
332 for(var i = 0; i < handlers.length; i++ ){
333 doHandle(processedData, data, handlers[i]);
338 var data = this._prevRequests[requestKey];
340 //If the data was previously retrieved, there is no need to fetch it again.
342 this._handlers[requestKey] = null;
343 doHandle(this._cache[primaryKey], data, thisHandler);
345 } else if(this._checkPrevRanges(primaryKey, request.start, request.count)) {
346 //If this range of data has already been retrieved, reuse it.
347 this._handlers[requestKey] = null;
348 doHandle(this._cache[primaryKey], null, thisHandler);
352 dojo.global[callbackFn] = function(data){
354 //Clean up the function, it should never be called again
355 dojo.global[callbackFn] = null;
358 var deferred = dojo.io.script.get(getArgs);
360 //We only set up the errback, because the callback isn't ever really used because we have
361 //to link to the jsonFlickrFeed function....
362 deferred.addErrback(function(error){
363 dojo.disconnect(handle);
364 errorHandler(error, request);
368 getAttributes: function(item){
370 // See dojo.data.api.Read.getAttributes()
371 return ["title", "author", "imageUrl", "imageUrlSmall",
372 "imageUrlMedium", "imageUrlThumb", "link",
373 "dateTaken", "datePublished"];
376 getValues: function(item, attribute){
378 // See dojo.data.api.Read.getValue()
379 this._assertIsItem(item);
380 this._assertIsAttribute(attribute);
381 if(attribute === "title"){
382 return [this._unescapeHtml(item.title)]; // String
383 }else if(attribute === "author"){
384 return [item.ownername]; // String
385 }else if(attribute === "imageUrlSmall"){
386 return [item.media.s]; // String
387 }else if(attribute === "imageUrl"){
388 return [item.media.l]; // String
389 }else if(attribute === "imageUrlMedium"){
390 return [item.media.m]; // String
391 }else if(attribute === "imageUrlThumb"){
392 return [item.media.t]; // String
393 }else if(attribute === "link"){
394 return ["http://www.flickr.com/photos/" + item.owner + "/" + item.id]; // String
395 }else if(attribute === "dateTaken"){
396 return item.datetaken;
397 }else if(attribute === "datePublished"){
398 return item.datepublished;
404 _processFlickrData: function(/* Object */data, /* Object */request, /* String */ cacheKey){
405 // summary: Processes the raw data from Flickr and updates the internal cache.
407 // Data returned from Flickr
409 // The original dojo.data.Request object passed in by the user.
411 //If the data contains an 'item' object, it has not come from the REST services,
412 //so process it using the FlickrStore.
414 return dojox.data.FlickrStore.prototype._processFlickrData.apply(this,arguments);
417 var template = ["http://farm", null, ".static.flickr.com/", null, "/", null, "_", null];
420 if(data.stat == "ok" && data.photos && data.photos.photo){
421 items = data.photos.photo;
423 //Add on the store ref so that isItem can work.
424 for(var i = 0; i < items.length; i++){
426 item[this._storeRef] = this;
428 template[1] = item.farm;
429 template[3] = item.server;
430 template[5] = item.id;
431 template[7] = item.secret;
433 var base = template.join("");
442 var start = request.start ? request.start : 0;
443 var arr = this._cache[cacheKey];
445 this._cache[cacheKey] = arr = [];
447 for(var count = 0; count < items.length; count++){
448 arr[count + start] = items[count];
454 _checkPrevRanges: function(primaryKey, start, count) {
455 var end = start + count;
456 var arr = this._prevRequestRanges[primaryKey];
460 for(var i = 0; i< arr.length; i++) {
461 if(start >= arr[i].start &&