Backbone is a popular JavaScript library that gives structure to web applications by providing models, collections and views, amongst other things. Backbone has been around since 2010, and has gained a very large following, with a wealth of commercial websites using the framework. According to Infoworld.com, Backbone has over 1,600 Backbone related projects on GitHub that rate over 3 stars – meaning that it has a vast ecosystem of extensions and related libraries.
Let's take a quick look at Backbone written in TypeScript.
From the Backbone documentation, we find an example of creating a Backbone.Model
in JavaScript as follows:
var Note = Backbone.Model.extend( { initialize: function() { alert("Note Model JavaScript initialize"); }, author: function () { }, coordinates: function () { }, allowedToEdit: function(account) { return true; } } );
This code shows a typical usage of Backbone in JavaScript. We start by creating a variable named Note
that extends (or derives from) Backbone.Model
. This can be seen with the Backbone.Model.extend
syntax. The Backbone extend
function uses JavaScript object notation to define an object within the outer curly braces { … }
. In the preceding code, this object has four functions: initialize
, author
, coordinates
and allowedToEdit
.
According to the Backbone documentation, the initialize
function will be called once a new instance of this class is created. In our preceding sample, the initialize
function simply creates an alert to indicate that the function was called. The author
and coordinates
functions are blank at this stage, with only the allowedToEdit
function actually doing something: return true
.
If we were to simply copy and paste the above JavaScript into a TypeScript file, we would generate the following compile error:
Build: 'Backbone.Model.extend' is inaccessible.
When working with a third party library, and a definition file from DefinitelyTyped, our first port of call should be to see if the definition file may be in error. After all, the JavaScript documentation says that we should be able to use the extend
method as shown, so why is this definition file causing an error? If we open up the backbone.d.ts
file, and then search to find the definition of the class Model
, we will find the cause of the compilation error:
class Model extends ModelBase { /** * Do not use, prefer TypeScript's extend functionality. **/ private static extend( properties: any, classProperties?: any): any;
This declaration file snippet shows some of the definition of the Backbone Model
class. Here, we can see that the extend
function is defined as private static
, and as such, it will not be available outside the Model class itself. This, however, seems contradictory to the JavaScript sample that we saw in the documentation. In the preceding comment on the extend
function definition, we find the key to using Backbone in TypeScript: prefer TypeScript's extend functionality.
This comment indicates that the declaration file for Backbone is built around TypeScript's extends
keyword – thereby allowing us to use natural TypeScript inheritance syntax to create Backbone objects. The TypeScript equivalent to this code, therefore, must use the extends
TypeScript keyword to derive a class from the base class Backbone.Model
, as follows:
class Note extends Backbone.Model { initialize() { alert("Note model Typescript initialize"); } author() { } coordinates() { } allowedToEdit(account) { return true; } }
We are now creating a class definition named Note
that extends
the Backbone.Model
base class. This class then has the functions initialize
, author
, coordinates
and allowedToEdit
, similar to the previous JavaScript version. Our Backbone sample will now compile and run correctly.
With either of these versions, we can create an instance of the Note
object by including the following script within an HTML page:
<script type="text/javascript"> $(document).ready( function () { var note = new Note(); }); </script>
This JavaScript sample simply waits for the jQuery document.ready
event to be fired, and then creates an instance of the Note
class. As documented earlier, the initialize
function will be called when an instance of the class is constructed, so we would see an alert box appear when we run this in a browser.
All of Backbone's core objects are designed with inheritance in mind. This means that creating new Backbone collections, views and routers will use the same extends
syntax in TypeScript. Backbone, therefore, is a very good fit for TypeScript, because we can use natural TypeScript syntax for inheritance to create new Backbone objects.
As Backbone allows us to use TypeScript inheritance to create objects, we can just as easily use TypeScript interfaces with any of our Backbone objects as well. Extracting an interface for the Note
class above would be as follows:
interface INoteInterface { initialize(); author(); coordinates(); allowedToEdit(account: string); }
We can now update our Note
class definition to implement this interface as follows:
class Note extends Backbone.Model implements INoteInterface { // existing code }
Our class definition now implements the INoteInterface
TypeScript interface. This simple change protects our code from being modified inadvertently, and also opens up the ability to work with core Backbone objects in standard object-oriented design patterns. We could, if we needed to, apply the Factory Pattern described in Chapter 3, Interfaces, Classes and Generics, to return a particular type of Backbone Model – or any other Backbone object for that matter.
The declaration file for Backbone has also added generic syntax to some class definitions. This brings with it further strong typing benefits when writing TypeScript code for Backbone. Backbone collections (surprise, surprise) house a collection of Backbone models, allowing us to define collections in TypeScript as follows:
class NoteCollection extends Backbone.Collection<Note> { model = Note; //model: Note; // generates compile error //model: { new (): Note }; // ok }
Here, we have a NoteCollection
that derives from, or extends
a Backbone.Collection
, but also uses generic syntax to constrain the collection to handle only objects of type Note
. This means that any of the standard collection functions such as at()
or pluck()
will be strongly typed to return Note
models, further enhancing our type safety and Intellisense.
Note the syntax used to assign a type to the internal model
property of the collection class on the second line. We cannot use the standard TypeScript syntax model: Note
, as this causes a compile time error. We need to assign the model
property to a the class definition, as seen with the model=Note
syntax, or we can use the { new(): Note }
syntax as seen on the last line.
Backbone also allows us to use ECMAScript 5 capabilities to define getters and setters for Backbone.Model
classes, as follows:
interface ISimpleModel { Name: string; Id: number; } class SimpleModel extends Backbone.Model implements ISimpleModel { get Name() { return this.get('Name'), } set Name(value: string) { this.set('Name', value); } get Id() { return this.get('Id'), } set Id(value: number) { this.set('Id', value); } }
In this snippet, we have defined an interface with two properties, named ISimpleModel
. We then define a SimpleModel
class that derives from Backbone.Model
, and also implements the ISimpleModel
interface. We then have ES 5 getters and setters for our Name
and Id
properties. Backbone uses class attributes to store model values, so our getters and setters simply call the underlying get
and set
methods of Backbone.Model
.
As we have seen, Backbone allows us to use all of TypeScript's language features within our code. We can use classes, interfaces, inheritance, generics and even ECMAScript 5 properties. All of our classes also derive from base Backbone objects. This makes Backbone a highly compatible library for building web applications with TypeScript. We will explore more of the Backbone framework in later chapters.
18.225.95.60