Separation of Concerns
Keeping different aspects of an
application separate
Separation of Concerns
So that everyone can focus on one thing
at a time
Unobtrusive Javascript
Techniques to enforce separation of javascript from
other web technologies
Web Technologies
Html
Css
Javascript
Server side
Content
Presentation
Presentation Logic
Business Logic
Rules of the Game
1 Javascript stays in its own files
2 Files are affected only by changes directly related to presentation logic
Game Strategy
is improving quality of existing code without changing its functional
behavior
Code refactoringHow do we play?
Game Strategy
No refactoringwithout testing
• unit testing with jsTestDriver & friends• minimal functional testing with selenium & friends• mock the server by wrapping XMLHttpRequest
1 Round
<script> doSomething(); // ... more code ...</script>
Html You see an inline script
1 Bad Smell
<script> doSomething(); // ... more code ...</script>
Html
Hey, it’s javascript in
html
1 Refactoring:Externalize Inline Script
Html Js
<script> doSomething(); // ... more code ...</script>
Html
<script src="myjavascript.js"></script>
Js
doSomething(); // ... more code ...
1 Refactoring:Externalize Inline Script
2 Round
Html You see an event handler registrationby element attribute
<buttononclick="refreshView();"/>
Refresh</button>
2 Bad Smell
Html
onclick="refreshView();"/>
Hey, it’s javascript in
html
Refresh</button>
<button
2 Refactoring:Attribute Event to Dom Event
Html
<button
Refresh</button>
onclick="refreshView();"/>
Js
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>
3 Round
Html
You see a javascript linkhref="javascript:showCredits();">
Show Credits</a>
<a
3 Bad Smell
Html
href="javascript:showCredits();">
Hey, it’s javascript in
html
Show Credits</a>
<a
3 Refactoring:Javascript Link to Click Event
Html Js
href="">
Show Credits</a>
<a
javascript:showCredits();
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>
Game Break
Now HTML should be Javascript free
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";
}
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;
}
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";
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" );
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!");
}
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!");
}
5 Refactoring:Business Logic Simple Test
Js
var account = JSON.parse(response
);
if () {
show("can’t transfer money!");
}
Server
account.balance < 0
<?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
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);
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);
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);
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
Game Extra Time
Make javascriptplay well with
other javascript
7 Round
Js
You see code placed outside
a function
var counter = 0;// ... more code ...
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 ...
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;
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
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) { // ...}
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!");
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
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
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!");
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
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");}
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
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
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
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
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
Game Over
Separation of concerns