Commit 3355fd7d authored by Sindre Sorhus's avatar Sindre Sorhus

Rewrite jQuery app and add routing

fixes #309
parent 24a0df0e
node_modules
bower_components
/bower_components
{
"name": "todomvc-jquery",
"version": "0.0.0",
"dependencies": {
"jquery": "~1.9.1",
"handlebars": "~1.0.0-rc.3",
"todomvc-common": "~0.1.4"
"todomvc-common": "~0.1.9",
"jquery": "~2.1.0",
"handlebars": "~1.3.0",
"director": "~1.2.2"
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -17,35 +17,44 @@
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul>
</section>
<footer id="footer">
<span id="todo-count"><strong>0</strong> item left</span>
<button id="clear-completed">Clear completed</button>
</footer>
<footer id="footer"></footer>
</section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/sindresorhus">Sindre Sorhus</a></p>
<p>Created by <a href="http://sindresorhus.com">Sindre Sorhus</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script id="todo-template" type="text/x-handlebars-template">
{{#this}}
<li {{#if completed}}class="completed"{{/if}} data-id="{{id}}">
<div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{title}}">
</li>
{{#this}}
<li {{#if completed}}class="completed"{{/if}} data-id="{{id}}">
<div class="view">
<input class="toggle" type="checkbox" {{#if completed}}checked{{/if}}>
<label>{{title}}</label>
<button class="destroy"></button>
</div>
<input class="edit" value="{{title}}">
</li>
{{/this}}
</script>
<script id="footer-template" type="text/x-handlebars-template">
<span id="todo-count"><strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left</span>
{{#if completedTodos}}<button id="clear-completed">Clear completed ({{completedTodos}})</button>{{/if}}
<span id="todo-count"><strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left</span>
<ul id="filters">
<li>
<a {{#eq filter 'all'}}class="selected"{{/eq}} href="#/all">All</a>
</li>
<li>
<a {{#eq filter 'active'}}class="selected"{{/eq}}href="#/active">Active</a>
</li>
<li>
<a {{#eq filter 'completed'}}class="selected"{{/eq}}href="#/completed">Completed</a>
</li>
</ul>
{{#if completedTodos}}<button id="clear-completed">Clear completed ({{completedTodos}})</button>{{/if}}
</script>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/handlebars/handlebars.js"></script>
<script src="bower_components/director/build/director.js"></script>
<script src="js/app.js"></script>
</body>
</html>
......@@ -2,7 +2,14 @@
jQuery(function ($) {
'use strict';
var Utils = {
Handlebars.registerHelper('eq', function(a, b, options) {
return a === b ? options.fn(this) : options.inverse(this);
});
var ENTER_KEY = 13;
var ESCAPE_KEY = 27;
var util = {
uuid: function () {
/*jshint bitwise:false */
var i, random;
......@@ -33,11 +40,16 @@ jQuery(function ($) {
var App = {
init: function () {
this.ENTER_KEY = 13;
this.todos = Utils.store('todos-jquery');
this.todos = util.store('todos-jquery');
this.cacheElements();
this.bindEvents();
this.render();
Router({
'/:filter': function (filter) {
this.filter = filter;
this.render();
}.bind(this)
}).init('/all');
},
cacheElements: function () {
this.todoTemplate = Handlebars.compile($('#todo-template').html());
......@@ -54,129 +66,144 @@ jQuery(function ($) {
},
bindEvents: function () {
var list = this.$todoList;
this.$newTodo.on('keyup', this.create);
this.$toggleAll.on('change', this.toggleAll);
this.$footer.on('click', '#clear-completed', this.destroyCompleted);
list.on('change', '.toggle', this.toggle);
list.on('dblclick', 'label', this.edit);
list.on('keypress', '.edit', this.blurOnEnter);
list.on('blur', '.edit', this.update);
list.on('click', '.destroy', this.destroy);
this.$newTodo.on('keyup', this.create.bind(this));
this.$toggleAll.on('change', this.toggleAll.bind(this));
this.$footer.on('click', '#clear-completed', this.destroyCompleted.bind(this));
list.on('change', '.toggle', this.toggle.bind(this));
list.on('dblclick', 'label', this.edit.bind(this));
list.on('keyup', '.edit', this.editKeyup.bind(this));
list.on('focusout', '.edit', this.update.bind(this));
list.on('click', '.destroy', this.destroy.bind(this));
},
render: function () {
this.$todoList.html(this.todoTemplate(this.todos));
this.$main.toggle(!!this.todos.length);
this.$toggleAll.prop('checked', !this.activeTodoCount());
var todos = this.getFilteredTodos();
this.$todoList.html(this.todoTemplate(todos));
this.$main.toggle(todos.length > 0);
this.$toggleAll.prop('checked', this.getActiveTodos().length === 0);
this.renderFooter();
Utils.store('todos-jquery', this.todos);
this.$newTodo.focus();
util.store('todos-jquery', this.todos);
},
renderFooter: function () {
var todoCount = this.todos.length;
var activeTodoCount = this.activeTodoCount();
var footer = {
var activeTodoCount = this.getActiveTodos().length;
var template = this.footerTemplate({
activeTodoCount: activeTodoCount,
activeTodoWord: Utils.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount
};
activeTodoWord: util.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount,
filter: this.filter
});
this.$footer.toggle(!!todoCount);
this.$footer.html(this.footerTemplate(footer));
this.$footer.toggle(todoCount > 0).html(template);
},
toggleAll: function () {
var isChecked = $(this).prop('checked');
toggleAll: function (e) {
var isChecked = $(e.target).prop('checked');
$.each(App.todos, function (i, val) {
val.completed = isChecked;
this.todos.forEach(function (todo) {
todo.completed = isChecked;
});
App.render();
this.render();
},
activeTodoCount: function () {
var count = 0;
$.each(this.todos, function (i, val) {
if (!val.completed) {
count++;
}
getActiveTodos: function () {
return this.todos.filter(function (todo) {
return !todo.completed;
});
return count;
},
destroyCompleted: function () {
var todos = App.todos;
var l = todos.length;
getCompletedTodos: function () {
return this.todos.filter(function (todo) {
return todo.completed;
});
},
getFilteredTodos: function () {
if (this.filter === 'active') {
return this.getActiveTodos();
}
while (l--) {
if (todos[l].completed) {
todos.splice(l, 1);
}
if (this.filter === 'completed') {
return this.getCompletedTodos();
}
App.render();
return this.todos;
},
destroyCompleted: function () {
this.todos = this.getActiveTodos();
this.filter = 'all';
this.render();
},
// accepts an element from inside the `.item` div and
// returns the corresponding todo in the todos array
getTodo: function (elem, callback) {
var id = $(elem).closest('li').data('id');
$.each(this.todos, function (i, val) {
if (val.id === id) {
callback.apply(App, arguments);
return false;
// returns the corresponding index in the `todos` array
indexFromEl: function (el) {
var id = $(el).closest('li').data('id');
var todos = this.todos;
var i = todos.length;
while (i--) {
if (todos[i].id === id) {
return i;
}
});
}
},
create: function (e) {
var $input = $(this);
var val = $.trim($input.val());
var $input = $(e.target);
var val = $input.val().trim();
if (e.which !== App.ENTER_KEY || !val) {
if (e.which !== ENTER_KEY || !val) {
return;
}
App.todos.push({
id: Utils.uuid(),
this.todos.push({
id: util.uuid(),
title: val,
completed: false
});
$input.val('');
App.render();
this.render();
},
toggle: function () {
App.getTodo(this, function (i, val) {
val.completed = !val.completed;
});
App.render();
toggle: function (e) {
var i = this.indexFromEl(e.target);
this.todos[i].completed = !this.todos[i].completed;
this.render();
},
edit: function () {
var $input = $(this).closest('li').addClass('editing').find('.edit');
var val = $input.val();
$input.val(val).focus();
edit: function (e) {
var $input = $(e.target).closest('li').addClass('editing').find('.edit');
$input.val($input.val()).focus();
},
blurOnEnter: function (e) {
if (e.which === App.ENTER_KEY) {
editKeyup: function (e) {
if (e.which === ENTER_KEY) {
e.target.blur();
}
if (e.which === ESCAPE_KEY) {
$(e.target).data('abort', true).blur();
}
},
update: function () {
var val = $.trim($(this).removeClass('editing').val());
update: function (e) {
var el = e.target;
var $el = $(el);
var val = $el.val().trim();
App.getTodo(this, function (i) {
if (val) {
this.todos[i].title = val;
} else {
this.todos.splice(i, 1);
}
if ($el.data('abort')) {
$el.data('abort', false);
this.render();
});
},
destroy: function () {
App.getTodo(this, function (i) {
return;
}
var i = this.indexFromEl(el);
if (val) {
this.todos[i].title = val;
} else {
this.todos.splice(i, 1);
this.render();
});
}
this.render();
},
destroy: function (e) {
this.todos.splice(this.indexFromEl(e.target), 1);
this.render();
}
};
......
......@@ -253,7 +253,7 @@
<li class="routing">
<a href="vanilla-examples/vanillajs/" data-source="https://developer.mozilla.org/en/JavaScript" data-content="You know JavaScript right? :P">Vanilla JS</a>
</li>
<li>
<li class="routing">
<a href="architecture-examples/jquery/" data-source="http://jquery.com" data-content="jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development. jQuery is designed to change the way that you write JavaScript.">jQuery</a>
</li>
</ul>
......
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