source: Dev/trunk/src/client/dojox/data/GoogleSearchStore.js @ 532

Last change on this file since 532 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 21.7 KB
Line 
1define(["dojo/_base/kernel", "dojo/_base/lang", "dojo/_base/declare", "dojo/_base/query",
2                "dojo/dom-construct","dojo/io/script"],
3  function(kernel, lang, declare, domQuery, domConstruct, scriptIO) {
4
5kernel.experimental("dojox.data.GoogleSearchStore");
6
7var SearchStore = declare("dojox.data.GoogleSearchStore",null,{
8        // summary:
9        //              A data store for retrieving search results from Google.
10        //              This data store acts as a base class for Google searches,
11        //              and has a number of child data stores that implement different
12        //              searches. This store defaults to searching the web, and is functionally
13        //              identical to the dojox.data.GoogleWebSearchStore object.
14        //              The following attributes are supported on each item:
15        //
16        //              - url - The URL for the item
17        //              - unescapedUrl - The URL for the item, with URL escaping. This is often more readable
18        //              - visibleUrl - The URL with no protocol specified.
19        //              - cacheUrl - The URL to the copy of the document cached by Google
20        //              - title - The page title in HTML format.
21        //              - titleNoFormatting - The page title in plain text
22        //              - content - A snippet of information about the page
23        //
24        //              The query accepts one parameter: text - The string to search for
25        constructor: function(/*Object*/args){
26                // summary:
27                //              Initializer for the GoogleSearchStore store.
28                // description:
29                //              The GoogleSearchStore is a Datastore interface to
30                //              the Google search service. The constructor accepts the following arguments:
31                //
32                //              - label - the label attribute to use. Defaults to titleNoFormatting
33                //              - key - The API key to use. This is optional
34                //              - lang - The language locale to use. Defaults to the browser locale
35
36                if(args){
37                        if(args.label){
38                                this.label = args.label;
39                        }
40                        if(args.key){
41                                this._key = args.key;
42                        }
43                        if(args.lang){
44                                this._lang = args.lang;
45                        }
46                        if("urlPreventCache" in args){
47                                this.urlPreventCache = args.urlPreventCache?true:false;
48                        }
49                }
50                this._id = dojox.data.GoogleSearchStore.prototype._id++;
51        },
52
53        // _id: Integer
54        //              A unique identifier for this store.
55        _id: 0,
56
57        // _requestCount: Integer
58        //              A counter for the number of requests made. This is used to define
59        //              the callback function that GoogleSearchStore will use.
60        _requestCount: 0,
61
62        // _googleUrl: String
63        //              The URL to Googles search web service.
64        _googleUrl: "http://ajax.googleapis.com/ajax/services/search/",
65
66        // _storeRef: String
67        //              The internal reference added to each item pointing at the store which owns it.
68        _storeRef: "_S",
69
70        // _attributes: Array
71        //              The list of attributes that this store supports
72        _attributes: [  "unescapedUrl", "url", "visibleUrl", "cacheUrl", "title",
73                        "titleNoFormatting", "content", "estimatedResultCount"],
74
75        // _aggregtedAttributes: Hash
76        //              Maps per-query aggregated attributes that this store supports to the result keys that they come from.
77        _aggregatedAttributes: {
78                estimatedResultCount: "cursor.estimatedResultCount"
79        },
80
81        // label: String
82        //              The default attribute which acts as a label for each item.
83        label: "titleNoFormatting",
84
85        // type: String
86        //              The type of search. Valid values are "web", "local", "video", "blogs", "news", "books", "images".
87        //              This should not be set directly. Instead use one of the child classes.
88        _type: "web",
89
90        // urlPreventCache: boolean
91        //              Sets whether or not to pass preventCache to dojo.io.script.
92        urlPreventCache: true,
93
94
95        // _queryAttrs: Hash
96        //              Maps query hash keys to Google query parameters.
97        _queryAttrs: {
98                text: 'q'
99        },
100
101        _assertIsItem: function(/* item */ item){
102                // summary:
103                //              This function tests whether the item passed in is indeed an item in the store.
104                // item:
105                //              The item to test for being contained by the store.
106                if(!this.isItem(item)){
107                        throw new Error("dojox.data.GoogleSearchStore: a function was passed an item argument that was not an item");
108                }
109        },
110
111        _assertIsAttribute: function(/* attribute-name-string */ attribute){
112                // summary:
113                //              This function tests whether the item passed in is indeed a valid 'attribute' like type for the store.
114                // attribute:
115                //              The attribute to test for being contained by the store.
116                if(typeof attribute !== "string"){
117                        throw new Error("dojox.data.GoogleSearchStore: a function was passed an attribute argument that was not an attribute name string");
118                }
119        },
120
121        getFeatures: function(){
122                // summary:
123                //              See dojo/data/api/Read.getFeatures()
124                return {
125                        'dojo.data.api.Read': true
126                };
127        },
128
129        getValue: function(item, attribute, defaultValue){
130                // summary:
131                //              See dojo/data/api/Read.getValue()
132                var values = this.getValues(item, attribute);
133                if(values && values.length > 0){
134                        return values[0];
135                }
136                return defaultValue;
137        },
138
139        getAttributes: function(item){
140                // summary:
141                //              See dojo/data/api/Read.getAttributes()
142                return this._attributes;
143        },
144
145        hasAttribute: function(item, attribute){
146                // summary:
147                //              See dojo/data/api/Read.hasAttributes()
148                if(this.getValue(item,attribute)){
149                        return true;
150                }
151                return false;
152        },
153
154        isItemLoaded: function(item){
155                // summary:
156                //              See dojo/data/api/Read.isItemLoaded()
157                return this.isItem(item);
158        },
159
160        loadItem: function(keywordArgs){
161                // summary:
162                //              See dojo/data/api/Read.loadItem()
163        },
164
165        getLabel: function(item){
166                // summary:
167                //              See dojo/data/api/Read.getLabel()
168                return this.getValue(item,this.label);
169        },
170
171        getLabelAttributes: function(item){
172                // summary:
173                //              See dojo/data/api/Read.getLabelAttributes()
174                return [this.label];
175        },
176
177        containsValue: function(item, attribute, value){
178                // summary:
179                //              See dojo/data/api/Read.containsValue()
180                var values = this.getValues(item,attribute);
181                for(var i = 0; i < values.length; i++){
182                        if(values[i] === value){
183                                return true;
184                        }
185                }
186                return false;
187        },
188
189        getValues: function(item, attribute){
190                // summary:
191                //              See dojo/data/api/Read.getValue()
192                this._assertIsItem(item);
193                this._assertIsAttribute(attribute);
194                var val = item[attribute];
195                if(lang.isArray(val)) {
196                        return val;
197                }else if(val !== undefined){
198                        return [val];
199                }else{
200                        return [];
201                }
202        },
203
204        isItem: function(item){
205                // summary:
206                //              See dojo/data/api/Read.isItem()
207                if(item && item[this._storeRef] === this){
208                        return true;
209                }
210                return false;
211        },
212
213        close: function(request){
214                // summary:
215                //              See dojo/data/api/Read.close()
216        },
217
218        _format: function(item, name){
219                return item;//base implementation does not format any items
220        },
221
222        fetch: function(request){
223                // summary:
224                //              Fetch Google search items that match to a query
225                // request:
226                //              A request object
227                // fetchHandler:
228                //              A function to call for fetched items
229                // errorHandler:
230                //              A function to call on error
231                request = request || {};
232
233                var scope = request.scope || kernel.global;
234
235                if(!request.query){
236                        if(request.onError){
237                                request.onError.call(scope, new Error(this.declaredClass +
238                                        ": A query must be specified."));
239                                return;
240                        }
241                }
242                //Make a copy of the request object, in case it is
243                //modified outside the store in the middle of a request
244                var query = {};
245                for(var attr in this._queryAttrs) {
246                        query[attr] = request.query[attr];
247                }
248                request = {
249                        query: query,
250                        onComplete: request.onComplete,
251                        onError: request.onError,
252                        onItem: request.onItem,
253                        onBegin: request.onBegin,
254                        start: request.start,
255                        count: request.count
256                };
257
258                //Google's web api will only return a max of 8 results per page.
259                var pageSize = 8;
260
261                //Generate a unique function to be called back
262                var callbackFn = "GoogleSearchStoreCallback_" + this._id + "_" + (++this._requestCount);
263
264                //Build up the content to send the request for.
265                //rsz is the result size, "large" gives 8 results each time
266                var content = this._createContent(query, callbackFn, request);
267
268                var firstRequest;
269
270                if(typeof(request.start) === "undefined" || request.start === null){
271                        request.start = 0;
272                }
273
274                if(!request.count){
275                        request.count = pageSize;
276                }
277                firstRequest = {start: request.start - request.start % pageSize};
278
279                var _this = this;
280                var searchUrl = this._googleUrl + this._type;
281
282                var getArgs = {
283                        url: searchUrl,
284                        preventCache: this.urlPreventCache,
285                        content: content
286                };
287
288                var items = [];
289                var successfulReq = 0;
290                var finished = false;
291                var lastOnItem = request.start -1;
292                var numRequests = 0;
293                var scriptIds = [];
294
295                // Performs the remote request.
296                function doRequest(req){
297                        //Record how many requests have been made.
298                        numRequests ++;
299                        getArgs.content.context = getArgs.content.start = req.start;
300
301                        var deferred = scriptIO.get(getArgs);
302                        scriptIds.push(deferred.ioArgs.id);
303
304                        //We only set up the errback, because the callback isn't ever really used because we have
305                        //to link to the jsonp callback function....
306                        deferred.addErrback(function(error){
307                                if(request.onError){
308                                        request.onError.call(scope, error, request);
309                                }
310                        });
311                }
312
313                // Function to handle returned data.
314                var myHandler = function(start, data){
315                        if (scriptIds.length > 0) {
316                                // Delete the script node that was created.
317                                domQuery("#" + scriptIds.splice(0,1)).forEach(domConstruct.destroy);
318                        }
319                        if(finished){return;}
320
321                        var results = _this._getItems(data);
322                        var cursor = data ? data['cursor']: null;
323
324                        if(results){
325                                //Process the results, adding the store reference to them
326                                for(var i = 0; i < results.length && i + start < request.count + request.start; i++) {
327                                        _this._processItem(results[i], data);
328                                        items[i + start] = results[i];
329                                }
330                                successfulReq ++;
331                                if(successfulReq == 1){
332                                        // After the first request, we know how many results exist.
333                                        // So perform any follow up requests to retrieve more data.
334                                        var pages = cursor ? cursor.pages : null;
335                                        var firstStart = pages ? Number(pages[pages.length - 1].start) : 0;
336
337                                        //Call the onBegin method if it exists
338                                        if (request.onBegin){
339                                                var est = cursor ? cursor.estimatedResultCount : results.length;
340                                                var total =  est ? Math.min(est, firstStart + results.length) : firstStart + results.length;
341                                                request.onBegin.call(scope, total, request);
342                                        }
343
344                                        // Request the next pages.
345                                        var nextPage = (request.start - request.start % pageSize) + pageSize;
346                                        var page = 1;
347                                        while(pages){
348                                                if(!pages[page] || Number(pages[page].start) >= request.start + request.count){
349                                                        break;
350                                                }
351                                                if(Number(pages[page].start) >= nextPage) {
352                                                        doRequest({start: pages[page].start});
353                                                }
354                                                page++;
355                                        }
356                                }
357
358                                // Call the onItem function on all retrieved items.
359                                if(request.onItem && items[lastOnItem + 1]){
360                                        do{
361                                                lastOnItem++;
362                                                request.onItem.call(scope, items[lastOnItem], request);
363                                        }while(items[lastOnItem + 1] && lastOnItem < request.start + request.count);
364                                }
365
366                                //If this is the last request, call final fetch handler.
367                                if(successfulReq == numRequests){
368                                        //Process the items...
369                                        finished = true;
370                                        //Clean up the function, it should never be called again
371                                        kernel.global[callbackFn] = null;
372                                        if(request.onItem){
373                                                request.onComplete.call(scope, null, request);
374                                        }else{
375                                                items = items.slice(request.start, request.start + request.count);
376                                                request.onComplete.call(scope, items, request);
377                                        }
378
379                                }
380                        }
381                };
382
383                var callbacks = [];
384                var lastCallback = firstRequest.start - 1;
385
386                // Attach a callback function to the global namespace, where Google can call it.
387                kernel.global[callbackFn] = function(start, data, responseCode, errorMsg){
388                        try {
389                                if(responseCode != 200){
390                                        if(request.onError){
391                                                request.onError.call(scope, new Error("Response from Google was: " + responseCode), request);
392                                        }
393                                        kernel.global[callbackFn] = function(){};//an error occurred, do not return anything else.
394                                        return;
395                                }
396       
397                                if(start == lastCallback + 1){
398                                        myHandler(Number(start), data);
399                                        lastCallback += pageSize;
400       
401                                        //make sure that the callbacks happen in the correct sequence
402                                        if(callbacks.length > 0){
403                                                callbacks.sort(_this._getSort());
404                                                //In case the requsts do not come back in order, sort the returned results.
405                                                while(callbacks.length > 0 && callbacks[0].start == lastCallback + 1){
406                                                        myHandler(Number(callbacks[0].start), callbacks[0].data);
407                                                        callbacks.splice(0,1);
408                                                        lastCallback += pageSize;
409                                                }
410                                        }
411                                }else{
412                                        callbacks.push({start:start, data: data});
413                                }
414                        } catch (e) {
415                                request.onError.call(scope, e, request);
416                        }
417                };
418
419                // Perform the first request. When this has finished
420                // we will have a list of pages, which can then be
421                // gone through
422                doRequest(firstRequest);
423        },
424       
425        _getSort: function() {
426                return function(a,b){
427                        if(a.start < b.start){return -1;}
428                        if(b.start < a.start){return 1;}
429                        return 0;
430                };
431        },
432
433        _processItem: function(item, data) {
434                item[this._storeRef] = this;
435                // Copy aggregated attributes from query results to the item.
436                for(var attribute in this._aggregatedAttributes) {
437                        item[attribute] = lang.getObject(this._aggregatedAttributes[attribute], false, data);
438                }
439        },
440
441        _getItems: function(data){
442                return data['results'] || data;
443        },
444
445        _createContent: function(query, callback, request){
446                var content = {
447                        v: "1.0",
448                        rsz: "large",
449                        callback: callback,
450                        key: this._key,
451                        hl: this._lang
452                };
453                for(var attr in this._queryAttrs) {
454                        content[this._queryAttrs[attr]] = query[attr];
455                }
456                return content;
457        }
458});
459
460var WebSearchStore = declare("dojox.data.GoogleWebSearchStore", SearchStore,{
461        // summary:
462        //              A data store for retrieving search results from Google.
463        //              The following attributes are supported on each item:
464        //
465        //              - title - The page title in HTML format.
466        //              - titleNoFormatting - The page title in plain text
467        //              - content - A snippet of information about the page
468        //              - url - The URL for the item
469        //              - unescapedUrl - The URL for the item, with URL escaping. This is often more readable
470        //              - visibleUrl - The URL with no protocol specified.
471        //              - cacheUrl - The URL to the copy of the document cached by Google
472        //              - estimatedResultCount - (aggregated per-query) estimated number of results
473        //
474        //              The query accepts one parameter: text - The string to search for
475});
476
477var BlogSearchStore = declare("dojox.data.GoogleBlogSearchStore", SearchStore,{
478        // summary:
479        //              A data store for retrieving search results from Google.
480        //              The following attributes are supported on each item:
481        //
482        //              - title - The blog post title in HTML format.
483        //              - titleNoFormatting - The  blog post title in plain text
484        //              - content - A snippet of information about the blog post
485        //              - blogUrl - The URL for the blog
486        //              - postUrl - The URL for the a single blog post
487        //              - visibleUrl - The URL with no protocol specified.
488        //              - cacheUrl - The URL to the copy of the document cached by Google
489        //              - author - The author of the blog post
490        //              - publishedDate - The published date, in RFC-822 format
491        //
492        //              The query accepts one parameter: text - The string to search for
493        _type: "blogs",
494        _attributes: ["blogUrl", "postUrl", "title", "titleNoFormatting", "content",
495                        "author", "publishedDate"],
496        _aggregatedAttributes: { }
497});
498
499
500var LocalSearchStore = declare("dojox.data.GoogleLocalSearchStore", SearchStore,{
501        // summary:
502        //              A data store for retrieving search results from Google.
503        //              The following attributes are supported on each item:
504        //
505        //              - title - The blog post title in HTML format.
506        //              - titleNoFormatting - The  blog post title in plain text
507        //              - content - A snippet of information about the blog post
508        //              - url - The URL for the item
509        //              - lat - The latitude.
510        //              - lng - The longtitude.
511        //              - streetAddress - The street address
512        //              - city - The city
513        //              - region - The region
514        //              - country - The country
515        //              - phoneNumbers - Phone numbers associated with this address. Can be one or more.
516        //              - ddUrl - A URL that can be used to provide driving directions from the center of the search results to this search results
517        //              - ddUrlToHere - A URL that can be used to provide driving directions from this search result to a user specified location
518        //              - staticMapUrl - The published date, in RFC-822 format
519        //              - viewport - Recommended viewport for the query results (same for all results in a query)
520        //                      - center - contains lat, lng properties
521        //                      - span - lat, lng properties for the viewport span
522        //                      - ne, sw - lat, lng properties for the viewport corners
523        //
524        //              The query accepts the following parameters:
525        //
526        //              - text - The string to search for
527        //              - centerLatLong - Comma-separated lat & long for the center of the search (e.g. "48.8565,2.3509")
528        //              - searchSpan - Comma-separated lat & long degrees indicating the size of the desired search area (e.g. "0.065165,0.194149")
529
530        _type: "local",
531        _attributes: ["title", "titleNoFormatting", "url", "lat", "lng", "streetAddress",
532                        "city", "region", "country", "phoneNumbers", "ddUrl", "ddUrlToHere",
533                        "ddUrlFromHere", "staticMapUrl", "viewport"],
534        _aggregatedAttributes: {
535                viewport: "viewport"
536        },
537        _queryAttrs: {
538                text: 'q',
539                centerLatLong: 'sll',
540                searchSpan: 'sspn'
541        }
542});
543
544var VideoSearchStore = declare("dojox.data.GoogleVideoSearchStore", SearchStore,{
545        // summary:
546        //              A data store for retrieving search results from Google.
547        //              The following attributes are supported on each item:
548        //
549        //              - title - The blog post title in HTML format.
550        //              - titleNoFormatting - The  blog post title in plain text
551        //              - content - A snippet of information about the blog post
552        //              - url - The URL for the item
553        //              - published - The published date, in RFC-822 format.
554        //              - publisher - The name of the publisher.
555        //              - duration - The approximate duration, in seconds, of the video.
556        //              - tbWidth - The width in pixels of the video.
557        //              - tbHeight - The height in pixels of the video
558        //              - tbUrl - The URL to a thumbnail representation of the video.
559        //              - playUrl - If present, supplies the url of the flash version of the video that can be played inline on your page. To play this video simply create and <embed> element on your page using this value as the src attribute and using application/x-shockwave-flash as the type attribute. If you want the video to play right away, make sure to append &autoPlay=true to the url..
560        //
561        //              The query accepts one parameter: text - The string to search for
562        _type: "video",
563        _attributes: ["title", "titleNoFormatting", "content", "url", "published", "publisher",
564                        "duration", "tbWidth", "tbHeight", "tbUrl", "playUrl"],
565        _aggregatedAttributes: { }
566});
567
568var NewsSearchStore = declare("dojox.data.GoogleNewsSearchStore", SearchStore,{
569        // summary:
570        //              A data store for retrieving search results from Google.
571        //              The following attributes are supported on each item:
572        //
573        //              - title - The news story title in HTML format.
574        //              - titleNoFormatting - The news story title in plain text
575        //              - content - A snippet of information about the news story
576        //              - url - The URL for the item
577        //              - unescapedUrl - The URL for the item, with URL escaping. This is often more readable
578        //              - publisher - The name of the publisher
579        //              - clusterUrl - A URL pointing to a page listing related storied.
580        //              - location - The location of the news story.
581        //              - publishedDate - The date of publication, in RFC-822 format.
582        //              - relatedStories - An optional array of objects specifying related stories.
583        //                      Each object has the following subset of properties:
584        //                      "title", "titleNoFormatting", "url", "unescapedUrl", "publisher", "location", "publishedDate".
585        //
586        //              The query accepts one parameter: text - The string to search for
587        _type: "news",
588        _attributes: ["title", "titleNoFormatting", "content", "url", "unescapedUrl", "publisher",
589                        "clusterUrl", "location", "publishedDate", "relatedStories" ],
590        _aggregatedAttributes: { }
591});
592
593var BookSearchStore = declare("dojox.data.GoogleBookSearchStore", SearchStore,{
594        // summary:
595        //              A data store for retrieving search results from Google.
596        //              The following attributes are supported on each item:
597        //
598        //              - title - The book title in HTML format.
599        //              - titleNoFormatting - The book title in plain text
600        //              - authors - An array of authors
601        //              - url - The URL for the item
602        //              - unescapedUrl - The URL for the item, with URL escaping. This is often more readable
603        //              - bookId - An identifier for the book, usually an ISBN.
604        //              - pageCount - The number of pages in the book.
605        //              - publishedYear - The year of publication.
606        //
607        //              The query accepts one parameter: text - The string to search for
608        _type: "books",
609        _attributes: ["title", "titleNoFormatting", "authors", "url", "unescapedUrl", "bookId",
610                        "pageCount", "publishedYear"],
611        _aggregatedAttributes: { }
612});
613
614var ImageSearchStore = declare("dojox.data.GoogleImageSearchStore", SearchStore,{
615        // summary:
616        //              A data store for retrieving search results from Google.
617        //              The following attributes are supported on each item:
618        //
619        //              - title - The image title in HTML format.
620        //              - titleNoFormatting - The image title in plain text
621        //              - url - The URL for the image
622        //              - unescapedUrl - The URL for the image, with URL escaping. This is often more readable
623        //              - tbUrl - The URL for the image thumbnail
624        //              - visibleUrl - A shortened version of the URL associated with the result, stripped of a protocol and path
625        //              - originalContextUrl - The URL of the page containing the image.
626        //              - width - The width of the image in pixels.
627        //              - height - The height of the image in pixels.
628        //              - tbWidth - The width of the image thumbnail in pixels.
629        //              - tbHeight - The height of the image thumbnail in pixels.
630        //              - content - A snippet of information about the image, in HTML format
631        //              - contentNoFormatting - A snippet of information about the image, in plain text
632        //
633        //              The query accepts one parameter: text - The string to search for
634        _type: "images",
635        _attributes: ["title", "titleNoFormatting", "visibleUrl", "url", "unescapedUrl",
636                        "originalContextUrl", "width", "height", "tbWidth", "tbHeight",
637                        "tbUrl", "content", "contentNoFormatting"],
638        _aggregatedAttributes: { }
639});
640
641return {
642        Search: SearchStore,
643        ImageSearch: ImageSearchStore,
644        BookSearch: BookSearchStore,
645        NewsSearch: NewsSearchStore,
646        VideoSearch: VideoSearchStore,
647        LocalSearch: LocalSearchStore,
648        BlogSearch: BlogSearchStore,
649        WebSearch: WebSearchStore
650        }
651});
Note: See TracBrowser for help on using the repository browser.