Commit 57817abc authored by Sindre Sorhus's avatar Sindre Sorhus

Rewrite SocketStream app - add routing and fix ESC

parent db95f7b6
...@@ -240,7 +240,7 @@ ...@@ -240,7 +240,7 @@
<li class="labs"> <li class="labs">
<a href="http://todomvc.derbyjs.com" data-source="http://derbyjs.com" data-content="MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.">Derby</a> <a href="http://todomvc.derbyjs.com" data-source="http://derbyjs.com" data-content="MVC framework making it easy to write realtime, collaborative applications that run in both Node.js and browsers.">Derby</a>
</li> </li>
<li class="labs"> <li class="routing labs">
<a href="https://github.com/tastejs/todomvc/blob/gh-pages/labs/architecture-examples/socketstream/readme.md" data-source="http://www.socketstream.org" data-content="SocketStream is a fast, modular Node.js web framework dedicated to building realtime single-page apps">SocketStream</a> <a href="https://github.com/tastejs/todomvc/blob/gh-pages/labs/architecture-examples/socketstream/readme.md" data-source="http://www.socketstream.org" data-content="SocketStream is a fast, modular Node.js web framework dedicated to building realtime single-page apps">SocketStream</a>
</li> </li>
<li class="routing labs"> <li class="routing labs">
......
...@@ -9,6 +9,7 @@ ss.client.define('main', { ...@@ -9,6 +9,7 @@ ss.client.define('main', {
code: [ code: [
'libs/todomvc-common/base.js', 'libs/todomvc-common/base.js',
'libs/jquery/jquery.js', 'libs/jquery/jquery.js',
'libs/director/build/director.js',
'app' 'app'
], ],
tmpl: '*' tmpl: '*'
......
{ {
"name": "todomvc-socketstream", "name": "todomvc-socketstream",
"dependencies": { "dependencies": {
"todomvc-common": "~0.1.9", "todomvc-common": "~0.1.9",
"jquery": "~2.1.0" "jquery": "~2.1.0",
} "director": "~1.2.2"
}
} }
/*global $, ss*/ /*global jQuery */
(function () { jQuery(function ($) {
'use strict'; 'use strict';
var Utils = { var ENTER_KEY = 13;
// https://gist.github.com/1308368 var ESCAPE_KEY = 27;
uuid: function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b},
var util = {
uuid: function () {
/*jshint bitwise:false */
var i, random;
var uuid = '';
for (i = 0; i < 32; i++) {
random = Math.random() * 16 | 0;
if (i === 8 || i === 12 || i === 16 || i === 20) {
uuid += '-';
}
uuid += (i === 12 ? 4 : (i === 16 ? (random & 3 | 8) : random)).toString(16);
}
return uuid;
},
pluralize: function (count, word) { pluralize: function (count, word) {
return count === 1 ? word : word + 's'; return count === 1 ? word : word + 's';
} }
...@@ -12,188 +28,186 @@ ...@@ -12,188 +28,186 @@
var App = { var App = {
init: function () { init: function () {
var self = this;
this.ENTER_KEY = 13;
ss.rpc('todos.getAll', function (todos) { ss.rpc('todos.getAll', function (todos) {
self.todos = todos; this.todos = todos;
self.cacheElements(); this.cacheElements();
self.bindEvents(); this.bindEvents();
self.render();
}); Router({
'/:filter': function (filter) {
this.filter = filter;
this.render();
}.bind(this)
}).init('/all');
}.bind(this));
}, },
cacheElements: function () { cacheElements: function () {
this.$todoApp = $('#todoapp'); this.$todoApp = $('#todoapp');
this.$newTodo = $('#new-todo'); this.$header = this.$todoApp.find('#header');
this.$toggleAll = $('#toggle-all'); this.$main = this.$todoApp.find('#main');
this.$main = $('#main');
this.$todoList = $('#todo-list');
this.$footer = this.$todoApp.find('#footer'); this.$footer = this.$todoApp.find('#footer');
this.$count = $('#todo-count'); this.$newTodo = this.$header.find('#new-todo');
this.$clearBtn = $('#clear-completed'); this.$toggleAll = this.$main.find('#toggle-all');
this.$todoList = this.$main.find('#todo-list');
this.$count = this.$footer.find('#todo-count');
this.$clearBtn = this.$footer.find('#clear-completed');
}, },
bindEvents: function () { bindEvents: function () {
var list = this.$todoList; var list = this.$todoList;
this.$newTodo.on('keyup', this.create.bind(this));
this.$newTodo.on('keyup', this.create); this.$toggleAll.on('change', this.toggleAll.bind(this));
this.$toggleAll.on('change', this.toggleAll); this.$footer.on('click', '#clear-completed', this.destroyCompleted.bind(this));
this.$footer.on('click', '#clear-completed', this.destroyCompleted); list.on('change', '.toggle', this.toggle.bind(this));
list.on('dblclick', 'label', this.edit.bind(this));
list.on('change', '.toggle', this.toggle); list.on('keyup', '.edit', this.editKeyup.bind(this));
list.on('dblclick', 'label', this.edit); list.on('focusout', '.edit', this.update.bind(this));
list.on('keypress', '.edit', this.blurOnEnter); list.on('click', '.destroy', this.destroy.bind(this));
list.on('blur', '.edit', this.update);
list.on('click', '.destroy', this.destroy); ss.event.on('updateTodos', function (todos) {
this.todos = todos;
ss.event.on('updateTodos', this.updateTodos); this.render(true);
}, }.bind(this));
updateTodos: function (todos) {
App.todos = todos;
App.render(true);
}, },
render: function (preventRpc) { render: function (preventRpc) {
var html = this.todos.map(function (el) { var todos = this.getFilteredTodos();
return ss.tmpl.todo.render(el);
}).join('');
this.$todoList.html(html); this.$todoList.html(todos.map(function (el) {
this.$main.toggle(!!this.todos.length); return ss.tmpl.todo.render(el);
this.$toggleAll.prop('checked', !this.activeTodoCount()); }).join(''));
this.$main.toggle(todos.length > 0);
this.$toggleAll.prop('checked', this.getActiveTodos().length === 0);
this.renderFooter(); this.renderFooter();
this.$newTodo.focus();
if (!preventRpc) { if (!preventRpc) {
ss.rpc('todos.update', this.todos); ss.rpc('todos.update', this.todos);
} }
}, },
renderFooter: function () { renderFooter: function () {
var todoCount = this.todos.length; var todoCount = this.todos.length;
var activeTodoCount = this.getActiveTodos().length;
var activeTodoCount = this.activeTodoCount();
var footer = { var footer = {
activeTodoCount: activeTodoCount, activeTodoCount: activeTodoCount,
activeTodoWord: Utils.pluralize(activeTodoCount, 'item'), activeTodoWord: util.pluralize(activeTodoCount, 'item'),
completedTodos: todoCount - activeTodoCount completedTodos: todoCount - activeTodoCount,
filterAll: this.filter === 'all',
filterActive: this.filter === 'active',
filterCompleted: this.filter === 'completed'
}; };
this.$footer.toggle(!!todoCount); this.$footer.toggle(todoCount > 0).html(ss.tmpl.footer.render(footer));
this.$footer.html( ss.tmpl.footer.render(footer) );
}, },
toggleAll: function (e) {
var isChecked = $(e.target).prop('checked');
toggleAll: function () { this.todos.forEach(function (todo) {
var isChecked = $(this).prop('checked'); todo.completed = isChecked;
$.each(App.todos, function (i, val) {
val.completed = isChecked;
}); });
App.render(); this.render();
}, },
getActiveTodos: function () {
activeTodoCount: function () { return this.todos.filter(function (todo) {
var count = 0; return !todo.completed;
$.each(this.todos, function (i, val) {
if (!val.completed) {
count++;
}
}); });
return count;
}, },
getCompletedTodos: function () {
return this.todos.filter(function (todo) {
return todo.completed;
});
},
getFilteredTodos: function () {
if (this.filter === 'active') {
return this.getActiveTodos();
}
destroyCompleted: function () { if (this.filter === 'completed') {
var todos = App.todos; return this.getCompletedTodos();
var l = todos.length;
while (l--) {
if (todos[l].completed) {
todos.splice(l, 1);
}
} }
App.render(); return this.todos;
}, },
destroyCompleted: function () {
// Accepts an element from inside the ".item" div and this.todos = this.getActiveTodos();
// returns the corresponding todo in the todos array this.filter = 'all';
getTodo: function (elem, callback) { this.render();
var id = $(elem).closest('li').data('id'); },
// accepts an element from inside the `.item` div and
$.each(this.todos, function (i, val) { // returns the corresponding index in the `todos` array
if (val.id === id) { indexFromEl: function (el) {
callback.apply(App, arguments); var id = $(el).closest('li').data('id');
var todos = this.todos;
return false; var i = todos.length;
while (i--) {
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 (e) {
toggle: function () { var i = this.indexFromEl(e.target);
App.getTodo(this, function (i, val) { this.todos[i].completed = !this.todos[i].completed;
val.completed = !val.completed; this.render();
});
App.render();
}, },
edit: function (e) {
edit: function () { var $input = $(e.target).closest('li').addClass('editing').find('.edit');
$(this).closest('li').addClass('editing').find('.edit').focus(); $input.val($input.val()).focus();
}, },
editKeyup: function (e) {
blurOnEnter: function (e) { if (e.which === ENTER_KEY) {
if (e.keyCode === App.ENTER_KEY) {
e.target.blur(); e.target.blur();
} }
},
update: function () { if (e.which === ESCAPE_KEY) {
var val = $.trim($(this).removeClass('editing').val()); $(e.target).data('abort', true).blur();
App.getTodo(this, function (i) { }
if (val) { },
this.todos[i].title = val; update: function (e) {
} else { var el = e.target;
this.todos.splice(i, 1); var $el = $(el);
} var val = $el.val().trim();
if ($el.data('abort')) {
$el.data('abort', false);
this.render(); this.render();
}); return;
}, }
var i = this.indexFromEl(el);
destroy: function () { if (val) {
App.getTodo(this, function (i) { 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();
} }
}; };
App.init(); App.init();
})(); });
<span id="todo-count"> <span id="todo-count">
<strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left <strong>{{activeTodoCount}}</strong> {{activeTodoWord}} left
</span> </span>
<ul id="filters">
<li>
<a {{#filterAll}}class="selected"{{/filterAll}} href="#/all">All</a>
</li>
<li>
<a {{#filterActive}}class="selected"{{/filterActive}}href="#/active">Active</a>
</li>
<li>
<a {{#filterCompleted}}class="selected"{{/filterCompleted}}href="#/completed">Completed</a>
</li>
</ul>
{{#completedTodos}} {{#completedTodos}}
<button id="clear-completed">Clear completed ({{completedTodos}})</button> <button id="clear-completed">Clear completed ({{completedTodos}})</button>
{{/completedTodos}} {{/completedTodos}}
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
}, },
"dependencies": { "dependencies": {
"socketstream": "~0.3.10", "socketstream": "~0.3.10",
"ss-stylus": "~0.1.8",
"ss-hogan": "~0.1.3" "ss-hogan": "~0.1.3"
} }
} }
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