refactoring to unobtrusive javascript
DESCRIPTION
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/federicogalassiTRANSCRIPT
![Page 2: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/2.jpg)
Separation of Concerns
Keeping different aspects of an
application separate
![Page 3: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/3.jpg)
Separation of Concerns
So that everyone can focus on one thing
at a time
![Page 4: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/4.jpg)
Unobtrusive Javascript
Techniques to enforce separation of javascript from
other web technologies
![Page 5: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/5.jpg)
Web Technologies
Html
Css
Javascript
Server side
Content
Presentation
Presentation Logic
Business Logic
![Page 6: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/6.jpg)
Rules of the Game
1 Javascript stays in its own files
2 Files are affected only by changes directly related to presentation logic
![Page 7: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/7.jpg)
Game Strategy
is improving quality of existing code without changing its functional
behavior
Code refactoringHow do we play?
![Page 8: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/8.jpg)
Game Strategy
No refactoringwithout testing
• unit testing with jsTestDriver & friends• minimal functional testing with selenium & friends• mock the server by wrapping XMLHttpRequest
![Page 9: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/9.jpg)
1 Round
<script> doSomething(); // ... more code ...</script>
Html You see an inline script
![Page 10: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/10.jpg)
1 Bad Smell
<script> doSomething(); // ... more code ...</script>
Html
Hey, it’s javascript in
html
![Page 11: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/11.jpg)
1 Refactoring:Externalize Inline Script
Html Js
<script> doSomething(); // ... more code ...</script>
![Page 12: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/12.jpg)
Html
<script src="myjavascript.js"></script>
Js
doSomething(); // ... more code ...
1 Refactoring:Externalize Inline Script
![Page 13: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/13.jpg)
2 Round
Html You see an event handler registrationby element attribute
<buttononclick="refreshView();"/>
Refresh</button>
![Page 14: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/14.jpg)
2 Bad Smell
Html
onclick="refreshView();"/>
Hey, it’s javascript in
html
Refresh</button>
<button
![Page 15: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/15.jpg)
2 Refactoring:Attribute Event to Dom Event
Html
<button
Refresh</button>
onclick="refreshView();"/>
Js
![Page 16: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/16.jpg)
2 Refactoring:Attribute Event to Dom Event
Html
<!-- add id to locate it --><button id="btnRefresh"> Refresh</button>
var btnrefresh = document.getElementById(
"btnRefresh"); btnrefresh.addEventListener(
"click",refreshView,false
);
Js
<!-- loaded after DOM is built --> <script src="myjavascript.js"> </script></body></html>
![Page 17: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/17.jpg)
3 Round
Html
You see a javascript linkhref="javascript:showCredits();">
Show Credits</a>
<a
![Page 18: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/18.jpg)
3 Bad Smell
Html
href="javascript:showCredits();">
Hey, it’s javascript in
html
Show Credits</a>
<a
![Page 19: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/19.jpg)
3 Refactoring:Javascript Link to Click Event
Html Js
href="">
Show Credits</a>
<a
javascript:showCredits();
![Page 20: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/20.jpg)
3 Refactoring:Javascript Link to Click Event
Html Js
href="#">
Show Credits</a>
<!-- add id to locate it --><a id="linkShowCredits"
var showcredits = document.getElementById( "linkShowCredits");
// in refreshView you should// event.preventDefault showcredits.addEventListener( "click", refreshView, false);
<!-- loaded after DOM is built --> <script src="myjavascript.js"> </script></body></html>
![Page 21: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/21.jpg)
Game Break
Now HTML should be Javascript free
![Page 22: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/22.jpg)
4 Round
Js You see presentation
set by element.style properties
div.onclick = function(e) {
// div has been selected var clicked = this; clicked.style.border = "1px solid blue";
}
![Page 23: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/23.jpg)
4 Bad Smell
Js
clicked.style.border = "1px solid blue"; Hey, it’s
css in javascript
div.onclick = function(e) {
// div has been selected var clicked = this;
}
![Page 24: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/24.jpg)
4 Refactoring:Dynamic Style to Css Class
Js
div.onclick = function(e) {
// div has been selected var clicked = this;
}
Css
clicked.style.border = "1px solid blue";
![Page 25: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/25.jpg)
4 Refactoring:Dynamic Style to Css Class
Js
div.onclick = function(e) {
// div has been selected var clicked = this;
}
Css
.selected: { border: 1px solid blue;}
// should be addClassclicked.setAttribute( "class", "selected" );
![Page 26: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/26.jpg)
5 Round
Js You test a complex boolean
expression which is not presentation
var account = JSON.parse(response
);if (account.balance < 0) {
show("can’t transfer money!");
}
![Page 27: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/27.jpg)
5 Bad Smell
Js
if (account.balance < 0) {
Hey, it’s business logic in javascript
var account = JSON.parse(response
);
show("can’t transfer money!");
}
![Page 28: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/28.jpg)
5 Refactoring:Business Logic Simple Test
Js
var account = JSON.parse(response
);
if () {
show("can’t transfer money!");
}
Server
account.balance < 0
![Page 29: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/29.jpg)
<?php // use business logic to decide $account["canTransfer"] = false; echo json_encode($account);?>
5 Refactoring:Business Logic Simple Test
Js
var account = JSON.parse(response
);
if (account.canTransfer) {
show("can’t transfer money!");
}
Server
![Page 30: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/30.jpg)
6 Round
You see complex
dom code to generate html
Js
// add book to the listvar book = doc.createElement("li");var title = doc.createElement("strong");titletext = doc.createTextNode(name);title.appendChild(titletext);var cover = doc.createElement("img");cover.src = url;book.appendChild(cover);book.appendChild(title);bookList.appendChild(book);
![Page 31: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/31.jpg)
6 Bad Smell
Js
Hey, it’shtml in
javascript
// 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);
![Page 32: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/32.jpg)
6 Refactoring:Dom Creation to Html Template
Js
// add book to the list
Html
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);
![Page 33: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/33.jpg)
Js
// add book to the list
Html
<li> <strong>Title</strong> <img src="Cover" /></li>
var tplBook = loadTemplate( "book_tpl.html");var book = tplBook.substitute({ Title: name, Cover: coverurl });bookList.appendChild(book);
6 Refactoring:Dom Creation to Html Template
![Page 34: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/34.jpg)
Game Extra Time
Make javascriptplay well with
other javascript
![Page 35: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/35.jpg)
7 Round
Js
You see code placed outside
a function
var counter = 0;// ... more code ...
![Page 36: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/36.jpg)
7 Bad Smell
Hey, it’s aglobal variable
Other Js
// redeclares previous// counter !!var counter = 1;
Js
// ... later ...// counter is 1
var counter = 0;// ... more code ...
![Page 37: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/37.jpg)
Js
// ... later ...// counter is 1
var counter = 0;// ... more code ...
7 Refactoring:Global Abatement
Using the Module patternwe can make it
private
Other Js
// redeclares previous// counter !!var counter = 1;
![Page 38: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/38.jpg)
Js(function() { var counter = 0; // ... more code ...
})();
// ... later ...// counter is still 0
Other Js
// don’t see previous// countervar counter = 1;
7 Refactoring:Global Abatement
Wrap code in an anonymous function whichis immediately
invoked
![Page 39: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/39.jpg)
8 Round
You see anevent handler
which callsmany unrelated
modules
Login JsbtnLogin.onclick = function(e){ login(); toolbar.update(); logger.log("login!");}
Toolbar Js
Logger Js
function update() { // ...}
function log(msg) { // ...}
![Page 40: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/40.jpg)
btnLogin.onclick = function(e){ login();
}
Login Js
Toolbar Js
Logger Js
function update() { // ...}
function log(msg) { // ...}
8 Bad Smell
Hey, it’sother modules
javascript
toolbar.update();logger.log("login!");
![Page 41: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/41.jpg)
Login JsbtnLogin.onclick = function(e){ login();
}
Toolbar Js
Logger Js
function update() { // ...}
function log(msg) { // ...}
8 Impact
Time coupling issues
Not initialized
toolbar.update();logger.log("login!");
FAIL
![Page 42: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/42.jpg)
Login JsbtnLogin.onclick = function(e){ login();
}
Toolbar Js
Logger Js
function update() { // ...}
function log(msg) { // ...}
8 Impact
Divergent change
Friends Js
function notify(event) { // ...}
Added
toolbar.update();logger.log("login!");friends.notify("login");
Needs Change
![Page 43: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/43.jpg)
Login JsbtnLogin.onclick = function(e){ login();
}
Toolbar Js
Logger Js
8 Refactoring:Custom Events
toolbar.update();
Custom eventsinvert
dependencyand make
code readable
function update() {
}
function log(msg) {
}logger.log("login!");
![Page 44: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/44.jpg)
Login JsbtnLogin.onclick = function(e){ login(); event.fire("login");
}
Toolbar Js
Logger Js
function update() {...}
function log(msg) {...}
8 Refactoring:Custom Events
event.listen("login", function(e) { log("login attempt");});
event.listen("login", function(e) { update();});
Fire an high level custom
event and make other modules
listen to it
![Page 45: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/45.jpg)
9 Round
Js
You see many similar event
handlers
// home getElementById "tabHome"// news getElementById "tabNews"// about getElementById "tabAbout"
home.onclick = function() { showPage("pageHome");}news.onclick = function() { showPage("pageNews");}about.onclick = function() { showPage("pageAbout");}
![Page 46: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/46.jpg)
9 Bad Smell
Js
// home getElementById "tabHome"// news getElementById "tabNews"// about getElementById "tabAbout"
home.onclick = function() { showPage("pageHome");}news.onclick = function() { showPage("pageNews");}about.onclick = function() { showPage("pageAbout");}
Hey, it’sduplicated javascript
![Page 47: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/47.jpg)
9 Impact
Js
// home getElementById "tabHome"// news getElementById "tabNews"// about getElementById "tabAbout"
home.onclick = function() { showPage("pageHome");}news.onclick = function() { showPage("pageNews");}about.onclick = function() { showPage("pageAbout");}contact.onclick = function() { showPage("pageContact");}
Needs Change
More tabsmore code
more handlersmore memory
usage
![Page 48: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/48.jpg)
9 Impact
Need to trackif new elements
are added to register handlers
Js
// home getElementById "tabHome"// news getElementById "tabNews"// about getElementById "tabAbout"
home.onclick = function() { showPage("pageHome");}news.onclick = function() { showPage("pageNews");}about.onclick = function() { showPage("pageAbout");}
Contact JstabContainer.addChild( "tabContact");
Missing click handler
![Page 49: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/49.jpg)
9 Refactoring:Events Delegation
Js
// home getElementById "tabHome"// news getElementById "tabNews"// about getElementById "tabAbout"
home.onclick = function() { showPage("pageHome");}news.onclick = function() { showPage("pageNews");}about.onclick = function() { showPage("pageAbout");}
Event delegation makes code
more compact and
maintainable
![Page 50: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/50.jpg)
9 Refactoring:Events Delegation
Js
// container getElementById// "tabContainer"
container.onclick = function(e) { var id = e.target.id var page = id.replace( "tab", "page" ); showPage(page);}
Handle the event in an elements
ancestor.Bubbling makes
it work
![Page 51: Refactoring to Unobtrusive Javascript](https://reader033.vdocuments.net/reader033/viewer/2022061201/5478e868b37959532b8b45f3/html5/thumbnails/51.jpg)
Game Over
Separation of concerns