` whose class name is tied to a
+ // property of a JS object.
+ //
+ // Template:
+ //
+ //
+ //
+ // Data:
+ //
+ // {
+ // "post": {
+ // "title": "My Post",
+ // "entry": "My Entry",
+ // "author": {
+ // "name": "@aq"
+ // }
+ // }
+ // }
+ //
+ // Result:
+ //
+ //
+ //
+ // Templates can be much more complex, and more deeply nested.
+ // More examples can be found in `test/fixtures/meld/`
+ //
+ // If you don't think the lookup by classes is semantic for you, you can easily
+ // switch the method of lookup by defining a selector function in the options
+ //
+ // For example:
+ //
+ // meld($('.post'), post_data, {
+ // selector: function(k) {
+ // return '[data-key=' + k + ']';
+ // }
+ // });
+ //
+ // Would look for template nodes like `
`
+ //
+ Sammy.Meld = function(app, method_alias) {
+ var default_options = {
+ selector: function(k) { return '.' + k; },
+ remove_false: true
+ };
+
+ var meld = function(template, data, options) {
+ var $template = $(template);
+
+ options = $.extend(default_options, options || {});
+
+ if (typeof data === 'string') {
+ $template.html(data);
+ } else {
+ $.each(data, function(key, value) {
+ var selector = options.selector(key),
+ $sub = $template.filter(selector),
+ $container,
+ $item,
+ is_list = false,
+ subindex = $template.index($sub);
+
+ if ($sub.length === 0) { $sub = $template.find(selector); }
+ if ($sub.length > 0) {
+ if ($.isArray(value)) {
+ $container = $('
');
+ if ($sub.is('ol, ul')) {
+ is_list = true;
+ $item = $sub.children('li:first');
+ if ($item.length == 0) { $item = $('
'); }
+ } else if ($sub.children().length == 1) {
+ is_list = true;
+ $item = $sub.children(':first').clone();
+ } else {
+ $item = $sub.clone();
+ }
+ for (var i = 0; i < value.length; i++) {
+ $container.append(meld($item.clone(), value[i], options));
+ }
+ if (is_list) {
+ $sub.html($container.html());
+ } else if ($sub[0] == $template[0]) {
+ $template = $($container.html());
+ } else if (subindex >= 0) {
+ var args = [subindex, 1];
+ args = args.concat($container.children().get());
+ $template.splice.apply($template, args);
+ }
+ } else if (options.remove_false && value === false) {
+ $template.splice(subindex, 1);
+ } else if (typeof value === 'object') {
+ if ($sub.is(':empty')) {
+ $sub.attr(value, true);
+ } else {
+ $sub.html(meld($sub.html(), value, options));
+ }
+ } else {
+ $sub.html(value.toString());
+ }
+ } else {
+ $template.attr(key, value, true);
+ }
+ });
+ }
+ var dom = $template;
+ return dom;
+ };
+
+ // set the default method name/extension
+ if (!method_alias) method_alias = 'meld';
+ // create the helper at the method alias
+ app.helper(method_alias, meld);
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.mustache.js b/public/javascripts/plugins/sammy.mustache.js
new file mode 100644
index 0000000..169f0c7
--- /dev/null
+++ b/public/javascripts/plugins/sammy.mustache.js
@@ -0,0 +1,444 @@
+(function($) {
+
+if (!window.Mustache) {
+
+ /*
+ mustache.js — Logic-less templates in JavaScript
+
+ See http://mustache.github.com/ for more info.
+ */
+
+ var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",
+ ctag: "}}",
+ pragmas: {},
+ buffer: [],
+ pragmas_implemented: {
+ "IMPLICIT-ITERATOR": true
+ },
+ context: {},
+
+ render: function(template, context, partials, in_recursion) {
+ // reset buffer & set context
+ if(!in_recursion) {
+ this.context = context;
+ this.buffer = []; // TODO: make this non-lazy
+ }
+
+ // fail fast
+ if(!this.includes("", template)) {
+ if(in_recursion) {
+ return template;
+ } else {
+ this.send(template);
+ return;
+ }
+ }
+
+ template = this.render_pragmas(template);
+ var html = this.render_section(template, context, partials);
+ if(in_recursion) {
+ return this.render_tags(html, context, partials, in_recursion);
+ }
+
+ this.render_tags(html, context, partials, in_recursion);
+ },
+
+ /*
+ Sends parsed lines
+ */
+ send: function(line) {
+ if(line != "") {
+ this.buffer.push(line);
+ }
+ },
+
+ /*
+ Looks for %PRAGMAS
+ */
+ render_pragmas: function(template) {
+ // no pragmas
+ if(!this.includes("%", template)) {
+ return template;
+ }
+
+ var that = this;
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
+ this.ctag);
+ return template.replace(regex, function(match, pragma, options) {
+ if(!that.pragmas_implemented[pragma]) {
+ throw({message:
+ "This implementation of mustache doesn't understand the '" +
+ pragma + "' pragma"});
+ }
+ that.pragmas[pragma] = {};
+ if(options) {
+ var opts = options.split("=");
+ that.pragmas[pragma][opts[0]] = opts[1];
+ }
+ return "";
+ // ignore unknown pragmas silently
+ });
+ },
+
+ /*
+ Tries to find a partial in the curent scope and render it
+ */
+ render_partial: function(name, context, partials) {
+ name = this.trim(name);
+ if(!partials || partials[name] === undefined) {
+ throw({message: "unknown_partial '" + name + "'"});
+ }
+ if(typeof(context[name]) != "object") {
+ return this.render(partials[name], context, partials, true);
+ }
+ return this.render(partials[name], context[name], partials, true);
+ },
+
+ /*
+ Renders inverted (^) and normal (#) sections
+ */
+ render_section: function(template, context, partials) {
+ if(!this.includes("#", template) && !this.includes("^", template)) {
+ return template;
+ }
+
+ var that = this;
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
+ "\\s*", "mg");
+
+ // for each {{#foo}}{{/foo}} section do...
+ return template.replace(regex, function(match, type, name, content) {
+ var value = that.find(name, context);
+ if(type == "^") { // inverted section
+ if(!value || that.is_array(value) && value.length === 0) {
+ // false or empty list, render it
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ } else if(type == "#") { // normal section
+ if(that.is_array(value)) { // Enumerable, Let's loop!
+ return that.map(value, function(row) {
+ return that.render(content, that.create_context(row),
+ partials, true);
+ }).join("");
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
+ return that.render(content, that.create_context(value),
+ partials, true);
+ } else if(typeof value === "function") {
+ // higher order section
+ return value.call(context, content, function(text) {
+ return that.render(text, context, partials, true);
+ });
+ } else if(value) { // boolean section
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ }
+ });
+ },
+
+ /*
+ Replace {{foo}} and friends with values from our view
+ */
+ render_tags: function(template, context, partials, in_recursion) {
+ // tit for tat
+ var that = this;
+
+ var new_regex = function() {
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
+ that.ctag + "+", "g");
+ };
+
+ var regex = new_regex();
+ var tag_replace_callback = function(match, operator, name) {
+ switch(operator) {
+ case "!": // ignore comments
+ return "";
+ case "=": // set new delimiters, rebuild the replace regexp
+ that.set_delimiters(name);
+ regex = new_regex();
+ return "";
+ case ">": // render partial
+ return that.render_partial(name, context, partials);
+ case "{": // the triple mustache is unescaped
+ return that.find(name, context);
+ default: // escape the value
+ return that.escape(that.find(name, context));
+ }
+ };
+ var lines = template.split("\n");
+ for(var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
+ if(!in_recursion) {
+ this.send(lines[i]);
+ }
+ }
+
+ if(in_recursion) {
+ return lines.join("\n");
+ }
+ },
+
+ set_delimiters: function(delimiters) {
+ var dels = delimiters.split(" ");
+ this.otag = this.escape_regex(dels[0]);
+ this.ctag = this.escape_regex(dels[1]);
+ },
+
+ escape_regex: function(text) {
+ // thank you Simon Willison
+ if(!arguments.callee.sRE) {
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+ arguments.callee.sRE = new RegExp(
+ '(\\' + specials.join('|\\') + ')', 'g'
+ );
+ }
+ return text.replace(arguments.callee.sRE, '\\$1');
+ },
+
+ /*
+ find `name` in current `context`. That is find me a value
+ from the view object
+ */
+ find: function(name, context) {
+ name = this.trim(name);
+
+ // Checks whether a value is thruthy or false or 0
+ function is_kinda_truthy(bool) {
+ return bool === false || bool === 0 || bool;
+ }
+
+ var value;
+ if(is_kinda_truthy(context[name])) {
+ value = context[name];
+ } else if(is_kinda_truthy(this.context[name])) {
+ value = this.context[name];
+ }
+
+ if(typeof value === "function") {
+ return value.apply(context);
+ }
+ if(value !== undefined) {
+ return value;
+ }
+ // silently ignore unkown variables
+ return "";
+ },
+
+ // Utility methods
+
+ /* includes tag */
+ includes: function(needle, haystack) {
+ return haystack.indexOf(this.otag + needle) != -1;
+ },
+
+ /*
+ Does away with nasty characters
+ */
+ escape: function(s) {
+ s = String(s === null ? "" : s);
+ return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
+ switch(s) {
+ case "&": return "&";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "<";
+ case ">": return ">";
+ default: return s;
+ }
+ });
+ },
+
+ // by @langalex, support for arrays of strings
+ create_context: function(_context) {
+ if(this.is_object(_context)) {
+ return _context;
+ } else {
+ var iterator = ".";
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
+ }
+ var ctx = {};
+ ctx[iterator] = _context;
+ return ctx;
+ }
+ },
+
+ is_object: function(a) {
+ return a && typeof a == "object";
+ },
+
+ is_array: function(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+ },
+
+ /*
+ Gets rid of leading and trailing whitespace
+ */
+ trim: function(s) {
+ return s.replace(/^\s*|\s*$/g, "");
+ },
+
+ /*
+ Why, why, why? Because IE. Cry, cry cry.
+ */
+ map: function(array, fn) {
+ if (typeof array.map == "function") {
+ return array.map(fn);
+ } else {
+ var r = [];
+ var l = array.length;
+ for(var i = 0; i < l; i++) {
+ r.push(fn(array[i]));
+ }
+ return r;
+ }
+ }
+ };
+
+ return({
+ name: "mustache.js",
+ version: "0.3.1-dev",
+
+ /*
+ Turns a template and view into HTML
+ */
+ to_html: function(template, view, partials, send_fun) {
+ var renderer = new Renderer();
+ if(send_fun) {
+ renderer.send = send_fun;
+ }
+ renderer.render(template, view, partials);
+ if(!send_fun) {
+ return renderer.buffer.join("\n");
+ }
+ }
+ });
+ }();
+
+} // Ensure Mustache
+
+ Sammy = Sammy || {};
+
+ //
Sammy.Mustache provides a quick way of using mustache style templates in your app.
+ // The plugin itself includes the awesome mustache.js lib created and maintained by Jan Lehnardt
+ // at http://github.com/janl/mustache.js
+ //
+ // Mustache is a clever templating system that relys on double brackets {{}} for interpolation.
+ // For full details on syntax check out the original Ruby implementation created by Chris Wanstrath at
+ // http://github.com/defunkt/mustache
+ //
+ // By default using Sammy.Mustache in your app adds the
mustache() method to the EventContext
+ // prototype. However, just like
Sammy.Template you can change the default name of the method
+ // by passing a second argument (e.g. you could use the ms() as the method alias so that all the template
+ // files could be in the form file.ms instead of file.mustache)
+ //
+ // ### Example #1
+ //
+ // The template (mytemplate.ms):
+ //
+ //
\{\{title\}\}
+ //
+ // Hey, {{name}}! Welcome to Mustache!
+ //
+ // The app:
+ //
+ // var $.app = $.sammy(function() {
+ // // include the plugin and alias mustache() to ms()
+ // this.use(Sammy.Mustache, 'ms');
+ //
+ // this.get('#/hello/:name', function() {
+ // // set local vars
+ // this.title = 'Hello!'
+ // this.name = this.params.name;
+ // // render the template and pass it through mustache
+ // this.partial('mytemplate.ms');
+ // });
+ //
+ // });
+ //
+ // If I go to #/hello/AQ in the browser, Sammy will render this to the body:
+ //
+ // Hello!
+ //
+ // Hey, AQ! Welcome to Mustache!
+ //
+ //
+ // ### Example #2 - Mustache partials
+ //
+ // The template (mytemplate.ms)
+ //
+ // Hey, {{name}}! {{>hello_friend}}
+ //
+ //
+ // The partial (mypartial.ms)
+ //
+ // Say hello to your friend {{friend}}!
+ //
+ // The app:
+ //
+ // var $.app = $.sammy(function() {
+ // // include the plugin and alias mustache() to ms()
+ // this.use(Sammy.Mustache, 'ms');
+ //
+ // this.get('#/hello/:name/to/:friend', function() {
+ // var context = this;
+ //
+ // // fetch mustache-partial first
+ // $.get('mypartial.ms', function(response){
+ // context.partials = response;
+ //
+ // // set local vars
+ // context.name = this.params.name;
+ // context.hello_friend = {name: this.params.friend};
+ //
+ // // render the template and pass it through mustache
+ // context.partial('mytemplate.ms');
+ // });
+ // });
+ //
+ // });
+ //
+ // If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the body:
+ //
+ // Hey, AQ! Say hello to your friend dP!
+ //
+ // Note: You dont have to include the mustache.js file on top of the plugin as the plugin
+ // includes the full source.
+ //
+ Sammy.Mustache = function(app, method_alias) {
+
+ // *Helper* Uses Mustache.js to parse a template and interpolate and work with the passed data
+ //
+ // ### Arguments
+ //
+ // * `template` A String template. {{}} Tags are evaluated and interpolated by Mustache.js
+ // * `data` An Object containing the replacement values for the template.
+ // data is extended with the EventContext allowing you to call its methods within the template.
+ // * `partials` An Object containing one or more partials (String templates
+ // that are called from the main template).
+ //
+ var mustache = function(template, data, partials) {
+ data = $.extend({}, this, data);
+ partials = $.extend({}, data.partials, partials);
+ return Mustache.to_html(template, data, partials);
+ };
+
+ // set the default method name/extension
+ if (!method_alias) method_alias = 'mustache';
+ app.helper(method_alias, mustache);
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.nested_params.js b/public/javascripts/plugins/sammy.nested_params.js
new file mode 100644
index 0000000..645f8c9
--- /dev/null
+++ b/public/javascripts/plugins/sammy.nested_params.js
@@ -0,0 +1,118 @@
+(function($) {
+
+ Sammy = Sammy || {};
+
+ function parseValue(value) {
+ value = unescape(value);
+ if (value === "true") {
+ return true;
+ } else if (value === "false") {
+ return false;
+ } else {
+ return value;
+ }
+ };
+
+ function parseNestedParam(params, field_name, field_value) {
+ var match, name, rest;
+
+ if (field_name.match(/^[^\[]+$/)) {
+ // basic value
+ params[field_name] = parseValue(field_value);
+ } else if (match = field_name.match(/^([^\[]+)\[\](.*)$/)) {
+ // array
+ name = match[1];
+ rest = match[2];
+
+ if(params[name] && !$.isArray(params[name])) { throw('400 Bad Request'); }
+
+ if (rest) {
+ // array is not at the end of the parameter string
+ match = rest.match(/^\[([^\]]+)\](.*)$/);
+ if(!match) { throw('400 Bad Request'); }
+
+ if (params[name]) {
+ if(params[name][params[name].length - 1][match[1]]) {
+ params[name].push(parseNestedParam({}, match[1] + match[2], field_value));
+ } else {
+ $.extend(true, params[name][params[name].length - 1], parseNestedParam({}, match[1] + match[2], field_value));
+ }
+ } else {
+ params[name] = [parseNestedParam({}, match[1] + match[2], field_value)];
+ }
+ } else {
+ // array is at the end of the parameter string
+ if (params[name]) {
+ params[name].push(parseValue(field_value));
+ } else {
+ params[name] = [parseValue(field_value)];
+ }
+ }
+ } else if (match = field_name.match(/^([^\[]+)\[([^\[]+)\](.*)$/)) {
+ // hash
+ name = match[1];
+ rest = match[2] + match[3];
+
+ if (params[name] && $.isArray(params[name])) { throw('400 Bad Request'); }
+
+ if (params[name]) {
+ $.extend(true, params[name], parseNestedParam(params[name], rest, field_value));
+ } else {
+ params[name] = parseNestedParam({}, rest, field_value);
+ }
+ }
+ return params;
+ };
+
+ // Sammy.NestedParams overrides the default form parsing behavior to provide
+ // extended functionality for parsing Rack/Rails style form name/value pairs into JS
+ // Objects. In fact it passes the same suite of tests as Rack's nested query parsing.
+ // The code and tests were ported to JavaScript/Sammy by http://github.com/endor
+ //
+ // This allows you to translate a form with properly named inputs into a JSON object.
+ //
+ // ### Example
+ //
+ // Given an HTML form like so:
+ //
+ //
+ //
+ // And a Sammy app like:
+ //
+ // var app = $.sammy(function(app) {
+ // this.use(Sammy.NestedParams);
+ //
+ // this.post('#/parse_me', function(context) {
+ // $.log(this.params);
+ // });
+ // });
+ //
+ // If you filled out the form with some values and submitted it, you would see something
+ // like this in your log:
+ //
+ // {
+ // 'obj': {
+ // 'first': 'value',
+ // 'second': 'value',
+ // 'hash': {
+ // 'first': 'value',
+ // 'second': 'value'
+ // }
+ // }
+ // }
+ //
+ // It supports creating arrays with [] and other niceities. Check out the tests for
+ // full specs.
+ //
+ Sammy.NestedParams = function(app) {
+
+ app._parseParamPair = parseNestedParam;
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.oauth2.js b/public/javascripts/plugins/sammy.oauth2.js
new file mode 100644
index 0000000..6d0fab2
--- /dev/null
+++ b/public/javascripts/plugins/sammy.oauth2.js
@@ -0,0 +1,144 @@
+(function($) {
+ Sammy = Sammy || {};
+
+ // Sammy.OAuth2 is a plugin for using OAuth 2.0 to authenticate users and
+ // access your application's API. Requires Sammy.Session.
+ //
+ // Triggers the following events:
+ //
+ // * `oauth.connected` - Access token set and ready to use. Triggered when new
+ // access token acquired, of when application starts and already has access
+ // token.
+ // * `oauth.disconnected` - Access token reset. Triggered by
+ // loseAccessToken().
+ // * `oauth.denied` - Authorization attempt rejected.
+ //
+ // ### Example
+ //
+ // this.use('Storage');
+ // this.use('OAuth2');
+ // this.oauthorize = "/oauth/authorize";
+ //
+ // // The quick & easy way
+ // this.requireOAuth();
+ // // Specific path
+ // this.requireOAuth("/private");
+ // // Filter you can apply to specific URLs
+ // this.before(function(context) { return context.requireOAuth(); })
+ // // Apply to specific request
+ // this.get("/private", function(context) {
+ // this.requireOAuth(function() {
+ // // Do something
+ // });
+ // });
+ //
+ // // Sign in/sign out.
+ // this.bind("oauth.connected", function() { $("#signin").hide() });
+ // this.bind("oauth.disconnected", function() { $("#signin").show() });
+ //
+ // // Handle access denied and other errors
+ // this.bind("oauth.denied", function(evt, error) {
+ // this.partial("admin/views/no_access.tmpl", { error: error.message });
+ // });
+ //
+ // // Sign out.
+ // this.get("#/signout", function(context) {
+ // context.loseAccessToken();
+ // context.redirect("#/");
+ // });
+ //
+ Sammy.OAuth2 = function(app) {
+ app.use('JSON');
+ this.authorize = "/oauth/authorize";
+
+ // Use this on request that require OAuth token. You can use this in a
+ // filter: it will redirect and return false if the access token is missing.
+ // You can use it in a route, it will redirect to get the access token, or
+ // call the callback function if it has an access token.
+ this.helper("requireOAuth", function(cb) {
+ if (this.app.getAccessToken()) {
+ if (cb)
+ cb.apply(this);
+ } else {
+ this.redirect(this.app.authorize + "?state=" + escape(this.path));
+ return false;
+ }
+ });
+
+ // Use this to sign out.
+ this.helper("loseAccessToken", function() {
+ this.app.loseAccessToken();
+ });
+
+ // Use this in your application to require an OAuth access token on all, or
+ // the specified paths. It sets up a before filter on the specified paths.
+ this.requireOAuth = function(options) {
+ this.before(options || {}, function(context) {
+ return context.requireOAuth();
+ });
+ }
+
+ // Returns the access token. Uses Sammy.Session to store the token.
+ this.getAccessToken = function() {
+ return this.session("oauth.token");
+ }
+ // Stores the access token in the session.
+ this.setAccessToken = function(token) {
+ this.session("oauth.token", token);
+ this.trigger("oauth.connected");
+ }
+ // Lose access token: use this to sign out.
+ this.loseAccessToken = function() {
+ this.session("oauth.token", null);
+ this.trigger("oauth.disconnected");
+ }
+
+ // Add OAuth 2.0 access token to all XHR requests.
+ $(document).ajaxSend(function(evt, xhr) {
+ var token = app.getAccessToken();
+ if (token)
+ xhr.setRequestHeader("Authorization", "OAuth " + token);
+ });
+
+ // Converts query string parameters in fragment identifier to object.
+ function parseParams(hash) {
+ var pairs = hash.substring(1).split("&"), params = {};
+ for (var i in pairs) {
+ var splat = pairs[i].split("=");
+ params[splat[0]] = splat[1].replace(/\+/g, " ");
+ }
+ return params;
+ }
+
+ var start_url;
+ // Capture the application's start URL, we'll need that later on for
+ // redirection.
+ this.bind("run", function(evt, params) {
+ start_url = params.start_url || "#";
+ if (this.app.getAccessToken())
+ this.trigger("oauth.connected");
+ });
+
+ // Intercept OAuth authorization response with access token, stores it and
+ // redirects to original URL, or application root.
+ this.before(/^#(access_token=|[^\\].*\&access_token=)/, function(context) {
+ var params = parseParams(context.path);
+ this.app.setAccessToken(params.access_token);
+ // When the filter redirected the original request, it passed the original
+ // request's URL in the state parameter, which we get back after
+ // authorization.
+ context.redirect(params.state.length == 0 ? this.app.start_url : unescape(params.state));
+ return false;
+ }).get(/^#(access_token=|[^\\].*\&access_token=)/, function(context) { });
+
+ // Intercept OAuth authorization response with error (typically access
+ // denied).
+ this.before(/^#(error=|[^\\].*\&error=)/, function(context) {
+ var params = parseParams(context.path);
+ var message = params.error_description || "Access denined";
+ context.trigger("oauth.denied", { code: params.error, message: message });
+ return false;
+ }).get(/^#(error=|[^\\].*\&error=)/, function(context) { });
+
+ }
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.path_location_proxy.js b/public/javascripts/plugins/sammy.path_location_proxy.js
new file mode 100644
index 0000000..13f9014
--- /dev/null
+++ b/public/javascripts/plugins/sammy.path_location_proxy.js
@@ -0,0 +1,29 @@
+(function($) {
+
+ Sammy = Sammy || {};
+
+ // `Sammy.PathLocationProxy` is a simple Location Proxy that just
+ // gets and sets window.location. This allows you to use
+ // Sammy to route on the full URL path instead of just the hash. It
+ // will take a full refresh to get the app to change state.
+ //
+ // To read more about location proxies, check out the
+ // documentation for `Sammy.HashLocationProxy`
+ Sammy.PathLocationProxy = function(app) {
+ this.app = app;
+ };
+
+ Sammy.PathLocationProxy.prototype = {
+ bind: function() {},
+ unbind: function() {},
+
+ getLocation: function() {
+ return [window.location.pathname, window.location.search].join('');
+ },
+
+ setLocation: function(new_location) {
+ return window.location = new_location;
+ }
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.pure.js b/public/javascripts/plugins/sammy.pure.js
new file mode 100644
index 0000000..16fe3df
--- /dev/null
+++ b/public/javascripts/plugins/sammy.pure.js
@@ -0,0 +1,756 @@
+(function($) {
+
+/*!
+ PURE Unobtrusive Rendering Engine for HTML
+
+ Licensed under the MIT licenses.
+ More information at: http://www.opensource.org
+
+ Copyright (c) 2010 Michael Cvilic - BeeBole.com
+
+ Thanks to Rog Peppe for the functional JS jump
+ revision: 2.47
+*/
+
+var $p, pure = $p = function(){
+ var sel = arguments[0],
+ ctxt = false;
+
+ if(typeof sel === 'string'){
+ ctxt = arguments[1] || false;
+ }
+ return $p.core(sel, ctxt);
+};
+
+$p.core = function(sel, ctxt, plugins){
+ //get an instance of the plugins
+ var plugins = getPlugins(),
+ templates = [];
+
+ //search for the template node(s)
+ switch(typeof sel){
+ case 'string':
+ templates = plugins.find(ctxt || document, sel);
+ if(templates.length === 0) {
+ error('The template "' + sel + '" was not found');
+ }
+ break;
+ case 'undefined':
+ error('The template root is undefined, check your selector');
+ break;
+ default:
+ templates = [sel];
+ }
+
+ for(var i = 0, ii = templates.length; i < ii; i++){
+ plugins[i] = templates[i];
+ }
+ plugins.length = ii;
+
+ // set the signature string that will be replaced at render time
+ var Sig = '_s' + Math.floor( Math.random() * 1000000 ) + '_',
+ // another signature to prepend to attributes and avoid checks: style, height, on[events]...
+ attPfx = '_a' + Math.floor( Math.random() * 1000000 ) + '_',
+ // rx to parse selectors, e.g. "+tr.foo[class]"
+ selRx = /^(\+)?([^\@\+]+)?\@?([^\+]+)?(\+)?$/,
+ // set automatically attributes for some tags
+ autoAttr = {
+ IMG:'src',
+ INPUT:'value'
+ },
+ // check if the argument is an array - thanks salty-horse (Ori Avtalion)
+ isArray = Array.isArray ?
+ function(o) {
+ return Array.isArray(o);
+ } :
+ function(o) {
+ return Object.prototype.toString.call(o) === "[object Array]";
+ };
+
+ return plugins;
+
+
+ /* * * * * * * * * * * * * * * * * * * * * * * * * *
+ core functions
+ * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+
+ // error utility
+ function error(e){
+ if(typeof console !== 'undefined'){
+ console.log(e);
+ }else{ alert(e); }
+ throw('pure error: ' + e);
+ }
+
+ //return a new instance of plugins
+ function getPlugins(){
+ var plugins = $p.plugins,
+ f = function(){};
+ f.prototype = plugins;
+
+ // do not overwrite functions if external definition
+ f.prototype.compile = plugins.compile || compile;
+ f.prototype.render = plugins.render || render;
+ f.prototype.autoRender = plugins.autoRender || autoRender;
+ f.prototype.find = plugins.find || find;
+
+ // give the compiler and the error handling to the plugin context
+ f.prototype._compiler = compiler;
+ f.prototype._error = error;
+
+ return new f();
+ }
+
+ // returns the outer HTML of a node
+ function outerHTML(node){
+ // if IE take the internal method otherwise build one
+ return node.outerHTML || (
+ function(n){
+ var div = document.createElement('div'), h;
+ div.appendChild( n.cloneNode(true) );
+ h = div.innerHTML;
+ div = null;
+ return h;
+ })(node);
+ }
+
+ // returns the string generator function
+ function wrapquote(qfn, f){
+ return function(ctxt){
+ return qfn('' + f.call(ctxt.context, ctxt));
+ };
+ }
+
+ // default find using querySelector when available on the browser
+ function find(n, sel){
+ if(typeof n === 'string'){
+ sel = n;
+ n = false;
+ }
+ if(typeof document.querySelectorAll !== 'undefined'){
+ return (n||document).querySelectorAll( sel );
+ }else{
+ error('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');
+ }
+ }
+
+ // create a function that concatenates constant string
+ // sections (given in parts) and the results of called
+ // functions to fill in the gaps between parts (fns).
+ // fns[n] fills in the gap between parts[n-1] and parts[n];
+ // fns[0] is unused.
+ // this is the inner template evaluation loop.
+ function concatenator(parts, fns){
+ return function(ctxt){
+ var strs = [ parts[ 0 ] ],
+ n = parts.length,
+ fnVal, pVal, attLine, pos;
+
+ for(var i = 1; i < n; i++){
+ fnVal = fns[i]( ctxt );
+ pVal = parts[i];
+
+ // if the value is empty and attribute, remove it
+ if(fnVal === ''){
+ attLine = strs[ strs.length - 1 ];
+ if( ( pos = attLine.search( /[\w]+=\"?$/ ) ) > -1){
+ strs[ strs.length - 1 ] = attLine.substring( 0, pos );
+ pVal = pVal.substr( 1 );
+ }
+ }
+
+ strs[ strs.length ] = fnVal;
+ strs[ strs.length ] = pVal;
+ }
+ return strs.join('');
+ };
+ }
+
+ // parse and check the loop directive
+ function parseloopspec(p){
+ var m = p.match( /^(\w+)\s*<-\s*(\S+)?$/ );
+ if(m === null){
+ error('bad loop spec: "' + p + '"');
+ }
+ if(m[1] === 'item'){
+ error('"item<-..." is a reserved word for the current running iteration.\n\nPlease choose another name for your loop.');
+ }
+ if( !m[2] || (m[2] && (/context/i).test(m[2]))){ //undefined or space(IE)
+ m[2] = function(ctxt){return ctxt.context;};
+ }
+ return {name: m[1], sel: m[2]};
+ }
+
+ // parse a data selector and return a function that
+ // can traverse the data accordingly, given a context.
+ function dataselectfn(sel){
+ if(typeof(sel) === 'function'){
+ return sel;
+ }
+ //check for a valid js variable name with hyphen(for properties only), $, _ and :
+ var m = sel.match(/^[a-zA-Z\$_\@][\w\$:-]*(\.[\w\$:-]*[^\.])*$/);
+ if(m === null){
+ var found = false, s = sel, parts = [], pfns = [], i = 0, retStr;
+ // check if literal
+ if(/\'|\"/.test( s.charAt(0) )){
+ if(/\'|\"/.test( s.charAt(s.length-1) )){
+ retStr = s.substring(1, s.length-1);
+ return function(){ return retStr; };
+ }
+ }else{
+ // check if literal + #{var}
+ while((m = s.match(/#\{([^{}]+)\}/)) !== null){
+ found = true;
+ parts[i++] = s.slice(0, m.index);
+ pfns[i] = dataselectfn(m[1]);
+ s = s.slice(m.index + m[0].length, s.length);
+ }
+ }
+ if(!found){
+ error('bad data selector syntax: ' + sel);
+ }
+ parts[i] = s;
+ return concatenator(parts, pfns);
+ }
+ m = sel.split('.');
+ return function(ctxt){
+ var data = ctxt.context;
+ if(!data){
+ return '';
+ }
+ var v = ctxt[m[0]],
+ i = 0;
+ if(v && v.item){
+ data = v.item;
+ i += 1;
+ }
+ var n = m.length;
+ for(; i < n; i++){
+ if(!data){break;}
+ data = data[m[i]];
+ }
+ return (!data && data !== 0) ? '':data;
+ };
+ }
+
+ // wrap in an object the target node/attr and their properties
+ function gettarget(dom, sel, isloop){
+ var osel, prepend, selector, attr, append, target = [];
+ if( typeof sel === 'string' ){
+ osel = sel;
+ var m = sel.match(selRx);
+ if( !m ){
+ error( 'bad selector syntax: ' + sel );
+ }
+
+ prepend = m[1];
+ selector = m[2];
+ attr = m[3];
+ append = m[4];
+
+ if(selector === '.' || ( !selector && attr ) ){
+ target[0] = dom;
+ }else{
+ target = plugins.find(dom, selector);
+ }
+ if(!target || target.length === 0){
+ return error('The node "' + sel + '" was not found in the template');
+ }
+ }else{
+ // autoRender node
+ prepend = sel.prepend;
+ attr = sel.attr;
+ append = sel.append;
+ target = [dom];
+ }
+
+ if( prepend || append ){
+ if( prepend && append ){
+ error('append/prepend cannot take place at the same time');
+ }else if( isloop ){
+ error('no append/prepend/replace modifiers allowed for loop target');
+ }else if( append && isloop ){
+ error('cannot append with loop (sel: ' + osel + ')');
+ }
+ }
+ var setstr, getstr, quotefn, isStyle, isClass, attName, setfn;
+ if(attr){
+ isStyle = (/^style$/i).test(attr);
+ isClass = (/^class$/i).test(attr);
+ attName = isClass ? 'className' : attr;
+ setstr = function(node, s) {
+ node.setAttribute(attPfx + attr, s);
+ if (attName in node && !isStyle) {
+ node[attName] = '';
+ }
+ if (node.nodeType === 1) {
+ node.removeAttribute(attr);
+ isClass && node.removeAttribute(attName);
+ }
+ };
+ if (isStyle || isClass) {//IE no quotes special care
+ if(isStyle){
+ getstr = function(n){ return n.style.cssText; };
+ }else{
+ getstr = function(n){ return n.className; };
+ }
+ quotefn = function(s){ return s.replace(/\"/g, '"'); };
+ }else {
+ getstr = function(n){ return n.getAttribute(attr); };
+ quotefn = function(s){ return s.replace(/\"/g, '"').replace(/\s/g, ' '); };
+ }
+ if(prepend){
+ setfn = function(node, s){ setstr( node, s + getstr( node )); };
+ }else if(append){
+ setfn = function(node, s){ setstr( node, getstr( node ) + s); };
+ }else{
+ setfn = function(node, s){ setstr( node, s ); };
+ }
+ }else{
+ if (isloop) {
+ setfn = function(node, s) {
+ var pn = node.parentNode;
+ if (pn) {
+ //replace node with s
+ pn.insertBefore(document.createTextNode(s), node.nextSibling);
+ pn.removeChild(node);
+ }
+ };
+ } else {
+ if (prepend) {
+ setfn = function(node, s) { node.insertBefore(document.createTextNode(s), node.firstChild); };
+ } else if (append) {
+ setfn = function(node, s) { node.appendChild(document.createTextNode(s));};
+ } else {
+ setfn = function(node, s) {
+ while (node.firstChild) { node.removeChild(node.firstChild); }
+ node.appendChild(document.createTextNode(s));
+ };
+ }
+ }
+ quotefn = function(s) { return s; };
+ }
+ return { attr: attr, nodes: target, set: setfn, sel: osel, quotefn: quotefn };
+ }
+
+ function setsig(target, n){
+ var sig = Sig + n + ':';
+ for(var i = 0; i < target.nodes.length; i++){
+ // could check for overlapping targets here.
+ target.set( target.nodes[i], sig );
+ }
+ }
+
+ // read de loop data, and pass it to the inner rendering function
+ function loopfn(name, dselect, inner, sorter, filter){
+ return function(ctxt){
+ var a = dselect(ctxt),
+ old = ctxt[name],
+ temp = { items : a },
+ filtered = 0,
+ length,
+ strs = [],
+ buildArg = function(idx, temp, ftr, len){
+ ctxt.pos = temp.pos = idx;
+ ctxt.item = temp.item = a[ idx ];
+ ctxt.items = a;
+ //if array, set a length property - filtered items
+ typeof len !== 'undefined' && (ctxt.length = len);
+ //if filter directive
+ if(typeof ftr === 'function' && ftr(ctxt) === false){
+ filtered++;
+ return;
+ }
+ strs.push( inner.call(temp, ctxt ) );
+ };
+ ctxt[name] = temp;
+ if( isArray(a) ){
+ length = a.length || 0;
+ // if sort directive
+ if(typeof sorter === 'function'){
+ a.sort(sorter);
+ }
+ //loop on array
+ for(var i = 0, ii = length; i < ii; i++){
+ buildArg(i, temp, filter, length - filtered);
+ }
+ }else{
+ if(a && typeof sorter !== 'undefined'){
+ error('sort is only available on arrays, not objects');
+ }
+ //loop on collections
+ for(var prop in a){
+ a.hasOwnProperty( prop ) && buildArg(prop, temp, filter);
+ }
+ }
+
+ typeof old !== 'undefined' ? ctxt[name] = old : delete ctxt[name];
+ return strs.join('');
+ };
+ }
+ // generate the template for a loop node
+ function loopgen(dom, sel, loop, fns){
+ var already = false, ls, sorter, filter, prop;
+ for(prop in loop){
+ if(loop.hasOwnProperty(prop)){
+ if(prop === 'sort'){
+ sorter = loop.sort;
+ continue;
+ }else if(prop === 'filter'){
+ filter = loop.filter;
+ continue;
+ }
+ if(already){
+ error('cannot have more than one loop on a target');
+ }
+ ls = prop;
+ already = true;
+ }
+ }
+ if(!ls){
+ error('Error in the selector: ' + sel + '\nA directive action must be a string, a function or a loop(<-)');
+ }
+ var dsel = loop[ls];
+ // if it's a simple data selector then we default to contents, not replacement.
+ if(typeof(dsel) === 'string' || typeof(dsel) === 'function'){
+ loop = {};
+ loop[ls] = {root: dsel};
+ return loopgen(dom, sel, loop, fns);
+ }
+ var spec = parseloopspec(ls),
+ itersel = dataselectfn(spec.sel),
+ target = gettarget(dom, sel, true),
+ nodes = target.nodes;
+
+ for(i = 0; i < nodes.length; i++){
+ var node = nodes[i],
+ inner = compiler(node, dsel);
+ fns[fns.length] = wrapquote(target.quotefn, loopfn(spec.name, itersel, inner, sorter, filter));
+ target.nodes = [node]; // N.B. side effect on target.
+ setsig(target, fns.length - 1);
+ }
+ }
+
+ function getAutoNodes(n, data){
+ var ns = n.getElementsByTagName('*'),
+ an = [],
+ openLoops = {a:[],l:{}},
+ cspec,
+ isNodeValue,
+ i, ii, j, jj, ni, cs, cj;
+ //for each node found in the template
+ for(i = -1, ii = ns.length; i < ii; i++){
+ ni = i > -1 ?ns[i]:n;
+ if(ni.nodeType === 1 && ni.className !== ''){
+ //when a className is found
+ cs = ni.className.split(' ');
+ // for each className
+ for(j = 0, jj=cs.length;j
-1 || isNodeValue){
+ ni.className = ni.className.replace('@'+cspec.attr, '');
+ if(isNodeValue){
+ cspec.attr = false;
+ }
+ }
+ an.push({n:ni, cspec:cspec});
+ }
+ }
+ }
+ }
+ return an;
+
+ function checkClass(c, tagName){
+ // read the class
+ var ca = c.match(selRx),
+ attr = ca[3] || autoAttr[tagName],
+ cspec = {prepend:!!ca[1], prop:ca[2], attr:attr, append:!!ca[4], sel:c},
+ i, ii, loopi, loopil, val;
+ // check in existing open loops
+ for(i = openLoops.a.length-1; i >= 0; i--){
+ loopi = openLoops.a[i];
+ loopil = loopi.l[0];
+ val = loopil && loopil[cspec.prop];
+ if(typeof val !== 'undefined'){
+ cspec.prop = loopi.p + '.' + cspec.prop;
+ if(openLoops.l[cspec.prop] === true){
+ val = val[0];
+ }
+ break;
+ }
+ }
+ // not found check first level of data
+ if(typeof val === 'undefined'){
+ val = isArray(data) ? data[0][cspec.prop] : data[cspec.prop];
+ // nothing found return
+ if(typeof val === 'undefined'){
+ return false;
+ }
+ }
+ // set the spec for autoNode
+ if(isArray(val)){
+ openLoops.a.push( {l:val, p:cspec.prop} );
+ openLoops.l[cspec.prop] = true;
+ cspec.t = 'loop';
+ }else{
+ cspec.t = 'str';
+ }
+ return cspec;
+ }
+ }
+
+ // returns a function that, given a context argument,
+ // will render the template defined by dom and directive.
+ function compiler(dom, directive, data, ans){
+ var fns = [];
+ // autoRendering nodes parsing -> auto-nodes
+ ans = ans || data && getAutoNodes(dom, data);
+ if(data){
+ var j, jj, cspec, n, target, nodes, itersel, node, inner;
+ // for each auto-nodes
+ while(ans.length > 0){
+ cspec = ans[0].cspec;
+ n = ans[0].n;
+ ans.splice(0, 1);
+ if(cspec.t === 'str'){
+ // if the target is a value
+ target = gettarget(n, cspec, false);
+ setsig(target, fns.length);
+ fns[fns.length] = wrapquote(target.quotefn, dataselectfn(cspec.prop));
+ }else{
+ // if the target is a loop
+ itersel = dataselectfn(cspec.sel);
+ target = gettarget(n, cspec, true);
+ nodes = target.nodes;
+ for(j = 0, jj = nodes.length; j < jj; j++){
+ node = nodes[j];
+ inner = compiler(node, false, data, ans);
+ fns[fns.length] = wrapquote(target.quotefn, loopfn(cspec.sel, itersel, inner));
+ target.nodes = [node];
+ setsig(target, fns.length - 1);
+ }
+ }
+ }
+ }
+ // read directives
+ var target, dsel;
+ for(var sel in directive){
+ if(directive.hasOwnProperty(sel)){
+ dsel = directive[sel];
+ if(typeof(dsel) === 'function' || typeof(dsel) === 'string'){
+ // set the value for the node/attr
+ target = gettarget(dom, sel, false);
+ setsig(target, fns.length);
+ fns[fns.length] = wrapquote(target.quotefn, dataselectfn(dsel));
+ }else{
+ // loop on node
+ loopgen(dom, sel, dsel, fns);
+ }
+ }
+ }
+ // convert node to a string
+ var h = outerHTML(dom), pfns = [];
+ // IE adds an unremovable "selected, value" attribute
+ // hard replace while waiting for a better solution
+ h = h.replace(/<([^>]+)\s(value\=""|selected)\s?([^>]*)>/ig, "<$1 $3>");
+
+ // remove attribute prefix
+ h = h.split(attPfx).join('');
+
+ // slice the html string at "Sig"
+ var parts = h.split( Sig ), p;
+ // for each slice add the return string of
+ for(var i = 1; i < parts.length; i++){
+ p = parts[i];
+ // part is of the form "fn-number:..." as placed there by setsig.
+ pfns[i] = fns[ parseInt(p, 10) ];
+ parts[i] = p.substring( p.indexOf(':') + 1 );
+ }
+ return concatenator(parts, pfns);
+ }
+ // compile the template with directive
+ // if a context is passed, the autoRendering is triggered automatically
+ // return a function waiting the data as argument
+ function compile(directive, ctxt, template){
+ var rfn = compiler( ( template || this[0] ).cloneNode(true), directive, ctxt);
+ return function(context){
+ return rfn({context:context});
+ };
+ }
+ //compile with the directive as argument
+ // run the template function on the context argument
+ // return an HTML string
+ // should replace the template and return this
+ function render(ctxt, directive){
+ var fn = typeof directive === 'function' ? directive : plugins.compile( directive, false, this[0] );
+ for(var i = 0, ii = this.length; i < ii; i++){
+ this[i] = replaceWith( this[i], fn( ctxt, false ));
+ }
+ context = null;
+ return this;
+ }
+
+ // compile the template with autoRender
+ // run the template function on the context argument
+ // return an HTML string
+ function autoRender(ctxt, directive){
+ var fn = plugins.compile( directive, ctxt, this[0] );
+ for(var i = 0, ii = this.length; i < ii; i++){
+ this[i] = replaceWith( this[i], fn( ctxt, false));
+ }
+ context = null;
+ return this;
+ }
+
+ function replaceWith(elm, html) {
+ var ne,
+ ep = elm.parentNode,
+ depth = 0;
+ switch (elm.tagName) {
+ case 'TBODY': case 'THEAD': case 'TFOOT':
+ html = '';
+ depth = 1;
+ break;
+ case 'TR':
+ html = '';
+ depth = 2;
+ break;
+ case 'TD': case 'TH':
+ html = '';
+ depth = 3;
+ break;
+ }
+ tmp = document.createElement('SPAN');
+ tmp.style.display = 'none';
+ document.body.appendChild(tmp);
+ tmp.innerHTML = html;
+ ne = tmp.firstChild;
+ while (depth--) {
+ ne = ne.firstChild;
+ }
+ ep.insertBefore(ne, elm);
+ ep.removeChild(elm);
+ document.body.removeChild(tmp);
+ elm = ne;
+
+ ne = ep = null;
+ return elm;
+ }
+};
+
+$p.plugins = {};
+
+$p.libs = {
+ dojo:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return dojo.query(sel, n);
+ };
+ }
+ },
+ domassistant:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return $(n).cssSelect(sel);
+ };
+ }
+ DOMAssistant.attach({
+ publicMethods : [ 'compile', 'render', 'autoRender'],
+ compile:function(directive, ctxt){ return $p(this).compile(directive, ctxt); },
+ render:function(ctxt, directive){ return $( $p(this).render(ctxt, directive) )[0]; },
+ autoRender:function(ctxt, directive){ return $( $p(this).autoRender(ctxt, directive) )[0]; }
+ });
+ },
+ jquery:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return jQuery(n).find(sel);
+ };
+ }
+ jQuery.fn.extend({
+ compile:function(directive, ctxt){ return $p(this[0]).compile(directive, ctxt); },
+ render:function(ctxt, directive){ return jQuery( $p( this[0] ).render( ctxt, directive ) ); },
+ autoRender:function(ctxt, directive){ return jQuery( $p( this[0] ).autoRender( ctxt, directive ) ); }
+ });
+ },
+ mootools:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return $(n).getElements(sel);
+ };
+ }
+ Element.implement({
+ compile:function(directive, ctxt){ return $p(this).compile(directive, ctxt); },
+ render:function(ctxt, directive){ return $p(this).render(ctxt, directive); },
+ autoRender:function(ctxt, directive){ return $p(this).autoRender(ctxt, directive); }
+ });
+ },
+ prototype:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ n = n === document ? n.body : n;
+ return typeof n === 'string' ? $$(n) : $(n).select(sel);
+ };
+ }
+ Element.addMethods({
+ compile:function(element, directive, ctxt){ return $p(element).compile(directive, ctxt); },
+ render:function(element, ctxt, directive){ return $p(element).render(ctxt, directive); },
+ autoRender:function(element, ctxt, directive){ return $p(element).autoRender(ctxt, directive); }
+ });
+ },
+ sizzle:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return Sizzle(sel, n);
+ };
+ }
+ },
+ sly:function(){
+ if(typeof document.querySelector === 'undefined'){
+ $p.plugins.find = function(n, sel){
+ return Sly(sel, n);
+ };
+ }
+ }
+};
+
+// get lib specifics if available
+(function(){
+ var libkey =
+ 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';
+
+ libkey && $p.libs[libkey]();
+})();
+
+
+ Sammy = Sammy || {};
+
+ // `Sammy.Pure` is a simple wrapper around the pure.js templating engine for
+ // use in Sammy apps.
+ //
+ // See http://beebole.com/pure/ for detailed documentation.
+ Sammy.Pure = function(app, method_alias) {
+
+ var pure = function(template, data, directives) {
+ return $(template).autoRender(data, directives);
+ };
+
+ // set the default method name/extension
+ if (!method_alias) method_alias = 'pure';
+ app.helper(method_alias, pure);
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.storage.js b/public/javascripts/plugins/sammy.storage.js
new file mode 100644
index 0000000..ad3cb86
--- /dev/null
+++ b/public/javascripts/plugins/sammy.storage.js
@@ -0,0 +1,577 @@
+(function($) {
+
+ Sammy = Sammy || {};
+
+ // Sammy.Store is an abstract adapter class that wraps the multitude of in
+ // browser data storage into a single common set of methods for storing and
+ // retreiving data. The JSON library is used (through the inclusion of the
+ // Sammy.JSON) plugin, to automatically convert objects back and forth from
+ // stored strings.
+ //
+ // Sammy.Store can be used directly, but within a Sammy.Application it is much
+ // easier to use the `Sammy.Storage` plugin and its helper methods.
+ //
+ // Sammy.Store also supports the KVO pattern, by firing DOM/jQuery Events when
+ // a key is set.
+ //
+ // ### Example
+ //
+ // // create a new store named 'mystore', tied to the #main element, using HTML5 localStorage
+ // // Note: localStorage only works on browsers that support it
+ // var store = new Sammy.Store({name: 'mystore', element: '#element', type: 'local'});
+ // store.set('foo', 'bar');
+ // store.get('foo'); //=> 'bar'
+ // store.set('json', {obj: 'this is an obj'});
+ // store.get('json'); //=> {obj: 'this is an obj'}
+ // store.keys(); //=> ['foo','json']
+ // store.clear('foo');
+ // store.keys(); //=> ['json']
+ // store.clearAll();
+ // store.keys(); //=> []
+ //
+ // ### Arguments
+ //
+ // The constructor takes a single argument which is a Object containing these possible options.
+ //
+ // * `name` The name/namespace of this store. Stores are unique by name/type. (default 'store')
+ // * `element` A selector for the element that the store is bound to. (default 'body')
+ // * `type` The type of storage/proxy to use (default 'memory')
+ //
+ // Extra options are passed to the storage constructor.
+ // Sammy.Store supports the following methods of storage:
+ //
+ // * `memory` Basic object storage
+ // * `data` jQuery.data DOM Storage
+ // * `cookie` Access to document.cookie. Limited to 2K
+ // * `local` HTML5 DOM localStorage, browswer support is currently limited.
+ // * `session` HTML5 DOM sessionStorage, browswer support is currently limited.
+ //
+ Sammy.Store = function(options) {
+ var store = this;
+ this.options = options || {};
+ this.name = this.options.name || 'store';
+ this.element = this.options.element || 'body';
+ this.$element = $(this.element);
+ if ($.isArray(this.options.type)) {
+ $.each(this.options.type, function(i, type) {
+ if (Sammy.Store.isAvailable(type)) {
+ store.type = type;
+ return false;
+ }
+ });
+ } else {
+ this.type = this.options.type || 'memory';
+ }
+ this.meta_key = this.options.meta_key || '__keys__';
+ this.storage = new Sammy.Store[Sammy.Store.stores[this.type]](this.name, this.element, this.options);
+ };
+
+ Sammy.Store.stores = {
+ 'memory': 'Memory',
+ 'data': 'Data',
+ 'local': 'LocalStorage',
+ 'session': 'SessionStorage',
+ 'cookie': 'Cookie'
+ };
+
+ $.extend(Sammy.Store.prototype, {
+ // Checks for the availability of the current storage type in the current browser/config.
+ isAvailable: function() {
+ if ($.isFunction(this.storage.isAvailable)) {
+ return this.storage.isAvailable();
+ } else {
+ true;
+ }
+ },
+ // Checks for the existance of `key` in the current store. Returns a boolean.
+ exists: function(key) {
+ return this.storage.exists(key);
+ },
+ // Sets the value of `key` with `value`. If `value` is an
+ // object, it is turned to and stored as a string with `JSON.stringify`.
+ // It also tries to conform to the KVO pattern triggering jQuery events on the
+ // element that the store is bound to.
+ //
+ // ### Example
+ //
+ // var store = new Sammy.Store({name: 'kvo'});
+ // $('body').bind('set-kvo-foo', function(e, data) {
+ // Sammy.log(data.key + ' changed to ' + data.value);
+ // });
+ // store.set('foo', 'bar'); // logged: foo changed to bar
+ //
+ set: function(key, value) {
+ var string_value = (typeof value == 'string') ? value : JSON.stringify(value);
+ key = key.toString();
+ this.storage.set(key, string_value);
+ if (key != this.meta_key) {
+ this._addKey(key);
+ this.$element.trigger('set-' + this.name, {key: key, value: value});
+ this.$element.trigger('set-' + this.name + '-' + key, {key: key, value: value});
+ };
+ // always return the original value
+ return value;
+ },
+ // Returns the set value at `key`, parsing with `JSON.parse` and
+ // turning into an object if possible
+ get: function(key) {
+ var value = this.storage.get(key);
+ if (typeof value == 'undefined' || value == null || value == '') {
+ return value;
+ }
+ try {
+ return JSON.parse(value);
+ } catch(e) {
+ return value;
+ }
+ },
+ // Removes the value at `key` from the current store
+ clear: function(key) {
+ this._removeKey(key);
+ return this.storage.clear(key);
+ },
+ // Clears all the values for the current store.
+ clearAll: function() {
+ var self = this;
+ this.each(function(key, value) {
+ self.clear(key);
+ });
+ },
+ // Returns the all the keys set for the current store as an array.
+ // Internally Sammy.Store keeps this array in a 'meta_key' for easy access.
+ keys: function() {
+ return this.get(this.meta_key) || [];
+ },
+ // Iterates over each key value pair passing them to the `callback` function
+ //
+ // ### Example
+ //
+ // store.each(function(key, value) {
+ // Sammy.log('key', key, 'value', value);
+ // });
+ //
+ each: function(callback) {
+ var i = 0,
+ keys = this.keys(),
+ returned;
+
+ for (i; i < keys.length; i++) {
+ returned = callback(keys[i], this.get(keys[i]));
+ if (returned === false) { return false; }
+ };
+ },
+ // Filters the store by a filter function that takes a key value.
+ // Returns an array of arrays where the first element of each array
+ // is the key and the second is the value of that key.
+ //
+ // ### Example
+ //
+ // var store = new Sammy.Store;
+ // store.set('one', 'two');
+ // store.set('two', 'three');
+ // store.set('1', 'two');
+ // var returned = store.filter(function(key, value) {
+ // // only return
+ // return value === 'two';
+ // });
+ // // returned => [['one', 'two'], ['1', 'two']];
+ //
+ filter: function(callback) {
+ var found = [];
+ this.each(function(key, value) {
+ if (callback(key, value)) {
+ found.push([key, value]);
+ }
+ return true;
+ });
+ return found;
+ },
+ // Works exactly like filter except only returns the first matching key
+ // value pair instead of all of them
+ first: function(callback) {
+ var found = false;
+ this.each(function(key, value) {
+ if (callback(key, value)) {
+ found = [key, value];
+ return false;
+ }
+ });
+ return found;
+ },
+ // Returns the value at `key` if set, otherwise, runs the callback
+ // and sets the value to the value returned in the callback.
+ //
+ // ### Example
+ //
+ // var store = new Sammy.Store;
+ // store.exists('foo'); //=> false
+ // store.fetch('foo', function() {
+ // return 'bar!';
+ // }); //=> 'bar!'
+ // store.get('foo') //=> 'bar!'
+ // store.fetch('foo', function() {
+ // return 'baz!';
+ // }); //=> 'bar!
+ //
+ fetch: function(key, callback) {
+ if (!this.exists(key)) {
+ return this.set(key, callback.apply(this));
+ } else {
+ return this.get(key);
+ }
+ },
+ // loads the response of a request to `path` into `key`.
+ //
+ // ### Example
+ //
+ // In /mytemplate.tpl:
+ //
+ // My Template
+ //
+ // In app.js:
+ //
+ // var store = new Sammy.Store;
+ // store.load('mytemplate', '/mytemplate.tpl', function() {
+ // s.get('mytemplate') //=> My Template
+ // });
+ //
+ load: function(key, path, callback) {
+ var s = this;
+ $.get(path, function(response) {
+ s.set(key, response);
+ if (callback) { callback.apply(this, [response]); }
+ });
+ },
+
+ _addKey: function(key) {
+ var keys = this.keys();
+ if ($.inArray(key, keys) == -1) { keys.push(key); }
+ this.set(this.meta_key, keys);
+ },
+ _removeKey: function(key) {
+ var keys = this.keys();
+ var index = $.inArray(key, keys);
+ if (index != -1) { keys.splice(index, 1); }
+ this.set(this.meta_key, keys);
+ }
+ });
+
+ // Tests if the type of storage is available/works in the current browser/config.
+ // Especially useful for testing the availability of the awesome, but not widely
+ // supported HTML5 DOM storage
+ Sammy.Store.isAvailable = function(type) {
+ try {
+ return Sammy.Store[Sammy.Store.stores[type]].prototype.isAvailable();
+ } catch(e) {
+ return false;
+ }
+ };
+
+ // Memory ('memory') is the basic/default store. It stores data in a global
+ // JS object. Data is lost on refresh.
+ Sammy.Store.Memory = function(name, element) {
+ this.name = name;
+ this.element = element;
+ this.namespace = [this.element, this.name].join('.');
+ Sammy.Store.Memory.store = Sammy.Store.Memory.store || {};
+ Sammy.Store.Memory.store[this.namespace] = Sammy.Store.Memory.store[this.namespace] || {};
+ this.store = Sammy.Store.Memory.store[this.namespace];
+ };
+ $.extend(Sammy.Store.Memory.prototype, {
+ isAvailable: function() { return true; },
+ exists: function(key) {
+ return (typeof this.store[key] != "undefined");
+ },
+ set: function(key, value) {
+ return this.store[key] = value;
+ },
+ get: function(key) {
+ return this.store[key];
+ },
+ clear: function(key) {
+ delete this.store[key];
+ }
+ });
+
+ // Data ('data') stores objects using the jQuery.data() methods. This has the advantadge
+ // of scoping the data to the specific element. Like the 'memory' store its data
+ // will only last for the length of the current request (data is lost on refresh/etc).
+ Sammy.Store.Data = function(name, element) {
+ this.name = name;
+ this.element = element;
+ this.$element = $(element);
+ };
+ $.extend(Sammy.Store.Data.prototype, {
+ isAvailable: function() { return true; },
+ exists: function(key) {
+ return !!this.$element.data(this._key(key));
+ },
+ set: function(key, value) {
+ return this.$element.data(this._key(key), value);
+ },
+ get: function(key) {
+ return this.$element.data(this._key(key));
+ },
+ clear: function(key) {
+ this.$element.removeData(this._key(key));
+ },
+ _key: function(key) {
+ return ['store', this.name, key].join('.');
+ }
+ });
+
+ // LocalStorage ('local') makes use of HTML5 DOM Storage, and the window.localStorage
+ // object. The great advantage of this method is that data will persist beyond
+ // the current request. It can be considered a pretty awesome replacement for
+ // cookies accessed via JS. The great disadvantage, though, is its only available
+ // on the latest and greatest browsers.
+ //
+ // For more info on DOM Storage:
+ // https://developer.mozilla.org/en/DOM/Storage
+ // http://www.w3.org/TR/2009/WD-webstorage-20091222/
+ //
+ Sammy.Store.LocalStorage = function(name, element) {
+ this.name = name;
+ this.element = element;
+ };
+ $.extend(Sammy.Store.LocalStorage.prototype, {
+ isAvailable: function() {
+ return ('localStorage' in window) && (window.location.protocol != 'file:');
+ },
+ exists: function(key) {
+ return (this.get(key) != null);
+ },
+ set: function(key, value) {
+ return window.localStorage.setItem(this._key(key), value);
+ },
+ get: function(key) {
+ return window.localStorage.getItem(this._key(key));
+ },
+ clear: function(key) {
+ window.localStorage.removeItem(this._key(key));;
+ },
+ _key: function(key) {
+ return ['store', this.element, this.name, key].join('.');
+ }
+ });
+
+ // .SessionStorage ('session') is similar to LocalStorage (part of the same API)
+ // and shares similar browser support/availability. The difference is that
+ // SessionStorage is only persistant through the current 'session' which is defined
+ // as the length that the current window is open. This means that data will survive
+ // refreshes but not close/open or multiple windows/tabs. For more info, check out
+ // the `LocalStorage` documentation and links.
+ Sammy.Store.SessionStorage = function(name, element) {
+ this.name = name;
+ this.element = element;
+ };
+ $.extend(Sammy.Store.SessionStorage.prototype, {
+ isAvailable: function() {
+ return ('sessionStorage' in window) &&
+ (window.location.protocol != 'file:') &&
+ ($.isFunction(window.sessionStorage.setItem));
+ },
+ exists: function(key) {
+ return (this.get(key) != null);
+ },
+ set: function(key, value) {
+ return window.sessionStorage.setItem(this._key(key), value);
+ },
+ get: function(key) {
+ var value = window.sessionStorage.getItem(this._key(key));
+ if (value && typeof value.value != "undefined") { value = value.value }
+ return value;
+ },
+ clear: function(key) {
+ window.sessionStorage.removeItem(this._key(key));;
+ },
+ _key: function(key) {
+ return ['store', this.element, this.name, key].join('.');
+ }
+ });
+
+ // .Cookie ('cookie') storage uses browser cookies to store data. JavaScript
+ // has access to a single document.cookie variable, which is limited to 2Kb in
+ // size. Cookies are also considered 'unsecure' as the data can be read easily
+ // by other sites/JS. Cookies do have the advantage, though, of being widely
+ // supported and persistent through refresh and close/open. Where available,
+ // HTML5 DOM Storage like LocalStorage and SessionStorage should be used.
+ //
+ // .Cookie can also take additional options:
+ //
+ // * `expires_in` Number of seconds to keep the cookie alive (default 2 weeks).
+ // * `path` The path to activate the current cookie for (default '/').
+ //
+ // For more information about document.cookie, check out the pre-eminint article
+ // by ppk: http://www.quirksmode.org/js/cookies.html
+ //
+ Sammy.Store.Cookie = function(name, element, options) {
+ this.name = name;
+ this.element = element;
+ this.options = options || {};
+ this.path = this.options.path || '/';
+ // set the expires in seconds or default 14 days
+ this.expires_in = this.options.expires_in || (14 * 24 * 60 * 60);
+ };
+ $.extend(Sammy.Store.Cookie.prototype, {
+ isAvailable: function() {
+ return ('cookie' in document) && (window.location.protocol != 'file:');
+ },
+ exists: function(key) {
+ return (this.get(key) != null);
+ },
+ set: function(key, value) {
+ return this._setCookie(key, value);
+ },
+ get: function(key) {
+ return this._getCookie(key);
+ },
+ clear: function(key) {
+ this._setCookie(key, "", -1);
+ },
+ _key: function(key) {
+ return ['store', this.element, this.name, key].join('.');
+ },
+ _getCookie: function(key) {
+ var escaped = this._key(key).replace(/(\.|\*|\(|\)|\[|\])/g, '\\$1');
+ var match = document.cookie.match("(^|;\\s)" + escaped + "=([^;]*)(;|$)")
+ return (match ? match[2] : null);
+ },
+ _setCookie: function(key, value, expires) {
+ if (!expires) { expires = (this.expires_in * 1000) }
+ var date = new Date();
+ date.setTime(date.getTime() + expires);
+ var set_cookie = [
+ this._key(key), "=", value,
+ "; expires=", date.toGMTString(),
+ "; path=", this.path
+ ].join('');
+ document.cookie = set_cookie;
+ }
+ });
+
+ // Sammy.Storage is a plugin that provides shortcuts for creating and using
+ // Sammy.Store objects. Once included it provides the `store()` app level
+ // and helper methods. Depends on Sammy.JSON (or json2.js).
+ Sammy.Storage = function(app) {
+ this.use(Sammy.JSON);
+
+ this.stores = this.stores || {};
+
+ // `store()` creates and looks up existing `Sammy.Store` objects
+ // for the current application. The first time used for a given `'name'`
+ // initializes a `Sammy.Store` and also creates a helper under the store's
+ // name.
+ //
+ // ### Example
+ //
+ // var app = $.sammy(function() {
+ // this.use(Sammy.Storage);
+ //
+ // // initializes the store on app creation.
+ // this.store('mystore', {type: 'cookie'});
+ //
+ // this.get('#/', function() {
+ // // returns the Sammy.Store object
+ // this.store('mystore');
+ // // sets 'foo' to 'bar' using the shortcut/helper
+ // // equivilent to this.store('mystore').set('foo', 'bar');
+ // this.mystore('foo', 'bar');
+ // // returns 'bar'
+ // // equivilent to this.store('mystore').get('foo');
+ // this.mystore('foo');
+ // // returns 'baz!'
+ // // equivilent to:
+ // // this.store('mystore').fetch('foo!', function() {
+ // // return 'baz!';
+ // // })
+ // this.mystore('foo!', function() {
+ // return 'baz!';
+ // });
+ //
+ // this.clearMystore();
+ // // equivilent to:
+ // // this.store('mystore').clearAll()
+ // });
+ //
+ // });
+ //
+ // ### Arguments
+ //
+ // * `name` The name of the store and helper. the name must be unique per application.
+ // * `options` A JS object of options that can be passed to the Store constuctor on initialization.
+ //
+ this.store = function(name, options) {
+ // if the store has not been initialized
+ if (typeof this.stores[name] == 'undefined') {
+ // create initialize the store
+ var clear_method_name = "clear" + name.substr(0,1).toUpperCase() + name.substr(1);
+ this.stores[name] = new Sammy.Store($.extend({
+ name: name,
+ element: this.element_selector
+ }, options || {}));
+ // app.name()
+ this[name] = function(key, value) {
+ if (typeof value == 'undefined') {
+ return this.stores[name].get(key);
+ } else if ($.isFunction(value)) {
+ return this.stores[name].fetch(key, value);
+ } else {
+ return this.stores[name].set(key, value)
+ }
+ };
+ // app.clearName();
+ this[clear_method_name] = function() {
+ return this.stores[name].clearAll();
+ }
+ // context.name()
+ this.helper(name, function() {
+ return this.app[name].apply(this.app, arguments);
+ });
+ // context.clearName();
+ this.helper(clear_method_name, function() {
+ return this.app[clear_method_name]();
+ });
+ }
+ return this.stores[name];
+ };
+
+ this.helpers({
+ store: function() {
+ return this.app.store.apply(this.app, arguments);
+ }
+ });
+ };
+
+ // Sammy.Session is an additional plugin for creating a common 'session' store
+ // for the given app. It is a very simple wrapper around `Sammy.Storage`
+ // that provides a simple fallback mechanism for trying to provide the best
+ // possible storage type for the session. This means, `LocalStorage`
+ // if available, otherwise `Cookie`, otherwise `Memory`.
+ // It provides the `session()` helper through `Sammy.Storage#store()`.
+ //
+ // See the `Sammy.Storage` plugin for full documentation.
+ //
+ Sammy.Session = function(app, options) {
+ this.use(Sammy.Storage);
+ // check for local storage, then cookie storage, then just use memory
+ this.store('session', $.extend({type: ['local', 'cookie', 'memory']}, options));
+ };
+
+ // Sammy.Cache provides helpers for caching data within the lifecycle of a
+ // Sammy app. The plugin provides two main methods on `Sammy.Application`,
+ // `cache` and `clearCache`. Each app has its own cache store so that
+ // you dont have to worry about collisions. As of 0.5 the original Sammy.Cache module
+ // has been deprecated in favor of this one based on Sammy.Storage. The exposed
+ // API is almost identical, but Sammy.Storage provides additional backends including
+ // HTML5 Storage. `Sammy.Cache` will try to use these backends when available
+ // (in this order) `LocalStorage`, `SessionStorage`, and `Memory`
+ Sammy.Cache = function(app, options) {
+ this.use(Sammy.Storage);
+ // set cache_partials to true
+ this.cache_partials = true;
+ // check for local storage, then session storage, then just use memory
+ this.store('cache', $.extend({type: ['local', 'session', 'memory']}, options));
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.template.js b/public/javascripts/plugins/sammy.template.js
new file mode 100644
index 0000000..2ce315f
--- /dev/null
+++ b/public/javascripts/plugins/sammy.template.js
@@ -0,0 +1,141 @@
+(function($) {
+
+ // Simple JavaScript Templating
+ // John Resig - http://ejohn.org/ - MIT Licensed
+ // adapted from: http://ejohn.org/blog/javascript-micro-templating/
+ // originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009
+ // modified for Sammy by Aaron Quint for caching templates by name
+ var srender_cache = {};
+ var srender = function(name, template, data, options) {
+ var fn, escaped_string;
+ // target is an optional element; if provided, the result will be inserted into it
+ // otherwise the result will simply be returned to the caller
+ if (srender_cache[name]) {
+ fn = srender_cache[name];
+ } else {
+ if (typeof template == 'undefined') {
+ // was a cache check, return false
+ return false;
+ }
+ // If options escape_html is false, dont escape the contents by default
+ if (options && options.escape_html === false) {
+ escaped_string = "\",$1,\"";
+ } else {
+ escaped_string = "\",h($1),\"";
+ }
+ // Generate a reusable function that will serve as a template
+ // generator (and which will be cached).
+ fn = srender_cache[name] = new Function("obj",
+ "var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};" +
+
+ // Introduce the data as local variables using with(){}
+ "with(obj){___$$$___.push(\"" +
+
+ // Convert the template into pure JavaScript
+ String(template)
+ .replace(/[\r\t\n]/g, " ")
+ .replace(/\"/g, '\\"')
+ .split("<%").join("\t")
+ .replace(/((^|%>)[^\t]*)/g, "$1\r")
+ .replace(/\t=(.*?)%>/g, escaped_string)
+ .replace(/\t!(.*?)%>/g, "\",$1,\"")
+ .split("\t").join("\");")
+ .split("%>").join("___$$$___.push(\"")
+ .split("\r").join("")
+ + "\");}return ___$$$___.join('');");
+ }
+
+ if (typeof data != 'undefined') {
+ return fn(data);
+ } else {
+ return fn;
+ }
+ };
+
+ Sammy = Sammy || {};
+
+ // `Sammy.Template` is a simple plugin that provides a way to create
+ // and render client side templates. The rendering code is based on John Resig's
+ // quick templates and Greg Borenstien's srender plugin.
+ // This is also a great template/boilerplate for Sammy plugins.
+ //
+ // Templates use `<% %>` tags to denote embedded javascript.
+ //
+ // ### Examples
+ //
+ // Here is an example template (user.template):
+ //
+ // // user.template
+ //
+ //
<%= user.name %>
+ // <% if (user.photo_url) { %>
+ //
+ // <% } %>
+ //
+ //
+ // Given that is a publicly accesible file, you would render it like:
+ //
+ // // app.js
+ // $.sammy(function() {
+ // // include the plugin
+ // this.use('Template');
+ //
+ // this.get('#/', function() {
+ // // the template is rendered in the current context.
+ // this.user = {name: 'Aaron Quint'};
+ // // partial calls template() because of the file extension
+ // this.partial('user.template');
+ // })
+ // });
+ //
+ // You can also pass a second argument to use() that will alias the template
+ // method and therefore allow you to use a different extension for template files
+ // in partial()
+ //
+ // // alias to 'tpl'
+ // this.use(Sammy.Template, 'tpl');
+ //
+ // // now .tpl files will be run through srender
+ // this.get('#/', function() {
+ // this.partial('myfile.tpl');
+ // });
+ //
+ // By default, the data passed into the tempalate is passed automatically passed through
+ // Sammy's `escapeHTML` method in order to prevent possible XSS attacks. This is
+ // a problem though if you're using something like `Sammy.Form` which renders HTML
+ // within the templates. You can get around this in two ways. One, you can use the
+ // `<%! %>` instead of `<%= %>`. Two, you can pass the `escape_html = false` option
+ // when interpolating, i.e:
+ //
+ // this.get('#/', function() {
+ // this.template('myform.tpl', {form: ""}, {escape_html: false});
+ // });
+ //
+ Sammy.Template = function(app, method_alias) {
+
+ // *Helper:* Uses simple templating to parse ERB like templates.
+ //
+ // ### Arguments
+ //
+ // * `template` A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data.
+ // * `data` An Object containing the replacement values for the template.
+ // data is extended with the EventContext allowing you to call its methods within the template.
+ // * `name` An optional String name to cache the template.
+ //
+ var template = function(template, data, name, options) {
+ // use name for caching
+ if (typeof name == 'undefined') { name = template; }
+ if (typeof options == 'undefined' && typeof name == 'object') {
+ options = name; name = template;
+ }
+ return srender(name, template, $.extend({}, this, data), options);
+ };
+
+ // set the default method name/extension
+ if (!method_alias) { method_alias = 'template'; }
+ // create the helper at the method alias
+ app.helper(method_alias, template);
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.title.js b/public/javascripts/plugins/sammy.title.js
new file mode 100644
index 0000000..9874a1e
--- /dev/null
+++ b/public/javascripts/plugins/sammy.title.js
@@ -0,0 +1,59 @@
+(function($) {
+
+ Sammy = Sammy || {};
+
+ // Sammy.Title is a very simple plugin to easily set the document's title.
+ // It supplies a helper for setting the title (`title()`) within routes,
+ // and an app level method for setting the global title (`setTitle()`)
+ Sammy.Title = function() {
+
+ // setTitle allows setting a global title or a function that modifies the
+ // title for each route/page.
+ //
+ // ### Example
+ //
+ // // setting a title prefix
+ // $.sammy(function() {
+ //
+ // this.setTitle('My App -');
+ //
+ // this.get('#/', function() {
+ // this.title('Home'); // document's title == "My App - Home"
+ // });
+ // });
+ //
+ // // setting a title with a function
+ // $.sammy(function() {
+ //
+ // this.setTitle(function(title) {
+ // return [title, " /// My App"].join('');
+ // });
+ //
+ // this.get('#/', function() {
+ // this.title('Home'); // document's title == "Home /// My App";
+ // });
+ // });
+ //
+ this.setTitle = function(title) {
+ if (!$.isFunction(title)) {
+ this.title_function = function(additional_title) {
+ return [title, additional_title].join(' ');
+ }
+ } else {
+ this.title_function = title;
+ }
+ };
+
+ // *Helper* title() sets the document title, passing it through the function
+ // defined by setTitle() if set.
+ this.helper('title', function() {
+ var new_title = $.makeArray(arguments).join(' ');
+ if (this.app.title_function) {
+ new_title = this.app.title_function(new_title);
+ }
+ document.title = new_title;
+ });
+
+ };
+
+})(jQuery);
diff --git a/public/javascripts/plugins/sammy.tmpl.js b/public/javascripts/plugins/sammy.tmpl.js
new file mode 100644
index 0000000..0e9f0a1
--- /dev/null
+++ b/public/javascripts/plugins/sammy.tmpl.js
@@ -0,0 +1,520 @@
+(function( jQuery, undefined ){
+
+ /*
+ * a port of the...
+ * jQuery Templating Plugin
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ */
+
+ var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
+ newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
+
+ function newTmplItem( options, parentItem, fn, data ) {
+ // Returns a template item data structure for a new rendered instance of a template (a 'template item').
+ // The content field is a hierarchical array of strings and nested items (to be
+ // removed and replaced by nodes field of dom elements, once inserted in DOM).
+ var newItem = {
+ data: data || (parentItem ? parentItem.data : {}),
+ _wrap: parentItem ? parentItem._wrap : null,
+ tmpl: null,
+ parent: parentItem || null,
+ nodes: [],
+ calls: tiCalls,
+ nest: tiNest,
+ wrap: tiWrap,
+ html: tiHtml,
+ update: tiUpdate
+ };
+ if ( options ) {
+ jQuery.extend( newItem, options, { nodes: [], parent: parentItem } );
+ }
+ if ( fn ) {
+ // Build the hierarchical content to be used during insertion into DOM
+ newItem.tmpl = fn;
+ newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem );
+ newItem.key = ++itemKey;
+ // Keep track of new template item, until it is stored as jQuery Data on DOM element
+ (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
+ }
+ return newItem;
+ }
+
+ // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
+ jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+ }, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems,
+ parent = this.length === 1 && this[0].parentNode;
+
+ appendToTmplItems = newTmplItems || {};
+ if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
+ insert[ original ]( this[0] );
+ ret = this;
+ } else {
+ for ( i = 0, l = insert.length; i < l; i++ ) {
+ cloneIndex = i;
+ elems = (i > 0 ? this.clone(true) : this).get();
+ jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
+ ret = ret.concat( elems );
+ }
+ cloneIndex = 0;
+ ret = this.pushStack( ret, name, insert.selector );
+ }
+ tmplItems = appendToTmplItems;
+ appendToTmplItems = null;
+ jQuery.tmpl.complete( tmplItems );
+ return ret;
+ };
+ });
+
+ jQuery.fn.extend({
+ // Use first wrapped element as template markup.
+ // Return wrapped set of template items, obtained by rendering template against data.
+ tmpl: function( data, options, parentItem ) {
+ return jQuery.tmpl( this[0], data, options, parentItem );
+ },
+
+ // Find which rendered template item the first wrapped DOM element belongs to
+ tmplItem: function() {
+ return jQuery.tmplItem( this[0] );
+ },
+
+ // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
+ template: function( name ) {
+ return jQuery.template( name, this[0] );
+ },
+
+ domManip: function( args, table, callback, options ) {
+ // This appears to be a bug in the appendTo, etc. implementation
+ // it should be doing .call() instead of .apply(). See #6227
+ if ( args[0] && args[0].nodeType ) {
+ var dmArgs = jQuery.makeArray( arguments ), argsLength = args.length, i = 0, tmplItem;
+ while ( i < argsLength && !(tmplItem = jQuery.data( args[i++], "tmplItem" ))) {}
+ if ( argsLength > 1 ) {
+ dmArgs[0] = [jQuery.makeArray( args )];
+ }
+ if ( tmplItem && cloneIndex ) {
+ dmArgs[2] = function( fragClone ) {
+ // Handler called by oldManip when rendered template has been inserted into DOM.
+ jQuery.tmpl.afterManip( this, fragClone, callback );
+ };
+ }
+ oldManip.apply( this, dmArgs );
+ } else {
+ oldManip.apply( this, arguments );
+ }
+ cloneIndex = 0;
+ if ( !appendToTmplItems ) {
+ jQuery.tmpl.complete( newTmplItems );
+ }
+ return this;
+ }
+ });
+
+ jQuery.extend({
+ // Return wrapped set of template items, obtained by rendering template against data.
+ tmpl: function( tmpl, data, options, parentItem ) {
+ var ret, topLevel = !parentItem;
+ if ( topLevel ) {
+ // This is a top-level tmpl call (not from a nested template using {{tmpl}})
+ parentItem = topTmplItem;
+ tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl );
+ wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
+ } else if ( !tmpl ) {
+ // The template item is already associated with DOM - this is a refresh.
+ // Re-evaluate rendered template for the parentItem
+ tmpl = parentItem.tmpl;
+ newTmplItems[parentItem.key] = parentItem;
+ parentItem.nodes = [];
+ if ( parentItem.wrapped ) {
+ updateWrapped( parentItem, parentItem.wrapped );
+ }
+ // Rebuild, without creating a new template item
+ return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) ));
+ }
+ if ( !tmpl ) {
+ return []; // Could throw...
+ }
+ if ( typeof data === "function" ) {
+ data = data.call( parentItem || {} );
+ }
+ if ( options && options.wrapped ) {
+ updateWrapped( options, options.wrapped );
+ }
+ ret = jQuery.isArray( data ) ?
+ jQuery.map( data, function( dataItem ) {
+ return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null;
+ }) :
+ [ newTmplItem( options, parentItem, tmpl, data ) ];
+ return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret;
+ },
+
+ // Return rendered template item for an element.
+ tmplItem: function( elem ) {
+ var tmplItem;
+ if ( elem instanceof jQuery ) {
+ elem = elem[0];
+ }
+ while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {}
+ return tmplItem || topTmplItem;
+ },
+
+ // Set:
+ // Use $.template( name, tmpl ) to cache a named template,
+ // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
+ // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
+
+ // Get:
+ // Use $.template( name ) to access a cached template.
+ // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
+ // will return the compiled template, without adding a name reference.
+ // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
+ // to $.template( null, templateString )
+ template: function( name, tmpl ) {
+ if (tmpl) {
+ // Compile template and associate with name
+ if ( typeof tmpl === "string" ) {
+ // This is an HTML string being passed directly in.
+ tmpl = buildTmplFn( tmpl )
+ } else if ( tmpl instanceof jQuery ) {
+ tmpl = tmpl[0] || {};
+ }
+ if ( tmpl.nodeType ) {
+ // If this is a template block, use cached copy, or generate tmpl function and cache.
+ tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML ));
+ }
+ return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
+ }
+ // Return named compiled template
+ return name ? (typeof name !== "string" ? jQuery.template( null, name ):
+ (jQuery.template[name] ||
+ // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec)
+ jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null;
+ },
+
+ encode: function( text ) {
+ // Do HTML encoding replacing < > & and ' and " by corresponding entities.
+ return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'");
+ }
+ });
+
+ jQuery.extend( jQuery.tmpl, {
+ tag: {
+ "tmpl": {
+ _default: { $2: "null" },
+ open: "if($notnull_1){___$$$___=___$$$___.concat($item.nest($1,$2));}"
+ // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
+ // This means that {{tmpl foo}} treats foo as a template (which IS a function).
+ // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
+ },
+ "wrap": {
+ _default: { $2: "null" },
+ open: "$item.calls(_,$1,$2);_=[];",
+ close: "call=$item.calls();___$$$___=call.___$$$___.concat($item.wrap(call,___$$$___));"
+ },
+ "each": {
+ _default: { $2: "$index, $value" },
+ open: "if($notnull_1){$.each($1a,function($2){with(this){",
+ close: "}});}"
+ },
+ "if": {
+ open: "if(($notnull_1) && $1a){",
+ close: "}"
+ },
+ "else": {
+ _default: { $1: "true" },
+ open: "}else if(($notnull_1) && $1a){"
+ },
+ "html": {
+ // Unecoded expression evaluation.
+ open: "if($notnull_1){___$$$___.push($1a);}"
+ },
+ "=": {
+ // Encoded expression evaluation. Abbreviated form is ${}.
+ _default: { $1: "$data" },
+ open: "if($notnull_1){___$$$___.push($.encode($1a));}"
+ },
+ "!": {
+ // Comment tag. Skipped by parser
+ open: ""
+ }
+ },
+
+ // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
+ complete: function( items ) {
+ newTmplItems = {};
+ },
+
+ // Call this from code which overrides domManip, or equivalent
+ // Manage cloning/storing template items etc.
+ afterManip: function afterManip( elem, fragClone, callback ) {
+ // Provides cloned fragment ready for fixup prior to and after insertion into DOM
+ var content = fragClone.nodeType === 11 ?
+ jQuery.makeArray(fragClone.childNodes) :
+ fragClone.nodeType === 1 ? [fragClone] : [];
+
+ // Return fragment to original caller (e.g. append) for DOM insertion
+ callback.call( elem, fragClone );
+
+ // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
+ storeTmplItems( content );
+ cloneIndex++;
+ }
+ });
+
+ //========================== Private helper functions, used by code above ==========================
+
+ function build( tmplItem, nested, content ) {
+ // Convert hierarchical content into flat string array
+ // and finally return array of fragments ready for DOM insertion
+ var frag, ret = content ? jQuery.map( content, function( item ) {
+ return (typeof item === "string") ?
+ // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
+ (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) :
+ // This is a child template item. Build nested template.
+ build( item, tmplItem, item._ctnt );
+ }) :
+ // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
+ tmplItem;
+ if ( nested ) {
+ return ret;
+ }
+
+ // top-level template
+ ret = ret.join("");
+
+ // Support templates which have initial or final text nodes, or consist only of text
+ // Also support HTML entities within the HTML markup.
+ ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) {
+ frag = jQuery( middle ).get();
+
+ storeTmplItems( frag );
+ if ( before ) {
+ frag = unencode( before ).concat(frag);
+ }
+ if ( after ) {
+ frag = frag.concat(unencode( after ));
+ }
+ });
+ return frag ? frag : unencode( ret );
+ }
+
+ function unencode( text ) {
+ // Use createElement, since createTextNode will not render HTML entities correctly
+ var el = document.createElement( "div" );
+ el.innerHTML = text;
+ return jQuery.makeArray(el.childNodes);
+ }
+
+ // Generate a reusable function that will serve to render a template against data
+ function buildTmplFn( markup ) {
+ return new Function("jQuery","$item",
+ "var $=jQuery,call,___$$$___=[],$data=$item.data;" +
+
+ // Introduce the data as local variables using with(){}
+ "with($data){___$$$___.push('" +
+
+ // Convert the template into pure JavaScript
+ jQuery.trim(markup)
+ .replace( /([\\'])/g, "\\$1" )
+ .replace( /[\r\t\n]/g, " " )
+ .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" )
+ .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
+ function( all, slash, type, fnargs, target, parens, args ) {
+ var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
+ if ( !tag ) {
+ throw "Template command not found: " + type;
+ }
+ def = tag._default || [];
+ if ( parens && !/\w$/.test(target)) {
+ target += parens;
+ parens = "";
+ }
+ if ( target ) {
+ target = unescape( target );
+ args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : "");
+ // Support for target being things like a.toLowerCase();
+ // In that case don't call with template item as 'this' pointer. Just evaluate...
+ expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($item" + args)) : target;
+ exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
+ } else {
+ exprAutoFnDetect = expr = def.$1 || "null";
+ }
+ fnargs = unescape( fnargs );
+ return "');" +
+ tag[ slash ? "close" : "open" ]
+ .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" )
+ .split( "$1a" ).join( exprAutoFnDetect )
+ .split( "$1" ).join( expr )
+ .split( "$2" ).join( fnargs ?
+ fnargs.replace( /\s*([^\(]+)\s*(\((.*?)\))?/g, function( all, name, parens, params ) {
+ params = params ? ("," + params + ")") : (parens ? ")" : "");
+ return params ? ("(" + name + ").call($item" + params) : all;
+ })
+ : (def.$2||"")
+ ) +
+ "___$$$___.push('";
+ }) +
+ "');}return ___$$$___;"
+ );
+ }
+ function updateWrapped( options, wrapped ) {
+ // Build the wrapped content.
+ options._wrap = build( options, true,
+ // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
+ jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()]
+ ).join("");
+ }
+
+ function unescape( args ) {
+ return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null;
+ }
+ function outerHtml( elem ) {
+ var div = document.createElement("div");
+ div.appendChild( elem.cloneNode(true) );
+ return div.innerHTML;
+ }
+
+ // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
+ function storeTmplItems( content ) {
+ var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
+ for ( i = 0, l = content.length; i < l; i++ ) {
+ if ( (elem = content[i]).nodeType !== 1 ) {
+ continue;
+ }
+ elems = elem.getElementsByTagName("*");
+ for ( m = elems.length - 1; m >= 0; m-- ) {
+ processItemKey( elems[m] );
+ }
+ processItemKey( elem );
+ }
+ function processItemKey( el ) {
+ var pntKey, pntNode = el, pntItem, tmplItem, key;
+ // Ensure that each rendered template inserted into the DOM has its own template item,
+ if ( (key = el.getAttribute( tmplItmAtt ))) {
+ while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { }
+ if ( pntKey !== key ) {
+ // The next ancestor with a _tmplitem expando is on a different key than this one.
+ // So this is a top-level element within this template item
+ // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
+ pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0;
+ if ( !(tmplItem = newTmplItems[key]) ) {
+ // The item is for wrapped content, and was copied from the temporary parent wrappedItem.
+ tmplItem = wrappedItems[key];
+ tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode], null, true );
+ tmplItem.key = ++itemKey;
+ newTmplItems[itemKey] = tmplItem;
+ }
+ if ( cloneIndex ) {
+ cloneTmplItem( key );
+ }
+ }
+ el.removeAttribute( tmplItmAtt );
+ } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) {
+ // This was a rendered element, cloned during append or appendTo etc.
+ // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
+ cloneTmplItem( tmplItem.key );
+ newTmplItems[tmplItem.key] = tmplItem;
+ pntNode = jQuery.data( el.parentNode, "tmplItem" );
+ pntNode = pntNode ? pntNode.key : 0;
+ }
+ if ( tmplItem ) {
+ pntItem = tmplItem;
+ // Find the template item of the parent element.
+ // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
+ while ( pntItem && pntItem.key != pntNode ) {
+ // Add this element as a top-level node for this rendered template item, as well as for any
+ // ancestor items between this item and the item of its parent element
+ pntItem.nodes.push( el );
+ pntItem = pntItem.parent;
+ }
+ // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
+ delete tmplItem._ctnt;
+ delete tmplItem._wrap;
+ // Store template item as jQuery data on the element
+ jQuery.data( el, "tmplItem", tmplItem );
+ }
+ function cloneTmplItem( key ) {
+ key = key + keySuffix;
+ tmplItem = newClonedItems[key] =
+ (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true ));
+ }
+ }
+ }
+
+ //---- Helper functions for template item ----
+
+ function tiCalls( content, tmpl, data, options ) {
+ if ( !content ) {
+ return stack.pop();
+ }
+ stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options });
+ }
+
+ function tiNest( tmpl, data, options ) {
+ // nested template, using {{tmpl}} tag
+ return jQuery.tmpl( jQuery.template( tmpl ), data, options, this );
+ }
+
+ function tiWrap( call, wrapped ) {
+ // nested template, using {{wrap}} tag
+ var options = call.options || {};
+ options.wrapped = wrapped;
+ // Apply the template, which may incorporate wrapped content,
+ return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item );
+ }
+
+ function tiHtml( filter, textOnly ) {
+ var wrapped = this._wrap;
+ return jQuery.map(
+ jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ),
+ function(e) {
+ return textOnly ?
+ e.innerText || e.textContent :
+ e.outerHTML || outerHtml(e);
+ });
+ }
+
+ function tiUpdate() {
+ var coll = this.nodes;
+ jQuery.tmpl( null, null, null, this).insertBefore( coll[0] );
+ jQuery( coll ).remove();
+ }
+
+ Sammy = Sammy || {};
+
+ Sammy.Tmpl = function(app, method_alias) {
+
+ // *Helper:* Uses jQuery-tmpl to parse a template and interpolate and work with the passed data
+ //
+ // ### Arguments
+ //
+ // * `template` A String template. '${ }' tags are evaluated as Javascript and replaced with the elements in data.
+ // * `data` An Object containing the replacement values for the template.
+ // data is extended with the EventContext allowing you to call its methods within the template.
+ // * `name` An optional String name to cache the template.
+ //
+ var template = function(template, data, name) {
+ // use name for caching
+ if (typeof name == 'undefined') name = template;
+
+ // check the cache
+ if (!jQuery.template[name]) jQuery.template(name, template);
+
+ // we could also pass along jQuery-tmpl options as a last param?
+ return jQuery.tmpl(name, jQuery.extend({}, this, data));
+ };
+
+ // set the default method name/extension
+ if (!method_alias) method_alias = 'tmpl';
+ // create the helper at the method alias
+ app.helper(method_alias, template);
+
+ };
+})( jQuery );
diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js
deleted file mode 100644
index 06249a6..0000000
--- a/public/javascripts/prototype.js
+++ /dev/null
@@ -1,6001 +0,0 @@
-/* Prototype JavaScript framework, version 1.7_rc2
- * (c) 2005-2010 Sam Stephenson
- *
- * Prototype is freely distributable under the terms of an MIT-style license.
- * For details, see the Prototype web site: http://www.prototypejs.org/
- *
- *--------------------------------------------------------------------------*/
-
-var Prototype = {
-
- Version: '1.7_rc2',
-
- Browser: (function(){
- var ua = navigator.userAgent;
- var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
- return {
- IE: !!window.attachEvent && !isOpera,
- Opera: isOpera,
- WebKit: ua.indexOf('AppleWebKit/') > -1,
- Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
- MobileSafari: /Apple.*Mobile/.test(ua)
- }
- })(),
-
- BrowserFeatures: {
- XPath: !!document.evaluate,
-
- SelectorsAPI: !!document.querySelector,
-
- ElementExtensions: (function() {
- var constructor = window.Element || window.HTMLElement;
- return !!(constructor && constructor.prototype);
- })(),
- SpecificElementExtensions: (function() {
- if (typeof window.HTMLDivElement !== 'undefined')
- return true;
-
- var div = document.createElement('div'),
- form = document.createElement('form'),
- isSupported = false;
-
- if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
- isSupported = true;
- }
-
- div = form = null;
-
- return isSupported;
- })()
- },
-
- ScriptFragment: '