Building your first JavascriptMVC application

Featured

Note! This blog is out of date and will not be updated anymore.

I will share my experiences in creating a very simple JavascriptMVC application from scratch, and then add functionality and make some refactoring. It is my hope that both experienced and newbies will follow this blog, and give feedback.
I will normally test all examples in four browsers, see Test environment.
As I have chosen to use a blog for this purpose (see Intro), and the most recent blog entries are presented first, the story should read backwards, starting here.
NOTE! Most examples given in this blog are currently not working with the latest JavascriptMVC Release (3.2). This will be fixed eventually.

Camel case and updated tests

Preceding entry was not the last, as I wanted to make some refactorig to leave a nicer code. This will be the last entry (for a while anyway).

Renaming things using camel case

Now when I have an application up and running, I will remove a lot of underscores in order to try to follow the name standard. I have:

  1. Renamed airport_controller.js to  AirportController.js.
  2. Renamed airport_model.js to  AirportModel.js.
  3. Renamed first_div_controller.js to firstDivController.js
  4. Renamed second_div_controller.js to secondDivController.js
  5. Renamed folder first_div to firstDiv
  6. Renamed folder second_div to secondDiv
  7. Renamed first_div_init.ejs to firstDivInit.ejs
  8. Renamed second_div_init.ejs to secondDivInit.ejs
  9. In  firstDivController.js, renamed  “tut.first_div” to “Tut.FirstDiv”  (no change in mvcForNewbies.js where we create a firstDivController on the #first_div element)
  10. In  the same way, renamed  ”tut.second_div” to “Tut.SecondtDiv” , and tut.airport_div to Tut.AirportDiv
  11. In AirportModel.js, renamed tut.airport_model to Tut.AirportModel, (and in airportController.js)

Updated tests

Besides making the corresponding name changes in the test programs, I have added some tests to check for (div) visibility at start-up and after clicking on a tab.

Funcunit test fails on Opera.

The example can be run here, quint test here, fununit test here and the complete code is here.

Adding a theme and a tab control

In this last? blog entry I will add a JQuery UI Tab control and a jQuery UI Theme.

  1. Select a (standard) theme from  http://jqueryui.com/themeroller (click on Gallery). (I have chosen the Redmond theme).
  2. Download a  zip file  (jquery-ui-1.8.14.custom.zip) with the Redmond css file, and some javascript files for the JQuery UI widgets. What you download is decided on the Build your download page. For this tutorial we will need UI core and the widgets  Tabs and Button.
  3. Unpack the zip file to a folder called jquery-ui-1.8.14, this folder is placed in the same folder as in the javascriptmvc.
  4. Modify the mvcForNewbies.html  file.
  5. We add a div, in this case  with id= tabs_div (but you can use any id you want). Within this div we create a list. Each list entry will become a tab and is created as a bookmark hyperlink .

    mvcForNewbies.html

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
       "http://www.w3.org/TR/html4/strict.dtd">
    <html lang="en">
    <head>
       <title>JavascriptMVC For Newbies Part 8</title>
       <link type="text/css"
          href="../jquery-ui-1.8.14/css/redmond/jquery-ui-1.8.14.custom.css"
             rel="Stylesheet"/>
       <link rel="stylesheet" type="text/css" href="mvcForNewbies.css"/>
       <script type='text/javascript'
           src='../steal/steal.js?mvcForNewbies,development'></script>
    <body>
    <div id="tabs_div">
       <ul>
          <li><a href="#first_div">First</a></li>
          <li><a href="#second_div">Second</a></li>
          <li><a href="#airport_div">Airport</a></li>
       </ul>
       <div id="first_div"></div>
       <div id="second_div"></div>
       <div id="airport_div"></div>
    </div>
    
    </body>
    </html>
    

     

  6. Modify the mvcForNewbies.js  file.
  7. We have to steal the compressed jquery-ui file, and add the tabs widget to the page.
    mvcForNewbies.js

    steal.plugins(
    'jquery/controller',
    'jquery/view/ejs',
    'jquery/controller/view',
    'jquery/controller/subscribe',
    'jquery/model',
    'jquery/dom/fixture',
    'jquery/dom/form_params')
    .then(
    '//jquery-ui-1.8.14/js/jquery-ui-1.8.14.custom.min.js',
    'application/airport/airport_model.js').then(
    'application/first_div/first_div_controller.js',
    'application/second_div/second_div_controller.js',
    'application/airport/airport_controller.js').then(
    
    function($) {
       $(document).ready(
          function() {
    
          // Create a new tut_test_div controller on the #first_div element
          $("#first_div").tut_first_div({myElement: "#first_div"});
    
          // Create a new tut_test_div controller on the #second_div element
          $("#second_div").tut_second_div({myElement: "#second_div"});
    
          // Create a new tut_airport_div controller on the #airport_div element
          $("#airport_div").tut_airport_div();
    
          // Add the tabs widget, and bind it to the tabs_div element
          $("#tabs_div").tabs();
          }
       )
    }
    )
  8. Modify the css file. Add the tabs_div and set a width on it.  Remove background color of the divs (should be decided by the theme). Remove the div positions.  Also, I have made the following additions (personal choice, and maybe there is a better way to this):
    1. Add  an h2  style inheriting from the jQuery UI ui-widget-header class. Remove the background image and set display: table-cell (otherwise the backrgound color will be stretched to the end of the surrounding div.)
    2. Set a background color for table cells in .ui-widget-content.
  9.  
    Additions in mvcForNewbies.css

    tabs_div {
       width: 450px;
    }
    h2.ui-widget-header {display: table-cell; background-image: none;}
    .ui-widget-content td {background-color:#F0F0FF;}
  10. Modify the airport init view, using some jQuery UI CSS classes.
  11. airport/views/init.ejs

    <h2 class='ui-widget-header ui-corner-all' >Airports</h2> < br >
    <table   class="outerTable ui-widget" width=300>
      <thead class="ui-widget-header">
        <tr>
          <th   class='AirportName'>Name</th>
          <th   class='Latitude'>Latitude</th>
          <th   class='Longitude'>Longitude</th>
          <th   width='15'></th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td   colspan="4">
            <div   class="innerDiv">
              <table   class="innerTable"   id="airports_table">
                <tbody class='ui-widget-content'>
                  <%= $.View('//mvcForNewbies/application/airport/views/list.ejs',{airports: airports})%>
                        </tbody>
                    </table>
                </div>
            </td></tr>
        </tbody>
    </table>
    

     

  12. Modify the tests. This is left as an exercise to the reader.

The example can be run here and the complete code is here.

Unit test cont. and Functional test

Failed first_div unit tests (see last entry)

See also answers in the forum.

  1. The first test fails as tut.first_div.options is undefined. This is probably due to load order. Controllers  are not supposed to be tested in qunit, so I will not try to solve this problem. (Both these tests have been removed in this release.)
  2. The second test fails as tut_first_div is undefined. So here we have to use the name of the controller as spelled in its definition (Class name), but when we bind the controller to its element (in mvcForNewbies.js) , we have to replace the dot with underscore, (this is the JQuery Plugin name which can’t have a period in it).
    I should really rename the class to follow the standard, maybe I will do it in a later release.

Functional tests

Funcunit.html will load test/funcunit.js which will start the functional test, where we can access the DOM model, click on buttons, insert text in input fileds and examine html elements etc etc.

Funcunit.html

<html>
<head>
<link rel="stylesheet" type="text/css" href="../funcunit/qunit/qunit.css" />
<title>mvcForNewbies Part 7, FuncUnit Test </title>
<script type='text/javascript'
    src='../steal/steal.js?mvcForNewbies/test/funcunit'></script>
</head>
<body>
<h1 id="qunit-header">mvcForNewbies Part 7, FuncUnit Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>

Funcunit.js

steal
.plugins("funcunit")
.then("mvcForNewbies_test", "airport_controller_test",
   "first_div_controller_test");
 

mvcForNewbies_test.js

This is just a simple test to verify that funcunit is running Ok. It will check that we have an h2 element with correct text somewhere on the page.

module("mvcForNewbies test", {
setup: function(){
S.open("//mvcForNewbies/mvcForNewbies.html");
}
});
 
test("Copy Test", function(){
equals(S("h2").text(), "Airports","welcome text");
});

Airport controller test

The airport controller test is veryd simple, as we have very little functionality in this controller. We will merely test that there are some elements of class AirportName in the page.

/*global module: true, ok: true, equals: true, S: true, test: true */
module("airport", {
setup: function(){
S.open("//mvcForNewbies/mvcForNewbies.html");
 
// make sure there's at least one element of class AirportName
// on the page before running a test
S('.AirportName').exists();
}
});
 
/**
* Test that there are at least two elements of class AirportName om the page
*/
test("airports present", function () {
ok(S('.AirportName').size() > 1, "There is at least two airports");
});
 

First_div controller test

In order to make this test, the html code has been modified. We introduce an html SPAN element to hold the value of the pressed1 counter:

My name is: <%= my_name %><br>
You pressed me <span id='pressed1'><%= pressed1 %></span> times<br>
<button id='my_first_button'>Press me</button>
 

This test will check:

  1. The pressed1 SPAN element exists and  is zero when we start the test
  2. The same element is one after the first click on my_first_button, and two after the second click.
module("first_div Controller", {
setup: function(){
S.open("//mvcForNewbies/mvcForNewbies.html");
 
//make sure the html element with the "pressed1" counter have been created before running a test
S('#pressed1').exists();
}
});
/*
*/
test("test1", function(){
equals(S("#pressed1").text(), "0");
})
 
test("test2", function(){
 
// Click on first_div's button
S("#my_first_button").click();
 
// As nothing ele changes when we press this button, we have to wait until
// the html of the #pressed element has been incremented.
// I believe the default timeout is 10 seconds for this call,
// I will set it down to 100 ms
 
var myTimeOut=100;
 
// Wait until pressed's html (in both divs) is 1
S('#pressed1').html("1",myTimeOut);
S('#pressed2').html("1",myTimeOut);
 
// Click again
S("#my_first_button").click();
// Wait until pressed's html (in both divs) is 2
S('#pressed1').html("2",myTimeOut);
S('#pressed2').html("2",myTimeOut);
})

 
The application can be run here, the funcunit tests here, and the qunit tests here.
The complete code is here.

Unit test

Now it is high time to start using QUnit.
I will start with adding a simple test of the findAll function in the Airport model, and another simple test of the firs_div controller.
First, modify an existing quint.html (for example from the cookbook example).

qunit.html

The only change we have to do in quinit.html is to modify the path to the qunit.js script, and change the qunit-header to something more appropriate.

<html>
   <head>
      <link rel="stylesheet" type="text/css"
          href="../funcunit/qunit/qunit.css" />
      <title>mvcForNewbies QUnit Test</title>
      <script type='text/javascript'>
         steal = {ignoreControllers: true}
      </script>
      <script type='text/javascript'
          src='../steal/steal.js?mvcForNewbies/test/qunit'></script>
   </head>
   <body>

      <h1 id="qunit-header">mvcForNewbies Unit Test Suite</h1>
      <h2 id="qunit-banner"></h2>
      <div id="qunit-testrunner-toolbar"></div>
      <h2 id="qunit-userAgent"></h2>
      <div id="test-content"></div>
      <ol id="qunit-tests"></ol>
      <div id="qunit-test-area"></div>
   </body>
</html>

quinit.js

quinit.js will steal the two test scripts airport_test.js and first_div_test.js. We will also create test script called mvcForNewbies_test.js, which will be a simple test showing that qunit is installed correctly. All these scripts will be created in a new top-level folder called test.

'steal
  .plugins("funcunit/qunit", "mvcForNewbies")
  .then("mvcForNewbies_test", "airport_test")
        .then("first_div_test");

mvcForNewbies_test.js

module("mvcForNewbies");

test("mvcForNewbies testing works", function(){
	ok(true,"an assert is run");
});

Note

The parameter, “mvcForNewbies”, to the module function is an arbitrary name, showing up in the test results. It is also possible to add setup and teardown callbacks in this call.

Testing the model

The script airport_test.js looks like this:

module("module("airport_model")

/**
 * Test the find-all function in the airport model
 */
asyncTest("findAll", function(){
   stop();
   tut.airport_model.findAll({}, function(airports){
      //object array airports exists
      ok(airports)
      //object array airports has length > 0
      ok(airports.length)
      //AirportName and Latitude are defined on the first object in the array
      ok(airports[0].AirportName)
      ok(airports[0].Latitude)
      // Correct AirportName on the first object in the array
      equals(airports[0].AirportName, "Stockholm, Arlanda");
      start()
   });
})

Note

The test runs OK.

Testing the controller

The first_div_test.js script should just test the initialization of the options.myElement variable, and looks like this:

module("first_div Controller")
/*
* These two tests fail due to tut.first_div.options being undefined
 */
test("test1", function(){
   equals(tut.first_div.options.myElement, "#first_div");
})

test("test2", function(){
   equals(tut_first_div.options.myElement, "#first_div");
})

Notes

Both tests fail.

  1. The first test fails as tut.first_div.options is undefined. I do not understand why, and will post this as a question to the forum.
  2. The second test fails as tut_first_div is undefined. So here we have to use the name of the controller as spelled in its definition, but when we bind the controller to its element (in mvcForNewbies.js) , we have to replace the dot with underscore. This looks illogical to me.

 
The application can be run here, and the unit tests here.
The complete code is here.

Adding a model

(Adjusted for Release 3.2)

Instead of creating tests, I will now create a new DIV, with a corresponding controller, model and view, all very simple.
The view will present a list of airports, with positions (latitude and longitude). In this version, the data will be read from a JSON file, using the fixture functionality of the javascriptMVC framework.
So we first create a new DIV element (airport_div) in the mvcForNewbies.html file. We also have to add size and position of the new div in the mvcForNewbies.css file (Download the code to see this).

mvcForNewbies.js…

looks like this now:

steal(
    '//jquery/class/class.js',
    '//jquery/controller/controller.js',
    '//jquery/view/ejs/ejs.js',
    '//jquery/controller/view/view.js',
    '//jquery/controller/subscribe/subscribe.js',
    '//jquery/model/model.js',
    '//jquery/dom/fixture/fixture.js',
    '//jquery/dom/form_params/form_params.js').then(
    'mvcForNewbies5/application/airport/airport_model.js').then(
    'mvcForNewbies5/application/first_div/first_div_controller.js',
    'mvcForNewbies5/application/second_div/second_div_controller.js',
    'mvcForNewbies5/application/airport/airport_controller.js').then (
   function($){
      $(document).ready(
         function() {
            // Create a new tut_test_div controller on the #first_div element
            $("#first_div").tut_first_div({myElement: "#first_div"});
            // Create a new tut_test_div controller on the #second_div element
            $("#second_div").tut_second_div({myElement: "#second_div"});
            // Create a new tut_airport_div controller on the #airport_div element
            $("#airport_div").tut_airport_div();
            }
         )
      }
   )

Note

In this simple example it does not seem to be necessary, but I use the then function to ensure that the airport_model is loaded before the airport_controller, as the controller initially loads data from the model.

The airport controller

The controller and model files will reside in the new folder mvcForNewbies/application/airport.

/**
 * @tag controllers, home
 * Controls a table of airports.
 */
$.Controller('tut.airport_div',
   /* @Static */
   {
      defaults : {
           //For future use
      }
   },
   /* @Prototype */
   {
   /**
    * Called by the framework when the controller is initiated
    * Fetch and display all airports.
   */
   init: function(){
      this.fetchAndDisplay();
   },

   /**
     * Let the Model fetch data for all airports
     *  When that is done, call the list function to display them.
     */
   'fetchAndDisplay': function() {
       var deferredAirports=tut.airport_model.findAll({}, this.callback('backendError'));
       var that=this;
       deferredAirports.then(function(airports){
           that.list(airports);
       })
   },

   /**
    * Displays an alert with error status and text
    * @param {Object} jqXHR
    * @param {Object} textStatus
    * @param {Object} errorThrown
    */
   backendError: function(jqXHR, textStatus, errorThrown){
      alert(textStatus+'\n'+errorThrown+'\njqXHR='+jqXHR);
   },

   /**
   * Display a list of airports, via the init view.
   * @param {Array} airports An array of mvcForNewbies.application.airport objects.
   */
   list: function( airports ){
       $('#airport_div').html(this.view('//mvcForNewbies5/application/airport/views/showTable.ejs', {airports:airports} ));
   }
})

Notes

  • The fetchAndDisplayfunction calls the findAll function in the model. If that succeeds, the list function in the controller is called (via the deferred technique).
  • The fetchAndDisplayfunction is called by the init function, when the controller is created. If he list of airports is huge, and fetched from backend, it will probably cause a noticeable delay.
  • If the fetchAndDisplayfunction fails, an alert is shown by the backendError function. One easy way of testing this is to rename or remove the fixture file (see below).
  • The path for the model seems to have to start with “//mvcForNewbies5″. I am not happy with this, it makes it harder to reuse the model in other applications. There is probably an elegant solution to this, but I have not had time to try to find it.
  • The use of “that” maybe should be explained if you are a Javascript newbie. A good explanation can be found here.

The airport model

/**
 * @tag models, home
 * Wraps backend services.
 */
$.Model('tut.airport_model',
/* @Static */
{
	/**
 	 * Retrieves airport data from your backend services, or from fixture
 	 * @param {Object} params params that might refine your results.
 	 * @param {Function} success a callback function that returns wrapped location objects.
 	 * @param {Function} error a callback function for an error in the ajax request.
 	 */

    findAll: function( params,  error ){
        var myFixture = "";
        var myUrl = "";
        var fixtures= true;
        if (fixtures) {
            myUrl= '/airport';
            myFixture = "//mvcForNewbies/application/airport/airport.json.get";
        } else {
            myUrl= 'perl/getAllAirports.pl';
        }

        return $.ajax({
            url: myUrl,
            type: 'get',
            dataType: 'json airport_model.models',
            data: params,
            error: error,
            fixture: myFixture
        });

    }

},
/* @Prototype */
{});

Notes

  • I have the same problem with the fixture path as with the view path in the controller.
  • The choice between fixture and a backend database is not the most elegant, but maybe pedagogic. I know there are better ways documented. maybe I will implement a more elegant solution in a later release.

The airport view (showTable.ejs)

This view will just create a simple table:

<h2>Airports</h2>
<table	width=300>
    <thead>
        <tr>
            <th	class='AirportName'>Name</th>
            <th	class='Latitude'>Latitude</th>
            <th	class='Longitude'>Longitude</th>
        </tr>
    </thead>
    <tbody>
        <%for(var i = 0; i < airports.length ; i++){%>
           <tr>
              <td class='AirportName'><%=airports[i]['AirportName']%></td>
              <td class='Latitude'><%=airports[i]['Latitude']%></td>
              <td class='Longitude'><%=airports[i]['Longitude']%></td>
           </tr>
        <%}%>
    </tbody>
</table>

Notes

  • It is possible to use the information in the airport objects to generate the column header contents, and get a more generic view, (see the cookbook example)

Test this here, and download code from here

Event handling

(Adjusted for Release 3.2)

I will now add a very simple event handling.
For this purpose, I will create a second controller for second_div, and (for clarity) rename test_div to the original name, first_div. Second_div will listen to the click event in first_div, and report both the number of clicks on itself and on first_div.

The first step is to trigger the event pressed1 in the my_first_button click event handler in first_div’s controller, after the refresh action:

   '#my_first_button click': function( el ){
      this.pressCounter++;
      $(this.refresh());
      $('#second_div').trigger('pressed1');
   },

The next step is to create a controller for second_div, which besides listening to its own my_first_button click event also will listen for the pressed1 event coming from first_div.

Second_div controller

$.Controller.extend("tut.second_div",
    /* @static */
    {
        defaults : {
            myElement: "#second_div"  //
        },

        listensTo:["pressed1"]
    },
    /* @prototype */
    {
        pressCounter: 0,// press counter
        pressCounter1: 0,// press counter, first_div
        /**
         * The init function is called by the framework when we create the controller.
         * Here, we will just refresh the view.
         */
        init : function() {
            $(this.refresh());
        },
        /**
         * Responds to a click on the button created by the template.
         * Increment the press counter, and refresh.
         * @param el    Not used
         */
        '#my_first_button click': function( el ){
            this.pressCounter++;
            $(this.refresh());
        },
        /**
         * Refresh the element by completely replacing all HTML.
         */
        refresh : function() {
            $(this.options.myElement).html('application/second_div/second_div_init.ejs',
                {pressed1: this.pressCounter,  my_name:this.options.myElement,
                    pressed2: this.pressCounter1});
        },
        /*
         * Event triggered by button click in first_div
         * Fetch the pressCounter from first_div
         */
        'pressed1': function( el, ev ) {
            this.pressCounter1=$("#first_div").controllers()[0].pressCounter;
            this.refresh();
        }
    }
);

Notes

  1. $(“#first_div”).controllers() (on event ‘pressed1′) will fetch all conrollers bound to the element with id=first_div. We have only one, so the index to use is zero. Probably there is a better place to store the pressCounter value from first_div.
  2. The refresh function uses the new view for second_div. in file second_div_init.ejs:

second_div_init.ejs

My name is: <%= my_name %><br>
You pressed me <%= pressed1 %> times<br>
You pressed first_div <%= pressed2 %> times<br>
<button id='my_first_button'>Press me</button>

Test this here, and download code from here

Next entry

In the next entry in this blog I will try to use the testing tools provided with javascriptMVC. As I am unused to them, it will probably take some time.

Tidying up

(Adjusted for Release 3.2)

I will

  1. Remove duplicated code
  2. Remove unnecessary variables
  3. Remove hardcoding of #first_div
  4. Rename the controller, with a namespace

Remove duplicated code

As both the init function an the my_first_button click event handler partly execute the same code, I will create a refresh function, and move the common code there.

Remove unnecessary variables

I will skip the presses variable in the defaults section, and only use the variable called x, which I now rename to the more readable name pressCounter (I do not think I will have any problem with CamelCase here).

Remove hard coding of #first_div

The name of the element bound to the controller will be passed as a parameter. Therefore I will add a myElement variable to the defaults section. (I think it might be a good practice to have default values set to all parameters.)
In that way, I can reuse the controller and bind it to more than one div. There is probably an easier/(better) way to get the name of the element, but I do this to demonstrate parameter passing to the controller.

Rename the controller, with a namespace

I will call the controller tut.test_div, partly to indicate that it does not belong to a specific div, and partly to start using a namespace. As the view will present the name of the div, this will be passed as a parameter to the view.

New controller code

$.Controller.extend("tut.test_div",
    /* @static */
    {
        defaults : {
            myElement: "#first_div"  //
        }
    },
    /* @prototype */
    {
        pressCounter: 0,// press counter
        /**
         * The init function is called by the framework when we create the controller.
         * Here, we will refresh the view.
         */
        init : function() {
            $(this.refresh());
        },
        /**
         * Responds to a click on the button created by the template.
         * Increment the press counter, and refresh.
         * @param el    Not used
         */
        '#my_first_button click': function( el ){
            this.pressCounter++;
            $(this.refresh());
        },
        /**
         * Refresh the element by completely replacing all HTML.
         */
        refresh : function() {
            $(this.options.myElement).html('application/first_div/first_div_init.ejs',
                {pressed1: this.pressCounter,  my_name:this.options.myElement});
        }

    }
);

New view code

first_div_init.ejs now looks like this:

    My name is: <%= my_name %><br>
    You pressed me <%= pressed1 %> times<br>
    <button id='my_first_button'>Press me</button>

New code for mvcForNewbies.js

In mvcForNewbies.js I have to add the div name as a parameter when I create the controller:

steal(
    '//jquery/class/class.js',
    '//jquery/controller/controller.js',
    '//jquery/view/ejs/ejs.js',
    '//jquery/controller/view/view.js',
    '//jquery/controller/subscribe/subscribe.js',
    '//jquery/model/model.js',
    '//jquery/dom/fixture/fixture.js').then(
    'mvcForNewbies3/application/first_div/first_div_controller.js').then (
    function($){
        $(document).ready(
            function() {
                // Create a new tut_test_div controller on the #first_div element
                $("#first_div").tut_test_div({myElement: "#first_div"});

                // Create another tut_test_div controller on the #second_div element
                $("#second_div").tut_test_div({myElement: "#second_div"});
            }
        )

    }
)

Notes

  • The name of the controller must (here) be tut_test_div, the dot is converted to underscore, see Controller and other names at in this blog entry.
  • I have also created a second div, controlled by another instance of the tut_test_div controller. (The size and position of this second div has been added to the css file, and the div itself is created in the html file).

Test here and download code here.

Adding some functionality

(Adjusted for Release 3.2)

Adding a counter

I will now make the application a little more interesting by adding a counter, and presenting the number of times the user has pressed the button.
I will also make some small changes to the stylesheet (not documented here).
The counter requires a variable to hold a value.
As I am used to declarative languages, I prefer to declare variables, even if it not necessary in Javascript. There are probably many ways of declaring (or creating) such a variable here, with corresponding different scopes.
One way I know of is to create it as an attribute (here with the name pressed) in the controller’s defaults section, it will then be accessed via this.options.pressed.
I will also try to create a counter variable called x in the prototype section, and present both in the view.
 

The controller

The new controller code will be:

$.Controller.extend("first_div",
    /* @static */
    {
        defaults : {
            presses: 0 // press counter, alt.1
        }
    },
    /* @prototype */
    {
        x: 0,// press counter, alt.2
        /**
         * The init function is called by the framework when we create the controller.
         * Here, we will fill the div with some content from the view.
         */
        init : function() {
            this.x= 0;
            this.refresh();
        },

        /**
         * Responds to a click on th button created by the template
         * @param el    Not used
         */
        '#my_first_button click': function( el ){
            this.options.presses= this.options.presses+1;
            this.x= this.x+1;
            this.refresh();
        },

        /**
         * Here, we will fill the div with some dynamic content from the view.
         */
        refresh: function() {
            $('#first_div ').html('application/first_div/first_div_init.ejs',
                {pressed1: this.options.presses, pressed2: this.x});

        }

    }
);

Notes

  • The counter values (this.options.presses and x) are passed as a named parameters (pressed1) and pressed2) to the view. This might be confusing for a newbie.
  • The view must of course also be changed, as the existing one does not take care of a parameter with the name pressed1 or pressed2.

The view

The new version of first_div_init.ejs will look like this:

<h2><My firs div></h2>
You pressed me <%= pressed1 %>/<%= pressed2 %> times<br><br>
<button id='my_first_button'>Press me</button>

Testing the application (here) shows that both x and pressed works as expected.
You can download the source code here

Create a simple application

(Adjusted for Release 3.2)

One problem with the cookbook application is that the recipe controller is attached to the document element. (This is achieved by setting the onDocument attribute to true in the static section of the Recipe controller). Normally you would like to attach your controllers to lower level elements, such as DIVs or FORMs.
As I personally like to have another folder structure than the one created by the js jquery/generate/app code generator,  I will create the application manually.

Controller and other names

One thing a newbie must be aware of is the underscore() function, converting for example OneTwo to one_two. This function seems to be applied at least by code generators, producing unexpected results.

Example: If you choose to use the name MyRecipe when you are “scaffolding” the cookbook (js jquery\generate\scaffold Cookbook.Models.MyRecipe), the resulting controller file will be my_recipe_controller.js. Inside that file the created controller will be named Cookbook.Controllers.MyRecipe. If you later would want to rename MyRecipe to something else,  you will have a problem.

I will therefore avoid  CamelCase and use underscore to separate elements of names, until I understand better how the underscore function is applied in the framework. The only exception is the project folder, which I will call mvcForNewbies1 (as I have detected no problems with that, yet :-) ).

Create a project folder

My application will start very simple, with just one DIV element with some text, and a button. The div will have the Id my_first_div (note the absence of CamelCase).
Create the project folder in the javascriptmvc installation folder, in my case it will be javascriptmvc/mvcForNewbies.

Create your start page

… in the project folder,  loading the steal.js script, and the project “top” script, mvcForNewbies.js. I will name my startpage mvcForNewbies.html, but you can use any name you want.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
   "http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
   <title>JavascriptMVC For Newbies Part 1
   <link rel="stylesheet" type="text/css" href="mvcForNewbies.css" />
   <script type='text/javascript' src='../steal/steal.js?mvcForNewbies1,development'>
   </script>
<body>
    <div id='first_div'>
    </div>
</body>
</html>

The document contains only one div, all other content will be created by Javascript code, often via templates.

Create the top script

The first version of the top script (mvcForNewbies.js) looks like this:

steal(
    '//jquery/class/class.js',
    '//jquery/controller/controller.js',
    '//jquery/view/ejs/ejs.js',
    '//jquery/controller/view/view.js',
    '//jquery/controller/subscribe/subscribe.js',
    '//jquery/model/model.js',
    '//jquery/dom/fixture/fixture.js').then(
    'mvcForNewbies/application/first_div/first_div_controller.js').then (
        function($){
             $(document).ready(
                function() {
                    // Create a new first_div controller on the #first_div element
                    $("#first_div").first_div();
               }
            )

        }
    )

The steal function first loads the jquery and javascriptMVC files, then the controller file, and then, on $(document).ready, connects the controller to the div. I am not sure why I have to use the $(document).ready function, the srchr application seems not to need it.

Create the controller

The first version of the first_div controller looks like this:

$.Controller.extend("first_div",
    /* @static */
    {
        defaults : {
            defaultText : "Hi, i am firstDiv!"
        }
    },
    /* @prototype */
    {
        /**
         * Initialize a new instance of the firstDiv controller.
         */
        init : function() {
            $('#first_div ').html('application/first_div/first_div_init.ejs',  {header:"My first div"});
        },
        /**
         * Responds to a click on th button created by the template
         * @param el    Not used
         */
        '#my_first_button click': function( el ){
            $('#first_div').append("<br>You pressed me!");
        }

    }
);

Notes

  • The loading of the template also could also be written like this:
    $('#myFirstDiv').html('//mvcForNewbies/application/first_div/first_div_init.ejs',
                                 {header:""My first div""});

    I prefer not to include the application name here, as renaming the current application and reuse in another application will be easier. The future will perhaps show if this is a good idea.

  • defaultText is not used.
  • When the button with id my_first_button is clicked, the controller will append the text You pressed me! to the div. Everytime you click, new text will be added on a new row. Maybe not very amusing…
  • The name of the controller is just first_div, I will probably add some prefix or namespace later (testing refactoring).

Create a view

We only need one more piece of code to get this simple application to work, and that is the view, which in this case is the template first_div_init.ejs:

<h2><%= header %></h2>
<button id='my_first_button'>Press me</button>

This code will produce an h2 element, with text passed as the named parameter header, and the button with id my_first_button.

Adding style

As you may have noticed, the style sheet loaded by the html page has not been defined yet. We cretae a simple css file (mvcForNewbies.css), giving the first_div some background color:

#first_div {
	background: #CCCCCC;
	position: absolute;
	top: 10px;
	left: 10px;
	width: 200px;
}

Not setting the height will allow the div to grow vertically as we add more rows.
This example can be tested here, and downloaded here