diff --git a/Definitions/sammyjs-0.7.d.ts b/Definitions/sammyjs-0.7.d.ts
new file mode 100644
index 000000000..b26d23ca2
--- /dev/null
+++ b/Definitions/sammyjs-0.7.d.ts
@@ -0,0 +1,228 @@
+// Type definitions for Sammy.js
+// Project: http://sammyjs.org/
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+
+///
+
+interface Sammy {
+ (): Application;
+ (selector: string): Application;
+ (handler: Function): Application;
+ (selector: string, handler: Function): Application;
+
+ Cache(app, options);
+ DataCacheProxy(initial, $element);
+ DataLocationProxy(app, data_name, href_attribute);
+ DefaultLocationProxy(app, run_interval_every);
+ EJS(app, method_alias);
+
+ Exceptional(app, errorReporter);
+ Flash(app);
+ Form(app); // formFor ( name, object, content_callback )
+
+ Haml(app, method_alias);
+ Handlebars(app, method_alias);
+ Hogan(app, method_alias);
+ Hoptoad(app, errorReporter);
+ JSON(app);
+ Meld(app, method_alias);
+ MemoryCacheProxy(initial);
+ Mustache(app, method_alias);
+ NestedParams(app);
+ OAuth2(app);
+ PathLocationProxy(app);
+ Pure(app, method_alias);
+ PushLocationProxy(app);
+ Session(app, options);
+ Storage(app);
+
+ Title();
+ Tmpl(app, method_alias);
+ addLogger(logger);
+ log();
+
+ Object: Object;
+}
+
+interface Object {
+
+ constructor (obj: any);
+
+ escapeHTML(s: string): string;
+ h(s: string): string;
+
+ has(key: string): bool;
+ join(...args: any[]): string;
+ keys(attributes_only?: bool): string[];
+ log(...args: any[]): void;
+ toHTML(): string;
+ toHash(): any;
+ toString(include_functions?: bool): string;
+}
+
+interface Application extends Object {
+
+ ROUTE_VERBS: string[];
+ APP_EVENTS: string[];
+
+ (appFn: Function);
+
+ $element(selector: string): JQuery;
+ after: (callback: Function): Application;
+ any(verb: string, path: string, callback: Function): void;
+ route(verb: string, path: string, callback: Function): void;
+ around(callback);
+ before(options: any, callback: Function): Application;
+ bind(name: string, data: any, callback: Function): Application;
+ bindToAllEvents(callback);
+ clearTemplateCache();
+ contextMatchesOptions(context, match_options, positive);
+ del(path: string, callback: Function): Application;
+ del(path: RegExp, callback: Function): Application;
+ destroy();
+ error(message, original_error);
+ eventNamespace(): string;
+ get(path: string, callback: Function): Application;
+ get(path: RegExp, callback: Function): Application;
+ getLocation(): string;
+ helper(name, method);
+ helpers(extensions);
+ isRunning();
+ log(...params: any[]): void;
+ lookupRoute(verb, path);
+ mapRoutes(route_array: any[]): Application;
+ notFound(verb, path);
+ post(path: string, callback: Function): Application;
+ post(path: RegExp, callback: Function): Application;
+ put(path: string, callback: Function): Application;
+ put(path: RegExp, callback: Function): Application;
+ refresh(): Application;
+ routablePath(path);
+ route(verb: string, path: string, callback: Function): Application;
+ route(verb: string, path: RegExp, callback: Function): Application;
+ run(start_url);
+ runRoute(verb, path, params, target);
+ setLocation(new_location: string): string;
+ setLocationProxy(new_proxy: DataLocationProxy): void;
+ swap(content, callback);
+ templateCache(key, value);
+ toString(): string;
+ trigger(name: string, data: any): Application;
+ unload();
+ use(...params: any[]): void;
+}
+
+interface DataLocationProxy {
+ (app, run_interval_every): DataLocationProxy;
+ fullPath(location_obj): string;
+ bind(): void;
+ unbind(): void;
+ setLocation(new_location: string): string;
+ _startPolling(every: number): void;
+}
+
+interface EventContext {
+ constructor (app, verb, path, params, target);
+ $element();
+ engineFor(engine);
+ eventNamespace();
+ interpolate(content, data, engine, partials);
+ json(string);
+ load(location, options, callback);
+ loadPartials(partials);
+ notFound();
+ partial(location, data, callback, partials);
+ redirect();
+ render(location, data, callback, partials);
+ renderEach(location, name, data, callback);
+ send();
+ swap(contents, callback);
+ toString();
+ trigger(name, data);
+}
+
+interface FormBuilder {
+ constructor (name, object);
+ checkbox(keypath, value, attributes);
+ close();
+ hidden(keypath, attributes);
+ label(keypath, content, attributes);
+ open(attributes);
+ password(keypath, attributes);
+ radio(keypath, value, attributes);
+ select(keypath, options, attributes);
+ submit(attributes);
+ text(keypath, attributes);
+ textarea(keypath, attributes);
+}
+
+interface GoogleAnalytics {
+ constructor (app, tracker);
+ noTrack();
+ track(path);
+}
+
+interface RenderContext {
+ constructor (event_context);
+ appendTo(selector);
+ collect(array, callback, now);
+ interpolate(data, engine, retain);
+ load(location, options, callback);
+ loadPartials(partials);
+ next(content);
+ partial(location, data, callback, partials);
+ prependTo(selector);
+ render(location, data, callback, partials);
+ renderEach(location, name, data, callback);
+ replace(selector);
+ send();
+ swap(callback);
+ then(callback);
+ trigger(name, data);
+ wait();
+}
+
+
+interface StoreOptions {
+ name?: string;
+ element?: string;
+ type?: string;
+ memory?: any;
+ data?: any;
+ cookie?: any;
+ local?: any;
+ session?: any;
+}
+
+interface Store {
+ constructor (options);
+
+ clear(key);
+ clearAll();
+ each(callback);
+ exists(key);
+ fetch(key, callback);
+ filter(callback);
+ first(callback);
+ get(key);
+ isAvailable();
+ keys();
+ load(key, path, callback);
+ set(key, value);
+
+ Cookie(name, element, options);
+ Data(name, element);
+ LocalStorage(name, element);
+ Memory(name, element);
+ SessionStorage(name, element);
+ isAvailable(type);
+ Template(app, method_alias);
+}
+
+
+interface JQueryStatic {
+ sammy: Sammy;
+}
+
+declare var Sammy: Sammy;
\ No newline at end of file
diff --git a/README.md b/README.md
index a92d08c42..4f68d27a2 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ Complete
* [node_redis](https://github.com/mranney/node_redis)
* [QUnit](http://qunitjs.com/) (by [Diullei Gomes](https://github.com/Diullei))
* [Raphael](http://raphaeljs.com/) (by [CheCoxshall](https://github.com/CheCoxshall))
+* [Sammy.js](http://sammyjs.org/)
* [Spin](http://fgnass.github.com/spin.js/)
* [Underscore.js](http://underscorejs.org/)
@@ -47,4 +48,6 @@ Next
* Meteor
* PhoneGap
* Isotope
-* Zepto
\ No newline at end of file
+* Zepto
+* Socket.IO
+* MongoDB
\ No newline at end of file
diff --git a/Tests/sammyjs-tests.ts b/Tests/sammyjs-tests.ts
new file mode 100644
index 000000000..fa0fa3a67
--- /dev/null
+++ b/Tests/sammyjs-tests.ts
@@ -0,0 +1,500 @@
+///
+
+var _this: RenderContext;
+
+function test_general() {
+ var app = Sammy('#main', function () {
+ var _this: Application;
+ _this.use('Mustache');
+ _this.get('#/', function () {
+ _this.load('posts.json')
+ .renderEach('post.mustache')
+ .swap();
+ });
+ });
+
+ app.run('#/');
+
+ _this.get('#/', function(context) {
+ _this.load('data/items.json')
+ .then(function(items) {
+ $.each(items, function(i, item) {
+ context.log(item.title, '-', item.artist);
+ });
+ });
+ });
+}
+
+function test_app() {
+ var s = new Sammy.Object({ first_name: 'Sammy', last_name: 'Davis Jr.' });
+ s.toHTML();
+
+ var app = $.sammy(function () {
+ var current_user = false;
+ function checkLoggedIn(callback) {
+ if (!current_user) {
+ $.getJSON('/session', function (json) {
+ if (json.login) {
+ current_user = json;
+ callback();
+ } else {
+ current_user = false;
+ _this.redirect('#/login');
+ }
+ });
+ } else {
+ callback();
+ }
+ };
+ _this.around(checkLoggedIn);
+ });
+
+ var app = $.sammy(function () {
+ _this.before('#/route', function () { });
+ _this.before({ except: { path: '#/route' } }, function () {
+ _this.log('not before #/route');
+ });
+ _this.get('#/', function () { });
+ _this.get('#/route', function () { });
+ });
+
+ var app = $.sammy(),
+ context = { verb: 'get', path: '#/mypath' };
+
+ app.contextMatchesOptions(context, '#/mypath');
+ app.contextMatchesOptions(context, '#/otherpath');
+ app.contextMatchesOptions(context, { only: { path: '#/mypath' } });
+ app.contextMatchesOptions(context, { only: { path: '#/otherpath' } });
+ app.contextMatchesOptions(context, /path/);
+ app.contextMatchesOptions(context, /^path/);
+ app.contextMatchesOptions(context, { only: { verb: 'get' } });
+ app.contextMatchesOptions(context, { only: { verb: 'post' } });
+ app.contextMatchesOptions(context, { except: { verb: 'post' } });
+ app.contextMatchesOptions(context, { except: { verb: 'get' } });
+ app.contextMatchesOptions(context, { except: { path: '#/otherpath' } });
+ app.contextMatchesOptions(context, { except: { path: '#/mypath' } });
+ app.contextMatchesOptions(context, { path: ['#/mypath', '#/otherpath'] });
+ app.contextMatchesOptions(context, { path: ['#/otherpath', '#/thirdpath'] });
+ app.contextMatchesOptions(context, { only: { path: ['#/mypath', '#/otherpath'] } });
+ app.contextMatchesOptions(context, { only: { path: ['#/otherpath', '#/thirdpath'] } });
+ app.contextMatchesOptions(context, { except: { path: ['#/mypath', '#/otherpath'] } });
+ app.contextMatchesOptions(context, { except: { path: ['#/otherpath', '#/thirdpath'] } });
+
+ var app = $.sammy(function (app) {
+ $.each([1, 2, 3], function (i, num) {
+ app.helper('helper' + num, function () {
+ _this.log("I'm helper number " + num);
+ });
+ });
+ _this.get('#/', function () {
+ _this.helper2();
+ });
+ });
+
+ var app = $.sammy(function () {
+ helpers({
+ upcase: function (text) {
+ return text.toString().toUpperCase();
+ }
+ });
+ get('#/', function () {
+ with (_this) {
+ $('#main').html(upcase($('#main').text());
+ }
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.mapRoutes([
+ ['get', '#/', function () { _this.log('index'); }],
+ ['post', '#/create', 'addUser'],
+ [/dowhatever/, function () { _this.log(_this.verb, _this.path) }];
+ ]);
+ });
+
+ var app = $.sammy(function () { });
+ $(function ()
+ app.run();
+ });
+
+ var app = $.sammy(function () {
+ _this.setLocationProxy(new Sammy.DataLocationProxy(_this));
+ });
+
+ var app = $.sammy(function () {
+ _this.swap = function (content, callback) {
+ var context = _this;
+ context.$element().fadeOut('slow', function () {
+ context.$element().html(content);
+ context.$element().fadeIn('slow', function () {
+ if (callback) {
+ callback.apply();
+ }
+ });
+ });
+ };
+ });
+
+ var MyPlugin = function (app, prepend) {
+ _this.helpers({
+ myhelper: function (text) {
+ alert(prepend + " " + text);
+ }
+ });
+ };
+ var app = $.sammy(function () {
+ _this.use(MyPlugin, '_this is my plugin');
+ _this.get('#/', function () {
+ _this.myhelper('and dont you forget it!');
+ });
+ });
+
+ $.sammy(function () {
+ _this.use('Mustache');
+ _this.use('Storage');
+ });
+}
+
+function test_misc() {
+ var app = $.sammy(function () {
+ _this.setLocationProxy(new Sammy.DataLocationProxy(_this, 'location', 'rel'));
+ _this.get('about', function () {
+ _this.partial('about.html');
+ });
+ });
+
+ $.sammy(function () {
+ _this.get('#/:name', function () {
+ if (_this.params['name'] == 'sammy') {
+ _this.partial('name.html.erb', { name: 'Sammy' });
+ } else {
+ _this.redirect('#/somewhere-else')
+ }
+ });
+ });
+
+ redirect('#/other/route');
+ redirect('#', 'other', 'route');
+ render('mytemplate.mustache', { name: 'quirkey' })
+ .appendTo('ul');
+ renderEach('mytemplate.mustache', [{ name: 'quirkey' }, { name: 'endor' }]);
+
+ var item = {
+ name: 'My Item',
+ price: '$25.50',
+ meta: {
+ id: '123'
+ }
+ };
+ var form = new Sammy.FormBuilder('item', item);
+ form.text('name');
+
+ var options = [
+ ['Small', 's'],
+ ['Medium', 'm'],
+ ['Large', 'l']
+ ];
+ form.select('size', options);
+
+ $.sammy(function () {
+ _this.use('GoogleAnalytics')
+ _this.get('#/dont/track/me', function () {
+ _this.noTrack();
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use(Sammy.Haml);
+ _this.get('#/hello/:name', function () {
+ _this.title = 'Hello!'
+ _this.name = _this.params.name;
+ _this.partial('mytemplate.haml');
+ });
+ });
+ app.run()
+
+ var app = $.sammy(function () {
+ _this.use('Handlebars', 'hb');
+ _this.get('#/hello/:name', function () {
+ _this.title = 'Hello!'
+ _this.name = _this.params.name;
+ _this.partial('mytemplate.hb');
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use('Handlebars', 'hb');
+ _this.get('#/hello/:name/to/:friend', function (context) {
+ _this.load('mypartial.hb')
+ .then(function (partial) {
+ context.partials = { hello_friend: partial };
+ context.name = context.params.name;
+ context.friend = context.params.friend;
+ context.partial('mytemplate.hb');
+ });
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use('Hogan', 'hg');
+ _this.get('#/hello/:name', function () {
+ _this.title = 'Hello!'
+ _this.name = _this.params.name;
+ _this.partial('mytemplate.hg');
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use('Hogan', 'hg');
+ _this.get('#/hello/:name/to/:friend', function (context) {
+ _this.load('mypartial.hg')
+ .then(function (partial) {
+ context.partials = { hello_friend: partial };
+ context.name = context.params.name;
+ context.friend = context.params.friend;
+ context.partial('mytemplate.hg');
+ });
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use(Sammy.JSON);
+ _this.get('#/', function () {
+ _this.json({ user_id: 123 });
+ _this.json("{\"user_id\":\"123\"}");
+ _this.json("{\"user_id\":\"123\"}").user_id;
+ });
+ })
+
+ var app = $.sammy(function () {
+ _this.use('Mustache', 'ms');
+ _this.get('#/hello/:name', function () {
+ _this.title = 'Hello!'
+ _this.name = _this.params.name;
+ _this.partial('mytemplate.ms');
+ });
+ });
+
+ var app = $.sammy(function () {
+ _this.use('Mustache', 'ms');
+ _this.get('#/hello/:name/to/:friend', function (context) {
+ _this.load('mypartial.ms')
+ .then(function (partial) {
+ context.partials = { hello_friend: partial };
+ context.name = context.params.name;
+ context.friend = context.params.friend;
+ context.partial('mytemplate.ms');
+ });
+ });
+ });
+
+ var app = $.sammy(function (app) {
+ _this.use(Sammy.NestedParams);
+ _this.post('#/parse_me', function (context) {
+ $.log(_this.params);
+ });
+ });
+
+ _this.use('Storage');
+ _this.use('OAuth2');
+ _this.oauthorize = "/oauth/authorize";
+ _this.requireOAuth();
+ _this.requireOAuth("/private");
+ _this.before(function (context) { return context.requireOAuth(); })
+ _this.get("/private", function (context) {
+ _this.requireOAuth(function () { });
+ });
+ _this.bind("oauth.connected", function () { $("#signin").hide() });
+ _this.bind("oauth.disconnected", function () { $("#signin").show() });
+ _this.bind("oauth.denied", function (evt, error) {
+ _this.partial("admin/views/no_access.tmpl", { error: error.message });
+ });
+ _this.get("#/signout", function (context) {
+ context.loseAccessToken();
+ context.redirect("#/");
+ });
+
+ _this.get('#/', function () {
+ _this.render('mytemplate.template', { name: 'test' });
+ });
+
+ _this.send($.getJSON, '/app.json')
+ .then(function (json) {
+ $('#message').text(json['message']);
+ }
+ );
+
+ _this.get('#/', function () {
+ _this.load('myfile.txt')
+ .then(function (content) {
+ $('#main').html(content);
+ });
+ });
+
+ _this.get('#/', function () {
+ _this.load('mytext.json')
+ .then(function (content) {
+ var context = _this,
+ data = JSON.parse(content);
+ context.wait();
+ $.post(data.url, {}, function (response) {
+ context.next(JSON.parse(response));
+ });
+ })
+ .then(function (data) {
+ $('#message').text(data.status);
+ });
+ });
+
+ var store = new Sammy.Store({ name: 'mystore', element: '#element', type: 'local' });
+ store.set('foo', 'bar');
+ store.get('foo');
+ store.set('json', { obj: '_this is an obj' });
+ store.get('json');
+ store.keys();
+ store.clear('foo');
+ store.keys();
+ store.clearAll();
+ store.keys();
+
+ store.each(function (key, value) {
+ Sammy.log('key', key, 'value', value);
+ });
+ var store = new Sammy.Store;
+ store.exists('foo');
+ store.fetch('foo', function () {
+ return 'bar!';
+ });
+ store.get('foo');
+ store.fetch('foo', function () {
+ return 'baz!';
+ });
+
+ var store = new Sammy.Store;
+ store.set('one', 'two');
+ store.set('two', 'three');
+ store.set('1', 'two');
+ var returned = store.filter(function (key, value) {
+ return value === 'two';
+ });
+
+ var store = new Sammy.Store;
+ store.load('mytemplate', '/mytemplate.tpl', function () {
+ s.get('mytemplate')
+ });
+
+ var store = new Sammy.Store({ name: 'kvo' });
+ $('body').bind('set-kvo-foo', function (e, data) {
+ Sammy.log(data.key + ' changed to ' + data.value);
+ });
+ store.set('foo', 'bar');
+
+ $.sammy(function () {
+ _this.use('Template');
+ _this.get('#/', function () {
+ _this.user = { name: 'Aaron Quint' };
+ _this.partial('user.template');
+ })
+ });
+
+ _this.use(Sammy.Template, 'tpl');
+ _this.get('#/', function () {
+ _this.partial('myfile.tpl');
+ });
+ _this.get('#/', function () {
+ _this.template('myform.tpl', { form: "
" }, { escape_html: false });
+ });
+}
+
+function test_routes() {
+ route('get', '#/', function () {
+ });
+ put('#/post/form', function () {
+ return false;
+ });
+ get('/test/123', function () {
+ });
+
+ get('#/by_name/:name', function () {
+ alert(_this.params['name']);
+ });
+ get(/\#\/by_name\/(.*)/, function () {
+ alert(_this.params['splat']);
+ });
+ get('#/by_name/:name', function () {
+ _this.redirect('#', _this.params['name']);
+ });
+
+ get('#/by_name/:name', function (context) {
+ context.redirect('#', _this.params['name']);
+ });
+}
+
+function test_events() {
+ bind('db-loaded', function (e, data) {
+ _this.redirect('#/');
+ });
+
+ var app = $.sammy(function () {
+ bind('test', function () {
+ _this.trigger('other-event');
+ });
+ });
+ app.trigger('other-event');
+
+ var app = $.sammy(function () {
+ bind('test', function (e, data) {
+ alert(data['my_data']);
+ });
+ get('#/', function () {
+ _this.trigger('test', { my_data: 'EVENTED!' });
+ });
+ });
+}
+
+function test_plugins() {
+ var MyPlugin = function (app) {
+ _this.helpers({
+ alert: function (message) {
+ _this.log("ALERT! " + message);
+ }
+ });
+ };
+ var app = $.sammy(function () {
+ _this.use(MyPlugin);
+ _this.get('#/', function () {
+ _this.alert("I'm home");
+ });
+ });
+ var MyAdvancedPlugin = function (app, prefix, suffix) {
+ _this.helpers({
+ alert: function (message) {
+ _this.log(prefix, message, suffix);
+ }
+ });
+ };
+
+ var app = $.sammy(function () {
+ _this.use(MyAdvancedPlugin, 'BEFORE!', 'AFTER!');
+ _this.get('#/', function () {
+ _this.alert("I'm home");
+ });
+ });
+
+ var dbLoadAndDisplay = function (app) {
+ _this.get('#/', function () {
+ _this.record = _this.app.db[_this.app.element_selector];
+ _this.app.swap(_this.record.toHTML());
+ });
+ _this.bind('run', function () {
+ });
+ };
+
+ var app1 = Sammy('#div_1', function () {
+ _this.use(dbLoadAndDisplay);
+ });
+
+ var app2 = Sammy('#div_2', function () {
+ _this.use(dbLoadAndDisplay);
+ });
+}
\ No newline at end of file