Slide 1

Slide 1 text

Write your jQuery in console Marek Kalnik

Slide 2

Slide 2 text

2/32 1995 JavaScript is created

Slide 3

Slide 3 text

3/32 240 000 results for “javascript spaghetti code” 3 times more than Python 2 times more than Java 2.5 times less than PHP ;)

Slide 4

Slide 4 text

4/32 2006 jQuery was released

Slide 5

Slide 5 text

5/32 jQuery now runs 68.8 % of the web* * in top 10k websites by builtwith.com

Slide 6

Slide 6 text

6/32 2010 Theodo writes JS code, we are not happy with the spaghetti we create

Slide 7

Slide 7 text

7/32

Slide 8

Slide 8 text

8/32 We try to replace jQuery by something else Changing framework proves to be expensive and inefficient

Slide 9

Slide 9 text

9/32 Step back...

Slide 10

Slide 10 text

10/32 2009 Node.js is released

Slide 11

Slide 11 text

11/32 Node.js makes it childishly easy and blazingly fast to test JavaScript Photo credit: Viernest / Foter.com / CC BY

Slide 12

Slide 12 text

12/32 Idea

Slide 13

Slide 13 text

13/32 Testing JavaScript in browser encourages trial and error and quick and dirty code

Slide 14

Slide 14 text

14/32 Removing browser from the development process will improve the code quality

Slide 15

Slide 15 text

15/32 The proof of concept

Slide 16

Slide 16 text

16/32 The methodology • Object-Oriented • TDD : write the test first, then the code • continuous testing

Slide 17

Slide 17 text

17/32 The modules • jsdom : https://github.com/tmpvar/jsdom • jQuery : https://github.com/coolaj86/node-jquery • mocha & expect.js • Guard

Slide 18

Slide 18 text

18/32 . ├── Guardfile ├── lib │ └── hider.coffee ├── package.json └── spec └── hider_spec.coffee Hider – an object that hides a DOM node

Slide 19

Slide 19 text

19/32 # A sample Guardfile # More info at https://github.com/guard/guard#readme # Installed by guard-mocha-node guard "mocha-node", :all_after_pass => false, :mocha_bin => File.expand_path(File.dirname(__FILE__) + "/node_modules/mocha/bin/mocha") do watch(%r{^spec/.+_spec\.js$}) watch(%r{^lib/(.+)\.js$}) { |m| "spec/#{m[1]}_spec.js" } end Guardfile

Slide 20

Slide 20 text

20/32 A Guard to watch it all guard "mocha-node", :reporter => 'nyan', … do

Slide 21

Slide 21 text

21/32 var expect = require('expect.js'), hiderModule = require('../lib/hider.coffee'), jsdom = require('jsdom'), jQuery = require('jquery'), doc = jsdom.jsdom('' + '

' + ''), window = doc.parentWindow(); $ = global.jQuery = jQuery.create(window); describe('Test jQuery works', function () { it('Should work', function () { expect($('input').length).to.equal(1); }); describe('Test Hider', function () { selector = '#test'; it('should hide the element when hide is called', function () { hider = new hiderModule.Hider(selector); hider.hide(); expect($(selector).is(':visible')).not.to.be.ok(); }); }); The test

Slide 22

Slide 22 text

22/32 var Hider = function(element) { this.element = element; this.hide = function() { $(this.element).hide(); }; }; module.exports = { Hider: Hider } And the class!

Slide 23

Slide 23 text

23/32 Implement!

Slide 24

Slide 24 text

24/32 Tested in playground with satisfying results

Slide 25

Slide 25 text

25/32 Applied to two current projects to help with refactoring of the JavaScript code

Slide 26

Slide 26 text

26/32 Refactoring methodology - copy-paste the HTML to create your DOM document - clean up HTML code to see clearer - focus on one functionality: cover it by tests - refactor your functionality into OO, by adding unit tests and continuously running integration tests to see if they are still green

Slide 27

Slide 27 text

27/32 $(document).ready(function() { $('.postalCodePopin').on('change', function(e) { var selectedOption = this.value, href = $('#postal-code-popin').data('href'), modal = $('#modal'); if (selectedOption == 'postalcode') { modal.load(href); modal.modal(); } }); $('.row-postalCodeWithAutoComplete').append(''); $('.removePostalCode').on('click', function(e) { $(this).prevAll('input').val('').parent().hide().prev('.row-perimeter').show().children('.postalCodeShow').each(function(){ $(this).val($(this).find('option:first').val()); }); }); $('.postalCodeShow').on('change', function(e) { var selectedOption = this.value; if (selectedOption == 'postalcode') { $(this).parent().hide().next('.row-postalCodeWithAutoComplete').show().children('.postalCodeField').focus(); } }); $('.postalCodeField').each(function(){ if ($(this).val() != "") { $(this).parent().show().prev('.row-perimeter').hide(); } }); $('.postalCodeShow').each(function(){ if ($(this).val() == "postalcode") { $(this).parent().hide().next('.row-postalCodeWithAutoComplete').show(); } }); }); Example, the code to refactor

Slide 28

Slide 28 text

28/32 describe('hide select', function() { it ('when selected choice should hide the select', function() { var $ = createContext(); testedCode.init($); expect($('.row-perimeter').is(':visible')).to.be.ok(); $('#select').val("postalcode").change(); expect($('.row-perimeter').is(':visible')).not.to.be.ok(); }); it ('when the choice is selected the input should show', function() { var $ = createContext(); testedCode.init($); expect($('.row-postalCodeWithAutoComplete').is(':visible')).not.to.be.ok(); $('#select').val("postalcode").change(); expect($('.row-postalCodeWithAutoComplete').is(':visible')).to.be.ok(); }); it ('when close clicked the input should disappear', function() { var $ = createContext(); testedCode.init($); $('#select').val("postalcode").change(); $('.removePostalCode').click(); expect($('.row-perimeter').is(':visible')).to.be.ok(); expect($('.row-postalCodeWithAutoComplete').is(':visible')).not.to.be.ok(); }); }); Example, the test

Slide 29

Slide 29 text

29/32 PostalCodeWidget = function(context) { if (typeof context === 'undefined') { context = 'body'; } var PLACEHOLDER = 'postalcode', inputContainer = $(context).find('.row-postalCodeWithAutoComplete'), input = inputContainer.find('input'), selectContainer = $(context).find('.row-perimeter'), codesList = selectContainer.find('select'), defaultValue = codesList.val() !== PLACEHOLDER ? select.val() : codesList.find('option:first').val(), removeBtn = inputContainer.append('').find('.removePostalCode'), _this = this; this.showSelect = function () { codesList.val(defaultValue); selectContainer.show(); inputContainer.hide(); input.val(''); }; this.showInput = function () { selectContainer.hide(); inputContainer.show(); if (arguments[0] === true) { inputContainer.children('.postalCodeField').focus(); } }; // bindings codesList.on('change', function() { if (this.value === PLACEHOLDER) { _this.showInput(true); } }); removeBtn.on('click', function() { _this.showSelect(); }); // initialize if (input.val() !== '' || select.val() === PLACEHOLDER) { this.showInput(); } }; Example, refactored

Slide 30

Slide 30 text

30/32 Not tested: - AJAX - third party libs integration - dynamic JS insertion - applying in a project form the beginning

Slide 31

Slide 31 text

31/32 2014 All JavaScript written by Theodo is written using Node.js

Slide 32

Slide 32 text

32/32 Brought to you by @marekkalnik a @theodo developer GitHub repository: https://github.com/theodo/nodejs-frontend-dev Thanks for listening!