dojo.provide("dojox.data.GoogleSearchStore"); dojo.provide("dojox.data.GoogleWebSearchStore"); dojo.provide("dojox.data.GoogleBlogSearchStore"); dojo.provide("dojox.data.GoogleLocalSearchStore"); dojo.provide("dojox.data.GoogleVideoSearchStore"); dojo.provide("dojox.data.GoogleNewsSearchStore"); dojo.provide("dojox.data.GoogleBookSearchStore"); dojo.provide("dojox.data.GoogleImageSearchStore"); dojo.require("dojo.io.script"); dojo.experimental("dojox.data.GoogleSearchStore"); dojo.declare("dojox.data.GoogleSearchStore",null,{ // summary: // A data store for retrieving search results from Google. // This data store acts as a base class for Google searches, // and has a number of child data stores that implement different // searches. This store defaults to searching the web, and is functionally // identical to the dojox.data.GoogleWebSearchStore object. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for constructor: function(/*Object*/args){ // summary: // Initializer for the GoogleSearchStore store. // description: // The GoogleSearchStore is a Datastore interface to // the Google search service. The constructor accepts the following arguments: // if(args){ if(args.label){ this.label = args.label; } if(args.key){ this._key = args.key; } if(args.lang){ this._lang = args.lang; } } this._id = dojox.data.GoogleSearchStore.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 GoogleSearchStore will use. _requestCount: 0, // _googleUrl: String // The URL to Googles search web service. _googleUrl: "http://ajax.googleapis.com/ajax/services/search/", // _storeRef: String // The internal reference added to each item pointing at the store which owns it. _storeRef: "_S", // _attributes: Array // The list of attributes that this store supports _attributes: ["unescapedUrl", "url", "visibleUrl", "cacheUrl", "title", "titleNoFormatting", "content"], // label: String // The default attribute which acts as a label for each item. label: "titleNoFormatting", // type: String // The type of search. Valid values are "web", "local", "video", "blogs", "news", "books", "images". // This should not be set directly. Instead use one of the child classes. _type: "web", _queryAttr: "text", _assertIsItem: function(/* item */ item){ // summary: // This function tests whether the item passed in is indeed an item in the store. // item: // The item to test for being contained by the store. if(!this.isItem(item)){ throw new Error("dojox.data.GoogleSearchStore: a function was passed an item argument that was not an item"); } }, _assertIsAttribute: function(/* attribute-name-string */ attribute){ // summary: // This function tests whether the item passed in is indeed a valid 'attribute' like type for the store. // attribute: // The attribute to test for being contained by the store. if(typeof attribute !== "string"){ throw new Error("dojox.data.GoogleSearchStore: a function was passed an attribute argument that was not an attribute name string"); } }, getFeatures: function(){ // summary: // See dojo.data.api.Read.getFeatures() return { 'dojo.data.api.Read': true }; }, getValue: function(item, attribute, defaultValue){ // summary: // See dojo.data.api.Read.getValue() var values = this.getValues(item, attribute); if(values && values.length > 0){ return values[0]; } return defaultValue; }, getAttributes: function(item){ // summary: // See dojo.data.api.Read.getAttributes() return this._attributes; }, hasAttribute: function(item, attribute){ // summary: // See dojo.data.api.Read.hasAttributes() if(this.getValue(item,attribute)){ return true; } return false; }, isItemLoaded: function(item){ // summary: // See dojo.data.api.Read.isItemLoaded() return this.isItem(item); }, loadItem: function(keywordArgs){ // summary: // See dojo.data.api.Read.loadItem() }, getLabel: function(item){ // summary: // See dojo.data.api.Read.getLabel() return this.getValue(item,this.label); }, getLabelAttributes: function(item){ // summary: // See dojo.data.api.Read.getLabelAttributes() return [this.label]; }, containsValue: function(item, attribute, value){ // summary: // See dojo.data.api.Read.containsValue() var values = this.getValues(item,attribute); for(var i = 0; i < values.length; i++){ if(values[i] === value){ return true; } } return false; }, getValues: function(item, attribute){ // summary: // See dojo.data.api.Read.getValue() this._assertIsItem(item); this._assertIsAttribute(attribute); var val = item[attribute]; if(dojo.isArray(val)) { return val; }else if(val !== undefined){ return [val]; }else{ return []; } }, isItem: function(item){ // summary: // See dojo.data.api.Read.isItem() if(item && item[this._storeRef] === this){ return true; } return false; }, close: function(request){ // summary: // See dojo.data.api.Read.close() }, _format: function(item, name){ return item;//base implementation does not format any items }, fetch: function(request){ // summary: // Fetch Google search 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 request = request || {}; var scope = request.scope || dojo.global; if(!request.query || !request.query[this._queryAttr]){ if(request.onError){ request.onError.call(scope, new Error(this.declaredClass + ": A query must be specified, with a '" + [this._queryAttr] + "' parameter.")); return; } } //Make a copy of the request object, in case it is //modified outside the store in the middle of a request var query = request.query[this._queryAttr]; request = { query: { }, onComplete: request.onComplete, onError: request.onError, onItem: request.onItem, onBegin: request.onBegin, start: request.start, count: request.count }; request.query[this._queryAttr] = query; //Google's web api will only return a max of 8 results per page. var pageSize = 8; //Generate a unique function to be called back var callbackFn = "GoogleSearchStoreCallback_" + this._id + "_" + (++this._requestCount); //Build up the content to send the request for. //rsz is the result size, "large" gives 8 results each time var content = this._createContent(query, callbackFn, request); var firstRequest; if(typeof(request.start) === "undefined" || request.start === null){ request.start = 0; } if(!request.count){ request.count = pageSize; } firstRequest = {start: request.start - request.start % pageSize}; var _this = this; var handle = null; var searchUrl = this._googleUrl + this._type; var getArgs = { url: searchUrl, preventCache: true, content: content }; var items = []; var successfulReq = 0; var finished = false; var lastOnItem = request.start -1; var numRequests = 0; // Performs the remote request. function doRequest(req){ //Record how many requests have been made. numRequests ++; getArgs.content.context = getArgs.content.start = req.start; 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 jsonp callback function.... deferred.addErrback(function(error){ if(request.onError){ request.onError.call(scope, error, request); } }); } // Function to handle returned data. var myHandler = function(start, data){ if(finished){return;} var results = _this._getItems(data); var cursor = data ? data['cursor']: null; if(results){ //Process the results, adding the store reference to them for(var i = 0; i < results.length && i + start < request.count + request.start; i++) { _this._processItem(results[i], data); items[i + start] = results[i]; } successfulReq ++; if(successfulReq == 1){ // After the first request, we know how many results exist. // So perform any follow up requests to retrieve more data. var pages = cursor ? cursor.pages : null; var firstStart = pages ? Number(pages[pages.length - 1].start) : 0; //Call the onBegin method if it exists if (request.onBegin){ var est = cursor ? cursor.estimatedResultCount : results.length; var total = est ? Math.min(est, firstStart + results.length) : firstStart + results.length; request.onBegin.call(scope, total, request); } // Request the next pages. var nextPage = (request.start - request.start % pageSize) + pageSize; var page = 1; while(pages){ if(!pages[page] || Number(pages[page].start) >= request.start + request.count){ break; } if(Number(pages[page].start) >= nextPage) { doRequest({start: pages[page].start}); } page++; } } // Call the onItem function on all retrieved items. if(request.onItem && items[lastOnItem + 1]){ do{ lastOnItem++; request.onItem.call(scope, items[lastOnItem], request); }while(items[lastOnItem + 1] && lastOnItem < request.start + request.count); } //If this is the last request, call final fetch handler. if(successfulReq == numRequests){ //Process the items... finished = true; //Clean up the function, it should never be called again dojo.global[callbackFn] = null; if(request.onItem){ request.onComplete.call(scope, null, request); }else{ items = items.slice(request.start, request.start + request.count); request.onComplete.call(scope, items, request); } } } }; var callbacks = []; var lastCallback = firstRequest.start - 1; var sortFn = function(a,b){ if(a.start < b.start){return -1;} if(b.start < a.start){return 1;} return 0; }; // Attach a callback function to the global namespace, where Google can call it. dojo.global[callbackFn] = function(start, data, responseCode, errorMsg){ try { if(responseCode != 200){ if(request.onError){ request.onError.call(scope, new Error("Response from Google was: " + responseCode), request); } dojo.global[callbackFn] = function(){};//an error occurred, do not return anything else. return; } if(start == lastCallback + 1){ myHandler(Number(start), data); lastCallback += pageSize; //make sure that the callbacks happen in the correct sequence if(callbacks.length > 0){ callbacks.sort(sortFn); //In case the requsts do not come back in order, sort the returned results. while(callbacks.length > 0 && callbacks[0].start == lastCallback + 1){ myHandler(Number(callbacks[0].start), callbacks[0].data); callbacks.splice(0,1); lastCallback += pageSize; } } }else{ callbacks.push({start:start, data: data}); } } catch (e) { request.onError.call(scope, e, request); } }; // Perform the first request. When this has finished // we will have a list of pages, which can then be // gone through doRequest(firstRequest); }, _processItem: function(item, data) { item[this._storeRef] = this; }, _getItems: function(data){ return data['results'] || data; }, _createContent: function(query, callback, request){ return { q: query, v:"1.0", rsz:"large", callback: callback, key:this._key, hl: this._lang }; } }); dojo.declare("dojox.data.GoogleWebSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for }); dojo.declare("dojox.data.GoogleBlogSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "blogs", _attributes: ["blogUrl", "postUrl", "title", "titleNoFormatting", "content", "author", "publishedDate"] }); dojo.declare("dojox.data.GoogleLocalSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "local", _attributes: ["title", "titleNoFormatting", "url", "lat", "lng", "streetAddress", "city", "region", "country", "phoneNumbers", "ddUrl", "ddUrlToHere", "ddUrlFromHere", "staticMapUrl"] }); dojo.declare("dojox.data.GoogleVideoSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "video", _attributes: ["title", "titleNoFormatting", "content", "url", "published", "publisher", "duration", "tbWidth", "tbHeight", "tbUrl", "playUrl"] }); dojo.declare("dojox.data.GoogleNewsSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "news", _attributes: ["title", "titleNoFormatting", "content", "url", "unescapedUrl", "publisher", "clusterUrl", "location", "publishedDate", "relatedStories" ] }); dojo.declare("dojox.data.GoogleBookSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "books", _attributes: ["title", "titleNoFormatting", "authors", "url", "unescapedUrl", "bookId", "pageCount", "publishedYear"] }); dojo.declare("dojox.data.GoogleImageSearchStore", dojox.data.GoogleSearchStore,{ // summary: // A data store for retrieving search results from Google. // The following attributes are supported on each item: // // The query accepts one parameter: text - The string to search for _type: "images", _attributes: ["title", "titleNoFormatting", "visibleUrl", "url", "unescapedUrl", "originalContextUrl", "width", "height", "tbWidth", "tbHeight", "tbUrl", "content", "contentNoFormatting"] });