Most of the time when you're building a web site, you are pullng information from a database according to some criteria and then formatting for output, usually HTML but it could be RSS or XML or something else entirely. The way to handle this in Syntax CMS is to create a list capability within a module. You'll probably end up having a module per datatype to handle the various individualities of working with different datatypes. This tutorial shows you a general approach to writing flexible, reusable, and powerful list capabilities that can then be called from section templates or even other modules.
Listing capability sections
A generic list module will have the parts listed below. Its easier to think of a list capability as a controller that handles listing requests than a php script that produces one or more simple lists. Most of the power comes in the filtering and output parts as you'll see. For this example, let's work on a list capability for document datatypes.
- A local initialization all
- Filtering
- Sorting
- Collection Retrieval
- Output
Local Initialization
Any module capability will call a local init.php file that sets local path variables, loads any module specific code libraries you need, and does any other sort of setup work for the module.
// Include module-specific init file.
include(dirname(_FILE_) . '/init/init.php');
Filtering
The filtering section is the heart of a list capability in that it controls what will get shown on output. You can create very flexible lists by testing for the existence of Request variables and filtering appropriately. But first you need to create the filter and setup global filtering conditions to check for 'Go Live' and 'Expires' dates. DB_TIME_STAMP is a constant defined by Syntax when you're script is loaded.
/ setup any filtering /
$filter = &pxdb_search::filter( DATATYPE_DOCUMENT );
// filter by sunrise/sunset
$filter->add_value('sunset', DB_TIME_STAMP, '>=', $opt=null, $quote=false);
$filter->add_value('sunrise', DB_TIME_STAMP, '<', $opt=null, $quote=false);
Documents can be classifed by topic, region, country. Furthermore, they can be related to an individual author's record. The following conditions let us filter our list by any combination of these four classifications:
/ filter by topic if needed /
if ( null !== $Request->getVar( 'topic' ) )
{
$filter->add_value( 'topic', $Request->getVar( 'topic' ) )
}
/ filter by region if needed /
if ( null !== $Request->getVar( 'region' ) )
{
$filter->add_value( 'region', $Request->getVar( 'region' ) )
}
/ filter by country if needed /
if ( null !== $Request->getVar( 'country' ) )
{
$filter->add_value( 'country', $Request->getVar( 'country' ) )
}
/ filter by author if needed /
if ( null !== $Request->getVar( 'author' ) )
{
$filter->add_value( 'topic', $Request->getVar( 'author' ) )
}
Content could also be tagged to show up in site Section. We can take care of checking for that using filters as well, although its a few more lines of code. I use a request variable named in_section because I've found using 'section' can cause errors.
/ filter by section if needed /
if ( null !== $Request->getVar( 'in_section' ) )
{
// create parent filter
$parentFilter = &pxdb_search::filter( DATATYPE_NODE );
$parentFilter->add_value( 'parent', $Request->getVar( 'in_section' ) );
// add to main filter
$filter->add_parent( $parentFilter );
}
The filtering examples are pretty basic but cover most of the situations you'd encounter. There are many more ways to setup filters based on request parameters to solve new situations creatively instead of resorting to writing raw SQL.
Sorting
Its also useful to make sorting respond to request parameters. Of course, the collection variable must exist first.
$Records = new pxdb_collection( TYPE_DOCUMENT );
if ( $Request->getVar( 'orderby') )
{
$Records->orderBy( $Request->getVar( 'orderby') );
} else {
// set a default order by
$Records->orderBy( 'sunrise') );
}
Collection Retrieval
You'll want to account for a number of ways that the list may be displayed: limited to a certain number of records, all records, or a paginated subset:
// bind the filter first
$Records->find($filter);
// limit recordset results
if ( null !== $Request->getVar('limit') )
{
if (false === $Records->execute_limit($Request->getVar('limit')))
{
trigger_error('Execute Limit failed', E_USER_WARNING);
}
}
// paginate recordset
else if (null !== $Request->getVar('page_no') && null !== $rows_per_page)
{
if (false === $Records->execute_page($rows_per_page, $Request->getVar('current_page')))
{
trigger_error('Execute Page failed', E_USER_WARNING);
}
// show all records
} else {
$Records->execute();
}
Output
The final part of the puzzle is the part of the script that handles output generation. Here it is also useful to make it smart about recognizing a request variable that swaps the template used. You also have to account for whether the current request is an internal one or not.
if (!$Request->isIncluded())
{
include_once(SITE_TPL_PATH . '/header.tpl');
}
// if there are no recrords don't output anything
if ( 0 < $Records->num_records() )
{
if ( null !== $Request->getVar( 'template' ) )
{
include($MODULE_TPL_PATH . '/'.$Request->getVar( 'template' ).'.tpl');
} else {
include($MODULE_TPL_PATH . '/list.tpl');
}
}
if (!$Request->isIncluded())
{
include_once(SITE_TPL_PATH . '/footer.tpl');
}
Conclusion
So, now you've seen how to structure a list capability. Why do it this way? The chief reason is that it makes it ridiculously easy to reuse the list capability to generate page elements without having to retool the script or worry about generating any SQL.
We used this implementation on a project recently and it made it very easy to deploy the same kinds of data in different ways on the site. Especially nice when a manager approaches you to ask 'Can we filter by X,Y and show it in a pulldown menu?' It allows front-end designers to ignore most of the Syntax CMS code and concentrate on writing the templates they need for their section templates.
Lists are integrated into other templates using the f1cms::callModule( 'documents', 'list', array ( $parameters ) ). Here are some more sample calls for our documents listing capablility. Once you have the list filters defined, you mostly spend time writing the different templates you might need. You might also find that a combination of exisiting filters and a template will handle a new requirement.
- All Documents, sorted by title, bulleted list
- echo f1cms::callModule( 'documents', 'list', array( 'order_by'=>'title', 'template'=>'list_unordered' )
- Newest 15 documented, bulleted list
- echo f1cms::callModule( 'documents', 'list', array( 'order_by'=>'sunrise DESC', 'limit'=>'15', 'template'=>'list_unordered' )
- Documents tagged to section with id 19, title & brief description
- echo f1cms::callModule( 'documents', 'list', array( 'in_section'=>19, 'order_by'=>'title', 'template'=>'brief_desc' )
- Documents in a topic with id 19, title & brief description
- echo f1cms::callModule( 'documents', 'list', array( 'topic'=>19, 'order_by'=>'title', 'template'=>'brief_desc' )