Top Banner
Refactoring to Unobtrusive Javascript Federico Galassi [email protected] http://federico.galassi.net /
51

Refactoring to Unobtrusive Javascript

Nov 29, 2014

Download

Technology

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
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 2: Refactoring to Unobtrusive Javascript

Separation of Concerns

Keeping different aspects of an

application separate

Page 3: Refactoring to Unobtrusive Javascript

Separation of Concerns

So that everyone can focus on one thing

at a time

Page 4: Refactoring to Unobtrusive Javascript

Unobtrusive Javascript

Techniques to enforce separation of javascript from

other web technologies

Page 5: Refactoring to Unobtrusive Javascript

Web Technologies

Html

Css

Javascript

Server side

Content

Presentation

Presentation Logic

Business Logic

Page 6: Refactoring to Unobtrusive Javascript

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

Game Strategy

is improving quality of existing code without changing its functional

behavior

Code refactoringHow do we play?

Page 8: Refactoring to Unobtrusive Javascript

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

1 Round

<script> doSomething(); // ... more code ...</script>

Html You see an inline script

Page 10: Refactoring to Unobtrusive Javascript

1 Bad Smell

<script> doSomething(); // ... more code ...</script>

Html

Hey, it’s javascript in

html

Page 11: Refactoring to Unobtrusive Javascript

1 Refactoring:Externalize Inline Script

Html Js

<script> doSomething(); // ... more code ...</script>

Page 12: Refactoring to Unobtrusive Javascript

Html

<script src="myjavascript.js"></script>

Js

doSomething(); // ... more code ...

1 Refactoring:Externalize Inline Script

Page 13: Refactoring to Unobtrusive Javascript

2 Round

Html You see an event handler registrationby element attribute

<buttononclick="refreshView();"/>

Refresh</button>

Page 14: Refactoring to Unobtrusive Javascript

2 Bad Smell

Html

onclick="refreshView();"/>

Hey, it’s javascript in

html

Refresh</button>

<button

Page 15: Refactoring to Unobtrusive Javascript

2 Refactoring:Attribute Event to Dom Event

Html

<button

Refresh</button>

onclick="refreshView();"/>

Js

Page 16: Refactoring to Unobtrusive Javascript

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

3 Round

Html

You see a javascript linkhref="javascript:showCredits();">

Show Credits</a>

<a

Page 18: Refactoring to Unobtrusive Javascript

3 Bad Smell

Html

href="javascript:showCredits();">

Hey, it’s javascript in

html

Show Credits</a>

<a

Page 19: Refactoring to Unobtrusive Javascript

3 Refactoring:Javascript Link to Click Event

Html Js

href="">

Show Credits</a>

<a

javascript:showCredits();

Page 20: Refactoring to Unobtrusive Javascript

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

Game Break

Now HTML should be Javascript free

Page 22: Refactoring to Unobtrusive Javascript

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

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

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

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

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

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

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

<?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

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

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

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

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

Game Extra Time

Make javascriptplay well with

other javascript

Page 35: Refactoring to Unobtrusive Javascript

7 Round

Js

You see code placed outside

a function

var counter = 0;// ... more code ...

Page 36: Refactoring to Unobtrusive Javascript

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Game Over

Separation of concerns