├── todo ├── .gitignore ├── public │ ├── bower_components │ │ ├── todomvc-common │ │ │ ├── bg.png │ │ │ ├── base.js │ │ │ └── base.css │ │ └── angular-route │ │ │ └── angular-route.js │ ├── bower.json │ ├── js │ │ ├── directives │ │ │ ├── todoEscape.js │ │ │ └── todoFocus.js │ │ ├── app.js │ │ ├── services │ │ │ └── todoStorage.js │ │ └── controllers │ │ │ └── todoCtrl.js │ └── index.html ├── config.js ├── package.json ├── init.js ├── readme.md └── app.js ├── README.md ├── .gitignore ├── import-export ├── package.json ├── README.md ├── export.js └── import.js └── LICENSE /todo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rethinkdbdash-examples 2 | ====================== 3 | 4 | Examples using rethinkdbdash 5 | -------------------------------------------------------------------------------- /todo/public/bower_components/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neumino/rethinkdbdash-examples/HEAD/todo/public/bower_components/todomvc-common/bg.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | import-export/data* 18 | -------------------------------------------------------------------------------- /todo/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rethinkdb: { 3 | host: "localhost", 4 | port: 28015, 5 | authKey: "", 6 | db: "dashex" 7 | }, 8 | db: "dashex", 9 | table: "todo", 10 | koa: { 11 | port: 3000 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /todo/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 | -------------------------------------------------------------------------------- /import-export/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rethinkdbdash-stream-export" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "rethinkdbdash": ">= 1.16.10" 7 | , "streaming-json-stringify": "1.0.0" 8 | , "minimist": "1.1.0" 9 | , "split": "0.3.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thinky-blog-example" 3 | , "version": "0.0.1" 4 | , "private": true 5 | , "dependencies": { 6 | "rethinkdbdash": ">= 1.16.0" 7 | , "bluebird": "1.0.0" 8 | , "koa": "0.3.0" 9 | , "koa-static": "1.4.2" 10 | , "koa-route": "1.0.2" 11 | , "co-body": "0.0.1" 12 | , "co": "3.0.2" 13 | , "co-assert-timeout": "0.0.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /todo/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/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/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/init.js: -------------------------------------------------------------------------------- 1 | var Bluebird = require('bluebird'); 2 | var config = require('./config'); 3 | var r = require('rethinkdbdash')(config.rethinkdb); 4 | 5 | Bluebird.coroutine(function* () { 6 | try{ 7 | yield r.dbCreate("dashex"); 8 | console.log("Database `dashex` created"); 9 | 10 | yield r.db("dashex").tableCreate("todo"); 11 | console.log("Table `todo` created"); 12 | 13 | yield r.db("dashex").table("todo").indexCreate("createdAt"); 14 | console.log("Index `createdAt` created."); 15 | } 16 | catch(e) { 17 | console.log(e.message); 18 | console.log(e); 19 | } 20 | yield r.getPool().drain(); 21 | })(); 22 | 23 | -------------------------------------------------------------------------------- /todo/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)); 26 | } 27 | 28 | }; 29 | }); 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Michel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /import-export/README.md: -------------------------------------------------------------------------------- 1 | # Examples using streams in rethinkdbdash 2 | 3 | This repository show simple examples on how to use streams with rethinkdbdash. 4 | 5 | ### Install: 6 | 7 | ``` 8 | npm install 9 | ``` 10 | 11 | 12 | ### Commands 13 | 14 | Export your data: 15 | ``` 16 | node export.js 17 | ``` 18 | 19 | Import your data: 20 | ``` 21 | node import.js 22 | ``` 23 | 24 | Run the scripts with `--help` for more options. 25 | 26 | ### Interesting snippets 27 | 28 | The interesting snippets are: 29 | 30 | ```js 31 | // Pipe a RethinkDB table to a file 32 | r.db(db).table(table).toStream({timeFormat: 'raw', binaryFormat: 'raw'}) 33 | .pipe(Stringify()) 34 | .pipe(fs.createWriteStream(dir+'/'+db+'.'+table+'.txt')) 35 | .on('finish', cb); 36 | ``` 37 | 38 | ```js 39 | // Pipe a file to a RethinkDB table 40 | fs.createReadStream(file) 41 | .pipe(split(JSON.parse)) 42 | .pipe(r.db(db).table(table).toStream({writable: true})) 43 | .on('finish', cb); 44 | ``` 45 | 46 | 47 | ### Learn more about rethinkdbdash ### 48 | 49 | * [Official repo](https://github.com/neumino/rethinkdbdash) 50 | * [Examples](https://github.com/neumino/rethinkdbdash-examples) 51 | 52 | 53 | ### Learn more about RethinkDB ### 54 | 55 | * [Advancing the realtime web](http://rethinkdb.com/blog/realtime-web/) 56 | * [RethinkDB vs today's NoSQL](http://www.rethinkdb.com/blog/mongodb-biased-comparison/) 57 | * [Quickstart](http://rethinkdb.com/docs/quickstart/) 58 | * [API reference](http://rethinkdb.com/api/javascript/) 59 | 60 | 61 | ### Learn more about Streams ### 62 | 63 | * [API Reference](https://nodejs.org/api/stream.html) 64 | 65 | 66 | 67 | ### License ### 68 | 69 | MIT 70 | 71 | Copyright (c) 2013-2014 Michel Tu 72 | 73 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 74 | software and associated documentation files (the 'Software'), to deal in the Software 75 | without restriction, including without limitation the rights to use, copy, modify, merge, 76 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 77 | persons to whom the Software is furnished to do so, subject to the following conditions: 78 | 79 | The above copyright notice and this permission notice shall be included in all copies or 80 | substantial portions of the Software. 81 | 82 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 83 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 84 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 85 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 86 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 87 | DEALINGS IN THE SOFTWARE. 88 | -------------------------------------------------------------------------------- /todo/readme.md: -------------------------------------------------------------------------------- 1 | # Todo example with Rethinkdbdash, 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 rethinkdbdash is in the file `app.js`. 5 | 6 | ### Run ### 7 | You need node > 0.11.9 (and currently built from source for rethinkdbdash). 8 | 9 | Run `npm install` to install the dependencies. 10 | 11 | Update `config.js` if you are not using RethinkDB default parameters. 12 | 13 | Start the server with 14 | ``` 15 | node --harmony app.js 16 | ``` 17 | 18 | 19 | ### Original source ### 20 | 21 | The original source was taken from: 22 | [https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs](https://github.com/tastejs/todomvc/tree/gh-pages/architecture-examples/angularjs) 23 | 24 | 25 | 26 | ### Learn more about rethinkdbdash ### 27 | [Official repo](https://github.com/neumino/rethinkdbdash) 28 | [Examples](https://github.com/neumino/rethinkdbdash-examples) 29 | 30 | 31 | 32 | ### Learn more about koa ### 33 | [Official repo](https://github.com/koajs/koa) 34 | [Koa examples](https://github.com/koajs/examples) 35 | 36 | 37 | 38 | ### Learn more about AngularJS ### 39 | 40 | * [Tutorial](http://docs.angularjs.org/tutorial) 41 | * [API Reference](http://docs.angularjs.org/api) 42 | * [Developer Guide](http://docs.angularjs.org/guide) 43 | * [Applications built with AngularJS](http://builtwith.angularjs.org) 44 | * [Blog](http://blog.angularjs.org) 45 | * [FAQ](http://docs.angularjs.org/misc/faq) 46 | * [AngularJS Meetups](http://www.youtube.com/angularjs) 47 | 48 | 49 | 50 | ### License ### 51 | MIT 52 | Author: Michel Tu 53 | 54 | Orginal copyright: [https://github.com/tastejs/todomvc/blob/gh-pages/license.md](https://github.com/tastejs/todomvc/blob/gh-pages/license.md) 55 | 56 | Copyright (c) Addy Osmani, Sindre Sorhus, Pascal Hartig, Stephen Sawchuk. 57 | 58 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 59 | software and associated documentation files (the "Software"), to deal in the Software 60 | without restriction, including without limitation the rights to use, copy, modify, merge, 61 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit 62 | persons to whom the Software is furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all copies or 65 | substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 68 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 69 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 70 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 71 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 72 | DEALINGS IN THE SOFTWARE. 73 | 74 | -------------------------------------------------------------------------------- /todo/app.js: -------------------------------------------------------------------------------- 1 | var app = require('koa')(); 2 | 3 | // Middleware 4 | var serve = require('koa-static'); 5 | var parse = require('co-body'); 6 | var route = require('koa-route'); 7 | var assertTimeout = require('co-assert-timeout'); 8 | var http = require('http'); 9 | 10 | // Load config for RethinkDB and koa 11 | var config = require(__dirname+"/config.js") 12 | 13 | // Import rethinkdbdash 14 | var r = require('rethinkdbdash')(config.rethinkdb); 15 | 16 | app.use(function* (next) { 17 | // Each request has 5 seconds to return 18 | yield assertTimeout(next, '5 seconds') 19 | }) 20 | 21 | app.use(route.get('/todo/get', get)); 22 | app.use(route.put('/todo/new', create)); 23 | app.use(route.post('/todo/update', update)); 24 | app.use(route.post('/todo/delete', del)); 25 | 26 | // Static content 27 | app.use(serve(__dirname+'/public')); 28 | 29 | // Retrieve all todos 30 | function* get() { 31 | try{ 32 | var result = yield r.table(config.table).orderBy({index: "createdAt"}); 33 | this.body = JSON.stringify(result); 34 | } 35 | catch(e) { 36 | this.status = 500; 37 | this.body = e.message || http.STATUS_CODES[this.status]; 38 | } 39 | } 40 | 41 | // Create a new todo 42 | function* create() { 43 | try{ 44 | var todo = yield parse(this); 45 | var result = yield r.table(config.table).insert( 46 | r.expr(todo).merge({ 47 | createdAt: r.now() // The date r.now(0 gets computed on the server -- new Date() would have work fine too 48 | }), 49 | {returnChanges: true} 50 | ); 51 | 52 | todo = result.changes[0].new_val; // todo now contains the previous todo + a field `id` and `createdAt` 53 | this.body = JSON.stringify(todo); 54 | } 55 | catch(e) { 56 | this.status = 500; 57 | this.body = e.message || http.STATUS_CODES[this.status]; 58 | } 59 | } 60 | 61 | // Update a todo 62 | function* update() { 63 | try{ 64 | var todo = yield parse(this); 65 | delete todo._saving; 66 | var id = todo.id; 67 | 68 | var result = yield r.table(config.table).get(id).update( 69 | {title: todo.title, completed: todo.completed}, // We just want to update these two fields 70 | {returnChanges: true} 71 | ); 72 | 73 | result = result.changes[0].new_val; 74 | this.body = JSON.stringify(result); 75 | } 76 | catch(e) { 77 | this.status = 500; 78 | this.body = e.message || http.STATUS_CODES[this.status]; 79 | } 80 | } 81 | 82 | // Delete a todo 83 | function* del() { 84 | try{ 85 | var id = yield parse(this); 86 | var result = yield r.table(config.table).get(id).delete(); 87 | this.body = ""; 88 | } 89 | catch(e) { 90 | this.status = 500; 91 | this.body = e.message || http.STATUS_CODES[this.status]; 92 | } 93 | } 94 | 95 | // Start koa 96 | app.listen(config.koa.port); 97 | console.log('listening on port '+config.koa.port); 98 | -------------------------------------------------------------------------------- /todo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularJS • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 | 12 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /todo/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 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 59 | $scope.editedTodo = null; 60 | }).error(function() { 61 | alert("Failed to update the status of this TODO"); 62 | }); 63 | 64 | }; 65 | $scope.editTodo = function (todo) { 66 | $scope.editedTodo = todo; 67 | // Clone the original todo to restore it on demand. 68 | $scope.originalTodo = angular.extend({}, todo); 69 | }; 70 | 71 | $scope.doneEditing = function (todo, $event) { 72 | todo.title = todo.title.trim(); 73 | if ((todo._saving !== true) && ($scope.originalTodo.title !== todo.title)) { 74 | todo._saving = true; // submit and blur trigger this method. Let's save the document just once 75 | todoStorage.update(todo).success(function(newTodo) { 76 | $scope.todos[$scope.todos.indexOf(todo)] = newTodo; 77 | $scope.editedTodo = null; 78 | }).error(function() { 79 | todo._saving = false; 80 | alert("Failed to update this TODO"); 81 | }); 82 | } 83 | else { 84 | $scope.editedTodo = null; 85 | } 86 | }; 87 | 88 | $scope.revertEditing = function (todo) { 89 | $scope.todos[$scope.todos.indexOf(todo)] = $scope.originalTodo; 90 | $scope.doneEditing($scope.originalTodo); 91 | }; 92 | 93 | $scope.removeTodo = function (todo) { 94 | todoStorage.delete(todo.id).success(function() { 95 | $scope.todos.splice($scope.todos.indexOf(todo), 1); 96 | }).error(function() { 97 | alert("Failed to delete this TODO"); 98 | }); 99 | }; 100 | 101 | $scope.clearCompletedTodos = function () { 102 | $scope.todos = todos.filter(function (val) { 103 | return !val.completed; 104 | }); 105 | }; 106 | 107 | $scope.markAll = function (completed) { 108 | $scope.todos.forEach(function (todo) { 109 | if (todo.completed !== !completed) { 110 | $scope.toggleTodo(todo); 111 | } 112 | //todo.completed = !completed; 113 | }); 114 | }; 115 | }); 116 | -------------------------------------------------------------------------------- /import-export/export.js: -------------------------------------------------------------------------------- 1 | var argv = require('minimist')(process.argv.slice(2)); 2 | 3 | var options = {}; 4 | options.host = argv.host || 'localhost'; 5 | options.port = argv.port || 28015; 6 | options.silent = true; 7 | var r = require('rethinkdbdash')(options); 8 | 9 | var fs = require('fs'); 10 | var Stringify = require('streaming-json-stringify'); 11 | Stringify.prototype.open = new Buffer('', 'utf8') 12 | Stringify.prototype.seperator = new Buffer('\n', 'utf8') 13 | Stringify.prototype.close = new Buffer('', 'utf8') 14 | 15 | // Define the directory where export files will be created 16 | var now = new Date().toISOString(); 17 | var dir = 'data'+now; 18 | 19 | // Handler for errors 20 | function _handleError(error) { 21 | console.error('An error happened during the export operation:'); 22 | console.error(' '+error.message); 23 | process.exit(1); 24 | } 25 | 26 | // Export a given a database and a table 27 | function _export(db, table, cb) { 28 | console.log('Exporting '+db+'.'+table); 29 | r.db(db).table(table).toStream({timeFormat: 'raw', binaryFormat: 'raw'}) 30 | .on('error', _handleError) 31 | .pipe(Stringify()) 32 | .on('error', _handleError) 33 | .pipe(fs.createWriteStream(dir+'/'+db+'.'+table+'.txt')) 34 | .on('error', _handleError) 35 | .on('finish', cb); 36 | } 37 | 38 | // Export all the tables provided as {db: ..., table: ...} 39 | function _exportAll(tables, cb, index) { 40 | index = index | 0; 41 | if (index < tables.length) { 42 | var db = tables[index].db; 43 | var table = tables[index].table; 44 | _export(db, table, function() { 45 | index++; 46 | _exportAll(tables, cb, index) 47 | }); 48 | } 49 | else { 50 | cb(); 51 | } 52 | } 53 | 54 | // Final handler, purge the pool and print 'Done' 55 | function _done() { 56 | r.getPool().drain(); 57 | console.log('Done.'); 58 | } 59 | 60 | function _help() { 61 | console.log('Export the data from your RethinkDB instance.') 62 | console.log('This is an example for rethinkdbdash. Use `rethinkdb export` for faster imports.'); 63 | console.log(''); 64 | console.log('Optional arguments are'); 65 | console.log(' --help Print this help'); 66 | console.log(' --host host Host of the rethinkdb instance to connect to.'); 67 | console.log(' Default `localhost`'); 68 | console.log(' --port port Driver port of the rethinkdb instance'); 69 | console.log(' Default 28015'); 70 | console.log(' --db database Database to export'); 71 | console.log(' By default all the databases will be export except `rethinkdb`'); 72 | console.log(' --table table Table to export'); 73 | console.log(' By default all the table of the provided database will be exported'); 74 | 75 | process.exit(0); 76 | } 77 | 78 | // Main function 79 | function _main() { 80 | if (argv.help != null) { 81 | _help(); 82 | } 83 | console.log('Creating the `data` directory...'); 84 | try { 85 | fs.mkdirSync(dir) 86 | } 87 | catch(err) { 88 | _handleError(err); 89 | } 90 | console.log('Exporting...'); 91 | 92 | if (argv.db != null) { 93 | if (argv.table != null) { 94 | // Export a unique table 95 | _export(argv.db, argv.table, _done); 96 | } 97 | else { 98 | // Export all the tables in the provided database 99 | r.db(argv.db).tableList().map(function(table) { 100 | return {db: argv.db, table: table}; 101 | }).run().then(function(tables) { 102 | _exportAll(tables, _done); 103 | }).error(_handleError); 104 | } 105 | } 106 | else { 107 | // Export all the databases and tables 108 | r.dbList().filter(function(db) { 109 | return db.ne('rethinkdb') // do not export the system table 110 | }).concatMap(function(db) { 111 | return r.db(db).tableList().map(function(table) { 112 | return {db: db, table: table} 113 | }) 114 | }).run().then(function(tables) { 115 | _exportAll(tables, _done); 116 | }).error(_handleError); 117 | } 118 | } 119 | 120 | _main(); 121 | -------------------------------------------------------------------------------- /import-export/import.js: -------------------------------------------------------------------------------- 1 | var argv = require('minimist')(process.argv.slice(2)); 2 | 3 | var options = {}; 4 | options.host = argv.host || 'localhost'; 5 | options.port = argv.port || 28015; 6 | options.silent = true; 7 | var r = require('rethinkdbdash')(options); 8 | 9 | var fs = require('fs'); 10 | var split = require('split'); 11 | 12 | // Handler for errors 13 | function _handleError(error) { 14 | console.error('An error happened during the export operation:'); 15 | console.error(' '+error.message); 16 | process.exit(1); 17 | } 18 | 19 | // Import the file in the table 20 | function _import(file, db, table, cb) { 21 | console.log('Importing file '+file); 22 | fs.createReadStream(file) 23 | .on('error', _handleError) 24 | .pipe(split(JSON.parse)) 25 | .on('error', _handleError) 26 | .pipe(r.db(db).table(table).toStream({writable: true})) 27 | .on('finish', cb); 28 | } 29 | 30 | // Create the table if needed 31 | function _createTables(files, cb, index) { 32 | index = index | 0; 33 | if (index < files.length) { 34 | var file = files[index].file; 35 | var db = files[index].db; 36 | var table = files[index].table; 37 | 38 | r.branch( 39 | r.dbList().contains(db), 40 | {dbs_created: 1}, 41 | r.dbCreate(db) 42 | ).do(function() { 43 | return r.db(db).tableCreate(table) 44 | }).run().then(function(result) { 45 | 46 | _import(file, db, table, function() { 47 | index++; 48 | _createTables(files, cb, index); 49 | }); 50 | 51 | }).error(_handleError); 52 | } 53 | else { 54 | cb(); 55 | } 56 | 57 | } 58 | 59 | // Read all the files from the directory and make sure they match the format `db.table.txt` 60 | function _readDir(dir) { 61 | try { 62 | var fileNames = fs.readdirSync(dir); 63 | var files = []; 64 | for(var i=0; i 0) { 125 | dirs.sort(); 126 | var dir = dirs[dirs.length-1]; 127 | console.log('Importing files from '+dir); 128 | _readDir(dir, _done); 129 | } 130 | else { 131 | console.log('Could not find any directories starting with `data`.'); 132 | process.exit(1); 133 | } 134 | } 135 | catch(err) { 136 | _handleError(err); 137 | } 138 | } 139 | } 140 | 141 | _main(); 142 | -------------------------------------------------------------------------------- /todo/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/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/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 | --------------------------------------------------------------------------------