Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
todomvc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Eugene Shen
todomvc
Commits
aa3b74ed
Commit
aa3b74ed
authored
Feb 07, 2013
by
Pascal Hartig
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Sammy.js: Whitespace + XSS fixes
parent
dd92c429
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
341 additions
and
192 deletions
+341
-192
labs/architecture-examples/sammyjs/js/app.js
labs/architecture-examples/sammyjs/js/app.js
+185
-184
labs/architecture-examples/sammyjs/js/lib/sammy.template.js
labs/architecture-examples/sammyjs/js/lib/sammy.template.js
+152
-2
labs/architecture-examples/sammyjs/templates/todo.template
labs/architecture-examples/sammyjs/templates/todo.template
+4
-6
No files found.
labs/architecture-examples/sammyjs/js/app.js
View file @
aa3b74ed
(
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
'
)
||
"
Todos
"
;
$
(
'
h1
'
).
text
(
title
);
var
ESCAPE_KEY
=
13
;
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
'
)
||
"
Todos
"
;
$
(
'
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
){
if
(
e
.
keyCode
==
ESCAPE_KEY
){
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
)
{
...
...
@@ -50,153 +51,153 @@
});
$
(
this
).
val
(
''
);
}
});
$
(
'
.trashcan
'
)
.
live
(
'
click
'
,
function
()
{
var
$this
=
$
(
this
);
app
.
trigger
(
'
delete
'
,
{
type
:
$this
.
attr
(
'
data-type
'
),
id
:
$this
.
attr
(
'
data-id
'
)
});
});
$
(
'
.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
'
)
});
});
$
(
'
[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
);
}
});
/*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
});
});
$
(
'
.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
'
)
});
});
$
(
'
[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
);
}
});
/*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
});
});
/*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
);
}
}
else
{
// delete the todo from the view
$
(
'
li[data-id=
'
+
data
.
id
+
'
]
'
).
remove
();
}
});
});
// lists model
Lists
=
Object
.
create
(
Model
);
Lists
.
name
=
'
lists-sammyjs
'
;
Lists
.
init
();
// todos model
Todos
=
Object
.
create
(
Model
);
Todos
.
name
=
'
todos-sammyjs
'
;
Todos
.
init
();
$
(
function
()
{
app
.
run
();
});
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
);
}
}
else
{
// delete the todo from the view
$
(
'
li[data-id=
'
+
data
.
id
+
'
]
'
).
remove
();
}
});
});
// lists model
Lists
=
Object
.
create
(
Model
);
Lists
.
name
=
'
lists-sammyjs
'
;
Lists
.
init
();
// todos model
Todos
=
Object
.
create
(
Model
);
Todos
.
name
=
'
todos-sammyjs
'
;
Todos
.
init
();
$
(
function
()
{
app
.
run
();
});
})(
jQuery
);
labs/architecture-examples/sammyjs/js/lib/sammy.template.js
View file @
aa3b74ed
(
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
(
""
)
+
"
\"
);}return p.join('');
"
)}
a
=
"
undefined
"
!=
typeof
c
?
fn
(
c
):
fn
}
return
a
})}})(
jQuery
);
\ No newline at end of file
(
function
(
factory
)
{
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
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
,
'
&
'
).
replace
(
/</g
,
'
<
'
).
replace
(
/>/g
,
'
>
'
).
replace
(
/"/g
,
'
"
'
);
};
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
;
}));
labs/architecture-examples/sammyjs/templates/todo.template
View file @
aa3b74ed
<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>
<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>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment