diff --git a/public/javascripts/app.js b/public/javascripts/app.js index e03d150..7ffa76f 100644 --- a/public/javascripts/app.js +++ b/public/javascripts/app.js @@ -5,7 +5,8 @@ require.config({ jquery: '../bower_components/jquery/dist/jquery', lodash: "../bower_components/lodash/dist/lodash.min", backbone: '../bower_components/backbone/backbone', - localstorage: '../bower_components/backbone.localStorage/backbone.localStorage', + localforage: '../bower_components/localforage/dist/localforage', + localforagebackbone: '../bower_components/localforage-backbone/dist/localforage.backbone', modernizr: "vendor/custom.modernizr", socket: "../bower_components/socket.io-client/socket.io", text: '../bower_components/text/text', diff --git a/public/javascripts/collections/list.js b/public/javascripts/collections/list.js index 0dde463..1fd7ba4 100644 --- a/public/javascripts/collections/list.js +++ b/public/javascripts/collections/list.js @@ -2,19 +2,30 @@ define( [ 'backbone', 'models/task', -'localstorage', +'localforage', +'localforagebackbone' ], function( Backbone, Task, -LocalStorage +localforage, +localforageBackbone ) { var List = Backbone.Collection.extend({ - localStorage: new Backbone.LocalStorage("tasks"), + model: Task, + offlineSync: Backbone.localforage.sync("tasks"), + /** switches sync between server and local databases **/ + sync: function(){ + if (window.hackflowyOffline) + return this.offlineSync.apply(this, arguments); + else + return Backbone.sync.apply(this, arguments); + }, + url: '/tasks' }); diff --git a/public/javascripts/data/demo.js b/public/javascripts/data/demo.js new file mode 100644 index 0000000..bb8aac6 --- /dev/null +++ b/public/javascripts/data/demo.js @@ -0,0 +1,115 @@ + define(function (require) { + return [{ + "id": 80, + "content": "Welcome to HackFlowy!", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 81, + "content": "An open-source WorkFlowy clone", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 82, + "content": "Built using Backbone + Socket.IO", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 83, + "content": "Desyncr pulled this together in a few hours to learn Backbone", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 84, + "content": "Feel free to try it out and hack on it", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 85, + "content": "Good Luck!", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:30.858Z", + "updatedAt": "2016-01-29T05:44:30.858Z" + }, { + "id": 86, + "content": "P.S", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:40.978Z", + "updatedAt": "2016-01-29T05:44:40.978Z" + }, { + "id": 88, + "content": "It makes sense if you don't think about it; I haven't", + "parentId": 0, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:44:58.737Z", + "updatedAt": "2016-01-29T05:45:50.939Z" + }, { + "id": 89, + "content": "Make love not war", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:45:03.048Z", + "updatedAt": "2016-01-29T05:45:57.481Z" + }, { + "id": 91, + "content": "Love can be brought not sold", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:45:32.331Z", + "updatedAt": "2016-01-29T05:46:10.478Z" + }, { + "id": 93, + "content": "How do I love thee? Let me count the ways - Shakespeare", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:46:25.119Z", + "updatedAt": "2016-01-29T05:48:00.604Z" + }, { + "id": 95, + "content": "Therefore: love can be listed and lists can be loved", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:46:38.998Z", + "updatedAt": "2016-01-29T05:48:22.937Z" + }, { + "id": 96, + "content": "Conclusion: lists and love should be free", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:47:26.684Z", + "updatedAt": "2016-01-29T05:48:29.796Z" + }, { + "id": 97, + "content": "But how can our lists be real if our love isnt? - Jaden Smith", + "parentId": 88, + "isCompleted": false, + "priority": 0, + "createdAt": "2016-01-29T05:47:46.930Z", + "updatedAt": "2016-01-29T05:47:46.930Z" + }]; + }); diff --git a/public/javascripts/models/task.js b/public/javascripts/models/task.js index 4d7564c..0f0d318 100644 --- a/public/javascripts/models/task.js +++ b/public/javascripts/models/task.js @@ -1,13 +1,26 @@ define( -['backbone' +['backbone', +'localforage', +'localforagebackbone' ], function( -Backbone +Backbone, +localforage, +localforageBackbone ) { var TaskModel = Backbone.Model.extend({ + offlineSync: Backbone.localforage.sync('TaskModel'), + /** switches sync between server and local databases **/ + sync: function(){ + if (window.hackflowyOffline) + return this.offlineSync.apply(this,arguments); + else + return Backbone.sync.apply(this, arguments); + }, + defaults: { parentId: 0, content: '', @@ -25,7 +38,7 @@ Backbone //REVERT BACK ON ERROR self.set({'isCompleted':prev_isCompleted}); } - }) + }); } }); diff --git a/public/javascripts/views/list.js b/public/javascripts/views/list.js index c9de32c..0f02a72 100644 --- a/public/javascripts/views/list.js +++ b/public/javascripts/views/list.js @@ -27,32 +27,35 @@ define( this.listenTo(this.collection, 'add', this.renderTask); - this.collection.fetch() - .fail(function (e) { - // if the server isn't running so load some demo data and a demo warning - $('#header').append('
Warning: Running in demo mode, all work will be lost
'); - for (var i = 0; i < demoData.length; i++) { - Tasks.add(demoData[i]); - } - }) - .always(function(data){ - console.log({data:data}); - if (data.length===0){ - // if the server isn't running so load some demo data and a demo warning - $('#header').append('
Warning: Running in demo mode, work will be lost
'); - for (var i = 0; i < demoData.length; i++) { - var task = Tasks.add(demoData[i]); - task.save(); - } - } - }); + /** Load demo data and warn users **/ + function loadDemoData() { + for (var i = 0; i < demoData.length; i++) { + var task = Tasks.add(demoData[i]); + task.save(); + } + } + + function success(data) { + // load demo data if the server returns nothing + if (data.length === 0) + loadDemoData(); + } + + this.collection.fetch({ + success: success, + error: function () { + // switch to localforage database if server isn't present + window.hackflowyOffline=true; + $('#header').append('
Running in offline mode, data may be lost
'); + Tasks.fetch({ + success: success + }); + } + }); }, - render: function (data) { - for (var i = 0; i < data.length; i++) { - Tasks.add(data[i]); - } + render: function () { this.collection.each(function (task) { this.renderTask(task); }, this); @@ -66,10 +69,13 @@ define( if (a.model.get('parentId') === 0) { this.$el.append(a.el); } else { - var parent = $('*[data-id="' + a.model.get('parentId')+ '"]'); - if (parent.length===0) { + var parent = $('*[data-id="' + a.model.get('parentId') + '"]'); + if (parent.length === 0) { // TODO deal with loading order - console.error("Parent not rendered yet: ", {selector: parent.selector, task: task}); + console.error("Parent not rendered yet: ", { + selector: parent.selector, + task: task + }); this.$el.append(a.el); } else { a.$el.insertBefore(parent.parents('li:first')); diff --git a/public/javascripts/views/task.js b/public/javascripts/views/task.js index 3e7caf9..84b0e20 100644 --- a/public/javascripts/views/task.js +++ b/public/javascripts/views/task.js @@ -127,6 +127,7 @@ define( var value = this.$input.val().trim(); if (value === '') { this.model.destroy(); + // collection.at(this.model.get('id')).destroy(); } else { this.model.save({ content: value,