Skip to content
Browse files

initial version

  • Loading branch information...
0 parents commit 591019f59543854fd5d80fd207f39af396d44285 @jamuhl jamuhl committed
Showing with 9,885 additions and 0 deletions.
  1. +15 −0 .gitignore
  2. +290 −0 Jakefile.js
  3. +19 −0 license
  4. +25 −0 package.json
  5. +40 −0 sample/basic.html
  6. +42 −0 sample/node/app/assets/css/bootstrap_override.styl
  7. +43 −0 sample/node/app/assets/css/main.styl
  8. +44 −0 sample/node/app/assets/js/backbone/base/baseCollection.js
  9. +68 −0 sample/node/app/assets/js/backbone/base/baseModel.js
  10. +31 −0 sample/node/app/assets/js/backbone/base/baseRouter.js
  11. +71 −0 sample/node/app/assets/js/backbone/base/baseView.js
  12. +30 −0 sample/node/app/assets/js/backbone/collections/persons.js
  13. +49 −0 sample/node/app/assets/js/backbone/models/person.js
  14. +25 −0 sample/node/app/assets/js/backbone/routers/addRouter.js
  15. +57 −0 sample/node/app/assets/js/backbone/routers/controller/appRouter.js
  16. +35 −0 sample/node/app/assets/js/backbone/routers/indexRouter.js
  17. +52 −0 sample/node/app/assets/js/backbone/state.js
  18. +61 −0 sample/node/app/assets/js/backbone/views/addView.js
  19. +91 −0 sample/node/app/assets/js/backbone/views/controller/appView.js
  20. +160 −0 sample/node/app/assets/js/backbone/views/indexView.js
  21. +40 −0 sample/node/app/assets/js/backbone/views/shared/formHelpers.js
  22. +126 −0 sample/node/app/assets/js/backbone/views/sidebars/personDetailView.js
  23. +65 −0 sample/node/app/assets/js/bootstrap.js
  24. +22 −0 sample/node/app/assets/js/globals.js
  25. +401 −0 sample/node/app/assets/js/lib/ICanHaz.js
  26. +9 −0 sample/node/app/assets/js/lib/ICanHaz.min.js
  27. +1,158 −0 sample/node/app/assets/js/lib/backbone-0.5.3.js
  28. +33 −0 sample/node/app/assets/js/lib/backbone-0.5.3.min.js
  29. +53 −0 sample/node/app/assets/js/lib/bootstrap-dropdown-1.3.1.js
  30. +67 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.af-ZA.js
  31. +67 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.af.js
  32. +74 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.am-ET.js
  33. +74 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.am.js
  34. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-AE.js
  35. +462 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-BH.js
  36. +458 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-DZ.js
  37. +484 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-EG.js
  38. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-IQ.js
  39. +462 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-JO.js
  40. +462 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-KW.js
  41. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-LB.js
  42. +462 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-LY.js
  43. +458 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-MA.js
  44. +458 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-OM.js
  45. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-QA.js
  46. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-SA.js
  47. +457 −0 sample/node/app/assets/js/lib/cultures/globalize.culture.ar-SY.js
Sorry, we could not display the entire diff because too many files (425) changed.
15 .gitignore
@@ -0,0 +1,15 @@
+$ cat .gitignore
+
+# Can ignore specific files
+.settings.xml
+.monitor
+
+# Use wildcards as well
+*~
+#*.swp
+
+# Can also ignore all directories and files in a directory.
+node_modules
+node_modules/**/*
+build
+build/**/*
290 Jakefile.js
@@ -0,0 +1,290 @@
+// Jakefile.js v0.0.1
+// (c) 2011 Kaba AG, CC EAC
+// (by) Jan Muehlemann (jamuhl)
+
+// Here we will document the common [jake](https://github.com/mde/jake) tasks generic to all other jake
+// files in other projects.
+
+// ## used modules
+// - __fs__ -> is all about filesystem [documentation](http://nodejs.org/docs/v0.3.1/api/fs.html)
+// - __spawn__ -> a longer running app call in console normally exit with some exitcode [documentation](http://nodejs.org/docs/v0.3.1/api/child_processes.html#child_process.spawn)
+// - __exec__ -> a simple version of console execution [documentation](http://nodejs.org/docs/v0.3.1/api/child_processes.html#child_process.exec)
+// - __smoosh__ -> is a javascript compressing tool [documentation](https://github.com/fat/smoosh)
+var fs = require('fs')
+ , spawn = require('child_process').spawn
+ , exec = require('child_process').exec
+ , smoosh = require('smoosh');
+
+// ## variables
+// - __port__ -> the port on which express and socket.io is running
+var port = 3000;
+
+// # root tasks
+// This are the main tasks in our project.
+// __hint:__ `jake -T` will display all avaiable tasks
+
+// ## DOC
+// run `jake doc`
+// This task will document provided modules by using [docco](http://jashkenas.github.com/docco/)
+desc('Creates the documentation');
+task('doc', [], function() {
+
+ // add your docs to this array
+ //
+ // - __name__ will be displayed in output
+ // - __files__ add your files (wildcards supported)
+ // - __target__ folder where to output the docs
+ var docs = [
+ { name: 'backbone',
+ files: [
+ 'app/assets/js/backbone/*.js'
+ , 'app/assets/js/backbone/base/*.js'
+ , 'app/assets/js/backbone/routers/controller/*.js'
+ , 'app/assets/js/backbone/routers/indexRouter.js'
+ , 'app/assets/js/backbone/views/controller/*.js'
+ , 'app/assets/js/bootstrap.js'
+ ],
+ target: 'build/docs/client/backbone' },
+ { name: 'jakefile', files: ['Jakefile.js'], target: 'build/docs/jake' },
+ ];
+
+ // __documents all items in docs array serial:__
+ // the selfcalling function next will shift the first item from the array
+ // and call the function process with itself as callback. The process
+ // function than calls the function document passing next as callback.
+ // When the callback _next_ is called it will take the next item from the array.
+ (function next(e) {
+ var process = function(doc, next) {
+ document(doc.name, doc.files.join(' '), doc.target, next);
+ };
+
+ (!e && docs.length) ? process(docs.shift(), next) : console.log((e) ? e : '');
+ }
+ )();
+});
+
+// # client tasks
+// This are tasks used to generate the clientside assets of the application
+namespace('client', function () {
+
+ // ## BUILD
+ // run `jake client:build`
+ // or `jake client:build[true]` for debugging output
+ // will build the projects clientsources.
+ desc('Builds the clientside scripts');
+ task('build', [], function(debug) {
+
+ // first grab all javascript files for client app
+ // __hint:__ the option `{debug:true}` will return the full list -
+ // in production we would only get the _client.js_.
+ var files = require('./app/assets/jsFileList').init({debug: true}).client;
+
+ var readFiles = [],
+ remaining = files.length;
+
+ // this will read all files from filesystem in parallel
+ for (i = 0, len = files.length; i < len; i++) {
+
+ // as we need the index (current i) later to put the file into
+ // the right readFiles array space we put the index into a closure function.
+ var read = function(index) {
+ fs.readFile(files[index], 'utf8', function(err, file) {
+ if (err) console.log(('- failed to read ' + files[index]).red);
+
+ readFiles[index] = file;
+ if (debug) console.log(('read ' + files[index]).grey)
+ remaining--;
+
+ // go on if all files are read in
+ if (remaining === 0) {
+ process();
+ }
+ });
+ };
+
+ read(i);
+ }
+
+ // process the _readFiles_ array
+ var process = function() {
+
+ // fist create target directory and log on error and call fail
+ mkdirs(dirs('public/js'), 0755, function(err) {
+ if (err) {
+ console.log('- failed to make folder public/js'.red);
+ fail('client:build - failed to make folder public/js')
+ }
+
+ // concate the files array
+ var concate = readFiles.join('\n\n');
+
+ // replace placeholders in the concated file.
+ concate = concate.replace('.#socketIoPort#.', port);
+
+ // write the file to the _public/js_ folder
+ fs.writeFile('./public/js/client.js', concate, 'utf8', function(err) {
+ if (err) {
+ console.log('- failed to write public/js/client.js'.red);
+ fail('client:build - failed to write public/js/client.js')
+ } else {
+
+ // a basic smoosh configuration object
+ smoosh.config({
+ /*"VERSION": "0.1",*/
+ "JAVASCRIPT": {
+ "DIST_DIR": "/public/js",
+ "client": [
+ "public/js/client.js",
+ ]
+ }
+ })
+
+ // run smoosh to get minified version of the js file
+ smoosh.build().analyze();
+
+ console.log('+ written public/js/client.js successfully'.green);
+ }
+ });
+ });
+ }
+ });
+});
+
+// # functions
+
+// ### function pathDepth
+// will return a string seperator with length depending on '/' count
+var pathDepth = function(str) {
+ var deep = (str.split("/").length - 1) * 2;
+ var sep = '';
+ for (i = 0; i < deep; i++) {
+ sep += ' ';
+ }
+ return sep;
+}
+
+// ### function dirs
+// will generate an array of folders out of a string
+// __Example:__
+// passing `root/sub1/sub2` will result in
+// `{'root','root/sub1','root/sub1/sub2'}`
+var dirs = function(path) {
+ var parts = path.split('/')
+ , arr = [];
+ for (i = 0, y = 0, len = parts.length; i < len; i++) {
+ var dir = parts[0];
+ for (z = 1; z <= y; z++) {
+ dir += '/' + parts[z];
+ }
+ arr.push(dir);
+ y++;
+ }
+ return arr;
+};
+
+// ### function mkdirs
+// will create the folders provided in the _dirs_ array
+// __hint:__ mode is permission set on folder as digit (like in chmod)
+var mkdirs = function(dirs, mode, cb){
+
+ var createIfNotExists = function(dir, mode, cb) {
+ fs.stat(dir, function(err, stat) {
+ if (stat && stat.isDirectory()) {
+ cb()
+ } else {
+ fs.mkdir(dir, mode, cb);
+ }
+ });
+ };
+
+ // creates all folder in dirs serial
+ (function next(e) {
+ (!e && dirs.length) ? createIfNotExists(dirs.shift(), mode, next) : cb((e) ? e : undefined);
+ })(null);
+};
+
+// ### function document
+// will document passed in source string and copy docs to output folder.
+var document = function(name, source, target, cb) {
+
+ // first it will create the target folder and only callback on error
+ // __hint:__ the function _dirs_ will generate an array out of the target string.
+ mkdirs(dirs(target), 0755, function(err) {
+ if (err) cb(err)
+
+ // __execute docco__ on success `docco myFile1 myFile2`
+ var docco = exec('docco ' + source, function (err, stdout, stderr) {
+
+ // on error log it and pass error to callback
+ if (err !== null) {
+ console.log(('+ failed to document ' + name + ' files').red);
+ cb(err);
+ }
+
+ // else __move the files__ from the _docs folder_ to target folder `mv source target`
+ else {
+ var move = exec('mv docs/* ' + target, function (err, stdout, stderr) {
+
+ // on error log it and pass error to callback
+ if (err !== null) {
+ console.log(('+ failed to move ' + name + ' documentation').red);
+ cb(error);
+ }
+
+ // else __remove the docs folder__ to have an empty one for next run `rm -rf folder`
+ else {
+ var remove = exec('rm -rf docs', function (err, stdout, stderr) {
+
+ // on error log it and pass error to callback
+ if (err !== null) {
+ console.log('+ failed to remove docs folder'.red);
+ cb(error);
+ }
+
+ // else __log success and callback__
+ else {
+ console.log(('+ documented ' + name + ' files successfully').green);
+ cb();
+ }
+ });
+ }
+ });
+ }
+ });
+ });
+};
+
+
+// ### function document
+// will extend the string object to append a _stylize function_ which will
+// style the console output.
+// __Example:__
+// `console.log('my string'.blue)`
+function stylize(str, style) {
+
+ // define the styles
+ var styles = {
+ //styles
+ 'bold' : [1, 22], 'italic' : [3, 23],
+ 'underline' : [4, 24], 'inverse' : [7, 27],
+ //grayscales
+ 'white' : [37, 39], 'grey' : [90, 39],
+ 'black' : [90, 39],
+ //colors
+ 'blue' : [34, 39], 'cyan' : [36, 39],
+ 'green' : [32, 39], 'magenta' : [35, 39],
+ 'red' : [31, 39], 'yellow' : [33, 39]
+ };
+ return '\033[' + styles[style][0] + 'm' + str + '\033[' + styles[style][1] + 'm';
+}
+
+['bold', 'underline', 'italic',
+ 'inverse', 'grey', 'yellow',
+ 'red', 'green', 'blue',
+ 'white', 'cyan', 'magenta'].forEach(function (style) {
+
+ String.prototype.__defineGetter__(style, function () {
+ return stylize(this, style);
+ });
+
+});
19 license
@@ -0,0 +1,19 @@
+Copyright (c) 2011 Jan Mühlemann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
25 package.json
@@ -0,0 +1,25 @@
+{
+ "author": "jamuhl"
+ , "name": "express-backbone-bootstrap"
+ , "version": "0.0.1"
+ , "private": false
+ , "main": "server.js"
+ , "engines": {
+ "node": "~v0.4.12"
+ }
+ , "dependencies": {
+ "express": "2.4.6"
+ , "gzippo": ">= 0.0.1"
+ , "stylus": ">= 0.0.1"
+ , "nib" : ">= 0.0.1"
+ , "jade": ">= 0.0.1"
+ , "smoosh": "0.2.x"
+ , "glob": ">= 0.0.1"
+ }
+ , "devDependencies": {
+ "docco": ">=0.0.1"
+ , "jake": ">=0.0.1"
+ }
+ , "scripts": {
+ }
+}
40 sample/basic.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+ <title>Basic Sample Usage</title>
+
+ <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
+ <script type="text/javascript" src="../src/i18next.js"></script>
+
+</head>
+
+<body>
+
+ <ul>
+ <li><span id='fillMe'></span></li>
+ </ul>
+
+ <script type="text/javascript">
+
+ $.i18n.init(
+ {
+ lang: 'en',
+ dictionary: {
+ mykey: 'this is a simple value'
+ }
+ },
+ function(t) {
+ $('#fillMe').text($.t('mykey'));
+ }
+
+
+ );
+
+
+
+ </script>
+
+</body>
+
+</html>
42 sample/node/app/assets/css/bootstrap_override.styl
@@ -0,0 +1,42 @@
+/* Override some defaults */
+html, body
+ background-color: #eee
+
+body
+ padding-top: 40px /* 40px to make the container go all the way to the bottom of the topbar */
+
+.container > footer p
+ text-align: center /* center align it with the container */
+
+/* The white background content wrapper */
+.content
+ background-color: #fff
+ padding: 20px
+ margin: 0 -20px /* negative indent the amount of the padding to maintain the grid system */
+ border-radius: 0 0 6px 6px
+ box-shadow: 0 1px 2px rgba(0,0,0,.15)
+
+/* Page header tweaks */
+.page-header
+ background-color: #f5f5f5
+ padding: 20px 20px 10px
+ margin: -20px -20px 20px
+
+/* Styles you shouldn't keep as they are for displaying this base example only */
+.content .span10,
+.content .span4
+ min-height: 500px
+
+/* Give a quick and non-cross-browser friendly divider */
+.content .span4
+ margin-left: 0
+ padding-left: 19px
+ border-left: 1px solid #eee
+
+.topbar .btn
+ border: 0
+
+// action extension
+.actions
+ &.left
+ padding: 17px 20px 18px 20px
43 sample/node/app/assets/css/main.styl
@@ -0,0 +1,43 @@
+@import "bootstrap_override.styl"
+
+// colors
+$highlight = #0069D6
+
+.personItem
+ position: relative
+ border: solid 1px $highlight
+ background: #fff
+ padding: 0 60px 0 20px
+ margin: 4px 0
+ border-radius: 2px
+
+ &:hover
+ border: solid 1px $highlight
+ border-left: solid 2px $highlight
+ color: $highlight
+ padding: 0 60px 0 19px
+
+ .name
+ font-family: Arial, sans-serif
+ font-size: 12pt
+
+ &.selected
+ border: solid 1px $highlight
+ border-left: solid 2px $highlight
+ color: $highlight
+ padding: 0 60px 0 19px
+
+ .itemcontent
+ padding: 12px 0 10px 0
+
+ .commands
+ display: inline-block
+ position: absolute
+ right: 10px
+ bottom: 5px
+
+ a
+ margin: 0 4px
+
+
+// add you styles
44 sample/node/app/assets/js/backbone/base/baseCollection.js
@@ -0,0 +1,44 @@
+// This is the base collection for our app. All our collections should inherit (by extending)
+// from this baseCollection.
+// __Example:__
+//
+// var collection = myApp.Collection.extend({});
+
+(function(myApp) {
+
+ // set _calix.Collection to our baseCollection
+ myApp.Collection = Backbone.Collection.extend({
+
+ // fetch list without overwriting existing objects (copied from backbone's fetch())
+ fetchNew: function(options) {
+ options = options || {};
+ var collection = this,
+ success = options.success;
+ options.success = function(resp, status, xhr) {
+ _(collection.parse(resp, xhr)).each(function(item) {
+ if (!collection.get(item.id)) {
+ collection.add(item, {silent:true});
+ }
+ });
+ if (!options.silent) collection.trigger('reset', collection, options);
+ if (success) success(collection, resp);
+ };
+ return (this.sync || Backbone.sync).call(this, 'read', this, options);
+ },
+
+ // will get existing model or create a new model
+ // __Example:__
+ //
+ // var myModel = myCollection.getOrCreate(myModelsId);
+ getOrCreate: function(modelId) {
+ var model = this.get(modelId);
+ if (!model) {
+ model = new this.model({ id: modelId});
+ this.add(model);
+ }
+ return model;
+ }
+
+ });
+
+}(myApp));
68 sample/node/app/assets/js/backbone/base/baseModel.js
@@ -0,0 +1,68 @@
+// This is the base model for our app. All our models should inherit (by extending)
+// from this baseModel.
+// __Example:__
+//
+// var model = myApp.Model.extend({});
+
+(function(myApp) {
+
+ // set _calix.Model_ to our baseModel
+ myApp.Model = Backbone.Model.extend({
+
+ // this will set the passed values to the model and validate it by calling
+ // the underlaying model.save function (which will not be executed thanks to
+ // our backbone.sync).
+ // __Example:__
+ //
+ // changeValues: function(e) {
+ // this.model.setAndValidate({myAttr: 'newValue'}, {
+ // error: _.bind(function(model, error) {
+ // // give feedback in ui
+ // }, this),
+ // success: _.bind(function(model, res) {
+ // model.emitChange();
+ // this.render();
+ // }, this)
+ // });
+ // },
+ setAndValidate: function(changes, options) {
+ this.save(changes, {
+ error: function(model, error) {
+ if (options && options.error) options.error(model, error)
+ },
+ success: function(model) {
+ if (options && options.success) options.success(model)
+ }
+ });
+ },
+
+ // override this in subclasses if needed
+ isFullyLoaded: function() {
+ return true;
+ },
+
+ // callback for actions to take when fully loaded
+ onReady: $.noop,
+
+ // support for common pattern
+ ready: function(loadCallback, immediateCallback) {
+ var model = this,
+ immediateCallback = immediateCallback || loadCallback;
+ if (!model.isFullyLoaded()) {
+ model.fetch({
+ success: function() {
+ model.onReady();
+ loadCallback();
+ },
+ error: function() {
+ console.log('Error fetching model with id ' + model.id)
+ }
+ });
+ } else {
+ immediateCallback();
+ }
+ }
+
+ });
+
+}(myApp));
31 sample/node/app/assets/js/backbone/base/baseRouter.js
@@ -0,0 +1,31 @@
+// This is the base router for our app. All our routers should inherit (by extending)
+// from this baseRouter.
+// __Example:__
+//
+// var router = myApp.Router.extend({});
+
+(function(myApp) {
+
+ // set _calix.Router to our baseRouter
+ myApp.Router = Backbone.Router.extend({
+
+ // navigate to the current route (you must override this in subclasses)
+ getRoute: function() {
+ return '';
+ },
+
+ // update the url based on the current state
+ updateRoute: function() {
+ this.navigate(this.getRoute());
+ },
+
+ // update the url if this router's view is the top view
+ updateViewRoute: function() {
+ if (this.topview && this.topview == calix.state.get('topview')) {
+ this.updateRoute();
+ }
+ }
+
+ });
+
+}(myApp));
71 sample/node/app/assets/js/backbone/base/baseView.js
@@ -0,0 +1,71 @@
+// This is the base view for our app. All our views should inherit (by extending)
+// from this baseView.
+// __Example:__
+//
+// var view = myApp.View.extend({});
+
+(function(myApp, window) {
+ var state = myApp.state;
+
+ // set _calix.View_ to our baseView
+ myApp.View = Backbone.View.extend({
+
+ // basic open/close/remove support you can override this in specific view
+ open: function() {
+ $(this.el).show();
+ },
+
+ close: function() {
+ $(this.el).hide();
+ },
+
+ remove: function() {
+ $(this.el).slideUp(500, _.bind(function() {
+ $(this.el).remove();
+ }, this));
+ },
+
+ // bind/unbind state listeners. you can use this in your view to bind
+ // listeners to changes.
+ // __Example:__
+ //
+ // initialize: function() {
+ // this.bindState('change:myAttribute', this.myFunction, this);
+ // },
+ bindState: function(event, handler, context) {
+ if (!this._stateHandlers) {
+ this._stateHandlers = [];
+ }
+ state.bind(event, handler, context);
+ this._stateHandlers.push({ event: event, handler: handler });
+ },
+
+ unbindState: function() {
+ (this._stateHandlers || []).forEach(function(h) {
+ state.unbind(h.event, h.handler);
+ });
+ },
+
+ // unbind UI event handlers
+ unbindEvents: function() {
+ var view = this,
+ eventSplitter = /^(\S+)\s*(.*)$/,
+ events = view.events || [];
+ _(events).each(function(e, key) {
+ var match = key.match(eventSplitter),
+ eventName = match[1],
+ selector = match[2];
+ $(view.el).undelegate(selector, eventName);
+ });
+ },
+
+ // basic clear support
+ clear: function() {
+ $(this.el).empty();
+ this.unbindState();
+ this.unbindEvents();
+ }
+
+ });
+
+}(myApp, this));
30 sample/node/app/assets/js/backbone/collections/persons.js
@@ -0,0 +1,30 @@
+(function(myApp) {
+ var ns = myApp.collections || extend(myApp, 'myApp.collections')
+ , models = myApp.models
+ , Persons;
+
+ // the main collection holding our person models
+ Persons = ns.Persons = myApp.Collection.extend({
+
+ url: "/data/persons",
+
+ model: models.Person,
+
+ // will return the response part from json fetch
+ // to autofill the models
+ parse: function(res) {
+ var persons = res.response.persons;
+
+ for (i = 0, len = persons.length; i < len; i++) {
+ var person = persons[i];
+ if (person.persons) {
+ var list = new ns.Persons(person.persons);
+ person.persons = list;
+ }
+ }
+
+ return persons;
+ }
+ });
+
+}(myApp));
49 sample/node/app/assets/js/backbone/models/person.js
@@ -0,0 +1,49 @@
+(function(myApp) {
+ var ns = myApp.models || extend(myApp, 'myApp.models')
+ , collections = myApp.collections || extend(myApp, 'myApp.collections')
+ , Person;
+
+ // ### Visit
+ // the main model representing a visit
+ Person = ns.Person = myApp.Model.extend({
+
+ url: function(){
+ if (this.isNew()){
+ return "/data/addPerson";
+ } else {
+ return "/data/persons/" + this.id;
+ }
+ },
+
+ defaults: {
+ },
+
+ addPerson: function(person) {
+ var list = this.get('persons');
+
+ if (!list) {
+ list = new collections.Persons();
+ this.set({persons: list});
+ }
+
+ list.add(person);
+ this.trigger('change:persons', list);
+ },
+
+ onReady: function() {
+ },
+
+ isFullyLoaded: function() {
+ return true;
+ },
+
+ validate: function(attrs) {
+ if (attrs.firstname === '') {
+ return 'cannot have an empty firstname';
+ }
+ if (attrs.lastname === '') {
+ return 'cannot have an empty lastname';
+ }
+ }
+ });
+}(myApp));
25 sample/node/app/assets/js/backbone/routers/addRouter.js
@@ -0,0 +1,25 @@
+(function(myApp) {
+ var ns = myApp.routers || extend(myApp, 'myApp.routers')
+ , state = myApp.state
+ , AddView = myApp.views.AddView
+ , AddRouter;
+
+ AddRouter = myApp.Router.extend({
+
+ routes: {
+ "add": "add"
+ },
+
+ add: function() {
+ state.set({ topview: AddView });
+ },
+
+ getRoute: function() {
+ return 'add';
+ }
+
+ });
+
+ ns.AppRouter.register(AddRouter, AddView);
+
+}(myApp));
57 sample/node/app/assets/js/backbone/routers/controller/appRouter.js
@@ -0,0 +1,57 @@
+// All routers need to register itself to this _approuter_ which will handling all state
+// changes by calling updateRoute on the right router.
+
+(function(myApp) {
+ var ns = myApp.routers || extend(myApp, 'myApp.routers')
+ , state = myApp.state
+ , AppRouter
+ // array to hold registered routers
+ , routers = [];
+
+ AppRouter = ns.AppRouter = myApp.Router.extend({
+
+ initialize: function() {
+
+ // instantiate registered routers
+ routers.forEach(function(r) {
+ r.router = new r.cls();
+ });
+
+ // listen for state changes - if topview value changes the corresponding route
+ // will be updated
+ state.bind('change:topview', this.updateRoute, this);
+ },
+
+ // get the router for the current top view
+ getRouter: function() {
+ var topview = state.get('topview');
+ try {
+ return _(routers).detect(function(r) {
+ return r.view == topview;
+ }).router;
+ } catch (e) {
+ console.error('top view not found');
+ }
+ },
+
+ getRoute: function() {
+ // delegate
+ return this.getRouter().getRoute();
+ },
+
+ navigate: function(route, trigger) {
+ // delegate
+ return this.getRouter().navigate(route, trigger);
+ }
+
+ });
+
+ // register a router class to deal with a top-level view
+ AppRouter.register = function(router, view) {
+ routers.push({
+ view: view,
+ cls: router
+ })
+ };
+
+}(myApp));
35 sample/node/app/assets/js/backbone/routers/indexRouter.js
@@ -0,0 +1,35 @@
+// All routers work the same way - so we will only document this one.
+// A router registers itself to the _approuter_ which will handling all state
+// changes by calling updateRoute on the right router.
+
+(function(myApp) {
+ var ns = myApp.routers || extend(myApp, 'myApp.routers')
+ , state = myApp.state
+ , IndexView = myApp.views.IndexView
+ , DetailView = myApp.views.PersonDetailView
+ , IndexRouter;
+
+ ns.IndexRouter = IndexRouter = myApp.Router.extend({
+
+ routes: {
+ "": "index",
+ "index": "index"
+ },
+
+ index: function() {
+ // when the route match set the needed values in state
+ // this will invoke changes in _approuter_ and _appview_.
+ state.set({ topview: IndexView });
+ state.set({ sidebarview: DetailView });
+ },
+
+ getRoute: function() {
+ return 'index';
+ }
+
+ });
+
+ // register the router at the _approuter_ with the matching (top)view
+ ns.AppRouter.register(IndexRouter, IndexView);
+
+}(myApp));
52 sample/node/app/assets/js/backbone/state.js
@@ -0,0 +1,52 @@
+// The state backbone model holds the current state of the application.
+// It will hold:
+//
+// - shared collections and models
+// - current views (topview, sidebarview,...)
+//
+// Because this is a regular backbone model we can use all functionality
+// described under the [backbone documentation](http://documentcloud.github.com/backbone/#Model).
+// Mostly we will get / set attributes and watch if they changed.
+
+// This will self-execute the function with the _calix_ namespace as this.
+(function(myApp) {
+ var State, state;
+
+ // model to hold current state
+ State = Backbone.Model.extend({
+
+ // (de)serialization functions
+ deserialize: function(key, value) {
+ var params = this.params,
+ f = params[key] && params[key].deserialize || _.identity;
+ return f(value);
+ },
+
+ // serialization functions
+ serialize: function(key, value) {
+ var params = this.params,
+ f = params[key] && params[key].serialize || _.identity;
+ return f(value);
+ },
+
+ // convenience function to set a serialized value
+ setSerialized: function(key, value) {
+ o = {};
+ o[key] = this.deserialize(key, value);
+ this.set(o);
+ },
+
+ });
+
+ // initialize the singleton
+ state = myApp.state = new State();
+
+ // factory for de/serializable state parameters
+ function param(deserialize, serialize) {
+ return {
+ deserialize: deserialize || _.identity,
+ serialize: serialize || _.identity
+ };
+ };
+
+}(myApp));
61 sample/node/app/assets/js/backbone/views/addView.js
@@ -0,0 +1,61 @@
+(function(myApp) {
+ var ns = myApp.views || extend(myApp, 'myApp.views')
+ , models = myApp.models
+ , state = myApp.state
+ , formHelpers = myApp.views.shared.helpers.formHelpers;
+
+ // View: Add Person
+ ns.AddView = myApp.View.extend({
+
+ el: '#add-person-view',
+
+ initialize: function() {
+ var person = this.model = new models.Person();
+ this.render();
+ },
+
+ // route ui events to functions
+ events: {
+ 'click .createPerson' : 'createPerson',
+ 'click .cancel' : 'cancelAdd'
+ },
+
+ // is called from ui event to create a new visit
+ createPerson: function(e) {
+ // prevent default behavior -> form submit
+ e.preventDefault();
+
+ this.model.save({
+ firstname: this.$('#firstname').val(),
+ lastname: this.$('#lastname').val()
+ }, {
+ success: function(person) {
+ myApp.app.persons.add(person);
+ }
+ });
+
+ // go back
+ state.set({ 'topview': ns.IndexView });
+ },
+
+ cancelAdd: function(e) {
+ e.preventDefault();
+ state.set({ 'topview': ns.IndexView });
+ },
+
+ // is called to render the view
+ render: function() {
+ $(this.el).html(ich.addPerson(this.model.toJSON()));
+ return this;
+ },
+
+ open: function(fromRight) {
+ $(this.el).show('slide', {direction: (fromRight ? 'right' : 'left') }, 500);
+ },
+
+ close: function(fromRight) {
+ $(this.el).hide('slide', {direction: (fromRight ? 'left' : 'right') }, 500);
+ }
+ });
+
+}(myApp));
91 sample/node/app/assets/js/backbone/views/controller/appView.js
@@ -0,0 +1,91 @@
+// The _appView_ will listen on changes of topview, sidebarview and load the
+// matching view on change.
+
+(function(myApp) {
+ var ns = myApp.views || extend(myApp, 'myApp.views')
+ , state = myApp.state
+ , topviewOrder = [
+ ns.IndexView,
+ ns.AddVisitView,
+ ns.ConfigurationView
+ ]
+ , viewCache = [];
+
+ ns.AppView = myApp.View.extend({
+
+ initialize: function() {
+ // listen for state changes
+ state.bind('change:topview', this.updateTopView, this);
+ state.bind('change:sidebarview', this.updateSidebarView, this);
+ },
+
+ // this will cache all 'mainviews' views so they only have to be loaded once
+ cached: function(cls) {
+ var cached = _(viewCache).detect(function(c) {
+ return c.view == cls;
+ });
+ // if no key has been set, this has not been cached
+ if (!cached) {
+ // instantiate and cache
+ cached = {
+ view: cls,
+ instance: new cls({ parent: this })
+ };
+ viewCache.push(cached);
+ }
+ return cached.instance;
+ },
+
+ // update the top-level view
+ updateTopView: function() {
+ var cls = state.get('topview'),
+ view = this.cached(cls);
+ this.openTopView(view, cls);
+ },
+
+ // close the current view and open a new one
+ openTopView: function(view, cls) {
+ if (view) {
+ var oldview = this.currentTopView,
+ fromRight = true;
+ if (oldview && oldview != view) {
+ // get the old view class
+ oldCls = _(viewCache).detect(function(c) {
+ return c.instance == oldview;
+ }).view;
+ // work out left/right
+ fromRight = topviewOrder.indexOf(oldCls) < topviewOrder.indexOf(cls);
+ oldview.close(fromRight);
+ }
+ this.currentTopView = view;
+ view.open(fromRight);
+ }
+ },
+
+ // update the sidebar view
+ updateSidebarView: function() {
+ var cls = state.get('sidebarview'),
+ view = this.cached(cls);
+ this.openSidebarView(view, cls);
+ },
+
+ // close the current view and open a new one
+ openSidebarView: function(view, cls) {
+ if (view) {
+ var oldview = this.currentSidebarView;
+ if (oldview && oldview != view) {
+ // get the old view class
+ oldCls = _(viewCache).detect(function(c) {
+ return c.instance == oldview;
+ }).view;
+
+ oldview.close(true);
+ }
+ this.currentSidebarView = view;
+ view.open(true);
+ }
+ }
+
+ });
+
+}(myApp));
160 sample/node/app/assets/js/backbone/views/indexView.js
@@ -0,0 +1,160 @@
+(function(myApp) {
+ var ns = myApp.views || extend(myApp, 'myApp.views')
+ , state = myApp.state
+ , formHelpers = myApp.views.shared.helpers.formHelpers;
+
+ ns.PersonView = myApp.View.extend({
+ tagName: 'li',
+
+ className: 'personItem',
+
+ initialize: function() {
+ // bind functions to this
+ _.bindAll(this, 'editPerson', 'changePerson', 'render', 'remove');
+
+ // bind changes in the underlying model to the matching function.
+ this.model.bind('change', this.render);
+ this.model.bind('destroy', this.remove);
+
+
+ this.bindState('change:selectedPerson', this.toggleSelectedState, this);
+ },
+
+ // route ui events to functions
+ events: {
+ 'click .itemcontent' : 'selectPerson',
+ 'click .editPerson' : 'editPerson',
+ 'click .cancelEdit' : 'cancelEdit',
+ 'click .changePerson' : 'changePerson',
+ 'click .deletePerson' : 'deletePerson'
+ },
+
+ selectPerson: function() {
+ if (state.get('selectedPerson') !== this.model) {
+ state.set({selectedPerson: this.model});
+ } else {
+ state.unset('selectedPerson');
+ }
+ },
+
+ toggleSelectedState: function() {
+ if (state.get('selectedPerson') === this.model) {
+ if (this.model.selected === true) return;
+
+ this.model.selected = true;
+ this.render();
+ } else {
+ if (this.model.selected === false) return;
+
+ this.model.selected = false;
+ this.render();
+ }
+ },
+
+ // sets model to editMode and renders the view
+ editPerson: function(e) {
+ e.preventDefault();
+ this.model.editMode = true;
+ this.render();
+ },
+
+ // sets model back to non editMode and rerenders the view
+ cancelEdit: function(e) {
+ e.preventDefault();
+ this.model.editMode = false;
+ this.render();
+ },
+
+ // is called from the ui event to change the person.
+ changePerson: function(e) {
+ // prevent default behavior -> form submit
+ e.preventDefault();
+
+ this.model.save({
+ firstname: this.$('#firstname').val(),
+ lastname: this.$('#lastname').val()
+ });
+
+ this.model.editMode = false;
+ this.render();
+ },
+
+ // is called from the ui event to delete the person.
+ deletePerson: function() {
+ this.model.destroy();
+ state.set({ 'selectedPerson': null });
+ },
+
+ // is called to render the view
+ render: function() {
+ var model = this.model.toJSON();
+ model.selected = this.model.selected;
+ model.editMode = this.model.editMode;
+
+ // set html of this element using [icanhaz](http://icanhazjs.com/)
+ $(this.el).html(ich.person(model));
+
+ // select this item
+ if (this.model.selected === true) {
+ $(this.el).addClass('selected');
+ } else {
+ $(this.el).removeClass('selected');
+ }
+
+ return this;
+ },
+
+ // is called to remove the element from dom with a slideUp animation
+ remove: function() {
+ $(this.el).slideUp(500, _.bind(function() {
+ $(this.el).remove();
+ }, this));
+ }
+
+ });
+
+ // View: IndexView (index page)
+ ns.IndexView = myApp.View.extend({
+ el: '#index-view',
+
+ initialize: function() {
+ if (myApp.app.persons && !this.collection) {
+ this.collection = myApp.app.persons;
+ }
+ if (this.collection) {
+ this.collection.bind('reset', this.render, this);
+ this.collection.bind('add', this.addPerson, this);
+ this.collection.fetchNew();
+ }
+ },
+
+ events: {
+ 'click #newPerson' : 'uiAddPerson'
+ },
+
+ uiAddPerson: function() {
+ state.set({ 'topview': ns.AddView });
+ },
+
+ // is called to render all visit models in this collection
+ render: function() {
+ this.collection.each(this.addPerson);
+ },
+
+ // creates a personView with the given person model and
+ // appends it to the list element
+ addPerson: function(person) {
+ var view = new ns.PersonView({model: person});
+ this.$('#persons').append(view.render().el);
+ },
+
+ open: function(fromRight) {
+ $(this.el).show('slide', {direction: (fromRight ? 'right' : 'left') }, 500);
+ },
+
+ close: function(fromRight) {
+ $(this.el).hide('slide', {direction: (fromRight ? 'left' : 'right') }, 500);
+ }
+ });
+
+}(myApp));
40 sample/node/app/assets/js/backbone/views/shared/formHelpers.js
@@ -0,0 +1,40 @@
+// This holds shared elements used by more than one view. especially interesting
+// if you use includes in the serverside templates (eg. in add and edit forms).
+
+(function(myApp) {
+
+ var mod = extend(myApp, 'myApp.views.shared.helpers.formHelpers')
+ , formHelpers = {};
+
+ formHelpers.Select = {
+
+ // addes the elements of the array to the passed in select
+ fill: function(ele, arr) {
+ for(i=0, len=arr.length; i<len; i++) {
+ ele.append(
+ $('<option></option>').val(arr[i]).html(arr[i])
+ );
+ }
+ },
+
+ selectValue: function(ele, val) {
+ var selector = "option[value='" + val + "']";
+ ele.find(selector).attr('selected', true);
+ }
+ }
+
+ formHelpers.Input = {
+
+ validateNotEmpty: function(ele) {
+ if (ele.val() === '') {
+ ele.addClass('error');
+ } else {
+ ele.removeClass('error');
+ }
+ }
+ }
+
+ // finally extend the namespace with the helper
+ $.extend(mod, formHelpers);
+
+})(myApp);
126 sample/node/app/assets/js/backbone/views/sidebars/personDetailView.js
@@ -0,0 +1,126 @@
+(function(myApp) {
+ var ns = myApp.views || extend(myApp, 'myApp.views')
+ , models = myApp.models
+ , collections = myApp.collections
+ , formHelpers = myApp.views.shared.helpers.formHelpers
+ , state = myApp.state
+ , PersonItemView;
+
+
+ PersonItemView = myApp.View.extend({
+ tagName: 'li',
+
+ className: 'personItem',
+
+ initialize: function() {
+ // bind changes in the underlying model to the matching function.
+ this.model.bind('change', this.render, this);
+ this.model.bind('destroy', this.remove, this);
+
+ this.model.parent.bind('change:selectedPerson', this.toggleSelectedState, this);
+ },
+
+ // route ui events to functions
+ events: {
+ 'click .itemcontent' : 'uiSelectPerson',
+ 'click .removePerson' : 'uiRemovePerson'
+ },
+
+ uiSelectPerson: function() {
+ if (this.model.parent.get('selectedPerson') !== this.model) {
+ this.model.parent.set({selectedPerson: this.model});
+ } else {
+ this.model.parent.unset('selectedPerson');
+ }
+ },
+
+ uiRemovePerson: function(e) {
+ e.preventDefault();
+
+ this.model.destroy();
+ },
+
+ toggleSelectedState: function() {
+ if (this.model.parent.get('selectedPerson') === this.model) {
+ if (this.model.selected === true) return;
+
+ this.model.selected = true;
+ this.render();
+ } else {
+ if (this.model.selected === false) return;
+
+ this.model.selected = false;
+ this.render();
+ }
+ },
+
+ // is called to render the view
+ render: function() {
+ var model = this.model.toJSON();
+ model.selected = this.model.selected;
+
+ // set html of this element using [icanhaz](http://icanhazjs.com/)
+ $(this.el).html(ich.personItem(model));
+
+ if (this.model.selected === true) {
+ // select this item
+ $(this.el).addClass('selected');
+ } else {
+ $(this.el).removeClass('selected');
+ }
+
+ return this;
+ }
+ });
+
+ // View: Sidebar
+ ns.PersonDetailView = myApp.View.extend({
+ el: '#sidebar',
+
+ initialize: function() {
+ _.bindAll(this, 'render');
+
+ this.bindState('change:selectedPerson', this.setModel, this);
+ },
+
+ events: {
+ },
+
+ setModel: function() {
+ $(this.el).hide();
+ this.model = state.get('selectedPerson');
+ if (this.model && this.model.bind) {
+ this.model.bind('change:persons', this.render, this);
+ }
+ this.render();
+ this.open();
+ },
+
+ // is called to render all visit models in this collection
+ render: function() {
+ $(this.el).html(ich.personSidebar(this.model.toJSON()));
+
+ var persons = this.model.get('persons');
+ var ele = this.$('#persons');
+ if (persons) {
+ for (i = 0, len = persons.models.length; i < len; i++) {
+ var model = persons.models[i];
+ model.parent = this.model;
+ var view = new PersonItemView({model: model});
+ ele.append(view.render().el);
+ }
+ }
+
+ return this;
+ },
+
+ open: function(fromRight) {
+ $(this.el).show('slide', {direction: (fromRight ? 'right' : 'left') }, 500);
+ },
+
+ close: function(fromRight) {
+ $(this.el).hide('slide', {direction: (fromRight ? 'left' : 'right') }, 500);
+ }
+ });
+
+}(myApp));
65 sample/node/app/assets/js/bootstrap.js
@@ -0,0 +1,65 @@
+// app/assets/js/bootstrap.js v0.0.1
+// (c) 2011 Kaba AG, CC EAC
+// (by) Jan Muehlemann (jamuhl)
+
+
+// Basic architecture:
+
+// - __Models__ and __Collections__ are responsible for getting data from API
+// or emitting commands
+// - Singleton _state model_ is responsible for ui state data
+// - __Views__ are responsible for:
+// initialize:
+// - instantiating/fetching their models if necessary
+// - instantiating sub-views
+// - listening for state changes
+// - listening for model changes
+// render:
+// - adjusting the layout of their container boxes
+// - creating their content
+// events:
+// - listening for ui events, updating state
+// ui methods:
+// - updating ui on state change
+// - updating ui on model change
+// - __Routers__ are responsible for:
+// - setting state depending on route
+// - setting route depending on state
+ //
+// Process of opening a view:
+//
+// - URL router or UI event sets state.topview to the requested view class
+// - State fires topview:change
+// - AppView receives event, closes other views, calls view.open()
+// - view clears previous content if necessary
+// - view either renders, or fetches data and renders in the callback
+
+
+// setup app
+(function(myApp) {
+
+ myApp.app.init = function() {
+
+ myApp.app.persons = new myApp.collections.Persons();
+ myApp.app.router = new myApp.routers.AppRouter();
+ myApp.app.view = new myApp.views.AppView();
+
+ $.i18n.init({
+ lng: 'de-DE',
+ ns: { namespaces: ['ns.common', 'ns.special'], defaultNs: 'ns.special'}, // or just 'ns1' set default and list
+ dynamicLoad: true,
+ sendMissing: true
+ }, function() {
+ Backbone.history.start();
+
+ $('a.brand').text($.t('app.name'));
+ $('.page-header h1').text($.t('app.area'));
+ $('footer p').text($.t('ns.common:app.company.name'));
+ $('#newPerson').text($.t('ns.common:add'));
+ });
+ };
+
+})(myApp);
+
+// kick things off
+$(myApp.app.init);
22 sample/node/app/assets/js/globals.js
@@ -0,0 +1,22 @@
+// extend function
+function extend( ns, ns_string ) {
+ var parts = ns_string.split('.'),
+ parent = ns,
+ pl, i;
+ if (parts[0] == "myApp") {
+ parts = parts.slice(1);
+ }
+ pl = parts.length;
+ for (i = 0; i < pl; i++) {
+ //create a property if it doesnt exist
+ if (typeof parent[parts[i]] == 'undefined') {
+ parent[parts[i]] = {};
+ }
+ parent = parent[parts[i]];
+ }
+ return parent;
+}
+
+// root namespace
+var myApp = myApp = myApp || {};
+myApp.app = {};
401 sample/node/app/assets/js/lib/ICanHaz.js
@@ -0,0 +1,401 @@
+/*!
+ICanHaz.js version 0.9 -- by @HenrikJoreteg
+More info at: http://icanhazjs.com
+*/
+(function ($) {
+/*!
+ mustache.js -- Logic-less templates in JavaScript
+
+ by @janl (MIT Licensed, https://github.com/janl/mustache.js/blob/master/LICENSE).
+
+ See http://mustache.github.com/ for more info.
+*/
+
+var Mustache = function() {
+ var Renderer = function() {};
+
+ Renderer.prototype = {
+ otag: "{{",
+ ctag: "}}",
+ pragmas: {},
+ buffer: [],
+ pragmas_implemented: {
+ "IMPLICIT-ITERATOR": true
+ },
+ context: {},
+
+ render: function(template, context, partials, in_recursion) {
+ // reset buffer & set context
+ if(!in_recursion) {
+ this.context = context;
+ this.buffer = []; // TODO: make this non-lazy
+ }
+
+ // fail fast
+ if(!this.includes("", template)) {
+ if(in_recursion) {
+ return template;
+ } else {
+ this.send(template);
+ return;
+ }
+ }
+
+ template = this.render_pragmas(template);
+ var html = this.render_section(template, context, partials);
+ if(in_recursion) {
+ return this.render_tags(html, context, partials, in_recursion);
+ }
+
+ this.render_tags(html, context, partials, in_recursion);
+ },
+
+ /*
+ Sends parsed lines
+ */
+ send: function(line) {
+ if(line != "") {
+ this.buffer.push(line);
+ }
+ },
+
+ /*
+ Looks for %PRAGMAS
+ */
+ render_pragmas: function(template) {
+ // no pragmas
+ if(!this.includes("%", template)) {
+ return template;
+ }
+
+ var that = this;
+ var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
+ this.ctag);
+ return template.replace(regex, function(match, pragma, options) {
+ if(!that.pragmas_implemented[pragma]) {
+ throw({message:
+ "This implementation of mustache doesn't understand the '" +
+ pragma + "' pragma"});
+ }
+ that.pragmas[pragma] = {};
+ if(options) {
+ var opts = options.split("=");
+ that.pragmas[pragma][opts[0]] = opts[1];
+ }
+ return "";
+ // ignore unknown pragmas silently
+ });
+ },
+
+ /*
+ Tries to find a partial in the curent scope and render it
+ */
+ render_partial: function(name, context, partials) {
+ name = this.trim(name);
+ if(!partials || partials[name] === undefined) {
+ throw({message: "unknown_partial '" + name + "'"});
+ }
+ if(typeof(context[name]) != "object") {
+ return this.render(partials[name], context, partials, true);
+ }
+ return this.render(partials[name], context[name], partials, true);
+ },
+
+ /*
+ Renders inverted (^) and normal (#) sections
+ */
+ render_section: function(template, context, partials) {
+ if(!this.includes("#", template) && !this.includes("^", template)) {
+ return template;
+ }
+
+ var that = this;
+ // CSW - Added "+?" so it finds the tighest bound, not the widest
+ var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
+ "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
+ "\\s*", "mg");
+
+ // for each {{#foo}}{{/foo}} section do...
+ return template.replace(regex, function(match, type, name, content) {
+ var value = that.find(name, context);
+ if(type == "^") { // inverted section
+ if(!value || that.is_array(value) && value.length === 0) {
+ // false or empty list, render it
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ } else if(type == "#") { // normal section
+ if(that.is_array(value)) { // Enumerable, Let's loop!
+ return that.map(value, function(row) {
+ return that.render(content, that.create_context(row),
+ partials, true);
+ }).join("");
+ } else if(that.is_object(value)) { // Object, Use it as subcontext!
+ return that.render(content, that.create_context(value),
+ partials, true);
+ } else if(typeof value === "function") {
+ // higher order section
+ return value.call(context, content, function(text) {
+ return that.render(text, context, partials, true);
+ });
+ } else if(value) { // boolean section
+ return that.render(content, context, partials, true);
+ } else {
+ return "";
+ }
+ }
+ });
+ },
+
+ /*
+ Replace {{foo}} and friends with values from our view
+ */
+ render_tags: function(template, context, partials, in_recursion) {
+ // tit for tat
+ var that = this;
+
+ var new_regex = function() {
+ return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
+ that.ctag + "+", "g");
+ };
+
+ var regex = new_regex();
+ var tag_replace_callback = function(match, operator, name) {
+ switch(operator) {
+ case "!": // ignore comments
+ return "";
+ case "=": // set new delimiters, rebuild the replace regexp
+ that.set_delimiters(name);
+ regex = new_regex();
+ return "";
+ case ">": // render partial
+ return that.render_partial(name, context, partials);
+ case "{": // the triple mustache is unescaped
+ return that.find(name, context);
+ default: // escape the value
+ return that.escape(that.find(name, context));
+ }
+ };
+ var lines = template.split("\n");
+ for(var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].replace(regex, tag_replace_callback, this);
+ if(!in_recursion) {
+ this.send(lines[i]);
+ }
+ }
+
+ if(in_recursion) {
+ return lines.join("\n");
+ }
+ },
+
+ set_delimiters: function(delimiters) {
+ var dels = delimiters.split(" ");
+ this.otag = this.escape_regex(dels[0]);
+ this.ctag = this.escape_regex(dels[1]);
+ },
+
+ escape_regex: function(text) {
+ // thank you Simon Willison
+ if(!arguments.callee.sRE) {
+ var specials = [
+ '/', '.', '*', '+', '?', '|',
+ '(', ')', '[', ']', '{', '}', '\\'
+ ];
+ arguments.callee.sRE = new RegExp(
+ '(\\' + specials.join('|\\') + ')', 'g'
+ );
+ }
+ return text.replace(arguments.callee.sRE, '\\$1');
+ },
+
+ /*
+ find `name` in current `context`. That is find me a value
+ from the view object
+ */
+ find: function(name, context) {
+ name = this.trim(name);
+
+ // Checks whether a value is thruthy or false or 0
+ function is_kinda_truthy(bool) {
+ return bool === false || bool === 0 || bool;
+ }
+
+ var value;
+ if(is_kinda_truthy(context[name])) {
+ value = context[name];
+ } else if(is_kinda_truthy(this.context[name])) {
+ value = this.context[name];
+ }
+
+ if(typeof value === "function") {
+ return value.apply(context);
+ }
+ if(value !== undefined) {
+ return value;
+ }
+ // silently ignore unkown variables
+ return "";
+ },
+
+ // Utility methods
+
+ /* includes tag */
+ includes: function(needle, haystack) {
+ return haystack.indexOf(this.otag + needle) != -1;
+ },
+
+ /*
+ Does away with nasty characters
+ */
+ escape: function(s) {
+ s = String(s === null ? "" : s);
+ return s.replace(/&(?!\w+;)|["<>\\]/g, function(s) {
+ switch(s) {
+ case "&": return "&amp;";
+ case "\\": return "\\\\";
+ case '"': return '\"';
+ case "<": return "&lt;";
+ case ">": return "&gt;";
+ default: return s;
+ }
+ });
+ },
+
+ // by @langalex, support for arrays of strings
+ create_context: function(_context) {
+ if(this.is_object(_context)) {
+ return _context;
+ } else {
+ var iterator = ".";
+ if(this.pragmas["IMPLICIT-ITERATOR"]) {
+ iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
+ }
+ var ctx = {};
+ ctx[iterator] = _context;
+ return ctx;
+ }
+ },
+
+ is_object: function(a) {
+ return a && typeof a == "object";
+ },
+
+ is_array: function(a) {
+ return Object.prototype.toString.call(a) === '[object Array]';
+ },
+
+ /*
+ Gets rid of leading and trailing whitespace
+ */
+ trim: function(s) {
+ return s.replace(/^\s*|\s*$/g, "");
+ },
+
+ /*
+ Why, why, why? Because IE. Cry, cry cry.
+ */
+ map: function(array, fn) {
+ if (typeof array.map == "function") {
+ return array.map(fn);
+ } else {
+ var r = [];
+ var l = array.length;
+ for(var i = 0; i < l; i++) {
+ r.push(fn(array[i]));
+ }
+ return r;
+ }
+ }
+ };
+
+ return({
+ name: "mustache.js",
+ version: "0.3.0",
+
+ /*
+ Turns a template and view into HTML
+ */
+ to_html: function(template, view, partials, send_fun) {
+ var renderer = new Renderer();
+ if(send_fun) {
+ renderer.send = send_fun;
+ }
+ renderer.render(template, view, partials);
+ if(!send_fun) {
+ return renderer.buffer.join("\n");
+ }
+ }
+ });
+}();/*!
+ ICanHaz.js -- by @HenrikJoreteg
+*/
+/*global jQuery */
+function ICanHaz() {
+ var self = this;
+ self.VERSION = "0.9";
+ self.templates = {};
+ self.partials = {};
+
+ // public function for adding templates
+ // We're enforcing uniqueness to avoid accidental template overwrites.
+ // If you want a different template, it should have a different name.
+ self.addTemplate = function (name, templateString) {
+ if (self[name]) throw "Invalid name: " + name + ".";
+ if (self.templates[name]) throw "Template \" + name + \" exists";
+
+ self.templates[name] = templateString;
+ self[name] = function (data, raw) {
+ data = data || {};
+ var result = Mustache.to_html(self.templates[name], data, self.partials);
+ return raw ? result : $(result);
+ };
+ };
+
+ // public function for adding partials
+ self.addPartial = function (name, templateString) {
+ if (self.partials[name]) {
+ throw "Partial \" + name + \" exists";
+ } else {
+ self.partials[name] = templateString;
+ }
+ };
+
+ // grabs templates from the DOM and caches them.
+ // Loop through and add templates.
+ // Whitespace at beginning and end of all templates inside <script> tags will
+ // be trimmed. If you want whitespace around a partial, add it in the parent,
+ // not the partial. Or do it explicitly using <br/> or &nbsp;
+ self.grabTemplates = function () {
+ $('script[type="text/html"]').each(function (a, b) {
+ var script = $((typeof a === 'number') ? b : a), // Zepto doesn't bind this
+ text = (''.trim) ? script.html().trim() : $.trim(script.html());
+
+ self[script.hasClass('partial') ? 'addPartial' : 'addTemplate'](script.attr('id'), text);
+ script.remove();
+ });
+ };
+
+ // clears all retrieval functions and empties caches
+ self.clearAll = function () {
+ for (var key in self.templates) {
+ delete self[key];
+ }
+ self.templates = {};
+ self.partials = {};
+ };
+
+ self.refresh = function () {
+ self.clearAll();
+ self.grabTemplates();
+ };
+}
+
+window.ich = new ICanHaz();
+
+// init itself on document ready
+$(function () {
+ ich.grabTemplates();
+});
+})(window.jQuery || window.Zepto);
9 sample/node/app/assets/js/lib/ICanHaz.min.js
@@ -0,0 +1,9 @@
+(function(i){var n=function(){var f=function(){};f.prototype={otag:"{{",ctag:"}}",pragmas:{},buffer:[],pragmas_implemented:{"IMPLICIT-ITERATOR":true},context:{},render:function(a,b,c,d){if(!d){this.context=b;this.buffer=[]}if(!this.includes("",a))if(d)return a;else{this.send(a);return}a=this.render_pragmas(a);a=this.render_section(a,b,c);if(d)return this.render_tags(a,b,c,d);this.render_tags(a,b,c,d)},send:function(a){a!=""&&this.buffer.push(a)},render_pragmas:function(a){if(!this.includes("%",a))return a;
+var b=this;return a.replace(RegExp(this.otag+"%([\\w-]+) ?([\\w]+=[\\w]+)?"+this.ctag),function(c,d,e){if(!b.pragmas_implemented[d])throw{message:"This implementation of mustache doesn't understand the '"+d+"' pragma"};b.pragmas[d]={};if(e){c=e.split("=");b.pragmas[d][c[0]]=c[1]}return""})},render_partial:function(a,b,c){a=this.trim(a);if(!c||c[a]===undefined)throw{message:"unknown_partial '"+a+"'"};if(typeof b[a]!="object")return this.render(c[a],b,c,true);return this.render(c[a],b[a],c,true)},render_section:function(a,
+b,c){if(!this.includes("#",a)&&!this.includes("^",a))return a;var d=this;return a.replace(RegExp(this.otag+"(\\^|\\#)\\s*(.+)\\s*"+this.ctag+"\n*([\\s\\S]+?)"+this.otag+"\\/\\s*\\2\\s*"+this.ctag+"\\s*","mg"),function(e,j,k,h){e=d.find(k,b);if(j=="^")return!e||d.is_array(e)&&e.length===0?d.render(h,b,c,true):"";else if(j=="#")return d.is_array(e)?d.map(e,function(g){return d.render(h,d.create_context(g),c,true)}).join(""):d.is_object(e)?d.render(h,d.create_context(e),c,true):typeof e==="function"?
+e.call(b,h,function(g){return d.render(g,b,c,true)}):e?d.render(h,b,c,true):""})},render_tags:function(a,b,c,d){var e=this,j=function(){return RegExp(e.otag+"(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?"+e.ctag+"+","g")},k=j(),h=function(o,m,l){switch(m){case "!":return"";case "=":e.set_delimiters(l);k=j();return"";case ">":return e.render_partial(l,b,c);case "{":return e.find(l,b);default:return e.escape(e.find(l,b))}};a=a.split("\n");for(var g=0;g<a.length;g++){a[g]=a[g].replace(k,h,this);d||this.send(a[g])}if(d)return a.join("\n")},
+set_delimiters:function(a){a=a.split(" ");this.otag=this.escape_regex(a[0]);this.ctag=this.escape_regex(a[1])},escape_regex:function(a){if(!arguments.callee.sRE)arguments.callee.sRE=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\)","g");return a.replace(arguments.callee.sRE,"\\$1")},find:function(a,b){a=this.trim(a);var c;if(b[a]===false||b[a]===0||b[a])c=b[a];else if(this.context[a]===false||this.context[a]===0||this.context[a])c=this.context[a];if(typeof c==="function")return c.apply(b);
+if(c!==undefined)return c;return""},includes:function(a,b){return b.indexOf(this.otag+a)!=-1},escape:function(a){a=String(a===null?"":a);return a.replace(/&(?!\w+;)|["<>\\]/g,function(b){switch(b){case "&":return"&amp;";case "\\":return"\\\\";case '"':return'"';case "<":return"&lt;";case ">":return"&gt;";default:return b}})},create_context:function(a){if(this.is_object(a))return a;else{var b=".";if(this.pragmas["IMPLICIT-ITERATOR"])b=this.pragmas["IMPLICIT-ITERATOR"].iterator;var c={};c[b]=a;return c}},
+is_object:function(a){return a&&typeof a=="object"},is_array:function(a){return Object.prototype.toString.call(a)==="[object Array]"},trim:function(a){return a.replace(/^\s*|\s*$/g,"")},map:function(a,b){if(typeof a.map=="function")return a.map(b);else{for(var c=[],d=a.length,e=0;e<d;e++)c.push(b(a[e]));return c}}};return{name:"mustache.js",version:"0.3.0",to_html:function(a,b,c,d){var e=new f;if(d)e.send=d;e.render(a,b,c);if(!d)return e.buffer.join("\n")}}}();window.ich=new function(){var f=this;
+f.VERSION="0.9";f.templates={};f.partials={};f.addTemplate=function(a,b){if(f[a])throw"Invalid name: "+a+".";if(f.templates[a])throw'Template " + name + " exists';f.templates[a]=b;f[a]=function(c,d){c=c||{};var e=n.to_html(f.templates[a],c,f.partials);return d?e:i(e)}};f.addPartial=function(a,b){if(f.partials[a])throw'Partial " + name + " exists';else f.partials[a]=b};f.grabTemplates=function(){i('script[type="text/html"]').each(function(a,b){var c=i(typeof a==="number"?b:a),d="".trim?c.html().trim():
+i.trim(c.html());f[c.hasClass("partial")?"addPartial":"addTemplate"](c.attr("id"),d);c.remove()})};f.clearAll=function(){for(var a in f.templates)delete f[a];f.templates={};f.partials={}};f.refresh=function(){f.clearAll();f.grabTemplates()}};i(function(){ich.grabTemplates()})})(window.jQuery||window.Zepto);
1,158 sample/node/app/assets/js/lib/backbone-0.5.3.js
@@ -0,0 +1,1158 @@
+// Backbone.js 0.5.3
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Backbone may be freely distributed under the MIT license.
+// For all details and documentation:
+// http://documentcloud.github.com/backbone
+
+(function(){
+
+ // Initial Setup
+ // -------------
+
+ // Save a reference to the global object.
+ var root = this;
+
+ // Save the previous value of the `Backbone` variable.
+ var previousBackbone = root.Backbone;
+
+ // The top-level namespace. All public Backbone classes and modules will
+ // be attached to this. Exported for both CommonJS and the browser.
+ var Backbone;
+ if (typeof exports !== 'undefined') {
+ Backbone = exports;
+ } else {
+ Backbone = root.Backbone = {};
+ }
+
+ // Current version of the library. Keep in sync with `package.json`.
+ Backbone.VERSION = '0.5.3';
+
+ // Require Underscore, if we're on the server, and it's not already present.
+ var _ = root._;
+ if (!_ && (typeof require !== 'undefined')) _ = require('underscore')._;
+
+ // For Backbone's purposes, jQuery or Zepto owns the `$` variable.
+ var $ = root.jQuery || root.Zepto;
+
+ // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+ // to its previous owner. Returns a reference to this Backbone object.
+ Backbone.noConflict = function() {
+ root.Backbone = previousBackbone;
+ return this;
+ };
+
+ // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option will
+ // fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and set a
+ // `X-Http-Method-Override` header.
+ Backbone.emulateHTTP = false;
+
+ // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+ // `application/json` requests ... will encode the body as
+ // `application/x-www-form-urlencoded` instead and will send the model in a
+ // form param named `model`.
+ Backbone.emulateJSON = false;
+
+ // Backbone.Events
+ // -----------------
+
+ // A module that can be mixed in to *any object* in order to provide it with
+ // custom events. You may `bind` or `unbind` a callback function to an event;
+ // `trigger`-ing an event fires all callbacks in succession.
+ //
+ // var object = {};
+ // _.extend(object, Backbone.Events);
+ // object.bind('expand', function(){ alert('expanded'); });
+ // object.trigger('expand');
+ //
+ Backbone.Events = {
+
+ // Bind an event, specified by a string name, `ev`, to a `callback` function.
+ // Passing `"all"` will bind the callback to all events fired.
+ bind : function(ev, callback, context) {
+ var calls = this._callbacks || (this._callbacks = {});
+ var list = calls[ev] || (calls[ev] = []);
+ list.push([callback, context]);
+ return this;
+ },
+
+ // Remove one or many callbacks. If `callback` is null, removes all
+ // callbacks for the event. If `ev` is null, removes all bound callbacks
+ // for all events.
+ unbind : function(ev, callback) {
+ var calls;
+ if (!ev) {
+ this._callbacks = {};
+ } else if (calls = this._callbacks) {
+ if (!callback) {
+ calls[ev] = [];
+ } else {
+ var list = calls[ev];
+ if (!list) return this;
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (list[i] && callback === list[i][0]) {
+ list[i] = null;
+ break;
+ }
+ }
+ }
+ }
+ return this;
+ },
+
+ // Trigger an event, firing all bound callbacks. Callbacks are passed the
+ // same arguments as `trigger` is, apart from the event name.
+ // Listening for `"all"` passes the true event name as the first argument.
+ trigger : function(eventName) {
+ var list, calls, ev, callback, args;
+ var both = 2;
+ if (!(calls = this._callbacks)) return this;
+ while (both--) {
+ ev = both ? eventName : 'all';
+ if (list = calls[ev]) {
+ for (var i = 0, l = list.length; i < l; i++) {
+ if (!(callback = list[i])) {
+ list.splice(i, 1); i--; l--;
+ } else {
+ args = both ? Array.prototype.slice.call(arguments, 1) : arguments;
+ callback[0].apply(callback[1] || this, args);
+ }
+ }
+ }
+ }
+ return this;
+ }
+
+ };
+
+ // Backbone.Model
+ // --------------
+
+ // Create a new model, with defined attributes. A client id (`cid`)
+ // is automatically generated and assigned for you.
+ Backbone.Model = function(attributes, options) {
+ var defaults;
+ attributes || (attributes = {});
+ if (defaults = this.defaults) {
+ if (_.isFunction(defaults)) defaults = defaults.call(this);
+ attributes = _.extend({}, defaults, attributes);
+ }
+ this.attributes = {};
+ this._escapedAttributes = {};
+ this.cid = _.uniqueId('c');
+ this.set(attributes, {silent : true});
+ this._changed = false;
+ this._previousAttributes = _.clone(this.attributes);
+ if (options && options.collection) this.collection = options.collection;
+ this.initialize(attributes, options);
+ };
+
+ // Attach all inheritable methods to the Model prototype.
+ _.extend(Backbone.Model.prototype, Backbone.Events, {
+
+ // A snapshot of the model's previous attributes, taken immediately
+ // after the last `"change"` event was fired.
+ _previousAttributes : null,
+
+ // Has the item been changed since the last `"change"` event?
+ _changed : false,
+
+ // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+ // CouchDB users may want to set this to `"_id"`.
+ idAttribute : 'id',
+
+ // Initialize is an empty function by default. Override it with your own
+ // initialization logic.
+ initialize : function(){},
+
+ // Return a copy of the model's `attributes` object.
+ toJSON : function() {
+ return _.clone(this.attributes);
+ },
+
+ // Get the value of an attribute.
+ get : function(attr) {
+ return this.attributes[attr];
+ },
+
+ // Get the HTML-escaped value of an attribute.
+ escape : function(attr) {
+ var html;
+ if (html = this._escapedAttributes[attr]) return html;
+ var val = this.attributes[attr];
+ return this._escapedAttributes[attr] = escapeHTML(val == null ? '' : '' + val);
+ },
+
+ // Returns `true` if the attribute contains a value that is not null
+ // or undefined.
+ has : function(attr) {
+ return this.attributes[attr] != null;
+ },
+
+ // Set a hash of model attributes on the object, firing `"change"` unless you
+ // choose to silence it.
+ set : function(attrs, options) {
+
+ // Extract attributes and options.
+ options || (options = {});
+ if (!attrs) return this;
+ if (attrs.attributes) attrs = attrs.attributes;
+ var now = this.attributes, escaped = this._escapedAttributes;
+