dojo.provide("dojox.data.OpenSearchStore"); dojo.require("dojo.data.util.simpleFetch"); dojo.require("dojox.xml.DomParser"); dojo.require("dojox.xml.parser"); dojo.experimental("dojox.data.OpenSearchStore"); dojo.declare("dojox.data.OpenSearchStore", null, { constructor: function(/*Object*/args){ // summary: // Initializer for the OpenSearchStore store. // description: // The OpenSearchStore is a Datastore interface to any search // engine that implements the open search specifications. if(args){ this.label = args.label; this.url = args.url; this.itemPath = args.itemPath; } var def = dojo.xhrGet({ url: this.url, handleAs: "xml", sync: true }); def.addCallback(this, "_processOsdd"); def.addErrback(function() { throw new Error("Unable to load OpenSearch Description document from " . args.url); }); }, // URL to the open search description document url: "", itemPath: "", _storeRef: "_S", urlElement: null, iframeElement: null, ATOM_CONTENT_TYPE: 3, ATOM_CONTENT_TYPE_STRING: "atom", RSS_CONTENT_TYPE: 2, RSS_CONTENT_TYPE_STRING: "rss", XML_CONTENT_TYPE: 1, XML_CONTENT_TYPE_STRING: "xml", _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.OpenSearchStore: 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.OpenSearchStore: 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){ return values[0]; } return defaultValue; }, getAttributes: function(item){ // summary: // See dojo.data.api.Read.getAttributes() return ["content"]; }, 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 undefined; }, getLabelAttributes: function(item){ // summary: // See dojo.data.api.Read.getLabelAttributes() return null; }, 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 value = this.processItem(item, attribute); if(value){ return [value]; } return undefined; }, 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() }, process: function(data){ // This should return an array of items. This would be the function to override if the // developer wanted to customize the processing/parsing of the entire batch of search // results. return this["_processOSD"+this.contentType](data); }, processItem: function(item, attribute){ // This returns the text that represents the item. If a developer wanted to customize // how an individual item is rendered/parsed, they'd override this function. return this["_processItem"+this.contentType](item.node, attribute); }, _createSearchUrl: function(request){ var template = this.urlElement.attributes.getNamedItem("template").nodeValue; var attrs = this.urlElement.attributes; var index = template.indexOf("{searchTerms}"); template = template.substring(0, index) + request.query.searchTerms + template.substring(index+13); dojo.forEach([ {'name': 'count', 'test': request.count, 'def': '10'}, {'name': 'startIndex', 'test': request.start, 'def': this.urlElement.attributes.getNamedItem("indexOffset")?this.urlElement.attributes.getNamedItem("indexOffset").nodeValue:0}, {'name': 'startPage', 'test': request.startPage, 'def': this.urlElement.attributes.getNamedItem("pageOffset")?this.urlElement.attributes.getNamedItem("pageOffset").nodeValue:0}, {'name': 'language', 'test': request.language, 'def': "*"}, {'name': 'inputEncoding', 'test': request.inputEncoding, 'def': 'UTF-8'}, {'name': 'outputEncoding', 'test': request.outputEncoding, 'def': 'UTF-8'} ], function(item){ template = template.replace('{'+item.name+'}', item.test || item.def); template = template.replace('{'+item.name+'?}', item.test || item.def); }); return template; }, _fetchItems: function(request, fetchHandler, errorHandler){ // summary: // Fetch OpenSearch 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 if(!request.query){ request.query={}; } //Build up the content using information from the request var self = this; var url = this._createSearchUrl(request); var getArgs = { url: url, preventCache: true }; // Change to fetch the query results. var xhr = dojo.xhrGet(getArgs); xhr.addErrback(function(error){ errorHandler(error, request); }); xhr.addCallback(function(data){ var items = []; if(data) { //Process the items... items = self.process(data); for(var i=0; i < items.length; i++){ items[i] = {node: items[i]}; items[i][self._storeRef] = self; } } fetchHandler(items, request); }); }, _processOSDxml: function(data){ var div = dojo.doc.createElement("div"); div.innerHTML = data; return dojo.query(this.itemPath, div); }, _processItemxml: function(item, attribute){ if(attribute === "content"){ return item.innerHTML; } return undefined; }, _processOSDatom: function(data){ return this._processOSDfeed(data, "entry"); }, _processItematom: function(item, attribute){ return this._processItemfeed(item, attribute, "content"); }, _processOSDrss: function(data){ return this._processOSDfeed(data, "item"); }, _processItemrss: function(item, attribute){ return this._processItemfeed(item, attribute, "description"); }, _processOSDfeed: function(data, type){ data = dojox.xml.parser.parse(data); var items = []; var nodeList = data.getElementsByTagName(type); for(var i=0; i"); } for(i=0; i\n"); } return xml.join(""); case 3: case 4: return node.nodeValue; } return undefined; }, _processOsdd: function(doc){ var urlnodes = doc.getElementsByTagName("Url"); //TODO: Check all the urlnodes and determine what our best one is... var types = []; var contentType; var i; for(i=0; icurrentType) { index = i; currentType = types[i]; } } // We'll be using urlnodes[index] as it's the best option (ATOM > RSS > XML) var label = urlnodes[index].nodeName.toLowerCase(); if(label == 'url'){ var urlattrs = urlnodes[index].attributes; this.urlElement = urlnodes[index]; switch(types[index]){ case this.ATOM_CONTENT_TYPE: this.contentType = this.ATOM_CONTENT_TYPE_STRING; break; case this.RSS_CONTENT_TYPE: this.contentType = this.RSS_CONTENT_TYPE_STRING; break; case this.XML_CONTENT_TYPE: this.contentType = this.XML_CONTENT_TYPE_STRING; break; } } } }); dojo.extend(dojox.data.OpenSearchStore,dojo.data.util.simpleFetch);