• plainTemplates - CSS driven templating engine

    by Tobiasz Cudnik on 2007-07-30 17:16:18

    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

    PHP phpQuery project plainTemplates plainTemplates release

  • phpQuery - a jQuery port to PHP

    by Tobiasz Cudnik on 2007-07-07 21:06:47

    Introduction

    phpQuery is PHP-port of jQuery - well known and great web2.0 JS library

    It's something defferent than jQPie, which is form of JS code generator and server-client layer.

    For example You can do something like this:

    print _('file.htm')
        ->find('body div.cls1.cls2 ul > li:first')
            ->parent()
                ->prepend('<li>my new first LI</li>')
                ->parents('.myClass')
                    ->remove()
                    ->end()
                ->appendTo('body')
                ->parents('html')
                    ->html();

    Code above will find first LI inside specific UL, then move pointer into it's parent (UL), then prepend (add at the begining) new LI, then pointer will move to parent element with class .myClass, which will be removed, and pointer will go back to UL (with end() method), and then UL will be appended to BODY (moved, not copied). Atfer all this operations parent with tag HTML will be searched and it's content will be returned to print statement.

    phpQuery acts almost like jQuery - it returns new instance on certain methods and allows to revert stack.

    It works on DOM Extension and is designed for PHP5 only.

    There is almost no docs yet, so please refer to jQuery's one (DOM section).

    Difference against jQuery

    phpQuery differs in some cases from jQuery:

    1. Interation
    2. Callbacks
    3. No DOM nodes
    4. In some method names (PHP reserved words)
    5. PHP specific addons
    6. Other addons

    Interation

    phpQuery makes use of PHP's SPL Interator interafce, so You can do:

    foreach(_('ul>*') as $_li) {
    	$_li->prepend('new beginning of every LI');
    }

    Callbacks

    PHP doesn't have closures, but You can still use callbacks - direct or created with create_function() like so:

    function imTheCallback($_node){
    	$_node->html("i'm changed content");
    }
    class imTheClass {
    	static function imTheStaticCallback($_node){
    		$_node->html("i'm changed content v2");
    	}
    	function imTheCallbackToo($_node){
    		$_node->html("i'm changed content v3");
    	}
    }
    $class = new imTheClass;
    _('ul>*')
    	.each('imTheCallback')
    	.each(array('imTheClass', 'imTheStaticCallback'))
    	.each(array($class, 'imTheCallbackToo'))
    	.each(create_function('$_node', '
    		$_node->html("i\'m changed content v4");'
    	));
    }

    No DOM nodes

    Every node passed to callback or inside iteration is phpQuery object, not a DOM node. Also there isn't a get() method.

    Method names

    There are several methods in jQuery's interface with names which couldn't be used as PHP class method or was changed to preserve consistent naming convention.

    All those methods have been prefixed with _underscore and here's the list:

    • _clone
    • _next
    • _prev
    • _empty

    PHP specific addons

    There are couple of PHP specific addons in phpQuery for easier developement:

    • appendPHP($code) - equals to append(<?php $code ?>)
    • prependPHP($code) - equals to prepend(<?php $code ?>)
    • beforePHP($code) - equals to before(<?php $code ?>)
    • afterPHP($code) - equals to after(<?php $code ?>)
    • attrPHP($attr, $code) - equals to attr($attr, <?php $code ?>)
    • php($code) - equals to html(<?php $code ?>)
    • phpPrint($code) - equals to html(<?php print $code ?>)
    • phpMeta($selector, $code) - equals to find($selector)->php($code)->end()
    • __toString() - equals to htmlWithTag()

    Other addons

    There is/will be several methods not present in standard jQuery, which i use (with jQuery) in my projects. More about this later.

    Developement status

    Actually phpQuery seems to be quite stable and is main part of plainTemplates lib, which powers this blog.

    Although there are couple of things to be done:

    • Dedicated docs (copy jQuery's one, add PHP specific, generate phpdoc)
    • Missing methods (css, val)

    Download and links

    Here are the link which could be helpfull when dealing with phpQuery:

    Comments: 0

    PHP jQuery phpQuery project DOM release phpQuery release