Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Refactoring Java Script Applications

DaFED
February 04, 2015

Refactoring Java Script Applications

DaFED#29
Speaker: Jovan Vidić, codecentric
Cilj predavanja je da prikaže jedan od mogućih pristupa rešavanju nagomilanih problema na veb aplikacijama. Predavanje će dati odgovor na pitanje da li je potrebno refaktorisati JavaScript aplikacije, kao i na koji način pristupiti refaktorisanju, a da se pri tom ne zaustavi kompletan razvoj na projektu.

DaFED

February 04, 2015
Tweet

More Decks by DaFED

Other Decks in Programming

Transcript

  1. ~ 6 years of development Java + DWR 732 JavaScript

    files ~ 20 000 lines of javascript code Motivation - Legacy project
  2. What was wrong? - JavaScript in HTML - Global functions

    - Inline styles - No coding style guidelines - No tests MOTIVATION - Legacy project
  3. What was OK? - OOP - Throwing own errors -

    Wrapping third party libraries - Prototype.js MOTIVATION - Legacy project
  4. 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
  5. agile manifesto 8th principle Agile processes promote sustainable development. The

    sponsors, developers, and users should be able to maintain a constant pace indefinitely.
  6. Refactoring Sample Application – v1 – index.html </head>! <body>! <ul

    class="nav nav-pills">! <li class="active"><a href="../../v1/client/index.html">V1</a></ li>! <li><a href="../../v2/client/index.html">V2</a></li>! <li><a href="../../v3/client/index.html">V3</a></li>! <li><a href="../../v4/client/index.html">V4</a></li>! <li><a href="../../v5/client/index.html">V5</a></li>! </ul>! <div class="page-header">! <h1>Coding Serbia <small> Refactoring JavaScript Applications - V1</small></h1>! </div>! <!-- Button trigger modal -->! <button class="btn btn-primary btn-lg" data-toggle="modal" onclick="createSpeaker()">! Add Speaker! </button>!
  7. 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 += "<tr><td>" + sessionTime.toLocaleString() +"</td><! });! $(".table-striped tbody").html("");! $(".table-striped tbody").append(newRows);! });! };!
  8. 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();! });! <script data-main="scripts/main" src="scripts/lib/require.js"></script>!
  9. 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"! };! });!
  10. Refactoring Sample Application – SPEAKERS.JS define(["jquery","app/theme"], function ($, theme) {!

    "use strict";! ! var SpeakerControler = {! loadAll : function () {! cell.append($("<button class='"+theme.DELETE_BUTTON+"'></button>”)! SpeakerControler.remove(item.id);! })); remove : function(id) {! $.ajax({! type: "DELETE",! url: "http://localhost:4730/speaker/" + id,! success: function() {! SpeakerControler.loadAll();! }! });! }! return SpeakerControler;! });
  11. Refactoring Sample Application – v3 – bower.json {! "name": "javascript-refactoring",!

    "version": "1.0",! "authors": [! "Jovan Vidic <[email protected]>"! ],! "private": true,! "ignore": [! "node_modules",! "bower_components",! "test",! "tests"! ],! "dependencies": {! "jquery": "1.11.1",! "bootstrap" : "3.2.0",! "requirejs" : "2.1.15"! },! ! "devDependencies": {! ! }! }!
  12. 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! }! };!
  13. 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 <strong>' ! + speaker.getFullName() + "</strong>");! }! function convertToModel(speakerData) {! return new model.Agenda.Speaker(speakerData.id, speakerData.firstName, speakerData.lastName, speakerData.topic, speakerData.sessionTime, speakerData.track);! }
  14. 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");! });!
  15. 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);! });! },
  16. 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);! }!
  17. 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);! });! ! !
  18. 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);! }! });! },!
  19. 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]);! }! }!
  20. 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);! !
  21. 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);! }! };! }! !
  22. 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/"}! ]! }! }!
  23. 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"]! }! }!
  24. Coffeescript define [], () ->! ! class Speaker! constructor: (@id,

    @firstName, @lastName, @topic, @sessionTime, @track) ->! ! hasId: ->! @id?.length != 0! ! fullName: ->! @firstName + " " + @lastName! ! return {"Agenda": {"Speaker" : Speaker}} !