Copyright (c) 2008, University of Helsinki
A YUI2-based, light-weight JavaScript MVC framework for working with RESTful data sources. The REST back-end must conform to the coding practices defined by this library (so it's not really all that general-purpose).
The framework also uses [http://embeddedjs.com Embedded JavaScript] to separate controller-specific JavaScript code to controllers and pure html markup in templates that are rendered with JSON data to achieve the final view.
This is the entry point to the MVC. This is the file you include in your base HTML file to use this framework. The file takes a query parameter {controller name}/{action name} to define entry point into the system. If action name is omitted, index is assumed. You need to include YUI Loader library before this. Example of a HTML file:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<title>Latest Diggs</title>
<script src="lib/yui/build/yuiloader/yuiloader-beta.js" type="text/javascript"></script>
<script src="init.js?stories" type="text/javascript"></script>
</head>
<body>
</body>
</html>
The framework uses the REST to get data from the server. A JavaScript-object of the type Resource is used to fetch single data entity from the web server via AJAX using the REST interface.
This is used to define resources (or models). The syntax for resource definition is:
Resource new Collab.resource(<String> name[, <Object> options]);
where
Parameter | Description | Optional | Default value |
---|---|---|---|
name | The name of the resource - if in plural, plural resource is created; if in singular, singular resource is created | False | |
options | An object containing additional options as described below | true | see below |
Option | Description | Default value |
---|---|---|
name | Same as the first argument (, but used when creating subresources | |
type | Valid values are "plural" or "singular". Defines the type of the resource to be either a plural one (listable and with id's) or a singular (only one exists, so no list and no id's) | Based on the singualirty or plurality of **name** |
singular | Singular form of the resource's name | **name** singularized |
plural | Plural form of the resource's name, used for generating default urls | **name** pluralized |
methodField | Name of the hidden field used to transfer method data for puts and deletes | "_method" |
urls | An object containing the url's for different actions, described below | described below |
has | An array of strings, where each entry creates a new instance level sub-resource. If an array of [string, Object] is passed, the second element is similar options array as on any other resource. The url's are prepended by the mother-resource's show-url | |
collection | Collections are a differerent kind of subresources. They live on the class level, not on the instance level as has-type of subresources. They are meant to implement filtering for collections and will only work on plural resources. They are plural themselves as well. |
Plural resources have the following urls:
Action | Default url | HTTP method (unchangeable) |
---|---|---|
show | "/" + singular resource name + "/:id" | get |
list | "/" + plural resource name | get |
create | "/" + singular resource name | post |
update | "/" + singular resource name + "/:id" | put |
destroy | "/" + singular resource name + "/:id" | delete |
Singular resources have the following urls:
Action | Default url | HTTP method (unchangeable) |
---|---|---|
show | "/" + resource name | get |
create | "/" + resource name | post |
update | "/" + resource name | put |
destroy | "/" + resource name | delete |
Creating a resources exposes the following methods:
Void find(<String|Integer> id[, <Object> params], <Function> onSuccess(<Object> response), <Function> onError(<Object> response));
Void create(<Element|String> form, <Function> onSuccess(<Object> response), <Function> onError(<Object> response));
Void update(<Integer> id, <Element|String> form, <Function> onSuccess(<Object> response), <Function> onError(<Object> response));
Void destroy(<Integer> id, <Function> onSuccess(<Object> response), <Function> onError(<Object> response));
where
Parameter | Description | Optional | Default value |
---|---|---|---|
id | Either string "all" in which case the list of the resources will be fetched, or an id of a single resource, in which case that particular resource is fetched | false | |
id | The id of a single resource to operate upon | false | |
form | An element reference of the form containing the data to be submitted to the server or its id as a string | false | |
params | Additional query parameters for the AJAX call | true | |
onSuccess( response) | Callback function on a succesful query, ie what to do with the response. Receives an object parsed from the JSON received from the server as parameter | false | |
onError( response) | Callback function on a failed query | false |
Note that singular resources omit the id parameter and the rest are shifted left by one!
The JSON response object has some extra syntactic sugar augmented into it:
- if your call is to id "all", the response will be an array containing all the results, otherwise it will be just the object referenced by the id
- any sub-resources are available through parameters named after them on all objects
- all the extra properties passed in the JSON are available as properties on the array or, in case of singular fetch, on the object itself
Examples:
var Story = new Collab.Resource('stories');
Story.find('all', {item_count: 5}, function(stories) {
// Ajax will hit .../stories?item_count=5
stories.forEach(function(story) {
...
}, this);
});
var Post = new Collab.Resource('posts', {has: ['tags', 'comments', 'rating']});
Post.find(3, function(post) {
// Ajax will hit .../post/3
post.tags.find('all', function(tags) {
// Ajax will hit .../post/3/tags
tags.forEach(function(tag) {
...
});
}
}
var Car = new Collab.Resource('cars', {collections: ['keyword']});
Car.keyword.find('driftable', function(drifters) {
// -> Ajax will hit .../cars/keyword/driftable
drifters.forEach(function(drifter) {
...
});
});
The following convenience methods are available on each resource:
boolean isPlural();
boolean isSingular();
boolean isCollection();
There is also one static method on Collab.Resource:
String Collab.Resource.getType(<String|Object> url);
The function of this method is to 'reverse' an api url back to a string representing the type of the resource. The only parameter can either be the object received from a resource call or a plain url. The type is returned in singular, lowercase form. For example:
Collab.Resource.getTypePrefix = '/api';
Collab.Resource.getType('/api/foos');
--> 'foo'
Collab.Resource.getType('/api/bar/34');
--> 'bar'
Collab.Resource.getType('/api/banana');
--> 'banana'
There are two configuration parameters for resources:
Collab.Resource.baseURL = <String>;
Collab.Resource.getTypePrefix = <String>;
baseURL is prefixed on all outgoing calls, so it should define the location of the back-end-exposed API. getTypePrefix is removed from all url's handled with Collab.Resource.getType(), so it should match with any extra prefixes the back-end returns in its url fields.
Controller is used to render data from the backend to the ejs-templates mentioned earlier. Controllers, likewise to Resource-models, are types of JavaScript-objects that are responsible of rendering all the components in the view.
These files are used to define your controllers. The convention is to name the JavaScript file after the controller's name. The syntax for controller definition is:
Controller new Collab.Controller(<String> name);
where
Parameter | Description | Optional | Default value |
---|---|---|---|
name | Name of the controller, used to refer to the controller from other controllers and dispatching | false |
After the controller has been defined like this and assigned to a variable, you define actions with
Void action(<String> name, <Function> action([...[, ...]]));
where
Parameter | Description | Optional | Default value |
---|---|---|---|
name | Name of the action, used to call the action | false | |
action([...[, ...]])) | The action itself. Takes a freely definable argument list. | false |
Consider this:
var c = new Collab.Controller('stories');
c.action('index', function() {
Collab.dispatch('Login', 'show');
Story.find('all', {
count: 5
}, function(stories) {
this.render('stories', stories);
var storyElements = $('stories').getChildren();
storyElements.forEach(function(e) {
e.on('click', function() {
Collab.dispatch('stories', 'show', e.get('id').split('_', 2)[1]);
});
})
}.bind(c)); // This is unfortunately required to make the anonymous callback function execute in the correct scope
});
c.action('show', function(id) {
Story.find(id, function(story) {
alert(story.link);
});
});
Observe the bind call on the callback function of the resource used to make it execute in the scope of the controller. This is very important!
A special render-method is available to your controllers. Use this to render EJS templates. The syntax for render is
Void render(<Element|String> target[, <Object> data[, <String> template]]);
where
Parameter | Description | Optional | Default value |
---|---|---|---|
target | The DOM element or the id of the DOM element inside which the template will be rendered | false | |
data | A object containing data for the template replacements. Most likely a parsed JSON object returned from a resource fetch | true | |
template | The path to the template file to render, inside the views directory, without file extension | {controller name}/{action name} |
Method calls to controllers are dispatched with
Void Collab.dispatch(<String> controller, <String action>[, ...[, ...]])
where the first argument is the name of the controller, the second argument is the name of the action in that controller and the rest of the arguments are arguments to be passed to the action function.