Slide 1

Slide 1 text

codecentric d.o.o Jovan Vidić, IT Consultant @ codecentric Refactoring Java Script Applications

Slide 2

Slide 2 text

Motivation Should We Refactor JavaScript? Refactoring Sample Application Beyond Refactoring Agenda

Slide 3

Slide 3 text

~ 6 years of development Java + DWR 732 JavaScript files ~ 20 000 lines of javascript code Motivation - Legacy project

Slide 4

Slide 4 text

What was wrong? - JavaScript in HTML - Global functions - Inline styles - No coding style guidelines - No tests MOTIVATION - Legacy project

Slide 5

Slide 5 text

What was OK? - OOP - Throwing own errors - Wrapping third party libraries - Prototype.js MOTIVATION - Legacy project

Slide 6

Slide 6 text

Should We Refactor JavaScript ?

Slide 7

Slide 7 text

“It is not necessary to change. Survival is not mandatory” W. Edwards Deming W E Deming

Slide 8

Slide 8 text

What is Refactoring?

Slide 9

Slide 9 text

What is Refactoring? a change made to the internal structure of software to make it easier to understand and cheaper to modify without changing its observable behavior M. Fowler

Slide 10

Slide 10 text

This is not refactoring

Slide 11

Slide 11 text

agile manifesto 8th principle Agile processes promote sustainable development. The sponsors, developers, and users should be able to maintain a constant pace indefinitely.

Slide 12

Slide 12 text

Refactoring Sample Application – v1 – index.html ! ! ! ! ! ! Add Speaker! !

Slide 13

Slide 13 text

! ! ! ! ! load();! ! ! Refactoring Sample Application – v1 – index.html

Slide 14

Slide 14 text

Refactoring Sample Application – v1 – speakers.js function add() {! var speakerId = $('#speakerId').val();! ! $.ajax({! type: speakerId == '' ? "POST" : "PUT",! url: speakerId == '' ? "http://localhost:4730/speakers" : "http:/! data: JSON.stringify({id: speakerId, firstName: $('#firstName').v! contentType: "application/json; charset=utf-8",! dataType: "json",! success: function(data){ $('#speakerId').val(''); $('#firstName’)! failure: function(errMsg) {alert(errMsg);}! ! function load() {! $.getJSON( "http://localhost:4730/speakers", function( data ) {! var newRows = "";! $.each(data, function(i,item) {! var sessionTime = new Date(Date.parse(item.sessionTime));! ! newRows += "" + sessionTime.toLocaleString() +"

Slide 15

Slide 15 text

Refactoring Sample Application – v1 It is a complete mess! How should I start?

Slide 16

Slide 16 text

Refactoring Sample Application – v1 – selenium

Slide 17

Slide 17 text

Refactoring Sample Application – v1 What about JSLint/JSHIT?

Slide 18

Slide 18 text

It will scream at you!

Slide 19

Slide 19 text

Refactoring Sample Application – v2 Invert dependencies

Slide 20

Slide 20 text

Refactoring Sample Application – v2 - REQUIREJS requirejs.config({! paths: {! "jquery": "lib/jquery.min",! "bootstrap": "lib/bootstrap.min"! },! shim: {! "bootstrap": {! deps: ["jquery"]! }! }! });! ! require(["app/speakers","bootstrap"], function (speakers) {! ! $("#btnSaveSpeaker").click(function() {! speakers.save();! });! ! speakers.loadAll();! });! !

Slide 21

Slide 21 text

Refactoring Sample Application – V2 – SPEAKERS.JS & THEME.jS define(["jquery","app/theme"], function ($, theme) {! "use strict";! ! var SpeakerControler = {! loadAll : function () {! $.getJSON("http://localhost:4730/speakers", function (data) {! var speakersTable = $("." + theme.SPEAKERS_TABLE +" tbody") define([], function () {! "use strict";! ! return {! DELETE_BUTTON : "btn btn-danger glyphicon glyphicon-remove",! EDIT_BUTTON : "btn btn-default glyphicon glyphicon-pencil",! SPEAKERS_TABLE : "table-striped"! };! });!

Slide 22

Slide 22 text

Refactoring Sample Application – SPEAKERS.JS define(["jquery","app/theme"], function ($, theme) {! "use strict";! ! var SpeakerControler = {! loadAll : function () {! cell.append($("”)! SpeakerControler.remove(item.id);! })); remove : function(id) {! $.ajax({! type: "DELETE",! url: "http://localhost:4730/speaker/" + id,! success: function() {! SpeakerControler.loadAll();! }! });! }! return SpeakerControler;! });

Slide 23

Slide 23 text

My code is isolated now Can I write unit tests?

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Refactoring Sample Application – v3 Manage dependencies

Slide 26

Slide 26 text

Refactoring Sample Application – v3 – bower.json {! "name": "javascript-refactoring",! "version": "1.0",! "authors": [! "Jovan Vidic "! ],! "private": true,! "ignore": [! "node_modules",! "bower_components",! "test",! "tests"! ],! "dependencies": {! "jquery": "1.11.1",! "bootstrap" : "3.2.0",! "requirejs" : "2.1.15"! },! ! "devDependencies": {! ! }! }!

Slide 27

Slide 27 text

Refactoring Sample Application – v4 Improve design Introduce model objects & unit tests

Slide 28

Slide 28 text

Refactoring Sample Application – v4 – MODEL.JS function Speaker(id, firstName, lastName, topic, sessionTime, track) {! this.id = id;! this.firstName = firstName;! this.lastName = lastName;! this.topic = topic;! this.sessionTime = sessionTime;! this.track = track;! ! this.hasId = function () {! return (this.id !== undefined) && (this.id !== null) ! && (this.id !== "");! };! ! this.getFullName = function () {! return this.firstName + " " + this.lastName;! };! }! ! return {! "Agenda" : {! "Speaker" : Speaker! }! };!

Slide 29

Slide 29 text

Refactoring Sample Application – v4 – Speakers.JS define(["jquery", "app/model", "app/theme"], function ($, model, theme) {! "use strict";! var SpeakerControler = {! editSpeaker : function(id) {! $.getJSON( "http://localhost:4730/speaker/" + id, ! function(speakerData) {! if(speakerData) {! var speaker = convertToModel(speakerData);! showEditSpeakerPopup(speaker);! }! });! }! function showEditSpeakerPopup(speaker) {! $('#myModalLabel').html('Edit speaker ' ! + speaker.getFullName() + "");! }! function convertToModel(speakerData) {! return new model.Agenda.Speaker(speakerData.id, speakerData.firstName, speakerData.lastName, speakerData.topic, speakerData.sessionTime, speakerData.track);! }

Slide 30

Slide 30 text

Refactoring Sample Application – v4 – jasmine describe("Test model objects", function() {! "use strict";! ! var Model;! ! beforeEach(function(done) {! require(["app/model"], function (model) {! Model = model;! done();! });! }); it("should return Jovan Vidic when firstName is Jovan and lastName is Vidic", function() {! ! var speaker = new Model.Agenda.Speaker(1, "Jovan", "Vidic");! ! expect(speaker.getFullName()).toBe("Jovan Vidic");! });!

Slide 31

Slide 31 text

Refactoring Sample Application – v4 That's all cool but my code still stinks?!

Slide 32

Slide 32 text

Trust me I've been there

Slide 33

Slide 33 text

Refactoring Sample Application – v5 Improve design & automate

Slide 34

Slide 34 text

Refactoring Sample Application – v5 – SPEAKERS AJAX CLIENT define(["jquery", "app/model"], function ($, model) {! "use strict";! ! var baseURL = requirejs.s.contexts._.config.cs["api-url"],! speakersURL = baseURL + "speakers/",! speakerURL = baseURL + "speaker/";! ! function convertToModel(speakerData) {! return new model.Agenda.Speaker(speakerData.id, speakerData.firstNa }! return {! loadAllSpeakers : function (callbackHandle) {! $.getJSON(speakersURL, function (speakersData) {! var speakers = [];! $.each(speakersData, function (index, speakerData) {! var speaker = convertToModel(speakerData);! speakers.push(speaker);! });! callbackHandle.success(speakers);! });! },

Slide 35

Slide 35 text

Refactoring Sample Application – v5 – Backend FACADE define(["app/client/speakers_ajax_client"], function (speakersClient) {! "use strict";! ! return {! loadAllSpeakers : function (callbackHandle) {! speakersClient.loadAllSpeakers(callbackHandle);! },! ! saveSpeaker : function (speaker, callbackHandle) {! speakersClient.saveSpeaker(speaker, callbackHandle);! }!

Slide 36

Slide 36 text

Refactoring Sample Application – v5 - TesTS define(["squire"], function (Squire) {! "use strict";! ! var injector = new Squire(),! client = jasmine.createSpyObj("client", ["loadAllSpeakers", "saveSpeaker", "removeSpeaker"]),! callbackHandle = jasmine.createSpy("callback"),! builder = injector.mock("app/client/speakers_ajax_client”, client); ! describe("Test backend facade", function() {! ! var backend;! ! beforeEach(function(done) {! builder.require(["app/backend_facade"], function(backendFacade) backend = backendFacade;! done();! });! });! it("should loadAllSpeakers", function() {! backend.loadAllSpeakers(callbackHandle);! expect(client.loadAllSpeakers)! .toHaveBeenCalledWith(callbackHandle);! });! ! !

Slide 37

Slide 37 text

Refactoring Sample Application – v5 - MVP http://warren.chinalle.com/2010/12/18/model-view-presenter/

Slide 38

Slide 38 text

Refactoring Sample Application – v5 – speakers PRESENTER define(["app/backend_facade", "app/speakers/speakers_view", ! "app/events"], function (backend, SpeakersView, events) {! ! "use strict";! ! var EventManager = events.EventManager,! Actions = events.Actions;! ! function SpeakerPresenter() { ! var view = new SpeakersView(),! self;! ! return {! init : function () {! self = this;! EventManager.register(Actions.LOAD_ALL_SPEAKERS, this.loadAll);! },! loadAll : function () {! backend.loadAllSpeakers({! "success" : function (speakers) {! view.showAll(speakers);! }! });! },!

Slide 39

Slide 39 text

Refactoring Sample Application – v5 – Speakers view define(["app/events", "app/components", "app/speakers/speakers_popup"], ! "use strict”;! var EventManager = events.EventManager;! ! function SpeakersView() {! var speakersTable = new components.SpeakersTable(),! createButton = new components.Button("btnAddSpeaker"),! popup = new SpeakersPopup();! ! function showCreateSpeakerDialog() {! EventManager.fire(events.Actions.SHOW_CREATE_SPEAKER);! }! function init() {! createButton.addClickListener(showCreateSpeakerDialog);! }! init();! return {! showAll : function (speakers) {! var i, len;! speakersTable.clear();! for (i = 0, len = speakers.length; i < len; i += 1) {! speakersTable.addSpeaker(speakers[i]);! }! }!

Slide 40

Slide 40 text

Refactoring Sample Application – v5 – SPEAKERS POPUP define(["app/model", "app/events", "app/components"], function (model, even ! function SpeakersPopup() {! var speaker,! popup = new components.Popup("myModal"),! firstNameInput = new TextField("firstName"),! ! function saveSpeaker() {! speaker.firstName = firstNameInput.val();! speaker.lastName = lastNameInput.val();! speaker.topic = topicInput.val();! ! if (speaker.hasId()) {! EventManager.fire(events.Actions.EDIT_SPEAKER, speaker);! } else {! EventManager.fire(events.Actions.SAVE_SPEAKER, speaker);! }! }! return {! openAndSet : function (speakerToUpdate) {! speaker = speakerToUpdate;! firstNameInput.val(speaker.firstName);! lastNameInput.val(speaker.lastName);! !

Slide 41

Slide 41 text

Refactoring Sample Application – v5 – components http://warren.chinalle.com/2010/12/18/model-view-presenter/ define(["jquery", "app/events", "app/theme"], function ($, events, theme) ! function TextField(id) {! var textField = $("#" + id);! return {! val : function (value) {! if (value !== undefined) {! textField.val(value);! } else {! return textField.val();! }! }! };! }! ! function SimpleButton(id) {! var button = $("#" + id);! return {! addClickListener : function (listener) {! button.click(listener);! }! };! }! !

Slide 42

Slide 42 text

For the win

Slide 43

Slide 43 text

Refactoring Sample Application – v5 - GRUNT grunt.initConfig({! ! jslint: {! src: [! "scripts/**/*.js"! ],! ! },! ! karma: {! unit: {! configFile: "karma.conf.js"! }! },! ! copy: {! main: {! files: [! {expand: true, src: ["lib/bootstrap/dist/css/**"], dest: "dist/"},! {expand: true, src: ["lib/bootstrap/dist/fonts/**"], dest: "dist/"} {expand: true, src: ["lib/requirejs/require.js"], dest: "dist/"}! ]! }! }!

Slide 44

Slide 44 text

Refactoring Sample Application – v5 - GRUNT requirejs: {! compile: {! options: {! baseUrl: "scripts/",! name: "main",! out: "dist/app-built.js”,! paths: {! app: "app/",! "jquery": "../lib/jquery/dist/jquery.min",! "bootstrap": "../lib/bootstrap/dist/js/bootstrap"! }! }! }! },! processhtml: {! options: {! data: { ! }! },! dist: {! files: {! "dist/index.html": ["index.html"]! }! }!

Slide 45

Slide 45 text

BEYOND REFACTORING

Slide 46

Slide 46 text

Coffeescript define [], () ->! ! class Speaker! constructor: (@id, @firstName, @lastName, @topic, @sessionTime, @track) ->! ! hasId: ->! @id?.length != 0! ! fullName: ->! @firstName + " " + @lastName! ! return {"Agenda": {"Speaker" : Speaker}} !

Slide 47

Slide 47 text

Literature

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

QUESTIONS https://github.com/jovanvidic/javascript-refactoring [email protected] @_yowan_ ?