-
plainTemplates - CSS driven templating engine
by Tobiasz Cudnik on 2007-07-30 17:16:18Introduction
plainTemplates is a PHP and JS library that aims to divide template into 2 separate layers:
- CSS selectors driven data attaching mechanism
- Plain HTML
It's simply creates your templates from plain HTML files on-the-fly, filling it with data and fetching parts you like.
It's basen on phpQuery - a jQuery port to PHP.
Pros
- True separation of data and visual layer
- HTML can be filled with example data
- High reusability (several templates from one HTML, one template from several HTMLs)
- Framework independed template organisation
- Transparent refreshes (including PHP code changes!)
- phpQuery integration
- No need to lear new, template-only language
- Saves a lot of time ;)
Cons
- You have to know CSS selectors (XPath will also help)
- In most cases you have to know jQuery/phpQuery
- You have to format data outside template (eg date).
Here is simple example, which inserts data from $data array into template in template.htm:
// get the class require_once('../../plainTemplates/plainTemplates.php'); // always end dir with a slash plainTemplates::$cacheDir = '../../plainTemplates/cache/'; $data = array( array( 'title' => 'News 1 title', 'body' => 'News 1 body', ), array( 'title' => 'News 2 title', 'body' => 'News 2 body', ), array( 'title' => 'News 3', 'body' => 'News 3 body', ), ); // include to fully preserve scope include( plainTemplates::createTemplate( // first the template source 'template.htm', // second the var info array( // var name in scope '$data', // and var value $data, // finally the selectors (of course there are defaults, that is UL and LI) array( 'container' => '.someclass ul', 'row' => 'li' ) ) ) );Here is template.htm itself:
<div class='someclass'> <p>UL below will be container, and every LI will be searched agains classes with field names.</p> <ul> <li> <p>Only tags with field classes are replaced.</p> <p class='title'>This will be replaces by the title</p> <p class='body'>And this by the body...</p> </li> <li> Only first row will be in the result and this wont be considered by any chance... so You can use as many row examples as You want. </li> </ul> </div>Now the result's source:
<ul><?php foreach( $data as $dataRow ): ?> <li> <p>Only tags with field classes are replaced.</p> <p class="title"><?php print $dataRow['title']; ?></p> <p class="body"><?php print $dataRow['body']; ?></p> </li><?php endforeach; ?></ul>Those are the basics, it can do a lot more, but i think its good that it can do simple things in a simple way...
Intermediate examples
EXAMPLE: Nested data
In this example you will learn about nested data, which is very common thing in eg CakePHP. So let's get started!
Code (examples/nested-data/code.php)
$data = array( array( 'Post' => array( 'title' => 'Im the title', 'body' => 'Im the body', ), 'Comment' => array( array( 'author' => 'Im the 1st comment of 1st post', 'body' => "This field has same name as post's one", ), array( 'author' => 'Im the 2nd comment of 1st post', 'body' => 'So am i', ), ), ), array( 'Post' => array( 'title' => 'Im the post without comments', 'body' => 'His right', ), 'Comment' => array(), ), ); // include to fully preserve scope include( plainTemplates::createTemplate( // first the template source 'template.htm', // second the var info array( // var name in scope '$data', // and var value $data, // finally the selectors (of course there are defaults, that is UL and LI) array( // selectors for 'Post' row 'container' => '.someclass > ul', 'row' => '> li', // selectors for 'Comment'. we have to group them inside 'Comment' index because it's another data (list), not row 'Comment' => array( 'container' => '.someclass > ul', ), ) ) ) );Template (examples/nested-data/template.htm)
<div class='someclass'> <p>UL below will be container, and every LI will be searched agains classes with field names.</p> <ul> <li> <p>In nested row, class names must have parent index prepended.</p> <p class='Post-title'>This will be replaces by the title</p> <p class='Post-body'>And this by the body...</p> <div> <ul> <li> <p>Here is different situation - we have nested data (list), with container etc, so we DONT prepend index to class name.</p> <p class='author'>Comment's author field</p> <p class='body'>And the body</p> </li> </ul> </div> </li> <li> Only first row will be in the result and this wont be considered by any chance... so You can use as many row examples as You want. </li> </ul> </div>Result::
<ul><?php foreach( $data as $dataRow ): ?> <li> <p>In nested data class names must have parent index prepended.</p> <p class="Post-title"><?php print $dataRow['Post']['title']; ?></p> <p class="Post-body"><?php print $dataRow['Post']['body']; ?></p> <div> <ul><?php foreach( $dataRow['Comment'] as $dataRowComment ): ?> <li> <p class="author"><?php print $dataRowComment['author']; ?></p> <p class="body"><?php print $dataRowComment['body']; ?></p> </li><?php endforeach; ?></ul></div> </li><?php endforeach; ?></ul>EXAMPLE: Row insertion
Previous examples was about inserting a list of data. Now you will learn about inserting only one row, eg to show this post.
Code (examples/row/code.php)
$data = array( 'Post' => array( 'title' => 'Im the title', 'body' => 'Im the body', ), 'Comment' => array( array( 'author' => 'Im the 1st comment of 1st post', 'body' => "This field has same name as post's one", ), array( 'author' => 'Im the 2nd comment of 1st post', 'body' => 'So am i', ), ) ); // include to fully preserve scope include( plainTemplates::createTemplate( // first the template source 'template.htm', // second the var info array( // var name in scope '$data', // and var value $data, // finally the selectors (of course there are defaults, that is UL and LI) array( // empty container means that we dont want a loop 'container' => '', // you can't use plain 'li', because all elements selected by this seletor except first one will be removed // in this case LI for comment row // there are other ways to write this selector, but this is the simplest 'row' => '.someclass > ul > li', // this hasn't changed 'Comment' => array( 'container' => '.someclass > ul ul', ), ) ) ) );Template (examples/row/template.htm) is the same as previous one.
Result
<p>In nested row, class names must have parent index prepended.</p> <p class="Post-title"><?php print $data['Post']['title']; ?></p> <p class="Post-body"><?php print $data['Post']['body']; ?></p> <div> <ul><?php foreach( $data['Comment'] as $dataComment ): ?> <li> <p>Here is different situation - we have nested data (list), with container etc, so we DONT prepend index to class name.</p> <p class="author"><?php print $dataComment['author']; ?></p> <p class="body"><?php print $dataComment['body']; ?></p> </li><?php endforeach; ?></ul></div>EXAMPLE: Callback
In this example i will introduce callback function and it's often use to hidding uneeded content (comments listing wrapper in this case).
Callback is a function/method which gets phpQuery object at the end of template creation, but before saving file, so it can affect result.
Code (examples/callback/code.php), $data is same as in Nested data example
function callbackName($_) { // find the only div in template $_->find('div') // insert condition before div ->beforePHP('if ( $dataRow[\'Comment\'] ):') // close condition after div ->afterPHP('endif;'); } // include to fully preserve scope include( plainTemplates::createTemplate( // first the template source 'template.htm', // second the var info array( // var name in scope '$data', // and var value $data, // finally the selectors (of course there are defaults, that is UL and LI) array( 'container' => 'ul:first', 'row' => '> li', 'Comment' => array( 'container' => 'ul ul', ), ) ), // now the new thing - callback // callback function will have phpQuery object as only arg 'callbackName' ) );Template (examples/callback/template.htm)
<p>UL below will be container, and every LI will be searched agains classes with field names.</p> <ul> <li> <p>In nested row, class names must have parent index prepended.</p> <p class='Post-title'>This will be replaces by the title</p> <p class='Post-body'>And this by the body...</p> <div> <h1>Comments</h1> <ul> <li> <p>Here is different situation - we have nested data (list), with container etc, so we DONT prepend index to class name.</p> <p class='author'>Comment's author field</p> <p class='body'>And the body</p> </li> </ul> </div> </li> <li> Only first row will be in the result and this wont be considered by any chance... so You can use as many row examples as You want. </li> </ul>Result
<ul><?php foreach( $data as $dataRow ): ?> <li> <p>In nested row, class names must have parent index prepended.</p> <p class="Post-title"><?php print $dataRow['Post']['title']; ?></p> <p class="Post-body"><?php print $dataRow['Post']['body']; ?></p> <?php if ( $dataRow['Comment'] ): ?><div> <h1>Comments</h1> <ul><?php foreach( $dataRow['Comment'] as $dataRowComment ): ?> <li> <p>Here is different situation - we have nested data (list), with container etc, so we DONT prepend index to class name.</p> <p class="author"><?php print $dataRowComment['author']; ?></p> <p class="body"><?php print $dataRowComment['body']; ?></p> </li><?php endforeach; ?></ul></div><?php endif; ?> </li><?php endforeach; ?></ul>Advanced examples
I will post some advanced examples here near future, so stay tuned and for now you can read generated phpdocs or those in the source code.
Download and links
Comments: 0