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
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