Commit 9ec9a524 authored by Sindre Sorhus's avatar Sindre Sorhus

Merge pull request #813 from tastejs/improve-jquery-app

Rewrite jQuery app and add routing and abort on escape
parents 74d16714 3355fd7d
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"
} }
} }
...@@ -17,14 +17,11 @@ ...@@ -17,14 +17,11 @@
<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">
...@@ -41,11 +38,23 @@ ...@@ -41,11 +38,23 @@
</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>
<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}} {{#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();
Router({
'/:filter': function (filter) {
this.filter = filter;
this.render(); 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;
});
while (l--) { },
if (todos[l].completed) { getFilteredTodos: function () {
todos.splice(l, 1); if (this.filter === 'active') {
return this.getActiveTodos();
} }
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 // 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();
if ($el.data('abort')) {
$el.data('abort', false);
this.render();
return;
}
var i = this.indexFromEl(el);
App.getTodo(this, function (i) {
if (val) { if (val) {
this.todos[i].title = val; this.todos[i].title = val;
} else { } else {
this.todos.splice(i, 1); this.todos.splice(i, 1);
} }
this.render(); this.render();
});
}, },
destroy: function () { destroy: function (e) {
App.getTodo(this, function (i) { this.todos.splice(this.indexFromEl(e.target), 1);
this.todos.splice(i, 1);
this.render(); 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