├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── frontend ├── bower.json ├── bower_components │ ├── ember-data │ │ └── ember-data.js │ ├── ember-localstorage-adapter │ │ └── localstorage_adapter.js │ ├── ember │ │ └── ember.js │ ├── handlebars │ │ └── handlebars.js │ ├── jquery │ │ └── dist │ │ │ └── jquery.js │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ ├── bg.png │ │ └── bower.json ├── index.html ├── js │ ├── app.js │ ├── controllers │ │ ├── todo_controller.js │ │ ├── todos_controller.js │ │ └── todos_list_controller.js │ ├── helpers │ │ └── pluralize.js │ ├── models │ │ └── todo.js │ ├── router.js │ └── views │ │ └── todo_input_component.js ├── learn.json └── readme.md └── src ├── bin └── create_databases.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | script: cargo build 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rustmvc" 3 | version = "0.0.1" 4 | dependencies = [ 5 | "nickel 0.1.0 (git+https://github.com/nickel-org/nickel.rs.git)", 6 | "postgres 0.0.0 (git+https://github.com/sfackler/rust-postgres.git)", 7 | "r2d2_postgres 0.0.0 (git+https://github.com/sfackler/r2d2-postgres.git)", 8 | "time 0.0.2 (git+https://github.com/rust-lang/time.git)", 9 | ] 10 | 11 | [[package]] 12 | name = "anymap" 13 | version = "0.9.0" 14 | source = "git+https://github.com/nickel-org/anymap.git#c29e78c5634f9e6741287e9cfb281a73d4eddcc6" 15 | 16 | [[package]] 17 | name = "encoding" 18 | version = "0.2.1" 19 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 20 | dependencies = [ 21 | "encoding-index-japanese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding)", 22 | "encoding-index-korean 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding)", 23 | "encoding-index-simpchinese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding)", 24 | "encoding-index-singlebyte 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding)", 25 | "encoding-index-tradchinese 1.0.20140915 (git+https://github.com/lifthrasiir/rust-encoding)", 26 | ] 27 | 28 | [[package]] 29 | name = "encoding-index-japanese" 30 | version = "1.0.20140915" 31 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 32 | dependencies = [ 33 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding)", 34 | ] 35 | 36 | [[package]] 37 | name = "encoding-index-korean" 38 | version = "1.0.20140915" 39 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 40 | dependencies = [ 41 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding)", 42 | ] 43 | 44 | [[package]] 45 | name = "encoding-index-simpchinese" 46 | version = "1.0.20140915" 47 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 48 | dependencies = [ 49 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding)", 50 | ] 51 | 52 | [[package]] 53 | name = "encoding-index-singlebyte" 54 | version = "1.0.20140915" 55 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 56 | dependencies = [ 57 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding)", 58 | ] 59 | 60 | [[package]] 61 | name = "encoding-index-tradchinese" 62 | version = "1.0.20140915" 63 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 64 | dependencies = [ 65 | "encoding_index_tests 0.1.0 (git+https://github.com/lifthrasiir/rust-encoding)", 66 | ] 67 | 68 | [[package]] 69 | name = "encoding_index_tests" 70 | version = "0.1.0" 71 | source = "git+https://github.com/lifthrasiir/rust-encoding#6a5a95950572590485cbbf64509036b252339205" 72 | 73 | [[package]] 74 | name = "gcc" 75 | version = "0.0.1" 76 | source = "git+https://github.com/alexcrichton/gcc-rs#f25b3ba9c40303781189cc137fb98fffe5b56de7" 77 | 78 | [[package]] 79 | name = "groupable" 80 | version = "0.1.0" 81 | source = "git+https://github.com/nickel-org/groupable-rs#74c9e38ede02008fc04c47a33c4ba9b995dc3606" 82 | 83 | [[package]] 84 | name = "http" 85 | version = "0.1.0-pre" 86 | source = "git+https://github.com/nickel-org/rust-http.git#5b4786ca9b74434d91aab0e4fde8ed1a3aec77bd" 87 | dependencies = [ 88 | "openssl 0.0.1 (git+https://github.com/sfackler/rust-openssl)", 89 | "time 0.0.2 (git+https://github.com/rust-lang/time.git)", 90 | "url 0.1.0 (git+https://github.com/nickel-org/rust-url.git)", 91 | ] 92 | 93 | [[package]] 94 | name = "libressl-pnacl-sys" 95 | version = "2.0.0" 96 | source = "git+https://github.com/DiamondLovesYou/libressl-pnacl-sys.git#24b6800971587e8881215d9398b180c0a79739dc" 97 | dependencies = [ 98 | "pnacl-build-helper 0.0.1 (git+https://github.com/DiamondLovesYou/cargo-pnacl-helper.git)", 99 | ] 100 | 101 | [[package]] 102 | name = "nickel" 103 | version = "0.1.0" 104 | source = "git+https://github.com/nickel-org/nickel.rs.git#18d8f04bb7095ef202fba7ca3b9c3c7b3157554c" 105 | dependencies = [ 106 | "anymap 0.9.0 (git+https://github.com/nickel-org/anymap.git)", 107 | "groupable 0.1.0 (git+https://github.com/nickel-org/groupable-rs)", 108 | "http 0.1.0-pre (git+https://github.com/nickel-org/rust-http.git)", 109 | "nickel_macros 0.0.1 (git+https://github.com/nickel-org/nickel.rs.git)", 110 | "rust-mustache 0.3.0 (git+https://github.com/nickel-org/rust-mustache.git)", 111 | "time 0.0.2 (git+https://github.com/rust-lang/time.git)", 112 | "url 0.1.0 (git+https://github.com/nickel-org/rust-url.git)", 113 | ] 114 | 115 | [[package]] 116 | name = "nickel_macros" 117 | version = "0.0.1" 118 | source = "git+https://github.com/nickel-org/nickel.rs.git#18d8f04bb7095ef202fba7ca3b9c3c7b3157554c" 119 | 120 | [[package]] 121 | name = "openssl" 122 | version = "0.0.1" 123 | source = "git+https://github.com/sfackler/rust-openssl#3e98880fe8cdeaccd3c08e423bc6ce7a211bae0a" 124 | dependencies = [ 125 | "libressl-pnacl-sys 2.0.0 (git+https://github.com/DiamondLovesYou/libressl-pnacl-sys.git)", 126 | "openssl-sys 0.0.1 (git+https://github.com/sfackler/rust-openssl)", 127 | ] 128 | 129 | [[package]] 130 | name = "openssl-sys" 131 | version = "0.0.1" 132 | source = "git+https://github.com/sfackler/rust-openssl#3e98880fe8cdeaccd3c08e423bc6ce7a211bae0a" 133 | dependencies = [ 134 | "pkg-config 0.0.1 (git+https://github.com/alexcrichton/pkg-config-rs)", 135 | ] 136 | 137 | [[package]] 138 | name = "phf" 139 | version = "0.0.0" 140 | source = "git+https://github.com/sfackler/rust-phf#aa3e2d0aedea4d55c2e4cea1bdf6e89418dc1206" 141 | dependencies = [ 142 | "xxhash 0.0.1 (git+https://github.com/Jurily/rust-xxhash)", 143 | ] 144 | 145 | [[package]] 146 | name = "phf_mac" 147 | version = "0.0.0" 148 | source = "git+https://github.com/sfackler/rust-phf#aa3e2d0aedea4d55c2e4cea1bdf6e89418dc1206" 149 | dependencies = [ 150 | "time 0.0.2 (git+https://github.com/rust-lang/time.git)", 151 | "xxhash 0.0.1 (git+https://github.com/Jurily/rust-xxhash)", 152 | ] 153 | 154 | [[package]] 155 | name = "pkg-config" 156 | version = "0.0.1" 157 | source = "git+https://github.com/alexcrichton/pkg-config-rs#d24a08d87d63df8dc9526c503944415b86719220" 158 | 159 | [[package]] 160 | name = "pnacl-build-helper" 161 | version = "0.0.1" 162 | source = "git+https://github.com/DiamondLovesYou/cargo-pnacl-helper.git#5402d48d242d11bdd61f801655fca364155c4511" 163 | 164 | [[package]] 165 | name = "postgres" 166 | version = "0.0.0" 167 | source = "git+https://github.com/sfackler/rust-postgres.git#3a0f1897277d0e1f5568e733854649f1e7fd018d" 168 | dependencies = [ 169 | "openssl 0.0.1 (git+https://github.com/sfackler/rust-openssl)", 170 | "phf 0.0.0 (git+https://github.com/sfackler/rust-phf)", 171 | "phf_mac 0.0.0 (git+https://github.com/sfackler/rust-phf)", 172 | "time 0.0.2 (git+https://github.com/rust-lang/time.git)", 173 | "uuid 0.0.2 (git+https://github.com/rust-lang/uuid)", 174 | ] 175 | 176 | [[package]] 177 | name = "r2d2" 178 | version = "0.0.0" 179 | source = "git+https://github.com/sfackler/r2d2#8337055e2a5a2db9151ab62ea6d2e7e401d1c0d5" 180 | 181 | [[package]] 182 | name = "r2d2_postgres" 183 | version = "0.0.0" 184 | source = "git+https://github.com/sfackler/r2d2-postgres.git#b624982db9068932f39b3a618ade1ea3dcc7d5ca" 185 | dependencies = [ 186 | "postgres 0.0.0 (git+https://github.com/sfackler/rust-postgres.git)", 187 | "r2d2 0.0.0 (git+https://github.com/sfackler/r2d2)", 188 | ] 189 | 190 | [[package]] 191 | name = "rust-mustache" 192 | version = "0.3.0" 193 | source = "git+https://github.com/nickel-org/rust-mustache.git#6f98a76ba46068f04c14b942a182e29eea24350e" 194 | 195 | [[package]] 196 | name = "time" 197 | version = "0.0.2" 198 | source = "git+https://github.com/rust-lang/time.git#b58210c93a44d7b83a1a9b68b462f5a7fa1942f3" 199 | dependencies = [ 200 | "gcc 0.0.1 (git+https://github.com/alexcrichton/gcc-rs)", 201 | ] 202 | 203 | [[package]] 204 | name = "url" 205 | version = "0.1.0" 206 | source = "git+https://github.com/nickel-org/rust-url.git#f5fdbe5741ff75d87ef05b468097432c012c3e6e" 207 | dependencies = [ 208 | "encoding 0.2.1 (git+https://github.com/lifthrasiir/rust-encoding)", 209 | ] 210 | 211 | [[package]] 212 | name = "uuid" 213 | version = "0.0.2" 214 | source = "git+https://github.com/rust-lang/uuid#410aa2f519a66431214282c1d74d9f95316fb0ff" 215 | 216 | [[package]] 217 | name = "xxhash" 218 | version = "0.0.1" 219 | source = "git+https://github.com/Jurily/rust-xxhash#6027ce91f9164f2f936149547f789103b1b671e6" 220 | 221 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "rustmvc" 4 | version = "0.0.1" 5 | authors = ["Steve Klabnik ", "Josef Pospíšil "] 6 | 7 | [dependencies.nickel] 8 | 9 | git = "https://github.com/nickel-org/nickel.rs.git" 10 | 11 | [dependencies.postgres] 12 | 13 | git = "https://github.com/sfackler/rust-postgres.git" 14 | 15 | [dependencies.r2d2_postgres] 16 | 17 | git = "https://github.com/sfackler/r2d2-postgres.git" 18 | 19 | [dependencies.time] 20 | 21 | git = "https://github.com/rust-lang/time.git" 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustmvc 2 | 3 | [![Build Status](https://travis-ci.org/steveklabnik/rustmvc.svg)](https://travis-ci.org/steveklabnik/rustmvc) 4 | 5 | This is a [TodoMVC](http://todomvc.com/) implementation, using 6 | [Ember](http://emberjs.com/) on the front end and 7 | [Nickel.rs](https://nickel-org.github.io/) on the back end. 8 | 9 | In other words: Rust + Ember, sitting in a tree. K I S... 10 | 11 | ## Getting started 12 | 13 | You'll need a postgres instance running on localhost, with a 14 | `rustmvc` user and a `rustmvc` database: 15 | 16 | ```bash 17 | $ createrole rustmvc 18 | $ createdb rustmvc 19 | ``` 20 | 21 | When you've got that going... 22 | 23 | ```bash 24 | $ git clone https://github.com/steveklabnik/rustmvc 25 | $ cd rustmvc 26 | $ cargo build 27 | $ ./target/create_databases # you only need this the first time 28 | $ ./target/rustmvc 29 | $ firefox http://localhost:6767/ # in a different shell, of course 30 | $ curl --data "{\"title\":\"Title ha ha ha\",\"is_completed\":false}" http://localhost:6767/todos 31 | ``` 32 | -------------------------------------------------------------------------------- /frontend/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-emberjs", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "todomvc-common": "~0.1.4", 6 | "jquery": "~2.1.0", 7 | "handlebars": "~1.3.0", 8 | "ember": "~1.6.0", 9 | "ember-data": "1.0.0-beta.7", 10 | "ember-localstorage-adapter": "latest" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /frontend/bower_components/ember-localstorage-adapter/localstorage_adapter.js: -------------------------------------------------------------------------------- 1 | /*global Ember*/ 2 | /*global DS*/ 3 | (function () { 4 | 'use strict'; 5 | 6 | DS.LSSerializer = DS.JSONSerializer.extend({ 7 | 8 | serializeHasMany: function(record, json, relationship) { 9 | var key = relationship.key, 10 | relationshipType = DS.RelationshipChange.determineRelationshipType(record.constructor, relationship); 11 | 12 | if (relationshipType === 'manyToNone' || 13 | relationshipType === 'manyToMany' || 14 | relationshipType === 'manyToOne') { 15 | json[key] = record.get(key).mapBy('id'); 16 | // TODO support for polymorphic manyToNone and manyToMany relationships 17 | } 18 | }, 19 | 20 | /** 21 | * Extracts whatever was returned from the adapter. 22 | * 23 | * If the adapter returns relationships in an embedded way, such as follows: 24 | * 25 | * ```js 26 | * { 27 | * "id": 1, 28 | * "title": "Rails Rambo", 29 | * 30 | * "_embedded": { 31 | * "comment": [{ 32 | * "id": 1, 33 | * "comment_title": "FIRST" 34 | * }, { 35 | * "id": 2, 36 | * "comment_title": "Rails is unagi" 37 | * }] 38 | * } 39 | * } 40 | * 41 | * this method will create separated JSON for each resource and then push 42 | * them individually to the Store. 43 | * 44 | * In the end, only the main resource will remain, containing the ids of its 45 | * relationships. Given the relations are already in the Store, we will 46 | * return a JSON with the main resource alone. The Store will sort out the 47 | * associations by itself. 48 | * 49 | * @method extractSingle 50 | * @private 51 | * @param {DS.Store} store the returned store 52 | * @param {DS.Model} type the type/model 53 | * @param {Object} payload returned JSON 54 | */ 55 | extractSingle: function(store, type, payload) { 56 | if (payload && payload._embedded) { 57 | for (var relation in payload._embedded) { 58 | var relType = type.typeForRelationship(relation); 59 | var typeName = relType.typeKey, 60 | embeddedPayload = payload._embedded[relation]; 61 | 62 | if (embeddedPayload) { 63 | if (Ember.isArray(embeddedPayload)) { 64 | store.pushMany(typeName, embeddedPayload); 65 | } else { 66 | store.push(typeName, embeddedPayload); 67 | } 68 | } 69 | } 70 | 71 | delete payload._embedded; 72 | } 73 | 74 | return this.normalize(type, payload); 75 | }, 76 | 77 | /** 78 | * This is exactly the same as extractSingle, but used in an array. 79 | * 80 | * @method extractSingle 81 | * @private 82 | * @param {DS.Store} store the returned store 83 | * @param {DS.Model} type the type/model 84 | * @param {Array} payload returned JSONs 85 | */ 86 | extractArray: function(store, type, payload) { 87 | var serializer = this; 88 | 89 | return payload.map(function(record) { 90 | var extracted = serializer.extractSingle(store, type, record); 91 | return serializer.normalize(type, record); 92 | }); 93 | } 94 | 95 | }); 96 | 97 | DS.LSAdapter = DS.Adapter.extend(Ember.Evented, { 98 | /** 99 | This is the main entry point into finding records. The first parameter to 100 | this method is the model's name as a string. 101 | 102 | @method find 103 | @param {DS.Model} type 104 | @param {Object|String|Integer|null} id 105 | */ 106 | find: function(store, type, id, opts) { 107 | var adapter = this; 108 | var allowRecursive = true; 109 | var namespace = this._namespaceForType(type); 110 | 111 | /** 112 | * In the case where there are relationships, this method is called again 113 | * for each relation. Given the relations have references to the main 114 | * object, we use allowRecursive to avoid going further into infinite 115 | * recursiveness. 116 | * 117 | * Concept from ember-indexdb-adapter 118 | */ 119 | if (opts && typeof opts.allowRecursive !== 'undefined') { 120 | allowRecursive = opts.allowRecursive; 121 | } 122 | 123 | return new Ember.RSVP.Promise(function(resolve, reject) { 124 | var record = Ember.A(namespace.records[id]); 125 | 126 | if (!record || !record.hasOwnProperty('id')) { 127 | store.dematerializeRecord(store.typeMapFor(type).idToRecord[id]); 128 | reject(); 129 | return; 130 | } 131 | 132 | if (allowRecursive) { 133 | adapter.loadRelationships(type, record).then(function(finalRecord) { 134 | resolve(finalRecord); 135 | }); 136 | } else { 137 | resolve(record); 138 | } 139 | }); 140 | }, 141 | 142 | findMany: function (store, type, ids) { 143 | var adapter = this; 144 | var namespace = this._namespaceForType(type); 145 | 146 | return new Ember.RSVP.Promise(function(resolve, reject) { 147 | var results = []; 148 | 149 | for (var i = 0; i < ids.length; i++) { 150 | results.push(Ember.copy(namespace.records[ids[i]])); 151 | } 152 | 153 | resolve(results); 154 | }).then(function(records) { 155 | if (records.get('length')) { 156 | return adapter.loadRelationshipsForMany(type, records); 157 | } else { 158 | return records; 159 | } 160 | }); 161 | }, 162 | 163 | // Supports queries that look like this: 164 | // 165 | // { 166 | // : , 167 | // ... 168 | // } 169 | // 170 | // Every property added to the query is an "AND" query, not "OR" 171 | // 172 | // Example: 173 | // 174 | // match records with "complete: true" and the name "foo" or "bar" 175 | // 176 | // { complete: true, name: /foo|bar/ } 177 | findQuery: function (store, type, query, recordArray) { 178 | var namespace = this._namespaceForType(type), 179 | results = this.query(namespace.records, query); 180 | 181 | if (results.get('length')) { 182 | results = this.loadRelationshipsForMany(type, results); 183 | return Ember.RSVP.resolve(results); 184 | } else { 185 | return Ember.RSVP.reject(); 186 | } 187 | }, 188 | 189 | query: function (records, query) { 190 | var results = [], 191 | id, record, property, test, push; 192 | for (id in records) { 193 | record = records[id]; 194 | for (property in query) { 195 | test = query[property]; 196 | push = false; 197 | if (Object.prototype.toString.call(test) === '[object RegExp]') { 198 | push = test.test(record[property]); 199 | } else { 200 | push = record[property] === test; 201 | } 202 | } 203 | if (push) { 204 | results.push(record); 205 | } 206 | } 207 | return results; 208 | }, 209 | 210 | findAll: function (store, type) { 211 | var namespace = this._namespaceForType(type), 212 | results = []; 213 | 214 | for (var id in namespace.records) { 215 | results.push(Ember.copy(namespace.records[id])); 216 | } 217 | return Ember.RSVP.resolve(results); 218 | }, 219 | 220 | createRecord: function (store, type, record) { 221 | var namespaceRecords = this._namespaceForType(type), 222 | recordHash = record.serialize({includeId: true}); 223 | 224 | namespaceRecords.records[recordHash.id] = recordHash; 225 | 226 | this.persistData(type, namespaceRecords); 227 | return Ember.RSVP.resolve(); 228 | }, 229 | 230 | updateRecord: function (store, type, record) { 231 | var namespaceRecords = this._namespaceForType(type), 232 | id = record.get('id'); 233 | 234 | namespaceRecords.records[id] = record.serialize({ includeId: true }); 235 | 236 | this.persistData(type, namespaceRecords); 237 | return Ember.RSVP.resolve(); 238 | }, 239 | 240 | deleteRecord: function (store, type, record) { 241 | var namespaceRecords = this._namespaceForType(type), 242 | id = record.get('id'); 243 | 244 | delete namespaceRecords.records[id]; 245 | 246 | this.persistData(type, namespaceRecords); 247 | return Ember.RSVP.resolve(); 248 | }, 249 | 250 | generateIdForRecord: function () { 251 | return Math.random().toString(32).slice(2).substr(0, 5); 252 | }, 253 | 254 | // private 255 | 256 | adapterNamespace: function () { 257 | return this.get('namespace') || 'DS.LSAdapter'; 258 | }, 259 | 260 | loadData: function () { 261 | var storage = localStorage.getItem(this.adapterNamespace()); 262 | return storage ? JSON.parse(storage) : {}; 263 | }, 264 | 265 | persistData: function(type, data) { 266 | var modelNamespace = this.modelNamespace(type), 267 | localStorageData = this.loadData(); 268 | 269 | localStorageData[modelNamespace] = data; 270 | 271 | localStorage.setItem(this.adapterNamespace(), JSON.stringify(localStorageData)); 272 | }, 273 | 274 | _namespaceForType: function (type) { 275 | var namespace = this.modelNamespace(type), 276 | storage = localStorage.getItem(this.adapterNamespace()); 277 | 278 | return storage ? JSON.parse(storage)[namespace] || {records: {}} : {records: {}}; 279 | }, 280 | 281 | modelNamespace: function(type) { 282 | return type.url || type.typeKey; 283 | }, 284 | 285 | 286 | /** 287 | * This takes a record, then analyzes the model relationships and replaces 288 | * ids with the actual values. 289 | * 290 | * Stolen from ember-indexdb-adapter 291 | * 292 | * Consider the following JSON is entered: 293 | * 294 | * ```js 295 | * { 296 | * "id": 1, 297 | * "title": "Rails Rambo", 298 | * "comments": [1, 2] 299 | * } 300 | * 301 | * This will return: 302 | * 303 | * ```js 304 | * { 305 | * "id": 1, 306 | * "title": "Rails Rambo", 307 | * "comments": [1, 2] 308 | * 309 | * "_embedded": { 310 | * "comment": [{ 311 | * "_id": 1, 312 | * "comment_title": "FIRST" 313 | * }, { 314 | * "_id": 2, 315 | * "comment_title": "Rails is unagi" 316 | * }] 317 | * } 318 | * } 319 | * 320 | * This way, whenever a resource returned, its relationships will be also 321 | * returned. 322 | * 323 | * @method loadRelationships 324 | * @private 325 | * @param {DS.Model} type 326 | * @param {Object} record 327 | */ 328 | loadRelationships: function(type, record) { 329 | var adapter = this; 330 | 331 | return new Ember.RSVP.Promise(function(resolve, reject) { 332 | var resultJSON = {}, 333 | typeKey = type.typeKey, 334 | relationshipNames, relationships, 335 | relationshipPromises = []; 336 | 337 | relationshipNames = Ember.get(type, 'relationshipNames'); 338 | relationships = relationshipNames.belongsTo; 339 | relationships = relationships.concat(relationshipNames.hasMany); 340 | 341 | relationships.forEach(function(relationName) { 342 | var relationModel = type.typeForRelationship(relationName), 343 | relationEmbeddedId = record[relationName], 344 | relationProp = adapter.relationshipProperties(type, relationName), 345 | relationType = relationProp.kind, 346 | /** 347 | * This is the relationship field. 348 | */ 349 | promise, embedPromise; 350 | 351 | var opts = {allowRecursive: false}; 352 | 353 | /** 354 | * embeddedIds are ids of relations that are included in the main 355 | * payload, such as: 356 | * 357 | * { 358 | * cart: { 359 | * id: "s85fb", 360 | * customer: "rld9u" 361 | * } 362 | * } 363 | * 364 | * In this case, cart belongsTo customer and its id is present in the 365 | * main payload. We find each of these records and add them to _embedded. 366 | */ 367 | if (relationEmbeddedId) { 368 | if (relationType == 'belongsTo' || relationType == 'hasOne') { 369 | promise = adapter.find(null, relationModel, relationEmbeddedId, opts) 370 | } else if (relationType == 'hasMany') { 371 | promise = adapter.findMany(null, relationModel, relationEmbeddedId, opts) 372 | } 373 | 374 | embedPromise = new Ember.RSVP.Promise(function(resolve, reject) { 375 | promise.then(function(relationRecord) { 376 | var finalPayload = adapter.addEmbeddedPayload(record, relationName, relationRecord) 377 | resolve(finalPayload); 378 | }); 379 | }); 380 | 381 | relationshipPromises.push(embedPromise); 382 | } 383 | }); 384 | 385 | Ember.RSVP.all(relationshipPromises).then(function() { 386 | resolve(record); 387 | }); 388 | }); 389 | }, 390 | 391 | 392 | /** 393 | * Given the following payload, 394 | * 395 | * { 396 | * cart: { 397 | * id: "1", 398 | * customer: "2" 399 | * } 400 | * } 401 | * 402 | * With `relationshipName` being `customer` and `relationshipRecord` 403 | * 404 | * {id: "2", name: "Rambo"} 405 | * 406 | * This method returns the following payload: 407 | * 408 | * { 409 | * cart: { 410 | * id: "1", 411 | * customer: "2" 412 | * }, 413 | * _embedded: { 414 | * customer: { 415 | * id: "2", 416 | * name: "Rambo" 417 | * } 418 | * } 419 | * } 420 | * 421 | * which is then treated by the serializer later. 422 | * 423 | * @method addEmbeddedPayload 424 | * @private 425 | * @param {Object} payload 426 | * @param {String} relationshipName 427 | * @param {Object} relationshipRecord 428 | */ 429 | addEmbeddedPayload: function(payload, relationshipName, relationshipRecord) { 430 | var objectHasId = (relationshipRecord && relationshipRecord.id), 431 | arrayHasIds = (relationshipRecord.length && relationshipRecord.everyBy("id")), 432 | isValidRelationship = (objectHasId || arrayHasIds); 433 | 434 | if (isValidRelationship) { 435 | if (!payload['_embedded']) { 436 | payload['_embedded'] = {} 437 | } 438 | 439 | payload['_embedded'][relationshipName] = relationshipRecord; 440 | if (relationshipRecord.length) { 441 | payload[relationshipName] = relationshipRecord.mapBy('id'); 442 | } else { 443 | payload[relationshipName] = relationshipRecord.id; 444 | } 445 | } 446 | 447 | if (this.isArray(payload[relationshipName])) { 448 | payload[relationshipName] = payload[relationshipName].filter(function(id) { 449 | return id; 450 | }); 451 | } 452 | 453 | return payload; 454 | }, 455 | 456 | 457 | isArray: function(value) { 458 | return Object.prototype.toString.call(value) === '[object Array]'; 459 | }, 460 | 461 | /** 462 | * Same as `loadRelationships`, but for an array of records. 463 | * 464 | * @method loadRelationshipsForMany 465 | * @private 466 | * @param {DS.Model} type 467 | * @param {Object} recordsArray 468 | */ 469 | loadRelationshipsForMany: function(type, recordsArray) { 470 | var adapter = this; 471 | 472 | return new Ember.RSVP.Promise(function(resolve, reject) { 473 | var recordsWithRelationships = [], 474 | recordsToBeLoaded = [], 475 | promises = []; 476 | 477 | /** 478 | * Some times Ember puts some stuff in arrays. We want to clean it so 479 | * we know exactly what to iterate over. 480 | */ 481 | for (var i in recordsArray) { 482 | if (recordsArray.hasOwnProperty(i)) { 483 | recordsToBeLoaded.push(recordsArray[i]); 484 | } 485 | } 486 | 487 | var loadNextRecord = function(record) { 488 | /** 489 | * Removes the first item from recordsToBeLoaded 490 | */ 491 | recordsToBeLoaded = recordsToBeLoaded.slice(1); 492 | 493 | var promise = adapter.loadRelationships(type, record); 494 | 495 | promise.then(function(recordWithRelationships) { 496 | recordsWithRelationships.push(recordWithRelationships); 497 | 498 | if (recordsToBeLoaded[0]) { 499 | loadNextRecord(recordsToBeLoaded[0]); 500 | } else { 501 | resolve(recordsWithRelationships); 502 | } 503 | }); 504 | } 505 | 506 | /** 507 | * We start by the first record 508 | */ 509 | loadNextRecord(recordsToBeLoaded[0]); 510 | }); 511 | }, 512 | 513 | 514 | /** 515 | * 516 | * @method relationshipProperties 517 | * @private 518 | * @param {DS.Model} type 519 | * @param {String} relationName 520 | */ 521 | relationshipProperties: function(type, relationName) { 522 | var relationships = Ember.get(type, 'relationshipsByName'); 523 | if (relationName) { 524 | return relationships.get(relationName); 525 | } else { 526 | return relationships; 527 | } 528 | } 529 | }); 530 | }()); 531 | -------------------------------------------------------------------------------- /frontend/bower_components/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #header { 91 | padding-top: 15px; 92 | border-radius: inherit; 93 | } 94 | 95 | #header:before { 96 | content: ''; 97 | position: absolute; 98 | top: 0; 99 | right: 0; 100 | left: 0; 101 | height: 15px; 102 | z-index: 2; 103 | border-bottom: 1px solid #6c615c; 104 | background: #8d7d77; 105 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 106 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 107 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 108 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 109 | border-top-left-radius: 1px; 110 | border-top-right-radius: 1px; 111 | } 112 | 113 | #new-todo, 114 | .edit { 115 | position: relative; 116 | margin: 0; 117 | width: 100%; 118 | font-size: 24px; 119 | font-family: inherit; 120 | line-height: 1.4em; 121 | border: 0; 122 | outline: none; 123 | color: inherit; 124 | padding: 6px; 125 | border: 1px solid #999; 126 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 127 | -moz-box-sizing: border-box; 128 | -ms-box-sizing: border-box; 129 | -o-box-sizing: border-box; 130 | box-sizing: border-box; 131 | -webkit-font-smoothing: antialiased; 132 | -moz-font-smoothing: antialiased; 133 | -ms-font-smoothing: antialiased; 134 | -o-font-smoothing: antialiased; 135 | font-smoothing: antialiased; 136 | } 137 | 138 | #new-todo { 139 | padding: 16px 16px 16px 60px; 140 | border: none; 141 | background: rgba(0, 0, 0, 0.02); 142 | z-index: 2; 143 | box-shadow: none; 144 | } 145 | 146 | #main { 147 | position: relative; 148 | z-index: 2; 149 | border-top: 1px dotted #adadad; 150 | } 151 | 152 | label[for='toggle-all'] { 153 | display: none; 154 | } 155 | 156 | #toggle-all { 157 | position: absolute; 158 | top: -42px; 159 | left: -4px; 160 | width: 40px; 161 | text-align: center; 162 | /* Mobile Safari */ 163 | border: none; 164 | } 165 | 166 | #toggle-all:before { 167 | content: '»'; 168 | font-size: 28px; 169 | color: #d9d9d9; 170 | padding: 0 25px 7px; 171 | } 172 | 173 | #toggle-all:checked:before { 174 | color: #737373; 175 | } 176 | 177 | #todo-list { 178 | margin: 0; 179 | padding: 0; 180 | list-style: none; 181 | } 182 | 183 | #todo-list li { 184 | position: relative; 185 | font-size: 24px; 186 | border-bottom: 1px dotted #ccc; 187 | } 188 | 189 | #todo-list li:last-child { 190 | border-bottom: none; 191 | } 192 | 193 | #todo-list li.editing { 194 | border-bottom: none; 195 | padding: 0; 196 | } 197 | 198 | #todo-list li.editing .edit { 199 | display: block; 200 | width: 506px; 201 | padding: 13px 17px 12px 17px; 202 | margin: 0 0 0 43px; 203 | } 204 | 205 | #todo-list li.editing .view { 206 | display: none; 207 | } 208 | 209 | #todo-list li .toggle { 210 | text-align: center; 211 | width: 40px; 212 | /* auto, since non-WebKit browsers doesn't support input styling */ 213 | height: auto; 214 | position: absolute; 215 | top: 0; 216 | bottom: 0; 217 | margin: auto 0; 218 | /* Mobile Safari */ 219 | border: none; 220 | -webkit-appearance: none; 221 | -ms-appearance: none; 222 | -o-appearance: none; 223 | appearance: none; 224 | } 225 | 226 | #todo-list li .toggle:after { 227 | content: '✔'; 228 | /* 40 + a couple of pixels visual adjustment */ 229 | line-height: 43px; 230 | font-size: 20px; 231 | color: #d9d9d9; 232 | text-shadow: 0 -1px 0 #bfbfbf; 233 | } 234 | 235 | #todo-list li .toggle:checked:after { 236 | color: #85ada7; 237 | text-shadow: 0 1px 0 #669991; 238 | bottom: 1px; 239 | position: relative; 240 | } 241 | 242 | #todo-list li label { 243 | white-space: pre; 244 | word-break: break-word; 245 | padding: 15px 60px 15px 15px; 246 | margin-left: 45px; 247 | display: block; 248 | line-height: 1.2; 249 | -webkit-transition: color 0.4s; 250 | transition: color 0.4s; 251 | } 252 | 253 | #todo-list li.completed label { 254 | color: #a9a9a9; 255 | text-decoration: line-through; 256 | } 257 | 258 | #todo-list li .destroy { 259 | display: none; 260 | position: absolute; 261 | top: 0; 262 | right: 10px; 263 | bottom: 0; 264 | width: 40px; 265 | height: 40px; 266 | margin: auto 0; 267 | font-size: 22px; 268 | color: #a88a8a; 269 | -webkit-transition: all 0.2s; 270 | transition: all 0.2s; 271 | } 272 | 273 | #todo-list li .destroy:hover { 274 | text-shadow: 0 0 1px #000, 275 | 0 0 10px rgba(199, 107, 107, 0.8); 276 | -webkit-transform: scale(1.3); 277 | transform: scale(1.3); 278 | } 279 | 280 | #todo-list li .destroy:after { 281 | content: '✖'; 282 | } 283 | 284 | #todo-list li:hover .destroy { 285 | display: block; 286 | } 287 | 288 | #todo-list li .edit { 289 | display: none; 290 | } 291 | 292 | #todo-list li.editing:last-child { 293 | margin-bottom: -1px; 294 | } 295 | 296 | #footer { 297 | color: #777; 298 | padding: 0 15px; 299 | position: absolute; 300 | right: 0; 301 | bottom: -31px; 302 | left: 0; 303 | height: 20px; 304 | z-index: 1; 305 | text-align: center; 306 | } 307 | 308 | #footer:before { 309 | content: ''; 310 | position: absolute; 311 | right: 0; 312 | bottom: 31px; 313 | left: 0; 314 | height: 50px; 315 | z-index: -1; 316 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 317 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 318 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 319 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 320 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 321 | } 322 | 323 | #todo-count { 324 | float: left; 325 | text-align: left; 326 | } 327 | 328 | #filters { 329 | margin: 0; 330 | padding: 0; 331 | list-style: none; 332 | position: absolute; 333 | right: 0; 334 | left: 0; 335 | } 336 | 337 | #filters li { 338 | display: inline; 339 | } 340 | 341 | #filters li a { 342 | color: #83756f; 343 | margin: 2px; 344 | text-decoration: none; 345 | } 346 | 347 | #filters li a.selected { 348 | font-weight: bold; 349 | } 350 | 351 | #clear-completed { 352 | float: right; 353 | position: relative; 354 | line-height: 20px; 355 | text-decoration: none; 356 | background: rgba(0, 0, 0, 0.1); 357 | font-size: 11px; 358 | padding: 0 10px; 359 | border-radius: 3px; 360 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 361 | } 362 | 363 | #clear-completed:hover { 364 | background: rgba(0, 0, 0, 0.15); 365 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 366 | } 367 | 368 | #info { 369 | margin: 65px auto 0; 370 | color: #a6a6a6; 371 | font-size: 12px; 372 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 373 | text-align: center; 374 | } 375 | 376 | #info a { 377 | color: inherit; 378 | } 379 | 380 | /* 381 | Hack to remove background from Mobile Safari. 382 | Can't use it globally since it destroys checkboxes in Firefox and Opera 383 | */ 384 | 385 | @media screen and (-webkit-min-device-pixel-ratio:0) { 386 | #toggle-all, 387 | #todo-list li .toggle { 388 | background: none; 389 | } 390 | 391 | #todo-list li .toggle { 392 | height: 40px; 393 | } 394 | 395 | #toggle-all { 396 | top: -56px; 397 | left: -15px; 398 | width: 65px; 399 | height: 41px; 400 | -webkit-transform: rotate(90deg); 401 | transform: rotate(90deg); 402 | -webkit-appearance: none; 403 | appearance: none; 404 | } 405 | } 406 | 407 | .hidden { 408 | display: none; 409 | } 410 | 411 | hr { 412 | margin: 20px 0; 413 | border: 0; 414 | border-top: 1px dashed #C5C5C5; 415 | border-bottom: 1px dashed #F7F7F7; 416 | } 417 | 418 | .learn a { 419 | font-weight: normal; 420 | text-decoration: none; 421 | color: #b83f45; 422 | } 423 | 424 | .learn a:hover { 425 | text-decoration: underline; 426 | color: #787e7e; 427 | } 428 | 429 | .learn h3, 430 | .learn h4, 431 | .learn h5 { 432 | margin: 10px 0; 433 | font-weight: 500; 434 | line-height: 1.2; 435 | color: #000; 436 | } 437 | 438 | .learn h3 { 439 | font-size: 24px; 440 | } 441 | 442 | .learn h4 { 443 | font-size: 18px; 444 | } 445 | 446 | .learn h5 { 447 | margin-bottom: 0; 448 | font-size: 14px; 449 | } 450 | 451 | .learn ul { 452 | padding: 0; 453 | margin: 0 0 30px 25px; 454 | } 455 | 456 | .learn li { 457 | line-height: 20px; 458 | } 459 | 460 | .learn p { 461 | font-size: 15px; 462 | font-weight: 300; 463 | line-height: 1.3; 464 | margin-top: 0; 465 | margin-bottom: 0; 466 | } 467 | 468 | .quote { 469 | border: none; 470 | margin: 20px 0 60px 0; 471 | } 472 | 473 | .quote p { 474 | font-style: italic; 475 | } 476 | 477 | .quote p:before { 478 | content: '“'; 479 | font-size: 50px; 480 | opacity: .15; 481 | position: absolute; 482 | top: -20px; 483 | left: 3px; 484 | } 485 | 486 | .quote p:after { 487 | content: '”'; 488 | font-size: 50px; 489 | opacity: .15; 490 | position: absolute; 491 | bottom: -42px; 492 | right: 3px; 493 | } 494 | 495 | .quote footer { 496 | position: absolute; 497 | bottom: -40px; 498 | right: 0; 499 | } 500 | 501 | .quote footer img { 502 | border-radius: 3px; 503 | } 504 | 505 | .quote footer a { 506 | margin-left: 5px; 507 | vertical-align: middle; 508 | } 509 | 510 | .speech-bubble { 511 | position: relative; 512 | padding: 10px; 513 | background: rgba(0, 0, 0, .04); 514 | border-radius: 5px; 515 | } 516 | 517 | .speech-bubble:after { 518 | content: ''; 519 | position: absolute; 520 | top: 100%; 521 | right: 30px; 522 | border: 13px solid transparent; 523 | border-top-color: rgba(0, 0, 0, .04); 524 | } 525 | 526 | .learn-bar > .learn { 527 | position: absolute; 528 | width: 272px; 529 | top: 8px; 530 | left: -300px; 531 | padding: 10px; 532 | border-radius: 5px; 533 | background-color: rgba(255, 255, 255, .6); 534 | -webkit-transition-property: left; 535 | transition-property: left; 536 | -webkit-transition-duration: 500ms; 537 | transition-duration: 500ms; 538 | } 539 | 540 | @media (min-width: 899px) { 541 | .learn-bar { 542 | width: auto; 543 | margin: 0 0 0 300px; 544 | } 545 | 546 | .learn-bar > .learn { 547 | left: 8px; 548 | } 549 | 550 | .learn-bar #todoapp { 551 | width: 550px; 552 | margin: 130px auto 40px auto; 553 | } 554 | } 555 | -------------------------------------------------------------------------------- /frontend/bower_components/todomvc-common/base.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | // Underscore's Template Module 5 | // Courtesy of underscorejs.org 6 | var _ = (function (_) { 7 | _.defaults = function (object) { 8 | if (!object) { 9 | return object; 10 | } 11 | for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) { 12 | var iterable = arguments[argsIndex]; 13 | if (iterable) { 14 | for (var key in iterable) { 15 | if (object[key] == null) { 16 | object[key] = iterable[key]; 17 | } 18 | } 19 | } 20 | } 21 | return object; 22 | } 23 | 24 | // By default, Underscore uses ERB-style template delimiters, change the 25 | // following template settings to use alternative delimiters. 26 | _.templateSettings = { 27 | evaluate : /<%([\s\S]+?)%>/g, 28 | interpolate : /<%=([\s\S]+?)%>/g, 29 | escape : /<%-([\s\S]+?)%>/g 30 | }; 31 | 32 | // When customizing `templateSettings`, if you don't want to define an 33 | // interpolation, evaluation or escaping regex, we need one that is 34 | // guaranteed not to match. 35 | var noMatch = /(.)^/; 36 | 37 | // Certain characters need to be escaped so that they can be put into a 38 | // string literal. 39 | var escapes = { 40 | "'": "'", 41 | '\\': '\\', 42 | '\r': 'r', 43 | '\n': 'n', 44 | '\t': 't', 45 | '\u2028': 'u2028', 46 | '\u2029': 'u2029' 47 | }; 48 | 49 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 50 | 51 | // JavaScript micro-templating, similar to John Resig's implementation. 52 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 53 | // and correctly escapes quotes within interpolated code. 54 | _.template = function(text, data, settings) { 55 | var render; 56 | settings = _.defaults({}, settings, _.templateSettings); 57 | 58 | // Combine delimiters into one regular expression via alternation. 59 | var matcher = new RegExp([ 60 | (settings.escape || noMatch).source, 61 | (settings.interpolate || noMatch).source, 62 | (settings.evaluate || noMatch).source 63 | ].join('|') + '|$', 'g'); 64 | 65 | // Compile the template source, escaping string literals appropriately. 66 | var index = 0; 67 | var source = "__p+='"; 68 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 69 | source += text.slice(index, offset) 70 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 71 | 72 | if (escape) { 73 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 74 | } 75 | if (interpolate) { 76 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 77 | } 78 | if (evaluate) { 79 | source += "';\n" + evaluate + "\n__p+='"; 80 | } 81 | index = offset + match.length; 82 | return match; 83 | }); 84 | source += "';\n"; 85 | 86 | // If a variable is not specified, place data values in local scope. 87 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 88 | 89 | source = "var __t,__p='',__j=Array.prototype.join," + 90 | "print=function(){__p+=__j.call(arguments,'');};\n" + 91 | source + "return __p;\n"; 92 | 93 | try { 94 | render = new Function(settings.variable || 'obj', '_', source); 95 | } catch (e) { 96 | e.source = source; 97 | throw e; 98 | } 99 | 100 | if (data) return render(data, _); 101 | var template = function(data) { 102 | return render.call(this, data, _); 103 | }; 104 | 105 | // Provide the compiled function source as a convenience for precompilation. 106 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 107 | 108 | return template; 109 | }; 110 | 111 | return _; 112 | })({}); 113 | 114 | if (location.hostname === 'todomvc.com') { 115 | window._gaq = [['_setAccount','UA-31081062-1'],['_trackPageview']];(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.src='//www.google-analytics.com/ga.js';s.parentNode.insertBefore(g,s)}(document,'script')); 116 | } 117 | 118 | function redirect() { 119 | if (location.hostname === 'tastejs.github.io') { 120 | location.href = location.href.replace('tastejs.github.io/todomvc', 'todomvc.com'); 121 | } 122 | } 123 | 124 | function findRoot() { 125 | var base = location.href.indexOf('examples/'); 126 | return location.href.substr(0, base); 127 | } 128 | 129 | function getFile(file, callback) { 130 | if (!location.host) { 131 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 132 | } 133 | 134 | var xhr = new XMLHttpRequest(); 135 | 136 | xhr.open('GET', findRoot() + file, true); 137 | xhr.send(); 138 | 139 | xhr.onload = function () { 140 | if (xhr.status === 200 && callback) { 141 | callback(xhr.responseText); 142 | } 143 | }; 144 | } 145 | 146 | function Learn(learnJSON, config) { 147 | if (!(this instanceof Learn)) { 148 | return new Learn(learnJSON, config); 149 | } 150 | 151 | var template, framework; 152 | 153 | if (typeof learnJSON !== 'object') { 154 | try { 155 | learnJSON = JSON.parse(learnJSON); 156 | } catch (e) { 157 | return; 158 | } 159 | } 160 | 161 | if (config) { 162 | template = config.template; 163 | framework = config.framework; 164 | } 165 | 166 | if (!template && learnJSON.templates) { 167 | template = learnJSON.templates.todomvc; 168 | } 169 | 170 | if (!framework && document.querySelector('[data-framework]')) { 171 | framework = document.querySelector('[data-framework]').dataset.framework; 172 | } 173 | 174 | 175 | if (template && learnJSON[framework]) { 176 | this.frameworkJSON = learnJSON[framework]; 177 | this.template = template; 178 | 179 | this.append(); 180 | } 181 | } 182 | 183 | Learn.prototype.append = function () { 184 | var aside = document.createElement('aside'); 185 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 186 | aside.className = 'learn'; 187 | 188 | // Localize demo links 189 | var demoLinks = aside.querySelectorAll('.demo-link'); 190 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 191 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 192 | }); 193 | 194 | document.body.className = (document.body.className + ' learn-bar').trim(); 195 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 196 | }; 197 | 198 | redirect(); 199 | getFile('learn.json', Learn); 200 | })(); 201 | -------------------------------------------------------------------------------- /frontend/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/steveklabnik/rustmvc/9bb7391cbb0f7594dfabe03a3644979f51916238/frontend/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /frontend/bower_components/todomvc-common/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "description": "Common TodoMVC utilities used by our apps", 4 | "license": "MIT", 5 | "authors": [ 6 | "TasteJS team" 7 | ], 8 | "main": [ 9 | "base.js", 10 | "base.css" 11 | ], 12 | "keywords": [ 13 | "todomvc", 14 | "tastejs", 15 | "util", 16 | "utilities" 17 | ], 18 | "ignore": [ 19 | "package.json", 20 | "readme.md" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember.js • TodoMVC 6 | 7 | 8 | 9 | 31 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /frontend/js/app.js: -------------------------------------------------------------------------------- 1 | /*global Ember, DS, Todos:true */ 2 | window.Todos = Ember.Application.create(); 3 | 4 | Todos.ApplicationAdapter = DS.RESTAdapter.extend({ 5 | }); 6 | -------------------------------------------------------------------------------- /frontend/js/controllers/todo_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | Todos.TodoController = Ember.ObjectController.extend({ 5 | isEditing: false, 6 | 7 | // We use the bufferedTitle to store the original value of 8 | // the model's title so that we can roll it back later in the 9 | // `cancelEditing` action. 10 | bufferedTitle: Ember.computed.oneWay('title'), 11 | 12 | actions: { 13 | editTodo: function () { 14 | this.set('isEditing', true); 15 | }, 16 | 17 | doneEditing: function () { 18 | var bufferedTitle = this.get('bufferedTitle').trim(); 19 | 20 | if (Ember.isEmpty(bufferedTitle)) { 21 | // The `doneEditing` action gets sent twice when the user hits 22 | // enter (once via 'insert-newline' and once via 'focus-out'). 23 | // 24 | // We debounce our call to 'removeTodo' so that it only gets 25 | // made once. 26 | Ember.run.debounce(this, 'removeTodo', 0); 27 | } else { 28 | var todo = this.get('model'); 29 | todo.set('title', bufferedTitle); 30 | todo.save(); 31 | } 32 | 33 | // Re-set our newly edited title to persist its trimmed version 34 | this.set('bufferedTitle', bufferedTitle); 35 | this.set('isEditing', false); 36 | }, 37 | 38 | cancelEditing: function () { 39 | this.set('bufferedTitle', this.get('title')); 40 | this.set('isEditing', false); 41 | }, 42 | 43 | removeTodo: function () { 44 | this.removeTodo(); 45 | } 46 | }, 47 | 48 | removeTodo: function () { 49 | var todo = this.get('model'); 50 | 51 | todo.deleteRecord(); 52 | todo.save(); 53 | }, 54 | 55 | saveWhenCompleted: function () { 56 | this.get('model').save(); 57 | }.observes('isCompleted') 58 | }); 59 | })(); 60 | -------------------------------------------------------------------------------- /frontend/js/controllers/todos_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodosController = Ember.ArrayController.extend({ 6 | actions: { 7 | createTodo: function () { 8 | var title, todo; 9 | 10 | // Get the todo title set by the "New Todo" text field 11 | title = this.get('newTitle').trim(); 12 | if (!title) { 13 | return; 14 | } 15 | 16 | // Create the new Todo model 17 | todo = this.store.createRecord('todo', { 18 | title: title, 19 | isCompleted: false 20 | }); 21 | todo.save(); 22 | 23 | // Clear the "New Todo" text field 24 | this.set('newTitle', ''); 25 | }, 26 | 27 | clearCompleted: function () { 28 | var completed = this.get('completed'); 29 | completed.invoke('deleteRecord'); 30 | completed.invoke('save'); 31 | }, 32 | }, 33 | 34 | /* properties */ 35 | 36 | remaining: Ember.computed.filterBy('model', 'isCompleted', false), 37 | completed: Ember.computed.filterBy('model', 'isCompleted', true), 38 | 39 | allAreDone: function (key, value) { 40 | if (value !== undefined) { 41 | this.setEach('isCompleted', value); 42 | return value; 43 | } else { 44 | var length = this.get('length'); 45 | var completedLength = this.get('completed.length'); 46 | 47 | return length > 0 && length === completedLength; 48 | } 49 | }.property('length', 'completed.length') 50 | }); 51 | })(); 52 | -------------------------------------------------------------------------------- /frontend/js/controllers/todos_list_controller.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodosListController = Ember.ArrayController.extend({ 6 | needs: ['todos'], 7 | allTodos: Ember.computed.alias('controllers.todos'), 8 | itemController: 'todo', 9 | canToggle: function () { 10 | var anyTodos = this.get('allTodos.length'); 11 | var isEditing = this.isAny('isEditing'); 12 | 13 | return anyTodos && !isEditing; 14 | }.property('allTodos.length', '@each.isEditing') 15 | }); 16 | })(); 17 | -------------------------------------------------------------------------------- /frontend/js/helpers/pluralize.js: -------------------------------------------------------------------------------- 1 | /*global Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Ember.Handlebars.helper('pluralize', function (singular, count) { 6 | /* From Ember-Data */ 7 | var inflector = Ember.Inflector.inflector; 8 | 9 | return count === 1 ? singular : inflector.pluralize(singular); 10 | }); 11 | })(); 12 | -------------------------------------------------------------------------------- /frontend/js/models/todo.js: -------------------------------------------------------------------------------- 1 | /*global Todos, DS */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.Todo = DS.Model.extend({ 6 | title: DS.attr('string'), 7 | isCompleted: DS.attr('boolean') 8 | }); 9 | })(); 10 | -------------------------------------------------------------------------------- /frontend/js/router.js: -------------------------------------------------------------------------------- 1 | /*global Ember, Todos */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.Router.map(function () { 6 | this.resource('todos', { path: '/' }, function () { 7 | this.route('active'); 8 | this.route('completed'); 9 | }); 10 | }); 11 | 12 | Todos.TodosRoute = Ember.Route.extend({ 13 | model: function () { 14 | return this.store.find('todo'); 15 | } 16 | }); 17 | 18 | Todos.TodosIndexRoute = Todos.TodosRoute.extend({ 19 | templateName: 'todo-list', 20 | controllerName: 'todos-list' 21 | }); 22 | 23 | Todos.TodosActiveRoute = Todos.TodosIndexRoute.extend({ 24 | model: function () { 25 | return this.store.filter('todo', function (todo) { 26 | return !todo.get('isCompleted'); 27 | }); 28 | } 29 | }); 30 | 31 | Todos.TodosCompletedRoute = Todos.TodosIndexRoute.extend({ 32 | model: function () { 33 | return this.store.filter('todo', function (todo) { 34 | return todo.get('isCompleted'); 35 | }); 36 | } 37 | }); 38 | })(); 39 | -------------------------------------------------------------------------------- /frontend/js/views/todo_input_component.js: -------------------------------------------------------------------------------- 1 | /*global Todos, Ember */ 2 | (function () { 3 | 'use strict'; 4 | 5 | Todos.TodoInputComponent = Ember.TextField.extend({ 6 | focusOnInsert: function () { 7 | // Re-set input value to get rid of a reduntant text selection 8 | this.$().val(this.$().val()); 9 | this.$().focus(); 10 | }.on('didInsertElement') 11 | }); 12 | })(); 13 | -------------------------------------------------------------------------------- /frontend/learn.json: -------------------------------------------------------------------------------- 1 | lityjs": { 2 | "name": "Agility.js", 3 | "description": "Agility.js is an MVC library for Javascript that lets you write maintainable and reusable browser code without the verbose or infrastructural overhead found in other MVC libraries. The goal is to enable developers to write web apps at least as quickly as with jQuery, while simplifying long-term maintainability through MVC objects.", 4 | "homepage": "agilityjs.com", 5 | "examples": [{ 6 | "name": "Example", 7 | "url": "examples/agilityjs" 8 | }], 9 | "link_groups": [{ 10 | "heading": "Official Resources", 11 | "links": [{ 12 | "name": "Official Documentation", 13 | "url": "http://agilityjs.com/docs/docs.html" 14 | }, { 15 | "name": "Try it out on JSBin", 16 | "url": "http://jsbin.com/agility/224/edit" 17 | }, { 18 | "name": "Applications built with Agility.js", 19 | "url": "http://agilityjs.com/docs/gallery.html" 20 | }] 21 | }, { 22 | "heading": "Articles and Guides", 23 | "links": [{ 24 | "name": "Step by step from jQuery to Agility.js", 25 | "url": "https://gist.github.com/pindia/3166678" 26 | }] 27 | }, { 28 | "heading": "Community", 29 | "links": [{ 30 | "name": "Google Groups mailing list", 31 | "url": "http://groups.google.com/group/agilityjs" 32 | }, { 33 | "name": "agility.js on Stack Overflow", 34 | "url": "http://stackoverflow.com/questions/tagged/agility.js" 35 | }, { 36 | "name": "Agility.js on Twitter", 37 | "url": "https://twitter.com/agilityjs" 38 | }, { 39 | "name": "Agility.js on Google+", 40 | "url": "https://plus.google.com/116251025970928820842/posts" 41 | }] 42 | }] 43 | }, 44 | "angularjs": { 45 | "name": "AngularJS", 46 | "description": "HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop.", 47 | "homepage": "angularjs.org", 48 | "examples": [{ 49 | "name": "Example", 50 | "url": "examples/angularjs" 51 | }, { 52 | "name": "Example", 53 | "url": "examples/angularjs_require" 54 | }, { 55 | "name": "AngularJS Optimized", 56 | "url": "examples/angularjs-perf" 57 | }, { 58 | "name": "TypeScript & AngularJS", 59 | "url": "examples/typescript-angular" 60 | }], 61 | "link_groups": [{ 62 | "heading": "Official Resources", 63 | "links": [{ 64 | "name": "Tutorial", 65 | "url": "http://docs.angularjs.org/tutorial" 66 | }, { 67 | "name": "API Reference", 68 | "url": "http://docs.angularjs.org/api" 69 | }, { 70 | "name": "Developer Guide", 71 | "url": "http://docs.angularjs.org/guide" 72 | }, { 73 | "name": "Applications built with AngularJS", 74 | "url": "http://builtwith.angularjs.org" 75 | }, { 76 | "name": "Blog", 77 | "url": "http://blog.angularjs.org" 78 | }, { 79 | "name": "FAQ", 80 | "url": "http://docs.angularjs.org/misc/faq" 81 | }, { 82 | "name": "Videos", 83 | "url": "https://www.youtube.com/user/angularjs" 84 | }] 85 | }, { 86 | "heading": "Articles and Guides", 87 | "links": [{ 88 | "name": "Code School AngularJS course", 89 | "url": "https://www.codeschool.com/courses/shaping-up-with-angular-js" 90 | }, { 91 | "name": "5 Awesome AngularJS Features", 92 | "url": "http://net.tutsplus.com/tutorials/javascript-ajax/5-awesome-angularjs-features" 93 | }, { 94 | "name": "Using Yeoman with AngularJS", 95 | "url": "http://briantford.com/blog/angular-yeoman.html" 96 | }, { 97 | "name": "me&ngular - an introduction to MVW", 98 | "url": "http://stephenplusplus.github.io/meangular" 99 | }] 100 | }, { 101 | "heading": "Community", 102 | "links": [{ 103 | "name": "Walkthroughs and Tutorials on YouTube", 104 | "url": "http://www.youtube.com/playlist?list=PL1w1q3fL4pmgqpzb-XhG7Clgi67d_OHXz" 105 | }, { 106 | "name": "Google Groups mailing list", 107 | "url": "https://groups.google.com/forum/?fromgroups#!forum/angular" 108 | }, { 109 | "name": "angularjs on Stack Overflow", 110 | "url": "http://stackoverflow.com/questions/tagged/angularjs" 111 | }, { 112 | "name": "AngularJS on Twitter", 113 | "url": "https://twitter.com/angularjs" 114 | }, { 115 | "name": "AngularjS on Google+", 116 | "url": "https://plus.google.com/+AngularJS/posts" 117 | }] 118 | }] 119 | }, 120 | "angulardart": { 121 | "name": "AngularDart", 122 | "description": "Dart is a class-based, object-oriented language with lexical scoping, closures, and optional static typing. AngularDart is a port of Angular to Dart.", 123 | "homepage": "github.com/angular/angular.dart", 124 | "examples": [{ 125 | "name": "Example", 126 | "url": "examples/angular-dart" 127 | }], 128 | "link_groups": [{ 129 | "heading": "Official Resources", 130 | "links": [{ 131 | "name": "API Reference", 132 | "url": "http://ci.angularjs.org/view/Dart/job/angular.dart-master/javadoc/" 133 | }, { 134 | "name": "Tutorial", 135 | "url": "https://github.com/angular/angular.dart.tutorial" 136 | }, { 137 | "name": "A Tour of the Dart Language", 138 | "url": "http://www.dartlang.org/docs/dart-up-and-running/contents/ch02.html" 139 | }] 140 | }, { 141 | "heading": "Community", 142 | "links": [{ 143 | "name": "AngularDart Mailing List", 144 | "url": "https://groups.google.com/forum/#!forum/angular-dart" 145 | }, { 146 | "name": "AngularDart on Stack Overflow", 147 | "url": "http://stackoverflow.com/questions/tagged/angulardart" 148 | }, { 149 | "name": "+AngularDart on Google+", 150 | "url": "https://plus.google.com/+AngularJS" 151 | }, { 152 | "name": "@angularjs on Twitter", 153 | "url": "https://twitter.com/angularjs" 154 | }, { 155 | "name": "Bugtracker on GitHub", 156 | "url": "https://github.com/angular/angular.dart/issues?state=open" 157 | }] 158 | }] 159 | }, 160 | "ariatemplates": { 161 | "name": "Aria Templates", 162 | "description": "Aria Templates (aka AT) is an application framework written in JavaScript for building rich and large-scaled enterprise web applications.", 163 | "homepage": "ariatemplates.com", 164 | "examples": [{ 165 | "name": "Example", 166 | "url": "examples/ariatemplates" 167 | }], 168 | "link_groups": [{ 169 | "heading": "Official Resources", 170 | "links": [{ 171 | "name": "Documentation", 172 | "url": "http://ariatemplates.com/usermanual" 173 | }, { 174 | "name": "API Reference", 175 | "url": "http://ariatemplates.com/aria/guide/apps/apidocs" 176 | }, { 177 | "name": "Guides", 178 | "url": "http://ariatemplates.com/guides" 179 | }, { 180 | "name": "Blog", 181 | "url": "http://ariatemplates.com/blog" 182 | }, { 183 | "name": "FAQ", 184 | "url": "http://ariatemplates.com/faq" 185 | }, { 186 | "name": "Aria Templates on GitHub", 187 | "url": "https://github.com/ariatemplates" 188 | }] 189 | }, { 190 | "heading": "Community", 191 | "links": [{ 192 | "name": "Aria Templates on Stack Overflow", 193 | "url": "http://stackoverflow.com/questions/tagged/ariatemplates" 194 | }, { 195 | "name": "Forums", 196 | "url": "http://ariatemplates.com/forum" 197 | }, { 198 | "name": "Aria Templates on Twitter", 199 | "url": "http://twitter.com/ariatemplates" 200 | }] 201 | }] 202 | }, 203 | "atmajs": { 204 | "name": "Atma.js", 205 | "description": "HMVC and the component-based architecture for building client, server or hybrid applications", 206 | "homepage": "atmajs.com", 207 | "examples": [{ 208 | "name": "Example", 209 | "url": "examples/atmajs/" 210 | }], 211 | "link_groups": [{ 212 | "heading": "Official Resources", 213 | "links": [{ 214 | "name": "Get Started", 215 | "url": "http://atmajs.com/get/github" 216 | }, { 217 | "name": "Atma.js on GitHub", 218 | "url": "https://github.com/atmajs" 219 | }, { 220 | "name": "Atma.js DevTool", 221 | "url": "https://chrome.google.com/webstore/detail/atmajs-devtool/bpaepkmcmoablpdahclhdceapndfhdpo" 222 | }] 223 | }, { 224 | "heading": "Overview", 225 | "links": [{ 226 | "name": "Libraries", 227 | "url": "https://github.com/tastejs/todomvc/blob/gh-pages/examples/atmajs/readme.md" 228 | }] 229 | }, { 230 | "heading": "Community", 231 | "links": [{ 232 | "name": "Atma.js on Stack Overflow", 233 | "url": "http://stackoverflow.com/questions/tagged/atmajs" 234 | }, { 235 | "name": "Mailing list on Google Groups", 236 | "url": "https://groups.google.com/forum/?fromgroups#!forum/atmajs" 237 | }] 238 | }] 239 | }, 240 | "backbonejs": { 241 | "name": "Backbone.js", 242 | "description": "Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.", 243 | "homepage": "backbonejs.org", 244 | "examples": [{ 245 | "name": "Example", 246 | "url": "examples/backbone" 247 | }, { 248 | "name": "Example", 249 | "url": "examples/backbone_require" 250 | }, { 251 | "name": "Enyo & Backbone.js", 252 | "url": "examples/enyo_backbone" 253 | }, { 254 | "name": "TypeScript & Backbone.js", 255 | "url": "examples/typescript-backbone" 256 | }], 257 | "link_groups": [{ 258 | "heading": "Official Resources", 259 | "links": [{ 260 | "name": "Annotated source code", 261 | "url": "http://backbonejs.org/docs/backbone.html" 262 | }, { 263 | "name": "Applications built with Backbone.js", 264 | "url": "http://backbonejs.org/#examples" 265 | }, { 266 | "name": "FAQ", 267 | "url": "http://backbonejs.org/#faq" 268 | }] 269 | }, { 270 | "heading": "Articles and Guides", 271 | "links": [{ 272 | "name": "Developing Backbone.js Applications", 273 | "url": "http://addyosmani.github.io/backbone-fundamentals" 274 | }, { 275 | "name": "Collection of tutorials, blog posts, and example sites", 276 | "url": "https://github.com/documentcloud/backbone/wiki/Tutorials%2C-blog-posts-and-example-sites" 277 | }] 278 | }, { 279 | "heading": "Community", 280 | "links": [{ 281 | "name": "Backbone.js on Stack Overflow", 282 | "url": "http://stackoverflow.com/questions/tagged/backbone.js" 283 | }, { 284 | "name": "Google Groups mailing list", 285 | "url": "https://groups.google.com/forum/#!forum/backbonejs" 286 | }, { 287 | "name": "Backbone.js on Twitter", 288 | "url": "http://twitter.com/documentcloud" 289 | }] 290 | }] 291 | }, 292 | "ampersand": { 293 | "name": "Ampersand.js", 294 | "description": "A highly modular, loosely coupled, non-frameworky framework for building advanced JavaScript apps.", 295 | "homepage": "http://ampersandjs.com", 296 | "examples": [{ 297 | "name": "Architecture Example", 298 | "url": "examples/ampersand" 299 | }], 300 | "link_groups": [{ 301 | "heading": "Official Resources", 302 | "links": [{ 303 | "name": "Project Site", 304 | "url": "http://ampersandjs.com" 305 | }, { 306 | "name": "Guides", 307 | "url": "http://ampersandjs.com/learn" 308 | }, { 309 | "name": "API Reference", 310 | "url": "http://ampersandjs.com/docs" 311 | }, { 312 | "name": "Curated Front-end Modules", 313 | "url": "http://tools.ampersandjs.com" 314 | }, { 315 | "name": "#&yet IRC Channel on Freenode", 316 | "url": "https://botbot.me/freenode/andyet/" 317 | }] 318 | }, { 319 | "heading": "Related Materials", 320 | "links": [{ 321 | "name": "Human JavaScript (free online book)", 322 | "url": "http://learn.humanjavascript.com" 323 | }, { 324 | "name": "Introducing Ampersand Blogpost", 325 | "url": "http://blog.andyet.com/2014/06/25/introducing-ampersand-js/" 326 | }, { 327 | "name": "&yet – The team behind Ampersand.js", 328 | "url": "http://andyet.com" 329 | }] 330 | }] 331 | }, 332 | "batman": { 333 | "name": "Batman.js", 334 | "description": "A client-side framework for Rails developers. Batman.js is a framework for building rich web applications with CoffeeScript.", 335 | "homepage": "batmanjs.org", 336 | "examples": [{ 337 | "name": "Example", 338 | "url": "examples/batman" 339 | }], 340 | "link_groups": [{ 341 | "heading": "Official Resources", 342 | "links": [{ 343 | "name": "Documentation", 344 | "url": "http://batmanjs.org/docs/batman.html" 345 | }, { 346 | "name": "Get Started", 347 | "url": "http://batmanjs.org/download.html" 348 | }, { 349 | "name": "Applications built with Batman.js", 350 | "url": "http://batmanjs.org/examples.html" 351 | }, { 352 | "name": "Blog", 353 | "url": "http://batmanjs.org/2012/04/02/batman-packs-a-punch.html" 354 | }, { 355 | "name": "Batman.js on GitHub", 356 | "url": "https://github.com/shopify/batman" 357 | }] 358 | }, { 359 | "heading": "Articles and Guides", 360 | "links": [{ 361 | "name": "Simple address book app with Batman.js", 362 | "url": "http://kubyshkin.ru/posts/simple-address-book-app-with-batman-js.html" 363 | }, { 364 | "name": "Batman.js vs Knockout.js", 365 | "url": "http://blog.erlware.org/2011/08/28/batman-js-vs-knockout-js" 366 | }] 367 | }, { 368 | "heading": "Community", 369 | "links": [{ 370 | "name": "Batman.js on Stack Overflow", 371 | "url": "http://stackoverflow.com/questions/tagged/batman.js" 372 | }, { 373 | "name": "Mailing list on Google Groups", 374 | "url": "https://groups.google.com/forum/?fromgroups#!forum/batmanjs" 375 | }, { 376 | "name": "Batman.js on Twitter", 377 | "url": "http://twitter.com/batmanjs" 378 | }] 379 | }] 380 | }, 381 | "canjs": { 382 | "name": "CanJS", 383 | "description": "CanJS is a MIT-licensed, client-side, JavaScript framework that makes building rich web applications easy.", 384 | "homepage": "canjs.com", 385 | "examples": [{ 386 | "name": "Example", 387 | "url": "examples/canjs" 388 | }, { 389 | "name": "Example", 390 | "url": "examples/canjs_require" 391 | }], 392 | "link_groups": [{ 393 | "heading": "Official Resources", 394 | "links": [{ 395 | "name": "Documentation", 396 | "url": "http://canjs.com/docs/index.html" 397 | }, { 398 | "name": "Getting started", 399 | "url": "http://canjs.com/guides/Tutorial.html" 400 | }, { 401 | "name": "Applications built with CanJS", 402 | "url": "http://canjs.com/#examples" 403 | }, { 404 | "name": "Blog", 405 | "url": "http://bitovi.com/blog/tag/canjs" 406 | }, { 407 | "name": "Getting started video", 408 | "url": "http://www.youtube.com/watch?v=GdT4Oq6ZQ68" 409 | }] 410 | }, { 411 | "heading": "Articles and Guides", 412 | "links": [{ 413 | "name": "Diving into CanJS", 414 | "url": "http://net.tutsplus.com/tutorials/javascript-ajax/diving-into-canjs" 415 | }] 416 | }, { 417 | "heading": "Community", 418 | "links": [{ 419 | "name": "CanJS on Stack Overflow", 420 | "url": "http://stackoverflow.com/questions/tagged/canjs" 421 | }, { 422 | "name": "CanJS Forums", 423 | "url": "http://forum.javascriptmvc.com/#Forum/canjs" 424 | }, { 425 | "name": "CanJS on Twitter", 426 | "url": "http://twitter.com/canjs" 427 | }, { 428 | "name": "#canjs IRC", 429 | "url": "http://webchat.freenode.net/?channels=canjs" 430 | }] 431 | }] 432 | }, 433 | "chaplin": { 434 | "name": "Chaplin", 435 | "description": "Chaplin is an architecture for JavaScript applications using the Backbone.js library. Chaplin addresses Backbone’s limitations by providing a lightweight and flexible structure that features well-proven design patterns and best practices.", 436 | "homepage": "chaplinjs.org", 437 | "examples": [{ 438 | "name": "Example", 439 | "url": "examples/chaplin-brunch/public" 440 | }], 441 | "link_groups": [{ 442 | "heading": "Official Resources", 443 | "links": [{ 444 | "name": "Getting Started", 445 | "url": "http://docs.chaplinjs.org/getting_started.html" 446 | }, { 447 | "name": "Documentation", 448 | "url": "http://docs.chaplinjs.org" 449 | }, { 450 | "name": "Annotated Source Code", 451 | "url": "http://chaplinjs.org/annotated/chaplin.html" 452 | }, { 453 | "name": "Applications built with Chaplin", 454 | "url": "http://chaplinjs.org/examples.html" 455 | }, { 456 | "name": "Cookbook", 457 | "url": "https://github.com/chaplinjs/chaplin/wiki/Cookbook" 458 | }, { 459 | "name": "Chaplin on GitHub", 460 | "url": "https://github.com/chaplinjs" 461 | }] 462 | }, { 463 | "heading": "Articles and Guides", 464 | "links": [{ 465 | "name": "JavaScript MVC frameworks: A Comparison of Marionette and Chaplin", 466 | "url": "http://9elements.com/io/index.php/comparison-of-marionette-and-chaplin" 467 | }] 468 | }, { 469 | "heading": "Community", 470 | "links": [{ 471 | "name": "Support forum on ost.io", 472 | "url": "http://ost.io/@chaplinjs/chaplin" 473 | }, { 474 | "name": "Chaplin on Stack Overflow", 475 | "url": "http://stackoverflow.com/questions/tagged/chaplinjs" 476 | }, { 477 | "name": "Chaplin on Twitter", 478 | "url": "http://twitter.com/chaplinjs" 479 | }] 480 | }] 481 | }, 482 | "closure": { 483 | "name": "Closure Tools", 484 | "description": "The Closure Tools project is an effort by Google engineers to open source the tools used in many of Google's sites and web applications for use by the wider Web development community.", 485 | "homepage": "developers.google.com/closure", 486 | "examples": [{ 487 | "name": "Example", 488 | "url": "examples/closure" 489 | }], 490 | "link_groups": [{ 491 | "heading": "Official Resources", 492 | "links": [{ 493 | "name": "Documentation", 494 | "url": "https://developers.google.com/closure/library/docs/overview" 495 | }, { 496 | "name": "API Reference", 497 | "url": "http://docs.closure-library.googlecode.com/git/index.html" 498 | }, { 499 | "name": "Blog", 500 | "url": "http://closuretools.blogspot.com" 501 | }, { 502 | "name": "FAQ", 503 | "url": "https://developers.google.com/closure/faq" 504 | }] 505 | }, { 506 | "heading": "Articles and Guides", 507 | "links": [{ 508 | "name": "Examples, walkthroughs, and articles", 509 | "url": "http://www.googleclosure.com" 510 | }, { 511 | "name": "First Adventure in Google Closure", 512 | "url": "http://www.codeproject.com/Articles/265364/First-Adventures-in-Google-Closure" 513 | }] 514 | }, { 515 | "heading": "Community", 516 | "links": [{ 517 | "name": "Google Groups mailing list", 518 | "url": "https://groups.google.com/group/closure-library-discuss" 519 | }, { 520 | "name": "Closure Tools on Twitter", 521 | "url": "http://twitter.com/closuretools" 522 | }, { 523 | "name": "Closure Tools on Google+", 524 | "url": "https://plus.google.com/communities/113969319608324762672" 525 | }] 526 | }] 527 | }, 528 | "componentjs": { 529 | "name": "ComponentJS", 530 | "description": "Provides a powerful run-time Component System for hierarchically structuring the UI dialogs of complex HTML5-based Clients. Fully isolates each UI segment, with sophisticated hierarchical Event, Service, Hook, Model, Socket and Property mechanisms.", 531 | "homepage": "componentjs.com/", 532 | "source_path": [{ 533 | "name": "Example", 534 | "url": "examples/componentjs" 535 | }], 536 | "link_groups": [{ 537 | "heading": "Official Resources", 538 | "links": [{ 539 | "name": "Overview Video", 540 | "url": "http://www.youtube.com/watch?v=gtz7PCMxzVA" 541 | }, { 542 | "name": "Demo", 543 | "url": "http://componentjs.com/demo.html" 544 | }, { 545 | "name": "Tutorial", 546 | "url": "http://componentjs.com/tutorial.html" 547 | }, { 548 | "name": "API Reference", 549 | "url": "http://componentjs.com/api.html" 550 | }] 551 | }, { 552 | "heading": "Community", 553 | "links": [{ 554 | "name": "ComponentJS on GitHub", 555 | "url": "https://github.com/rse/componentjs" 556 | }, { 557 | "name": "ComponentJS on Twitter", 558 | "url": "http://twitter.com/componentjs" 559 | }] 560 | }] 561 | }, 562 | "cujo": { 563 | "name": "cujoJS", 564 | "description": "cujo is an architectural toolkit for next generation JavaScript applications. It encourages highly modular development, declarative application assembly, and embraces the asynchronous nature of JavaScript and its fusion of object-oriented and functional programming styles.", 565 | "homepage": "cujojs.com", 566 | "examples": [{ 567 | "name": "Example", 568 | "url": "examples/cujo" 569 | }], 570 | "link_groups": [{ 571 | "heading": "Official Resources", 572 | "links": [{ 573 | "name": "know cujoJS", 574 | "url": "http://know.cujojs.com/" 575 | }, { 576 | "name": "cujoJS on GitHub", 577 | "url": "https://github.com/cujojs" 578 | }] 579 | }, { 580 | "heading": "Articles and Guides", 581 | "links": [{ 582 | "name": "An introductory presentation", 583 | "url": "http://www.youtube.com/watch?v=TqX-CqYYwEc" 584 | }] 585 | }, { 586 | "heading": "Community", 587 | "links": [{ 588 | "name": "Google Groups mailing list", 589 | "url": "https://groups.google.com/forum/#!forum/cujojs" 590 | }, { 591 | "name": "cujoJS on Twitter", 592 | "url": "http://twitter.com/cujojs" 593 | }] 594 | }] 595 | }, 596 | "dart": { 597 | "name": "Dart", 598 | "description": "Dart is a class-based, object-oriented language with lexical scoping, closures, and optional static typing. Dart helps you build structured modern web apps and is easy to learn for a wide range of developers.", 599 | "homepage": "dartlang.org", 600 | "examples": [{ 601 | "name": "Example", 602 | "url": "examples/vanilladart/web" 603 | }], 604 | "link_groups": [{ 605 | "heading": "Official Resources", 606 | "links": [{ 607 | "name": "Documentation", 608 | "url": "https://www.dartlang.org/docs/dart-up-and-running/contents/ch01.html" 609 | }, { 610 | "name": "API Reference", 611 | "url": "http://api.dartlang.org/docs/releases/latest" 612 | }, { 613 | "name": "A Tour of the Dart Language", 614 | "url": "http://www.dartlang.org/docs/dart-up-and-running/contents/ch02.html" 615 | }, { 616 | "name": "Articles", 617 | "url": "http://www.dartlang.org/articles" 618 | }, { 619 | "name": "Tutorials", 620 | "url": "http://www.dartlang.org/docs/tutorials" 621 | }, { 622 | "name": "FAQ", 623 | "url": "http://www.dartlang.org/support/faq.html" 624 | }] 625 | }, { 626 | "heading": "Articles and Guides", 627 | "links": [{ 628 | "name": "Getting started with Google Dart", 629 | "url": "http://www.techrepublic.com/blog/webmaster/getting-started-with-google-dart/931" 630 | }] 631 | }, { 632 | "heading": "Community", 633 | "links": [{ 634 | "name": "Dart on Stack Overflow", 635 | "url": "http://stackoverflow.com/questions/tagged/dart" 636 | }, { 637 | "name": "Dart on Twitter", 638 | "url": "http://twitter.com/dart_lang" 639 | }, { 640 | "name": "Dart on Google+", 641 | "url": "https://plus.google.com/+dartlang/posts" 642 | }] 643 | }] 644 | }, 645 | "deftjs": { 646 | "name": "DeftJS", 647 | "description": "DeftJS enhances Ext JS and Sencha Touch’s APIs with additional building blocks that enable large development teams to rapidly build enterprise scale applications, leveraging best practices and proven patterns discovered by top RIA developers at some of the best consulting firms in the industry.", 648 | "homepage": "deftjs.org", 649 | "examples": [{ 650 | "name": "Example", 651 | "url": "examples/extjs_deftjs" 652 | }], 653 | "link_groups": [{ 654 | "heading": "Official Resources", 655 | "links": [{ 656 | "name": "Documentation", 657 | "url": "https://github.com/deftjs/DeftJS/wiki" 658 | }, { 659 | "name": "DeftJS on GitHub", 660 | "url": "https://github.com/deftjs/DeftJS" 661 | }] 662 | }, { 663 | "heading": "Articles and Guides", 664 | "links": [{ 665 | "name": "Exploring ExtJS with DeftJS", 666 | "url": "http://www.briankotek.com/blog/index.cfm/2012/5/8/Exploring-ExtJS-with-DeftJS" 667 | }] 668 | }, { 669 | "heading": "Community", 670 | "links": [{ 671 | "name": "DeftJS on Stack Overflow", 672 | "url": "http://stackoverflow.com/questions/tagged/deftjs" 673 | }, { 674 | "name": "Mailing list on Google Groups", 675 | "url": "https://groups.google.com/forum/?fromgroups#!forum/deftjs" 676 | }, { 677 | "name": "DeftJS on Twitter", 678 | "url": "http://twitter.com/deftjs" 679 | }] 680 | }] 681 | }, 682 | "derby": { 683 | "name": "Derby", 684 | "description": "MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.", 685 | "homepage": "derbyjs.com", 686 | "examples": [{ 687 | "name": "Real-time Example", 688 | "url": "http://todomvc.derbyjs.com", 689 | "source_url": "examples/derby" 690 | }], 691 | "link_groups": [{ 692 | "heading": "Official Resources", 693 | "links": [{ 694 | "name": "Introduction", 695 | "url": "http://derbyjs.com/#introduction" 696 | }, { 697 | "name": "Getting Started", 698 | "url": "http://derbyjs.com/#getting_started" 699 | }, { 700 | "name": "Applications built with Derby", 701 | "url": "https://github.com/codeparty/derby/wiki/Community-Projects#website-showcase" 702 | }, { 703 | "name": "Blog", 704 | "url": "http://blog.derbyjs.com" 705 | }, { 706 | "name": "FAQ", 707 | "url": "https://github.com/codeparty/derby/wiki/Frequently-Asked-Questions" 708 | }, { 709 | "name": "Derby on GitHub", 710 | "url": "https://github.com/codeparty/derby" 711 | }] 712 | }, { 713 | "heading": "Articles and Guides", 714 | "links": [{ 715 | "name": "Learning DerbyJS", 716 | "url": "http://nickofnicks.com/2013/04/24/nodejs/derbyjs/learning-derbyjs/" 717 | }, { 718 | "name": "Screencast - 6 Things I'm Loving about DerbyJS", 719 | "url": "http://micknelson.wordpress.com/2012/07/27/6-things-im-loving-about-derbyjs" 720 | }] 721 | }, { 722 | "heading": "Community", 723 | "links": [{ 724 | "name": "Derby on Stack Overflow", 725 | "url": "http://stackoverflow.com/questions/tagged/derbyjs" 726 | }, { 727 | "name": "Mailing list on Google Groups", 728 | "url": "https://groups.google.com/forum/?fromgroups#!forum/derbyjs" 729 | }, { 730 | "name": "Derby on Twitter", 731 | "url": "http://twitter.com/derbyjs" 732 | }] 733 | }] 734 | }, 735 | "dijon": { 736 | "name": "Dijon", 737 | "description": "An IOC/DI framework in Javascript, inspired by Robotlegs and Swiftsuspenders.", 738 | "homepage": "github.com/creynders/dijon-framework", 739 | "examples": [{ 740 | "name": "Example", 741 | "url": "examples/dijon" 742 | }], 743 | "link_groups": [{ 744 | "heading": "Official Resources", 745 | "links": [{ 746 | "name": "Documentation", 747 | "url": "http://creynders.github.com/dijon-framework/docs" 748 | }, { 749 | "name": "Dijon on GitHub", 750 | "url": "https://github.com/creynders/dijon-framework" 751 | }] 752 | }, { 753 | "heading": "Community", 754 | "links": [{ 755 | "name": "Dijon on Twitter", 756 | "url": "http://twitter.com/camillereynders" 757 | }] 758 | }] 759 | }, 760 | "dojo": { 761 | "name": "Dojo", 762 | "description": "Dojo saves you time and scales with your development process, using web standards as its platform. It’s the toolkit experienced developers turn to for building high quality desktop and mobile web applications.", 763 | "homepage": "dojotoolkit.org", 764 | "examples": [{ 765 | "name": "Example", 766 | "url": "examples/dojo" 767 | }], 768 | "link_groups": [{ 769 | "heading": "Official Resources", 770 | "links": [{ 771 | "name": "Documentation", 772 | "url": "http://dojotoolkit.org/documentation" 773 | }, { 774 | "name": "Getting started guide", 775 | "url": "https://dojotoolkit.org/reference-guide/1.8/quickstart" 776 | }, { 777 | "name": "API Reference", 778 | "url": "http://dojotoolkit.org/api" 779 | }, { 780 | "name": "Blog", 781 | "url": "http://dojotoolkit.org/blog" 782 | }] 783 | }, { 784 | "heading": "Articles and Guides", 785 | "links": [{ 786 | "name": "Getting StartED with Dojo", 787 | "url": "http://startdojo.com" 788 | }] 789 | }, { 790 | "heading": "Community", 791 | "links": [{ 792 | "name": "Dojo on Stack Overflow", 793 | "url": "http://stackoverflow.com/questions/tagged/dojo" 794 | }, { 795 | "name": "Mailing list", 796 | "url": "http://dojotoolkit.org/community" 797 | }, { 798 | "name": "Dojo on Twitter", 799 | "url": "http://twitter.com/dojo" 800 | }] 801 | }] 802 | }, 803 | "duel": { 804 | "name": "DUEL", 805 | "description": "DUEL is a dual-side templating engine using HTML for layout and 100% pure JavaScript as the binding language. The same views may be executed both directly in the browser (client-side template) and on the server (server-side template).", 806 | "homepage": "bitbucket.org/mckamey/duel/wiki/Home", 807 | "examples": [{ 808 | "name": "Example", 809 | "url": "examples/duel/www" 810 | }], 811 | "link_groups": [{ 812 | "heading": "Official Resources", 813 | "links": [{ 814 | "name": "Syntax", 815 | "url": "https://bitbucket.org/mckamey/duel/wiki/Syntax" 816 | }, { 817 | "name": "Examples", 818 | "url": "https://bitbucket.org/mckamey/duel/wiki/Examples" 819 | }, { 820 | "name": "DUEL on BitBucket", 821 | "url": "https://bitbucket.org/mckamey/duel/src" 822 | }] 823 | }] 824 | }, 825 | "durandal": { 826 | "name": "Durandal", 827 | "description": "Single Page Apps Done Right.", 828 | "homepage": "durandaljs.com", 829 | "examples": [{ 830 | "name": "Example", 831 | "url": "examples/durandal/index.html" 832 | }], 833 | "link_groups": [{ 834 | "heading": "Official Resources", 835 | "links": [{ 836 | "name": "Getting Started", 837 | "url": "http://durandaljs.com/pages/get-started/" 838 | }, { 839 | "name": "Documentation", 840 | "url": "http://durandaljs.com/pages/docs" 841 | }, { 842 | "name": "Videos", 843 | "url": "http://durandaljs.com/pages/videos/" 844 | }, { 845 | "name": "Durandal on GitHub", 846 | "url": "https://github.com/BlueSpire/Durandal" 847 | }] 848 | } 849 | , { 850 | "heading": "Articles and Guides", 851 | "links": [{ 852 | "name": "HotTowel Template - Durandal with ASP.Net MVC", 853 | "url": "http://www.johnpapa.net/hottowel/" 854 | }, { 855 | "name": "Using Durandal to Create Single Page Apps", 856 | "url": "http://stephenwalther.com/archive/2013/02/08/using-durandal-to-create-single-page-apps" 857 | },{ 858 | "name": "nuGet Download", 859 | "url": "http://www.nuget.org/packages/Durandal/" 860 | }] 861 | }, { 862 | "heading": "Community", 863 | "links": [{ 864 | "name": "Durandal on Stack Overflow", 865 | "url": "http://stackoverflow.com/questions/tagged/durandal" 866 | }, { 867 | "name": "Mailing list on Google Groups", 868 | "url": "https://groups.google.com/forum/#!forum/durandaljs" 869 | }, { 870 | "name": "Durandal on Twitter", 871 | "url": "http://twitter.com/durandaljs" 872 | }] 873 | }] 874 | }, 875 | "emberjs": { 876 | "name": "Ember.js", 877 | "description": "A framework for creating ambitious web applications.", 878 | "homepage": "emberjs.com", 879 | "examples": [{ 880 | "name": "Example", 881 | "url": "examples/emberjs" 882 | }, { 883 | "name": "Example", 884 | "url": "examples/emberjs_require" 885 | }], 886 | "link_groups": [{ 887 | "heading": "Official Resources", 888 | "links": [{ 889 | "name": "Guides", 890 | "url": "http://emberjs.com/guides" 891 | }, { 892 | "name": "API Reference", 893 | "url": "http://emberjs.com/api" 894 | }, { 895 | "name": "Screencast - Building an App with Ember.js", 896 | "url": "https://www.youtube.com/watch?v=Ga99hMi7wfY" 897 | }, { 898 | "name": "Applications built with Ember.js", 899 | "url": "http://emberjs.com/ember-users" 900 | }, { 901 | "name": "Blog", 902 | "url": "http://emberjs.com/blog" 903 | }] 904 | }, { 905 | "heading": "Articles and Guides", 906 | "links": [{ 907 | "name": "Getting Into Ember.js", 908 | "url": "http://net.tutsplus.com/tutorials/javascript-ajax/getting-into-ember-js" 909 | }, { 910 | "name": "EmberWatch", 911 | "url": "http://emberwatch.com" 912 | }] 913 | }, { 914 | "heading": "Community", 915 | "links": [{ 916 | "name": "Ember.js on Stack Overflow", 917 | "url": "http://stackoverflow.com/questions/tagged/ember.js" 918 | }, { 919 | "name": "Ember.js on Twitter", 920 | "url": "http://twitter.com/emberjs" 921 | }, { 922 | "name": "Ember.js on Google+", 923 | "url": "https://plus.google.com/communities/106387049790387471205" 924 | }] 925 | }] 926 | }, 927 | "enyo": { 928 | "name": "Enyo", 929 | "description": "Use the same framework to develop apps for the web and for all major platforms, desktop and mobile.", 930 | "homepage": "enyojs.com", 931 | "examples": [{ 932 | "name": "Example", 933 | "url": "examples/enyo_backbone" 934 | }], 935 | "link_groups": [{ 936 | "heading": "Official Resources", 937 | "links": [{ 938 | "name": "Documentation", 939 | "url": "http://enyojs.com/docs" 940 | }, { 941 | "name": "About", 942 | "url": "http://enyojs.com/about" 943 | }, { 944 | "name": "Applications built with Enyo", 945 | "url": "http://enyojs.com/showcase" 946 | }, { 947 | "name": "Blog", 948 | "url": "http://blog.enyojs.com" 949 | }, { 950 | "name": "FAQ", 951 | "url": "http://enyojs.com/about/faq" 952 | }, { 953 | "name": "Enyo on GitHub", 954 | "url": "https://github.com/enyojs" 955 | }] 956 | }, { 957 | "heading": "Community", 958 | "links": [{ 959 | "name": "Enyo on Stack Overflow", 960 | "url": "http://stackoverflow.com/questions/tagged/enyo" 961 | }, { 962 | "name": "Forums", 963 | "url": "http://forums.enyojs.com" 964 | }, { 965 | "name": "Mailing list on Google Groups", 966 | "url": "https://groups.google.com/forum/#!forum/enyo-development" 967 | }, { 968 | "name": "Enyo on Twitter", 969 | "url": "http://twitter.com/enyojs" 970 | }] 971 | }] 972 | }, 973 | "epitome": { 974 | "name": "Epitome", 975 | "description": "Epitome is a new extensible and modular open-source MVC* framework, built out of MooTools Classes and Events.", 976 | "homepage": "dimitarchristoff.github.io/Epitome", 977 | "examples": [{ 978 | "name": "Example", 979 | "url": "examples/epitome" 980 | }], 981 | "link_groups": [{ 982 | "heading": "Official Resources", 983 | "links": [{ 984 | "name": "API Reference", 985 | "url": "http://dimitarchristoff.github.io/Epitome" 986 | }, { 987 | "name": "Examples", 988 | "url": "http://dimitarchristoff.github.io/Epitome/#examples" 989 | }, { 990 | "name": "Download & Building", 991 | "url": "http://dimitarchristoff.github.io/Epitome/#download-building" 992 | }, { 993 | "name": "Epitome on GitHub", 994 | "url": "https://github.com/DimitarChristoff/Epitome" 995 | }] 996 | }, { 997 | "heading": "Community", 998 | "links": [{ 999 | "name": "Epitome on Twitter", 1000 | "url": "http://twitter.com/D_mitar" 1001 | }] 1002 | }] 1003 | }, 1004 | "exoskeleton": { 1005 | "name": "Exoskeleton", 1006 | "description": "A faster and leaner Backbone for your HTML5 apps.", 1007 | "homepage": "exosjs.com", 1008 | "examples": [{ 1009 | "name": "Example", 1010 | "url": "examples/exoskeleton" 1011 | }], 1012 | "link_groups": [{ 1013 | "heading": "Official Resources", 1014 | "links": [{ 1015 | "name": "Documentation", 1016 | "url": "http://backbonejs.org" 1017 | }, { 1018 | "name": "Exoskeleton on GitHub", 1019 | "url": "https://github.com/paulmillr/exoskeleton" 1020 | }] 1021 | }, { 1022 | "heading": "Community", 1023 | "links": [{ 1024 | "name": "Exoskeleton on Stack Overflow", 1025 | "url": "http://stackoverflow.com/questions/tagged/exoskeleton" 1026 | }, { 1027 | "name": "Backbone on Stack Overflow", 1028 | "url": "http://stackoverflow.com/questions/tagged/backbone.js" 1029 | }, { 1030 | "name": "Exoskeleton's author on Twitter", 1031 | "url": "http://twitter.com/paulmillr" 1032 | }] 1033 | }] 1034 | }, 1035 | "firebase": { 1036 | "name": "Firebase", 1037 | "description": "Firebase is a scalable realtime backend that lets you build apps fast without managing servers.", 1038 | "homepage": "firebase.com", 1039 | "examples": [{ 1040 | "name": "Firebase + AngularJS Realtime Example", 1041 | "url": "examples/firebase-angular" 1042 | }], 1043 | "link_groups": [{ 1044 | "heading": "Official Resources", 1045 | "links": [{ 1046 | "name": "AngularFire Site", 1047 | "url": "http://angularfire.com/" 1048 | }, { 1049 | "name": "Documentation & Examples", 1050 | "url": "https://www.firebase.com/docs/" 1051 | }, { 1052 | "name": "Blog", 1053 | "url": "https://www.firebase.com/blog/" 1054 | }, { 1055 | "name": "Firebase on GitHub", 1056 | "url": "http://firebase.github.io" 1057 | }, { 1058 | "name": "Tutorial", 1059 | "url": "https://www.firebase.com/tutorial/" 1060 | }] 1061 | }, { 1062 | "heading": "Community", 1063 | "links": [{ 1064 | "name": "Firebase + Angular Mailing list on Google Groups", 1065 | "url": "https://groups.google.com/forum/#!forum/firebase-angular" 1066 | }, { 1067 | "name": "Firebase on Stack Overflow", 1068 | "url": "http://stackoverflow.com/questions/tagged/firebase" 1069 | }, { 1070 | "name": "Firebase on Twitter", 1071 | "url": "http://twitter.com/Firebase" 1072 | }, { 1073 | "name": "Firebase on Facebook", 1074 | "url": "http://facebook.com/Firebase" 1075 | }, { 1076 | "name": "Firebase on Google+", 1077 | "url": "https://plus.google.com/115330003035930967645/posts" 1078 | }] 1079 | }] 1080 | }, 1081 | "flight": { 1082 | "name": "Flight", 1083 | "description": "Flight is a lightweight, component-based JavaScript framework that maps behavior to DOM nodes.", 1084 | "homepage": "flightjs.github.io", 1085 | "examples": [{ 1086 | "name": "Example", 1087 | "url": "examples/flight" 1088 | }], 1089 | "link_groups": [{ 1090 | "heading": "Official Resources", 1091 | "links": [{ 1092 | "name": "GitHub", 1093 | "url": "https://github.com/flightjs/flight" 1094 | }, { 1095 | "name": "Demo Application", 1096 | "url": "http://twitter.github.io/flight/demo/" 1097 | }, { 1098 | "name": "Installation", 1099 | "url": "https://github.com/flightjs/flight/blob/master/README.md#installation" 1100 | }, { 1101 | "name": "Flight on Twitter", 1102 | "url": "http://twitter.com/flight" 1103 | }] 1104 | }] 1105 | }, 1106 | "gwt": { 1107 | "name": "Google Web Toolkit", 1108 | "description": "Google Web Toolkit (GWT) is a development toolkit for building and optimizing complex browser-based applications. GWT is used by many products at Google, including Google AdWords and Orkut. It's open source, completely free, and used by thousands of developers around the world.", 1109 | "homepage": "developers.google.com/web-toolkit", 1110 | "examples": [{ 1111 | "name": "Example", 1112 | "url": "examples/gwt" 1113 | }], 1114 | "link_groups": [{ 1115 | "heading": "Official Resources", 1116 | "links": [{ 1117 | "name": "Documentation", 1118 | "url": "https://developers.google.com/web-toolkit/doc/latest/DevGuide" 1119 | }, { 1120 | "name": "Getting Started with the GWT SDK", 1121 | "url": "https://developers.google.com/web-toolkit/gettingstarted" 1122 | }, { 1123 | "name": "Articles", 1124 | "url": "https://developers.google.com/web-toolkit/articles" 1125 | }, { 1126 | "name": "Case Studies", 1127 | "url": "https://developers.google.com/web-toolkit/casestudies" 1128 | }, { 1129 | "name": "Blog", 1130 | "url": "http://googlewebtoolkit.blogspot.com" 1131 | }, { 1132 | "name": "FAQ", 1133 | "url": "https://developers.google.com/web-toolkit/doc/latest/FAQ" 1134 | }] 1135 | }, { 1136 | "heading": "Community", 1137 | "links": [{ 1138 | "name": "Google Web Toolkit on Stack Overflow", 1139 | "url": "http://stackoverflow.com/questions/tagged/gwt" 1140 | }, { 1141 | "name": "Mailing list on Google Groups", 1142 | "url": "http://groups.google.com/group/Google-Web-Toolkit" 1143 | }, { 1144 | "name": "Google Web Toolkit on Twitter", 1145 | "url": "http://twitter.com/googledevtools" 1146 | }] 1147 | }] 1148 | }, 1149 | "javascript": { 1150 | "name": "JavaScript", 1151 | "description": "JavaScript® (often shortened to JS) is a lightweight, interpreted, object-oriented language with first-class functions, most known as the scripting language for Web pages, but used in many non-browser environments as well such as node.js or Apache CouchDB.", 1152 | "homepage": "developer.mozilla.org/en-US/docs/JavaScript", 1153 | "examples": [{ 1154 | "name": "Vanilla JavaScript Example", 1155 | "url": "examples/vanillajs" 1156 | }] 1157 | }, 1158 | "jquery": { 1159 | "name": "jQuery", 1160 | "description": "jQuery is a fast, small, and feature-rich JavaScript library. It makes things like HTML document traversal and manipulation, event handling, animation, and Ajax much simpler with an easy-to-use API that works across a multitude of browsers. With a combination of versatility and extensibility, jQuery has changed the way that millions of people write JavaScript.", 1161 | "homepage": "jquery.com", 1162 | "examples": [{ 1163 | "name": "Example", 1164 | "url": "examples/jquery" 1165 | }], 1166 | "link_groups": [{ 1167 | "heading": "Official Resources", 1168 | "links": [{ 1169 | "name": "How jQuery Works", 1170 | "url": "http://learn.jquery.com/about-jquery/how-jquery-works" 1171 | }, { 1172 | "name": "API Reference", 1173 | "url": "http://api.jquery.com" 1174 | }, { 1175 | "name": "Plugins", 1176 | "url": "http://plugins.jquery.com" 1177 | }, { 1178 | "name": "Brower Support", 1179 | "url": "http://jquery.com/browser-support" 1180 | }, { 1181 | "name": "Blog", 1182 | "url": "http://blog.jquery.com" 1183 | }] 1184 | }, { 1185 | "heading": "Articles and Guides", 1186 | "links": [{ 1187 | "name": "Try jQuery", 1188 | "url": "http://try.jquery.com" 1189 | }, { 1190 | "name": "jQuery Annotated Source", 1191 | "url": "http://robflaherty.github.io/jquery-annotated-source/" 1192 | }, { 1193 | "name": "10 Things I Learned From the jQuery Source", 1194 | "url": "http://paulirish.com/2010/10-things-i-learned-from-the-jquery-source" 1195 | }] 1196 | }, { 1197 | "heading": "Community", 1198 | "links": [{ 1199 | "name": "jQuery on Stack Overflow", 1200 | "url": "http://stackoverflow.com/questions/tagged/jquery" 1201 | }, { 1202 | "name": "Forums", 1203 | "url": "http://forum.jquery.com" 1204 | }, { 1205 | "name": "jQuery on Twitter", 1206 | "url": "http://twitter.com/jquery" 1207 | }, { 1208 | "name": "jQuery on Google+", 1209 | "url": "https://plus.google.com/102828491884671003608/posts" 1210 | }] 1211 | }] 1212 | }, 1213 | "kendo": { 1214 | "name": "Kendo UI", 1215 | "description": "Comprehensive HTML5/JavaScript framework for modern web and mobile app development.", 1216 | "homepage": "kendoui.com", 1217 | "examples": [{ 1218 | "name": "Example", 1219 | "url": "examples/kendo" 1220 | }], 1221 | "link_groups": [{ 1222 | "heading": "Official Resources", 1223 | "links": [{ 1224 | "name": "Documentation", 1225 | "url": "http://docs.kendoui.com" 1226 | }, { 1227 | "name": "API Reference", 1228 | "url": "http://docs.kendoui.com/api/dataviz/chart" 1229 | }, { 1230 | "name": "What is Kendo UI", 1231 | "url": "http://docs.kendoui.com/getting-started/introduction" 1232 | }, { 1233 | "name": "Applications built with Kendo UI", 1234 | "url": "http://demos.kendoui.com" 1235 | }, { 1236 | "name": "Blog", 1237 | "url": "http://www.kendoui.com/blogs.aspx" 1238 | }, { 1239 | "name": "FAQ", 1240 | "url": "http://www.kendoui.com/faq/faq.aspx" 1241 | }] 1242 | }, { 1243 | "heading": "Community", 1244 | "links": [{ 1245 | "name": "Kendo UI on Stack Overflow", 1246 | "url": "http://stackoverflow.com/questions/tagged/kendo-ui" 1247 | }, { 1248 | "name": "Kendo UI on Twitter", 1249 | "url": "http://twitter.com/kendoui" 1250 | }, { 1251 | "name": "Kendo UI on Google+", 1252 | "url": "https://plus.google.com/117798269023828336983/posts" 1253 | }] 1254 | }] 1255 | }, 1256 | "knockback": { 1257 | "name": "Knockback.js", 1258 | "description": "Both Knockout.js and Backbone.js have their strengths and weaknesses, but together they are amazing! With Knockback.js, you can use the strong ORM provided by Backbone and create dynamic views using Knockout bindings.", 1259 | "homepage": "kmalakoff.github.io/knockback", 1260 | "examples": [{ 1261 | "name": "Example", 1262 | "url": "examples/knockback" 1263 | }], 1264 | "link_groups": [{ 1265 | "heading": "Official Resources", 1266 | "links": [{ 1267 | "name": "Getting Started with Knockback.js", 1268 | "url": "http://kmalakoff.github.io/knockback/getting_started_introduction.html" 1269 | }, { 1270 | "name": "Tutorials", 1271 | "url": "http://kmalakoff.github.io/knockback/tutorials_introduction.html" 1272 | }, { 1273 | "name": "API Reference", 1274 | "url": "http://kmalakoff.github.io/knockback/doc/index.html" 1275 | }, { 1276 | "name": "Knockback.js Reference App", 1277 | "url": "http://kmalakoff.github.io/knockback/app_knockback_reference.html" 1278 | }, { 1279 | "name": "Knockback.js on Twitter", 1280 | "url": "http://twitter.com/knockbackjs" 1281 | }] 1282 | }] 1283 | }, 1284 | "knockoutjs": { 1285 | "name": "Knockout.js", 1286 | "description": "Knockout.js helps you simplify dynamic JavaScript UIs using the Model-View-ViewModel (MVVM) pattern.", 1287 | "homepage": "knockoutjs.com", 1288 | "examples": [{ 1289 | "name": "Example", 1290 | "url": "examples/knockoutjs" 1291 | }, { 1292 | "name": "Example", 1293 | "url": "examples/knockoutjs_require" 1294 | }], 1295 | "link_groups": [{ 1296 | "heading": "Official Resources", 1297 | "links": [{ 1298 | "name": "Documentation", 1299 | "url": "http://knockoutjs.com/documentation/introduction.html" 1300 | }, { 1301 | "name": "Tutorials", 1302 | "url": "http://learn.knockoutjs.com" 1303 | }, { 1304 | "name": "Live examples", 1305 | "url": "http://knockoutjs.com/examples" 1306 | }] 1307 | }, { 1308 | "heading": "Articles and Guides", 1309 | "links": [{ 1310 | "name": "Getting Started with Knockout.js", 1311 | "url": "http://www.adobe.com/devnet/html5/articles/getting-started-with-knockoutjs.html" 1312 | }, { 1313 | "name": "Into the Ring with Knockout.js", 1314 | "url": "http://net.tutsplus.com/tutorials/javascript-ajax/into-the-ring-with-knockout-js" 1315 | }, { 1316 | "name": "Beginners Guide to Knockout.js", 1317 | "url": "http://www.sitepoint.com/beginners-guide-to-knockoutjs-part-1" 1318 | }] 1319 | }, { 1320 | "heading": "Community", 1321 | "links": [{ 1322 | "name": "Knockout.js on Stack Overflow", 1323 | "url": "http://stackoverflow.com/questions/tagged/knockout" 1324 | }, { 1325 | "name": "Mailing list on Google Groups", 1326 | "url": "http://groups.google.com/group/knockoutjs" 1327 | }, { 1328 | "name": "Knockout.js on Twitter", 1329 | "url": "http://twitter.com/knockoutjs" 1330 | }, { 1331 | "name": "Knockout.js on Google+", 1332 | "url": "https://plus.google.com/communities/106789046312204355684/stream/c5bfcfdf-3690-44a6-b015-35aad4f4e42e" 1333 | }] 1334 | }] 1335 | }, 1336 | "lavaca": { 1337 | "name": "Lavaca", 1338 | "description": "A curated collection of tools for building mobile web applications.", 1339 | "homepage": "getlavaca.com", 1340 | "examples": [{ 1341 | "name": "Example", 1342 | "url": "examples/lavaca_require" 1343 | }], 1344 | "link_groups": [{ 1345 | "heading": "Official Resources", 1346 | "links": [{ 1347 | "name": "Guide", 1348 | "url": "http://getlavaca.com/#/guide" 1349 | }, { 1350 | "name": "API Reference", 1351 | "url": "http://getlavaca.com/#/apidoc" 1352 | }, { 1353 | "name": "Live examples", 1354 | "url": "http://getlavaca.com/#/examples" 1355 | }] 1356 | }, { 1357 | "heading": "Articles and Guides", 1358 | "links": [{ 1359 | "name": "Why Lavaca is the only sane HTML5 mobile development framework out there", 1360 | "url": "http://povolotski.me/2013/09/20/lavaca-intro/" 1361 | }] 1362 | }, { 1363 | "heading": "Community", 1364 | "links": [{ 1365 | "name": "Lavaca on Twitter", 1366 | "url": "http://twitter.com/getlavaca" 1367 | }] 1368 | }] 1369 | }, 1370 | "maria": { 1371 | "name": "Maria", 1372 | "description": "The MVC framework for JavaScript applications. The real MVC. The Smalltalk MVC. The Gang of Four MVC.", 1373 | "homepage": "peter.michaux.ca/maria", 1374 | "examples": [{ 1375 | "name": "Example", 1376 | "url": "examples/maria" 1377 | }], 1378 | "link_groups": [{ 1379 | "heading": "Official Resources", 1380 | "links": [{ 1381 | "name": "Quick Start Tutorial", 1382 | "url": "http://peter.michaux.ca/maria/quick-start-tutorial-for-the-impatient.html" 1383 | }, { 1384 | "name": "API Reference", 1385 | "url": "http://peter.michaux.ca/maria/api/maria.html" 1386 | }, { 1387 | "name": "GitHub", 1388 | "url": "https://github.com/petermichaux/maria" 1389 | }] 1390 | }] 1391 | }, 1392 | "marionettejs": { 1393 | "name": "Backbone.Marionette", 1394 | "description": "Backbone.Marionette is a composite application library for Backbone.js that aims to simplify the construction of large scale JavaScript applications.", 1395 | "homepage": "marionettejs.com", 1396 | "examples": [{ 1397 | "name": "Example", 1398 | "url": "examples/backbone_marionette" 1399 | }, { 1400 | "name": "Example", 1401 | "url": "examples/backbone_marionette_require" 1402 | }], 1403 | "link_groups": [{ 1404 | "heading": "Official Resources", 1405 | "links": [{ 1406 | "name": "API Reference", 1407 | "url": "https://github.com/marionettejs/backbone.marionette/tree/master/docs" 1408 | }, { 1409 | "name": "Applications built with Backbone.Marionette", 1410 | "url": "https://github.com/marionettejs/backbone.marionette/wiki/Projects-and-websites-using-marionette" 1411 | }, { 1412 | "name": "Introduction to Composite JavaScript Apps", 1413 | "url": "https://github.com/marionettejs/backbone.marionette/wiki/Introduction-to-composite-javascript-apps" 1414 | }, { 1415 | "name": "FAQ", 1416 | "url": "https://github.com/marionettejs/backbone.marionette/wiki#frequently-asked-questions" 1417 | }, { 1418 | "name": "Backbone.Marionette on GitHub", 1419 | "url": "https://github.com/marionettejs/backbone.marionette" 1420 | }] 1421 | }, { 1422 | "heading": "Articles and Guides", 1423 | "links": [{ 1424 | "name": "A Thorough Introduction to Backbone.Marionette", 1425 | "url": "http://coding.smashingmagazine.com/2013/02/11/introduction-backbone-marionette" 1426 | }, { 1427 | "name": "Backbone Marionette: Better Backbone Apps", 1428 | "url": "http://www.joezimjs.com/javascript/backbone-marionette-better-backbone-apps" 1429 | }] 1430 | }, { 1431 | "heading": "Community", 1432 | "links": [{ 1433 | "name": "Backbone.Marionette on Stack Overflow", 1434 | "url": "http://stackoverflow.com/questions/tagged/backbone.marionette" 1435 | }, { 1436 | "name": "Backbone.Marionette on Twitter", 1437 | "url": "http://twitter.com/marionettejs" 1438 | }] 1439 | }] 1440 | }, 1441 | "meteor": { 1442 | "name": "Meteor", 1443 | "description": "Meteor is an open-source platform for building top-quality web apps in a fraction of the time, whether you're an expert developer or just getting started.", 1444 | "homepage": "meteor.com", 1445 | "examples": [{ 1446 | "name": "Real-time Example", 1447 | "url": "http://todomvcapp.meteor.com", 1448 | "source_url": "examples/meteor" 1449 | }], 1450 | "link_groups": [{ 1451 | "heading": "Official Resources", 1452 | "links": [{ 1453 | "name": "Documentation", 1454 | "url": "http://docs.meteor.com" 1455 | }, { 1456 | "name": "Applications built with Meteor", 1457 | "url": "http://madewith.meteor.com" 1458 | }, { 1459 | "name": "Examples", 1460 | "url": "http://meteor.com/examples" 1461 | }, { 1462 | "name": "Blog", 1463 | "url": "http://meteor.com/blog" 1464 | }, { 1465 | "name": "FAQ", 1466 | "url": "http://meteor.com/faq" 1467 | }, { 1468 | "name": "Meteor on GitHub", 1469 | "url": "https://github.com/meteor" 1470 | }, { 1471 | "name": "Meteor on YouTube", 1472 | "url": "http://www.youtube.com/user/MeteorVideos" 1473 | }] 1474 | }, { 1475 | "heading": "Articles and Guides", 1476 | "links": [{ 1477 | "name": "Learn Meteor Fundamentals and Best Practices", 1478 | "url": "http://andrewscala.com/meteor" 1479 | }, { 1480 | "name": "Introduction to Realtime Web with Meteor and Node.js", 1481 | "url": "http://www.andrewmunsell.com/blog/introduction-to-realtime-web-meteor-and-nodejs" 1482 | }, { 1483 | "name": "Confessions of a Meteor Newb", 1484 | "url": "http://blog.jerodsanto.net/2012/04/confessions-of-a-meteor-newb" 1485 | }] 1486 | }, { 1487 | "heading": "Community", 1488 | "links": [{ 1489 | "name": "Meteor on Stack Overflow", 1490 | "url": "http://stackoverflow.com/questions/tagged/meteor" 1491 | }, { 1492 | "name": "Mailing list on Google Groups", 1493 | "url": "https://groups.google.com/forum/?fromgroups#!forum/meteor-core" 1494 | }, { 1495 | "name": "Meteor on Twitter", 1496 | "url": "http://twitter.com/meteorjs" 1497 | }] 1498 | }] 1499 | }, 1500 | "mithril": { 1501 | "name": "Mithril", 1502 | "description": "Mithril is a client-side MVC framework - a tool to organize code in a way that is easy to think about and to maintain.", 1503 | "homepage": "lhorie.github.io/mithril/", 1504 | "examples": [{ 1505 | "name": "Architecture Example", 1506 | "url": "examples/mithril" 1507 | }], 1508 | "link_groups": [{ 1509 | "heading": "Official Resources", 1510 | "links": [{ 1511 | "name": "Documentation", 1512 | "url": "http://lhorie.github.io/mithril/getting-started.html" 1513 | }, { 1514 | "name": "API Reference", 1515 | "url": "http://lhorie.github.io/mithril/mithril.html" 1516 | }, { 1517 | "name": "Tutorials", 1518 | "url": "http://lhorie.github.io/mithril-blog/" 1519 | }, { 1520 | "name": "Mithril on Github", 1521 | "url": "https://github.com/lhorie/mithril.js" 1522 | }] 1523 | }, { 1524 | "heading": "Community", 1525 | "links": [{ 1526 | "name": "Mailing list on Google Groups", 1527 | "url": "https://groups.google.com/forum/#!forum/mithriljs" 1528 | }, { 1529 | "name": "StackOverflow", 1530 | "url": "http://stackoverflow.com/questions/tagged/mithril.js" 1531 | }, { 1532 | "name": "Projects and Snippets", 1533 | "url": "https://github.com/lhorie/mithril.js/wiki/Community-Projects-and-Snippets" 1534 | }] 1535 | }] 1536 | }, 1537 | "montage": { 1538 | "name": "MontageJS", 1539 | "description": "MontageJS is a framework for building rich HTML5 applications optimized for today and tomorrow’s range of connected devices. It offers time-tested design patterns and software principles, a modular architecture, a friendly method to achieve a clean separation of concerns, and supports sharing packages and modules with your NodeJS server.", 1540 | "homepage": "montagejs.org", 1541 | "examples": [{ 1542 | "name": "Example", 1543 | "url": "examples/montage" 1544 | }], 1545 | "link_groups": [{ 1546 | "heading": "Official Resources", 1547 | "links": [{ 1548 | "name": "Quick Start", 1549 | "url": "http://montagejs.org/docs/montagejs-setup.html" 1550 | }, { 1551 | "name": "Documentation", 1552 | "url": "http://montagejs.org/docs" 1553 | }, { 1554 | "name": "API Reference", 1555 | "url": "http://montagejs.org/api" 1556 | }, { 1557 | "name": "Applications built with MontageJS", 1558 | "url": "http://montagejs.org/apps" 1559 | }, { 1560 | "name": "MontageJS on GitHub", 1561 | "url": "https://github.com/montagejs/montage" 1562 | }, { 1563 | "name": "Minit - MontageJS Initializer", 1564 | "url": "https://github.com/montagejs/minit" 1565 | }, { 1566 | "name": "MOP - MontageJS Optimizer", 1567 | "url": "https://github.com/montagejs/mop" 1568 | }] 1569 | }, { 1570 | "heading": "Articles and Guides", 1571 | "links": [{ 1572 | "name": "YouTube - Getting Started", 1573 | "url": "http://www.youtube.com/watch?v=JfT1ML200JI" 1574 | }, { 1575 | "name": "My First MontageJS Application", 1576 | "url": "http://renaun.com/blog/2013/05/my-first-montagejs-application/" 1577 | }] 1578 | }, { 1579 | "heading": "Community", 1580 | "links": [{ 1581 | "name": "IRC", 1582 | "url": "http://webchat.freenode.net/?channels=montage" 1583 | }, { 1584 | "name": "Mailing list on Google Groups", 1585 | "url": "https://groups.google.com/forum/?fromgroups#!forum/montagejs" 1586 | }, { 1587 | "name": "Montage on Twitter", 1588 | "url": "http://twitter.com/montagejs" 1589 | }, { 1590 | "name": "Montage on Google+", 1591 | "url": "https://plus.google.com/116915300739108010954" 1592 | }] 1593 | }] 1594 | }, 1595 | "olives": { 1596 | "name": "Olives.js", 1597 | "description": "A JS Framework for creating realtime and scalable applications. Based on Emily.js and socket.io.", 1598 | "homepage": "flams.github.io/olives", 1599 | "examples": [{ 1600 | "name": "Example", 1601 | "url": "examples/olives" 1602 | }], 1603 | "link_groups": [{ 1604 | "heading": "Official Resources", 1605 | "links": [{ 1606 | "name": "Documentation", 1607 | "url": "http://flams.github.io/olives/docs/latest" 1608 | }, { 1609 | "name": "Applications built with Olives.js", 1610 | "url": "http://flams.github.io/olives/#liveexamples" 1611 | }, { 1612 | "name": "Olives.js on GitHub", 1613 | "url": "https://github.com/flams/olives" 1614 | }] 1615 | }] 1616 | }, 1617 | "plastronjs": { 1618 | "name": "PlastronJS", 1619 | "description": "PlastronJS is an MVC library which uses the Google Closure library for use with the Closure Compiler.", 1620 | "homepage": "rhysbrettbowen.github.io/PlastronJS", 1621 | "examples": [{ 1622 | "name": "Example", 1623 | "url": "examples/plastronjs" 1624 | }], 1625 | "link_groups": [{ 1626 | "heading": "Official Resources", 1627 | "links": [{ 1628 | "name": "PlastronJS on GitHub", 1629 | "url": "https://github.com/rhysbrettbowen/PlastronJS" 1630 | }] 1631 | }, { 1632 | "heading": "Articles and Guides", 1633 | "links": [{ 1634 | "name": "The Future of PlastronJS", 1635 | "url": "http://modernjavascript.blogspot.com/2012/11/the-future-of-plastronjs.html" 1636 | }, { 1637 | "name": "Krisztian Toth's JavaScript Games, XRegExp, PlastronJS", 1638 | "url": "http://dailyjs.com/2012/04/06/toth-xregexp-plastron" 1639 | }] 1640 | }] 1641 | }, 1642 | "polymer": { 1643 | "name": "Polymer", 1644 | "description": "Polymer is a new type of library for the web, built on top of Web Components, and designed to leverage the evolving web platform on modern browsers. It is comprised of core platform features (e.g Shadow DOM, Custom Elements, MDV) enabled with polyfills and a next generation web application framework built on these technologies.", 1645 | "homepage": "polymer-project.org", 1646 | "examples": [{ 1647 | "name": "Example", 1648 | "url": "examples/polymer" 1649 | }], 1650 | "link_groups": [{ 1651 | "heading": "Official Resources", 1652 | "links": [{ 1653 | "name": "Documentation", 1654 | "url": "http://www.polymer-project.org/docs/start/usingelements.html" 1655 | }, { 1656 | "name": "API Reference", 1657 | "url": "http://www.polymer-project.org/docs/polymer/polymer.html" 1658 | }, { 1659 | "name": "Polymer on GitHub", 1660 | "url": "https://github.com/polymer" 1661 | }, { 1662 | "name": "Polymer on Stack Overflow", 1663 | "url": "http://stackoverflow.com/questions/tagged/polymer" 1664 | }] 1665 | }, { 1666 | "heading": "Videos", 1667 | "links": [{ 1668 | "name": "Polymer And The Web Components Revolution", 1669 | "url": "https://www.youtube.com/watch?v=yRbOSdAe_JU" 1670 | }, { 1671 | "name": "Polymer And Web Components Change Everything", 1672 | "url": "https://www.youtube.com/watch?v=8OJ7ih8EE7s" 1673 | }] 1674 | }, { 1675 | "heading": "Community", 1676 | "links": [{ 1677 | "name": "Mailing list on Google Groups", 1678 | "url": "https://groups.google.com/forum/#!msg/polymer-dev/" 1679 | }, { 1680 | "name": "Web Components on Google+", 1681 | "url": "https://plus.google.com/103330502635338602217/" 1682 | }] 1683 | }] 1684 | }, 1685 | "puremvc": { 1686 | "name": "PureMVC", 1687 | "description": "PureMVC is a lightweight framework for creating applications based upon the classic Model, View and Controller concept.", 1688 | "homepage": "puremvc.org", 1689 | "examples": [{ 1690 | "name": "Example", 1691 | "url": "examples/puremvc" 1692 | }], 1693 | "link_groups": [{ 1694 | "heading": "Official Resources", 1695 | "links": [{ 1696 | "name": "Documentation", 1697 | "url": "http://puremvc.org/content/view/98/189" 1698 | }, { 1699 | "name": "Applications built with PureMVC", 1700 | "url": "http://puremvc.org/content/blogsection/9/176" 1701 | }, { 1702 | "name": "FAQ", 1703 | "url": "http://puremvc.org/content/section/3/188" 1704 | }, { 1705 | "name": "PureMVC on GitHub", 1706 | "url": "https://github.com/puremvc" 1707 | }] 1708 | }, { 1709 | "heading": "Articles and Guides", 1710 | "links": [{ 1711 | "name": "PureMVC Performance Test", 1712 | "url": "http://blog.kaegi.net/puremvc-performance-test-compared-to-using-no-framework" 1713 | }] 1714 | }, { 1715 | "heading": "Community", 1716 | "links": [{ 1717 | "name": "PureMVC on Stack Overflow", 1718 | "url": "http://stackoverflow.com/questions/tagged/puremvc" 1719 | }, { 1720 | "name": "PureMVC on Twitter", 1721 | "url": "http://twitter.com/puremvc" 1722 | }, { 1723 | "name": "PureMVC on Google+", 1724 | "url": "https://plus.google.com/+puremvc/posts" 1725 | }] 1726 | }] 1727 | }, 1728 | "ractive": { 1729 | "name": "Ractive.js", 1730 | "description": "Ractive is a next-generation DOM manipulation library for creating reactive user interfaces, optimised for developer sanity. It was originally developed to create interactive news applications at theguardian.com.", 1731 | "homepage": "ractivejs.org", 1732 | "examples": [{ 1733 | "name": "Example", 1734 | "url": "examples/ractive" 1735 | }], 1736 | "link_groups": [{ 1737 | "heading": "Official Resources", 1738 | "links": [{ 1739 | "name": "Ractive.js on GitHub", 1740 | "url": "https://github.com/RactiveJS/Ractive" 1741 | }, { 1742 | "name": "Wiki", 1743 | "url": "https://github.com/RactiveJS/Ractive/wiki" 1744 | }, { 1745 | "name": "60-second setup", 1746 | "url": "https://github.com/Rich-Harris/Ractive/wiki/60-second-setup" 1747 | }, { 1748 | "name": "Interactive tutorials", 1749 | "url": "http://learn.ractivejs.org" 1750 | }, { 1751 | "name": "Examples", 1752 | "url": "http://ractivejs.org/examples" 1753 | }] 1754 | }, { 1755 | "heading": "Community", 1756 | "links": [{ 1757 | "name": "Ractive.js on Twitter", 1758 | "url": "http://twitter.com/RactiveJS" 1759 | }, { 1760 | "name": "Ractive.js on Stack Overflow", 1761 | "url": "http://stackoverflow.com/questions/tagged/ractivejs" 1762 | }] 1763 | }] 1764 | }, 1765 | "rappidjs": { 1766 | "name": "rAppid.js", 1767 | "description": "The declarative Rich Internet Application Javascript MVC Framework.", 1768 | "homepage": "rappidjs.com", 1769 | "examples": [{ 1770 | "name": "Example", 1771 | "url": "examples/rappidjs" 1772 | }], 1773 | "link_groups": [{ 1774 | "heading": "Official Resources", 1775 | "links": [{ 1776 | "name": "API Reference", 1777 | "url": "http://www.rappidjs.com/#/api" 1778 | }, { 1779 | "name": "Wiki", 1780 | "url": "http://www.rappidjs.com/#/wiki" 1781 | }, { 1782 | "name": "UI Components", 1783 | "url": "http://www.rappidjs.com/#/ui" 1784 | }, { 1785 | "name": "Blog", 1786 | "url": "http://blog.rappidjs.com" 1787 | }, { 1788 | "name": "rAppid.js on GitHub", 1789 | "url": "https://github.com/rappid/rAppid.js" 1790 | }] 1791 | }, { 1792 | "heading": "Community", 1793 | "links": [{ 1794 | "name": "rAppid.js on Twitter", 1795 | "url": "http://twitter.com/rappidjs" 1796 | }] 1797 | }] 1798 | }, 1799 | "react": { 1800 | "name": "React", 1801 | "description": "React is a JavaScript library for creating user interfaces. Its core principles are declarative code, efficiency, and flexibility. Simply specify what your component looks like and React will keep it up-to-date when the underlying data changes.", 1802 | "homepage": "facebook.github.io/react", 1803 | "examples": [{ 1804 | "name": "Example", 1805 | "url": "examples/react" 1806 | }, { 1807 | "name": "React & Backbone.js", 1808 | "url": "examples/react-backbone" 1809 | }], 1810 | "link_groups": [{ 1811 | "heading": "Official Resources", 1812 | "links": [{ 1813 | "name": "Tutorial", 1814 | "url": "http://facebook.github.io/react/docs/tutorial.html" 1815 | }, { 1816 | "name": "Philosophy", 1817 | "url": "http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood" 1818 | }, { 1819 | "name": "Support", 1820 | "url": "http://facebook.github.io/react/support.html" 1821 | }, { 1822 | "name": "Flux architecture example", 1823 | "url": "https://github.com/facebook/react/tree/master/examples/todomvc-flux" 1824 | }] 1825 | }, { 1826 | "heading": "Community", 1827 | "links": [{ 1828 | "name": "ReactJS on Stack Overflow", 1829 | "url": "https://stackoverflow.com/questions/tagged/reactjs" 1830 | }, { 1831 | "name": "Google Groups Mailing List", 1832 | "url": "https://groups.google.com/group/reactjs" 1833 | }, { 1834 | "name": "IRC", 1835 | "url": "irc://chat.freenode.net/reactjs" 1836 | }] 1837 | }] 1838 | }, 1839 | "sammyjs": { 1840 | "name": "Sammy.js", 1841 | "description": "A small web framework with class.", 1842 | "homepage": "sammyjs.org", 1843 | "examples": [{ 1844 | "name": "Example", 1845 | "url": "examples/sammyjs" 1846 | }], 1847 | "link_groups": [{ 1848 | "heading": "Official Resources", 1849 | "links": [{ 1850 | "name": "Introduction", 1851 | "url": "http://sammyjs.org/intro" 1852 | }, { 1853 | "name": "Documentation", 1854 | "url": "http://sammyjs.org/docs" 1855 | }, { 1856 | "name": "Wiki", 1857 | "url": "http://sammyjs.org/wiki" 1858 | }, { 1859 | "name": "FAQ", 1860 | "url": "http://sammyjs.org/faq" 1861 | }, { 1862 | "name": "Sammy.js on GitHub", 1863 | "url": "http://github.com/quirkey/sammy" 1864 | }] 1865 | }, { 1866 | "heading": "Articles and Guides", 1867 | "links": [{ 1868 | "name": "Sammy.js For RESTful Evented JavaScript", 1869 | "url": "http://churchm.ag/sammy-js-for-restful-evented-javascript" 1870 | }] 1871 | }, { 1872 | "heading": "Community", 1873 | "links": [{ 1874 | "name": "Mailing list on Google Groups", 1875 | "url": "http://groups.google.com/group/sammyjs" 1876 | }, { 1877 | "name": "Sammy.js on Twitter", 1878 | "url": "http://twitter.com/sammy_js" 1879 | }] 1880 | }] 1881 | }, 1882 | "sapui5": { 1883 | "name": "SAPUI5", 1884 | "description": "SAP's HTML5-based UI technology that allows you to build rich, interactive Web applications.", 1885 | "homepage": "scn.sap.com/community/developer-center/front-end", 1886 | "examples": [{ 1887 | "name": "Example", 1888 | "url": "examples/sapui5" 1889 | }], 1890 | "link_groups": [{ 1891 | "heading": "Official Resources", 1892 | "links": [{ 1893 | "name": "Introduction", 1894 | "url": "http://scn.sap.com/community/developer-center/front-end/blog/2013/03/19/how-to-build-testable-sapui5-applications" 1895 | }, { 1896 | "name": "Getting Started", 1897 | "url": "https://sapui5.netweaver.ondemand.com/" 1898 | }, { 1899 | "name": "API Reference", 1900 | "url": "https://sapui5.netweaver.ondemand.com/sdk/#content/Overview.html" 1901 | }, { 1902 | "name": "Twitter Search", 1903 | "url": "https://twitter.com/search?q=%23sapui5" 1904 | }, { 1905 | "name": "OpenUI5 Twitter", 1906 | "url": "https://twitter.com/OpenUI5" 1907 | }] 1908 | }] 1909 | }, 1910 | "serenadejs": { 1911 | "name": "Serenade.js", 1912 | "description": "Serenade.js is a client side framework built on the MVC pattern. It makes it simple to create rich client side applications by freeing you from having to keep the DOM up to date with your data through powerful data bindings.", 1913 | "homepage": "serenadejs.org", 1914 | "examples": [{ 1915 | "name": "Example", 1916 | "url": "examples/serenadejs" 1917 | }], 1918 | "link_groups": [{ 1919 | "heading": "Official Resources", 1920 | "links": [{ 1921 | "name": "Introduction", 1922 | "url": "http://serenadejs.org/introduction.html" 1923 | }, { 1924 | "name": "Applications built with Serenade.js", 1925 | "url": "http://serenade.herokuapp.com" 1926 | }, { 1927 | "name": "Serenade.js on GitHub", 1928 | "url": "https://github.com/elabs/serenade.js" 1929 | }] 1930 | }, { 1931 | "heading": "Community", 1932 | "links": [{ 1933 | "name": "Serenade.js on Twitter", 1934 | "url": "http://twitter.com/serenadejs" 1935 | }] 1936 | }] 1937 | }, 1938 | "socketstream": { 1939 | "name": "SocketStream", 1940 | "description": "SocketStream 0.3 is a fast, modular Node.js web framework dedicated to building realtime single-page apps.", 1941 | "homepage": "socketstream.org", 1942 | "examples": [{ 1943 | "name": "Real-time Example", 1944 | "url": "examples/socketstream/README.md", 1945 | "source_url": "examples/socketstream" 1946 | }], 1947 | "link_groups": [{ 1948 | "heading": "Official Resources", 1949 | "links": [{ 1950 | "name": "Tour", 1951 | "url": "http://www.socketstream.org/tour" 1952 | }, { 1953 | "name": "SocketStream on GitHub", 1954 | "url": "https://github.com/socketstream" 1955 | }] 1956 | }, { 1957 | "heading": "Articles and Guides", 1958 | "links": [{ 1959 | "name": "Video - Owen Barnes introduces SocketStream", 1960 | "url": "http://www.infoq.com/presentations/SocketStream" 1961 | }] 1962 | }, { 1963 | "heading": "Community", 1964 | "links": [{ 1965 | "name": "SocketStream on Stack Overflow", 1966 | "url": "http://stackoverflow.com/questions/tagged/socketstream" 1967 | }, { 1968 | "name": "SocketStream on Twitter", 1969 | "url": "http://twitter.com/socketstream" 1970 | }] 1971 | }] 1972 | }, 1973 | "somajs": { 1974 | "name": "soma.js", 1975 | "description": "soma.js is a framework created to build scalable and maintainable javascript applications.", 1976 | "homepage": "somajs.github.io/somajs", 1977 | "examples": [{ 1978 | "name": "Example", 1979 | "url": "examples/somajs" 1980 | }, { 1981 | "name": "Example", 1982 | "url": "examples/somajs_require" 1983 | }], 1984 | "link_groups": [{ 1985 | "heading": "Official Resources", 1986 | "links": [{ 1987 | "name": "Slides: Introduction", 1988 | "url": "http://somajs.github.io/somajs/#/1" 1989 | }, { 1990 | "name": "Quick Start", 1991 | "url": "http://somajs.github.io/somajs/site/#quick-start" 1992 | }, { 1993 | "name": "Demos", 1994 | "url": "http://somajs.github.io/somajs/site/#demos" 1995 | }, { 1996 | "name": "Blog", 1997 | "url": "http://www.soundstep.com/blog" 1998 | }, { 1999 | "name": "soma.js on GitHub", 2000 | "url": "https://github.com/somajs/somajs" 2001 | }] 2002 | }, { 2003 | "heading": "Community", 2004 | "links": [{ 2005 | "name": "Mailing list on Google Groups", 2006 | "url": "https://groups.google.com/forum/#!forum/somajs" 2007 | }, { 2008 | "name": "soma.js on Twitter", 2009 | "url": "http://twitter.com/soundstep" 2010 | }] 2011 | }] 2012 | }, 2013 | "spine": { 2014 | "name": "Spine.js", 2015 | "description": "Build Awesome JavaScript MVC Applications.", 2016 | "homepage": "spinejs.com", 2017 | "examples": [{ 2018 | "name": "Example", 2019 | "url": "examples/spine" 2020 | }], 2021 | "link_groups": [{ 2022 | "heading": "Official Resources", 2023 | "links": [{ 2024 | "name": "Documentation", 2025 | "url": "http://spinejs.com/docs/" 2026 | }, { 2027 | "name": "Step by Step Tutorials", 2028 | "url": "http://spinejs.com/docs/example" 2029 | }, { 2030 | "name": "API Reference", 2031 | "url": "http://spinejs.com/api/index" 2032 | }] 2033 | }, { 2034 | "heading": "Articles and Guides", 2035 | "links": [{ 2036 | "name": "Building JavaScript Web Apps With MVC & Spine.js", 2037 | "url": "http://addyosmani.com/blog/building-apps-spinejs" 2038 | }] 2039 | }, { 2040 | "heading": "Community", 2041 | "links": [{ 2042 | "name": "Spine on Stack Overflow", 2043 | "url": "http://stackoverflow.com/questions/tagged/spine.js" 2044 | }, { 2045 | "name": "Mailing list on Google Groups", 2046 | "url": "https://groups.google.com/forum/#!forum/spinejs" 2047 | }, { 2048 | "name": "Spine's author, Alex MacCaw, on Twitter", 2049 | "url": "http://twitter.com/maccman" 2050 | }] 2051 | }] 2052 | }, 2053 | "stapes": { 2054 | "name": "Stapes.js", 2055 | "description": "A (really) tiny Javascript MVC microframework.", 2056 | "homepage": "hay.github.io/stapes", 2057 | "examples": [{ 2058 | "name": "Example", 2059 | "url": "examples/stapes" 2060 | }, { 2061 | "name": "Example", 2062 | "url": "examples/stapes_require" 2063 | }], 2064 | "link_groups": [{ 2065 | "heading": "Official Resources", 2066 | "links": [{ 2067 | "name": "Introduction", 2068 | "url": "http://hay.github.io/stapes/#m-intro" 2069 | }, { 2070 | "name": "Documentation & API Reference", 2071 | "url": "http://hay.github.io/stapes" 2072 | }, { 2073 | "name": "Stapes.js on GitHub", 2074 | "url": "http://github.com/hay/stapes" 2075 | }] 2076 | }] 2077 | }, 2078 | "thorax": { 2079 | "name": "Thorax", 2080 | "description": "An opinionated, battle-tested Backbone + Handlebars framework to build large scale web applications.", 2081 | "homepage": "thoraxjs.org", 2082 | "examples": [{ 2083 | "name": "Example", 2084 | "url": "examples/thorax" 2085 | }, { 2086 | "name": "Thorax & Lumbar", 2087 | "url": "examples/thorax_lumbar/public" 2088 | }], 2089 | "link_groups": [{ 2090 | "heading": "Official Resources", 2091 | "links": [{ 2092 | "name": "Getting Started", 2093 | "url": "http://thoraxjs.org/start.html" 2094 | }, { 2095 | "name": "API Reference", 2096 | "url": "http://thoraxjs.org/api.html" 2097 | }, { 2098 | "name": "Screencast - Introduction to Thorax", 2099 | "url": "http://vimeo.com/60230630" 2100 | }, { 2101 | "name": "Seed Project", 2102 | "url": "https://github.com/walmartlabs/thorax-seed" 2103 | }, { 2104 | "name": "Thorax on GitHub", 2105 | "url": "https://github.com/walmartlabs/thorax" 2106 | }] 2107 | }, { 2108 | "heading": "Community", 2109 | "links": [{ 2110 | "name": "Thorax on Twitter", 2111 | "url": "http://twitter.com/walmartlabs" 2112 | }] 2113 | }] 2114 | }, 2115 | "troopjs": { 2116 | "name": "TroopJS", 2117 | "description": "The simple js framework that does as little as possible, then stays out of the way.", 2118 | "homepage": "troopjs.com", 2119 | "examples": [{ 2120 | "name": "Example", 2121 | "url": "examples/troopjs_require" 2122 | }], 2123 | "link_groups": [{ 2124 | "heading": "Official Resources", 2125 | "links": [{ 2126 | "name": "TODOs Application (latest)", 2127 | "url": "https://github.com/troopjs/troopjs-todos" 2128 | }, { 2129 | "name": "TroopJS on GitHub", 2130 | "url": "https://github.com/troopjs" 2131 | }] 2132 | }] 2133 | }, 2134 | "typescript": { 2135 | "name": "TypeScript", 2136 | "description": "TypeScript is a language for application-scale JavaScript development. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open Source.", 2137 | "homepage": "typescriptlang.org", 2138 | "examples": [{ 2139 | "name": "TypeScript & AngularJS", 2140 | "url": "examples/typescript-angular" 2141 | }, { 2142 | "name": "TypeScript & Backbone.js", 2143 | "url": "examples/typescript-backbone" 2144 | }], 2145 | "link_groups": [{ 2146 | "heading": "Official Resources", 2147 | "links": [{ 2148 | "name": "Tutorial", 2149 | "url": "http://www.typescriptlang.org/Tutorial" 2150 | }, { 2151 | "name": "Code Playground", 2152 | "url": "http://www.typescriptlang.org/Playground" 2153 | }, { 2154 | "name": "Documentation", 2155 | "url": "http://typescript.codeplex.com/documentation" 2156 | }, { 2157 | "name": "Applications built with TypeScript", 2158 | "url": "http://www.typescriptlang.org/Samples" 2159 | }, { 2160 | "name": "Blog", 2161 | "url": "http://blogs.msdn.com/b/typescript" 2162 | }, { 2163 | "name": "Source Code", 2164 | "url": "http://typescript.codeplex.com/sourcecontrol/latest#README.txt" 2165 | }] 2166 | }, { 2167 | "heading": "Articles and Guides", 2168 | "links": [{ 2169 | "name": "Thoughts on TypeScript", 2170 | "url": "http://www.nczonline.net/blog/2012/10/04/thoughts-on-typescript" 2171 | }, { 2172 | "name": "ScreenCast - Why I Like TypeScript", 2173 | "url": "http://www.leebrimelow.com/why-i-like-typescripts" 2174 | }] 2175 | }, { 2176 | "heading": "Community", 2177 | "links": [{ 2178 | "name": "TypeScript on Stack Overflow", 2179 | "url": "http://stackoverflow.com/questions/tagged/typescript" 2180 | }, { 2181 | "name": "Forums", 2182 | "url": "http://typescript.codeplex.com/discussions" 2183 | }, { 2184 | "name": "TypeScript on Twitter", 2185 | "url": "http://twitter.com/typescriptlang" 2186 | }] 2187 | }] 2188 | }, 2189 | "vue": { 2190 | "name": "Vue.js", 2191 | "description": "Vue.js provides efficient MVVM data bindings with a simple and flexible API. It uses plain JavaScript object models, DOM-based templating and extendable directives and filters.", 2192 | "homepage": "vuejs.org", 2193 | "examples": [{ 2194 | "name": "Example", 2195 | "url": "examples/vue" 2196 | }], 2197 | "link_groups": [{ 2198 | "heading": "Official Resources", 2199 | "links": [{ 2200 | "name": "Documentation", 2201 | "url": "http://vuejs.org/guide/" 2202 | }, { 2203 | "name": "API Reference", 2204 | "url": "http://vuejs.org/api/" 2205 | }, { 2206 | "name": "Examples", 2207 | "url": "http://vuejs.org/examples/" 2208 | }, { 2209 | "name": "Vue.js on GitHub", 2210 | "url": "https://github.com/yyx990803/vue" 2211 | }] 2212 | }, { 2213 | "heading": "Community", 2214 | "links": [{ 2215 | "name": "Twitter", 2216 | "url": "http://twitter.com/vuejs" 2217 | }, { 2218 | "name": "Google+ Community", 2219 | "url": "https://plus.google.com/communities/112229843610661683911" 2220 | }, { 2221 | "name": "IRC Channel: #vuejs", 2222 | "url": "http://freenode.net/faq.shtml#whatwhy" 2223 | }] 2224 | }] 2225 | }, 2226 | "yui": { 2227 | "name": "YUI", 2228 | "description": "YUI is a free, open source JavaScript and CSS library for building richly interactive web applications.", 2229 | "homepage": "yuilibrary.com", 2230 | "examples": [{ 2231 | "name": "Example", 2232 | "url": "examples/yui" 2233 | }], 2234 | "link_groups": [{ 2235 | "heading": "Official Resources", 2236 | "links": [{ 2237 | "name": "Documentation", 2238 | "url": "http://yuilibrary.com/yui/docs" 2239 | }, { 2240 | "name": "Quick Start", 2241 | "url": "http://yuilibrary.com/yui/quick-start" 2242 | }, { 2243 | "name": "Tutorials", 2244 | "url": "http://yuilibrary.com/yui/docs/tutorials" 2245 | }, { 2246 | "name": "Examples", 2247 | "url": "http://yuilibrary.com/yui/docs/examples" 2248 | }, { 2249 | "name": "Blog", 2250 | "url": "http://yuiblog.com" 2251 | }] 2252 | }, { 2253 | "heading": "Community", 2254 | "links": [{ 2255 | "name": "YUI on Stack Overflow", 2256 | "url": "http://stackoverflow.com/questions/tagged/yui" 2257 | }, { 2258 | "name": "Forums", 2259 | "url": "http://yuilibrary.com/forum" 2260 | }, { 2261 | "name": "YUI on Twitter", 2262 | "url": "http://twitter.com/yuilibrary" 2263 | }] 2264 | }] 2265 | }, 2266 | "templates": { 2267 | "todomvc": "

<%= name %>

<% if (typeof examples !== 'undefined') { %> <% examples.forEach(function (example) { %>
<%= example.name %>
<% if (!location.href.match(example.url + '/')) { %> \">Demo, <% } %> \">Source <% }); %> <% } %>

<%= description %>

<% if (typeof link_groups !== 'undefined') { %>
<% link_groups.forEach(function (link_group) { %>

<%= link_group.heading %>

<% }); %> <% } %>

If you have other helpful links to share, or find any of the links above no longer work, please let us know.
" 2268 | } 2269 | } 2270 | 2271 | -------------------------------------------------------------------------------- /frontend/readme.md: -------------------------------------------------------------------------------- 1 | # Ember.js TodoMVC Example 2 | 3 | > A framework for creating ambitious web applications. 4 | 5 | > _[Ember.js - emberjs.com](http://emberjs.com)_ 6 | 7 | 8 | ## Learning Ember.js 9 | 10 | The [Ember.js website](http://emberjs.com) is a great resource for getting started. 11 | 12 | Here are some links you may find helpful: 13 | 14 | * [Guides](http://emberjs.com/guides) 15 | * [API Reference](http://emberjs.com/api) 16 | * [Screencast - Building an App with Ember.js](https://www.youtube.com/watch?v=Ga99hMi7wfY) 17 | * [Applications built with Ember.js](http://emberjs.com/ember-users) 18 | * [Blog](http://emberjs.com/blog) 19 | 20 | Articles and guides from the community: 21 | 22 | * [Getting Into Ember.js](http://net.tutsplus.com/tutorials/javascript-ajax/getting-into-ember-js) 23 | * [EmberWatch](http://emberwatch.com) 24 | * [CodeSchool course Warming Up With Ember.js](https://www.codeschool.com/courses/warming-up-with-emberjs) 25 | 26 | Get help from other Ember.js users: 27 | 28 | * [Ember.js on StackOverflow](http://stackoverflow.com/questions/tagged/ember.js) 29 | * [Ember.js on Twitter](http://twitter.com/emberjs) 30 | * [Ember.js on Google +](https://plus.google.com/communities/106387049790387471205) 31 | 32 | _If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._ 33 | -------------------------------------------------------------------------------- /src/bin/create_databases.rs: -------------------------------------------------------------------------------- 1 | extern crate postgres; 2 | 3 | use postgres::{Connection, SslMode}; 4 | 5 | fn main() { 6 | let conn = Connection::connect("postgres://rustmvc@localhost", 7 | &SslMode::None).unwrap(); 8 | 9 | conn.execute("CREATE TABLE todos ( 10 | id SERIAL PRIMARY KEY, 11 | title VARCHAR NOT NULL, 12 | is_completed BOOLEAN NOT NULL 13 | )", &[]).unwrap(); 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate nickel; 2 | extern crate postgres; 3 | extern crate serialize; 4 | extern crate http; 5 | extern crate r2d2; 6 | extern crate r2d2_postgres; 7 | 8 | use http::status; 9 | use std::io::net::ip::Ipv4Addr; 10 | use nickel::{ 11 | Nickel, 12 | Request, 13 | Response, 14 | HttpRouter, 15 | StaticFilesHandler, 16 | JsonBody, 17 | Middleware, 18 | MiddlewareResult, 19 | Continue, 20 | }; 21 | 22 | use postgres::{ 23 | Connection, 24 | SslMode 25 | }; 26 | 27 | use r2d2_postgres::PostgresPoolManager; 28 | use r2d2::PoolManager; 29 | 30 | use std::collections::TreeMap; 31 | use serialize::json::{ToJson, Json}; 32 | use serialize::json; 33 | 34 | #[deriving(Decodable, Encodable)] 35 | struct Todo { 36 | id: i32, 37 | title: String, 38 | is_completed: bool, 39 | } 40 | 41 | impl ToJson for Todo { 42 | fn to_json(&self) -> json::Json { 43 | let mut d = TreeMap::new(); 44 | d.insert("id".to_string(), self.id.to_json()); 45 | d.insert("title".to_string(), self.title.to_json()); 46 | d.insert("is_completed".to_string(), self.is_completed.to_json()); 47 | json::Object(d) 48 | } 49 | } 50 | 51 | struct ConnectionPool { 52 | pool: PostgresPoolManager, 53 | } 54 | 55 | impl ConnectionPool { 56 | fn new() -> ConnectionPool { 57 | // this isn't super secure but it's also just a toy so whatever 58 | ConnectionPool { 59 | pool: PostgresPoolManager::new("postgres://rustmvc@localhost", SslMode::None), 60 | } 61 | } 62 | } 63 | 64 | impl Middleware for ConnectionPool { 65 | fn invoke(&self, req: &mut Request, _res: &mut Response) -> MiddlewareResult { 66 | println!("Connection pool middleware called"); 67 | let conn = self.pool.connect().ok().expect("could not grab a connection"); 68 | 69 | req.map.insert(conn); 70 | 71 | Ok(Continue) 72 | } 73 | } 74 | 75 | fn main() { 76 | let mut server = Nickel::new(); 77 | let port = 6767u16; 78 | 79 | 80 | server.utilize(StaticFilesHandler::new("frontend/")); 81 | server.utilize(Nickel::json_body_parser()); 82 | server.utilize(Nickel::query_string()); 83 | server.utilize(ConnectionPool::new()); 84 | 85 | server.get( "/todos", get_todos); 86 | server.post("/todos", post_todo); 87 | 88 | println!("Server listening on port {}", port); 89 | server.listen(Ipv4Addr(127, 0, 0, 1), port); 90 | } 91 | 92 | fn get_todos(req: &Request, _: &mut Response) -> Json { 93 | let opt_conn: Option<&Connection> = req.map.get(); 94 | let conn = opt_conn.unwrap(); 95 | 96 | let stmt = conn.prepare("SELECT id, title, is_completed FROM todos").unwrap(); 97 | let results = stmt.query(&[]).unwrap().map(|row| { 98 | Todo { 99 | id: row.get(0u), 100 | title: row.get(1u), 101 | is_completed: row.get(2u), 102 | } 103 | }).collect::>(); 104 | 105 | let mut d = TreeMap::new(); 106 | d.insert("todos".to_string(), results.to_json()); 107 | 108 | d.to_json() 109 | } 110 | 111 | fn post_todo(req: &Request, _: &mut Response) -> (status::Status, String) { 112 | println!("called post_todo"); 113 | let opt_conn: Option<&Connection> = req.map.get(); 114 | let conn = opt_conn.unwrap(); 115 | 116 | match req.json_as::() { 117 | Some(t) => (http::status::Created, store_todo(t, conn)), 118 | None => (http::status::BadRequest, "{\"error\":\"cannot be parsed\"}".to_string()), 119 | } 120 | } 121 | 122 | fn store_todo(todo: Todo, conn: &Connection) -> String { 123 | println!("called store_todo"); 124 | 125 | conn.execute("INSERT INTO todos (title, is_completed) VALUES ($1, $2)", 126 | &[&todo.title, &todo.is_completed]).unwrap(); 127 | 128 | json::encode(&todo) 129 | } 130 | --------------------------------------------------------------------------------