Project information

  • Category: Web framework
  • Project date: December, 2012

Why would we need another web framework?

In the decade of AJAX-boom, web applications has evolved from being a bunch of linked web pages towards the highly interactive rich clients running in the browser. This experience is not just limited to the users of the AJAXed web applications. Programmers, too, try to align their development practices to what is actually happening in the application, rather than to tweak underlying HTTP protocol day after day. And the great variety of existing web frameworks is supposed to help them to get there.

Low-level web frameworks, like Struts, Spring or ASP.NET MVC remain very HTTP-centric and provide a generic set of utilities and helpers for handling HTTP requests and rendering responses. The AJAX support in such frameworks is very basic, encouraging developers to invent their own approach for effective client-server interaction. And they gladly use the opportunity in the fanciest ways, even within one product. The common result is the chaos on the server- and javascript floods on the client-side.

In contrast to the classical web frameworks, AJAX web application frameworks like GWT, Vaadin, ZK or Echo2 offer great possibilities for authoring highly interactive web applications with virtually no javascript, in a high-level language and in a very desktop-like style. The only caveat of those frameworks is their heaviness.

Such heavyweight frameworks offer a holistic approach to web application development, imposing own rules and even markup languages for creating layouts, binding data, events etc. Sometimes it works quite well. Sometimes, however, it feels like an overkill with a pretty steep learning curve.

Besides, many heavyweight frameworks seriously affect crucial technical aspects of a web application. For instance, to keep application state server-centric frameworks like ZK or echo2 extensively use server memory, which is not very good for scalability.

Probably, I got dazzled by the abundance of the existing AJAX web frameworks at some point, but I'm really missing a simple and handy one among them. So, I came up with my own.

What would I expect from a good lightweight AJAX web framework?

  • minimalistic and tidy client scripts
  • seamless and close integration of server and client code
  • server-centric approach for the business logic
  • fat-client programming model: plain and stateful objects, event driven development
  • simplicity and comprehensible mode of operation
  • high scalability
  • low intrusiveness and usage of the good old HTML-templating

The points 1-4 are pretty good addressed by many AJAX web frameworks, but unfortunately by sacrificing 5-7. In this framework I'm trying to reconcile all the points mentioned above. The next chapter explains how I'm going to get there.

Framework Features

To meet the targets mentioned before, the web framework builds on the following concepts.

Total AJAX

Unification and simplification of the client-server communication

  • Every request is an AJAX request
  • Response payload is Javascript-code
  • The Javascript functions are bundled in the client-side Javascript components
  • Server-side complementary components (proxy components) are used to generate Javascript code for calling the corresponding Javascript components

Providing a context

Maintaining (quasi) stateful form instances, modelled with plain old objects

  • HTML form component serves as a context holder
  • Every "page" is represented by a derivation of the Form-component (form object)
  • Page context or state is implemented with instance variables of the form object
  • User actions cause an AJAX submit of the whole form, including state descriptor and form fields
  • Event handlers are implemented as methods of the form component
  • Event handlers can modify the form state (instance variables), change UI by calling proxy components and do any other actions
  • Form object maintains its state between the actions on the form, until disposed

Standard view rendering

  • Using respective HTML templating technologies to generate views
  • Proxy components can be bound into templates via server-side tags or similar

Modelling flow control with objects

Form objects are just plain objects

  • Navigation to another page as instantiation of a new form component
  • Using parameterized constructors
  • Keeping parent form as an instance variable for later use

AJAX Web Framework Architecture

Architecture of the Web Framework

User actions

Every client-side component can trigger component specific events. Every event is triggered on the parent form component by calling jasty.raiseEvent method (see click handler in the Button-component). The "raiseEvent"-method makes an AJAX-submit including event handler name, event parameters and the whole form data. All AJAX-submits are sent to a central Form Engine URL

The submit is then processed on the server by the Form Engine, which retrieves request parameters, restores the stateful presenter (Form) and calls the specified event handler method.

The classical submits are obsolete and not used anymore. AJAX form submits can do all the same things and doesn't require the page to be completely re-rendered.

Forms

In the classical MVC/MVP frameworks, actions don't share any data between requests by design. It may make those frameworks slim, fast and easy to use for building dynamic web sites, but doesn't facilitate the modelling of rich client applications.

In a rich client application, life cycle of a view can span a number of actions, each working on the same data context. Thus, view object could be modelled by a POJO with the instance fields as the data context and with the methods as event handlers. Such view object is very similar to a classical controller/presenter, except for being stateful. We call such view object a Form.

Form state on the client

The next question is how to maintain Form object between requests. Keeping it in session, like some frameworks do, is not a particularly good idea, because of the memory and scalability issues. A slightly better alternative is to keep it on the client.

When rendering the Form's main view, the Form-object gets serialized and sent in the response to the browser. Client-side form component keeps serialized Form and jasty.raiseEvent posts it back along with the other data. Form Engine on the server side deserializes the Form and calls the appropriate event handler method.

Event handlers

Event handlers are instance methods of the Form-class. The name of the handler to be called is posted back to the server on user action, along with the serialized Form object, form data and event parameters.

Event handlers contain business logic and interact with the client side via proxy components.

    public class TestForm extends Form {
        private int numberClicked;

        public void buttonClicked(EventArgs e) {
            Button btn = get(Button.class, "someId");
            numberClicked++;
            btn.setText("I'm clicked "+ numberClicked + " time(s)");
        }
    }

Event handlers may also use and modify Form instance variables. The Form-object gets serialized at the end of every request and sent with the AJAX-response to the client, so that the object can be exactly restored to handle subsequent events.

Form Views

Every Form always has one associated main view. This view gets rendered when the Form has to be visualized. Every main view is implicitly rendered within its own form component, which is responsible for providing necessary context for every event. Method prepareModel of the Form is used to obtain the model for the main view.

Apart from the main view, partial views can be defined for a Form. These partial views can be rendered on actions and used to dynamically overwrite particular fragments of the main view.

Finding and rendering of the views depends on the underlying template technology and is bridged by the Form Engine.

Client-side components and proxies can be bound into the views by implementing custom tags. Custom tag typically renders some HTML code (an outer tag or a placeholder tag) and javascript initialization for the component.

Underlying Form-object serves for the associated views also as a naming provider. It automatically ensures uniquity of the component ids to avoid clashes in case of several simultaneous Forms displayed.

Object-based navigation

Since Form-class together with the views represents a cohesive and stateful unit, Form object can be directly used for navigation within the web application. For instance, every Form can replace itself with another form:

    public void detailsClicked(EventArgs e) {
        replaceWith(new DetailsForm(e.get("data")));
    }

Another important scenario is the modal navigation in inline and popup modes.

Examples

See examples for Java/Servlets, Groovy/Grails and .NET/MVC

Extensibility Points

Web framework exposes some extensibility points in form of interfaces and their default implementations, which can be overridden to customize behavior. Some of them

Interface FormPersister

This interface is used to abstract out the persistence and lookup of the form objects. The state descriptor, generated by persist is sent to the client and stored on the client side. On every user interaction the state descriptor is posted to the server along with form data. To restore the form object, lookup is called with the sent state descriptor. After event is processed, the form is persisted again and a new state descriptor is sent back to the client and so on.

Possible implementation for this interface are:

  • Form state on the client (default implementation). State descriptor is the serialized form itself.
  • Form state in the session. State descriptor could be a unique id to resolve the form in the memory. Form object disposal needs to be implemented
  • Form state in the database. State descriptor could be a unique id to read the form from the database. Disposal of the stored form objects needs to be implemented

Additional functionality to be implemented could be:

  • Encrypting/decrypting serialized form object for the client side form persister
  • Encrypting/decrypting state descriptor for server side store mode (or use UUIDs)
  • Implement XSRF (Cross-Site-Request-Forgery) protection by modifying state descriptor (or of the special instance variable in case of client side form persister)

Interface MethodInvoker

Implement specific processing around event handler invocation. For example:

  • Open Hibernate session and expose it e.g. on the current thread. Close session after the call is finished
  • Custom exception processing and handling
  • Custom population of the method parameters

Interface ParameterProvider

This interface abstracts out the obtaining of request parameters. The default implementation is normally sufficient.

Interface ViewRenderer

The implementation is dependent on the underlying view engine in the particular technology (e.g. JSP, GSP, WebForms, Razor etc.)

  • implements lookup of the main and partial views for the specified form
  • renders resolved views to strings, applying the specified model object

Authoring Components

Component GUI model

Bundling UI functionality in client-side components is a pretty obvious idea. The reference implementation of the component library uses just trivial stateless function maps, with the simple inheritance.

    jasty.Button = jasty.extend(jasty.Control, {

        init: function(self, opts) {
            self.text(opts.text);
            self.click(function() {
                jasty.raiseEvent(self, opts.onClick, {srcId: self.attr("id"),
                data: opts.data});
                return false;
            });
        },

        text: function(self, value) {
            self.text(value);
        }
    });

Method init is reserved for component initialization. I would generally recommend to put the whole rendering code for the component into this method. So that the only thing, rendered on the server is the outer tag with nothing but an id specified. The remaining initialization occurs in the init-method.

    <button id="someId"></button>
    <script>
        jasty.Button.init($("#someId"), {
            text: "initial text",
            onClick: "buttonClicked"})
    </script>

Behavioural methods may substantially depend on the DOM-representation of the component. That's why it's reasonable to keep everything in one place - in the component code - only having a single DOM-tag to be rendered by the server.

To facilitate calling methods on complex selectors, as well as to provide a common interface to various component writing styles, dispatching plugin may be used:

    $("#someId").jasty("Button", "text", ["some text"])

This jQuery-plugin resolves the component and calls the specified method sequentially for every selected element, applying parameters from the array in the 3rd parameter.

For the components designed in a different way, alternative dispatching plugins might be necessary. But the calling interface should be kept the same:


$(<selector>).<dispatcher>(<component>, <method>, <parameters array>)

Sticking to the common interface for call dispatchers allows for easy code generation by the server-side component proxies.

Server-side proxy components

Proxy components are used to enable interaction with the client components from the server-side code.

    public class Button extends Component {

        @InitProperty
        private String text;
        @InitProperty
        private String onClick;

        public void setText(String text) {
            this.text = text;
            invoke("text", text);
        }

        @Override
        public String getHtmlTag() {
            return "button";
        }
    }

By calling invoke method, component method (setText) generates javascript code, which is then included in the AJAX-response and executed in the browser.

For example, these calls

    Button btn = new Button();
    btn.setId("someId");
    btn.setText("new text");

produce following script:

    $("#someId").jasty("Button", "text", ["new text"])

Emitting executable javascript is the only method of AJAX-response in the framework, and it perfectly covers all needs.

The proxy components are actively used in the event handlers of the Forms.

Downloads

See information for download and installation for Java/Servlets, Groovy/Grails and .NET/MVC