├── .gitignore ├── README.md ├── todo-angular-express-promise ├── .gitignore ├── README.md ├── app.js ├── config.js ├── package.json └── public │ ├── bower.json │ ├── bower_components │ ├── angular-route │ │ └── angular-route.js │ ├── angular │ │ └── angular.js │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png │ ├── index.html │ └── js │ ├── app.js │ ├── controllers │ └── todoCtrl.js │ ├── directives │ ├── todoEscape.js │ └── todoFocus.js │ └── services │ └── todoStorage.js ├── todo-angular-express ├── .gitignore ├── README.md ├── app.js ├── config.js ├── package.json └── public │ ├── bower.json │ ├── bower_components │ ├── angular-route │ │ └── angular-route.js │ ├── angular │ │ └── angular.js │ └── todomvc-common │ │ ├── base.css │ │ ├── base.js │ │ └── bg.png │ ├── index.html │ └── js │ ├── app.js │ ├── controllers │ └── todoCtrl.js │ ├── directives │ ├── todoEscape.js │ └── todoFocus.js │ └── services │ └── todoStorage.js └── todo-angular-koa ├── README.md ├── app.js ├── config.js ├── package.json └── public ├── bower.json ├── bower_components ├── angular-route │ └── angular-route.js ├── angular │ └── angular.js └── todomvc-common │ ├── base.css │ ├── base.js │ └── bg.png ├── index.html └── js ├── app.js ├── controllers └── todoCtrl.js ├── directives ├── todoEscape.js └── todoFocus.js └── services └── todoStorage.js /.gitignore: -------------------------------------------------------------------------------- 1 | */node_modules/ 2 | */npm-debug.log 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Examples with Node.js and RethinkDB 2 | ======================== 3 | 4 | Examples using Node.js and RethinkDB. 5 | -------------------------------------------------------------------------------- /todo-angular-express-promise/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/bower_components 3 | rethinkdb_data 4 | -------------------------------------------------------------------------------- /todo-angular-express-promise/README.md: -------------------------------------------------------------------------------- 1 | # Todo example with RethinkDB, ExpressJS and AngularJS 2 | 3 | This was originally an example to illustrate AngularJS. It was slightly modified to support a real server that would save the todos. 4 | The code using rethinkdb is in the file `app.js`. 5 | 6 | ### Run ### 7 | 8 | Run `npm install` to install the dependencies. 9 | 10 | Start the server with 11 | ``` 12 | node app.js 13 | ``` 14 | 15 | 16 | ### Original source ### 17 | 18 | The original source was taken from: 19 | [https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs) 20 | 21 | 22 | ### Learn more about RethinkDB ### 23 | [Website](http://rethinkdb.com/) 24 | [Repo](https://github.com/rethinkdb/rethinkdb/) 25 | [API Reference](http://rethinkdb.com/api/javascript/) 26 | [More examples](http://rethinkdb.com/docs/examples/) 27 | 28 | 29 | 30 | ### Learn more about ExpressJS ### 31 | [Official repo](https://github.com/visionmedia/express) 32 | [Documentation](http://expressjs.com/) 33 | 34 | 35 | ### Learn more about AngularJS ### 36 | 37 | * [Tutorial](http://docs.angularjs.org/tutorial) 38 | * [API Reference](http://docs.angularjs.org/api) 39 | * [Developer Guide](http://docs.angularjs.org/guide) 40 | * [Applications built with AngularJS](http://builtwith.angularjs.org) 41 | * [Blog](http://blog.angularjs.org) 42 | * [FAQ](http://docs.angularjs.org/misc/faq) 43 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 44 | 45 | 46 | 47 | ### License ### 48 | MIT 49 | Author: RethinkDB 50 | 51 | Orginal copyright: [https://github.com/tastejs/todomvc/blob/gh-pages/license.md](https://github.com/tastejs/todomvc/blob/gh-pages/license.md) 52 | 53 | Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 56 | software and associated documentation files (the "Software"), to deal in the Software 57 | without restriction, including without limitation the rights to use, copy, modify, merge, 58 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 59 | persons to whom the Software is furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in all copies or 62 | substantial portions of the Software. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 65 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 66 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 67 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 68 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 69 | DEALINGS IN THE SOFTWARE. 70 | 71 | -------------------------------------------------------------------------------- /todo-angular-express-promise/app.js: -------------------------------------------------------------------------------- 1 | // Import express 2 | var express = require('express'); 3 | var bodyParser = require('body-parser'); 4 | var app = express(); 5 | 6 | // Load config for RethinkDB and express 7 | var config = require(__dirname+"/config.js"); 8 | 9 | var r = require('rethinkdb'); 10 | 11 | app.use(express.static(__dirname + '/public')); 12 | app.use(bodyParser()); 13 | 14 | // Middleware that will create a connection to the database 15 | app.use(createConnection); 16 | 17 | // Define main routes 18 | app.route('/todo/get').get(get); 19 | app.route('/todo/new').put(create); 20 | app.route('/todo/update').post(update); 21 | app.route('/todo/delete').post(del); 22 | 23 | // Middleware to close a connection to the database 24 | app.use(closeConnection); 25 | 26 | 27 | /* 28 | * Retrieve all todos 29 | */ 30 | function get(req, res, next) { 31 | r.table('todos').orderBy({index: "createdAt"}).run(req._rdbConn).then(function(cursor) { 32 | return cursor.toArray(); 33 | }).then(function(result) { 34 | res.send(JSON.stringify(result)); 35 | }).error(handleError(res)) 36 | .finally(next); 37 | } 38 | 39 | /* 40 | * Insert a todo 41 | */ 42 | function create(req, res, next) { 43 | var todo = req.body; 44 | todo.createdAt = r.now(); // Set the field `createdAt` to the current time 45 | r.table('todos').insert(todo, {returnChanges: true}).run(req._rdbConn).then(function(result) { 46 | if (result.inserted !== 1) { 47 | handleError(res, next)(new Error("Document was not inserted.")); 48 | } 49 | else { 50 | res.send(JSON.stringify(result.changes[0].new_val)); 51 | } 52 | }).error(handleError(res)) 53 | .finally(next); 54 | } 55 | 56 | /* 57 | * Update a todo 58 | */ 59 | function update(req, res, next) { 60 | var todo = req.body; 61 | if ((todo != null) && (todo.id != null)) { 62 | r.table('todos').get(todo.id).update(todo, {returnChanges: true}).run(req._rdbConn).then(function(result) { 63 | res.send(JSON.stringify(result.changes[0].new_val)); 64 | }).error(handleError(res)) 65 | .finally(next); 66 | } 67 | else { 68 | handleError(res)(new Error("The todo must have a field `id`.")); 69 | next(); 70 | } 71 | } 72 | 73 | /* 74 | * Delete a todo 75 | */ 76 | function del(req, res, next) { 77 | var todo = req.body; 78 | if ((todo != null) && (todo.id != null)) { 79 | r.table('todos').get(todo.id).delete().run(req._rdbConn).then(function(result) { 80 | res.send(JSON.stringify(result)); 81 | }).error(handleError(res)) 82 | .finally(next); 83 | } 84 | else { 85 | handleError(res)(new Error("The todo must have a field `id`.")); 86 | next(); 87 | } 88 | } 89 | 90 | /* 91 | * Send back a 500 error 92 | */ 93 | function handleError(res) { 94 | return function(error) { 95 | res.send(500, {error: error.message}); 96 | } 97 | } 98 | 99 | /* 100 | * Create a RethinkDB connection, and save it in req._rdbConn 101 | */ 102 | function createConnection(req, res, next) { 103 | r.connect(config.rethinkdb).then(function(conn) { 104 | req._rdbConn = conn; 105 | next(); 106 | }).error(handleError(res)); 107 | } 108 | 109 | /* 110 | * Close the RethinkDB connection 111 | */ 112 | function closeConnection(req, res, next) { 113 | req._rdbConn.close(); 114 | } 115 | 116 | /* 117 | * Create tables/indexes then start express 118 | */ 119 | r.connect(config.rethinkdb, function(err, conn) { 120 | if (err) { 121 | console.log("Could not open a connection to initialize the database"); 122 | console.log(err.message); 123 | process.exit(1); 124 | } 125 | 126 | r.table('todos').indexWait('createdAt').run(conn).then(function(err, result) { 127 | console.log("Table and index are available, starting express..."); 128 | startExpress(); 129 | }).error(function(err) { 130 | // The database/table/index was not available, create them 131 | r.dbCreate(config.rethinkdb.db).run(conn).finally(function() { 132 | return r.tableCreate('todos').run(conn) 133 | }).finally(function() { 134 | r.table('todos').indexCreate('createdAt').run(conn); 135 | }).finally(function(result) { 136 | r.table('todos').indexWait('createdAt').run(conn) 137 | }).then(function(result) { 138 | console.log("Table and index are available, starting express..."); 139 | startExpress(); 140 | conn.close(); 141 | }).error(function(err) { 142 | if (err) { 143 | console.log("Could not wait for the completion of the index `todos`"); 144 | console.log(err); 145 | process.exit(1); 146 | } 147 | console.log("Table and index are available, starting express..."); 148 | startExpress(); 149 | conn.close(); 150 | }); 151 | }); 152 | }); 153 | 154 | function startExpress() { 155 | app.listen(config.express.port); 156 | console.log('Listening on port '+config.express.port); 157 | } 158 | -------------------------------------------------------------------------------- /todo-angular-express-promise/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rethinkdb: { 3 | host: "localhost", 4 | port: 28015, 5 | authKey: "", 6 | db: "rethinkdb_ex" 7 | }, 8 | express: { 9 | port: 3000 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo-angular-express-promise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "0.0.2", 4 | "private": true, 5 | "dependencies": { 6 | "body-parser": "1.0.2", 7 | "express": "4.17.1", 8 | "rethinkdb": "^2.2.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.2.8", 6 | "todomvc-common": "~0.1.4" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "1.2.8", 10 | "angular-route": "1.2.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/bower_components/angular-route/angular-route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.8 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name ngRoute 11 | * @description 12 | * 13 | * # ngRoute 14 | * 15 | * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 16 | * 17 | * ## Example 18 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 19 | * 20 | * {@installModule route} 21 | * 22 | *
23 | */ 24 | /* global -ngRouteModule */ 25 | var ngRouteModule = angular.module('ngRoute', ['ng']). 26 | provider('$route', $RouteProvider); 27 | 28 | /** 29 | * @ngdoc object 30 | * @name ngRoute.$routeProvider 31 | * @function 32 | * 33 | * @description 34 | * 35 | * Used for configuring routes. 36 | * 37 | * ## Example 38 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 39 | * 40 | * ## Dependencies 41 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 42 | */ 43 | function $RouteProvider(){ 44 | function inherit(parent, extra) { 45 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 46 | } 47 | 48 | var routes = {}; 49 | 50 | /** 51 | * @ngdoc method 52 | * @name ngRoute.$routeProvider#when 53 | * @methodOf ngRoute.$routeProvider 54 | * 55 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 56 | * contains redundant trailing slash or is missing one, the route will still match and the 57 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 58 | * route definition. 59 | * 60 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 61 | * to the next slash are matched and stored in `$routeParams` under the given `name` 62 | * when the route matches. 63 | * * `path` can contain named groups starting with a colon and ending with a star: 64 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 65 | * when the route matches. 66 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 67 | * 68 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 69 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 70 | * 71 | * * `color: brown` 72 | * * `largecode: code/with/slashs`. 73 | * 74 | * 75 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 76 | * match. 77 | * 78 | * Object properties: 79 | * 80 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 81 | * newly created scope or the name of a {@link angular.Module#controller registered 82 | * controller} if passed as a string. 83 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 84 | * published to scope under the `controllerAs` name. 85 | * - `template` – `{string=|function()=}` – html template as a string or a function that 86 | * returns an html template as a string which should be used by {@link 87 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 88 | * This property takes precedence over `templateUrl`. 89 | * 90 | * If `template` is a function, it will be called with the following parameters: 91 | * 92 | * - `{Array.}` - route parameters extracted from the current 93 | * `$location.path()` by applying the current route 94 | * 95 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 96 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 97 | * 98 | * If `templateUrl` is a function, it will be called with the following parameters: 99 | * 100 | * - `{Array.}` - route parameters extracted from the current 101 | * `$location.path()` by applying the current route 102 | * 103 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 104 | * be injected into the controller. If any of these dependencies are promises, the router 105 | * will wait for them all to be resolved or one to be rejected before the controller is 106 | * instantiated. 107 | * If all the promises are resolved successfully, the values of the resolved promises are 108 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 109 | * fired. If any of the promises are rejected the 110 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 111 | * is: 112 | * 113 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 114 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 115 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 116 | * and the return value is treated as the dependency. If the result is a promise, it is 117 | * resolved before its value is injected into the controller. Be aware that 118 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 119 | * functions. Use `$route.current.params` to access the new route parameters, instead. 120 | * 121 | * - `redirectTo` – {(string|function())=} – value to update 122 | * {@link ng.$location $location} path with and trigger route redirection. 123 | * 124 | * If `redirectTo` is a function, it will be called with the following parameters: 125 | * 126 | * - `{Object.}` - route parameters extracted from the current 127 | * `$location.path()` by applying the current route templateUrl. 128 | * - `{string}` - current `$location.path()` 129 | * - `{Object}` - current `$location.search()` 130 | * 131 | * The custom `redirectTo` function is expected to return a string which will be used 132 | * to update `$location.path()` and `$location.search()`. 133 | * 134 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 135 | * or `$location.hash()` changes. 136 | * 137 | * If the option is set to `false` and url in the browser changes, then 138 | * `$routeUpdate` event is broadcasted on the root scope. 139 | * 140 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 141 | * 142 | * If the option is set to `true`, then the particular route can be matched without being 143 | * case sensitive 144 | * 145 | * @returns {Object} self 146 | * 147 | * @description 148 | * Adds a new route definition to the `$route` service. 149 | */ 150 | this.when = function(path, route) { 151 | routes[path] = angular.extend( 152 | {reloadOnSearch: true}, 153 | route, 154 | path && pathRegExp(path, route) 155 | ); 156 | 157 | // create redirection for trailing slashes 158 | if (path) { 159 | var redirectPath = (path[path.length-1] == '/') 160 | ? path.substr(0, path.length-1) 161 | : path +'/'; 162 | 163 | routes[redirectPath] = angular.extend( 164 | {redirectTo: path}, 165 | pathRegExp(redirectPath, route) 166 | ); 167 | } 168 | 169 | return this; 170 | }; 171 | 172 | /** 173 | * @param path {string} path 174 | * @param opts {Object} options 175 | * @return {?Object} 176 | * 177 | * @description 178 | * Normalizes the given path, returning a regular expression 179 | * and the original path. 180 | * 181 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 182 | */ 183 | function pathRegExp(path, opts) { 184 | var insensitive = opts.caseInsensitiveMatch, 185 | ret = { 186 | originalPath: path, 187 | regexp: path 188 | }, 189 | keys = ret.keys = []; 190 | 191 | path = path 192 | .replace(/([().])/g, '\\$1') 193 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 194 | var optional = option === '?' ? option : null; 195 | var star = option === '*' ? option : null; 196 | keys.push({ name: key, optional: !!optional }); 197 | slash = slash || ''; 198 | return '' 199 | + (optional ? '' : slash) 200 | + '(?:' 201 | + (optional ? slash : '') 202 | + (star && '(.+?)' || '([^/]+)') 203 | + (optional || '') 204 | + ')' 205 | + (optional || ''); 206 | }) 207 | .replace(/([\/$\*])/g, '\\$1'); 208 | 209 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 210 | return ret; 211 | } 212 | 213 | /** 214 | * @ngdoc method 215 | * @name ngRoute.$routeProvider#otherwise 216 | * @methodOf ngRoute.$routeProvider 217 | * 218 | * @description 219 | * Sets route definition that will be used on route change when no other route definition 220 | * is matched. 221 | * 222 | * @param {Object} params Mapping information to be assigned to `$route.current`. 223 | * @returns {Object} self 224 | */ 225 | this.otherwise = function(params) { 226 | this.when(null, params); 227 | return this; 228 | }; 229 | 230 | 231 | this.$get = ['$rootScope', 232 | '$location', 233 | '$routeParams', 234 | '$q', 235 | '$injector', 236 | '$http', 237 | '$templateCache', 238 | '$sce', 239 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 240 | 241 | /** 242 | * @ngdoc object 243 | * @name ngRoute.$route 244 | * @requires $location 245 | * @requires $routeParams 246 | * 247 | * @property {Object} current Reference to the current route definition. 248 | * The route definition contains: 249 | * 250 | * - `controller`: The controller constructor as define in route definition. 251 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 252 | * controller instantiation. The `locals` contain 253 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 254 | * 255 | * - `$scope` - The current route scope. 256 | * - `$template` - The current route template HTML. 257 | * 258 | * @property {Array.} routes Array of all configured routes. 259 | * 260 | * @description 261 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 262 | * It watches `$location.url()` and tries to map the path to an existing route definition. 263 | * 264 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 265 | * 266 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 267 | * 268 | * The `$route` service is typically used in conjunction with the 269 | * {@link ngRoute.directive:ngView `ngView`} directive and the 270 | * {@link ngRoute.$routeParams `$routeParams`} service. 271 | * 272 | * @example 273 | This example shows how changing the URL hash causes the `$route` to match a route against the 274 | URL, and the `ngView` pulls in the partial. 275 | 276 | Note that this example is using {@link ng.directive:script inlined templates} 277 | to get it working on jsfiddle as well. 278 | 279 | 280 | 281 |
282 | Choose: 283 | Moby | 284 | Moby: Ch1 | 285 | Gatsby | 286 | Gatsby: Ch4 | 287 | Scarlet Letter
288 | 289 |
290 |
291 | 292 |
$location.path() = {{$location.path()}}
293 |
$route.current.templateUrl = {{$route.current.templateUrl}}
294 |
$route.current.params = {{$route.current.params}}
295 |
$route.current.scope.name = {{$route.current.scope.name}}
296 |
$routeParams = {{$routeParams}}
297 |
298 |
299 | 300 | 301 | controller: {{name}}
302 | Book Id: {{params.bookId}}
303 |
304 | 305 | 306 | controller: {{name}}
307 | Book Id: {{params.bookId}}
308 | Chapter Id: {{params.chapterId}} 309 |
310 | 311 | 312 | angular.module('ngViewExample', ['ngRoute']) 313 | 314 | .config(function($routeProvider, $locationProvider) { 315 | $routeProvider.when('/Book/:bookId', { 316 | templateUrl: 'book.html', 317 | controller: BookCntl, 318 | resolve: { 319 | // I will cause a 1 second delay 320 | delay: function($q, $timeout) { 321 | var delay = $q.defer(); 322 | $timeout(delay.resolve, 1000); 323 | return delay.promise; 324 | } 325 | } 326 | }); 327 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 328 | templateUrl: 'chapter.html', 329 | controller: ChapterCntl 330 | }); 331 | 332 | // configure html5 to get links working on jsfiddle 333 | $locationProvider.html5Mode(true); 334 | }); 335 | 336 | function MainCntl($scope, $route, $routeParams, $location) { 337 | $scope.$route = $route; 338 | $scope.$location = $location; 339 | $scope.$routeParams = $routeParams; 340 | } 341 | 342 | function BookCntl($scope, $routeParams) { 343 | $scope.name = "BookCntl"; 344 | $scope.params = $routeParams; 345 | } 346 | 347 | function ChapterCntl($scope, $routeParams) { 348 | $scope.name = "ChapterCntl"; 349 | $scope.params = $routeParams; 350 | } 351 | 352 | 353 | 354 | it('should load and compile correct template', function() { 355 | element('a:contains("Moby: Ch1")').click(); 356 | var content = element('.doc-example-live [ng-view]').text(); 357 | expect(content).toMatch(/controller\: ChapterCntl/); 358 | expect(content).toMatch(/Book Id\: Moby/); 359 | expect(content).toMatch(/Chapter Id\: 1/); 360 | 361 | element('a:contains("Scarlet")').click(); 362 | sleep(2); // promises are not part of scenario waiting 363 | content = element('.doc-example-live [ng-view]').text(); 364 | expect(content).toMatch(/controller\: BookCntl/); 365 | expect(content).toMatch(/Book Id\: Scarlet/); 366 | }); 367 | 368 |
369 | */ 370 | 371 | /** 372 | * @ngdoc event 373 | * @name ngRoute.$route#$routeChangeStart 374 | * @eventOf ngRoute.$route 375 | * @eventType broadcast on root scope 376 | * @description 377 | * Broadcasted before a route change. At this point the route services starts 378 | * resolving all of the dependencies needed for the route change to occurs. 379 | * Typically this involves fetching the view template as well as any dependencies 380 | * defined in `resolve` route property. Once all of the dependencies are resolved 381 | * `$routeChangeSuccess` is fired. 382 | * 383 | * @param {Object} angularEvent Synthetic event object. 384 | * @param {Route} next Future route information. 385 | * @param {Route} current Current route information. 386 | */ 387 | 388 | /** 389 | * @ngdoc event 390 | * @name ngRoute.$route#$routeChangeSuccess 391 | * @eventOf ngRoute.$route 392 | * @eventType broadcast on root scope 393 | * @description 394 | * Broadcasted after a route dependencies are resolved. 395 | * {@link ngRoute.directive:ngView ngView} listens for the directive 396 | * to instantiate the controller and render the view. 397 | * 398 | * @param {Object} angularEvent Synthetic event object. 399 | * @param {Route} current Current route information. 400 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 401 | * first route entered. 402 | */ 403 | 404 | /** 405 | * @ngdoc event 406 | * @name ngRoute.$route#$routeChangeError 407 | * @eventOf ngRoute.$route 408 | * @eventType broadcast on root scope 409 | * @description 410 | * Broadcasted if any of the resolve promises are rejected. 411 | * 412 | * @param {Object} angularEvent Synthetic event object 413 | * @param {Route} current Current route information. 414 | * @param {Route} previous Previous route information. 415 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 416 | */ 417 | 418 | /** 419 | * @ngdoc event 420 | * @name ngRoute.$route#$routeUpdate 421 | * @eventOf ngRoute.$route 422 | * @eventType broadcast on root scope 423 | * @description 424 | * 425 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 426 | * instance of the Controller. 427 | */ 428 | 429 | var forceReload = false, 430 | $route = { 431 | routes: routes, 432 | 433 | /** 434 | * @ngdoc method 435 | * @name ngRoute.$route#reload 436 | * @methodOf ngRoute.$route 437 | * 438 | * @description 439 | * Causes `$route` service to reload the current route even if 440 | * {@link ng.$location $location} hasn't changed. 441 | * 442 | * As a result of that, {@link ngRoute.directive:ngView ngView} 443 | * creates new scope, reinstantiates the controller. 444 | */ 445 | reload: function() { 446 | forceReload = true; 447 | $rootScope.$evalAsync(updateRoute); 448 | } 449 | }; 450 | 451 | $rootScope.$on('$locationChangeSuccess', updateRoute); 452 | 453 | return $route; 454 | 455 | ///////////////////////////////////////////////////// 456 | 457 | /** 458 | * @param on {string} current url 459 | * @param route {Object} route regexp to match the url against 460 | * @return {?Object} 461 | * 462 | * @description 463 | * Check if the route matches the current url. 464 | * 465 | * Inspired by match in 466 | * visionmedia/express/lib/router/router.js. 467 | */ 468 | function switchRouteMatcher(on, route) { 469 | var keys = route.keys, 470 | params = {}; 471 | 472 | if (!route.regexp) return null; 473 | 474 | var m = route.regexp.exec(on); 475 | if (!m) return null; 476 | 477 | for (var i = 1, len = m.length; i < len; ++i) { 478 | var key = keys[i - 1]; 479 | 480 | var val = 'string' == typeof m[i] 481 | ? decodeURIComponent(m[i]) 482 | : m[i]; 483 | 484 | if (key && val) { 485 | params[key.name] = val; 486 | } 487 | } 488 | return params; 489 | } 490 | 491 | function updateRoute() { 492 | var next = parseRoute(), 493 | last = $route.current; 494 | 495 | if (next && last && next.$$route === last.$$route 496 | && angular.equals(next.pathParams, last.pathParams) 497 | && !next.reloadOnSearch && !forceReload) { 498 | last.params = next.params; 499 | angular.copy(last.params, $routeParams); 500 | $rootScope.$broadcast('$routeUpdate', last); 501 | } else if (next || last) { 502 | forceReload = false; 503 | $rootScope.$broadcast('$routeChangeStart', next, last); 504 | $route.current = next; 505 | if (next) { 506 | if (next.redirectTo) { 507 | if (angular.isString(next.redirectTo)) { 508 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 509 | .replace(); 510 | } else { 511 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 512 | .replace(); 513 | } 514 | } 515 | } 516 | 517 | $q.when(next). 518 | then(function() { 519 | if (next) { 520 | var locals = angular.extend({}, next.resolve), 521 | template, templateUrl; 522 | 523 | angular.forEach(locals, function(value, key) { 524 | locals[key] = angular.isString(value) ? 525 | $injector.get(value) : $injector.invoke(value); 526 | }); 527 | 528 | if (angular.isDefined(template = next.template)) { 529 | if (angular.isFunction(template)) { 530 | template = template(next.params); 531 | } 532 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 533 | if (angular.isFunction(templateUrl)) { 534 | templateUrl = templateUrl(next.params); 535 | } 536 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 537 | if (angular.isDefined(templateUrl)) { 538 | next.loadedTemplateUrl = templateUrl; 539 | template = $http.get(templateUrl, {cache: $templateCache}). 540 | then(function(response) { return response.data; }); 541 | } 542 | } 543 | if (angular.isDefined(template)) { 544 | locals['$template'] = template; 545 | } 546 | return $q.all(locals); 547 | } 548 | }). 549 | // after route change 550 | then(function(locals) { 551 | if (next == $route.current) { 552 | if (next) { 553 | next.locals = locals; 554 | angular.copy(next.params, $routeParams); 555 | } 556 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 557 | } 558 | }, function(error) { 559 | if (next == $route.current) { 560 | $rootScope.$broadcast('$routeChangeError', next, last, error); 561 | } 562 | }); 563 | } 564 | } 565 | 566 | 567 | /** 568 | * @returns the current active route, by matching it against the URL 569 | */ 570 | function parseRoute() { 571 | // Match a route 572 | var params, match; 573 | angular.forEach(routes, function(route, path) { 574 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 575 | match = inherit(route, { 576 | params: angular.extend({}, $location.search(), params), 577 | pathParams: params}); 578 | match.$$route = route; 579 | } 580 | }); 581 | // No route matched; fallback to "otherwise" route 582 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 583 | } 584 | 585 | /** 586 | * @returns interpolation of the redirect path with the parameters 587 | */ 588 | function interpolate(string, params) { 589 | var result = []; 590 | angular.forEach((string||'').split(':'), function(segment, i) { 591 | if (i === 0) { 592 | result.push(segment); 593 | } else { 594 | var segmentMatch = segment.match(/(\w+)(.*)/); 595 | var key = segmentMatch[1]; 596 | result.push(params[key]); 597 | result.push(segmentMatch[2] || ''); 598 | delete params[key]; 599 | } 600 | }); 601 | return result.join(''); 602 | } 603 | }]; 604 | } 605 | 606 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 607 | 608 | 609 | /** 610 | * @ngdoc object 611 | * @name ngRoute.$routeParams 612 | * @requires $route 613 | * 614 | * @description 615 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 616 | * 617 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 618 | * 619 | * The route parameters are a combination of {@link ng.$location `$location`}'s 620 | * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. 621 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 622 | * 623 | * In case of parameter name collision, `path` params take precedence over `search` params. 624 | * 625 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 626 | * (but its properties will likely change) even when a route change occurs. 627 | * 628 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 629 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 630 | * Instead you can use `$route.current.params` to access the new route's parameters. 631 | * 632 | * @example 633 | *
634 |  *  // Given:
635 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
636 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
637 |  *  //
638 |  *  // Then
639 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
640 |  * 
641 | */ 642 | function $RouteParamsProvider() { 643 | this.$get = function() { return {}; }; 644 | } 645 | 646 | ngRouteModule.directive('ngView', ngViewFactory); 647 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 648 | 649 | 650 | /** 651 | * @ngdoc directive 652 | * @name ngRoute.directive:ngView 653 | * @restrict ECA 654 | * 655 | * @description 656 | * # Overview 657 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 658 | * including the rendered template of the current route into the main layout (`index.html`) file. 659 | * Every time the current route changes, the included view changes with it according to the 660 | * configuration of the `$route` service. 661 | * 662 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 663 | * 664 | * @animations 665 | * enter - animation is used to bring new content into the browser. 666 | * leave - animation is used to animate existing content away. 667 | * 668 | * The enter and leave animation occur concurrently. 669 | * 670 | * @scope 671 | * @priority 400 672 | * @example 673 | 674 | 675 |
676 | Choose: 677 | Moby | 678 | Moby: Ch1 | 679 | Gatsby | 680 | Gatsby: Ch4 | 681 | Scarlet Letter
682 | 683 |
684 |
685 |
686 |
687 | 688 |
$location.path() = {{main.$location.path()}}
689 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
690 |
$route.current.params = {{main.$route.current.params}}
691 |
$route.current.scope.name = {{main.$route.current.scope.name}}
692 |
$routeParams = {{main.$routeParams}}
693 |
694 |
695 | 696 | 697 |
698 | controller: {{book.name}}
699 | Book Id: {{book.params.bookId}}
700 |
701 |
702 | 703 | 704 |
705 | controller: {{chapter.name}}
706 | Book Id: {{chapter.params.bookId}}
707 | Chapter Id: {{chapter.params.chapterId}} 708 |
709 |
710 | 711 | 712 | .view-animate-container { 713 | position:relative; 714 | height:100px!important; 715 | position:relative; 716 | background:white; 717 | border:1px solid black; 718 | height:40px; 719 | overflow:hidden; 720 | } 721 | 722 | .view-animate { 723 | padding:10px; 724 | } 725 | 726 | .view-animate.ng-enter, .view-animate.ng-leave { 727 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 728 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 729 | 730 | display:block; 731 | width:100%; 732 | border-left:1px solid black; 733 | 734 | position:absolute; 735 | top:0; 736 | left:0; 737 | right:0; 738 | bottom:0; 739 | padding:10px; 740 | } 741 | 742 | .view-animate.ng-enter { 743 | left:100%; 744 | } 745 | .view-animate.ng-enter.ng-enter-active { 746 | left:0; 747 | } 748 | .view-animate.ng-leave.ng-leave-active { 749 | left:-100%; 750 | } 751 | 752 | 753 | 754 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], 755 | function($routeProvider, $locationProvider) { 756 | $routeProvider.when('/Book/:bookId', { 757 | templateUrl: 'book.html', 758 | controller: BookCntl, 759 | controllerAs: 'book' 760 | }); 761 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 762 | templateUrl: 'chapter.html', 763 | controller: ChapterCntl, 764 | controllerAs: 'chapter' 765 | }); 766 | 767 | // configure html5 to get links working on jsfiddle 768 | $locationProvider.html5Mode(true); 769 | }); 770 | 771 | function MainCntl($route, $routeParams, $location) { 772 | this.$route = $route; 773 | this.$location = $location; 774 | this.$routeParams = $routeParams; 775 | } 776 | 777 | function BookCntl($routeParams) { 778 | this.name = "BookCntl"; 779 | this.params = $routeParams; 780 | } 781 | 782 | function ChapterCntl($routeParams) { 783 | this.name = "ChapterCntl"; 784 | this.params = $routeParams; 785 | } 786 | 787 | 788 | 789 | it('should load and compile correct template', function() { 790 | element('a:contains("Moby: Ch1")').click(); 791 | var content = element('.doc-example-live [ng-view]').text(); 792 | expect(content).toMatch(/controller\: ChapterCntl/); 793 | expect(content).toMatch(/Book Id\: Moby/); 794 | expect(content).toMatch(/Chapter Id\: 1/); 795 | 796 | element('a:contains("Scarlet")').click(); 797 | content = element('.doc-example-live [ng-view]').text(); 798 | expect(content).toMatch(/controller\: BookCntl/); 799 | expect(content).toMatch(/Book Id\: Scarlet/); 800 | }); 801 | 802 |
803 | */ 804 | 805 | 806 | /** 807 | * @ngdoc event 808 | * @name ngRoute.directive:ngView#$viewContentLoaded 809 | * @eventOf ngRoute.directive:ngView 810 | * @eventType emit on the current ngView scope 811 | * @description 812 | * Emitted every time the ngView content is reloaded. 813 | */ 814 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 815 | function ngViewFactory( $route, $anchorScroll, $animate) { 816 | return { 817 | restrict: 'ECA', 818 | terminal: true, 819 | priority: 400, 820 | transclude: 'element', 821 | link: function(scope, $element, attr, ctrl, $transclude) { 822 | var currentScope, 823 | currentElement, 824 | autoScrollExp = attr.autoscroll, 825 | onloadExp = attr.onload || ''; 826 | 827 | scope.$on('$routeChangeSuccess', update); 828 | update(); 829 | 830 | function cleanupLastView() { 831 | if (currentScope) { 832 | currentScope.$destroy(); 833 | currentScope = null; 834 | } 835 | if(currentElement) { 836 | $animate.leave(currentElement); 837 | currentElement = null; 838 | } 839 | } 840 | 841 | function update() { 842 | var locals = $route.current && $route.current.locals, 843 | template = locals && locals.$template; 844 | 845 | if (angular.isDefined(template)) { 846 | var newScope = scope.$new(); 847 | var current = $route.current; 848 | 849 | // Note: This will also link all children of ng-view that were contained in the original 850 | // html. If that content contains controllers, ... they could pollute/change the scope. 851 | // However, using ng-view on an element with additional content does not make sense... 852 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 853 | // function is called before linking the content, which would apply child 854 | // directives to non existing elements. 855 | var clone = $transclude(newScope, function(clone) { 856 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 857 | if (angular.isDefined(autoScrollExp) 858 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 859 | $anchorScroll(); 860 | } 861 | }); 862 | cleanupLastView(); 863 | }); 864 | 865 | currentElement = clone; 866 | currentScope = current.scope = newScope; 867 | currentScope.$emit('$viewContentLoaded'); 868 | currentScope.$eval(onloadExp); 869 | } else { 870 | cleanupLastView(); 871 | } 872 | } 873 | } 874 | }; 875 | } 876 | 877 | // This directive is called during the $transclude call of the first `ngView` directive. 878 | // It will replace and compile the content of the element with the loaded template. 879 | // We need this directive so that the element content is already filled when 880 | // the link function of another directive on the same element as ngView 881 | // is called. 882 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 883 | function ngViewFillContentFactory($compile, $controller, $route) { 884 | return { 885 | restrict: 'ECA', 886 | priority: -400, 887 | link: function(scope, $element) { 888 | var current = $route.current, 889 | locals = current.locals; 890 | 891 | $element.html(locals.$template); 892 | 893 | var link = $compile($element.contents()); 894 | 895 | if (current.controller) { 896 | locals.$scope = scope; 897 | var controller = $controller(current.controller, locals); 898 | if (current.controllerAs) { 899 | scope[current.controllerAs] = controller; 900 | } 901 | $element.data('$ngControllerController', controller); 902 | $element.children().data('$ngControllerController', controller); 903 | } 904 | 905 | link(scope); 906 | } 907 | }; 908 | } 909 | 910 | 911 | })(window, window.angular); 912 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/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 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/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; 126 | 127 | [/labs/, /\w*-examples/].forEach(function (href) { 128 | var match = location.href.match(href); 129 | 130 | if (!base && match) { 131 | base = location.href.indexOf(match); 132 | } 133 | }); 134 | 135 | return location.href.substr(0, base); 136 | } 137 | 138 | function getFile(file, callback) { 139 | if (!location.host) { 140 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 141 | } 142 | 143 | var xhr = new XMLHttpRequest(); 144 | 145 | xhr.open('GET', findRoot() + file, true); 146 | xhr.send(); 147 | 148 | xhr.onload = function () { 149 | if (xhr.status === 200 && callback) { 150 | callback(xhr.responseText); 151 | } 152 | }; 153 | } 154 | 155 | function Learn(learnJSON, config) { 156 | if (!(this instanceof Learn)) { 157 | return new Learn(learnJSON, config); 158 | } 159 | 160 | var template, framework; 161 | 162 | if (typeof learnJSON !== 'object') { 163 | try { 164 | learnJSON = JSON.parse(learnJSON); 165 | } catch (e) { 166 | return; 167 | } 168 | } 169 | 170 | if (config) { 171 | template = config.template; 172 | framework = config.framework; 173 | } 174 | 175 | if (!template && learnJSON.templates) { 176 | template = learnJSON.templates.todomvc; 177 | } 178 | 179 | if (!framework && document.querySelector('[data-framework]')) { 180 | framework = document.querySelector('[data-framework]').getAttribute('data-framework'); 181 | } 182 | 183 | 184 | if (template && learnJSON[framework]) { 185 | this.frameworkJSON = learnJSON[framework]; 186 | this.template = template; 187 | 188 | this.append(); 189 | } 190 | } 191 | 192 | Learn.prototype.append = function () { 193 | var aside = document.createElement('aside'); 194 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 195 | aside.className = 'learn'; 196 | 197 | // Localize demo links 198 | var demoLinks = aside.querySelectorAll('.demo-link'); 199 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 200 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 201 | }); 202 | 203 | document.body.className = (document.body.className + ' learn-bar').trim(); 204 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 205 | }; 206 | 207 | redirect(); 208 | getFile('learn.json', Learn); 209 | })(); 210 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rethinkdb/rethinkdb-example-nodejs/51e09f01030a4211bd2b278745ddea172305e358/todo-angular-express-promise/public/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /todo-angular-express-promise/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoMVC with AngularJS, Express and RethinkDB (using promises) 6 | 7 | 8 | 9 | 10 | 11 | 12 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', ['ngRoute']) 11 | .config(function ($routeProvider) { 12 | $routeProvider.when('/', { 13 | controller: 'TodoCtrl', 14 | templateUrl: 'todomvc-index.html' 15 | }).when('/:status', { 16 | controller: 'TodoCtrl', 17 | templateUrl: 'todomvc-index.html' 18 | }).otherwise({ 19 | redirectTo: '/' 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, todoStorage, filterFilter) { 10 | $scope.todos = []; 11 | 12 | todoStorage.get().success(function(todos) { 13 | $scope.todos = todos; 14 | }).error(function(error) { 15 | alert("Failed to load TODOs"); 16 | 17 | }); 18 | 19 | $scope.newTodo = ''; 20 | $scope.editedTodo = null; 21 | 22 | $scope.$watch('todos', function (newValue, oldValue) { 23 | $scope.remainingCount = filterFilter($scope.todos, { completed: false }).length; 24 | $scope.completedCount = $scope.todos.length - $scope.remainingCount; 25 | $scope.allChecked = !$scope.remainingCount; 26 | }, true); 27 | 28 | // Monitor the current route for changes and adjust the filter accordingly. 29 | $scope.$on('$routeChangeSuccess', function () { 30 | var status = $scope.status = $routeParams.status || ''; 31 | 32 | $scope.statusFilter = (status === 'active') ? 33 | { completed: false } : (status === 'completed') ? 34 | { completed: true } : null; 35 | }); 36 | 37 | $scope.addTodo = function () { 38 | var newTitle = $scope.newTodo.trim(); 39 | if (!newTitle.length) { 40 | return; 41 | } 42 | var newTodo = { 43 | title: newTitle, 44 | completed: false 45 | } 46 | todoStorage.create(newTodo).success(function(savedTodo) { 47 | $scope.todos.push(savedTodo); 48 | }).error(function(error) { 49 | alert("Failed to save the new TODO"); 50 | }); 51 | $scope.newTodo = ''; 52 | }; 53 | 54 | $scope.toggleTodo = function (todo) { 55 | var copyTodo = angular.extend({}, todo); 56 | copyTodo.completed = !copyTodo.completed 57 | todoStorage.update(copyTodo).success(function(newTodo) { 58 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 59 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 60 | } 61 | else { 62 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 63 | $scope.editedTodo = null; 64 | } 65 | }).error(function() { 66 | console.log('fds'); 67 | alert("Failed to update the status of this TODO"); 68 | }); 69 | 70 | }; 71 | $scope.editTodo = function (todo) { 72 | $scope.editedTodo = todo; 73 | // Clone the original todo to restore it on demand. 74 | $scope.originalTodo = angular.extend({}, todo); 75 | }; 76 | 77 | $scope.doneEditing = function (todo, $event) { 78 | todo.title = todo.title.trim(); 79 | if ((todo._saving !== true) && ($scope.originalTodo.title !== todo.title)) { 80 | todo._saving = true; // submit and blur trigger this method. Let's save the document just once 81 | todoStorage.update(todo).success(function(newTodo) { 82 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 83 | console.log('hum'); 84 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 85 | } 86 | else { 87 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 88 | $scope.editedTodo = null; 89 | } 90 | }).error(function() { 91 | todo._saving = false; 92 | alert("Failed to update this TODO"); 93 | }); 94 | } 95 | else { 96 | $scope.editedTodo = null; 97 | } 98 | }; 99 | 100 | $scope.revertEditing = function (todo) { 101 | $scope.todos[$scope.todos.indexOf(todo)] = $scope.originalTodo; 102 | $scope.doneEditing($scope.originalTodo); 103 | }; 104 | 105 | $scope.removeTodo = function (todo) { 106 | todoStorage.delete(todo.id).success(function() { 107 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 108 | }).error(function() { 109 | alert("Failed to delete this TODO"); 110 | }); 111 | }; 112 | 113 | $scope.clearCompletedTodos = function () { 114 | $scope.todos.forEach(function (todo) { 115 | if(todo.completed) { 116 | $scope.removeTodo(todo); 117 | } 118 | }); 119 | }; 120 | 121 | $scope.markAll = function (completed) { 122 | $scope.todos.forEach(function (todo) { 123 | if (todo.completed !== !completed) { 124 | $scope.toggleTodo(todo); 125 | } 126 | //todo.completed = !completed; 127 | }); 128 | }; 129 | }); 130 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/js/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to gets 6 | * an `escape` keydown event. 7 | */ 8 | todomvc.directive('todoEscape', function () { 9 | var ESCAPE_KEY = 27; 10 | return function (scope, elem, attrs) { 11 | elem.bind('keydown', function (event) { 12 | if (event.keyCode === ESCAPE_KEY) { 13 | scope.$apply(attrs.todoEscape); 14 | } 15 | }); 16 | }; 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function todoFocus($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /todo-angular-express-promise/public/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function ($http) { 8 | var STORAGE_ID = 'todos-angularjs'; 9 | 10 | return { 11 | get: function () { 12 | var url = "/todo/get"; 13 | return $http.get(url); 14 | }, 15 | create: function (todo) { 16 | var url = "/todo/new"; 17 | return $http.put(url, todo); 18 | }, 19 | update: function (todo) { 20 | var url = "/todo/update"; 21 | return $http.post(url, JSON.stringify(todo)); 22 | }, 23 | delete: function(id) { 24 | var url = "/todo/delete"; 25 | return $http.post(url, JSON.stringify({id: id})); 26 | } 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /todo-angular-express/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | public/bower_components 3 | rethinkdb_data 4 | -------------------------------------------------------------------------------- /todo-angular-express/README.md: -------------------------------------------------------------------------------- 1 | # Todo example with RethinkDB, ExpressJS and AngularJS 2 | 3 | This was originally an example to illustrate AngularJS. It was slightly modified to support a real server that would save the todos. 4 | The code using rethinkdb is in the file `app.js`. 5 | 6 | ### Run ### 7 | 8 | Run `npm install` to install the dependencies. 9 | 10 | Start the server with 11 | ``` 12 | node app.js 13 | ``` 14 | 15 | 16 | ### Original source ### 17 | 18 | The original source was taken from: 19 | [https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs) 20 | 21 | 22 | ### Learn more about RethinkDB ### 23 | [Website](http://rethinkdb.com/) 24 | [Repo](https://github.com/rethinkdb/rethinkdb/) 25 | [API Reference](http://rethinkdb.com/api/javascript/) 26 | [More examples](http://rethinkdb.com/docs/examples/) 27 | 28 | 29 | 30 | ### Learn more about ExpressJS ### 31 | [Official repo](https://github.com/visionmedia/express) 32 | [Documentation](http://expressjs.com/) 33 | 34 | 35 | ### Learn more about AngularJS ### 36 | 37 | * [Tutorial](http://docs.angularjs.org/tutorial) 38 | * [API Reference](http://docs.angularjs.org/api) 39 | * [Developer Guide](http://docs.angularjs.org/guide) 40 | * [Applications built with AngularJS](http://builtwith.angularjs.org) 41 | * [Blog](http://blog.angularjs.org) 42 | * [FAQ](http://docs.angularjs.org/misc/faq) 43 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 44 | 45 | 46 | 47 | ### License ### 48 | MIT 49 | Author: RethinkDB 50 | 51 | Orginal copyright: [https://github.com/tastejs/todomvc/blob/gh-pages/license.md](https://github.com/tastejs/todomvc/blob/gh-pages/license.md) 52 | 53 | Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. 54 | 55 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 56 | software and associated documentation files (the "Software"), to deal in the Software 57 | without restriction, including without limitation the rights to use, copy, modify, merge, 58 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 59 | persons to whom the Software is furnished to do so, subject to the following conditions: 60 | 61 | The above copyright notice and this permission notice shall be included in all copies or 62 | substantial portions of the Software. 63 | 64 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 65 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 66 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 67 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 68 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 69 | DEALINGS IN THE SOFTWARE. 70 | 71 | -------------------------------------------------------------------------------- /todo-angular-express/app.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var express = require('express'); 3 | var bodyParser = require('body-parser'); 4 | var r = require('rethinkdb'); 5 | 6 | var config = require(__dirname + '/config.js'); 7 | 8 | var app = express(); 9 | 10 | 11 | //For serving the index.html and all the other front-end assets. 12 | app.use(express.static(__dirname + '/public')); 13 | 14 | app.use(bodyParser.json()); 15 | 16 | //The REST routes for "todos". 17 | app.route('/todos') 18 | .get(listTodoItems) 19 | .post(createTodoItem); 20 | 21 | app.route('/todos/:id') 22 | .get(getTodoItem) 23 | .put(updateTodoItem) 24 | .delete(deleteTodoItem); 25 | 26 | //If we reach this middleware the route could not be handled and must be unknown. 27 | app.use(handle404); 28 | 29 | //Generic error handling middleware. 30 | app.use(handleError); 31 | 32 | 33 | /* 34 | * Retrieve all todo items. 35 | */ 36 | function listTodoItems(req, res, next) { 37 | r.table('todos').orderBy({index: 'createdAt'}).run(req.app._rdbConn, function(err, cursor) { 38 | if(err) { 39 | return next(err); 40 | } 41 | 42 | //Retrieve all the todos in an array. 43 | cursor.toArray(function(err, result) { 44 | if(err) { 45 | return next(err); 46 | } 47 | 48 | res.json(result); 49 | }); 50 | }); 51 | } 52 | 53 | /* 54 | * Insert a new todo item. 55 | */ 56 | function createTodoItem(req, res, next) { 57 | var todoItem = req.body; 58 | todoItem.createdAt = r.now(); 59 | 60 | console.dir(todoItem); 61 | 62 | r.table('todos').insert(todoItem, {returnChanges: true}).run(req.app._rdbConn, function(err, result) { 63 | if(err) { 64 | return next(err); 65 | } 66 | 67 | res.json(result.changes[0].new_val); 68 | }); 69 | } 70 | 71 | /* 72 | * Get a specific todo item. 73 | */ 74 | function getTodoItem(req, res, next) { 75 | var todoItemID = req.params.id; 76 | 77 | r.table('todos').get(todoItemID).run(req.app._rdbConn, function(err, result) { 78 | if(err) { 79 | return next(err); 80 | } 81 | 82 | res.json(result); 83 | }); 84 | } 85 | 86 | /* 87 | * Update a todo item. 88 | */ 89 | function updateTodoItem(req, res, next) { 90 | var todoItem = req.body; 91 | var todoItemID = req.params.id; 92 | 93 | r.table('todos').get(todoItemID).update(todoItem, {returnChanges: true}).run(req.app._rdbConn, function(err, result) { 94 | if(err) { 95 | return next(err); 96 | } 97 | 98 | res.json(result.changes[0].new_val); 99 | }); 100 | } 101 | 102 | /* 103 | * Delete a todo item. 104 | */ 105 | function deleteTodoItem(req, res, next) { 106 | var todoItemID = req.params.id; 107 | 108 | r.table('todos').get(todoItemID).delete().run(req.app._rdbConn, function(err, result) { 109 | if(err) { 110 | return next(err); 111 | } 112 | 113 | res.json({success: true}); 114 | }); 115 | } 116 | 117 | /* 118 | * Page-not-found middleware. 119 | */ 120 | function handle404(req, res, next) { 121 | res.status(404).end('not found'); 122 | } 123 | 124 | /* 125 | * Generic error handling middleware. 126 | * Send back a 500 page and log the error to the console. 127 | */ 128 | function handleError(err, req, res, next) { 129 | console.error(err.stack); 130 | res.status(500).json({err: err.message}); 131 | } 132 | 133 | /* 134 | * Store the db connection and start listening on a port. 135 | */ 136 | function startExpress(connection) { 137 | app._rdbConn = connection; 138 | app.listen(config.express.port); 139 | console.log('Listening on port ' + config.express.port); 140 | } 141 | 142 | /* 143 | * Connect to rethinkdb, create the needed tables/indexes and then start express. 144 | * Create tables/indexes then start express 145 | */ 146 | async.waterfall([ 147 | function connect(callback) { 148 | r.connect(config.rethinkdb, callback); 149 | }, 150 | function createDatabase(connection, callback) { 151 | //Create the database if needed. 152 | r.dbList().contains(config.rethinkdb.db).do(function(containsDb) { 153 | return r.branch( 154 | containsDb, 155 | {created: 0}, 156 | r.dbCreate(config.rethinkdb.db) 157 | ); 158 | }).run(connection, function(err) { 159 | callback(err, connection); 160 | }); 161 | }, 162 | function createTable(connection, callback) { 163 | //Create the table if needed. 164 | r.tableList().contains('todos').do(function(containsTable) { 165 | return r.branch( 166 | containsTable, 167 | {created: 0}, 168 | r.tableCreate('todos') 169 | ); 170 | }).run(connection, function(err) { 171 | callback(err, connection); 172 | }); 173 | }, 174 | function createIndex(connection, callback) { 175 | //Create the index if needed. 176 | r.table('todos').indexList().contains('createdAt').do(function(hasIndex) { 177 | return r.branch( 178 | hasIndex, 179 | {created: 0}, 180 | r.table('todos').indexCreate('createdAt') 181 | ); 182 | }).run(connection, function(err) { 183 | callback(err, connection); 184 | }); 185 | }, 186 | function waitForIndex(connection, callback) { 187 | //Wait for the index to be ready. 188 | r.table('todos').indexWait('createdAt').run(connection, function(err, result) { 189 | callback(err, connection); 190 | }); 191 | } 192 | ], function(err, connection) { 193 | if(err) { 194 | console.error(err); 195 | process.exit(1); 196 | return; 197 | } 198 | 199 | startExpress(connection); 200 | }); -------------------------------------------------------------------------------- /todo-angular-express/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rethinkdb: { 3 | host: 'localhost', 4 | port: 28015, 5 | authKey: '', 6 | db: 'rethinkdb_ex' 7 | }, 8 | express: { 9 | port: 3000 10 | } 11 | }; -------------------------------------------------------------------------------- /todo-angular-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "0.0.2", 4 | "private": true, 5 | "dependencies": { 6 | "async": "0.9.0", 7 | "body-parser": "1.12.3", 8 | "express": "4.12.3", 9 | "rethinkdb": "^2.2.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo-angular-express/public/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.2.8", 6 | "todomvc-common": "~0.1.4" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "1.2.8", 10 | "angular-route": "1.2.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /todo-angular-express/public/bower_components/angular-route/angular-route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.8 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name ngRoute 11 | * @description 12 | * 13 | * # ngRoute 14 | * 15 | * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 16 | * 17 | * ## Example 18 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 19 | * 20 | * {@installModule route} 21 | * 22 | *
23 | */ 24 | /* global -ngRouteModule */ 25 | var ngRouteModule = angular.module('ngRoute', ['ng']). 26 | provider('$route', $RouteProvider); 27 | 28 | /** 29 | * @ngdoc object 30 | * @name ngRoute.$routeProvider 31 | * @function 32 | * 33 | * @description 34 | * 35 | * Used for configuring routes. 36 | * 37 | * ## Example 38 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 39 | * 40 | * ## Dependencies 41 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 42 | */ 43 | function $RouteProvider(){ 44 | function inherit(parent, extra) { 45 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 46 | } 47 | 48 | var routes = {}; 49 | 50 | /** 51 | * @ngdoc method 52 | * @name ngRoute.$routeProvider#when 53 | * @methodOf ngRoute.$routeProvider 54 | * 55 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 56 | * contains redundant trailing slash or is missing one, the route will still match and the 57 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 58 | * route definition. 59 | * 60 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 61 | * to the next slash are matched and stored in `$routeParams` under the given `name` 62 | * when the route matches. 63 | * * `path` can contain named groups starting with a colon and ending with a star: 64 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 65 | * when the route matches. 66 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 67 | * 68 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 69 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 70 | * 71 | * * `color: brown` 72 | * * `largecode: code/with/slashs`. 73 | * 74 | * 75 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 76 | * match. 77 | * 78 | * Object properties: 79 | * 80 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 81 | * newly created scope or the name of a {@link angular.Module#controller registered 82 | * controller} if passed as a string. 83 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 84 | * published to scope under the `controllerAs` name. 85 | * - `template` – `{string=|function()=}` – html template as a string or a function that 86 | * returns an html template as a string which should be used by {@link 87 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 88 | * This property takes precedence over `templateUrl`. 89 | * 90 | * If `template` is a function, it will be called with the following parameters: 91 | * 92 | * - `{Array.}` - route parameters extracted from the current 93 | * `$location.path()` by applying the current route 94 | * 95 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 96 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 97 | * 98 | * If `templateUrl` is a function, it will be called with the following parameters: 99 | * 100 | * - `{Array.}` - route parameters extracted from the current 101 | * `$location.path()` by applying the current route 102 | * 103 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 104 | * be injected into the controller. If any of these dependencies are promises, the router 105 | * will wait for them all to be resolved or one to be rejected before the controller is 106 | * instantiated. 107 | * If all the promises are resolved successfully, the values of the resolved promises are 108 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 109 | * fired. If any of the promises are rejected the 110 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 111 | * is: 112 | * 113 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 114 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 115 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 116 | * and the return value is treated as the dependency. If the result is a promise, it is 117 | * resolved before its value is injected into the controller. Be aware that 118 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 119 | * functions. Use `$route.current.params` to access the new route parameters, instead. 120 | * 121 | * - `redirectTo` – {(string|function())=} – value to update 122 | * {@link ng.$location $location} path with and trigger route redirection. 123 | * 124 | * If `redirectTo` is a function, it will be called with the following parameters: 125 | * 126 | * - `{Object.}` - route parameters extracted from the current 127 | * `$location.path()` by applying the current route templateUrl. 128 | * - `{string}` - current `$location.path()` 129 | * - `{Object}` - current `$location.search()` 130 | * 131 | * The custom `redirectTo` function is expected to return a string which will be used 132 | * to update `$location.path()` and `$location.search()`. 133 | * 134 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 135 | * or `$location.hash()` changes. 136 | * 137 | * If the option is set to `false` and url in the browser changes, then 138 | * `$routeUpdate` event is broadcasted on the root scope. 139 | * 140 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 141 | * 142 | * If the option is set to `true`, then the particular route can be matched without being 143 | * case sensitive 144 | * 145 | * @returns {Object} self 146 | * 147 | * @description 148 | * Adds a new route definition to the `$route` service. 149 | */ 150 | this.when = function(path, route) { 151 | routes[path] = angular.extend( 152 | {reloadOnSearch: true}, 153 | route, 154 | path && pathRegExp(path, route) 155 | ); 156 | 157 | // create redirection for trailing slashes 158 | if (path) { 159 | var redirectPath = (path[path.length-1] == '/') 160 | ? path.substr(0, path.length-1) 161 | : path +'/'; 162 | 163 | routes[redirectPath] = angular.extend( 164 | {redirectTo: path}, 165 | pathRegExp(redirectPath, route) 166 | ); 167 | } 168 | 169 | return this; 170 | }; 171 | 172 | /** 173 | * @param path {string} path 174 | * @param opts {Object} options 175 | * @return {?Object} 176 | * 177 | * @description 178 | * Normalizes the given path, returning a regular expression 179 | * and the original path. 180 | * 181 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 182 | */ 183 | function pathRegExp(path, opts) { 184 | var insensitive = opts.caseInsensitiveMatch, 185 | ret = { 186 | originalPath: path, 187 | regexp: path 188 | }, 189 | keys = ret.keys = []; 190 | 191 | path = path 192 | .replace(/([().])/g, '\\$1') 193 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 194 | var optional = option === '?' ? option : null; 195 | var star = option === '*' ? option : null; 196 | keys.push({ name: key, optional: !!optional }); 197 | slash = slash || ''; 198 | return '' 199 | + (optional ? '' : slash) 200 | + '(?:' 201 | + (optional ? slash : '') 202 | + (star && '(.+?)' || '([^/]+)') 203 | + (optional || '') 204 | + ')' 205 | + (optional || ''); 206 | }) 207 | .replace(/([\/$\*])/g, '\\$1'); 208 | 209 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 210 | return ret; 211 | } 212 | 213 | /** 214 | * @ngdoc method 215 | * @name ngRoute.$routeProvider#otherwise 216 | * @methodOf ngRoute.$routeProvider 217 | * 218 | * @description 219 | * Sets route definition that will be used on route change when no other route definition 220 | * is matched. 221 | * 222 | * @param {Object} params Mapping information to be assigned to `$route.current`. 223 | * @returns {Object} self 224 | */ 225 | this.otherwise = function(params) { 226 | this.when(null, params); 227 | return this; 228 | }; 229 | 230 | 231 | this.$get = ['$rootScope', 232 | '$location', 233 | '$routeParams', 234 | '$q', 235 | '$injector', 236 | '$http', 237 | '$templateCache', 238 | '$sce', 239 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 240 | 241 | /** 242 | * @ngdoc object 243 | * @name ngRoute.$route 244 | * @requires $location 245 | * @requires $routeParams 246 | * 247 | * @property {Object} current Reference to the current route definition. 248 | * The route definition contains: 249 | * 250 | * - `controller`: The controller constructor as define in route definition. 251 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 252 | * controller instantiation. The `locals` contain 253 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 254 | * 255 | * - `$scope` - The current route scope. 256 | * - `$template` - The current route template HTML. 257 | * 258 | * @property {Array.} routes Array of all configured routes. 259 | * 260 | * @description 261 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 262 | * It watches `$location.url()` and tries to map the path to an existing route definition. 263 | * 264 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 265 | * 266 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 267 | * 268 | * The `$route` service is typically used in conjunction with the 269 | * {@link ngRoute.directive:ngView `ngView`} directive and the 270 | * {@link ngRoute.$routeParams `$routeParams`} service. 271 | * 272 | * @example 273 | This example shows how changing the URL hash causes the `$route` to match a route against the 274 | URL, and the `ngView` pulls in the partial. 275 | 276 | Note that this example is using {@link ng.directive:script inlined templates} 277 | to get it working on jsfiddle as well. 278 | 279 | 280 | 281 |
282 | Choose: 283 | Moby | 284 | Moby: Ch1 | 285 | Gatsby | 286 | Gatsby: Ch4 | 287 | Scarlet Letter
288 | 289 |
290 |
291 | 292 |
$location.path() = {{$location.path()}}
293 |
$route.current.templateUrl = {{$route.current.templateUrl}}
294 |
$route.current.params = {{$route.current.params}}
295 |
$route.current.scope.name = {{$route.current.scope.name}}
296 |
$routeParams = {{$routeParams}}
297 |
298 |
299 | 300 | 301 | controller: {{name}}
302 | Book Id: {{params.bookId}}
303 |
304 | 305 | 306 | controller: {{name}}
307 | Book Id: {{params.bookId}}
308 | Chapter Id: {{params.chapterId}} 309 |
310 | 311 | 312 | angular.module('ngViewExample', ['ngRoute']) 313 | 314 | .config(function($routeProvider, $locationProvider) { 315 | $routeProvider.when('/Book/:bookId', { 316 | templateUrl: 'book.html', 317 | controller: BookCntl, 318 | resolve: { 319 | // I will cause a 1 second delay 320 | delay: function($q, $timeout) { 321 | var delay = $q.defer(); 322 | $timeout(delay.resolve, 1000); 323 | return delay.promise; 324 | } 325 | } 326 | }); 327 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 328 | templateUrl: 'chapter.html', 329 | controller: ChapterCntl 330 | }); 331 | 332 | // configure html5 to get links working on jsfiddle 333 | $locationProvider.html5Mode(true); 334 | }); 335 | 336 | function MainCntl($scope, $route, $routeParams, $location) { 337 | $scope.$route = $route; 338 | $scope.$location = $location; 339 | $scope.$routeParams = $routeParams; 340 | } 341 | 342 | function BookCntl($scope, $routeParams) { 343 | $scope.name = "BookCntl"; 344 | $scope.params = $routeParams; 345 | } 346 | 347 | function ChapterCntl($scope, $routeParams) { 348 | $scope.name = "ChapterCntl"; 349 | $scope.params = $routeParams; 350 | } 351 | 352 | 353 | 354 | it('should load and compile correct template', function() { 355 | element('a:contains("Moby: Ch1")').click(); 356 | var content = element('.doc-example-live [ng-view]').text(); 357 | expect(content).toMatch(/controller\: ChapterCntl/); 358 | expect(content).toMatch(/Book Id\: Moby/); 359 | expect(content).toMatch(/Chapter Id\: 1/); 360 | 361 | element('a:contains("Scarlet")').click(); 362 | sleep(2); // promises are not part of scenario waiting 363 | content = element('.doc-example-live [ng-view]').text(); 364 | expect(content).toMatch(/controller\: BookCntl/); 365 | expect(content).toMatch(/Book Id\: Scarlet/); 366 | }); 367 | 368 |
369 | */ 370 | 371 | /** 372 | * @ngdoc event 373 | * @name ngRoute.$route#$routeChangeStart 374 | * @eventOf ngRoute.$route 375 | * @eventType broadcast on root scope 376 | * @description 377 | * Broadcasted before a route change. At this point the route services starts 378 | * resolving all of the dependencies needed for the route change to occurs. 379 | * Typically this involves fetching the view template as well as any dependencies 380 | * defined in `resolve` route property. Once all of the dependencies are resolved 381 | * `$routeChangeSuccess` is fired. 382 | * 383 | * @param {Object} angularEvent Synthetic event object. 384 | * @param {Route} next Future route information. 385 | * @param {Route} current Current route information. 386 | */ 387 | 388 | /** 389 | * @ngdoc event 390 | * @name ngRoute.$route#$routeChangeSuccess 391 | * @eventOf ngRoute.$route 392 | * @eventType broadcast on root scope 393 | * @description 394 | * Broadcasted after a route dependencies are resolved. 395 | * {@link ngRoute.directive:ngView ngView} listens for the directive 396 | * to instantiate the controller and render the view. 397 | * 398 | * @param {Object} angularEvent Synthetic event object. 399 | * @param {Route} current Current route information. 400 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 401 | * first route entered. 402 | */ 403 | 404 | /** 405 | * @ngdoc event 406 | * @name ngRoute.$route#$routeChangeError 407 | * @eventOf ngRoute.$route 408 | * @eventType broadcast on root scope 409 | * @description 410 | * Broadcasted if any of the resolve promises are rejected. 411 | * 412 | * @param {Object} angularEvent Synthetic event object 413 | * @param {Route} current Current route information. 414 | * @param {Route} previous Previous route information. 415 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 416 | */ 417 | 418 | /** 419 | * @ngdoc event 420 | * @name ngRoute.$route#$routeUpdate 421 | * @eventOf ngRoute.$route 422 | * @eventType broadcast on root scope 423 | * @description 424 | * 425 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 426 | * instance of the Controller. 427 | */ 428 | 429 | var forceReload = false, 430 | $route = { 431 | routes: routes, 432 | 433 | /** 434 | * @ngdoc method 435 | * @name ngRoute.$route#reload 436 | * @methodOf ngRoute.$route 437 | * 438 | * @description 439 | * Causes `$route` service to reload the current route even if 440 | * {@link ng.$location $location} hasn't changed. 441 | * 442 | * As a result of that, {@link ngRoute.directive:ngView ngView} 443 | * creates new scope, reinstantiates the controller. 444 | */ 445 | reload: function() { 446 | forceReload = true; 447 | $rootScope.$evalAsync(updateRoute); 448 | } 449 | }; 450 | 451 | $rootScope.$on('$locationChangeSuccess', updateRoute); 452 | 453 | return $route; 454 | 455 | ///////////////////////////////////////////////////// 456 | 457 | /** 458 | * @param on {string} current url 459 | * @param route {Object} route regexp to match the url against 460 | * @return {?Object} 461 | * 462 | * @description 463 | * Check if the route matches the current url. 464 | * 465 | * Inspired by match in 466 | * visionmedia/express/lib/router/router.js. 467 | */ 468 | function switchRouteMatcher(on, route) { 469 | var keys = route.keys, 470 | params = {}; 471 | 472 | if (!route.regexp) return null; 473 | 474 | var m = route.regexp.exec(on); 475 | if (!m) return null; 476 | 477 | for (var i = 1, len = m.length; i < len; ++i) { 478 | var key = keys[i - 1]; 479 | 480 | var val = 'string' == typeof m[i] 481 | ? decodeURIComponent(m[i]) 482 | : m[i]; 483 | 484 | if (key && val) { 485 | params[key.name] = val; 486 | } 487 | } 488 | return params; 489 | } 490 | 491 | function updateRoute() { 492 | var next = parseRoute(), 493 | last = $route.current; 494 | 495 | if (next && last && next.$$route === last.$$route 496 | && angular.equals(next.pathParams, last.pathParams) 497 | && !next.reloadOnSearch && !forceReload) { 498 | last.params = next.params; 499 | angular.copy(last.params, $routeParams); 500 | $rootScope.$broadcast('$routeUpdate', last); 501 | } else if (next || last) { 502 | forceReload = false; 503 | $rootScope.$broadcast('$routeChangeStart', next, last); 504 | $route.current = next; 505 | if (next) { 506 | if (next.redirectTo) { 507 | if (angular.isString(next.redirectTo)) { 508 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 509 | .replace(); 510 | } else { 511 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 512 | .replace(); 513 | } 514 | } 515 | } 516 | 517 | $q.when(next). 518 | then(function() { 519 | if (next) { 520 | var locals = angular.extend({}, next.resolve), 521 | template, templateUrl; 522 | 523 | angular.forEach(locals, function(value, key) { 524 | locals[key] = angular.isString(value) ? 525 | $injector.get(value) : $injector.invoke(value); 526 | }); 527 | 528 | if (angular.isDefined(template = next.template)) { 529 | if (angular.isFunction(template)) { 530 | template = template(next.params); 531 | } 532 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 533 | if (angular.isFunction(templateUrl)) { 534 | templateUrl = templateUrl(next.params); 535 | } 536 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 537 | if (angular.isDefined(templateUrl)) { 538 | next.loadedTemplateUrl = templateUrl; 539 | template = $http.get(templateUrl, {cache: $templateCache}). 540 | then(function(response) { return response.data; }); 541 | } 542 | } 543 | if (angular.isDefined(template)) { 544 | locals['$template'] = template; 545 | } 546 | return $q.all(locals); 547 | } 548 | }). 549 | // after route change 550 | then(function(locals) { 551 | if (next == $route.current) { 552 | if (next) { 553 | next.locals = locals; 554 | angular.copy(next.params, $routeParams); 555 | } 556 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 557 | } 558 | }, function(error) { 559 | if (next == $route.current) { 560 | $rootScope.$broadcast('$routeChangeError', next, last, error); 561 | } 562 | }); 563 | } 564 | } 565 | 566 | 567 | /** 568 | * @returns the current active route, by matching it against the URL 569 | */ 570 | function parseRoute() { 571 | // Match a route 572 | var params, match; 573 | angular.forEach(routes, function(route, path) { 574 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 575 | match = inherit(route, { 576 | params: angular.extend({}, $location.search(), params), 577 | pathParams: params}); 578 | match.$$route = route; 579 | } 580 | }); 581 | // No route matched; fallback to "otherwise" route 582 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 583 | } 584 | 585 | /** 586 | * @returns interpolation of the redirect path with the parameters 587 | */ 588 | function interpolate(string, params) { 589 | var result = []; 590 | angular.forEach((string||'').split(':'), function(segment, i) { 591 | if (i === 0) { 592 | result.push(segment); 593 | } else { 594 | var segmentMatch = segment.match(/(\w+)(.*)/); 595 | var key = segmentMatch[1]; 596 | result.push(params[key]); 597 | result.push(segmentMatch[2] || ''); 598 | delete params[key]; 599 | } 600 | }); 601 | return result.join(''); 602 | } 603 | }]; 604 | } 605 | 606 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 607 | 608 | 609 | /** 610 | * @ngdoc object 611 | * @name ngRoute.$routeParams 612 | * @requires $route 613 | * 614 | * @description 615 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 616 | * 617 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 618 | * 619 | * The route parameters are a combination of {@link ng.$location `$location`}'s 620 | * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. 621 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 622 | * 623 | * In case of parameter name collision, `path` params take precedence over `search` params. 624 | * 625 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 626 | * (but its properties will likely change) even when a route change occurs. 627 | * 628 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 629 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 630 | * Instead you can use `$route.current.params` to access the new route's parameters. 631 | * 632 | * @example 633 | *
634 |  *  // Given:
635 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
636 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
637 |  *  //
638 |  *  // Then
639 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
640 |  * 
641 | */ 642 | function $RouteParamsProvider() { 643 | this.$get = function() { return {}; }; 644 | } 645 | 646 | ngRouteModule.directive('ngView', ngViewFactory); 647 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 648 | 649 | 650 | /** 651 | * @ngdoc directive 652 | * @name ngRoute.directive:ngView 653 | * @restrict ECA 654 | * 655 | * @description 656 | * # Overview 657 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 658 | * including the rendered template of the current route into the main layout (`index.html`) file. 659 | * Every time the current route changes, the included view changes with it according to the 660 | * configuration of the `$route` service. 661 | * 662 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 663 | * 664 | * @animations 665 | * enter - animation is used to bring new content into the browser. 666 | * leave - animation is used to animate existing content away. 667 | * 668 | * The enter and leave animation occur concurrently. 669 | * 670 | * @scope 671 | * @priority 400 672 | * @example 673 | 674 | 675 |
676 | Choose: 677 | Moby | 678 | Moby: Ch1 | 679 | Gatsby | 680 | Gatsby: Ch4 | 681 | Scarlet Letter
682 | 683 |
684 |
685 |
686 |
687 | 688 |
$location.path() = {{main.$location.path()}}
689 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
690 |
$route.current.params = {{main.$route.current.params}}
691 |
$route.current.scope.name = {{main.$route.current.scope.name}}
692 |
$routeParams = {{main.$routeParams}}
693 |
694 |
695 | 696 | 697 |
698 | controller: {{book.name}}
699 | Book Id: {{book.params.bookId}}
700 |
701 |
702 | 703 | 704 |
705 | controller: {{chapter.name}}
706 | Book Id: {{chapter.params.bookId}}
707 | Chapter Id: {{chapter.params.chapterId}} 708 |
709 |
710 | 711 | 712 | .view-animate-container { 713 | position:relative; 714 | height:100px!important; 715 | position:relative; 716 | background:white; 717 | border:1px solid black; 718 | height:40px; 719 | overflow:hidden; 720 | } 721 | 722 | .view-animate { 723 | padding:10px; 724 | } 725 | 726 | .view-animate.ng-enter, .view-animate.ng-leave { 727 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 728 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 729 | 730 | display:block; 731 | width:100%; 732 | border-left:1px solid black; 733 | 734 | position:absolute; 735 | top:0; 736 | left:0; 737 | right:0; 738 | bottom:0; 739 | padding:10px; 740 | } 741 | 742 | .view-animate.ng-enter { 743 | left:100%; 744 | } 745 | .view-animate.ng-enter.ng-enter-active { 746 | left:0; 747 | } 748 | .view-animate.ng-leave.ng-leave-active { 749 | left:-100%; 750 | } 751 | 752 | 753 | 754 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], 755 | function($routeProvider, $locationProvider) { 756 | $routeProvider.when('/Book/:bookId', { 757 | templateUrl: 'book.html', 758 | controller: BookCntl, 759 | controllerAs: 'book' 760 | }); 761 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 762 | templateUrl: 'chapter.html', 763 | controller: ChapterCntl, 764 | controllerAs: 'chapter' 765 | }); 766 | 767 | // configure html5 to get links working on jsfiddle 768 | $locationProvider.html5Mode(true); 769 | }); 770 | 771 | function MainCntl($route, $routeParams, $location) { 772 | this.$route = $route; 773 | this.$location = $location; 774 | this.$routeParams = $routeParams; 775 | } 776 | 777 | function BookCntl($routeParams) { 778 | this.name = "BookCntl"; 779 | this.params = $routeParams; 780 | } 781 | 782 | function ChapterCntl($routeParams) { 783 | this.name = "ChapterCntl"; 784 | this.params = $routeParams; 785 | } 786 | 787 | 788 | 789 | it('should load and compile correct template', function() { 790 | element('a:contains("Moby: Ch1")').click(); 791 | var content = element('.doc-example-live [ng-view]').text(); 792 | expect(content).toMatch(/controller\: ChapterCntl/); 793 | expect(content).toMatch(/Book Id\: Moby/); 794 | expect(content).toMatch(/Chapter Id\: 1/); 795 | 796 | element('a:contains("Scarlet")').click(); 797 | content = element('.doc-example-live [ng-view]').text(); 798 | expect(content).toMatch(/controller\: BookCntl/); 799 | expect(content).toMatch(/Book Id\: Scarlet/); 800 | }); 801 | 802 |
803 | */ 804 | 805 | 806 | /** 807 | * @ngdoc event 808 | * @name ngRoute.directive:ngView#$viewContentLoaded 809 | * @eventOf ngRoute.directive:ngView 810 | * @eventType emit on the current ngView scope 811 | * @description 812 | * Emitted every time the ngView content is reloaded. 813 | */ 814 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 815 | function ngViewFactory( $route, $anchorScroll, $animate) { 816 | return { 817 | restrict: 'ECA', 818 | terminal: true, 819 | priority: 400, 820 | transclude: 'element', 821 | link: function(scope, $element, attr, ctrl, $transclude) { 822 | var currentScope, 823 | currentElement, 824 | autoScrollExp = attr.autoscroll, 825 | onloadExp = attr.onload || ''; 826 | 827 | scope.$on('$routeChangeSuccess', update); 828 | update(); 829 | 830 | function cleanupLastView() { 831 | if (currentScope) { 832 | currentScope.$destroy(); 833 | currentScope = null; 834 | } 835 | if(currentElement) { 836 | $animate.leave(currentElement); 837 | currentElement = null; 838 | } 839 | } 840 | 841 | function update() { 842 | var locals = $route.current && $route.current.locals, 843 | template = locals && locals.$template; 844 | 845 | if (angular.isDefined(template)) { 846 | var newScope = scope.$new(); 847 | var current = $route.current; 848 | 849 | // Note: This will also link all children of ng-view that were contained in the original 850 | // html. If that content contains controllers, ... they could pollute/change the scope. 851 | // However, using ng-view on an element with additional content does not make sense... 852 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 853 | // function is called before linking the content, which would apply child 854 | // directives to non existing elements. 855 | var clone = $transclude(newScope, function(clone) { 856 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 857 | if (angular.isDefined(autoScrollExp) 858 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 859 | $anchorScroll(); 860 | } 861 | }); 862 | cleanupLastView(); 863 | }); 864 | 865 | currentElement = clone; 866 | currentScope = current.scope = newScope; 867 | currentScope.$emit('$viewContentLoaded'); 868 | currentScope.$eval(onloadExp); 869 | } else { 870 | cleanupLastView(); 871 | } 872 | } 873 | } 874 | }; 875 | } 876 | 877 | // This directive is called during the $transclude call of the first `ngView` directive. 878 | // It will replace and compile the content of the element with the loaded template. 879 | // We need this directive so that the element content is already filled when 880 | // the link function of another directive on the same element as ngView 881 | // is called. 882 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 883 | function ngViewFillContentFactory($compile, $controller, $route) { 884 | return { 885 | restrict: 'ECA', 886 | priority: -400, 887 | link: function(scope, $element) { 888 | var current = $route.current, 889 | locals = current.locals; 890 | 891 | $element.html(locals.$template); 892 | 893 | var link = $compile($element.contents()); 894 | 895 | if (current.controller) { 896 | locals.$scope = scope; 897 | var controller = $controller(current.controller, locals); 898 | if (current.controllerAs) { 899 | scope[current.controllerAs] = controller; 900 | } 901 | $element.data('$ngControllerController', controller); 902 | $element.children().data('$ngControllerController', controller); 903 | } 904 | 905 | link(scope); 906 | } 907 | }; 908 | } 909 | 910 | 911 | })(window, window.angular); 912 | -------------------------------------------------------------------------------- /todo-angular-express/public/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 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /todo-angular-express/public/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; 126 | 127 | [/labs/, /\w*-examples/].forEach(function (href) { 128 | var match = location.href.match(href); 129 | 130 | if (!base && match) { 131 | base = location.href.indexOf(match); 132 | } 133 | }); 134 | 135 | return location.href.substr(0, base); 136 | } 137 | 138 | function getFile(file, callback) { 139 | if (!location.host) { 140 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 141 | } 142 | 143 | var xhr = new XMLHttpRequest(); 144 | 145 | xhr.open('GET', findRoot() + file, true); 146 | xhr.send(); 147 | 148 | xhr.onload = function () { 149 | if (xhr.status === 200 && callback) { 150 | callback(xhr.responseText); 151 | } 152 | }; 153 | } 154 | 155 | function Learn(learnJSON, config) { 156 | if (!(this instanceof Learn)) { 157 | return new Learn(learnJSON, config); 158 | } 159 | 160 | var template, framework; 161 | 162 | if (typeof learnJSON !== 'object') { 163 | try { 164 | learnJSON = JSON.parse(learnJSON); 165 | } catch (e) { 166 | return; 167 | } 168 | } 169 | 170 | if (config) { 171 | template = config.template; 172 | framework = config.framework; 173 | } 174 | 175 | if (!template && learnJSON.templates) { 176 | template = learnJSON.templates.todomvc; 177 | } 178 | 179 | if (!framework && document.querySelector('[data-framework]')) { 180 | framework = document.querySelector('[data-framework]').getAttribute('data-framework'); 181 | } 182 | 183 | 184 | if (template && learnJSON[framework]) { 185 | this.frameworkJSON = learnJSON[framework]; 186 | this.template = template; 187 | 188 | this.append(); 189 | } 190 | } 191 | 192 | Learn.prototype.append = function () { 193 | var aside = document.createElement('aside'); 194 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 195 | aside.className = 'learn'; 196 | 197 | // Localize demo links 198 | var demoLinks = aside.querySelectorAll('.demo-link'); 199 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 200 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 201 | }); 202 | 203 | document.body.className = (document.body.className + ' learn-bar').trim(); 204 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 205 | }; 206 | 207 | redirect(); 208 | getFile('learn.json', Learn); 209 | })(); 210 | -------------------------------------------------------------------------------- /todo-angular-express/public/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rethinkdb/rethinkdb-example-nodejs/51e09f01030a4211bd2b278745ddea172305e358/todo-angular-express/public/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /todo-angular-express/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoMVC with AngularJS, Express and RethinkDB 6 | 7 | 8 | 9 | 10 | 11 | 12 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /todo-angular-express/public/js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', ['ngRoute']) 11 | .config(function ($routeProvider) { 12 | $routeProvider.when('/', { 13 | controller: 'TodoCtrl', 14 | templateUrl: 'todomvc-index.html' 15 | }).when('/:status', { 16 | controller: 'TodoCtrl', 17 | templateUrl: 'todomvc-index.html' 18 | }).otherwise({ 19 | redirectTo: '/' 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /todo-angular-express/public/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, todoStorage, filterFilter) { 10 | $scope.todos = []; 11 | 12 | todoStorage.get().success(function(todos) { 13 | $scope.todos = todos; 14 | }).error(function(error) { 15 | alert('Failed to load TODOs'); 16 | 17 | }); 18 | 19 | $scope.newTodo = ''; 20 | $scope.editedTodo = null; 21 | 22 | $scope.$watch('todos', function (newValue, oldValue) { 23 | $scope.remainingCount = filterFilter($scope.todos, { completed: false }).length; 24 | $scope.completedCount = $scope.todos.length - $scope.remainingCount; 25 | $scope.allChecked = !$scope.remainingCount; 26 | }, true); 27 | 28 | // Monitor the current route for changes and adjust the filter accordingly. 29 | $scope.$on('$routeChangeSuccess', function () { 30 | var status = $scope.status = $routeParams.status || ''; 31 | 32 | $scope.statusFilter = (status === 'active') ? 33 | { completed: false } : (status === 'completed') ? 34 | { completed: true } : null; 35 | }); 36 | 37 | $scope.addTodo = function () { 38 | var newTitle = $scope.newTodo.trim(); 39 | if (!newTitle.length) { 40 | return; 41 | } 42 | var newTodo = { 43 | title: newTitle, 44 | completed: false 45 | } 46 | todoStorage.create(newTodo).success(function(savedTodo) { 47 | $scope.todos.push(savedTodo); 48 | }).error(function(error) { 49 | alert('Failed to save the new TODO'); 50 | }); 51 | $scope.newTodo = ''; 52 | }; 53 | 54 | $scope.toggleTodo = function (todo) { 55 | var copyTodo = angular.extend({}, todo); 56 | copyTodo.completed = !copyTodo.completed 57 | todoStorage.update(copyTodo).success(function(newTodo) { 58 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 59 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 60 | } 61 | else { 62 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 63 | $scope.editedTodo = null; 64 | } 65 | }).error(function() { 66 | console.log('fds'); 67 | alert('Failed to update the status of this TODO'); 68 | }); 69 | 70 | }; 71 | $scope.editTodo = function (todo) { 72 | $scope.editedTodo = todo; 73 | // Clone the original todo to restore it on demand. 74 | $scope.originalTodo = angular.extend({}, todo); 75 | }; 76 | 77 | $scope.doneEditing = function (todo, $event) { 78 | todo.title = todo.title.trim(); 79 | if ((todo._saving !== true) && ($scope.originalTodo.title !== todo.title)) { 80 | todo._saving = true; // submit and blur trigger this method. Let's save the document just once 81 | todoStorage.update(todo).success(function(newTodo) { 82 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 83 | console.log('hum'); 84 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 85 | } 86 | else { 87 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 88 | $scope.editedTodo = null; 89 | } 90 | }).error(function() { 91 | todo._saving = false; 92 | alert('Failed to update this TODO'); 93 | }); 94 | } 95 | else { 96 | $scope.editedTodo = null; 97 | } 98 | }; 99 | 100 | $scope.revertEditing = function (todo) { 101 | $scope.todos[$scope.todos.indexOf(todo)] = $scope.originalTodo; 102 | $scope.doneEditing($scope.originalTodo); 103 | }; 104 | 105 | $scope.removeTodo = function (todo) { 106 | todoStorage.delete(todo.id).success(function() { 107 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 108 | }).error(function() { 109 | alert('Failed to delete this TODO'); 110 | }); 111 | }; 112 | 113 | $scope.clearCompletedTodos = function () { 114 | $scope.todos.forEach(function (todo) { 115 | if(todo.completed) { 116 | $scope.removeTodo(todo); 117 | } 118 | }); 119 | }; 120 | 121 | $scope.markAll = function (completed) { 122 | $scope.todos.forEach(function (todo) { 123 | if (todo.completed !== !completed) { 124 | $scope.toggleTodo(todo); 125 | } 126 | //todo.completed = !completed; 127 | }); 128 | }; 129 | }); 130 | -------------------------------------------------------------------------------- /todo-angular-express/public/js/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to gets 6 | * an `escape` keydown event. 7 | */ 8 | todomvc.directive('todoEscape', function () { 9 | var ESCAPE_KEY = 27; 10 | return function (scope, elem, attrs) { 11 | elem.bind('keydown', function (event) { 12 | if (event.keyCode === ESCAPE_KEY) { 13 | scope.$apply(attrs.todoEscape); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /todo-angular-express/public/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function todoFocus($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /todo-angular-express/public/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function ($http) { 8 | var STORAGE_ID = 'todos-angularjs'; 9 | 10 | return { 11 | get: function () { 12 | var url = '/todos'; 13 | return $http.get(url); 14 | }, 15 | create: function (todo) { 16 | var url = '/todos'; 17 | return $http.post(url, todo); 18 | }, 19 | update: function (todo) { 20 | var url = '/todos/' + todo.id; 21 | return $http.put(url, todo); 22 | }, 23 | delete: function(id) { 24 | var url = '/todos/' + id; 25 | return $http.delete(url); 26 | } 27 | }; 28 | }); 29 | -------------------------------------------------------------------------------- /todo-angular-koa/README.md: -------------------------------------------------------------------------------- 1 | # Todo example with RethinkDB, KoaJS and AngularJS 2 | 3 | This was originally an example to illustrate AngularJS. It was slightly modified to support a real server that would save the todos. 4 | The code using rethinkdb is in the file `app.js`. 5 | 6 | This example uses generators, and require an unstable version of Node (>= 0.11.9). 7 | 8 | ### Run ### 9 | 10 | Run `npm install` to install the dependencies. 11 | 12 | Start the server with 13 | ``` 14 | node --harmony app.js 15 | ``` 16 | 17 | 18 | ### Original source ### 19 | 20 | The original source was taken from: 21 | [https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs) 22 | 23 | 24 | ### Learn more about RethinkDB ### 25 | [Website](http://rethinkdb.com/) 26 | [Repo](https://github.com/rethinkdb/rethinkdb/) 27 | [API Reference](http://rethinkdb.com/api/javascript/) 28 | [More examples](http://rethinkdb.com/docs/examples/) 29 | 30 | 31 | 32 | ### Learn more about ExpressJS ### 33 | [Official repo](https://github.com/visionmedia/express) 34 | [Documentation](http://expressjs.com/) 35 | 36 | 37 | ### Learn more about AngularJS ### 38 | 39 | * [Tutorial](http://docs.angularjs.org/tutorial) 40 | * [API Reference](http://docs.angularjs.org/api) 41 | * [Developer Guide](http://docs.angularjs.org/guide) 42 | * [Applications built with AngularJS](http://builtwith.angularjs.org) 43 | * [Blog](http://blog.angularjs.org) 44 | * [FAQ](http://docs.angularjs.org/misc/faq) 45 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 46 | 47 | 48 | 49 | ### License ### 50 | MIT 51 | Author: RethinkDB 52 | 53 | Orginal copyright: [https://github.com/tastejs/todomvc/blob/gh-pages/license.md](https://github.com/tastejs/todomvc/blob/gh-pages/license.md) 54 | 55 | Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 58 | software and associated documentation files (the "Software"), to deal in the Software 59 | without restriction, including without limitation the rights to use, copy, modify, merge, 60 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 61 | persons to whom the Software is furnished to do so, subject to the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be included in all copies or 64 | substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 67 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 68 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 69 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 70 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 71 | DEALINGS IN THE SOFTWARE. 72 | 73 | -------------------------------------------------------------------------------- /todo-angular-koa/app.js: -------------------------------------------------------------------------------- 1 | var koa = require('koa'); 2 | 3 | // Middleware and helpers 4 | var serve = require('koa-static'); 5 | var parse = require('co-body'); 6 | var router = require('koa-router'); 7 | var http = require('http'); 8 | 9 | // Import rethinkdb 10 | var r = require('rethinkdb'); 11 | 12 | // Load config for RethinkDB and koa 13 | var config = require(__dirname+"/config.js"); 14 | 15 | var app = koa(); 16 | 17 | // Static content 18 | app.use(serve(__dirname+'/public')); 19 | 20 | // Create a RethinkDB connection 21 | app.use(createConnection); 22 | 23 | app.use(router(app)); 24 | app.get('/todo/get', get); 25 | app.put('/todo/new', create); 26 | app.post('/todo/update', update); 27 | app.post('/todo/delete', del); 28 | 29 | // Close the RethinkDB connection 30 | app.use(closeConnection); 31 | 32 | /* 33 | * Create a RethinkDB connection, and save it in req._rdbConn 34 | */ 35 | function* createConnection(next) { 36 | try{ 37 | var conn = yield r.connect(config.rethinkdb); 38 | this._rdbConn = conn; 39 | } 40 | catch(err) { 41 | this.status = 500; 42 | this.body = err.message || http.STATUS_CODES[this.status]; 43 | } 44 | yield next; 45 | } 46 | 47 | // Retrieve all todos 48 | function* get(next) { 49 | try{ 50 | var cursor = yield r.table('todos').orderBy({index: "createdAt"}).run(this._rdbConn); 51 | var result = yield cursor.toArray(); 52 | this.body = JSON.stringify(result); 53 | } 54 | catch(e) { 55 | this.status = 500; 56 | this.body = e.message || http.STATUS_CODES[this.status]; 57 | } 58 | yield next; 59 | } 60 | 61 | // Create a new todo 62 | function* create(next) { 63 | try{ 64 | var todo = yield parse(this); 65 | todo.createdAt = r.now(); // Set the field `createdAt` to the current time 66 | var result = yield r.table('todos').insert(todo, {returnChanges: true}).run(this._rdbConn); 67 | 68 | todo = result.changes[0].new_val; // todo now contains the previous todo + a field `id` and `createdAt` 69 | this.body = JSON.stringify(todo); 70 | } 71 | catch(e) { 72 | this.status = 500; 73 | this.body = e.message || http.STATUS_CODES[this.status]; 74 | } 75 | yield next; 76 | } 77 | 78 | // Update a todo 79 | function* update(next) { 80 | try{ 81 | var todo = yield parse(this); 82 | delete todo._saving; 83 | if ((todo == null) || (todo.id == null)) { 84 | throw new Error("The todo must have a field `id`."); 85 | } 86 | 87 | var result = yield r.table('todos').get(todo.id).update(todo, {returnChanges: true}).run(this._rdbConn); 88 | this.body = JSON.stringify(result.changes[0].new_val); 89 | } 90 | catch(e) { 91 | this.status = 500; 92 | this.body = e.message || http.STATUS_CODES[this.status]; 93 | } 94 | yield next; 95 | } 96 | 97 | // Delete a todo 98 | function* del(next) { 99 | try{ 100 | var todo = yield parse(this); 101 | if ((todo == null) || (todo.id == null)) { 102 | throw new Error("The todo must have a field `id`."); 103 | } 104 | var result = yield r.table('todos').get(todo.id).delete().run(this._rdbConn); 105 | this.body = ""; 106 | } 107 | catch(e) { 108 | this.status = 500; 109 | this.body = e.message || http.STATUS_CODES[this.status]; 110 | } 111 | yield next; 112 | } 113 | 114 | /* 115 | * Close the RethinkDB connection 116 | */ 117 | function* closeConnection(next) { 118 | this._rdbConn.close(); 119 | } 120 | 121 | r.connect(config.rethinkdb, function(err, conn) { 122 | if (err) { 123 | console.log("Could not open a connection to initialize the database"); 124 | console.log(err.message); 125 | process.exit(1); 126 | } 127 | 128 | r.table('todos').indexWait('createdAt').run(conn).then(function(err, result) { 129 | console.log("Table and index are available, starting koa..."); 130 | startKoa(); 131 | }).error(function(err) { 132 | // The database/table/index was not available, create them 133 | r.dbCreate(config.rethinkdb.db).run(conn).finally(function() { 134 | return r.tableCreate('todos').run(conn) 135 | }).finally(function() { 136 | r.table('todos').indexCreate('createdAt').run(conn); 137 | }).finally(function(result) { 138 | r.table('todos').indexWait('createdAt').run(conn) 139 | }).then(function(result) { 140 | console.log("Table and index are available, starting koa..."); 141 | startKoa(); 142 | conn.close(); 143 | }).error(function(err) { 144 | if (err) { 145 | console.log("Could not wait for the completion of the index `todos`"); 146 | console.log(err); 147 | process.exit(1); 148 | } 149 | console.log("Table and index are available, starting koa..."); 150 | startKoa(); 151 | conn.close(); 152 | }); 153 | }); 154 | }); 155 | 156 | 157 | function startKoa() { 158 | app.listen(config.koa.port); 159 | console.log('Listening on port '+config.koa.port); 160 | } 161 | -------------------------------------------------------------------------------- /todo-angular-koa/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rethinkdb: { 3 | host: "localhost", 4 | port: 28015, 5 | authKey: "", 6 | db: "rethinkdb_ex" 7 | }, 8 | koa: { 9 | port: 3000 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo-angular-koa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo" 3 | , "version": "0.0.2" 4 | , "private": true 5 | , "dependencies": { 6 | "koa": "0.6.0" 7 | , "koa-static": "1.4.2" 8 | , "koa-router": "3.1.4" 9 | , "co-body": "0.0.1" 10 | , "co": "3.0.2" 11 | , "body-parser": "1.0.2" 12 | , "rethinkdb": ">=2.1.1" 13 | , "co-assert-timeout": "0.0.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /todo-angular-koa/public/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-angular", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.2.8", 6 | "todomvc-common": "~0.1.4" 7 | }, 8 | "devDependencies": { 9 | "angular-mocks": "1.2.8", 10 | "angular-route": "1.2.8" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /todo-angular-koa/public/bower_components/angular-route/angular-route.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.2.8 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc overview 10 | * @name ngRoute 11 | * @description 12 | * 13 | * # ngRoute 14 | * 15 | * The `ngRoute` module provides routing and deeplinking services and directives for angular apps. 16 | * 17 | * ## Example 18 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 19 | * 20 | * {@installModule route} 21 | * 22 | *
23 | */ 24 | /* global -ngRouteModule */ 25 | var ngRouteModule = angular.module('ngRoute', ['ng']). 26 | provider('$route', $RouteProvider); 27 | 28 | /** 29 | * @ngdoc object 30 | * @name ngRoute.$routeProvider 31 | * @function 32 | * 33 | * @description 34 | * 35 | * Used for configuring routes. 36 | * 37 | * ## Example 38 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 39 | * 40 | * ## Dependencies 41 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 42 | */ 43 | function $RouteProvider(){ 44 | function inherit(parent, extra) { 45 | return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); 46 | } 47 | 48 | var routes = {}; 49 | 50 | /** 51 | * @ngdoc method 52 | * @name ngRoute.$routeProvider#when 53 | * @methodOf ngRoute.$routeProvider 54 | * 55 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 56 | * contains redundant trailing slash or is missing one, the route will still match and the 57 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 58 | * route definition. 59 | * 60 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 61 | * to the next slash are matched and stored in `$routeParams` under the given `name` 62 | * when the route matches. 63 | * * `path` can contain named groups starting with a colon and ending with a star: 64 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 65 | * when the route matches. 66 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 67 | * 68 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 69 | * `/color/brown/largecode/code/with/slashs/edit` and extract: 70 | * 71 | * * `color: brown` 72 | * * `largecode: code/with/slashs`. 73 | * 74 | * 75 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 76 | * match. 77 | * 78 | * Object properties: 79 | * 80 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 81 | * newly created scope or the name of a {@link angular.Module#controller registered 82 | * controller} if passed as a string. 83 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 84 | * published to scope under the `controllerAs` name. 85 | * - `template` – `{string=|function()=}` – html template as a string or a function that 86 | * returns an html template as a string which should be used by {@link 87 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 88 | * This property takes precedence over `templateUrl`. 89 | * 90 | * If `template` is a function, it will be called with the following parameters: 91 | * 92 | * - `{Array.}` - route parameters extracted from the current 93 | * `$location.path()` by applying the current route 94 | * 95 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 96 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 97 | * 98 | * If `templateUrl` is a function, it will be called with the following parameters: 99 | * 100 | * - `{Array.}` - route parameters extracted from the current 101 | * `$location.path()` by applying the current route 102 | * 103 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 104 | * be injected into the controller. If any of these dependencies are promises, the router 105 | * will wait for them all to be resolved or one to be rejected before the controller is 106 | * instantiated. 107 | * If all the promises are resolved successfully, the values of the resolved promises are 108 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 109 | * fired. If any of the promises are rejected the 110 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 111 | * is: 112 | * 113 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 114 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 115 | * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} 116 | * and the return value is treated as the dependency. If the result is a promise, it is 117 | * resolved before its value is injected into the controller. Be aware that 118 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 119 | * functions. Use `$route.current.params` to access the new route parameters, instead. 120 | * 121 | * - `redirectTo` – {(string|function())=} – value to update 122 | * {@link ng.$location $location} path with and trigger route redirection. 123 | * 124 | * If `redirectTo` is a function, it will be called with the following parameters: 125 | * 126 | * - `{Object.}` - route parameters extracted from the current 127 | * `$location.path()` by applying the current route templateUrl. 128 | * - `{string}` - current `$location.path()` 129 | * - `{Object}` - current `$location.search()` 130 | * 131 | * The custom `redirectTo` function is expected to return a string which will be used 132 | * to update `$location.path()` and `$location.search()`. 133 | * 134 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 135 | * or `$location.hash()` changes. 136 | * 137 | * If the option is set to `false` and url in the browser changes, then 138 | * `$routeUpdate` event is broadcasted on the root scope. 139 | * 140 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 141 | * 142 | * If the option is set to `true`, then the particular route can be matched without being 143 | * case sensitive 144 | * 145 | * @returns {Object} self 146 | * 147 | * @description 148 | * Adds a new route definition to the `$route` service. 149 | */ 150 | this.when = function(path, route) { 151 | routes[path] = angular.extend( 152 | {reloadOnSearch: true}, 153 | route, 154 | path && pathRegExp(path, route) 155 | ); 156 | 157 | // create redirection for trailing slashes 158 | if (path) { 159 | var redirectPath = (path[path.length-1] == '/') 160 | ? path.substr(0, path.length-1) 161 | : path +'/'; 162 | 163 | routes[redirectPath] = angular.extend( 164 | {redirectTo: path}, 165 | pathRegExp(redirectPath, route) 166 | ); 167 | } 168 | 169 | return this; 170 | }; 171 | 172 | /** 173 | * @param path {string} path 174 | * @param opts {Object} options 175 | * @return {?Object} 176 | * 177 | * @description 178 | * Normalizes the given path, returning a regular expression 179 | * and the original path. 180 | * 181 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 182 | */ 183 | function pathRegExp(path, opts) { 184 | var insensitive = opts.caseInsensitiveMatch, 185 | ret = { 186 | originalPath: path, 187 | regexp: path 188 | }, 189 | keys = ret.keys = []; 190 | 191 | path = path 192 | .replace(/([().])/g, '\\$1') 193 | .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ 194 | var optional = option === '?' ? option : null; 195 | var star = option === '*' ? option : null; 196 | keys.push({ name: key, optional: !!optional }); 197 | slash = slash || ''; 198 | return '' 199 | + (optional ? '' : slash) 200 | + '(?:' 201 | + (optional ? slash : '') 202 | + (star && '(.+?)' || '([^/]+)') 203 | + (optional || '') 204 | + ')' 205 | + (optional || ''); 206 | }) 207 | .replace(/([\/$\*])/g, '\\$1'); 208 | 209 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 210 | return ret; 211 | } 212 | 213 | /** 214 | * @ngdoc method 215 | * @name ngRoute.$routeProvider#otherwise 216 | * @methodOf ngRoute.$routeProvider 217 | * 218 | * @description 219 | * Sets route definition that will be used on route change when no other route definition 220 | * is matched. 221 | * 222 | * @param {Object} params Mapping information to be assigned to `$route.current`. 223 | * @returns {Object} self 224 | */ 225 | this.otherwise = function(params) { 226 | this.when(null, params); 227 | return this; 228 | }; 229 | 230 | 231 | this.$get = ['$rootScope', 232 | '$location', 233 | '$routeParams', 234 | '$q', 235 | '$injector', 236 | '$http', 237 | '$templateCache', 238 | '$sce', 239 | function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { 240 | 241 | /** 242 | * @ngdoc object 243 | * @name ngRoute.$route 244 | * @requires $location 245 | * @requires $routeParams 246 | * 247 | * @property {Object} current Reference to the current route definition. 248 | * The route definition contains: 249 | * 250 | * - `controller`: The controller constructor as define in route definition. 251 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 252 | * controller instantiation. The `locals` contain 253 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 254 | * 255 | * - `$scope` - The current route scope. 256 | * - `$template` - The current route template HTML. 257 | * 258 | * @property {Array.} routes Array of all configured routes. 259 | * 260 | * @description 261 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 262 | * It watches `$location.url()` and tries to map the path to an existing route definition. 263 | * 264 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 265 | * 266 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 267 | * 268 | * The `$route` service is typically used in conjunction with the 269 | * {@link ngRoute.directive:ngView `ngView`} directive and the 270 | * {@link ngRoute.$routeParams `$routeParams`} service. 271 | * 272 | * @example 273 | This example shows how changing the URL hash causes the `$route` to match a route against the 274 | URL, and the `ngView` pulls in the partial. 275 | 276 | Note that this example is using {@link ng.directive:script inlined templates} 277 | to get it working on jsfiddle as well. 278 | 279 | 280 | 281 |
282 | Choose: 283 | Moby | 284 | Moby: Ch1 | 285 | Gatsby | 286 | Gatsby: Ch4 | 287 | Scarlet Letter
288 | 289 |
290 |
291 | 292 |
$location.path() = {{$location.path()}}
293 |
$route.current.templateUrl = {{$route.current.templateUrl}}
294 |
$route.current.params = {{$route.current.params}}
295 |
$route.current.scope.name = {{$route.current.scope.name}}
296 |
$routeParams = {{$routeParams}}
297 |
298 |
299 | 300 | 301 | controller: {{name}}
302 | Book Id: {{params.bookId}}
303 |
304 | 305 | 306 | controller: {{name}}
307 | Book Id: {{params.bookId}}
308 | Chapter Id: {{params.chapterId}} 309 |
310 | 311 | 312 | angular.module('ngViewExample', ['ngRoute']) 313 | 314 | .config(function($routeProvider, $locationProvider) { 315 | $routeProvider.when('/Book/:bookId', { 316 | templateUrl: 'book.html', 317 | controller: BookCntl, 318 | resolve: { 319 | // I will cause a 1 second delay 320 | delay: function($q, $timeout) { 321 | var delay = $q.defer(); 322 | $timeout(delay.resolve, 1000); 323 | return delay.promise; 324 | } 325 | } 326 | }); 327 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 328 | templateUrl: 'chapter.html', 329 | controller: ChapterCntl 330 | }); 331 | 332 | // configure html5 to get links working on jsfiddle 333 | $locationProvider.html5Mode(true); 334 | }); 335 | 336 | function MainCntl($scope, $route, $routeParams, $location) { 337 | $scope.$route = $route; 338 | $scope.$location = $location; 339 | $scope.$routeParams = $routeParams; 340 | } 341 | 342 | function BookCntl($scope, $routeParams) { 343 | $scope.name = "BookCntl"; 344 | $scope.params = $routeParams; 345 | } 346 | 347 | function ChapterCntl($scope, $routeParams) { 348 | $scope.name = "ChapterCntl"; 349 | $scope.params = $routeParams; 350 | } 351 | 352 | 353 | 354 | it('should load and compile correct template', function() { 355 | element('a:contains("Moby: Ch1")').click(); 356 | var content = element('.doc-example-live [ng-view]').text(); 357 | expect(content).toMatch(/controller\: ChapterCntl/); 358 | expect(content).toMatch(/Book Id\: Moby/); 359 | expect(content).toMatch(/Chapter Id\: 1/); 360 | 361 | element('a:contains("Scarlet")').click(); 362 | sleep(2); // promises are not part of scenario waiting 363 | content = element('.doc-example-live [ng-view]').text(); 364 | expect(content).toMatch(/controller\: BookCntl/); 365 | expect(content).toMatch(/Book Id\: Scarlet/); 366 | }); 367 | 368 |
369 | */ 370 | 371 | /** 372 | * @ngdoc event 373 | * @name ngRoute.$route#$routeChangeStart 374 | * @eventOf ngRoute.$route 375 | * @eventType broadcast on root scope 376 | * @description 377 | * Broadcasted before a route change. At this point the route services starts 378 | * resolving all of the dependencies needed for the route change to occurs. 379 | * Typically this involves fetching the view template as well as any dependencies 380 | * defined in `resolve` route property. Once all of the dependencies are resolved 381 | * `$routeChangeSuccess` is fired. 382 | * 383 | * @param {Object} angularEvent Synthetic event object. 384 | * @param {Route} next Future route information. 385 | * @param {Route} current Current route information. 386 | */ 387 | 388 | /** 389 | * @ngdoc event 390 | * @name ngRoute.$route#$routeChangeSuccess 391 | * @eventOf ngRoute.$route 392 | * @eventType broadcast on root scope 393 | * @description 394 | * Broadcasted after a route dependencies are resolved. 395 | * {@link ngRoute.directive:ngView ngView} listens for the directive 396 | * to instantiate the controller and render the view. 397 | * 398 | * @param {Object} angularEvent Synthetic event object. 399 | * @param {Route} current Current route information. 400 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 401 | * first route entered. 402 | */ 403 | 404 | /** 405 | * @ngdoc event 406 | * @name ngRoute.$route#$routeChangeError 407 | * @eventOf ngRoute.$route 408 | * @eventType broadcast on root scope 409 | * @description 410 | * Broadcasted if any of the resolve promises are rejected. 411 | * 412 | * @param {Object} angularEvent Synthetic event object 413 | * @param {Route} current Current route information. 414 | * @param {Route} previous Previous route information. 415 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 416 | */ 417 | 418 | /** 419 | * @ngdoc event 420 | * @name ngRoute.$route#$routeUpdate 421 | * @eventOf ngRoute.$route 422 | * @eventType broadcast on root scope 423 | * @description 424 | * 425 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 426 | * instance of the Controller. 427 | */ 428 | 429 | var forceReload = false, 430 | $route = { 431 | routes: routes, 432 | 433 | /** 434 | * @ngdoc method 435 | * @name ngRoute.$route#reload 436 | * @methodOf ngRoute.$route 437 | * 438 | * @description 439 | * Causes `$route` service to reload the current route even if 440 | * {@link ng.$location $location} hasn't changed. 441 | * 442 | * As a result of that, {@link ngRoute.directive:ngView ngView} 443 | * creates new scope, reinstantiates the controller. 444 | */ 445 | reload: function() { 446 | forceReload = true; 447 | $rootScope.$evalAsync(updateRoute); 448 | } 449 | }; 450 | 451 | $rootScope.$on('$locationChangeSuccess', updateRoute); 452 | 453 | return $route; 454 | 455 | ///////////////////////////////////////////////////// 456 | 457 | /** 458 | * @param on {string} current url 459 | * @param route {Object} route regexp to match the url against 460 | * @return {?Object} 461 | * 462 | * @description 463 | * Check if the route matches the current url. 464 | * 465 | * Inspired by match in 466 | * visionmedia/express/lib/router/router.js. 467 | */ 468 | function switchRouteMatcher(on, route) { 469 | var keys = route.keys, 470 | params = {}; 471 | 472 | if (!route.regexp) return null; 473 | 474 | var m = route.regexp.exec(on); 475 | if (!m) return null; 476 | 477 | for (var i = 1, len = m.length; i < len; ++i) { 478 | var key = keys[i - 1]; 479 | 480 | var val = 'string' == typeof m[i] 481 | ? decodeURIComponent(m[i]) 482 | : m[i]; 483 | 484 | if (key && val) { 485 | params[key.name] = val; 486 | } 487 | } 488 | return params; 489 | } 490 | 491 | function updateRoute() { 492 | var next = parseRoute(), 493 | last = $route.current; 494 | 495 | if (next && last && next.$$route === last.$$route 496 | && angular.equals(next.pathParams, last.pathParams) 497 | && !next.reloadOnSearch && !forceReload) { 498 | last.params = next.params; 499 | angular.copy(last.params, $routeParams); 500 | $rootScope.$broadcast('$routeUpdate', last); 501 | } else if (next || last) { 502 | forceReload = false; 503 | $rootScope.$broadcast('$routeChangeStart', next, last); 504 | $route.current = next; 505 | if (next) { 506 | if (next.redirectTo) { 507 | if (angular.isString(next.redirectTo)) { 508 | $location.path(interpolate(next.redirectTo, next.params)).search(next.params) 509 | .replace(); 510 | } else { 511 | $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) 512 | .replace(); 513 | } 514 | } 515 | } 516 | 517 | $q.when(next). 518 | then(function() { 519 | if (next) { 520 | var locals = angular.extend({}, next.resolve), 521 | template, templateUrl; 522 | 523 | angular.forEach(locals, function(value, key) { 524 | locals[key] = angular.isString(value) ? 525 | $injector.get(value) : $injector.invoke(value); 526 | }); 527 | 528 | if (angular.isDefined(template = next.template)) { 529 | if (angular.isFunction(template)) { 530 | template = template(next.params); 531 | } 532 | } else if (angular.isDefined(templateUrl = next.templateUrl)) { 533 | if (angular.isFunction(templateUrl)) { 534 | templateUrl = templateUrl(next.params); 535 | } 536 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 537 | if (angular.isDefined(templateUrl)) { 538 | next.loadedTemplateUrl = templateUrl; 539 | template = $http.get(templateUrl, {cache: $templateCache}). 540 | then(function(response) { return response.data; }); 541 | } 542 | } 543 | if (angular.isDefined(template)) { 544 | locals['$template'] = template; 545 | } 546 | return $q.all(locals); 547 | } 548 | }). 549 | // after route change 550 | then(function(locals) { 551 | if (next == $route.current) { 552 | if (next) { 553 | next.locals = locals; 554 | angular.copy(next.params, $routeParams); 555 | } 556 | $rootScope.$broadcast('$routeChangeSuccess', next, last); 557 | } 558 | }, function(error) { 559 | if (next == $route.current) { 560 | $rootScope.$broadcast('$routeChangeError', next, last, error); 561 | } 562 | }); 563 | } 564 | } 565 | 566 | 567 | /** 568 | * @returns the current active route, by matching it against the URL 569 | */ 570 | function parseRoute() { 571 | // Match a route 572 | var params, match; 573 | angular.forEach(routes, function(route, path) { 574 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 575 | match = inherit(route, { 576 | params: angular.extend({}, $location.search(), params), 577 | pathParams: params}); 578 | match.$$route = route; 579 | } 580 | }); 581 | // No route matched; fallback to "otherwise" route 582 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 583 | } 584 | 585 | /** 586 | * @returns interpolation of the redirect path with the parameters 587 | */ 588 | function interpolate(string, params) { 589 | var result = []; 590 | angular.forEach((string||'').split(':'), function(segment, i) { 591 | if (i === 0) { 592 | result.push(segment); 593 | } else { 594 | var segmentMatch = segment.match(/(\w+)(.*)/); 595 | var key = segmentMatch[1]; 596 | result.push(params[key]); 597 | result.push(segmentMatch[2] || ''); 598 | delete params[key]; 599 | } 600 | }); 601 | return result.join(''); 602 | } 603 | }]; 604 | } 605 | 606 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 607 | 608 | 609 | /** 610 | * @ngdoc object 611 | * @name ngRoute.$routeParams 612 | * @requires $route 613 | * 614 | * @description 615 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 616 | * 617 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 618 | * 619 | * The route parameters are a combination of {@link ng.$location `$location`}'s 620 | * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. 621 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 622 | * 623 | * In case of parameter name collision, `path` params take precedence over `search` params. 624 | * 625 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 626 | * (but its properties will likely change) even when a route change occurs. 627 | * 628 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 629 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 630 | * Instead you can use `$route.current.params` to access the new route's parameters. 631 | * 632 | * @example 633 | *
634 |  *  // Given:
635 |  *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
636 |  *  // Route: /Chapter/:chapterId/Section/:sectionId
637 |  *  //
638 |  *  // Then
639 |  *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
640 |  * 
641 | */ 642 | function $RouteParamsProvider() { 643 | this.$get = function() { return {}; }; 644 | } 645 | 646 | ngRouteModule.directive('ngView', ngViewFactory); 647 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 648 | 649 | 650 | /** 651 | * @ngdoc directive 652 | * @name ngRoute.directive:ngView 653 | * @restrict ECA 654 | * 655 | * @description 656 | * # Overview 657 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 658 | * including the rendered template of the current route into the main layout (`index.html`) file. 659 | * Every time the current route changes, the included view changes with it according to the 660 | * configuration of the `$route` service. 661 | * 662 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 663 | * 664 | * @animations 665 | * enter - animation is used to bring new content into the browser. 666 | * leave - animation is used to animate existing content away. 667 | * 668 | * The enter and leave animation occur concurrently. 669 | * 670 | * @scope 671 | * @priority 400 672 | * @example 673 | 674 | 675 |
676 | Choose: 677 | Moby | 678 | Moby: Ch1 | 679 | Gatsby | 680 | Gatsby: Ch4 | 681 | Scarlet Letter
682 | 683 |
684 |
685 |
686 |
687 | 688 |
$location.path() = {{main.$location.path()}}
689 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
690 |
$route.current.params = {{main.$route.current.params}}
691 |
$route.current.scope.name = {{main.$route.current.scope.name}}
692 |
$routeParams = {{main.$routeParams}}
693 |
694 |
695 | 696 | 697 |
698 | controller: {{book.name}}
699 | Book Id: {{book.params.bookId}}
700 |
701 |
702 | 703 | 704 |
705 | controller: {{chapter.name}}
706 | Book Id: {{chapter.params.bookId}}
707 | Chapter Id: {{chapter.params.chapterId}} 708 |
709 |
710 | 711 | 712 | .view-animate-container { 713 | position:relative; 714 | height:100px!important; 715 | position:relative; 716 | background:white; 717 | border:1px solid black; 718 | height:40px; 719 | overflow:hidden; 720 | } 721 | 722 | .view-animate { 723 | padding:10px; 724 | } 725 | 726 | .view-animate.ng-enter, .view-animate.ng-leave { 727 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 728 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 729 | 730 | display:block; 731 | width:100%; 732 | border-left:1px solid black; 733 | 734 | position:absolute; 735 | top:0; 736 | left:0; 737 | right:0; 738 | bottom:0; 739 | padding:10px; 740 | } 741 | 742 | .view-animate.ng-enter { 743 | left:100%; 744 | } 745 | .view-animate.ng-enter.ng-enter-active { 746 | left:0; 747 | } 748 | .view-animate.ng-leave.ng-leave-active { 749 | left:-100%; 750 | } 751 | 752 | 753 | 754 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], 755 | function($routeProvider, $locationProvider) { 756 | $routeProvider.when('/Book/:bookId', { 757 | templateUrl: 'book.html', 758 | controller: BookCntl, 759 | controllerAs: 'book' 760 | }); 761 | $routeProvider.when('/Book/:bookId/ch/:chapterId', { 762 | templateUrl: 'chapter.html', 763 | controller: ChapterCntl, 764 | controllerAs: 'chapter' 765 | }); 766 | 767 | // configure html5 to get links working on jsfiddle 768 | $locationProvider.html5Mode(true); 769 | }); 770 | 771 | function MainCntl($route, $routeParams, $location) { 772 | this.$route = $route; 773 | this.$location = $location; 774 | this.$routeParams = $routeParams; 775 | } 776 | 777 | function BookCntl($routeParams) { 778 | this.name = "BookCntl"; 779 | this.params = $routeParams; 780 | } 781 | 782 | function ChapterCntl($routeParams) { 783 | this.name = "ChapterCntl"; 784 | this.params = $routeParams; 785 | } 786 | 787 | 788 | 789 | it('should load and compile correct template', function() { 790 | element('a:contains("Moby: Ch1")').click(); 791 | var content = element('.doc-example-live [ng-view]').text(); 792 | expect(content).toMatch(/controller\: ChapterCntl/); 793 | expect(content).toMatch(/Book Id\: Moby/); 794 | expect(content).toMatch(/Chapter Id\: 1/); 795 | 796 | element('a:contains("Scarlet")').click(); 797 | content = element('.doc-example-live [ng-view]').text(); 798 | expect(content).toMatch(/controller\: BookCntl/); 799 | expect(content).toMatch(/Book Id\: Scarlet/); 800 | }); 801 | 802 |
803 | */ 804 | 805 | 806 | /** 807 | * @ngdoc event 808 | * @name ngRoute.directive:ngView#$viewContentLoaded 809 | * @eventOf ngRoute.directive:ngView 810 | * @eventType emit on the current ngView scope 811 | * @description 812 | * Emitted every time the ngView content is reloaded. 813 | */ 814 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 815 | function ngViewFactory( $route, $anchorScroll, $animate) { 816 | return { 817 | restrict: 'ECA', 818 | terminal: true, 819 | priority: 400, 820 | transclude: 'element', 821 | link: function(scope, $element, attr, ctrl, $transclude) { 822 | var currentScope, 823 | currentElement, 824 | autoScrollExp = attr.autoscroll, 825 | onloadExp = attr.onload || ''; 826 | 827 | scope.$on('$routeChangeSuccess', update); 828 | update(); 829 | 830 | function cleanupLastView() { 831 | if (currentScope) { 832 | currentScope.$destroy(); 833 | currentScope = null; 834 | } 835 | if(currentElement) { 836 | $animate.leave(currentElement); 837 | currentElement = null; 838 | } 839 | } 840 | 841 | function update() { 842 | var locals = $route.current && $route.current.locals, 843 | template = locals && locals.$template; 844 | 845 | if (angular.isDefined(template)) { 846 | var newScope = scope.$new(); 847 | var current = $route.current; 848 | 849 | // Note: This will also link all children of ng-view that were contained in the original 850 | // html. If that content contains controllers, ... they could pollute/change the scope. 851 | // However, using ng-view on an element with additional content does not make sense... 852 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 853 | // function is called before linking the content, which would apply child 854 | // directives to non existing elements. 855 | var clone = $transclude(newScope, function(clone) { 856 | $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { 857 | if (angular.isDefined(autoScrollExp) 858 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 859 | $anchorScroll(); 860 | } 861 | }); 862 | cleanupLastView(); 863 | }); 864 | 865 | currentElement = clone; 866 | currentScope = current.scope = newScope; 867 | currentScope.$emit('$viewContentLoaded'); 868 | currentScope.$eval(onloadExp); 869 | } else { 870 | cleanupLastView(); 871 | } 872 | } 873 | } 874 | }; 875 | } 876 | 877 | // This directive is called during the $transclude call of the first `ngView` directive. 878 | // It will replace and compile the content of the element with the loaded template. 879 | // We need this directive so that the element content is already filled when 880 | // the link function of another directive on the same element as ngView 881 | // is called. 882 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 883 | function ngViewFillContentFactory($compile, $controller, $route) { 884 | return { 885 | restrict: 'ECA', 886 | priority: -400, 887 | link: function(scope, $element) { 888 | var current = $route.current, 889 | locals = current.locals; 890 | 891 | $element.html(locals.$template); 892 | 893 | var link = $compile($element.contents()); 894 | 895 | if (current.controller) { 896 | locals.$scope = scope; 897 | var controller = $controller(current.controller, locals); 898 | if (current.controllerAs) { 899 | scope[current.controllerAs] = controller; 900 | } 901 | $element.data('$ngControllerController', controller); 902 | $element.children().data('$ngControllerController', controller); 903 | } 904 | 905 | link(scope); 906 | } 907 | }; 908 | } 909 | 910 | 911 | })(window, window.angular); 912 | -------------------------------------------------------------------------------- /todo-angular-koa/public/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 | -ms-transform: scale(1.3); 278 | transform: scale(1.3); 279 | } 280 | 281 | #todo-list li .destroy:after { 282 | content: '✖'; 283 | } 284 | 285 | #todo-list li:hover .destroy { 286 | display: block; 287 | } 288 | 289 | #todo-list li .edit { 290 | display: none; 291 | } 292 | 293 | #todo-list li.editing:last-child { 294 | margin-bottom: -1px; 295 | } 296 | 297 | #footer { 298 | color: #777; 299 | padding: 0 15px; 300 | position: absolute; 301 | right: 0; 302 | bottom: -31px; 303 | left: 0; 304 | height: 20px; 305 | z-index: 1; 306 | text-align: center; 307 | } 308 | 309 | #footer:before { 310 | content: ''; 311 | position: absolute; 312 | right: 0; 313 | bottom: 31px; 314 | left: 0; 315 | height: 50px; 316 | z-index: -1; 317 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 318 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 319 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 320 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 321 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 322 | } 323 | 324 | #todo-count { 325 | float: left; 326 | text-align: left; 327 | } 328 | 329 | #filters { 330 | margin: 0; 331 | padding: 0; 332 | list-style: none; 333 | position: absolute; 334 | right: 0; 335 | left: 0; 336 | } 337 | 338 | #filters li { 339 | display: inline; 340 | } 341 | 342 | #filters li a { 343 | color: #83756f; 344 | margin: 2px; 345 | text-decoration: none; 346 | } 347 | 348 | #filters li a.selected { 349 | font-weight: bold; 350 | } 351 | 352 | #clear-completed { 353 | float: right; 354 | position: relative; 355 | line-height: 20px; 356 | text-decoration: none; 357 | background: rgba(0, 0, 0, 0.1); 358 | font-size: 11px; 359 | padding: 0 10px; 360 | border-radius: 3px; 361 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 362 | } 363 | 364 | #clear-completed:hover { 365 | background: rgba(0, 0, 0, 0.15); 366 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 367 | } 368 | 369 | #info { 370 | margin: 65px auto 0; 371 | color: #a6a6a6; 372 | font-size: 12px; 373 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 374 | text-align: center; 375 | } 376 | 377 | #info a { 378 | color: inherit; 379 | } 380 | 381 | /* 382 | Hack to remove background from Mobile Safari. 383 | Can't use it globally since it destroys checkboxes in Firefox and Opera 384 | */ 385 | 386 | @media screen and (-webkit-min-device-pixel-ratio:0) { 387 | #toggle-all, 388 | #todo-list li .toggle { 389 | background: none; 390 | } 391 | 392 | #todo-list li .toggle { 393 | height: 40px; 394 | } 395 | 396 | #toggle-all { 397 | top: -56px; 398 | left: -15px; 399 | width: 65px; 400 | height: 41px; 401 | -webkit-transform: rotate(90deg); 402 | -ms-transform: rotate(90deg); 403 | transform: rotate(90deg); 404 | -webkit-appearance: none; 405 | appearance: none; 406 | } 407 | } 408 | 409 | .hidden { 410 | display: none; 411 | } 412 | 413 | hr { 414 | margin: 20px 0; 415 | border: 0; 416 | border-top: 1px dashed #C5C5C5; 417 | border-bottom: 1px dashed #F7F7F7; 418 | } 419 | 420 | .learn a { 421 | font-weight: normal; 422 | text-decoration: none; 423 | color: #b83f45; 424 | } 425 | 426 | .learn a:hover { 427 | text-decoration: underline; 428 | color: #787e7e; 429 | } 430 | 431 | .learn h3, 432 | .learn h4, 433 | .learn h5 { 434 | margin: 10px 0; 435 | font-weight: 500; 436 | line-height: 1.2; 437 | color: #000; 438 | } 439 | 440 | .learn h3 { 441 | font-size: 24px; 442 | } 443 | 444 | .learn h4 { 445 | font-size: 18px; 446 | } 447 | 448 | .learn h5 { 449 | margin-bottom: 0; 450 | font-size: 14px; 451 | } 452 | 453 | .learn ul { 454 | padding: 0; 455 | margin: 0 0 30px 25px; 456 | } 457 | 458 | .learn li { 459 | line-height: 20px; 460 | } 461 | 462 | .learn p { 463 | font-size: 15px; 464 | font-weight: 300; 465 | line-height: 1.3; 466 | margin-top: 0; 467 | margin-bottom: 0; 468 | } 469 | 470 | .quote { 471 | border: none; 472 | margin: 20px 0 60px 0; 473 | } 474 | 475 | .quote p { 476 | font-style: italic; 477 | } 478 | 479 | .quote p:before { 480 | content: '“'; 481 | font-size: 50px; 482 | opacity: .15; 483 | position: absolute; 484 | top: -20px; 485 | left: 3px; 486 | } 487 | 488 | .quote p:after { 489 | content: '”'; 490 | font-size: 50px; 491 | opacity: .15; 492 | position: absolute; 493 | bottom: -42px; 494 | right: 3px; 495 | } 496 | 497 | .quote footer { 498 | position: absolute; 499 | bottom: -40px; 500 | right: 0; 501 | } 502 | 503 | .quote footer img { 504 | border-radius: 3px; 505 | } 506 | 507 | .quote footer a { 508 | margin-left: 5px; 509 | vertical-align: middle; 510 | } 511 | 512 | .speech-bubble { 513 | position: relative; 514 | padding: 10px; 515 | background: rgba(0, 0, 0, .04); 516 | border-radius: 5px; 517 | } 518 | 519 | .speech-bubble:after { 520 | content: ''; 521 | position: absolute; 522 | top: 100%; 523 | right: 30px; 524 | border: 13px solid transparent; 525 | border-top-color: rgba(0, 0, 0, .04); 526 | } 527 | 528 | .learn-bar > .learn { 529 | position: absolute; 530 | width: 272px; 531 | top: 8px; 532 | left: -300px; 533 | padding: 10px; 534 | border-radius: 5px; 535 | background-color: rgba(255, 255, 255, .6); 536 | -webkit-transition-property: left; 537 | transition-property: left; 538 | -webkit-transition-duration: 500ms; 539 | transition-duration: 500ms; 540 | } 541 | 542 | @media (min-width: 899px) { 543 | .learn-bar { 544 | width: auto; 545 | margin: 0 0 0 300px; 546 | } 547 | 548 | .learn-bar > .learn { 549 | left: 8px; 550 | } 551 | 552 | .learn-bar #todoapp { 553 | width: 550px; 554 | margin: 130px auto 40px auto; 555 | } 556 | } 557 | -------------------------------------------------------------------------------- /todo-angular-koa/public/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; 126 | 127 | [/labs/, /\w*-examples/].forEach(function (href) { 128 | var match = location.href.match(href); 129 | 130 | if (!base && match) { 131 | base = location.href.indexOf(match); 132 | } 133 | }); 134 | 135 | return location.href.substr(0, base); 136 | } 137 | 138 | function getFile(file, callback) { 139 | if (!location.host) { 140 | return console.info('Miss the info bar? Run TodoMVC from a server to avoid a cross-origin error.'); 141 | } 142 | 143 | var xhr = new XMLHttpRequest(); 144 | 145 | xhr.open('GET', findRoot() + file, true); 146 | xhr.send(); 147 | 148 | xhr.onload = function () { 149 | if (xhr.status === 200 && callback) { 150 | callback(xhr.responseText); 151 | } 152 | }; 153 | } 154 | 155 | function Learn(learnJSON, config) { 156 | if (!(this instanceof Learn)) { 157 | return new Learn(learnJSON, config); 158 | } 159 | 160 | var template, framework; 161 | 162 | if (typeof learnJSON !== 'object') { 163 | try { 164 | learnJSON = JSON.parse(learnJSON); 165 | } catch (e) { 166 | return; 167 | } 168 | } 169 | 170 | if (config) { 171 | template = config.template; 172 | framework = config.framework; 173 | } 174 | 175 | if (!template && learnJSON.templates) { 176 | template = learnJSON.templates.todomvc; 177 | } 178 | 179 | if (!framework && document.querySelector('[data-framework]')) { 180 | framework = document.querySelector('[data-framework]').getAttribute('data-framework'); 181 | } 182 | 183 | 184 | if (template && learnJSON[framework]) { 185 | this.frameworkJSON = learnJSON[framework]; 186 | this.template = template; 187 | 188 | this.append(); 189 | } 190 | } 191 | 192 | Learn.prototype.append = function () { 193 | var aside = document.createElement('aside'); 194 | aside.innerHTML = _.template(this.template, this.frameworkJSON); 195 | aside.className = 'learn'; 196 | 197 | // Localize demo links 198 | var demoLinks = aside.querySelectorAll('.demo-link'); 199 | Array.prototype.forEach.call(demoLinks, function (demoLink) { 200 | demoLink.setAttribute('href', findRoot() + demoLink.getAttribute('href')); 201 | }); 202 | 203 | document.body.className = (document.body.className + ' learn-bar').trim(); 204 | document.body.insertAdjacentHTML('afterBegin', aside.outerHTML); 205 | }; 206 | 207 | redirect(); 208 | getFile('learn.json', Learn); 209 | })(); 210 | -------------------------------------------------------------------------------- /todo-angular-koa/public/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rethinkdb/rethinkdb-example-nodejs/51e09f01030a4211bd2b278745ddea172305e358/todo-angular-koa/public/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /todo-angular-koa/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoMVC with AngularJS, Koa and RethinkDB 6 | 7 | 8 | 9 | 10 | 11 | 12 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /todo-angular-koa/public/js/app.js: -------------------------------------------------------------------------------- 1 | /*global angular */ 2 | /*jshint unused:false */ 3 | 'use strict'; 4 | 5 | /** 6 | * The main TodoMVC app module 7 | * 8 | * @type {angular.Module} 9 | */ 10 | var todomvc = angular.module('todomvc', ['ngRoute']) 11 | .config(function ($routeProvider) { 12 | $routeProvider.when('/', { 13 | controller: 'TodoCtrl', 14 | templateUrl: 'todomvc-index.html' 15 | }).when('/:status', { 16 | controller: 'TodoCtrl', 17 | templateUrl: 'todomvc-index.html' 18 | }).otherwise({ 19 | redirectTo: '/' 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /todo-angular-koa/public/js/controllers/todoCtrl.js: -------------------------------------------------------------------------------- 1 | /*global todomvc, angular */ 2 | 'use strict'; 3 | 4 | /** 5 | * The main controller for the app. The controller: 6 | * - retrieves and persists the model via the todoStorage service 7 | * - exposes the model to the template and provides event handlers 8 | */ 9 | todomvc.controller('TodoCtrl', function TodoCtrl($scope, $routeParams, todoStorage, filterFilter) { 10 | $scope.todos = []; 11 | 12 | todoStorage.get().success(function(todos) { 13 | $scope.todos = todos; 14 | }).error(function(error) { 15 | alert("Failed to load TODOs"); 16 | 17 | }); 18 | 19 | $scope.newTodo = ''; 20 | $scope.editedTodo = null; 21 | 22 | $scope.$watch('todos', function (newValue, oldValue) { 23 | $scope.remainingCount = filterFilter($scope.todos, { completed: false }).length; 24 | $scope.completedCount = $scope.todos.length - $scope.remainingCount; 25 | $scope.allChecked = !$scope.remainingCount; 26 | }, true); 27 | 28 | // Monitor the current route for changes and adjust the filter accordingly. 29 | $scope.$on('$routeChangeSuccess', function () { 30 | var status = $scope.status = $routeParams.status || ''; 31 | 32 | $scope.statusFilter = (status === 'active') ? 33 | { completed: false } : (status === 'completed') ? 34 | { completed: true } : null; 35 | }); 36 | 37 | $scope.addTodo = function () { 38 | var newTitle = $scope.newTodo.trim(); 39 | if (!newTitle.length) { 40 | return; 41 | } 42 | var newTodo = { 43 | title: newTitle, 44 | completed: false 45 | } 46 | todoStorage.create(newTodo).success(function(savedTodo) { 47 | $scope.todos.push(savedTodo); 48 | }).error(function(error) { 49 | alert("Failed to save the new TODO"); 50 | }); 51 | $scope.newTodo = ''; 52 | }; 53 | 54 | $scope.toggleTodo = function (todo) { 55 | var copyTodo = angular.extend({}, todo); 56 | copyTodo.completed = !copyTodo.completed 57 | todoStorage.update(copyTodo).success(function(newTodo) { 58 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 59 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 60 | } 61 | else { 62 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 63 | $scope.editedTodo = null; 64 | } 65 | }).error(function() { 66 | console.log('fds'); 67 | alert("Failed to update the status of this TODO"); 68 | }); 69 | 70 | }; 71 | $scope.editTodo = function (todo) { 72 | $scope.editedTodo = todo; 73 | // Clone the original todo to restore it on demand. 74 | $scope.originalTodo = angular.extend({}, todo); 75 | }; 76 | 77 | $scope.doneEditing = function (todo, $event) { 78 | todo.title = todo.title.trim(); 79 | if ((todo._saving !== true) && ($scope.originalTodo.title !== todo.title)) { 80 | todo._saving = true; // submit and blur trigger this method. Let's save the document just once 81 | todoStorage.update(todo).success(function(newTodo) { 82 | if (newTodo === 'null') { // Compare with a string because of https://github.com/angular/angular.js/issues/2973 83 | console.log('hum'); 84 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 85 | } 86 | else { 87 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 88 | $scope.editedTodo = null; 89 | } 90 | }).error(function() { 91 | todo._saving = false; 92 | alert("Failed to update this TODO"); 93 | }); 94 | } 95 | else { 96 | $scope.editedTodo = null; 97 | } 98 | }; 99 | 100 | $scope.revertEditing = function (todo) { 101 | $scope.todos[$scope.todos.indexOf(todo)] = $scope.originalTodo; 102 | $scope.doneEditing($scope.originalTodo); 103 | }; 104 | 105 | $scope.removeTodo = function (todo) { 106 | todoStorage.delete(todo.id).success(function() { 107 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 108 | }).error(function() { 109 | alert("Failed to delete this TODO"); 110 | }); 111 | }; 112 | 113 | $scope.clearCompletedTodos = function () { 114 | $scope.todos = todos.filter(function (val) { 115 | return !val.completed; 116 | }); 117 | }; 118 | 119 | $scope.markAll = function (completed) { 120 | $scope.todos.forEach(function (todo) { 121 | if (todo.completed !== !completed) { 122 | $scope.toggleTodo(todo); 123 | } 124 | //todo.completed = !completed; 125 | }); 126 | }; 127 | }); 128 | -------------------------------------------------------------------------------- /todo-angular-koa/public/js/directives/todoEscape.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that executes an expression when the element it is applied to gets 6 | * an `escape` keydown event. 7 | */ 8 | todomvc.directive('todoEscape', function () { 9 | var ESCAPE_KEY = 27; 10 | return function (scope, elem, attrs) { 11 | elem.bind('keydown', function (event) { 12 | if (event.keyCode === ESCAPE_KEY) { 13 | scope.$apply(attrs.todoEscape); 14 | } 15 | }); 16 | }; 17 | }); 18 | 19 | -------------------------------------------------------------------------------- /todo-angular-koa/public/js/directives/todoFocus.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Directive that places focus on the element it is applied to when the expression it binds to evaluates to true 6 | */ 7 | todomvc.directive('todoFocus', function todoFocus($timeout) { 8 | return function (scope, elem, attrs) { 9 | scope.$watch(attrs.todoFocus, function (newVal) { 10 | if (newVal) { 11 | $timeout(function () { 12 | elem[0].focus(); 13 | }, 0, false); 14 | } 15 | }); 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /todo-angular-koa/public/js/services/todoStorage.js: -------------------------------------------------------------------------------- 1 | /*global todomvc */ 2 | 'use strict'; 3 | 4 | /** 5 | * Services that persists and retrieves TODOs from localStorage 6 | */ 7 | todomvc.factory('todoStorage', function ($http) { 8 | var STORAGE_ID = 'todos-angularjs'; 9 | 10 | return { 11 | get: function () { 12 | var url = "/todo/get"; 13 | return $http.get(url); 14 | }, 15 | create: function (todo) { 16 | var url = "/todo/new"; 17 | return $http.put(url, todo); 18 | }, 19 | update: function (todo) { 20 | var url = "/todo/update"; 21 | return $http.post(url, JSON.stringify(todo)); 22 | }, 23 | delete: function(id) { 24 | var url = "/todo/delete"; 25 | return $http.post(url, JSON.stringify({id: id})); 26 | } 27 | 28 | }; 29 | }); 30 | --------------------------------------------------------------------------------