
$Revision: 1.3 $
Copyright © 2004 Mark West
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation, with no Invariant Sections, no Front-Cover Texts and no Back-Cover Texts.
A copy of the license can be obtained from the Free Software foundation.
| Revision History | |
|---|---|
| Revision 0.1 | 07 January 2004 |
|
Initial Version | |
| Revision 0.2 | 07 January 2004 |
|
Added Section 2 - additional features of the example module. Corrected some typos and missing form value in modify example. | |
Abstract
This document contains a series of notes on porting module code from pnHTML to pnRender
Intended Audience
Developers
Abstract
This document is not meant as a definitive HOWTO on converting modules from using pnHTML to pnRender but seeks to offer some advice, tips and guidelines a developer can follow when porting from one output methodology to another. This document does not seek to be either definitive nor a beginners tutorial. This document expects that the person performing the port be familiar with the code to be ported, with pnHTML and the architecture of pnRender.
These tips are based on a number of months working with pnRender while porting the core modules across for PN .8.
The example module is provided to developers as a demonstration of writing API compliant modules with pnRender as the output methodology. Since the methodology is under constant evolution it's worth checking the CVS for this module to check for the latest code and templates which may not have made it into a release as yet. CVS for the example module can be found at http://cvs.postnuke.com/viewcvs.cgi/PostNuke/Modules/Miscellaneous/Example/.
Now, with all the preamble out of the way, lets look at the transfer of some pnHTML code to pnRender.
As this is simply included in other templates this is a good starting point. Take a copy of the admin menu template from the example module. Then edit this replacing the function names and language constants with yours.
As this is simply included in other templates this is a good starting point. Take a copy of the admin menu template from the example module. Then edit this replacing the function names and language constants with yours.
Now remove all traces of the old admin menu function from the php code. There will be two sections here; firstly the admin menu function itself i.e. example_adminmenu and the call to this function in all other GUI functions.
We remove pnHTML usage from any error messages as this simplifies the module somewhat. So code like
if (!pnSecAuthAction(0, 'Example::Item', '::', ACCESS_EDIT)) {
$output->Text(_EXAMPLENOAUTH);
return $output->GetOutput();
}
becomes
if (!pnSecAuthAction(0, 'Example::Item', '::', ACCESS_EDIT)) {
return pnvarPrepHTMLDisplay(_EXAMPLENOAUTH);
}
Now transfer the creation of a pnHTML object to a pnRender object. So code link becomes
// Create output object - this object will store all of our output so that
// we can return it easily when required
$output = new pnHTML();
becomes
// Create output object - this object will store all of our output so that
// we can return it easily when required
$pnRender =& new pnRender();
The use of & is important as it reduces the memory overhead of the application. The old HTML style created a duplicate of the pnHTML class for our module. The & indicates that a reference to the pnRender class is created thus not duplicating the entire class in memory.
For the admin panel is likely that you'll not want any caching so we turn caching off for admin output i.e.
// As Admin output changes often, we do not want caching.
$pnRender->caching = false;
All admin pages have a series of page headers so we transfer these first.
We've already removed the admin menu code in step 1.3 so first we add the menu to the template.
Code:
<!--[include file="example_admin_menu.htm"]-->
Each page should have a title that shows the user what action their currently performing in the module.
Code:
// Title - putting a title ad the head of each page reminds the user what
// they are doing
$output->Title(_EXAMPLEEDIT);
becomes
Template:
<div class="pn-title"><!--[pnml name="_EXAMPLEEDIT"]--></div>
Any page that contains a form will have a form header. This header contains the destination URL.
Code:
// Start form - note the use of pnModURL() to create the recipient URL of
// this form. All URLs should be generated through pnModURL() to ensure
// compatibility with future versions of PostNuke
$output->FormStart(pnModURL('Example', 'admin', 'create'));
becomes
Template:
<form action="<!--[pnmodurl modname="Example" type="admin" func="create"]-->" method="post" enctype="application/x-www-form-urlencoded">
Any page containing a form requires a form authorisation ID.
Code:
// Add an authorisation ID - this adds a hidden field in the form that
// contains an authorisation ID. The authorisation ID is very important in
// preventing certain attacks on the website
$output->FormHidden('authid', pnSecGenAuthKey());
becomes
Template:
<input type="hidden" name="authid" value="<!--[pnsecgenauthkey module="Example"]-->">
Note: we've added the module name here - pnSenGenAuthKey takes an (optional) parameter so modules should start to make actively make this of this parameter.
Lastly we have the form closing tag.
Code:
$output->FormEnd();
becomes
Template:
</form>
I found it best to comment out the entire pnHTML output of a function and then work on each 'chunk' of output one at a time until each 'chunk' is ported. By 'chunk' I mean a code segment like:
// Name
$row = array();
$output->SetOutputMode(_PNH_RETURNOUTPUT);
$row[] = $output->Text(pnVarPrepForDisplay(_EXAMPLENAME));
$row[] = $output->FormText('name', '', 32, 32);
$output->SetOutputMode(_PNH_KEEPOUTPUT);
$output->SetInputMode(_PNH_VERBATIMINPUT);
$output->TableAddrow($row, 'left');
$output->SetInputMode(_PNH_PARSEINPUT);
Un comment each 'chuck' in turn. Firstly reduce the segment by removing the pnHTML state modifiers so the code becomes:
// Name
$row[] = $output->Text(pnVarPrepForDisplay(_EXAMPLENAME));
$row[] = $output->FormText('name', '', 32, 32);
Now add the 'wrapping' table HTML into the template. e.g.
Template:
<tr>
<td></td>
<td></td>
</tr>
Now add the language define using the pnml plug in and remove the language define .e.g.
Code:
// Name
$row[] = $output->FormText('name', '', 32, 32);
Template:
<tr> <td><!--[pnml name="_EXAMPLENAME"]--></td> <td></td> </tr>
Now add in the control for the form and remove the last piece of code.
Template:
<tr>
<td><!--[pnml name="_EXAMPLENAME"]--></td>
<td><input name="name" type="text" size="32" maxlength="32"></td>
</tr>
Repeat this step for each pnHTML chuck in the current function.
Instead for returning the output of a pnHTML object we're returning the output of a pnRender object. So the return of output statement
// Return the output that has been generated by this function
return $output->GetOutput();
becomes
// Return the output that has been generated by this function
return $pnRender->fetch(<template_name_including_extension>);
For those forms that contain data (modify, modifyconfig etc.) then we need to assign the data to the template. Usually an API call would have got the item from the database. i.e@:
// The user API function is called. This takes the item ID which we
// obtained from the input and gets us the information on the appropriate
// item. If the item does not exist we post an appropriate message and
// return
$item = pnModAPIFunc('Example', 'user', 'get', array('tid' => $tid));
Now we have the item we can simply assign it to the template i.e.
// Assign the item array to the template
$pnRender->assign('item', $item);
Similarly we assign module vars to the template in the same way i.e.
// Bold
$pnRender->assign('bold', pnModGetVar('Example', 'bold'));
We have a template structure and we have data being asssigned to the template. The final step is to add the data output to the template should the function (modify, view, modifyconfig etc.) need it.
Using the form element example from step 1.8 and the data assignment from 1.10
<tr>
<td><!--[pnml name="_EXAMPLENAME"]--></td>
<td><input name="name" type="text" size="32" maxlength="32"></td>
</tr>
would become
<tr>
<td><!--[pnml name="_EXAMPLENAME"]--></td>
<td><input name="name" type="text" size="32" maxlength="32" value="<!--[$name|pnvarprepfordisplay]-->"</td>
</tr>
Note: If your data value could contain HTML code then change the pnvarprepfordisplay modifier to pnvarprephtmldisplay.
Steps 1.7-1.11 need repeating for each function in the admin panel which creates output. For the example module the functions are example_admin_new, example_admin_modify, example_admin_view and example_admin_modifyconfig. At this stage you should have ported across the admin panel. Now Test!, Test!, Test!.
The user side of a module will likely be less formulaic so you'll need to take each module as it comes but the previous steps give a idea of what processes I've found to work. Additional work needs to be done to formulate a caching strategy for the user interface. See the example module for a working example of content caching.
Hopefully these notes will be found useful by a least one person ;). As outlined in the intro these notes outline a procedure that has worked for me - nothing more, nothing less. Each developer will, no doubt, find their own path and their own methods. Any comments, suggestions, additions then please e-mail me at markwest at postnuke dot com. Any complaints, insults then please e-mail at b dot gates at Microsoft dot com.......
This section covers a few of the additional features that can be found in the example module. Some features may only be found in CVS depending on the release schedule of the example module code.
Many pnHTML modules format tables directly calling the Row and Column methods of the pnHTML class. The downside of this is that the tables in these modules may not use the table header (th) tag. Make any table that has headers uses the table header tag. .e.g the admin view function from the example module
<table style="text-align:center;width:100%;" border="3">
<tr>
<th><!--[pnml name="_EXAMPLENAME"]--></th>
<th><!--[pnml name="_EXAMPLENUMBER"]--></th>
<th><!--[pnml name="_EXAMPLEOPTIONS"]--></th>
</tr> .......
Always take into account HTML compliance. From PN .726 the core of PN was approx 95% HTML 4.01 transitional compliant. It's important to run your code through an HTML validator prior to release. The W3C Validator can be found at http://validator.w3.org.
Always ensure that your module supports transform and display hooks we're approapriate. To test transform hooks use the core autolinks module. To test display hooks use in the core ratings module.
In the area of templating there are a number of additional steps you can take with your module now to ensure that your module meets some of the standards set by PN .8. These tips are constantly evolving so when developing a module always look at the
Although strictly valid within the HTML spec many tags and attibutes are deprecated or not recommended for use. Ensure that your templates don't use any of these tags and attributes. Some examples are
Font tag e.g. <font class="pn-normal"> - replace this within an span or div tag e.g.<span class="pn-normal">. Fonts should be controlled by the style sheet.
align attribute of the div tag e.g. <div align="center">- The theme stylesheet should control layout of divs. To overrride the theme default for a specific div then use a inline style. e.g. <div style="text-align:center;">
Ensuring that your templates produce 'accessable code'. This is important to widen the audience of PN and your module. While PN .7x takes no steps in this direction PN .8 is currently being run through various accessability checkers and the devopment team learning the ropes of producing accessable code.
e.g. All form elements should have a label and that label be associated with the form element by use of the 'for' attribute on the label and the id attribute of the form element. So the form element is step 1.11
<tr>
<td><!--[pnml name="_EXAMPLENAME"]--></td>
<td><input name="name" type="text" size="32" maxlength="32" value="<!--[$name|pnvarprepfordisplay]-->"</td>
</tr>
becomes
<tr>
<td><label for="example_name"><!--[pnml name="_EXAMPLENAME"]--></label></td>
<td><input id="example_name" name="name" type="text" size="32" maxlength="32" value="<!--[$name|pnvarprepfordisplay]-->"></td>
</tr>
PHPDoc comments blocks are used to automatically generate documentation from a piece of code. The example module is documented using phpdoc comments. When porting a module consider adding phpdoc comments throughout your code base. Full details on phpdoc can be found at http://phpdocu.sourceforge.net.