Introduction

Project Avatar is an end-to-end web application framework based on the thin-server architecture (TSA) designed to build modern HTML5 applications. Project Avatar supports both view components (client) as well as service components (server). However, unlike other frameworks, view and service components are not tightly coupled so developers can opt to use them à la carte.

Service components are written in JavaScript and are executed on top of a Java EE 7 server extended with the Avatar Scripting Container (Avatar.js). View components are developed using HTML5 plus a rich set of widgets and data binding with minimal JavaScript code. In this tutorial, you’ll learn about Project Avatar by examining code fragments and running sample applications that demonstrate several key concepts.

Getting Started

Project Avatar introduces several new concepts. The first one is modules of which there are two types. View modules, which are specific to clients and provide the UI components that are downloaded to the browser, and service modules, which run on the server and provide supporting services to the view components. By having view and service modules in a single application, Project Avatar provides an end-to-end framework that contains the client and server code in a single deployable archive. A Project Avatar application consists of at least one module, either a view module or a service module. As we shall see, view modules are often implicitly defined by the inclusion of one or more HTML pages in your application.

Clock

This first example of a Project Avatar application is a clock that displays the time retrieved from the server. It shows some of the most fundamental concepts of Project Avatar including services and models.

REST Service

The server part of this application is contained in one single JavaScript file that is executed by the Avatar Scripting Container when the application is deployed. The first step in the implementation of a service module is to get a reference to the avatar object, which can be used to register services.

var avatar = require("org/glassfish/avatar");

var getTime = function() {
    var current = new Date();
    return {
        msg: 'The server time is ',
        h: current.getHours(),
        m: current.getMinutes(),
        s: current.getSeconds() };
};

avatar.registerRestService({ url: "data/time" },
    function() {
        this.onGet = function(request, response) {
            return response.send(getTime());
        };
    }
);

The first argument to the registerRestService is a meta-data object that declares the path in the application’s URL namespace of the REST resource, in this case data/time. The second argument is a constructor function that defines the implementation of the service. The onGet method is invoked for each GET request that matches the service’s path, passing a request and a response object. The send method on the response object is used to return the requested resource to the client, in this example, an object returned by calling the function getTime. By default, resource representations are assumed to be application/json.

View Module

View modules make up the presentation layer of an application, defining the user interface and the logic for interacting with the server. The view module for this application comprises a single HTML file shown next:

<html>
    <head>...</head>
    <body>
        <script data-model="rest">
            var Message = function() {
                this.msg = this.h = this.m = this.s = '';
            };
        </script>
        <script data-type="Message" data-instance="message" data-url="data/time"></script>
        <output class="time">#{message.msg}#{message.h}:#{message.m}:#{message.s}</output><br><br>
        <button onclick="#{message.get()}">Update</button>
    </body>
</html>

The body of this HTML page consists of a Project Avatar model and Project Avatar model instance defined using script tags and a UI defined using standard HTML elements with a few extensions. A model, in this context, is defined using a JavaScript constructor function that combines application data, in the form of properties, together with application logic, in the form of methods that operate on the data. The special data-model attribute of script is used to indicate the model kind and the data-instance attribute is used to define instances of a model. A single instance named message is defined in this view.

This UI has a single output element that displays the time retrieved from the server and a button that can be used to refresh the time.


The output element gets the actual data from the model using the Expression Language (EL). This data binding results in the text being updated every time the data model value is changed. The use of EL expressions eliminates the need for writing controller logic in JavaScript in order to make the page dynamic.

The onclick event handler on the button is another EL expression, which tells the data model to fetch new data from the server. The method get() on the rest model comes from its prototype object which is part of the Project Avatar framework and is inherited by all rest models you define in your application. It will send a GET request to the URL of the model and populate the resulting data as properties on the model object. As a result of doing that, because of data binding, the output text will automatically be updated with the new time as soon as the response is received.

Push Service

In addition to REST, Project Avatar also has built-in support for Server Sent Events (SSE), consisting of push services on the server side and push models on the client side. To show how to use these we will add an automatic update feature to the clock where the server continuously pushes the new time to the client every second. In order to show another aspect of REST services, we will allow users to update the message returned by the server as well.

var avatar = require("org/glassfish/avatar");

var message = 'The server time is ';

var getTime = function() {
    var current = new Date();
    return {
        msg: message,
        h: current.getHours(),
        m: current.getMinutes(),
        s: current.getSeconds() };
};

avatar.registerRestService({ url: "data/message" },
    function() {
        this.onPut = function(request, response) {
            message = request.data.msg;
            response.send(null);
        };
    }
);

avatar.registerPushService({ url: "push/time" },
    function() {
        this.onOpen = this.onTimeout = function(context) {
            context.setTimeout(1000);
            return context.sendMessage(getTime());
        };
    }
);

The rest service now overrides the onPut method and allows users to update the message returned by the push service. The push service is registered in a similar way as the rest service: a meta-data object containing the only mandatory property url and a constructor function. The onOpen method is called when a new SSE connection is established, passing in a context object. The push service uses its built-in timer facility, by calling context.setTimeout and overriding onTimeout, to send a new message to the client every second. As before, the function getTime is invoked to return the latest time.

To communicate with this new push service, the client side needs a push model as well as some other changes to support updating the message.

<html>
    <head>...</head>
    <body>
        <script data-model="rest">
            var Message = function() {
                this.msg = '';
            };
        </script>
        <script data-model="push">
            var Time = function() {
                this.msg = this.h = this.m = this.s = '';
            };
        </script>
        <script data-type="Message" data-instance="message" data-url="data/message"></script>
        <script data-type="Time" data-instance="time" data-url="push/time"></script>

        <output class="time">#{time.msg}#{time.h}:#{time.m}:#{time.s}</output><br><br>
        <label for="im">New Message: </label>
        <input id="im" size="35" data-value="#{message.msg}"/>
        <button onclick="#{message.put()}">Update</button>
    </body>
</html>

The new view defines two model types, Message and Time, and corresponding instances for each one, message and time. The output element now displays the data from the push model time and the input field binds to message and calls put to update the corresponding REST resource.


By entering a new message and clicking the Update button, the server’s message will be updated and the client UI refreshed once the next push message is received.

Web Store

Using the Web Store sample application, you can store and then retrieve a collection of key/value pairs. You can perform any REST operation on the keys; PUT to add or update key/value pairs in the collection; GET to retrieve a key’s value, and DELETE to delete a key/value pair from the collection. When you update the keys and values, the collection gets updated and because of data binding, all the data is synchronized. This sample can be found in the examples directory that is part of the distribution.

The Web Store application defines two REST services. The first service returns a single collection resource corresponding to all registered key/value pairs. The second service is defined for each individual key/value pair and is used to create, retrieve, update and delete (a.k.a. CRUD) key/value pairs. The collection service is shown first:

avatar.registerRestService({url: "data/items/"},
    function() {
        this.onGet = function(request, response) {
            return itemsProvider.getCollection()
            .then(function(result){
                var items = { "items" : result.data };
                return response.send(items);
            });
        };
    }
);

The code snippet above registers a REST service on the relative URL “data/items/”. It overrides a single onGet method from the prototype that uses a data provider to retrieve and return an object containing all the items. A data provider is akin to an associative array in which values can be stored and retrieved using keys. However, providers can be implemented on different forms of data sources such as text files, distributed caches, databases, etc.

The service shown above calls the method getCollection in order to retrieve the entire item collection. Since Project Avatar is based on a Node programming model, all method calls are asynchronous and either return promises, as in the example above, or accept callbacks. By default, all method calls return promises if no callback is supplied. Since, in this case, we want to return the entire collection, the method then on the promise is immediately invoked to wait for the result and return the response back to the client. Using promises or callbacks is mostly a matter of personal preference; however, promises tend to make the code more readable by avoiding the so-called Pyramid of Doom problem. When using promises it’s important to terminate the promise chain in order to not loose any errors that may happen in any of the handler functions. This can be done by registering an error handler at the end of the chain, but a better way is to simply return the promise and let the Project Avatar framework terminate the chain. This ensures that a proper error response is sent back to the client if an error occurs. Note in the example above that the handler returns the promise returned by the send() method. When a handler returns a promise (called inner promise) the outer promise becomes the inner promise. This ensures that any error that occurs in the send() method is also caught.

Let us now look at the implementation of the item service:

avatar.registerRestService({url: "data/items/{item}"},
    function() {
        this.onGet = function(request, response) {
            itemsProvider.get(this.item, function(error, itemValue) {
                if (!itemValue) {
                    itemValue = {};
                }
                response.send(itemValue);
            });
        };

        this.onPut = function(request, response) {
            itemsProvider.put(this.item, request.data, function(result) {
                response.send(result);
            });
        };

        this.onDelete = function(request, response) {
            itemsProvider.del(this.item, function(result) {
                response.send(result);
            });
        };
    }
);

The methods onGet, onPut and onDelete are overridden in order to interface with the provider. These method implementations show the alternative, callback style whereby the last parameter passed is a function to be called when the result becomes available. These callback functions, in turn, call the method send on response to send the result to the client.

Data Providers

Finally, this example uses a data provider to store the key/value pairs. As stated previously, providers can be implemented on different forms of data sources. Let us explore the creation of itemsProvider using a file data source and a database data source based on the Java Persistence API (JPA).

The file data source is defined by providing a path to a file and a property name to be used as an object’s key:

var itemsProvider = new avatar.FileDataProvider(
        { filename: "rest-sample.txt", key: "key" });

No additional configuration is required in the case of a file data provider. A database provider requires some additional parameters and configuration, namely, the name of the persistence unit, which defines the connection to the database, and the entity type, which defines the object structure and relational mapping:

var itemsProvider = new avatar.JPADataProvider(
        { persistenceUnit: "rest", createTables: "true", entityType: "Item" });

JPA uses an ORM file (object-relational mapping) that must be bundled with the Project Avatar application in the META-INF folder. The ORM file for this sample is shown next:

<entity-mappings ...>
    <entity class="rest.Item" access="VIRTUAL">
        <attributes>
            <id name="key" attribute-type="String">
                <column name="akey"/>       <!-- KEY is a reserved word in SQL -->
            </id>
            <basic name="value" attribute-type="String">
            </basic>
        </attributes>
    </entity>
</entity-mappings>

This XML file defines an entity comprising two attributes, key and value, with the former being the entity’s ID and the latter a simple string value. For more information on JPA, see this Java Persistence API page.

View Module

The view module for this application comprises a single HTML file shown next:

<html>
  <head>...</head>
  <body>
    <script data-model="rest" id="ItemModel">
      var ItemModel = function(collection) {
        this.key = "key1";
        this.value = "";

        this.clear = function() {
          this.key = this.value = "";
        };
        this.onPutDone = function() {
          collection.get();
        };
        this.onDeleteDone = function() {
          collection.get();
        };
      };
    </script>

    <script data-model="rest" id="ItemCollectionModel">
      var ItemCollectionModel = function() {
        this.displayItems = function() {
          if (typeof this.items == 'undefined') {
            return "No Items. Add a key and value and click PUT.";
          }
          var result = "";
          for (var i in this.items) {
            var item = this.items[i];
            result += item.key + ":" + item.value + "</br>";
          }
          return result;
        };
      };
    </script>

    <script data-type="ItemCollectionModel" data-instance="itemCollection"
            data-url="data/items/"></script>

    <script data-type="ItemModel" data-instance="item" data-url="data/items/#{this.key}"
            data-props="dependsOn:'itemCollection', progressIndicator:'true'"></script>

    <h2>Item Collection</h2>
    <div>
      <button onclick="#{itemCollection.get()}">GET ALL</button>
    </div>
    <br>
    <span id="output">#{itemCollection.displayItems()}</span>

    <h2>Item</h2>
    <div>
      <button onclick="#{item.get()}">GET</button>
      <button onclick="#{item.put()}" id="put">PUT</button>
      <button onclick="#{item.del()}" id="delete">DELETE</button>
      <button onclick="#{item.clear()}">Clear</button>
    </div>
    <br>
    <form>
      <label for="ik">Item Key: </label>
      <input id="ik" data-value="#{item.key}"><br>
      <label for="iv">Item Value: </label>
      <input id="iv" data-value="#{item.value}">
    </form>
  </body>
</html>

As seen in these declarations, the model type is rest. In JavaScript, a prototype provides a default behavior for an object, somewhat like a base class in Java. In Project Avatar, you can use the object’s default behavior, override it with application code, or use a combination of the two as we’ve done in this application. It is worth pointing out that services that interact with Project Avatar applications need not be written exclusively in JavaScript; an EE service written in Java can serve data to a client component given that client and service components in Project Avatar use standard protocols.

In this application, get, put, and del methods retain their default behaviors, whereas the methods onPutDone and onDeleteDone are overriden by the ItemModel constructor. The reason for doing this is that, by default, REST communications are asynchronous and, in some cases, you want to intercept certain events to update data models. In this application, we’ve overridden the onPutDone and onDeleteDone methods to allow us to intercept the PUT and DELETE operations and update the associated collection model whenever they are called.

In the code that defines the item instance, there’s a dependency via a dependsOn attribute (an attribute foo can be defined either as data-foo or as part of data-props as in this case):

<script data-type="ItemModel" data-instance="item" data-url="data/items/#{this.key}"
        data-props="dependsOn:'itemCollection', progressIndicator:'true'"></script>

This indicates that the item instance depends on the itemCollection instance. Here’s how it works. Each instance of the item model holds an individual key/value pair, whereas an item collection instance holds all the key/value pairs. When you update the item model (a single key/value pair), it correspondingly updates the data in the collection of key/value pairs. In other words, the methods onPutDone and onDeleteDone force a refresh of the collection object, and the collection object, in turn, provides the updated data to the UI.

In a rest model instance, you define a URL with which the application interacts and persists information on the server. Each instance definition contains a URL, one for each item and one for the collection. Project Avatar uses the URL to identify each resource in the system, each item resource and the collection resource. Notice that the item resource appends the key value (using #{this.key}) to the end of the URL so the URL changes as the model changes.

Finally, the UI portion defines an HTML form where a user can type in a key and a value. These properties are data bound to model properties using the Expression Language (EL) and the special attribute data-value. For example, property key is bound to the input element ik and property value is bound to the input element iv. Because of the nature of these HTML elements, as input elements, the type of binding in these cases is two-way: the model is updated when the input fields are updated and vice-versa.

Stock Tickers

The Stock Tickers application uses HTML5 and Server Sent Events (SSE) to push data from the server to the client asynchronously. The server is notified of the data change (for example, incoming stock quotes) and then pushes the data to the browser. This sample can be found in the examples directory that is part of the distribution.

Service Module

Just like the REST services we explored before, push services are registered on the avatar object. The difference, in this case, is in the prototype object assigned to the service instance.

The push service has three methods onOpen, onClose and onTimeOut. In this example, when a new connection is established, the onOpen method is called and a wake-up timeout of 1 second (1000 milliseconds) is set. When the timeout expires the onTimeout method is invoked which updates the data (in the method updateData, omitted for brevity), sends the data to the client and resets the timer. In this manner, the data is updated and periodically sent to the client every second.

avatar.registerPushService({
    url: "push/stocks"
},
function() {
    this.data = {
        tickers: ["ACME", "EACM", "MEAC", "CMEA"],
        values: [25.0, 50.0, 75.0, 100.0]
    };

    this.onOpen = function(context) {
        avatar.logFine("onOpen called")
        context.setTimeout(1000);      // start timer
    };

    this.onClose = function(context) {
        avatar.logFine("onClose called");
    };

    this.onTimeout = function(context) {
        avatar.logFine("onTimeout called")
        this.updateData();
        var promise = context.sendMessage(this.data);
        promise.then(function() {
              avatar.logFine("Message sent (promise)");
          }, function(err) {
              avatar.log("Error sending message (promise): " + err);
          });
        context.setTimeout(1000);      // re-start timer
    };

    this.updateData = function() { ... };
});

In the method onTimeout, the data is sent by calling sentMessage on the context object. Method then on the promise returned accepts two callback functions to handle success and error cases. This example also shows the use of Project Avatar’s logging facility.

View Module

Stock Tickers is a purely declarative sample application; it is data binding that causes the UI to be updated when the model is refreshed from the server. Here is the code from the application’s view module.

<html>
    <head>...</head>
    <body>
        <script data-model="push">
            var TickersModel = function() {
                this.tickers = ["ACME", "EACM", "MEAC", "CMEA"];
                this.values = [0.0, 0.0, 0.0, 0.0];
            };
        </script>
        <script data-type="TickersModel" data-instance="tk"
                data-url="push/stocks"></script>
        <h2>Stock Tickers SSE Sample</h2>
        <hr><br>
        <table>
            <tr><th>#{tk.tickers[0]}</th>
                <th>#{tk.tickers[1]}</th>
                <th>#{tk.tickers[2]}</th>
                <th>#{tk.tickers[3]}</th></tr>
            <tr><td>#{tk.values[0]}</td>
                <td>#{tk.values[1]}</td>
                <td>#{tk.values[2]}</td>
                <td>#{tk.values[3]}</td></tr>
        </table>
    </body>
</html>

Note the declaration of the model instance tk whose type is defined as push. The push model demonstrates a one-way data communication model, from the server to the client. The server sends data to the client which updates the model. When the model gets updated, data binding ensures that the UI gets updated as well. There is no procedural code required to accomplish this behavior; data from the server is received without ever having to request it.

Chat

The Chat application uses HTML5 and Web Sockets (WS) to handle asynchronous bidirectional data. For example, you can get or send messages whenever they become available, as in a typical chat system. This sample can be found in the examples directory that is part of the distribution.

Service Module

A socket service can override methods to handle events for opening and closing connections as well as receiving messages. Messages can be sent to a specific peer or to all the peers connected to the same socket endpoint, identified in this application by the “/websockets/chat” path.

avatar.registerSocketService({
    url: "/websockets/chat"
},
function() {
    this.data = {
        transcript : ""
    };

    this.onMessage = function(peer, message) {
        avatar.log("onMessage called");
        this.data.transcript += message;
        this.data.transcript += "\n";

        peer.getContext().sendAll(this.data, function() {
            avatar.log("Message sent");
          }, function(err) {
            avatar.log("Error sending message");
          });
    };
});

In this example, a new message received from a peer is concatenated to the transcript and then broadcasted to all peers connected to the endpoint, in other words all the participants in the chat room. Callback functions are passed to the context method sendAll in order to log the outcome of the asynchronous send operation.

View Module

You log in the Chat application by typing your name and clicking Login. In the Chat Room, you type text in the Message field and click Send. In the Transcript output text box, you see that you are logged in and your message appears alongside your login name.

Let us start by exploring the socket model defined in the application’s view module.

<script data-model="socket">
    var ChatModel = function() {
        this.transcript = "";
        this.message = "";
        this.user = "";

        this.login = function() {
            this.send(this.user + " logged in");
            avatar.navigateTo('#chat');
        };
        this.sendMsg = function() {
            this.send(this.user + ":" + this.message);
            this.message = "";
        };
    };
</script>

In this case the model’s type is socket. The model type does not override any of the prototype’s method; instead, it defines two new methods for logging in the chat room and for sending messages, both of which call the prototype’s send method.

The view module consists of two views, one used for logging into the chat room and the other for displaying the transcript and typing messages.

<html>
    <head>...</head>
    <body>
        <script data-model="socket">...</script>
        <script data-type="ChatModel" data-instance="ch" data-url="/websockets/chat">
        </script>

        <div data-widget="view" id="login" data-title="Chat WebSocket Sample"
             data-main="true">
            <h2>Login Page</h2>
            <label for="user">Username: </label>
            <input id="user" data-value="#{ch.user}"><br>
            <button onclick="#{ch.login()}" type="submit">Login</button>
        </div>

        <div data-widget="view" id="chat" data-title="Chat WebSocket Sample">
            <h2>Chat Room</h2>
            <textarea rows="5" cols="30" label="Transcript"
                      data-value="#{ch.transcript}"></textarea>
            <br>
            <label for="msg">Message: </label>
            <input id="msg" data-value="#{ch.message}"><br>
            <button type="text" onclick="#{ch.sendMsg()}">Send</button>
        </div>
    </body>
</html>

The login button on the main view invokes the login method on the model, which in turn, sends a message over the Web Socket connection (adding a user to the chat) and then navigates to the chat view. Once in the chat room, messages can be typed and sent over the connection; every time the transcript is updated, it is sent back to all clients causing their corresponding UI’s to be updated.

JMS Chat

The JMS Chat sample shows integration with the Java Message Service API (JMS). This is a variation of the Chat example whereby messages are also delivered to and received from a JMS message queue, effectively implementing a gateway between a Web Socket connection and a JMS connection.

Service Module

Socket services in Project Avatar can be declaratively configured to interact with a JMS destination. The special jms property is an object that includes the name of the JMS connection factory, a destination name as well as a message selector and message properties. All this data is used internally by the socket implementation (more precisely, the socket’s prototype object) to establish a connection with a JMS destination for the purpose of relaying messages to and from a JMS service.

Additionally, this sample creates a file data provider in order to store each chat room’s transcript in external storage. The code for the service is shown next:

var chatStorage = new avatar.FileDataProvider({
    filename: "jmschat.txt",
    key: "id"
});

avatar.registerSocketService({
    url: "/websockets/jmschat/{chatroom}",
    jms: {
        connectionFactoryName: "jms/AvatarConnectionFactory",
        destinationName: "jms/AvatarTopic",
        messageSelector: "chatroom='#{this.chatroom}'",
        messageProperties: {
            chatroom: "#{this.chatroom}"
        }
    }
},
function() {
    var superOnMessage = this.onMessage;
    var that = this;
    this.onMessage = function(ctx, msg) {
        avatar.log("Received websocket message: "+msg);
        chatStorage.get(this.chatroom)
            .then(function(transcript) {
                if (!transcript) {
                    transcript = {
                        messages: []
                    };
                }
                transcript.messages.push(msg);
                return chatStorage.put(that.chatroom, transcript);
            });
        superOnMessage.call(this, ctx, msg);
    };
});

The socket service in this application overrides the method onMessage from the socket prototype in order to update and store the transcript in an external file. Note that the method being overridden is also invoked to ensure delivery of the message to the JMS queue, in accordance with the declarative configuration provided.

As in other samples shown before, all calls to a provider are asynchronous and return promises. Only when a promise is fulfilled, in this case only after the transcript of the chat room has been read from storage, can certain actions like pushing a new message be carried out.

Given that the JMS configuration is purely declarative, the code needed to establish the JMS connection and forward messages back and forth is part of the socket prototype implementation. Note that Project Avatar also supports the creation of JMS destinations imperatively as well as an API to send and receive messages directly.

View Module

The view component of this application is similar to Chat. In addition to messages and transcripts, the view also supports the notion of a chat room that is selectable at login time. The chat room’s name is part of the URL used to establish the Web Socket connection for the chat. As a result, two different models are defined: one local and one socket.

Let us start by describing the views that are part of this module:

<div data-widget="view" id="login" data-title="JMS &amp; WebSocket Chat" data-main="true">
    <h2>Login Page</h2>
    <form>
        <label for="user">Username: </label>
        <input id="user" data-value="#{um.user}"><br>
        <label for="room">Chatroom: </label>
        <input id="room" data-value="#{um.room}"><br>
        <button id="loginButton" onclick="#{avatar.navigateTo('#chat')}">Login</button>
    </form>
</div>

<div data-widget="view" id="chat" data-title="JMS &amp; WebSocket Chat">
    <script data-type="ChatModel" data-instance="ch" data-dependsOn="um"
            data-url="/websockets/jmschat/#{um.room}"></script>
    <h2>Chat Room #{um.room}</h2>
    <form>
        <textarea rows="5" cols="30" label="Transcript" data-value="#{ch.transcript}"></textarea>
        <br>
        <label for="msg">Message: </label>
        <input id="msg" data-value="#{ch.message}"><br>
        <button id="sendButton" onclick="#{ch.sendMsg()}">Send</button>
    </form>
</div>

The login view uses the model um, a local model, and the chat view uses the model ch, a socket model defined locally on that view and that depends (see dependsOn attribute) on the um model. The former is used to collect the user and chat room names, while the latter is used to exchange messages over the Web Socket connection. Note that the model ch becomes active only when its enclosing view is shown; this is required given that its URL is initialized using the expression #{um.room}. The following are the definitions for these models:

<script data-model="local" data-instance="um">
    var UserModel = function() {
        this.user = "";
        this.room = "";
    };
</script>

<script data-model="socket">
    var ChatModel = function(um) {
        this.transcript = "";
        this.message = "";

        this.onOpen = function() {
            this.send({user: um.user, message: "logged in"});
        };
        this.sendMsg = function() {
            this.send({user: um.user, message: this.message});
            this.message = "";
        };

        var superUpdate = this.update;
        this.update = function(source) {
            this.transcript += source.user + ": " + source.message + "\n";
            superUpdate.call(this, this);       // Trigger data binding
        };
    };
</script>

The ChatModel type overrides the method update which is invoked every time the model is updated as a result of the deserialization of new data received on the socket channel. In this sample, the internal transcript property is updated and the original method invoked to resume normal processing.

More Samples

You will find additional sample applications in the examples directory of your distribution. These include applications illustrating the use of some of the off-the-shelf widgets that Project Avatar supports including forms, trees, tables, menus, and various pane containers. Project Avatar supports a pluggable model for widget libraries. The widget library used by a Project Avatar application is controlled by a property in a file that is part of the application artifact.

Conclusion

In this tutorial, we have learned about the main Project Avatar concepts by examining a few samples: primarily the notion of view and service modules as well as models and data binding. Additional sample applications demonstrate more advanced widgets, other types of service providers and HTML5 client storage. The purpose of this tutorial has been to demonstrate how Project Avatar delivers an end-to-end framework for building modern web applications that are fast, lightweight, and responsive, exactly what is needed for today’s desktop and mobile clients.

Back to top