Refactoring to Unobtrusive Javascript

Post on 29-Nov-2014

10048 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

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/federicogalassi

Transcript

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

top related