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

Rewrite jQuery app and add routing

fixes #309
parent 24a0df0e
node_modules node_modules
bower_components /bower_components
{ {
"name": "todomvc-jquery", "name": "todomvc-jquery",
"version": "0.0.0",
"dependencies": { "dependencies": {
"jquery": "~1.9.1", "todomvc-common": "~0.1.9",
"handlebars": "~1.0.0-rc.3", "jquery": "~2.1.0",
"todomvc-common": "~0.1.4" "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 @@ ...@@ -17,35 +17,44 @@
<label for="toggle-all">Mark all as complete</label> <label for="toggle-all">Mark all as complete</label>
<ul id="todo-list"></ul> <ul id="todo-list"></ul>
</section> </section>
<footer id="footer"> <footer id="footer"></footer>
<span id="todo-count"><strong>0</strong> item left</span>
<button id="clear-completed">Clear completed</button>
</footer>
</section> </section>
<footer id="info"> <footer id="info">
<p>Double-click to edit a todo</p> <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> <p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer> </footer>
<script id="todo-template" type="text/x-handlebars-template"> <script id="todo-template" type="text/x-handlebars-template">
{{#this}} {{#this}}
<li {{#if completed}}class="completed"{{/if}} data-id="{{id}}"> <li {{#if completed}}class="completed"{{/if}} data-id="{{id}}">
<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>
<button class="destroy"></button> <button class="destroy"></button>
</div> </div>
<input class="edit" value="{{title}}"> <input class="edit" value="{{title}}">
</li> </li>
{{/this}} {{/this}}
</script> </script>
<script id="footer-template" type="text/x-handlebars-template"> <script id="footer-template" type="text/x-handlebars-template">
<span id="todo-count"><strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left</span> <span id="todo-count"><strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left</span>
{{#if completedTodos}}<button id="clear-completed">Clear completed ({{completedTodos}})</button>{{/if}} <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>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/jquery/jquery.js"></script> <script src="bower_components/jquery/jquery.js"></script>
<script src="bower_components/handlebars/handlebars.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> <script src="js/app.js"></script>
</body> </body>
</html> </html>
...@@ -2,7 +2,14 @@ ...@@ -2,7 +2,14 @@
jQuery(function ($) { jQuery(function ($) {
'use strict'; '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 () { uuid: function () {
/*jshint bitwise:false */ /*jshint bitwise:false */
var i, random; var i, random;
...@@ -33,11 +40,16 @@ jQuery(function ($) { ...@@ -33,11 +40,16 @@ jQuery(function ($) {
var App = { var App = {
init: function () { init: function () {
this.ENTER_KEY = 13; this.todos = util.store('todos-jquery');
this.todos = Utils.store('todos-jquery');
this.cacheElements(); this.cacheElements();
this.bindEvents(); this.bindEvents();
this.render();
Router({
'/:filter': function (filter) {
this.filter = filter;
this.render();
}.bind(this)
}).init('/all');
}, },
cacheElements: function () { cacheElements: function () {
this.todoTemplate = Handlebars.compile($('#todo-template').html()); this.todoTemplate = Handlebars.compile($('#todo-template').html());
...@@ -54,129 +66,144 @@ jQuery(function ($) { ...@@ -54,129 +66,144 @@ jQuery(function ($) {
}, },
bindEvents: function () { bindEvents: function () {
var list = this.$todoList; var list = this.$todoList;
this.$newTodo.on('keyup', this.create); this.$newTodo.on('keyup', this.create.bind(this));
this.$toggleAll.on('change', this.toggleAll); this.$toggleAll.on('change', this.toggleAll.bind(this));
this.$footer.on('click', '#clear-completed', this.destroyCompleted); this.$footer.on('click', '#clear-completed', this.destroyCompleted.bind(this));
list.on('change', '.toggle', this.toggle); list.on('change', '.toggle', this.toggle.bind(this));
list.on('dblclick', 'label', this.edit); list.on('dblclick', 'label', this.edit.bind(this));
list.on('keypress', '.edit', this.blurOnEnter); list.on('keyup', '.edit', this.editKeyup.bind(this));
list.on('blur', '.edit', this.update); list.on('focusout', '.edit', this.update.bind(this));
list.on('click', '.destroy', this.destroy); list.on('click', '.destroy', this.destroy.bind(this));
}, },
render: function () { render: function () {
this.$todoList.html(this.todoTemplate(this.todos)); var todos = this.getFilteredTodos();
this.$main.toggle(!!this.todos.length); this.$todoList.html(this.todoTemplate(todos));
this.$toggleAll.prop('checked', !this.activeTodoCount()); this.$main.toggle(todos.length > 0);
this.$toggleAll.prop('checked', this.getActiveTodos().length === 0);
this.renderFooter(); this.renderFooter();
Utils.store('todos-jquery', this.todos); this.$newTodo.focus();
util.store('todos-jquery', this.todos);
}, },
renderFooter: function () { renderFooter: function () {
var todoCount = this.todos.length; var todoCount = this.todos.length;
var activeTodoCount = this.activeTodoCount(); var activeTodoCount = this.getActiveTodos().length;
var footer = { var template = this.footerTemplate({
activeTodoCount: activeTodoCount, activeTodoCount: activeTodoCount,
activeTodoWord: Utils.pluralize(activeTodoCount, 'item'), activeTodoWord: util.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount completedTodos: todoCount - activeTodoCount,
}; filter: this.filter
});
this.$footer.toggle(!!todoCount); this.$footer.toggle(todoCount > 0).html(template);
this.$footer.html(this.footerTemplate(footer));
}, },
toggleAll: function () { toggleAll: function (e) {
var isChecked = $(this).prop('checked'); var isChecked = $(e.target).prop('checked');
$.each(App.todos, function (i, val) { this.todos.forEach(function (todo) {
val.completed = isChecked; todo.completed = isChecked;
}); });
App.render(); this.render();
}, },
activeTodoCount: function () { getActiveTodos: function () {
var count = 0; return this.todos.filter(function (todo) {
return !todo.completed;
$.each(this.todos, function (i, val) {
if (!val.completed) {
count++;
}
}); });
return count;
}, },
destroyCompleted: function () { getCompletedTodos: function () {
var todos = App.todos; return this.todos.filter(function (todo) {
var l = todos.length; return todo.completed;
});
},
getFilteredTodos: function () {
if (this.filter === 'active') {
return this.getActiveTodos();
}
while (l--) { if (this.filter === 'completed') {
if (todos[l].completed) { return this.getCompletedTodos();
todos.splice(l, 1);
}
} }
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 // accepts an element from inside the `.item` div and
// returns the corresponding todo in the todos array // returns the corresponding index in the `todos` array
getTodo: function (elem, callback) { indexFromEl: function (el) {
var id = $(elem).closest('li').data('id'); var id = $(el).closest('li').data('id');
var todos = this.todos;
$.each(this.todos, function (i, val) { var i = todos.length;
if (val.id === id) {
callback.apply(App, arguments); while (i--) {
return false; if (todos[i].id === id) {
return i;
} }
}); }
}, },
create: function (e) { create: function (e) {
var $input = $(this); var $input = $(e.target);
var val = $.trim($input.val()); var val = $input.val().trim();
if (e.which !== App.ENTER_KEY || !val) { if (e.which !== ENTER_KEY || !val) {
return; return;
} }
App.todos.push({ this.todos.push({
id: Utils.uuid(), id: util.uuid(),
title: val, title: val,
completed: false completed: false
}); });
$input.val(''); $input.val('');
App.render();
this.render();
}, },
toggle: function () { toggle: function (e) {
App.getTodo(this, function (i, val) { var i = this.indexFromEl(e.target);
val.completed = !val.completed; this.todos[i].completed = !this.todos[i].completed;
}); this.render();
App.render();
}, },
edit: function () { edit: function (e) {
var $input = $(this).closest('li').addClass('editing').find('.edit'); var $input = $(e.target).closest('li').addClass('editing').find('.edit');
var val = $input.val(); $input.val($input.val()).focus();
$input.val(val).focus();
}, },
blurOnEnter: function (e) { editKeyup: function (e) {
if (e.which === App.ENTER_KEY) { if (e.which === ENTER_KEY) {
e.target.blur(); e.target.blur();
} }
if (e.which === ESCAPE_KEY) {
$(e.target).data('abort', true).blur();
}
}, },
update: function () { update: function (e) {
var val = $.trim($(this).removeClass('editing').val()); var el = e.target;
var $el = $(el);
var val = $el.val().trim();
App.getTodo(this, function (i) { if ($el.data('abort')) {
if (val) { $el.data('abort', false);
this.todos[i].title = val;
} else {
this.todos.splice(i, 1);
}
this.render(); this.render();
}); return;
}, }
destroy: function () {
App.getTodo(this, function (i) { var i = this.indexFromEl(el);
if (val) {
this.todos[i].title = val;
} else {
this.todos.splice(i, 1); 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 @@ ...@@ -253,7 +253,7 @@
<li class="routing"> <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> <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> <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> <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> </li>
</ul> </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