├── test ├── fixtures │ ├── partial │ ├── list.html │ ├── partial.noengine │ ├── item.hogan │ ├── item.mustache │ ├── item2.mustache │ ├── other_partial.template │ ├── item.template │ ├── partial2.mustache │ ├── partial.hogan │ ├── partial.html │ ├── partial.json │ ├── partial.mustache │ ├── partial.template │ └── meld │ │ ├── 3.meld │ │ ├── 1.json │ │ ├── 1.meld │ │ ├── 2.meld │ │ ├── 3.html │ │ ├── 1.html │ │ ├── 2.json │ │ ├── 3.json │ │ └── 2.html ├── test_server ├── hoptoad_spec.js ├── exceptional_spec.js ├── index.html ├── meld_spec.js └── flash_spec.js ├── examples ├── backend │ ├── config.ru │ ├── public │ │ ├── templates │ │ │ ├── task_details.html.erb │ │ │ ├── index.html.erb │ │ │ └── task.html.erb │ │ └── javascripts │ │ │ ├── app.js │ │ │ └── sammy.template.js │ ├── app.rb │ ├── Rakefile │ ├── views │ │ ├── index.haml │ │ └── app.sass │ └── README.md ├── ejs │ ├── what.ejs │ ├── who.ejs │ └── index.html ├── form_handling │ ├── files │ │ └── form.html │ └── index.html └── hello_world │ └── index.html ├── .gitignore ├── Gemfile ├── package.json ├── Gemfile.lock ├── lib ├── min │ └── plugins │ │ ├── sammy.hoptoad-0.7.6.min.js │ │ ├── sammy.hoptoad-latest.min.js │ │ ├── sammy.pure-0.7.6.min.js │ │ ├── sammy.pure-latest.min.js │ │ ├── sammy.exceptional-0.7.6.min.js │ │ ├── sammy.exceptional-latest.min.js │ │ ├── sammy.ejs-0.7.6.min.js │ │ ├── sammy.ejs-latest.min.js │ │ ├── sammy.haml-0.7.6.min.js │ │ ├── sammy.haml-latest.min.js │ │ ├── sammy.mustache-0.7.6.min.js │ │ ├── sammy.mustache-latest.min.js │ │ ├── sammy.path_location_proxy-0.7.6.min.js │ │ ├── sammy.path_location_proxy-latest.min.js │ │ ├── sammy.title-0.7.6.min.js │ │ ├── sammy.title-latest.min.js │ │ ├── sammy.tmpl-0.7.6.min.js │ │ ├── sammy.tmpl-latest.min.js │ │ ├── sammy.hogan-0.7.6.min.js │ │ ├── sammy.hogan-latest.min.js │ │ ├── sammy.mixpanel-0.7.6.min.js │ │ ├── sammy.mixpanel-latest.min.js │ │ ├── sammy.kissmetrics-0.7.6.min.js │ │ ├── sammy.kissmetrics-latest.min.js │ │ ├── sammy.handlebars-0.7.6.min.js │ │ ├── sammy.handlebars-latest.min.js │ │ ├── sammy.push_location_proxy-0.7.6.min.js │ │ ├── sammy.push_location_proxy-latest.min.js │ │ ├── sammy.googleanalytics-0.7.6.min.js │ │ ├── sammy.googleanalytics-latest.min.js │ │ ├── sammy.data_location_proxy-0.7.6.min.js │ │ ├── sammy.data_location_proxy-latest.min.js │ │ ├── sammy.template-0.7.6.min.js │ │ ├── sammy.template-latest.min.js │ │ ├── sammy.flash-0.7.6.min.js │ │ ├── sammy.flash-latest.min.js │ │ ├── sammy.nested_params-0.7.6.min.js │ │ ├── sammy.nested_params-latest.min.js │ │ ├── sammy.meld-0.7.6.min.js │ │ ├── sammy.meld-latest.min.js │ │ ├── sammy.cache-0.7.6.min.js │ │ ├── sammy.cache-latest.min.js │ │ ├── sammy.oauth2-0.7.6.min.js │ │ ├── sammy.oauth2-latest.min.js │ │ ├── sammy.form_2_json-0.7.6.min.js │ │ ├── sammy.form_2_json-latest.min.js │ │ ├── sammy.json-0.7.6.min.js │ │ ├── sammy.json-latest.min.js │ │ ├── sammy.form-0.7.6.min.js │ │ └── sammy.form-latest.min.js └── plugins │ ├── sammy.pure.js │ ├── sammy.hoptoad.js │ ├── sammy.path_location_proxy.js │ ├── sammy.exceptional.js │ ├── sammy.ejs.js │ ├── sammy.tmpl.js │ ├── sammy.push_location_proxy.js │ ├── sammy.title.js │ ├── sammy.mixpanel.js │ ├── sammy.kissmetrics.js │ ├── sammy.haml.js │ ├── sammy.data_location_proxy.js │ ├── sammy.flash.js │ ├── sammy.googleanalytics.js │ ├── sammy.cache.js │ ├── sammy.nested_params.js │ ├── sammy.mustache.js │ ├── sammy.hogan.js │ ├── sammy.meld.js │ ├── sammy.handlebars.js │ ├── sammy.template.js │ └── sammy.oauth2.js ├── bower.json ├── vendor ├── jsdoc │ └── templates │ │ ├── menu.haml │ │ ├── klass.haml │ │ └── method.haml ├── mocha │ ├── helpers.js │ └── mocha.css ├── qunit │ └── qunit.css └── qunit-spec.js ├── LICENSE ├── Rakefile └── README.md /test/fixtures/partial: -------------------------------------------------------------------------------- 1 | NOENGINE -------------------------------------------------------------------------------- /test/fixtures/list.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/partial.noengine: -------------------------------------------------------------------------------- 1 | NOENGINE -------------------------------------------------------------------------------- /test/fixtures/item.hogan: -------------------------------------------------------------------------------- 1 | {{name}} -------------------------------------------------------------------------------- /test/fixtures/item.mustache: -------------------------------------------------------------------------------- 1 | {{name}} -------------------------------------------------------------------------------- /test/fixtures/item2.mustache: -------------------------------------------------------------------------------- 1 | my name -------------------------------------------------------------------------------- /examples/backend/config.ru: -------------------------------------------------------------------------------- 1 | require 'app.rb' 2 | 3 | run Tasks -------------------------------------------------------------------------------- /test/fixtures/other_partial.template: -------------------------------------------------------------------------------- 1 | <%= name %> -------------------------------------------------------------------------------- /test/fixtures/item.template: -------------------------------------------------------------------------------- 1 |
  • <%= item.name %>
  • -------------------------------------------------------------------------------- /test/fixtures/partial2.mustache: -------------------------------------------------------------------------------- 1 |
    {{>item}}
    -------------------------------------------------------------------------------- /test/fixtures/partial.hogan: -------------------------------------------------------------------------------- 1 |
    {{>item}}
    -------------------------------------------------------------------------------- /test/fixtures/partial.html: -------------------------------------------------------------------------------- 1 |
    PARTIAL
    2 | -------------------------------------------------------------------------------- /test/fixtures/partial.json: -------------------------------------------------------------------------------- 1 | {"class_name": "original", "name": "json"} 2 | -------------------------------------------------------------------------------- /test/fixtures/partial.mustache: -------------------------------------------------------------------------------- 1 |
    {{>item}}
    -------------------------------------------------------------------------------- /test/fixtures/partial.template: -------------------------------------------------------------------------------- 1 |
    <%= name %>
    -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | vegas/* 3 | .lock 4 | .bundle 5 | .rvmrc 6 | _layouts 7 | _site 8 | .DS_Store 9 | *.swp 10 | -------------------------------------------------------------------------------- /examples/ejs/what.ejs: -------------------------------------------------------------------------------- 1 |
      2 | <% for (var i=0; i < users.length; i++) { %> 3 |
    1. <%= users[i].name %>
    2. 4 | <% } %> 5 |
    6 | -------------------------------------------------------------------------------- /examples/ejs/who.ejs: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /test/fixtures/meld/3.meld: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /examples/backend/public/templates/task_details.html.erb: -------------------------------------------------------------------------------- 1 | <%= console.log('template task', task) %> 2 |
    3 |

    <%= task.entry %>

    4 |
    5 | -------------------------------------------------------------------------------- /test/fixtures/meld/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "post": { 3 | "title": "My Post", 4 | "body": "Lorem ipsum dolor sit amet.", 5 | "tags": ["one", "two", "three"], 6 | "meta": { 7 | "comments": 5, 8 | "time": "Yesterday" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | group :development do 4 | gem 'rake' 5 | gem 'haml', '3.0.24' 6 | gem 'rdiscount' 7 | gem 'activesupport', '2.3.8' 8 | gem 'yajl-ruby' 9 | end 10 | 11 | group :test do 12 | gem 'vegas' 13 | gem 'sinatra' 14 | end 15 | -------------------------------------------------------------------------------- /test/fixtures/meld/1.meld: -------------------------------------------------------------------------------- 1 |
    2 |

    3 |
    4 | 5 | 9 |
    -------------------------------------------------------------------------------- /examples/backend/public/templates/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
    3 |
    4 |

    5 | 6 |

    7 |

    8 | 9 |

    10 |
    11 |
    12 | -------------------------------------------------------------------------------- /examples/backend/public/templates/task.html.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | " /> 3 | <%= task.attr('entry') %> 4 |
  • 5 | -------------------------------------------------------------------------------- /test/fixtures/meld/2.meld: -------------------------------------------------------------------------------- 1 |
    2 |

    3 |
    4 | 5 | 6 | 7 |
    8 |

    9 |

    10 | 11 |

    Links

    12 | 13 |
    14 |
    -------------------------------------------------------------------------------- /test/fixtures/meld/3.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    First

    4 | My Link 5 |
    6 |
    7 |

    Second

    8 | Another Link 9 |
    10 |
    11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sammy", 3 | "version": "0.7.6", 4 | "main": "lib/sammy.js", 5 | "maintainers": [{"name":"Aaron Quint", "web": "http://www.quirkey.com"}], 6 | "description": "Sammy is a RESTful Evented JavaScript framework built on top of jQuery", 7 | "keywords": ["framework", "jquery", "sammy"], 8 | "licenses": [{"type": "MIT"}], 9 | "homepage": "http://code.quirkey.com/sammy" 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/meld/1.html: -------------------------------------------------------------------------------- 1 |
    2 |

    My Post

    3 |
    Lorem ipsum dolor sit amet.
    4 | one 5 | two 6 | three 7 | 11 |
    12 | -------------------------------------------------------------------------------- /examples/backend/app.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra/base' 2 | require 'haml' 3 | require 'sass' 4 | require 'cloudkit' 5 | 6 | class Tasks < Sinatra::Application 7 | 8 | use CloudKit::Service, :collections => [:tasks] 9 | 10 | set :public, File.join(File.dirname(__FILE__), 'public') 11 | 12 | get '/' do 13 | haml :index 14 | end 15 | 16 | get '/stylesheets/:sheet.css' do 17 | sass :"#{params['sheet']}" 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /test/fixtures/meld/2.json: -------------------------------------------------------------------------------- 1 | { 2 | "post": { 3 | "name": "My Post", 4 | "body": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do.", 5 | "tags": ["one", "two", "three"], 6 | "author": { 7 | "name": "AQ", 8 | "bio": "This is my bio.", 9 | "updated": "Two weeks ago", 10 | "links": [ 11 | "http://twitter.com/aq", 12 | "http://www.quirkey.com" 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/meld/3.json: -------------------------------------------------------------------------------- 1 | { 2 | "posts": [ 3 | { 4 | "name": "First", 5 | "url": { 6 | "text": "My Link", 7 | "href": "http://www.quirkey.com", 8 | "title": "My title" 9 | } 10 | }, 11 | { 12 | "name": "Second", 13 | "url": { 14 | "text": "Another Link", 15 | "title": "Another title", 16 | "href": "http://www.google.com" 17 | } 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /examples/backend/Rakefile: -------------------------------------------------------------------------------- 1 | task :start => :copy_files do 2 | system 'open http://localhost:9292/' 3 | system 'rackup config.ru' 4 | end 5 | 6 | desc 'copy latest sammy and jquery from lib and vendor' 7 | task :copy_files do 8 | root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')) 9 | { 10 | ['lib', 'sammy.js'] => 'sammy.js', 11 | ['vendor', 'jquery.js'] => 'jquery.js' 12 | }.each do |from, to| 13 | FileUtils.cp(File.join(root, *from), File.join('public', 'javascripts', to)) 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | activesupport (2.3.8) 5 | haml (3.0.24) 6 | rack (1.2.1) 7 | rake (0.8.7) 8 | rdiscount (1.6.5) 9 | sinatra (1.1.0) 10 | rack (~> 1.1) 11 | tilt (~> 1.1) 12 | tilt (1.1) 13 | vegas (0.1.8) 14 | rack (>= 1.0.0) 15 | yajl-ruby (0.7.8) 16 | 17 | PLATFORMS 18 | ruby 19 | 20 | DEPENDENCIES 21 | activesupport (= 2.3.8) 22 | haml (= 3.0.24) 23 | rake 24 | rdiscount 25 | sinatra 26 | vegas 27 | yajl-ruby 28 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.hoptoad-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.hoptoad.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Hoptoad=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Hoptoad=function(app,errorReporter){errorReporter=errorReporter||window.Hoptoad;app.bind("error",function(e,data){if(data&&data.error){errorReporter.notify(data.error)}})};return Sammy.Hoptoad}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.hoptoad-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.hoptoad.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Hoptoad=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Hoptoad=function(app,errorReporter){errorReporter=errorReporter||window.Hoptoad;app.bind("error",function(e,data){if(data&&data.error){errorReporter.notify(data.error)}})};return Sammy.Hoptoad}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.pure-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.pure.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","pure"],factory)}else{(window.Sammy=window.Sammy||{}).Pure=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Pure=function(app,method_alias){var pure=function(template,data,directives){return $(template).autoRender(data,directives)};if(!method_alias){method_alias="pure"}app.helper(method_alias,pure)};return Sammy.Pure}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.pure-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.pure.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","pure"],factory)}else{(window.Sammy=window.Sammy||{}).Pure=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Pure=function(app,method_alias){var pure=function(template,data,directives){return $(template).autoRender(data,directives)};if(!method_alias){method_alias="pure"}app.helper(method_alias,pure)};return Sammy.Pure}); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sammy", 3 | "version": "0.7.6", 4 | "homepage": "http://sammyjs.org", 5 | "authors": [ 6 | "Aaron Quint", 7 | "Frank Prößdorf" 8 | ], 9 | "description": "Sammy is a tiny javascript framework built on top of jQuery, It's RESTful Evented Javascript", 10 | "main": "/lib/sammy.js", 11 | "keywords": [ 12 | "mvc", 13 | "REST", 14 | "framework", 15 | "routing" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "test", 20 | "vendor" 21 | ], 22 | "dependencies": { 23 | "jquery": ">=1.4.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.exceptional-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.exceptional.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Exceptional=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Exceptional=function(app,errorReporter){errorReporter=errorReporter||window.Exceptional;app.bind("error",function(e,data){if(data&&data.error){errorReporter.handle(data.error.message,window.location.href,"0")}})};return Sammy.Exceptional}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.exceptional-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.exceptional.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Exceptional=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Exceptional=function(app,errorReporter){errorReporter=errorReporter||window.Exceptional;app.bind("error",function(e,data){if(data&&data.error){errorReporter.handle(data.error.message,window.location.href,"0")}})};return Sammy.Exceptional}); -------------------------------------------------------------------------------- /test/fixtures/meld/2.html: -------------------------------------------------------------------------------- 1 |
    2 |

    My Post

    3 |
    Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do.
    4 | 5 | 10 | 11 |
    12 |

    AQ

    13 |

    This is my bio.

    14 | Two weeks ago 15 |

    Links

    16 | 20 |
    21 |
    22 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.ejs-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.ejs.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","ejs"],factory)}else{(window.Sammy=window.Sammy||{}).EJS=factory(window.jQuery,window.Sammy,window.EJS)}})(function($,Sammy,EJS){Sammy.EJS=function(app,method_alias){var template=function(template,data,name){if(typeof name=="undefined"){name=template}return new EJS({text:template,name:name}).render(data)};if(!method_alias){method_alias="ejs"}app.helper(method_alias,template)};return Sammy.EJS}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.ejs-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.ejs.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","ejs"],factory)}else{(window.Sammy=window.Sammy||{}).EJS=factory(window.jQuery,window.Sammy,window.EJS)}})(function($,Sammy,EJS){Sammy.EJS=function(app,method_alias){var template=function(template,data,name){if(typeof name=="undefined"){name=template}return new EJS({text:template,name:name}).render(data)};if(!method_alias){method_alias="ejs"}app.helper(method_alias,template)};return Sammy.EJS}); -------------------------------------------------------------------------------- /vendor/jsdoc/templates/menu.haml: -------------------------------------------------------------------------------- 1 | - core, plugins = docs.partition {|klass_name, klass| !klass[:plugin] } 2 | %h2 Core 3 | %ol.api-menu 4 | - core.each do |klass_name, klass| 5 | %li 6 | %h3 7 | %a{:href => "all##{klass[:name]}"}= klass[:name] 8 | - unless klass[:methods].empty? 9 | %ol 10 | - klass[:methods].each do |method| 11 | %li 12 | %a{:href => "all##{klass[:name]}-#{method[:name]}"}= method[:name] 13 | 14 | %h2 Plugins 15 | %ol.api-menu 16 | - plugins.each do |klass_name, klass| 17 | %li 18 | %h3 19 | %a{:href => "all##{klass[:name]}"}= klass[:name] 20 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.haml-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.haml.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","haml"],factory)}else{(window.Sammy=window.Sammy||{}).Haml=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Haml=function(app,method_alias){var haml_cache={};var haml=function(template,data,name){if(typeof name=="undefined"){name=template}var fn=haml_cache[name];if(!fn){fn=haml_cache[name]=Haml(template)}return fn($.extend({},this,data))};if(!method_alias){method_alias="haml"}app.helper(method_alias,haml)};return Sammy.Haml}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.haml-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.haml.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","haml"],factory)}else{(window.Sammy=window.Sammy||{}).Haml=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Haml=function(app,method_alias){var haml_cache={};var haml=function(template,data,name){if(typeof name=="undefined"){name=template}var fn=haml_cache[name];if(!fn){fn=haml_cache[name]=Haml(template)}return fn($.extend({},this,data))};if(!method_alias){method_alias="haml"}app.helper(method_alias,haml)};return Sammy.Haml}); -------------------------------------------------------------------------------- /examples/form_handling/files/form.html: -------------------------------------------------------------------------------- 1 |
    2 |

    3 |

    4 |

    5 |

    6 |

    7 |
    8 | 9 |

    Plain old form

    10 |
    11 |

    12 |
    13 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.mustache-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.mustache.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","mustache"],factory)}else{(window.Sammy=window.Sammy||{}).Mustache=factory(window.jQuery,window.Sammy,window.Mustache)}})(function($,Sammy,Mustache){Sammy.Mustache=function(app,method_alias){var mustache=function(template,data,partials){data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return Mustache.to_html(template,data,partials)};if(!method_alias){method_alias="mustache"}app.helper(method_alias,mustache)};return Sammy.Mustache}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.mustache-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.mustache.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","mustache"],factory)}else{(window.Sammy=window.Sammy||{}).Mustache=factory(window.jQuery,window.Sammy,window.Mustache)}})(function($,Sammy,Mustache){Sammy.Mustache=function(app,method_alias){var mustache=function(template,data,partials){data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return Mustache.to_html(template,data,partials)};if(!method_alias){method_alias="mustache"}app.helper(method_alias,mustache)};return Sammy.Mustache}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.path_location_proxy-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.path_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).PathLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.PathLocationProxy=function(app){this.app=app};$.extend(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}});return Sammy.PathLocationProxy}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.path_location_proxy-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.path_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).PathLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.PathLocationProxy=function(app){this.app=app};$.extend(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}});return Sammy.PathLocationProxy}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.title-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.title.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Title=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Title=function(){this.setTitle=function(title){if(!$.isFunction(title)){this.title_function=function(additional_title){return[title,additional_title].join(" ")}}else{this.title_function=title}};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})};return Sammy.Title}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.title-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.title.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Title=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Title=function(){this.setTitle=function(title){if(!$.isFunction(title)){this.title_function=function(additional_title){return[title,additional_title].join(" ")}}else{this.title_function=title}};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})};return Sammy.Title}); -------------------------------------------------------------------------------- /examples/backend/views/index.haml: -------------------------------------------------------------------------------- 1 | %html{:xmlns=> "http://www.w3.org/1999/xhtml", 'xml:lang' => "en", :lang => "en"} 2 | %head 3 | %meta{'http-equiv' => "Content-Type", 'content' => "text/html; charset=utf-8"} 4 | %title Backended 5 | 6 | %link{:rel => 'stylesheet', :href => '/stylesheets/app.css', :media => 'screen'} 7 | 8 | %body 9 | #container 10 | #header 11 | %h1 12 | %a{:href => '#/', :title => 'Home'} Sammy Todo 13 | #error{:style => 'display:none'} 14 | %span.close x 15 | #main 16 | #debug 17 | - ['jquery', 'jquery.cloudkit', 'sammy', 'sammy.json', 'sammy.storage', 'sammy.template', 'app'].each do |js| 18 | %script{:src => "/javascripts/#{js}.js", :type => 'text/javascript', :charset => 'utf-8'} 19 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.tmpl-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.tmpl.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","jquery.tmpl"],factory)}else{(window.Sammy=window.Sammy||{}).Tmpl=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Tmpl=function(app,method_alias){var template=function(template,data,partials){var name=template;if(!$.template[name]){$.template(name,template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);for(partial in partials){if(!$.template[partial]){$.template(partial,partials[partial])}}return $.tmpl(name,$.extend({},this,data))};if(!method_alias){method_alias="tmpl"}app.helper(method_alias,template)};return Sammy.Tmpl}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.tmpl-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.tmpl.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","jquery.tmpl"],factory)}else{(window.Sammy=window.Sammy||{}).Tmpl=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Tmpl=function(app,method_alias){var template=function(template,data,partials){var name=template;if(!$.template[name]){$.template(name,template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);for(partial in partials){if(!$.template[partial]){$.template(partial,partials[partial])}}return $.tmpl(name,$.extend({},this,data))};if(!method_alias){method_alias="tmpl"}app.helper(method_alias,template)};return Sammy.Tmpl}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.hogan-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.hogan.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","hogan"],factory)}else{(window.Sammy=window.Sammy||{}).Hogan=factory(window.jQuery,window.Sammy,window.Hogan)}})(function($,Sammy){Sammy.Hogan=function(app,method_alias){var cached_templates={};var hogan=function(template,data,partials){var compiled_template=cached_templates[compiled_template];if(!compiled_template){compiled_template=Hogan.compile(template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return compiled_template.render(data,partials)};if(!method_alias){method_alias="hogan"}app.helper(method_alias,hogan)};return Sammy.Hogan}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.hogan-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.hogan.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","hogan"],factory)}else{(window.Sammy=window.Sammy||{}).Hogan=factory(window.jQuery,window.Sammy,window.Hogan)}})(function($,Sammy){Sammy.Hogan=function(app,method_alias){var cached_templates={};var hogan=function(template,data,partials){var compiled_template=cached_templates[compiled_template];if(!compiled_template){compiled_template=Hogan.compile(template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return compiled_template.render(data,partials)};if(!method_alias){method_alias="hogan"}app.helper(method_alias,hogan)};return Sammy.Hogan}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.mixpanel-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.mixpanel.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Flash=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Mixpanel=function(app){var shouldTrack=true;function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}this.helpers({doNotTrackMixpanel:function(){disableTracking()},trackMixpanel:function(path){if(typeof window.mixpanel!="undefined"&&shouldTrack){this.log("tracking mixpanel",path);window.mixpanel.track(path)}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.trackMixpanel(path);enableTracking()}})};return Sammy.Mixpanel}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.mixpanel-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.mixpanel.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Flash=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.Mixpanel=function(app){var shouldTrack=true;function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}this.helpers({doNotTrackMixpanel:function(){disableTracking()},trackMixpanel:function(path){if(typeof window.mixpanel!="undefined"&&shouldTrack){this.log("tracking mixpanel",path);window.mixpanel.track(path)}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.trackMixpanel(path);enableTracking()}})};return Sammy.Mixpanel}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.kissmetrics-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.kissmetrics.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).JSON=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.KISSmetrics=function(app){var shouldTrack=true;function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}this.helpers({doNotTrackKISSmetrics:function(){disableTracking()},trackKISSmetrics:function(path){if(typeof window._kmq!="undefined"&&shouldTrack){this.log("tracking KISSmetrics",path);window._kmq.push(["record",path])}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.trackKISSmetrics(path);enableTracking()}})};return Sammy.KISSmetrics}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.kissmetrics-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.kissmetrics.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).JSON=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.KISSmetrics=function(app){var shouldTrack=true;function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}this.helpers({doNotTrackKISSmetrics:function(){disableTracking()},trackKISSmetrics:function(path){if(typeof window._kmq!="undefined"&&shouldTrack){this.log("tracking KISSmetrics",path);window._kmq.push(["record",path])}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.trackKISSmetrics(path);enableTracking()}})};return Sammy.KISSmetrics}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.handlebars-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.handlebars.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","handlebars"],factory)}else{(window.Sammy=window.Sammy||{}).Handlebars=factory(window.jQuery,window.Sammy)}})(function($,Sammy,Handlebars){Handlebars=Handlebars||window.Handlebars;Sammy.Handlebars=function(app,method_alias){var handlebars_cache={};var handlebars=function(template,data,partials,name){if(typeof name=="undefined"){name=template}var fn=handlebars_cache[name];if(!fn){fn=handlebars_cache[name]=Handlebars.compile(template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return fn(data,{partials:partials})};if(!method_alias){method_alias="handlebars"}app.helper(method_alias,handlebars)};return Sammy.Handlebars}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.handlebars-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.handlebars.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy","handlebars"],factory)}else{(window.Sammy=window.Sammy||{}).Handlebars=factory(window.jQuery,window.Sammy)}})(function($,Sammy,Handlebars){Handlebars=Handlebars||window.Handlebars;Sammy.Handlebars=function(app,method_alias){var handlebars_cache={};var handlebars=function(template,data,partials,name){if(typeof name=="undefined"){name=template}var fn=handlebars_cache[name];if(!fn){fn=handlebars_cache[name]=Handlebars.compile(template)}data=$.extend({},this,data);partials=$.extend({},data.partials,partials);return fn(data,{partials:partials})};if(!method_alias){method_alias="handlebars"}app.helper(method_alias,handlebars)};return Sammy.Handlebars}); -------------------------------------------------------------------------------- /lib/plugins/sammy.pure.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy', 'pure'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Pure = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // `Sammy.Pure` is a simple wrapper around the pure.js templating engine for 10 | // use in Sammy apps. 11 | // 12 | // Note: You must include the pure.js source before including sammy.pure.js. 13 | // 14 | // See http://beebole.com/pure/ for detailed documentation. 15 | Sammy.Pure = function(app, method_alias) { 16 | 17 | var pure = function(template, data, directives) { 18 | return $(template).autoRender(data, directives); 19 | }; 20 | 21 | // set the default method name/extension 22 | if (!method_alias) { method_alias = 'pure'; } 23 | app.helper(method_alias, pure); 24 | 25 | }; 26 | 27 | return Sammy.Pure; 28 | 29 | })); 30 | -------------------------------------------------------------------------------- /examples/backend/README.md: -------------------------------------------------------------------------------- 1 | # Sammy 2 | 3 | ## Backend Example 4 | 5 | This is a simple To-do list type application built with a simple ruby backend and a sammy front-end. 6 | 7 | The backend is built using sinatra (+rack), and [cloudkit](http://getcloudkit.com). 8 | 9 | ### Setup 10 | 11 | Besides Ruby you also need some gems. 12 | Currently, there are some issues with Rack 1.0 and Sinatra so you need to install the pre-release sinatra from github. 13 | 14 | sudo gem install cloudkit haml 15 | sudo gem install sinatra-sinatra -s http://gems.github.com 16 | 17 | Once you have everything installed from this directory (examples/backend) run: 18 | 19 | rake start 20 | 21 | ### Notes 22 | 23 | !!! Todo's are stored in memory! AKA if you restart the rack application you will loose you're todos. This is really just a demonstration. If you really want to use this, check the [cloudkit](http://getcloudkit.com) docs to see how to set cloudkit up to store info in Tokyo Cabinent. -------------------------------------------------------------------------------- /lib/min/plugins/sammy.push_location_proxy-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.push_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).PushLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.PushLocationProxy=function(app,selector){this.app=app;this.selector=selector||"a"};$.extend(Sammy.PushLocationProxy.prototype,{bind:function(){var proxy=this;$(window).bind("popstate",function(e){proxy.app.trigger("location-changed")});this.app.$element().on("click",this.selector,function(e){if(location.hostname==this.hostname){e.preventDefault();proxy.setLocation($(this).attr("href"));proxy.app.trigger("location-changed")}})},unbind:function(){this.app.$element().off("click",this.selector);$(window).unbind("popstate")},getLocation:function(){return window.location.pathname},setLocation:function(new_location){history.pushState({path:this.path},"",new_location)}});return Sammy.PushLocationProxy}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.push_location_proxy-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.push_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).PushLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.PushLocationProxy=function(app,selector){this.app=app;this.selector=selector||"a"};$.extend(Sammy.PushLocationProxy.prototype,{bind:function(){var proxy=this;$(window).bind("popstate",function(e){proxy.app.trigger("location-changed")});this.app.$element().on("click",this.selector,function(e){if(location.hostname==this.hostname){e.preventDefault();proxy.setLocation($(this).attr("href"));proxy.app.trigger("location-changed")}})},unbind:function(){this.app.$element().off("click",this.selector);$(window).unbind("popstate")},getLocation:function(){return window.location.pathname},setLocation:function(new_location){history.pushState({path:this.path},"",new_location)}});return Sammy.PushLocationProxy}); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008 Aaron Quint, Quirkey NYC, LLC 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/test_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'rubygems' 3 | require 'sinatra/base' 4 | require 'vegas' 5 | 6 | Rack::Mime::MIME_TYPES['.template'] = Rack::Mime.mime_type('.html') 7 | Rack::Mime::MIME_TYPES['.mustache'] = Rack::Mime.mime_type('.mustache') 8 | Rack::Mime::MIME_TYPES['.hogan'] = Rack::Mime.mime_type('.hogan') 9 | Rack::Mime::MIME_TYPES['.meld'] = Rack::Mime.mime_type('.html') 10 | Rack::Mime::MIME_TYPES['.noengine'] = Rack::Mime.mime_type('.txt') 11 | Rack::Mime::MIME_TYPES['.'] = Rack::Mime.mime_type('.txt') 12 | 13 | class SammyTest < Sinatra::Application 14 | 15 | set :public_folder, File.expand_path(File.dirname(__FILE__)) 16 | 17 | get '/' do 18 | content_type 'text/html' 19 | read_relative_file 'index.html' 20 | end 21 | 22 | get /\/(lib|vendor)\/(.*)/ do 23 | filename = File.join(params['captures'][0],params['captures'][1]) 24 | content_type File.extname(filename) 25 | read_relative_file '..', filename 26 | end 27 | 28 | def read_relative_file(*args) 29 | File.read(File.join(File.expand_path(File.dirname(__FILE__)), *args)) 30 | end 31 | 32 | end 33 | 34 | Vegas::Runner.new(SammyTest, 'sammy_test_server') -------------------------------------------------------------------------------- /vendor/jsdoc/templates/klass.haml: -------------------------------------------------------------------------------- 1 | .klass{:id => klass[:name]} 2 | %h2.klass-name 3 | = klass[:name] 4 | %span 5 | ( 6 | =klass[:args].join(', ') 7 | ) 8 | .doc>< 9 | =preserve klass[:doc] || "" 10 | - unless klass[:attributes].empty? 11 | %h3.list Attributes 12 | %ul 13 | - klass[:attributes].each_with_index do |attribute,i| 14 | %li 15 | %a{:href => "##{klass[:name]}-#{attribute[:name]}"}=attribute[:name] 16 | - unless klass[:methods].empty? 17 | %h3.list Methods 18 | %ul 19 | -klass[:methods].each_with_index do |meth,i| 20 | %li 21 | %a{:href => "##{klass[:name]}-#{meth[:name]}"}=meth[:name] 22 | - unless klass[:attributes].empty? 23 | - klass[:attributes].each_with_index do |attribute, i| 24 | .attr{:id => "#{klass[:name]}-#{attribute[:name]}"} 25 | %h4 26 | =attribute[:name] 27 | \= 28 | %span=attribute[:default] 29 | .doc>< 30 | =preserve attribute[:doc] 31 | - unless klass[:methods].empty? 32 | -klass[:methods].each do |meth| 33 | =render('method', :klass => klass, :meth => meth, :individual => false) 34 | -------------------------------------------------------------------------------- /lib/plugins/sammy.hoptoad.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Hoptoad = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // A plugin that posts errors to Hoptoad. 10 | // 11 | // ### Requirements 12 | // 13 | // The sole requirement is a Hoptoad object with a notify function. 14 | // Thoughtbot have published an implementation (see below). 15 | // 16 | // ### Arguments 17 | // 18 | // Sammy.Hoptoad accepts an optional argument that is the 19 | // Hoptoad implementation. It will default to the global `Hoptoad` object. 20 | // 21 | // ### See Also 22 | // * http://hoptoadapp.com/ 23 | // * http://robots.thoughtbot.com/post/899737797 24 | // * http://hoptoadapp.com/javascripts/notifier.js 25 | Sammy.Hoptoad = function(app, errorReporter) { 26 | errorReporter = errorReporter || window.Hoptoad; 27 | app.bind('error', function(e, data) { 28 | if (data && data.error) { 29 | errorReporter.notify(data.error); 30 | } 31 | }); 32 | }; 33 | 34 | return Sammy.Hoptoad; 35 | 36 | })); 37 | -------------------------------------------------------------------------------- /lib/plugins/sammy.path_location_proxy.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).PathLocationProxy = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // `Sammy.PathLocationProxy` is a simple Location Proxy that just 10 | // gets and sets window.location. This allows you to use 11 | // Sammy to route on the full URL path instead of just the hash. It 12 | // will take a full refresh to get the app to change state. 13 | // 14 | // To read more about location proxies, check out the 15 | // documentation for `Sammy.HashLocationProxy` 16 | Sammy.PathLocationProxy = function(app) { 17 | this.app = app; 18 | }; 19 | 20 | $.extend(Sammy.PathLocationProxy.prototype , { 21 | bind: function() {}, 22 | unbind: function() {}, 23 | 24 | getLocation: function() { 25 | return [window.location.pathname, window.location.search].join(''); 26 | }, 27 | 28 | setLocation: function(new_location) { 29 | return window.location = new_location; 30 | } 31 | }); 32 | 33 | return Sammy.PathLocationProxy; 34 | 35 | })); 36 | -------------------------------------------------------------------------------- /lib/plugins/sammy.exceptional.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Exceptional = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // A plugin that posts errors to Exceptional. 10 | // 11 | // ### Arguments 12 | // 13 | // Sammy.Exceptional accepts an optional argument that is the Exceptional 14 | // implementation. It will default to the global `Exceptional` object. 15 | // 16 | // ### Requirements 17 | // 18 | // The sole requirement is a global Exceptional object with a handle 19 | // function. Contrast have published an implementation (see below). 20 | // 21 | // ### See Also 22 | // * http://www.getexceptional.com/ 23 | // * https://github.com/contrast/exceptional-js 24 | Sammy.Exceptional = function(app, errorReporter) { 25 | errorReporter = errorReporter || window.Exceptional; 26 | app.bind('error', function(e, data) { 27 | if (data && data.error) { 28 | errorReporter.handle(data.error.message, window.location.href, '0'); 29 | } 30 | }); 31 | }; 32 | 33 | return Sammy.Exceptional; 34 | 35 | })); 36 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.googleanalytics-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.googleanalytics.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).GoogleAnalytics=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.GoogleAnalytics=function(app,tracker){var _tracker=tracker||window.pageTracker,trackerName="send",shouldTrack=true;if(typeof tracker=="string"||tracker instanceof String){trackerName=tracker+".send"}function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}function trackPageview(path){if(typeof ga=="function"){ga(trackerName,"pageview",{page:path})}else if(typeof _gaq!="undefined"){_gaq.push(["_trackPageview",path])}else if(typeof _tracker!="undefined"){_tracker._trackPageview(path)}}this.helpers({noTrack:function(){disableTracking()},track:function(path){if((typeof _tracker!="undefined"||typeof _gaq!="undefined"||typeof ga=="function")&&shouldTrack){this.log("tracking google analytics",path);trackPageview(path)}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.track(path);enableTracking()}})};return Sammy.GoogleAnalytics}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.googleanalytics-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.googleanalytics.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).GoogleAnalytics=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.GoogleAnalytics=function(app,tracker){var _tracker=tracker||window.pageTracker,trackerName="send",shouldTrack=true;if(typeof tracker=="string"||tracker instanceof String){trackerName=tracker+".send"}function disableTracking(){shouldTrack=false}function enableTracking(){shouldTrack=true}function trackPageview(path){if(typeof ga=="function"){ga(trackerName,"pageview",{page:path})}else if(typeof _gaq!="undefined"){_gaq.push(["_trackPageview",path])}else if(typeof _tracker!="undefined"){_tracker._trackPageview(path)}}this.helpers({noTrack:function(){disableTracking()},track:function(path){if((typeof _tracker!="undefined"||typeof _gaq!="undefined"||typeof ga=="function")&&shouldTrack){this.log("tracking google analytics",path);trackPageview(path)}}});this.bind("event-context-after",function(){var path=this.app.last_location[1];if(path){this.track(path);enableTracking()}})};return Sammy.GoogleAnalytics}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.data_location_proxy-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.data_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).DataLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.DataLocationProxy=function(app,data_name,href_attribute){this.app=app;this.data_name=data_name||"sammy-location";this.href_attribute=href_attribute};$.extend(Sammy.DataLocationProxy.prototype,{bind:function(){var proxy=this;this.app.$element().bind("setData",function(e,key,value){if(key==proxy.data_name){proxy.app.$element().each(function(){$.data(this,proxy.data_name,value)});proxy.app.trigger("location-changed")}});if(this.href_attribute){this.app.$element().delegate("["+this.href_attribute+"]","click",function(e){e.preventDefault();proxy.setLocation($(this).attr(proxy.href_attribute))})}},unbind:function(){if(this.href_attribute){this.app.$element().undelegate("["+this.href_attribute+"]","click")}this.app.$element().unbind("setData")},getLocation:function(){return this.app.$element().data(this.data_name)||""},setLocation:function(new_location){return this.app.$element().data(this.data_name,new_location)}});return Sammy.DataLocationProxy}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.data_location_proxy-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.data_location_proxy.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).DataLocationProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.DataLocationProxy=function(app,data_name,href_attribute){this.app=app;this.data_name=data_name||"sammy-location";this.href_attribute=href_attribute};$.extend(Sammy.DataLocationProxy.prototype,{bind:function(){var proxy=this;this.app.$element().bind("setData",function(e,key,value){if(key==proxy.data_name){proxy.app.$element().each(function(){$.data(this,proxy.data_name,value)});proxy.app.trigger("location-changed")}});if(this.href_attribute){this.app.$element().delegate("["+this.href_attribute+"]","click",function(e){e.preventDefault();proxy.setLocation($(this).attr(proxy.href_attribute))})}},unbind:function(){if(this.href_attribute){this.app.$element().undelegate("["+this.href_attribute+"]","click")}this.app.$element().unbind("setData")},getLocation:function(){return this.app.$element().data(this.data_name)||""},setLocation:function(new_location){return this.app.$element().data(this.data_name,new_location)}});return Sammy.DataLocationProxy}); -------------------------------------------------------------------------------- /examples/hello_world/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Hello World 9 | 10 | 13 | 14 | 15 | 16 | 35 | 36 | 37 | 38 | 39 |
    40 |
    41 |
    42 | 46 |
    47 | 48 | 49 | -------------------------------------------------------------------------------- /examples/backend/views/app.sass: -------------------------------------------------------------------------------- 1 | !font = "'Lucida Grande', Helvetica, Arial, sans-serif" 2 | 3 | body 4 | :font-family = !font 5 | :color #333 6 | :margin 0px 7 | :background #F3F3F3 8 | 9 | 10 | a:link, a:visited 11 | :text-decoration none 12 | :color #5588CA 13 | a:hover 14 | :color #333 15 | :background #E8E8E8 16 | :-moz-border-radius 2px 17 | :-webkit-border-radius 2px 18 | 19 | h1 20 | :color #000 21 | :margin 0px 22 | :font-weight normal 23 | 24 | a:link, a:visited 25 | :color #000 26 | a:hover 27 | :color #5588CA 28 | :background none 29 | 30 | #header 31 | :padding 5px 20px 32 | :background #D7D7D7 33 | :border-bottom 4px solid #C0C0C0 34 | #main 35 | :padding 15px 36 | #tasks 37 | :list-style-type none 38 | :margin 5px 0px 39 | :padding 0px 40 | 41 | li 42 | :margin 5px 0px 43 | :padding 4px 44 | a 45 | :padding 2px 46 | :font-size 18px 47 | &.completed a 48 | :text-decoration line-through 49 | :color #999 50 | 51 | #task_entry 52 | input[type=text] 53 | :width 100% 54 | :font-size 18px 55 | :padding 4px 56 | :-moz-border-radius 4px 57 | :-webkit-border-radius 4px 58 | :border 1px solid #CCC 59 | input[type=submit] 60 | :-moz-border-radius 4px 61 | :-webkit-border-radius 4px 62 | :font-size 16px 63 | :padding 4px 20px 64 | :color #333 65 | :background #ECECEC 66 | :border 1px solid #CCC 67 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.template-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.template.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Template=factory(window.jQuery,window.Sammy)}})(function($,Sammy){var srender_cache={};var srender=function(name,template,data,options){var fn,escaped_string;if(srender_cache[name]){fn=srender_cache[name]}else{if(typeof template=="undefined"){return false}if(options&&options.escape_html===false){escaped_string='",$1,"'}else{escaped_string='",h($1),"'}fn=srender_cache[name]=new Function("obj","var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};"+'with(obj){___$$$___.push("'+String(template).replace(/[\r\t\n]/g," ").replace(/\"/g,'\\"').split("<%").join(" ").replace(/((^|%>)[^\t]*)/g,"$1\r").replace(/\t=(.*?)%>/g,escaped_string).replace(/\t!(.*?)%>/g,'",$1,"').split(" ").join('");').split("%>").join('___$$$___.push("').split("\r").join("")+"\");}return ___$$$___.join('');")}if(typeof data!="undefined"){return fn(data)}else{return fn}};Sammy.Template=function(app,method_alias){var template=function(template,data,name,options){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)};if(!method_alias){method_alias="template"}app.helper(method_alias,template)};return Sammy.Template}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.template-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.template.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:34 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Template=factory(window.jQuery,window.Sammy)}})(function($,Sammy){var srender_cache={};var srender=function(name,template,data,options){var fn,escaped_string;if(srender_cache[name]){fn=srender_cache[name]}else{if(typeof template=="undefined"){return false}if(options&&options.escape_html===false){escaped_string='",$1,"'}else{escaped_string='",h($1),"'}fn=srender_cache[name]=new Function("obj","var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};"+'with(obj){___$$$___.push("'+String(template).replace(/[\r\t\n]/g," ").replace(/\"/g,'\\"').split("<%").join(" ").replace(/((^|%>)[^\t]*)/g,"$1\r").replace(/\t=(.*?)%>/g,escaped_string).replace(/\t!(.*?)%>/g,'",$1,"').split(" ").join('");').split("%>").join('___$$$___.push("').split("\r").join("")+"\");}return ___$$$___.join('');")}if(typeof data!="undefined"){return fn(data)}else{return fn}};Sammy.Template=function(app,method_alias){var template=function(template,data,name,options){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)};if(!method_alias){method_alias="template"}app.helper(method_alias,template)};return Sammy.Template}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.flash-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.flash.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Flash=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.FlashHash=function(){this.now={}};$.extend(Sammy.FlashHash.prototype,{toHTML:function(){var result=this._renderUL();this.clear();return result},clear:function(){this._clearHash(this);this._clearHash(this.now)},_onRedirect:function(){this._clearHash(this.now)},_clearHash:function(hash){var key;for(key in hash){if(key!=="now"&&hash.hasOwnProperty(key)){delete hash[key]}}},_renderUL:function(){return'"},_renderLIs:function(hash){var result="",key;for(key in hash){if(hash[key]&&key!=="now"&&hash.hasOwnProperty(key)){result=result+'
  • '+hash[key]+"
  • "}}Sammy.log("rendered flash: "+result);return result}});Sammy.Flash=function(app){app.flash=new Sammy.FlashHash;app.helper("flash",function(key,value){if(arguments.length===0){return this.app.flash}else if(arguments.length===2){this.app.flash[key]=value}return this.app.flash[key]});app.helper("flashNow",function(key,value){if(arguments.length===0){return this.app.flash.now}else if(arguments.length===2){this.app.flash.now[key]=value}return this.app.flash.now[key]});app.bind("redirect",function(){this.app.flash._onRedirect()})};return Sammy.Flash}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.flash-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.flash.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Flash=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.FlashHash=function(){this.now={}};$.extend(Sammy.FlashHash.prototype,{toHTML:function(){var result=this._renderUL();this.clear();return result},clear:function(){this._clearHash(this);this._clearHash(this.now)},_onRedirect:function(){this._clearHash(this.now)},_clearHash:function(hash){var key;for(key in hash){if(key!=="now"&&hash.hasOwnProperty(key)){delete hash[key]}}},_renderUL:function(){return'"},_renderLIs:function(hash){var result="",key;for(key in hash){if(hash[key]&&key!=="now"&&hash.hasOwnProperty(key)){result=result+'
  • '+hash[key]+"
  • "}}Sammy.log("rendered flash: "+result);return result}});Sammy.Flash=function(app){app.flash=new Sammy.FlashHash;app.helper("flash",function(key,value){if(arguments.length===0){return this.app.flash}else if(arguments.length===2){this.app.flash[key]=value}return this.app.flash[key]});app.helper("flashNow",function(key,value){if(arguments.length===0){return this.app.flash.now}else if(arguments.length===2){this.app.flash.now[key]=value}return this.app.flash.now[key]});app.bind("redirect",function(){this.app.flash._onRedirect()})};return Sammy.Flash}); -------------------------------------------------------------------------------- /lib/plugins/sammy.ejs.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy', 'ejs'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).EJS = factory(window.jQuery, window.Sammy, window.EJS); 6 | } 7 | }(function ($, Sammy, EJS) { 8 | 9 | // `Sammy.EJS` is a thin wrapper around the EJS templating engine which can be donwloaded 10 | // at http://embeddedjs.com/ 11 | // 12 | // Note: As of Sammy 0.7, Sammy.EJS does not include the actual templating engine in the source. 13 | // Include ejs.js before including sammy.ejs.js 14 | Sammy.EJS = function(app, method_alias) { 15 | 16 | // *Helper:* Uses simple templating to parse ERB like templates. 17 | // 18 | // ### Arguments 19 | // 20 | // * `template` A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data. 21 | // * `data` An Object containing the replacement values for the template. 22 | //data is extended with the EventContext allowing you to call its methods within the template. 23 | // * `name` An optional String name to cache the template. 24 | // 25 | var template = function(template, data, name) { 26 | // use name for caching 27 | if (typeof name == 'undefined') { name = template; } 28 | return new EJS({text: template, name: name}).render(data); 29 | }; 30 | 31 | // set the default method name/extension 32 | if (!method_alias) { method_alias = 'ejs'; } 33 | 34 | // create the helper at the method alias 35 | app.helper(method_alias, template); 36 | 37 | }; 38 | 39 | return Sammy.EJS; 40 | 41 | })); 42 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc 'Pulls the current version from lib/sammy.js' 2 | task :version do 3 | f = File.read('lib/sammy.js') 4 | @version = f.match(/Sammy.VERSION \= \'([\d\w\.]+)\'/)[1] 5 | puts "VERSION: " + @version 6 | end 7 | 8 | desc 'Uses the uglify to minify lib/sammy.js' 9 | task :minify => :version do 10 | puts "Minify-ing" 11 | 12 | # compress each file 13 | Dir['lib/**/*.js'].each do |path| 14 | if path =~ /\.min\.js$/ 15 | File.unlink(path) 16 | next 17 | end 18 | path.gsub!('lib','') 19 | 20 | dir = 'lib/min' 21 | min_path = File.join(dir, path.gsub(/\.js$/, "-#{@version}.min.js")) 22 | latest_min_path = File.join(dir, path.gsub(/\.js$/, "-latest.min.js")) 23 | 24 | `uglifyjs lib/#{path} > #{min_path}` 25 | minified = File.read(min_path) 26 | prefix = [] 27 | prefix << "// -- Sammy.js -- #{path}" 28 | prefix << "// http://sammyjs.org" 29 | prefix << "// Version: #{@version}" 30 | prefix << "// Built: #{Time.now}" 31 | File.open(min_path, 'w') do |f| 32 | f << prefix.join("\n") << "\n" 33 | f << minified 34 | end 35 | FileUtils.copy(min_path, latest_min_path) 36 | end 37 | end 38 | 39 | desc 'Tag with the current version' 40 | task :tag => :version do 41 | sh "git add ." 42 | sh "git commit -a -m'Pushing version #{@version}'" 43 | sh "git tag v#{@version}" 44 | sh "git push --tags" 45 | end 46 | 47 | task :release => [:minify, :tag] 48 | 49 | desc 'Generate the docs for the current version to DIR' 50 | task :docs => :version do 51 | @version = ENV['VERSION'] if ENV['VERSION'] 52 | sh "ruby vendor/jsdoc/jsdoc.rb #{ENV['DIR']} #{@version} lib/ lib/plugins/" 53 | end 54 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.nested_params-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.nested_params.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).NestedParams=factory(window.jQuery,window.Sammy)}})(function($,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(/^[^\[]+$/)){params[field_name]=parseValue(field_value)}else if(match=field_name.match(/^([^\[]+)\[\](.*)$/)){name=match[1];rest=match[2];if(params[name]&&!$.isArray(params[name])){throw"400 Bad Request"}if(rest){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{if(params[name]){params[name].push(parseValue(field_value))}else{params[name]=[parseValue(field_value)]}}}else if(match=field_name.match(/^([^\[]+)\[([^\[]+)\](.*)$/)){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=function(app){app._parseParamPair=parseNestedParam};return Sammy.NestedParams}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.nested_params-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.nested_params.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).NestedParams=factory(window.jQuery,window.Sammy)}})(function($,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(/^[^\[]+$/)){params[field_name]=parseValue(field_value)}else if(match=field_name.match(/^([^\[]+)\[\](.*)$/)){name=match[1];rest=match[2];if(params[name]&&!$.isArray(params[name])){throw"400 Bad Request"}if(rest){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{if(params[name]){params[name].push(parseValue(field_value))}else{params[name]=[parseValue(field_value)]}}}else if(match=field_name.match(/^([^\[]+)\[([^\[]+)\](.*)$/)){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=function(app){app._parseParamPair=parseNestedParam};return Sammy.NestedParams}); -------------------------------------------------------------------------------- /test/hoptoad_spec.js: -------------------------------------------------------------------------------- 1 | // a mock Hoptoad 2 | // @see http://hoptoadapp.com/javascripts/notifier.js for the real deal 3 | window.Hoptoad = { 4 | errors: [], 5 | notify: function(error) { 6 | this.errors.push(error); 7 | } 8 | }; 9 | 10 | describe('Hoptoad', function() { 11 | var app = null; 12 | 13 | beforeEach(function() { 14 | app = new Sammy.Application(function() { 15 | this.element_selector = '#main'; 16 | this.raise_errors = false; 17 | this.use(Sammy.Hoptoad); 18 | this.get('#/', function() { 19 | this.trigger('done'); 20 | }); 21 | }); 22 | 23 | app.run('#/'); 24 | }); 25 | 26 | afterEach(function() { 27 | window.location.href = '#/'; 28 | }); 29 | 30 | it('does not send an error to Hoptoad when none is thrown', function(done) { 31 | app.bind('done', function() { 32 | expect(window.Hoptoad.errors).to.be.empty(); 33 | app.unload(); 34 | done(); 35 | }); 36 | 37 | app.get('#/test', function() { 38 | this.redirect('#/'); 39 | }); 40 | 41 | window.location.href = '#/test'; 42 | }); 43 | 44 | it('sends an error to Hoptoad when one is thrown', function(done) { 45 | app.bind('error', function(e) { 46 | e.preventDefault(); 47 | e.stopPropagation(); 48 | if(e.stopImmediatePropagation) { 49 | e.stopImmediatePropagation(); 50 | } 51 | 52 | expect(window.Hoptoad.errors).to.have.length(1); 53 | expect(window.Hoptoad.errors[0].message).to.match(/Communications error/); 54 | 55 | app.unload(); 56 | done(); 57 | }); 58 | 59 | app.get('#/test', function() { 60 | throw new Error('Communications error.'); 61 | }); 62 | 63 | window.location.href = "#/test"; 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.meld-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.meld.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Meld=factory(window.jQuery,window.Sammy)}})(function($,Sammy){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=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};if(!method_alias)method_alias="meld";app.helper(method_alias,meld)};return Sammy.Meld}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.meld-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.meld.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Meld=factory(window.jQuery,window.Sammy)}})(function($,Sammy){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=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};if(!method_alias)method_alias="meld";app.helper(method_alias,meld)};return Sammy.Meld}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.cache-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.cache.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).MemoryCacheProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){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]}});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=function(app,proxy){app.log("**WARNING:** This version of Sammy.Cache has been deprecated in favor of using the version in Sammy.Storage and will be removed in 1.0");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: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)}},clearCache:function(name){return this.cache_proxy.clear(name)}});app.helpers({cache:function(name,value){return this.app.cache(name,value)}})};return Sammy.MemoryCacheProxy}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.cache-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.cache.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:30 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).MemoryCacheProxy=factory(window.jQuery,window.Sammy)}})(function($,Sammy){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]}});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=function(app,proxy){app.log("**WARNING:** This version of Sammy.Cache has been deprecated in favor of using the version in Sammy.Storage and will be removed in 1.0");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: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)}},clearCache:function(name){return this.cache_proxy.clear(name)}});app.helpers({cache:function(name,value){return this.app.cache(name,value)}})};return Sammy.MemoryCacheProxy}); -------------------------------------------------------------------------------- /vendor/jsdoc/templates/method.haml: -------------------------------------------------------------------------------- 1 | .meth{:id => "#{klass[:name]}-#{meth[:name]}", :class => individual ? 'individual' : nil} 2 | - if individual 3 | %a{:href => "/docs/api/#{version}/"} 4 | %strong « Back 5 | %h3 6 | - if individual 7 | %strong= klass[:name] 8 | %a{:href => "#{klass[:name]}._methods_.#{meth[:name]}"}=meth[:name] 9 | %span 10 | ( 11 | %em=meth[:args].join(', ') 12 | ) 13 | .links 14 | 15 | %a{:href => "#{klass[:name]}._methods_.#{meth[:name]}#comments"} Comments 16 | | 17 | %a{:href => gh_url(meth), :target => "all"} Show in GitHub 18 | .doc>< 19 | =preserve meth[:doc] 20 | - if individual 21 | #comments 22 | :erb 23 |
    24 | 38 | 39 | comments powered by Disqus 40 | -------------------------------------------------------------------------------- /test/exceptional_spec.js: -------------------------------------------------------------------------------- 1 | // a mock Exceptional 2 | // @see https://github.com/contrast/exceptional-js/blob/master/public/exceptional.js for the real deal 3 | window.Exceptional = { 4 | errors: [], 5 | handle: function(msg, url, line) { 6 | this.errors.push({ 7 | message: msg, 8 | url: url, 9 | line: line 10 | }); 11 | } 12 | }; 13 | 14 | describe('Exceptional', function() { 15 | var app; 16 | 17 | beforeEach(function() { 18 | $('#main').html('
    '); 19 | app = new Sammy.Application(function() { 20 | this.element_selector = '#main'; 21 | this.raise_errors = false; 22 | this.use(Sammy.Exceptional); 23 | this.get('#/', function() { 24 | this.trigger('done'); 25 | }); 26 | }); 27 | app.run('#/'); 28 | }); 29 | 30 | it('does not send an error to Exceptional when none is thrown', function(done) { 31 | app.bind('done', function() { 32 | expect(window.Exceptional.errors).to.be.empty(); 33 | app.unload(); 34 | done(); 35 | }); 36 | app.post('#/handle_my_form', function() { 37 | this.redirect('#/'); 38 | }); 39 | $('#myform').submit(); 40 | }); 41 | 42 | it('sends an error to Exceptional if one is thrown', function(done) { 43 | app.bind('error', function(e) { 44 | e.preventDefault(); 45 | e.stopPropagation(); 46 | if(e.stopImmediatePropagation) { 47 | e.stopImmediatePropagation(); 48 | } 49 | 50 | expect(window.Exceptional.errors).to.have.length(1); 51 | expect(window.Exceptional.errors[0].message).to.match(/Communications error/); 52 | expect(window.Exceptional.errors[0].url).to.match(new RegExp(window.location.href)); 53 | 54 | app.unload(); 55 | done(); 56 | }); 57 | 58 | app.post('#/handle_my_form', function() { 59 | throw new Error('Communications error.'); 60 | }); 61 | 62 | $('#myform').submit(); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /examples/form_handling/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | Form Handling 9 | 10 | 13 | 14 | 15 | 16 | 56 | 57 | 58 | 59 | 60 |
    61 |
    62 |
    63 | 68 |
    69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/ejs/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | EJS 8 | 11 | 12 | 13 | 14 | 58 | 59 | 60 | 61 | 62 |
    63 | 68 |
    69 |
    70 | 71 | 72 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.oauth2-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.oauth2.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:33 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).OAuth2=factory(window.jQuery,window.Sammy)}})(function($,Sammy){Sammy.OAuth2=function(app){app.use("JSON");this.authorize="/oauth/authorize";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}});this.helper("loseAccessToken",function(){this.app.loseAccessToken()});this.requireOAuth=function(options){this.before(options||{},function(context){return context.requireOAuth()})};this.getAccessToken=function(){return this.session("oauth.token")};this.setAccessToken=function(token){this.session("oauth.token",token);this.trigger("oauth.connected")};this.loseAccessToken=function(){this.session("oauth.token",null);this.trigger("oauth.disconnected")};$(document).ajaxSend(function(evt,xhr){var token=app.getAccessToken();if(token){xhr.setRequestHeader("Authorization","OAuth "+token)}});function parseParams(path){var hash=path.match(/#(.*)$/)[1];var pairs=hash.split("&"),params={};var i,len=pairs.length;for(i=0;iEventContext allowing you to call its methods within the template. 24 | // * `partials` An Object containing one or more partials (String templates 25 | // that are called from the main template). 26 | // 27 | var template = function(template, data, partials) { 28 | // use name for caching 29 | var name = template 30 | 31 | // check the cache 32 | if (!$.template[name]) { $.template(name, template); } 33 | 34 | data = $.extend({}, this, data); 35 | partials = $.extend({}, data.partials, partials); 36 | for (partial in partials) { 37 | if (!$.template[partial]) { $.template(partial, partials[partial]); } 38 | } 39 | 40 | // we could also pass along jQuery-tmpl options as a last param? 41 | return $.tmpl(name, $.extend({}, this, data)); 42 | }; 43 | 44 | // set the default method name/extension 45 | if (!method_alias) { method_alias = 'tmpl'; } 46 | // create the helper at the method alias 47 | app.helper(method_alias, template); 48 | 49 | }; 50 | 51 | return Sammy.Tmpl; 52 | 53 | })); 54 | -------------------------------------------------------------------------------- /lib/plugins/sammy.push_location_proxy.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).PushLocationProxy = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // The PushLocationProxy is an optional location proxy prototype. 10 | // PushLocationProxy gets its location from history API. No hash needed here. 11 | // Only compatible with Firefox >= Chrom 6, Firefox 4.0, Safari 5.0 12 | // 13 | // ### Example 14 | // 15 | // var app = $.sammy(function() { 16 | // // set up the location proxy 17 | // this.setLocationProxy(new Sammy.PushLocationProxy(this)); 18 | // 19 | // this.get('/about', function() { 20 | // // Do something here 21 | // }); 22 | // 23 | // }); 24 | // 25 | // Clicking on that link would not go to /about, but would set the apps location 26 | // to 'about' and trigger the route. 27 | Sammy.PushLocationProxy = function(app, selector) { 28 | this.app = app; 29 | this.selector = selector || 'a'; 30 | }; 31 | 32 | $.extend(Sammy.PushLocationProxy.prototype , { 33 | bind: function() { 34 | var proxy = this; 35 | $(window).bind('popstate', function(e) { 36 | proxy.app.trigger('location-changed'); 37 | }); 38 | this.app.$element().on('click', this.selector, function(e) { 39 | // Do not bind external links 40 | if (location.hostname == this.hostname) { 41 | e.preventDefault(); 42 | proxy.setLocation($(this).attr('href')); 43 | proxy.app.trigger('location-changed'); 44 | } 45 | }); 46 | }, 47 | 48 | unbind: function() { 49 | this.app.$element().off('click', this.selector); 50 | $(window).unbind('popstate'); 51 | }, 52 | 53 | getLocation: function() { 54 | return window.location.pathname; 55 | }, 56 | 57 | setLocation: function(new_location) { 58 | history.pushState({ path: this.path }, '', new_location); 59 | } 60 | }); 61 | 62 | return Sammy.PushLocationProxy; 63 | 64 | })); 65 | -------------------------------------------------------------------------------- /lib/plugins/sammy.title.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Title = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // Sammy.Title is a very simple plugin to easily set the document's title. 10 | // It supplies a helper for setting the title (`title()`) within routes, 11 | // and an app level method for setting the global title (`setTitle()`) 12 | Sammy.Title = function() { 13 | 14 | // setTitle allows setting a global title or a function that modifies the 15 | // title for each route/page. 16 | // 17 | // ### Example 18 | // 19 | // // setting a title prefix 20 | // $.sammy(function() { 21 | // 22 | // this.setTitle('My App -'); 23 | // 24 | // this.get('#/', function() { 25 | // this.title('Home'); // document's title == "My App - Home" 26 | // }); 27 | // }); 28 | // 29 | // // setting a title with a function 30 | // $.sammy(function() { 31 | // 32 | // this.setTitle(function(title) { 33 | // return [title, " /// My App"].join(''); 34 | // }); 35 | // 36 | // this.get('#/', function() { 37 | // this.title('Home'); // document's title == "Home /// My App"; 38 | // }); 39 | // }); 40 | // 41 | this.setTitle = function(title) { 42 | if (!$.isFunction(title)) { 43 | this.title_function = function(additional_title) { 44 | return [title, additional_title].join(' '); 45 | } 46 | } else { 47 | this.title_function = title; 48 | } 49 | }; 50 | 51 | // *Helper* title() sets the document title, passing it through the function 52 | // defined by setTitle() if set. 53 | this.helper('title', function() { 54 | var new_title = $.makeArray(arguments).join(' '); 55 | if (this.app.title_function) { 56 | new_title = this.app.title_function(new_title); 57 | } 58 | document.title = new_title; 59 | }); 60 | 61 | }; 62 | 63 | return Sammy.Title; 64 | 65 | })); 66 | -------------------------------------------------------------------------------- /lib/plugins/sammy.mixpanel.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Flash = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // A simple plugin that pings Mixpanel tracker 10 | // every time a route is triggered. Created by Juan Pablo Garcia Dalolla 11 | // (jpgarcia), based on the Sammy.GoogleAnalytics 12 | // plugin developed by Brit Gardner (britg) with updates from 13 | // Aaron Quint (quirkey). 14 | // 15 | // ### Example 16 | // 17 | // Install Mixpanel to your site as you normally would. Be sure that 18 | // the 'mixpanel' global variable exists (it should be created by the 19 | // script provided by Mixpanel). 20 | // 21 | // Then, simply add the plugin to your Sammy App and it will automatically 22 | // track all of your routes in Mixpanel. 23 | // They will appear as page views to the route's path. 24 | // 25 | // $.sammy(function() { 26 | // this.use('Mixpanel'); 27 | // 28 | // ... 29 | // }); 30 | // 31 | // If you have routes that you do not want to track, simply call 32 | // `doNotTrackMixpanel within the route. 33 | // 34 | // $.sammy(function() { 35 | // this.use('Mixpanel') 36 | // 37 | // this.get('#/dont/track/me', function() { 38 | // this.doNotTrackMixpanel(); // This route will not be tracked 39 | // }); 40 | // }); 41 | // 42 | Sammy.Mixpanel = function(app) { 43 | var shouldTrack = true; 44 | 45 | function disableTracking() { 46 | shouldTrack = false; 47 | } 48 | 49 | function enableTracking() { 50 | shouldTrack = true; 51 | } 52 | 53 | this.helpers({ 54 | // Disable tracking for the current route. Put at the begining of the 55 | // route's callback 56 | doNotTrackMixpanel: function() { 57 | disableTracking(); 58 | }, 59 | // send a page view to the tracker with `path` 60 | trackMixpanel: function(path) { 61 | if((typeof window.mixpanel != 'undefined') && shouldTrack) { 62 | this.log('tracking mixpanel', path); 63 | window.mixpanel.track(path); 64 | } 65 | } 66 | }); 67 | 68 | this.bind('event-context-after', function() { 69 | var path = this.app.last_location[1]; 70 | 71 | if (path) { 72 | this.trackMixpanel(path); 73 | enableTracking(); 74 | } 75 | }); 76 | }; 77 | 78 | return Sammy.Mixpanel; 79 | 80 | })); 81 | -------------------------------------------------------------------------------- /lib/plugins/sammy.kissmetrics.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).JSON = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // A simple plugin that pings KISSmetrics tracker 10 | // every time a route is triggered. Created by Juan Pablo Garcia Dalolla 11 | // (jpgarcia), based on the Sammy.GoogleAnalytics 12 | // plugin developed by Brit Gardner (britg) with updates from 13 | // Aaron Quint (quirkey). 14 | // 15 | // ### Example 16 | // 17 | // Install KISSmetrics to your site as you normally would. Be sure that 18 | // the '_kmq' global variable exists (it should be created by the 19 | // script provided by KISSmetrics). 20 | // 21 | // Then, simply add the plugin to your Sammy App and it will automatically 22 | // track all of your routes in KISSmetrics. 23 | // They will appear as page views to the route's path. 24 | // 25 | // $.sammy(function() { 26 | // this.use('KISSmetrics'); 27 | // 28 | // ... 29 | // }); 30 | // 31 | // If you have routes that you do not want to track, simply call 32 | // `doNotTrackKISSmetrics within the route. 33 | // 34 | // $.sammy(function() { 35 | // this.use('KISSmetrics') 36 | // 37 | // this.get('#/dont/track/me', function() { 38 | // this.doNotTrackKISSmetrics(); // This route will not be tracked 39 | // }); 40 | // }); 41 | // 42 | Sammy.KISSmetrics = function(app) { 43 | var shouldTrack = true; 44 | 45 | function disableTracking() { 46 | shouldTrack = false; 47 | } 48 | 49 | function enableTracking() { 50 | shouldTrack = true; 51 | } 52 | 53 | this.helpers({ 54 | // Disable tracking for the current route. Put at the begining of the 55 | // route's callback 56 | doNotTrackKISSmetrics: function() { 57 | disableTracking(); 58 | }, 59 | // send a page view to the tracker with `path` 60 | trackKISSmetrics: function(path) { 61 | if((typeof window._kmq != 'undefined') && shouldTrack) { 62 | this.log('tracking KISSmetrics', path); 63 | window._kmq.push(['record', path]); 64 | } 65 | } 66 | }); 67 | 68 | this.bind('event-context-after', function() { 69 | var path = this.app.last_location[1]; 70 | 71 | if (path) { 72 | this.trackKISSmetrics(path); 73 | enableTracking(); 74 | } 75 | }); 76 | }; 77 | 78 | return Sammy.KISSmetrics; 79 | 80 | })); 81 | -------------------------------------------------------------------------------- /vendor/mocha/helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is necessary when evaluating functions, 3 | * because mocha is calling them with an Assertion 4 | * as context instead of the original context. 5 | */ 6 | function bind(fn, scope) { 7 | return function() { 8 | return fn.apply(scope, arguments); 9 | }; 10 | } 11 | 12 | /** 13 | * Disables the Sammy trigger mechanism by 14 | * setting an invalid element as $element. 15 | * 16 | * This is for example necessary when triggering 17 | * errors, because mocha is going to catch those 18 | * and display them as errors, even if we catch 19 | * the original error with throwException(). 20 | */ 21 | function disableTrigger(app, callback, done) { 22 | var origElement = app.$element; 23 | app.$element = function() { return $('.doesNotExist'); }; 24 | callback(); 25 | app.$element = origElement; 26 | done(); 27 | } 28 | 29 | /** 30 | * Sets up an environment where it is possible 31 | * to listen to the "changed" event which is 32 | * triggered by "swap", "appendTo", etc. 33 | */ 34 | function listenToChanged(app, callbacks) { 35 | app.get('#/', function() {}); 36 | app.run('#/'); 37 | app.bind('changed', callbacks.onChange); 38 | callbacks.setup(); 39 | } 40 | 41 | /** 42 | * Runs the callback the second time the 43 | * function is called. 44 | */ 45 | function evaluateSecondCall(callback) { 46 | var i = 0; 47 | 48 | return function() { 49 | if(i == 1) { 50 | callback(); 51 | } 52 | 53 | i += 1; 54 | }; 55 | } 56 | 57 | /** 58 | * Test if a jquery element has the same HTML 59 | * as the given string. 60 | */ 61 | expect.Assertion.prototype.sameHTMLAs = function(obj) { 62 | var strippedHTML = function(element) { 63 | return $(element).wrap('
    ').parent().html().toString().replace(/(>)(\s*)(<)/g, "><"); 64 | }; 65 | 66 | this.assert( 67 | strippedHTML(obj) === strippedHTML(this.obj) 68 | , 'expected ' + strippedHTML(this.obj) + ' to have the same html as ' + strippedHTML(obj) 69 | , 'expected ' + strippedHTML(this.obj) + ' to not have the same html as ' + strippedHTML(obj)); 70 | return this; 71 | }; 72 | 73 | 74 | /** 75 | * IE8 fixes 76 | */ 77 | if (!Array.prototype.indexOf) { 78 | Array.prototype.indexOf = function(elt /*, from*/) { 79 | var len = this.length >>> 0; 80 | 81 | var from = Number(arguments[1]) || 0; 82 | from = (from < 0) 83 | ? Math.ceil(from) 84 | : Math.floor(from); 85 | if (from < 0) { 86 | from += len; 87 | } 88 | 89 | for (; from < len; from++) { 90 | if (from in this && 91 | this[from] === elt) { 92 | return from; 93 | } 94 | } 95 | 96 | return -1; 97 | }; 98 | } -------------------------------------------------------------------------------- /vendor/mocha/mocha.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 3 | padding: 60px 50px; 4 | } 5 | 6 | #mocha h1, h2 { 7 | margin: 0; 8 | } 9 | 10 | #mocha h1 { 11 | margin-top: 15px; 12 | font-size: 1em; 13 | font-weight: 200; 14 | } 15 | 16 | #mocha .suite .suite h1 { 17 | margin-top: 0; 18 | font-size: .8em; 19 | } 20 | 21 | #mocha h2 { 22 | font-size: 12px; 23 | font-weight: normal; 24 | cursor: pointer; 25 | } 26 | 27 | #mocha .suite { 28 | margin-left: 15px; 29 | } 30 | 31 | #mocha .test { 32 | margin-left: 15px; 33 | } 34 | 35 | #mocha .test:hover h2::after { 36 | position: relative; 37 | top: 0; 38 | right: -10px; 39 | content: '(view source)'; 40 | font-size: 12px; 41 | font-family: arial; 42 | color: #888; 43 | } 44 | 45 | #mocha .test.pending:hover h2::after { 46 | content: '(pending)'; 47 | font-family: arial; 48 | } 49 | 50 | #mocha .test.pass::before { 51 | content: '✓'; 52 | font-size: 12px; 53 | display: block; 54 | float: left; 55 | margin-right: 5px; 56 | color: #00c41c; 57 | } 58 | 59 | #mocha .test.pending { 60 | color: #0b97c4; 61 | } 62 | 63 | #mocha .test.pending::before { 64 | content: '◦'; 65 | color: #0b97c4; 66 | } 67 | 68 | #mocha .test.fail { 69 | color: #c00; 70 | } 71 | 72 | #mocha .test.fail pre { 73 | color: black; 74 | } 75 | 76 | #mocha .test.fail::before { 77 | content: '✖'; 78 | font-size: 12px; 79 | display: block; 80 | float: left; 81 | margin-right: 5px; 82 | color: #c00; 83 | } 84 | 85 | #mocha .test pre.error { 86 | color: #c00; 87 | } 88 | 89 | #mocha .test pre { 90 | display: inline-block; 91 | font: 12px/1.5 monaco, monospace; 92 | margin: 5px; 93 | padding: 15px; 94 | border: 1px solid #eee; 95 | border-bottom-color: #ddd; 96 | -webkit-border-radius: 3px; 97 | -webkit-box-shadow: 0 1px 3px #eee; 98 | } 99 | 100 | #error { 101 | color: #c00; 102 | font-size: 1.5 em; 103 | font-weight: 100; 104 | letter-spacing: 1px; 105 | } 106 | 107 | #stats { 108 | position: fixed; 109 | top: 15px; 110 | right: 10px; 111 | font-size: 12px; 112 | margin: 0; 113 | color: #888; 114 | } 115 | 116 | #stats .progress { 117 | float: right; 118 | padding-top: 0; 119 | } 120 | 121 | #stats em { 122 | color: black; 123 | } 124 | 125 | #stats li { 126 | display: inline-block; 127 | margin: 0 5px; 128 | list-style: none; 129 | padding-top: 11px; 130 | } 131 | 132 | code .comment { color: #ddd } 133 | code .init { color: #2F6FAD } 134 | code .string { color: #5890AD } 135 | code .keyword { color: #8A6343 } 136 | code .number { color: #2F6FAD } -------------------------------------------------------------------------------- /lib/min/plugins/sammy.form_2_json-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.form_2_json.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Form2JSON=factory(window.jQuery,window.Sammy)}})(function($,Sammy){(function(){window.form2object=function(rootNode,delimiter,skipEmpty){if(typeof skipEmpty=="undefined"||skipEmpty==null)skipEmpty=true;if(typeof delimiter=="undefined"||delimiter==null)delimiter=".";rootNode=typeof rootNode=="string"?document.getElementById(rootNode):rootNode;var formValues=getFormValues(rootNode);var result={};var arrays={};for(var i=0;i-1&&j==nameParts.length-1){arrayKey=arrName=namePart.substr(0,namePart.indexOf("["));if(!currResult[arrName])currResult[arrName]=[];currResult[arrName].push(value)}else{if(namePart.indexOf("[")>-1){arrName=namePart.substr(0,namePart.indexOf("["));var arrIdx=namePart.replace(/^[a-z]+\[|\]$/gi,"");arrayKey=arrayKey+arrName+arrIdx;if(!arrays[arrayKey])arrays[arrayKey]={};if(!currResult[arrName])currResult[arrName]=[];if(j==nameParts.length-1){currResult[arrName].push(value)}else{if(!arrays[arrayKey][arrIdx]){currResult[arrName].push({});arrays[arrayKey][arrIdx]=currResult[arrName][currResult[arrName].length-1]}}currResult=arrays[arrayKey][arrIdx]}else{if(j-1&&j==nameParts.length-1){arrayKey=arrName=namePart.substr(0,namePart.indexOf("["));if(!currResult[arrName])currResult[arrName]=[];currResult[arrName].push(value)}else{if(namePart.indexOf("[")>-1){arrName=namePart.substr(0,namePart.indexOf("["));var arrIdx=namePart.replace(/^[a-z]+\[|\]$/gi,"");arrayKey=arrayKey+arrName+arrIdx;if(!arrays[arrayKey])arrays[arrayKey]={};if(!currResult[arrName])currResult[arrName]=[];if(j==nameParts.length-1){currResult[arrName].push(value)}else{if(!arrays[arrayKey][arrIdx]){currResult[arrName].push({});arrays[arrayKey][arrIdx]=currResult[arrName][currResult[arrName].length-1]}}currResult=arrays[arrayKey][arrIdx]}else{if(jhaml() method to the EventContext 20 | // prototype. However, just like `Sammy.Template` you can change the default name of the method 21 | // by passing a second argument (e.g. you could use the hml() as the method alias so that all the template 22 | // files could be in the form file.hml instead of file.haml) 23 | // 24 | // ### Example 25 | // 26 | // The template (mytemplate.haml): 27 | // 28 | // %h1&= title 29 | // 30 | // Hey, #{name}! Welcome to Haml! 31 | // 32 | // The app: 33 | // 34 | // var app = $.sammy(function() { 35 | // // include the plugin 36 | // this.use(Sammy.Haml); 37 | // 38 | // this.get('#/hello/:name', function() { 39 | // // set local vars 40 | // this.title = 'Hello!' 41 | // this.name = this.params.name; 42 | // // render the template and pass it through haml 43 | // this.partial('mytemplate.haml'); 44 | // }); 45 | // }); 46 | // 47 | // $(function() { 48 | // app.run() 49 | // }); 50 | // 51 | // If I go to `#/hello/AQ` in the browser, Sammy will render this to the `body`: 52 | // 53 | //

    Hello!

    54 | // 55 | // Hey, AQ! Welcome to HAML! 56 | // 57 | // Note: You dont have to include the haml.js file on top of the plugin as the plugin 58 | // includes the full source. 59 | // 60 | Sammy.Haml = function(app, method_alias) { 61 | var haml_cache = {}; 62 | // *Helper* Uses haml-js to parse a template and interpolate and work with the passed data 63 | // 64 | // ### Arguments 65 | // 66 | // * `template` A String template. 67 | // * `data` An Object containing the replacement values for the template. 68 | // data is extended with the EventContext allowing you to call its methods within the template. 69 | // 70 | var haml = function(template, data, name) { 71 | // use name for caching 72 | if (typeof name == 'undefined') { name = template; } 73 | var fn = haml_cache[name]; 74 | if (!fn) { 75 | fn = haml_cache[name] = Haml(template); 76 | } 77 | return fn($.extend({}, this, data)); 78 | }; 79 | 80 | // set the default method name/extension 81 | if (!method_alias) { method_alias = 'haml'; } 82 | app.helper(method_alias, haml); 83 | 84 | }; 85 | 86 | return Sammy.Haml; 87 | 88 | })); 89 | -------------------------------------------------------------------------------- /lib/plugins/sammy.data_location_proxy.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).DataLocationProxy = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // The DataLocationProxy is an optional location proxy prototype. As opposed to 10 | // the `HashLocationProxy` it gets its location from a jQuery.data attribute 11 | // tied to the application's element. You can set the name of the attribute by 12 | // passing a string as the second argument to the constructor. The default attribute 13 | // name is 'sammy-location'. To read more about location proxies, check out the 14 | // documentation for `Sammy.HashLocationProxy` 15 | // 16 | // An optional `href_attribute` can be passed, which specifies a DOM element 17 | // attribute that holds "links" to different locations in the app. When the 18 | // proxy is bound, clicks to element that have this attribute activate a 19 | // `setLocation()` using the contents of the `href_attribute`. 20 | // 21 | // ### Example 22 | // 23 | // var app = $.sammy(function() { 24 | // // set up the location proxy 25 | // this.setLocationProxy(new Sammy.DataLocationProxy(this, 'location', 'rel')); 26 | // 27 | // this.get('about', function() { 28 | // this.partial('about.html'); 29 | // }); 30 | // 31 | // }); 32 | // 33 | // In this scenario, if an element existed within the template: 34 | // 35 | // About Us 36 | // 37 | // Clicking on that link would not go to /about, but would set the apps location 38 | // to 'about' and trigger the route. 39 | Sammy.DataLocationProxy = function(app, data_name, href_attribute) { 40 | this.app = app; 41 | this.data_name = data_name || 'sammy-location'; 42 | this.href_attribute = href_attribute; 43 | }; 44 | 45 | $.extend(Sammy.DataLocationProxy.prototype , { 46 | bind: function() { 47 | var proxy = this; 48 | this.app.$element().bind('setData', function(e, key, value) { 49 | if (key == proxy.data_name) { 50 | // jQuery unfortunately fires the event before it sets the value 51 | // work around it, by setting the value ourselves 52 | proxy.app.$element().each(function() { 53 | $.data(this, proxy.data_name, value); 54 | }); 55 | proxy.app.trigger('location-changed'); 56 | } 57 | }); 58 | if (this.href_attribute) { 59 | this.app.$element().delegate('[' + this.href_attribute + ']', 'click', function(e) { 60 | e.preventDefault(); 61 | proxy.setLocation($(this).attr(proxy.href_attribute)); 62 | }); 63 | } 64 | }, 65 | 66 | unbind: function() { 67 | if (this.href_attribute) { 68 | this.app.$element().undelegate('[' + this.href_attribute + ']', 'click'); 69 | } 70 | this.app.$element().unbind('setData'); 71 | }, 72 | 73 | getLocation: function() { 74 | return this.app.$element().data(this.data_name) || ''; 75 | }, 76 | 77 | setLocation: function(new_location) { 78 | return this.app.$element().data(this.data_name, new_location); 79 | } 80 | }); 81 | 82 | return Sammy.DataLocationProxy; 83 | 84 | })); 85 | -------------------------------------------------------------------------------- /examples/backend/public/javascripts/app.js: -------------------------------------------------------------------------------- 1 | ;(function($) { 2 | var app = $.sammy('#main', function() { 3 | this.debug = true; 4 | this.use(Sammy.Cache); 5 | this.use(Sammy.Template, 'erb'); 6 | 7 | var db = null, 8 | db_loaded = false; 9 | 10 | this.before(function() { 11 | if (!db_loaded) { 12 | this.redirect('#/connecting'); 13 | return false; 14 | } 15 | }); 16 | 17 | // display tasks 18 | this.get('#/', function(context) { 19 | this.partial('/templates/index.html.erb', function(html) { 20 | $('#main').html(html); 21 | $.each(db.collection('tasks').all(), function(i, task) { 22 | context.log('task', task.json()); 23 | context.partial('/templates/task.html.erb', {task: task}, function(task_html) { 24 | $(task_html).data('task', task).prependTo('#tasks'); 25 | }); 26 | }); 27 | }); 28 | }); 29 | 30 | this.get('#/tasks/:id', function() { 31 | this.task = db.collection('tasks').get(this.params['id']).json(); 32 | this.partial('/templates/task_details.html.erb') 33 | }); 34 | 35 | this.post('#/tasks', function(context) { 36 | var task = { 37 | entry: this.params['entry'], 38 | completed: false, 39 | created_at: Date() 40 | }; 41 | db.collection('tasks').create(task, { 42 | success: function(task) { 43 | context.partial('/templates/task.html.erb', {task: task}, function(task_html) { 44 | $('#tasks').prepend(task_html); 45 | }); 46 | // clear the form 47 | $('.entry').val(''); 48 | }, 49 | error: function() { 50 | context.trigger('error', {message: 'Sorry, could not save your task.'}) 51 | } 52 | }); 53 | }); 54 | 55 | this.get('#/connecting', function() { 56 | $('#main').html('... Loading ...'); 57 | }); 58 | 59 | this.bind('task-toggle', function(e, data) { 60 | this.log('data', data) 61 | var $task = data.$task; 62 | this.task = db.collection('tasks').get($task.attr('id')); 63 | this.task.attr('completed', function() { return (this == true ? false : true); }); 64 | this.task.update({}, { 65 | success: function() { 66 | $task.toggleClass('completed'); 67 | } 68 | }); 69 | }); 70 | 71 | this.bind('run', function() { 72 | var context = this; 73 | db = $.cloudkit; 74 | db.boot({ 75 | success: function() { 76 | db_loaded = true; 77 | context.trigger('db-loaded'); 78 | }, 79 | failure: function() { 80 | db_loaded = false; 81 | context.trigger('error', {message: 'Could not connect to CloudKit.'}) 82 | } 83 | }); 84 | 85 | $('li.task :checkbox').live('click', function() { 86 | var $task = $(this).parents('.task'); 87 | context.trigger('task-toggle', {$task: $task}); 88 | }); 89 | }); 90 | 91 | this.bind('db-loaded', function() { 92 | this.redirect('#/') 93 | }); 94 | 95 | this.bind('error', function(e, data) { 96 | $('#error').text(data.message).show(); 97 | }); 98 | 99 | 100 | }); 101 | 102 | $(function() { 103 | app.run('#/'); 104 | }) 105 | })(jQuery); 106 | -------------------------------------------------------------------------------- /vendor/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | 2 | ol#qunit-tests { 3 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 4 | margin:0; 5 | padding:0; 6 | list-style-position:inside; 7 | 8 | font-size: smaller; 9 | } 10 | ol#qunit-tests li{ 11 | padding:0.4em 0.5em 0.4em 2.5em; 12 | border-bottom:1px solid #fff; 13 | font-size:small; 14 | list-style-position:inside; 15 | } 16 | ol#qunit-tests li ol{ 17 | box-shadow: inset 0px 2px 13px #999; 18 | -moz-box-shadow: inset 0px 2px 13px #999; 19 | -webkit-box-shadow: inset 0px 2px 13px #999; 20 | margin-top:0.5em; 21 | margin-left:0; 22 | padding:0.5em; 23 | background-color:#fff; 24 | border-radius:15px; 25 | -moz-border-radius: 15px; 26 | -webkit-border-radius: 15px; 27 | } 28 | ol#qunit-tests li li{ 29 | border-bottom:none; 30 | margin:0.5em; 31 | background-color:#fff; 32 | list-style-position: inside; 33 | padding:0.4em 0.5em 0.4em 0.5em; 34 | } 35 | 36 | ol#qunit-tests li li.pass{ 37 | border-left:26px solid #C6E746; 38 | background-color:#fff; 39 | color:#5E740B; 40 | } 41 | ol#qunit-tests li li.fail{ 42 | border-left:26px solid #EE5757; 43 | background-color:#fff; 44 | color:#710909; 45 | } 46 | ol#qunit-tests li.pass{ 47 | background-color:#D2E0E6; 48 | color:#528CE0; 49 | } 50 | ol#qunit-tests li.fail{ 51 | background-color:#EE5757; 52 | color:#000; 53 | } 54 | ol#qunit-tests li strong { 55 | cursor:pointer; 56 | } 57 | h1#qunit-header{ 58 | background-color:#0d3349; 59 | margin:0; 60 | padding:0.5em 0 0.5em 1em; 61 | color:#fff; 62 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 63 | border-top-right-radius:15px; 64 | border-top-left-radius:15px; 65 | -moz-border-radius-topright:15px; 66 | -moz-border-radius-topleft:15px; 67 | -webkit-border-top-right-radius:15px; 68 | -webkit-border-top-left-radius:15px; 69 | text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; 70 | } 71 | h2#qunit-banner{ 72 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 73 | height:5px; 74 | margin:0; 75 | padding:0; 76 | } 77 | h2#qunit-banner.qunit-pass{ 78 | background-color:#C6E746; 79 | } 80 | h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { 81 | background-color:#EE5757; 82 | } 83 | #qunit-testrunner-toolbar { 84 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 85 | padding:0; 86 | /*width:80%;*/ 87 | padding:0em 0 0.5em 2em; 88 | font-size: small; 89 | } 90 | h2#qunit-userAgent { 91 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 92 | background-color:#2b81af; 93 | margin:0; 94 | padding:0; 95 | color:#fff; 96 | font-size: small; 97 | padding:0.5em 0 0.5em 2.5em; 98 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 99 | } 100 | p#qunit-testresult{ 101 | font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; 102 | margin:0; 103 | font-size: small; 104 | color:#2b81af; 105 | border-bottom-right-radius:15px; 106 | border-bottom-left-radius:15px; 107 | -moz-border-radius-bottomright:15px; 108 | -moz-border-radius-bottomleft:15px; 109 | -webkit-border-bottom-right-radius:15px; 110 | -webkit-border-bottom-left-radius:15px; 111 | background-color:#D2E0E6; 112 | padding:0.5em 0.5em 0.5em 2.5em; 113 | } 114 | strong b.fail{ 115 | color:#710909; 116 | } 117 | strong b.pass{ 118 | color:#5E740B; 119 | } 120 | -------------------------------------------------------------------------------- /lib/plugins/sammy.flash.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Flash = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | Sammy.FlashHash = function() { 10 | this.now = {}; 11 | }; 12 | 13 | $.extend(Sammy.FlashHash.prototype , { 14 | // @return [String] this Flash, rendered as an
      . 15 | toHTML: function() { 16 | var result = this._renderUL(); 17 | this.clear(); 18 | return result; 19 | }, 20 | 21 | clear: function() { 22 | this._clearHash(this); 23 | this._clearHash(this.now); 24 | }, 25 | 26 | // Callback on redirect. 27 | // @api private 28 | _onRedirect: function() { 29 | this._clearHash(this.now); 30 | }, 31 | 32 | // clear out all flash keys 33 | // @api private 34 | _clearHash: function(hash) { 35 | var key; 36 | for (key in hash) { 37 | if (key !== 'now' && hash.hasOwnProperty(key)) { 38 | delete hash[key]; 39 | } 40 | } 41 | }, 42 | 43 | _renderUL: function() { 44 | return '
        ' + 45 | this._renderLIs(this) + 46 | this._renderLIs(this.now) + 47 | '
      '; 48 | }, 49 | 50 | _renderLIs: function(hash) { 51 | var result = '', 52 | key; 53 | for (key in hash) { 54 | if (hash[key] && key !== 'now' && hash.hasOwnProperty(key)) { 55 | result = result + '
    • ' + hash[key] + '
    • '; 56 | } 57 | } 58 | Sammy.log('rendered flash: ' + result); 59 | return result; 60 | } 61 | }); 62 | 63 | // Sammy.Flash is a plugin for storing and sending status messages to the client. It's API and use 64 | // is similar to Ruby on Rails' `flash` explained here: 65 | // [http://apidock.com/rails/ActionController/Flash](http://apidock.com/rails/ActionController/Flash) 66 | Sammy.Flash = function(app) { 67 | app.flash = new Sammy.FlashHash(); 68 | 69 | // *Helper* flash(key, value) get or set a flash message that will 70 | // be erased on the next render (but not on redirect). 71 | // 72 | // @param [String] key, the Flash key 73 | // @param [String] value, the new value; optional 74 | // @return [Sammy.FlashHash, String, null] if a key was given, the value for that key; else, the Flash 75 | app.helper('flash', function(key, value) { 76 | if (arguments.length === 0) { 77 | return this.app.flash; 78 | } else if (arguments.length === 2) { 79 | this.app.flash[key] = value; 80 | } 81 | return this.app.flash[key]; 82 | }); 83 | 84 | // *Helper* flashNow(key, value) get or set a flash message that 85 | // will be erased on the next render or redirect. 86 | // 87 | // @param [String] key, the Flash key 88 | // @param [String] value, the new value; optional 89 | // @return [String, null] the value for the given key 90 | app.helper('flashNow', function(key, value) { 91 | if (arguments.length === 0) { 92 | return this.app.flash.now; 93 | }else if (arguments.length === 2) { 94 | this.app.flash.now[key] = value; 95 | } 96 | return this.app.flash.now[key]; 97 | }); 98 | 99 | app.bind('redirect', function() { 100 | this.app.flash._onRedirect(); 101 | }); 102 | }; 103 | 104 | return Sammy.Flash; 105 | 106 | })); 107 | -------------------------------------------------------------------------------- /lib/plugins/sammy.googleanalytics.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).GoogleAnalytics = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // A simple plugin that pings Google Analytics tracker 10 | // every time a route is triggered. Originally by Brit Gardner (britg), 11 | // with updates from Aaron Quint (quirkey). 12 | // 13 | // ### Arguments 14 | // 15 | // +tracker+:: the Google Analytics pageTracker object. Defaults to 16 | // the default object defined by the GA snippet, or pass your own if you 17 | // have a custom install. If using analytics.js pass a string with the 18 | // name of your tracker. 19 | // 20 | // ### Example 21 | // 22 | // Install Google Analytics to your site as you normally would. 23 | // 24 | // If using ga.js be sure that the 'pageTracker' global variable exists. 25 | // If using analytics.js there is nothing to do (but you probably want 26 | // to remove the ga('send','pageview') line) 27 | // 28 | // Then, simply add the plugin to your Sammy App and it will automatically 29 | // track all of your routes in Google Analytics. 30 | // They will appear as page views to the route's path. 31 | // 32 | // $.sammy(function() { 33 | // this.use('GoogleAnalytics'); 34 | // 35 | // ... 36 | // }); 37 | // 38 | // If you have routes that you do not want to track, simply call `noTrack` 39 | // within the route. 40 | // 41 | // $.sammy(function() { 42 | // this.use('GoogleAnalytics') 43 | // 44 | // this.get('#/dont/track/me', function() { 45 | // this.noTrack(); // This route will not be tracked 46 | // }); 47 | // }); 48 | // 49 | Sammy.GoogleAnalytics = function(app, tracker) { 50 | var _tracker = tracker || window.pageTracker, 51 | trackerName = 'send', 52 | shouldTrack = true; 53 | 54 | if (typeof tracker == 'string' || tracker instanceof String){ 55 | trackerName = tracker + '.send'; 56 | } 57 | 58 | function disableTracking() { 59 | shouldTrack = false; 60 | } 61 | 62 | function enableTracking() { 63 | shouldTrack = true; 64 | } 65 | 66 | function trackPageview(path) { 67 | if (typeof ga == 'function') { 68 | ga(trackerName, 'pageview', { 'page': path }); 69 | } else if (typeof _gaq != 'undefined') { 70 | _gaq.push(['_trackPageview', path]); 71 | } else if (typeof _tracker != 'undefined') { 72 | _tracker._trackPageview(path); 73 | } 74 | } 75 | 76 | this.helpers({ 77 | // Disable tracking for the current route. Put at the begining of the 78 | // route's callback 79 | noTrack: function() { 80 | disableTracking(); 81 | }, 82 | // send a page view to the tracker with `path` 83 | track: function(path) { 84 | if((typeof _tracker != 'undefined' || typeof _gaq != 'undefined' || typeof ga == 'function') && shouldTrack) { 85 | this.log('tracking google analytics', path); 86 | trackPageview(path); 87 | } 88 | } 89 | }); 90 | 91 | this.bind('event-context-after', function() { 92 | var path = this.app.last_location[1]; 93 | if (path) { 94 | this.track(path); 95 | enableTracking(); 96 | } 97 | }); 98 | }; 99 | 100 | return Sammy.GoogleAnalytics; 101 | 102 | })); 103 | -------------------------------------------------------------------------------- /lib/min/plugins/sammy.json-0.7.6.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.json.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:32 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).JSON=factory(window.jQuery,window.Sammy)}})(function($,Sammy){if(!window.JSON){window.JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z"};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b"," ":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i"}else if(typeof content!="undefined"){html+=">";html+=getStringContent(this,content);html+=""}else{html+=" />"}return html}Sammy.FormBuilder=function(name,object){this.name=name;this.object=object};$.extend(Sammy.FormBuilder.prototype,{open:function(attributes){return simple_element("form",$.extend({method:"post",action:"#/"+this.name+"s"},attributes),false)},close:function(){return""},label:function(keypath,content,attributes){var attrs={"for":this._attributesForKeyPath(keypath).name};return simple_element("label",$.extend(attrs,attributes),content)},hidden:function(keypath,attributes){attributes=$.extend({type:"hidden"},this._attributesForKeyPath(keypath),attributes);return simple_element("input",attributes)},text:function(keypath,attributes){attributes=$.extend({type:"text"},this._attributesForKeyPath(keypath),attributes);return simple_element("input",attributes)},textarea:function(keypath,attributes){var current;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);current=attributes.value;delete attributes["value"];return simple_element("textarea",attributes,current)},password:function(keypath,attributes){return this.text(keypath,$.extend({type:"password"},attributes))},select:function(keypath,options,attributes){var option_html="",selected;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);selected=attributes.value;delete attributes["value"];$.each(options,function(i,option){var value,text,option_attrs;if($.isArray(option)){value=option[1];text=option[0]}else{value=option;text=option}option_attrs={value:getStringContent(this.object,value)};if(value===selected){option_attrs.selected="selected"}option_html+=simple_element("option",option_attrs,text)});return simple_element("select",attributes,option_html)},radio:function(keypath,value,attributes){var selected;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);selected=attributes.value;attributes.value=getStringContent(this.object,value);if(selected==attributes.value){attributes.checked="checked"}return simple_element("input",$.extend({type:"radio"},attributes))},checkbox:function(keypath,value,attributes){var content="";if(!attributes){attributes={}}if(attributes.hidden_element!==false){content+=this.hidden(keypath,{value:!value})}delete attributes["hidden_element"];content+=this.radio(keypath,value,$.extend({type:"checkbox"},attributes));return content},submit:function(attributes){return simple_element("input",$.extend({type:"submit"},attributes))},_attributesForKeyPath:function(keypath){var builder=this,keys=$.isArray(keypath)?keypath:keypath.split(/\./),name=builder.name,value=builder.object,class_name=builder.name;$.each(keys,function(i,key){if(typeof value==="undefined"||value===""){value=""}else if(typeof key=="number"||key.match(/^\d+$/)){value=value[parseInt(key,10)]}else{value=value[key]}name+="["+key+"]";class_name+="-"+key});return{name:name,value:getStringContent(builder.object,value),"class":class_name}}});Sammy.Form=function(app){app.helpers({simple_element:simple_element,formFor:function(name,object,content_callback){var builder;if($.isFunction(object)){content_callback=object;object=this[name]}builder=new Sammy.FormBuilder(name,object);content_callback.apply(this,[builder]);return builder}})};return Sammy.Form}); -------------------------------------------------------------------------------- /lib/min/plugins/sammy.form-latest.min.js: -------------------------------------------------------------------------------- 1 | // -- Sammy.js -- /plugins/sammy.form.js 2 | // http://sammyjs.org 3 | // Version: 0.7.6 4 | // Built: 2014-08-26 10:45:31 +0300 5 | (function(factory){if(typeof define==="function"&&define.amd){define(["jquery","sammy"],factory)}else{(window.Sammy=window.Sammy||{}).Form=factory(window.jQuery,window.Sammy)}})(function($,Sammy){function getStringContent(object,content){if(typeof content==="undefined"){return""}else if($.isFunction(content)){content=content.apply(object)}return content.toString()}function simple_element(tag,attributes,content){var html="<";html+=tag;if(typeof attributes!="undefined"){$.each(attributes,function(key,value){if(value!==null){html+=" "+key+"='";html+=getStringContent(attributes,value).replace(/\'/g,"'");html+="'"}})}if(content===false){html+=">"}else if(typeof content!="undefined"){html+=">";html+=getStringContent(this,content);html+=""}else{html+=" />"}return html}Sammy.FormBuilder=function(name,object){this.name=name;this.object=object};$.extend(Sammy.FormBuilder.prototype,{open:function(attributes){return simple_element("form",$.extend({method:"post",action:"#/"+this.name+"s"},attributes),false)},close:function(){return""},label:function(keypath,content,attributes){var attrs={"for":this._attributesForKeyPath(keypath).name};return simple_element("label",$.extend(attrs,attributes),content)},hidden:function(keypath,attributes){attributes=$.extend({type:"hidden"},this._attributesForKeyPath(keypath),attributes);return simple_element("input",attributes)},text:function(keypath,attributes){attributes=$.extend({type:"text"},this._attributesForKeyPath(keypath),attributes);return simple_element("input",attributes)},textarea:function(keypath,attributes){var current;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);current=attributes.value;delete attributes["value"];return simple_element("textarea",attributes,current)},password:function(keypath,attributes){return this.text(keypath,$.extend({type:"password"},attributes))},select:function(keypath,options,attributes){var option_html="",selected;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);selected=attributes.value;delete attributes["value"];$.each(options,function(i,option){var value,text,option_attrs;if($.isArray(option)){value=option[1];text=option[0]}else{value=option;text=option}option_attrs={value:getStringContent(this.object,value)};if(value===selected){option_attrs.selected="selected"}option_html+=simple_element("option",option_attrs,text)});return simple_element("select",attributes,option_html)},radio:function(keypath,value,attributes){var selected;attributes=$.extend(this._attributesForKeyPath(keypath),attributes);selected=attributes.value;attributes.value=getStringContent(this.object,value);if(selected==attributes.value){attributes.checked="checked"}return simple_element("input",$.extend({type:"radio"},attributes))},checkbox:function(keypath,value,attributes){var content="";if(!attributes){attributes={}}if(attributes.hidden_element!==false){content+=this.hidden(keypath,{value:!value})}delete attributes["hidden_element"];content+=this.radio(keypath,value,$.extend({type:"checkbox"},attributes));return content},submit:function(attributes){return simple_element("input",$.extend({type:"submit"},attributes))},_attributesForKeyPath:function(keypath){var builder=this,keys=$.isArray(keypath)?keypath:keypath.split(/\./),name=builder.name,value=builder.object,class_name=builder.name;$.each(keys,function(i,key){if(typeof value==="undefined"||value===""){value=""}else if(typeof key=="number"||key.match(/^\d+$/)){value=value[parseInt(key,10)]}else{value=value[key]}name+="["+key+"]";class_name+="-"+key});return{name:name,value:getStringContent(builder.object,value),"class":class_name}}});Sammy.Form=function(app){app.helpers({simple_element:simple_element,formFor:function(name,object,content_callback){var builder;if($.isFunction(object)){content_callback=object;object=this[name]}builder=new Sammy.FormBuilder(name,object);content_callback.apply(this,[builder]);return builder}})};return Sammy.Form}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sammy 2 | 3 | [http://sammyjs.org](http://sammyjs.org) 4 | 5 | ## Description 6 | 7 | Sammy is a tiny javascript framework built on top of jQuery inspired by Ruby's [Sinatra](http://sinatrarb.com). 8 | 9 | ## Installation 10 | 11 | Download Sammy.js and install it in your public javascripts directory. 12 | Include it in your document AFTER jQuery. 13 | 14 | ## Usage 15 | 16 | Like Sinatra, a Sammy application revolves around 'routes'. Routes in Sammy are a little different, though. Not only can you define 'get' and 'post' routes, but you can also bind routes to custom events triggered by your application. 17 | 18 | You set up a Sammy Application by passing a Function to the `$.sammy` (which is a shortcut for the Sammy.Application constructor). 19 | 20 | $.sammy(function() { 21 | 22 | this.get('#/', function() { 23 | $('#main').text('Welcome!'); 24 | }); 25 | 26 | }); 27 | 28 | Inside the 'app' function() `this` is the Application. This is where you can configure the application and add routes. 29 | 30 | Above, we defined a `get()` route. When the browser is pointed to `#/` the function passed to that route will be run. Inside the route function, `this` is a Sammy.EventContext. EventContext has a bunch of special methods and properties including a params hash, the ability to redirect, render partials, and more. 31 | 32 | In its coolness, Sammy can handle multiple chained asynchronous callbacks on a route. 33 | 34 | this.get('#/', function(context,next) { 35 | $('#main').text('Welcome!'); 36 | $.get('/some/url',function(){ 37 | // save the data somewhere 38 | next(); 39 | }); 40 | }, function(context,next) { 41 | $.get('/some/other/url',function(){ 42 | // save this data too 43 | next(); 44 | }); 45 | }); 46 | 47 | 48 | Once you've defined an application the only thing left to do is run it. The best-practice behavior is to encapsulate `run()` in a document.ready block: 49 | 50 | var app = $.sammy(...) 51 | 52 | $(function() { 53 | app.run(); 54 | }); 55 | 56 | This will guarantee that the DOM is loaded before we try to apply functionality to it. 57 | 58 | ## Dependencies 59 | 60 | Sammy requires jQuery >= 1.4.1 61 | Get it from: [http://jquery.com](http://jquery.com) 62 | 63 | ## More! 64 | 65 | ### Learn! 66 | 67 | * [Intro](http://code.quirkey.com/sammy) 68 | * [Docs](http://code.quirkey.com/sammy/docs/) 69 | * [Examples](http://github.com/quirkey/sammy/tree/master/examples/) 70 | * [More Resources on the Sammy Wiki](http://github.com/quirkey/sammy/wiki/) 71 | 72 | ### Keep informed! 73 | 74 | * [Follow @sammy_js](http://twitter.com/sammy_js) 75 | * [Join the mailing list](http://groups.google.com/group/sammyjs) 76 | * [Chat with us in #sammy](irc://irc.freenode.net/#sammy) 77 | 78 | ## Authors 79 | 80 | Sammy.js was created and is maintained by Aaron Quint with additional features and fixes contributed by these talented individuals: 81 | 82 | * Frank Prößdorf / endor 83 | * Alexander Lang / langalex 84 | * Scott McMillin / scottymac 85 | * ZhangJinzhu / jinzhu 86 | * Jesse Hallett / hallettj 87 | * Jonathan Vaught / gravelpup 88 | * Jason Davies / jasondavies 89 | * Russell Jones / CodeOfficer 90 | * Geoff Longman 91 | * Jens Bissinger / dpree 92 | * Tim Caswell / creationix 93 | * Mark Needham 94 | * SamDeLaGarza 95 | * Mickael Bailly / dready92 96 | * Rich Manalang / manalang 97 | * Brian Mitchell / binary42 98 | * Assaf Arkin / assaf 99 | * James Rosen / jamesrosen 100 | * Chris Mytton 101 | * kbuckler 102 | * dvv 103 | * Ben Vinegar / benvinegar 104 | * Avi Deitcher / deitch 105 | 106 | ## Donate! 107 | 108 | If you're using Sammy.js in production or just for fun, instead of gifting me a beer - please consider donating to the [Code for Other People Fund](http://pledgie.com/campaigns/15239). - you can probably spare a dollar or ten and it will be greatly appreciated. 109 | 110 | ## License 111 | 112 | Sammy is covered by the MIT License. See LICENSE for more information. 113 | 114 | -------------------------------------------------------------------------------- /lib/plugins/sammy.cache.js: -------------------------------------------------------------------------------- 1 | // deprecated 2 | (function (factory) { 3 | if (typeof define === 'function' && define.amd) { 4 | define(['jquery', 'sammy'], factory); 5 | } else { 6 | (window.Sammy = window.Sammy || {}).MemoryCacheProxy = factory(window.jQuery, window.Sammy); 7 | } 8 | }(function ($, Sammy) { 9 | 10 | // A simple cache strategy that stores key/values in memory. 11 | Sammy.MemoryCacheProxy = function(initial) { 12 | this._cache = initial || {}; 13 | }; 14 | 15 | $.extend(Sammy.MemoryCacheProxy.prototype, { 16 | exists: function(name) { 17 | return (typeof this._cache[name] != "undefined"); 18 | }, 19 | set: function(name, value) { 20 | return this._cache[name] = value; 21 | }, 22 | get: function(name) { 23 | return this._cache[name]; 24 | }, 25 | clear: function(name) { 26 | delete this._cache[name]; 27 | } 28 | }); 29 | 30 | // A simple cache strategy that stores key/values $element.data() with a cache. prefix 31 | Sammy.DataCacheProxy = function(initial, $element) { 32 | initial = initial || {}; 33 | this.$element = $element; 34 | $.each(initial, function(key, value) { 35 | $element.data('cache.' + key, value); 36 | }); 37 | }; 38 | 39 | $.extend(Sammy.DataCacheProxy.prototype, { 40 | exists: function(name) { 41 | return (typeof this.$element.data('cache.' + name) != "undefined"); 42 | }, 43 | set: function(name, value) { 44 | return this.$element.data('cache.' + name, value); 45 | }, 46 | get: function(name) { 47 | return this.$element.data('cache.' + name); 48 | }, 49 | clear: function(name) { 50 | this.$element.removeData('cache.' + name); 51 | } 52 | }); 53 | 54 | // Sammy.Cache provides helpers for caching data within the lifecycle of a 55 | // Sammy app. The plugin provides two main methods on Sammy.Application, 56 | // cache and clearCache. Each app has its own cache store so that 57 | // you dont have to worry about collisions. There are currently two different 'cache proxies' 58 | // that share the same API but store the data in different ways. 59 | // 60 | // ### Arguments 61 | // 62 | // * `proxy` decides which caching proxy to use, either 'memory'(default) or 'data' 63 | // 64 | Sammy.Cache = function(app, proxy) { 65 | 66 | app.log('**WARNING:** This version of Sammy.Cache has been deprecated in favor of using the version in Sammy.Storage and will be removed in 1.0') 67 | 68 | if (proxy == 'data') { 69 | this.cache_proxy = new Sammy.DataCacheProxy({}, this.$element()); 70 | } else { 71 | this.cache_proxy = new Sammy.MemoryCacheProxy({}); 72 | } 73 | 74 | app.cache_partials = true; 75 | 76 | $.extend(app, { 77 | // cache is the main method for interacting with the cache store. The same 78 | // method is used for both setting and getting the value. The API is similar 79 | // to jQuery.fn.attr() 80 | // 81 | // ### Examples 82 | // 83 | // // setting a value 84 | // cache('key', 'value'); 85 | // 86 | // // getting a value 87 | // cache('key'); //=> 'value' 88 | // 89 | // // setting a value with a callback 90 | // cache('key', function() { 91 | // // this is the app 92 | // return app.element_selector; 93 | // }); 94 | // 95 | cache: function(name, value) { 96 | if (typeof value == 'undefined') { 97 | return this.cache_proxy.get(name); 98 | } else if ($.isFunction(value) && !this.cache_proxy.exists(name)) { 99 | return this.cache_proxy.set(name, value.apply(this)); 100 | } else { 101 | return this.cache_proxy.set(name, value) 102 | } 103 | }, 104 | 105 | // clears the cached value for name 106 | clearCache: function(name) { 107 | return this.cache_proxy.clear(name); 108 | } 109 | }); 110 | 111 | app.helpers({ 112 | // a helper shortcut for use in Sammy.EventContext 113 | cache: function(name, value) { 114 | return this.app.cache(name, value); 115 | } 116 | }); 117 | }; 118 | 119 | return Sammy.MemoryCacheProxy; 120 | 121 | })); 122 | -------------------------------------------------------------------------------- /lib/plugins/sammy.nested_params.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).NestedParams = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | function parseValue(value) { 10 | value = unescape(value); 11 | if (value === "true") { 12 | return true; 13 | } else if (value === "false") { 14 | return false; 15 | } else { 16 | return value; 17 | } 18 | } 19 | 20 | function parseNestedParam(params, field_name, field_value) { 21 | var match, name, rest; 22 | 23 | if (field_name.match(/^[^\[]+$/)) { 24 | // basic value 25 | params[field_name] = parseValue(field_value); 26 | } else if (match = field_name.match(/^([^\[]+)\[\](.*)$/)) { 27 | // array 28 | name = match[1]; 29 | rest = match[2]; 30 | 31 | if(params[name] && !$.isArray(params[name])) { throw('400 Bad Request'); } 32 | 33 | if (rest) { 34 | // array is not at the end of the parameter string 35 | match = rest.match(/^\[([^\]]+)\](.*)$/); 36 | if(!match) { throw('400 Bad Request'); } 37 | 38 | if (params[name]) { 39 | if(params[name][params[name].length - 1][match[1]]) { 40 | params[name].push(parseNestedParam({}, match[1] + match[2], field_value)); 41 | } else { 42 | $.extend(true, params[name][params[name].length - 1], parseNestedParam({}, match[1] + match[2], field_value)); 43 | } 44 | } else { 45 | params[name] = [parseNestedParam({}, match[1] + match[2], field_value)]; 46 | } 47 | } else { 48 | // array is at the end of the parameter string 49 | if (params[name]) { 50 | params[name].push(parseValue(field_value)); 51 | } else { 52 | params[name] = [parseValue(field_value)]; 53 | } 54 | } 55 | } else if (match = field_name.match(/^([^\[]+)\[([^\[]+)\](.*)$/)) { 56 | // hash 57 | name = match[1]; 58 | rest = match[2] + match[3]; 59 | 60 | if (params[name] && $.isArray(params[name])) { throw('400 Bad Request'); } 61 | 62 | if (params[name]) { 63 | $.extend(true, params[name], parseNestedParam(params[name], rest, field_value)); 64 | } else { 65 | params[name] = parseNestedParam({}, rest, field_value); 66 | } 67 | } 68 | return params; 69 | } 70 | 71 | // Sammy.NestedParams overrides the default form parsing behavior to provide 72 | // extended functionality for parsing Rack/Rails style form name/value pairs into JS 73 | // Objects. In fact it passes the same suite of tests as Rack's nested query parsing. 74 | // The code and tests were ported to JavaScript/Sammy by http://github.com/endor 75 | // 76 | // This allows you to translate a form with properly named inputs into a JSON object. 77 | // 78 | // ### Example 79 | // 80 | // Given an HTML form like so: 81 | // 82 | //
      83 | // 84 | // 85 | // 86 | // 87 | //
      88 | // 89 | // And a Sammy app like: 90 | // 91 | // var app = $.sammy(function(app) { 92 | // this.use(Sammy.NestedParams); 93 | // 94 | // this.post('#/parse_me', function(context) { 95 | // $.log(this.params); 96 | // }); 97 | // }); 98 | // 99 | // If you filled out the form with some values and submitted it, you would see something 100 | // like this in your log: 101 | // 102 | // { 103 | // 'obj': { 104 | // 'first': 'value', 105 | // 'second': 'value', 106 | // 'hash': { 107 | // 'first': 'value', 108 | // 'second': 'value' 109 | // } 110 | // } 111 | // } 112 | // 113 | // It supports creating arrays with [] and other niceities. Check out the tests for 114 | // full specs. 115 | // 116 | Sammy.NestedParams = function(app) { 117 | 118 | app._parseParamPair = parseNestedParam; 119 | 120 | }; 121 | 122 | return Sammy.NestedParams; 123 | 124 | })); 125 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mocha Tests 5 | 6 | 11 | 12 | 13 | 14 | 15 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 95 | 96 | 97 |
      98 |
      99 |
      100 | 101 | 102 | -------------------------------------------------------------------------------- /examples/backend/public/javascripts/sammy.template.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | // Simple JavaScript Templating 4 | // John Resig - http://ejohn.org/ - MIT Licensed 5 | // adapted from: http://ejohn.org/blog/javascript-micro-templating/ 6 | // originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009 7 | // modified for Sammy by Aaron Quint for caching templates by name 8 | var srender_cache = {}; 9 | var srender = function(name, template, data) { 10 | // target is an optional element; if provided, the result will be inserted into it 11 | // otherwise the result will simply be returned to the caller 12 | if (srender_cache[name]) { 13 | fn = srender_cache[name]; 14 | } else { 15 | if (typeof template == 'undefined') { 16 | // was a cache check, return false 17 | return false; 18 | } 19 | // Generate a reusable function that will serve as a template 20 | // generator (and which will be cached). 21 | fn = srender_cache[name] = new Function("obj", 22 | "var p=[],print=function(){p.push.apply(p,arguments);};" + 23 | 24 | // Introduce the data as local variables using with(){} 25 | "with(obj){p.push(\"" + 26 | 27 | // Convert the template into pure JavaScript 28 | template 29 | .replace(/[\r\t\n]/g, " ") 30 | .replace(/\"/g, '\\"') 31 | .split("<%").join("\t") 32 | .replace(/((^|%>)[^\t]*)/g, "$1\r") 33 | .replace(/\t=(.*?)%>/g, "\",$1,\"") 34 | .split("\t").join("\");") 35 | .split("%>").join("p.push(\"") 36 | .split("\r").join("") 37 | + "\");}return p.join('');"); 38 | } 39 | 40 | if (typeof data != 'undefined') { 41 | return fn(data); 42 | } else { 43 | return fn; 44 | } 45 | }; 46 | 47 | Sammy = Sammy || {}; 48 | 49 | // Sammy.Template is a simple plugin that provides a way to create 50 | // and render client side templates. The rendering code is based on John Resig's 51 | // quick templates and Greg Borenstien's srender plugin. 52 | // This is also a great template/boilerplate for Sammy plugins. 53 | // 54 | // Templates use <% %> tags to denote embedded javascript. 55 | // 56 | // === Examples 57 | // 58 | // Here is an example template (user.template): 59 | // 60 | //
      61 | //
      <%= user.name %>
      62 | // <% if (user.photo_url) { %> 63 | //
      64 | // <% } %> 65 | //
      66 | // 67 | // Given that is a publicly accesible file, you would render it like: 68 | // 69 | // $.sammy(function() { 70 | // // include the plugin 71 | // this.use(Sammy.Template); 72 | // 73 | // this.get('#/', function() { 74 | // // the template is rendered in the current context. 75 | // this.user = {name: 'Aaron Quint'}; 76 | // // partial calls template() because of the file extension 77 | // this.partial('user.template'); 78 | // }) 79 | // }); 80 | // 81 | // You can also pass a second argument to use() that will alias the template 82 | // method and therefore allow you to use a different extension for template files 83 | // in partial() 84 | // 85 | // // alias to 'tpl' 86 | // this.use(Sammy.Template, 'tpl'); 87 | // 88 | // // now .tpl files will be run through srender 89 | // this.get('#/', function() { 90 | // this.partial('myfile.tpl'); 91 | // }); 92 | // 93 | Sammy.Template = function(app, method_alias) { 94 | 95 | // *Helper:* Uses simple templating to parse ERB like templates. 96 | // 97 | // === Arguments 98 | // 99 | // +template+:: A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data. 100 | // +data+:: An Object containing the replacement values for the template. 101 | // data is extended with the EventContext allowing you to call its methods within the template. 102 | // +name+:: An optional String name to cache the template. 103 | // 104 | var template = function(template, data, name) { 105 | // use name for caching 106 | if (typeof name == 'undefined') name = template; 107 | return srender(name, template, $.extend({}, this, data)); 108 | }; 109 | 110 | // set the default method name/extension 111 | if (!method_alias) method_alias = 'template'; 112 | // create the helper at the method alias 113 | app.helper(method_alias, template); 114 | 115 | }; 116 | 117 | })(jQuery); 118 | -------------------------------------------------------------------------------- /vendor/qunit-spec.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | QUnit.Spec = function(name) { 4 | this.before = false; 5 | this.after = false; 6 | QUnit.module(name); 7 | }; 8 | 9 | function extend(a, b) { 10 | for ( var prop in b ) { 11 | a[prop] = b[prop]; 12 | } 13 | return a; 14 | } 15 | 16 | // RSpec style describe 17 | // takes an arbitrary number of arguments that are contactenated as strings 18 | // the last argument is the configuration object 19 | // which can have before: after: callbacks 20 | function describe() { 21 | var args = Array.prototype.slice.call(arguments), 22 | // configuration function 23 | config = (args.length > 0 && args[args.length - 1]['before']) ? args.pop() : {}, 24 | spec = new QUnit.Spec(args.join(' ')); 25 | spec['before'] = config['before'] || config['setup']; 26 | spec['after'] = config['after'] || config['teardown']; 27 | return spec; 28 | } 29 | 30 | extend(QUnit.Spec.prototype, { 31 | 32 | // RSpec style test definition 33 | it: function(name, callback, nowait) { 34 | var spec = this; 35 | var spec_context = {}; 36 | QUnit.test(name, function() { 37 | if (spec.before) spec.before.apply(spec_context); 38 | callback.apply(spec_context, [this, spec]); 39 | if (spec.after) spec.after.apply(spec_context); 40 | }); 41 | return spec; 42 | }, 43 | 44 | // Shoulda style test definition 45 | should: function(name, callback, nowait) { 46 | name = 'should ' + name; 47 | return this.it.apply(this, [name, callback, nowait]); 48 | }, 49 | 50 | pending: function(name, callback, nowait) { 51 | name = 'DEFERRED: ' + name + ''; 52 | return this.it.apply(this, [name, function () { QUnit.ok(true) }, nowait]); 53 | }, 54 | 55 | should_eventually: function(name, callback, nowait) { 56 | return this.pending(name, callback, nowait); 57 | } 58 | 59 | }); 60 | 61 | 62 | extend(QUnit, { 63 | // aliases for describe 64 | describe: describe, 65 | context: describe, 66 | 67 | // asserts that the method is defined (like respond_to?) 68 | defined: function(object, method) { 69 | return QUnit.ok(QUnit.is('Function', object[method]), method + ' is not defined on ' + object); 70 | }, 71 | 72 | // asserts that the object is of a certain type 73 | isType: function(object, type) { 74 | return ok(QUnit.is(type, object), "expected " + object + " to be a " + type); 75 | }, 76 | 77 | // assert a string matches a regex 78 | matches: function(matcher, string, message) { 79 | return QUnit.ok(!!matcher.test(string), message || "expected: " + string + "match(" + matcher.toString() + ")"); 80 | }, 81 | 82 | // assert that a matching error is raised 83 | // expected can be a regex, a string, or an object 84 | raised: function(expected_error, callback) { 85 | var error = null; 86 | try { 87 | callback.apply(this); 88 | } catch(e) { 89 | error = e; 90 | } 91 | error_message = error && error.message ? error.message.toString() : error.toString(); 92 | message = "expected error: " + expected_error.toString() + ", actual error:" + error_message; 93 | if (expected_error.constructor == RegExp) { 94 | return QUnit.matches(expected_error, error_message, message); 95 | } else if (expected_error.constructor == String) { 96 | return QUnit.equal(expected_error, error_message, message); 97 | } else { 98 | return QUnit.deepEqual(expected_error, error, message); 99 | } 100 | }, 101 | 102 | notRaised: function(callback) { 103 | var error = ''; 104 | try { 105 | callback.apply(this); 106 | } catch(e) { 107 | error = e; 108 | } 109 | message = "expected: no errors, actual error: " + error.toString(); 110 | QUnit.equal('', error, message); 111 | }, 112 | 113 | soon: function(callback, context, secs, many_expects) { 114 | if (typeof context == 'undefined') context = this; 115 | if (typeof secs == 'undefined') secs = 1; 116 | if (typeof many_expects == 'undefined') many_expects = 1; 117 | QUnit.expect(many_expects); 118 | QUnit.stop(); 119 | setTimeout(function() { 120 | callback.apply(context); 121 | QUnit.start(); 122 | }, secs * 500); 123 | }, 124 | 125 | flunk: function() { 126 | QUnit.ok(false, 'FLUNK'); 127 | }, 128 | 129 | sameHTML: function(actual, expected) { 130 | var strippedHTML = function(element) { 131 | return $(element) 132 | .wrap('
      ') 133 | .parent() 134 | .html() 135 | .toString() 136 | .replace(/(>)(\s+)(<)/g, "><"); 137 | }; 138 | 139 | actual = strippedHTML(actual); 140 | expected = strippedHTML(expected); 141 | equal(actual, expected, "HTML is equal"); 142 | } 143 | 144 | }); 145 | 146 | 147 | })(); 148 | -------------------------------------------------------------------------------- /lib/plugins/sammy.mustache.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy', 'mustache'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Mustache = factory(window.jQuery, window.Sammy, window.Mustache); 6 | } 7 | }(function ($, Sammy, Mustache) { 8 | 9 | // Sammy.Mustache provides a quick way of using mustache style templates in your app. 10 | // The plugin wraps the awesome mustache.js lib created and maintained by Jan Lehnardt 11 | // at http://github.com/janl/mustache.js 12 | // 13 | // Note: As of Sammy 0.7 the Mustache lib is not included in the templates source. Please download 14 | // mustache.js and include it before Sammy.Mustache. 15 | // 16 | // Mustache is a clever templating system that relys on double brackets {{}} for interpolation. 17 | // For full details on syntax check out the original Ruby implementation created by Chris Wanstrath at 18 | // http://github.com/defunkt/mustache 19 | // 20 | // By default using Sammy.Mustache in your app adds the mustache() method to the EventContext 21 | // prototype. However, just like Sammy.Template you can change the default name of the method 22 | // by passing a second argument (e.g. you could use the ms() as the method alias so that all the template 23 | // files could be in the form file.ms instead of file.mustache) 24 | // 25 | // ### Example #1 26 | // 27 | // The template (mytemplate.ms): 28 | // 29 | //

      {{title}}

      30 | // 31 | // Hey, {{name}}! Welcome to Mustache! 32 | // 33 | // The app: 34 | // 35 | // var app = $.sammy(function() { 36 | // // include the plugin and alias mustache() to ms() 37 | // this.use('Mustache', 'ms'); 38 | // 39 | // this.get('#/hello/:name', function() { 40 | // // set local vars 41 | // this.title = 'Hello!' 42 | // this.name = this.params.name; 43 | // // render the template and pass it through mustache 44 | // this.partial('mytemplate.ms'); 45 | // }); 46 | // }); 47 | // 48 | // $(function() { 49 | // app.run() 50 | // }); 51 | // 52 | // If I go to #/hello/AQ in the browser, Sammy will render this to the body: 53 | // 54 | //

      Hello!

      55 | // 56 | // Hey, AQ! Welcome to Mustache! 57 | // 58 | // 59 | // ### Example #2 - Mustache partials 60 | // 61 | // The template (mytemplate.ms) 62 | // 63 | // Hey, {{name}}! {{>hello_friend}} 64 | // 65 | // 66 | // The partial (mypartial.ms) 67 | // 68 | // Say hello to your friend {{friend}}! 69 | // 70 | // The app: 71 | // 72 | // var app = $.sammy(function() { 73 | // // include the plugin and alias mustache() to ms() 74 | // this.use('Mustache', 'ms'); 75 | // 76 | // this.get('#/hello/:name/to/:friend', function(context) { 77 | // // fetch mustache-partial first 78 | // this.load('mypartial.ms') 79 | // .then(function(partial) { 80 | // // set local vars 81 | // context.partials = {hello_friend: partial}; 82 | // context.name = context.params.name; 83 | // context.friend = context.params.friend; 84 | // 85 | // // render the template and pass it through mustache 86 | // context.partial('mytemplate.ms'); 87 | // }); 88 | // }); 89 | // }); 90 | // 91 | // $(function() { 92 | // app.run() 93 | // }); 94 | // 95 | // If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the body: 96 | // 97 | // Hey, AQ! Say hello to your friend dP! 98 | // 99 | // Note: You need to include the mustache.js file before this plugin. 100 | // 101 | Sammy.Mustache = function(app, method_alias) { 102 | 103 | // *Helper* Uses Mustache.js to parse a template and interpolate and work with the passed data 104 | // 105 | // ### Arguments 106 | // 107 | // * `template` A String template. {{}} Tags are evaluated and interpolated by Mustache.js 108 | // * `data` An Object containing the replacement values for the template. 109 | // data is extended with the EventContext allowing you to call its methods within the template. 110 | // * `partials` An Object containing one or more partials (String templates 111 | // that are called from the main template). 112 | // 113 | var mustache = function(template, data, partials) { 114 | data = $.extend({}, this, data); 115 | partials = $.extend({}, data.partials, partials); 116 | return Mustache.to_html(template, data, partials); 117 | }; 118 | 119 | // set the default method name/extension 120 | if (!method_alias) { method_alias = 'mustache'; } 121 | app.helper(method_alias, mustache); 122 | }; 123 | 124 | return Sammy.Mustache; 125 | 126 | })); 127 | -------------------------------------------------------------------------------- /lib/plugins/sammy.hogan.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy', 'hogan'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Hogan = factory(window.jQuery, window.Sammy, window.Hogan); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // Sammy.Hogan provides a quick way of using hogan.js style templates in your app. 10 | // The plugin wraps the awesome hogan.js lib created and maintained by Twitter 11 | // at http://twitter.github.com/hogan.js/ 12 | // 13 | // Note: As of Sammy 0.7 the Hogan.js lib is not included in the templates source. Please download 14 | // hogan.js and include it before Sammy.Hogan. 15 | // 16 | // Hogan.js is a clever templating system that relys on double brackets {{}} for interpolation. 17 | // For full details on syntax check out the documentation at 18 | // http://twitter.github.com/hogan.js/ 19 | // 20 | // By default using Sammy.Hogan in your app adds the hogan() method to the EventContext 21 | // prototype. However, just like Sammy.Hogan you can change the default name of the method 22 | // by passing a second argument (e.g. you could use the hg() as the method alias so that all the template 23 | // files could be in the form file.hg instead of file.hogan) 24 | // 25 | // ### Example #1 26 | // 27 | // The template (mytemplate.hg): 28 | // 29 | //

      {{title}}

      30 | // 31 | // Hey, {{name}}! Welcome to Mustache! 32 | // 33 | // The app: 34 | // 35 | // var app = $.sammy(function() { 36 | // // include the plugin and alias hogan() to hg() 37 | // this.use('Hogan', 'hg'); 38 | // 39 | // this.get('#/hello/:name', function() { 40 | // // set local vars 41 | // this.title = 'Hello!' 42 | // this.name = this.params.name; 43 | // // render the template and pass it through hogan 44 | // this.partial('mytemplate.hg'); 45 | // }); 46 | // }); 47 | // 48 | // $(function() { 49 | // app.run() 50 | // }); 51 | // 52 | // If I go to #/hello/AQ in the browser, Sammy will render this to the body: 53 | // 54 | //

      Hello!

      55 | // 56 | // Hey, AQ! Welcome to Mustache! 57 | // 58 | // 59 | // ### Example #2 - Hogan partials 60 | // 61 | // The template (mytemplate.hg) 62 | // 63 | // Hey, {{name}}! {{>hello_friend}} 64 | // 65 | // 66 | // The partial (mypartial.hg) 67 | // 68 | // Say hello to your friend {{friend}}! 69 | // 70 | // The app: 71 | // 72 | // var app = $.sammy(function() { 73 | // // include the plugin and alias hogan() to hg() 74 | // this.use('Hogan', 'hg'); 75 | // 76 | // this.get('#/hello/:name/to/:friend', function(context) { 77 | // // fetch hogan-partial first 78 | // this.load('mypartial.hg') 79 | // .then(function(partial) { 80 | // // set local vars 81 | // context.partials = {hello_friend: partial}; 82 | // context.name = context.params.name; 83 | // context.friend = context.params.friend; 84 | // 85 | // // render the template and pass it through hogan 86 | // context.partial('mytemplate.hg'); 87 | // }); 88 | // }); 89 | // }); 90 | // 91 | // $(function() { 92 | // app.run() 93 | // }); 94 | // 95 | // If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the body: 96 | // 97 | // Hey, AQ! Say hello to your friend dP! 98 | // 99 | // Note: You dont have to include the hogan.js file on top of the plugin as the plugin 100 | // includes the full source. 101 | // 102 | Sammy.Hogan = function(app, method_alias) { 103 | var cached_templates = {}; 104 | 105 | // *Helper* Uses Hogan.js to parse a template and interpolate and work with the passed data 106 | // 107 | // ### Arguments 108 | // 109 | // * `template` A String template. {{}} Tags are evaluated and interpolated by Hogan.js 110 | // * `data` An Object containing the replacement values for the template. 111 | // data is extended with the EventContext allowing you to call its methods within the template. 112 | // * `partials` An Object containing one or more partials (String templates 113 | // that are called from the main template). 114 | // 115 | var hogan = function(template, data, partials) { 116 | var compiled_template = cached_templates[compiled_template]; 117 | if (!compiled_template){ 118 | compiled_template = Hogan.compile(template); 119 | } 120 | data = $.extend({}, this, data); 121 | partials = $.extend({}, data.partials, partials); 122 | return compiled_template.render(data, partials); 123 | }; 124 | 125 | // set the default method name/extension 126 | if (!method_alias) { method_alias = 'hogan'; } 127 | app.helper(method_alias, hogan); 128 | }; 129 | 130 | return Sammy.Hogan; 131 | 132 | })); 133 | -------------------------------------------------------------------------------- /lib/plugins/sammy.meld.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Meld = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // `Sammy.Meld` is a simple templating engine that uses the power of jQuery's 10 | // DOM manipulation to easily meld JSON data and HTML templates very quickly. 11 | // 12 | // The template can either be a string (i.e. loaded from a remote template) 13 | // or a DOM Element/jQuery object. This allows you to have templates be DOM 14 | // elements as the initial document load. 15 | // 16 | // ### Example 17 | // 18 | // The simplest case is a nested `
      ` whose class name is tied to a 19 | // property of a JS object. 20 | // 21 | // Template: 22 | // 23 | //
      24 | //
      25 | //
      26 | //
      27 | // 28 | //
      29 | //
      30 | // 31 | // Data: 32 | // 33 | // { 34 | // "post": { 35 | // "title": "My Post", 36 | // "entry": "My Entry", 37 | // "author": { 38 | // "name": "@aq" 39 | // } 40 | // } 41 | // } 42 | // 43 | // Result: 44 | // 45 | //
      46 | //
      My Post
      47 | //
      My Entry
      48 | //
      49 | // @aq 50 | //
      51 | //
      52 | // 53 | // Templates can be much more complex, and more deeply nested. 54 | // More examples can be found in `test/fixtures/meld/` 55 | // 56 | // If you don't think the lookup by classes is semantic for you, you can easily 57 | // switch the method of lookup by defining a selector function in the options 58 | // 59 | // For example: 60 | // 61 | // meld($('.post'), post_data, { 62 | // selector: function(k) { 63 | // return '[data-key=' + k + ']'; 64 | // } 65 | // }); 66 | // 67 | // Would look for template nodes like `
      ` 68 | // 69 | Sammy.Meld = function(app, method_alias) { 70 | var default_options = { 71 | selector: function(k) { return '.' + k; }, 72 | remove_false: true 73 | }; 74 | 75 | var meld = function(template, data, options) { 76 | var $template = $(template); 77 | 78 | options = $.extend(default_options, options || {}); 79 | 80 | if (typeof data === 'string') { 81 | $template.html(data); 82 | } else { 83 | $.each(data, function(key, value) { 84 | var selector = options.selector(key), 85 | $sub = $template.filter(selector), 86 | $container, 87 | $item, 88 | is_list = false, 89 | subindex = $template.index($sub); 90 | 91 | if ($sub.length === 0) { $sub = $template.find(selector); } 92 | if ($sub.length > 0) { 93 | if ($.isArray(value)) { 94 | $container = $('
      '); 95 | if ($sub.is('ol, ul')) { 96 | is_list = true; 97 | $item = $sub.children('li:first'); 98 | if ($item.length == 0) { $item = $('
    • '); } 99 | } else if ($sub.children().length == 1) { 100 | is_list = true; 101 | $item = $sub.children(':first').clone(); 102 | } else { 103 | $item = $sub.clone(); 104 | } 105 | for (var i = 0; i < value.length; i++) { 106 | $container.append(meld($item.clone(), value[i], options)); 107 | } 108 | if (is_list) { 109 | $sub.html($container.html()); 110 | } else if ($sub[0] == $template[0]) { 111 | $template = $($container.html()); 112 | } else if (subindex >= 0) { 113 | var args = [subindex, 1]; 114 | args = args.concat($container.children().get()); 115 | $template.splice.apply($template, args); 116 | } 117 | } else if (options.remove_false && value === false) { 118 | $template.splice(subindex, 1); 119 | } else if (typeof value === 'object') { 120 | if ($sub.is(':empty')) { 121 | $sub.attr(value, true); 122 | } else { 123 | $sub.html(meld($sub.html(), value, options)); 124 | } 125 | } else { 126 | $sub.html(value.toString()); 127 | } 128 | } else { 129 | $template.attr(key, value, true); 130 | } 131 | }); 132 | } 133 | var dom = $template; 134 | return dom; 135 | }; 136 | 137 | // set the default method name/extension 138 | if (!method_alias) method_alias = 'meld'; 139 | // create the helper at the method alias 140 | app.helper(method_alias, meld); 141 | 142 | }; 143 | 144 | return Sammy.Meld; 145 | 146 | })); 147 | -------------------------------------------------------------------------------- /lib/plugins/sammy.handlebars.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy', 'handlebars'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Handlebars = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy, Handlebars) { 8 | // version 1.0.0 has no support for AMD but upwards does, this way we support both. 9 | Handlebars = Handlebars || window.Handlebars; 10 | 11 | // Sammy.Handlebars provides a quick way of using Handlebars templates in your app. 12 | // 13 | // Note: As of Sammy 0.7 Handlebars itself is not included in the source. Please download and 14 | // include handlebars.js before Sammy.Handlebars. 15 | // 16 | // Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. Handlebars.js 17 | // and Mustache are both logicless templating languages that keep the view and the code separated like 18 | // we all know they should be. 19 | // 20 | // By default using Sammy.Handlbars in your app adds the handlebars() method to the EventContext 21 | // prototype. However, just like Sammy.Template you can change the default name of the method 22 | // by passing a second argument (e.g. you could use the hbr() as the method alias so that all the template 23 | // files could be in the form file.hbr instead of file.handlebars) 24 | // 25 | // ### Example #1 26 | // 27 | // The template (mytemplate.hb): 28 | // 29 | //

      {{title}}

      30 | // 31 | // Hey, {{name}}! Welcome to Handlebars! 32 | // 33 | // The app: 34 | // 35 | // var app = $.sammy(function() { 36 | // // include the plugin and alias handlebars() to hb() 37 | // this.use('Handlebars', 'hb'); 38 | // 39 | // this.get('#/hello/:name', function() { 40 | // // set local vars 41 | // this.title = 'Hello!' 42 | // this.name = this.params.name; 43 | // // render the template and pass it through handlebars 44 | // this.partial('mytemplate.hb'); 45 | // }); 46 | // }); 47 | // 48 | // $(function() { 49 | // app.run() 50 | // }); 51 | // 52 | // If I go to #/hello/AQ in the browser, Sammy will render this to the body: 53 | // 54 | //

      Hello!

      55 | // 56 | // Hey, AQ! Welcome to Handlebars! 57 | // 58 | // 59 | // ### Example #2 - Handlebars partials 60 | // 61 | // The template (mytemplate.hb) 62 | // 63 | // Hey, {{name}}! {{>hello_friend}} 64 | // 65 | // 66 | // The partial (mypartial.hb) 67 | // 68 | // Say hello to your friend {{friend}}! 69 | // 70 | // The app: 71 | // 72 | // var app = $.sammy(function() { 73 | // // include the plugin and alias handlebars() to hb() 74 | // this.use('Handlebars', 'hb'); 75 | // 76 | // this.get('#/hello/:name/to/:friend', function(context) { 77 | // // fetch handlebars-partial first 78 | // this.load('mypartial.hb') 79 | // .then(function(partial) { 80 | // // set local vars 81 | // context.partials = {hello_friend: partial}; 82 | // context.name = context.params.name; 83 | // context.friend = context.params.friend; 84 | // 85 | // // render the template and pass it through handlebars 86 | // context.partial('mytemplate.hb'); 87 | // }); 88 | // }); 89 | // }); 90 | // 91 | // $(function() { 92 | // app.run() 93 | // }); 94 | // 95 | // If I go to #/hello/AQ/to/dP in the browser, Sammy will render this to the body: 96 | // 97 | // Hey, AQ! Say hello to your friend dP! 98 | // 99 | // Note: You dont have to include the handlebars.js file on top of the plugin as the plugin 100 | // includes the full source. 101 | // 102 | Sammy.Handlebars = function(app, method_alias) { 103 | var handlebars_cache = {}; 104 | // *Helper* Uses handlebars.js to parse a template and interpolate and work with the passed data 105 | // 106 | // ### Arguments 107 | // 108 | // * `template` A String template. 109 | // * `data` An Object containing the replacement values for the template. 110 | // data is extended with the EventContext allowing you to call its methods within the template. 111 | // 112 | var handlebars = function(template, data, partials, name) { 113 | // use name for caching 114 | if (typeof name == 'undefined') { name = template; } 115 | var fn = handlebars_cache[name]; 116 | if (!fn) { 117 | fn = handlebars_cache[name] = Handlebars.compile(template); 118 | } 119 | 120 | data = $.extend({}, this, data); 121 | partials = $.extend({}, data.partials, partials); 122 | 123 | return fn(data, {"partials":partials}); 124 | }; 125 | 126 | // set the default method name/extension 127 | if (!method_alias) { method_alias = 'handlebars'; } 128 | app.helper(method_alias, handlebars); 129 | }; 130 | 131 | return Sammy.Handlebars; 132 | 133 | })); 134 | -------------------------------------------------------------------------------- /test/meld_spec.js: -------------------------------------------------------------------------------- 1 | describe('Meld', function() { 2 | var context; 3 | 4 | beforeEach(function() { 5 | var app = new Sammy.Application(function() { 6 | this.raise_errors = false; 7 | this.element_selector = '#main'; 8 | this.use(Sammy.Meld); 9 | this.get('#/', function() {}); 10 | }); 11 | 12 | context = new app.context_prototype(app, 'get', '#/test/:test', {test: 'hooray'}); 13 | }); 14 | 15 | it('does simple interpolation', function() { 16 | var template = "
      "; 17 | data = {'title': 'TEST'}, 18 | expected = "
      TEST
      "; 19 | 20 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 21 | }); 22 | 23 | it('interpolates one level deep', function() { 24 | var template = "
      "; 25 | data = {'post': {'title': 'TEST'}}, 26 | expected = "
      TEST
      "; 27 | 28 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 29 | }); 30 | 31 | it('interpolates multiple keys', function() { 32 | var template = "
      "; 33 | data = {'post': 'my post', 'title': 'TEST'}, 34 | expected = "
      my post
      TEST
      "; 35 | 36 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 37 | }); 38 | 39 | it('interpolates multiple nested keys', function() { 40 | var template = "
      "; 41 | data = {'post': {'title': 'TEST', 'author': 'AQ'}}, 42 | expected = "
      TEST
      AQ
      "; 43 | 44 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 45 | }); 46 | 47 | it('multiplies an array of tags', function() { 48 | var template = "
      "; 49 | data = {'post': {'tags': ['one', 'two']}}, 50 | expected = "
      onetwo
      "; 51 | 52 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 53 | }); 54 | 55 | it('multiplies an array of objects', function() { 56 | var template = "

      "; 57 | data = {'post': {'authors': [{'name': 'AQ', 'twitter': 'aq'}, {'name':'Mike', 'twitter':'mrb_bk'}]}}, 58 | expected = "

      AQ

      Mike

      "; 59 | 60 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 61 | }); 62 | 63 | it('multiplies an array on a list item', function() { 64 | var template = "
        "; 65 | data = {'post': {'tags': ['one', 'two']}}, 66 | expected = "
        • one
        • two
        "; 67 | 68 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 69 | }); 70 | 71 | it('multiplies an array on a list item using the example list item', function() { 72 | var template = "
        "; 73 | data = {'post': {'tags': ['one', 'two']}}, 74 | expected = "
        1. one
        2. two
        "; 75 | 76 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 77 | }); 78 | 79 | it('replaces attributes of elements as a fallback to class lookup', function() { 80 | var template = "
        ", 81 | data = {'post': {'name': {'href': 'http://www.google.com', 'text': 'Link'}}}, 82 | expected = "
        Link
        "; 83 | 84 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 85 | }); 86 | 87 | it('removes nodes if value is false', function() { 88 | var template = "

        Active
        ", 89 | data = {'post': {'name': "My Name", 'active': false}}, 90 | expected = "

        My Name

        "; 91 | 92 | expect(context.meld(template, data)).to.have.sameHTMLAs(expected); 93 | }); 94 | 95 | it('allows the setting of a selector function', function() { 96 | var template = "

        ", 97 | data = {'post': {'name': "My Name"}}, 98 | expected = "

        My Name

        "; 99 | 100 | expect(context.meld(template, data, {selector: function(k) { 101 | return "[rel='"+ k + "']"; 102 | }})).to.have.sameHTMLAs(expected); 103 | }); 104 | 105 | it('renders templates correctly', function(done) { 106 | var templates = 3; 107 | 108 | var getAndAssertTemplate = function(i) { 109 | var template, json, result; 110 | $.get('fixtures/meld/' + i + '.meld', function(t) { 111 | template = t; 112 | $.getJSON('fixtures/meld/' + i + '.json', function(j) { 113 | json = j; 114 | $.get('fixtures/meld/' + i + '.html', function(r) { 115 | expect(context.meld(template, json)).to.have.sameHTMLAs(r); 116 | if (i == templates) { 117 | done(); 118 | } else { 119 | getAndAssertTemplate(i + 1); 120 | } 121 | }); 122 | }); 123 | }); 124 | } 125 | 126 | getAndAssertTemplate(1); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /lib/plugins/sammy.template.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).Template = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // Simple JavaScript Templating 10 | // John Resig - http://ejohn.org/ - MIT Licensed 11 | // adapted from: http://ejohn.org/blog/javascript-micro-templating/ 12 | // originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009 13 | // modified for Sammy by Aaron Quint for caching templates by name 14 | var srender_cache = {}; 15 | var srender = function(name, template, data, options) { 16 | var fn, escaped_string; 17 | // target is an optional element; if provided, the result will be inserted into it 18 | // otherwise the result will simply be returned to the caller 19 | if (srender_cache[name]) { 20 | fn = srender_cache[name]; 21 | } else { 22 | if (typeof template == 'undefined') { 23 | // was a cache check, return false 24 | return false; 25 | } 26 | // If options escape_html is false, dont escape the contents by default 27 | if (options && options.escape_html === false) { 28 | escaped_string = "\",$1,\""; 29 | } else { 30 | escaped_string = "\",h($1),\""; 31 | } 32 | // Generate a reusable function that will serve as a template 33 | // generator (and which will be cached). 34 | fn = srender_cache[name] = new Function("obj", 35 | "var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};" + 36 | 37 | // Introduce the data as local variables using with(){} 38 | "with(obj){___$$$___.push(\"" + 39 | 40 | // Convert the template into pure JavaScript 41 | String(template) 42 | .replace(/[\r\t\n]/g, " ") 43 | .replace(/\"/g, '\\"') 44 | .split("<%").join("\t") 45 | .replace(/((^|%>)[^\t]*)/g, "$1\r") 46 | .replace(/\t=(.*?)%>/g, escaped_string) 47 | .replace(/\t!(.*?)%>/g, "\",$1,\"") 48 | .split("\t").join("\");") 49 | .split("%>").join("___$$$___.push(\"") 50 | .split("\r").join("") 51 | + "\");}return ___$$$___.join('');"); 52 | } 53 | 54 | if (typeof data != 'undefined') { 55 | return fn(data); 56 | } else { 57 | return fn; 58 | } 59 | }; 60 | 61 | // `Sammy.Template` is a simple plugin that provides a way to create 62 | // and render client side templates. The rendering code is based on John Resig's 63 | // quick templates and Greg Borenstien's srender plugin. 64 | // This is also a great template/boilerplate for Sammy plugins. 65 | // 66 | // Templates use `<% %>` tags to denote embedded javascript. 67 | // 68 | // ### Examples 69 | // 70 | // Here is an example template (user.template): 71 | // 72 | // // user.template 73 | //
        74 | //
        <%= user.name %>
        75 | // <% if (user.photo_url) { %> 76 | //
        77 | // <% } %> 78 | //
        79 | // 80 | // Given that is a publicly accesible file, you would render it like: 81 | // 82 | // // app.js 83 | // $.sammy(function() { 84 | // // include the plugin 85 | // this.use('Template'); 86 | // 87 | // this.get('#/', function() { 88 | // // the template is rendered in the current context. 89 | // this.user = {name: 'Aaron Quint'}; 90 | // // partial calls template() because of the file extension 91 | // this.partial('user.template'); 92 | // }) 93 | // }); 94 | // 95 | // You can also pass a second argument to use() that will alias the template 96 | // method and therefore allow you to use a different extension for template files 97 | // in partial() 98 | // 99 | // // alias to 'tpl' 100 | // this.use(Sammy.Template, 'tpl'); 101 | // 102 | // // now .tpl files will be run through srender 103 | // this.get('#/', function() { 104 | // this.partial('myfile.tpl'); 105 | // }); 106 | // 107 | // By default, the data passed into the tempalate is passed automatically passed through 108 | // Sammy's `escapeHTML` method in order to prevent possible XSS attacks. This is 109 | // a problem though if you're using something like `Sammy.Form` which renders HTML 110 | // within the templates. You can get around this in two ways. One, you can use the 111 | // `<%! %>` instead of `<%= %>`. Two, you can pass the `escape_html = false` option 112 | // when interpolating, i.e: 113 | // 114 | // this.get('#/', function() { 115 | // this.template('myform.tpl', {form: "
        "}, {escape_html: false}); 116 | // }); 117 | // 118 | Sammy.Template = function(app, method_alias) { 119 | 120 | // *Helper:* Uses simple templating to parse ERB like templates. 121 | // 122 | // ### Arguments 123 | // 124 | // * `template` A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data. 125 | // * `data` An Object containing the replacement values for the template. 126 | // data is extended with the EventContext allowing you to call its methods within the template. 127 | // * `name` An optional String name to cache the template. 128 | // 129 | var template = function(template, data, name, options) { 130 | // use name for caching 131 | if (typeof name == 'undefined') { name = template; } 132 | if (typeof options == 'undefined' && typeof name == 'object') { 133 | options = name; name = template; 134 | } 135 | return srender(name, template, $.extend({}, this, data), options); 136 | }; 137 | 138 | // set the default method name/extension 139 | if (!method_alias) { method_alias = 'template'; } 140 | // create the helper at the method alias 141 | app.helper(method_alias, template); 142 | 143 | }; 144 | 145 | return Sammy.Template; 146 | 147 | })); 148 | -------------------------------------------------------------------------------- /lib/plugins/sammy.oauth2.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | if (typeof define === 'function' && define.amd) { 3 | define(['jquery', 'sammy'], factory); 4 | } else { 5 | (window.Sammy = window.Sammy || {}).OAuth2 = factory(window.jQuery, window.Sammy); 6 | } 7 | }(function ($, Sammy) { 8 | 9 | // Sammy.OAuth2 is a plugin for using OAuth 2.0 to authenticate users and 10 | // access your application's API. Requires Sammy.Session. 11 | // 12 | // Triggers the following events: 13 | // 14 | // * `oauth.connected` - Access token set and ready to use. Triggered when new 15 | // access token acquired, of when application starts and already has access 16 | // token. 17 | // * `oauth.disconnected` - Access token reset. Triggered by 18 | // loseAccessToken(). 19 | // * `oauth.denied` - Authorization attempt rejected. 20 | // 21 | // ### Example 22 | // 23 | // this.use('Storage'); 24 | // this.use('OAuth2'); 25 | // this.oauthorize = "/oauth/authorize"; 26 | // 27 | // // The quick & easy way 28 | // this.requireOAuth(); 29 | // // Specific path 30 | // this.requireOAuth("/private"); 31 | // // Filter you can apply to specific URLs 32 | // this.before(function(context) { return context.requireOAuth(); }) 33 | // // Apply to specific request 34 | // this.get("/private", function(context) { 35 | // this.requireOAuth(function() { 36 | // // Do something 37 | // }); 38 | // }); 39 | // 40 | // // Sign in/sign out. 41 | // this.bind("oauth.connected", function() { $("#signin").hide() }); 42 | // this.bind("oauth.disconnected", function() { $("#signin").show() }); 43 | // 44 | // // Handle access denied and other errors 45 | // this.bind("oauth.denied", function(evt, error) { 46 | // this.partial("admin/views/no_access.tmpl", { error: error.message }); 47 | // }); 48 | // 49 | // // Sign out. 50 | // this.get("#/signout", function(context) { 51 | // context.loseAccessToken(); 52 | // context.redirect("#/"); 53 | // }); 54 | // 55 | Sammy.OAuth2 = function(app) { 56 | app.use('JSON'); 57 | this.authorize = "/oauth/authorize"; 58 | 59 | // Use this on request that require OAuth token. You can use this in a 60 | // filter: it will redirect and return false if the access token is missing. 61 | // You can use it in a route, it will redirect to get the access token, or 62 | // call the callback function if it has an access token. 63 | this.helper("requireOAuth", function(cb) { 64 | if (this.app.getAccessToken()) { 65 | if (cb) { 66 | cb.apply(this); 67 | } 68 | } else { 69 | this.redirect(this.app.authorize + "?state=" + escape(this.path)); 70 | return false; 71 | } 72 | }); 73 | 74 | // Use this to sign out. 75 | this.helper("loseAccessToken", function() { 76 | this.app.loseAccessToken(); 77 | }); 78 | 79 | // Use this in your application to require an OAuth access token on all, or 80 | // the specified paths. It sets up a before filter on the specified paths. 81 | this.requireOAuth = function(options) { 82 | this.before(options || {}, function(context) { 83 | return context.requireOAuth(); 84 | }); 85 | } 86 | 87 | // Returns the access token. Uses Sammy.Session to store the token. 88 | this.getAccessToken = function() { 89 | return this.session("oauth.token"); 90 | } 91 | // Stores the access token in the session. 92 | this.setAccessToken = function(token) { 93 | this.session("oauth.token", token); 94 | this.trigger("oauth.connected"); 95 | } 96 | // Lose access token: use this to sign out. 97 | this.loseAccessToken = function() { 98 | this.session("oauth.token", null); 99 | this.trigger("oauth.disconnected"); 100 | } 101 | 102 | // Add OAuth 2.0 access token to all XHR requests. 103 | $(document).ajaxSend(function(evt, xhr) { 104 | var token = app.getAccessToken(); 105 | if (token) { 106 | xhr.setRequestHeader("Authorization", "OAuth " + token); 107 | } 108 | }); 109 | 110 | // Converts query string parameters in fragment identifier to object. 111 | function parseParams(path) { 112 | var hash = path.match(/#(.*)$/)[1]; 113 | var pairs = hash.split("&"), params = {}; 114 | var i, len = pairs.length; 115 | 116 | for (i = 0; i < len; i += 1) { 117 | var splat = pairs[i].split("="); 118 | params[splat[0]] = splat[1].replace(/\+/g, " "); 119 | } 120 | return params; 121 | } 122 | 123 | var start_url; 124 | // Capture the application's start URL, we'll need that later on for 125 | // redirection. 126 | this.bind("run", function(evt, params) { 127 | start_url = params.start_url || "#"; 128 | if (this.app.getAccessToken()) { 129 | this.trigger("oauth.connected"); 130 | } 131 | }); 132 | 133 | // Intercept OAuth authorization response with access token, stores it and 134 | // redirects to original URL, or application root. 135 | this.before(/#(access_token=|[^\\].*\&access_token=)/, function(context) { 136 | var params = parseParams(context.path); 137 | this.app.setAccessToken(params.access_token); 138 | // When the filter redirected the original request, it passed the original 139 | // request's URL in the state parameter, which we get back after 140 | // authorization. 141 | context.redirect(params.state.length === 0 ? this.app.start_url : unescape(params.state)); 142 | return false; 143 | }).get(/#(access_token=|[^\\].*\&access_token=)/, function(context) { }); 144 | 145 | // Intercept OAuth authorization response with error (typically access 146 | // denied). 147 | this.before(/#(error=|[^\\].*\&error=)/, function(context) { 148 | var params = parseParams(context.path); 149 | var message = params.error_description || "Access denined"; 150 | context.trigger("oauth.denied", { code: params.error, message: message }); 151 | return false; 152 | }).get(/#(error=|[^\\].*\&error=)/, function(context) { }); 153 | 154 | } 155 | 156 | return Sammy.OAuth2; 157 | 158 | })); 159 | -------------------------------------------------------------------------------- /test/flash_spec.js: -------------------------------------------------------------------------------- 1 | describe('Flash', function() { 2 | var app, nowApp, context, output, 3 | createAppsWithFlash, unloadAppsWithFlash; 4 | 5 | createAppsWithFlash = function() { 6 | app = Sammy(function() { 7 | this.use(Sammy.Flash); 8 | this.element_selector = '#main'; 9 | 10 | this.get('#/', function() { 11 | this.flash('welcome info', 'Welcome!'); 12 | }); 13 | this.post('#/test', function() { 14 | this.flash('info', "Successfully POSTed nested params"); 15 | this.redirect('#/'); 16 | }); 17 | }); 18 | 19 | nowApp = Sammy(function() { 20 | this.use(Sammy.Flash); 21 | this.element_selector = '#main2'; 22 | 23 | this.get('#/', function() { 24 | this.flashNow('info', '您好'); 25 | }); 26 | this.post('#/test', function() { 27 | this.flashNow('warn', 'Uh-oh?'); 28 | this.redirect('#/doNothing'); 29 | }); 30 | this.get('#/doNothing', function() {}); 31 | }); 32 | }; 33 | 34 | unloadApps = function() { 35 | app.unload(); 36 | nowApp.unload(); 37 | window.location.href = '#/'; 38 | }; 39 | 40 | describe('app.flash', function() { 41 | beforeEach(createAppsWithFlash); 42 | afterEach(unloadApps); 43 | 44 | it('exists', function() { 45 | expect(app.flash).to.be.an(Object); 46 | }); 47 | 48 | it('retains entries after a non-redirect', function() { 49 | app.run('#/'); 50 | expect(app.flash['welcome info']).to.eql('Welcome!'); 51 | }); 52 | 53 | it('retains entries after a redirect', function() { 54 | $('#main').html('
        ' + 55 | '' + 56 | '
        '); 57 | 58 | app.run('#/'); 59 | $('#test_form').submit(); 60 | expect(app.flash['info']).to.eql("Successfully POSTed nested params"); 61 | }); 62 | 63 | it('loses all entries after being rendered', function() { 64 | app.run('#/'); 65 | app.flash.toHTML(); 66 | expect(app.flash['welcome info']).to.be(undefined); 67 | }); 68 | 69 | it('retains entries after a redirect in another app', function() { 70 | $('#main2').html('
        ' + 71 | '' + 72 | '
        '); 73 | 74 | app.run('#/'); 75 | nowApp.run('#/'); 76 | $('#test_form').submit(); 77 | expect(app.flash['welcome info']).to.eql('Welcome!'); 78 | }); 79 | }); 80 | 81 | describe('app.flash.now', function() { 82 | beforeEach(createAppsWithFlash); 83 | afterEach(unloadApps); 84 | 85 | it('exists', function() { 86 | expect(nowApp.flash).to.be.an(Object); 87 | }); 88 | 89 | it('retains entries after a non-redirect', function() { 90 | nowApp.run('#/'); 91 | window.location.hash = '#/'; 92 | expect(nowApp.flash.now.info).to.eql('您好'); 93 | }); 94 | 95 | it('loses all entries after a redirect', function() { 96 | $('#main').html(''); 97 | $('#main2').html('
        ' + 98 | '' + 99 | '
        '); 100 | 101 | nowApp.run('#/'); 102 | $('#test_form').submit(); 103 | expect(nowApp.flash.now.warn).to.be(undefined); 104 | }); 105 | 106 | it('loses all entries after being rendered', function() { 107 | nowApp.run('#/'); 108 | nowApp.flash.toHTML(); 109 | expect(nowApp.flash.now.info).to.be(undefined); 110 | }); 111 | 112 | it('retains entries after a redirect in another app', function() { 113 | $('#main2').html(''); 114 | $('#main').html('
        ' + 115 | '' + 116 | '
        '); 117 | 118 | app.run('#/'); 119 | nowApp.run('#/'); 120 | $('#test_form').submit(); 121 | expect(nowApp.flash.now.info).to.eql('您好'); 122 | }); 123 | }); 124 | 125 | describe('#flash()', function() { 126 | beforeEach(function() { 127 | createAppsWithFlash(); 128 | context = new app.context_prototype(app, 'get', '#/', {}); 129 | }); 130 | 131 | it('returns the Flash object when passed no arguments', function() { 132 | expect(context.flash()).to.eql(app.flash); 133 | }); 134 | 135 | it('returns the value of the given key when passed one argument', function() { 136 | app.flash.foo = 'bar'; 137 | expect(context.flash('foo')).to.eql('bar'); 138 | }); 139 | 140 | it('sets a flash value when passed two arguments', function() { 141 | context.flash('foo2', 'bar2'); 142 | expect(context.flash('foo2')).to.eql('bar2'); 143 | }); 144 | }); 145 | 146 | describe('#flashNow()', function() { 147 | beforeEach(function() { 148 | createAppsWithFlash(); 149 | context = new app.context_prototype(app, 'get', '#/', {}); 150 | }); 151 | 152 | it('returns the Flash-Now object when passed no arguments', function() { 153 | expect(context.flashNow()).to.eql(app.flash.now); 154 | }); 155 | 156 | it('returns the value of the given key when passed one argument', function() { 157 | app.flash.now.foo = 'bar'; 158 | expect(context.flashNow('foo')).to.eql('bar'); 159 | }); 160 | 161 | it('sets a flash value when passed two arguments', function() { 162 | context.flashNow('foo2', 'bar2'); 163 | expect(context.flashNow('foo2')).to.eql('bar2'); 164 | }); 165 | }); 166 | 167 | describe('#app.flash.toHTML()', function() { 168 | beforeEach(function() { 169 | createAppsWithFlash(); 170 | app.flash.clear(); 171 | nowApp.flash.clear(); 172 | 173 | app.flash.error = 'Boom!'; 174 | app.flash.warn = 'Beep!'; 175 | app.flash.now.info = 'Info!'; 176 | nowApp.flash.debug = 'Debug!'; 177 | 178 | output = $('
        ') 179 | .append(app.flash.toHTML()) 180 | .appendTo($('body')); 181 | }); 182 | 183 | afterEach(function() { 184 | output.remove(); 185 | }); 186 | 187 | it('renders a ul.flash', function() { 188 | expect($('ul.flash', this.output)).to.have.length(1); 189 | }); 190 | 191 | it('includes entries from both flash and flash.now, with keys as classes', function() { 192 | expect($('ul.flash li.error', this.output).text()).to.eql('Boom!'); 193 | expect($('ul.flash li.warn', this.output).text()).to.eql('Beep!'); 194 | expect($('ul.flash li.info', this.output).text()).to.eql('Info!'); 195 | }); 196 | 197 | it("does not include entries from another app's flash", function() { 198 | expect($('.debug', output)).to.have.length(0); 199 | }); 200 | }); 201 | }); 202 | --------------------------------------------------------------------------------