The custom model is used when no other model suits your requirements. The custom model can be used to generate any type of content including HTLM, XML, CSS and JavaScript. Generally speaking, the custom model should be used for "one off" types of pages, rather than page types that occur frequently. This is because new (specialized) models should be developed to handle page types that are needed on a regular basis.

Required Function

The custom model creates a php class and you are required to define the body of the generate function of the class. This function must return whatever content is to be generated. Here we see an example of a generate function that generates the classic "Hello World!" page.

Simple custom generate function

Notice that the entire function declaration is not entered, only the body of the function. The function name is indicated by the Section Id in column 3 and the Location column is set to Replace Body to indicate that only the body portion if the function is being entered.

Optional Functions

The Example Code button can be clicked to obtain examples of other functions that can be coded. To use one of these functions, change the Function Type column from example to function.

Accessing Information

In most cases the custom page is going to need information from various sources in order to decide what to generate. Here we discuss some of the more common data sources.

From Site Information

The custom page has access to the site object. This object contains with a great deal of information about the current environment. For example, consider the following generate code:

$environment = $this->site->server->get_environment();
if ($environment === 0) {
  return 'This is the live site';
}
else {
  return 'This is the sandbox site';
}

Requesting this page in the live site would return the text 'This is the live site'. In the sandbox this would return 'This is the sandbox site'.

The site object also exposes all dollar functions programmatically. Here we see a sample generate function that appears in the page called sitemap located in system. This is used to build a sitemap page (as opposed to an xml sitemap) for any site that has a menu or a bootstrap navigation object.

// If bootnav exists, use this to render the sitemap
$bootnav = $this->site->get_site_path('classes').'bootnav/bootnav.php';
if (file_exists($bootnav)) {
  	// Generate sitemap from bootnav
	return $this->site->dollar_function('bootnav','bootnav','sitemap'); 
}
$menu = $this->site->get_site_path('classes').'menu/menu.php';
if (file_exists($menu)) {
  	// Generate sitemap from menu
	return $this->site->dollar_function('menu','menu','sitemap');
}
// Redirect to the home page (or other default indicated).
$this->site->redirect('','Sitemap is not available');  

Notice that the first parameter to the site object's dollar_function method is the name of the function that you want to execute. This is followed by the parameters that you would normally pass to the function. Pass in NULL to skip parameters. We will see later that it is also possible to pass the parameters using a keyed array. When there are several parameters that is the preferred approach for increased readability.

From Passed Parameters

Let's suppose you are building a page that returns employee information and we want to allow the following three parameters to be passed to the page:

  1. last_name
  2. city
  3. occupation

There are two main ways to dynamically pass these values:

  1. Via the querysting
  2. Via the $page call

Let's look at a sample custom page that uses these parameters.

Recall that all custom pages are implemented as php classes. Methods of this class will be called at various points in the page building/rendering process so you might want to define some class variables to "remember" information from one call to the next. In our example we are going to define the following class properties (variables):

Page class properties

set_parameters Method

Next we will need a way to copy parameters passed into this page over to the class properties we defined. This could be done using the set_parameter method as we see here:

set_properties method is called first

Notice this method accepts a parameters object as a parameter. This is the same object that can be accessed from most code via $this->site->parameters. This object offers several ways to extract parameter data. Here we are using the set_if_supplied method to set properties if they have been passed. Other methods that could be used include:

$this->city = $parameters->get_required_parameter('city');

The get_required_parameter method would trigger an error if the requested parameter was not passed.

We could also use, this method:


$this->occupation = $parameters->get_or_default('occupation','*');

This would set the occupation property to the occupation value that was passed to the program, if no such value was passed it would set it to '*'.

Note that it is not necessary to save your parameters in class variables. This is done when using the set_parameters method so that the parameters are available to other methods, such as generate. If you only need these parameters within the generate method you can also access the site's parameters object directly as we show here:

$city = $this->site->parameters->get_required_parameter('city');
$occupation = $this->site->parameters->get_or_default('occupation','*');
$this->site->parameters->set_if_supplied($last_name','last_name');

Let's look at some examples of how these parameters can be passed into this page.

Passing Parameters Via the Querystring

Suppose our page is called emplist and the page is requested using a url such as www.example.com/emplist?last_name=smith&city=toronto

This would have the effect of setting the last_name and city parameters.

Passing Parameters Via the $page function

If you are rendering the page as a component of another page, you can supply parameters to the $page function as in:

$page(emplist,last_name=jones&occupation=programmer)

It is important to note that parameters passed to the page function in this way cannot be overridden via the querystring. The parameters object will define the union of both sets of parameters but the page parameters will take precedents over the querystring parameters.

pre_generate Method

The pre_generate method should be used if you need to define any custom styles or script or set certain containers. For example, let's suppose that you want to dynamically populate the meta description for the page based on the parameters passed to it. This could not be done in the generate method since the meta description tag would have already been generated by the time the page's generate method is triggered. On the other hand, you can do this in the pre_generate method as we see in this example:

function pre_generate() {
  // This method should be used to set dynamic styles or script needed to support 
  // the content or update containers. Here we Set the meta description.
  $metatags = $this->site->layout->get_object('metatags');
  if ($metatags) {
    if ($this->last_name) {
      $desc[] = 'whose last name is '.$this->last_name;	  
    }
    if ($this->city) {
      $desc[] = 'who reside in city '.$this->city;	  
    }
    if ($this->occupation) {
      $desc[] = 'whose occupation is '.$this->occupation;	  
    }
    $metadesc = 'This page shows information about employees';
    $metadesc .= isset($desc) ? ' '.implode(' and ',$desc).'.' : '.';
    $metatags->define_meta_name('description',$metadesc,false,__CLASS__);
  }
}

Since this page could be embedded into different layouts, it is unwise to assume that the containing layout even supports meta descriptions. For example, perhaps this page is being rendered as the contents of an email. Meta data would normally not be rendered in such a case. We handle this uncertainty by first asking the layout for the metatags object and we only attempt to update the meta description if the layout returned an object.

Other page content, such as title and header tags could be updated dynamically if suitable containers exist within the current layout (codeframe). The following sample code shows how this could be done:

$this->site->layout->container->set('title',$some_title);
$this->site->layout->tag->set_content('page_header',$some_h1);

Setting Styles and Javascript Dynamically

In most cases, styles and script don't change based on the actual page contents so we can set these "statically" at either the page level or within the layout or site_settings. Nevertheless, there may be situations whereby you want to defer the addition of styles or script until you determine that they are needed. Eliminating unnecessary styles and script is an effective way to improve the speed at which your pages load. Both the javascript object and the styles object are passed a keyed array which contains all of the values needed to configure the script or styles.

Adding JavaScript Dynamically

Here we show an example of how to add script programmatically. In the first portion of the code we add a reference to script that has already been saved using the javascript model. In the second part of the code we create some script on-the-fly and define this as inline script within the head section of the current page. 

// Add external script
$script_info['file_name'] = 'myscript.js';
$script_info['file_location'] = 'SITE';  // Use FRAMEWORK for script defined in system
$script_info['placement'] = 'bottom';  // Could also be head-top, head, top
$this->site->add_script('myscript',$script_info,__CLASS__);
// Add inline script
$inline['javascript'] = <<<'EOT'
function someJavascriptFunction() {
  alert ('someJavascriptFunction called');
}
EOT
$inline['placement'] = 'head';
$this->site->add_script('inline',$inline,__CLASS__);
List of JavaScript Parameters

In the above example we only show a subset of the parameters that can be defined when calling the site object's add_script method. Let's review the other parameters that can be passed. Here we see the signature of the method:

add_script($identifier,$script_info,$requestor=false,$requestor_detail=false)
  1. The first parameter is a required identifier. Each script added should have a unique identifier. This is used to prevent duplicate script inclusion. This can also be used to override script that has been defined at a higher level. For example, if bootstrap script is set within site_setting, a particular page that does not require bootstrap could override this script request so that bootstrap is not loaded.
  2. The second parameter is a keyed array to configure the behavior of the script. The keys are described below.
  3. The third parameter identifies the caller. This will be shown when debugging the page to help ascertain which component requested a certain script.
  4. The last parameter is used to refine the requestor information in cases where more detail is required.

Let's review all of the keys that can be set in the array passed in parameter 2.

file_nameThe name of the JavaScript file to be included. This is just the last part of the name.
file_locationLeave this off or use the keyword SITE to load the script from the current site's support folder. Use the keyword FRAMEWORK to load the script from the system support folder. Alternatively this can be a url to load the script from a content deliver network. The complete url will be formed by combining file_location/file_name to form the full address.
javascript
This can be used to supply the inline script to be added directly. When this key is set, the framework will ignore any values in the file_name and file_location keys since these are not necessary for inline script. Also, the implied synchronization setting will be inline.
placementCan be head_top, head, top, bottom, nowhere. Defaults to head. head_top will ensure the script is place before any styles. top will place the script at the beginning of the page's body tag. bottom will position the script at the end of the page's body tag. nowhere will override a previously called for script to cancel the request.
synchronization
Leave this off if the browser should wait for the script to load before continuing to load the rest of the page. async will cause the script to load asynchronously. defer will defer loading until the page is loaded. inline will cause the script to be loaded inline, for small scripts this will improve performance since the browser does not have to make a separate request to load the script.
priority
This is used to tweak the order in which the script is loaded. The default priority is 1000. To load scripts near the top of a location (head, top, bottom, etc.) set the priority to a lower number. Larger numbers will be loaded later. This is mainly important for dependent scripts that must be loaded in a certain sequence.
group
This can be used to group several external scripts into a single file or inline script.
deviceIf this script is specific to small devices such as phones, enter mobile. Use desktop for scripts that are specific to larger screens,
versionThis can be used to force the browser to reload a script, as opposed to loading it from its cache. When you change the version number, the browser will treat it as a new script file.
mime_type
This is only used in cases where this is not a javascript file.
integrity
Some content delivery methods support an integrity setting to help guard against spoofing.
crossorigin
Can be anonymous or credentials.
finalSet to true to prevent a lower level component from changing or overriding your script request.
requestor
This can be passed in the array rather than parameter 3 of the add_script method.
requestor_detail
This can be passed in the array rather than parameter 4 of the add_script method.
commentCan be used to add a comment to the script request for debugging purposes.

Adding Styles Dynamically

To dynamically add styles to the current page, the process is similar to what we have shown for JavaScript above. Here is an example of this:

// Add a stylesheet from system, make it inline
$ext_css = array('file_name'=>'dollar/responsive_iframe.css','file_location'=>'FRAMEWORK','inline'=>true);
$this->site->add_styles('responsive_iframe',$ext_css,__CLASS__);
$css['styles'] = <<<EOT 
#lightbox{background-color:#eee; padding: 10px; border-bottom: 2px solid #666; border-right: 2px solid #666;}
#lightboxDetails{font-size: 0.8em; padding-top: 0.4em;}
#lightboxCaption{ float: left;}
EOT
$this->site->add_styles('lightbox',$css,__CLASS__);

This signature of the site object's add_styles method is similar to add_script as we see here:

add_styles($identifier,$style_info,$requestor=false,$requestor_detail=false)

The parameters behave in a similar way except parameter 2 is an array used to configure style behavior. The keys to this array are defined below:

file_nameThe name of the stylesheet file to be included. This is just the last part of the name.
file_locationLeave this off or use the keyword SITE to load the stylesheet from the current site's styles folder. Use the keyword FRAMEWORK to load the stylesheet from the system styles folder. Alternatively this can be a url to load the stylesheet from a content deliver network. The complete url will be formed by combining file_location/file_name to form the full address.
stylesThis can be used to supply the styles to be added inline directly. When this key is set, the framework will ignore any values in the file_name and file_location keys since these are not necessary for inline styles.
mediaThis is an optional media query to control the devices to which the styles pertain.
inlineSet to true if the styles are to be loaded inline.
priorityThis can be used to fine tune the order in which the styles are loaded. The default priority is 1000. Use lower numbers for styles that should be loaded first.
groupThis can be used to group several stylesheets into one so that the browser only needs to make one request to load the styles.
versionThis can be used to force the browser to reload the styles, as opposed to loading them from its cache. When you change the version number, the browser will treat it as a new stylesheet that needs to be loaded.
finalSet to true to prevent a lower level component from changing or overriding your styles request.
requestorThis can be passed in the array rather than parameter 3 of the add_script method.
requestor_detailThis can be passed in the array rather than parameter 4 of the add_script method.
commentCan be used to add a comment to the script request for debugging purposes.

The site's add_script and add_styles methods will be ignored when called from pages using codeframes that don't support the javascript or styles objects, respectively.

Extracting Information From an SQL Database

In many cases the data you will be presenting in the generate method will come from a database. Often, you will be able to use the db_table_browse model for this purpose but there will be times when you may want more control over the presentation of the data. There are several ways that a custom generate method can access SQL data.

  1. Using SQL oriented $functions such as $dbselect and $dblookup.
  2. Using the db_table_browse_wrapper class to fetch data
  3. Calling a generated db_object class to update data
  4. Using PDO to directly access the database

When using the first option, you may want to utilize the function code builder by clicking on the $function Help button shown here.

Function Help button

This will bring up a screen that will help you ascertain what parameters the function needs as we see below:

$dbselect parameters

After clicking on the Accept button in the popup page, the suggested code will be returned next to $Function Help button as we see here:

Generated by dollar function help

Copy and paste the code to use this as part of your generate method. In many cases you will need to adapt the code to cater to special situations. Here we show how the search fields could be dynamically added based on the parameters passed to the page.

$dbselect = array(
 'return_format' => 'bootgrid', // Set this to array to just return the raw data
 'return_column' => array('first_name','last_name','occupation','city','state','date_of_birth'),
 'schema' => 'hr',
 'table' => 'employee'));
if ($this->last_name) {
  $dbselect['key'][] = 'last_name';
  $dbselect['value'][] = $this->last_name;
}
if ($this->city) {
  $dbselect['key'][] = 'city';
  $dbselect['value'][] = $this->city;
}
if ($this->occupation) {
  $dbselect['key'][] = 'occupation';
  $dbselect['value'][] = $this->occupation;
}
if (!isset($dbselect['key'])) {
  return 'You must pass either last_name, city or occupation';
}
return $this->site->dollar_function('dbselect',$dbselect); // Return the generated grid

Developing Custom SQL Queries

Sometimes the queries you need to write may be too complex to be handled by the $dbselect or $dblookup functions. In such a case you can build your query from scratch using supplied base classes provided by the GenHelm framework. In this example, we show a custom query developed using the supplied sql_handler class.

// Custom SQL query used to find actors who have acted in the most number of films.
// The number of top results can be passed in the querystring
require_once CLASS_PATH.'sql_handler.php';
// Get optional limit from the querystring. Since this is cast to an integer
// we don't need to check for SQL injection
$sql_parms['limit'] = $this->site->parameters->get_or_default('limit',30,'integer');
//
// Show the limit in the H1
$this->site->layout->tag->set_content('page_header',
                    'Top '.$sql_parms['limit'].' Actors with Most Film Roles');
//
// Connect to the database and process the query
// Use PHP's nowdoc syntax to assign the sql string
$sql_handler = new \system\sql_handler($this->site);
$sql = <<<'SQL'
select actor.first_name, actor.last_name, count(actor_id) as film_count
from actor join film_actor using (actor_id)
group by actor_id
order by film_count desc limit :limit;
SQL;
$rows = $sql_handler->fetch_all_rows($sql,$sql_parms,'sakila');
if ($rows === false) { // SQL error
    // If layout has a message_object, pass the message to it
	$message_object = $this->site->layout->get_object('message_area');
	if ($message_object) {
		$sql_handler->transfer_your_messages_to_me($message_object);
		return 'Error processing the request';
	}
	else {
   		return $sql_handler->obtain_message(true,true);
	}
}
if (!$rows) { // No records were found
    return 'No results found';
}
// Use array_to_table to render the results
require_once CLASS_PATH.'array_to_table.php';
$totable = new system\array_to_table();
$totable->set_table_properties('class="actor_tbl"');
$totable->set_header(array('First Name','Last Name','Film Roles'));
return $totable->generate($rows);

Prevent Parsing of Dollar Functions

If you are using the custom model to generate content which may contain dollar functions but you don't want these dollar functions to be parsed you can supply an optional function named get_parse_dollar as shown here:

function get_parse_dollar() {
	return false;
}

Since this function returns false, any dollar functions generated by the custom component will remain as dollar functions, they won't be resolved. Keep in mind that if your page is nested inside another page that does parse dollar functions, your page's dollar functions may be parsed as part of the container page's rendering.

Using Custom Pages in Combination with Other Models

Another potential data source for custom pages may come from components generated by other models. For example, the xml_sitemap model allows you to specify pages from system and other inherited sites to be included in the xml sitemap for the site. The information is saved as a php "snippet" (.inc file). The sitemapindex page (defined in system) is based on the custom model and it uses components generated by the xml_sitemap model to determine what the xml sitemap should contain for the current site.

Message Handling

Some of your custom pages may want to render on-page messages within the message area codeframe object. To do so, they should first obtain a copy of the message_area object from the site's layout object to make sure that the layout they are currently running within supports the message object. Here we show how you can check for this.

$msg_area = $this->site->layout->get_object('message_area');
if (!empty($msg_area)) {
    // The layout supports standard message handling
}

Once you have determined that your container codeframe supports standard messaging there are a number of ways that your custom page can set a message. One option is to assign the messages directly to the message object as shown here:

$msg_area = $this->site->layout->get_object('message_area');
if (!empty($msg_area)) {
	// Assign the message directly within the message area object  
	$msg_area->assign_message('!general',36,'search','Your :1: request was completed successfully.',
                              __LINE__.' '.__CLASS__);
}
return 'test page content';

The above code defined within the generate method of the custom page would render the following content:

Custom page with a message

Custom pages should generally call upon other objects to implement business logic and processes. In so doing, the business logic becomes more reusable since it is not tightly coupled to a specific custom page. If the objects that your custom page calls extend the standard_base class, you will normally want to capture any messages that your called objects have set.

Here we show an example in which the custom generate method calls another "processor" object which may set one or more messages. After calling the object we transfer any messages that it may have set to the message_area object by calling the transfer_your_messages_to_me method. If needed, we can also add more messages to the message_area object.

$msg_area = $this->site->layout->get_object('message_area');
$some_object = $this->site->create_object('some_object');
$success = $some_object->do_something();
if (!empty($msg_area)) {
  	// both objects referenced below must inherit standard_base
	$some_object->transfer_your_messages_to_me($msg_area);
}
// We can also supplement the messages transfered to message_area
if ($success) {
	$msg_area->assign_message('!general',36,'search','Your :1: request was completed successfully.',
                    __LINE__.' '.__CLASS__);
}
else {
	$msg_area->assign_message('!general',37,'search','Your :1: request failed.',
                    __LINE__.' '.__CLASS__);
}

The transfer_your_messages_to_me method should normally be called after calling any object that inherits standard_base (even if the object does not set a message). By doing so, the caller's code will not have to be changed if the called object is updated at a later date and introduces message assignments.

In most cases, the caller will pass itself as a parameter to transfer_your_messages_to_me as in:

$result = $some_object->do_something();
$some_object->transfer_your_messages_to_me($this);

The advantage of having a standard approach to message handling is that the object you call can call methods of other objects which call methods of still other objects and if all of these objects handle messaging the same way all messages will percolate up the call chain right back to you without requiring any complex logic.

Sample custom specification
🡇
Sample rendered custom specification