Commit aa3b74ed authored by Pascal Hartig's avatar Pascal Hartig

Sammy.js: Whitespace + XSS fixes

parent dd92c429
(function($) { (function($) {
var app = $.sammy(function() { var ESCAPE_KEY = 13;
this.use(Sammy.Template); var app = $.sammy(function() {
this.use(Sammy.Template);
this.notFound = function(verb, path) {
this.runRoute('get', '#/404'); this.notFound = function(verb, path) {
}; this.runRoute('get', '#/404');
};
this.get('#/404', function() {
this.partial('templates/404.template', {}, function(html) { this.get('#/404', function() {
$('#todo-list').html(html); this.partial('templates/404.template', {}, function(html) {
}); $('#todo-list').html(html);
}); });
});
this.get('#/list/:id', function() {
var list = Lists.get(this.params['id']); this.get('#/list/:id', function() {
if (list) { var list = Lists.get(this.params['id']);
this.partial('templates/todolist.template', { if (list) {
list: list, this.partial('templates/todolist.template', {
todos: Todos.filter('listId', list.id) list: list,
}, function(html) { todos: Todos.filter('listId', list.id)
$('#todo-list').html(html); }, function(html) {
}); $('#todo-list').html(html);
} else { });
this.notFound(); } else {
} this.notFound();
}); }
});
// events
this.bind('run', function(e, data) { // events
var context = this; this.bind('run', function(e, data) {
var context = this;
var title = localStorage.getItem('title') || "Todos";
$('h1').text(title); var title = localStorage.getItem('title') || "Todos";
$('h1').text(title);
if(Lists._data.length <=0){ if(Lists._data.length <=0){
var list = Lists.create({ name: 'My new list' }); var list = Lists.create({ name: 'My new list' });
//app.trigger('updateLists'); //app.trigger('updateLists');
} }
$('#new-todo').keydown(function(e) { $('#new-todo').keydown(function(e) {
if (e.keyCode == 13){ if (e.keyCode == ESCAPE_KEY){
var todoContent = $(this).val(); var todoContent = $(this).val();
var todo = Todos.create({ name: todoContent, done: false, listId: parseInt($('h2').attr('data-id'), 10) }); var todo = Todos.create({ name: todoContent, done: false, listId: parseInt($('h2').attr('data-id'), 10) });
context.partial('templates/todo.template', todo, function(html) { context.partial('templates/todo.template', todo, function(html) {
...@@ -50,153 +51,153 @@ ...@@ -50,153 +51,153 @@
}); });
$(this).val(''); $(this).val('');
} }
}); });
$('.trashcan') $('.trashcan')
.live('click', function() { .live('click', function() {
var $this = $(this); var $this = $(this);
app.trigger('delete', { app.trigger('delete', {
type: $this.attr('data-type'), type: $this.attr('data-type'),
id: $this.attr('data-id') id: $this.attr('data-id')
}); });
}); });
//new //new
$('.check') $('.check')
.live('click', function() { .live('click', function() {
var $this = $(this), var $this = $(this),
$li = $this.parents('li').toggleClass('done'), $li = $this.parents('li').toggleClass('done'),
isDone = $li.is('.done'); isDone = $li.is('.done');
app.trigger('mark' + (isDone ? 'Done' : 'Undone'), { id: $li.attr('data-id') }); app.trigger('mark' + (isDone ? 'Done' : 'Undone'), { id: $li.attr('data-id') });
}); });
$('[contenteditable]') $('[contenteditable]')
.live('focus', function() { .live('focus', function() {
// store the current value // store the current value
$.data(this, 'prevValue', $(this).text()); $.data(this, 'prevValue', $(this).text());
}) })
.live('blur', function() { .live('blur', function() {
var $this = $(this), var $this = $(this),
// grab the, likely, modified value // grab the, likely, modified value
text = $.trim($this.text()); text = $.trim($this.text());
if (!text) { if (!text) {
// restore the previous value if text is empty // restore the previous value if text is empty
$this.text($.data(this, 'prevValue')); $this.text($.data(this, 'prevValue'));
} else { } else {
if ($this.is('h1')) { if ($this.is('h1')) {
// it is the title // it is the title
localStorage.setItem('title', text); localStorage.setItem('title', text);
} else { } else {
// save it // save it
app.trigger('save', { app.trigger('save', {
type: $this.attr('data-type'), type: $this.attr('data-type'),
id: $this.attr('data-id'), id: $this.attr('data-id'),
name: text name: text
}); });
} }
} }
}) })
.live('keypress', function(event) { .live('keypress', function(event) {
// save on enter // save on enter
if (event.which === 13) { if (event.which === 13) {
this.blur(); this.blur();
return false; return false;
} }
}); });
if (!localStorage.getItem('initialized')) { if (!localStorage.getItem('initialized')) {
// create first list and todo // create first list and todo
var listId = Lists.create({ var listId = Lists.create({
name: 'My first list' name: 'My first list'
}).id; }).id;
/* /*
Todos.create({ Todos.create({
name: 'My first todo', name: 'My first todo',
done: false, done: false,
listId: listId listId: listId
}); });
*/ */
localStorage.setItem('initialized', 'yup'); localStorage.setItem('initialized', 'yup');
this.redirect('#/list/'+listId); this.redirect('#/list/'+listId);
} else { } else {
var lastViewedOrFirstList = localStorage.getItem('lastviewed') || '#/list/' + Lists.first().id; var lastViewedOrFirstList = localStorage.getItem('lastviewed') || '#/list/' + Lists.first().id;
this.redirect(lastViewedOrFirstList); this.redirect(lastViewedOrFirstList);
} }
}); });
/*save the route as the lastviewed item*/ /*save the route as the lastviewed item*/
this.bind('route-found', function(e, data) { this.bind('route-found', function(e, data) {
localStorage.setItem('lastviewed', document.location.hash); localStorage.setItem('lastviewed', document.location.hash);
}); });
this.bind('save', function(e, data) { this.bind('save', function(e, data) {
var model = data.type == 'todo' ? Todos : Lists; var model = data.type == 'todo' ? Todos : Lists;
model.update(data.id, { name: data.name }); model.update(data.id, { name: data.name });
}); });
/*marking the selected item as done*/ /*marking the selected item as done*/
this.bind('markDone', function(e, data) { this.bind('markDone', function(e, data) {
Todos.update(data.id, { done: true }); Todos.update(data.id, { done: true });
}); });
/*mark the todo with the selected id as not done*/ /*mark the todo with the selected id as not done*/
this.bind('markUndone', function(e, data) { this.bind('markUndone', function(e, data) {
Todos.update(data.id, { done: false }); Todos.update(data.id, { done: false });
}); });
this.bind('delete', function(e, data) { this.bind('delete', function(e, data) {
//if (confirm('Are you sure you want to delete this ' + data.type + '?')) { //if (confirm('Are you sure you want to delete this ' + data.type + '?')) {
var model = data.type == 'list' ? Lists : Todos; var model = data.type == 'list' ? Lists : Todos;
model.destroy(data.id); model.destroy(data.id);
if (data.type == 'list') { if (data.type == 'list') {
var list = Lists.first(); var list = Lists.first();
if (list) { if (list) {
this.redirect('#/list/'+list.id); this.redirect('#/list/'+list.id);
} else { } else {
// create first list and todo // create first list and todo
var listId = Lists.create({ var listId = Lists.create({
name: 'Initial list' name: 'Initial list'
}).id; }).id;
Todos.create({ Todos.create({
name: 'A sample todo item', name: 'A sample todo item',
done: false, done: false,
listId: listId listId: listId
}); });
this.redirect('#/list/'+listId); this.redirect('#/list/'+listId);
} }
} else { } else {
// delete the todo from the view // delete the todo from the view
$('li[data-id=' + data.id + ']').remove(); $('li[data-id=' + data.id + ']').remove();
} }
}); });
}); });
// lists model // lists model
Lists = Object.create(Model); Lists = Object.create(Model);
Lists.name = 'lists-sammyjs'; Lists.name = 'lists-sammyjs';
Lists.init(); Lists.init();
// todos model // todos model
Todos = Object.create(Model); Todos = Object.create(Model);
Todos.name = 'todos-sammyjs'; Todos.name = 'todos-sammyjs';
Todos.init(); Todos.init();
$(function() { app.run(); }); $(function() { app.run(); });
})(jQuery); })(jQuery);
(function(f){var d={};Sammy=Sammy||{};Sammy.Template=function(g,e){e||(e="template");g.helper(e,function(a,c,b){"undefined"==typeof b&&(b=a);a:{c=f.extend({},this,c);if(d[b])fn=d[b];else{if("undefined"==typeof a){a=!1;break a}fn=d[b]=new Function("obj",'var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push("'+a.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("")+ (function (factory) {
"\");}return p.join('');")}a="undefined"!=typeof c?fn(c):fn}return a})}})(jQuery); if (typeof define === 'function' && define.amd) {
\ No newline at end of file define(['jquery', 'sammy'], factory);
} else {
(window.Sammy = window.Sammy || {}).Template = factory(window.jQuery, window.Sammy);
}
}(function ($, Sammy) {
// 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
// Backport of escapeHTML from sammy.js 0.7
var _escapeHTML = function(s) {
return String(s).replace(/&(?!\w+;)/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
};
var srender_cache = {};
var srender = function(name, template, data, options) {
var fn, escaped_string;
// 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;
}
// If options escape_html is false, dont escape the contents by default
if (options && options.escape_html === false) {
escaped_string = "\",$1,\"";
} else {
escaped_string = "\",h($1),\"";
}
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
fn = srender_cache[name] = new Function("obj",
"var ___$$$___=[],print=function(){___$$$___.push.apply(___$$$___,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){___$$$___.push(\"" +
// Convert the template into pure JavaScript
String(template)
.replace(/[\r\t\n]/g, " ")
.replace(/\"/g, '\\"')
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)/g, "$1\r")
.replace(/\t=(.*?)%>/g, escaped_string)
.replace(/\t!(.*?)%>/g, "\",$1,\"")
.split("\t").join("\");")
.split("%>").join("___$$$___.push(\"")
.split("\r").join("")
+ "\");}return ___$$$___.join('');");
}
if (typeof data != 'undefined') {
return fn(data);
} else {
return fn;
}
};
// `Sammy.Template` 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):
//
// // 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:
//
// // app.js
// $.sammy(function() {
// // include the plugin
// this.use('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');
// });
//
// By default, the data passed into the tempalate is passed automatically passed through
// Sammy's `escapeHTML` method in order to prevent possible XSS attacks. This is
// a problem though if you're using something like `Sammy.Form` which renders HTML
// within the templates. You can get around this in two ways. One, you can use the
// `<%! %>` instead of `<%= %>`. Two, you can pass the `escape_html = false` option
// when interpolating, i.e:
//
// this.get('#/', function() {
// this.template('myform.tpl', {form: "<form></form>"}, {escape_html: false});
// });
//
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, options) {
// use name for caching
if (typeof name == 'undefined') { name = template; }
if (typeof options == 'undefined' && typeof name == 'object') {
options = name; name = template;
}
return srender(name, template, $.extend({h: _escapeHTML}, this, data), options);
};
// set the default method name/extension
if (!method_alias) { method_alias = 'template'; }
// create the helper at the method alias
app.helper(method_alias, template);
};
return Sammy.Template;
}));
<li data-type="todo" data-id="<%= id %>" class="<%= done ? 'done' : '' %>"> <li data-type="todo" data-id="<%= id %>" class="<%= done ? 'done' : '' %>">
<div class="todo"> <div class="todo">
<div class="display"> <div class="display">
<input class="check" type="checkbox" <%= done ? 'checked' : '' %>/> <input class="check" type="checkbox" <%= done ? 'checked' : '' %>/>
<span class="trashcan" data-type="todo" data-id="<%= id %>"></span> <span class="trashcan" data-type="todo" data-id="<%= id %>"></span>
<span contenteditable="true" data-type="todo" data-id="<%= id %>" class="todo-item"><%= name %></span> <span contenteditable="true" data-type="todo" data-id="<%= id %>" class="todo-item"><%= name %></span>
</div> </div>
</div> </div>
</li> </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