Problem: Storing data in JavaScript.

Setup

You will need copies of the following files:

PLEASE NOTE: You must add a reference to the mikesmarionette.d.ts file at the top of the typescript file


        ⁄⁄⁄ <path="typings/backbone.marionette/index.d.ts" ⁄>
    

Motivation

We need to create a way of storing data in JavaScript that has been read from a webserver or webservices. Backbone.js proivides a class called Backbone.Model.

This model must also be used to display the data on the web pages and record any modifications made to it prior to it being written back to the webservice

We want to change to using a Typescript version of this Model class

Usage

Assume we need a new Model, we must begin by create a new class that inherits from Backbone.Model

The backbone model constructor and initializer method looks like this:

constructor(attributes?: any, options?: any);
initialize(attributes?: any, options?: any): void;

Both the arguments are optional JS object-literals and need not be supplied. The attributes describes the attribute names (and their default values) of the class, while the options will be described in later notes. When you inherit from the class you call the parent constructor using super(). For example, specify a string attribute called name

constructor(initialText: string = "Hello World", initialNumber: number = 1234)
{
    super({
        name: "Bob"  ←Of course, we can replace the value "Bob" with a variable to set any value we want
        });←I have decided not to provide any options, hence none are passed in the call to the parent constructor
}

We can create a Model1 class like this:

namespace BasicModelNS
{
    export class Model1 extends Backbone.Model
    {
        // You can set default attribue values instead of setting them manually
        // when you create the object...of course you probably want to override
        // at least some of the default values when you create the object.
        // It's done the same way!!
        constructor(initialText: string = "Hello World", initialNumber: number = 1234)
        {
            // **************************************************
            // NOTE: THE MODEL ATTRIBUTE VALUES ARE STORED IN THE
            // Backbone.js MODEL CLASS, AND HAVE TO BE PASSED
            // AS AN OBJECT LITERAL IN THE CONSTRUCTOR
            // OR INITIALIZER METHOD.
            // ***************************************************
            super({
                text: initialText, // Attribute called 'text'
                num: initialNumber // Attribute called 'num'
            });
        }
    }
}
    

Of course we now want to read and set the attribute values. Backbone models provide get() and a set() methods for doing this. They work like this:

// Using Model1 from the above code.
    let model: BasicModelNS.Model1 = new BasicModelNS.Model1("Hi", 10);
    let text: string;
    text = model.get('text');       // Read it
    model.set('text', 'Hi Again');  // Write to it
    

Of course, we want to wrap and hide Backbones model, and use the OOPs approach of properties. So lets modify the Model1 class

namespace BasicModelNS
{
    export class Model1 extends Backbone.Model
    {
        constructor(initialText: string = "Hello World", initialNumber: number = 1234)
        {
            super({
                text: initialText, // Attribute called 'text'
                num: initialNumber // Attribute called 'num'
            });
        }
        // Properties
                                                                                    
        get Id(): string { return this.id; } // id is provided by Backbone, you can set it to uniquely idenify model if you wish
        set Id(value: string) { this.id = value; }
        get Text(): string { return this.get('text'); }       
        get SafeText(): string { return this.escape('text'); }  // To prevent XSS attack, you need to escape the text
        set Text(value: string) { this.set('text', value); }
        get HasText(): boolean { return this.has('text'); }
        get Number(): number { return this.get('num'); }
        set Number(value: number) { this.set('num', value); }
        get HasNumber(): boolean { return this.has('num'); }
    }
}
    

Here's how we might use this class.

class ExamplePage
{
    PageLoad(): void
    {
        let instance: BasicModelNS.Model1 = new BasicModelNS.Model1("Hi", 4567);
        if (instance.HasText)
        {
            window.alert("Model instance has text attribute (using has)");
        }
        window.alert("Initial values " + instance.Text + ":" + instance.Number);
        
        // Change values 
        instance.Text = "Ha ha";
        window.alert("Text changed to " + instance.Text);
        
        // Each model has a unique id
        // You can set and get it if you wish
        // When a new model is created, the identifier is empty 
        // unless it is manually assigned
        instance.Id = Math.random().toString(36).substr(2);
        window.alert("Id " + instance.Id);
        // Backbone gives the model its own unique id
        window.alert("Client Id (cid) is automatically set " + instance.cid);
    }
}
$(function ()
{
    let page: ExamplePage = new ExamplePage();
    page.PageLoad();
});

Often we want to "collect" multiple models together, typically when we read a number of rows of data from a database table. Backbone provides a class called a Collection. Just like a model, a Collection has a constructor and an initialize method.

constructor(models?: TModel[] | Object[], options?: any);
initialize(models?: TModel[] | Object[], options?: any): void;

Both arguments ar optional. Here the models argument optionally allows you to provide an array of initial models to populate the collection, while the second argument provides options for using the collection (used in later notes) Creating a new collection class is fairly easy:

namespace BasicModelNS
{    
    ......
    export class Model1Collection extends Backbone.Collection<Model1>
    {
        constructor(models?:Model1[])
        {
            super(models);
        }        
    }
}
    

Here's how we might use this class.

PageLoad(): void
{
        let m1: BasicModelNS.BookModel = new BasicModelNS.Model1();// Use default values for this model instance
        let m2: BasicModelNS.BookModel = new BasicModelNS.Model1("bb", 22);
        let m3: BasicModelNS.BookModel = new BasicModelNS.Model1("cc", 33);
        // Add books to collection in an initial array
        // of models.
        let models: Example3NS.BookModel[] = [
            m1,
            m2,
            m3
        ];
        let modelCollection: BasicModelNS.Model1Collection = new BasicModelNS.Model1Collection(books);
        window.alert("Collection length " + bookCollection.length);
        this.DumpCollection("modelCollection", modelCollection);
        // Or like this, dynamically after the collection
        // has been created.
        let modelCollection2: BasicModelNS.Model1Collection = new BasicModelNS.Model1Collection();
        modelCollection2.add(m1);
        modelCollection2.add(m2);
        modelCollection2.add(m3);
        // You can also push() models on to a collection and pop() them off
        // You can remove a model (or an array of models)
        this.DumpCollection("modelCollection2 pre remove", modelCollection2);
        modelCollection2.remove(m2);
        this.DumpCollection("modelCollection2 post remove", modelCollection2);
    }
    DumpCollection(name: string, collection: BasicModelNS.Model1Collection): void
    {
        let msgStr: string = "";
        collection.each(function (model: BasicModelNS.Model1)
        {
            msgStr += model.Text + " ";
        });
        window.alert("["+collection.size()+"] Dumping " + name + ": " + msgStr);
    }
}
    

Details

Build an application JavaScript application for a bookstore. The book details should include:

Create at least 5 books and display the books details. A windows.alert() will do for this.

Hints

  1. Use the Backbone Model and Collection