plainTemplates - CSS driven templating engine

Introduction

plainTemplates is a PHP and JS library that aims to divide template into 2 separate layers:

  1. CSS selectors driven data attaching mechanism
  2. 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

Tags: PHP project phpQuery plainTemplates plainTemplates release

Bookmark

  • del.icio.us