Commit 561b501e authored by Ryan Eastridge's avatar Ryan Eastridge

use Thorax collection filtering

parent 37551624
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
<section id="main"> <section id="main">
<input id="toggle-all" type="checkbox"> <input id="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label> <label for="toggle-all">Mark all as complete</label>
{{#collection todosCollection item-view="todo-item" tag="ul" id="todo-list"}} {{#collection todosCollection filter="filterTodoItem" item-view="todo-item" tag="ul" id="todo-list"}}
<div class="view"> <div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}> <input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label> <label>{{title}}</label>
...@@ -59,7 +59,7 @@ ...@@ -59,7 +59,7 @@
<script src="../../../assets/jquery.min.js"></script> <script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.min.js"></script> <script src="../../../assets/lodash.min.js"></script>
<script src="../../../assets/handlebars.min.js"></script> <script src="../../../assets/handlebars.min.js"></script>
<script src="js/lib/backbone-min.js"></script> <script src="js/lib/backbone.js"></script>
<script src="js/lib/backbone-localstorage.js"></script> <script src="js/lib/backbone-localstorage.js"></script>
<script src="js/lib/thorax.js"></script> <script src="js/lib/thorax.js"></script>
<script> <script>
......
This diff is collapsed.
...@@ -1056,15 +1056,6 @@ Thorax.CollectionView = Thorax.HelperView.extend({ ...@@ -1056,15 +1056,6 @@ Thorax.CollectionView = Thorax.HelperView.extend({
//without triggering event on collection //without triggering event on collection
this.reset(); this.reset();
} }
//if we rendered with item views model changes will be observed
//by the generated item view but if we rendered with templates
//then model changes need to be bound as nothing is watching
if (!this.options['item-view']) {
this.on(collection, 'change', function(model) {
this.$el.find('[' + modelCidAttributeName + '="' + model.cid +'"]').remove();
this.appendItem(model, collection.indexOf(model));
}, this);
}
} }
return this; return this;
}, },
...@@ -1115,7 +1106,15 @@ Thorax.CollectionView = Thorax.HelperView.extend({ ...@@ -1115,7 +1106,15 @@ Thorax.CollectionView = Thorax.HelperView.extend({
this.$el.prepend(itemElement); this.$el.prepend(itemElement);
} else { } else {
//use last() as appendItem can accept multiple nodes from a template //use last() as appendItem can accept multiple nodes from a template
this.$el.find('[' + modelCidAttributeName + '="' + previousModel.cid + '"]').last().after(itemElement); var last = this.$el.find('[' + modelCidAttributeName + '="' + previousModel.cid + '"]').last();
if (last.length) {
last.after(itemElement);
} else {
//TODO: this is a hack to make item filtering work since the previous
//query may not find an element if it was filtered, should re-write
//filtering and tests to use hide/show
this.$el.prepend(itemElement);
}
} }
this._appendViews(null, function(el) { this._appendViews(null, function(el) {
el.setAttribute(modelCidAttributeName, model.cid); el.setAttribute(modelCidAttributeName, model.cid);
...@@ -1129,6 +1128,24 @@ Thorax.CollectionView = Thorax.HelperView.extend({ ...@@ -1129,6 +1128,24 @@ Thorax.CollectionView = Thorax.HelperView.extend({
} }
return itemView; return itemView;
}, },
//updateItem only useful if there is no item view, otherwise
//itemView.render() provideds the same functionality
updateItem: function(model) {
this.removeItem(model);
this.appendItem(model);
},
removeItem: function(model) {
var viewEl = this.$('[' + modelCidAttributeName + '="' + model.cid + '"]');
if (!viewEl.length) {
return false;
}
var viewCid = viewEl.attr(viewCidAttributeName);
if (this.children[viewCid]) {
delete this.children[viewCid];
}
viewEl.remove();
return true;
},
reset: function() { reset: function() {
this.render(); this.render();
}, },
...@@ -1141,13 +1158,7 @@ Thorax.CollectionView = Thorax.HelperView.extend({ ...@@ -1141,13 +1158,7 @@ Thorax.CollectionView = Thorax.HelperView.extend({
} else { } else {
this.$el.removeAttr(collectionEmptyAttributeName); this.$el.removeAttr(collectionEmptyAttributeName);
this.collection.forEach(function(item, i) { this.collection.forEach(function(item, i) {
if (!this.options.filter || this.options.filter && this.appendItem(item, i);
(typeof this.options.filter === 'string'
? this.parent[this.options.filter]
: this.options.filter).call(this.parent, item, i)
) {
this.appendItem(item, i);
}
}, this); }, this);
} }
this.parent.trigger('rendered:collection', this, this.collection); this.parent.trigger('rendered:collection', this, this.collection);
...@@ -1255,24 +1266,65 @@ function bindCollectionEvents(collection, events) { ...@@ -1255,24 +1266,65 @@ function bindCollectionEvents(collection, events) {
}, this); }, this);
} }
function applyVisibilityFilter() {
if (this.options.filter) {
this.collection.forEach(function(model) {
applyItemVisiblityFilter.call(this, model);
}, this);
}
}
function applyItemVisiblityFilter(model) {
if (this.options.filter) {
$('[' + modelCidAttributeName + '="' + model.cid + '"]')[itemShouldBeVisible.call(this, model) ? 'show' : 'hide']();
}
}
function itemShouldBeVisible(model, i) {
return (typeof this.options.filter === 'string'
? this.parent[this.options.filter]
: this.options.filter).call(this.parent, model, this.collection.indexOf(model))
;
}
function handleChangeFromEmptyToNotEmpty() {
if (this.collection.length === 1) {
if(this.$el.length) {
this.$el.removeAttr(collectionEmptyAttributeName);
this.$el.empty();
}
}
}
function handleChangeFromNotEmptyToEmpty() {
if (this.collection.length === 0) {
if (this.$el.length) {
this.$el.attr(collectionEmptyAttributeName, true);
this.appendEmpty();
}
}
}
Thorax.View.on({ Thorax.View.on({
collection: { collection: {
add: function(collectionView, model, collection) { filter: function(collectionView) {
if (collection.length === 1) { applyVisibilityFilter.call(collectionView);
if(collectionView.$el.length) { },
collectionView.$el.removeAttr(collectionEmptyAttributeName); change: function(collectionView, model) {
collectionView.$el.empty(); //if we rendered with item views, model changes will be observed
} //by the generated item view but if we rendered with templates
//then model changes need to be bound as nothing is watching
if (!collectionView.options['item-view']) {
collectionView.updateItem(model);
} }
applyItemVisiblityFilter.call(collectionView, model);
},
add: function(collectionView, model, collection) {
handleChangeFromEmptyToNotEmpty.call(collectionView);
if (collectionView.$el.length) { if (collectionView.$el.length) {
var index = collection.indexOf(model); var index = collection.indexOf(model);
if (!collectionView.options.filter || collectionView.options.filter && collectionView.appendItem(model, index);
(typeof collectionView.options.filter === 'string' applyItemVisiblityFilter.call(collectionView, model);
? this[collectionView.options.filter]
: collectionView.options.filter).call(this, model, index)
) {
collectionView.appendItem(model, index);
}
} }
}, },
remove: function(collectionView, model, collection) { remove: function(collectionView, model, collection) {
...@@ -1284,12 +1336,7 @@ Thorax.View.on({ ...@@ -1284,12 +1336,7 @@ Thorax.View.on({
break; break;
} }
} }
if (collection.length === 0) { handleChangeFromNotEmptyToEmpty.call(collectionView);
if (collectionView.$el.length) {
collectionView.$el.attr(collectionEmptyAttributeName, true);
collectionView.appendEmpty();
}
}
}, },
reset: function(collectionView, collection) { reset: function(collectionView, collection) {
collectionView.reset(); collectionView.reset();
......
...@@ -21,6 +21,17 @@ var app = app || {}; ...@@ -21,6 +21,17 @@ var app = app || {};
this.save({ this.save({
completed: !this.get('completed') completed: !this.get('completed')
}); });
},
isVisible: function () {
var isCompleted = this.get('completed');
if (app.TodoFilter === '') {
return true;
} else if (app.TodoFilter === 'completed') {
return isCompleted;
} else if (app.TodoFilter === 'active') {
return !isCompleted;
}
} }
}); });
......
...@@ -8,15 +8,15 @@ var app = app || {}; ...@@ -8,15 +8,15 @@ var app = app || {};
app.TodoRouter = new (Thorax.Router.extend({ app.TodoRouter = new (Thorax.Router.extend({
routes: { routes: {
'*filter': 'setFilter' '': 'setFilter',
':filter': 'setFilter'
}, },
setFilter: function( param ) { setFilter: function( param ) {
// Set the current filter to be used // Set the current filter to be used
window.app.TodoFilter = param.trim() || ''; window.app.TodoFilter = param ? param.trim().replace(/^\//, '') : '';
// Thorax listens for a `filter` event which will
// Trigger a collection filter event, causing hiding/unhiding // force the collection to re-filter
// of Todo view items
window.app.Todos.trigger('filter'); window.app.Todos.trigger('filter');
} }
})); }));
......
...@@ -20,9 +20,7 @@ $(function( $ ) { ...@@ -20,9 +20,7 @@ $(function( $ ) {
// to the view. Any events in this hash will be bound to the // to the view. Any events in this hash will be bound to the
// collection. // collection.
collection: { collection: {
all: 'toggleToggleAllButton', all: 'toggleToggleAllButton'
'change:completed': 'filterOne',
'filter': 'filterAll'
}, },
rendered: 'toggleToggleAllButton' rendered: 'toggleToggleAllButton'
}, },
...@@ -41,12 +39,11 @@ $(function( $ ) { ...@@ -41,12 +39,11 @@ $(function( $ ) {
this.$('#toggle-all').attr('checked', !this.todosCollection.remaining().length); this.$('#toggle-all').attr('checked', !this.todosCollection.remaining().length);
}, },
filterOne : function (todo) { // This function is specified in the collection helper as the filter
todo.trigger("visible"); // and will be called each time a model changes, or for each item
}, // when the collection is rendered
filterTodoItem: function(model) {
filterAll : function () { return model.isVisible();
app.Todos.each(this.filterOne, this);
}, },
// Generate the attributes for a new Todo item. // Generate the attributes for a new Todo item.
......
...@@ -22,33 +22,14 @@ $(function() { ...@@ -22,33 +22,14 @@ $(function() {
'click .destroy': 'clear', 'click .destroy': 'clear',
'keypress .edit': 'updateOnEnter', 'keypress .edit': 'updateOnEnter',
'blur .edit': 'close', 'blur .edit': 'close',
// Events in this hash will be bound to the model
// when it is assigned to the view
model: {
visible: 'toggleVisible'
},
// The "rendered" event is triggered by Thorax each time render() // The "rendered" event is triggered by Thorax each time render()
// is called and the result of the template has been appended // is called and the result of the template has been appended
// to the View's $el // to the View's $el
rendered: function() { rendered: function() {
this.$el.toggleClass( 'completed', this.model.get('completed') ); this.$el.toggleClass( 'completed', this.model.get('completed') );
this.toggleVisible();
this.input = this.$('.edit');
} }
}, },
toggleVisible : function () {
this.$el.toggleClass( 'hidden', this.isHidden());
},
isHidden : function () {
var isCompleted = this.model.get('completed');
return ( // hidden cases only
(!isCompleted && app.TodoFilter === 'completed')
|| (isCompleted && app.TodoFilter === 'active')
);
},
// Toggle the `"completed"` state of the model. // Toggle the `"completed"` state of the model.
toggleCompleted: function() { toggleCompleted: function() {
this.model.toggle(); this.model.toggle();
...@@ -57,12 +38,12 @@ $(function() { ...@@ -57,12 +38,12 @@ $(function() {
// Switch this view into `"editing"` mode, displaying the input field. // Switch this view into `"editing"` mode, displaying the input field.
edit: function() { edit: function() {
this.$el.addClass('editing'); this.$el.addClass('editing');
this.input.focus(); this.$('.edit').focus();
}, },
// Close the `"editing"` mode, saving changes to the todo. // Close the `"editing"` mode, saving changes to the todo.
close: function() { close: function() {
var value = this.input.val().trim(); var value = this.$('.edit').val().trim();
if ( value ) { if ( value ) {
this.model.save({ title: value }); this.model.save({ title: value });
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment