View Source

<ac:macro ac:name="unmigrated-inline-wiki-markup"><ac:plain-text-body><![CDATA[{zone-template-instance:ZFPROP:Proposal Zone Template}
{composition-setup}

{zone-data:component-name}
Zend_View_Dtl
{zone-data}

{zone-data:proposer-list}
[Benjamin Eberlei|mailto:benny@whitewashing.de]
{zone-data}

{zone-data:liaison}
TBD
{zone-data}

{zone-data:revision}
08/04/2008 - Finished first proposal
{zone-data}

{zone-data:overview}
Zend_View_Dtl is an attempt to completly clone the Django Template Language Library (Django is a Python
Web Framework) and integrate it as Zend_View_Interface implementation. It is an object oriented templating
language that has some roots in Smarty syntax. It allows to inherit templates and overide specific blocks
of the template. All Tags and filters should be ported so that there is full compability between both
implementations. Additionally a helper will be suggested to to integrate the View with the DojoX DTL
parser in the most simple way, to share templating logic between server and client.

This proposal attempts to bring a DTL Parser into the ZendX extras library as an additional alternative to Zend
View, since the main component already has a template engine. Its in no way a proposal to replace Zend View.
I have already implemented a first version for my personal Zend Framework extensions library, it can be
loaded from: http://www.beberlei.de/calypso/
{zone-data}

{zone-data:references}
* [Django Template Language: For template authors|http://www.djangoproject.com/documentation/templates/]
* [Django Template Language: For programmers|http://www.djangoproject.com/documentation/templates_python/]
* [DojoX DTL Parser|http://dojotoolkit.org/book/dojo-book-0-9/part-5-dojox/dojox-dtl]
{zone-data}

{zone-data:requirements}
* *implements* Zend_View_Interface
* *must* implement all functionality DTL and DojoX DTL offer
* *must* offer functionality to syntesize with DojoX DTL
* *should* integrate with Zend_Cache
* *should* allow easy integration of already exisiting View Helpers
{zone-data}

{zone-data:dependencies}
* Zend_View_Interface
* Zend_View_Exception
* Zend_Json
* Zend_View_Helper_Abstract
{zone-data}

{zone-data:operation}
Zend_View_Dtl is a proposed extension to the Zend Framework to allow the integration of a Template Engine
into projects that are in need of non-php rendering of views. It implements one of the most state of the art
template engines that has proven to be succesful in the Django framework. It uses object oriented patterns
like inheritence to generate templates. Using this component would start with overriding your MVC application
to use a Zend_View_Dtl object instead of the default Zend_View. View scripts will then be written using the django
template language syntax as specified in the Django Template Language documentation.

No high hopes: Using DTL is slower than using the pure Zend_View, but it is convienient for projects that stricly
seperate programming from webdesign (In Outsourcing relationsships for example), it offers
very nice programming shortcuts that webdevlopers easily understand and its OO background makes using Zend_Layout
obsolete. Under the hood it integrates with Zend_Cache to save the compiled Node List structure for faster page generation.

The neat thing to use will be the switches to shift templating from the server to the client using the DojoX DTL Parser,
which implements the Django template language via Dojo on the client side. You would be able to specify any template
script to render in the client and take data from a JSON string. The template engine would recognize the client side
block, generate a javascript snippet to be included on the rendered server site page and on load the client will fetch
the missing data from a specified url returning a JSON object. This would allow for a seamless and fast integration of
dynamicly ajax generated content and traditional server side templating without any additional costs and complexity.
{zone-data}

{zone-data:milestones}
* Milestone 1: Finish First Running alpha version (clone python to php code) *DONE* (http://www.beberlei.de/calypso/)
* Milestone 2: Finish the proposal (Implement varity of Use Cases)
* Milestone 3: Community Review
* Milestone 4: Implement Helper that allows for DojoX and Zend_View_Dtl integration and build use cases and demo
* Milestone 5: Implement Unit-Tests
* Milestone 6: Ready for Review Phase / Find Bottlenecks in different enviroements and optimize Parsing and Rendering
* Milestone 7: Request for Recommendation
* Milestone 8: Documentation
{zone-data}

{zone-data:class-list}
* Zend_View_Dtl
* Zend_View_Dtl_Exception
* Zend_View_Dtl_Template
* Zend_View_Dtl_Lexer
* Zend_View_Dtl_Token
* Zend_View_Dtl_Parser
* Zend_View_Dtl_Parser_FilterExpression
* Zend_View_Dtl_Parser_Variable
* Zend_View_Dtl_Parser_Tag_Interface (+ lots of implementations)
* Zend_View_Dtl_Node_Abstract (+ lots of implementations)
* Zend_View_Dtl_Node_Text
* Zend_View_Dtl_Node_Variable
* Zend_View_Dtl_Filter_Abstract (+ lots of implementations)
{zone-data}

{zone-data:use-cases}
{deck:id=UseCaseBasic}
{card:label=UC-01: example1.php}
{code}
class Author {
public $name = "Benjamin";
public $email = "benny at whitewashing dot de";
}

$entries = array(
array('title' => "Test's 1", 'body' => 'Lorem ipsum dolor sit amet'),
array('title' => 'Test 2', 'body' => 'Quisque arcu leo, fermentum sed, elementum ullamcorper'),
array('title' => 'Lorem ipsum dolor sit amet', 'body' => 'Proin cursus nibh id purus. Vestibulum ut sapien'),
);

$start = microtime(true);
$template = new Zend_View_Dtl();
$template->setScriptPath(dirname(__FILE__)."/templates/");
$template->assign('data', 'test value');
$template->assign('entries', $entries);
$template->assign('admin', new Author());
$template->assign('athlete_list', '<strong>Ein Athlet!!</strong>');
$template->assign('coach_list', false);

$html = $template->render("extends.tpl");

echo $html;
{code}
{card}

{card:label=UC-01b: extends.tpl}
{code}
{% extends "base.tpl" %}

{% regroup entries by title as ets %}

{% block title %}My amazing blog{% endblock %}

{% block content %}
{% for entry in entries %}
{% ifchanged entry.title %}
test
{% endifchanged %}

{% with entry.title as title %}
<h2>#{{ forloop.counter }} - {{ title|safe }}</h2>
<p>{{ entry.body }}</p>
{% endwith %}
{% endfor %}
{% endblock %}
{code}
{card}

{card:label=UC-01c base.tpl}
{code}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<link rel="stylesheet" href="{% block stylesheet %}style.css{% endblock %}" />
<title>{% block title %}My amazing site{% endblock %}</title>
</head>

<body>

{% comment %}
Lorem ipsum dolor sit amet.
{% endcomment %}

<div id="sidebar">
{% block sidebar %}
<ul>
<li><a href="/">Home</a></li>
<li><a href="/blog/">Blog</a></li>
</ul>
{% endblock %}

{% spaceless %}
<ul>
<li>Author {{ admin.name }}</li>
<li>Email {{ admin.email }}</li>
</ul>
{% endspaceless %}

<p>FirstOf: {% firstof empty_var "Hello World!" "Test!" %}</p>

<p>Now: {% now "%d.%m.%Y" %}</p>

{% spaceless %}
<ul>
<li>
{% if athlete_list %}
Number of athletes: {{ athlete_list }}
{% else %}
No athletes.
{% endif %}
</li>

{% autoescape off %}
{% if athlete_list and not coach_list %}
<li>There are some athletes ({{ athlete_list }}) and absolutely no coaches.</li>
{% endif %}
{% endautoescape %}

{% if not athlete_list or coach_list %}
<li>There are no athletes, or there are some coaches.</li>
{% endif %}
</ul>
{% endspaceless %}
</div>

<div id="content" style="width: {% widthratio 175 200 600 %}px;">
<h1>Welcome to this {{ "Site" }}</h1>

{% block content %}{% endblock %}
</div>
</body>
</html>
{code}
{card}

{card:label=UC-02 Implement DTL in Zend MVC}
$application_path = "path/to/your/apps/folder";
$front = Zend_Controller_Front::getInstance();

$view = new Calypso_View_Dtl();

$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setView($view)
->setViewBasePathSpec($application_path."/modules/:module/views")
->setViewSuffix('tpl');

Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

$front->dispatch();
{deck}
{zone-data}

{zone-data:skeletons}

{deck:id=Skeletons}
{card:label=ZendX_View_Dtl}
{code}
<?php
class Calypso_View_Dtl implements Zend_View_Interface
{
const FILTER_SEPARATOR = '|';
const FILTER_ARGUMENT_SEPARATOR = ':';
const VARIABLE_ATTRIBUTE_SEPARATOR = '.';
const BLOCK_TAG_START = '{%';
const BLOCK_TAG_END = '%}';
const VARIABLE_TAG_START = '{{';
const VARIABLE_TAG_END = '}}';
const COMMENT_TAG_START = '{#';
const COMMENT_TAG_END = '#}';
const SINGLE_BRACE_START = '{';
const SINGLE_BRACE_END = '}';

public function __construct();

public function setCache(Zend_Cache_Core $cache);

/**
* Return the NodeList of this View
*
* @return Calypso_View_Dtl_Node_List
*/
public function getNodeList()

/**
* Return the template engine object, if any
*
* If using a third-party template engine, such as Smarty, patTemplate,
* phplib, etc, return the template engine object. Useful for calling
* methods on these objects, such as for setting filters, modifiers, etc.
*
* @return mixed
*/
public function getEngine()

/**
* Set the path to find the view script used by render()
*
* @param string|array The directory (-ies) to set as the path. Note that
* the concrete view implentation may not necessarily support multiple
* directories.
* @return void
*/
public function setScriptPath($path)

public function addScriptPath($path)

/**
* Retrieve all view script paths
*
* @return array
*/
public function getScriptPaths()

/**
* Set a base path to all view resources
*
* @param string $path
* @param string $classPrefix
* @return void
*/
public function setBasePath($path, $classPrefix = 'Zend_View')

/**
* Add an additional path to view resources
*
* @param string $path
* @param string $classPrefix
* @return void
*/
public function addBasePath($path, $classPrefix = 'Zend_View')

/**
* Directly assigns a variable to the context of the view script.
*
* Checks first to ensure that the caller is not attempting to set a
* protected or private member (by checking for a prefixed underscore); if
* not, the public member is set; otherwise, an exception is raised.
*
* @see Calypso_View_Dtl_Context::__set
* @param string $key The variable name.
* @param mixed $val The variable value.
* @return void
* @throws Zend_View_Exception if an attempt to set a private or protected
* member is detected
*/
public function __set($key, $val);

/**
* Allows testing with empty() and isset() to work inside
* templates.
*
* @see Calypso_View_Dtl_Context::__isset
* @param string $key
* @return boolean
*/
public function __isset($key);

/**
* Allows unset() on object properties to work
*
* @see Calypso_View_Dtl_Context::__unset
* @param string $key
* @return void
*/
public function __unset($key);

/**
* Assign variables to the view script via differing strategies.
*
* Suggested implementation is to allow setting a specific key to the
* specified value, OR passing an array of key => value pairs to set en
* masse.
*
* @see __set()
* @param string|array $spec The assignment strategy to use (key or array of key
* => value pairs)
* @param mixed $value (Optional) If assigning a named variable, use this
* as the value.
* @return void
*/
public function assign($spec, $value = null);

/**
* Clear all assigned variables
*
* Clears all variables assigned to Zend_View either via {@link assign()} or
* property overloading ({@link __get()}/{@link __set()}).
*
* @return void
*/
public function clearVars();

/**
* Processes a view script and returns the output.
*
* @param string $name The script script name to process.
* @return string The script output.
*/
public function render($name);

protected function _run();
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Template}
{code}
<?php
class Calypso_View_Dtl_Template
{
protected $_nodeList = null;
protected $_paths = array();
protected $_cache = null;

protected $_tags = array();
protected $_filters = array();

/**
* Call with an array of Script Paths and optionally a Caching object
*
* @param unknown_type $paths
* @param unknown_type $cache
*/
public function __construct($paths=null, $cache=null);

/**
* Compile the Template from DTL format and return a List of Nodes that respresent
* the compiled template.
*
* @param String $name
* @return Calypso_View_Dtl_Node_List
*/
protected function _compile($name);

/**
* Render the Template given the Context. Search the existing
* Script Paths for a template that matches the given name.
*
* @param String $name
* @param Calypso_View_Dtl_Context $context
* @return String
*/
public function render($name, Calypso_View_Dtl_Context $context);

public function getNodeList();

public function __clone();

/**
* Register a Filter with this Template
*
* @param String $name
* @param Calypso_View_Dtl_Filter_Abstract $class
* @return Calypso_View_Dtl_Template
*/
public function addFilter($name, Calypso_View_Dtl_Filter_Abstract $class);

/**
* Add a new tag to the known tag list and register ist with $name,
* which is the name the tag can be accessed in the template. Tags,
* (even base tags like Include, Extends, Block) can be overwritten.
*
* @param String $name
* @param Calypso_View_Dtl_Tag_Interface $class
* @return Calypso_View_Dtl_Template
*/
public function addTag($name, Calypso_View_Dtl_Tag_Interface $class);

/**
*
* @param String $name
*/
public function getTag($name);

/**
* Return the filter object of given specified name
* @param String $filterName
* @return Calypso_View_Dtl_Filter_Abstract
*/
public function getFilter($filterName);

/**
* Reset and set Script Path
*
* @param String $path
* @return Calypso_View_Dtl_Template
*/
public function setScriptPath($path);

/**
* Return all currently set Script Paths
*
* @return Array
*/
public function getScriptPaths();

/**
* Add Script Path
*
* @param String $path
* @return Calypso_View_Dtl_Template
*/
public function addScriptPath($path);

/**
* Return the complete scriptpath for a given template script $name
*
* @param String $name
* @throws Calypso_View_Dtl_Exception
* @return String
*/
protected function _getScript($name);
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Parser}
{code}
<?php
class Calypso_View_Dtl_Parser implements ArrayAccess
{
private $tokens = array();

private $_tags = array();

private $_filters = array();

private $_template = null;

private $_data = array();

public function __construct($tokens, Calypso_View_Dtl_Template $template);

/**
* Return the current Template object
*
* @return Calypso_View_Dtl_Template
*/
public function getTemplate();

/**
* Specific Ajax Block Parsing function that takes all tokens
* from the stack until {% endajax %} occurs.
* @throws Calypso_View_Dtl_Parser_Exception
* @return String[] $tokenList
*/
public function parseClient($until="endajax");

/**
* Parse an array of tokens.
*
* @param String $parseUntil Just parse up to a given tag, which is needed for tag operations
* @return Calypso_View_Dtl_Node_List
*/
public function parse($parseUntil=null);

/**
* Loop over $array and use each entry as haystack
* for a strstr($haystack, $needle) comparission.
*
* @param Array $array
* @param String $needle
* @return Boolean
*/
private function _strStrInArray($array, $needle);

/**
* Skip Past the next tokens until the Block Token $endtag occurs.
*
* @throws Calypso_View_Dtl_Parser_Exception
* @return void
*/
public function skipPast($endtag);


/**
* Return the next token from the stack that should be processed
*
* @return Calypso_View_Dtl_Token
*/
public function nextToken();

/**
* Delete the next token on the stack
*
* @return void
*/
public function deteleFirstToken();

public function __toString();

/**
* Compile a Variable String by Filtering its parts using the
* FilterExpression class.
*
* @param String $string
* @return Calypso_View_Dtl_Parser_FilterExpression
*/
public function compileFilter($string);

public function offsetExists($offset);

public function offsetGet($offset);

public function offsetSet($offset, $value);

public function offsetUnset($offset);
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Lexer}
{code}
<?php
class Calypso_View_Dtl_Lexer
{
private $template_string = "";

public function __construct($template_string);

/**
* Splits the template string and returns an Array of Tokens
*
* @return Array
*/
public function tokenize();

private function _createToken($bit, $in_tag);
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Context}
{code}
<?php
class Calypso_View_Dtl_Context implements ArrayAccess
{
// Array of dictonaries
private $_dicts = null;
private $_autoEscape = true;
private $_scope = array();

public function __construct($dict=null, $autoEscape = true);

public function push();

public function pop();

/**
* Set a variable in the current context
*
* @param String $name
* @param Mixed $value
*/
public function __set($name, $value);

/**
* Get a variable's value, starting at the current context and going upward
*
* @param String $name
* @return Mixed
*/
public function __get($name);

/**
* Delete a variable from the current context
* @param String $name
*/
public function __unset($name);

public function __isset($name);

public function get($name, $otherwise=null);

/**
* Like dict.update(). Pushes an entire dictionary's keys and values onto the context.
*
* @param array $dict
*/
public function update(Array $dict);

public function offsetExists($offset);

public function offsetGet($offset);

public function offsetSet($offset, $value);

public function offsetUnset($offset);

/**
* Clear the complete context
*/
public function clearVars();
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Token}
{code}
<?php
class Calypso_View_Dtl_Token
{
const TOKEN_TEXT = 0;
const TOKEN_VAR = 1;
const TOKEN_BLOCK = 2;
const TOKEN_COMMENT = 3;

private $type = null;
private $content = "";

public function __construct($type, $content);

/**
* Split the Contents of this token at whitespaces but do not split
* within strings in single or double quotes.
* TODO: Missing the support the ticks for innerquote: "someone's group"
* @return Array
*/
public function splitContents();

/**
* Return the Type of this Token
*
* @return Integer
*/
public function getType();



/**
* Return the Contents of this token as a string
*
* @return String
*/
public function getContents();

/**
* Return the Tag representation of the saved contents
*
* @return String
*/
public function getContentsAsTag();

/**
* Return debug description of this token
*
* @return String
*/
public function __toString();
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Tag_Interface}
{code}
<?php
interface Calypso_View_Dtl_Tag_Interface
{
/**
* Any tag if detected and tried to be parsed, will have access
* to the current Parser and the token that triggered the tag.
*
* @see Calypso_View_Dtl_Parser
* @see Calypso_View_Dtl_Token
* @param Calypso_View_Dtl_Parser $parser
* @param Calypso_View_Dtl_Token $token
*/
function parse(Calypso_View_Dtl_Parser $parser, Calypso_View_Dtl_Token $token);
}
{code}
{card}

{card:label=ZendX_View_Dtl_Node_Abstract}
{code}
<?php
abstract class Calypso_View_Dtl_Node_Abstract
{
/**
* Render the Content of this Node Server-Side
* filling possible variables from data of the current context.
*
* @param Calypso_View_Dtl_Context $context
* @return String
*/
abstract public function render(Calypso_View_Dtl_Context $context);

/**
* Return Node Description for Debugging Purposes
* @return String
*/
abstract public function __toString();

/**
* Must this tag be the first of the template? Used for Extends Block
*
* @return Boolean
*/
public function mustBeFirst();
}
?>
{code}
{card}

{card:label=ZendX_View_Dtl_Filter_Abstract}
{code}
<?php
abstract class Calypso_View_Dtl_Filter_Abstract
{
/**
* Apply the current filter to the $value and take
* $options as parameters to handle with the filter.
*
* @param Mixed $value
* @param Array $options
* @return Mixed $filteredValue
*/
abstract public function apply($value, $options=null);

/**
* By default a filter does take no required argument at all.
* You can override this value for your own filters.
*
* @return Integer
*/
public function getNumRequiredArgs()
{
return 0;
}
}
?>
{code}
{card}
{deck}
{zone-data}

{zone-template-instance}]]></ac:plain-text-body></ac:macro>