diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 diff --git a/javascripts/sammy/min/plugins/sammy.cache-0.3.0.min.js b/javascripts/sammy/min/plugins/sammy.cache-0.3.0.min.js new file mode 100644 index 0000000..90f20f7 --- /dev/null +++ b/javascripts/sammy/min/plugins/sammy.cache-0.3.0.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /plugins/sammy.cache.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:20 -0400 2009 +(function(a){Sammy=Sammy||{};Sammy.MemoryCacheProxy=function(b){this._cache=b||{}};a.extend(Sammy.MemoryCacheProxy.prototype,{exists:function(b){return(typeof this._cache[b]!="undefined")},set:function(b,c){return this._cache[b]=c},get:function(b){return this._cache[b]},clear:function(b){delete this._cache[b]}});Sammy.DataCacheProxy=function(c,b){c=c||{};this.$element=b;a.each(c,function(d,e){b.data("cache."+d,e)})};a.extend(Sammy.DataCacheProxy.prototype,{exists:function(b){return(typeof this.$element.data("cache."+b)!="undefined")},set:function(b,c){return this.$element.data("cache."+b,c)},get:function(b){return this.$element.data("cache."+b)},clear:function(b){this.$element.removeData("cache."+b)}});Sammy.Cache=function(c,b){if(b=="data"){this.cache_proxy=new Sammy.DataCacheProxy({},this.$element())}else{this.cache_proxy=new Sammy.MemoryCacheProxy({})}c.cache_partials=true;a.extend(c,{cache:function(d,e){if(typeof e=="undefined"){return this.cache_proxy.get(d)}else{if(a.isFunction(e)&&!this.cache_proxy.exists(d)){return this.cache_proxy.set(d,e.apply(this))}else{return this.cache_proxy.set(d,e)}}},clearCache:function(d){return this.cache_proxy.clear(d)}});c.helpers({cache:function(d,e){return this.app.cache(d,e)}})}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/min/plugins/sammy.cache-lastest.min.js b/javascripts/sammy/min/plugins/sammy.cache-lastest.min.js new file mode 100644 index 0000000..90f20f7 --- /dev/null +++ b/javascripts/sammy/min/plugins/sammy.cache-lastest.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /plugins/sammy.cache.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:20 -0400 2009 +(function(a){Sammy=Sammy||{};Sammy.MemoryCacheProxy=function(b){this._cache=b||{}};a.extend(Sammy.MemoryCacheProxy.prototype,{exists:function(b){return(typeof this._cache[b]!="undefined")},set:function(b,c){return this._cache[b]=c},get:function(b){return this._cache[b]},clear:function(b){delete this._cache[b]}});Sammy.DataCacheProxy=function(c,b){c=c||{};this.$element=b;a.each(c,function(d,e){b.data("cache."+d,e)})};a.extend(Sammy.DataCacheProxy.prototype,{exists:function(b){return(typeof this.$element.data("cache."+b)!="undefined")},set:function(b,c){return this.$element.data("cache."+b,c)},get:function(b){return this.$element.data("cache."+b)},clear:function(b){this.$element.removeData("cache."+b)}});Sammy.Cache=function(c,b){if(b=="data"){this.cache_proxy=new Sammy.DataCacheProxy({},this.$element())}else{this.cache_proxy=new Sammy.MemoryCacheProxy({})}c.cache_partials=true;a.extend(c,{cache:function(d,e){if(typeof e=="undefined"){return this.cache_proxy.get(d)}else{if(a.isFunction(e)&&!this.cache_proxy.exists(d)){return this.cache_proxy.set(d,e.apply(this))}else{return this.cache_proxy.set(d,e)}}},clearCache:function(d){return this.cache_proxy.clear(d)}});c.helpers({cache:function(d,e){return this.app.cache(d,e)}})}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/min/plugins/sammy.template-0.3.0.min.js b/javascripts/sammy/min/plugins/sammy.template-0.3.0.min.js new file mode 100644 index 0000000..16b5144 --- /dev/null +++ b/javascripts/sammy/min/plugins/sammy.template-0.3.0.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /plugins/sammy.template.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:21 -0400 2009 +(function(c){var a={};var b=function(d,e,f){if(a[d]){fn=a[d]}else{if(typeof e=="undefined"){return false}fn=a[d]=new Function("obj",'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push("'+e.replace(/[\r\t\n]/g," ").replace(/\"/g,'\\"').split("<%").join("\t").replace(/((^|%>)[^\t]*)/g,"$1\r").replace(/\t=(.*?)%>/g,'",$1,"').split("\t").join('");').split("%>").join('p.push("').split("\r").join("")+"\");}return p.join('');")}if(typeof f!="undefined"){return fn(f)}else{return fn}};Sammy=Sammy||{};Sammy.Template=function(d){d.helpers({template:function(f,g,e){if(typeof e=="undefined"){e=f}return b(e,f,c.extend({},g,this))}})}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/min/plugins/sammy.template-lastest.min.js b/javascripts/sammy/min/plugins/sammy.template-lastest.min.js new file mode 100644 index 0000000..16b5144 --- /dev/null +++ b/javascripts/sammy/min/plugins/sammy.template-lastest.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /plugins/sammy.template.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:21 -0400 2009 +(function(c){var a={};var b=function(d,e,f){if(a[d]){fn=a[d]}else{if(typeof e=="undefined"){return false}fn=a[d]=new Function("obj",'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push("'+e.replace(/[\r\t\n]/g," ").replace(/\"/g,'\\"').split("<%").join("\t").replace(/((^|%>)[^\t]*)/g,"$1\r").replace(/\t=(.*?)%>/g,'",$1,"').split("\t").join('");').split("%>").join('p.push("').split("\r").join("")+"\");}return p.join('');")}if(typeof f!="undefined"){return fn(f)}else{return fn}};Sammy=Sammy||{};Sammy.Template=function(d){d.helpers({template:function(f,g,e){if(typeof e=="undefined"){e=f}return b(e,f,c.extend({},g,this))}})}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/min/sammy-0.3.0.min.js b/javascripts/sammy/min/sammy-0.3.0.min.js new file mode 100644 index 0000000..7d5d174 --- /dev/null +++ b/javascripts/sammy/min/sammy-0.3.0.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /sammy.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:22 -0400 2009 +(function(e){var b="([^/]+)";var d=/:([\w\d]+)/g;var a=/\?([^#]*)$/;var c=[];Sammy={};Sammy.VERSION="0.3.0";Sammy.addLogger=function(f){c.push(f)};Sammy.log=function(){var f=e.makeArray(arguments);f.unshift("["+Date()+"]");e.each(c,function(h,g){g.apply(Sammy,f)})};if(typeof window.console!="undefined"){Sammy.addLogger(function(){window.console.log.apply(window.console,arguments)})}else{if(typeof console!="undefined"){Sammy.addLogger.push(function(){console.log.apply(console,arguments)})}}Sammy.Object=function(f){this.extend(f)};e.extend(Sammy.Object.prototype,{extend:function(f){e.extend(this,f)},clone:function(f){if(typeof f=="undefined"){f=this}return e.extend({},f)},toHash:function(){var f={};this.each(function(h,g){if(!e.isFunction(g)){f[h]=g}});return f},toHTML:function(){var f="";this.each(function(h,g){if(!e.isFunction(g)){f+=""+h+" "+g+"
"}});return f},uuid:function(){if(typeof this._uuid=="undefined"||!this._uuid){this._uuid=(new Date()).getTime()+"-"+parseInt(Math.random()*1000)}return this._uuid},each:function(){var h,g,i,f;h=this;if(typeof arguments[0]!="function"){g=arguments[0];i=arguments[1]}else{g=this;i=arguments[0]}f=function(){return i.apply(h,arguments)};e.each(g,f)},keys:function(f){var g=[];for(var h in this){if(!e.isFunction(this[h])||!f){g.push(h)}}return g},join:function(){var g=e.makeArray(arguments);var f=g.shift();return g.join(f)},log:function(){Sammy.log.apply(Sammy,arguments)},toString:function(f){var g=[];this.each(function(i,h){if(!e.isFunction(h)||f){g.push('"'+i+'": '+h.toString())}});return"Sammy.Object: {"+g.join(",")+"}"}});Sammy.Application=function(f){var g=this;this.routes={};this.listeners=new Sammy.Object({});this.befores=[];this.namespace=this.uuid();this.context_prototype=function(){Sammy.EventContext.apply(this,arguments)};this.context_prototype.prototype=new Sammy.EventContext();this.each(this.ROUTE_VERBS,function(h,j){this._defineRouteShortcut(j)});f.apply(this,[this]);if(this.debug){this.bindToAllEvents(function(i,h){g.log(g.toString(),i.cleaned_type,h||{})})}};Sammy.Application.prototype=e.extend({},Sammy.Object.prototype,{ROUTE_VERBS:["get","post","put","delete"],APP_EVENTS:["run","unload","lookup-route","run-route","route-found","event-context-before","event-context-after","changed","error-404","check-form-submission","redirect"],_last_route:null,_running:false,data_store_name:"sammy-app",element_selector:"body",debug:false,silence_404:true,run_interval_every:50,toString:function(){return"Sammy.Application:"+this.element_selector},$element:function(){return e(this.element_selector)},use:function(){var f=e.makeArray(arguments);var g=f.shift();f.unshift(this);g.apply(this,f)},route:function(i,g,k){var h=this;var j=[];if(g.constructor==String){while((path_match=d.exec(g))!=null){j.push(path_match[1])}g=new RegExp(g.replace(d,b)+"$")}var f={verb:i,path:g,callback:k,param_names:j};if(typeof this.routes[i]=="undefined"||this.routes[i].length==0){this.routes[i]=[f]}else{this.routes[i].push(f)}return f},eventNamespace:function(){return[this.data_store_name,this.namespace].join("-")},bind:function(f,h,j){var i=this;if(typeof j=="undefined"){j=h}var g=function(){var m,k,l;m=arguments[0];l=arguments[1];if(l&&l.context){k=l.context;delete l.context}else{k=new i.context_prototype(i,"bind",m.type,l)}m.cleaned_type=m.type.replace(i.eventNamespace(),"");j.apply(k,[m,l])};if(!this.listeners[f]){this.listeners[f]=[]}this.listeners[f].push(g);if(this.isRunning()){return this._listen(f,g)}},trigger:function(f,g){return this.$element().triggerHandler([f,this.eventNamespace()].join("."),[g])},refresh:function(){this.last_location=null},before:function(f){return this.befores.push(f)},after:function(f){return this.bind("event-context-after",f)},isRunning:function(){return this._running},helpers:function(f){e.extend(this.context_prototype.prototype,f)},run:function(f){if(this.isRunning()){return false}var g=this;this.each(this.listeners.toHash(),function(h,i){this.each(i,function(k,j){this._listen(h,j)})});this.trigger("run",{start_url:f});this._running=true;this.$element().data(this.data_store_name,this);this.last_location=null;if(this.getLocation()==""&&typeof f!="undefined"){this.setLocation(f)}this._checkLocation();this._interval=setInterval(function(){g._checkLocation.apply(g)},this.run_interval_every);this.bind("changed",function(){g.$element().find("form:not(."+g.eventNamespace()+")").bind("submit",function(){return g._checkFormSubmission(this)}).addClass(g.eventNamespace())});e("body").bind("onunload",function(){g.unload()});this.trigger("changed")},unload:function(){if(!this.isRunning()){return false}var f=this;this.trigger("unload");clearInterval(this._interval);this.$element().find("form").unbind("submit").removeClass(f.eventNamespace());this.$element().removeData(this.data_store_name);this.each(this.listeners.toHash(),function(g,h){this.each(h,function(k,j){this._unlisten(g,j)})});this._running=false},bindToAllEvents:function(f){this.each(this.APP_EVENTS,function(g,h){this.bind(h,f)});this.each(this.listeners.keys(true),function(h,g){if(this.APP_EVENTS.indexOf(g)==-1){this.bind(g,f)}})},routablePath:function(f){return f.replace(a,"")},lookupRoute:function(h,g){var f=false;this.trigger("lookup-route",{verb:h,path:g});if(typeof this.routes[h]!="undefined"){this.each(this.routes[h],function(k,j){if(this.routablePath(g).match(j.path)){f=j;return false}})}return f},runRoute:function(m,j,l){this.log("runRoute",[m,j].join(" "));this.trigger("run-route",{verb:m,path:j,params:l});if(typeof l=="undefined"){l={}}e.extend(l,this._parseQueryString(j));var g=this.lookupRoute(m,j);if(g){this.trigger("route-found",{route:g});if((path_params=g.path.exec(this.routablePath(j)))!=null){path_params.shift();this.each(path_params,function(n,o){if(g.param_names[n]){l[g.param_names[n]]=o}else{if(!l.splat){l.splat=[]}l.splat.push(o)}})}var h=new this.context_prototype(this,m,j,l);this.last_route=g;var f=true;var k=this.befores.slice(0);while(k.length>0){if(k.shift().apply(h)===false){return false}}h.trigger("event-context-before",{context:h});var i=g.callback.apply(h,[h]);h.trigger("event-context-after",{context:h});return i}else{this.notFound(m,j)}},getLocation:function(){var f=window.location.toString().match(/^[^#]*(#.+)$/);if(f){return f[1]}else{return""}},setLocation:function(f){window.location=f},swap:function(f){return this.$element().html(f)},notFound:function(g,f){this.trigger("error-404",{verb:g,path:f});throw ("404 Not Found "+g+" "+f)},_defineRouteShortcut:function(g){var f=this;this[g]=function(h,i){f.route.apply(f,[g,h,i])}},_checkLocation:function(){try{var f,g;f=this.getLocation();if(f!=this.last_location){g=this.runRoute("get",f)}this.last_location=f}catch(h){this.last_location=f;if(h.toString().match(/^404/)&&this.silence_404){return g}else{throw (h)}}return g},_checkFormSubmission:function(h){var f,j,l,k,g;this.trigger("check-form-submission",{form:h});f=e(h);j=f.attr("action");l=f.attr("method").toString().toLowerCase();k={"$form":f};e.each(f.serializeArray(),function(m,n){if(k[n.name]){if(e.isArray(k[n.name])){k[n.name].push(n.value)}else{k[n.name]=[k[n.name],n.value]}}else{k[n.name]=n.value}});try{g=this.runRoute(l,j,k)}catch(i){if(i.toString().match(/^404/)&&this.silence_404){return true}else{throw (i)}}return(typeof g=="undefined")?false:g},_parseQueryString:function(k){var h={},j,g,l,f;j=k.match(a);if(j){g=j[1].split("&");for(f=0;f1){f.unshift("/");g=this.join.apply(this,f)}else{g=f[0]}this.trigger("redirect",{to:g});this.app.last_location=this.path;return this.app.setLocation(g)},trigger:function(f,g){if(typeof g=="undefined"){g={}}if(!g.context){g.context=this}return this.app.trigger(f,g)},eventNamespace:function(){return this.app.eventNamespace()},notFound:function(){return this.app.notFound(this.verb,this.path)},toString:function(){return"Sammy.EventContext: "+[this.verb,this.path,this.params].join(" ")}});e.sammy=function(f){return new Sammy.Application(f)}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/min/sammy-lastest.min.js b/javascripts/sammy/min/sammy-lastest.min.js new file mode 100644 index 0000000..7d5d174 --- /dev/null +++ b/javascripts/sammy/min/sammy-lastest.min.js @@ -0,0 +1,5 @@ +// -- Sammy -- /sammy.js +// http://code.quirkey.com/sammy +// Version: 0.3.0 +// Built: Tue Sep 29 23:39:22 -0400 2009 +(function(e){var b="([^/]+)";var d=/:([\w\d]+)/g;var a=/\?([^#]*)$/;var c=[];Sammy={};Sammy.VERSION="0.3.0";Sammy.addLogger=function(f){c.push(f)};Sammy.log=function(){var f=e.makeArray(arguments);f.unshift("["+Date()+"]");e.each(c,function(h,g){g.apply(Sammy,f)})};if(typeof window.console!="undefined"){Sammy.addLogger(function(){window.console.log.apply(window.console,arguments)})}else{if(typeof console!="undefined"){Sammy.addLogger.push(function(){console.log.apply(console,arguments)})}}Sammy.Object=function(f){this.extend(f)};e.extend(Sammy.Object.prototype,{extend:function(f){e.extend(this,f)},clone:function(f){if(typeof f=="undefined"){f=this}return e.extend({},f)},toHash:function(){var f={};this.each(function(h,g){if(!e.isFunction(g)){f[h]=g}});return f},toHTML:function(){var f="";this.each(function(h,g){if(!e.isFunction(g)){f+=""+h+" "+g+"
"}});return f},uuid:function(){if(typeof this._uuid=="undefined"||!this._uuid){this._uuid=(new Date()).getTime()+"-"+parseInt(Math.random()*1000)}return this._uuid},each:function(){var h,g,i,f;h=this;if(typeof arguments[0]!="function"){g=arguments[0];i=arguments[1]}else{g=this;i=arguments[0]}f=function(){return i.apply(h,arguments)};e.each(g,f)},keys:function(f){var g=[];for(var h in this){if(!e.isFunction(this[h])||!f){g.push(h)}}return g},join:function(){var g=e.makeArray(arguments);var f=g.shift();return g.join(f)},log:function(){Sammy.log.apply(Sammy,arguments)},toString:function(f){var g=[];this.each(function(i,h){if(!e.isFunction(h)||f){g.push('"'+i+'": '+h.toString())}});return"Sammy.Object: {"+g.join(",")+"}"}});Sammy.Application=function(f){var g=this;this.routes={};this.listeners=new Sammy.Object({});this.befores=[];this.namespace=this.uuid();this.context_prototype=function(){Sammy.EventContext.apply(this,arguments)};this.context_prototype.prototype=new Sammy.EventContext();this.each(this.ROUTE_VERBS,function(h,j){this._defineRouteShortcut(j)});f.apply(this,[this]);if(this.debug){this.bindToAllEvents(function(i,h){g.log(g.toString(),i.cleaned_type,h||{})})}};Sammy.Application.prototype=e.extend({},Sammy.Object.prototype,{ROUTE_VERBS:["get","post","put","delete"],APP_EVENTS:["run","unload","lookup-route","run-route","route-found","event-context-before","event-context-after","changed","error-404","check-form-submission","redirect"],_last_route:null,_running:false,data_store_name:"sammy-app",element_selector:"body",debug:false,silence_404:true,run_interval_every:50,toString:function(){return"Sammy.Application:"+this.element_selector},$element:function(){return e(this.element_selector)},use:function(){var f=e.makeArray(arguments);var g=f.shift();f.unshift(this);g.apply(this,f)},route:function(i,g,k){var h=this;var j=[];if(g.constructor==String){while((path_match=d.exec(g))!=null){j.push(path_match[1])}g=new RegExp(g.replace(d,b)+"$")}var f={verb:i,path:g,callback:k,param_names:j};if(typeof this.routes[i]=="undefined"||this.routes[i].length==0){this.routes[i]=[f]}else{this.routes[i].push(f)}return f},eventNamespace:function(){return[this.data_store_name,this.namespace].join("-")},bind:function(f,h,j){var i=this;if(typeof j=="undefined"){j=h}var g=function(){var m,k,l;m=arguments[0];l=arguments[1];if(l&&l.context){k=l.context;delete l.context}else{k=new i.context_prototype(i,"bind",m.type,l)}m.cleaned_type=m.type.replace(i.eventNamespace(),"");j.apply(k,[m,l])};if(!this.listeners[f]){this.listeners[f]=[]}this.listeners[f].push(g);if(this.isRunning()){return this._listen(f,g)}},trigger:function(f,g){return this.$element().triggerHandler([f,this.eventNamespace()].join("."),[g])},refresh:function(){this.last_location=null},before:function(f){return this.befores.push(f)},after:function(f){return this.bind("event-context-after",f)},isRunning:function(){return this._running},helpers:function(f){e.extend(this.context_prototype.prototype,f)},run:function(f){if(this.isRunning()){return false}var g=this;this.each(this.listeners.toHash(),function(h,i){this.each(i,function(k,j){this._listen(h,j)})});this.trigger("run",{start_url:f});this._running=true;this.$element().data(this.data_store_name,this);this.last_location=null;if(this.getLocation()==""&&typeof f!="undefined"){this.setLocation(f)}this._checkLocation();this._interval=setInterval(function(){g._checkLocation.apply(g)},this.run_interval_every);this.bind("changed",function(){g.$element().find("form:not(."+g.eventNamespace()+")").bind("submit",function(){return g._checkFormSubmission(this)}).addClass(g.eventNamespace())});e("body").bind("onunload",function(){g.unload()});this.trigger("changed")},unload:function(){if(!this.isRunning()){return false}var f=this;this.trigger("unload");clearInterval(this._interval);this.$element().find("form").unbind("submit").removeClass(f.eventNamespace());this.$element().removeData(this.data_store_name);this.each(this.listeners.toHash(),function(g,h){this.each(h,function(k,j){this._unlisten(g,j)})});this._running=false},bindToAllEvents:function(f){this.each(this.APP_EVENTS,function(g,h){this.bind(h,f)});this.each(this.listeners.keys(true),function(h,g){if(this.APP_EVENTS.indexOf(g)==-1){this.bind(g,f)}})},routablePath:function(f){return f.replace(a,"")},lookupRoute:function(h,g){var f=false;this.trigger("lookup-route",{verb:h,path:g});if(typeof this.routes[h]!="undefined"){this.each(this.routes[h],function(k,j){if(this.routablePath(g).match(j.path)){f=j;return false}})}return f},runRoute:function(m,j,l){this.log("runRoute",[m,j].join(" "));this.trigger("run-route",{verb:m,path:j,params:l});if(typeof l=="undefined"){l={}}e.extend(l,this._parseQueryString(j));var g=this.lookupRoute(m,j);if(g){this.trigger("route-found",{route:g});if((path_params=g.path.exec(this.routablePath(j)))!=null){path_params.shift();this.each(path_params,function(n,o){if(g.param_names[n]){l[g.param_names[n]]=o}else{if(!l.splat){l.splat=[]}l.splat.push(o)}})}var h=new this.context_prototype(this,m,j,l);this.last_route=g;var f=true;var k=this.befores.slice(0);while(k.length>0){if(k.shift().apply(h)===false){return false}}h.trigger("event-context-before",{context:h});var i=g.callback.apply(h,[h]);h.trigger("event-context-after",{context:h});return i}else{this.notFound(m,j)}},getLocation:function(){var f=window.location.toString().match(/^[^#]*(#.+)$/);if(f){return f[1]}else{return""}},setLocation:function(f){window.location=f},swap:function(f){return this.$element().html(f)},notFound:function(g,f){this.trigger("error-404",{verb:g,path:f});throw ("404 Not Found "+g+" "+f)},_defineRouteShortcut:function(g){var f=this;this[g]=function(h,i){f.route.apply(f,[g,h,i])}},_checkLocation:function(){try{var f,g;f=this.getLocation();if(f!=this.last_location){g=this.runRoute("get",f)}this.last_location=f}catch(h){this.last_location=f;if(h.toString().match(/^404/)&&this.silence_404){return g}else{throw (h)}}return g},_checkFormSubmission:function(h){var f,j,l,k,g;this.trigger("check-form-submission",{form:h});f=e(h);j=f.attr("action");l=f.attr("method").toString().toLowerCase();k={"$form":f};e.each(f.serializeArray(),function(m,n){if(k[n.name]){if(e.isArray(k[n.name])){k[n.name].push(n.value)}else{k[n.name]=[k[n.name],n.value]}}else{k[n.name]=n.value}});try{g=this.runRoute(l,j,k)}catch(i){if(i.toString().match(/^404/)&&this.silence_404){return true}else{throw (i)}}return(typeof g=="undefined")?false:g},_parseQueryString:function(k){var h={},j,g,l,f;j=k.match(a);if(j){g=j[1].split("&");for(f=0;f1){f.unshift("/");g=this.join.apply(this,f)}else{g=f[0]}this.trigger("redirect",{to:g});this.app.last_location=this.path;return this.app.setLocation(g)},trigger:function(f,g){if(typeof g=="undefined"){g={}}if(!g.context){g.context=this}return this.app.trigger(f,g)},eventNamespace:function(){return this.app.eventNamespace()},notFound:function(){return this.app.notFound(this.verb,this.path)},toString:function(){return"Sammy.EventContext: "+[this.verb,this.path,this.params].join(" ")}});e.sammy=function(f){return new Sammy.Application(f)}})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/plugins/sammy.cache.js b/javascripts/sammy/plugins/sammy.cache.js new file mode 100644 index 0000000..9b7da0e --- /dev/null +++ b/javascripts/sammy/plugins/sammy.cache.js @@ -0,0 +1,112 @@ +(function($) { + + Sammy = Sammy || {}; + + // A simple cache strategy that stores key/values in memory. + Sammy.MemoryCacheProxy = function(initial) { + this._cache = initial || {}; + }; + + $.extend(Sammy.MemoryCacheProxy.prototype, { + exists: function(name) { + return (typeof this._cache[name] != "undefined"); + }, + set: function(name, value) { + return this._cache[name] = value; + }, + get: function(name) { + return this._cache[name]; + }, + clear: function(name) { + delete this._cache[name]; + } + }); + + // A simple cache strategy that stores key/values $element.data() with a cache. prefix + Sammy.DataCacheProxy = function(initial, $element) { + initial = initial || {}; + this.$element = $element; + $.each(initial, function(key, value) { + $element.data('cache.' + key, value); + }); + }; + + $.extend(Sammy.DataCacheProxy.prototype, { + exists: function(name) { + return (typeof this.$element.data('cache.' + name) != "undefined"); + }, + set: function(name, value) { + return this.$element.data('cache.' + name, value); + }, + get: function(name) { + return this.$element.data('cache.' + name); + }, + clear: function(name) { + this.$element.removeData('cache.' + name); + } + }); + + // 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. There are currently two different 'cache proxies' + // that share the same API but store the data in different ways. + // + // === Arguments + // + // +proxy+:: decides which caching proxy to use, either 'memory'(default) or 'data' + // + Sammy.Cache = function(app, proxy) { + + if (proxy == 'data') { + this.cache_proxy = new Sammy.DataCacheProxy({}, this.$element()); + } else { + this.cache_proxy = new Sammy.MemoryCacheProxy({}); + } + + app.cache_partials = true; + + $.extend(app, { + // cache is the main method for interacting with the cache store. The same + // method is used for both setting and getting the value. The API is similar + // to jQuery.fn.attr() + // + // === Examples + // + // // setting a value + // cache('key', 'value'); + // + // // getting a value + // cache('key'); //=> 'value' + // + // // setting a value with a callback + // cache('key', function() { + // // this is the app + // return app.element_selector; + // }); + // + cache: function(name, value) { + if (typeof value == 'undefined') { + return this.cache_proxy.get(name); + } else if ($.isFunction(value) && !this.cache_proxy.exists(name)) { + return this.cache_proxy.set(name, value.apply(this)); + } else { + return this.cache_proxy.set(name, value) + } + }, + + // clears the cached value for name + clearCache: function(name) { + return this.cache_proxy.clear(name); + } + }); + + app.helpers({ + // a helper shortcut for use in Sammy.EventContext + cache: function(name, value) { + return this.app.cache(name, value); + } + }); + }; + +})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/plugins/sammy.template.js b/javascripts/sammy/plugins/sammy.template.js new file mode 100644 index 0000000..f30624a --- /dev/null +++ b/javascripts/sammy/plugins/sammy.template.js @@ -0,0 +1,102 @@ +(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) { + // 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; + } + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + fn = srender_cache[name] = new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push(\"" + + + // Convert the template into pure JavaScript + template + .replace(/[\r\t\n]/g, " ") + .replace(/\"/g, '\\"') + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)/g, "$1\r") + .replace(/\t=(.*?)%>/g, "\",$1,\"") + .split("\t").join("\");") + .split("%>").join("p.push(\"") + .split("\r").join("") + + "\");}return p.join('');"); + } + + 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.name %>
+ // <% if (user.photo_url) { %> + //
+ // <% } %> + //
+ // + // Given that is a publicly accesible file, you would render it like: + // + // $.sammy(function() { + // // include the plugin + // this.use(Sammy.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'); + // }) + // }); + // + Sammy.Template = function(app) { + + app.helpers({ + // *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. + // + template: function(template, data, name) { + // use name for caching + if (typeof name == 'undefined') name = template; + return srender(name, template, $.extend({}, data, this)); + } + }); + + }; + +})(jQuery); \ No newline at end of file diff --git a/javascripts/sammy/sammy.js b/javascripts/sammy/sammy.js new file mode 100644 index 0000000..492cdd0 --- /dev/null +++ b/javascripts/sammy/sammy.js @@ -0,0 +1,906 @@ +;(function($) { + + var PATH_REPLACER = "([^\/]+)"; + var PATH_NAME_MATCHER = /:([\w\d]+)/g; + var QUERY_STRING_MATCHER = /\?([^#]*)$/; + + var loggers = []; + + Sammy = {}; + + Sammy.VERSION = '0.3.0'; + + // 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') { + Sammy.addLogger(function() { + window.console.log.apply(window.console, arguments); + }); + } else if (typeof console != 'undefined') { + Sammy.addLogger.push(function() { + console.log.apply(console, arguments); + }); + } + + // Sammy.Object is the base for all other Sammy classes. It provides some useful + // functionality, including cloning, iterating, etc. + Sammy.Object = function(obj) { // constructor + this.extend(obj); + }; + + $.extend(Sammy.Object.prototype, { + + // Extend this object with the passed object + extend: function(obj) { + $.extend(this, obj); + }, + + // If passed an obj, clone the attributes and methods of that object + // If called without arguments, clones the callee. + clone: function(obj) { + if (typeof obj == 'undefined') obj = this; + return $.extend({}, obj); + }, + + // Returns a copy of the object with Functions removed. + toHash: function() { + var json = {}; + this.each(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 = ""; + this.each(function(k, v) { + if (!$.isFunction(v)) { + display += "" + k + " " + v + "
"; + } + }); + return display; + }, + + // Generates a unique identifing string. Used for application namespaceing. + uuid: function() { + if (typeof this._uuid == 'undefined' || !this._uuid) { + this._uuid = (new Date()).getTime() + '-' + parseInt(Math.random() * 1000); + } + return this._uuid; + }, + + // If passed an object and a callback, will iterate over the object + // with (k, v) in the context of this object. + // If passed just an argument - will itterate over + // the properties of this Sammy.Object + each: function() { + var context, object, callback, bound_callback; + context = this; + if (typeof arguments[0] != 'function') { + object = arguments[0]; + callback = arguments[1]; + } else { + object = this; + callback = arguments[0]; + } + bound_callback = function() { + return callback.apply(context, arguments); + } + $.each(object, bound_callback); + }, + + // 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; + }, + + // 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 = [] + this.each(function(k, v) { + if (!$.isFunction(v) || include_functions) { + s.push('"' + k + '": ' + v.toString()); + } + }); + return "Sammy.Object: {" + s.join(',') + "}"; + } + }); + + // 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.befores = []; + this.namespace = this.uuid(); + this.context_prototype = function() { Sammy.EventContext.apply(this, arguments) }; + this.context_prototype.prototype = new Sammy.EventContext(); + this.each(this.ROUTE_VERBS, function(i, verb) { + this._defineRouteShortcut(verb); + }); + app_function.apply(this, [this]); + 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-404','check-form-submission','redirect'], + + _last_route: null, + _running: false, + + // On run() the application object is stored in a $.data entry + // assocciated with the application's $element() + data_store_name: 'sammy-app', + + // 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 false, will throw a javascript error when a route is invoked + // and can not be found. + silence_404: true, + + // The time in milliseconds that the URL is queried for changes + run_interval_every: 50, + + // //=> Sammy.Application: body + toString: function() { + return 'Sammy.Application:' + this.element_selector; + }, + + // returns a jQuery object of the Applications bound element. + $element: function() { + return $(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://code.quirkey.com/sammy/doc/plugins.html + // + // === 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! + // }); + // + // }); + // + use: function() { + // flatten the arguments + var args = $.makeArray(arguments); + var plugin = args.shift(); + args.unshift(this); + plugin.apply(this, args); + }, + + // route() is the main method for defining routes within an application. + // For great detail on routes, check out: http://code.quirkey.com/sammy/doc/routes.html + // + // 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 + // +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() + // + route: function(verb, path, callback) { + // turn path into regex + // create a simple object and add the route to it + var app = this; + var param_names = []; + // if path is a string turn it into a regex + if (path.constructor == String) { + // 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) + "$"); + } + var r = {verb: verb, path: path, callback: callback, param_names: param_names}; + // add route to routes array + if (typeof this.routes[verb] == 'undefined' || this.routes[verb].length == 0) { + // add to the front of an empty array + this.routes[verb] = [r]; + } else { + // place routes in order of definition + this.routes[verb].push(r); + } + // return the route + return r; + }, + + // A unique event namespace defined per application. + // All events bound with bind() are automatically bound within this space. + eventNamespace: function() { + return [this.data_store_name, 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 + // + // See http://code.quirkey.com/sammy/docs/events.html for more info. + // + 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.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 + return this._listen(name, listener_callback); + } + }, + + // 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) { + return this.$element().triggerHandler([name, this.eventNamespace()].join('.'), [data]); + }, + + // Reruns the current route + refresh: function() { + this.last_location = null; + }, + + // 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. + before: function(callback) { + return this.befores.push(callback); + }, + + // 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); + }, + + // Returns a boolean of weather 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); + }, + + // 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+:: "value", 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 + this.each(this.listeners.toHash(), function(name, callbacks) { + this.each(callbacks, function(i, listener_callback) { + this._listen(name, listener_callback); + }); + }); + + this.trigger('run', {start_url: start_url}); + this._running = true; + // set data for app + this.$element().data(this.data_store_name, this); + // set last location + this.last_location = null; + if (this.getLocation() == '' && typeof start_url != 'undefined') { + this.setLocation(start_url); + } + // check url + this._checkLocation(); + // set interval for url check + this._interval = setInterval(function () { + app._checkLocation.apply(app); + }, this.run_interval_every); + + // bind re-binding to after route + this.bind('changed', function() { + // bind form submission + app.$element() + .find('form:not(.' + app.eventNamespace() + ')') + .bind('submit', function() { + return app._checkFormSubmission(this); + }) + .addClass(app.eventNamespace()); + }); + // bind unload to body unload + $('body').bind('onunload', function() { + app.unload(); + }); + + // trigger html changed + 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 + clearInterval(this._interval); + // unbind form submits + this.$element().find('form') + .unbind('submit') + .removeClass(app.eventNamespace()); + // clear data + this.$element().removeData(this.data_store_name); + // unbind all events + this.each(this.listeners.toHash() , function(name, listeners) { + this.each(listeners, function(i, listener_callback) { + this._unlisten(name, listener_callback); + }); + }); + this._running = false; + }, + + // 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) { + // bind to the APP_EVENTS first + this.each(this.APP_EVENTS, function(i, e) { + this.bind(e, callback); + }); + // next, bind to listener names (only if they dont exist in APP_EVENTS) + this.each(this.listeners.keys(true), function(i, name) { + if (this.APP_EVENTS.indexOf(name) == -1) { + this.bind(name, callback); + } + }); + }, + + // 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 routed = false; + this.trigger('lookup-route', {verb: verb, path: path}); + if (typeof this.routes[verb] != 'undefined') { + this.each(this.routes[verb], function(i, route) { + if (this.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() and raise an error. If silence_404 is true + // this error will be caught be the internal methods that call runRoute. + // + // 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) { + 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)); + + var route = this.lookupRoute(verb, 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 + this.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]] = param; + } else { + // initialize 'splat' + if (!params['splat']) params['splat'] = []; + params['splat'].push(param); + } + }); + } + + // set event context + var context = new this.context_prototype(this, verb, path, params); + this.last_route = route; + // run all the before filters + var before_value = true; + var befores = this.befores.slice(0); + while (befores.length > 0) { + if (befores.shift().apply(context) === false) return false; + } + context.trigger('event-context-before', {context: context}); + var returned = route.callback.apply(context, [context]); + context.trigger('event-context-after', {context: context}); + return returned; + } else { + this.notFound(verb, path); + } + }, + + // The default behavior is to return the current window's location hash. + // Override this and setLocation() to detach the app from the + // window.location object. + 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(/^[^#]*(#.+)$/); + if (matches) { + return matches[1]; + } else { + return ''; + } + }, + + // The default behavior is to set the current window's location. + // Override this and getLocation() to detach the app from the + // window.location object. + // + // === Arguments + // + // +new_location+:: A new location string (e.g. '#/') + // + setLocation: function(new_location) { + window.location = 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); + }, + + // This thows a '404 Not Found' error. + notFound: function(verb, path) { + this.trigger('error-404', {verb: verb, path: path}); + throw('404 Not Found ' + verb + ' ' + path); + }, + + _defineRouteShortcut: function(verb) { + var app = this; + this[verb] = function(path, callback) { + app.route.apply(app, [verb, path, callback]); + } + }, + + _checkLocation: function() { + try { // try, catch 404s + // get current location + var location, returned; + location = this.getLocation(); + // compare to see if hash has changed + if (location != this.last_location) { + // lookup route for current hash + returned = this.runRoute('get', location); + } + // reset last location + this.last_location = location; + } catch(e) { + // reset last location + this.last_location = location; + // unless the error is a 404 and 404s are silenced + if (e.toString().match(/^404/) && this.silence_404) { + return returned; + } else { + throw(e); + } + } + return returned; + }, + + _checkFormSubmission: function(form) { + var $form, path, verb, params, returned; + this.trigger('check-form-submission', {form: form}); + $form = $(form); + path = $form.attr('action'); + verb = $form.attr('method').toString().toLowerCase(); + params = {'$form': $form}; + $.each($form.serializeArray(), function(i, field) { + if (params[field.name]) { + if ($.isArray(params[field.name])) { + params[field.name].push(field.value); + } else { + params[field.name] = [params[field.name], field.value]; + } + } else { + params[field.name] = field.value; + } + }); + try { // catch 404s + returned = this.runRoute(verb, path, params); + } catch(e) { + if (e.toString().match(/^404/) && this.silence_404) { + return true; + } else { + throw(e); + } + } + return (typeof returned == 'undefined') ? false : returned; + }, + + _parseQueryString: function(path) { + var query = {}, parts, pairs, pair, i; + + parts = path.match(QUERY_STRING_MATCHER); + if (parts) { + pairs = parts[1].split('&'); + for (i = 0; i < pairs.length; i += 1) { + pair = pairs[i].split('='); + query[pair[0]] = pair[1]; + } + } + + return query; + }, + + _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.EventContext objects are created every time a route is run or a + // bound event is triggered. The callbacks for these events are evaluated within a Sammy.EventContext + // This within these callbacks the special methods of EventContext are available. + // + // === Example + // + // $.sammy(function() { with(this) { + // // The context here is this Sammy.Application + // get('#/:name', function() { with(this) { + // // The context here is a new Sammy.EventContext + // if (params['name'] == 'sammy') { + // partial('name.html.erb', {name: 'Sammy'}); + // } else { + // redirect('#/somewhere-else') + // } + // }}); + // }}); + // + // Initialize a new EventContext + // + // === Arguments + // + // +app+:: The Sammy.Application this event is called within. + // +verb+:: The verb invoked to run this context/route. + // +path+:: The string path invoked to run this context/route. + // +params+:: An Object of optional params to pass to the context. Is converted + // to a Sammy.Object. + Sammy.EventContext = function(app, verb, path, params) { + this.app = app; + this.verb = verb; + this.path = path; + this.params = new Sammy.Object(params); + } + + Sammy.EventContext.prototype = $.extend({}, Sammy.Object.prototype, { + + // A shortcut to the app's $element() + $element: function() { + return this.app.$element(); + }, + + // Used for rendering remote templates or documents within the current application/DOM. + // By default Sammy and partial() know nothing about how your templates + // should be interpeted/rendered. This is easy to change, though. partial() looks + // for a method in EventContext that matches the extension of the file you're + // fetching (e.g. 'myfile.template' will look for a template() method, 'myfile.haml' => haml(), etc.) + // If no matching render method is found it just takes the file contents as is. + // + // === Caching + // + // If you use the Sammy.Cache plugin, remote requests will be automatically cached unless + // you explicitly set cache_partials to false + // + // === Examples + // + // There are a couple different ways to use partial(): + // + // partial('doc.html'); + // //=> Replaces $element() with the contents of doc.html + // + // use(Sammy.Template); + // //=> includes the template() method + // partial('doc.template', {name: 'Sammy'}); + // //=> Replaces $element() with the contents of doc.template run through template() + // + // partial('doc.html', function(data) { + // // data is the contents of the template. + // $('.other-selector').html(data); + // }); + // + partial: function(path, data, callback) { + var file_data, + wrapped_callback, + engine, + cache_key = 'partial:' + path, + context = this; + + if ((engine = path.match(/\.([^\.]+)$/))) { engine = engine[1]; } + if (typeof callback == 'undefined') { + if ($.isFunction(data)) { + // callback is in the data position + callback = data; + data = {}; + } else { + // we should use the default callback + callback = function(response) { + context.app.swap(response); + } + } + } + data = $.extend({}, data, this); + wrapped_callback = function(response) { + if (engine && $.isFunction(context[engine])) { + response = context[engine].apply(context, [response, data]); + } + callback.apply(context, [response]); + context.trigger('changed'); + }; + if (this.app.cache_partials && this.cache(cache_key)) { + // try to load the template from the cache + wrapped_callback.apply(context, [this.cache(cache_key)]) + } else { + // the template wasnt cached, we need to fetch it + $.get(path, function(response) { + if (context.app.cache_partials) context.cache(cache_key, response); + wrapped_callback.apply(context, [response]) + }); + } + }, + + // Changes the location of the current window. If to begins with + // '#' it only changes the document's hash. If passed more than 1 argument + // redirect will join them together with forward slashes. + // + // === Example + // + // redirect('#/other/route'); + // // equivilent to + // redirect('#', 'other', 'route'); + // + redirect: function() { + var to, args = $.makeArray(arguments); + if (args.length > 1) { + args.unshift('/'); + to = this.join.apply(this, args); + } else { + to = args[0]; + } + this.trigger('redirect', {to: to}); + this.app.last_location = this.path; + return this.app.setLocation(to); + }, + + // Triggers events on app within the current context. + trigger: function(name, data) { + if (typeof data == 'undefined') data = {}; + if (!data.context) data.context = this; + return this.app.trigger(name, data); + }, + + // A shortcut to app's eventNamespace() + eventNamespace: function() { + return this.app.eventNamespace(); + }, + + // Raises a possible notFound() error for the current path. + notFound: function() { + return this.app.notFound(this.verb, this.path); + }, + + // //=> Sammy.EventContext: get #/ {} + toString: function() { + return "Sammy.EventContext: " + [this.verb, this.path, this.params].join(' '); + } + + }); + + $.sammy = function(app_function) { + return new Sammy.Application(app_function); + }; + +})(jQuery);