source: Dev/branches/rest-dojo-ui/client/dojox/data/GoogleSearchStore.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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