Commits

Marco Yuen committed 0882582

Replace pure with mustache. Change jqm css back to remote.

  • Participants
  • Parent commits 31e5333

Comments (0)

Files changed (6)

resources/public/js/mustache-0.5.1-dev.js

+/*!
+ * mustache.js - Logic-less {{mustache}} templates with JavaScript
+ * http://github.com/janl/mustache.js
+ */
+var Mustache = (typeof module !== "undefined" && module.exports) || {};
+
+(function (exports) {
+
+  exports.name = "mustache.js";
+  exports.version = "0.5.1-dev";
+  exports.tags = ["{{", "}}"];
+
+  exports.parse = parse;
+  exports.clearCache = clearCache;
+  exports.compile = compile;
+  exports.compilePartial = compilePartial;
+  exports.render = render;
+
+  exports.Scanner = Scanner;
+  exports.Context = Context;
+  exports.Renderer = Renderer;
+
+  // This is here for backwards compatibility with 0.4.x.
+  exports.to_html = function (template, view, partials, send) {
+    var result = render(template, view, partials);
+
+    if (typeof send === "function") {
+      send(result);
+    } else {
+      return result;
+    }
+  };
+
+  var whiteRe = /\s*/;
+  var spaceRe = /\s+/;
+  var nonSpaceRe = /\S/;
+  var eqRe = /\s*=/;
+  var curlyRe = /\s*\}/;
+  var tagRe = /#|\^|\/|>|\{|&|=|!/;
+
+  // Workaround for https://issues.apache.org/jira/browse/COUCHDB-577
+  // See https://github.com/janl/mustache.js/issues/189
+  function testRe(re, string) {
+    return RegExp.prototype.test.call(re, string);
+  }
+
+  function isWhitespace(string) {
+    return !testRe(nonSpaceRe, string);
+  }
+
+  var isArray = Array.isArray || function (obj) {
+    return Object.prototype.toString.call(obj) === "[object Array]";
+  };
+
+  // OSWASP Guidlines: escape all non alphanumeric characters in ASCII space.
+  var jsCharsRe = /[\x00-\x2F\x3A-\x40\x5B-\x60\x7B-\xFF\u2028\u2029]/gm;
+
+  function quote(text) {
+    var escaped = text.replace(jsCharsRe, function (c) {
+      return "\\u" + ('0000' + c.charCodeAt(0).toString(16)).slice(-4);
+    });
+
+    return '"' + escaped + '"';
+  }
+
+  function escapeRe(string) {
+    return string.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+  }
+
+  var entityMap = {
+    "&": "&",
+    "<": "&lt;",
+    ">": "&gt;",
+    '"': '&quot;',
+    "'": '&#39;',
+    "/": '&#x2F;'
+  };
+
+  function escapeHtml(string) {
+    return String(string).replace(/[&<>"'\/]/g, function (s) {
+      return entityMap[s];
+    });
+  }
+
+  // Export these utility functions.
+  exports.isWhitespace = isWhitespace;
+  exports.isArray = isArray;
+  exports.quote = quote;
+  exports.escapeRe = escapeRe;
+  exports.escapeHtml = escapeHtml;
+
+  function Scanner(string) {
+    this.string = string;
+    this.tail = string;
+    this.pos = 0;
+  }
+
+  /**
+   * Returns `true` if the tail is empty (end of string).
+   */
+  Scanner.prototype.eos = function () {
+    return this.tail === "";
+  };
+
+  /**
+   * Tries to match the given regular expression at the current position.
+   * Returns the matched text if it can match, `null` otherwise.
+   */
+  Scanner.prototype.scan = function (re) {
+    var match = this.tail.match(re);
+
+    if (match && match.index === 0) {
+      this.tail = this.tail.substring(match[0].length);
+      this.pos += match[0].length;
+      return match[0];
+    }
+
+    return null;
+  };
+
+  /**
+   * Skips all text until the given regular expression can be matched. Returns
+   * the skipped string, which is the entire tail of this scanner if no match
+   * can be made.
+   */
+  Scanner.prototype.scanUntil = function (re) {
+    var match, pos = this.tail.search(re);
+
+    switch (pos) {
+    case -1:
+      match = this.tail;
+      this.pos += this.tail.length;
+      this.tail = "";
+      break;
+    case 0:
+      match = null;
+      break;
+    default:
+      match = this.tail.substring(0, pos);
+      this.tail = this.tail.substring(pos);
+      this.pos += pos;
+    }
+
+    return match;
+  };
+
+  function Context(view, parent) {
+    this.view = view;
+    this.parent = parent;
+    this.clearCache();
+  }
+
+  Context.make = function (view) {
+    return (view instanceof Context) ? view : new Context(view);
+  };
+
+  Context.prototype.clearCache = function () {
+    this._cache = {};
+  };
+
+  Context.prototype.push = function (view) {
+    return new Context(view, this);
+  };
+
+  Context.prototype.lookup = function (name) {
+    var value = this._cache[name];
+
+    if (!value) {
+      if (name === ".") {
+        value = this.view;
+      } else {
+        var context = this;
+
+        while (context) {
+          if (name.indexOf(".") > 0) {
+            var names = name.split("."), i = 0;
+
+            value = context.view;
+
+            while (value && i < names.length) {
+              value = value[names[i++]];
+            }
+          } else {
+            value = context.view[name];
+          }
+
+          if (value != null) {
+            break;
+          }
+
+          context = context.parent;
+        }
+      }
+
+      this._cache[name] = value;
+    }
+
+    if (typeof value === "function") {
+      value = value.call(this.view);
+    }
+
+    return value;
+  };
+
+  function Renderer() {
+    this.clearCache();
+  }
+
+  Renderer.prototype.clearCache = function () {
+    this._cache = {};
+    this._partialCache = {};
+  };
+
+  Renderer.prototype.compile = function (tokens, tags) {
+    var fn = compileTokens(tokens),
+        self = this;
+
+    return function (view) {
+      return fn(Context.make(view), self);
+    };
+  };
+
+  Renderer.prototype.compilePartial = function (name, tokens, tags) {
+    this._partialCache[name] = this.compile(tokens, tags);
+    return this._partialCache[name];
+  };
+
+  Renderer.prototype.render = function (template, view) {
+    var fn = this._cache[template];
+
+    if (!fn) {
+      fn = this.compile(template);
+      this._cache[template] = fn;
+    }
+
+    return fn(view);
+  };
+
+  Renderer.prototype._section = function (name, context, callback) {
+    var value = context.lookup(name);
+
+    switch (typeof value) {
+    case "object":
+      if (isArray(value)) {
+        var buffer = "";
+        for (var i = 0, len = value.length; i < len; ++i) {
+          buffer += callback(context.push(value[i]), this);
+        }
+        return buffer;
+      } else {
+        return callback(context.push(value), this);
+      }
+      break;
+    case "function":
+      var sectionText = callback(context, this), self = this;
+      var scopedRender = function (template) {
+        return self.render(template, context);
+      };
+      return value.call(context.view, sectionText, scopedRender) || "";
+      break;
+    default:
+      if (value) {
+        return callback(context, this);
+      }
+    }
+
+    return "";
+  };
+
+  Renderer.prototype._inverted = function (name, context, callback) {
+    var value = context.lookup(name);
+
+    // From the spec: inverted sections may render text once based on the
+    // inverse value of the key. That is, they will be rendered if the key
+    // doesn't exist, is false, or is an empty list.
+    if (value == null || value === false || (isArray(value) && value.length === 0)) {
+      return callback(context, this);
+    }
+
+    return "";
+  };
+
+  Renderer.prototype._partial = function (name, context) {
+    var fn = this._partialCache[name];
+
+    if (fn) {
+      return fn(context, this);
+    }
+
+    return "";
+  };
+
+  Renderer.prototype._name = function (name, context, escape) {
+    var value = context.lookup(name);
+
+    if (typeof value === "function") {
+      value = value.call(context.view);
+    }
+
+    var string = (value == null) ? "" : String(value);
+
+    if (escape) {
+      return escapeHtml(string);
+    }
+
+    return string;
+  };
+
+  /**
+   * Low-level function that compiles the given `tokens` into a
+   * function that accepts two arguments: a Context and a
+   * Renderer. Returns the body of the function as a string if
+   * `returnBody` is true.
+   */
+  function compileTokens(tokens, returnBody) {
+    if (typeof tokens === "string") {
+      tokens = parse(tokens);
+    }
+
+    var body = ['""'];
+    var token, method, escape;
+
+    for (var i = 0, len = tokens.length; i < len; ++i) {
+      token = tokens[i];
+
+      switch (token.type) {
+      case "#":
+      case "^":
+        method = (token.type === "#") ? "_section" : "_inverted";
+        body.push("r." + method + "(" + quote(token.value) + ", c, function (c, r) {\n" +
+          "  " + compileTokens(token.tokens, true) + "\n" +
+          "})");
+        break;
+      case "{":
+      case "&":
+      case "name":
+        escape = token.type === "name" ? "true" : "false";
+        body.push("r._name(" + quote(token.value) + ", c, " + escape + ")");
+        break;
+      case ">":
+        body.push("r._partial(" + quote(token.value) + ", c)");
+        break;
+      case "text":
+        body.push(quote(token.value));
+        break;
+      }
+    }
+
+    // Convert to a string body.
+    body = "return " + body.join(" + ") + ";";
+
+    // Good for debugging.
+    // console.log(body);
+
+    if (returnBody) {
+      return body;
+    }
+
+    // For great evil!
+    return new Function("c, r", body);
+  }
+
+  function escapeTags(tags) {
+    if (tags.length === 2) {
+      return [
+        new RegExp(escapeRe(tags[0]) + "\\s*"),
+        new RegExp("\\s*" + escapeRe(tags[1]))
+      ];
+    }
+
+    throw new Error("Invalid tags: " + tags.join(" "));
+  }
+
+  /**
+   * Forms the given linear array of `tokens` into a nested tree structure
+   * where tokens that represent a section have a "tokens" array property
+   * that contains all tokens that are in that section.
+   */
+  function nestTokens(tokens) {
+    var tree = [];
+    var collector = tree;
+    var sections = [];
+    var token, section;
+
+    for (var i = 0; i < tokens.length; ++i) {
+      token = tokens[i];
+
+      switch (token.type) {
+      case "#":
+      case "^":
+        token.tokens = [];
+        sections.push(token);
+        collector.push(token);
+        collector = token.tokens;
+        break;
+      case "/":
+        if (sections.length === 0) {
+          throw new Error("Unopened section: " + token.value);
+        }
+
+        section = sections.pop();
+
+        if (section.value !== token.value) {
+          throw new Error("Unclosed section: " + section.value);
+        }
+
+        if (sections.length > 0) {
+          collector = sections[sections.length - 1].tokens;
+        } else {
+          collector = tree;
+        }
+        break;
+      default:
+        collector.push(token);
+      }
+    }
+
+    // Make sure there were no open sections when we're done.
+    section = sections.pop();
+
+    if (section) {
+      throw new Error("Unclosed section: " + section.value);
+    }
+
+    return tree;
+  }
+
+  /**
+   * Combines the values of consecutive text tokens in the given `tokens` array
+   * to a single token.
+   */
+  function squashTokens(tokens) {
+    var lastToken;
+
+    for (var i = 0; i < tokens.length; ++i) {
+      token = tokens[i];
+
+      if (lastToken && lastToken.type === "text" && token.type === "text") {
+        lastToken.value += token.value;
+        tokens.splice(i--, 1); // Remove this token from the array.
+      } else {
+        lastToken = token;
+      }
+    }
+  }
+
+  /**
+   * Breaks up the given `template` string into a tree of token objects. If
+   * `tags` is given here it must be an array with two string values: the
+   * opening and closing tags used in the template (e.g. ["<%", "%>"]). Of
+   * course, the default is to use mustaches (i.e. Mustache.tags).
+   */
+  function parse(template, tags) {
+    tags = tags || exports.tags;
+    tagRes = escapeTags(tags);
+
+    var scanner = new Scanner(template);
+
+    var tokens = [],      // Buffer to hold the tokens
+        spaces = [],      // Indices of whitespace tokens on the current line
+        hasTag = false,   // Is there a {{tag}} on the current line?
+        nonSpace = false; // Is there a non-space char on the current line?
+
+    // Strips all whitespace tokens array for the current line
+    // if there was a {{#tag}} on it and otherwise only space.
+    var stripSpace = function () {
+      if (hasTag && !nonSpace) {
+        while (spaces.length) {
+          tokens.splice(spaces.pop(), 1);
+        }
+      } else {
+        spaces = [];
+      }
+
+      hasTag = false;
+      nonSpace = false;
+    };
+
+    var type, value, chr;
+
+    while (!scanner.eos()) {
+      value = scanner.scanUntil(tagRes[0]);
+
+      if (value) {
+        for (var i = 0, len = value.length; i < len; ++i) {
+          chr = value[i];
+
+          if (isWhitespace(chr)) {
+            spaces.push(tokens.length);
+          } else {
+            nonSpace = true;
+          }
+
+          tokens.push({type: "text", value: chr});
+
+          if (chr === "\n") {
+            stripSpace(); // Check for whitespace on the current line.
+          }
+        }
+      }
+
+      // Match the opening tag.
+      if (!scanner.scan(tagRes[0])) {
+        break;
+      }
+
+      hasTag = true;
+      type = scanner.scan(tagRe) || "name";
+
+      // Skip any whitespace between tag and value.
+      scanner.scan(whiteRe);
+
+      // Extract the tag value.
+      if (type === "=") {
+        value = scanner.scanUntil(eqRe);
+        scanner.scan(eqRe);
+        scanner.scanUntil(tagRes[1]);
+      } else if (type === "{") {
+        var closeRe = new RegExp("\\s*" + escapeRe("}" + tags[1]));
+        value = scanner.scanUntil(closeRe);
+        scanner.scan(curlyRe);
+        scanner.scanUntil(tagRes[1]);
+      } else {
+        value = scanner.scanUntil(tagRes[1]);
+      }
+
+      // Match the closing tag.
+      if (!scanner.scan(tagRes[1])) {
+        throw new Error("Unclosed tag at " + scanner.pos);
+      }
+
+      tokens.push({type: type, value: value});
+
+      if (type === "name" || type === "{" || type === "&") {
+        nonSpace = true;
+      }
+
+      // Set the tags for the next time around.
+      if (type === "=") {
+        tags = value.split(spaceRe);
+        tagRes = escapeTags(tags);
+      }
+    }
+
+    squashTokens(tokens);
+
+    return nestTokens(tokens);
+  }
+
+  // The high-level clearCache, compile, compilePartial, and render functions
+  // use this default renderer.
+  var _renderer = new Renderer;
+
+  /**
+   * Clears all cached templates and partials.
+   */
+  function clearCache() {
+    _renderer.clearCache();
+  }
+
+  /**
+   * High-level API for compiling the given `tokens` down to a reusable
+   * function. If `tokens` is a string it will be parsed using the given `tags`
+   * before it is compiled.
+   */
+  function compile(tokens, tags) {
+    return _renderer.compile(tokens, tags);
+  }
+
+  /**
+   * High-level API for compiling the `tokens` for the partial with the given
+   * `name` down to a reusable function. If `tokens` is a string it will be
+   * parsed using the given `tags` before it is compiled.
+   */
+  function compilePartial(name, tokens, tags) {
+    return _renderer.compilePartial(name, tokens, tags);
+  }
+
+  /**
+   * High-level API for rendering the `template` using the given `view`. The
+   * optional `partials` object may be given here for convenience, but note that
+   * it will cause all partials to be re-compiled, thus hurting performance. Of
+   * course, this only matters if you're going to render the same template more
+   * than once. If so, it is best to call `compilePartial` before calling this
+   * function and to leave the `partials` argument blank.
+   */
+  function render(template, view, partials) {
+    if (partials) {
+      for (var name in partials) {
+        compilePartial(name, partials[name]);
+      }
+    }
+
+    return _renderer.render(template, view);
+  }
+
+})(Mustache);

resources/public/js/nextrain.coffee

 
 nextrainapp = {}
 ((app) ->
+    app.templates = {
+      'stopslistTemplate':
+        "{{#stop}}<li><a href='{{stop_url}}'>{{stop_name}}</a></li>{{/stop}}"
+      'timeslistTemplate':
+        "<li>{{trip_headsign}}<span class='ui-li-aside'>{{departure_time}}</span></li>"
+    }
+
     app.locSuccess = (position)->
         console.log position
         $.post('/findstops',
                {lat: position.coords.latitude, lon: position.coords.longitude},
                (data, status, jqxhr) ->
-                   $('#stops-list-template').autoRender(data)
+                   $stopsList = $('#stops-list')
+                   content = Mustache.render(app.templates['stopslistTemplate'], data)
+                   $stopsList.append(content)
+                   $stopsList.listview('refresh')
                    console.log(data))
 
     app.locError = (error) ->
                         $timesList = $('#times-list')
                         for time in data.times
                           do (time) ->
-                            $timesList.append("<li>" + time.trip_headsign +
-                                              "<span class='ui-li-aside'>" +
-                                              time.departure_time + "</span></li>")
+                            content = Mustache.render(app.templates['timeslistTemplate'], time)
+                            $timesList.append(content)
                         $timesList.listview('refresh')
                       )
         )

resources/public/js/nextrain.js

   nextrainapp = {};
 
   (function(app) {
+    app.templates = {
+      'stopslistTemplate': "{{#stop}}<li><a href='{{stop_url}}'>{{stop_name}}</a></li>{{/stop}}",
+      'timeslistTemplate': "<li>{{trip_headsign}}<span class='ui-li-aside'>{{departure_time}}</span></li>"
+    };
     app.locSuccess = function(position) {
       console.log(position);
       return $.post('/findstops', {
         lat: position.coords.latitude,
         lon: position.coords.longitude
       }, function(data, status, jqxhr) {
-        $('#stops-list-template').autoRender(data);
+        var $stopsList, content;
+        $stopsList = $('#stops-list');
+        content = Mustache.render(app.templates['stopslistTemplate'], data);
+        $stopsList.append(content);
+        $stopsList.listview('refresh');
         return console.log(data);
       });
     };
           $timesList = $('#times-list');
           _ref = data.times;
           _fn = function(time) {
-            return $timesList.append("<li>" + time.trip_headsign + "<span class='ui-li-aside'>" + time.departure_time + "</span></li>");
+            var content;
+            content = Mustache.render(app.templates['timeslistTemplate'], time);
+            return $timesList.append(content);
           };
           for (_i = 0, _len = _ref.length; _i < _len; _i++) {
             time = _ref[_i];

resources/public/js/pure_min.js

-/*!
-	PURE Unobtrusive Rendering Engine for HTML
-
-	Licensed under the MIT licenses.
-	More information at: http://www.opensource.org
-
-	Copyright (c) 2012 Michael Cvilic - BeeBole.com
-
-	Thanks to Rog Peppe for the functional JS jump
-	revision: 2.76
-*/
-var $p,pure=$p=function(d,h){var n=d,s=false;if(typeof n==="string")s=h||false;else if(n&&!n[0]&&!n.length)n=[n];return $p.core(n,s)};
-$p.core=function(d,h,n){function s(a){if(typeof console!=="undefined"){console.log(a);debugger}throw"pure error: "+a;}function O(){var a=$p.plugins,b=function(){};b.prototype=a;b.prototype.compile=a.compile||P;b.prototype.render=a.render||Q;b.prototype.autoRender=a.autoRender||R;b.prototype.find=a.find||S;b.prototype._compiler=B;b.prototype._error=s;return new b}function G(a){return a.outerHTML||function(b){var f=document.createElement("div");f.appendChild(b.cloneNode(true));return f.innerHTML}(a)}
-function C(a,b){return function(f){return a(""+b.call(f.item||f.context,f))}}function S(a,b){if(typeof a==="string"){b=a;a=false}return typeof document.querySelectorAll!=="undefined"?(a||document).querySelectorAll(b):s("You can test PURE standalone with: iPhone, FF3.5+, Safari4+ and IE8+\n\nTo run PURE on your browser, you need a JS library/framework with a CSS selector engine")}function H(a,b){return function(f){var c=[a[0]],g=a.length,i,k,l,e;try{for(var o=1;o<g;o++){i=b[o].call(this,f);k=a[o];
-if(i===""){l=c[c.length-1];if((e=l.search(/[^\s]+=\"?$/))>-1){c[c.length-1]=l.substring(0,e);k=k.substr(1)}}c[c.length]=i;c[c.length]=k}return c.join("")}catch(m){if(console&&console.log)console.log(m.stack?m.stack:m.message+" ("+m.type+", "+m.arguments.join("-")+"). Use Firefox or Chromium/Chrome to get a full stack of the error. ");return""}}}function T(a){var b=a.match(/^(\w+)\s*<-\s*(\S+)?$/);b===null&&s('bad loop spec: "'+a+'"');b[1]==="item"&&s('"item<-..." is a reserved word for the current running iteration.\n\nPlease choose another name for your loop.');
-if(!b[2]||b[2].toLowerCase()==="context")b[2]=function(f){return f.context};else if(b[2]&&b[2].indexOf("context")===0)b[2]=x(b[2].replace(/^context\.?/,""));return{name:b[1],sel:b[2]}}function x(a){if(typeof a==="function")return function(e){e=a.call(e.item||e.context||e,e);return!e&&e!==0?"":e};var b=a.match(/^[\da-zA-Z\$_\@][\w\$:-]*(\.[\w\$:-]*[^\.])*$/);if(b===null){var f=false,c=a,g=[],i=[],k=0,l;if(/\'|\"/.test(c.charAt(0))){if(/\'|\"/.test(c.charAt(c.length-1))){l=c.substring(1,c.length-1);
-return function(){return l}}}else for(;(b=c.match(/#\{([^{}]+)\}/))!==null;){f=true;g[k++]=c.slice(0,b.index);i[k]=x(b[1]);c=c.slice(b.index+b[0].length,c.length)}if(!f)return function(){return a};g[k]=c;return H(g,i)}b=a.split(".");return function(e){var o=e.context||e,m=e[b[0]];e=0;if(m&&typeof m.item!=="undefined"){e+=1;if(b[e]==="pos")return m.pos;else o=m.item}m=b.length;for(var t;e<m;e++){if(!o)break;t=o[b[e]];o=typeof t==="function"?o[b[e]].call(o):t}return!o&&o!==0?"":o}}function D(a,b,f){var c,
-g,i,k,l,e=[];if(typeof b==="string"){c=b;(l=b.match(I))||s("bad selector syntax: "+b);g=l[1];i=l[2];k=l[3];l=l[4];if(i==="."||!i&&k)e[0]=a;else e=n.find(a,i);if(!e||e.length===0)return s('The node "'+b+'" was not found in the template:\n'+G(a).replace(/\t/g,"  "))}else{g=b.prepend;k=b.attr;l=b.append;e=[a]}if(g||l)if(g&&l)s("append/prepend cannot take place at the same time");else if(f)s("no append/prepend/replace modifiers allowed for loop target");else l&&f&&s("cannot append with loop (sel: "+c+
-")");var o,m,t,u,q;if(k){t=/^style$/i.test(k);q=(u=/^class$/i.test(k))?"className":k;o=function(j,r){j.setAttribute(J+k,r);if(q in j&&!t)try{j[q]=""}catch(p){}if(j.nodeType===1){j.removeAttribute(k);u&&j.removeAttribute(q)}};m=t||u?t?function(j){return j.style.cssText}:function(j){return j.className}:function(j){return j.getAttribute(k)};a=function(j){return j.replace(/\"/g,"&quot;")};g=g?function(j,r){o(j,r+m(j))}:l?function(j,r){o(j,m(j)+r)}:function(j,r){o(j,r)}}else{g=f?function(j,r){var p=j.parentNode;
-if(p){p.insertBefore(document.createTextNode(r),j.nextSibling);p.removeChild(j)}else s("The template root, can't be looped.")}:g?function(j,r){j.insertBefore(document.createTextNode(r),j.firstChild)}:l?function(j,r){j.appendChild(document.createTextNode(r))}:function(j,r){for(;j.firstChild;)j.removeChild(j.firstChild);j.appendChild(document.createTextNode(r))};a=function(j){return j}}return{attr:k,nodes:e,set:g,sel:c,quotefn:a}}function E(a,b){for(var f=K+b+":",c=0;c<a.nodes.length;c++)a.set(a.nodes[c],
-f)}function L(a,b,f,c,g){return function(i){var k=b(i),l=i[a],e={items:k},o=0,m,t=[],u=function(j,r,p,y){var z=i.pos,v=i.item,U=i.items;i.pos=r.pos=j;i.item=r.item=k[j];i.items=k;typeof y!=="undefined"&&(i.length=y);if(typeof p==="function"&&p.call(i.item,i)===false)o++;else{t.push(f.call(i.item,i));i.pos=z;i.item=v;i.items=U}};i[a]=e;if(F(k)){m=k.length||0;typeof c==="function"&&k.sort(c);for(var q=0;q<m;q++)u(q,e,g,m-o)}else{k&&typeof c!=="undefined"&&s("sort is only available on arrays, not objects");
-for(m in k)k.hasOwnProperty(m)&&u(m,e,g)}typeof l!=="undefined"?i[a]=l:delete i[a];return t.join("")}}function M(a,b,f,c){var g=false,i,k,l,e;for(e in f)if(f.hasOwnProperty(e))if(e==="sort")k=f.sort;else if(e==="filter")l=f.filter;else{g&&s("cannot have more than one loop on a target");i=e;g=true}i||s("Error in the selector: "+b+"\nA directive action must be a string, a function or a loop(<-)");g=f[i];if(typeof g==="string"||typeof g==="function"){f={};f[i]={root:g};return M(a,b,f,c)}f=T(i);i=x(f.sel);
-a=D(a,b,true);b=a.nodes;for(w=0;w<b.length;w++){e=b[w];var o=B(e,g);c[c.length]=C(a.quotefn,L(f.name,i,o,k,l));a.nodes=[e];E(a,c.length-1)}return a}function V(a,b){function f(j,r){var p=j.match(I);p={prepend:!!p[1],prop:p[2],attr:p[3]||W[r],append:!!p[4],sel:j};var y,z,v;for(y=i.a.length-1;y>=0;y--){z=i.a[y];v=(v=z.l[0])&&v[p.prop];if(typeof v!=="undefined"){p.prop=z.p+"."+p.prop;if(i.l[p.prop]===true)v=v[0];break}}if(typeof v==="undefined"){v=x(p.prop)(F(b)?b[0]:b);if(v==="")return false}if(F(v)){i.a.push({l:v,
-p:p.prop});i.l[p.prop]=true;p.t="loop"}else p.t="str";return p}var c=a.getElementsByTagName("*"),g=[],i={a:[],l:{}},k,l,e,o,m,t,u,q;e=-1;for(o=c.length;e<o;e++){u=e>-1?c[e]:a;if(u.nodeType===1&&u.className!==""){q=u.className.split(" ");m=0;for(t=q.length;m<t;m++){k=q[m];k=f(k,u.tagName);if(k!==false){l=/nodevalue/i.test(k.attr);if(k.sel.indexOf("@")>-1||l){u.className=u.className.replace("@"+k.attr,"");if(l)k.attr=false}g.push({n:u,cspec:k})}}}}return g}function B(a,b,f,c){var g=[],i,k,l,e,o,m,t,
-u,q,j=[];c=c||f&&V(a,f);if(f)for(;c.length>0;){l=c[0].cspec;e=c[0].n;c.splice(0,1);if(l.t==="str"){e=D(e,l,false);E(e,g.length);g[g.length]=C(e.quotefn,x(l.prop))}else{m=x(l.sel);e=D(e,l,true);o=e.nodes;i=0;for(k=o.length;i<k;i++){t=o[i];u=B(t,false,f,c);g[g.length]=C(e.quotefn,L(l.sel,m,u));e.nodes=[t];E(e,g.length-1)}}}for(q in b)if(b.hasOwnProperty(q)){f=0;c=b[q];l=q.split(/\s*,\s*/);m=l.length;do if(typeof c==="function"||typeof c==="string"){q=l[f];e=D(a,q,false);E(e,g.length);g[g.length]=C(e.quotefn,
-x(c))}else M(a,q,c,g);while(++f<m)}a=G(a);a=a.replace(/<([^>]+)\s(value\=""|selected)\s?([^>]*)>/ig,"<$1 $3>");a=a.split(J).join("");a=a.split(K);for(f=1;f<a.length;f++){b=a[f];j[f]=g[parseInt(b,10)];a[f]=b.substring(b.indexOf(":")+1)}return H(a,j)}function P(a,b,f){var c=B((f||this[0]).cloneNode(true),a,b);return function(g){return c({context:g})}}function Q(a,b){for(var f=typeof b==="function"&&b,c=0,g=this.length;c<g;c++)this[c]=N(this[c],(f||n.compile(b,false,this[c]))(a,false));context=null;
-return this}function R(a,b){for(var f=n.compile(b,a,this[0]),c=0,g=this.length;c<g;c++)this[c]=N(this[c],f(a,false));context=null;return this}function N(a,b){var f,c=a.parentNode,g=0;if(!c){c=document.createElement("DIV");c.appendChild(a)}switch(a.tagName){case "BODY":c.removeChild(a);c.innerHTML+=b;return c.getElementsByTagName("BODY")[0];case "TBODY":case "THEAD":case "TFOOT":b="<TABLE>"+b+"</TABLE>";g=1;break;case "TR":b="<TABLE><TBODY>"+b+"</TBODY></TABLE>";g=2;break;case "TD":case "TH":b="<TABLE><TBODY><TR>"+
-b+"</TR></TBODY></TABLE>";g=3;break}tmp=document.createElement("SPAN");tmp.style.display="none";document.body.appendChild(tmp);tmp.innerHTML=b;for(f=tmp.firstChild;g--;)f=f.firstChild;c.insertBefore(f,a);c.removeChild(a);document.body.removeChild(tmp);return a=f}var A=[];n=n||O();switch(typeof d){case "string":A=n.find(h||document,d);A.length===0&&s('The template "'+d+'" was not found');break;case "undefined":s("The root of the template is undefined, check your selector");break;default:A=d}var w=
-0;for(d=A.length;w<d;w++)n[w]=A[w];n.length=d;var K="_s"+Math.floor(Math.random()*1E6)+"_",J="_a"+Math.floor(Math.random()*1E6)+"_",I=/^(\+)?([^\@\+]+)?\@?([^\+]+)?(\+)?$/,W={IMG:"src",INPUT:"value"},F=Array.isArray?function(a){return Array.isArray(a)}:function(a){return Object.prototype.toString.call(a)==="[object Array]"};return n};$p.plugins={};
-$p.libs={dojo:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){return dojo.query(h,d)}},domassistant:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){return $(d).cssSelect(h)};DOMAssistant.attach({publicMethods:["compile","render","autoRender"],compile:function(d,h){return $p([this]).compile(d,h)},render:function(d,h){return $($p([this]).render(d,h))[0]},autoRender:function(d,h){return $($p([this]).autoRender(d,h))[0]}})},
-jquery:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){return jQuery(d).find(h)};jQuery.fn.extend({directives:function(d){this._pure_d=d;return this},compile:function(d,h){return $p(this).compile(this._pure_d||d,h)},render:function(d,h){return jQuery($p(this).render(d,this._pure_d||h))},autoRender:function(d,h){return jQuery($p(this).autoRender(d,this._pure_d||h))}})},mootools:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,
-h){return $(d).getElements(h)};Element.implement({compile:function(d,h){return $p(this).compile(d,h)},render:function(d,h){return $p([this]).render(d,h)},autoRender:function(d,h){return $p([this]).autoRender(d,h)}})},prototype:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){d=d===document?d.body:d;return typeof d==="string"?$$(d):$(d).select(h)};Element.addMethods({compile:function(d,h,n){return $p([d]).compile(h,n)},render:function(d,h,n){return $p([d]).render(h,
-n)},autoRender:function(d,h,n){return $p([d]).autoRender(h,n)}})},sizzle:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){return Sizzle(h,d)}},sly:function(){if(typeof document.querySelector==="undefined")$p.plugins.find=function(d,h){return Sly(h,d)}},yui:function(){typeof document.querySelector==="undefined"&&YUI().use("node",function(d){$p.plugins.find=function(h,n){return d.NodeList.getDOMNodes(d.one(h).all(n))}});YUI.add("pure-yui",function(d){d.Node.prototype.directives=
-function(h){this._pure_d=h;return this};d.Node.prototype.compile=function(h,n){return $p([this._node]).compile(this._pure_d||h,n)};d.Node.prototype.render=function(h,n){return d.one($p([this._node]).render(h,this._pure_d||n))};d.Node.prototype.autoRender=function(h,n){return d.one($p([this._node]).autoRender(h,this._pure_d||n))}},"0.1",{requires:["node"]})}};
-(function(){var d=typeof dojo!=="undefined"&&"dojo"||typeof DOMAssistant!=="undefined"&&"domassistant"||typeof jQuery!=="undefined"&&"jquery"||typeof MooTools!=="undefined"&&"mootools"||typeof Prototype!=="undefined"&&"prototype"||typeof Sizzle!=="undefined"&&"sizzle"||typeof Sly!=="undefined"&&"sly"||typeof YUI!=="undefined"&&"yui";d&&$p.libs[d]();if(typeof exports!=="undefined")exports.$p=$p})();

src/nextrain/pages.clj

       [:title "NexTrain"]
       [:meta {:name "viewport" :content "width=device-width, initial-scale=1"}]
       [:meta {:name "apple-mobile-web-app-capable" :content "yes"}]
-      (include-css "/css/jquery.mobile-1.1.0.min.css")
+      (include-css "http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css")
       (include-js ;"https://www.google.com/jsapi"
                   "/js/jquery-1.7.2.min.js"
                   "/js/jquery.mobile-1.1.0.min.js"
-                  "/js/pure_min.js"
+                  "/js/mustache-0.5.1-dev.js"
                   "/js/underscore-1.3.3.min.js"
                   "/js/nextrain.js")
       (when-let [scripts (:scripts params)]

src/nextrain/snippets.clj

   (mobile-page :id "indexpage"
                :header (html [:h1 "Nearest Stops"])
                :content (html [:div#stops-list-template
-                                [:ul {:data-role "listview"}
-                                 [:li.stop
-                                  [:a {:class "stop_name stop_url@href"}]]]])))
+                                [:ul#stops-list {:data-role "listview"}]])))
 
 (defn stop-mobile-page [stop]
   (println stop)