Create A Sample Module

A sample module to list, create, edit and delete contacts

A complete version of the module can be found at wtv2/modules/Sample

This module is in Tools->Sample of the "James Test Lite Site 4" website

1. Create the folder structure for the module

Create a "modules" folder within the website's folder if it does not already exist.

Next create the folder structure for the module as follows:

"/modules/{Module Name}/office/templates" will contain the templates which define the layout of the back end interface for the module
"/modules/{Module Name}/office/js" will contain javascript files which will define the client side functionality of the back end interface
"/modules/{Module Name}/office/code" will contain PHP files which will define the server side functionality of the back end interface

2. Add the module to the office menu

Using the node browser, create a "wtModule" node with name {Module Name} at "/Modules/{Module Name}"

Next, create a "wtOfficeMenuItem" node where the module is to appear in the office menu under "/The Office/Menu". The wtOfficeMenuItem should have "Use WTUI" set to "Yes"

3. Define the layout for the back end interface

Within "modules/{Module Name}/office/templates", create a file called "main.wt". This will contain the main panel.

The main panel is the top level panel of the interface and will contain everything else. 

The contents of main.wt should be:

<wtui:mainpanel breadcrumbs="yes">

</wtui:mainpanel>

Next, create a file called "contactList.wt". This will be the panel which contains the list of contacts. The contents should be:

<wtui:panel id="contactListPanel">
  <wtui:button icon="plus" id="createContactButton">Create Contact</wtui:button>
  <wtui:button icon="trash" id="deleteContactButton">Delete Selected</wtui:button>
  <wtui:datatable id="contactList" canselectrow="yes">
    <wtui:column>First Name</wtui:column>
    <wtui:column>Last Name</wtui:column>
    <wtui:column>Email Address</wtui:column>
  </wtui:datatable>
</wtui:panel>

A <wtui:datatable> can display data returned from a PHP function. The <wtui:column> tags determine the columns in the table.

The contact list panel can then be inserted into the main panel by using <wt:include> and referring to it by its id. Change "main.wt" to this:

<wtui:mainpanel breadcrumbs="yes">
  <wtui:include component="contactListPanel"/>
</wtui:mainpanel>

Create the panel for creating/editing contacts in the file editContact.wt which will contain:

<wtui:panel id="editContactPanel" visible="no">
  <wtui:form type="Contact" id="editContactForm" hassubmitbutton="yes" hascancelbutton="yes"/>
</wtui:panel>

Note in the above the "editContactPanel" has "visible" set to "no". This panel will not initially be visible. The visibility of the panels can be changed later using JavaScript.

<wtui:form> can be used to create forms for WebTemplate Node Types. Set the "type" attribute to the WebTemplate Node Type.

Include the editContactPanel in the main panel (main.wt):

<wtui:mainpanel breadcrumbs="yes">
  <wtui:include component="contactListPanel"/>
  <wtui:include component="editContactPanel"/>
</wtui:mainpanel>

Note: "editContactPanel" has "visible" set to "no", so it will initially not be visible.

4. Define functionality of the back end interface

4.1 Accessing WTUI components from JavaScript

A component defined in the templates can be referenced in JavaScript using its id attribute and the WTUI object.  For example, to get an object for the "Create Contact" button with id "createContactButton" you would use:

WTUI('createContactButton')

Once you have the object for the component, you can defined handlers for events on the component by using the "on" function of the object. For example, to react to a button click on the "Create Contact" button, you write a handler for the "click" event:

WTUI('createContactButton').on('click', function() {
  // code to be executed when the button is clicked..
});

You can also call methods on the objects to get information about the components. For example, to get the value of a plain text control you can use the "getValue()" method:

var value = WTUI('plainTextControlID').getValue();

The API for all the WTUI components can be found here:

http://docs.webtemplate.com.au/wtuiapi/

4.2 The Sample Module

Create the file "modules/{Module Name}/office/js/main.js"

// create an object to add functions to so they are not added to the global namespace.
var Contacts = {};

Contacts.showContactList = function() {
  // set the breadcrumb when the contact list is shown:
  // "label" is the text displayed in the breadcrumb,   
  // "action" is the javascript function executed when the label is clicked
  WTUI.History.setState({"label": "Contacts", "action": Contacts.showContactList});

  // reload the list of contacts
  WTUI("contactList").reload();

  // only show the contactListPanel
  WTUI("wtuiMainPanel").showOnly("contactListPanel");
}

// when WTUI has loaded, show the Contact List panel
WTUI.on('ready', Contacts.showContactList);

Create the file contactList.js:

var ContactList = {};

ContactList.createContact = function() {
  WTUI.History.setState({"label": "Create Contact", "action": ContactList.createContact});

  // reset the edit contact form and only show the edit contact panel
  WTUI("editContactForm").reset();
  WTUI("wtuiMainPanel").showOnly("editContactPanel");
}
// call the "ContactList.createContact" function when the "createContactButton" is clicked
WTUI("createContactButton").on("click", ContactList.createContact);

Create the file "editContact.js":

// event handlers for the "editContactForm"
WTUI('editContactForm').on('cancel', function() {
  // user clicked the cancel button, so go back to the contact list panel
  Contacts.showContactList();
});

WTUI('editContactForm').on('success', function() {
  // form was successfully submitted to the server, so go back to the contact list panel.
  Contacts.showContactList();
});

5. Loading Data From The Server

5.1 A Simple Example

The JavaScript and Template files can make calls to a PHP class in the code directory named {Module Name}Office in order to send and retrieve data. 

The data from a JavaScript request will be converted to an "$args" associative array which is passed to the PHP function. A return value from the PHP function will be converted into JSON data and handed back to the JavaScript.

For example, using JavaScript to obtain data from the PHP function "getData":

in a JavaScript file:

WTUI.call('getData', { "__guid": guid }, function(response) {
  alert('get data says' + response['message']);
});
 
In a PHP file:
 
<?php

class SampleOffice {
  public static function getData($args) {
    // get the guid passed from the javascript
    $guid = (int)$args["__guid"];

    // return an associative array containing a key called "message"
    // this will be converted to a JavaScript object with a property called message
    $response = Array();
    $response["message"] = "hello from PHP";
    return $response;
  }
}

5.2 Loading Data into a <wtui:datatable>

A <wtui:datatable> can specify a datasource. This will be the name of a static PHP function which will return the data for the table. The request will come to PHP asking for a subset of the results, depending on how many rows/page the datatable is to display.  For example, the $args to a PHP function will be something like:

public static function getData($args) {
  // the number of results to return
  $resultsPerPage = (int)$args["resultsPerPage"];

  // the "page" of results to return
  $page = (int)$args["page"];
}

The PHP function should return the data, as well as the total number of results and the position of the first and last result returned. For example

$response = Array();
// Data is the data to be displayed in the table
// The Data is an array of associative arrays. 
// Each associative array should contain keys which match up to the columns in the <wtui:datatable> as well as a key called "__guid"
$response["Data"] = Array(....);
// Total is the total number of results across all pages
$response["Total"] = $totalResults;
// First is the index of the first result returned in Data
$response["First"] = $firstResult;
// Last is the index of the last result returned in Data
$response["Last"] = $lastResult;
return $response;

For the sample module, in contactList.wt, add a datasource to the datatable:

<wtui:panel id="contactListPanel">

  <wtui:button icon="plus" id="createContactButton">Create Contact</wtui:button>
  <wtui:button icon="trash" id="deleteContactButton">Delete Selected</wtui:button>

  <wtui:datatable id="contactList" datasource="getContactList" canselectrow="yes">
    <wtui:column>First Name</wtui:column>
    <wtui:column>Last Name</wtui:column>
    <wtui:column>Email Address</wtui:column>
  </wtui:datatable>
</wtui:panel>

Now create a file code/server.php which will initially contain:

<?php
class SampleOffice {
  public static function getContactList($args) {
    $response = Array();

    return $response;
  }
}
?>

Now we need to add the code to return the data. Change server.php to this:

<?php

class SampleOffice {

  public static function getContactList($args) {
    $response = Array();

    $page = (int)$args["page"];
    $resultsPerPage = (int)$args["resultsPerPage"];

    // first find the total number of contacts
    $q = Array();
    $q["Node Type"] = "Contact";
    $q["Path"] = "/Contacts/*";
    $q["Select"] = "COUNT(*) as itemCount";
    $totalResults = (int) $GLOBALS["WT"]->query($q, "singleValueCallback");

    // work out the first result to return
    $firstResult = ($page - 1) * $resultsPerPage;
    if($firstResult > $totalResults) {
      $firstResult = 0;
    }

    if($resultsPerPage) {
      $q["Limit"] = "$firstResult, $resultsPerPage";
    }

    // has the datatable requested to sort the results by a particular column
    $q["Order By"] = "`Last Name`";
    if(array_key_exists("orderby", $args)) {
      $direction = "";
      if(array_key_exists("direction", $args) && $args["direction"] == "desc") {
        $direction = " DESC";
      }
      $q["Order By"] = "`" . $GLOBALS["WT"]->dbEscape($args["orderby"]) . "` $direction";
    }

    // put all the results into an array
    $results = Array();
    $q["Results Array"] = &$results;
    $q["Select"] = "*";

    $GLOBALS["WT"]->query($q);

    // format the data into the required format needed by the datatable..
    $data = Array();
    foreach($results as $result) {
      $data[] = Array("First Name" => $result["First Name"], "Last Name" => $result["Last Name"], "Username" => $result["Username"], "Email Address" => $result["Email Address"], "Status" => $result["Status"], "__guid" => $result["__guid"]);
    }

    // build the response in the format required by the 
    $response["Data"] = $data;
    $response["First"] = $firstResult;
    if($totalResults > 0) {
      $response["First"]++;
    }
    $response["Last"] = $firstResult + $resultsPerPage;
    if($response["Last"] > $totalResults) {
      $response["Last"] = $totalResults;
    }

    $response["Total"] = $totalResults;

    return $response;
  }
}

5.3 Submitting a Form

You can specify the PHP function which should be called when a form is submitted buy using the "call" attribute of the <wtui:form> tag.  The values from the form will be passed to the $args parameter of the PHP function.

In the sample example, change editContact.wt to this:

<wtui:panel id="editContactPanel" visible="no">
  <wtui:form type="Contact" id="editContactForm" call="setContact" hassubmitbutton="yes" hascancelbutton="yes"/>
</wtui:panel>

The "setContact($args)" PHP function will now be called when the form is submitted. You can change code/server.php to this:

<?php

class SampleOffice {
  public static function setContact($args) {
    // the form contains a hidden field named "__guid"
    $guid = (int)$args["__guid"];

    // get the form values as an associative array
    // with the keys as Node Type Attributes 
    $attributes = WTUI::getFormValues();

    // if a guid was passed, set the values for that node
    // otherwise create a new contact
    if($guid) {
      $contact = $GLOBALS["WT"]->getNode($guid);
      // make sure it's a valid guid which refers to a Contact node
      if(!$contact || $contact->getType() != "Contact") {
        return Array("success" => false);
      }
      $contact->setAttributes($attributes);

    } else {
      $contacts = $GLOBALS["WT"]->getNode("/Contacts");
      $contact = $contacts->createChild("Contact", $attributes);
      if(!$contact) {
        return Array("success" => false);
      }
    }

    return Array("success" => true, "__guid" => $contact->m_guid);
  }

  public static function getContactList($args) {
  //... as before...//
  }
}

5.4 Loading Data Into a Form

The next step is to be able to click rows in the <wtui:datatable> and edit the record. In contactList.wt, set the attribute "canclickrow" of the datatable to "yes":

<wtui:panel id="contactListPanel">

  <wtui:button icon="plus" id="createContactButton">Create Contact</wtui:button>
  <wtui:button icon="trash" id="deleteContactButton">Delete Selected</wtui:button>

  <wtui:datatable id="contactList" datasource="getContactList" canselectrow="yes" canclickrow="yes">
    <wtui:column>First Name</wtui:column>
    <wtui:column>Last Name</wtui:column>
    <wtui:column>Email Address</wtui:column>
  </wtui:datatable>
</wtui:panel>

The datatable will now trigger "rowclick" events when a row is clicked.  In contactList.js, add the handler for the "rowclick" event:

var ContactList = {};

ContactList.createContact = function() {
  WTUI.History.setState({"label": "Create Contact", "action": ContactList.createContact});

  WTUI("editContactForm").reset();
  WTUI("wtuiMainPanel").showOnly("editContactPanel");
}

WTUI("createContactButton").on("click", ContactList.createContact);

ContactList.editContact = function(contactID) {

  // parameters is an array of parameters to be passed to the action
  WTUI.History.setState({"label": "Edit Contact", "action": ContactList.editContact, "parameters": [ contactID ]});

  // call the PHP function getContact to get the details of the contact with guid contactID
  WTUI.call('getContact', { "__guid": contactID }, function(response) {
    // set the values in the form from the response
    WTUI('editContactForm').setValues(response);

    // switch to the edit contact panel
    WTUI('wtuiMainPanel').showOnly('editContactPanel');
  });
}

WTUI("contactList").on('rowclick', function(data, row) {
  ContactList.editContact(data['__guid']);
});

Now need to create the PHP  function "getContact($args)" in server.php:

<?php

class SampleOffice {

  public static function getContact($args) {
    $guid = (int)$args["__guid"];
    if(!$guid) {
      // invalide guid
      return Array();
    }

    $contactNode = $GLOBALS["WT"]->getNode($guid);
    if(!$contactNode || $contactNode->getType() != "Contact") {
      // invalide guid
      return Array();
    }

    // get the attributes, convert them so the keys match the fields in the form and return them
    $attributes = $contactNode->getAttributes();
    $response = WTUI::toFormValues("Contact", $attributes);
    $response["__guid"] = $guid;
    return $response;
  }

//.... rest as before...