Commit 78157af4 authored by addyosmani's avatar addyosmani

trying to re-add sammy todo app as git decided it should be a submodule last time

parent 4320f1ce
sammyjs @ 166eb403
Subproject commit 166eb4038d1b3c0842c824e5090fb282b03135c3
This version/changes copyright 2011, Addy Osmani
Original multi-list/multi-todo/non todo standard version copyright 2010, Brandon Aaron
\ No newline at end of file
This diff is collapsed.
(function($) {
var app = $.sammy(function() {
this.use(Sammy.Template);
this.notFound = function(verb, path) {
this.runRoute('get', '#/404');
};
this.get('#/404', function() {
this.partial('templates/404.template', {}, function(html) {
$('#todo-list').html(html);
});
});
this.get('#/list/:id', function() {
var list = Lists.get(this.params['id']);
if (list) {
this.partial('templates/todolist.template', {
list: list,
todos: Todos.filter('listId', list.id)
}, function(html) {
$('#todo-list').html(html);
});
} else {
this.notFound();
}
});
// events
this.bind('run', function(e, data) {
var context = this;
var title = localStorage.getItem('title') || "Todo";
$('h1').text(title);
if(Lists._data.length <=0){
var list = Lists.create({ name: 'My new list' });
//app.trigger('updateLists');
}
$('#new-todo').keydown(function(e) {
if (e.keyCode == 13){
var todoContent = $(this).val();
var todo = Todos.create({ name: todoContent, done: false, listId: parseInt($('h2').attr('data-id'), 10) });
context.partial('templates/_todo.template', todo, function(html) {
//$(html).insertAfter('#todo-list li:last');
var q = $(html);
console.log(q);
$('#todo-list').append(q);
});
$(this).val('');
}
});
/*
$('#lists')
.delegate('dd[data-id]', 'click', function() {
context.redirect('#/list/'+$(this).attr('data-id'));
app.trigger('updateList');
});*/
$('.trashcan')
.live('click', function() {
var $this = $(this);
app.trigger('delete', {
type: $this.attr('data-type'),
id: $this.attr('data-id')
});
});
//new
$('.check')
.live('click', function() {
var $this = $(this),
$li = $this.parents('li').toggleClass('done'),
isDone = $li.is('.done');
app.trigger('mark' + (isDone ? 'Done' : 'Undone'), { id: $li.attr('data-id') });
});
/*
$('.checkbox')
.live('click', function() {
var $this = $(this),
$li = $this.parents('li').toggleClass('done'),
isDone = $li.is('.done');
app.trigger('mark' + (isDone ? 'Done' : 'Undone'), { id: $li.attr('data-id') });
});*/
$('[contenteditable]')
.live('focus', function() {
// store the current value
$.data(this, 'prevValue', $(this).text());
})
.live('blur', function() {
var $this = $(this),
// grab the, likely, modified value
text = $.trim($this.text());
if (!text) {
// restore the previous value if text is empty
$this.text($.data(this, 'prevValue'));
} else {
if ($this.is('h1')) {
// it is the title
localStorage.setItem('title', text);
} else {
// save it
app.trigger('save', {
type: $this.attr('data-type'),
id: $this.attr('data-id'),
name: text
});
}
}
})
.live('keypress', function(event) {
// save on enter
if (event.which === 13) {
this.blur();
return false;
}
});
if (!localStorage.getItem('initialized')) {
// create first list and todo
var listId = Lists.create({
name: 'My first list'
}).id;
Todos.create({
name: 'My first todo',
done: false,
listId: listId
});
localStorage.setItem('initialized', 'yup');
this.redirect('#/list/'+listId);
} else {
var lastViewedOrFirstList = localStorage.getItem('lastviewed') || '#/list/' + Lists.first().id;
this.redirect(lastViewedOrFirstList);
}
//app.trigger('updateLists');
});
/*save the route as the lastviewed item*/
this.bind('route-found', function(e, data) {
localStorage.setItem('lastviewed', document.location.hash);
});
this.bind('save', function(e, data) {
var model = data.type == 'todo' ? Todos : Lists;
model.update(data.id, { name: data.name });
//if (data.type == 'list') {
//app.trigger('updateLists');
// }
});
/*marking the selected item as done*/
this.bind('markDone', function(e, data) {
Todos.update(data.id, { done: true });
});
/*mark the todo with the selected id as not done*/
this.bind('markUndone', function(e, data) {
Todos.update(data.id, { done: false });
});
this.bind('delete', function(e, data) {
//if (confirm('Are you sure you want to delete this ' + data.type + '?')) {
var model = data.type == 'list' ? Lists : Todos;
model.destroy(data.id);
if (data.type == 'list') {
var list = Lists.first();
if (list) {
this.redirect('#/list/'+list.id);
} else {
// create first list and todo
var listId = Lists.create({
name: 'Initial list'
}).id;
Todos.create({
name: 'A sample todo item',
done: false,
listId: listId
});
this.redirect('#/list/'+listId);
}
//app.trigger('updateLists');
} else {
// delete the todo from the view
$('li[data-id=' + data.id + ']').remove();
}
// }
});
/*
this.bind('updateLists', function(e, data) {
var selected = parseInt(location.hash.substr(location.hash.lastIndexOf('/')+1), 10);
this.partial('templates/_lists.template', {
lists: Lists.getAll(),
selected: selected
}, function(html) {
$('#lists').html(html);
});
});*/
});
// lists model
Lists = Object.create(Model);
Lists.name = 'lists';
Lists.init();
// todos model
Todos = Object.create(Model);
Todos.name = 'todos';
Todos.init();
$(function() { app.run(); });
})(jQuery);
\ No newline at end of file
This diff is collapsed.
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Todo App</title>
<link rel="stylesheet" href="app.css" type="text/css" media="screen" charset="utf-8">
<script src="http://code.jquery.com/jquery-1.6.1.min.js" type="text/javascript" charset="utf-8"></script>
<script src="lib/sammy.js" type="text/javascript" charset="utf-8"></script>
<script src="lib/sammy.template.js" type="text/javascript" charset="utf-8"></script>
<script src="lib/model.js" type="text/javascript" charset="utf-8"></script>
<script src="app.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript" charset="utf-8">
if (!'localStorage' in window) {
alert('Your browser does not support localStorage');
}
</script>
</head>
<body>
<div id="todoapp">
<div class="title">
<h1>Todos</h1>
</div>
<div class="content">
<div id="create-todo">
<input id="new-todo" placeholder="What needs to be done?"
type="text" />
<span class="ui-tooltip-top" style="display: none;">Press Enter to save this task</span>
</div>
<div id="todos">
<ul id="todo-list">
</ul>
</div>
<div id="todo-stats">
</div>
</div>
</div>
<div id="credits">
This version by
<br />
<a href="http://twitter.com/addyosmani">Addy Osmani</a>
<br />
based on some code by Brandon Aaron
</div>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
Model = {
name: 'model',
init: function() {
this._id = 0;
this._data = [];
this._deserialize();
return this;
},
create: function(attributes, save) {
attributes.id = this._newId();
var item = this._data[ (this._data.push(attributes)) - 1 ];
if (save !== false) {
this.save();
}
return this._clone(item);
},
first: function() {
return this._clone(this._data[0]);
},
last: function() {
return this._clone(_data[ this._data.length-1 ]);
},
get: function(id) {
return this._clone(this._get(id));
},
getAll: function() {
return this._clone(this._data);
},
filter: function(attribute, value) {
return this._clone(this._filter(attribute, value));
},
multiFilter: function(filters) {
return this._clone(this._multiFilter(filter));
},
update: function(id, attributes, save) {
var item = this._get(id) || false;
if (item) {
this._mixin(item, attributes);
if (save !== false) {
this.save();
}
}
return item;
},
destroy: function(id, save) {
this._data.splice(this._indexOf(id), 1);
if (save !== false) {
this.save();
}
return true;
},
destroyAll: function(save) {
this._data = [];
if (save !== false) {
this.save();
}
return true;
},
save: function() {
this._serialize();
return true;
},
_first: function() {
return this._data[0];
},
_last: function() {
return _data[ this._data.length-1 ];
},
_get: function(id) {
return this._filter('id', id)[0];
},
_filter: function(attribute, value) {
var items = [], key, item, undefValue = (typeof value == "undefined");
for (key in this._data) {
if (this._data.hasOwnProperty(key)) {
item = this._data[key];
if (undefValue || item[attribute] == value) {
items.push(item);
}
}
}
return items;
},
_multiFilter: function(filters) {
var items = [], key, attribute, item;
for (key in this._data) {
if (this._data.hasOwnProperty(key)) {
item = this._data[key];
for (attribute in filters) {
if (filters.hasOwnProperty(attribute)) {
if (filters[attribute] == item[attribute]) {
items.push(item);
}
}
}
}
}
return items;
},
_indexOf: function(id) {
return this._data.indexOf(this._get(id));
},
_serialize: function() {
var data = {
prevId: this._id,
data: this._data
};
localStorage[this.name] = JSON.stringify(data);
},
_deserialize: function() {
var data = localStorage[this.name];
if (data) {
data = JSON.parse(data);
this._id = data.prevId;
this._data = data.data;
}
},
_newId: function() {
return this._id++;
},
_mixin: function(to, from) {
for (var key in from) {
if (from.hasOwnProperty(key)) {
to[key] = from[key];
}
}
},
_clone: function(obj) {
var type = Object.prototype.toString.call(obj),
cloned = obj;
if (type == '[object Object]') {
cloned = {};
for (var key in obj) {
obj.hasOwnProperty(key) && (cloned[key] = this._clone(obj[key]));
}
} else if (type == '[object Array]') {
cloned = [];
for (var index = 0, length = obj.length; index < length; index++) {
cloned[index] = this._clone(obj[index]);
}
}
return cloned;
}
};
// http://javascript.crockford.com/prototypal.html
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
\ No newline at end of file
This diff is collapsed.
(function($) {
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
// adapted from: http://ejohn.org/blog/javascript-micro-templating/
// originally $.srender by Greg Borenstein http://ideasfordozens.com in Feb 2009
// modified for Sammy by Aaron Quint for caching templates by name
var srender_cache = {};
var srender = function(name, template, data) {
// target is an optional element; if provided, the result will be inserted into it
// otherwise the result will simply be returned to the caller
if (srender_cache[name]) {
fn = srender_cache[name];
} else {
if (typeof template == 'undefined') {
// was a cache check, return false
return false;
}
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
fn = srender_cache[name] = new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push(\"" +
// Convert the template into pure JavaScript
template
.replace(/[\r\t\n]/g, " ")
.replace(/\"/g, '\\"')
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)/g, "$1\r")
.replace(/\t=(.*?)%>/g, "\",$1,\"")
.split("\t").join("\");")
.split("%>").join("p.push(\"")
.split("\r").join("")
+ "\");}return p.join('');");
}
if (typeof data != 'undefined') {
return fn(data);
} else {
return fn;
}
};
Sammy = Sammy || {};
// <tt>Sammy.Template</tt> is a simple plugin that provides a way to create
// and render client side templates. The rendering code is based on John Resig's
// quick templates and Greg Borenstien's srender plugin.
// This is also a great template/boilerplate for Sammy plugins.
//
// Templates use <% %> tags to denote embedded javascript.
//
// === Examples
//
// Here is an example template (user.template):
//
// <div class="user">
// <div class="user-name"><%= user.name %></div>
// <% if (user.photo_url) { %>
// <div class="photo"><img src="<%= user.photo_url %>" /></div>
// <% } %>
// </div>
//
// Given that is a publicly accesible file, you would render it like:
//
// $.sammy(function() {
// // include the plugin
// this.use(Sammy.Template);
//
// this.get('#/', function() {
// // the template is rendered in the current context.
// this.user = {name: 'Aaron Quint'};
// // partial calls template() because of the file extension
// this.partial('user.template');
// })
// });
//
// You can also pass a second argument to use() that will alias the template
// method and therefore allow you to use a different extension for template files
// in <tt>partial()</tt>
//
// // alias to 'tpl'
// this.use(Sammy.Template, 'tpl');
//
// // now .tpl files will be run through srender
// this.get('#/', function() {
// this.partial('myfile.tpl');
// });
//
Sammy.Template = function(app, method_alias) {
// *Helper:* Uses simple templating to parse ERB like templates.
//
// === Arguments
//
// +template+:: A String template. '<% %>' tags are evaluated as Javascript and replaced with the elements in data.
// +data+:: An Object containing the replacement values for the template.
// data is extended with the <tt>EventContext</tt> allowing you to call its methods within the template.
// +name+:: An optional String name to cache the template.
//
var template = function(template, data, name) {
// use name for caching
if (typeof name == 'undefined') name = template;
return srender(name, template, $.extend({}, this, data));
};
// set the default method name/extension
if (!method_alias) method_alias = 'template';
// create the helper at the method alias
app.helper(method_alias, template);
};
})(jQuery);
\ No newline at end of file
# Sammy's Todos
This is a demo todo list app built on top of Sammy.js.
Original base code: [Brandon Aaron](http://brandonaaron.net). This version, refactored and rewritten: Addy Osmani
\ No newline at end of file
<h2>What?!</h2>
<p>Try clicking on one of your lists to the right.</p>
\ No newline at end of file
<li data-type="todo" data-id="<%= id %>" class="<%= done ? 'done' : '' %>">
<div class="todo">
<div class="display">
<input class="check" type="checkbox" <%= done ? 'checked' : '' %>/>
<span class="trashcan" data-type="todo" data-id="<%= id %>"></span>
<span contenteditable="true" data-type="todo" data-id="<%= id %>" class="todo-item"><%= name %></span>
</div>
</div>
</li>
<h2 data-type="list" data-id="<%= list.id %>"></h2>
<% $.each(todos, function(index, todo) { %>
<li data-type="todo" data-id="<%= todo.id %>" class="<%= todo.done ? 'done' : '' %>">
<div class="todo">
<div class="display">
<input class="check" type="checkbox" <%= todo.done ? 'checked' : '' %>/>
<span class="trashcan" data-type="todo" data-id="<%= todo.id %>"></span><span contenteditable="true" data-type="todo" data-id="<%= todo.id %>" class="todo-item"><%= todo.name %></span>
</div>
</div>
</li>
<% }); %>
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