Add an MVC Widget to VM

Sahana can be extended in two ways:

  1. Add a module to Sahana. Modules are directories in the mod directory under the Sahana root directory. Modules have their own menus and should be used to carry out functionality that deviates from the main Volunteer Management functionality. For more information, see [1] or try the hello world example.
  2. Extend the functionality of a module, in this case the vm module. All Volunteer Management functionality is carried out within the vm module, which is based on a Model view controller design. All major entities in the vm module have a model, view, and controller associated with them. That is the approach we take in this example.


Problem Statement

Add a Widget entity to the VM module and a menu item to access it. To implement the Widget create widget subclasses of the model, view, and controller.

1. Modify the VM Access Controller to Recognize a Widget

Each page in the VM has distinct act and vm_action URL parameters. The VM uses the act parameter to decide which controller to use, and the vm_action is used by a controller to decide what action to carry out.

For each page that you add to the VM, you must insert into the vm_access_request database table an entry describing your new page. For your first entry, the act should be your controller's name, and the vm_action should be default.

Execute the following query on the database:

INSERT INTO vm_access_request (request_id, act, vm_action, description) 
   VALUES (201, 'widget', 'default', 'Display Widget - Default');

You can then give groups of users access to it by inserting into the vm_access_request_role table, which links user roles to pages:

INSERT INTO vm_access_request_role (fk_access_request, fk_role) VALUES
 (201, 0),
 (201, 1),
 (201, 2),
 (201, 3),
 (201, 4);

The above query will give access to all user groups to the above default page. To make these database changes happen automatically upon installation of the VM module, you must add them to the inst/dbcreate.sql database script under the vm module.

2. Create a Widget Model

A model class contains the state of one entity. So, a Widget model would simply store and retrive information about a widget. Each model class should extend the Model class:

 class Widget extends Model {
     public $createdDate;

     function __construct() {
       $this->createdDate = $this->dao->getCurrentDatetime();

Put this class definition in a file called Widget.php in the model directory under the vm module directory.

It is important to note that all model classes, since they extend the Model class, have a reference to the Data Access Object (DAO, in model/dao.php), which is where all queries to the datbase are stored and executed. No SQL code should leave the DAO. DAO functions typically execute a query on the database and return its results in some type of data structure.

In the Widget model above, a Widget stores its creation date by asking the DAO to retrieve the current date and time from the database. The getCurrentDatetime() function is not yet defined in the DAO, we must modify the model/dao.php file to add this function to the DAO class:

function getCurrentDatetime() {
  $result = $this->execute("SELECT NOW()");
  return $result->fields[0];

The DAO uses the ADODB Database Abstraction Library, so the execute() function in the DAO is actually a wrapper around the ADODB execute() function on a database connection. (See Sourceforge for more information).

Since each model class extends the Model class, it has access to the DAO via the $this->dao variable.

3. Create a Widget View

All views extend the general View class. Following the Widget example, we would create a file in the 'view' directory under the 'vm' module directory called "WidgetView.php". A skeleton WidgetView would look like this:

  class WidgetView extends View {}

The view classes should have functions that display information. The VM uses a templating engine, which is accessed from a view class by the $this->engine variable. You may assign information to the engine, and then choose to display a template. The template will be passed the information that is assigned to the engine.

So, for example, we could add a function to our WidgetView class to display a Widget model:

function displayWidget($model) {
   $this->engine->assign('createdDate', $model->createdDate);

In order for this to work, we must create a template for our page, which is described in the step below.

4. Create a Template

Templates should include only presentation logic, not business logic. This includes any HTML, CSS, JavaScript, or other type of code that the browser will see. What should not go in templates is lots of PHP computations and processing.

This separation of presentation and business logic increases maintainability of the code and facilitates change. Following the Widget example, we need to create a directory in the 'templates' directory called 'widget' and create template in it called 'default.tpl', which is displayed in our displayWidget() function from the WidgetView.

For this file, we could simply have the following code:

<h2>Welcome to the Widget Display Page</h2>
 A new widget was created on: {$createdDate}

Since we assigned 'createdDate' to be $model->createdDate in our WidgetView code, when we call displayWidget() passing it a valid Widget model, it will display the createdDate field of the Widget model passed to it.

5. Create a Controller

A controller is the interface between the system and the entity's functionality. In other words, the system will give control to the correct controller when necessary.

All controllers must implement the Controller interface to ensure compatibility with the system. Also, for simplicity, all controllers in the VM extend their respective view classes to give them easier access to view functionality.

Following the Widget example, we would create a file in the 'Controller' directory under the 'vm' module directory called "WidgetController.php".

A skeleton controller for our Widget would be as follows:

 class WidgetController extends WidgetView implements Controller {
    function controlHandler($getvars) {
    //first authorize the user
        $ac = new AccessController($getvars);

        switch($getvars['vm_action']) {
           $widget = new Widget();

$getvars is a collection of all $_GET and $_POST variables.

Note that since this is a simple example, Widgets are not stored in the database. If this was desired, we would need to create a database table to store widget information, and create functions in the DAO to store or load Widget models. Also, we might want to have pages that would collect information about Widgets and then store that information in the database. Such is the case with Volunteer and Project entities.

In this simple example, however, a new Widget is created every time the page is refreshed, and is destroyed once the page has finished loading.

6. Modify

We modify to connect VM dispatch control to the WidgetController when necessary. In the file, in the shn_vm_default() function, before the final 'else', add a check if the 'act' URL parameter is our controller and dispatch control to it if so:

 else if ($_GET['act'] == 'widget'){
    $controller = new WidgetController();

Also, at the top of the file, we must include the classes we added:


7. Create a Widget Menu Item

In the file, under the shn_vm_mainmenu() function, add this line of code before the shn_mod_menuclose() function is called.

 $ac->addMenuItem('Widget', $ac->buildURLParams('widget', 'default'));

When you select the Widget menu item the following page will be generated:

image:Workshop widget screen.jpg

Since we assigned 'createdDate' to be $model->createdDate in our WidgetView code, when we call displayWidget() passing it a valid Widget model, it will display the $createdDate field of the Widget model passed to it.

Optional Exercises

  • The Summer 2008 Sahana team extended the example to add a fortune cookie submenu to the Widget example. This is a diagram of the solution to the exercise, as well as the fortune extension.


  • VMOSS Calendar Example: This example shows how to add a simple calendar to VMOSS. It requires adding a new table to the VMOSS database.