Essential JavaScript and jQuery

  • 3/15/2013

Practice exercises

If you encounter a problem completing any of these exercises, the completed projects can be installed from the Practice Exercises folder that is provided with the companion content.

Exercise 1: Create a calculator object

In this exercise, you apply your JavaScript object-oriented programming knowledge by modifying the calculator you’ve been using to have a calculator object in the calculatorLibrary namespace and changing JavaScript code to use jQuery when necessary.

  1. Start Visual Studio Express 2012 for Web. Click File and choose Open Project. Navigate to the solution you created in Chapter 5,“More HTML,” and select the webCalculator.sln file. Click Open.

    If you didn’t complete the exercises in Chapter 5, you can use the solution in the Chapter 6 Exercise 1 Start folder.

  2. In the Solution Explorer window, right-click the CalculatorTests.html file and choose Set As Start Page. Press F5 to verify that your test runs and passes.

  3. In the Solution Explorer window, add jQuery to the project by right-clicking the project node. Choose Manage NuGet Packages. Type jQuery in the search online text box and click the search button. Click the Install button on the jQuery result.

  4. Add a file to the Scripts folder called _references.js and, in the file, add a reference to jQuery, QUnit, and the CalculatorLibrary.

    Your file should look like the following.

    /// <reference path="jquery-1.8.2.js" />
    /// <reference path="qunit.js" />
    /// <reference path="CalculatorLibrary.js" />
  5. Open the CalculatorLibrary.js file and add a reference to the _references.js file.

  6. Create the calculatorLibrary namespace by surrounding the existing code in the CalculatorLIbrary.js file with an immediately invoked function expression (IIFE). In the IIFE, create an alias to calculatorNamespace called ns, which will save you from typing the complete namespace while you’re in the IIFE.

    Your code should look like the following.

    /// <reference path="_references.js" />
    
    (function () {
        this.calculatorNamespace = this.calculatorNamespace || {};
        var ns = this.calculatorNamespace;
    
        //existing code here....
    
    })();
  7. Remove the variables that reference txtInput and txtResult because jQuery will be used to access these DOM elements as needed.

    The initialize function will remain in the namespace.

  8. Surround the numberClick, plusClick, minusClick, clearEntry, and clear functions with an IIFE that is assigned to a Calculator property in calculatorNamespace.

    Your code should look like the following.

    ns.Calculator = (function () {
    
        function numberClick() {
            txtInput.value = txtInput.value == '0' ?
                this.innerText : txtInput.value + this.innerText;
        }
    
        function plusClick() {
            txtResult.value = Number(txtResult.value) + Number(txtInput.value);
            clearEntry();
        } 
    
        function minusClick() {
            txtResult.value = Number(txtResult.value) - Number(txtInput.value);
            clearEntry();
        }
    
        function clearEntry() {
            txtInput.value = '0';
        }
    
        function clear() {
            txtInput.value = '0';
            txtResult.value = '0';
        }
    }());
  9. Add a Calculator function inside the IIFE, which will be the constructor function. There is no code for the constructor at this time. At the bottom of the IIFE, add code to return this constructor function. Try this on your own, but if you have a problem, the sample code is shown in step 10.

  10. Modify the numberClick, plusClick, minusClick, clearEntry, and clear functions to define these functions on the Calculator prototype.

    The CalculatorLibrary.js should look like the following.

    /// <reference path="_references.js" />
    
    (function () {
        this.calculatorNamespace = this.calculatorNamespace || {};
        var ns = this.calculatorNamespace;
    
        function initialize() {
            for (var i = 0; i < 10; i++) {
                document.getElementById('btn' + i)
                   .addEventListener('click', numberClick, false);
            }
            txtInput = document.getElementById('txtInput');
            txtResult = document.getElementById('txtResult');
    
     
            document.getElementById('btnPlus')
               .addEventListener('click', plusClick, false);
            document.getElementById('btnMinus')
               .addEventListener('click', minusClick, false);
            document.getElementById('btnClearEntry')
               .addEventListener('click', clearEntry, false);
            document.getElementById('btnClear')
               .addEventListener('click', clear, false);
            clear();
      }
    
      ns.Calculator = (function () {
    
          function Calculator() {
          }
    
          Calculator.prototype.numberClick = function () {
              txtInput.value = txtInput.value == '0' ?
                 this.innerText : txtInput.value + this.innerText;
          };
    
          Calculator.prototype.plusClick = function () {
              txtResult.value = Number(txtResult.value) + Number(txtInput.value);
              clearEntry();
          };
    
          Calculator.prototype.minusClick = function () {
              txtResult.value = Number(txtResult.value) - Number(txtInput.value);
              clearEntry();
          };
    
          Calculator.prototype.clearEntry = function () {
              txtInput.value = '0';
          };
    
          Calculator.prototype.clear = function () {
                 txtInput.value = '0';
                 txtResult.value = '0';
          };
    
          return Calculator;
      }());
    
    })();
  11. In the initialize function, create a calculator variable and assign a new Calculator object to it. Be sure to use the namespace when creating the new Calculator object.

    The state should look like the following.

    var calculator = new ns.Calculator();
  12. Convert the loop that adds event listeners to each of the number buttons to a single jQuery statement based on finding all button elements that have an id that starts with btnNumber.

    The statement should look like the following.

    $('button[id^="btnNumber"]').on('click', calculator.numberClick);

    To make the code work in this step, change the ids on the number buttons.

  13. Open the default.html file and replace the number button ids with btnNumberX where X is the number on the button.

  14. Open the CalculatorTests.html file and replace the number button ids with btnNumberX where X is the number on the button.

  15. In the CalculatorLibrary.js file, locate the initialize function and delete the statements that set txtInput and txtResult.

  16. Convert the code that adds event listeners to btnPlus, btnMinus, btnClearEntry, and btnClear to use jQuery.

    The completed initialize function should look like the following.

    function initialize() {
        var calculator = new ns.Calculator();
        $('button[id^="btnNumber"]').on('click', calculator.numberClick);
        $('#btnPlus').on('click', calculator.plusClick);
        $('#btnMinus').on('click', calculator.minusClick);
        $('#btnClearEntry').on('click', calculator.clearEntry);
        $('#btnClear').on('click', calculator.clear);
        clear();
    }
  17. Convert the numberClick method to use jQuery.

    You can use the jQuery text method to retrieve the inner text. The completed method should look like the following.

    Calculator.prototype.numberClick = function () {
        $('#txtInput').val($('#txtInput').val() == '0' ?
        $(this).text() : $('#txtInput').val() + $(this).text());
    };
  18. Convert the plusClick method to use jQuery.

    You must call the clearEntry method, but you can’t use the this keyword to call clearEntry because the clicked button is referenced by this. Because there is only one copy of the clearEntry method, and it’s on the prototype, call the clearEntry method from the Calculator prototype. Your code should look like the following.

    Calculator.prototype.plusClick = function () {
        $('#txtResult').val(Number($('#txtResult').val()) +
            Number($('#txtInput').val()));
        Calculator.prototype.clearEntry();
    };
  19. Convert the minusClick method to use jQuery.

    Your code should look like the following.

    Calculator.prototype.minusClick = function () {
        $('#txtResult').val(Number($('#txtResult').val()) - 
            Number($('#txtInput').val()));
        Calculator.prototype.clearEntry();
    };
  20. Convert the clearEntry method and the clear method to use jQuery.

    The completed CalculatorLibrary.js file should look like the following.

    /// <reference path="_references.js" />
    
    (function () {
        this.calculatorNamespace = this.calculatorNamespace || {};
        var ns = this.calculatorNamespace;
    
      ns.initialize = function () {
          var calculator = new ns.Calculator();
          $('button[id^="btnNumber"]').on('click', calculator.numberClick);
          $('#btnPlus').on('click', calculator.plusClick);
          $('#btnMinus').on('click', calculator.minusClick);
          $('#btnClearEntry').on('click', calculator.clearEntry);
          $('#btnClear').on('click', calculator.clear);
          calculator.clear();
      }
    
      ns.Calculator = (function () {
    
          function Calculator() {
          }
    
          Calculator.prototype.numberClick = function () {
              $('#txtInput').val($('#txtInput').val() == '0' ?
                  $(this).text() : $('#txtInput').val() + $(this).text());
          };
    
          Calculator.prototype.plusClick = function () {
              $('#txtResult').val(Number($('#txtResult').val()) +
                  Number($('#txtInput').val()));
              Calculator.prototype.clearEntry();
          };
    
          Calculator.prototype.minusClick = function () {
              $('#txtResult').val(Number($('#txtResult').val()) -
                  Number($('#txtInput').val()));
              Calculator.prototype.clearEntry();
          };
    
          Calculator.prototype.clearEntry = function () {
              $('#txtInput').val('0');
          };
    
          Calculator.prototype.clear = function () {
              $('#txtInput').val('0');
              $('#txtResult').val('0');
          };
    
          return Calculator;
      }());
    
    })();
  21. Open the default.html file and add a reference to the jQuery library.

    Be sure to add the reference before the reference to the CalculatorLibrary.js file because that file uses jQuery. Don’t forget that you can drag and drop the file to create the reference. The <head> element should look like the following.

    <head>
       <title>web Calculator</title>
       <link href="Content/default.css" rel="stylesheet" />
       <script src="Scripts/jquery-1.8.2.js"></script>
       <script type="text/javascript" src="Scripts/CalculatorLibrary.js"></script>
    </head>
  22. At the bottom of the default.html file, change the code so that the initialize function in calculatorNamespace is executed when the document is ready.

    The completed default.html file should look like the following.

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>web Calculator</title>
        <link href="Content/default.css" rel="stylesheet" />
        <script src="Scripts/jquery-1.8.2.js"></script>
        <script type="text/javascript" src="Scripts/CalculatorLibrary.js"></script>
    </head>
    <body>
        <div id="container">
            <header>
                <hgroup id="headerText">
                <h1>Contoso Ltd.</h1>
                <h2>Your success equals our success</h2>
            </hgroup>
        </header>
        <nav>
            <a href="default.html">Home</a>
        </nav>
        <div role="main">
            <div id="calculator">
                <table>
                    <tr>
                        <td colspan="4">
                            <input id="txtResult" type="text"
                                readonly="readonly" />
                        </td>
                    </tr>
                    <tr>
                        <td colspan="4">
                            <input id="txtInput" type="text" />
                        </td>
                    </tr>
                    <tr>
                        <td></td>
                        <td></td>
                        <td>
                            <button id="btnClearEntry">CE</button>
                        </td>
                        <td>
                            <button id="btnClear">C</button>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <button id="btnNumber7">7</button></td>
                        <td>
                            <button id="btnNumber8">8</button></td>
                        <td>
                            <button id="btnNumber9">9</button></td>
                        <td>
                            <button id="btnPlus">+</button>
                        </td>
                    </tr>
                    <tr>
                        <td>
                            <button id="btnNumber4">4</button>
                        </td>
                        <td>
                            <button id="btnNumber5">5</button>
                        </td>
                        <td>
                            <button id="btnNumber6">6</button>
                        </td>
                        <td>
                            <button id="btnMinus">-</button>
                        </td>
                        </tr>
                        <tr>
                            <td>
                                <button id="btnNumber1">1</button>
                            </td>
                            <td>
                                <button id="btnNumber2">2</button>
                            </td>
                            <td>
                                <button id="btnNumber3">3</button>
                            </td>
                            <td></td>
                        </tr>
                        <tr>
                            <td></td>
                            <td>
                                <button id="btnNumber0">0</button>
                            </td>
                            <td></td>
                            <td></td>
                        </tr>
                    </table>
                </div>
            </div>
            <aside>
                <p>Advertisements</p>
            </aside>
            <footer>
                <p>
                    Copyright &copy; 2012, Contoso Ltd., All rights reserved
                </p>
            </footer>
        </div>
        <script type="text/javascript">
            $(document).ready(function () {
                calculatorNamespace.initialize();
            });
        </script>
    </body>
    </html>

    You must modify the tests to use jQuery.

  23. Open the CalculatorTests.html file and add a reference to the jQuery library.

    The completed CalculatorTests.html file should look like the following.

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <link rel="stylesheet" type="text/css" href="Content/qunit.css" />
        <script type="text/javascript" src="Scripts/qunit.js"></script>
        <script src="Scripts/jquery-1.8.2.js"></script>
        <script type="text/javascript" src="Scripts/CalculatorLibrary.js"></script>
        <script type="text/javascript" src="Scripts/tests.js"></script>
    </head>
    <body>
        <h1 id="qunit-header">QUnit example</h1>
        <h2 id="qunit-banner"></h2>
        <div id="qunit-testrunner-toolbar"></div>
        <h2 id="qunit-userAgent"></h2>
        <ol id="qunit-tests"></ol>
        <div id="qunit-fixture">
            test markup, will be hidden
            <input id="txtResult" type="text" readonly="readonly" /><br />
            <input id="txtInput" type="text" /><br />
            <button id="btnNumber7">7</button>
            <button id="btnNumber8">8</button>
            <button id="btnNumber9">9</button><br />
            <button id="btnNumber4">4</button>
            <button id="btnNumber5">5</button>
            <button id="btnNumber6">6</button><br />
            <button id="btnNumber1">1</button>
            <button id="btnNumber2">2</button>
            <button id="btnNumber3">3</button><br />
            <button id="btnClear">C</button>
            <button id="btnNumber0">0</button>
            <button id="btnClearEntry">CE</button><br />
            <button id="btnPlus">+</button>
            <button id="btnMinus">-</button>
       </div>
    </body>
    </html>

    You must modify the tests.js file to use jQuery, calculatorNamespace, and the Calculator object.

  24. Open the tests.js file.

  25. In the tests.js file, add a reference to the _references.js file and modify the module function to call calculatorLibrary.initialize( ) as follows.

    /// <reference path="_references.js" />
    module('Calculator Test Suite', {
        setup: function () {
            calculatorNamespace.initialize();
        }
    });
  26. Modify the Initialize Test.

    You don’t need to set txtInput and txtResult because the initialize method calls the clear method to set these text boxes.

  27. Modify the rest of the method to use jQuery and run the test to see it pass.

    The completed Initialize Test should look like the following.

    test("Initialize Test", function () {
        expect(2);
        var expected = '0';
        equal($('#txtInput').val(), expected, 'Expected value: ' + expected +
            '  Actual value: ' + $('#txtInput').val());
        equal($('#txtResult').val(), expected, 'Expected value: ' + expected +
            '  Actual value: ' + $('#txtResult').val());
    });
  28. Modify the Button Click Test to use jQuery. Run the test to see it pass. Use jQuery’s triggerHandler method to test each button.

    Your code should look like the following.

    test("Button Click Test", function () {
        var buttonQuantity = 10;
        expect(buttonQuantity * 2);
        for (var i = 0; i < buttonQuantity; i++) {
            $('#btnNumber' + i).triggerHandler('click');
            var result = $('#txtInput').val()[$('#txtInput').val().length - 1];
            var expected = String(i);
            equal(result, expected, 'Expected value: ' + expected +
                '  Actual value: ' + result);
            var expectedLength = i < 2 ? 1 : i;
            equal($('#txtInput').val().length, expectedLength,
                'Expected string length: ' + expectedLength +
                '  Actual value: ' + $('#txtInput').val().length);
        }
    });
  29. Modify the Add Test to use jQuery. Run the test to see it pass.

    Your code should look like the following.

    test("Add Test", function () {
         expect(2);
         $('#txtInput').val('10');
         $('#txtResult').val('20');
         $('#btnPlus').triggerHandler('click');
         var expected = '30';
         equal($('#txtResult').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtResult').val());
         expected = '0';
         equal($('#txtInput').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtInput').val());
    });
  30. Modify the Subtract Test to use jQuery. Run the test to see it pass.

    Your code should look like the following.

    test("Subtract Test", function () {
         expect(2);
         $('#txtInput').val('10');
         $('#txtResult').val('20');
         $('#btnMinus').triggerHandler('click');
         var expected = '10';
         equal($('#txtResult').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtResult').val());
         expected = '0';
         equal($('#txtInput').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtInput').val());
    });
  31. Modify the Clear Entry Test to use jQuery. Run the test to see it pass.

    Your code should look like the following.

    test("Clear Entry Test", function () {
         expect(1);
         $('#txtInput').val('10');
         $('#btnClearEntry').triggerHandler('click');
         var expected = '0';
         equal($('#txtInput').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtInput').val());
    });
  32. Modify the Clear Test to use jQuery. Run the test to see it pass.

    Your code should look like the following.

    test("Clear Test", function () {
         expect(2);
         $('#txtInput').val('10');
         $('#txtResult').val('20');
         $('#btnClear').triggerHandler('click');
         var expected = '0';
         equal($('#txtInput').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtInput').val());
         equal($('#txtResult').val(), expected, 'Expected value: ' + expected +
             '  Actual value: ' + $('#txtResult').val());
    });

    At this point, you should be able to run all the tests, and they should all pass.

  33. Right-click the default.html file and choose Set As Start Page. To see that your calculator still works, press F5 to start debugging the application.

  34. Try entering data and clicking the plus and minus signs.

    You might need to refresh your screen, but the calculator should be working.