Wednesday, May 29, 2013

HTML5+MVC Course, Day 4

Continued from day 1, day 2, day 3.

This lesson was mostly about writing Object Oriented in JavaScript.

 

Lets start by creating a basic model Customer, customer contains a first name, last name and full name that contains both:

function customer() {
    var _this = this;
    _this.firstName = 'Yair';
    _this.lastName = 'Siwek';
    _this.fullName = _this.firstName + ' ' + this.lastName;
}

Given that this is our body:

<body>
    <div>
        <span id="firstName"></span>
        --
        <span id="lastName"></span>
    </div>
    <div>
        <span id="fullName"></span>
    </div>
</body>

A basic use for the model will be:

<script src="Scripts/App/Customer_a.js"></script>
<script>
    $(function () {
        var cust = new customer();
        cust.firstName = "Moshe";

        $('#firstName').html(cust.firstName);
        $('#lastName').html(cust.lastName);
        $('#fullName').html(cust.fullName);
    });
</script>

This works.

But what if before using jQuery we would have changed firstName? then fullName would have still been Yair Siwek. In order for the right value for fullName to appear we need to change fullName to a method:

_this.fullName = function ()
{
    //if we wrote this then it's value would have been fullName, but _this is customer!
    return (_this.firstName + ' ' + _this.lastName);
};

TODO: _this

We also need to change our html:


$('#fullName').html(cust.fullName());

to use a method instead of a property.

 

We can now create another instance of customer:

var cust = new customer();
var cust1 = new customer();

But if we use a debugger we will see the method fullName is recreated for each instance of customer:

CustomerMethodDefinition

To fix that we will use a Prototype. A Prototype is common to all instances:

function customer() {
    var _this = this;
    _this.firstName = 'Yair';
    _this.lastName = 'Siwek';
}

customer.prototype = {
    fullName:  function ()
    {
        //_this does not exist here!
        return (this.firstName + ' ' + this.lastName);
    }
}

Note: We can’t use _this here because it doesn’t exist in the scope.

Now the instance doesn’t include the implementation (the Prototype has it).

CustomerPrototype

 

We could have also used an Anonymous Type:

<script>
    $(function () {
        var cust = {
            firstName: 'Joe',
            lastName: 'Sh',
            fullName: function () {
                return cust.firstName + ' ' + cust.lastName;
            }
        };

        $('#firstName').html(cust.firstName);
        $('#lastName').html(cust.lastName);
        $('#fullName').html(cust.fullName());
    });
</script>

Though we can’t use multiple instances the result is the same.

 

Book recommendation: Learning JavaScript Design Patterns by Addy Osmani (166 pages)

Shows how to create an empty object – 3 ways.

 

With the last example there was a problem: changing the values does not change the controls:

cust.firstName = "Moshe";

$('#firstName').html(cust.firstName);
$('#lastName').html(cust.lastName);
$('#fullName').html(cust.fullName());

cust.lastName = 'Blah';

Both last name and full name won’t contain “Blah”.

To solve that we will have to do a data binding with KnockOut.

var cust = new customer();
cust.firstName = "Moshe";

ko.applyBindings(cust,
                        //$('#divTop'));//doesn't work
                        //document.getElementById('divTop'));//works
                        $('#divTop')[0]);//work
cust.lastName = 'Blah';

model – the data

node - the element on which we want to the binding,

$(‘#Name’) returns an array, node needs a single element so we will have to pass the first element by adding [0].

 

Note: to add inteliscence to the JavaScript code we will drag and drop the KnockOut library to the top of the script, it will be converted to:

/// <reference path="../knockout-2.2.1.debug.js" />

And from now on you have inteliscence turned on (if you have Resharper then you can skip this step).

 

To make the model observable we will have to change each field to be an observable by:

_this.firstName = ko.observable('Yair');
_this.lastName = ko.observable('Siwek');

that will convert it to a method which is used by:

In HTML:

<input id="firstName" data-bind="value: firstName"/>

In JavaScript:

customer.prototype = {
    fullName:  function ()
    {
        return (this.firstName() + ' ' + this.lastName());
    }
}

When writing the data-bind from the HTML you can use either name() or name. But from JavaScript you must use name().

TODO:check why in JS

 

TODO: 18:06 not clear: when working with ko.computed +ko.observable than firstName() in the HTML data-bind doesn’t work. TODO: add code

TODO: There is difference between ko.computed and function(), computed is only called once?

 

Observable Array

When the array is simple (non object):

_this.grades = ko.observableArray([10, 11, 12]);

<ul data-bind="foreach: grades">
    <li>
        <span data-bind="text: $index" ></span>
        .
        <span data-bind="text: $data" ></span>
    </li>
</ul>

$index – the index of the item in the array.

$data – the item data

Output:

ObservableSimpleArray

 

When the array is of objects we can bind to the property names:

_this.grades = ko.observableArray([
    { subject: 'hist', mark: 90 },
    { subject: 'math', mark: 95 } ]);

<ul data-bind="foreach: grades">
    <li>
        <span data-bind="text: subject" ></span>
        -
        <span data-bind="text: mark" ></span>
    </li>
</ul>

Note: for intelliscence in KO you will have to install ASP.NET and Web Tools 2012.2.

 

KO Templates

HTML usage of a Template has at least 2 prameters:

name – the template name

data – the observable data

The template definition:

TODO: Add code

id – the name of the template

 

Adding templates to a page:

1. Using href:

TODO

Will cause the template to be downloaded to the client even when the template is not used.

2. Dynamically through JS code:

TODO