├── .bowerrc ├── .gitignore ├── README.md ├── bower.json ├── gulpfile.js ├── package.json ├── plugins └── .gitignore └── www ├── config.xml ├── css └── app.css ├── index.html ├── js ├── app-ng-pouchdb.js ├── ng-pouchdb-collection.js ├── v1.js ├── v2.js ├── v3.js ├── v4.js ├── v5.js └── v6.js ├── res ├── ios │ └── icon.png └── screens │ └── ios │ ├── Default-568h@2x~iphone.png │ └── Default@2x~iphone.png ├── v1.html ├── v2.html ├── v3.html ├── v4.html ├── v5.html └── v6.html /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "www/lib" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*.sw* 2 | *.keystore 3 | node_modules 4 | www/lib 5 | .idea 6 | .DS_Store 7 | tmp 8 | platforms 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ionic-pouchdb-todo 2 | ========================== 3 | 4 | This is an older example of the use of [ng-pouchdb](https://github.com/danielzen/ng-pouchdb). 5 | 6 | Please see the latest example of this library in use at http://github.com/danielzen/todo-ng-pouchdb 7 | 8 | This is a 4-way data-binding library, in action using a simple Ionic Todo app with a PouchDb local storage backend configured to sync with a CouchDb installation. This is a demo of offline functionality with server synchronization. And is part of my Offline data synchronization talk. 9 | Slides available at: [http://zndg.tl/ng-pouchdb](http://zndg.tl/ng-pouchdb) 10 | 11 | You can watch me demo building an early version of the app at 12 | [FITC Spotlight: AngularJS](http://youtu.be/6ecuA-pOev0?t=14m9s) in Toronto. 13 | 14 | This repository has multiple releases you can download or tags you can checkout to see the incremental building 15 | of the application. Only the final version using the ng-pouchdb library. 16 | 17 | ## Installation 18 | 19 | You may need to install bower globally with `npm install -g bower` before running `bower install`, to download 20 | the necessary required frontend libraries. You may need to do a `npm -g install bower`, if you haven't already. 21 | 22 | ## Run the App 23 | 24 | You can `cd` into the `www` directory and run 25 | 26 | ```bash 27 | python -m SimpleHTTPServer 8000 28 | ``` 29 | If you're using Python 3.x or higher 30 | 31 | ```bash 32 | python -m http.server 8000 33 | ``` 34 | 35 | 36 | You can then just open [http://localhost:8000/index.html](http://localhost:8000/index.html) in a browser. 37 | 38 | Personally I use WebStorm which has a built in server. From a JetBrains product, you can select "View...", "Open in Browser" on index.html. 39 | 40 | The final version of this demo requires you to [download and install CouchDb](http://couchdb.apache.org/#download), which runs on port 5984. And, [enable CORS](http://docs.couchdb.org/en/1.6.1/config/http.html#cross-origin-resource-sharing). 41 | 42 | ## iOS version 43 | 44 | However, to run this as a mobile application in iOS emulator, 45 | do the following to setup : 46 | 47 | ```bash 48 | $ cd ionic-pouchdb-todo 49 | $ sudo npm install -g cordova ionic gulp 50 | ``` 51 | 52 | To run in the iPhone Simulator: 53 | 54 | ```bash 55 | ionic platform add ios 56 | ionic build ios 57 | ionic emulate ios 58 | ``` 59 | 60 | ## Building Out & Updating Ionic or PouchDb 61 | 62 | To update to a new version of Ionic, open bower.json and change the version listed there. 63 | 64 | For example, to update from version `1.0.0-beta.12` to `1.0.0-beta.13`, open bower.json and change this: 65 | 66 | ``` 67 | "ionic": "driftyco/ionic-bower#1.0.0-beta.12" 68 | ``` 69 | 70 | After saving the update to bower.json file, run `bower install`. 71 | 72 | I was a little forward thinking adding the `package.json` file. 73 | To continue working on this repository, adding tests, using SASS, you can 74 | 75 | ```bash 76 | $ npm install 77 | $ gulp install 78 | ``` 79 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ionic PouchDb Todo", 3 | "private": "true", 4 | "devDependencies": { 5 | "ionic": "driftyco/ionic-bower#1.0.0-beta.13" 6 | }, 7 | "dependencies": { 8 | "pouchdb": "~3", 9 | "angular-pouchdb": "0.1" 10 | }, 11 | "resolutions": { 12 | "angular": "~1.3", 13 | "angular-ui-router": "~0.2", 14 | "angular-animate": "~1.2", 15 | "angular-sanitize": "~1.2", 16 | "pouchdb": "~3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var bower = require('bower'); 4 | var concat = require('gulp-concat'); 5 | var sass = require('gulp-sass'); 6 | var minifyCss = require('gulp-minify-css'); 7 | var rename = require('gulp-rename'); 8 | var sh = require('shelljs'); 9 | 10 | var paths = { 11 | sass: ['./scss/**/*.scss'] 12 | }; 13 | 14 | gulp.task('default', ['sass']); 15 | 16 | gulp.task('sass', function(done) { 17 | gulp.src('./scss/ionic.app.scss') 18 | .pipe(sass()) 19 | .pipe(gulp.dest('./www/css/')) 20 | .pipe(minifyCss({ 21 | keepSpecialComments: 0 22 | })) 23 | .pipe(rename({ extname: '.min.css' })) 24 | .pipe(gulp.dest('./www/css/')) 25 | .on('end', done); 26 | }); 27 | 28 | gulp.task('watch', function() { 29 | gulp.watch(paths.sass, ['sass']); 30 | }); 31 | 32 | gulp.task('install', ['git-check'], function() { 33 | return bower.commands.install() 34 | .on('log', function(data) { 35 | gutil.log('bower', gutil.colors.cyan(data.id), data.message); 36 | }); 37 | }); 38 | 39 | gulp.task('git-check', function(done) { 40 | if (!sh.which('git')) { 41 | console.log( 42 | ' ' + gutil.colors.red('Git is not installed.'), 43 | '\n Git, the version control system, is required to download Ionic.', 44 | '\n Download git here:', gutil.colors.cyan('http://git-scm.com/downloads') + '.', 45 | '\n Once git is installed, run \'' + gutil.colors.cyan('gulp install') + '\' again.' 46 | ); 47 | process.exit(1); 48 | } 49 | done(); 50 | }); 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ionic-pouchdb-todo", 3 | "version": "1.1.0", 4 | "description": "An Example of the ng-pouchdb library in action", 5 | "repository" : 6 | { 7 | "type" : "git", 8 | "url" : "http://github.com/danielzen/ionic-pouchdb-todo.git" 9 | }, 10 | "dependencies": { 11 | "gulp": "^3.5.6", 12 | "gulp-sass": "^0.7.1", 13 | "gulp-concat": "^2.2.0", 14 | "gulp-minify-css": "^0.3.0", 15 | "gulp-rename": "^1.2.0" 16 | }, 17 | "devDependencies": { 18 | "bower": "^1.3", 19 | "gulp-util": "^2.2.14", 20 | "shelljs": "^0.3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzen/ionic-pouchdb-todo/76c8c591ea9565951e6a448940df716b1c0d6182/plugins/.gitignore -------------------------------------------------------------------------------- /www/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | IonPouch Todo 4 | 5 | An Ionic Framework TODO demo with a PouchDB sync backend 6 | 7 | 8 | Zen Digital 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /www/css/app.css: -------------------------------------------------------------------------------- 1 | .complete { 2 | text-decoration: line-through; 3 | color: grey; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v7:ToDo - ng-pouchdb 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |

Todos

32 | 33 | 36 | 39 |
40 | 41 | 42 | 45 | 46 | {{task.title}} 47 | 48 | Edit 49 | 50 | 51 | Delete 52 | 53 | 54 | 55 | 56 | 57 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /www/js/app-ng-pouchdb.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic','pouchdb']) 2 | .controller('TodoCtrl', function($scope, $ionicModal, $ionicPopup, $ionicListDelegate, pouchCollection) { 3 | 4 | 5 | //////////////////////// 6 | // Replace PouchDb database calls with pouchCollection 7 | //////////////////////// 8 | $scope.todos = pouchCollection('todoslib'); 9 | 10 | $scope.online = false; 11 | $scope.toggleOnline = function() { 12 | $scope.online = !$scope.online; 13 | if ($scope.online) { // Read http://pouchdb.com/api.html#sync 14 | //////////////////////// 15 | // sync to CouchDb with pouchCollection reference 16 | //////////////////////// 17 | $scope.sync = $scope.todos.$db.replicate.sync('http://127.0.0.1:5984/todoslib', {live: true}) 18 | .on('error', function (err) { 19 | console.log("Syncing stopped"); 20 | console.log(err); 21 | }); 22 | } else { 23 | $scope.sync.cancel(); 24 | } 25 | }; 26 | 27 | $scope.completionChanged = function(task) { 28 | task.completed = !task.completed; 29 | $scope.todos.$update(task); 30 | }; 31 | 32 | $scope.delete = function(task) { 33 | $scope.todos.$remove(task); 34 | }; 35 | 36 | $scope.editTitle = function (task) { 37 | var scope = $scope.$new(true); 38 | scope.data = { response: task.title } ; 39 | $ionicPopup.prompt({ 40 | title: 'Edit task:', 41 | scope: scope, 42 | buttons: [ 43 | { text: 'Cancel', onTap: function(e) { return false; } }, 44 | { 45 | text: 'Save', 46 | type: 'button-positive', 47 | onTap: function(e) { 48 | return scope.data.response 49 | } 50 | }, 51 | ] 52 | }).then(function (newTitle) { 53 | if (newTitle && newTitle != task.title) { 54 | task.title = newTitle; 55 | $scope.todos.$update(task); 56 | } 57 | $ionicListDelegate.closeOptionButtons(); 58 | }); 59 | }; 60 | 61 | // Create our modal 62 | $ionicModal.fromTemplateUrl('new-task.html', function(modal) { 63 | $scope.taskModal = modal; 64 | }, { 65 | scope: $scope 66 | }); 67 | 68 | $scope.createTask = function(task) { 69 | task.completed = false; 70 | $scope.todos.$add(task); 71 | console.log("Added "+task.title+" to todos"); 72 | $scope.taskModal.hide(); 73 | }; 74 | 75 | $scope.newTask = function() { 76 | $scope.taskModal.show(); 77 | }; 78 | 79 | $scope.closeNewTask = function() { 80 | $scope.taskModal.hide(); 81 | }; 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /www/js/ng-pouchdb-collection.js: -------------------------------------------------------------------------------- 1 | angular.module('pouchdb') 2 | 3 | .factory('pouchCollection', ['$timeout', 'pouchdb', function($timeout, pouchdb) { 4 | 5 | /** 6 | * @class item in the collection 7 | * @param item 8 | * @param {int} index position of the item in the collection 9 | * 10 | * @property {String} _id unique identifier for this item within the collection 11 | * @property {int} $index position of the item in the collection 12 | */ 13 | function PouchDbItem(item, index) { 14 | this.$index = index; 15 | angular.extend(this, item); 16 | } 17 | 18 | /** 19 | * create a pouchCollection 20 | * @param {String} collectionUrl The pouchdb url where the collection lives 21 | * @return {Array} An array that will hold the items in the collection 22 | */ 23 | return function(collectionUrl) { 24 | var collection = []; 25 | var indexes = {}; 26 | var db = collection.$db = pouchdb.create(collectionUrl); 27 | 28 | function getIndex(prevId) { 29 | return prevId ? indexes[prevId] + 1 : 0; 30 | } 31 | 32 | function addChild(index, item) { 33 | indexes[item._id] = index; 34 | collection.splice(index,0,item); 35 | console.log('added: ', index, item); 36 | } 37 | 38 | function removeChild(id) { 39 | var index = indexes[id]; 40 | 41 | // Remove the item from the collection 42 | collection.splice(index, 1); 43 | indexes[id] = undefined; 44 | 45 | console.log('removed: ', id); 46 | } 47 | 48 | function updateChild (index, item) { 49 | collection[index] = item; 50 | console.log('changed: ', index, item); 51 | } 52 | 53 | function moveChild (from, to, item) { 54 | collection.splice(from, 1); 55 | collection.splice(to, 0, item); 56 | updateIndexes(from, to); 57 | console.log('moved: ', from, ' -> ', to, item); 58 | } 59 | 60 | function updateIndexes(from, to) { 61 | var length = collection.length; 62 | to = to || length; 63 | if ( to > length ) { to = length; } 64 | for(index = from; index < to; index++) { 65 | var item = collection[index]; 66 | item.$index = indexes[item._id] = index; 67 | } 68 | } 69 | 70 | db.changes({live: true, onChange: function(change) { 71 | if (!change.deleted) { 72 | db.get(change.id).then(function (data){ 73 | if (indexes[change.id]==undefined) { // CREATE / READ 74 | addChild(collection.length, new PouchDbItem(data, collection.length)); // Add to end 75 | updateIndexes(0); 76 | } else { // UPDATE 77 | var index = indexes[change.id]; 78 | var item = new PouchDbItem(data, index); 79 | updateChild(index, item); 80 | } 81 | }); 82 | } else { //DELETE 83 | removeChild(change.id); 84 | updateIndexes(indexes[change.id]); 85 | } 86 | }}); 87 | 88 | collection.$add = function(item) { 89 | db.post(angular.copy(item)).then( 90 | function(res) { 91 | item._rev = res.rev; 92 | item._id = res.id; 93 | } 94 | ); 95 | }; 96 | collection.$remove = function(itemOrId) { 97 | var item = angular.isString(itemOrId) ? collection[itemOrId] : itemOrId; 98 | db.remove(item) 99 | }; 100 | 101 | collection.$update = function(itemOrId) { 102 | var item = angular.isString(itemOrId) ? collection[itemOrId] : itemOrId; 103 | var copy = {}; 104 | angular.forEach(item, function(value, key) { 105 | if (key.indexOf('$') !== 0) { 106 | copy[key] = value; 107 | } 108 | }); 109 | db.get(item._id).then( 110 | function (res) { 111 | db.put(copy, res._rev); 112 | } 113 | ); 114 | }; 115 | 116 | return collection; 117 | }; 118 | }]); 119 | -------------------------------------------------------------------------------- /www/js/v1.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | .controller('TodoCtrl', function($scope) { 3 | // Initialize tasks 4 | $scope.tasks = 5 | [ 6 | {title: "First", completed: true}, 7 | {title: "Second", completed: false}, 8 | {title: "Third", completed: false}, 9 | ]; 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /www/js/v2.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | .controller('TodoCtrl', function($scope) { 3 | // Initialize tasks 4 | $scope.tasks = 5 | [ 6 | {title: "First", completed: true}, 7 | {title: "Second", completed: false}, 8 | {title: "Third", completed: false}, 9 | ]; 10 | 11 | //////////////////////// 12 | // TOGGLE TASK COMPLETED 13 | //////////////////////// 14 | $scope.completionChanged = function(task) { 15 | task.completed = !task.completed; 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /www/js/v3.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | .controller('TodoCtrl', function($scope, $ionicModal) { 3 | // Initialize tasks 4 | //////////////////////// 5 | // tasks starts empty 6 | //////////////////////// 7 | $scope.tasks = []; 8 | 9 | $scope.completionChanged = function(task) { 10 | task.completed = !task.completed; 11 | }; 12 | 13 | //////////////////////// 14 | // Use $ionicModal to put 'taskModal' in $scope 15 | //////////////////////// 16 | $ionicModal.fromTemplateUrl('new-task.html', function(modal) { 17 | $scope.taskModal = modal; 18 | }, { 19 | scope: $scope 20 | }); 21 | 22 | //////////////////////// 23 | // push new task onto tasks array 24 | //////////////////////// 25 | $scope.createTask = function(task) { 26 | task.completed = false; 27 | $scope.tasks.push(angular.copy(task)); 28 | task.title = ""; 29 | $scope.taskModal.hide(); 30 | }; 31 | 32 | //////////////////////// 33 | // show taskModal for newTask 34 | //////////////////////// 35 | $scope.newTask = function() { 36 | $scope.taskModal.show(); 37 | }; 38 | 39 | //////////////////////// 40 | // hide taskModal to closeNewTask 41 | //////////////////////// 42 | $scope.closeNewTask = function() { 43 | $scope.taskModal.hide(); 44 | }; 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /www/js/v4.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | //////////////////////// 3 | // Simple PouchDB synchronization factory 4 | //////////////////////// 5 | .factory('todoDb', function() { 6 | var db = new PouchDB('todos'); 7 | return db; 8 | }) 9 | .controller('TodoCtrl', function($scope, $ionicModal, todoDb) { // inject todoDb factory 10 | // Initialize tasks 11 | $scope.tasks = []; 12 | 13 | $scope.completionChanged = function(task) { 14 | task.completed = !task.completed; 15 | this.update(task); 16 | }; 17 | 18 | //////////////////////// 19 | // http://pouchdb.com/api.html#changes 20 | // list of changes to docs in todoDb 21 | // modify ng-model accordingly 22 | //////////////////////// 23 | todoDb.changes({ 24 | live: true, 25 | onChange: function (change) { 26 | if (!change.deleted) { // IF THE CHANGE IS A DELETE, IGNORE 27 | // GET THE doc (a task) from the change.id 28 | todoDb.get(change.id, function(err, doc) { 29 | if (err) console.log(err); //////////// 30 | $scope.$apply(function() { // UPDATE // 31 | // INEFFICIENTLY FIND task THAT HAS CHANGED 32 | for (var i = 0; i < $scope.tasks.length; i++) { 33 | if ($scope.tasks[i]._id === doc._id) { 34 | // REPLACE THE TASK WITH FETCHED doc 35 | $scope.tasks[i] = doc; 36 | return; 37 | } ////////////////////////////////////////// 38 | } // IF UNIQUE doc._id NOT FOUND ADD task // 39 | $scope.tasks.push(doc); 40 | }); 41 | }) 42 | } 43 | } 44 | }); 45 | 46 | //////////////////////// 47 | // UPDATE task IN POUCHDB 48 | //////////////////////// 49 | $scope.update = function (task) { 50 | todoDb.get(task._id, function (err, doc) { 51 | if (err) { 52 | console.log(err); 53 | } else { 54 | todoDb.put(angular.copy(task), doc._rev, function (err, res) { 55 | if (err) console.log(err); 56 | }); 57 | } 58 | }); 59 | }; 60 | 61 | // Create our modal 62 | $ionicModal.fromTemplateUrl('new-task.html', function(modal) { 63 | $scope.taskModal = modal; 64 | }, { 65 | scope: $scope 66 | }); 67 | 68 | $scope.createTask = function(task) { 69 | task.completed = false; 70 | //////////////////////// 71 | // CREATE task IN POUCHDB 72 | //////////////////////// 73 | todoDb.post(angular.copy(task), function(err, res) { 74 | if (err) console.log(err) 75 | task.title = ""; 76 | }); 77 | $scope.taskModal.hide(); 78 | }; 79 | 80 | $scope.newTask = function() { 81 | $scope.taskModal.show(); 82 | }; 83 | 84 | $scope.closeNewTask = function() { 85 | $scope.taskModal.hide(); 86 | }; 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /www/js/v5.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | // Simple PouchDB factory 3 | .factory('todoDb', function() { 4 | var db = new PouchDB('todos'); 5 | return db; 6 | }) // inject ionicPopup & ionicListDelegate 7 | .controller('TodoCtrl', function($scope, $ionicModal, todoDb, $ionicPopup, $ionicListDelegate) { 8 | // Initialize tasks 9 | $scope.tasks = []; 10 | 11 | $scope.completionChanged = function(task) { 12 | task.completed = !task.completed; 13 | $scope.update(task); 14 | }; 15 | 16 | // list changes to PouchDb database 17 | todoDb.changes({ 18 | live: true, 19 | onChange: function (change) { 20 | if (!change.deleted) { 21 | todoDb.get(change.id, function(err, doc) { 22 | if (err) console.log(err); 23 | $scope.$apply(function() { //UPDATE 24 | for (var i = 0; i < $scope.tasks.length; i++) { 25 | if ($scope.tasks[i]._id === doc._id) { 26 | $scope.tasks[i] = doc; 27 | return; 28 | } 29 | } // CREATE / READ 30 | $scope.tasks.push(doc); 31 | }); 32 | }) ////////////////////// 33 | } else { // if change.delete // 34 | $scope.$apply(function () { 35 | for (var i = 0; i<$scope.tasks.length; i++) { 36 | if ($scope.tasks[i]._id === change.id) { 37 | $scope.tasks.splice(i,1); 38 | } 39 | } 40 | }) 41 | } 42 | } 43 | }); 44 | 45 | $scope.update = function (task) { 46 | todoDb.get(task._id, function (err, doc) { 47 | if (err) { 48 | console.log(err); 49 | } else { 50 | todoDb.put(angular.copy(task), doc._rev, function (err, res) { 51 | if (err) console.log(err); 52 | }); 53 | } 54 | }); 55 | }; 56 | 57 | //////////////////////// 58 | // DELETE task IN POUCHDB 59 | //////////////////////// 60 | $scope.delete = function(task) { 61 | todoDb.get(task._id, function (err, doc) { 62 | todoDb.remove(doc, function (err, res) {}); 63 | }); 64 | }; 65 | 66 | //////////////////////// 67 | // EDIT task.title with ionicPopup 68 | //////////////////////// 69 | $scope.editTitle = function (task) { 70 | var scope = $scope.$new(true); 71 | scope.data = {response: task.title }; 72 | $ionicPopup.prompt({ 73 | title: 'Edit task:', 74 | scope: scope, 75 | buttons: [ 76 | { text: 'Cancel', onTap: function(e) { return false; } }, 77 | { 78 | text: 'Save', 79 | type: 'button-positive', 80 | onTap: function(e) { 81 | return scope.data.response 82 | } 83 | }, 84 | ] 85 | }).then(function (newTitle) { 86 | if (newTitle && newTitle != task.title) { 87 | task.title = newTitle; 88 | $scope.update(task); 89 | } 90 | $ionicListDelegate.closeOptionButtons(); 91 | }); 92 | }; 93 | 94 | // Create our modal 95 | $ionicModal.fromTemplateUrl('new-task.html', function(modal) { 96 | $scope.taskModal = modal; 97 | }, { 98 | scope: $scope 99 | }); 100 | 101 | $scope.createTask = function(task) { 102 | task.completed = false; 103 | todoDb.post(angular.copy(task), function(err, res) { 104 | if (err) console.log(err) 105 | task.title = ""; 106 | }); 107 | $scope.taskModal.hide(); 108 | }; 109 | 110 | $scope.newTask = function() { 111 | $scope.taskModal.show(); 112 | }; 113 | 114 | $scope.closeNewTask = function() { 115 | $scope.taskModal.hide(); 116 | }; 117 | 118 | }); 119 | -------------------------------------------------------------------------------- /www/js/v6.js: -------------------------------------------------------------------------------- 1 | angular.module('todo', ['ionic']) 2 | // Simple PouchDB factory 3 | .factory('todoDb', function() { 4 | var db = new PouchDB('todos'); 5 | return db; 6 | }) 7 | .controller('TodoCtrl', function($scope, $ionicModal, todoDb, $ionicPopup, $ionicListDelegate) { 8 | // Initialize tasks 9 | $scope.tasks = []; 10 | 11 | //////////////////////// 12 | // Online sync to CouchDb 13 | //////////////////////// 14 | $scope.online = false; 15 | $scope.toggleOnline = function() { 16 | $scope.online = !$scope.online; 17 | if ($scope.online) { // Read http://pouchdb.com/api.html#sync 18 | $scope.sync = todoDb.sync('http://127.0.0.1:5984/todos', {live: true}) 19 | .on('error', function (err) { 20 | console.log("Syncing stopped"); 21 | console.log(err); 22 | }); 23 | } else { 24 | $scope.sync.cancel(); 25 | } 26 | }; 27 | 28 | $scope.completionChanged = function(task) { 29 | task.completed = !task.completed; 30 | $scope.update(task); 31 | }; 32 | 33 | todoDb.changes({ 34 | live: true, 35 | onChange: function (change) { 36 | if (!change.deleted) { 37 | todoDb.get(change.id, function(err, doc) { 38 | if (err) console.log(err); 39 | $scope.$apply(function() { //UPDATE 40 | for (var i = 0; i < $scope.tasks.length; i++) { 41 | if ($scope.tasks[i]._id === doc._id) { 42 | $scope.tasks[i] = doc; 43 | return; 44 | } 45 | } // CREATE / READ 46 | $scope.tasks.push(doc); 47 | }); 48 | }) 49 | } else { //DELETE 50 | $scope.$apply(function () { 51 | for (var i = 0; i<$scope.tasks.length; i++) { 52 | if ($scope.tasks[i]._id === change.id) { 53 | $scope.tasks.splice(i,1); 54 | } 55 | } 56 | }) 57 | } 58 | } 59 | }); 60 | 61 | $scope.update = function (task) { 62 | todoDb.get(task._id, function (err, doc) { 63 | if (err) { 64 | console.log(err); 65 | } else { 66 | todoDb.put(angular.copy(task), doc._rev, function (err, res) { 67 | if (err) console.log(err); 68 | }); 69 | } 70 | }); 71 | }; 72 | 73 | $scope.delete = function(task) { 74 | todoDb.get(task._id, function (err, doc) { 75 | todoDb.remove(doc, function (err, res) {}); 76 | }); 77 | }; 78 | 79 | $scope.editTitle = function (task) { 80 | var scope = $scope.$new(true); 81 | scope.data = { response: task.title } ; 82 | $ionicPopup.prompt({ 83 | title: 'Edit task:', 84 | scope: scope, 85 | buttons: [ 86 | { text: 'Cancel', onTap: function(e) { return false; } }, 87 | { 88 | text: 'Save', 89 | type: 'button-positive', 90 | onTap: function(e) { 91 | return scope.data.response 92 | } 93 | }, 94 | ] 95 | }).then(function (newTitle) { 96 | if (newTitle && newTitle != task.title) { 97 | task.title = newTitle; 98 | $scope.update(task); 99 | } 100 | $ionicListDelegate.closeOptionButtons(); 101 | }); 102 | }; 103 | 104 | // Create our modal 105 | $ionicModal.fromTemplateUrl('new-task.html', function(modal) { 106 | $scope.taskModal = modal; 107 | }, { 108 | scope: $scope 109 | }); 110 | 111 | $scope.createTask = function(task) { 112 | task.completed = false; 113 | todoDb.post(angular.copy(task), function(err, res) { 114 | if (err) console.log(err) 115 | task.title = ""; 116 | }); 117 | $scope.taskModal.hide(); 118 | }; 119 | 120 | $scope.newTask = function() { 121 | $scope.taskModal.show(); 122 | }; 123 | 124 | $scope.closeNewTask = function() { 125 | $scope.taskModal.hide(); 126 | }; 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /www/res/ios/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzen/ionic-pouchdb-todo/76c8c591ea9565951e6a448940df716b1c0d6182/www/res/ios/icon.png -------------------------------------------------------------------------------- /www/res/screens/ios/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzen/ionic-pouchdb-todo/76c8c591ea9565951e6a448940df716b1c0d6182/www/res/screens/ios/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /www/res/screens/ios/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielzen/ionic-pouchdb-todo/76c8c591ea9565951e6a448940df716b1c0d6182/www/res/screens/ios/Default@2x~iphone.png -------------------------------------------------------------------------------- /www/v1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v1:ToDo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Todos

24 |
25 | 26 | 27 | 29 | {{task.title}} 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /www/v2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v2:ToDo - Toggle 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Todos

24 |
25 | 26 | 27 | 30 | 31 | 32 | 33 | {{task.title}} 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /www/v3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v3:ToDo - New Tasks 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |

Todos

24 | 25 | 26 | 27 | 30 |
31 | 32 | 33 | 36 | {{task.title}} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /www/v4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v4:ToDo - PouchDb 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |

Todos

29 | 32 |
33 | 34 | 35 | 38 | {{task.title}} 39 | 40 | 41 | 42 | 43 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /www/v5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v5:ToDo - Edit/Delete 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

Todos

26 | 29 |
30 | 31 | 32 | 35 | 36 | {{task.title}} 37 | 38 | 39 | 40 | 41 | Edit 42 | 43 | 44 | Delete 45 | 46 | 47 | 48 | 49 | 50 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /www/v6.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | v6:ToDo - online 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |

Todos

26 | 27 | 28 | 29 | 32 | 35 |
36 | 37 | 38 | 41 | 42 | {{task.title}} 43 | 44 | Edit 45 | 46 | 47 | Delete 48 | 49 | 50 | 51 | 52 | 53 | 78 | 79 | 80 | 81 | --------------------------------------------------------------------------------