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

use Thorax collection filtering

parent 37551624
......@@ -20,7 +20,7 @@
<section id="main">
<input id="toggle-all" type="checkbox">
<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">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label>
......@@ -59,7 +59,7 @@
<script src="../../../assets/jquery.min.js"></script>
<script src="../../../assets/lodash.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/thorax.js"></script>
<script>
......
This diff is collapsed.
......@@ -1056,15 +1056,6 @@ Thorax.CollectionView = Thorax.HelperView.extend({
//without triggering event on collection
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;
},
......@@ -1115,7 +1106,15 @@ Thorax.CollectionView = Thorax.HelperView.extend({
this.$el.prepend(itemElement);
} else {
//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) {
el.setAttribute(modelCidAttributeName, model.cid);
......@@ -1129,6 +1128,24 @@ Thorax.CollectionView = Thorax.HelperView.extend({
}
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() {
this.render();
},
......@@ -1141,13 +1158,7 @@ Thorax.CollectionView = Thorax.HelperView.extend({
} else {
this.$el.removeAttr(collectionEmptyAttributeName);
this.collection.forEach(function(item, i) {
if (!this.options.filter || this.options.filter &&
(typeof this.options.filter === 'string'
? this.parent[this.options.filter]
: this.options.filter).call(this.parent, item, i)
) {
this.appendItem(item, i);
}
this.appendItem(item, i);
}, this);
}
this.parent.trigger('rendered:collection', this, this.collection);
......@@ -1255,24 +1266,65 @@ function bindCollectionEvents(collection, events) {
}, 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({
collection: {
add: function(collectionView, model, collection) {
if (collection.length === 1) {
if(collectionView.$el.length) {
collectionView.$el.removeAttr(collectionEmptyAttributeName);
collectionView.$el.empty();
}
filter: function(collectionView) {
applyVisibilityFilter.call(collectionView);
},
change: function(collectionView, model) {
//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) {
var index = collection.indexOf(model);
if (!collectionView.options.filter || collectionView.options.filter &&
(typeof collectionView.options.filter === 'string'
? this[collectionView.options.filter]
: collectionView.options.filter).call(this, model, index)
) {
collectionView.appendItem(model, index);
}
collectionView.appendItem(model, index);
applyItemVisiblityFilter.call(collectionView, model);
}
},
remove: function(collectionView, model, collection) {
......@@ -1284,12 +1336,7 @@ Thorax.View.on({
break;
}
}
if (collection.length === 0) {
if (collectionView.$el.length) {
collectionView.$el.attr(collectionEmptyAttributeName, true);
collectionView.appendEmpty();
}
}
handleChangeFromNotEmptyToEmpty.call(collectionView);
},
reset: function(collectionView, collection) {
collectionView.reset();
......
......@@ -21,6 +21,17 @@ var app = app || {};
this.save({
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 || {};
app.TodoRouter = new (Thorax.Router.extend({
routes: {
'*filter': 'setFilter'
'': 'setFilter',
':filter': 'setFilter'
},
setFilter: function( param ) {
// Set the current filter to be used
window.app.TodoFilter = param.trim() || '';
// Trigger a collection filter event, causing hiding/unhiding
// of Todo view items
window.app.TodoFilter = param ? param.trim().replace(/^\//, '') : '';
// Thorax listens for a `filter` event which will
// force the collection to re-filter
window.app.Todos.trigger('filter');
}
}));
......
......@@ -20,9 +20,7 @@ $(function( $ ) {
// to the view. Any events in this hash will be bound to the
// collection.
collection: {
all: 'toggleToggleAllButton',
'change:completed': 'filterOne',
'filter': 'filterAll'
all: 'toggleToggleAllButton'
},
rendered: 'toggleToggleAllButton'
},
......@@ -41,12 +39,11 @@ $(function( $ ) {
this.$('#toggle-all').attr('checked', !this.todosCollection.remaining().length);
},
filterOne : function (todo) {
todo.trigger("visible");
},
filterAll : function () {
app.Todos.each(this.filterOne, this);
// This function is specified in the collection helper as the filter
// and will be called each time a model changes, or for each item
// when the collection is rendered
filterTodoItem: function(model) {
return model.isVisible();
},
// Generate the attributes for a new Todo item.
......
......@@ -22,33 +22,14 @@ $(function() {
'click .destroy': 'clear',
'keypress .edit': 'updateOnEnter',
'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()
// is called and the result of the template has been appended
// to the View's $el
rendered: function() {
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.
toggleCompleted: function() {
this.model.toggle();
......@@ -57,12 +38,12 @@ $(function() {
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
this.$el.addClass('editing');
this.input.focus();
this.$('.edit').focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
var value = this.input.val().trim();
var value = this.$('.edit').val().trim();
if ( 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