diff --git a/public/javascripts/app.js b/public/javascripts/app.js
index 11a1f9b..e54bf07 100644
--- a/public/javascripts/app.js
+++ b/public/javascripts/app.js
@@ -28,5 +28,5 @@ require([
'views/page'
],
function (App) {
- new App();
+ pageView = new App();
});
diff --git a/public/javascripts/collections/list.js b/public/javascripts/collections/list.js
index 45b0330..0e6cdd6 100644
--- a/public/javascripts/collections/list.js
+++ b/public/javascripts/collections/list.js
@@ -24,20 +24,30 @@ localforageBackbone
this.on('add remove', this.updateModelPriority);
},
+ /**
+ * Move a model in the list up. Only a sort event is emitted
+ * @param {Backbone.Model} model - Model to be moved
+ */
moveUp: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index > 0) {
- this.remove(model, {silent: true}); // silence this to stop excess event triggers
- this.add(model, {at: index-1});
+ this.remove(model, {silent: true});
+ this.add(model, {at: index-1, silent: true});
}
+ this.trigger('sort', this, {});
},
+ /**
+ * Move a model in the list up. Only a sort event is emitted
+ * @param {Backbone.Model} model - Model to be moved
+ */
moveDown: function(model) { // I see move up as the -1
var index = this.indexOf(model);
if (index < this.models.length) {
- this.remove(model, {silent: true}); // silence this to stop excess event triggers
- this.add(model, {at: index+1});
+ this.remove(model, {silent: true});
+ this.add(model, {at: index+1, silent: true});
}
+ this.trigger('sort', this, {});
},
/** Updated priority of each member of list **/
diff --git a/public/javascripts/views/list.js b/public/javascripts/views/list.js
index a6f3499..82f4125 100644
--- a/public/javascripts/views/list.js
+++ b/public/javascripts/views/list.js
@@ -3,7 +3,6 @@ define(
'backbone',
'collections/list',
'views/task',
- 'data/demo',
'text!../../templates/task.html',
'marionette'
],
@@ -13,7 +12,6 @@ define(
Backbone,
List,
TaskView,
- demoData,
listTemplate,
Marionette
) {
@@ -27,59 +25,53 @@ define(
template: _.template(listTemplate),
events: {
- 'click #add': 'addTask'
+ 'click #add': 'addTask',
},
initialize: function () {
- var self = this;
-
- // this wholeCollection holds all items
- this.wholeCollection = Tasks = new List();
- //this.collection = new List();
-
- // custom events
- this.listenTo(this, 'childview:rerender', this.render);
- // this.listenTo(this.collection, 'add remove', this.render);
-
- /** Load demo data **/
- function loadDemoData() {
- for (var i = 0; i < demoData.length; i++) {
- var task = Tasks.add(demoData[i]);
- task.save();
- }
- }
-
- function success(children, data, promise) {
- // load demo data if the server returns nothing
- var directChildren = children.filter(this.filterDirectChildren);
- if (directChildren.length === 0)
- loadDemoData();
- this.collection = new List(Tasks.filter(this.filterDirectChildren));
- this.render();
- }
-
- Tasks.fetch({
- success: success,
- error: function () {
-
- // switch to localforage database if server isn't present and fetch again
- // from there
- window.hackflowyOffline = true;
- $('#header').append('
Running in offline mode, data may be lost
');
- Tasks.fetch({
- success: success,
- context: this
- });
- },
- context: this
- });
-
},
+
+ /** Get root id for the displayed list from the url hash or 0 **/
+ // getRootId: function(){
+ // var hash = window.location.hash.slice(1);
+ // return hash ? hash: 0;
+ //
+ // },
+
+ // /** Called when window location hash changes **/
+ // changeRootId: function(){
+ // this.collection.remove(this.collection.models);
+ // this.collection.add(pageView.collection.filter(this.filterDirectChildren,this));
+ // this.updateBreadCrumbs();
+ // },
+
+ // updateBreadCrumbs: function(){
+ // var rootId = this.getRootId();
+ // if (rootId){
+ // var current = Tasks.get(rootId);
+ // var breadCrumbs = _.template('
<%= content %>')(current.attributes);
+ // var depth=0;
+ // while (current.get('parentId') && depth<100){
+ // depth++;
+ // current = Tasks.get(current.get('parentId'));
+ // breadCrumbs=_.template('
<%= content %> > ')(current.attributes)+breadCrumbs;
+ // }
+ // if (depth>=100) console.error('Max depth exceeded while making breadCrumbs');
+ // breadCrumbs='
Home > '+breadCrumbs;
+ // $('#task-breadcrumbs').append(breadCrumbs);
+ //
+ // }
+ // },
+
// Only show direct children
- filterDirectChildren: function (child, index, collection) {
- return child.get('parentId') === 0;
- },
+ // filterDirectChildren: function (child, index, collection) {
+ // var rootId = this.getRootId();
+ // if (rootId)
+ // return child.get('id') == rootId;
+ // else
+ // return child.get('parentId') == rootId;
+ // },
/** This is the root view in the tree **/
getParentView: function () {
@@ -88,8 +80,9 @@ define(
/** Update parentId when added to collection **/
onAddChild: function(childView){
- if (childView.model.get('parentId')!==0)
- childView.model.save({parentId: 0});
+ var rootId = pageView.getRootId();
+ if (childView.model.get('parentId')!=rootId && childView.model==rootId)
+ childView.model.save({parentId: rootId});
},
});
diff --git a/public/javascripts/views/page.js b/public/javascripts/views/page.js
index b3b7c8e..3e9a0b5 100644
--- a/public/javascripts/views/page.js
+++ b/public/javascripts/views/page.js
@@ -1,41 +1,130 @@
define(
['jquery',
'backbone',
+'marionette',
'views/list',
-'models/task'
+'data/demo',
+'models/task',
+'collections/list'
],
function(
$,
Backbone,
+Marionette,
ListView,
-Task
+demoData,
+Task,
+List
) {
- var PageView = Backbone.View.extend({
+ var PageView = Marionette.View.extend({
- el: $("#main"),
+ el: $("#hackflowy"),
events: {
- 'keypress #newTask': 'createNewTask',
- 'blur #newTask': 'createNewTask'
},
initialize: function() {
- listView = this.listView = new ListView();
- this.input = $('#newTask');
+
+ // this wholeCollection holds all items
+ this.collection = new List();
+
+
+ function success(children, data, promise) {
+ // load demo data if the server returns nothing
+ var directChildren = children.filter(this.filterDirectChildren,this);
+ if (directChildren.length === 0){
+ for (var i = 0; i < demoData.length; i++) {
+ var task = this.collection.add(demoData[i]);
+ task.save();
+ }
+ }
+ this.listView.collection.add(children.filter(this.filterDirectChildren,this));
+ this.listView.render();
+ this.updateBreadCrumbs();
+ }
+
+ this.collection.fetch({
+ success: success,
+ error: function () {
+ // switch to localforage database if server isn't present and fetch again
+ // from there
+ window.hackflowyOffline = true;
+ this.$('#header-alerts').append('
Running in offline mode, data may be lost
');
+ this.collection.fetch({
+ success: success,
+ context: this
+ });
+ },
+ context: this
+ });
+
+ var rootItems = new List(this.collection.filter(this.filterDirectChildren,this));
+ this.listView = new ListView({collection: rootItems});
+
+
+ // change root item when the url hash changes
+ // HACK should ideally use http://stackoverflow.com/a/19114496/221742
+ $(window).on("hashchange", this.changeRootId.bind(this));
// stop browser going back a page when jamming backspace
- window.addEventListener('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}},true);
+ $(window).on('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}});
+
},
- createNewTask: function(e) {
- if (e.keyCode != 13) return;
- if (!this.input.val().trim()) return;
- this.listView.collection.add(new Task({content: this.input.val().trim() }));
- this.input.val('');
- }
+ /** remove non backbone listener on delete **/
+ onDestroy: function() {
+ $(window).off("haschange",this.changeRootId);
+ $(window).off('keydown',function(e){if(e.keyIdentifier=='U+0008'||e.keyIdentifier=='Backspace'){if(e.target==document.body){e.preventDefault();}}});
+ },
+
+ /** Get root id for the displayed list from the url hash or 0 **/
+ getRootId: function(){
+ var hash = window.location.hash.slice(1);
+ if (hash && pageView.collection.get(hash))
+ return hash;
+ else if (hash)
+ window.location.hash='';
+ return 0;
+ },
+
+ /** Called when window location hash changes **/
+ changeRootId: function(){
+ // change the listview children
+ this.listView.collection.remove(this.collection.models);
+ this.listView.collection.add(this.collection.filter(this.filterDirectChildren,this));
+ this.updateBreadCrumbs();
+ },
+
+ // Only show direct children
+ filterDirectChildren: function (child, index, collection) {
+ var rootId = this.getRootId();
+ if (rootId)
+ return child.get('id') == rootId;
+ else
+ return child.get('parentId') == rootId;
+ },
+
+ updateBreadCrumbs: function(){
+ var rootId = this.getRootId();
+ if (rootId){
+ this.$('#task-breadcrumbs').empty();
+ var current = this.collection.get(rootId);
+ var breadCrumbs = '';//_.template('
<%= content %>')(current.attributes);
+ var depth=0;
+ while (current.get('parentId') && depth<100){
+ depth++;
+ current = this.collection.get(current.get('parentId'));
+ breadCrumbs=_.template('
<%= content %> > ')(current.attributes)+breadCrumbs;
+ }
+ if (depth>=100) console.error('Max depth exceeded while making breadCrumbs');
+ breadCrumbs='
Home > '+breadCrumbs;
+ this.$('#task-breadcrumbs').append(breadCrumbs);
+
+ }
+ },
});
diff --git a/public/javascripts/views/task.js b/public/javascripts/views/task.js
index 68161bf..a08689e 100644
--- a/public/javascripts/views/task.js
+++ b/public/javascripts/views/task.js
@@ -22,7 +22,8 @@ define(
template: _.template(taskTemplate), //'#task-view-template',
tagName: 'ul',
- className: "shift",
+ className: "task-view",
+ viewComparator: List.prototype.comporator,
childView: TaskView,
childViewContainer: '.children',
childViewOptions: {
@@ -50,11 +51,14 @@ define(
'click .complete:first': 'markComplete',
'click .uncomplete:first': 'unmarkComlete',
'click .note:first': 'addNote',
- 'click .mouse-tip:first': 'foldChildren',
+ 'click .fold-button:first': 'foldChildren',
+ // custom events
+ 'focus': 'this.model.focusOnView',
},
- collectionEvents: {},
+ collectionEvents: {
+ },
childEvents: {},
@@ -69,19 +73,23 @@ define(
// backlink
this.model.view = this;
- var children = Tasks.filter(
+ var children = pageView.collection.filter(
function (child, index, collection) {
return child.get('parentId') === this.model.id;
}, this);
this.collection = new List(children);
+ // there is probobly a better way to do this
+ if (this.isEmpty()){
+ this.$el.addClass('empty');
+ }
+
// events
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
// refresh ui hashes after children are rendered
this.listenTo(this, 'render:collection', this.bindUIElements);
// custom event
- this.listenTo(this, 'childview:rerender', this.render);
this.listenTo(this, 'focus', this.model.focusOnView);
// updates from server
@@ -113,9 +121,9 @@ define(
/** Get the parent view or root view **/
getParentView: function () {
- var parent = Tasks.get(this.model.get('parentId'));
+ var parent = pageView.collection.get(this.model.get('parentId'));
if (parent) return parent.view;
- else return listView;
+ else return pageView.listView;
},
/** Update parentId when added to collection **/
@@ -126,7 +134,7 @@ define(
/** Focus on the next visible element down despite list level **/
focusOnPrev: function () {
- var all = listView.$el.find('ul:visible');
+ var all = pageView.listView.$el.find('ul:visible');
var prev = $(all[all.index(this.$el) - 1]);
if (prev.length){
prev.find('input:first').focus();
@@ -135,7 +143,7 @@ define(
},
focusOnNext: function () {
- var all = listView.$el.find('ul:visible');
+ var all = pageView.listView.$el.find('ul:visible');
var next = $(all[all.index(this.$el) + 1]);
if (next)
next.find('input:first').focus();
@@ -169,13 +177,11 @@ define(
// move down list by swapping priority with next sibling
e.preventDefault();
this.getParentView().collection.moveDown(this.model);
- this.trigger('rerender');
this.model.focusOnView();
} else if (e.ctrlKey && e.keyCode == constants.UP_ARROW) {
// move up the list
e.preventDefault();
this.getParentView().collection.moveUp(this.model);
- this.trigger('rerender');
this.model.focusOnView();
} else if (e.keyCode == constants.DOWN_ARROW) {
this.focusOnNext();
@@ -188,7 +194,7 @@ define(
if (parentId===0){
} else if (parentId){
- parentModel = Tasks.get(parentId);
+ parentModel = pageView.collection.get(parentId);
this.getParentView().collection.remove(this.model);
var index = parentModel.view.getParentView().collection.indexOf(parentModel);
if (index<0) index=this.getParentView().collection.length-1;
@@ -203,7 +209,7 @@ define(
var prevSibling = this.$el.prev('ul');
if (prevSibling.length) {
var prevSibId = prevSibling.find('input:first').data('id');
- var prevSibView =Tasks.get(prevSibId).view;
+ var prevSibView =pageView.collection.get(prevSibId).view;
this.getParentView().collection.remove(this.model);
prevSibView.collection.add(this.model);
this.model.focusOnView();
@@ -269,7 +275,7 @@ define(
var index= this.getParentView().collection.indexOf(this.model);
- var task = Tasks.add({parentId: this.model.get('parentId')});
+ var task = pageView.collection.add({parentId: this.model.get('parentId')});
task.save();
if (index>=0)
this.getParentView().collection.add(task, {at:index+1});
diff --git a/public/sass/app.scss b/public/sass/app.scss
index d2f4bec..df57299 100644
--- a/public/sass/app.scss
+++ b/public/sass/app.scss
@@ -162,6 +162,9 @@ body {
li.shift9 {
margin-left: 270px;
}
+ ul.shift {
+ margin-bottom: 0px;
+ }
}
}
#main > .children {
diff --git a/public/stylesheets/app.css b/public/stylesheets/app.css
index 5dc4700..351b4ca 100644
--- a/public/stylesheets/app.css
+++ b/public/stylesheets/app.css
@@ -5681,9 +5681,6 @@ body #hackflowy #main .children li.shift8 {
body #hackflowy #main .children li.shift9 {
margin-left: 270px;
}
-body #hackflowy #main .children ul.shift {
- margin-bottom: 0px;
-}
/* line 167, ../sass/app.scss */
body #hackflowy #main > .children {
margin-left: 0;
@@ -5695,54 +5692,58 @@ body #hackflowy #footer {
font-size: 12px;
}
+body #hackflowy #main .children ul.task-view {
+ margin-bottom: 0px;
+ margin-left: 0px;
+}
.options-div{
display: none;
}
.options{
- position: absolute;
-z-index: 1000;
- color: rgb(51, 51, 51);
-cursor: auto;
-display: none;
-font-family: 'Helvetica Neue', Arial, sans-serif;
-font-size: 16px;
-font-weight: normal;
-height: 0px;
-line-height: 16px;
-width: 675px;
+ position: absolute;
+ z-index: 1000;
+ color: rgb(51, 51, 51);
+ cursor: auto;
+ display: none;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ font-size: 16px;
+ font-weight: normal;
+ height: 0px;
+ line-height: 16px;
+ width: 675px;
}
.options a {
-display: inline-block;
-width: 20px;
-height: 20px;
-cursor: pointer;
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ cursor: pointer;
}
.options-left {
-background: #ddd;
-border: 1px solid #bbb;
-width: 80px;
-min-height: 58px;
-padding: 10px 0 5px 10px;
-margin-bottom: 10px;
-border-radius: 4px;
-font-family: 'Helvetica Neue', Arial, Sans-serif;
-z-index: 8;
-margin-top: 8px;
-margin-left: -29px;
+ background: #ddd;
+ border: 1px solid #bbb;
+ width: 80px;
+ min-height: 58px;
+ padding: 10px 0 5px 10px;
+ margin-bottom: 10px;
+ border-radius: 4px;
+ font-family: 'Helvetica Neue', Arial, Sans-serif;
+ z-index: 8;
+ margin-top: 8px;
+ margin-left: -29px;
}
.options-left a {
-color: #555;
-margin-bottom: 5px;
-font-size: 12px;
-display: block;
-margin-right: 10px;
-width: auto;
+ color: #555;
+ margin-bottom: 5px;
+ font-size: 12px;
+ display: block;
+ margin-right: 10px;
+ width: auto;
}
.options-left a:hover {
@@ -5750,42 +5751,86 @@ width: auto;
}
.options-left > .mouse-tip {
-content: '';
-position: absolute;
-left: 0;
-height: 25px;
-width: 90px;
-margin-top: -36px;
-margin-left: -1%;
+ content: '';
+ position: absolute;
+ left: 0;
+ height: 25px;
+ width: 90px;
+ margin-top: -36px;
+ margin-left: -1%;
}
.options-left > .mouse-tip:before {
-content: '';
-position: absolute;
-top: 10px;
-left: 19%;
-margin-left: -15px;
-height: 0;
-width: 0;
-border-bottom: 15px solid #ddd;
-border-left: 15px solid transparent;
-border-right: 15px solid transparent;
+ content: '';
+ position: absolute;
+ top: 10px;
+ left: 19%;
+ margin-left: -15px;
+ height: 0;
+ width: 0;
+ border-bottom: 15px solid #ddd;
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
}
.mouse-tip:before {
-content: '';
-position: absolute;
-top: 10px;
-left: 50%;
-margin-left: -15px;
-height: 0;
-width: 0;
-border-bottom: 15px solid #ddd;
-border-left: 15px solid transparent;
-border-right: 15px solid transparent;
+ content: '';
+ position: absolute;
+ top: 10px;
+ left: 50%;
+ margin-left: -15px;
+ height: 0;
+ width: 0;
+ border-bottom: 15px solid #ddd;
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
}
+.options-left > .fold-button {
+ position: absolute;
+ left: 0;
+ /*height: 25px;*/
+ /*width: 90px;*/
+ margin-top: -36px;
+ margin-left: -3%;
+ font-size: 1.5em;
+}
+.options-left > .fold-button:hover {
+ text-decoration: none;
+}
+
+/* By default show fold button */
+.options-left>.fold-button:before {
+ content: "-";
+}
+/* no fold button on empty nodes */
+.empty .options-left:first-child > .fold-button:before {
+ content: '';
+}
+/* expand button on folded nodes */
+.folded .options-left:first-child>.fold-button:before {
+ content: "+";
+}
.task-completed{
text-decoration: line-through;
}
+
+.subtle-link {
+ color: rgb(51, 51, 51);
+}
+.subtle-link:hover {
+ color: rgb(51, 51, 51);
+ text-decoration: underline;
+}
+
+#task-breadcrumbs {
+ font-size: smaller;
+
+}
+#task-breadcrumbs a {
+ color: rgb(51, 51, 51);
+}
+#task-breadcrumbs a:hover {
+ color: rgb(51, 51, 51);
+}
diff --git a/public/templates/task.html b/public/templates/task.html
index 7ddece2..8295f1e 100644
--- a/public/templates/task.html
+++ b/public/templates/task.html
@@ -4,7 +4,8 @@
•
-
+
+
<% if(isCompleted){ %>
Uncomplete
<% }else { %>