AFrameJS Tutorial - A Response to "Backbone.js Tutorial - by noob for noobs"

Note: This article was updated on Feb 7, 2011 to use the new syntax of AFrame.create.

I am trying to follow the format given by Thomas Davis in "Backbone.js Tutorial - by noob for noobs", so this is going to look extremely similar.

The basic theory behind AFrameJS is identical to Backbone.js, bring the MVC programming paradigm to Javascript applications. For too long, front end developers have been muddling their models, views, control, and data access code together resulting in very difficult to maintain/modify/reuse code. The goals of MVC are to first and most importantly, simplify the cognitive load on the developer. Code reuse, easier unit testing and all the other goodies that people talk about when referring to MVC are natural outcomes.

An MVC Primer

The main idea of MVC is to separate data, views of that data, and the bindings of the two into distinct units. Data and all operations on that data create a Model. Views are as the name implies, views of a piece of data. Controllers are the glue that bind the two together.

How does this "reduce the cognative load on the developer?" Well, simply put, by keeping these ideas separated in code, it helps you keep them separated in mind. The human mind is terrible at multi-tasking, by focusing on one thing at a time, we have a much easier time at getting that one thing right.

So what is a model? A model is a piece of data and any operations on that data. Sounds a bit like the standard definition of a Class. Well, it is, the model contains data and any operations/validations on that data. It is where "business logic" typically lives. When thinking about a model, we don't have to think about how that data is presented, we only worry about what happens with that data.

What is a view? A view is how a piece of data is presented. A view doesn't have to be graphical only, it could be purely textual, it could be a way of representing the model to transmit to an RSS reader, it can be any way of "viewing" the data. Views should be semi-dumb to the underlying business/validation logic of the data. The reason - say I write a way of inputting a piece of data today, but tomorrow I need a second way to input that same bit of data. Both of these use Views, but if I wrote the validation logic in the Views, I would have to write it in both views. Not very good. A view often times reacts to changes in its corresponding model. So, if data changes in the model, the view is updated to reflect that, it is preferable that all of this happens automatically.

On to the Code

First, download a copy of AFrameJS - Currently there is no CDN for it. There is a version of AFrameJS for jQuery, MooTools, and Prototype. This tutorial is going to use jQuery and the jQuery version of AFrameJS. All scripts are saved into the scripts subdirectory.

<!DOCTYPE html>
<html>

<head>
    <title>AFrame Introduction</title>

    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
    <script type="text/javascript" src="scripts/aframe-current-jquery.js"></script>
    <script type="text/javascript" src="scripts/demoapp.js"></script>
</head>

<body>

    <ul id="friendList">
    </ul>

    <button id="add-friend">Add Friend</button>
</body>

</html>

Setting Up the Main Controller

While I could, I am not going to create a "Main View" in the Backbone sense. A Display could be used for this purpose, but for startup, I prefer to create a "Main Controller - a controller that initializes up my Model and View objects and then gets rolling.

$( function() {

    $( '#add-friend' ).click( function( event ) {
           var friend_name = prompt("Who is your friend?");
           // We need to create a friend model with this information.  
           // More on this later.
    } );

} );

Lists

Similarly to Backbone, AFrame requires that each View(Display) have a target. The target is where the View is displayed. The target can be updated from within the Display, but for now, we are going to use the list with the id "friendList" as the target of our List. Every list needs a listElementFactory. A listElementFactory is the equivalent of "addFriendLi" in the Backbone demo. We will bind this list to a collection of friends in a moment.

// This is the friends list.  
var friendsList = AFrame.List.create( {
     target: '#friendList',
     listElementFactory: function( model, index ) {
         return AFrame.DOM.createElement( 'li', 
             model.get( 'name' ) );
    }
} );

Modelling our Friends

Our Friends are going to have a very simple data model, in AFrameJS, the layout of our models is defined by a Schema and its schemaConfig.

Our Friend models are going to be created automatically by the way we associate our Collection with the friendSchemaConfig shown
below.

// To define data, create a SchemaConfig.  A Model in AFrame is an
// instance of data combined with a Schema.  The schema config
// will be used when creating the model.
var friendSchemaConfig = {
    name: { type: 'text' }
};

Do We Only Have One Friend? Collections of Friends

As Thomas says, a single model by itself isn't the most exciting thing, imagine a world where you were only ever allowed to have one friend. So we are going to create a Collection to keep track of all of our friends. The collection used is going to be a CollectionArray. AFrameJS also offers a CollectionHash for when you want to access items by a key instead of an index.
In this instance, we know that all of the data inserted into our collection is going to be based on the same Schema, so to make our lives a bit simpler, we can associate a Schema with the Collection. Every time we insert data into the Collection, a model will be created automatically.

// Our friends collection is an array. Our Collection is going to 
// use the friendSchemaConfig to define what each model looks like.
var friendsCollection = AFrame.CollectionArray.create( {
   plugins: [ [ AFrame.CollectionPluginModel, {
        schema: friendSchemaConfig
   } ] ]
} );

Tying Up Loose Ends

Now it is time to tie up the loose ends.

First, I bind the List to the Collection. Whenever items are added or removed from the Collection, these changes will be automatically reflected in the List.

// Updated friendsList, it is now bound to the friendsCollection, 
//so any time a model is added or removed from the friends 
// collection, the list will update.
var friendsList = AFrame.List.create( {
     target: '#friendList',
     listElementFactory: function( model, index ) {
         return AFrame.DOM.createElement( 'li', 
             model.get( 'name' ) );
     },
     plugins: [ [ AFrame.ListPluginBindToCollection, {
            collection: friendsCollection
     } ] ]
} );

Finally, back to our initial "Controller" which is a button click handler that asks what our friend's names are. When we have the name, we are going to call createFriendModel, which creates our model, then we are going to insert the model into the collection. This will cause the list to update.

// we insert into the friendsCollection once we have a name.  
//  The list will be automatically updated.
$( '#add-friend' ).click( function( event ) {
       var friend_name = prompt("Who is your friend?");
       // Add a new friend model to our friend collection.  
       // The Model will be created automatically from the data.
       friendsCollection.insert( { name: friend_name } );
} );

Putting it All Together

Now time for the completed code. A fully functioning demo can be found on JSFiddle. All code can be found on GitHub.

$( function() {

    // Instead of creating a model, create a SchemaConfig.
    // A Model in AFrame is an instance of data combined
    // with a Schema.  The schema config will be used when
    // creating the model.
    var friendSchemaConfig = {
        name: { type: 'text' }
    };

    // Our friends collection is an array.  Instead of binding
    // directly to an event on the collection, when creating
    // the list, we bind the list to the collection.
    // The ListPluginBindToCollection will take care of
    // updating of the list.
    var friendsCollection = AFrame.CollectionArray.create( {
        plugins: [ [ AFrame.CollectionPluginModel, {
             schema: friendSchemaConfig
        } ] ]
    } );

    // This is the friends list.  It is bound to the
    // friendsCollection, so any time a model is added or
    //  removed from the friends collection, the list will update.
    var friendsList = AFrame.List.create( {
        target: '#friendList',
        listElementFactory: function( model, index ) {
            return AFrame.DOM.createElement( 'li',
                model.get( 'name' ) );
        },
        plugins: [ [ AFrame.ListPluginBindToCollection, {
             collection: friendsCollection
        } ] ]
    } );

    // we insert into the friendsCollection once we
    // have a name.  The list will be updated automatically.
    $( '#add-friend' ).click( function( event ) {
           var friend_name = prompt("Who is your friend?");
           // A new model will be created when adding
           // to the friend collection.
           friendsCollection.insert( { name: friend_name } );
    } );
} );

Again, a fully functioning demo can be found on JSFiddle, and all code can be found on GitHub.