wiki:AjaxList

Ajax List

The Ajax List component of iPeer is responsible for creating lists from data. It is implemented as a Cake PHP component, and works with cake-like formated data.
The advantages of Ajax Listinclude quick list control creation, with features default like filters, sorting, pagination, state retention, and search. The amount of initial set up is usually minimal. As Ajax List is a single component, any list-related bug in iPeer can be traced down to this component, and fixed. There are some mechanisms to extend the list's functionality, but moderate to highly custom features could require code modifications to Ajax List.

Requirements

  • For best results, PHP 5.2 is recommended:
    • PHP 5.2 needs no extra configuration.
    • PHP version bellow 5.2 should compile, and enable the PHP JSON plugin to achieve best performance.
    • A much slower version of JSON parce is included with iPeer, and will be used if all the above are missing.
    • A session component is required to keep track of a list' state. Ajax List uses CakePHP's Session component.
    • If possible, the client's browser should leave the cookies enabled, so that Ajax List can keep track of what page number the client is on.

Files

Ajax List consists of 3 files.

  • \app\controllers\components\ajax_list.php
    • Holds the PHP side of Ajax List code.
    • Contains functions to initialize the list, creation of SQL queries, code to query the database for the data to be listed, converting data to JSON for Javascript client, and general list state management.
  • \app\views\elements\list\ajaxList.tpl.php
    • The elements that is inserted into the pages view where the list is to be created.
    • Passes the list data to the JS Ajax List component, making an initial ajax call unnecessary.
  • \apps\webroot\js\ajaxList.js
    • The JavaScript? component Ajax List. Interprets the JSON, and renders the list as a table, along with the search control, and etc..

Manual

Instantiation

There is usually a single list per controller in cakePHP. The first step should be to include the ajax List component into your controller, but declaring a $components variable, inside the controller class.

var $components = array('AjaxList');

If the $components variable already exists, just add the ajax list to the array:

var $components = array('ExistingComponenet1', 'AjaxList', 'ExistingComponenet2');

Next, add your model to the $uses array, in the same fashion as the components above: var $uses = array('ListedModel');

Next, create the following 3 functions in the controller:

// 1) Sets up the data for the ajax list.
function setUpAjaxList () {

    // Which columns to list, and include data from.
    $columns = array(
        array("ListedModel.id"),
        array("ListedModel.value"),);
    // The available actions for each column
    $actions = array();
    // Any extra filtering to perform on the data from the database.
    $extraFilters = "";
    // Any tables to join in, and any extra filters to implement on those tables
    $joinTables = "";
    // Whether to fetch associated data or not ( (-1) means not at all ).
    $recursive = (-1);

    // Call the Ajax List components to fetch data and other list variables,
    //  and set them up for the view.
    $this->AjaxList->setUp($this->ListedModel, 
       $columns, $actions,
       "ListedModel.id",  // Initial Sorting Column
       "ListedModel.id",  // Initial Search-by column
       $joinTables, $extraFilters, $recursive);
}
// 2) Processes an Ajax request from the client.
function ajaxList() {
    // Set up the list
    $this->setUpAjaxList();
    // Process the request for data
    $this->AjaxList->asyncGet();
}
// 3) Creates the initial list to display by the client.
function list() {
    // Set up the basic static ajax list variables
    $this->setUpAjaxList();

    // Set the display list for render by the view
    $this->set('paramsForList', $this->AjaxList->getParamsForList());
}

The new list can be accessed from the URL:http://server/your-app/controller/list. If you'd like to change the name of the function that renders the list, just change the list() function's name (but leave the others as they are :-).

Bellow sections describe the variables that are defined in setUpAjaxList() function.

Columns

The $columns variable is an array of arrays. Each entry in the top array described a column. Each column entry is an array itself. The is the general format is the following:

$columns = array(
    array(<Model.column 1>[, <Column Name 1>[, <CSS width of column 1>[, <column 1 type>> [, column 1 type parameters]]]]),
    array(<Model.column 2>[, <Column Name 2>[, <CSS width of column 2>[, <column 2 type>> [, column 2 type parameters]]]]),
    [ etc...],
    );
  • Model.column - The CakePHP column id, in the format Model.column. This corresponds to the table.column format of the SQL database. This is the only required column in the column array entry, but it must be inside the an array, even if it's the only one.
  • Column Name - The column name will be displayed to the user. It is used in the table headers, as well as the search-by-field descriptions. If this field is left out, the user will see the Column.id instead.
  • CSS width of column - This entry is applied to inside the tableColumn.style.width CSS property of each column. By default, the value "auto" is used, which means that all columns are equally spaced. I would like to recommend that:
    • The exact width be defined for all columns, but one. That is, leaving one column (who's entries are the lonest) as auto will give the best consistent look of the table as its data changes.
    • Use "em" units for to define column widths. "em" are about the size of 1 text line height at present zoom level, and scale better than inches of pixels. Example "10em".
  • Column Type - This field describes the type of data entered. Depending on the type, Ajax List may do some processing of data, or display it in a different way, or tie and action to it. The default type is "string", which is inert. The possible types are
    • string - The basic type. The value is display just like it appears in the database.
    • number - Specifies a numerical value. In the present implementation, it acts like a string, but Ajax List could be extended in the future to search for greater or lesser numbers instead of just equivalents . So it's a good idea to specify numerical columns are "number" and not as "string".
    • map - this is a dictionary look-up type. It takes the column entry to be a key, and look it up in an associative array. This array is passes as a Column Type Parameter, following the column type. For example, the following is used in the users controller to translate entries like "A", "S", and "I" to "Admin", "Student", and "Instructor", respectively.
      • array("User.role", "Role", "6em", "map", array( "A" => "Admin", "I" => "Instructor", "S" => "Student")),
      • The general format for this column is: array(<Model.column>, <Column Name>, <CSS width of column>, <column type>, array(<key1> => <meaning1>, <key2> => <meaning 2>, etc...),
      • An extra feature of the map is that the JS AjaxList client also creates a drop-down filter for it. In this way, the end-user can choose to selectively view the mapped types, or all the types at once. In the iPeer's users controller this is used to switch the view between Admin's, Instructors, and Students.
      • If the column contents is not a valid key in the supplied dictionary, the entry is listed, and unknown keyword appears beside.
    • hidden - this type simply defined a column whose data are passed to the client, but not displayed in any way. For example, if you'd like to pass the entry ID to the client, for use in actions, but would not like the user to see it, you could define the column like this:
      • array("ListedModel.id", "", "", "hidden),
      • There are a few reasons to do this, If you'd like to use this column's data:
        • In an action (explained bellow), but don't want the data itself to show up.
        • In data post-processing. The column which do not appear in the columns array are not present when the data is given to the custom post-processing function.
    • action - Display the cell contents as a link, which, when clicked by the user, performs the action specified. The Type Parameter field is the Action's Name (the first value in the actions array).
    • link - the entry is a link, which will be display either as a text link, or a link icon. If the Column Type Parameter is specified, it should be the name of the icon file inside the webroot/img/icons folder, and if it's left out, the link is displayed as text. An example column entry from iPeer's courses controller:
      • array("Course.homepage", "Web", "4em", "link", "home.gif"),

Actions

The Actions array is an array-of-arrays that lets you define a set of actions for each entry in the list. These will be accessible when the user right click an action item. The general syntax for the actions array is:

$actions = array(
   array(<action 1 name>, [warning message 1], [restrictions array 1], [controller 1], <function>, [param 1 [, param 2am 3 etc...]]]),
   array(<action 2 name>, [warning message 2], [restrictions array 2], [controller 2], <function> [, param 1 [, param 2 etc...]]]),
   [etc...]
);
  • Action Name - The name of the action shown to the user. Best to decide on one, and leave it be, since this name is referred to by some other parts of Ajax List (Action-type columns, for example). Required.
  • Warning Message - A message that will show up, asking the user to confirm the action. Enter "" if unused.
  • Restrictions - An array containing any restrictions on the action based on the entry's map column types, more on this bellow. Enter "" if unused.
  • Controller - If the target controller is not the same as the controller this Ajax List is in, specify it here. Otherwise, enter "".
  • Function - The name of a function to execute in the present controller on the action. Required. If the function name is prefixed with a '!', the action will open a new, smaller browser window, and render the result there. So for example:
    • view - action executes in the present window.
    • !view - action opens a new window, executes, and renders the results there.
  • Param 1,2,3,etc… - The parameters passed to a controller function. If you specify a Model.column type string, Ajax List will try to substitute that entry's column for this parameter, meaning that you can specify actions based item id's in the list. If the column is not found, the string will be given at the parameter literally.
Action Restrictions

If an action is restricted for an item, if will be greyed out and disabled when the user right-clicks the action menu. Now, this doesn't mean that the user can't manipulate the URL to try and execute this function anyways, so it's a very good idea to put some access restrictions in the controller functions themselves. The access restriction array is (again :-) an array of array, and has the following general format

   $actionRestrictions = array(
      <Column ID 1> => array (
         <map entry 1> => <true / false>,
         <map entry 2> => <true / false>,
         <map entry 3> => <true / false>,
         [etc...]
         "!default"  => <true / false>),
      <Column ID 2> => array (
         <map entry 1> => <true / false>,
         <map entry 2> => <true / false>,
         <map entry 3> => <true / false>,
         [etc...]
         "!default"  => <true / false>),
     [etc...]
);

Every column ID corresponds to a map-type column. When the column's entry (raw, not yet translated), marched the map entry's in the above structure, then the boolean true or false varible will deterime wherether the user is allowed to perform an action. The "!default" map entry takes effect when the column's cell contents don't match any of the other entries. For example, the following real example is taken from the user's controller, designed to allow instructors to only edit students, and not other user types

   $actionRestrictions = array(
      "User.role" => array (
         "S" => true,              // Allow modification of students
         "!default" => false));    // Dis-allow modification for all other user types

Extra Filters

The extra filters variable can either be an array or a string. If it's a string, it's put in literally, and after the WHERE clause in an SQL statement, along with other filters in the list. If the extra filters is an array, it is put in as:
WHERE key1=value1 AND key2=value2 AND key3=value3 AND etc...
for an array like:
$extraFilters = array(key1 => value1, key2 => value2, key3 => value3);

Join Tables

Joining in table in Ajax List lets you bring in other relevant data into your list. You can look up course name by their id's or check with classes the student is enrolled in.

All joins are LEFT JOINs. One-to-one joins are supported the best, and can be used to look up a name using an id, and so forth. One-to-many joins a little more complex, since number on items in the list won't increase to accommodate the "many" entries". However, an extra filter or a join filter (bellow) can be used to isolate items of interest from the join. Otherwise, a GROUP by Model.id statement is issued to automatically group those "many" items back into one item per original list entry.

If simply joining in tables, without join filter, the $joinTable array contains an array for each table joined in:

$joinTables = array(
   array( "joinTable" => "table_name",  // - The name of the table to be joined in
          "joinModel" => "JoinModel",   // - The name of the Model to create from this table. 
                                        //     this does not have to march any CakePHP model at all, and 
                                        //     in fact, should be different if you're joining in the same table
                                        //     twice, or don't want to interfere with a model joined by CakePHP.
          "localKey" => "local_key",    // - The key to match in local table. If left out, "id" is substituted.
          "foreignKey"=>"foreign_key",  // - The key to match in foreign table. If left out, "id" is substituted
          "localModel" => "LocalModel", // - Usually left out, because the Ajax List's local model is specified in
                                        //     the setUpAjaxList() function. However, if you'd like to use a joined-in
                                        //     list's column to join another table, user this variable to specify
                                        //     that joined Model's name here.
),
   array( "joinTable" => "table_name2", // -- Same format as above
          "joinModel" => "JoinModel2",  //   "       "
           "localKey" => "local_key",   //  "       "
          "foreignKey"=>"foreign_key2", //   "       "
          "localModel" => "LocalModel", //  "       "
));

The joined in columns should be included in the $columns list, if you'd like to display them. The columns you're joining on (in local model) also need to be present in the $columns list, but may be of hidden-type if you prefer not to display them. Remember, all the columns that are referred to by Ajax List must be in the $columns list.

If the foreign key is not "id", you should create an index in MySQL for the foreign key's column. The speed degradation will be awefull if the joinTable has 1000+ entries, and no index is present (~10 or more seconds per list page).

A real example from the evaluations controller, to join in the Group, the Event, and the Course

   $joinTables = array(
       array( "joinTable" => "groups",
              "joinModel" => "Group",
              "localKey" => "group_id"),
       array( "joinTable" => "events",
              "joinModel" => "Event",
              "localKey" => "event_id"),
       array( "joinTable" => "courses",
              "joinModel" => "Course",
              "localkey" => "course_id")
);

Join Table Filters

Join Table Filters are used to render a drop down menu, where the user can select from a list of item the specific category of items to display. For example, it is used in iPeer to allow instructors to select which courses to display students for. Join Table filters are created by passing a few extra fields for each table joined in the $joinTables array. In the above case, the $joinTable is:

// Set up the course list for Ajax List
$courseList = array();
// Add in the unassigned course entry:
$courseList{"!!!null"} = "-- Unassigned --";
foreach ($userCourseList as $id => $course) {
    $courseList{$id} = $course['course'];
}

// The course to list for is the extra filter in this case
$joinTables =
    array(
        array(  // Define the GUI aspecs
            "id"            => "course_id",
            "description"   => "for Course:",
            // What are the choises and the default values?
            "list"  => $courseList,
            "default" => $this->rdAuth->courseId,
            // What table do we join to get these
            "joinTable"     => "user_enrols",
            "joinModel"     => "UserEnrol",
            "foreignKey"    => "user_id",

            // Any show/hide features based on maps
            "dependsMap"    => "User.role",    // Look to this column
            "dependsValues" => array("S")  // Display only when this column is one of these values
        )
    );

The above is a great example, because it shows all the features of the Join Table Filters in one example. Most of the entries are just like the last section's Join Table example, except that there are a few new ones:

  • "id" ⇒ Which specific column will be filtered by.
  • "description" ⇒ A small string right before the filter selection on the GUI. Let the user know what the filter does :-)
  • "list" ⇒ The list of choises for the user. The format of the list is:
    • $extraFilters = array(key1 ⇒ value1, key2 ⇒ value2, key3 ⇒ value3, etc…);
    • The keys are the actual values filtered for in SQL.
    • The values are the entry values shown to the user instead of the key.
    • My suggestion is to tie the keys to Table.id columns, and their name/title to the values.
  • "default" ⇒ The default selection for the filter.
  • "dependsMap" ⇒ Optional entry to tie this filter to the state of a map column. In the above examples, when the "dependsMap" ⇒"User.role" indicates that the column is only present if a user selects a specific key in the "User.role" column.
  • "dependsValues" ⇒ Optional entry to give an array of keys that the dependsMap map-column should have so that this joinTable is displayed for user, and taken into account when searching. Note: the table is always joined to the data, regardless of the state; this merely controls filtering of the joined data.
$joinTables = 
    array( "joinTable" => "table_name", // -- Same format as the last section
           "joinModel" => "JoinModel",  //   "       "
           "localKey" => "local_key",   //  "       "
           "foreignKey"=>"foreign_key", //   "       "
           "localModel" => "LocalModel", //  "       "

Data Post Processing

If post process function name was given to the AjaxList→setUp() function, the post-process function will be called once the data is retrieved from the database. At this point, data can be modified, or another column can be added for display.

  • Custom columns should be put into the array named ['!Custom'] (or any other name starting with !) at each of the entries, just as if it were a joined-in model name.
  • Any custom columns that you'd like to be displayed been to be entered into the $columns array passed to ajax list. Example:
    • $columns = array(array("!Custom.results", "View", "4em", "action", "View Results"));
  • note that custom columns can't be searched, or sorted by.
Last modified 6 years ago Last modified on 2010-12-07T11:16:59-08:00