Revamp resources listing, part 2 (droiddoc).

- Fix docs navigation JS to correctly handle pages with query strings (i.e. browser.html?tag=article)
- Add resource browser CSS and JS to complement browser.jd in frameworks/base.
- Add microtemplate.js library for ease HTML templates in JS.

Change-Id: I518eeb5fc5a05dc6775eb3870eb88ebb0fc7b72c
diff --git a/tools/droiddoc/templates/assets/android-developer-docs.js b/tools/droiddoc/templates/assets/android-developer-docs.js
index 4c59dd6..d61ce72 100644
--- a/tools/droiddoc/templates/assets/android-developer-docs.js
+++ b/tools/droiddoc/templates/assets/android-developer-docs.js
@@ -27,10 +27,10 @@
 
 var agent = navigator['userAgent'].toLowerCase();
 // If a mobile phone, set flag and do mobile setup
-if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod 
+if ((agent.indexOf("mobile") != -1) ||      // android, iphone, ipod
     (agent.indexOf("blackberry") != -1) ||
     (agent.indexOf("webos") != -1) ||
-    (agent.indexOf("mini") != -1)) {        // opera mini browsers 
+    (agent.indexOf("mini") != -1)) {        // opera mini browsers
   isMobile = true;
   addLoadEvent(mobileSetup);
 // If not a mobile browser, set the onresize event for IE6, and others
@@ -126,7 +126,7 @@
     expiration = date.toGMTString();
   }
   document.cookie = cookie_namespace + section + cookie + "=" + val + "; expires=" + expiration+"; path=/";
-} 
+}
 
 function init() {
   $("#side-nav").css({position:"absolute",left:0});
@@ -162,11 +162,84 @@
     }
   }
 
-  if (devdocNav.length) { // only dev guide and sdk 
-    highlightNav(location.href); 
+  if (devdocNav.length) { // only dev guide, resources, and sdk
+    tryPopulateResourcesNav();
+    highlightNav(location.href);
   }
 }
 
+function tryPopulateResourcesNav() {
+  var sampleList = $('#devdoc-nav-sample-list');
+  var articleList = $('#devdoc-nav-article-list');
+  var tutorialList = $('#devdoc-nav-tutorial-list');
+  var topicList = $('#devdoc-nav-topic-list');
+
+  if (!topicList.length || !ANDROID_TAGS || !ANDROID_RESOURCES)
+    return;
+
+  var topics = [];
+  for (var topic in ANDROID_TAGS['topic']) {
+    topics.push({name:topic,title:ANDROID_TAGS['topic'][topic]});
+  }
+  topics.sort(function(x,y){ return (x.title < y.title) ? -1 : 1; });
+  for (var i = 0; i < topics.length; i++) {
+    topicList.append(
+        $('<li>').append(
+          $('<a>')
+            .attr('href', toRoot + "resources/browser.html?tag=" + topics[i].name)
+            .append($('<span>')
+              .addClass('en')
+              .html(topics[i].title)
+            )
+          )
+        );
+  }
+
+  var _renderResourceList = function(tag, listNode) {
+    var resources = [];
+    var tags;
+    var resource;
+    var i, j;
+    for (i = 0; i < ANDROID_RESOURCES.length; i++) {
+      resource = ANDROID_RESOURCES[i];
+      tags = resource.tags || [];
+      var hasTag = false;
+      for (j = 0; j < tags.length; j++)
+        if (tags[j] == tag) {
+          hasTag = true;
+          break;
+        }
+      if (!hasTag)
+        continue;
+      resources.push(resource);
+    }
+    //resources.sort(function(x,y){ return (x.title.en < y.title.en) ? -1 : 1; });
+    for (i = 0; i < resources.length; i++) {
+      resource = resources[i];
+      var listItemNode = $('<li>').append(
+          $('<a>')
+            .attr('href', toRoot + "resources/" + resource.path)
+            .append($('<span>')
+              .addClass('en')
+              .html(resource.title.en)
+            )
+          );
+      tags = resource.tags || [];
+      for (j = 0; j < tags.length; j++) {
+        if (tags[j] == 'new') {
+          listItemNode.get(0).innerHTML += '&nbsp;<span class="new">new!</span>';
+          break;
+        }
+      }
+      listNode.append(listItemNode);
+    }
+  };
+
+  _renderResourceList('sample', sampleList);
+  _renderResourceList('article', articleList);
+  _renderResourceList('tutorial', tutorialList);
+}
+
 function highlightNav(fullPageName) {
   var lastSlashPos = fullPageName.lastIndexOf("/");
   var firstSlashPos;
@@ -180,17 +253,23 @@
   if (lastSlashPos == (fullPageName.length - 1)) { // if the url ends in slash (add 'index.html')
     fullPageName = fullPageName + "index.html";
   }
-  var htmlPos = fullPageName.lastIndexOf(".html", fullPageName.length);
-  var pathPageName = fullPageName.slice(firstSlashPos, htmlPos + 5);
+  // First check if the exact URL, with query string and all, is in the navigation menu
+  var pathPageName = fullPageName.substr(firstSlashPos);
   var link = $("#devdoc-nav a[href$='"+ pathPageName+"']");
-  if ((link.length == 0) && ((fullPageName.indexOf("/guide/") != -1) || (fullPageName.indexOf("/resources/") != -1))) { 
-// if there's no match, then let's backstep through the directory until we find an index.html page that matches our ancestor directories (only for dev guide)
-    lastBackstep = pathPageName.lastIndexOf("/");
-    while (link.length == 0) {
-      backstepDirectory = pathPageName.lastIndexOf("/", lastBackstep);
-      link = $("#devdoc-nav a[href$='"+ pathPageName.slice(0, backstepDirectory + 1)+"index.html']");
-      lastBackstep = pathPageName.lastIndexOf("/", lastBackstep - 1);
-      if (lastBackstep == 0) break;
+  if (link.length == 0) {
+    var htmlPos = fullPageName.lastIndexOf(".html", fullPageName.length);
+    pathPageName = fullPageName.slice(firstSlashPos, htmlPos + 5); // +5 advances past ".html"
+    link = $("#devdoc-nav a[href$='"+ pathPageName+"']");
+    if ((link.length == 0) && ((fullPageName.indexOf("/guide/") != -1) || (fullPageName.indexOf("/resources/") != -1))) {
+      // if there's no match, then let's backstep through the directory until we find an index.html page
+      // that matches our ancestor directories (only for dev guide and resources)
+      lastBackstep = pathPageName.lastIndexOf("/");
+      while (link.length == 0) {
+        backstepDirectory = pathPageName.lastIndexOf("/", lastBackstep);
+        link = $("#devdoc-nav a[href$='"+ pathPageName.slice(0, backstepDirectory + 1)+"index.html']");
+        lastBackstep = pathPageName.lastIndexOf("/", lastBackstep - 1);
+        if (lastBackstep == 0) break;
+      }
     }
   }
 
@@ -436,12 +515,12 @@
 
 function changeTabLang(lang) {
   var nodes = $("#header-tabs").find("."+lang);
-  for (i=0; i < nodes.length; i++) { // for each node in this language 
+  for (i=0; i < nodes.length; i++) { // for each node in this language
     var node = $(nodes[i]);
-    node.siblings().css("display","none"); // hide all siblings 
-    if (node.not(":empty").length != 0) { //if this languages node has a translation, show it 
+    node.siblings().css("display","none"); // hide all siblings
+    if (node.not(":empty").length != 0) { //if this languages node has a translation, show it
       node.css("display","inline");
-    } else { //otherwise, show English instead 
+    } else { //otherwise, show English instead
       node.css("display","none");
       node.siblings().filter(".en").css("display","inline");
     }
@@ -450,12 +529,12 @@
 
 function changeNavLang(lang) {
   var nodes = $("#side-nav").find("."+lang);
-  for (i=0; i < nodes.length; i++) { // for each node in this language 
+  for (i=0; i < nodes.length; i++) { // for each node in this language
     var node = $(nodes[i]);
-    node.siblings().css("display","none"); // hide all siblings 
-    if (node.not(":empty").length != 0) { // if this languages node has a translation, show it 
+    node.siblings().css("display","none"); // hide all siblings
+    if (node.not(":empty").length != 0) { // if this languages node has a translation, show it
       node.css("display","inline");
-    } else { // otherwise, show English instead 
+    } else { // otherwise, show English instead
       node.css("display","none");
       node.siblings().filter(".en").css("display","inline");
     }
diff --git a/tools/droiddoc/templates/assets/android-developer-resource-browser.css b/tools/droiddoc/templates/assets/android-developer-resource-browser.css
new file mode 100644
index 0000000..a454caa
--- /dev/null
+++ b/tools/droiddoc/templates/assets/android-developer-resource-browser.css
@@ -0,0 +1,31 @@
+/* Resource Browser */
+
+#resource-browser-results .no-results {
+  font-style: italic;
+  display: none;
+}
+
+#resource-browser-results .result {
+  position: relative;
+  padding-left: 84px;
+  background: transparent none no-repeat scroll 4px 12px;
+  border-bottom: 1px solid #ddd;
+}
+
+#resource-browser-results .tagged-article {
+  background-image: url(images/resource-article.png);
+}
+
+#resource-browser-results .tagged-sample {
+  background-image: url(images/resource-sample.png);
+}
+
+#resource-browser-results .tagged-tutorial {
+  background-image: url(images/resource-tutorial.png);
+}
+
+#resource-browser-results .resource-meta {
+  margin-top: -1em;
+  font-size: 0.85em;
+  font-weight: normal;
+}
diff --git a/tools/droiddoc/templates/assets/android-developer-resource-browser.js b/tools/droiddoc/templates/assets/android-developer-resource-browser.js
new file mode 100644
index 0000000..dc65aa2
--- /dev/null
+++ b/tools/droiddoc/templates/assets/android-developer-resource-browser.js
@@ -0,0 +1,235 @@
+(function() { // anonymize
+
+var allTags = {};
+var loadedResults = [];
+
+/**
+ * Initialization code run upon the DOM being ready.
+ */
+$(document).ready(function() {
+  // Parse page query parameters.
+  var params = parseParams(document.location.search);
+  params.tag = params.tag ? makeArray(params.tag) : null;
+
+  // Load tag and resource dataset.
+  loadTags();
+  loadResources();
+
+  showResults(params);
+
+  // Watch for keypresses in the keyword filter textbox, and update
+  // search results to reflect the keyword filter.
+  $('#resource-browser-keyword-filter').keyup(function() {
+    // Filter results on screen by keyword.
+    var keywords = $(this).val().split(/\s+/g);
+    for (var i = 0; i < loadedResults.length; i++) {
+      var hide = false;
+      for (var j = 0; j < keywords.length; j++) {
+        if (!resultMatchesKeyword(loadedResults[i].result, keywords[j])) {
+          hide = true;
+          break;
+        }
+      }
+
+      loadedResults[i].node[hide ? 'hide' : 'show']();
+    }
+  });
+});
+
+/**
+ * Returns whether or not the given search result contains the given keyword.
+ */
+function resultMatchesKeyword(result, keyword) {
+  keyword = keyword.toLowerCase();
+  if (result.title &&
+      result.title.en.toLowerCase().indexOf(keyword) >= 0)
+    return true;
+  else if (result.description &&
+           result.description.en.toLowerCase().indexOf(keyword) >= 0)
+    return true;
+  else if (result.topicsHtml &&
+           result.topicsHtml.replace(/\<.*?\>/g,'').toLowerCase().indexOf(keyword) >= 0)
+    return true;
+  return false;
+}
+
+/**
+ * Populates the allTags array with tag data from the ANDROID_TAGS
+ * variable in the resource data JS file.
+ */
+function loadTags() {
+  for (var tagClass in ANDROID_TAGS) {
+    for (var tag in ANDROID_TAGS[tagClass]) {
+      allTags[tag] = {
+        displayTag: ANDROID_TAGS[tagClass][tag],
+        tagClass: tagClass
+      };
+    }
+  }
+}
+
+/**
+ * Massage the ANDROID_RESOURCES resource list in the resource data JS file.
+ */
+function loadResources() {
+  for (var i = 0; i < ANDROID_RESOURCES.length; i++) {
+    var resource = ANDROID_RESOURCES[i];
+
+    // Convert the tags array to a tags hash for easier querying.
+    resource.tagsHash = {};
+    for (var j = 0; j < resource.tags.length; j++)
+      resource.tagsHash[resource.tags[j]] = true;
+
+    // Determine the type and topics of the resource by inspecting its tags.
+    resource.topics = [];
+    for (tag in resource.tagsHash)
+      if (tag in allTags) {
+        if (allTags[tag].tagClass == 'type') {
+          resource.type = tag;
+        } else if (allTags[tag].tagClass == 'topic') {
+          resource.topics.push(tag);
+        }
+      }
+
+    // Add a humanized topics list string.
+    resource.topicsHtml = humanizeList(resource.topics, function(item) {
+      return '<strong>' + allTags[item].displayTag + '</strong>';
+    });
+  }
+}
+
+/**
+ * Loads resources for the given query parameters.
+ */
+function showResults(params) {
+  loadedResults = [];
+  $('#resource-browser-search-params').empty();
+  $('#resource-browser-results').empty();
+
+  var i, j;
+  var searchTags = [];
+  if (params.tag) {
+    for (i = 0; i < params.tag.length; i++) {
+      var tag = params.tag[i];
+      if (tag.toLowerCase() in allTags) {
+        searchTags.push(tag.toLowerCase());
+      }
+    }
+  }
+
+  if (searchTags.length) {
+    // Show query params.
+    var taggedWithHtml = ['Showing technical resources tagged with '];
+    taggedWithHtml.push(humanizeList(searchTags, function(item) {
+      return '<strong>' + allTags[item].displayTag + '</strong>';
+    }));
+    $('#resource-browser-search-params').html(taggedWithHtml.join('') + ':');
+  } else {
+    $('#resource-browser-search-params').html('Showing all technical resources:');
+  }
+
+  var results = [];
+
+  // Create the list of resources to show.
+  for (i = 0; i < ANDROID_RESOURCES.length; i++) {
+    var resource = ANDROID_RESOURCES[i];
+    var skip = false;
+
+    if (searchTags.length) {
+      for (j = 0; j < searchTags.length; j++)
+        if (!(searchTags[j] in resource.tagsHash)) {
+          skip = true;
+          break;
+        }
+
+      if (skip)
+        continue;
+
+      results.push(resource);
+      continue;
+    }
+
+    results.push(resource);
+  }
+
+  // Format and show the list of resource results.
+  if (results.length) {
+    $('#resource-browser-results .no-results').hide();
+    for (i = 0; i < results.length; i++) {
+      var result = results[i];
+      var resultJqNode = $(tmpl('tmpl_resource_browser_result', result));
+      for (tag in result.tagsHash)
+        resultJqNode.addClass('tagged-' + tag);
+      $('#resource-browser-results').append(resultJqNode);
+
+      loadedResults.push({ node: resultJqNode, result: result });
+    }
+  } else {
+    $('#resource-browser-results .no-results').show();
+  }
+}
+
+/**
+ * Formats the given array into a human readable, English string, ala
+ * 'a, b and c', with an optional item formatter/wrapper function.
+ */
+function humanizeList(arr, itemFormatter) {
+  itemFormatter = itemFormatter || function(o){ return o; };
+  arr = arr || [];
+
+  var out = [];
+  for (var i = 0; i < arr.length; i++) {
+    out.push(itemFormatter(arr[i]) +
+        ((i < arr.length - 2) ? ', ' : '') +
+        ((i == arr.length - 2) ? ' and ' : ''));
+  }
+
+  return out.join('');
+}
+
+/**
+ * Parses a parameter string, i.e. foo=1&bar=2 into
+ * a dictionary object.
+ */
+function parseParams(paramStr) {
+  var params = {};
+  paramStr = paramStr.replace(/^[?#]/, '');
+
+  var pairs = paramStr.split('&');
+  for (var i = 0; i < pairs.length; i++) {
+    var p = pairs[i].split('=');
+    var key = p[0] ? decodeURIComponent(p[0]) : p[0];
+    var val = p[1] ? decodeURIComponent(p[1]) : p[1];
+    if (val === '0')
+      val = 0;
+    if (val === '1')
+      val = 1;
+
+    if (key in params) {
+      // Handle array values.
+      params[key] = makeArray(params[key]);
+      params[key].push(val);
+    } else {
+      params[key] = val;
+    }
+  }
+
+  return params;
+}
+
+/**
+ * Returns the argument as a single-element array, or the argument itself
+ * if it's already an array.
+ */
+function makeArray(o) {
+  if (!o)
+    return [];
+
+  if (typeof o === 'object' && 'splice' in o) {
+    return o;
+  } else {
+    return [o];
+  }
+}
+
+})();
diff --git a/tools/droiddoc/templates/assets/images/resource-article.png b/tools/droiddoc/templates/assets/images/resource-article.png
new file mode 100644
index 0000000..416493f
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-article.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-big-article.png b/tools/droiddoc/templates/assets/images/resource-big-article.png
new file mode 100644
index 0000000..7273275
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-big-article.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-big-sample.png b/tools/droiddoc/templates/assets/images/resource-big-sample.png
new file mode 100644
index 0000000..59b6b68
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-big-sample.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-big-tutorial.png b/tools/droiddoc/templates/assets/images/resource-big-tutorial.png
new file mode 100644
index 0000000..c32e89a
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-big-tutorial.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-big-video.png b/tools/droiddoc/templates/assets/images/resource-big-video.png
new file mode 100644
index 0000000..59d46a0
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-big-video.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-sample.png b/tools/droiddoc/templates/assets/images/resource-sample.png
new file mode 100644
index 0000000..f7a411c
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-sample.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-tutorial.png b/tools/droiddoc/templates/assets/images/resource-tutorial.png
new file mode 100644
index 0000000..10a14fe
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-tutorial.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/images/resource-video.png b/tools/droiddoc/templates/assets/images/resource-video.png
new file mode 100644
index 0000000..8fd5cae
--- /dev/null
+++ b/tools/droiddoc/templates/assets/images/resource-video.png
Binary files differ
diff --git a/tools/droiddoc/templates/assets/microtemplate.js b/tools/droiddoc/templates/assets/microtemplate.js
new file mode 100644
index 0000000..ada1235
--- /dev/null
+++ b/tools/droiddoc/templates/assets/microtemplate.js
@@ -0,0 +1,35 @@
+// Simple JavaScript Templating
+// John Resig - http://ejohn.org/ - MIT Licensed
+(function(){
+  var cache = {};
+
+  this.tmpl = function tmpl(str, data){
+    // Figure out if we're getting a template, or if we need to
+    // load the template - and be sure to cache the result.
+    var fn = !/\W/.test(str) ?
+      cache[str] = cache[str] ||
+        tmpl(document.getElementById(str).innerHTML) :
+
+      // Generate a reusable function that will serve as a template
+      // generator (and which will be cached).
+      new Function("obj",
+        "var p=[],print=function(){p.push.apply(p,arguments);};" +
+
+        // Introduce the data as local variables using with(){}
+        "with(obj){p.push('" +
+
+        // Convert the template into pure JavaScript
+        str
+          .replace(/[\r\t\n]/g, " ")
+          .split("<%").join("\t")
+          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
+          .replace(/\t=(.*?)%>/g, "',$1,'")
+          .split("\t").join("');")
+          .split("%>").join("p.push('")
+          .split("\r").join("\\'")
+      + "');}return p.join('');");
+
+    // Provide some basic currying to the user
+    return data ? fn( data ) : fn;
+  };
+})();
\ No newline at end of file
diff --git a/tools/droiddoc/templates/head_tag.cs b/tools/droiddoc/templates/head_tag.cs
index 5a7fd40..b418e1e 100644
--- a/tools/droiddoc/templates/head_tag.cs
+++ b/tools/droiddoc/templates/head_tag.cs
@@ -20,6 +20,9 @@
 if:reference ?>
 <script src="<?cs var:toroot ?>assets/android-developer-reference.js" type="text/javascript"></script>
 <script src="<?cs var:toroot ?>navtree_data.js" type="text/javascript"></script><?cs 
+/if ?><?cs 
+if:resources ?>
+<script src="<?cs var:toroot ?>resources/resources-data.js" type="text/javascript"></script><?cs 
 /if ?>
 <noscript>
   <style type="text/css">