You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by li...@apache.org on 2011/06/23 20:52:05 UTC

svn commit: r1139034 - in /shindig/trunk: content/samplecontainer/examples/commoncontainer/ features/ features/src/main/javascript/features/ features/src/main/javascript/features/opensearch/ features/src/test/javascript/features/opensearch/

Author: lindner
Date: Thu Jun 23 18:52:05 2011
New Revision: 1139034

URL: http://svn.apache.org/viewvc?rev=1139034&view=rev
Log:
Patch from igor1x@gmail.com | Opensearch feature reference implementation for Shindig

Added:
    shindig/trunk/content/samplecontainer/examples/commoncontainer/search.js
    shindig/trunk/features/src/main/javascript/features/opensearch/
    shindig/trunk/features/src/main/javascript/features/opensearch/feature.xml
    shindig/trunk/features/src/main/javascript/features/opensearch/opensearch.js
    shindig/trunk/features/src/test/javascript/features/opensearch/
    shindig/trunk/features/src/test/javascript/features/opensearch/opensearch_test.js
Modified:
    shindig/trunk/content/samplecontainer/examples/commoncontainer/gadgetCollections.json
    shindig/trunk/content/samplecontainer/examples/commoncontainer/index.html
    shindig/trunk/features/pom.xml
    shindig/trunk/features/src/main/javascript/features/features.txt

Modified: shindig/trunk/content/samplecontainer/examples/commoncontainer/gadgetCollections.json
URL: http://svn.apache.org/viewvc/shindig/trunk/content/samplecontainer/examples/commoncontainer/gadgetCollections.json?rev=1139034&r1=1139033&r2=1139034&view=diff
==============================================================================
--- shindig/trunk/content/samplecontainer/examples/commoncontainer/gadgetCollections.json (original)
+++ shindig/trunk/content/samplecontainer/examples/commoncontainer/gadgetCollections.json Thu Jun 23 18:52:05 2011
@@ -29,6 +29,14 @@
                       {"name": "Horoscope", "url": "http://www.google.com/ig/modules/horoscope.xml"},
                       {"name": "TODO", "url": "http://www.labpixies.com/campaigns/todo/todo.xml"}
                       ]
+             },
+             {
+             "name": "OpenSearch Demo",
+             "Description": "Sample gadgets used for opensearch",
+             "apps" : [
+                      {"name": "Twitter", "url": "http://hosting.gmodules.com/ig/gadgets/file/109228598702359180066/twitterfinal.xml"},
+                      {"name": "MySpace", "url": "http://hosting.gmodules.com/ig/gadgets/file/109228598702359180066/myspacefinal.xml"}
+                      ]
              }
          ]
 }
\ No newline at end of file

Modified: shindig/trunk/content/samplecontainer/examples/commoncontainer/index.html
URL: http://svn.apache.org/viewvc/shindig/trunk/content/samplecontainer/examples/commoncontainer/index.html?rev=1139034&r1=1139033&r2=1139034&view=diff
==============================================================================
--- shindig/trunk/content/samplecontainer/examples/commoncontainer/index.html (original)
+++ shindig/trunk/content/samplecontainer/examples/commoncontainer/index.html Thu Jun 23 18:52:05 2011
@@ -21,9 +21,9 @@
 <head>
 
 	<!-- My OpenSocial Beginnings -->
-	<link rel="stylesheet" href="../../../container/gadgets.css">
-	<script src="../../../gadgets/js/container:rpc:pubsub-2.js?c=1&debug=1&container=default"></script>
-	</script>
+	<link rel="stylesheet" href="/container/gadgets.css">
+	<script type="text/javascript"
+		src="/gadgets/js/container:views:opensearch:rpc:pubsub-2.js?c=1&debug=1&container=default""></script>
 	<script
 		src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
 	<script
@@ -34,6 +34,8 @@
 	<script type="text/javascript" src="./assembler.js"></script>
 	<script type="text/javascript" src="./viewController.js"></script>
 	<script type="text/javascript" src="./layout.js"></script>
+    <script type="text/javascript" src="./cconviews.js"></script>
+    <script type="text/javascript" src="./search.js"></script>
 
 	<style>
 	  .portlet-header .ui-icon { float: right; }
@@ -78,13 +80,29 @@
 	  #viewsDropdown li.li-header ul li a { 
 	    padding: 5px; height: 17px; 
 	  }
-		
+    
+      #tabs li .ui-icon-close { float: left; margin: 0.4em 0.2em 0 0; cursor: pointer; }
+      
+	  .resultTitle {
+        color: blue;
+      }
+
+      .searchEngine {
+        border: 1px solid blue;
+      }
+
 	</style>
 </head>
 
 <body onLoad="CommonContainer.init();">
 <!--  Need to add in an improved header with links to specification -->
-<a href="http://www.opensocial.org/"><img alt="OpenSocial" src="http://api.ning.com/files/chwE7fbkJ5D1q8NSzNCYJzzCqOiuo3xYyToZCsYuY0SViOKQ7EG-3UUv4KFSxOMACbIzs3oWtD076*cbEL-*ABd9hCO0p7Al/opensocialsitelogo1.png?width=288&amp;height=70&amp;xn_auth=no&amp;type=png"></a><div id="content">
+<a href="http://www.opensocial.org/"><img alt="OpenSocial" src="http://api.ning.com/files/chwE7fbkJ5D1q8NSzNCYJzzCqOiuo3xYyToZCsYuY0SViOKQ7EG-3UUv4KFSxOMACbIzs3oWtD076*cbEL-*ABd9hCO0p7Al/opensocialsitelogo1.png?width=288&amp;height=70&amp;xn_auth=no&amp;type=png"></a>
+<span style="float:right">
+<input type="text" id="query"/>
+  <button name="search" id="searchButton" onClick="updateSearchURLs()">Search </button>   
+  <br/><span id="engineList"></span>  
+ </span>
+<div id="content">
 
 <div id="controlPanel">
 
@@ -144,6 +162,8 @@ Enter the event payload
 <div>
 <div id="gadgetArea" class="column"> 
 </div>
+<div id="results"></div>
+</div>
 </div>
 </div>
 <span class="ui-icon ui-icon-grip-dotted-horizontal"

Added: shindig/trunk/content/samplecontainer/examples/commoncontainer/search.js
URL: http://svn.apache.org/viewvc/shindig/trunk/content/samplecontainer/examples/commoncontainer/search.js?rev=1139034&view=auto
==============================================================================
--- shindig/trunk/content/samplecontainer/examples/commoncontainer/search.js (added)
+++ shindig/trunk/content/samplecontainer/examples/commoncontainer/search.js Thu Jun 23 18:52:05 2011
@@ -0,0 +1,221 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+*/
+
+var currentEngines = [];
+var templateMap = [];
+/**
+ * Helper function to extract create map of new engines, keyed by template url 
+ * @param searchURLs template search urls
+ * @param description
+ *        opensearch description being added or removed
+ * @param added
+ *            true if new description, false if removed.
+ */
+extractURLs = function(searchUrls, description, added) {
+  var newEngines = [];
+  for (var i in searchUrls) {
+    var template = searchUrls[i]["@template"];
+    if (template != null) {
+      var descType = searchUrls[i]["@type"];
+      if (descType == "application/atom+xml") {
+        if (added) {
+          if (currentEngines[template] == null) {
+            newEngines[template] = description.OpenSearchDescription.ShortName;
+            templateMap[template] = 1;
+          } else {
+            templateMap[template]++;
+          }
+        } else {
+          if (currentEngines[template] != null) {
+            if (templateMap[template] == 1) {
+              var oldEngine = document.getElementById(template);
+              oldEngine.parentNode.removeChild(oldEngine);
+              delete currentEngines[template];
+            } else {
+              templateMap[template]--;
+            }
+          }
+        }
+      }
+    }
+  }
+  return newEngines;
+}
+
+/**
+ * Callback passed to the opensearch feature to react to addition/removal of
+ * gadgets containing OpenSearch descriptions.
+ * 
+ * @param description
+ *            opensearch description being added or removed
+ * @param added
+ *            true if new description, false if removed.
+ */
+updateEngines = function(description, added) {
+
+  var searchUrls = [];
+  if (!(description.OpenSearchDescription.Url instanceof Array)) {
+    searchUrls.push(description.OpenSearchDescription.Url);
+  } else {
+    searchUrls = description.OpenSearchDescription.Url;
+  }
+
+  var newEngines = extractURLs(searchUrls, description, added);
+
+  var span = document.getElementById("engineList");
+  for (templateUrl in newEngines) {
+    var current = newEngines[templateUrl];
+    span.innerHTML = span.innerHTML
+        + "<input type=\"checkbox\" checked=\"true\" id=\"" + templateUrl
+        + "\" />" + current;
+    span.innerHTML = span.innerHTML + "<br/>";
+    currentEngines[templateUrl] = current;
+  }
+
+}
+
+CommonContainer.opensearch.addOpenSearchCallback(updateEngines);
+
+/**
+ * Clears old search results, and fetches new ones.
+ * 
+ */
+function updateSearchURLs() {
+  // clear the old results
+  $(function() {
+    $("#results").dialog( {
+      autoOpen : false
+    });
+  });
+  $("#results").dialog("option", "minWidth", 1000);
+  $("#results").dialog('open');
+  //var div = document.getElementById("results");
+  /*while (div.hasChildNodes()) {
+    div.removeChild(div.firstChild);
+  }*/
+  $("#results").empty();
+  //div.innerHTML = document.getElementById("query").value;
+  $("#results").innerHTML = document.getElementById("query").value;
+  // fetch new results.
+  getSearchResults(currentEngines, document.getElementById("query").value);
+}
+
+/**
+ * Iterates over template urls and fetches search results.
+ * 
+ * @param urls
+ *            all the opensearch template urls in the container
+ * @param query
+ *            query string.
+ */
+function getSearchResults(urls, query) {
+  // callback function to be called by the fetching code.
+  /**
+   * @param obj
+   *            the result data object, should be XML
+   * @param engineTitle
+   *            title of the engine being searched
+   */
+  function urlResponse(obj, engineTitle) {
+    // create placeholder for results
+    var su = document.getElementById("results");
+    var resultDiv = document.createElement("div");
+    su.appendChild(resultDiv);
+    // if there are no errors, parse the results
+    if (obj.errors.length == 0) {
+      resultDiv.className = "searchEngine";
+      var domdata = obj.data;
+      if (domdata != null) {
+        var entries = domdata.getElementsByTagName("entry");
+        resultDiv.innerHTML = resultDiv.innerHTML + engineTitle + ":<br/>";
+        if (entries.legnth == 0) {
+          resultDiv.innerHTML = resultDiv.innerHTML + ("No results found");
+        } else {
+          var resultCount = entries.length;
+          if (resultCount > 15) {
+            resultCount = 15;
+          }
+          for (i = 0; i < resultCount; i++) {
+            if (entries[i].getElementsByTagName("title").length > 0) {
+              titles = entries[i].getElementsByTagName("title")
+              title =  titles[0].childNodes[0].nodeValue;
+            } else {
+              title = "Untitled";
+            }
+            var link = null;
+            //for standard atom results, we can extract the link
+            if (entries[i].getElementsByTagName("link").length > 0) {
+              links = entries[i].getElementsByTagName("link");
+              link = links[0].attributes.href.nodeValue;
+            } 
+            var summaryNode = entries[i].getElementsByTagName("summary")[0];
+            if (summaryNode == null) {
+              summaryNode = entries[i].getElementsByTagName("description")[0];
+            }
+            if (link == null) {
+            resultDiv.innerHTML = resultDiv.innerHTML
+                + "<p style=\"color:blue\"/>"
+                + gadgets.util.escapeString(title);
+            } else {
+              resultDiv.innerHTML = resultDiv.innerHTML
+              + "<p style=\"color:blue\"/>"
+              + "<a href=\""+link+"\" target=\"_blank\">"
+              + gadgets.util.escapeString(title)
+              + "</a>";
+            }
+            if (summaryNode != null) {
+              var summary = summaryNode.textContent;
+              if (summary != null) {
+                resultDiv.innerHTML = resultDiv.innerHTML
+                    + gadgets.util.escapeString(summary);
+              }
+            }
+          }
+        }
+      }
+    } else { // errors occured, notify the user.
+      resultDiv.innerHTML = resultDiv.innerHTML + engineTitle
+          + "<br/> An error has occured:" + obj.errors[0];
+    }
+  }
+  var params = {};
+  for (url in currentEngines) {
+    // check if the current engine is selected.
+    if (document.getElementById(url).checked) {
+      title = currentEngines[url];
+      // replace placeholder with actual search term.
+      url = url.replace('{searchTerms}', query);
+      // for now, start on page 1
+      url = url.replace('{startPage?}', 1);
+      // makes sure that the title corresponds to the engine being search.
+      // Resolves a prior timing issue.
+      var callback = function() {
+        var myTitle = '' + title;
+        return function(response) {
+          urlResponse(response, myTitle)
+        };
+      }();
+      // go fetch the results.
+      params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.DOM;
+      gadgets.io.makeRequest(url, callback, params);
+
+    }
+  }
+
+};

Modified: shindig/trunk/features/pom.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/pom.xml?rev=1139034&r1=1139033&r2=1139034&view=diff
==============================================================================
--- shindig/trunk/features/pom.xml (original)
+++ shindig/trunk/features/pom.xml Thu Jun 23 18:52:05 2011
@@ -177,6 +177,7 @@
                 <source>osapi/peoplehelpers.js</source>
                 <source>../../../../src/test/javascript/lib/testutils.js</source>
                 <source>oauthpopup/oauthpopup.js</source>
+                <source>opensearch/opensearch.js</source>
               </sources>
               <testSourceDirectory>${basedir}/src/test/javascript/features</testSourceDirectory>
               <testSuites>

Modified: shindig/trunk/features/src/main/javascript/features/features.txt
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/features.txt?rev=1139034&r1=1139033&r2=1139034&view=diff
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/features.txt (original)
+++ shindig/trunk/features/src/main/javascript/features/features.txt Thu Jun 23 18:52:05 2011
@@ -57,6 +57,7 @@ features/jsondom/feature.xml
 features/locked-domain/feature.xml
 features/minimessage/feature.xml
 features/oauthpopup/feature.xml
+features/opensearch/feature.xml
 features/opensocial-0.6/feature.xml
 features/opensocial-0.7/feature.xml
 features/opensocial-0.8/feature.xml

Added: shindig/trunk/features/src/main/javascript/features/opensearch/feature.xml
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensearch/feature.xml?rev=1139034&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/opensearch/feature.xml (added)
+++ shindig/trunk/features/src/main/javascript/features/opensearch/feature.xml Thu Jun 23 18:52:05 2011
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+-->
+<feature>
+  <name>opensearch</name>
+  <dependency>globals</dependency>
+  <dependency>container</dependency>
+  <dependency>gadgets.json.ext</dependency>
+  <container>
+    <script src="opensearch.js"/>
+    <api>
+      <exports type="js">osapi.container.Container.prototype.opensearch.getOpenSearchURLs</exports>
+      <exports type="js">osapi.container.Container.prototype.opensearch.getOpenSearchDescriptions</exports>
+      <exports type="js">osapi.container.Container.prototype.opensearch.addOpenSearchCallback</exports>
+      <exports type="js">osapi.container.Container.prototype.opensearch.removeOpenSearchCallback</exports>
+    </api>
+  </container>
+</feature>

Added: shindig/trunk/features/src/main/javascript/features/opensearch/opensearch.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/main/javascript/features/opensearch/opensearch.js?rev=1139034&view=auto
==============================================================================
--- shindig/trunk/features/src/main/javascript/features/opensearch/opensearch.js (added)
+++ shindig/trunk/features/src/main/javascript/features/opensearch/opensearch.js Thu Jun 23 18:52:05 2011
@@ -0,0 +1,313 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+/**
+ * @fileoverview This library adds open search support to the container.
+ */
+
+(function(){
+  // array of opensearch descriptors.
+  var descriptions = {};
+  var ids = {};
+  // open search callbacks.
+  var callbacks = new Array();
+
+  /**
+   * Converts an XML string into a DOM object
+   * 
+   * @param {xmlString}
+   *            string representation of a valid XML object
+   * @return DOM Object
+   */
+  function createDom(xmlString){
+    var xmlDoc;
+    if (window.DOMParser){
+      var parser = new DOMParser();
+      xmlDoc = parser.parseFromString(xmlString,"text/xml");
+    }
+    else{
+      xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
+      xmlDoc.async = "false";
+      xmlDoc.loadXML(xmlString); 
+    }
+    return xmlDoc;
+  }
+
+  /**
+   * Extracts OpenSearch descriptions from XML string containing the 
+   * description and stores them in the internal map If valid 
+   * descriptions are found, notifies all callbacks about changes.
+   * 
+   * @param {domDesc}
+   *            stringified XML OpenSearch description
+   * @param {title}
+   *            the title of the gadget that the description belongs to
+   */
+  function extractDescriptions(domDesc, url) {
+    var jsonDesc = gadgets.json.xml.convertXmlToJson(domDesc);
+    if (jsonDesc != null) {
+      if (jsonDesc.OpenSearchDescription.Url != null) {
+        if (descriptions[url] == null) {
+          descriptions[url] = jsonDesc;
+          applyCallbacks(true, jsonDesc);
+        }
+      }
+    }
+  }
+
+  /**
+   * Notifies the registered callbacks of changes in the OpenSearch registry.
+   * 
+   * @param added
+   *            true if added, false if removed.
+   * @param description
+   *            opensearch description
+   * @return
+   */
+  function applyCallbacks(added, description) {
+    for (var i in callbacks) {
+      callbacks[i].apply(this, [description, added]);
+    }
+  }
+
+  /**
+   * Removes an opensearch description from the registry after the containing
+   * gadget is unloaded or closed.
+   * 
+   * @param url
+   *            url of the gadget to be removed.
+   */
+  function removeDescription(url) {
+    if (descriptions[url] != null) {
+      applyCallbacks(false, descriptions[url]);
+      delete descriptions[url];    
+    }
+  }
+  
+  /**
+   * Processes a new gadget definition and checks if it has an opensearch
+   * feature
+   * 
+   * @param {response}
+   *            Metadata response containing the json representation of the
+   *            gadget module
+   */
+  function preloaded(response) {
+    for (item in response) {
+      if (!response[item].error) { 
+        // check for os feature
+        var feature = response[item].modulePrefs.features['opensearch'];
+        var title = response[item].modulePrefs.title;
+        if (feature != null) {
+          var params = feature.params;
+          if (params != null) {
+            // retrieve the description
+            var desc = params["opensearch-description"];
+            // The full description is in the gadget
+            if (desc != null) {
+              // convert string to dom object
+              var domDesc = createDom(desc);
+              // convert dom object to json
+              extractDescriptions(domDesc, item);
+              // only the url to the full description is provided.
+            } else {
+              var openSearchUrl = params["opensearch-url"];
+              if (openSearchUrl != null) {
+                function urlCallback(response){
+                  if (response.errors.length == 0) {
+                    var domData = response.data;
+                    if (domData != null) {
+                      extractDescriptions(domData, item);
+                    }
+                  }
+                }
+                var params = {};
+                params[gadgets.io.RequestParameters.CONTENT_TYPE] 
+                       = gadgets.io.ContentType.DOM;
+                gadgets.io.makeRequest(openSearchUrl, urlCallback, params);
+              }
+            }
+          }
+        } 
+      }
+    }
+  }
+
+  /**
+   * When a gadget is closed, checks if it has a corresponding OpenSearch
+   * definition that needs to be removed, and notifies the appropriate
+   * callbacks.
+   * 
+   * @param {gadgetSite}
+   *            site of the gadget being closed
+   */
+  function closed(gadgetSite) {
+    if (gadgetSite != null) {
+      url=ids[gadgetSite.getId()];
+      removeDescription(url);
+    }
+  }
+
+  /**
+   * called when a gadget is navigated to.
+   * 
+   * @param {gadgetInfo}
+   *            json object representing the gadget module.
+   */
+  function navigated(gadgetSite) {
+    if (gadgetSite != null) {
+      if (gadgetSite.getActiveGadgetHolder() != null) {
+        url = gadgetSite.getActiveGadgetHolder().getUrl();
+        if (descriptions[url]==null) {
+          preloaded([gadgetSite.getActiveGadgetHolder().getGadgetInfo()]);
+        }  
+        ids[gadgetSite.getId()]=url;
+      }
+    }
+  }
+
+  /**
+   * called when a gadget is unloaded
+   * 
+   * @param {gadgetURL}
+   *            the gadget url of the unloaded gadget.
+   */
+  function unloaded(gadgetURL) {
+    // do nothing--this doesn't guarantee the gadget is actually removed.
+  }
+  
+  /**
+   * finds opensearch descriptions/urls based on the mimetype of the search
+   * results
+   * 
+   * @param {type}
+   *            mimeType of the search results
+   * @param {isUrl}
+   *            true if looking for template urls, false if looking for full
+   *            OpenSearch descriptions
+   */
+  var findByType = function(type, isUrl) {
+    var typedDescriptions = [];
+    for (url in descriptions) {
+      var searchUrls = [];
+      if (!(descriptions[url].OpenSearchDescription.Url instanceof Array)) {
+        searchUrls.push(descriptions[url].OpenSearchDescription.Url);
+      } else {
+        searchUrls = descriptions[url].OpenSearchDescription.Url;
+      }
+      var found = false;
+      // go through all the urls in a description.
+      // if a description contains a template of the type, the
+      // entire description is returned. For URLs, only the matching
+      // template url is returned.
+      for (var i in searchUrls) {
+        var template = searchUrls[i]["@template"];
+        if (template != null) {
+          var descType = searchUrls[i]["@type"];
+          if (descType == type || type == null) {
+            if (isUrl) {
+              typedDescriptions.push(template);
+            } else {
+              typedDescriptions.push(descriptions[url]);
+              break;
+            }
+          } 
+        }
+      }
+    }
+    return typedDescriptions;
+  }
+
+  var containerCallback = new Object();
+  containerCallback[osapi.container.CallbackType.ON_PRELOADED] 
+                    = preloaded;
+  containerCallback[osapi.container.CallbackType.ON_CLOSED] 
+                    = closed;
+  containerCallback[osapi.container.CallbackType.ON_NAVIGATED] 
+                    = navigated;
+  
+  osapi.container.Container.addMixin('opensearch',function(context){
+
+    context.addGadgetLifecycleCallback('opensearch', containerCallback);
+
+    return /** @scope container.opensearch */ {
+    /**
+     * @param {type}
+     *            type type name, eg search-xml, search-atom, search-hmtl
+     * @return opensearch template URLs of a given type, or all URLs if type
+     *         was null
+     */
+    getOpenSearchURLs: function(type) {
+      return findByType(type, true);
+    },
+    
+    /**
+     * 
+     * @param {type}
+     *            type type name, eg search-xml, search-atom, search-hmtl
+     * @return Returns OpenSearch descriptions of a given type, or all
+     *         descriptions if type was null
+     */
+    getOpenSearchDescriptions: function(type) {
+      return findByType(type, false);
+    },
+
+    /**
+     * Allows other functions to subscribe to changes in the OpenSearch 
+     * registry
+     * 
+     * @param {callback}
+     *           function(description, boolean added), where description is the
+     *           opensearch description being updated and where added is true
+     *           if the gadget is new, and false if it has been
+     *           closed/unloaded.
+     * 
+     */
+    addOpenSearchCallback: function(callback) {
+      callbacks.push(callback);
+    },
+    
+    /**
+     * Removes a previously registered callback
+     * 
+     * @param {callback}
+     *            previously registered callback function
+     * @return true if function was present, false otherwise.
+     * 
+     */
+    removeOpenSearchCallback: function(callback) {
+      for (index in callbacks) { 
+        if(callbacks[index]==callback) {
+          callbacks.splice(index,1);
+          return true;
+        }
+      }
+      return false;
+    },
+
+    /*
+     * Convenience method for unit testing, allowing the test script to set
+     * internal descriptions. Uncomment for unit testing.
+     * 
+     */
+    //setDescriptions_: function(testDescriptions) {
+    // descriptions=testDescriptions;
+    //},
+    }
+  });
+})();
\ No newline at end of file

Added: shindig/trunk/features/src/test/javascript/features/opensearch/opensearch_test.js
URL: http://svn.apache.org/viewvc/shindig/trunk/features/src/test/javascript/features/opensearch/opensearch_test.js?rev=1139034&view=auto
==============================================================================
--- shindig/trunk/features/src/test/javascript/features/opensearch/opensearch_test.js (added)
+++ shindig/trunk/features/src/test/javascript/features/opensearch/opensearch_test.js Thu Jun 23 18:52:05 2011
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * @fileoverview
+ * 
+ * Unittests for the opensearch feature
+ */
+function OpenSearchTest(name) {
+  TestCase.call(this, name);
+}
+
+OpenSearchTest.inherits(TestCase);
+
+OpenSearchTest.prototype.setUp = function() {
+  this.apiUri = window.__API_URI;
+  window.__API_URI = shindig.uri('http://shindig.com');
+  this.containerUri = window.__CONTAINER_URI;
+  window.__CONTAINER_URI = shindig.uri('http://container.com');
+  this.shindigContainerGadgetSite = osapi.container.GadgetSite;
+  this.shindigContainerUrlSite = osapi.container.UrlSite;
+  this.gadgetsRpc = gadgets.rpc;
+};
+
+OpenSearchTest.prototype.tearDown = function() {
+  window.__API_URI = this.apiUri;
+  window.__CONTAINER_URI = this.containerUri;
+  osapi.container.GadgetSite = this.shindigContainerGadgetSite;
+  osapi.container.UrlSite = this.shindigContainerUrlSite;
+  gadgets.rpc = this.gadgetsRpc;
+};
+
+setDescriptions = function(container) {
+  container.opensearch
+  .setDescriptions_( {
+    "http://hosting.gmodules.com/ig/gadgets/file/109228598702359180066/twitterfinal.xml" : {
+    "OpenSearchDescription" : {
+    "@xmlns" : "http://a9.com/-/spec/opensearch/1.1/",
+    "#text" : [ "\u000a ", "\u000a ", "\u000a ", "\u000a ", "\u000a ",
+                "\u000a ", "\u000a\u000a" ],
+                "ShortName" : "Twitter Search",
+                "Description" : "Realtime Twitter Search",
+                "Url" : {
+    "@type" : "application/atom+xml",
+    "@method" : "get",
+    "@template" : "http://search.twitter.com/search.atom?q={searchTerms}"
+  },
+  "Image" : {
+    "@width" : "16",
+    "@height" : "16",
+    "#text" : "http://search.twitter.com/favicon.png"
+  },
+  "InputEncoding" : "UTF-8",
+  "SearchForm" : "http://search.twitter.com/"
+  }
+  },
+  "http://hosting.gmodules.com/ig/gadgets/file/109228598702359180066/myspacefinal.xml" : {
+    "OpenSearchDescription" : {
+    "@xmlns" : "http://a9.com/-/spec/opensearch/1.1/",
+    "#text" : [ "\u000a ", "\u000a ", "\u000a ", "\u000a ", "\u000a ",
+                "\u000a ", "\u000a" ],
+                "ShortName" : "MySpace Video",
+                "Description" : "Search MySpace videos.",
+                "Tags" : "myspace opensearch search video",
+                "Image" : {
+    "@height" : "16",
+    "@width" : "16",
+    "@type" : "image/x-icon",
+    "#text" : "http://www.myspace.com/favicon.ico"
+  },
+  "Url" : {
+    "@type" : "application/atom+xml",
+    "@xmlns:myspace" : "http://api.myspace.com/-/opensearch/extensions/1.0/",
+    "@template" : "http://api.myspace.com/opensearch/videos?format=xml&searchTerms={searchTerms}"
+  },
+  "Attribution" : "Search data Copyright 2003-2010 MySpace.com. All Rights Reserved."
+  }
+  },
+  "http://hosting.gmodules.com/ig/gadgets/file/109228598702359180066/myspacemultitemplate.xml" : {
+    "OpenSearchDescription" : {
+    "@xmlns" : "http://a9.com/-/spec/opensearch/1.1/",
+    "#text" : [ "\u000a ", "\u000a ", "\u000a ", "\u000a ", "\u000a ",
+                "\u000a ", "\u000a\u000a ", "\u000a ", "\u000a" ],
+                "ShortName" : "MySpace Video",
+                "Description" : "Search MySpace videos.",
+                "Tags" : "myspace opensearch search video",
+                "Image" : {
+    "@height" : "16",
+    "@width" : "16",
+    "@type" : "image/x-icon",
+    "#text" : "http://www.myspace.com/favicon.ico"
+  },
+  "Url" : [
+           {
+             "@type" : "text/html",
+             "@xmlns:myspace" : "http://api.myspace.com/-/opensearch/extensions/1.0/",
+             "@template" : "http://searchservice.myspace.com/index.cfm?fuseaction=sitesearch.results&type=MySpaceTV&qry={searchTerms}&pg={startPage?}"
+           },
+           {
+             "@type" : "application/atom+json",
+             "@xmlns:myspace" : "http://api.myspace.com/-/opensearch/extensions/1.0/",
+             "@template" : "http://api.myspace.com/opensearch/videos?format=json&searchTerms={searchTerms}&count={count?}&startPage={startPage?}&tag={myspace:tag?}&videoMode={myspace:videoMode?}&culture={myspace:culture?}"
+           },
+           {
+             "@type" : "application/atom+xml",
+             "@xmlns:myspace" : "http://api.myspace.com/-/opensearch/extensions/1.0/",
+             "@template" : "http://api.myspace.com/opensearch/videos?format=xml&searchTerms={searchTerms}&count={count?}&startPage={startPage?}&tag={myspace:tag?}&videoMode={myspace:videoMode?}&culture={myspace:culture?}"
+           } ],
+           "Attribution" : "Search data Copyright 2003-2010 MySpace.com. All Rights Reserved."
+  }
+  }
+  });
+}
+
+//The following two methods should be uncommented for unit testing after 
+//uncommenting the setDescriptions method in the opensearch.js code.
+/*OpenSearchTest.prototype.testUrls = function() {
+  this.setupGadgetsRpcRegister();
+  var container = new osapi.container.Container();
+  setDescriptions(container);
+  urls = container.opensearch.getOpenSearchURLs();
+  this.assertEquals('all urls', 5, urls.length);
+  urls = container.opensearch.getOpenSearchURLs("application/atom+xml");
+  this.assertEquals('atom urls', 3, urls.length);
+  urls = container.opensearch.getOpenSearchURLs("text/html");
+  this.assertEquals('text/html urls', 1, urls.length);
+  urls = container.opensearch.getOpenSearchURLs("application/shindig");
+  this.assertEquals('bad type urls', 0, urls.length);
+
+};*/
+
+/*OpenSearchTest.prototype.testDescriptions = function() {
+  this.setupGadgetsRpcRegister();
+  var container = new osapi.container.Container();
+
+  setDescriptions(container);
+
+  descriptions = container.opensearch.getOpenSearchDescriptions();
+  this.assertEquals('all descriptions', 3, descriptions.length);
+  descriptions = container.opensearch
+  .getOpenSearchDescriptions("application/atom+xml");
+  this.assertEquals('atom descriptions', 3, descriptions.length);
+  descriptions = container.opensearch.getOpenSearchDescriptions("text/html");
+  this.assertEquals('text/html descriptions', 1, descriptions.length);
+  descriptions = container.opensearch
+  .getOpenSearchDescriptions("application/shindig");
+  this.assertEquals('bad type descriptions', 0, descriptions.length);
+};*/
+
+OpenSearchTest.prototype.testCallbacks = function() {
+  this.setupGadgetsRpcRegister();
+  var container = new osapi.container.Container();
+  callback = function() {
+  };
+  //add callback to the container
+  container.opensearch.addOpenSearchCallback(callback);
+  //remove callback
+  this.assertTrue(container.opensearch.removeOpenSearchCallback(callback));
+  //check that the removed callback is no longer there. 
+  this.assertFalse(container.opensearch.removeOpenSearchCallback(callback));
+};
+
+OpenSearchTest.prototype.setupGadgetSite = function(id, gadgetInfo,
+    gadgetHolder) {
+  var self = this;
+  osapi.container.GadgetSite = function() {
+    return {
+      'getId' : function() {
+      return id;
+    },
+    'navigateTo' : function(gadgetUrl, viewParams, renderParams, func) {
+      self.site_navigateTo_gadgetUrl = gadgetUrl;
+      self.site_navigateTo_viewParams = viewParams;
+      self.site_navigateTo_renderParams = renderParams;
+      func(gadgetInfo);
+    },
+    'getActiveGadgetHolder' : function() {
+      return gadgetHolder;
+    }
+    };
+  };
+};
+
+OpenSearchTest.prototype.setupGadgetsRpcRegister = function() {
+  gadgets.rpc = {
+      register : function() {
+      }
+  }
+};