Talk i gave at JsCamp09 on September 25th 2009.
Slides revised, corrected and expanded.
Also
http://www.javascriptcamp.com/
Follow me on Twitter!
https://twitter.com/federicogalassi
4. Unobtrusive Javascript
Techniques to
enforce separation
of javascript from
other web
technologies
5. Web Technologies
Html Content
Css Presentation
Javascript Presentation Logic
Server side Business Logic
6. Rules of the Game
1 Javascript stays in its own files
2 Files are affected only by
changes directly related to
presentation logic
7. Game Strategy
How do we play?
Code refactoring
is improving quality of
existing code without
changing its functional
behavior
8. Game Strategy
No refactoring
without testing
• unit testing with jsTestDriver & friends
• minimal functional testing with selenium & friends
• mock the server by wrapping XMLHttpRequest
9. 1 Round
Html
You see an
<script>
doSomething();
// ... more code ...
inline script
</script>
10. 1 Bad Smell
Html
<script>
doSomething();
Hey, it’s
// ... more code ...
</script>
javascript in
html
12. 1 Refactoring:
Externalize Inline Script
Html Js
<script src="myjavascript.js">
doSomething();
</script>
// ... more code ...
13. 2 Round
Html
You see an
<button
onclick="refreshView();"/>
event handler
registration
Refresh
</button>
by element
attribute
14. 2 Bad Smell
Html
<button
onclick="refreshView();"/>
Hey, it’s
Refresh
</button>
javascript in
html
15. 2 Refactoring:
Attribute Event to Dom Event
Html Js
<button
onclick="refreshView();"/>
Refresh
</button>
16. 2 Refactoring:
Attribute Event to Dom Event
Html Js
<!-- add id to locate it --> var btnrefresh =
<button id="btnRefresh"> document.getElementById(
Refresh "btnRefresh"
</button> );
btnrefresh.addEventListener(
"click",
refreshView,
false
);
<!-- loaded after DOM is built -->
<script src="myjavascript.js">
</script>
</body>
</html>
17. 3 Round
Html
You see a
javascript link
<a
href="javascript:showCredits();">
Show Credits</a>
18. 3 Bad Smell
Html
<a
href="javascript:showCredits();">
Hey, it’s
Show Credits</a>
javascript in
html
19. 3 Refactoring:
Javascript Link to Click Event
Html Js
<a
href=""> javascript:showCredits();
Show Credits</a>
20. 3 Refactoring:
Javascript Link to Click Event
Html Js
var showcredits =
<!-- add id to locate it -->
document.getElementById(
<a id="linkShowCredits"
"linkShowCredits"
href="#"> );
Show Credits</a>
// in refreshView you should
// event.preventDefault
showcredits.addEventListener(
"click",
refreshView,
false
);
<!-- loaded after DOM is built -->
<script src="myjavascript.js">
</script>
</body>
</html>
22. 4 Round
Js
You see
div.onclick = function(e) {
// div has been selected
presentation
set by
var clicked = this;
clicked.style.border =
"1px solid blue";
element.style
}
properties
23. 4 Bad Smell
Js
div.onclick = function(e) {
// div has been selected
Hey, it’s
var clicked = this;
clicked.style.border =
"1px solid blue";
css in
}
javascript
24. 4 Refactoring:
Dynamic Style to Css Class
Js Css
div.onclick = function(e) {
// div has been selected
var clicked = this;
clicked.style.border =
"1px solid blue";
}
25. 4 Refactoring:
Dynamic Style to Css Class
Js Css
div.onclick = function(e) {
// div has been selected
var clicked = this;
.selected: {
// should be addClass
border: 1px solid blue;
clicked.setAttribute(
}
"class",
"selected"
);
}
26. 5 Round
Js You test a
var account = JSON.parse(
complex
boolean
response
);
if (account.balance < 0) {
expression
show("can’t transfer money!");
}
which is not
presentation
27. 5 Bad Smell
Js
var account = JSON.parse(
response
);
Hey, it’s
if (account.balance < 0) {
show("can’t transfer money!");
}
business logic
in javascript
28. 5 Refactoring:
Business Logic Simple Test
Js Server
var account = JSON.parse(
response
);
if () { account.balance < 0
show("can’t transfer money!");
}
29. 5 Refactoring:
Business Logic Simple Test
Js Server
var account = JSON.parse(
response
<?php
);
// use business logic to decide
if (account.canTransfer) { $account["canTransfer"] = false;
echo json_encode($account);
show("can’t transfer money!"); ?>
}
30. 6 Round
Js
// add book to the list
var book = doc.createElement("li");
You see
complex
var title = doc.createElement("strong");
titletext = doc.createTextNode(name);
title.appendChild(titletext);
var cover = doc.createElement("img");
dom code to
cover.src = url;
book.appendChild(cover);
book.appendChild(title);
bookList.appendChild(book);
generate html
31. 6 Bad Smell
Js
// add book to the list
var book = doc.createElement("li");
var title = doc.createElement("strong");
titletext = doc.createTextNode(name);
Hey, it’s
title.appendChild(titletext);
var cover = doc.createElement("img");
cover.src = coverurl;
book.appendChild(cover);
html in
book.appendChild(title);
bookList.appendChild(book);
javascript
32. 6 Refactoring:
Dom Creation to Html Template
Js Html
// add book to the list
var book = doc.createElement("li");
var title = doc.createElement("strong");
titletext = doc.createTextNode(name);
title.appendChild(titletext);
var cover = doc.createElement("img");
cover.src = coverurl;
book.appendChild(cover);
book.appendChild(title);
bookList.appendChild(book);
33. 6 Refactoring:
Dom Creation to Html Template
Js Html
// add book to the list
var tplBook = loadTemplate(
"book_tpl.html"
<li>
);
<strong>Title</strong>
var book = tplBook.substitute({
<img src="Cover" />
Title: name,
</li>
Cover: coverurl
});
bookList.appendChild(book);
35. 7 Round
Js
var counter = 0;
// ... more code ...
You see code
placed outside
a function
36. 7 Bad Smell
Js
var counter = 0;
// ... more code ...
// ... later ...
// counter is 1
Hey, it’s a
Other Js
// redeclares previous
// counter !!
global variable
var counter = 1;
37. 7 Refactoring:
Global Abatement
Js
var counter = 0;
// ... more code ...
// ... later ...
// counter is 1
Using the
Module pattern
Other Js
// redeclares previous
we can make it
private
// counter !!
var counter = 1;
38. 7 Refactoring:
Global Abatement
Wrap code in
(function() {
Js
var counter = 0;
// ... more code ...
// ... later ...
// counter is still 0
})();
an anonymous
Other Js function which
is immediately
// don’t see previous
// counter
var counter = 1;
invoked
39. 8 Round
Login Js
btnLogin.onclick = function(e){
login();
toolbar.update();
logger.log("login!");
You see an
event handler
}
function update() {
// ...
Toolbar Js
which calls
many unrelated
}
Logger Js
function log(msg) {
}
// ...
modules
40. 8 Bad Smell
Login Js
btnLogin.onclick = function(e){
login();
toolbar.update();
logger.log("login!");
}
Toolbar Js
function update() {
Hey, it’s
// ...
}
function log(msg) {
Logger Js
other modules
javascript
// ...
}
41. 8 Impact
Login Js
btnLogin.onclick = function(e){
login();
toolbar.update();
logger.log("login!");
}
FAIL
Toolbar Js
function update() {
Time coupling
// ...
}
function log(msg) {
Logger Js
issues
t initialized
// ...
No
}
42. 8 Impact
Login Js
btnLogin.onclick = function(e){
login();
toolbar.update();
logger.log("login!");
hange
friends.notify("login");
eds C
}
Ne
Toolbar Js
function update() {
// ... Friends Js
Divergent
}
function notify(event) {
// ...
dded
}
A
Logger Js
function log(msg) {
}
// ...
change
43. 8 Refactoring:
Custom Events
Login Js
Custom events
btnLogin.onclick = function(e){
login();
invert
}
function update() {
toolbar.update();
Toolbar Js
dependency
and make
}
code readable
Logger Js
function log(msg) {
logger.log("login!");
}
44. 8 Refactoring:
Custom Events
Login Js
btnLogin.onclick = function(e){
login();
event.fire("login"); Fire an high
level custom
}
Toolbar Js
function update() {...}
event.listen("login", function(e) {
update();
event and make
other modules
});
Logger Js
listen to it
function log(msg) {...}
event.listen("login", function(e) {
log("login attempt");
});
45. 9 Round
Js
// home getElementById "tabHome"
// news getElementById "tabNews"
// about getElementById "tabAbout"
You see many
home.onclick = function() {
}
showPage("pageHome");
news.onclick = function() {
similar event
handlers
showPage("pageNews");
}
about.onclick = function() {
showPage("pageAbout");
}
47. 9 Impact
Js
// home getElementById "tabHome"
// news getElementById "tabNews"
// about getElementById "tabAbout"
home.onclick = function() {
}
showPage("pageHome"); More tabs
more code
news.onclick = function() {
showPage("pageNews");
}
about.onclick = function() {
more handlers
showPage("pageAbout");
}
contact.onclick = function() {
showPage("pageContact");
}
Needs Change more memory
usage
48. 9 Impact
Js
// home getElementById "tabHome"
Need to track
// news getElementById "tabNews"
// about getElementById "tabAbout"
home.onclick = function() {
showPage("pageHome");
if new elements
}
news.onclick = function() {
showPage("pageNews");
}
about.onclick = function() {
are added to
showPage("pageAbout");
}
Contact Js
tabContainer.addChild(
"tabContact"
register handlers
);
Missing click handler
49. 9 Refactoring:
Events Delegation
Js
Event delegation
makes code
// home getElementById "tabHome"
// news getElementById "tabNews"
// about getElementById "tabAbout"
home.onclick = function() {
more compact
showPage("pageHome");
}
news.onclick = function() {
showPage("pageNews");
}
and
about.onclick = function() {
showPage("pageAbout");
}
maintainable
50. 9 Refactoring:
Events Delegation
Handle the event
Js
in an elements
// container getElementById
// "tabContainer"
container.onclick = function(e) {
ancestor.
var id = e.target.id
var page = id.replace(
"tab", "page"
);
showPage(page);
Bubbling makes
}
it work