The Golf Web Application Server home roadmap1.0 help rss

Golf's Clientside MVC Framework

29 Dec 2009 - Micha Niskin

Golf apps are built on a simple, straightforward MVC architecture. Requests are delegated to the controller. The controller selects the corresponding route and passes the request off to the associated action. The action then instantiates the appropriate component and inserts it into the DOM. The component fetches data from the model via AJAX and updates its DOM as necessary.

The MVC Pattern

You can see that Golf provides a fairly standard implementation of the MVC pattern. In Golf the the MVC model is a webservice, the view is a Golf component, and the controller is an array of { route, action } tuples.

The Controller

The main topic of this article is really the configuration of the app’s controller. Views and models are covered in detail elsewhere (see above).

The actual controller code is deep within the Golf runtime. When you develop apps you configure it by adding { route, action } tuples to a special array, $.golf.controller, in the controller.js file.

When a request comes in, the controller iterates through the $.golf.controller array. Each route is examined in turn. If the route’s route property (a regular expression) matches the request’s URL hash the route’s action method is called to handle the request.

A simple controller.js file may look like this:

$.golf.controller = [
  { route: "/home/",
    action: function(container, params) {
      container.append(new Component.Home());
    }
  },
  { route: "/blog/",
    action: function(container, params) {
      container.append(new Component.Blog());
    }
  }
];

This controller contains two routes, matching the /home/ and /blog/ URL hashes, and with actions instantiating and inserting into the DOM components of type Home and Blog, respectively.

Note: The route regex is anchored by default. That means that the examples there would actually be ^/home/$ and ^/blog/$. This is to make the regexes easier to read, as most of the time anchored expressions are more useful for routes.

Routes

The $.golf.controller array contains a number of objects, known as routes. Each route must have a route property and an action method.

<string> route
The route property is a regular expression. This regular expression will be matched against the URL hash. If a successful match is achieved, then the associated action will be called. The regex is anchored by default, which is important to keep in mind.
<bool> action( <jQuery> container, <array> params )
The action method is a function that will be called upon a successful match of the URL hash against the route property regex. This function instantiates components and inserts them into the DOM, thereby delegating control to the view.
container: The DOM element into which the component(s) will be inserted.
params: The result of the regular expression match between the URL hash and the route property.

The Default Route

There are really two distinct types of default route: the route which is chosen when there is no URL hash provided by the user, and the route which is chosen when the URL hash that was provided does not match any configured routes.

For the first type we assign a value to the $.golf.defaultRoute property in the controller.js file. The value is the URL hash to which the client will be redirected when they submit a request that doesn’t specify one.

The second type is serviced by a route with a regex of .*, meaning match everything. This route must be the last route in the controller array, obviously—any routes after it will be inaccessible, as the route will have already been matched by .*, the match-everything route.

Here is an example controller.js file containing both types of default route:

// type 1: causes redirect if no hash was provided in the URL
// i.e. http://me.com/app/ => http://me.com/app/#/home/default/
$.golf.defaultRoute = "/home/default/";

$.golf.controller = [
  // normal route: matches requests of the form
  // http://me.com/app/#/home/{something}/
  { route: "/home/([^/]+)/",
    action: function(container, params) {
      container.append(new Component.Home(params[1]));
    }
  },
  // type 2: matches anything that didn't match any of the above routes
  { route: ".*",
    action: function(container, params) {
      container.append(new Component.NotFound(params[0]));
    }
  }
];
blog comments powered by Disqus
Fork me on GitHub