` 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
deleted file mode 100644
index 169f0c7..0000000
--- a/public/javascripts/plugins/sammy.mustache.js
+++ /dev/null
@@ -1,444 +0,0 @@
-(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
deleted file mode 100644
index 645f8c9..0000000
--- a/public/javascripts/plugins/sammy.nested_params.js
+++ /dev/null
@@ -1,118 +0,0 @@
-(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
deleted file mode 100644
index 6d0fab2..0000000
--- a/public/javascripts/plugins/sammy.oauth2.js
+++ /dev/null
@@ -1,144 +0,0 @@
-(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
deleted file mode 100644
index 13f9014..0000000
--- a/public/javascripts/plugins/sammy.path_location_proxy.js
+++ /dev/null
@@ -1,29 +0,0 @@
-(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
deleted file mode 100644
index 16fe3df..0000000
--- a/public/javascripts/plugins/sammy.pure.js
+++ /dev/null
@@ -1,756 +0,0 @@
-(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
deleted file mode 100644
index ad3cb86..0000000
--- a/public/javascripts/plugins/sammy.storage.js
+++ /dev/null
@@ -1,577 +0,0 @@
-(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
deleted file mode 100644
index 2ce315f..0000000
--- a/public/javascripts/plugins/sammy.template.js
+++ /dev/null
@@ -1,141 +0,0 @@
-(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
deleted file mode 100644
index 9874a1e..0000000
--- a/public/javascripts/plugins/sammy.title.js
+++ /dev/null
@@ -1,59 +0,0 @@
-(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
deleted file mode 100644
index 0e9f0a1..0000000
--- a/public/javascripts/plugins/sammy.tmpl.js
+++ /dev/null
@@ -1,520 +0,0 @@
-(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/sammy.js b/public/javascripts/sammy.js
deleted file mode 100644
index d798892..0000000
--- a/public/javascripts/sammy.js
+++ /dev/null
@@ -1,1829 +0,0 @@
-// name: sammy
-// version: 0.6.3
-
-// Sammy.js / http://sammyjs.org
-
-(function($, window) {
-
- var Sammy,
- PATH_REPLACER = "([^\/]+)",
- PATH_NAME_MATCHER = /:([\w\d]+)/g,
- QUERY_STRING_MATCHER = /\?([^#]*)$/,
- // mainly for making `arguments` an Array
- _makeArray = function(nonarray) { return Array.prototype.slice.call(nonarray); },
- // borrowed from jQuery
- _isFunction = function( obj ) { return Object.prototype.toString.call(obj) === "[object Function]"; },
- _isArray = function( obj ) { return Object.prototype.toString.call(obj) === "[object Array]"; },
- _decode = function( str ) { return decodeURIComponent(str.replace(/\+/g, ' ')); },
- _encode = encodeURIComponent,
- _escapeHTML = function(s) {
- return String(s).replace(/&(?!\w+;)/g, '&').replace(//g, '>').replace(/"/g, '"');
- },
- _routeWrapper = function(verb) {
- return function(path, callback) { return this.route.apply(this, [verb, path, callback]); };
- },
- _template_cache = {},
- loggers = [];
-
-
- // `Sammy` (also aliased as $.sammy) is not only the namespace for a
- // number of prototypes, its also a top level method that allows for easy
- // creation/management of `Sammy.Application` instances. There are a
- // number of different forms for `Sammy()` but each returns an instance
- // of `Sammy.Application`. When a new instance is created using
- // `Sammy` it is added to an Object called `Sammy.apps`. This
- // provides for an easy way to get at existing Sammy applications. Only one
- // instance is allowed per `element_selector` so when calling
- // `Sammy('selector')` multiple times, the first time will create
- // the application and the following times will extend the application
- // already added to that selector.
- //
- // ### Example
- //
- // // returns the app at #main or a new app
- // Sammy('#main')
- //
- // // equivilent to "new Sammy.Application", except appends to apps
- // Sammy();
- // Sammy(function() { ... });
- //
- // // extends the app at '#main' with function.
- // Sammy('#main', function() { ... });
- //
- Sammy = function() {
- var args = _makeArray(arguments),
- app, selector;
- Sammy.apps = Sammy.apps || {};
- if (args.length === 0 || args[0] && _isFunction(args[0])) { // Sammy()
- return Sammy.apply(Sammy, ['body'].concat(args));
- } else if (typeof (selector = args.shift()) == 'string') { // Sammy('#main')
- app = Sammy.apps[selector] || new Sammy.Application();
- app.element_selector = selector;
- if (args.length > 0) {
- $.each(args, function(i, plugin) {
- app.use(plugin);
- });
- }
- // if the selector changes make sure the refrence in Sammy.apps changes
- if (app.element_selector != selector) {
- delete Sammy.apps[selector];
- }
- Sammy.apps[app.element_selector] = app;
- return app;
- }
- };
-
- Sammy.VERSION = '0.6.3';
-
- // Add to the global logger pool. Takes a function that accepts an
- // unknown number of arguments and should print them or send them somewhere
- // The first argument is always a timestamp.
- Sammy.addLogger = function(logger) {
- loggers.push(logger);
- };
-
- // Sends a log message to each logger listed in the global
- // loggers pool. Can take any number of arguments.
- // Also prefixes the arguments with a timestamp.
- Sammy.log = function() {
- var args = _makeArray(arguments);
- args.unshift("[" + Date() + "]");
- $.each(loggers, function(i, logger) {
- logger.apply(Sammy, args);
- });
- };
-
- if (typeof window.console != 'undefined') {
- if (_isFunction(window.console.log.apply)) {
- Sammy.addLogger(function() {
- window.console.log.apply(window.console, arguments);
- });
- } else {
- Sammy.addLogger(function() {
- window.console.log(arguments);
- });
- }
- } else if (typeof console != 'undefined') {
- Sammy.addLogger(function() {
- console.log.apply(console, arguments);
- });
- }
-
- $.extend(Sammy, {
- makeArray: _makeArray,
- isFunction: _isFunction,
- isArray: _isArray
- })
-
- // Sammy.Object is the base for all other Sammy classes. It provides some useful
- // functionality, including cloning, iterating, etc.
- Sammy.Object = function(obj) { // constructor
- return $.extend(this, obj || {});
- };
-
- $.extend(Sammy.Object.prototype, {
-
- // Escape HTML in string, use in templates to prevent script injection.
- // Also aliased as `h()`
- escapeHTML: _escapeHTML,
- h: _escapeHTML,
-
- // Returns a copy of the object with Functions removed.
- toHash: function() {
- var json = {};
- $.each(this, function(k,v) {
- if (!_isFunction(v)) {
- json[k] = v;
- }
- });
- return json;
- },
-
- // Renders a simple HTML version of this Objects attributes.
- // Does not render functions.
- // For example. Given this Sammy.Object:
- //
- // var s = new Sammy.Object({first_name: 'Sammy', last_name: 'Davis Jr.'});
- // s.toHTML()
- // //=> 'first_name Sammy
last_name Davis Jr.
'
- //
- toHTML: function() {
- var display = "";
- $.each(this, function(k, v) {
- if (!_isFunction(v)) {
- display += "" + k + " " + v + "
";
- }
- });
- return display;
- },
-
- // Returns an array of keys for this object. If `attributes_only`
- // is true will not return keys that map to a `function()`
- keys: function(attributes_only) {
- var keys = [];
- for (var property in this) {
- if (!_isFunction(this[property]) || !attributes_only) {
- keys.push(property);
- }
- }
- return keys;
- },
-
- // Checks if the object has a value at `key` and that the value is not empty
- has: function(key) {
- return this[key] && $.trim(this[key].toString()) != '';
- },
-
- // convenience method to join as many arguments as you want
- // by the first argument - useful for making paths
- join: function() {
- var args = _makeArray(arguments);
- var delimiter = args.shift();
- return args.join(delimiter);
- },
-
- // Shortcut to Sammy.log
- log: function() {
- Sammy.log.apply(Sammy, arguments);
- },
-
- // Returns a string representation of this object.
- // if `include_functions` is true, it will also toString() the
- // methods of this object. By default only prints the attributes.
- toString: function(include_functions) {
- var s = [];
- $.each(this, function(k, v) {
- if (!_isFunction(v) || include_functions) {
- s.push('"' + k + '": ' + v.toString());
- }
- });
- return "Sammy.Object: {" + s.join(',') + "}";
- }
- });
-
- // The HashLocationProxy is the default location proxy for all Sammy applications.
- // A location proxy is a prototype that conforms to a simple interface. The purpose
- // of a location proxy is to notify the Sammy.Application its bound to when the location
- // or 'external state' changes. The HashLocationProxy considers the state to be
- // changed when the 'hash' (window.location.hash / '#') changes. It does this in two
- // different ways depending on what browser you are using. The newest browsers
- // (IE, Safari > 4, FF >= 3.6) support a 'onhashchange' DOM event, thats fired whenever
- // the location.hash changes. In this situation the HashLocationProxy just binds
- // to this event and delegates it to the application. In the case of older browsers
- // a poller is set up to track changes to the hash. Unlike Sammy 0.3 or earlier,
- // the HashLocationProxy allows the poller to be a global object, eliminating the
- // need for multiple pollers even when thier are multiple apps on the page.
- Sammy.HashLocationProxy = function(app, run_interval_every) {
- this.app = app;
- // set is native to false and start the poller immediately
- this.is_native = false;
- this._startPolling(run_interval_every);
- };
-
- Sammy.HashLocationProxy.prototype = {
-
- // bind the proxy events to the current app.
- bind: function() {
- var proxy = this, app = this.app;
- $(window).bind('hashchange.' + this.app.eventNamespace(), function(e, non_native) {
- // if we receive a native hash change event, set the proxy accordingly
- // and stop polling
- if (proxy.is_native === false && !non_native) {
- Sammy.log('native hash change exists, using');
- proxy.is_native = true;
- window.clearInterval(Sammy.HashLocationProxy._interval);
- }
- app.trigger('location-changed');
- });
- if (!Sammy.HashLocationProxy._bindings) {
- Sammy.HashLocationProxy._bindings = 0;
- }
- Sammy.HashLocationProxy._bindings++;
- },
-
- // unbind the proxy events from the current app
- unbind: function() {
- $(window).unbind('hashchange.' + this.app.eventNamespace());
- Sammy.HashLocationProxy._bindings--;
- if (Sammy.HashLocationProxy._bindings <= 0) {
- window.clearInterval(Sammy.HashLocationProxy._interval);
- }
- },
-
- // get the current location from the hash.
- getLocation: function() {
- // Bypass the `window.location.hash` attribute. If a question mark
- // appears in the hash IE6 will strip it and all of the following
- // characters from `window.location.hash`.
- var matches = window.location.toString().match(/^[^#]*(#.+)$/);
- return matches ? matches[1] : '';
- },
-
- // set the current location to `new_location`
- setLocation: function(new_location) {
- return (window.location = new_location);
- },
-
- _startPolling: function(every) {
- // set up interval
- var proxy = this;
- if (!Sammy.HashLocationProxy._interval) {
- if (!every) { every = 10; }
- var hashCheck = function() {
- var current_location = proxy.getLocation();
- if (!Sammy.HashLocationProxy._last_location ||
- current_location != Sammy.HashLocationProxy._last_location) {
- window.setTimeout(function() {
- $(window).trigger('hashchange', [true]);
- }, 13);
- }
- Sammy.HashLocationProxy._last_location = current_location;
- };
- hashCheck();
- Sammy.HashLocationProxy._interval = window.setInterval(hashCheck, every);
- }
- }
- };
-
-
- // Sammy.Application is the Base prototype for defining 'applications'.
- // An 'application' is a collection of 'routes' and bound events that is
- // attached to an element when `run()` is called.
- // The only argument an 'app_function' is evaluated within the context of the application.
- Sammy.Application = function(app_function) {
- var app = this;
- this.routes = {};
- this.listeners = new Sammy.Object({});
- this.arounds = [];
- this.befores = [];
- // generate a unique namespace
- this.namespace = (new Date()).getTime() + '-' + parseInt(Math.random() * 1000, 10);
- this.context_prototype = function() { Sammy.EventContext.apply(this, arguments); };
- this.context_prototype.prototype = new Sammy.EventContext();
-
- if (_isFunction(app_function)) {
- app_function.apply(this, [this]);
- }
- // set the location proxy if not defined to the default (HashLocationProxy)
- if (!this._location_proxy) {
- this.setLocationProxy(new Sammy.HashLocationProxy(this, this.run_interval_every));
- }
- if (this.debug) {
- this.bindToAllEvents(function(e, data) {
- app.log(app.toString(), e.cleaned_type, data || {});
- });
- }
- };
-
- Sammy.Application.prototype = $.extend({}, Sammy.Object.prototype, {
-
- // the four route verbs
- ROUTE_VERBS: ['get','post','put','delete'],
-
- // An array of the default events triggered by the
- // application during its lifecycle
- APP_EVENTS: ['run', 'unload', 'lookup-route', 'run-route', 'route-found', 'event-context-before', 'event-context-after', 'changed', 'error', 'check-form-submission', 'redirect', 'location-changed'],
-
- _last_route: null,
- _location_proxy: null,
- _running: false,
-
- // Defines what element the application is bound to. Provide a selector
- // (parseable by `jQuery()`) and this will be used by `$element()`
- element_selector: 'body',
-
- // When set to true, logs all of the default events using `log()`
- debug: false,
-
- // When set to true, and the error() handler is not overriden, will actually
- // raise JS errors in routes (500) and when routes can't be found (404)
- raise_errors: false,
-
- // The time in milliseconds that the URL is queried for changes
- run_interval_every: 50,
-
- // The default template engine to use when using `partial()` in an
- // `EventContext`. `template_engine` can either be a string that
- // corresponds to the name of a method/helper on EventContext or it can be a function
- // that takes two arguments, the content of the unrendered partial and an optional
- // JS object that contains interpolation data. Template engine is only called/refered
- // to if the extension of the partial is null or unknown. See `partial()`
- // for more information
- template_engine: null,
-
- // //=> Sammy.Application: body
- toString: function() {
- return 'Sammy.Application:' + this.element_selector;
- },
-
- // returns a jQuery object of the Applications bound element.
- $element: function(selector) {
- return selector ? $(this.element_selector).find(selector) : $(this.element_selector);
- },
-
- // `use()` is the entry point for including Sammy plugins.
- // The first argument to use should be a function() that is evaluated
- // in the context of the current application, just like the `app_function`
- // argument to the `Sammy.Application` constructor.
- //
- // Any additional arguments are passed to the app function sequentially.
- //
- // For much more detail about plugins, check out:
- // [http://sammyjs.org/docs/plugins](http://sammyjs.org/docs/plugins)
- //
- // ### Example
- //
- // var MyPlugin = function(app, prepend) {
- //
- // this.helpers({
- // myhelper: function(text) {
- // alert(prepend + " " + text);
- // }
- // });
- //
- // };
- //
- // var app = $.sammy(function() {
- //
- // this.use(MyPlugin, 'This is my plugin');
- //
- // this.get('#/', function() {
- // this.myhelper('and dont you forget it!');
- // //=> Alerts: This is my plugin and dont you forget it!
- // });
- //
- // });
- //
- // If plugin is passed as a string it assumes your are trying to load
- // Sammy."Plugin". This is the prefered way of loading core Sammy plugins
- // as it allows for better error-messaging.
- //
- // ### Example
- //
- // $.sammy(function() {
- // this.use('Mustache'); //=> Sammy.Mustache
- // this.use('Storage'); //=> Sammy.Storage
- // });
- //
- use: function() {
- // flatten the arguments
- var args = _makeArray(arguments),
- plugin = args.shift(),
- plugin_name = plugin || '';
- try {
- args.unshift(this);
- if (typeof plugin == 'string') {
- plugin_name = 'Sammy.' + plugin;
- plugin = Sammy[plugin];
- }
- plugin.apply(this, args);
- } catch(e) {
- if (typeof plugin === 'undefined') {
- this.error("Plugin Error: called use() but plugin (" + plugin_name.toString() + ") is not defined", e);
- } else if (!_isFunction(plugin)) {
- this.error("Plugin Error: called use() but '" + plugin_name.toString() + "' is not a function", e);
- } else {
- this.error("Plugin Error", e);
- }
- }
- return this;
- },
-
- // Sets the location proxy for the current app. By default this is set to
- // a new `Sammy.HashLocationProxy` on initialization. However, you can set
- // the location_proxy inside you're app function to give your app a custom
- // location mechanism. See `Sammy.HashLocationProxy` and `Sammy.DataLocationProxy`
- // for examples.
- //
- // `setLocationProxy()` takes an initialized location proxy.
- //
- // ### Example
- //
- // // to bind to data instead of the default hash;
- // var app = $.sammy(function() {
- // this.setLocationProxy(new Sammy.DataLocationProxy(this));
- // });
- //
- setLocationProxy: function(new_proxy) {
- var original_proxy = this._location_proxy;
- this._location_proxy = new_proxy;
- if (this.isRunning()) {
- if (original_proxy) {
- // if there is already a location proxy, unbind it.
- original_proxy.unbind();
- }
- this._location_proxy.bind();
- }
- },
-
- // `route()` is the main method for defining routes within an application.
- // For great detail on routes, check out:
- // [http://sammyjs.org/docs/routes](http://sammyjs.org/docs/routes)
- //
- // This method also has aliases for each of the different verbs (eg. `get()`, `post()`, etc.)
- //
- // ### Arguments
- //
- // * `verb` A String in the set of ROUTE_VERBS or 'any'. 'any' will add routes for each
- // of the ROUTE_VERBS. If only two arguments are passed,
- // the first argument is the path, the second is the callback and the verb
- // is assumed to be 'any'.
- // * `path` A Regexp or a String representing the path to match to invoke this verb.
- // * `callback` A Function that is called/evaluated whent the route is run see: `runRoute()`.
- // It is also possible to pass a string as the callback, which is looked up as the name
- // of a method on the application.
- //
- route: function(verb, path, callback) {
- var app = this, param_names = [], add_route, path_match;
-
- // if the method signature is just (path, callback)
- // assume the verb is 'any'
- if (!callback && _isFunction(path)) {
- path = verb;
- callback = path;
- verb = 'any';
- }
-
- verb = verb.toLowerCase(); // ensure verb is lower case
-
- // if path is a string turn it into a regex
- if (path.constructor == String) {
-
- // Needs to be explicitly set because IE will maintain the index unless NULL is returned,
- // which means that with two consecutive routes that contain params, the second set of params will not be found and end up in splat instead of params
- // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/RegExp/lastIndex
- PATH_NAME_MATCHER.lastIndex = 0;
-
- // find the names
- while ((path_match = PATH_NAME_MATCHER.exec(path)) !== null) {
- param_names.push(path_match[1]);
- }
- // replace with the path replacement
- path = new RegExp("^" + path.replace(PATH_NAME_MATCHER, PATH_REPLACER) + "$");
- }
- // lookup callback
- if (typeof callback == 'string') {
- callback = app[callback];
- }
-
- add_route = function(with_verb) {
- var r = {verb: with_verb, path: path, callback: callback, param_names: param_names};
- // add route to routes array
- app.routes[with_verb] = app.routes[with_verb] || [];
- // place routes in order of definition
- app.routes[with_verb].push(r);
- };
-
- if (verb === 'any') {
- $.each(this.ROUTE_VERBS, function(i, v) { add_route(v); });
- } else {
- add_route(verb);
- }
-
- // return the app
- return this;
- },
-
- // Alias for route('get', ...)
- get: _routeWrapper('get'),
-
- // Alias for route('post', ...)
- post: _routeWrapper('post'),
-
- // Alias for route('put', ...)
- put: _routeWrapper('put'),
-
- // Alias for route('delete', ...)
- del: _routeWrapper('delete'),
-
- // Alias for route('any', ...)
- any: _routeWrapper('any'),
-
- // `mapRoutes` takes an array of arrays, each array being passed to route()
- // as arguments, this allows for mass definition of routes. Another benefit is
- // this makes it possible/easier to load routes via remote JSON.
- //
- // ### Example
- //
- // var app = $.sammy(function() {
- //
- // this.mapRoutes([
- // ['get', '#/', function() { this.log('index'); }],
- // // strings in callbacks are looked up as methods on the app
- // ['post', '#/create', 'addUser'],
- // // No verb assumes 'any' as the verb
- // [/dowhatever/, function() { this.log(this.verb, this.path)}];
- // ]);
- // });
- //
- mapRoutes: function(route_array) {
- var app = this;
- $.each(route_array, function(i, route_args) {
- app.route.apply(app, route_args);
- });
- return this;
- },
-
- // A unique event namespace defined per application.
- // All events bound with `bind()` are automatically bound within this space.
- eventNamespace: function() {
- return ['sammy-app', this.namespace].join('-');
- },
-
- // Works just like `jQuery.fn.bind()` with a couple noteable differences.
- //
- // * It binds all events to the application element
- // * All events are bound within the `eventNamespace()`
- // * Events are not actually bound until the application is started with `run()`
- // * callbacks are evaluated within the context of a Sammy.EventContext
- //
- bind: function(name, data, callback) {
- var app = this;
- // build the callback
- // if the arity is 2, callback is the second argument
- if (typeof callback == 'undefined') { callback = data; }
- var listener_callback = function() {
- // pull off the context from the arguments to the callback
- var e, context, data;
- e = arguments[0];
- data = arguments[1];
- if (data && data.context) {
- context = data.context;
- delete data.context;
- } else {
- context = new app.context_prototype(app, 'bind', e.type, data, e.target);
- }
- e.cleaned_type = e.type.replace(app.eventNamespace(), '');
- callback.apply(context, [e, data]);
- };
-
- // it could be that the app element doesnt exist yet
- // so attach to the listeners array and then run()
- // will actually bind the event.
- if (!this.listeners[name]) { this.listeners[name] = []; }
- this.listeners[name].push(listener_callback);
- if (this.isRunning()) {
- // if the app is running
- // *actually* bind the event to the app element
- this._listen(name, listener_callback);
- }
- return this;
- },
-
- // Triggers custom events defined with `bind()`
- //
- // ### Arguments
- //
- // * `name` The name of the event. Automatically prefixed with the `eventNamespace()`
- // * `data` An optional Object that can be passed to the bound callback.
- // * `context` An optional context/Object in which to execute the bound callback.
- // If no context is supplied a the context is a new `Sammy.EventContext`
- //
- trigger: function(name, data) {
- this.$element().trigger([name, this.eventNamespace()].join('.'), [data]);
- return this;
- },
-
- // Reruns the current route
- refresh: function() {
- this.last_location = null;
- this.trigger('location-changed');
- return this;
- },
-
- // Takes a single callback that is pushed on to a stack.
- // Before any route is run, the callbacks are evaluated in order within
- // the current `Sammy.EventContext`
- //
- // If any of the callbacks explicitly return false, execution of any
- // further callbacks and the route itself is halted.
- //
- // You can also provide a set of options that will define when to run this
- // before based on the route it proceeds.
- //
- // ### Example
- //
- // var app = $.sammy(function() {
- //
- // // will run at #/route but not at #/
- // this.before('#/route', function() {
- // //...
- // });
- //
- // // will run at #/ but not at #/route
- // this.before({except: {path: '#/route'}}, function() {
- // this.log('not before #/route');
- // });
- //
- // this.get('#/', function() {});
- //
- // this.get('#/route', function() {});
- //
- // });
- //
- // See `contextMatchesOptions()` for a full list of supported options
- //
- before: function(options, callback) {
- if (_isFunction(options)) {
- callback = options;
- options = {};
- }
- this.befores.push([options, callback]);
- return this;
- },
-
- // A shortcut for binding a callback to be run after a route is executed.
- // After callbacks have no guarunteed order.
- after: function(callback) {
- return this.bind('event-context-after', callback);
- },
-
-
- // Adds an around filter to the application. around filters are functions
- // that take a single argument `callback` which is the entire route
- // execution path wrapped up in a closure. This means you can decide whether
- // or not to proceed with execution by not invoking `callback` or,
- // more usefuly wrapping callback inside the result of an asynchronous execution.
- //
- // ### Example
- //
- // The most common use case for around() is calling a _possibly_ async function
- // and executing the route within the functions callback:
- //
- // var app = $.sammy(function() {
- //
- // var current_user = false;
- //
- // function checkLoggedIn(callback) {
- // // /session returns a JSON representation of the logged in user
- // // or an empty object
- // if (!current_user) {
- // $.getJSON('/session', function(json) {
- // if (json.login) {
- // // show the user as logged in
- // current_user = json;
- // // execute the route path
- // callback();
- // } else {
- // // show the user as not logged in
- // current_user = false;
- // // the context of aroundFilters is an EventContext
- // this.redirect('#/login');
- // }
- // });
- // } else {
- // // execute the route path
- // callback();
- // }
- // };
- //
- // this.around(checkLoggedIn);
- //
- // });
- //
- around: function(callback) {
- this.arounds.push(callback);
- return this;
- },
-
- // Returns `true` if the current application is running.
- isRunning: function() {
- return this._running;
- },
-
- // Helpers extends the EventContext prototype specific to this app.
- // This allows you to define app specific helper functions that can be used
- // whenever you're inside of an event context (templates, routes, bind).
- //
- // ### Example
- //
- // var app = $.sammy(function() {
- //
- // helpers({
- // upcase: function(text) {
- // return text.toString().toUpperCase();
- // }
- // });
- //
- // get('#/', function() { with(this) {
- // // inside of this context I can use the helpers
- // $('#main').html(upcase($('#main').text());
- // }});
- //
- // });
- //
- //
- // ### Arguments
- //
- // * `extensions` An object collection of functions to extend the context.
- //
- helpers: function(extensions) {
- $.extend(this.context_prototype.prototype, extensions);
- return this;
- },
-
- // Helper extends the event context just like `helpers()` but does it
- // a single method at a time. This is especially useful for dynamically named
- // helpers
- //
- // ### Example
- //
- // // Trivial example that adds 3 helper methods to the context dynamically
- // var app = $.sammy(function(app) {
- //
- // $.each([1,2,3], function(i, num) {
- // app.helper('helper' + num, function() {
- // this.log("I'm helper number " + num);
- // });
- // });
- //
- // this.get('#/', function() {
- // this.helper2(); //=> I'm helper number 2
- // });
- // });
- //
- // ### Arguments
- //
- // * `name` The name of the method
- // * `method` The function to be added to the prototype at `name`
- //
- helper: function(name, method) {
- this.context_prototype.prototype[name] = method;
- return this;
- },
-
- // Actually starts the application's lifecycle. `run()` should be invoked
- // within a document.ready block to ensure the DOM exists before binding events, etc.
- //
- // ### Example
- //
- // var app = $.sammy(function() { ... }); // your application
- // $(function() { // document.ready
- // app.run();
- // });
- //
- // ### Arguments
- //
- // * `start_url` Optionally, a String can be passed which the App will redirect to
- // after the events/routes have been bound.
- run: function(start_url) {
- if (this.isRunning()) { return false; }
- var app = this;
-
- // actually bind all the listeners
- $.each(this.listeners.toHash(), function(name, callbacks) {
- $.each(callbacks, function(i, listener_callback) {
- app._listen(name, listener_callback);
- });
- });
-
- this.trigger('run', {start_url: start_url});
- this._running = true;
- // set last location
- this.last_location = null;
- if (this.getLocation() == '' && typeof start_url != 'undefined') {
- this.setLocation(start_url);
- }
- // check url
- this._checkLocation();
- this._location_proxy.bind();
- this.bind('location-changed', function() {
- app._checkLocation();
- });
-
- // bind to submit to capture post/put/delete routes
- this.bind('submit', function(e) {
- var returned = app._checkFormSubmission($(e.target).closest('form'));
- return (returned === false) ? e.preventDefault() : false;
- });
-
- // bind unload to body unload
- $(window).bind('beforeunload', function() {
- app.unload();
- });
-
- // trigger html changed
- return this.trigger('changed');
- },
-
- // The opposite of `run()`, un-binds all event listeners and intervals
- // `run()` Automaticaly binds a `onunload` event to run this when
- // the document is closed.
- unload: function() {
- if (!this.isRunning()) { return false; }
- var app = this;
- this.trigger('unload');
- // clear interval
- this._location_proxy.unbind();
- // unbind form submits
- this.$element().unbind('submit').removeClass(app.eventNamespace());
- // unbind all events
- $.each(this.listeners.toHash() , function(name, listeners) {
- $.each(listeners, function(i, listener_callback) {
- app._unlisten(name, listener_callback);
- });
- });
- this._running = false;
- return this;
- },
-
- // Will bind a single callback function to every event that is already
- // being listened to in the app. This includes all the `APP_EVENTS`
- // as well as any custom events defined with `bind()`.
- //
- // Used internally for debug logging.
- bindToAllEvents: function(callback) {
- var app = this;
- // bind to the APP_EVENTS first
- $.each(this.APP_EVENTS, function(i, e) {
- app.bind(e, callback);
- });
- // next, bind to listener names (only if they dont exist in APP_EVENTS)
- $.each(this.listeners.keys(true), function(i, name) {
- if (app.APP_EVENTS.indexOf(name) == -1) {
- app.bind(name, callback);
- }
- });
- return this;
- },
-
- // Returns a copy of the given path with any query string after the hash
- // removed.
- routablePath: function(path) {
- return path.replace(QUERY_STRING_MATCHER, '');
- },
-
- // Given a verb and a String path, will return either a route object or false
- // if a matching route can be found within the current defined set.
- lookupRoute: function(verb, path) {
- var app = this, routed = false;
- this.trigger('lookup-route', {verb: verb, path: path});
- if (typeof this.routes[verb] != 'undefined') {
- $.each(this.routes[verb], function(i, route) {
- if (app.routablePath(path).match(route.path)) {
- routed = route;
- return false;
- }
- });
- }
- return routed;
- },
-
- // First, invokes `lookupRoute()` and if a route is found, parses the
- // possible URL params and then invokes the route's callback within a new
- // `Sammy.EventContext`. If the route can not be found, it calls
- // `notFound()`. If `raise_errors` is set to `true` and
- // the `error()` has not been overriden, it will throw an actual JS
- // error.
- //
- // You probably will never have to call this directly.
- //
- // ### Arguments
- //
- // * `verb` A String for the verb.
- // * `path` A String path to lookup.
- // * `params` An Object of Params pulled from the URI or passed directly.
- //
- // ### Returns
- //
- // Either returns the value returned by the route callback or raises a 404 Not Found error.
- //
- runRoute: function(verb, path, params, target) {
- var app = this,
- route = this.lookupRoute(verb, path),
- context,
- wrapped_route,
- arounds,
- around,
- befores,
- before,
- callback_args,
- path_params,
- final_returned;
-
- this.log('runRoute', [verb, path].join(' '));
- this.trigger('run-route', {verb: verb, path: path, params: params});
- if (typeof params == 'undefined') { params = {}; }
-
- $.extend(params, this._parseQueryString(path));
-
- if (route) {
- this.trigger('route-found', {route: route});
- // pull out the params from the path
- if ((path_params = route.path.exec(this.routablePath(path))) !== null) {
- // first match is the full path
- path_params.shift();
- // for each of the matches
- $.each(path_params, function(i, param) {
- // if theres a matching param name
- if (route.param_names[i]) {
- // set the name to the match
- params[route.param_names[i]] = _decode(param);
- } else {
- // initialize 'splat'
- if (!params.splat) { params.splat = []; }
- params.splat.push(_decode(param));
- }
- });
- }
-
- // set event context
- context = new this.context_prototype(this, verb, path, params, target);
- // ensure arrays
- arounds = this.arounds.slice(0);
- befores = this.befores.slice(0);
- // set the callback args to the context + contents of the splat
- callback_args = [context].concat(params.splat);
- // wrap the route up with the before filters
- wrapped_route = function() {
- var returned;
- while (befores.length > 0) {
- before = befores.shift();
- // check the options
- if (app.contextMatchesOptions(context, before[0])) {
- returned = before[1].apply(context, [context]);
- if (returned === false) { return false; }
- }
- }
- app.last_route = route;
- context.trigger('event-context-before', {context: context});
- returned = route.callback.apply(context, callback_args);
- context.trigger('event-context-after', {context: context});
- return returned;
- };
- $.each(arounds.reverse(), function(i, around) {
- var last_wrapped_route = wrapped_route;
- wrapped_route = function() { return around.apply(context, [last_wrapped_route]); };
- });
- try {
- final_returned = wrapped_route();
- } catch(e) {
- this.error(['500 Error', verb, path].join(' '), e);
- }
- return final_returned;
- } else {
- return this.notFound(verb, path);
- }
- },
-
- // Matches an object of options against an `EventContext` like object that
- // contains `path` and `verb` attributes. Internally Sammy uses this
- // for matching `before()` filters against specific options. You can set the
- // object to _only_ match certain paths or verbs, or match all paths or verbs _except_
- // those that match the options.
- //
- // ### Example
- //
- // var app = $.sammy(),
- // context = {verb: 'get', path: '#/mypath'};
- //
- // // match against a path string
- // app.contextMatchesOptions(context, '#/mypath'); //=> true
- // app.contextMatchesOptions(context, '#/otherpath'); //=> false
- // // equivilent to
- // app.contextMatchesOptions(context, {only: {path:'#/mypath'}}); //=> true
- // app.contextMatchesOptions(context, {only: {path:'#/otherpath'}}); //=> false
- // // match against a path regexp
- // app.contextMatchesOptions(context, /path/); //=> true
- // app.contextMatchesOptions(context, /^path/); //=> false
- // // match only a verb
- // app.contextMatchesOptions(context, {only: {verb:'get'}}); //=> true
- // app.contextMatchesOptions(context, {only: {verb:'post'}}); //=> false
- // // match all except a verb
- // app.contextMatchesOptions(context, {except: {verb:'post'}}); //=> true
- // app.contextMatchesOptions(context, {except: {verb:'get'}}); //=> false
- // // match all except a path
- // app.contextMatchesOptions(context, {except: {path:'#/otherpath'}}); //=> true
- // app.contextMatchesOptions(context, {except: {path:'#/mypath'}}); //=> false
- //
- contextMatchesOptions: function(context, match_options, positive) {
- // empty options always match
- var options = match_options;
- if (typeof options === 'undefined' || options == {}) {
- return true;
- }
- if (typeof positive === 'undefined') {
- positive = true;
- }
- // normalize options
- if (typeof options === 'string' || _isFunction(options.test)) {
- options = {path: options};
- }
- if (options.only) {
- return this.contextMatchesOptions(context, options.only, true);
- } else if (options.except) {
- return this.contextMatchesOptions(context, options.except, false);
- }
- var path_matched = true, verb_matched = true;
- if (options.path) {
- // wierd regexp test
- if (_isFunction(options.path.test)) {
- path_matched = options.path.test(context.path);
- } else {
- path_matched = (options.path.toString() === context.path);
- }
- }
- if (options.verb) {
- verb_matched = options.verb === context.verb;
- }
- return positive ? (verb_matched && path_matched) : !(verb_matched && path_matched);
- },
-
-
- // Delegates to the `location_proxy` to get the current location.
- // See `Sammy.HashLocationProxy` for more info on location proxies.
- getLocation: function() {
- return this._location_proxy.getLocation();
- },
-
- // Delegates to the `location_proxy` to set the current location.
- // See `Sammy.HashLocationProxy` for more info on location proxies.
- //
- // ### Arguments
- //
- // * `new_location` A new location string (e.g. '#/')
- //
- setLocation: function(new_location) {
- return this._location_proxy.setLocation(new_location);
- },
-
- // Swaps the content of `$element()` with `content`
- // You can override this method to provide an alternate swap behavior
- // for `EventContext.partial()`.
- //
- // ### Example
- //
- // var app = $.sammy(function() {
- //
- // // implements a 'fade out'/'fade in'
- // this.swap = function(content) {
- // this.$element().hide('slow').html(content).show('slow');
- // }
- //
- // get('#/', function() {
- // this.partial('index.html.erb') // will fade out and in
- // });
- //
- // });
- //
- swap: function(content) {
- return this.$element().html(content);
- },
-
- // a simple global cache for templates. Uses the same semantics as
- // `Sammy.Cache` and `Sammy.Storage` so can easily be replaced with
- // a persistant storage that lasts beyond the current request.
- templateCache: function(key, value) {
- if (typeof value != 'undefined') {
- return _template_cache[key] = value;
- } else {
- return _template_cache[key];
- }
- },
-
- // clear the templateCache
- clearTemplateCache: function() {
- return _template_cache = {};
- },
-
- // This thows a '404 Not Found' error by invoking `error()`.
- // Override this method or `error()` to provide custom
- // 404 behavior (i.e redirecting to / or showing a warning)
- notFound: function(verb, path) {
- var ret = this.error(['404 Not Found', verb, path].join(' '));
- return (verb === 'get') ? ret : true;
- },
-
- // The base error handler takes a string `message` and an `Error`
- // object. If `raise_errors` is set to `true` on the app level,
- // this will re-throw the error to the browser. Otherwise it will send the error
- // to `log()`. Override this method to provide custom error handling
- // e.g logging to a server side component or displaying some feedback to the
- // user.
- error: function(message, original_error) {
- if (!original_error) { original_error = new Error(); }
- original_error.message = [message, original_error.message].join(' ');
- this.trigger('error', {message: original_error.message, error: original_error});
- if (this.raise_errors) {
- throw(original_error);
- } else {
- this.log(original_error.message, original_error);
- }
- },
-
- _checkLocation: function() {
- var location, returned;
- // get current location
- location = this.getLocation();
- // compare to see if hash has changed
- if (!this.last_location || this.last_location[0] != 'get' || this.last_location[1] != location) {
- // reset last location
- this.last_location = ['get', location];
- // lookup route for current hash
- returned = this.runRoute('get', location);
- }
- return returned;
- },
-
- _getFormVerb: function(form) {
- var $form = $(form), verb, $_method;
- $_method = $form.find('input[name="_method"]');
- if ($_method.length > 0) { verb = $_method.val(); }
- if (!verb) { verb = $form[0].getAttribute('method'); }
- if (!verb || verb == '') { verb = 'get'; }
- return $.trim(verb.toString().toLowerCase());
- },
-
- _checkFormSubmission: function(form) {
- var $form, path, verb, params, returned;
- this.trigger('check-form-submission', {form: form});
- $form = $(form);
- path = $form.attr('action');
- verb = this._getFormVerb($form);
- this.log('_checkFormSubmission', $form, path, verb);
- if (verb === 'get') {
- this.setLocation(path + '?' + this._serializeFormParams($form));
- returned = false;
- } else {
- params = $.extend({}, this._parseFormParams($form));
- returned = this.runRoute(verb, path, params, form.get(0));
- };
- return (typeof returned == 'undefined') ? false : returned;
- },
-
- _serializeFormParams: function($form) {
- var queryString = "",
- fields = $form.serializeArray(),
- i;
- if (fields.length > 0) {
- queryString = this._encodeFormPair(fields[0].name, fields[0].value);
- for (i = 1; i < fields.length; i++) {
- queryString = queryString + "&" + this._encodeFormPair(fields[i].name, fields[i].value);
- }
- }
- return queryString;
- },
-
- _encodeFormPair: function(name, value){
- return _encode(name) + "=" + _encode(value);
- },
-
- _parseFormParams: function($form) {
- var params = {},
- form_fields = $form.serializeArray(),
- i;
- for (i = 0; i < form_fields.length; i++) {
- params = this._parseParamPair(params, form_fields[i].name, form_fields[i].value);
- }
- return params;
- },
-
- _parseQueryString: function(path) {
- var params = {}, parts, pairs, pair, i;
-
- parts = path.match(QUERY_STRING_MATCHER);
- if (parts) {
- pairs = parts[1].split('&');
- for (i = 0; i < pairs.length; i++) {
- pair = pairs[i].split('=');
- params = this._parseParamPair(params, _decode(pair[0]), _decode(pair[1] || ""));
- }
- }
- return params;
- },
-
- _parseParamPair: function(params, key, value) {
- if (params[key]) {
- if (_isArray(params[key])) {
- params[key].push(value);
- } else {
- params[key] = [params[key], value];
- }
- } else {
- params[key] = value;
- }
- return params;
- },
-
- _listen: function(name, callback) {
- return this.$element().bind([name, this.eventNamespace()].join('.'), callback);
- },
-
- _unlisten: function(name, callback) {
- return this.$element().unbind([name, this.eventNamespace()].join('.'), callback);
- }
-
- });
-
- // `Sammy.RenderContext` is an object that makes sequential template loading,
- // rendering and interpolation seamless even when dealing with asyncronous
- // operations.
- //
- // `RenderContext` objects are not usually created directly, rather they are
- // instatiated from an `Sammy.EventContext` by using `render()`, `load()` or
- // `partial()` which all return `RenderContext` objects.
- //
- // `RenderContext` methods always returns a modified `RenderContext`
- // for chaining (like jQuery itself).
- //
- // The core magic is in the `then()` method which puts the callback passed as
- // an argument into a queue to be executed once the previous callback is complete.
- // All the methods of `RenderContext` are wrapped in `then()` which allows you
- // to queue up methods by chaining, but maintaing a guarunteed execution order
- // even with remote calls to fetch templates.
- //
- Sammy.RenderContext = function(event_context) {
- this.event_context = event_context;
- this.callbacks = [];
- this.previous_content = null;
- this.content = null;
- this.next_engine = false;
- this.waiting = false;
- };
-
- Sammy.RenderContext.prototype = $.extend({}, Sammy.Object.prototype, {
-
- // The "core" of the `RenderContext` object, adds the `callback` to the
- // queue. If the context is `waiting` (meaning an async operation is happening)
- // then the callback will be executed in order, once the other operations are
- // complete. If there is no currently executing operation, the `callback`
- // is executed immediately.
- //
- // The value returned from the callback is stored in `content` for the
- // subsiquent operation. If you return `false`, the queue will pause, and
- // the next callback in the queue will not be executed until `next()` is
- // called. This allows for the guarunteed order of execution while working
- // with async operations.
- //
- // If then() is passed a string instead of a function, the string is looked
- // up as a helper method on the event context.
- //
- // ### Example
- //
- // this.get('#/', function() {
- // // initialize the RenderContext
- // // Even though `load()` executes async, the next `then()`
- // // wont execute until the load finishes
- // this.load('myfile.txt')
- // .then(function(content) {
- // // the first argument to then is the content of the
- // // prev operation
- // $('#main').html(content);
- // });
- // });
- //
- then: function(callback) {
- if (!_isFunction(callback)) {
- // if a string is passed to then, assume we want to call
- // a helper on the event context in its context
- if (typeof callback === 'string' && callback in this.event_context) {
- var helper = this.event_context[callback];
- callback = function(content) {
- return helper.apply(this.event_context, [content]);
- };
- } else {
- return this;
- }
- }
- var context = this;
- if (this.waiting) {
- this.callbacks.push(callback);
- } else {
- this.wait();
- window.setTimeout(function() {
- var returned = callback.apply(context, [context.content, context.previous_content]);
- if (returned !== false) {
- context.next(returned);
- }
- }, 13);
- }
- return this;
- },
-
- // Pause the `RenderContext` queue. Combined with `next()` allows for async
- // operations.
- //
- // ### Example
- //
- // this.get('#/', function() {
- // this.load('mytext.json')
- // .then(function(content) {
- // var context = this,
- // data = JSON.parse(content);
- // // pause execution
- // context.wait();
- // // post to a url
- // $.post(data.url, {}, function(response) {
- // context.next(JSON.parse(response));
- // });
- // })
- // .then(function(data) {
- // // data is json from the previous post
- // $('#message').text(data.status);
- // });
- // });
- wait: function() {
- this.waiting = true;
- },
-
- // Resume the queue, setting `content` to be used in the next operation.
- // See `wait()` for an example.
- next: function(content) {
- this.waiting = false;
- if (typeof content !== 'undefined') {
- this.previous_content = this.content;
- this.content = content;
- }
- if (this.callbacks.length > 0) {
- this.then(this.callbacks.shift());
- }
- },
-
- // Load a template into the context.
- // The `location` can either be a string specifiying the remote path to the
- // file, a jQuery object, or a DOM element.
- //
- // No interpolation happens by default, the content is stored in
- // `content`.
- //
- // In the case of a path, unless the option `{cache: false}` is passed the
- // data is stored in the app's `templateCache()`.
- //
- // If a jQuery or DOM object is passed the `innerHTML` of the node is pulled in.
- // This is useful for nesting templates as part of the initial page load wrapped
- // in invisible elements or `